gioser

tawasuyu/brahman

Rust 476TOML 190Markdown 50JSON 176602 Definiciones

feat(charka): EVALUATE TRUE y rangos WHEN ... THRU

Completa el EVALUATE con sus dos formas que faltaban.

- IR: la rama WhenBranch pasa de values: Vec<Operand> a
  tests: Vec<WhenTest>, donde WhenTest es Value (igualdad), Range
  (WHEN lo THRU hi) o Cond (EVALUATE TRUE WHEN cond).
- Parser: detecta EVALUATE TRUE y entonces cada WHEN parsea una
  condición; en modo valor reconoce WHEN lo THRU hi.
- Codegen y shadow: una prueba Range se traduce a lo <= s <= hi; una
  Cond, a la condición directa.
- Corpus: programa nuevo 14-clasifica (clasifica notas con rangos THRU
  y un EVALUATE TRUE). Verificado: intérprete sombra y crate compilado
  dan la misma salida.

Tests: charka-ir 27, charka-codegen 21, charka-shadow 19. fmt +
clippy limpios.

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

7867d6830e128b8a2afe95cb752093e12a184988 · por sergio · (hace 5 meses) · padre 2728698f5 · ver el árbol en este commit

Delta semántico · 10

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

12 archivos · +176 −36

diff --git a/crates/modules/charka/SDD.md b/crates/modules/charka/SDD.md
index 77ace3d..bd20fbc 100644
--- a/crates/modules/charka/SDD.md
+++ b/crates/modules/charka/SDD.md
@@ -99,8 +99,8 @@ Tercera etapa: `Program` → `Ir`. Aquí se parsea cada `Sentence` cruda
   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`.
-- `EVALUATE subject WHEN ... WHEN OTHER` — el `case` de COBOL, por
-  igualdad de valor (no la forma `EVALUATE TRUE` con condiciones).
+- `EVALUATE` — el `case` de COBOL: `WHEN valor`, `WHEN lo THRU hi`
+  (rango), `WHEN OTHER`, y la forma `EVALUATE TRUE WHEN condición`.
 - `STRING` (concatenación) y `UNSTRING` (partición por delimitador) —
   el manejo de cadenas. `INSPECT` — contar (`TALLYING FOR ALL`) y
   reemplazar (`REPLACING ALL`).
@@ -175,8 +175,8 @@ que corre el `Ir` directamente sobre `charka-runtime`, sin compilar.
 
 ## El corpus
 
-`crates/modules/charka/corpus/` — 13 programas COBOL graduados
-(`01-hola` … `13-inspeccion`), cada uno con su `.expected`. Ejercita el
+`crates/modules/charka/corpus/` — 14 programas COBOL graduados
+(`01-hola` … `14-clasifica`), 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 82331b2..df69ee9 100644
--- a/crates/modules/charka/charka-codegen/src/lib.rs
+++ b/crates/modules/charka/charka-codegen/src/lib.rs
@@ -399,6 +399,20 @@ mod tests {
         assert!(out.contains(".replace("));
     }
 
+    #[test]
+    fn evaluate_true_and_range_emit() {
+        let out = gen("DATA DIVISION.\n\
+             WORKING-STORAGE SECTION.\n\
+             01 WS-X PIC 9(3).\n\
+             PROCEDURE DIVISION.\n\
+             MAIN.\n\
+                 EVALUATE WS-X WHEN 1 THRU 9 DISPLAY 'R' END-EVALUATE.\n\
+                 EVALUATE TRUE WHEN WS-X > 5 DISPLAY 'T' END-EVALUATE.\n");
+        assert!(out.contains(">= (dec(\"1\"))"));
+        assert!(out.contains("<= (dec(\"9\"))"));
+        assert!(out.contains("> (dec(\"5\"))"));
+    }
+
     #[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 8e51c14..d0903e5 100644
--- a/crates/modules/charka/charka-codegen/src/stmt.rs
+++ b/crates/modules/charka/charka-codegen/src/stmt.rs
@@ -3,6 +3,7 @@
 
 use charka_ir::{
     CmpOp, Cond, InspectOp, Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch,
+    WhenTest,
 };
 
 use crate::emit::Emitter;
@@ -349,32 +350,45 @@ fn emit_evaluate(
     }
 }
 
-/// La condición de una rama `WHEN`: el sujeto igual a cualquiera de
-/// sus valores.
+/// La condición de una rama `WHEN`: pasa si **alguna** de sus pruebas
+/// se cumple.
 fn branch_condition(sym: &Symbols, subject: &Operand, branch: &WhenBranch) -> String {
-    if branch.values.is_empty() {
+    if branch.tests.is_empty() {
         return "false".to_string();
     }
     branch
-        .values
+        .tests
         .iter()
-        .map(|v| {
-            format!(
-                "({})",
-                emit_cond(
-                    sym,
-                    &Cond::Compare {
-                        lhs: subject.clone(),
-                        op: CmpOp::Eq,
-                        rhs: v.clone(),
-                    },
-                )
-            )
-        })
+        .map(|t| format!("({})", test_condition(sym, subject, t)))
         .collect::<Vec<_>>()
         .join(" || ")
 }
 
+/// Traduce una prueba `WHEN` a una expresión Rust de tipo `bool`.
+fn test_condition(sym: &Symbols, subject: &Operand, test: &WhenTest) -> String {
+    let compare = |op: CmpOp, rhs: &Operand| {
+        emit_cond(
+            sym,
+            &Cond::Compare {
+                lhs: subject.clone(),
+                op,
+                rhs: rhs.clone(),
+            },
+        )
+    };
+    match test {
+        WhenTest::Value(v) => compare(CmpOp::Eq, v),
+        WhenTest::Range(lo, hi) => {
+            format!(
+                "({}) && ({})",
+                compare(CmpOp::Ge, lo),
+                compare(CmpOp::Le, hi)
+            )
+        }
+        WhenTest::Cond(cond) => emit_cond(sym, cond),
+    }
+}
+
 /// Almacena una expresión `&str` en un destino: directo si es de
 /// texto, parseado a `Decimal` si es numérico.
 fn emit_store_text(em: &mut Emitter, sym: &Symbols, target: &Operand, text: &str) {
diff --git a/crates/modules/charka/charka-ir/src/ast.rs b/crates/modules/charka/charka-ir/src/ast.rs
index e186521..9a0db93 100644
--- a/crates/modules/charka/charka-ir/src/ast.rs
+++ b/crates/modules/charka/charka-ir/src/ast.rs
@@ -212,11 +212,23 @@ pub enum InspectOp {
     ReplacingAll { from: Operand, to: Operand },
 }
 
-/// Una rama `WHEN` de un `EVALUATE`: los valores que la disparan
+/// Cómo una rama `WHEN` decide si se dispara.
+#[derive(Debug, Clone, PartialEq)]
+pub enum WhenTest {
+    /// El sujeto es igual a este valor.
+    Value(Operand),
+    /// El sujeto está en el rango `[lo, hi]` (`WHEN lo THRU hi`).
+    Range(Operand, Operand),
+    /// La condición se cumple — la forma `EVALUATE TRUE WHEN cond`.
+    Cond(Cond),
+}
+
+/// Una rama `WHEN` de un `EVALUATE`: las pruebas que la disparan
 /// (varios `WHEN` apilados comparten cuerpo) y el cuerpo a ejecutar.
+/// La rama se dispara si **alguna** de sus pruebas pasa.
 #[derive(Debug, Clone, PartialEq)]
 pub struct WhenBranch {
-    pub values: Vec<Operand>,
+    pub tests: Vec<WhenTest>,
     pub body: Vec<Stmt>,
 }
 
diff --git a/crates/modules/charka/charka-ir/src/lib.rs b/crates/modules/charka/charka-ir/src/lib.rs
index a93e490..996b4a4 100644
--- a/crates/modules/charka/charka-ir/src/lib.rs
+++ b/crates/modules/charka/charka-ir/src/lib.rs
@@ -347,10 +347,16 @@ mod tests {
             } => {
                 assert_eq!(subject, &Operand::Data("WS-X".into()));
                 assert_eq!(whens.len(), 2);
-                assert_eq!(whens[0].values, vec![Operand::Num("1".into())]);
                 assert_eq!(
-                    whens[1].values,
-                    vec![Operand::Num("2".into()), Operand::Num("3".into())]
+                    whens[0].tests,
+                    vec![WhenTest::Value(Operand::Num("1".into()))]
+                );
+                assert_eq!(
+                    whens[1].tests,
+                    vec![
+                        WhenTest::Value(Operand::Num("2".into())),
+                        WhenTest::Value(Operand::Num("3".into())),
+                    ]
                 );
                 assert_eq!(other.len(), 1);
             }
@@ -358,6 +364,30 @@ mod tests {
         }
     }
 
+    #[test]
+    fn evaluate_true_and_range_when() {
+        // `EVALUATE TRUE` → los WHEN son condiciones.
+        let b = body("EVALUATE TRUE WHEN WS-X > 0 DISPLAY 'P' END-EVALUATE.");
+        match &b[0] {
+            Stmt::Evaluate { whens, .. } => {
+                assert!(matches!(whens[0].tests[0], WhenTest::Cond(_)));
+            }
+            other => panic!("se esperaba EVALUATE, vino {other:?}"),
+        }
+        // `WHEN lo THRU hi` → un rango.
+        let b = body("EVALUATE WS-X WHEN 1 THRU 9 DISPLAY 'D' END-EVALUATE.");
+        match &b[0] {
+            Stmt::Evaluate { whens, .. } => match &whens[0].tests[0] {
+                WhenTest::Range(lo, hi) => {
+                    assert_eq!(lo, &Operand::Num("1".into()));
+                    assert_eq!(hi, &Operand::Num("9".into()));
+                }
+                other => panic!("se esperaba Range, vino {other:?}"),
+            },
+            other => panic!("se esperaba EVALUATE, vino {other:?}"),
+        }
+    }
+
     #[test]
     fn string_and_unstring_parse() {
         let b = body("STRING WS-A WS-B DELIMITED BY SIZE INTO WS-OUT END-STRING.");
diff --git a/crates/modules/charka/charka-ir/src/stmt.rs b/crates/modules/charka/charka-ir/src/stmt.rs
index 73107be..5c03a9b 100644
--- a/crates/modules/charka/charka-ir/src/stmt.rs
+++ b/crates/modules/charka/charka-ir/src/stmt.rs
@@ -5,7 +5,9 @@
 
 use charka_parser::TokenKind;
 
-use crate::ast::{InspectOp, Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch};
+use crate::ast::{
+    InspectOp, Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch, WhenTest,
+};
 use crate::cursor::{parse_operand, Cursor};
 use crate::expr::{parse_cond, parse_expr};
 use crate::kw::{is_boundary, is_terminator, is_verb};
@@ -304,6 +306,8 @@ fn parse_if(c: &mut Cursor) -> Stmt {
 fn parse_evaluate(c: &mut Cursor) -> Stmt {
     c.bump(); // EVALUATE
     let subject = parse_operand(c);
+    // `EVALUATE TRUE` — los `WHEN` son condiciones, no valores.
+    let cond_mode = matches!(&subject, Operand::Data(s) if s == "TRUE");
     let mut whens = Vec::new();
     let mut other = Vec::new();
     while !c.done() && !c.at_word("END-EVALUATE") {
@@ -311,20 +315,27 @@ fn parse_evaluate(c: &mut Cursor) -> Stmt {
             break; // algo inesperado dentro del EVALUATE: se corta
         }
         // Varios `WHEN` apilados comparten el mismo cuerpo.
-        let mut values = Vec::new();
+        let mut tests = Vec::new();
         let mut is_other = false;
         while c.eat_word("WHEN") {
             if c.eat_word("OTHER") {
                 is_other = true;
+            } else if cond_mode {
+                tests.push(WhenTest::Cond(parse_cond(c)));
             } else {
-                values.push(parse_operand(c));
+                let lo = parse_operand(c);
+                if c.eat_word("THRU") || c.eat_word("THROUGH") {
+                    tests.push(WhenTest::Range(lo, parse_operand(c)));
+                } else {
+                    tests.push(WhenTest::Value(lo));
+                }
             }
         }
         let body = parse_statements(c, &["WHEN", "END-EVALUATE"]);
         if is_other {
             other = body;
         } else {
-            whens.push(WhenBranch { values, body });
+            whens.push(WhenBranch { tests, body });
         }
     }
     c.eat_word("END-EVALUATE");
diff --git a/crates/modules/charka/charka-shadow/src/interp.rs b/crates/modules/charka/charka-shadow/src/interp.rs
index ccbd439..0b6e2ba 100644
--- a/crates/modules/charka/charka-shadow/src/interp.rs
+++ b/crates/modules/charka/charka-shadow/src/interp.rs
@@ -9,7 +9,7 @@ use std::collections::HashMap;
 
 use charka_ir::{
     BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, InspectOp, Ir, Operand, Perform,
-    PerformControl, PerformTarget, Stmt,
+    PerformControl, PerformTarget, Stmt, WhenTest,
 };
 use charka_runtime::{cobol_text_cmp, Decimal, Rounding};
 
@@ -237,11 +237,7 @@ impl<'a> Machine<'a> {
                 other,
             } => {
                 for branch in whens {
-                    if branch
-                        .values
-                        .iter()
-                        .any(|v| self.operands_equal(subject, v))
-                    {
+                    if branch.tests.iter().any(|t| self.when_test(subject, t)) {
                         return self.exec_block(&branch.body);
                     }
                 }
@@ -582,6 +578,18 @@ impl<'a> Machine<'a> {
         }
     }
 
+    /// ¿Se cumple una prueba `WHEN` para el sujeto dado?
+    fn when_test(&self, subject: &Operand, test: &WhenTest) -> bool {
+        match test {
+            WhenTest::Value(v) => self.operands_equal(subject, v),
+            WhenTest::Range(lo, hi) => {
+                let s = self.eval_decimal(subject);
+                s >= self.eval_decimal(lo) && s <= self.eval_decimal(hi)
+            }
+            WhenTest::Cond(cond) => self.eval_cond(cond),
+        }
+    }
+
     /// ¿Son iguales dos operandos? (Para las ramas `WHEN` del `EVALUATE`.)
     fn operands_equal(&self, a: &Operand, b: &Operand) -> bool {
         if self.is_text(a) || self.is_text(b) {
diff --git a/crates/modules/charka/charka-shadow/src/lib.rs b/crates/modules/charka/charka-shadow/src/lib.rs
index 767d621..259c867 100644
--- a/crates/modules/charka/charka-shadow/src/lib.rs
+++ b/crates/modules/charka/charka-shadow/src/lib.rs
@@ -122,6 +122,7 @@ mod tests {
     corpus_test!(corpus_11_tabla, "11-tabla");
     corpus_test!(corpus_12_cadenas, "12-cadenas");
     corpus_test!(corpus_13_inspeccion, "13-inspeccion");
+    corpus_test!(corpus_14_clasifica, "14-clasifica");
 
     #[test]
     fn empty_source_runs_clean() {
diff --git a/crates/modules/charka/corpus/14-clasifica.cob b/crates/modules/charka/corpus/14-clasifica.cob
new file mode 100644
index 0000000..9ac6e21
--- /dev/null
+++ b/crates/modules/charka/corpus/14-clasifica.cob
@@ -0,0 +1,29 @@
+* corpus charka — nivel 6: EVALUATE TRUE y rangos WHEN ... THRU
+IDENTIFICATION DIVISION.
+PROGRAM-ID. CLASIFICA.
+DATA DIVISION.
+WORKING-STORAGE SECTION.
+01 WS-NOTA PIC 9(3) VALUE 0.
+01 WS-I    PIC 9(2) VALUE 0.
+PROCEDURE DIVISION.
+MAIN.
+    PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 5
+        COMPUTE WS-NOTA = WS-I * 18
+        EVALUATE WS-NOTA
+            WHEN 0 THRU 59
+                DISPLAY 'REPROBADO'
+            WHEN 60 THRU 89
+                DISPLAY 'APROBADO'
+            WHEN OTHER
+                DISPLAY 'EXCELENTE'
+        END-EVALUATE
+    END-PERFORM.
+    PERFORM EVAL-TRUE.
+    STOP RUN.
+EVAL-TRUE.
+    EVALUATE TRUE
+        WHEN WS-NOTA > 100
+            DISPLAY 'ULTIMA NOTA ALTA'
+        WHEN OTHER
+            DISPLAY 'ULTIMA NOTA NO ALTA'
+    END-EVALUATE.
diff --git a/crates/modules/charka/corpus/14-clasifica.expected b/crates/modules/charka/corpus/14-clasifica.expected
new file mode 100644
index 0000000..ab3caca
--- /dev/null
+++ b/crates/modules/charka/corpus/14-clasifica.expected
@@ -0,0 +1,6 @@
+REPROBADO
+REPROBADO
+REPROBADO
+APROBADO
+EXCELENTE
+ULTIMA NOTA NO ALTA
diff --git a/crates/modules/charka/corpus/README.md b/crates/modules/charka/corpus/README.md
index 69834d5..629c481 100644
--- a/crates/modules/charka/corpus/README.md
+++ b/crates/modules/charka/corpus/README.md
@@ -22,6 +22,7 @@ salida correcta, una línea por `DISPLAY`.
 | `11-tabla`          | 6     | tablas (`OCCURS`) y referencias con subíndice      |
 | `12-cadenas`        | 6     | `STRING` (concatenar) y `UNSTRING` (partir)        |
 | `13-inspeccion`     | 6     | `INSPECT` — contar (`TALLYING`) y reemplazar       |
+| `14-clasifica`      | 6     | `EVALUATE TRUE` y rangos `WHEN ... THRU`           |
 
 ## Formato
 
diff --git a/docs/changelog/charka.md b/docs/changelog/charka.md
index 546324b..5201887 100644
--- a/docs/changelog/charka.md
+++ b/docs/changelog/charka.md
@@ -3,6 +3,20 @@
 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): EVALUATE TRUE y rangos WHEN ... THRU
+
+Completa el `EVALUATE` con sus dos formas que faltaban.
+
+- IR: la rama `WhenBranch` pasa de `values: Vec<Operand>` a
+  `tests: Vec<WhenTest>`, donde `WhenTest` es `Value` (igualdad),
+  `Range` (`WHEN lo THRU hi`) o `Cond` (`EVALUATE TRUE WHEN cond`).
+- Parser: detecta `EVALUATE TRUE` y entonces cada `WHEN` parsea una
+  condición; en modo valor reconoce `WHEN lo THRU hi`.
+- Codegen y shadow: una prueba `Range` se traduce a `lo <= s <= hi`;
+  una `Cond`, a la condición directa.
+- Corpus: programa nuevo `14-clasifica` (clasifica notas con rangos
+  `THRU` y un `EVALUATE TRUE`). Verificado en ambas rutas.
+
 ### feat(charka): INSPECT — contar y reemplazar caracteres
 
 El verbo de COBOL para analizar y limpiar campos de texto.