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>
This commit is contained in:
sergio
2026-05-21 22:22:43 +00:00
parent 2728698f5e
commit 7867d6830e
12 changed files with 176 additions and 36 deletions
@@ -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("");
@@ -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) {