tawasuyu

tawasuyu/brahman

Rust 496TOML 195Markdown 55JSON 197001 Definiciones

feat(charka): PERFORM VARYING — el bucle con variable de control

El bucle más usado de COBOL, que antes el parser degradaba a un
PERFORM vacío (un hueco de corrección real). Ahora atraviesa el
pipeline entero como una rebanada vertical.

- IR: PerformControl::Varying { var, from, by, until }.
- Parser: reconoce PERFORM VARYING var FROM x BY y UNTIL cond en
  línea (END-PERFORM) y fuera de línea (PERFORM párrafo VARYING ...).
- Codegen: emite var = from; while !(until) { cuerpo; var += by; }.
- Shadow: el intérprete inicializa la variable, evalúa la condición
  antes de cada vuelta e incrementa al final.
- Corpus: programa nuevo 08-varying (suma 1..10). Verificado: el
  intérprete sombra y el crate compilado por scaffold dan ambos
  SUMA 1 A 10 = 00055 — las dos rutas concuerdan.

Tests: charka-ir 18, charka-codegen 15, charka-shadow 13. fmt +
clippy limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

321e6f8e27aea06b5a868b37598148e15bf71f57 · por sergio · (hace 5 meses) · padre b052c41e3 · ver el árbol en este commit

Delta semántico · 8

crates/modules/charka/charka-codegen/src/lib.rs

crates/modules/charka/charka-codegen/src/stmt.rs

crates/modules/charka/charka-ir/src/ast.rs

crates/modules/charka/charka-ir/src/lib.rs

crates/modules/charka/charka-ir/src/stmt.rs

crates/modules/charka/charka-shadow/src/interp.rs

crates/modules/charka/charka-shadow/src/lib.rs

12 archivos · +177 −7

diff --git a/crates/modules/charka/SDD.md b/crates/modules/charka/SDD.md
index 5177f76..73dab52 100644
--- a/crates/modules/charka/SDD.md
+++ b/crates/modules/charka/SDD.md
@@ -89,8 +89,10 @@ Tercera etapa: `Program` → `Ir`. Aquí se parsea cada `Sentence` cruda
   empieza el verbo del siguiente. El parser usa palabras "frontera"
   (verbos + terminadores `END-*`/`ELSE` + conectores `TO`/`GIVING`...)
   para delimitar listas de operandos.
+- `PERFORM` cubre las cuatro formas: párrafo / en línea, `n TIMES`,
+  `UNTIL cond` y `VARYING var FROM x BY y UNTIL cond`.
 - Fuera de alcance v1: `EVALUATE`, `STRING`/`UNSTRING`, E/S de
-  ficheros, `PERFORM VARYING`, CICS, SQL embebido.
+  ficheros, CICS, SQL embebido.
 
 ## charka-runtime
 
diff --git a/crates/modules/charka/charka-codegen/src/lib.rs b/crates/modules/charka/charka-codegen/src/lib.rs
index e7a151c..84147bd 100644
--- a/crates/modules/charka/charka-codegen/src/lib.rs
+++ b/crates/modules/charka/charka-codegen/src/lib.rs
@@ -334,6 +334,22 @@ mod tests {
         assert!(out.contains("for _ in 0..3usize {"));
     }
 
+    #[test]
+    fn perform_varying_emits_init_loop_and_increment() {
+        let out = gen("DATA DIVISION.\n\
+             WORKING-STORAGE SECTION.\n\
+             01 WS-I PIC 9(2).\n\
+             01 WS-N PIC 9(3).\n\
+             PROCEDURE DIVISION.\n\
+             MAIN.\n\
+                 PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 5\n\
+                     ADD 1 TO WS-N\n\
+                 END-PERFORM.\n");
+        assert!(out.contains("self.ws_i.store(dec(\"1\"));"));
+        assert!(out.contains("while !((self.ws_i.value()) > (dec(\"5\"))) {"));
+        assert!(out.contains("self.ws_i.store(self.ws_i.value().add(&(dec(\"1\"))));"));
+    }
+
     #[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 13d6803..c907577 100644
--- a/crates/modules/charka/charka-codegen/src/stmt.rs
+++ b/crates/modules/charka/charka-codegen/src/stmt.rs
@@ -360,5 +360,20 @@ fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
             em.dedent();
             em.line("}");
         }
+        PerformControl::Varying {
+            var,
+            from,
+            by,
+            until,
+        } => {
+            // var = from; mientras no se cumpla `until`: cuerpo; var += by.
+            emit_store(em, sym, var, &operand_decimal(sym, from), false);
+            em.line(&format!("while !({}) {{", emit_cond(sym, until)));
+            em.indent();
+            emit_body(em, sym);
+            emit_inplace(em, sym, var, "add", &operand_decimal(sym, by), false);
+            em.dedent();
+            em.line("}");
+        }
     }
 }
diff --git a/crates/modules/charka/charka-ir/src/ast.rs b/crates/modules/charka/charka-ir/src/ast.rs
index 5ea48b0..8bcbdc1 100644
--- a/crates/modules/charka/charka-ir/src/ast.rs
+++ b/crates/modules/charka/charka-ir/src/ast.rs
@@ -197,4 +197,13 @@ pub enum PerformControl {
     Times(Operand),
     /// `UNTIL cond`.
     Until(Cond),
+    /// `VARYING var FROM from BY by UNTIL until` — el bucle con
+    /// variable de control: `var = from`; mientras no se cumpla
+    /// `until`, ejecuta el cuerpo y hace `var = var + by`.
+    Varying {
+        var: String,
+        from: Operand,
+        by: Operand,
+        until: Cond,
+    },
 }
diff --git a/crates/modules/charka/charka-ir/src/lib.rs b/crates/modules/charka/charka-ir/src/lib.rs
index 9bccad4..2dc6abc 100644
--- a/crates/modules/charka/charka-ir/src/lib.rs
+++ b/crates/modules/charka/charka-ir/src/lib.rs
@@ -17,9 +17,9 @@
 //! `ACCEPT`, `COMPUTE` (con expresiones con precedencia), `ADD`,
 //! `SUBTRACT`, `MULTIPLY`, `DIVIDE`, `IF`/`ELSE`/`END-IF` (con
 //! condiciones `AND`/`OR`/`NOT`), `PERFORM` (fuera de línea, en línea,
-//! `TIMES`, `UNTIL`), `GO TO`, `STOP RUN`, `GOBACK`, `EXIT`,
-//! `CONTINUE`. Fuera de alcance: `EVALUATE`, `STRING`/`UNSTRING`, E/S
-//! de ficheros, `PERFORM VARYING`, CICS y SQL embebido.
+//! `TIMES`, `UNTIL`, `VARYING`), `GO TO`, `STOP RUN`, `GOBACK`,
+//! `EXIT`, `CONTINUE`. Fuera de alcance: `EVALUATE`,
+//! `STRING`/`UNSTRING`, E/S de ficheros, CICS y SQL embebido.
 
 #![forbid(unsafe_code)]
 
@@ -286,6 +286,31 @@ mod tests {
         }
     }
 
+    #[test]
+    fn perform_varying_inline() {
+        let b = body(
+            "PERFORM VARYING WS-I FROM 1 BY 2 UNTIL WS-I > 9 \
+             CONTINUE END-PERFORM.",
+        );
+        match &b[0] {
+            Stmt::Perform(p) => match &p.control {
+                PerformControl::Varying {
+                    var,
+                    from,
+                    by,
+                    until,
+                } => {
+                    assert_eq!(var, "WS-I");
+                    assert_eq!(from, &Operand::Num("1".into()));
+                    assert_eq!(by, &Operand::Num("2".into()));
+                    assert!(matches!(until, Cond::Compare { .. }));
+                }
+                other => panic!("se esperaba Varying, vino {other:?}"),
+            },
+            other => panic!("se esperaba PERFORM, 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 4dff640..74d712a 100644
--- a/crates/modules/charka/charka-ir/src/stmt.rs
+++ b/crates/modules/charka/charka-ir/src/stmt.rs
@@ -288,6 +288,14 @@ fn parse_if(c: &mut Cursor) -> Stmt {
 fn parse_perform(c: &mut Cursor) -> Stmt {
     c.bump(); // PERFORM
 
+    // `PERFORM VARYING ... ... END-PERFORM` — cuerpo en línea.
+    if c.eat_word("VARYING") {
+        let control = parse_varying(c);
+        let body = parse_statements(c, &["END-PERFORM"]);
+        c.eat_word("END-PERFORM");
+        return inline_perform(body, control);
+    }
+
     // `PERFORM UNTIL cond ... END-PERFORM` — cuerpo en línea.
     if c.eat_word("UNTIL") {
         let cond = parse_cond(c);
@@ -312,9 +320,9 @@ fn parse_perform(c: &mut Cursor) -> Stmt {
         return inline_perform(body, PerformControl::Once);
     }
 
-    // `PERFORM PARA [THRU PARA2] [n TIMES | UNTIL cond]` — fuera de línea.
+    // `PERFORM PARA [THRU PARA2] [VARYING ... | n TIMES | UNTIL cond]`.
     let Some(name) = parse_one_name(c) else {
-        // Forma no soportada (p. ej. `PERFORM VARYING`): perform vacío.
+        // Forma no reconocida tras `PERFORM`: perform vacío.
         return inline_perform(Vec::new(), PerformControl::Once);
     };
     let thru = if c.eat_word("THRU") || c.eat_word("THROUGH") {
@@ -322,7 +330,9 @@ fn parse_perform(c: &mut Cursor) -> Stmt {
     } else {
         None
     };
-    let control = if c.eat_word("UNTIL") {
+    let control = if c.eat_word("VARYING") {
+        parse_varying(c)
+    } else if c.eat_word("UNTIL") {
         PerformControl::Until(parse_cond(c))
     } else if at_count(c) {
         let n = parse_operand(c);
@@ -345,6 +355,24 @@ fn inline_perform(body: Vec<Stmt>, control: PerformControl) -> Stmt {
     })
 }
 
+/// Parsea la cláusula `VARYING var FROM x BY y UNTIL cond`, ya
+/// consumida la palabra `VARYING`.
+fn parse_varying(c: &mut Cursor) -> PerformControl {
+    let var = parse_one_name(c).unwrap_or_default();
+    c.eat_word("FROM");
+    let from = parse_operand(c);
+    c.eat_word("BY");
+    let by = parse_operand(c);
+    c.eat_word("UNTIL");
+    let until = parse_cond(c);
+    PerformControl::Varying {
+        var,
+        from,
+        by,
+        until,
+    }
+}
+
 /// ¿El cursor está sobre `<operando> TIMES`?
 fn at_count(c: &Cursor) -> bool {
     match c.peek().map(|t| t.kind) {
diff --git a/crates/modules/charka/charka-shadow/src/interp.rs b/crates/modules/charka/charka-shadow/src/interp.rs
index 71614f7..da84944 100644
--- a/crates/modules/charka/charka-shadow/src/interp.rs
+++ b/crates/modules/charka/charka-shadow/src/interp.rs
@@ -265,6 +265,28 @@ impl<'a> Machine<'a> {
                     return Flow::Stop;
                 }
             },
+            PerformControl::Varying {
+                var,
+                from,
+                by,
+                until,
+            } => {
+                let start = self.eval_decimal(from);
+                self.store(var, start, false);
+                loop {
+                    if self.tick() {
+                        return Flow::Stop;
+                    }
+                    if self.eval_cond(until) {
+                        return Flow::Normal;
+                    }
+                    if let Flow::Stop = self.run_target(&p.target) {
+                        return Flow::Stop;
+                    }
+                    let next = self.field_value(var).add(&self.eval_decimal(by));
+                    self.store(var, next, false);
+                }
+            }
         }
     }
 
diff --git a/crates/modules/charka/charka-shadow/src/lib.rs b/crates/modules/charka/charka-shadow/src/lib.rs
index 325963d..adf2230 100644
--- a/crates/modules/charka/charka-shadow/src/lib.rs
+++ b/crates/modules/charka/charka-shadow/src/lib.rs
@@ -116,6 +116,7 @@ mod tests {
     corpus_test!(corpus_05_factorial, "05-factorial");
     corpus_test!(corpus_06_nomina, "06-nomina");
     corpus_test!(corpus_07_clasificar, "07-clasificar");
+    corpus_test!(corpus_08_varying, "08-varying");
 
     #[test]
     fn empty_source_runs_clean() {
@@ -146,6 +147,27 @@ mod tests {
         assert_eq!(outcome.halt, Halt::StepLimit);
     }
 
+    #[test]
+    fn perform_varying_out_of_line() {
+        // `PERFORM CONTAR VARYING ...` — el párrafo es el cuerpo del bucle.
+        // WS-I = 1, 3, 5, 7, 9 (FROM 1 BY 2 UNTIL > 9) → 5 iteraciones.
+        let outcome = run_source(
+            "DATA DIVISION.\n\
+             WORKING-STORAGE SECTION.\n\
+             01 WS-I PIC 9(2) VALUE 0.\n\
+             01 WS-N PIC 9(3) VALUE 0.\n\
+             PROCEDURE DIVISION.\n\
+             MAIN.\n\
+                 PERFORM CONTAR VARYING WS-I FROM 1 BY 2 UNTIL WS-I > 9.\n\
+                 DISPLAY WS-N.\n\
+                 STOP RUN.\n\
+             CONTAR.\n\
+                 ADD 1 TO WS-N.\n",
+        )
+        .expect("pipeline OK");
+        assert_eq!(outcome.lines, vec!["005".to_string()]);
+    }
+
     #[test]
     fn lex_error_surfaces() {
         let err = run_source("PROCEDURE DIVISION.\nMAIN.\n DISPLAY 'sin cerrar.\n").unwrap_err();
diff --git a/crates/modules/charka/corpus/08-varying.cob b/crates/modules/charka/corpus/08-varying.cob
new file mode 100644
index 0000000..e33fcb4
--- /dev/null
+++ b/crates/modules/charka/corpus/08-varying.cob
@@ -0,0 +1,14 @@
+* corpus charka — nivel 4: PERFORM VARYING, el bucle clásico de COBOL
+IDENTIFICATION DIVISION.
+PROGRAM-ID. SUMAR.
+DATA DIVISION.
+WORKING-STORAGE SECTION.
+01 WS-I    PIC 9(2) VALUE 0.
+01 WS-SUMA PIC 9(5) VALUE 0.
+PROCEDURE DIVISION.
+MAIN.
+    PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 10
+        ADD WS-I TO WS-SUMA
+    END-PERFORM.
+    DISPLAY 'SUMA 1 A 10 = ' WS-SUMA.
+    STOP RUN.
diff --git a/crates/modules/charka/corpus/08-varying.expected b/crates/modules/charka/corpus/08-varying.expected
new file mode 100644
index 0000000..a747e94
--- /dev/null
+++ b/crates/modules/charka/corpus/08-varying.expected
@@ -0,0 +1 @@
+SUMA 1 A 10 = 00055
diff --git a/crates/modules/charka/corpus/README.md b/crates/modules/charka/corpus/README.md
index 27911b0..a11abac 100644
--- a/crates/modules/charka/corpus/README.md
+++ b/crates/modules/charka/corpus/README.md
@@ -16,6 +16,7 @@ salida correcta, una línea por `DISPLAY`.
 | `05-factorial`      | 4     | `PERFORM UNTIL` en línea, `MULTIPLY` in situ       |
 | `06-nomina`         | 5     | grupos, `COMPUTE` con paréntesis, `ROUNDED`, V99   |
 | `07-clasificar`     | 5     | `IF` anidado, condiciones con `AND`                |
+| `08-varying`        | 4     | `PERFORM VARYING` — el bucle con variable de control|
 
 ## Formato
 
diff --git a/docs/changelog/charka.md b/docs/changelog/charka.md
index 66beb67..045ef32 100644
--- a/docs/changelog/charka.md
+++ b/docs/changelog/charka.md
@@ -3,6 +3,21 @@
 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): PERFORM VARYING — el bucle con variable de control
+
+El bucle más usado de COBOL, que antes el parser degradaba a un
+`PERFORM` vacío. Ahora atraviesa el pipeline entero.
+
+- IR: `PerformControl::Varying { var, from, by, until }`.
+- Parser: reconoce `PERFORM VARYING var FROM x BY y UNTIL cond` en
+  línea (`END-PERFORM`) y fuera de línea (`PERFORM párrafo VARYING …`).
+- Codegen: emite `var = from; while !(until) { cuerpo; var += by; }`.
+- Shadow: el intérprete inicializa la variable, evalúa la condición
+  antes de cada vuelta e incrementa al final.
+- Corpus: programa nuevo `08-varying` (suma 1..10 con `PERFORM
+  VARYING`). Verificado: el intérprete sombra y el crate compilado por
+  `scaffold` dan ambos `SUMA 1 A 10 = 00055`.
+
 ### feat(charka): CLI del transpilador — transpile / scaffold / run / check
 
 App nueva `crates/apps/charka` — el binario `charka`, que vuelve usable