tawasuyu

tawasuyu/brahman

Rust 334TOML 129Markdown 39JSON 174745 Definitions

⎇ 7867d6830e128b8a2afe95cb752093e12a184988 crates/modules/charka/charka-ir/src/stmt.rs Rust · 568 lines · 16.0 KB History Who touched what raw

feat(charka): EVALUATE TRUE y rangos WHEN ... THRU 7867d6830 · 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::{
9    InspectOp, Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch, WhenTest,
10};
11use crate::cursor::{parse_operand, Cursor};
12use crate::expr::{parse_cond, parse_expr};
13use crate::kw::{is_boundary, is_terminator, is_verb};
14
15/// Parsea statements hasta agotar los tokens o toparse con una palabra
16/// de `stops` (los terminadores del bloque que llama).
17pub(crate) fn parse_statements(c: &mut Cursor, stops: &[&str]) -> Vec<Stmt> {
18    let mut out = Vec::new();
19    while !c.done() {
20        if let Some(w) = c.peek_word() {
21            if stops.contains(&w.as_str()) {
22                break;
23            }
24        }
25        out.push(parse_one_stmt(c, stops));
26    }
27    out
28}
29
30/// Parsea un statement: despacha por el verbo. Todo parser consume al
31/// menos un token, así que el bucle de [`parse_statements`] progresa.
32fn parse_one_stmt(c: &mut Cursor, stops: &[&str]) -> Stmt {
33    match c.peek_word().unwrap_or_default().as_str() {
34        "MOVE" => parse_move(c),
35        "DISPLAY" => parse_display(c),
36        "ACCEPT" => parse_accept(c),
37        "COMPUTE" => parse_compute(c),
38        "ADD" => parse_add(c),
39        "SUBTRACT" => parse_subtract(c),
40        "MULTIPLY" => parse_multiply(c),
41        "DIVIDE" => parse_divide(c),
42        "IF" => parse_if(c),
43        "EVALUATE" => parse_evaluate(c),
44        "STRING" => parse_string(c),
45        "UNSTRING" => parse_unstring(c),
46        "INSPECT" => parse_inspect(c),
47        "PERFORM" => parse_perform(c),
48        "GO" => parse_goto(c),
49        "STOP" => parse_stop(c),
50        "GOBACK" => {
51            c.bump();
52            Stmt::Goback
53        }
54        "EXIT" => parse_exit(c),
55        "CONTINUE" => {
56            c.bump();
57            Stmt::Continue
58        }
59        _ => parse_unknown(c, stops),
60    }
61}
62
63// ── Listas ────────────────────────────────────────────────────────
64
65/// Lee una lista de destinos de dato (separados por comas opcionales),
66/// hasta una palabra frontera. Cada destino puede llevar subíndice de
67/// tabla. Consume las apariciones de `ROUNDED`.
68fn parse_targets(c: &mut Cursor, rounded: &mut bool) -> Vec<Operand> {
69    let mut targets = Vec::new();
70    loop {
71        c.eat_sym(",");
72        if c.eat_word("ROUNDED") {
73            *rounded = true;
74            continue;
75        }
76        match c.peek_word() {
77            Some(w) if !is_boundary(&w) => targets.push(parse_operand(c)),
78            _ => break,
79        }
80    }
81    targets
82}
83
84/// Lee una lista de operandos hasta una palabra frontera.
85fn parse_operand_list(c: &mut Cursor) -> Vec<Operand> {
86    let mut ops = Vec::new();
87    loop {
88        c.eat_sym(",");
89        if c.done() {
90            break;
91        }
92        if let Some(w) = c.peek_word() {
93            if is_boundary(&w) {
94                break;
95            }
96        }
97        let is_start = matches!(
98            c.peek().map(|t| t.kind),
99            Some(TokenKind::Number | TokenKind::String | TokenKind::Word)
100        ) || c.at_sym("-")
101            || c.at_sym("+");
102        if !is_start {
103            break;
104        }
105        ops.push(parse_operand(c));
106    }
107    ops
108}
109
110/// Salta los tokens de una cláusula que la v1 no modela, hasta el
111/// siguiente verbo o terminador de ámbito.
112fn skip_to_stmt_boundary(c: &mut Cursor) {
113    while !c.done() {
114        if let Some(w) = c.peek_word() {
115            if is_verb(&w) || is_terminator(&w) {
116                break;
117            }
118        }
119        c.bump();
120    }
121}
122
123/// ¿El token actual puede iniciar un operando?
124fn is_operand_start(c: &Cursor) -> bool {
125    match c.peek().map(|t| t.kind) {
126        Some(TokenKind::Number | TokenKind::String) => true,
127        Some(TokenKind::Word) => c.peek_word().map(|w| !is_boundary(&w)).unwrap_or(false),
128        Some(TokenKind::Symbol) => c.at_sym("-") || c.at_sym("+"),
129        _ => false,
130    }
131}
132
133/// Lee un único nombre de dato, si lo hay y no es una palabra frontera.
134fn parse_one_name(c: &mut Cursor) -> Option<String> {
135    match c.peek_word() {
136        Some(w) if !is_boundary(&w) => {
137            c.bump();
138            Some(w)
139        }
140        _ => None,
141    }
142}
143
144// ── Statements ────────────────────────────────────────────────────
145
146fn parse_move(c: &mut Cursor) -> Stmt {
147    c.bump(); // MOVE
148    c.eat_word("CORRESPONDING");
149    c.eat_word("CORR");
150    let from = parse_operand(c);
151    c.eat_word("TO");
152    let mut rounded = false;
153    let to = parse_targets(c, &mut rounded);
154    Stmt::Move { from, to }
155}
156
157fn parse_display(c: &mut Cursor) -> Stmt {
158    c.bump(); // DISPLAY
159    let items = parse_operand_list(c);
160    skip_to_stmt_boundary(c); // p. ej. `WITH NO ADVANCING`, `UPON ...`
161    Stmt::Display { items }
162}
163
164fn parse_accept(c: &mut Cursor) -> Stmt {
165    c.bump(); // ACCEPT
166    let into = if c.peek_word().map(|w| !is_boundary(&w)).unwrap_or(false) {
167        parse_operand(c)
168    } else {
169        Operand::Data(String::new())
170    };
171    skip_to_stmt_boundary(c); // p. ej. `FROM DATE`
172    Stmt::Accept { into }
173}
174
175fn parse_compute(c: &mut Cursor) -> Stmt {
176    c.bump(); // COMPUTE
177    let mut rounded = false;
178    let targets = parse_targets(c, &mut rounded);
179    if !c.eat_sym("=") {
180        c.eat_word("EQUAL");
181    }
182    let expr = parse_expr(c);
183    c.eat_word("END-COMPUTE");
184    Stmt::Compute {
185        targets,
186        rounded,
187        expr,
188    }
189}
190
191fn parse_add(c: &mut Cursor) -> Stmt {
192    c.bump(); // ADD
193    c.eat_word("CORRESPONDING");
194    c.eat_word("CORR");
195    let addends = parse_operand_list(c);
196    let mut rounded = false;
197    let mut to = Vec::new();
198    let mut giving = Vec::new();
199    if c.eat_word("TO") {
200        to = parse_targets(c, &mut rounded);
201    }
202    if c.eat_word("GIVING") {
203        giving = parse_targets(c, &mut rounded);
204    }
205    c.eat_word("END-ADD");
206    Stmt::Add {
207        addends,
208        to,
209        giving,
210        rounded,
211    }
212}
213
214fn parse_subtract(c: &mut Cursor) -> Stmt {
215    c.bump(); // SUBTRACT
216    c.eat_word("CORRESPONDING");
217    c.eat_word("CORR");
218    let amounts = parse_operand_list(c);
219    let mut rounded = false;
220    let mut from = Vec::new();
221    let mut giving = Vec::new();
222    if c.eat_word("FROM") {
223        from = parse_targets(c, &mut rounded);
224    }
225    if c.eat_word("GIVING") {
226        giving = parse_targets(c, &mut rounded);
227    }
228    c.eat_word("END-SUBTRACT");
229    Stmt::Subtract {
230        amounts,
231        from,
232        giving,
233        rounded,
234    }
235}
236
237fn parse_multiply(c: &mut Cursor) -> Stmt {
238    c.bump(); // MULTIPLY
239    let left = parse_operand(c);
240    c.eat_word("BY");
241    let by = parse_operand(c);
242    let mut rounded = false;
243    let mut giving = Vec::new();
244    if c.eat_word("GIVING") {
245        giving = parse_targets(c, &mut rounded);
246    } else if c.eat_word("ROUNDED") {
247        rounded = true;
248    }
249    c.eat_word("END-MULTIPLY");
250    Stmt::Multiply {
251        left,
252        by,
253        giving,
254        rounded,
255    }
256}
257
258fn parse_divide(c: &mut Cursor) -> Stmt {
259    c.bump(); // DIVIDE
260    let left = parse_operand(c);
261    let by_form = if c.eat_word("BY") {
262        true
263    } else {
264        c.eat_word("INTO");
265        false
266    };
267    let right = parse_operand(c);
268    let mut rounded = false;
269    let mut giving = Vec::new();
270    if c.eat_word("GIVING") {
271        giving = parse_targets(c, &mut rounded);
272    } else if c.eat_word("ROUNDED") {
273        rounded = true;
274    }
275    if c.eat_word("REMAINDER") {
276        let _ = parse_targets(c, &mut rounded);
277    }
278    c.eat_word("END-DIVIDE");
279    Stmt::Divide {
280        left,
281        right,
282        by_form,
283        giving,
284        rounded,
285    }
286}
287
288fn parse_if(c: &mut Cursor) -> Stmt {
289    c.bump(); // IF
290    let cond = parse_cond(c);
291    c.eat_word("THEN");
292    let then_branch = parse_statements(c, &["ELSE", "END-IF"]);
293    let else_branch = if c.eat_word("ELSE") {
294        parse_statements(c, &["END-IF"])
295    } else {
296        Vec::new()
297    };
298    c.eat_word("END-IF");
299    Stmt::If {
300        cond,
301        then_branch,
302        else_branch,
303    }
304}
305
306fn parse_evaluate(c: &mut Cursor) -> Stmt {
307    c.bump(); // EVALUATE
308    let subject = parse_operand(c);
309    // `EVALUATE TRUE` — los `WHEN` son condiciones, no valores.
310    let cond_mode = matches!(&subject, Operand::Data(s) if s == "TRUE");
311    let mut whens = Vec::new();
312    let mut other = Vec::new();
313    while !c.done() && !c.at_word("END-EVALUATE") {
314        if !c.at_word("WHEN") {
315            break; // algo inesperado dentro del EVALUATE: se corta
316        }
317        // Varios `WHEN` apilados comparten el mismo cuerpo.
318        let mut tests = Vec::new();
319        let mut is_other = false;
320        while c.eat_word("WHEN") {
321            if c.eat_word("OTHER") {
322                is_other = true;
323            } else if cond_mode {
324                tests.push(WhenTest::Cond(parse_cond(c)));
325            } else {
326                let lo = parse_operand(c);
327                if c.eat_word("THRU") || c.eat_word("THROUGH") {
328                    tests.push(WhenTest::Range(lo, parse_operand(c)));
329                } else {
330                    tests.push(WhenTest::Value(lo));
331                }
332            }
333        }
334        let body = parse_statements(c, &["WHEN", "END-EVALUATE"]);
335        if is_other {
336            other = body;
337        } else {
338            whens.push(WhenBranch { tests, body });
339        }
340    }
341    c.eat_word("END-EVALUATE");
342    Stmt::Evaluate {
343        subject,
344        whens,
345        other,
346    }
347}
348
349fn parse_string(c: &mut Cursor) -> Stmt {
350    c.bump(); // STRING
351    let mut sources = Vec::new();
352    while !c.done() && !c.at_word("INTO") && !c.at_word("END-STRING") {
353        if c.eat_word("DELIMITED") {
354            c.eat_word("BY");
355            if !c.eat_word("SIZE") {
356                let _ = parse_operand(c); // delimitador: la v1 lo ignora
357            }
358        } else if is_operand_start(c) {
359            sources.push(parse_operand(c));
360        } else {
361            break;
362        }
363    }
364    c.eat_word("INTO");
365    let into = parse_operand(c);
366    skip_to_stmt_boundary(c); // p. ej. `WITH POINTER`, `ON OVERFLOW`
367    c.eat_word("END-STRING");
368    Stmt::StringConcat { sources, into }
369}
370
371fn parse_unstring(c: &mut Cursor) -> Stmt {
372    c.bump(); // UNSTRING
373    let source = parse_operand(c);
374    let delimiter = if c.eat_word("DELIMITED") {
375        c.eat_word("BY");
376        c.eat_word("ALL");
377        parse_operand(c)
378    } else {
379        Operand::Str(" ".to_string())
380    };
381    c.eat_word("INTO");
382    let mut rounded = false;
383    let into = parse_targets(c, &mut rounded);
384    skip_to_stmt_boundary(c); // p. ej. `DELIMITER IN`, `COUNT IN`
385    c.eat_word("END-UNSTRING");
386    Stmt::Unstring {
387        source,
388        delimiter,
389        into,
390    }
391}
392
393fn parse_inspect(c: &mut Cursor) -> Stmt {
394    c.bump(); // INSPECT
395    let target = parse_operand(c);
396    if c.eat_word("TALLYING") {
397        let counter = parse_operand(c);
398        c.eat_word("FOR");
399        c.eat_word("ALL");
400        let search = parse_operand(c);
401        skip_to_stmt_boundary(c);
402        Stmt::Inspect {
403            target,
404            op: InspectOp::TallyingForAll { counter, search },
405        }
406    } else if c.eat_word("REPLACING") {
407        c.eat_word("ALL");
408        let from = parse_operand(c);
409        c.eat_word("BY");
410        let to = parse_operand(c);
411        skip_to_stmt_boundary(c);
412        Stmt::Inspect {
413            target,
414            op: InspectOp::ReplacingAll { from, to },
415        }
416    } else {
417        // Forma de INSPECT que la v1 no modela.
418        skip_to_stmt_boundary(c);
419        Stmt::Unknown {
420            verb: "INSPECT".to_string(),
421            tokens: Vec::new(),
422        }
423    }
424}
425
426fn parse_perform(c: &mut Cursor) -> Stmt {
427    c.bump(); // PERFORM
428
429    // `PERFORM VARYING ... ... END-PERFORM` — cuerpo en línea.
430    if c.eat_word("VARYING") {
431        let control = parse_varying(c);
432        let body = parse_statements(c, &["END-PERFORM"]);
433        c.eat_word("END-PERFORM");
434        return inline_perform(body, control);
435    }
436
437    // `PERFORM UNTIL cond ... END-PERFORM` — cuerpo en línea.
438    if c.eat_word("UNTIL") {
439        let cond = parse_cond(c);
440        let body = parse_statements(c, &["END-PERFORM"]);
441        c.eat_word("END-PERFORM");
442        return inline_perform(body, PerformControl::Until(cond));
443    }
444
445    // `PERFORM n TIMES ... END-PERFORM` — cuerpo en línea.
446    if matches!(c.peek().map(|t| t.kind), Some(TokenKind::Number)) {
447        let n = parse_operand(c);
448        c.eat_word("TIMES");
449        let body = parse_statements(c, &["END-PERFORM"]);
450        c.eat_word("END-PERFORM");
451        return inline_perform(body, PerformControl::Times(n));
452    }
453
454    // `PERFORM ... END-PERFORM` — cuerpo en línea, una vez.
455    if c.peek_word().map(|w| is_verb(&w)).unwrap_or(false) {
456        let body = parse_statements(c, &["END-PERFORM"]);
457        c.eat_word("END-PERFORM");
458        return inline_perform(body, PerformControl::Once);
459    }
460
461    // `PERFORM PARA [THRU PARA2] [VARYING ... | n TIMES | UNTIL cond]`.
462    let Some(name) = parse_one_name(c) else {
463        // Forma no reconocida tras `PERFORM`: perform vacío.
464        return inline_perform(Vec::new(), PerformControl::Once);
465    };
466    let thru = if c.eat_word("THRU") || c.eat_word("THROUGH") {
467        parse_one_name(c)
468    } else {
469        None
470    };
471    let control = if c.eat_word("VARYING") {
472        parse_varying(c)
473    } else if c.eat_word("UNTIL") {
474        PerformControl::Until(parse_cond(c))
475    } else if at_count(c) {
476        let n = parse_operand(c);
477        c.eat_word("TIMES");
478        PerformControl::Times(n)
479    } else {
480        PerformControl::Once
481    };
482    Stmt::Perform(Perform {
483        target: PerformTarget::Paragraph { name, thru },
484        control,
485    })
486}
487
488/// Arma un `PERFORM` con cuerpo en línea.
489fn inline_perform(body: Vec<Stmt>, control: PerformControl) -> Stmt {
490    Stmt::Perform(Perform {
491        target: PerformTarget::Inline(body),
492        control,
493    })
494}
495
496/// Parsea la cláusula `VARYING var FROM x BY y UNTIL cond`, ya
497/// consumida la palabra `VARYING`.
498fn parse_varying(c: &mut Cursor) -> PerformControl {
499    let var = parse_one_name(c).unwrap_or_default();
500    c.eat_word("FROM");
501    let from = parse_operand(c);
502    c.eat_word("BY");
503    let by = parse_operand(c);
504    c.eat_word("UNTIL");
505    let until = parse_cond(c);
506    PerformControl::Varying {
507        var,
508        from,
509        by,
510        until,
511    }
512}
513
514/// ¿El cursor está sobre `<operando> TIMES`?
515fn at_count(c: &Cursor) -> bool {
516    match c.peek().map(|t| t.kind) {
517        Some(TokenKind::Number) => true,
518        Some(TokenKind::Word) => {
519            let w = c.peek_word().unwrap_or_default();
520            !is_boundary(&w) && c.word_at(1).as_deref() == Some("TIMES")
521        }
522        _ => false,
523    }
524}
525
526fn parse_goto(c: &mut Cursor) -> Stmt {
527    c.bump(); // GO
528    c.eat_word("TO");
529    Stmt::GoTo {
530        target: parse_one_name(c).unwrap_or_default(),
531    }
532}
533
534fn parse_stop(c: &mut Cursor) -> Stmt {
535    c.bump(); // STOP
536    c.eat_word("RUN");
537    Stmt::StopRun
538}
539
540fn parse_exit(c: &mut Cursor) -> Stmt {
541    c.bump(); // EXIT
542    c.eat_word("PROGRAM");
543    c.eat_word("PARAGRAPH");
544    c.eat_word("PERFORM");
545    c.eat_word("SECTION");
546    Stmt::Exit
547}
548
549/// Verbo no soportado: conserva el verbo y sus tokens hasta el próximo
550/// statement (otro verbo), terminador de ámbito o tope del bloque.
551fn parse_unknown(c: &mut Cursor, stops: &[&str]) -> Stmt {
552    let verb = c.peek_word().unwrap_or_default();
553    let mut tokens = Vec::new();
554    if let Some(t) = c.bump() {
555        tokens.push(t);
556    }
557    while !c.done() {
558        if let Some(w) = c.peek_word() {
559            if stops.contains(&w.as_str()) || is_verb(&w) || is_terminator(&w) {
560                break;
561            }
562        }
563        if let Some(t) = c.bump() {
564            tokens.push(t);
565        }
566    }
567    Stmt::Unknown { verb, tokens }
568}