⎇ 71a4068d12e81d0be7b08d0717114dd5b10868fe
crates/modules/charka/charka-ir/src/stmt.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(charka): charka-ir — representación intermedia con statements tipados
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 UNTIL cond ... END-PERFORM` — cuerpo en línea.
292 if c.eat_word("UNTIL") {
293 let cond = parse_cond(c);
294 let body = parse_statements(c, &["END-PERFORM"]);
295 c.eat_word("END-PERFORM");
296 return inline_perform(body, PerformControl::Until(cond));
297 }
298
299 // `PERFORM n TIMES ... END-PERFORM` — cuerpo en línea.
300 if matches!(c.peek().map(|t| t.kind), Some(TokenKind::Number)) {
301 let n = parse_operand(c);
302 c.eat_word("TIMES");
303 let body = parse_statements(c, &["END-PERFORM"]);
304 c.eat_word("END-PERFORM");
305 return inline_perform(body, PerformControl::Times(n));
306 }
307
308 // `PERFORM ... END-PERFORM` — cuerpo en línea, una vez.
309 if c.peek_word().map(|w| is_verb(&w)).unwrap_or(false) {
310 let body = parse_statements(c, &["END-PERFORM"]);
311 c.eat_word("END-PERFORM");
312 return inline_perform(body, PerformControl::Once);
313 }
314
315 // `PERFORM PARA [THRU PARA2] [n TIMES | UNTIL cond]` — fuera de línea.
316 let Some(name) = parse_one_name(c) else {
317 // Forma no soportada (p. ej. `PERFORM VARYING`): perform vacío.
318 return inline_perform(Vec::new(), PerformControl::Once);
319 };
320 let thru = if c.eat_word("THRU") || c.eat_word("THROUGH") {
321 parse_one_name(c)
322 } else {
323 None
324 };
325 let control = if c.eat_word("UNTIL") {
326 PerformControl::Until(parse_cond(c))
327 } else if at_count(c) {
328 let n = parse_operand(c);
329 c.eat_word("TIMES");
330 PerformControl::Times(n)
331 } else {
332 PerformControl::Once
333 };
334 Stmt::Perform(Perform {
335 target: PerformTarget::Paragraph { name, thru },
336 control,
337 })
338}
339
340/// Arma un `PERFORM` con cuerpo en línea.
341fn inline_perform(body: Vec<Stmt>, control: PerformControl) -> Stmt {
342 Stmt::Perform(Perform {
343 target: PerformTarget::Inline(body),
344 control,
345 })
346}
347
348/// ¿El cursor está sobre `<operando> TIMES`?
349fn at_count(c: &Cursor) -> bool {
350 match c.peek().map(|t| t.kind) {
351 Some(TokenKind::Number) => true,
352 Some(TokenKind::Word) => {
353 let w = c.peek_word().unwrap_or_default();
354 !is_boundary(&w) && c.word_at(1).as_deref() == Some("TIMES")
355 }
356 _ => false,
357 }
358}
359
360fn parse_goto(c: &mut Cursor) -> Stmt {
361 c.bump(); // GO
362 c.eat_word("TO");
363 Stmt::GoTo {
364 target: parse_one_name(c).unwrap_or_default(),
365 }
366}
367
368fn parse_stop(c: &mut Cursor) -> Stmt {
369 c.bump(); // STOP
370 c.eat_word("RUN");
371 Stmt::StopRun
372}
373
374fn parse_exit(c: &mut Cursor) -> Stmt {
375 c.bump(); // EXIT
376 c.eat_word("PROGRAM");
377 c.eat_word("PARAGRAPH");
378 c.eat_word("PERFORM");
379 c.eat_word("SECTION");
380 Stmt::Exit
381}
382
383/// Verbo no soportado: conserva el verbo y sus tokens hasta el próximo
384/// statement (otro verbo), terminador de ámbito o tope del bloque.
385fn parse_unknown(c: &mut Cursor, stops: &[&str]) -> Stmt {
386 let verb = c.peek_word().unwrap_or_default();
387 let mut tokens = Vec::new();
388 if let Some(t) = c.bump() {
389 tokens.push(t);
390 }
391 while !c.done() {
392 if let Some(w) = c.peek_word() {
393 if stops.contains(&w.as_str()) || is_verb(&w) || is_terminator(&w) {
394 break;
395 }
396 }
397 if let Some(t) = c.bump() {
398 tokens.push(t);
399 }
400 }
401 Stmt::Unknown { verb, tokens }
402}