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:
@@ -53,6 +53,9 @@ pub struct DataItem {
|
||||
/// Cláusula `VALUE`: literal numérico (con signo), constante
|
||||
/// figurativa en mayúsculas, o literal de texto entre comillas.
|
||||
pub value: Option<String>,
|
||||
/// Cláusula `OCCURS n [TIMES]`: el dato es una tabla de `n`
|
||||
/// elementos. `None` si es un dato escalar.
|
||||
pub occurs: Option<u32>,
|
||||
/// Ítems subordinados (de nivel numérico mayor).
|
||||
pub children: Vec<DataItem>,
|
||||
}
|
||||
@@ -205,9 +208,24 @@ fn parse_data_entry(level: u8, sent: &[Token]) -> Result<DataItem, ParseError> {
|
||||
|
||||
let mut picture = None;
|
||||
let mut value = None;
|
||||
let mut occurs = None;
|
||||
let mut i = 2;
|
||||
while i < sent.len() {
|
||||
match kw(sent.get(i)).as_deref() {
|
||||
Some("OCCURS") => {
|
||||
i += 1;
|
||||
if let Some(t) = sent.get(i) {
|
||||
if t.kind == TokenKind::Number {
|
||||
if occurs.is_none() {
|
||||
occurs = t.text.parse::<u32>().ok();
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if kw(sent.get(i)).as_deref() == Some("TIMES") {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
Some("PIC") | Some("PICTURE") => {
|
||||
i += 1;
|
||||
if kw(sent.get(i)).as_deref() == Some("IS") {
|
||||
@@ -239,6 +257,7 @@ fn parse_data_entry(level: u8, sent: &[Token]) -> Result<DataItem, ParseError> {
|
||||
name,
|
||||
picture,
|
||||
value,
|
||||
occurs,
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
@@ -605,6 +624,20 @@ mod tests {
|
||||
assert_eq!(p.data[0].children[1].name, "FILLER");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn occurs_clause_captured() {
|
||||
let p = parse_src(
|
||||
"DATA DIVISION.\n\
|
||||
WORKING-STORAGE SECTION.\n\
|
||||
01 WS-TABLA.\n\
|
||||
05 WS-ELEM PIC 9(3) OCCURS 10 TIMES.\n",
|
||||
);
|
||||
let elem = &p.data[0].children[0];
|
||||
assert_eq!(elem.name, "WS-ELEM");
|
||||
assert_eq!(elem.occurs, Some(10));
|
||||
assert_eq!(elem.picture.as_deref(), Some("9(3)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_level_number_is_error() {
|
||||
let toks = lex(
|
||||
|
||||
Reference in New Issue
Block a user