tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Definiciones

⎇ 321e6f8e27aea06b5a868b37598148e15bf71f57 crates/modules/charka/charka-ir/src/stmt.rs Rust · 430 líneas · 11.8 KB Historia Quién tocó qué crudo

feat(charka): PERFORM VARYING — el bucle con variable de control 321e6f8e2 · 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 VARYING ... ... END-PERFORM` — cuerpo en línea.
292    if c.eat_word("VARYING") {
293        let control = parse_varying(c);
294        let body = parse_statements(c, &["END-PERFORM"]);
295        c.eat_word("END-PERFORM");
296        return inline_perform(body, control);
297    }
298
299    // `PERFORM UNTIL cond ... END-PERFORM` — cuerpo en línea.
300    if c.eat_word("UNTIL") {
301        let cond = parse_cond(c);
302        let body = parse_statements(c, &["END-PERFORM"]);
303        c.eat_word("END-PERFORM");
304        return inline_perform(body, PerformControl::Until(cond));
305    }
306
307    // `PERFORM n TIMES ... END-PERFORM` — cuerpo en línea.
308    if matches!(c.peek().map(|t| t.kind), Some(TokenKind::Number)) {
309        let n = parse_operand(c);
310        c.eat_word("TIMES");
311        let body = parse_statements(c, &["END-PERFORM"]);
312        c.eat_word("END-PERFORM");
313        return inline_perform(body, PerformControl::Times(n));
314    }
315
316    // `PERFORM ... END-PERFORM` — cuerpo en línea, una vez.
317    if c.peek_word().map(|w| is_verb(&w)).unwrap_or(false) {
318        let body = parse_statements(c, &["END-PERFORM"]);
319        c.eat_word("END-PERFORM");
320        return inline_perform(body, PerformControl::Once);
321    }
322
323    // `PERFORM PARA [THRU PARA2] [VARYING ... | n TIMES | UNTIL cond]`.
324    let Some(name) = parse_one_name(c) else {
325        // Forma no reconocida tras `PERFORM`: perform vacío.
326        return inline_perform(Vec::new(), PerformControl::Once);
327    };
328    let thru = if c.eat_word("THRU") || c.eat_word("THROUGH") {
329        parse_one_name(c)
330    } else {
331        None
332    };
333    let control = if c.eat_word("VARYING") {
334        parse_varying(c)
335    } else if c.eat_word("UNTIL") {
336        PerformControl::Until(parse_cond(c))
337    } else if at_count(c) {
338        let n = parse_operand(c);
339        c.eat_word("TIMES");
340        PerformControl::Times(n)
341    } else {
342        PerformControl::Once
343    };
344    Stmt::Perform(Perform {
345        target: PerformTarget::Paragraph { name, thru },
346        control,
347    })
348}
349
350/// Arma un `PERFORM` con cuerpo en línea.
351fn inline_perform(body: Vec<Stmt>, control: PerformControl) -> Stmt {
352    Stmt::Perform(Perform {
353        target: PerformTarget::Inline(body),
354        control,
355    })
356}
357
358/// Parsea la cláusula `VARYING var FROM x BY y UNTIL cond`, ya
359/// consumida la palabra `VARYING`.
360fn parse_varying(c: &mut Cursor) -> PerformControl {
361    let var = parse_one_name(c).unwrap_or_default();
362    c.eat_word("FROM");
363    let from = parse_operand(c);
364    c.eat_word("BY");
365    let by = parse_operand(c);
366    c.eat_word("UNTIL");
367    let until = parse_cond(c);
368    PerformControl::Varying {
369        var,
370        from,
371        by,
372        until,
373    }
374}
375
376/// ¿El cursor está sobre `<operando> TIMES`?
377fn at_count(c: &Cursor) -> bool {
378    match c.peek().map(|t| t.kind) {
379        Some(TokenKind::Number) => true,
380        Some(TokenKind::Word) => {
381            let w = c.peek_word().unwrap_or_default();
382            !is_boundary(&w) && c.word_at(1).as_deref() == Some("TIMES")
383        }
384        _ => false,
385    }
386}
387
388fn parse_goto(c: &mut Cursor) -> Stmt {
389    c.bump(); // GO
390    c.eat_word("TO");
391    Stmt::GoTo {
392        target: parse_one_name(c).unwrap_or_default(),
393    }
394}
395
396fn parse_stop(c: &mut Cursor) -> Stmt {
397    c.bump(); // STOP
398    c.eat_word("RUN");
399    Stmt::StopRun
400}
401
402fn parse_exit(c: &mut Cursor) -> Stmt {
403    c.bump(); // EXIT
404    c.eat_word("PROGRAM");
405    c.eat_word("PARAGRAPH");
406    c.eat_word("PERFORM");
407    c.eat_word("SECTION");
408    Stmt::Exit
409}
410
411/// Verbo no soportado: conserva el verbo y sus tokens hasta el próximo
412/// statement (otro verbo), terminador de ámbito o tope del bloque.
413fn parse_unknown(c: &mut Cursor, stops: &[&str]) -> Stmt {
414    let verb = c.peek_word().unwrap_or_default();
415    let mut tokens = Vec::new();
416    if let Some(t) = c.bump() {
417        tokens.push(t);
418    }
419    while !c.done() {
420        if let Some(w) = c.peek_word() {
421            if stops.contains(&w.as_str()) || is_verb(&w) || is_terminator(&w) {
422                break;
423            }
424        }
425        if let Some(t) = c.bump() {
426            tokens.push(t);
427        }
428    }
429    Stmt::Unknown { verb, tokens }
430}