tawasuyu

tawasuyu/brahman

Rust 307TOML 110JSON 14Markdown 113965 Definiciones

⎇ 82ba0b7a1a04b7ff2d7e2175ac74766da0d16066 crates/modules/charka/charka-ir/src/stmt.rs Rust · 596 líneas · 16.8 KB Historia Quién tocó qué crudo

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