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:
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user