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:
@@ -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
|
||||
|
||||
@@ -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,30 +350,43 @@ 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!(
|
||||
"({})",
|
||||
.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: CmpOp::Eq,
|
||||
rhs: v.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)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" || ")
|
||||
}
|
||||
WhenTest::Cond(cond) => emit_cond(sym, cond),
|
||||
}
|
||||
}
|
||||
|
||||
/// Almacena una expresión `&str` en un destino: directo si es de
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,6 @@
|
||||
REPROBADO
|
||||
REPROBADO
|
||||
REPROBADO
|
||||
APROBADO
|
||||
EXCELENTE
|
||||
ULTIMA NOTA NO ALTA
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user