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
+2 -2
View File
@@ -290,13 +290,13 @@ mod tests {
let ir = ir_of( let ir = ir_of(
"PROCEDURE DIVISION.\n\ "PROCEDURE DIVISION.\n\
MAIN.\n\ MAIN.\n\
INITIALIZE WS-X.\n", CALL 'SUBPROG'.\n",
); );
let mut verbs = Vec::new(); let mut verbs = Vec::new();
for proc in &ir.procedures { for proc in &ir.procedures {
collect_unknowns(&proc.body, &mut verbs); collect_unknowns(&proc.body, &mut verbs);
} }
assert_eq!(verbs, vec!["INITIALIZE".to_string()]); assert_eq!(verbs, vec!["CALL".to_string()]);
} }
#[test] #[test]
+8 -4
View File
@@ -85,8 +85,9 @@ Tercera etapa: `Program` → `Ir`. Aquí se parsea cada `Sentence` cruda
reimplementar la clasificación. reimplementar la clasificación.
- `Procedure { name, body: Vec<Stmt> }`. `Stmt` cubre `Move`, - `Procedure { name, body: Vec<Stmt> }`. `Stmt` cubre `Move`,
`Display`, `Accept`, `Compute`, `Add`/`Subtract`/`Multiply`/`Divide`, `Display`, `Accept`, `Compute`, `Add`/`Subtract`/`Multiply`/`Divide`,
`If`, `Evaluate`, `StringConcat`, `Unstring`, `Inspect`, `Perform`, `If`, `Evaluate`, `StringConcat`, `Unstring`, `Inspect`,
`GoTo`, `StopRun`, `Goback`, `Exit`, `Continue`. `Initialize`, `Perform`, `GoTo`, `StopRun`, `Goback`, `Exit`,
`Continue`.
- `Expr` — expresiones aritméticas con precedencia y paréntesis (Pratt: - `Expr` — expresiones aritméticas con precedencia y paréntesis (Pratt:
`+ -` < `* /` < `**` der.). `Cond` — comparaciones (símbolo o forma `+ -` < `* /` < `**` der.). `Cond` — comparaciones (símbolo o forma
palabra) unidas por `AND`/`OR`/`NOT`, más nombres de condición palabra) unidas por `AND`/`OR`/`NOT`, más nombres de condición
@@ -104,6 +105,9 @@ Tercera etapa: `Program` → `Ir`. Aquí se parsea cada `Sentence` cruda
- `STRING` (concatenación) y `UNSTRING` (partición por delimitador) — - `STRING` (concatenación) y `UNSTRING` (partición por delimitador) —
el manejo de cadenas. `INSPECT` — contar (`TALLYING FOR ALL`) y el manejo de cadenas. `INSPECT` — contar (`TALLYING FOR ALL`) y
reemplazar (`REPLACING ALL`). reemplazar (`REPLACING ALL`).
- `INITIALIZE` — resetea un dato (o todos los elementales de un grupo)
a su valor por defecto. El `model` registra los grupos y sus
miembros (`DataModel::groups`).
- Fuera de alcance v1: E/S de ficheros, CICS, SQL embebido. - Fuera de alcance v1: E/S de ficheros, CICS, SQL embebido.
## charka-runtime ## charka-runtime
@@ -175,8 +179,8 @@ que corre el `Ir` directamente sobre `charka-runtime`, sin compilar.
## El corpus ## El corpus
`crates/modules/charka/corpus/` — 14 programas COBOL graduados `crates/modules/charka/corpus/` — 15 programas COBOL graduados
(`01-hola` … `14-clasifica`), cada uno con su `.expected`. Ejercita el (`01-hola` … `15-resetear`), cada uno con su `.expected`. Ejercita el
pipeline completo de punta a punta. Ver su `README.md`. pipeline completo de punta a punta. Ver su `README.md`.
## La CLI ## La CLI
@@ -280,8 +280,8 @@ mod tests {
fn unknown_verb_becomes_a_comment() { fn unknown_verb_becomes_a_comment() {
let out = gen("PROCEDURE DIVISION.\n\ let out = gen("PROCEDURE DIVISION.\n\
MAIN.\n\ MAIN.\n\
INITIALIZE WS-X.\n"); CALL 'SUBPROG'.\n");
assert!(out.contains("// charka: verbo no transpilado — INITIALIZE")); assert!(out.contains("// charka: verbo no transpilado — CALL"));
} }
#[test] #[test]
@@ -413,6 +413,20 @@ mod tests {
assert!(out.contains("> (dec(\"5\"))")); 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] #[test]
fn empty_program_still_compiles_shape() { fn empty_program_still_compiles_shape() {
let out = gen(""); let out = gen("");
@@ -86,6 +86,7 @@ pub(crate) fn emit_stmt(em: &mut Emitter, sym: &Symbols, stmt: &Stmt) {
into, into,
} => emit_unstring(em, sym, source, delimiter, into), } => emit_unstring(em, sym, source, delimiter, into),
Stmt::Inspect { target, op } => emit_inspect(em, sym, target, op), 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::Perform(p) => emit_perform(em, sym, p),
Stmt::GoTo { target } => { Stmt::GoTo { target } => {
em.line(&format!( 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) { fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
// Emite el "cuerpo": la llamada al párrafo o el bloque en línea. // 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 { let emit_body = |em: &mut Emitter, sym: &Symbols| match &p.target {
@@ -24,11 +24,12 @@ pub(crate) struct Field {
pub occurs: Option<u32>, 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(crate) struct Symbols {
pub fields: Vec<Field>, pub fields: Vec<Field>,
by_name: HashMap<String, usize>, by_name: HashMap<String, usize>,
conditions: HashMap<String, ConditionName>, conditions: HashMap<String, ConditionName>,
groups: HashMap<String, Vec<String>>,
} }
impl Symbols { impl Symbols {
@@ -56,13 +57,24 @@ impl Symbols {
.iter() .iter()
.map(|c| (c.name.clone(), c.clone())) .map(|c| (c.name.clone(), c.clone()))
.collect(); .collect();
let groups = model
.groups
.iter()
.map(|g| (g.name.clone(), g.members.clone()))
.collect();
Self { Self {
fields, fields,
by_name, by_name,
conditions, 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). /// Busca un campo por su nombre COBOL (sin distinguir mayúsculas).
pub(crate) fn lookup(&self, cobol: &str) -> Option<&Field> { pub(crate) fn lookup(&self, cobol: &str) -> Option<&Field> {
self.by_name self.by_name
@@ -185,6 +185,9 @@ pub enum Stmt {
}, },
/// `INSPECT target ...` — cuenta o reemplaza caracteres. /// `INSPECT target ...` — cuenta o reemplaza caracteres.
Inspect { target: Operand, op: InspectOp }, Inspect { target: Operand, op: InspectOp },
/// `INITIALIZE targets...` — pone cada dato (o grupo) en su valor
/// por defecto: 0 los numéricos, espacios los alfanuméricos.
Initialize { targets: Vec<Operand> },
/// `PERFORM ...` — ver [`Perform`]. /// `PERFORM ...` — ver [`Perform`].
Perform(Perform), Perform(Perform),
/// `GO TO target` /// `GO TO target`
+23 -3
View File
@@ -32,7 +32,7 @@ mod stmt;
pub use ast::*; pub use ast::*;
pub use charka_parser::Program; pub use charka_parser::Program;
pub use model::{resolve_data, ConditionName, DataModel, Field, FieldKind}; pub use model::{resolve_data, ConditionName, DataModel, Field, FieldKind, GroupInfo};
use cursor::Cursor; use cursor::Cursor;
@@ -440,6 +440,26 @@ mod tests {
} }
} }
#[test]
fn initialize_parses_and_groups_are_modeled() {
let program = ir("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");
let g = program.model.group("WS-REC").expect("grupo WS-REC");
assert_eq!(g.members, vec!["WS-A".to_string(), "WS-B".to_string()]);
match &program.procedures[0].body[0] {
Stmt::Initialize { targets } => {
assert_eq!(targets, &vec![Operand::Data("WS-REC".into())]);
}
other => panic!("se esperaba INITIALIZE, vino {other:?}"),
}
}
#[test] #[test]
fn several_statements_in_one_sentence() { fn several_statements_in_one_sentence() {
let b = body("MOVE 1 TO X DISPLAY X STOP RUN."); let b = body("MOVE 1 TO X DISPLAY X STOP RUN.");
@@ -451,10 +471,10 @@ mod tests {
#[test] #[test]
fn unrecognized_verb_becomes_unknown() { fn unrecognized_verb_becomes_unknown() {
let b = body("INITIALIZE WS-X WS-Y."); let b = body("CALL 'SUBPROG' USING WS-X.");
match &b[0] { match &b[0] {
Stmt::Unknown { verb, tokens } => { Stmt::Unknown { verb, tokens } => {
assert_eq!(verb, "INITIALIZE"); assert_eq!(verb, "CALL");
assert!(!tokens.is_empty()); assert!(!tokens.is_empty());
} }
other => panic!("se esperaba Unknown, vino {other:?}"), other => panic!("se esperaba Unknown, vino {other:?}"),
+34 -4
View File
@@ -45,6 +45,16 @@ pub struct ConditionName {
pub value: Operand, pub value: Operand,
} }
/// Un grupo de datos: su nombre y los datos elementales que contiene
/// (recursivamente).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GroupInfo {
/// Nombre COBOL del grupo, en mayúsculas.
pub name: String,
/// Nombres COBOL de los datos elementales que agrupa.
pub members: Vec<String>,
}
/// El modelo de datos resuelto de un programa. /// El modelo de datos resuelto de un programa.
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct DataModel { pub struct DataModel {
@@ -52,6 +62,8 @@ pub struct DataModel {
pub fields: Vec<Field>, pub fields: Vec<Field>,
/// Los nombres de condición (nivel 88). /// Los nombres de condición (nivel 88).
pub conditions: Vec<ConditionName>, pub conditions: Vec<ConditionName>,
/// Los grupos y sus datos elementales.
pub groups: Vec<GroupInfo>,
} }
impl DataModel { impl DataModel {
@@ -66,6 +78,12 @@ impl DataModel {
let up = name.to_uppercase(); let up = name.to_uppercase();
self.conditions.iter().find(|c| c.name == up) self.conditions.iter().find(|c| c.name == up)
} }
/// Busca un grupo por su nombre COBOL.
pub fn group(&self, name: &str) -> Option<&GroupInfo> {
let up = name.to_uppercase();
self.groups.iter().find(|g| g.name == up)
}
} }
/// Aplana el árbol de datos en un [`DataModel`]. /// Aplana el árbol de datos en un [`DataModel`].
@@ -75,9 +93,12 @@ pub fn resolve_data(data: &[DataItem]) -> DataModel {
model model
} }
/// Recorre el árbol: registra los 88 como condiciones sobre su dato /// Recorre el árbol: registra los 88 como condiciones, los grupos con
/// padre, recurre en los grupos y emite los datos elementales. /// sus miembros, y emite los datos elementales. Devuelve los nombres
fn walk(items: &[DataItem], model: &mut DataModel) { /// de los datos elementales producidos (para que el grupo que llama
/// los reúna como sus miembros).
fn walk(items: &[DataItem], model: &mut DataModel) -> Vec<String> {
let mut produced = Vec::new();
for it in items { for it in items {
if it.level == 66 || it.level == 88 { if it.level == 66 || it.level == 88 {
// Los 88 los registra su dato padre; los 66 se omiten. // Los 88 los registra su dato padre; los 66 se omiten.
@@ -96,13 +117,21 @@ fn walk(items: &[DataItem], model: &mut DataModel) {
// Un dato con hijos «reales» (no 88/66) es un grupo. // Un dato con hijos «reales» (no 88/66) es un grupo.
let is_group = it.children.iter().any(|c| c.level != 88 && c.level != 66); let is_group = it.children.iter().any(|c| c.level != 88 && c.level != 66);
if is_group { if is_group {
walk(&it.children, model); let members = walk(&it.children, model);
if it.name != "FILLER" {
model.groups.push(GroupInfo {
name: it.name.to_uppercase(),
members: members.clone(),
});
}
produced.extend(members);
} else if it.name != "FILLER" { } else if it.name != "FILLER" {
if let Some(kind) = classify(it.picture.as_deref()) { if let Some(kind) = classify(it.picture.as_deref()) {
let init = match kind { let init = match kind {
FieldKind::Num { .. } => numeric_value(it.value.as_deref()), FieldKind::Num { .. } => numeric_value(it.value.as_deref()),
FieldKind::Text { .. } => text_value(it.value.as_deref()), FieldKind::Text { .. } => text_value(it.value.as_deref()),
}; };
produced.push(it.name.to_uppercase());
model.fields.push(Field { model.fields.push(Field {
name: it.name.to_uppercase(), name: it.name.to_uppercase(),
kind, kind,
@@ -112,6 +141,7 @@ fn walk(items: &[DataItem], model: &mut DataModel) {
} }
} }
} }
produced
} }
/// Clasifica una cláusula PICTURE: alfanumérica si tiene `X`/`A`, /// Clasifica una cláusula PICTURE: alfanumérica si tiene `X`/`A`,
@@ -44,6 +44,7 @@ fn parse_one_stmt(c: &mut Cursor, stops: &[&str]) -> Stmt {
"STRING" => parse_string(c), "STRING" => parse_string(c),
"UNSTRING" => parse_unstring(c), "UNSTRING" => parse_unstring(c),
"INSPECT" => parse_inspect(c), "INSPECT" => parse_inspect(c),
"INITIALIZE" => parse_initialize(c),
"PERFORM" => parse_perform(c), "PERFORM" => parse_perform(c),
"GO" => parse_goto(c), "GO" => parse_goto(c),
"STOP" => parse_stop(c), "STOP" => parse_stop(c),
@@ -390,6 +391,14 @@ fn parse_unstring(c: &mut Cursor) -> Stmt {
} }
} }
fn parse_initialize(c: &mut Cursor) -> Stmt {
c.bump(); // INITIALIZE
let mut rounded = false;
let targets = parse_targets(c, &mut rounded);
skip_to_stmt_boundary(c); // p. ej. la cláusula `REPLACING`
Stmt::Initialize { targets }
}
fn parse_inspect(c: &mut Cursor) -> Stmt { fn parse_inspect(c: &mut Cursor) -> Stmt {
c.bump(); // INSPECT c.bump(); // INSPECT
let target = parse_operand(c); let target = parse_operand(c);
@@ -11,7 +11,7 @@ use charka_ir::{
BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, InspectOp, Ir, Operand, Perform, BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, InspectOp, Ir, Operand, Perform,
PerformControl, PerformTarget, Stmt, WhenTest, PerformControl, PerformTarget, Stmt, WhenTest,
}; };
use charka_runtime::{cobol_text_cmp, Decimal, Rounding}; use charka_runtime::{cobol_text_cmp, Decimal, Num, Rounding, Text};
use crate::field::{build_fields, Cell}; use crate::field::{build_fields, Cell};
@@ -293,6 +293,25 @@ impl<'a> Machine<'a> {
} }
Flow::Normal Flow::Normal
} }
Stmt::Initialize { targets } => {
for t in targets {
match t {
Operand::Data(name) => {
match self.ir.model.group(name).map(|g| g.members.clone()) {
Some(members) => {
for m in &members {
self.reset_field(m);
}
}
None => self.reset_field(name),
}
}
Operand::Indexed { .. } => self.reset_element(t),
_ => {}
}
}
Flow::Normal
}
Stmt::Perform(p) => self.exec_perform(p), Stmt::Perform(p) => self.exec_perform(p),
Stmt::GoTo { target } => { Stmt::GoTo { target } => {
// Aproximación: ejecuta el destino y sale del párrafo. // Aproximación: ejecuta el destino y sale del párrafo.
@@ -456,6 +475,44 @@ impl<'a> Machine<'a> {
} }
} }
/// Resetea un campo completo (escalar o tabla) a su valor por
/// defecto: 0 si es numérico, espacios si es alfanumérico.
fn reset_field(&mut self, name: &str) {
match self.fields.get_mut(&name.to_uppercase()) {
Some(Cell::Num(arr)) => {
for n in arr.iter_mut() {
*n = Num::new(n.picture());
}
}
Some(Cell::Text(arr)) => {
for t in arr.iter_mut() {
*t = Text::new(t.len());
}
}
None => {}
}
}
/// Resetea un solo elemento de tabla a su valor por defecto.
fn reset_element(&mut self, op: &Operand) {
let Some((key, idx)) = self.resolve(op) else {
return;
};
match self.fields.get_mut(&key) {
Some(Cell::Num(arr)) => {
if let Some(n) = arr.get_mut(idx) {
*n = Num::new(n.picture());
}
}
Some(Cell::Text(arr)) => {
if let Some(t) = arr.get_mut(idx) {
*t = Text::new(t.len());
}
}
None => {}
}
}
/// Almacena un texto en un destino, conformándolo a su tipo. /// Almacena un texto en un destino, conformándolo a su tipo.
fn store_text(&mut self, target: &Operand, text: &str) { fn store_text(&mut self, target: &Operand, text: &str) {
let Some((key, idx)) = self.resolve(target) else { let Some((key, idx)) = self.resolve(target) else {
@@ -123,6 +123,7 @@ mod tests {
corpus_test!(corpus_12_cadenas, "12-cadenas"); corpus_test!(corpus_12_cadenas, "12-cadenas");
corpus_test!(corpus_13_inspeccion, "13-inspeccion"); corpus_test!(corpus_13_inspeccion, "13-inspeccion");
corpus_test!(corpus_14_clasifica, "14-clasifica"); corpus_test!(corpus_14_clasifica, "14-clasifica");
corpus_test!(corpus_15_resetear, "15-resetear");
#[test] #[test]
fn empty_source_runs_clean() { fn empty_source_runs_clean() {
@@ -0,0 +1,20 @@
* corpus charka nivel 6: INITIALIZE (resetear datos y grupos)
IDENTIFICATION DIVISION.
PROGRAM-ID. RESETEAR.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-REGISTRO.
05 WS-NOMBRE PIC X(6) VALUE 'PEDRO'.
05 WS-EDAD PIC 9(3) VALUE 42.
01 WS-CONTADOR PIC 9(3) VALUE 77.
PROCEDURE DIVISION.
MAIN.
DISPLAY 'ANTES NOMBRE=' WS-NOMBRE.
DISPLAY 'ANTES EDAD=' WS-EDAD.
DISPLAY 'ANTES CONT=' WS-CONTADOR.
INITIALIZE WS-REGISTRO.
INITIALIZE WS-CONTADOR.
DISPLAY 'DESP NOMBRE=' WS-NOMBRE.
DISPLAY 'DESP EDAD=' WS-EDAD.
DISPLAY 'DESP CONT=' WS-CONTADOR.
STOP RUN.
@@ -0,0 +1,6 @@
ANTES NOMBRE=PEDRO
ANTES EDAD=042
ANTES CONT=077
DESP NOMBRE=
DESP EDAD=000
DESP CONT=000
+1
View File
@@ -23,6 +23,7 @@ salida correcta, una línea por `DISPLAY`.
| `12-cadenas` | 6 | `STRING` (concatenar) y `UNSTRING` (partir) | | `12-cadenas` | 6 | `STRING` (concatenar) y `UNSTRING` (partir) |
| `13-inspeccion` | 6 | `INSPECT` — contar (`TALLYING`) y reemplazar | | `13-inspeccion` | 6 | `INSPECT` — contar (`TALLYING`) y reemplazar |
| `14-clasifica` | 6 | `EVALUATE TRUE` y rangos `WHEN ... THRU` | | `14-clasifica` | 6 | `EVALUATE TRUE` y rangos `WHEN ... THRU` |
| `15-resetear` | 6 | `INITIALIZE` — resetear datos y grupos |
## Formato ## Formato
+15
View File
@@ -3,6 +3,21 @@
Transpilador COBOL → Rust. El módulo más grande del ecosistema (Fase D 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. del plan macro) — el parser COBOL completo es un esfuerzo multi-mes.
### 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`).
- 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 en ambas rutas.
### feat(charka): EVALUATE TRUE y rangos WHEN ... THRU ### feat(charka): EVALUATE TRUE y rangos WHEN ... THRU
Completa el `EVALUATE` con sus dos formas que faltaban. Completa el `EVALUATE` con sus dos formas que faltaban.