gioser

tawasuyu/brahman

Rust 321TOML 122JSON 14Markdown 114270 Definiciones

⎇ 28ee1ae2602662b216549ba4db0d34a9b55b8deb crates/modules/charka/charka-ir/src/stmt.rs Rust · 465 líneas · 12.8 KB Historia Quién tocó qué crudo

feat(charka): EVALUATE — el case de COBOL 4df7478b7 · 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, WhenBranch};
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        "EVALUATE" => parse_evaluate(c),
42        "PERFORM" => parse_perform(c),
43        "GO" => parse_goto(c),
44        "STOP" => parse_stop(c),
45        "GOBACK" => {
46            c.bump();
47            Stmt::Goback
48        }
49        "EXIT" => parse_exit(c),
50        "CONTINUE" => {
51            c.bump();
52            Stmt::Continue
53        }
54        _ => parse_unknown(c, stops),
55    }
56}
57
58// ── Listas ────────────────────────────────────────────────────────
59
60/// Lee una lista de nombres de dato (separados por comas opcionales),
61/// hasta una palabra frontera. Consume las apariciones de `ROUNDED`.
62fn parse_name_list(c: &mut Cursor, rounded: &mut bool) -> Vec<String> {
63    let mut names = Vec::new();
64    loop {
65        c.eat_sym(",");
66        if c.eat_word("ROUNDED") {
67            *rounded = true;
68            continue;
69        }
70        match c.peek_word() {
71            Some(w) if !is_boundary(&w) => {
72                c.bump();
73                names.push(w);
74            }
75            _ => break,
76        }
77    }
78    names
79}
80
81/// Lee una lista de operandos hasta una palabra frontera.
82fn parse_operand_list(c: &mut Cursor) -> Vec<Operand> {
83    let mut ops = Vec::new();
84    loop {
85        c.eat_sym(",");
86        if c.done() {
87            break;
88        }
89        if let Some(w) = c.peek_word() {
90            if is_boundary(&w) {
91                break;
92            }
93        }
94        let is_start = matches!(
95            c.peek().map(|t| t.kind),
96            Some(TokenKind::Number | TokenKind::String | TokenKind::Word)
97        ) || c.at_sym("-")
98            || c.at_sym("+");
99        if !is_start {
100            break;
101        }
102        ops.push(parse_operand(c));
103    }
104    ops
105}
106
107/// Salta los tokens de una cláusula que la v1 no modela, hasta el
108/// siguiente verbo o terminador de ámbito.
109fn skip_to_stmt_boundary(c: &mut Cursor) {
110    while !c.done() {
111        if let Some(w) = c.peek_word() {
112            if is_verb(&w) || is_terminator(&w) {
113                break;
114            }
115        }
116        c.bump();
117    }
118}
119
120/// Lee un único nombre de dato, si lo hay y no es una palabra frontera.
121fn parse_one_name(c: &mut Cursor) -> Option<String> {
122    match c.peek_word() {
123        Some(w) if !is_boundary(&w) => {
124            c.bump();
125            Some(w)
126        }
127        _ => None,
128    }
129}
130
131// ── Statements ────────────────────────────────────────────────────
132
133fn parse_move(c: &mut Cursor) -> Stmt {
134    c.bump(); // MOVE
135    c.eat_word("CORRESPONDING");
136    c.eat_word("CORR");
137    let from = parse_operand(c);
138    c.eat_word("TO");
139    let mut rounded = false;
140    let to = parse_name_list(c, &mut rounded);
141    Stmt::Move { from, to }
142}
143
144fn parse_display(c: &mut Cursor) -> Stmt {
145    c.bump(); // DISPLAY
146    let items = parse_operand_list(c);
147    skip_to_stmt_boundary(c); // p. ej. `WITH NO ADVANCING`, `UPON ...`
148    Stmt::Display { items }
149}
150
151fn parse_accept(c: &mut Cursor) -> Stmt {
152    c.bump(); // ACCEPT
153    let into = parse_one_name(c).unwrap_or_default();
154    skip_to_stmt_boundary(c); // p. ej. `FROM DATE`
155    Stmt::Accept { into }
156}
157
158fn parse_compute(c: &mut Cursor) -> Stmt {
159    c.bump(); // COMPUTE
160    let mut rounded = false;
161    let targets = parse_name_list(c, &mut rounded);
162    if !c.eat_sym("=") {
163        c.eat_word("EQUAL");
164    }
165    let expr = parse_expr(c);
166    c.eat_word("END-COMPUTE");
167    Stmt::Compute {
168        targets,
169        rounded,
170        expr,
171    }
172}
173
174fn parse_add(c: &mut Cursor) -> Stmt {
175    c.bump(); // ADD
176    c.eat_word("CORRESPONDING");
177    c.eat_word("CORR");
178    let addends = parse_operand_list(c);
179    let mut rounded = false;
180    let mut to = Vec::new();
181    let mut giving = Vec::new();
182    if c.eat_word("TO") {
183        to = parse_name_list(c, &mut rounded);
184    }
185    if c.eat_word("GIVING") {
186        giving = parse_name_list(c, &mut rounded);
187    }
188    c.eat_word("END-ADD");
189    Stmt::Add {
190        addends,
191        to,
192        giving,
193        rounded,
194    }
195}
196
197fn parse_subtract(c: &mut Cursor) -> Stmt {
198    c.bump(); // SUBTRACT
199    c.eat_word("CORRESPONDING");
200    c.eat_word("CORR");
201    let amounts = parse_operand_list(c);
202    let mut rounded = false;
203    let mut from = Vec::new();
204    let mut giving = Vec::new();
205    if c.eat_word("FROM") {
206        from = parse_name_list(c, &mut rounded);
207    }
208    if c.eat_word("GIVING") {
209        giving = parse_name_list(c, &mut rounded);
210    }
211    c.eat_word("END-SUBTRACT");
212    Stmt::Subtract {
213        amounts,
214        from,
215        giving,
216        rounded,
217    }
218}
219
220fn parse_multiply(c: &mut Cursor) -> Stmt {
221    c.bump(); // MULTIPLY
222    let left = parse_operand(c);
223    c.eat_word("BY");
224    let by = parse_operand(c);
225    let mut rounded = false;
226    let mut giving = Vec::new();
227    if c.eat_word("GIVING") {
228        giving = parse_name_list(c, &mut rounded);
229    } else if c.eat_word("ROUNDED") {
230        rounded = true;
231    }
232    c.eat_word("END-MULTIPLY");
233    Stmt::Multiply {
234        left,
235        by,
236        giving,
237        rounded,
238    }
239}
240
241fn parse_divide(c: &mut Cursor) -> Stmt {
242    c.bump(); // DIVIDE
243    let left = parse_operand(c);
244    let by_form = if c.eat_word("BY") {
245        true
246    } else {
247        c.eat_word("INTO");
248        false
249    };
250    let right = parse_operand(c);
251    let mut rounded = false;
252    let mut giving = Vec::new();
253    if c.eat_word("GIVING") {
254        giving = parse_name_list(c, &mut rounded);
255    } else if c.eat_word("ROUNDED") {
256        rounded = true;
257    }
258    if c.eat_word("REMAINDER") {
259        let _ = parse_name_list(c, &mut rounded);
260    }
261    c.eat_word("END-DIVIDE");
262    Stmt::Divide {
263        left,
264        right,
265        by_form,
266        giving,
267        rounded,
268    }
269}
270
271fn parse_if(c: &mut Cursor) -> Stmt {
272    c.bump(); // IF
273    let cond = parse_cond(c);
274    c.eat_word("THEN");
275    let then_branch = parse_statements(c, &["ELSE", "END-IF"]);
276    let else_branch = if c.eat_word("ELSE") {
277        parse_statements(c, &["END-IF"])
278    } else {
279        Vec::new()
280    };
281    c.eat_word("END-IF");
282    Stmt::If {
283        cond,
284        then_branch,
285        else_branch,
286    }
287}
288
289fn parse_evaluate(c: &mut Cursor) -> Stmt {
290    c.bump(); // EVALUATE
291    let subject = parse_operand(c);
292    let mut whens = Vec::new();
293    let mut other = Vec::new();
294    while !c.done() && !c.at_word("END-EVALUATE") {
295        if !c.at_word("WHEN") {
296            break; // algo inesperado dentro del EVALUATE: se corta
297        }
298        // Varios `WHEN` apilados comparten el mismo cuerpo.
299        let mut values = Vec::new();
300        let mut is_other = false;
301        while c.eat_word("WHEN") {
302            if c.eat_word("OTHER") {
303                is_other = true;
304            } else {
305                values.push(parse_operand(c));
306            }
307        }
308        let body = parse_statements(c, &["WHEN", "END-EVALUATE"]);
309        if is_other {
310            other = body;
311        } else {
312            whens.push(WhenBranch { values, body });
313        }
314    }
315    c.eat_word("END-EVALUATE");
316    Stmt::Evaluate {
317        subject,
318        whens,
319        other,
320    }
321}
322
323fn parse_perform(c: &mut Cursor) -> Stmt {
324    c.bump(); // PERFORM
325
326    // `PERFORM VARYING ... ... END-PERFORM` — cuerpo en línea.
327    if c.eat_word("VARYING") {
328        let control = parse_varying(c);
329        let body = parse_statements(c, &["END-PERFORM"]);
330        c.eat_word("END-PERFORM");
331        return inline_perform(body, control);
332    }
333
334    // `PERFORM UNTIL cond ... END-PERFORM` — cuerpo en línea.
335    if c.eat_word("UNTIL") {
336        let cond = parse_cond(c);
337        let body = parse_statements(c, &["END-PERFORM"]);
338        c.eat_word("END-PERFORM");
339        return inline_perform(body, PerformControl::Until(cond));
340    }
341
342    // `PERFORM n TIMES ... END-PERFORM` — cuerpo en línea.
343    if matches!(c.peek().map(|t| t.kind), Some(TokenKind::Number)) {
344        let n = parse_operand(c);
345        c.eat_word("TIMES");
346        let body = parse_statements(c, &["END-PERFORM"]);
347        c.eat_word("END-PERFORM");
348        return inline_perform(body, PerformControl::Times(n));
349    }
350
351    // `PERFORM ... END-PERFORM` — cuerpo en línea, una vez.
352    if c.peek_word().map(|w| is_verb(&w)).unwrap_or(false) {
353        let body = parse_statements(c, &["END-PERFORM"]);
354        c.eat_word("END-PERFORM");
355        return inline_perform(body, PerformControl::Once);
356    }
357
358    // `PERFORM PARA [THRU PARA2] [VARYING ... | n TIMES | UNTIL cond]`.
359    let Some(name) = parse_one_name(c) else {
360        // Forma no reconocida tras `PERFORM`: perform vacío.
361        return inline_perform(Vec::new(), PerformControl::Once);
362    };
363    let thru = if c.eat_word("THRU") || c.eat_word("THROUGH") {
364        parse_one_name(c)
365    } else {
366        None
367    };
368    let control = if c.eat_word("VARYING") {
369        parse_varying(c)
370    } else if c.eat_word("UNTIL") {
371        PerformControl::Until(parse_cond(c))
372    } else if at_count(c) {
373        let n = parse_operand(c);
374        c.eat_word("TIMES");
375        PerformControl::Times(n)
376    } else {
377        PerformControl::Once
378    };
379    Stmt::Perform(Perform {
380        target: PerformTarget::Paragraph { name, thru },
381        control,
382    })
383}
384
385/// Arma un `PERFORM` con cuerpo en línea.
386fn inline_perform(body: Vec<Stmt>, control: PerformControl) -> Stmt {
387    Stmt::Perform(Perform {
388        target: PerformTarget::Inline(body),
389        control,
390    })
391}
392
393/// Parsea la cláusula `VARYING var FROM x BY y UNTIL cond`, ya
394/// consumida la palabra `VARYING`.
395fn parse_varying(c: &mut Cursor) -> PerformControl {
396    let var = parse_one_name(c).unwrap_or_default();
397    c.eat_word("FROM");
398    let from = parse_operand(c);
399    c.eat_word("BY");
400    let by = parse_operand(c);
401    c.eat_word("UNTIL");
402    let until = parse_cond(c);
403    PerformControl::Varying {
404        var,
405        from,
406        by,
407        until,
408    }
409}
410
411/// ¿El cursor está sobre `<operando> TIMES`?
412fn at_count(c: &Cursor) -> bool {
413    match c.peek().map(|t| t.kind) {
414        Some(TokenKind::Number) => true,
415        Some(TokenKind::Word) => {
416            let w = c.peek_word().unwrap_or_default();
417            !is_boundary(&w) && c.word_at(1).as_deref() == Some("TIMES")
418        }
419        _ => false,
420    }
421}
422
423fn parse_goto(c: &mut Cursor) -> Stmt {
424    c.bump(); // GO
425    c.eat_word("TO");
426    Stmt::GoTo {
427        target: parse_one_name(c).unwrap_or_default(),
428    }
429}
430
431fn parse_stop(c: &mut Cursor) -> Stmt {
432    c.bump(); // STOP
433    c.eat_word("RUN");
434    Stmt::StopRun
435}
436
437fn parse_exit(c: &mut Cursor) -> Stmt {
438    c.bump(); // EXIT
439    c.eat_word("PROGRAM");
440    c.eat_word("PARAGRAPH");
441    c.eat_word("PERFORM");
442    c.eat_word("SECTION");
443    Stmt::Exit
444}
445
446/// Verbo no soportado: conserva el verbo y sus tokens hasta el próximo
447/// statement (otro verbo), terminador de ámbito o tope del bloque.
448fn parse_unknown(c: &mut Cursor, stops: &[&str]) -> Stmt {
449    let verb = c.peek_word().unwrap_or_default();
450    let mut tokens = Vec::new();
451    if let Some(t) = c.bump() {
452        tokens.push(t);
453    }
454    while !c.done() {
455        if let Some(w) = c.peek_word() {
456            if stops.contains(&w.as_str()) || is_verb(&w) || is_terminator(&w) {
457                break;
458            }
459        }
460        if let Some(t) = c.bump() {
461            tokens.push(t);
462        }
463    }
464    Stmt::Unknown { verb, tokens }
465}