From 82ba0b7a1a04b7ff2d7e2175ac74766da0d16066 Mon Sep 17 00:00:00 2001 From: sergio Date: Thu, 21 May 2026 22:32:08 +0000 Subject: [PATCH] =?UTF-8?q?feat(charka):=20SET=20...=20TO=20TRUE=20?= =?UTF-8?q?=E2=80=94=20escribir=20nombres=20de=20condici=C3=B3n=20(88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La cara de escritura de los nombres de condición de COBOL: si IF ES-VALIDO los lee, SET ES-VALIDO TO TRUE los escribe. - IR: Stmt::SetTrue { conditions }. - Parser: SET cond-1 cond-2 ... TO TRUE. Otras formas de SET (índices, TO FALSE) caen a Stmt::Unknown. - Codegen y shadow: SET cond TO TRUE asigna a su dato padre el valor del 88 (un MOVE del valor a la variable). - Corpus: programa nuevo 16-bandera (cambia banderas de texto y de número con SET). Verificado: el intérprete sombra y el crate compilado por scaffold dan la misma salida. Tests: charka-ir 29, charka-codegen 23, charka-shadow 21. fmt + clippy limpios. Co-Authored-By: Claude Opus 4.7 --- crates/modules/charka/SDD.md | 10 +++++---- .../modules/charka/charka-codegen/src/lib.rs | 12 ++++++++++ .../modules/charka/charka-codegen/src/stmt.rs | 16 ++++++++++++++ crates/modules/charka/charka-ir/src/ast.rs | 3 +++ crates/modules/charka/charka-ir/src/lib.rs | 11 ++++++++++ crates/modules/charka/charka-ir/src/stmt.rs | 19 ++++++++++++++++ .../charka/charka-shadow/src/interp.rs | 8 +++++++ .../modules/charka/charka-shadow/src/lib.rs | 1 + crates/modules/charka/corpus/16-bandera.cob | 22 +++++++++++++++++++ .../modules/charka/corpus/16-bandera.expected | 3 +++ crates/modules/charka/corpus/README.md | 1 + docs/changelog/charka.md | 13 +++++++++++ 12 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 crates/modules/charka/corpus/16-bandera.cob create mode 100644 crates/modules/charka/corpus/16-bandera.expected diff --git a/crates/modules/charka/SDD.md b/crates/modules/charka/SDD.md index 611bf3d..6dda34e 100644 --- a/crates/modules/charka/SDD.md +++ b/crates/modules/charka/SDD.md @@ -86,8 +86,8 @@ Tercera etapa: `Program` → `Ir`. Aquí se parsea cada `Sentence` cruda - `Procedure { name, body: Vec }`. `Stmt` cubre `Move`, `Display`, `Accept`, `Compute`, `Add`/`Subtract`/`Multiply`/`Divide`, `If`, `Evaluate`, `StringConcat`, `Unstring`, `Inspect`, - `Initialize`, `Perform`, `GoTo`, `StopRun`, `Goback`, `Exit`, - `Continue`. + `Initialize`, `SetTrue`, `Perform`, `GoTo`, `StopRun`, `Goback`, + `Exit`, `Continue`. - `Expr` — expresiones aritméticas con precedencia y paréntesis (Pratt: `+ -` < `* /` < `**` der.). `Cond` — comparaciones (símbolo o forma palabra) unidas por `AND`/`OR`/`NOT`, más nombres de condición @@ -108,6 +108,8 @@ Tercera etapa: `Program` → `Ir`. Aquí se parsea cada `Sentence` cruda - `INITIALIZE` — resetea un dato (o todos los elementales de un grupo) a su valor por defecto. El `model` registra los grupos y sus miembros (`DataModel::groups`). +- `SET cond... TO TRUE` — la cara de escritura de los nombres de + condición (nivel 88): asigna a su dato padre el valor del 88. - Fuera de alcance v1: E/S de ficheros, CICS, SQL embebido. ## charka-runtime @@ -179,8 +181,8 @@ que corre el `Ir` directamente sobre `charka-runtime`, sin compilar. ## El corpus -`crates/modules/charka/corpus/` — 15 programas COBOL graduados -(`01-hola` … `15-resetear`), cada uno con su `.expected`. Ejercita el +`crates/modules/charka/corpus/` — 16 programas COBOL graduados +(`01-hola` … `16-bandera`), cada uno con su `.expected`. Ejercita el pipeline completo de punta a punta. Ver su `README.md`. ## La CLI diff --git a/crates/modules/charka/charka-codegen/src/lib.rs b/crates/modules/charka/charka-codegen/src/lib.rs index 92cffc0..0b5ffd2 100644 --- a/crates/modules/charka/charka-codegen/src/lib.rs +++ b/crates/modules/charka/charka-codegen/src/lib.rs @@ -427,6 +427,18 @@ mod tests { assert!(out.contains("self.ws_b.fill(' ');")); } + #[test] + fn set_to_true_moves_the_88_value() { + let out = gen("DATA DIVISION.\n\ + WORKING-STORAGE SECTION.\n\ + 01 WS-F PIC X VALUE 'N'.\n\ + 88 LISTO VALUE 'S'.\n\ + PROCEDURE DIVISION.\n\ + MAIN.\n\ + SET LISTO TO TRUE.\n"); + assert!(out.contains("self.ws_f.store(\"S\");")); + } + #[test] fn empty_program_still_compiles_shape() { let out = gen(""); diff --git a/crates/modules/charka/charka-codegen/src/stmt.rs b/crates/modules/charka/charka-codegen/src/stmt.rs index 3542dba..9bbf22d 100644 --- a/crates/modules/charka/charka-codegen/src/stmt.rs +++ b/crates/modules/charka/charka-codegen/src/stmt.rs @@ -87,6 +87,7 @@ pub(crate) fn emit_stmt(em: &mut Emitter, sym: &Symbols, stmt: &Stmt) { } => emit_unstring(em, sym, source, delimiter, into), Stmt::Inspect { target, op } => emit_inspect(em, sym, target, op), Stmt::Initialize { targets } => emit_initialize(em, sym, targets), + Stmt::SetTrue { conditions } => emit_set_true(em, sym, conditions), Stmt::Perform(p) => emit_perform(em, sym, p), Stmt::GoTo { target } => { em.line(&format!( @@ -492,6 +493,21 @@ fn emit_initialize(em: &mut Emitter, sym: &Symbols, targets: &[Operand]) { } } +/// `SET cond... TO TRUE` — asigna a cada dato padre el valor que hace +/// verdadero su nombre de condición (nivel 88). +fn emit_set_true(em: &mut Emitter, sym: &Symbols, conditions: &[String]) { + for name in conditions { + match sym.condition(name) { + Some(cn) => { + let target = Operand::Data(cn.parent.clone()); + let value = cn.value.clone(); + emit_move(em, sym, &value, std::slice::from_ref(&target)); + } + None => em.line(&format!("// charka: condición 88 no resuelta — {name}")), + } + } +} + /// Resetea un campo completo (escalar o tabla entera). fn emit_reset(em: &mut Emitter, sym: &Symbols, name: &str) { let Some(f) = sym.lookup(name) else { diff --git a/crates/modules/charka/charka-ir/src/ast.rs b/crates/modules/charka/charka-ir/src/ast.rs index 77e6238..5492dc1 100644 --- a/crates/modules/charka/charka-ir/src/ast.rs +++ b/crates/modules/charka/charka-ir/src/ast.rs @@ -188,6 +188,9 @@ pub enum Stmt { /// `INITIALIZE targets...` — pone cada dato (o grupo) en su valor /// por defecto: 0 los numéricos, espacios los alfanuméricos. Initialize { targets: Vec }, + /// `SET cond-name... TO TRUE` — hace verdaderos esos nombres de + /// condición (nivel 88): asigna a su dato padre el valor del 88. + SetTrue { conditions: Vec }, /// `PERFORM ...` — ver [`Perform`]. Perform(Perform), /// `GO TO target` diff --git a/crates/modules/charka/charka-ir/src/lib.rs b/crates/modules/charka/charka-ir/src/lib.rs index 93a29c6..b1bd14c 100644 --- a/crates/modules/charka/charka-ir/src/lib.rs +++ b/crates/modules/charka/charka-ir/src/lib.rs @@ -460,6 +460,17 @@ mod tests { } } + #[test] + fn set_to_true_parses() { + let b = body("SET ACTIVO LISTO TO TRUE."); + match &b[0] { + Stmt::SetTrue { conditions } => { + assert_eq!(conditions, &vec!["ACTIVO".to_string(), "LISTO".to_string()]); + } + other => panic!("se esperaba SET, vino {other:?}"), + } + } + #[test] fn several_statements_in_one_sentence() { let b = body("MOVE 1 TO X DISPLAY X STOP RUN."); diff --git a/crates/modules/charka/charka-ir/src/stmt.rs b/crates/modules/charka/charka-ir/src/stmt.rs index ca570d6..b364b91 100644 --- a/crates/modules/charka/charka-ir/src/stmt.rs +++ b/crates/modules/charka/charka-ir/src/stmt.rs @@ -45,6 +45,7 @@ fn parse_one_stmt(c: &mut Cursor, stops: &[&str]) -> Stmt { "UNSTRING" => parse_unstring(c), "INSPECT" => parse_inspect(c), "INITIALIZE" => parse_initialize(c), + "SET" => parse_set(c), "PERFORM" => parse_perform(c), "GO" => parse_goto(c), "STOP" => parse_stop(c), @@ -391,6 +392,24 @@ fn parse_unstring(c: &mut Cursor) -> Stmt { } } +fn parse_set(c: &mut Cursor) -> Stmt { + c.bump(); // SET + let mut conditions = Vec::new(); + while let Some(name) = parse_one_name(c) { + conditions.push(name); + } + // La v1 sólo modela `SET ... TO TRUE`. + if c.eat_word("TO") && c.eat_word("TRUE") { + Stmt::SetTrue { conditions } + } else { + skip_to_stmt_boundary(c); + Stmt::Unknown { + verb: "SET".to_string(), + tokens: Vec::new(), + } + } +} + fn parse_initialize(c: &mut Cursor) -> Stmt { c.bump(); // INITIALIZE let mut rounded = false; diff --git a/crates/modules/charka/charka-shadow/src/interp.rs b/crates/modules/charka/charka-shadow/src/interp.rs index 7bf19f6..d0d83fb 100644 --- a/crates/modules/charka/charka-shadow/src/interp.rs +++ b/crates/modules/charka/charka-shadow/src/interp.rs @@ -312,6 +312,14 @@ impl<'a> Machine<'a> { } Flow::Normal } + Stmt::SetTrue { conditions } => { + for name in conditions { + if let Some(cn) = self.conditions.get(&name.to_uppercase()).cloned() { + self.do_move(&cn.value, &Operand::Data(cn.parent)); + } + } + Flow::Normal + } Stmt::Perform(p) => self.exec_perform(p), Stmt::GoTo { target } => { // Aproximación: ejecuta el destino y sale del párrafo. diff --git a/crates/modules/charka/charka-shadow/src/lib.rs b/crates/modules/charka/charka-shadow/src/lib.rs index 55c84fb..3404d63 100644 --- a/crates/modules/charka/charka-shadow/src/lib.rs +++ b/crates/modules/charka/charka-shadow/src/lib.rs @@ -124,6 +124,7 @@ mod tests { corpus_test!(corpus_13_inspeccion, "13-inspeccion"); corpus_test!(corpus_14_clasifica, "14-clasifica"); corpus_test!(corpus_15_resetear, "15-resetear"); + corpus_test!(corpus_16_bandera, "16-bandera"); #[test] fn empty_source_runs_clean() { diff --git a/crates/modules/charka/corpus/16-bandera.cob b/crates/modules/charka/corpus/16-bandera.cob new file mode 100644 index 0000000..ad59764 --- /dev/null +++ b/crates/modules/charka/corpus/16-bandera.cob @@ -0,0 +1,22 @@ +* corpus charka — nivel 5: SET de nombres de condición (nivel 88) +IDENTIFICATION DIVISION. +PROGRAM-ID. BANDERA. +DATA DIVISION. +WORKING-STORAGE SECTION. +01 WS-ESTADO PIC X VALUE 'N'. + 88 ACTIVO VALUE 'S'. + 88 INACTIVO VALUE 'N'. +01 WS-NIVEL PIC 9(1) VALUE 0. + 88 NIVEL-MAX VALUE 9. +PROCEDURE DIVISION. +MAIN. + IF INACTIVO + DISPLAY 'EMPIEZA INACTIVO' + END-IF. + SET ACTIVO TO TRUE. + IF ACTIVO + DISPLAY 'AHORA ACTIVO' + END-IF. + SET NIVEL-MAX TO TRUE. + DISPLAY 'NIVEL = ' WS-NIVEL. + STOP RUN. diff --git a/crates/modules/charka/corpus/16-bandera.expected b/crates/modules/charka/corpus/16-bandera.expected new file mode 100644 index 0000000..1aaf27a --- /dev/null +++ b/crates/modules/charka/corpus/16-bandera.expected @@ -0,0 +1,3 @@ +EMPIEZA INACTIVO +AHORA ACTIVO +NIVEL = 9 diff --git a/crates/modules/charka/corpus/README.md b/crates/modules/charka/corpus/README.md index 2c118d6..4a20837 100644 --- a/crates/modules/charka/corpus/README.md +++ b/crates/modules/charka/corpus/README.md @@ -24,6 +24,7 @@ salida correcta, una línea por `DISPLAY`. | `13-inspeccion` | 6 | `INSPECT` — contar (`TALLYING`) y reemplazar | | `14-clasifica` | 6 | `EVALUATE TRUE` y rangos `WHEN ... THRU` | | `15-resetear` | 6 | `INITIALIZE` — resetear datos y grupos | +| `16-bandera` | 5 | `SET` de nombres de condición (nivel 88) a `TRUE` | ## Formato diff --git a/docs/changelog/charka.md b/docs/changelog/charka.md index c72a843..5445c36 100644 --- a/docs/changelog/charka.md +++ b/docs/changelog/charka.md @@ -3,6 +3,19 @@ Transpilador COBOL → Rust. El módulo más grande del ecosistema (Fase D del plan macro) — el parser COBOL completo es un esfuerzo multi-mes. +### feat(charka): SET ... TO TRUE — escribir nombres de condición (88) + +La cara de escritura de los nombres de condición de COBOL: si `IF +ES-VALIDO` los lee, `SET ES-VALIDO TO TRUE` los escribe. + +- IR: `Stmt::SetTrue { conditions }`. +- Parser: `SET cond-1 cond-2 ... TO TRUE`. Otras formas de `SET` + (índices, `TO FALSE`) caen a `Stmt::Unknown`. +- Codegen y shadow: `SET cond TO TRUE` asigna a su dato padre el valor + del 88 (un `MOVE` del valor a la variable). +- Corpus: programa nuevo `16-bandera` (cambia banderas de texto y de + número con `SET`). Verificado en ambas rutas. + ### feat(charka): INITIALIZE — resetear datos y grupos El verbo de COBOL para volver un dato (o un registro entero) a su