feat(charka): nivel 88 + modelo de datos compartido en charka-ir

Los nombres de condición de COBOL (IF ES-VALIDO), que antes el
transpilador evaluaba siempre como false. Y, de paso, se elimina la
duplicación de la resolución del modelo de datos.

- charka-ir gana un módulo `model`: resolve_data(&[DataItem]) ->
  DataModel aplana el árbol de datos a campos elementales (Field con
  FieldKind) y a nombres de condición (ConditionName). El Ir lleva
  ahora un campo `model` — la fuente única de verdad sobre la
  clasificación de PICTURE.
- charka-codegen y charka-shadow consumen ir.model en vez de
  reimplementar cada uno la clasificación, el ancho de PICTURE y la
  normalización de VALUE. charka-codegen ya no depende de charka-bcd.
- Cond::Named (un nivel 88) se resuelve a `padre = valor`: el codegen
  emite la comparación, el intérprete sombra la evalúa.
- Corregido: un dato con hijos de nivel 88 antes se perdía como si
  fuera un grupo; ahora se reconoce como campo elemental.
- Corpus: programa nuevo 10-condicion (semáforo con 88 de texto y de
  número). Verificado: intérprete y crate compilado dan igual salida.

Tests: charka-ir 23, charka-codegen 17, charka-shadow 15. fmt +
clippy limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 21:50:06 +00:00
parent 4df7478b71
commit 28ee1ae260
17 changed files with 473 additions and 265 deletions
@@ -8,7 +8,8 @@
use std::collections::HashMap;
use charka_ir::{
BinOp, CmpOp, Cond, Expr, Figurative, Ir, Operand, Perform, PerformControl, PerformTarget, Stmt,
BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, Ir, Operand, Perform, PerformControl,
PerformTarget, Stmt,
};
use charka_runtime::{cobol_text_cmp, Decimal, Rounding};
@@ -37,6 +38,7 @@ pub(crate) struct Machine<'a> {
ir: &'a Ir,
fields: HashMap<String, Cell>,
para_index: HashMap<String, usize>,
conditions: HashMap<String, ConditionName>,
pub output: Vec<String>,
budget: u64,
pub step_limit_hit: bool,
@@ -50,10 +52,17 @@ impl<'a> Machine<'a> {
for (i, proc) in ir.procedures.iter().enumerate() {
para_index.entry(proc.name.to_uppercase()).or_insert(i);
}
let conditions = ir
.model
.conditions
.iter()
.map(|c| (c.name.clone(), c.clone()))
.collect();
Self {
ir,
fields: build_fields(&ir.data),
fields: build_fields(&ir.model),
para_index,
conditions,
output: Vec::new(),
budget: STEP_BUDGET,
step_limit_hit: false,
@@ -437,7 +446,12 @@ impl<'a> Machine<'a> {
CmpOp::Ge => ord.is_ge(),
}
}
Cond::Named(_) => false, // nombres de condición (88): no soportado
Cond::Named(name) => match self.conditions.get(&name.to_uppercase()) {
// Un nombre de condición (88): el dato padre igual al
// valor que la hace verdadera.
Some(cn) => self.operands_equal(&Operand::Data(cn.parent.clone()), &cn.value),
None => false,
},
Cond::Not(inner) => !self.eval_cond(inner),
Cond::And(a, b) => self.eval_cond(a) && self.eval_cond(b),
Cond::Or(a, b) => self.eval_cond(a) || self.eval_cond(b),