⎇ 321e6f8e27aea06b5a868b37598148e15bf71f57
crates/modules/charka/charka-ir/src/stmt.rs
Historia
Quién tocó qué
crudo
feat(charka): PERFORM VARYING — el bucle con variable de control
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}