⎇ 82ba0b7a1a04b7ff2d7e2175ac74766da0d16066
crates/modules/charka/charka-ir/src/stmt.rs
Historia
Quién tocó qué
crudo
feat(charka): SET ... TO TRUE — escribir nombres de condición (88)
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}