tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198635 Definitions

⎇ fa65f202065abca26e0fb28581427117b84af85c crates/modules/charka/charka-ir/src/stmt.rs Rust · 577 lines · 16.3 KB History Who touched what raw

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