⎇ 28ee1ae2602662b216549ba4db0d34a9b55b8deb
crates/modules/charka/charka-ir/src/stmt.rs
Historia
Quién tocó qué
crudo
feat(charka): EVALUATE — el case de COBOL
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}