⎇ 7867d6830e128b8a2afe95cb752093e12a184988
crates/modules/charka/charka-ir/src/stmt.rs
History
Who touched what
raw
feat(charka): EVALUATE TRUE y rangos WHEN ... THRU
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}