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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user