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:
@@ -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]
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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:?}"),
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user