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
+14 -2
View File
@@ -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>,
}
+33 -3
View File
@@ -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.");
+15 -4
View File
@@ -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");