feat(charka): OCCURS — tablas y referencias con subíndice
Los arrays de COBOL, que antes el transpilador descartaba en silencio.
Una rebanada vertical amplia que atraviesa el pipeline entero.
- Parser: la cláusula OCCURS n [TIMES] se captura en DataItem.
- IR: Operand::Indexed { name, index } — una referencia ELEM(I), con
subíndice 1-based. Los destinos de los statements pasan de
Vec<String> a Vec<Operand>, así que se puede escribir a un elemento
de tabla (MOVE x TO ELEM(I), COMPUTE ELEM(I) = ...). model::Field
gana occurs: Option<u32>.
- Codegen: un campo OCCURS se emite como Vec<Num>/Vec<Text>,
inicializado con vec![..; n]; una referencia con subíndice indexa el
vector (1-based -> 0-based).
- Shadow: en el intérprete todo campo es un vector — un escalar es de
longitud 1, una tabla de n; las referencias se resuelven a
(nombre, índice).
- Corpus: programa nuevo 11-tabla (llena una tabla con cuadrados y los
suma). Verificado: el intérprete sombra y el crate compilado por
scaffold dan ambos SUMA DE CUADRADOS = 000055.
Alcance v1: OCCURS elemental, una dimensión, subíndice de un operando.
Fuera: OCCURS de grupo, multidimensional, DEPENDING ON.
Tests: charka-parser 16, charka-ir 24, charka-codegen 18,
charka-shadow 16. fmt + clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -73,10 +73,14 @@ fn emit_struct(em: &mut Emitter, sym: &Symbols) {
|
||||
em.line("struct Program {");
|
||||
em.indent();
|
||||
for f in &sym.fields {
|
||||
let ty = match f.kind {
|
||||
let elem = match f.kind {
|
||||
FieldKind::Num { .. } => "Num",
|
||||
FieldKind::Text { .. } => "Text",
|
||||
};
|
||||
let ty = match f.occurs {
|
||||
None => elem.to_string(),
|
||||
Some(_) => format!("Vec<{elem}>"),
|
||||
};
|
||||
em.line(&format!("{}: {ty},", f.ident));
|
||||
}
|
||||
em.dedent();
|
||||
@@ -143,9 +147,10 @@ fn emit_main(em: &mut Emitter) {
|
||||
}
|
||||
|
||||
/// El inicializador de un campo, a partir de su `VALUE` ya
|
||||
/// normalizado por `charka-ir`.
|
||||
/// normalizado por `charka-ir`. Una tabla (`OCCURS n`) se inicializa
|
||||
/// como un `Vec` de `n` copias del valor inicial.
|
||||
fn field_init(f: &Field) -> String {
|
||||
match &f.kind {
|
||||
let scalar = match &f.kind {
|
||||
FieldKind::Num { int, frac, signed } => format!(
|
||||
"Num::with_value(Picture::new({int}, {frac}, {signed}), {})",
|
||||
rust_str(&f.init)
|
||||
@@ -153,6 +158,10 @@ fn field_init(f: &Field) -> String {
|
||||
FieldKind::Text { len } => {
|
||||
format!("Text::with_value({len}, {})", rust_str(&f.init))
|
||||
}
|
||||
};
|
||||
match f.occurs {
|
||||
None => scalar,
|
||||
Some(n) => format!("vec![{scalar}; {n}]"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +351,22 @@ mod tests {
|
||||
assert!(out.contains("cobol_text_cmp(self.ws_flag.display().as_str(), \"Y\").is_eq()"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn occurs_emits_a_vec_field_and_indexed_access() {
|
||||
let out = gen("DATA DIVISION.\n\
|
||||
WORKING-STORAGE SECTION.\n\
|
||||
01 WS-T.\n\
|
||||
05 WS-E PIC 9(3) OCCURS 4 TIMES.\n\
|
||||
01 WS-I PIC 9(1).\n\
|
||||
PROCEDURE DIVISION.\n\
|
||||
MAIN.\n\
|
||||
MOVE 7 TO WS-E(WS-I).\n");
|
||||
assert!(out.contains("ws_e: Vec<Num>,"));
|
||||
assert!(out.contains("; 4]"));
|
||||
assert!(out.contains("self.ws_e["));
|
||||
assert!(out.contains(".saturating_sub(1)]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_program_still_compiles_shape() {
|
||||
let out = gen("");
|
||||
|
||||
Reference in New Issue
Block a user