gioser

tawasuyu/brahman

Rust 476TOML 190Markdown 50JSON 176602 Definiciones

⎇ 71a4068d12e81d0be7b08d0717114dd5b10868fe crates/modules/charka/charka-ir/src/stmt.rs Rust · 402 líneas · 11.0 KB Historia Quién tocó qué crudo

feat(charka): charka-ir — representación intermedia con statements tipados 71a4068d1 · sergio ·

1//! Parseo de los statements del PROCEDURE division. COBOL no separa
2//! statements con un símbolo: cada uno termina donde empieza el verbo
3//! del siguiente, por eso las listas de operandos se cortan al ver una
4//! palabra "frontera" (ver [`crate::kw`]).
5
6use charka_parser::TokenKind;
7
8use crate::ast::{Operand, Perform, PerformControl, PerformTarget, Stmt};
9use crate::cursor::{parse_operand, Cursor};
10use crate::expr::{parse_cond, parse_expr};
11use crate::kw::{is_boundary, is_terminator, is_verb};
12
13/// Parsea statements hasta agotar los tokens o toparse con una palabra
14/// de `stops` (los terminadores del bloque que llama).
15pub(crate) fn parse_statements(c: &mut Cursor, stops: &[&str]) -> Vec<Stmt> {
16    let mut out = Vec::new();
17    while !c.done() {
18        if let Some(w) = c.peek_word() {
19            if stops.contains(&w.as_str()) {
20                break;
21            }
22        }
23        out.push(parse_one_stmt(c, stops));
24    }
25    out
26}
27
28/// Parsea un statement: despacha por el verbo. Todo parser consume al
29/// menos un token, así que el bucle de [`parse_statements`] progresa.
30fn parse_one_stmt(c: &mut Cursor, stops: &[&str]) -> Stmt {
31    match c.peek_word().unwrap_or_default().as_str() {
32        "MOVE" => parse_move(c),
33        "DISPLAY" => parse_display(c),
34        "ACCEPT" => parse_accept(c),
35        "COMPUTE" => parse_compute(c),
36        "ADD" => parse_add(c),
37        "SUBTRACT" => parse_subtract(c),
38        "MULTIPLY" => parse_multiply(c),
39        "DIVIDE" => parse_divide(c),
40        "IF" => parse_if(c),
41        "PERFORM" => parse_perform(c),
42        "GO" => parse_goto(c),
43        "STOP" => parse_stop(c),
44        "GOBACK" => {
45            c.bump();
46            Stmt::Goback
47        }
48        "EXIT" => parse_exit(c),
49        "CONTINUE" => {
50            c.bump();
51            Stmt::Continue
52        }
53        _ => parse_unknown(c, stops),
54    }
55}
56
57// ── Listas ────────────────────────────────────────────────────────
58
59/// Lee una lista de nombres de dato (separados por comas opcionales),
60/// hasta una palabra frontera. Consume las apariciones de `ROUNDED`.
61fn parse_name_list(c: &mut Cursor, rounded: &mut bool) -> Vec<String> {
62    let mut names = Vec::new();
63    loop {
64        c.eat_sym(",");
65        if c.eat_word("ROUNDED") {
66            *rounded = true;
67            continue;
68        }
69        match c.peek_word() {
70            Some(w) if !is_boundary(&w) => {
71                c.bump();
72                names.push(w);
73            }
74            _ => break,
75        }
76    }
77    names
78}
79
80/// Lee una lista de operandos hasta una palabra frontera.
81fn parse_operand_list(c: &mut Cursor) -> Vec<Operand> {
82    let mut ops = Vec::new();
83    loop {
84        c.eat_sym(",");
85        if c.done() {
86            break;
87        }
88        if let Some(w) = c.peek_word() {
89            if is_boundary(&w) {
90                break;
91            }
92        }
93        let is_start = matches!(
94            c.peek().map(|t| t.kind),
95            Some(TokenKind::Number | TokenKind::String | TokenKind::Word)
96        ) || c.at_sym("-")
97            || c.at_sym("+");
98        if !is_start {
99            break;
100        }
101        ops.push(parse_operand(c));
102    }
103    ops
104}
105
106/// Salta los tokens de una cláusula que la v1 no modela, hasta el
107/// siguiente verbo o terminador de ámbito.
108fn skip_to_stmt_boundary(c: &mut Cursor) {
109    while !c.done() {
110        if let Some(w) = c.peek_word() {
111            if is_verb(&w) || is_terminator(&w) {
112                break;
113            }
114        }
115        c.bump();
116    }
117}
118
119/// Lee un único nombre de dato, si lo hay y no es una palabra frontera.
120fn parse_one_name(c: &mut Cursor) -> Option<String> {
121    match c.peek_word() {
122        Some(w) if !is_boundary(&w) => {
123            c.bump();
124            Some(w)
125        }
126        _ => None,
127    }
128}
129
130// ── Statements ────────────────────────────────────────────────────
131
132fn parse_move(c: &mut Cursor) -> Stmt {
133    c.bump(); // MOVE
134    c.eat_word("CORRESPONDING");
135    c.eat_word("CORR");
136    let from = parse_operand(c);
137    c.eat_word("TO");
138    let mut rounded = false;
139    let to = parse_name_list(c, &mut rounded);
140    Stmt::Move { from, to }
141}
142
143fn parse_display(c: &mut Cursor) -> Stmt {
144    c.bump(); // DISPLAY
145    let items = parse_operand_list(c);
146    skip_to_stmt_boundary(c); // p. ej. `WITH NO ADVANCING`, `UPON ...`
147    Stmt::Display { items }
148}
149
150fn parse_accept(c: &mut Cursor) -> Stmt {
151    c.bump(); // ACCEPT
152    let into = parse_one_name(c).unwrap_or_default();
153    skip_to_stmt_boundary(c); // p. ej. `FROM DATE`
154    Stmt::Accept { into }
155}
156
157fn parse_compute(c: &mut Cursor) -> Stmt {
158    c.bump(); // COMPUTE
159    let mut rounded = false;
160    let targets = parse_name_list(c, &mut rounded);
161    if !c.eat_sym("=") {
162        c.eat_word("EQUAL");
163    }
164    let expr = parse_expr(c);
165    c.eat_word("END-COMPUTE");
166    Stmt::Compute {
167        targets,
168        rounded,
169        expr,
170    }
171}
172
173fn parse_add(c: &mut Cursor) -> Stmt {
174    c.bump(); // ADD
175    c.eat_word("CORRESPONDING");
176    c.eat_word("CORR");
177    let addends = parse_operand_list(c);
178    let mut rounded = false;
179    let mut to = Vec::new();
180    let mut giving = Vec::new();
181    if c.eat_word("TO") {
182        to = parse_name_list(c, &mut rounded);
183    }
184    if c.eat_word("GIVING") {
185        giving = parse_name_list(c, &mut rounded);
186    }
187    c.eat_word("END-ADD");
188    Stmt::Add {
189        addends,
190        to,
191        giving,
192        rounded,
193    }
194}
195
196fn parse_subtract(c: &mut Cursor) -> Stmt {
197    c.bump(); // SUBTRACT
198    c.eat_word("CORRESPONDING");
199    c.eat_word("CORR");
200    let amounts = parse_operand_list(c);
201    let mut rounded = false;
202    let mut from = Vec::new();
203    let mut giving = Vec::new();
204    if c.eat_word("FROM") {
205        from = parse_name_list(c, &mut rounded);
206    }
207    if c.eat_word("GIVING") {
208        giving = parse_name_list(c, &mut rounded);
209    }
210    c.eat_word("END-SUBTRACT");
211    Stmt::Subtract {
212        amounts,
213        from,
214        giving,
215        rounded,
216    }
217}
218
219fn parse_multiply(c: &mut Cursor) -> Stmt {
220    c.bump(); // MULTIPLY
221    let left = parse_operand(c);
222    c.eat_word("BY");
223    let by = parse_operand(c);
224    let mut rounded = false;
225    let mut giving = Vec::new();
226    if c.eat_word("GIVING") {
227        giving = parse_name_list(c, &mut rounded);
228    } else if c.eat_word("ROUNDED") {
229        rounded = true;
230    }
231    c.eat_word("END-MULTIPLY");
232    Stmt::Multiply {
233        left,
234        by,
235        giving,
236        rounded,
237    }
238}
239
240fn parse_divide(c: &mut Cursor) -> Stmt {
241    c.bump(); // DIVIDE
242    let left = parse_operand(c);
243    let by_form = if c.eat_word("BY") {
244        true
245    } else {
246        c.eat_word("INTO");
247        false
248    };
249    let right = parse_operand(c);
250    let mut rounded = false;
251    let mut giving = Vec::new();
252    if c.eat_word("GIVING") {
253        giving = parse_name_list(c, &mut rounded);
254    } else if c.eat_word("ROUNDED") {
255        rounded = true;
256    }
257    if c.eat_word("REMAINDER") {
258        let _ = parse_name_list(c, &mut rounded);
259    }
260    c.eat_word("END-DIVIDE");
261    Stmt::Divide {
262        left,
263        right,
264        by_form,
265        giving,
266        rounded,
267    }
268}
269
270fn parse_if(c: &mut Cursor) -> Stmt {
271    c.bump(); // IF
272    let cond = parse_cond(c);
273    c.eat_word("THEN");
274    let then_branch = parse_statements(c, &["ELSE", "END-IF"]);
275    let else_branch = if c.eat_word("ELSE") {
276        parse_statements(c, &["END-IF"])
277    } else {
278        Vec::new()
279    };
280    c.eat_word("END-IF");
281    Stmt::If {
282        cond,
283        then_branch,
284        else_branch,
285    }
286}
287
288fn parse_perform(c: &mut Cursor) -> Stmt {
289    c.bump(); // PERFORM
290
291    // `PERFORM UNTIL cond ... END-PERFORM` — cuerpo en línea.
292    if c.eat_word("UNTIL") {
293        let cond = parse_cond(c);
294        let body = parse_statements(c, &["END-PERFORM"]);
295        c.eat_word("END-PERFORM");
296        return inline_perform(body, PerformControl::Until(cond));
297    }
298
299    // `PERFORM n TIMES ... END-PERFORM` — cuerpo en línea.
300    if matches!(c.peek().map(|t| t.kind), Some(TokenKind::Number)) {
301        let n = parse_operand(c);
302        c.eat_word("TIMES");
303        let body = parse_statements(c, &["END-PERFORM"]);
304        c.eat_word("END-PERFORM");
305        return inline_perform(body, PerformControl::Times(n));
306    }
307
308    // `PERFORM ... END-PERFORM` — cuerpo en línea, una vez.
309    if c.peek_word().map(|w| is_verb(&w)).unwrap_or(false) {
310        let body = parse_statements(c, &["END-PERFORM"]);
311        c.eat_word("END-PERFORM");
312        return inline_perform(body, PerformControl::Once);
313    }
314
315    // `PERFORM PARA [THRU PARA2] [n TIMES | UNTIL cond]` — fuera de línea.
316    let Some(name) = parse_one_name(c) else {
317        // Forma no soportada (p. ej. `PERFORM VARYING`): perform vacío.
318        return inline_perform(Vec::new(), PerformControl::Once);
319    };
320    let thru = if c.eat_word("THRU") || c.eat_word("THROUGH") {
321        parse_one_name(c)
322    } else {
323        None
324    };
325    let control = if c.eat_word("UNTIL") {
326        PerformControl::Until(parse_cond(c))
327    } else if at_count(c) {
328        let n = parse_operand(c);
329        c.eat_word("TIMES");
330        PerformControl::Times(n)
331    } else {
332        PerformControl::Once
333    };
334    Stmt::Perform(Perform {
335        target: PerformTarget::Paragraph { name, thru },
336        control,
337    })
338}
339
340/// Arma un `PERFORM` con cuerpo en línea.
341fn inline_perform(body: Vec<Stmt>, control: PerformControl) -> Stmt {
342    Stmt::Perform(Perform {
343        target: PerformTarget::Inline(body),
344        control,
345    })
346}
347
348/// ¿El cursor está sobre `<operando> TIMES`?
349fn at_count(c: &Cursor) -> bool {
350    match c.peek().map(|t| t.kind) {
351        Some(TokenKind::Number) => true,
352        Some(TokenKind::Word) => {
353            let w = c.peek_word().unwrap_or_default();
354            !is_boundary(&w) && c.word_at(1).as_deref() == Some("TIMES")
355        }
356        _ => false,
357    }
358}
359
360fn parse_goto(c: &mut Cursor) -> Stmt {
361    c.bump(); // GO
362    c.eat_word("TO");
363    Stmt::GoTo {
364        target: parse_one_name(c).unwrap_or_default(),
365    }
366}
367
368fn parse_stop(c: &mut Cursor) -> Stmt {
369    c.bump(); // STOP
370    c.eat_word("RUN");
371    Stmt::StopRun
372}
373
374fn parse_exit(c: &mut Cursor) -> Stmt {
375    c.bump(); // EXIT
376    c.eat_word("PROGRAM");
377    c.eat_word("PARAGRAPH");
378    c.eat_word("PERFORM");
379    c.eat_word("SECTION");
380    Stmt::Exit
381}
382
383/// Verbo no soportado: conserva el verbo y sus tokens hasta el próximo
384/// statement (otro verbo), terminador de ámbito o tope del bloque.
385fn parse_unknown(c: &mut Cursor, stops: &[&str]) -> Stmt {
386    let verb = c.peek_word().unwrap_or_default();
387    let mut tokens = Vec::new();
388    if let Some(t) = c.bump() {
389        tokens.push(t);
390    }
391    while !c.done() {
392        if let Some(w) = c.peek_word() {
393            if stops.contains(&w.as_str()) || is_verb(&w) || is_terminator(&w) {
394                break;
395            }
396        }
397        if let Some(t) = c.bump() {
398            tokens.push(t);
399        }
400    }
401    Stmt::Unknown { verb, tokens }
402}