feat(charka): INITIALIZE — resetear datos y grupos

El verbo de COBOL para volver un dato (o un registro entero) a su
valor por defecto.

- IR: Stmt::Initialize { targets }. El model de charka-ir registra
  ahora los grupos y sus datos elementales (DataModel::groups,
  GroupInfo { name, members }).
- Parser: INITIALIZE name-1 name-2 ...
- Codegen y shadow: cada destino, si es un grupo, se expande a sus
  miembros; cada dato elemental se pone a 0 (numérico) o a espacios
  (alfanumérico); una tabla OCCURS resetea todos sus elementos.
- Corpus: programa nuevo 15-resetear (resetea un grupo y un escalar).
  Verificado: el intérprete sombra y el crate compilado por scaffold
  dan la misma salida.

Tests: charka-ir 28, charka-codegen 22, charka-shadow 20. fmt +
clippy limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 22:28:47 +00:00
parent 7867d6830e
commit fa65f20206
15 changed files with 260 additions and 17 deletions
@@ -280,8 +280,8 @@ mod tests {
fn unknown_verb_becomes_a_comment() {
let out = gen("PROCEDURE DIVISION.\n\
MAIN.\n\
INITIALIZE WS-X.\n");
assert!(out.contains("// charka: verbo no transpilado — INITIALIZE"));
CALL 'SUBPROG'.\n");
assert!(out.contains("// charka: verbo no transpilado — CALL"));
}
#[test]
@@ -413,6 +413,20 @@ mod tests {
assert!(out.contains("> (dec(\"5\"))"));
}
#[test]
fn initialize_resets_group_members() {
let out = gen("DATA DIVISION.\n\
WORKING-STORAGE SECTION.\n\
01 WS-REC.\n\
05 WS-A PIC 9(3).\n\
05 WS-B PIC X(4).\n\
PROCEDURE DIVISION.\n\
MAIN.\n\
INITIALIZE WS-REC.\n");
assert!(out.contains("self.ws_a.store(Decimal::zero());"));
assert!(out.contains("self.ws_b.fill(' ');"));
}
#[test]
fn empty_program_still_compiles_shape() {
let out = gen("");
@@ -86,6 +86,7 @@ pub(crate) fn emit_stmt(em: &mut Emitter, sym: &Symbols, stmt: &Stmt) {
into,
} => emit_unstring(em, sym, source, delimiter, into),
Stmt::Inspect { target, op } => emit_inspect(em, sym, target, op),
Stmt::Initialize { targets } => emit_initialize(em, sym, targets),
Stmt::Perform(p) => emit_perform(em, sym, p),
Stmt::GoTo { target } => {
em.line(&format!(
@@ -472,6 +473,56 @@ fn emit_inspect(em: &mut Emitter, sym: &Symbols, target: &Operand, op: &InspectO
}
}
/// `INITIALIZE` — pone cada destino (o los miembros de un grupo) en su
/// valor por defecto.
fn emit_initialize(em: &mut Emitter, sym: &Symbols, targets: &[Operand]) {
for t in targets {
match t {
Operand::Data(name) => match sym.group(name) {
Some(members) => {
for m in members {
emit_reset(em, sym, m);
}
}
None => emit_reset(em, sym, name),
},
Operand::Indexed { .. } => emit_reset_element(em, sym, t),
_ => {}
}
}
}
/// Resetea un campo completo (escalar o tabla entera).
fn emit_reset(em: &mut Emitter, sym: &Symbols, name: &str) {
let Some(f) = sym.lookup(name) else {
em.line(&format!("// charka: INITIALIZE de {name} no resuelto"));
return;
};
let reset = match f.kind {
FieldKind::Num { .. } => "store(Decimal::zero())",
FieldKind::Text { .. } => "fill(' ')",
};
match f.occurs {
None => em.line(&format!("self.{}.{reset};", f.ident)),
Some(_) => {
em.line(&format!("for __e in self.{}.iter_mut() {{", f.ident));
em.indent();
em.line(&format!("__e.{reset};"));
em.dedent();
em.line("}");
}
}
}
/// Resetea un solo elemento de tabla (`INITIALIZE ELEM(I)`).
fn emit_reset_element(em: &mut Emitter, sym: &Symbols, op: &Operand) {
match field_ref(sym, op) {
Some((lref, FieldKind::Num { .. })) => em.line(&format!("{lref}.store(Decimal::zero());")),
Some((lref, FieldKind::Text { .. })) => em.line(&format!("{lref}.fill(' ');")),
None => em.line("// charka: INITIALIZE no resuelto"),
}
}
fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
// Emite el "cuerpo": la llamada al párrafo o el bloque en línea.
let emit_body = |em: &mut Emitter, sym: &Symbols| match &p.target {
@@ -24,11 +24,12 @@ pub(crate) struct Field {
pub occurs: Option<u32>,
}
/// Los campos del programa y sus nombres de condición, indexados.
/// Los campos del programa, sus nombres de condición y sus grupos.
pub(crate) struct Symbols {
pub fields: Vec<Field>,
by_name: HashMap<String, usize>,
conditions: HashMap<String, ConditionName>,
groups: HashMap<String, Vec<String>>,
}
impl Symbols {
@@ -56,13 +57,24 @@ impl Symbols {
.iter()
.map(|c| (c.name.clone(), c.clone()))
.collect();
let groups = model
.groups
.iter()
.map(|g| (g.name.clone(), g.members.clone()))
.collect();
Self {
fields,
by_name,
conditions,
groups,
}
}
/// Los miembros de un grupo, si `name` es un grupo.
pub(crate) fn group(&self, name: &str) -> Option<&[String]> {
self.groups.get(&name.to_uppercase()).map(|v| v.as_slice())
}
/// Busca un campo por su nombre COBOL (sin distinguir mayúsculas).
pub(crate) fn lookup(&self, cobol: &str) -> Option<&Field> {
self.by_name