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:
@@ -185,6 +185,9 @@ pub enum Stmt {
|
||||
},
|
||||
/// `INSPECT target ...` — cuenta o reemplaza caracteres.
|
||||
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(Perform),
|
||||
/// `GO TO target`
|
||||
|
||||
@@ -32,7 +32,7 @@ mod stmt;
|
||||
|
||||
pub use ast::*;
|
||||
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;
|
||||
|
||||
@@ -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]
|
||||
fn several_statements_in_one_sentence() {
|
||||
let b = body("MOVE 1 TO X DISPLAY X STOP RUN.");
|
||||
@@ -451,10 +471,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn unrecognized_verb_becomes_unknown() {
|
||||
let b = body("INITIALIZE WS-X WS-Y.");
|
||||
let b = body("CALL 'SUBPROG' USING WS-X.");
|
||||
match &b[0] {
|
||||
Stmt::Unknown { verb, tokens } => {
|
||||
assert_eq!(verb, "INITIALIZE");
|
||||
assert_eq!(verb, "CALL");
|
||||
assert!(!tokens.is_empty());
|
||||
}
|
||||
other => panic!("se esperaba Unknown, vino {other:?}"),
|
||||
|
||||
@@ -45,6 +45,16 @@ pub struct ConditionName {
|
||||
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.
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct DataModel {
|
||||
@@ -52,6 +62,8 @@ pub struct DataModel {
|
||||
pub fields: Vec<Field>,
|
||||
/// Los nombres de condición (nivel 88).
|
||||
pub conditions: Vec<ConditionName>,
|
||||
/// Los grupos y sus datos elementales.
|
||||
pub groups: Vec<GroupInfo>,
|
||||
}
|
||||
|
||||
impl DataModel {
|
||||
@@ -66,6 +78,12 @@ impl DataModel {
|
||||
let up = name.to_uppercase();
|
||||
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`].
|
||||
@@ -75,9 +93,12 @@ pub fn resolve_data(data: &[DataItem]) -> DataModel {
|
||||
model
|
||||
}
|
||||
|
||||
/// Recorre el árbol: registra los 88 como condiciones sobre su dato
|
||||
/// padre, recurre en los grupos y emite los datos elementales.
|
||||
fn walk(items: &[DataItem], model: &mut DataModel) {
|
||||
/// Recorre el árbol: registra los 88 como condiciones, los grupos con
|
||||
/// sus miembros, y emite los datos elementales. Devuelve los nombres
|
||||
/// 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 {
|
||||
if it.level == 66 || it.level == 88 {
|
||||
// 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.
|
||||
let is_group = it.children.iter().any(|c| c.level != 88 && c.level != 66);
|
||||
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" {
|
||||
if let Some(kind) = classify(it.picture.as_deref()) {
|
||||
let init = match kind {
|
||||
FieldKind::Num { .. } => numeric_value(it.value.as_deref()),
|
||||
FieldKind::Text { .. } => text_value(it.value.as_deref()),
|
||||
};
|
||||
produced.push(it.name.to_uppercase());
|
||||
model.fields.push(Field {
|
||||
name: it.name.to_uppercase(),
|
||||
kind,
|
||||
@@ -112,6 +141,7 @@ fn walk(items: &[DataItem], model: &mut DataModel) {
|
||||
}
|
||||
}
|
||||
}
|
||||
produced
|
||||
}
|
||||
|
||||
/// 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),
|
||||
"UNSTRING" => parse_unstring(c),
|
||||
"INSPECT" => parse_inspect(c),
|
||||
"INITIALIZE" => parse_initialize(c),
|
||||
"PERFORM" => parse_perform(c),
|
||||
"GO" => parse_goto(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 {
|
||||
c.bump(); // INSPECT
|
||||
let target = parse_operand(c);
|
||||
|
||||
Reference in New Issue
Block a user