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