1use crate::ast::*;
2use crate::lexer::{Tok, Token};
3use crate::{Error, Pos};
4
5mod declaratives;
6mod oo;
7mod report;
8mod sort;
9
10pub fn parse(tokens: &[Token], options: Vec<String>) -> Result<Vec<Program>, Error> {
12 let mut parser = Parser { tokens, at: 0, exec_declarations: Vec::new(), cics: false, sql: SqlState::default(), mnemonics: Vec::new(), debugging: false };
13 let mut programs = Vec::new();
14 parser.program(&options, &mut programs)?;
15 while parser.peek().is_some() {
16 if !parser.at_division(&["IDENTIFICATION", "ID"]) {
17 return Err(parser.error("another program, or the end of the source"));
18 }
19 parser.program(&options, &mut programs)?;
20 }
21 Ok(programs)
22}
23
24const VERBS: &[&str] = &[
26 "MOVE", "COMPUTE", "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "IF", "PERFORM", "DISPLAY", "INITIALIZE", "GO", "GOBACK", "STOP",
27 "CONTINUE", "EXIT", "EVALUATE", "SET", "CALL", "ACCEPT", "STRING", "UNSTRING", "INSPECT", "READ", "WRITE", "OPEN", "CLOSE",
28 "REWRITE", "DELETE", "START", "SEARCH", "SORT", "MERGE", "RETURN", "RELEASE", "CANCEL", "EXEC", "NEXT", "INVOKE",
29 "INITIATE", "GENERATE", "TERMINATE", "SUPPRESS", "ALTER", "ENTRY",
30];
31
32const PHRASE_WORDS: &[&str] = &[
34 "ELSE", "END-IF", "END-PERFORM", "END-COMPUTE", "END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE", "END-DISPLAY", "WHEN",
35 "TO", "FROM", "BY", "INTO", "GIVING", "REMAINDER", "ROUNDED", "ON", "NOT", "SIZE", "UNTIL", "VARYING", "TIMES", "THRU", "THROUGH",
36 "AND", "OR", "THEN", "UPON", "WITH", "IS", "END-EVALUATE", "ALSO", "OTHER", "OF", "IN", "AT", "END", "END-READ", "END-WRITE",
37 "BEFORE", "AFTER", "ADVANCING", "INPUT", "OUTPUT", "EXTEND", "I-O", "REVERSED", "USING", "RETURNING", "EXCEPTION", "OVERFLOW",
38 "END-CALL", "OMITTED", "CONTENT", "REFERENCE", "VALUE", "UP", "DOWN", "DELIMITED", "DELIMITER", "COUNT", "POINTER", "TALLYING",
39 "REPLACING", "CONVERTING", "INITIAL", "FOR", "CHARACTERS", "LEADING", "FIRST", "ALL", "END-STRING", "END-UNSTRING", "END-SEARCH",
40 "NEXT", "INVALID", "KEY", "END-REWRITE", "END-DELETE", "END-START", "END-INVOKE", "END-RETURN", "END-OF-PAGE", "EOP",
41];
42
43fn advancing_environment_name(word: &str) -> bool {
46 let numbered = |prefix: char, last: u8| word.len() == 3 && word.starts_with(prefix) && word[1..].parse::<u8>().is_ok_and(|n| (1..=last).contains(&n));
47 matches!(word, "CSP" | "AFP-5A") || numbered('C', 12) || numbered('S', 5)
48}
49
50fn figurative(word: &str) -> Option<Figurative> {
51 Some(match word {
52 "ZERO" | "ZEROS" | "ZEROES" => Figurative::Zero,
53 "SPACE" | "SPACES" => Figurative::Space,
54 "HIGH-VALUE" | "HIGH-VALUES" => Figurative::HighValue,
55 "LOW-VALUE" | "LOW-VALUES" => Figurative::LowValue,
56 "QUOTE" | "QUOTES" => Figurative::Quote,
57 "NULL" | "NULLS" => Figurative::Null,
58 _ => return None,
59 })
60}
61
62fn share_configuration(outer: &Environment, inner: &mut Environment) {
66 inner.debugging_mode |= outer.debugging_mode;
67 inner.decimal_point_comma |= outer.decimal_point_comma;
68 if inner.collating_sequence.is_none() {
69 inner.collating_sequence.clone_from(&outer.collating_sequence);
70 }
71 for (name, alphabet) in &outer.alphabets {
72 if !inner.alphabets.iter().any(|(n, _)| n == name) {
73 inner.alphabets.push((name.clone(), alphabet.clone()));
74 }
75 }
76}
77
78fn usage_word(word: &str) -> Option<Usage> {
79 Some(match word {
80 "DISPLAY" => Usage::Display,
81 "BINARY" | "COMP" | "COMPUTATIONAL" | "COMP-4" | "COMPUTATIONAL-4" => Usage::Binary,
82 "COMP-5" | "COMPUTATIONAL-5" => Usage::NativeBinary,
83 "PACKED-DECIMAL" | "COMP-3" | "COMPUTATIONAL-3" => Usage::Packed,
84 "COMP-1" | "COMPUTATIONAL-1" => Usage::Float1,
85 "COMP-2" | "COMPUTATIONAL-2" => Usage::Float2,
86 "NATIONAL" => Usage::National,
87 "POINTER" => Usage::Pointer,
88 "INDEX" => Usage::Index,
89 "FUNCTION-POINTER" | "PROCEDURE-POINTER" => Usage::ProgramPointer,
90 _ => return None,
91 })
92}
93
94struct Parser<'a> {
95 tokens: &'a [Token],
96 at: usize,
97 exec_declarations: Vec<ExecBlock>,
99 cics: bool,
101 mnemonics: Vec<(String, String)>,
104 sql: SqlState,
105 debugging: bool,
107}
108
109#[derive(Default)]
112struct SqlState {
113 whenever: crate::sql::Whenever,
114 blocks: u32,
115 cursors: crate::sql::Cursors,
116}
117
118type R<T> = Result<T, Error>;
119
120impl Parser<'_> {
121 fn peek(&self) -> Option<&Tok> {
122 self.tokens.get(self.at).map(|t| &t.tok)
123 }
124
125 fn peek_at(&self, ahead: usize) -> Option<&Tok> {
126 self.tokens.get(self.at + ahead).map(|t| &t.tok)
127 }
128
129 fn pos(&self) -> Pos {
130 self.tokens.get(self.at).or(self.tokens.last()).map(|t| t.pos).unwrap_or_default()
131 }
132
133 fn word(&self) -> Option<&str> {
134 match self.peek() {
135 Some(Tok::Word(w)) => Some(w),
136 _ => None,
137 }
138 }
139
140 fn word_at(&self, ahead: usize) -> Option<&str> {
141 match self.peek_at(ahead) {
142 Some(Tok::Word(w)) => Some(w),
143 _ => None,
144 }
145 }
146
147 fn is_word(&self, w: &str) -> bool {
148 self.word() == Some(w)
149 }
150
151 fn accept_word(&mut self, w: &str) -> bool {
152 let yes = self.is_word(w);
153 if yes {
154 self.at += 1;
155 }
156 yes
157 }
158
159 fn accept_any(&mut self, words: &[&str]) -> Option<String> {
160 let w = self.word().filter(|w| words.contains(w))?.to_owned();
161 self.at += 1;
162 Some(w)
163 }
164
165 fn expect_word(&mut self, w: &str) -> R<()> {
166 if self.accept_word(w) { Ok(()) } else { Err(self.error(format!("expected {w}"))) }
167 }
168
169 fn accept(&mut self, tok: &Tok) -> bool {
170 let yes = self.peek() == Some(tok);
171 if yes {
172 self.at += 1;
173 }
174 yes
175 }
176
177 fn expect(&mut self, tok: &Tok, what: &str) -> R<()> {
178 if self.accept(tok) { Ok(()) } else { Err(self.error(format!("expected {what}"))) }
179 }
180
181 fn error(&self, message: impl Into<String>) -> Error {
182 let found = match self.peek() {
183 None => "end of source".to_owned(),
184 Some(Tok::Word(w)) => w.clone(),
185 Some(t) => format!("{t:?}"),
186 };
187 Error::at(self.pos(), format!("{}, found {found}", message.into()))
188 }
189
190 fn name(&mut self, what: &str) -> R<String> {
191 match self.peek() {
192 Some(Tok::Word(w)) => {
193 let w = w.clone();
194 self.at += 1;
195 Ok(w)
196 }
197 _ => Err(self.error(format!("expected {what}"))),
198 }
199 }
200
201 fn at_division(&self, names: &[&str]) -> bool {
202 self.word().is_some_and(|w| names.contains(&w)) && self.word_at(1) == Some("DIVISION")
203 }
204
205 fn program(&mut self, options: &[String], out: &mut Vec<Program>) -> R<()> {
206 let outer = (std::mem::take(&mut self.exec_declarations), std::mem::take(&mut self.cics), std::mem::take(&mut self.sql.blocks), self.mnemonics.clone(), self.debugging);
207 let parsed = self.one_program(options, out);
208 (self.exec_declarations, self.cics, self.sql.blocks, self.mnemonics, self.debugging) = outer;
209 parsed
210 }
211
212 fn one_program(&mut self, options: &[String], out: &mut Vec<Program>) -> R<()> {
213 if !self.accept_word("IDENTIFICATION") {
214 self.expect_word("ID")?;
215 }
216 self.expect_word("DIVISION")?;
217 self.expect(&Tok::Period, "a period")?;
218 if self.is_word("CLASS-ID") {
219 return self.class_definition(options, out);
220 }
221 self.expect_word("PROGRAM-ID")?;
222 self.accept(&Tok::Period);
223 let id = match self.peek() {
224 Some(Tok::Alnum(s)) => {
225 let s = s.clone();
226 self.at += 1;
227 s
228 }
229 _ => self.name("a program name")?,
230 };
231 let (mut initial, mut recursive) = (false, false);
232 while let Some(t) = self.peek() {
233 if *t == Tok::Period {
234 self.at += 1;
235 break;
236 }
237 initial |= self.is_word("INITIAL");
238 recursive |= self.is_word("RECURSIVE");
239 self.at += 1;
240 }
241 self.program_body(id, initial, recursive, options, out, false)
242 }
243
244 fn program_body(&mut self, id: String, initial: bool, recursive: bool, options: &[String], out: &mut Vec<Program>, method: bool) -> R<()> {
247 while self.peek().is_some() && !self.at_division(&["ENVIRONMENT", "DATA", "PROCEDURE", "IDENTIFICATION", "ID"]) && !self.at_end_program() {
248 self.at += 1;
249 }
250 let (mut files, mut repository, mut environment) = (Vec::new(), Vec::new(), Environment::default());
251 if self.at_division(&["ENVIRONMENT"]) {
252 (files, repository) = self.environment(&mut environment)?;
253 }
254 self.mnemonics.splice(0..0, environment.mnemonics.iter().cloned());
255 self.debugging |= environment.debugging_mode;
256 let (mut working_storage, mut local_storage, mut linkage) = (Vec::new(), Vec::new(), Vec::new());
257 let mut report_writer = crate::report::ReportWriter::default();
258 let mut declaratives = Declaratives::default();
259 if self.at_division(&["DATA"]) {
260 self.at += 2;
261 self.expect(&Tok::Period, "a period")?;
262 while !self.at_division(&["PROCEDURE", "IDENTIFICATION", "ID"]) && !self.at_end_program() && self.peek().is_some() {
263 if self.data_exec()? {
264 continue;
265 }
266 let section = self.name("a DATA DIVISION section")?;
267 self.expect_word("SECTION")?;
268 self.expect(&Tok::Period, "a period")?;
269 match section.as_str() {
270 "WORKING-STORAGE" => working_storage = self.data_entries()?,
271 "LINKAGE" => linkage = self.data_entries()?,
272 "LOCAL-STORAGE" => local_storage = self.data_entries()?,
273 "FILE" => self.file_section(&mut files)?,
274 "REPORT" => report_writer.reports.extend(self.report_section()?),
275 other => return Err(self.error(format!("the {other} SECTION is not supported yet"))),
276 }
277 }
278 }
279 let (mut using, mut returning) = (Vec::new(), None);
280 let paragraphs = if self.at_division(&["PROCEDURE"]) {
281 self.at += 2;
282 if self.accept_word("USING") {
283 using = self.parameters()?;
284 }
285 if self.accept_word("RETURNING") {
286 returning = Some(self.name("a RETURNING item")?);
287 }
288 self.expect(&Tok::Period, "a period after the PROCEDURE DIVISION header")?;
289 self.procedure_paragraphs(&mut report_writer, &mut declaratives)?
290 } else {
291 Vec::new()
292 };
293 declaratives::debugging_sections_allowed(&declaratives, recursive, method)?;
294 if let Some(f) = files.iter().find(|f| f.assign.is_empty()) {
295 return Err(Error::at(f.pos, format!("{} has no SELECT ... ASSIGN", f.name)));
296 }
297 if self.cics {
298 self.translator_additions(&mut linkage, &mut using)?;
299 }
300 let exec_declarations = std::mem::take(&mut self.exec_declarations);
301 let (mut nested, mut contained) = (Vec::new(), Vec::new());
302 while self.at_division(&["IDENTIFICATION", "ID"]) {
303 let first = nested.len();
304 self.program(options, &mut nested)?;
305 contained.extend(nested.get(first).map(|p: &Program| p.id.clone()));
306 }
307 let entry = nested.iter().flat_map(|p: &Program| &p.paragraphs).flat_map(|p| &p.statements).find_map(|s| match s {
308 Stmt::Entry { pos, .. } => Some(*pos),
309 _ => None,
310 });
311 if let Some(pos) = entry {
312 return Err(Error::at(pos, "ENTRY cannot be used in a nested program"));
313 }
314 oo::share_repository(&repository, &mut nested)?;
315 for inner in &mut nested {
316 share_configuration(&environment, &mut inner.environment);
317 }
318 declaratives::contained_programs(&declaratives, &report_writer, &nested)?;
319 if !method && self.at_end_program() && self.word_at(1) == Some("PROGRAM") {
320 self.at += 2;
321 if self.word().is_some() || matches!(self.peek(), Some(Tok::Alnum(_))) {
322 self.at += 1;
323 }
324 self.accept(&Tok::Period);
325 }
326 out.push(Program {
327 id,
328 options: options.to_vec(),
329 initial,
330 recursive,
331 working_storage,
332 local_storage,
333 linkage,
334 using,
335 returning,
336 paragraphs,
337 files,
338 exec_declarations,
339 report_writer,
340 declaratives,
341 oo: oo::program_oo(repository),
342 environment,
343 nested: contained,
344 ..Program::default()
345 });
346 out.extend(nested);
347 Ok(())
348 }
349
350 fn parameters(&mut self) -> R<Vec<Param>> {
352 let (mut using, mut by_value) = (Vec::new(), false);
353 loop {
354 if self.accept_word("BY") {
355 by_value = self.accept_any(&["REFERENCE", "VALUE"]).as_deref() == Some("VALUE");
356 continue;
357 }
358 if !self.starts_ref() {
359 return Ok(using);
360 }
361 using.push(Param { by_value, name: self.name("a LINKAGE item")? });
362 }
363 }
364
365 fn at_end_program(&self) -> bool {
366 self.is_word("END") && matches!(self.word_at(1), Some("PROGRAM" | "METHOD"))
367 }
368
369 fn environment(&mut self, clauses: &mut Environment) -> R<(Vec<FileDecl>, Vec<ClassEntry>)> {
372 let (mut files, mut repository) = (Vec::new(), Vec::new());
373 while self.peek().is_some() && !self.at_division(&["DATA", "PROCEDURE"]) {
374 if self.accept_word("DECIMAL-POINT") {
375 self.accept_word("IS");
376 self.expect_word("COMMA")?;
377 clauses.decimal_point_comma = true;
378 continue;
379 }
380 if self.is_word("CURRENCY") {
381 let pos = self.pos();
382 self.at += 1;
383 self.accept_word("SIGN");
384 self.accept_word("IS");
385 let sign = self.literal()?;
386 if sign != Literal::Alnum("$".into()) || self.is_word("WITH") || self.is_word("PICTURE") {
387 return Err(Error::at(pos, "a CURRENCY SIGN other than $ is not supported yet"));
388 }
389 continue;
390 }
391 if self.environment_clause(clauses)? {
392 continue;
393 }
394 if self.is_word("DEBUGGING") && self.word_at(1) == Some("MODE") {
395 self.at += 2;
396 clauses.debugging_mode = true;
397 continue;
398 }
399 if let Some(environment) = self.word().filter(|w| advancing_environment_name(w)).map(str::to_owned) {
400 let at_name = if self.word_at(1) == Some("IS") { 2 } else { 1 };
401 if let Some(name) = self.word_at(at_name).map(str::to_owned) {
402 self.at += at_name + 1;
403 clauses.mnemonics.push((name, environment));
404 continue;
405 }
406 }
407 if self.accept_word("SELECT") {
408 files.push(self.select()?);
409 continue;
410 }
411 if self.accept_word("REPOSITORY") {
412 repository = self.repository()?;
413 continue;
414 }
415 self.at += 1;
416 }
417 Ok((files, repository))
418 }
419
420 fn select(&mut self) -> R<FileDecl> {
421 let pos = self.pos();
422 let optional = self.accept_word("OPTIONAL");
423 let name = self.name("a file name")?;
424 let mut f = FileDecl {
425 name,
426 assign: String::new(),
427 organization: Organization::Sequential,
428 access: Access::Sequential,
429 record_key: None,
430 alternate_keys: Vec::new(),
431 relative_key: None,
432 optional,
433 status: None,
434 recording: None,
435 record_min: None,
436 record_max: None,
437 records: Vec::new(),
438 reports: Vec::new(),
439 linage: None,
440 sort: false,
441 pos,
442 };
443 while !self.accept(&Tok::Period) {
444 let clause = self.name("a SELECT clause or a period")?;
445 match clause.as_str() {
446 "ASSIGN" => {
447 self.accept_word("TO");
448 let target = match self.peek().cloned() {
449 Some(Tok::Word(w)) | Some(Tok::Alnum(w)) => w,
450 _ => return Err(self.error("a DD name after ASSIGN")),
451 };
452 self.at += 1;
453 let target = target.to_ascii_uppercase();
454 f.assign = target.rsplit('-').next().filter(|_| target.contains("-S-") || target.starts_with("S-") || target.starts_with("AS-")).unwrap_or(&target).to_owned();
455 }
456 "RECORD" if !self.is_word("SEQUENTIAL") => {
457 self.accept_word("KEY");
458 self.accept_word("IS");
459 f.record_key = Some(self.reference()?);
460 }
461 "RELATIVE" if self.is_word("KEY") => {
462 self.at += 1;
463 self.accept_word("IS");
464 f.relative_key = Some(self.reference()?);
465 }
466 "ALTERNATE" => {
467 self.accept_word("RECORD");
468 self.expect_word("KEY")?;
469 self.accept_word("IS");
470 let key = self.reference()?;
471 let duplicates = self.accept_word("WITH") | self.is_word("DUPLICATES");
472 if duplicates {
473 self.expect_word("DUPLICATES")?;
474 }
475 f.alternate_keys.push((key, duplicates));
476 }
477 "ORGANIZATION" | "LINE" | "RECORD" | "SEQUENTIAL" | "INDEXED" | "RELATIVE" => {
478 if clause == "ORGANIZATION" {
479 self.accept_word("IS");
480 }
481 let first = if clause == "ORGANIZATION" { self.name("an organization")? } else { clause.clone() };
482 f.organization = match first.as_str() {
483 "LINE" => {
484 self.expect_word("SEQUENTIAL")?;
485 Organization::LineSequential
486 }
487 "RECORD" => {
488 self.expect_word("SEQUENTIAL")?;
489 Organization::Sequential
490 }
491 "SEQUENTIAL" => Organization::Sequential,
492 "INDEXED" => Organization::Indexed,
493 "RELATIVE" => Organization::Relative,
494 other => return Err(self.error(format!("ORGANIZATION {other} is not supported yet"))),
495 };
496 }
497 "ACCESS" => {
498 self.accept_word("MODE");
499 self.accept_word("IS");
500 f.access = match self.name("an access mode")?.as_str() {
501 "SEQUENTIAL" => Access::Sequential,
502 "RANDOM" => Access::Random,
503 "DYNAMIC" => Access::Dynamic,
504 other => return Err(self.error(format!("ACCESS MODE {other} is not an access mode"))),
505 };
506 }
507 "FILE" | "STATUS" => {
508 if clause == "FILE" {
509 self.expect_word("STATUS")?;
510 }
511 self.accept_word("IS");
512 f.status = Some(self.reference()?);
513 if self.starts_ref() && !self.word().is_some_and(|w| SELECT_CLAUSES.contains(&w)) {
514 self.reference()?;
515 }
516 }
517 "RESERVE" | "PADDING" => {
518 while self.peek().is_some() && self.peek() != Some(&Tok::Period) && !self.word().is_some_and(|w| SELECT_CLAUSES.contains(&w)) {
519 self.at += 1;
520 }
521 }
522 other => return Err(self.error(format!("{other} is not a SELECT clause ironwork for COBOL supports yet"))),
523 }
524 }
525 Ok(f)
526 }
527
528 fn file_section(&mut self, files: &mut [FileDecl]) -> R<()> {
531 while self.is_word("FD") || self.is_word("SD") {
532 let indicator = self.name("FD or SD")?;
533 let pos = self.pos();
534 let name = self.name("a file name")?;
535 let Some(index) = files.iter().position(|f| f.name == name) else {
536 return Err(Error::at(pos, format!("{indicator} {name} has no SELECT")));
537 };
538 files[index].sort = indicator == "SD";
539 while !self.accept(&Tok::Period) {
540 match self.name("an FD clause or a period")?.as_str() {
541 "RECORDING" => {
542 self.accept_word("MODE");
543 self.accept_word("IS");
544 let mode = self.name("F, V, U or S")?;
545 files[index].recording = mode.chars().next();
546 }
547 "RECORD" => {
548 self.accept_word("CONTAINS");
549 self.accept_word("IS");
550 if self.accept_word("VARYING") {
551 files[index].recording.get_or_insert('V');
552 self.accept_word("IN");
553 self.accept_word("SIZE");
554 self.accept_word("FROM");
555 }
556 let number = |p: &mut Self| -> R<Option<u32>> {
557 match p.peek() {
558 Some(Tok::Number(n)) => {
559 let v = n.parse().map_err(|_| p.error("a record length"))?;
560 p.at += 1;
561 Ok(Some(v))
562 }
563 _ => Ok(None),
564 }
565 };
566 let first = number(self)?;
567 let second = if self.accept_word("TO") { number(self)? } else { None };
568 if first.is_some() {
569 files[index].record_min = first;
570 files[index].record_max = second.or(first);
571 }
572 self.accept_word("CHARACTERS");
573 if self.accept_word("DEPENDING") {
574 self.accept_word("ON");
575 self.reference()?;
576 }
577 }
578 "REPORT" | "REPORTS" if files[index].sort => return Err(Error::at(pos, format!("SD {name}: a sort or merge file takes no REPORT clause"))),
579 "REPORT" | "REPORTS" => {
580 let names = self.report_names()?;
581 files[index].reports.extend(names);
582 }
583 "LINAGE" => {
584 let linage = self.linage()?;
585 if files[index].linage.is_some() {
586 return Err(Error::at(pos, format!("{indicator} {name}: LINAGE is given twice")));
587 }
588 files[index].linage = (indicator == "FD").then_some(linage);
589 }
590 _ => {
591 while self.peek().is_some() && self.peek() != Some(&Tok::Period) && !self.word().is_some_and(|w| FD_WORDS.contains(&w)) {
592 self.at += 1;
593 }
594 }
595 }
596 }
597 files[index].records = self.data_entries()?;
598 }
599 Ok(())
600 }
601
602 fn linage(&mut self) -> R<Linage> {
604 self.accept_word("IS");
605 let lines = self.linage_value("LINAGE")?;
606 self.accept_word("LINES");
607 let mut linage = Linage { lines, footing: None, top: None, bottom: None };
608 loop {
609 let start = self.at;
610 self.accept_word("WITH");
611 self.accept_word("LINES");
612 self.accept_word("AT");
613 let Some(phrase) = self.accept_any(&["FOOTING", "TOP", "BOTTOM"]) else {
614 self.at = start;
615 return Ok(linage);
616 };
617 if phrase == "FOOTING" {
618 self.accept_word("AT");
619 }
620 let value = Some(self.linage_value(&phrase)?);
621 let slot = match phrase.as_str() {
622 "FOOTING" => &mut linage.footing,
623 "TOP" => &mut linage.top,
624 _ => &mut linage.bottom,
625 };
626 if slot.is_some() {
627 return Err(self.error(format!("LINAGE: {phrase} is given twice")));
628 }
629 *slot = value;
630 }
631 }
632
633 fn linage_value(&mut self, phrase: &str) -> R<LinageValue> {
635 match self.peek() {
636 Some(Tok::Number(n)) if n.bytes().all(|b| b.is_ascii_digit()) => {
637 let n = n.clone();
638 self.at += 1;
639 Ok(LinageValue::Integer(n))
640 }
641 Some(Tok::Number(n)) => Err(self.error(format!("LINAGE: {phrase} {n} is not an unsigned integer"))),
642 Some(Tok::Word(_)) if self.starts_ref() => {
643 let r = self.reference()?;
644 if !r.subscripts.is_empty() || r.refmod.is_some() {
645 return Err(Error::at(r.pos, format!("LINAGE: {phrase} {} takes no subscript or reference modification", r.name)));
646 }
647 Ok(LinageValue::Data(r))
648 }
649 _ => Err(self.error(format!("an integer or a data-name after {phrase}"))),
650 }
651 }
652
653 fn data_exec(&mut self) -> R<bool> {
655 let Some(Tok::Exec(text)) = self.peek().cloned() else { return Ok(false) };
656 let block = self.exec_block(&text, self.pos());
657 self.at += 1;
658 self.accept(&Tok::Period);
659 self.exec_declarations.push(block);
660 Ok(true)
661 }
662
663 fn translator_additions(&mut self, linkage: &mut Vec<DataEntry>, using: &mut Vec<Param>) -> R<()> {
666 let defined = |l: &[DataEntry], n: &str| l.iter().any(|e| e.level == 1 && e.name.as_deref() == Some(n));
667 let mut added = Vec::new();
668 if !defined(linkage, "DFHEIBLK") {
669 added.extend(system_entries("DFHEIBLK")?);
670 }
671 if !defined(linkage, "DFHCOMMAREA") {
672 added.extend(system_text_entries(" 01 DFHCOMMAREA PIC X(1).\n")?);
673 }
674 linkage.splice(0..0, added);
675 for (i, name) in ["DFHEIBLK", "DFHCOMMAREA"].into_iter().enumerate() {
676 if using.get(i).map(|p| p.name.as_str()) != Some(name) {
677 using.insert(i, Param { by_value: false, name: name.into() });
678 }
679 }
680 Ok(())
681 }
682
683 fn exec_block(&mut self, text: &str, pos: Pos) -> ExecBlock {
684 let (kind_word, body) = text.split_once(' ').unwrap_or((text, ""));
685 let kind = match kind_word.to_ascii_uppercase().as_str() {
686 "SQL" => ExecKind::Sql,
687 "CICS" => ExecKind::Cics,
688 "DLI" => ExecKind::Dli,
689 _ => ExecKind::Other,
690 };
691 let words: Vec<String> = body.split_whitespace().map(|w| w.to_ascii_uppercase()).collect();
692 let word = |i: usize| words.get(i).map(String::as_str).unwrap_or("");
693 let mut block = ExecBlock { kind, command: word(0).to_owned(), options: Vec::new(), host_variables: Vec::new(), sql: None, text: text.to_owned(), pos };
694 match kind {
695 ExecKind::Sql => {
696 block.command = match (word(0), word(1), word(2)) {
697 ("DECLARE", _, "CURSOR") => "DECLARE CURSOR".into(),
698 ("DECLARE", _, "TABLE") => "DECLARE TABLE".into(),
699 ("DECLARE", _, "STATEMENT") => "DECLARE STATEMENT".into(),
700 ("BEGIN" | "END", "DECLARE", "SECTION") => format!("{} DECLARE SECTION", word(0)),
701 (first, _, _) => first.into(),
702 };
703 let statement = self.sql.cursors.resolve(crate::sql::parse(body, pos));
704 if let crate::sql::Statement::Whenever { condition, action } = &statement {
705 self.sql.whenever.set(*condition, action.clone());
706 }
707 block.host_variables = match &statement {
708 crate::sql::Statement::Unsupported(_)
709 | crate::sql::Statement::Malformed(_)
710 | crate::sql::Statement::Declaration
711 | crate::sql::Statement::DeclareUnsupported { .. } => host_variables(body, pos),
712 typed => typed.references().into_iter().cloned().collect(),
713 };
714 self.sql.blocks += 1;
715 block.sql = Some(crate::sql::Sql { statement, ordinal: self.sql.blocks, whenever: self.sql.whenever.clone() });
716 }
717 ExecKind::Cics => {
718 self.cics = true;
719 block.options = cics_options(body);
720 if let Some((first, None)) = block.options.first().cloned() {
721 block.command = first.clone();
722 block.options.remove(0);
723 if let Some((second, None)) = block.options.first().cloned()
724 && crate::system::cics_two_word(&first, &second)
725 {
726 block.command = format!("{first} {second}");
727 block.options.remove(0);
728 }
729 }
730 let labels = block.command.starts_with("HANDLE");
731 for (_, arg) in &mut block.options {
732 if let Some(ExecArg::Text(t)) = arg
733 && !labels
734 && let Some(op) = operand_of(t, pos)
735 {
736 *arg = Some(ExecArg::Operand(op));
737 }
738 }
739 }
740 _ => {}
741 }
742 block
743 }
744
745 fn data_entries(&mut self) -> R<Vec<DataEntry>> {
746 let mut entries = Vec::new();
747 loop {
748 if self.data_exec()? {
749 continue;
750 }
751 let Some((level, pos)) = self.level_number()? else { break };
752 entries.push(self.data_entry(level, pos)?);
753 }
754 Ok(entries)
755 }
756
757 fn level_number(&mut self) -> R<Option<(u8, Pos)>> {
758 let Some(Tok::Number(level)) = self.peek() else { return Ok(None) };
759 let pos = self.pos();
760 let level = level.parse().map_err(|_| self.error("a level number"))?;
761 self.at += 1;
762 Ok(Some((level, pos)))
763 }
764
765 fn picture(&mut self) -> R<String> {
767 self.accept_word("IS");
768 match self.peek() {
769 Some(Tok::Pic(p)) => {
770 let p = p.clone();
771 self.at += 1;
772 Ok(p)
773 }
774 _ => Err(self.error("a PICTURE character-string")),
775 }
776 }
777
778 fn sign_clause(&mut self, first: &str) -> R<SignClause> {
780 let side = if first == "SIGN" {
781 self.accept_word("IS");
782 self.name("LEADING or TRAILING")?
783 } else {
784 first.to_owned()
785 };
786 let position = match side.as_str() {
787 "LEADING" => SignPosition::Leading,
788 "TRAILING" => SignPosition::Trailing,
789 _ => return Err(self.error("LEADING or TRAILING")),
790 };
791 let separate = self.accept_word("SEPARATE");
792 if separate {
793 self.accept_word("CHARACTER");
794 }
795 Ok(SignClause { position, separate })
796 }
797
798 fn blank_when_zero(&mut self) -> R<()> {
800 self.accept_word("WHEN");
801 if self.accept_any(&["ZERO", "ZEROS", "ZEROES"]).is_none() {
802 return Err(self.error("ZERO after BLANK WHEN"));
803 }
804 Ok(())
805 }
806
807 fn data_entry(&mut self, level: u8, pos: Pos) -> R<DataEntry> {
808 let mut e = DataEntry {
809 level,
810 name: None,
811 picture: None,
812 usage: None,
813 value: None,
814 redefines: None,
815 occurs: None,
816 depending_on: None,
817 sign: None,
818 justified: false,
819 sync: false,
820 blank_when_zero: false,
821 indexed_by: Vec::new(),
822 keys: Vec::new(),
823 condition_values: Vec::new(),
824 false_value: None,
825 renames: None,
826 object_class: None,
827 pos,
828 };
829 if let Some(w) = self.word()
830 && !is_clause_word(w)
831 {
832 if w != "FILLER" {
833 e.name = Some(w.to_owned());
834 }
835 self.at += 1;
836 }
837 while !self.accept(&Tok::Period) {
838 let clause = self.name("a data description clause or a period")?;
839 match clause.as_str() {
840 "PIC" | "PICTURE" => e.picture = Some(self.picture()?),
841 "USAGE" => {
842 self.accept_word("IS");
843 let w = self.name("a usage")?;
844 if w == "OBJECT" {
845 self.object_reference(&mut e)?;
846 } else {
847 e.usage = Some(usage_word(&w).ok_or_else(|| Error::at(pos, format!("USAGE {w} is not supported yet")))?);
848 }
849 }
850 "OBJECT" => self.object_reference(&mut e)?,
851 "VALUE" | "VALUES" => {
852 self.accept_word("IS");
853 self.accept_word("ARE");
854 if level == 88 {
855 while self.peek().is_some() && self.peek() != Some(&Tok::Period) && !self.is_word("WHEN") && !self.is_word("FALSE") {
856 let low = self.literal()?;
857 let high = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.literal()?) } else { None };
858 e.condition_values.push((low, high));
859 }
860 if self.accept_word("WHEN") {
861 self.accept_word("SET");
862 self.accept_word("TO");
863 self.expect_word("FALSE")?;
864 self.accept_word("IS");
865 e.false_value = Some(self.literal()?);
866 } else if self.accept_word("FALSE") {
867 self.accept_word("IS");
868 e.false_value = Some(self.literal()?);
869 }
870 } else {
871 e.value = Some(self.literal()?);
872 }
873 }
874 "REDEFINES" => e.redefines = Some(self.name("the item redefined")?),
875 "RENAMES" => {
876 let first = self.reference()?;
877 let last = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.reference()?) } else { None };
878 e.renames = Some((first, last));
879 }
880 "OCCURS" => {
881 let count = |p: &mut Self| -> R<u32> {
882 let n = match p.peek() {
883 Some(Tok::Number(n)) => n.parse().map_err(|_| p.error("an OCCURS count"))?,
884 _ => return Err(p.error("an OCCURS count")),
885 };
886 p.at += 1;
887 Ok(n)
888 };
889 let mut most = count(self)?;
890 if self.accept_word("TO") {
891 most = count(self)?;
892 }
893 e.occurs = Some(most);
894 self.accept_word("TIMES");
895 if self.accept_word("DEPENDING") {
896 self.accept_word("ON");
897 e.depending_on = Some(self.reference()?);
898 }
899 loop {
900 if let Some(order) = self.accept_any(&["ASCENDING", "DESCENDING"]) {
901 self.accept_word("KEY");
902 self.accept_word("IS");
903 while self.word().is_some_and(|w| !is_clause_word(w) && !matches!(w, "INDEXED" | "ASCENDING" | "DESCENDING")) {
904 e.keys.push((order == "ASCENDING", self.reference()?));
905 }
906 } else if self.accept_word("INDEXED") {
907 self.accept_word("BY");
908 while self.word().is_some_and(|w| !is_clause_word(w) && !matches!(w, "ASCENDING" | "DESCENDING")) {
909 e.indexed_by.push(self.name("an index name")?);
910 }
911 } else {
912 break;
913 }
914 }
915 }
916 "SIGN" | "LEADING" | "TRAILING" => e.sign = Some(self.sign_clause(&clause)?),
917 "JUSTIFIED" | "JUST" => {
918 self.accept_word("RIGHT");
919 e.justified = true;
920 }
921 "SYNC" | "SYNCHRONIZED" => {
922 self.accept_any(&["LEFT", "RIGHT"]);
923 e.sync = true;
924 }
925 "BLANK" => {
926 self.blank_when_zero()?;
927 e.blank_when_zero = true;
928 }
929 "GLOBAL" | "EXTERNAL" => {}
930 other => match usage_word(other) {
931 Some(u) => e.usage = Some(u),
932 None => return Err(Error::at(self.tokens[self.at - 1].pos, format!("{other} is not a data description clause ironwork for COBOL supports yet"))),
933 },
934 }
935 }
936 Ok(e)
937 }
938
939 fn literal(&mut self) -> R<Literal> {
940 let lit = match self.peek().cloned() {
941 Some(Tok::Alnum(s)) => Literal::Alnum(s),
942 Some(Tok::Hex(b)) => Literal::Hex(b),
943 Some(Tok::National(s)) => Literal::National(s),
944 Some(Tok::Number(n)) => Literal::Number(n),
945 Some(Tok::Word(w)) if w == "ALL" => {
946 self.at += 1;
947 return Ok(Literal::All(Box::new(self.literal()?)));
948 }
949 Some(Tok::Word(w)) => Literal::Figurative(figurative(&w).ok_or_else(|| self.error("a literal"))?),
950 _ => return Err(self.error("a literal")),
951 };
952 self.at += 1;
953 Ok(lit)
954 }
955
956 fn paragraph_header(&self) -> bool {
957 self.tokens.get(self.at).is_some_and(|t| t.area_a && matches!(t.tok, Tok::Word(_))) && self.peek_at(1) == Some(&Tok::Period)
958 }
959
960 fn section_header(&self) -> bool {
961 self.tokens.get(self.at).is_some_and(|t| matches!(t.tok, Tok::Word(_))) && self.word_at(0) != Some("EXIT") && self.word_at(1) == Some("SECTION")
963 }
964
965 fn paragraphs(&mut self) -> R<Vec<Paragraph>> {
966 let mut paragraphs = Vec::new();
967 while self.peek().is_some() && !self.at_end_program() && !self.at_division(&["IDENTIFICATION", "ID"]) {
968 if self.is_word("DECLARATIVES") {
969 return Err(self.error("DECLARATIVES must begin the PROCEDURE DIVISION"));
970 }
971 self.procedure_item(&mut paragraphs)?;
972 }
973 Ok(paragraphs)
974 }
975
976 fn procedure_item(&mut self, paragraphs: &mut Vec<Paragraph>) -> R<bool> {
978 if self.section_header() {
979 let pos = self.pos();
980 let name = self.name("a section name")?;
981 self.at += 1;
982 let mut priority = 0;
983 if let Some(Tok::Number(n)) = self.peek() {
984 priority = n.trim_start_matches('+').parse().ok().filter(|p| *p <= 99).ok_or_else(|| self.error("a priority-number from 0 to 99"))?;
985 self.at += 1;
986 }
987 self.expect(&Tok::Period, "a period after the section header")?;
988 paragraphs.push(Paragraph { section: Some(name.clone()), name, statements: Vec::new(), is_section: true, priority, pos });
989 return Ok(true);
990 }
991 if self.paragraph_header() {
992 let pos = self.pos();
993 let name = self.name("a paragraph name")?;
994 self.at += 1;
995 let (section, priority) = paragraphs.last().map_or((None, 0), |p| (p.section.clone(), p.priority));
996 paragraphs.push(Paragraph { name, statements: Vec::new(), section, is_section: false, priority, pos });
997 return Ok(false);
998 }
999 if self.accept(&Tok::Period) {
1000 if let Some(p) = paragraphs.last_mut()
1001 && p.statements.last().is_some_and(|s| *s != Stmt::SentenceEnd)
1002 {
1003 p.statements.push(Stmt::SentenceEnd);
1004 }
1005 return Ok(false);
1006 }
1007 let block = self.block(&[])?;
1008 if block.is_empty() {
1009 return Err(self.error("a statement"));
1010 }
1011 if paragraphs.is_empty() {
1012 paragraphs.push(Paragraph { name: String::new(), statements: Vec::new(), section: None, is_section: false, priority: 0, pos: self.pos() });
1013 }
1014 paragraphs.last_mut().unwrap().statements.extend(block);
1015 Ok(false)
1016 }
1017
1018 fn block(&mut self, stops: &[&str]) -> R<Vec<Stmt>> {
1020 let mut out = Vec::new();
1021 while let Some(tok) = self.peek() {
1022 if *tok == Tok::Period || self.paragraph_header() || self.section_header() || self.word().is_some_and(|w| stops.contains(&w)) {
1023 break;
1024 }
1025 if let Some(Tok::Exec(text)) = self.peek().cloned() {
1026 let block = self.exec_block(&text, self.pos());
1027 self.at += 1;
1028 out.push(Stmt::Exec(Box::new(block)));
1029 continue;
1030 }
1031 if !self.word().is_some_and(|w| VERBS.contains(&w)) {
1032 break;
1033 }
1034 out.push(self.statement()?);
1035 }
1036 Ok(out)
1037 }
1038
1039 fn statement(&mut self) -> R<Stmt> {
1040 let pos = self.pos();
1041 let verb = self.name("a statement")?;
1042 Ok(match verb.as_str() {
1043 "MOVE" => {
1044 if self.accept_any(&["CORRESPONDING", "CORR"]).is_some() {
1045 return Err(Error::at(pos, "MOVE CORRESPONDING is not supported yet"));
1046 }
1047 let from = self.operand()?;
1048 self.expect_word("TO")?;
1049 let to = self.refs()?;
1050 Stmt::Move { from, to, pos }
1051 }
1052 "COMPUTE" => {
1053 let targets = self.targets()?;
1054 if !self.accept(&Tok::Eq) {
1055 self.expect_word("EQUAL")?;
1056 }
1057 let expr = self.expr()?;
1058 let size_error = self.size_error()?;
1059 self.accept_word("END-COMPUTE");
1060 Stmt::Compute { targets, expr, size_error, pos }
1061 }
1062 "ADD" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" => Stmt::Arith(Box::new(self.arith(&verb, pos)?)),
1063 "IF" => {
1064 let cond = self.cond()?;
1065 self.accept_word("THEN");
1066 let then = self.block(&["ELSE", "END-IF"])?;
1067 let otherwise = if self.accept_word("ELSE") { self.block(&["END-IF"])? } else { Vec::new() };
1068 self.accept_word("END-IF");
1069 Stmt::If { cond, then, otherwise, pos }
1070 }
1071 "PERFORM" => self.perform(pos)?,
1072 "DISPLAY" => {
1073 let mut items = Vec::new();
1074 let no_advancing_ahead = |p: &Self| p.is_word("NO") && p.word_at(1) == Some("ADVANCING");
1075 while self.starts_operand() && !no_advancing_ahead(self) {
1076 items.push(self.operand()?);
1077 }
1078 if self.accept_word("UPON") {
1079 self.name("a mnemonic name")?;
1080 }
1081 let no_advancing = self.accept_word("WITH") | no_advancing_ahead(self);
1082 if no_advancing {
1083 self.expect_word("NO")?;
1084 self.expect_word("ADVANCING")?;
1085 if self.is_word("UPON") {
1086 return Err(self.error("the end of DISPLAY: Enterprise COBOL takes UPON before WITH NO ADVANCING"));
1087 }
1088 }
1089 self.accept_word("END-DISPLAY");
1090 Stmt::Display { items, no_advancing, pos }
1091 }
1092 "INITIALIZE" => Stmt::Initialize { targets: self.refs()?, pos },
1093 "CALL" => Stmt::Call(Box::new(self.call(pos)?)),
1094 "INVOKE" => Stmt::Invoke(Box::new(self.invoke(pos)?)),
1095 "CANCEL" => {
1096 let mut targets = Vec::new();
1097 while self.starts_operand() {
1098 targets.push(self.operand()?);
1099 }
1100 if targets.is_empty() {
1101 return Err(self.error("a program to CANCEL"));
1102 }
1103 Stmt::Cancel { targets, pos }
1104 }
1105 "SET" => Stmt::Set { set: self.set()?, pos },
1106 "STRING" => Stmt::String(Box::new(self.string(pos)?)),
1107 "UNSTRING" => Stmt::Unstring(Box::new(self.unstring(pos)?)),
1108 "INSPECT" => Stmt::Inspect(Box::new(self.inspect(pos)?)),
1109 "SEARCH" => Stmt::Search(Box::new(self.search(pos)?)),
1110 "SORT" | "MERGE" | "RELEASE" | "RETURN" => Stmt::Sorting(Box::new(self.sorting(&verb, pos)?)),
1111 "NEXT" => {
1112 self.expect_word("SENTENCE")?;
1113 Stmt::NextSentence
1114 }
1115 "ACCEPT" => {
1116 let target = self.reference()?;
1117 if self.is_word("FROM") && self.word_at(1) == Some("ENVIRONMENT") {
1118 return Err(Error::at(pos, "ACCEPT ... FROM ENVIRONMENT is GnuCOBOL's, not Enterprise COBOL's"));
1119 }
1120 let from = if self.accept_word("FROM") {
1121 match self.name("SYSIN, DATE, DAY, DAY-OF-WEEK or TIME")?.as_str() {
1122 "DATE" => AcceptFrom::Date { four_digit_year: self.accept_word("YYYYMMDD") },
1123 "DAY" => AcceptFrom::Day { four_digit_year: self.accept_word("YYYYDDD") },
1124 "DAY-OF-WEEK" => AcceptFrom::DayOfWeek,
1125 "TIME" => AcceptFrom::Time,
1126 _ => AcceptFrom::Sysin,
1127 }
1128 } else {
1129 AcceptFrom::Sysin
1130 };
1131 self.accept_word("END-ACCEPT");
1132 Stmt::Accept { target, from, pos }
1133 }
1134 "OPEN" => {
1135 let mut files = Vec::new();
1136 while let Some(mode) = self.accept_any(&["INPUT", "OUTPUT", "EXTEND", "I-O"]) {
1137 let mode = match mode.as_str() {
1138 "INPUT" => OpenMode::Input,
1139 "OUTPUT" => OpenMode::Output,
1140 "EXTEND" => OpenMode::Extend,
1141 _ => OpenMode::InputOutput,
1142 };
1143 while self.starts_ref() {
1144 files.push((mode, self.name("a file name")?));
1145 self.accept_any(&["REVERSED"]);
1146 if self.accept_word("WITH") {
1147 self.expect_word("NO")?;
1148 self.expect_word("REWIND")?;
1149 }
1150 }
1151 }
1152 if files.is_empty() {
1153 return Err(self.error("INPUT, OUTPUT, EXTEND or I-O and a file"));
1154 }
1155 Stmt::Open { files, pos }
1156 }
1157 "CLOSE" => {
1158 let mut files = Vec::new();
1159 while self.starts_ref() {
1160 files.push(self.name("a file name")?);
1161 if self.accept_word("WITH") {
1162 self.accept_any(&["LOCK", "NO"]);
1163 self.accept_word("REWIND");
1164 }
1165 }
1166 Stmt::Close { files, pos }
1167 }
1168 "READ" => {
1169 let file = self.name("a file name")?;
1170 let previous = self.accept_word("PREVIOUS");
1171 let next = previous || self.accept_word("NEXT");
1172 self.accept_word("RECORD");
1173 let into = if self.accept_word("INTO") { Some(self.reference()?) } else { None };
1174 let key = if self.accept_word("KEY") {
1175 self.accept_word("IS");
1176 Some(self.reference()?)
1177 } else {
1178 None
1179 };
1180 let [at_end, invalid] = self.on_phrases(&["AT", "END", "INVALID"], &["END-READ"], |p| {
1181 if p.accept_word("INVALID") {
1182 p.accept_word("KEY");
1183 return Ok(1);
1184 }
1185 p.accept_word("AT");
1186 p.expect_word("END").map(|()| 0)
1187 })?;
1188 self.accept_word("END-READ");
1189 Stmt::Read(Box::new(ReadStmt { file, next, previous, into, key, at_end, invalid, pos }))
1190 }
1191 "REWRITE" => {
1192 let record = self.reference()?;
1193 let from = if self.accept_word("FROM") { Some(self.operand()?) } else { None };
1194 let invalid = self.invalid_key("END-REWRITE")?;
1195 Stmt::Rewrite { record, from, invalid, pos }
1196 }
1197 "DELETE" => {
1198 let file = self.name("a file name")?;
1199 self.accept_word("RECORD");
1200 let invalid = self.invalid_key("END-DELETE")?;
1201 Stmt::Delete { file, invalid, pos }
1202 }
1203 "START" => {
1204 let file = self.name("a file name")?;
1205 let key = if self.accept_word("KEY") {
1206 self.accept_word("IS");
1207 let op = if self.accept_word("NOT") {
1208 match self.relop()? {
1209 Some(RelOp::Lt) => RelOp::Ge,
1210 _ => return Err(self.error("NOT < in START KEY")),
1211 }
1212 } else {
1213 self.relop()?.ok_or_else(|| self.error("a relation after START KEY"))?
1214 };
1215 Some((op, self.reference()?))
1216 } else {
1217 None
1218 };
1219 let invalid = self.invalid_key("END-START")?;
1220 Stmt::Start { file, key, invalid, pos }
1221 }
1222 "WRITE" => {
1223 let record = self.reference()?;
1224 let from = if self.accept_word("FROM") { Some(self.operand()?) } else { None };
1225 let mut advancing = None;
1226 if let Some(side) = self.accept_any(&["BEFORE", "AFTER"]) {
1227 let before = side == "BEFORE";
1228 self.accept_word("ADVANCING");
1229 let mnemonic = self.word().and_then(|w| self.mnemonics.iter().find(|(name, _)| name == w)).cloned();
1230 advancing = Some(if self.accept_word("PAGE") {
1231 Advancing::Page { before }
1232 } else if let Some((name, environment)) = mnemonic {
1233 self.at += 1;
1234 Advancing::Mnemonic { before, name, environment }
1235 } else {
1236 let count = self.expr()?;
1237 self.accept_any(&["LINE", "LINES"]);
1238 Advancing::Lines { before, count }
1239 });
1240 }
1241 let eop_ahead = |p: &Self, i: usize| {
1242 let at = usize::from(p.word_at(i) == Some("AT"));
1243 matches!(p.word_at(i + at), Some("END-OF-PAGE" | "EOP"))
1244 };
1245 let [end_of_page, invalid] = self.phrases_opening(|p, i| eop_ahead(p, i) || p.word_at(i) == Some("INVALID"), &["END-WRITE"], |p| {
1246 if p.accept_word("INVALID") {
1247 p.accept_word("KEY");
1248 return Ok(1);
1249 }
1250 p.accept_word("AT");
1251 p.at += 1;
1252 Ok(0)
1253 })?;
1254 self.accept_word("END-WRITE");
1255 Stmt::Write { record, from, advancing, invalid, end_of_page, pos }
1256 }
1257 "GO" => {
1258 self.accept_word("TO");
1259 if self.peek() == Some(&Tok::Period) {
1260 return Ok(Stmt::GoTo { target: None, pos });
1261 }
1262 let target = self.proc_name()?;
1263 if !self.starts_ref() && !self.is_word("DEPENDING") {
1264 return Ok(Stmt::GoTo { target: Some(target), pos });
1265 }
1266 let mut targets = vec![target];
1267 while self.starts_ref() && !self.is_word("DEPENDING") {
1268 targets.push(self.proc_name()?);
1269 }
1270 self.expect_word("DEPENDING")?;
1271 self.accept_word("ON");
1272 Stmt::GoToDepending { targets, on: self.reference()?, pos }
1273 }
1274 "ALTER" => {
1275 let mut pairs = Vec::new();
1276 loop {
1277 let paragraph = self.proc_name()?;
1278 self.expect_word("TO")?;
1279 if self.accept_word("PROCEED") {
1280 self.expect_word("TO")?;
1281 }
1282 pairs.push((paragraph, self.proc_name()?));
1283 if !self.starts_ref() {
1284 break;
1285 }
1286 }
1287 Stmt::Alter { pairs, pos }
1288 }
1289 "ENTRY" => {
1290 let Some(Tok::Alnum(name)) = self.peek().cloned() else {
1291 return Err(self.error("an alphanumeric literal naming the entry point"));
1292 };
1293 self.at += 1;
1294 let using = if self.accept_word("USING") { self.parameters()? } else { Vec::new() };
1295 Stmt::Entry { name: name.to_ascii_uppercase(), using, pos }
1296 }
1297 "EVALUATE" => self.evaluate(pos)?,
1298 "INITIATE" | "GENERATE" | "TERMINATE" | "SUPPRESS" => Stmt::Report(Box::new(self.report_statement(&verb, pos)?)),
1299 "GOBACK" => Stmt::Goback { pos },
1300 "STOP" => {
1301 self.expect_word("RUN")?;
1302 Stmt::StopRun { pos }
1303 }
1304 "CONTINUE" => Stmt::Continue,
1305 "EXIT" => match self.accept_any(&["PROGRAM", "PARAGRAPH", "SECTION", "PERFORM", "METHOD"]).as_deref() {
1306 Some("PROGRAM") => Stmt::ExitProgram { pos },
1307 Some("METHOD") => Stmt::ExitMethod { pos },
1308 Some("PARAGRAPH") => Stmt::Exit { kind: ExitKind::Paragraph, pos },
1309 Some("SECTION") => Stmt::Exit { kind: ExitKind::Section, pos },
1310 Some(_) if self.accept_word("CYCLE") => Stmt::Exit { kind: ExitKind::PerformCycle, pos },
1311 Some(_) => Stmt::Exit { kind: ExitKind::Perform, pos },
1312 None => Stmt::Exit { kind: ExitKind::Plain, pos },
1313 },
1314 other => return Err(Error::at(pos, format!("{other} is not a statement ironwork for COBOL supports yet"))),
1315 })
1316 }
1317
1318 fn size_error(&mut self) -> R<Option<SizeError>> {
1319 let [h] = self.on_phrases(&["ON", "SIZE"], &["END-COMPUTE", "END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE"], |p| {
1320 p.accept_word("ON");
1321 p.expect_word("SIZE")?;
1322 p.expect_word("ERROR").map(|()| 0)
1323 })?;
1324 Ok((h.on.is_some() || h.not_on.is_some()).then(|| SizeError { on: h.on.unwrap_or_default(), not_on: h.not_on.unwrap_or_default() }))
1325 }
1326
1327 fn targets(&mut self) -> R<Vec<Target>> {
1328 let mut out = Vec::new();
1329 while self.starts_ref() {
1330 let r = self.reference()?;
1331 out.push(Target { r, rounded: self.accept_word("ROUNDED") });
1332 }
1333 if out.is_empty() {
1334 return Err(self.error("a receiving item"));
1335 }
1336 Ok(out)
1337 }
1338
1339 fn refs(&mut self) -> R<Vec<Ref>> {
1340 let mut out = Vec::new();
1341 while self.starts_ref() {
1342 out.push(self.reference()?);
1343 }
1344 if out.is_empty() {
1345 return Err(self.error("a data name"));
1346 }
1347 Ok(out)
1348 }
1349
1350 fn operands_until(&mut self, stops: &[&str]) -> R<Vec<Expr>> {
1351 let mut out = Vec::new();
1352 while self.starts_operand() && !self.word().is_some_and(|w| stops.contains(&w)) {
1353 out.push(Expr::Operand(self.operand()?));
1354 }
1355 if out.is_empty() {
1356 return Err(self.error("an operand"));
1357 }
1358 Ok(out)
1359 }
1360
1361 fn arith(&mut self, verb: &str, pos: Pos) -> R<Arith> {
1362 let sum = |mut es: Vec<Expr>| {
1363 let first = es.remove(0);
1364 es.into_iter().fold(first, |acc, e| Expr::Bin(Box::new(acc), BinOp::Add, Box::new(e)))
1365 };
1366 let of = |t: &Target| Expr::Operand(Operand::Ref(t.r.clone()));
1367 let bin = |a: Expr, op: BinOp, b: Expr| Expr::Bin(Box::new(a), op, Box::new(b));
1368 let mut remainder = None;
1369 let (verb, computations) = match verb {
1370 "ADD" => {
1371 let addends = sum(self.operands_until(&["TO", "GIVING"])?);
1372 if self.accept_word("TO") {
1373 if self.words_ahead_include("GIVING") {
1374 let to = sum(self.operands_until(&["GIVING"])?);
1375 self.expect_word("GIVING")?;
1376 let targets = self.targets()?;
1377 let total = bin(addends, BinOp::Add, to);
1378 (ArithVerb::Add, targets.into_iter().map(|t| (t, total.clone())).collect())
1379 } else {
1380 let targets = self.targets()?;
1381 (ArithVerb::Add, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Add, addends.clone()))).collect())
1382 }
1383 } else {
1384 self.expect_word("GIVING")?;
1385 let targets = self.targets()?;
1386 (ArithVerb::Add, targets.into_iter().map(|t| (t, addends.clone())).collect())
1387 }
1388 }
1389 "SUBTRACT" => {
1390 let subtrahend = sum(self.operands_until(&["FROM"])?);
1391 self.expect_word("FROM")?;
1392 if self.words_ahead_include("GIVING") {
1393 let minuend = sum(self.operands_until(&["GIVING"])?);
1394 self.expect_word("GIVING")?;
1395 let targets = self.targets()?;
1396 let diff = bin(minuend, BinOp::Sub, subtrahend);
1397 (ArithVerb::Subtract, targets.into_iter().map(|t| (t, diff.clone())).collect())
1398 } else {
1399 let targets = self.targets()?;
1400 (ArithVerb::Subtract, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Sub, subtrahend.clone()))).collect())
1401 }
1402 }
1403 "MULTIPLY" => {
1404 let a = Expr::Operand(self.operand()?);
1405 self.expect_word("BY")?;
1406 if self.words_ahead_include("GIVING") {
1407 let b = Expr::Operand(self.operand()?);
1408 self.expect_word("GIVING")?;
1409 let targets = self.targets()?;
1410 (ArithVerb::Multiply, targets.into_iter().map(|t| (t, bin(a.clone(), BinOp::Mul, b.clone()))).collect())
1411 } else {
1412 let targets = self.targets()?;
1413 (ArithVerb::Multiply, targets.into_iter().map(|t| (t.clone(), bin(a.clone(), BinOp::Mul, of(&t)))).collect())
1414 }
1415 }
1416 _ => {
1417 let first = Expr::Operand(self.operand()?);
1418 let into = match self.accept_any(&["INTO", "BY"]).as_deref() {
1419 Some("INTO") => true,
1420 Some(_) => false,
1421 None => return Err(self.error("INTO or BY")),
1422 };
1423 if !into || self.words_ahead_include("GIVING") {
1424 let second = Expr::Operand(self.operand()?);
1425 let (dividend, divisor) = if into { (second, first) } else { (first, second) };
1426 self.expect_word("GIVING")?;
1427 let targets = self.targets()?;
1428 if self.accept_word("REMAINDER") {
1429 let r = self.reference()?;
1430 remainder = Some((Target { r, rounded: false }, dividend.clone(), divisor.clone()));
1431 }
1432 (ArithVerb::Divide, targets.into_iter().map(|t| (t, bin(dividend.clone(), BinOp::Div, divisor.clone()))).collect())
1433 } else {
1434 let targets = self.targets()?;
1435 (ArithVerb::Divide, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Div, first.clone()))).collect())
1436 }
1437 }
1438 };
1439 let size_error = self.size_error()?;
1440 self.accept_any(&["END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE"]);
1441 Ok(Arith { verb, computations, remainder, size_error, pos })
1442 }
1443
1444 fn words_ahead_include(&self, word: &str) -> bool {
1446 self.tokens[self.at..]
1447 .iter()
1448 .take_while(|t| t.tok != Tok::Period && !matches!(&t.tok, Tok::Word(w) if VERBS.contains(&w.as_str()) && w != word))
1449 .any(|t| matches!(&t.tok, Tok::Word(w) if w == word))
1450 }
1451
1452 fn perform(&mut self, pos: Pos) -> R<Stmt> {
1453 let named = self.word().is_some_and(|w| !VERBS.contains(&w) && !PHRASE_WORDS.contains(&w))
1454 && self.word_at(1) != Some("TIMES")
1455 && !matches!(self.peek(), Some(Tok::Number(_)));
1456 if named {
1457 let from = self.proc_name()?;
1458 let thru = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.proc_name()?) } else { None };
1459 let repeat = self.repeat()?;
1460 return Ok(Stmt::PerformProc { from, thru, repeat, pos });
1461 }
1462 let repeat = self.repeat()?;
1463 if matches!(&repeat, Loop::Varying { after, .. } if !after.is_empty()) {
1464 return Err(Error::at(pos, "an inline PERFORM cannot have AFTER phrases: Enterprise COBOL takes them only when PERFORM names a procedure"));
1465 }
1466 let body = self.block(&["END-PERFORM"])?;
1467 self.expect_word("END-PERFORM")?;
1468 Ok(Stmt::PerformInline { body, repeat, pos })
1469 }
1470
1471 fn on_phrases<const N: usize>(&mut self, starts: &[&str], ends: &[&str], head: impl Fn(&mut Self) -> R<usize>) -> R<[Handlers; N]> {
1473 self.phrases_opening(|p, i| p.word_at(i).is_some_and(|w| starts.contains(&w)), ends, head)
1474 }
1475
1476 fn phrases_opening<const N: usize>(&mut self, opens: impl Fn(&Self, usize) -> bool, ends: &[&str], head: impl Fn(&mut Self) -> R<usize>) -> R<[Handlers; N]> {
1478 let stops = [&["NOT"][..], ends].concat();
1479 let mut handlers = std::array::from_fn(|_| Handlers::default());
1480 loop {
1481 let negated = self.is_word("NOT") && opens(self, 1);
1482 if !negated && !opens(self, 0) {
1483 return Ok(handlers);
1484 }
1485 self.at += usize::from(negated);
1486 let h = &mut handlers[head(self)?];
1487 let body = self.block(&stops)?;
1488 if negated { h.not_on = Some(body) } else { h.on = Some(body) }
1489 }
1490 }
1491
1492 fn invalid_key(&mut self, end: &str) -> R<Handlers> {
1494 let [h] = self.on_phrases(&["INVALID"], &[end], |p| {
1495 p.at += 1;
1496 p.accept_word("KEY");
1497 Ok(0)
1498 })?;
1499 self.accept_word(end);
1500 Ok(h)
1501 }
1502
1503 fn call(&mut self, pos: Pos) -> R<Call> {
1504 let target = self.operand()?;
1505 let mut using = Vec::new();
1506 if self.accept_word("USING") {
1507 let mut mode = ArgMode::Reference;
1508 loop {
1509 if self.accept_word("BY") {
1510 mode = match self.accept_any(&["REFERENCE", "CONTENT", "VALUE"]).as_deref() {
1511 Some("CONTENT") => ArgMode::Content,
1512 Some("VALUE") => ArgMode::Value,
1513 Some(_) => ArgMode::Reference,
1514 None => return Err(self.error("REFERENCE, CONTENT or VALUE after BY")),
1515 };
1516 } else if self.accept_word("OMITTED") {
1517 using.push(Arg { mode, value: None });
1518 } else if self.starts_operand() {
1519 using.push(Arg { mode, value: Some(self.operand()?) });
1520 } else {
1521 break;
1522 }
1523 }
1524 }
1525 let returning = if self.accept_word("RETURNING") { Some(self.reference()?) } else { None };
1526 let [exception] = self.on_phrases(&["ON", "EXCEPTION", "OVERFLOW"], &["END-CALL"], |p| {
1527 p.accept_word("ON");
1528 p.accept_any(&["EXCEPTION", "OVERFLOW"]).map(|_| 0).ok_or_else(|| p.error("EXCEPTION or OVERFLOW"))
1529 })?;
1530 self.accept_word("END-CALL");
1531 Ok(Call { target, using, returning, on_exception: exception.on, not_on_exception: exception.not_on, pos })
1532 }
1533
1534 fn overflow(&mut self, end: &str) -> R<Handlers> {
1536 let [h] = self.on_phrases(&["ON", "OVERFLOW"], &[end], |p| {
1537 p.accept_word("ON");
1538 p.expect_word("OVERFLOW").map(|()| 0)
1539 })?;
1540 self.accept_word(end);
1541 Ok(h)
1542 }
1543
1544 fn string(&mut self, pos: Pos) -> R<StringStmt> {
1545 let mut sources = Vec::new();
1546 while !self.is_word("INTO") {
1547 let mut group = Vec::new();
1548 while self.starts_operand() && !self.is_word("DELIMITED") {
1549 group.push(self.operand()?);
1550 }
1551 if group.is_empty() {
1552 return Err(self.error("a sending item"));
1553 }
1554 let delimiter = if self.accept_word("DELIMITED") {
1555 self.accept_word("BY");
1556 if self.accept_word("SIZE") { Delimiter::Size } else { Delimiter::By(self.operand()?) }
1557 } else {
1558 Delimiter::Size
1559 };
1560 sources.extend(group.into_iter().map(|op| (op, delimiter.clone())));
1561 }
1562 self.expect_word("INTO")?;
1563 let into = self.reference()?;
1564 let pointer = if self.accept_word("WITH") || self.is_word("POINTER") {
1565 self.expect_word("POINTER")?;
1566 Some(self.reference()?)
1567 } else {
1568 None
1569 };
1570 let Handlers { on: on_overflow, not_on: not_on_overflow } = self.overflow("END-STRING")?;
1571 Ok(StringStmt { sources, into, pointer, on_overflow, not_on_overflow, pos })
1572 }
1573
1574 fn unstring(&mut self, pos: Pos) -> R<Unstring> {
1575 let source = self.reference()?;
1576 let mut delimiters = Vec::new();
1577 if self.accept_word("DELIMITED") {
1578 self.accept_word("BY");
1579 loop {
1580 let all = self.accept_word("ALL");
1581 delimiters.push((all, self.operand()?));
1582 if !self.accept_word("OR") {
1583 break;
1584 }
1585 }
1586 }
1587 self.expect_word("INTO")?;
1588 let mut into = Vec::new();
1589 while self.starts_ref() {
1590 let target = self.reference()?;
1591 let delimiter_in = if self.accept_word("DELIMITER") {
1592 self.accept_word("IN");
1593 Some(self.reference()?)
1594 } else {
1595 None
1596 };
1597 let count_in = if self.accept_word("COUNT") {
1598 self.accept_word("IN");
1599 Some(self.reference()?)
1600 } else {
1601 None
1602 };
1603 into.push(UnstringInto { target, delimiter_in, count_in });
1604 }
1605 if into.is_empty() {
1606 return Err(self.error("a receiving item after INTO"));
1607 }
1608 let pointer = if self.accept_word("WITH") || self.is_word("POINTER") {
1609 self.expect_word("POINTER")?;
1610 Some(self.reference()?)
1611 } else {
1612 None
1613 };
1614 let tallying = if self.accept_word("TALLYING") {
1615 self.accept_word("IN");
1616 Some(self.reference()?)
1617 } else {
1618 None
1619 };
1620 let Handlers { on: on_overflow, not_on: not_on_overflow } = self.overflow("END-UNSTRING")?;
1621 Ok(Unstring { source, delimiters, into, pointer, tallying, on_overflow, not_on_overflow, pos })
1622 }
1623
1624 fn bounds(&mut self) -> R<Vec<Bound>> {
1625 let mut bounds = Vec::new();
1626 while let Some(side) = self.accept_any(&["BEFORE", "AFTER"]) {
1627 self.accept_word("INITIAL");
1628 bounds.push(Bound { after: side == "AFTER", value: self.operand()? });
1629 }
1630 Ok(bounds)
1631 }
1632
1633 fn inspect(&mut self, pos: Pos) -> R<Inspect> {
1634 let target = self.reference()?;
1635 let (mut tallying, mut replacing, mut converting) = (Vec::new(), Vec::new(), None);
1636 if self.accept_word("TALLYING") {
1637 while self.starts_ref() && self.word_at(1) == Some("FOR") || self.starts_ref() && !self.is_word("REPLACING") && self.tally_counter_ahead() {
1638 let counter = self.reference()?;
1639 self.expect_word("FOR")?;
1640 loop {
1641 if self.accept_word("CHARACTERS") {
1642 tallying.push(InspectPhrase { mode: InspectMode::Characters, pattern: None, by: None, counter: Some(counter.clone()), bounds: self.bounds()? });
1643 } else if let Some(mode) = self.accept_any(&["ALL", "LEADING"]) {
1644 let mode = if mode == "ALL" { InspectMode::All } else { InspectMode::Leading };
1645 loop {
1646 let pattern = self.operand()?;
1647 tallying.push(InspectPhrase { mode, pattern: Some(pattern), by: None, counter: Some(counter.clone()), bounds: self.bounds()? });
1648 if !self.starts_operand() || self.word_at(1) == Some("FOR") || self.is_word("ALL") || self.is_word("LEADING") {
1649 break;
1650 }
1651 }
1652 } else {
1653 break;
1654 }
1655 }
1656 }
1657 }
1658 if self.accept_word("REPLACING") {
1659 loop {
1660 if self.accept_word("CHARACTERS") {
1661 self.expect_word("BY")?;
1662 let by = self.operand()?;
1663 replacing.push(InspectPhrase { mode: InspectMode::Characters, pattern: None, by: Some(by), counter: None, bounds: self.bounds()? });
1664 } else if let Some(mode) = self.accept_any(&["ALL", "LEADING", "FIRST"]) {
1665 let mode = match mode.as_str() {
1666 "ALL" => InspectMode::All,
1667 "LEADING" => InspectMode::Leading,
1668 _ => InspectMode::First,
1669 };
1670 loop {
1671 let pattern = self.operand()?;
1672 self.expect_word("BY")?;
1673 let by = self.operand()?;
1674 replacing.push(InspectPhrase { mode, pattern: Some(pattern), by: Some(by), counter: None, bounds: self.bounds()? });
1675 if !self.starts_operand() || self.is_word("ALL") || self.is_word("LEADING") || self.is_word("FIRST") {
1676 break;
1677 }
1678 }
1679 } else {
1680 break;
1681 }
1682 }
1683 }
1684 if self.accept_word("CONVERTING") {
1685 let from = self.operand()?;
1686 self.expect_word("TO")?;
1687 let to = self.operand()?;
1688 converting = Some((from, to, self.bounds()?));
1689 }
1690 if tallying.is_empty() && replacing.is_empty() && converting.is_none() {
1691 return Err(self.error("TALLYING, REPLACING or CONVERTING"));
1692 }
1693 Ok(Inspect { target, tallying, replacing, converting, pos })
1694 }
1695
1696 fn tally_counter_ahead(&self) -> bool {
1698 let mut i = self.at + 1;
1699 if self.tokens.get(i).map(|t| &t.tok) == Some(&Tok::LParen) {
1700 let mut depth = 0;
1701 while let Some(t) = self.tokens.get(i) {
1702 match t.tok {
1703 Tok::LParen => depth += 1,
1704 Tok::RParen => {
1705 depth -= 1;
1706 if depth == 0 {
1707 i += 1;
1708 break;
1709 }
1710 }
1711 _ => {}
1712 }
1713 i += 1;
1714 }
1715 }
1716 matches!(self.tokens.get(i).map(|t| &t.tok), Some(Tok::Word(w)) if w == "FOR")
1717 }
1718
1719 fn search(&mut self, pos: Pos) -> R<Search> {
1720 let all = self.accept_word("ALL");
1721 let table = self.reference()?;
1722 let varying = if self.accept_word("VARYING") { Some(self.reference()?) } else { None };
1723 let at_end = if self.accept_word("AT") || self.is_word("END") {
1724 self.expect_word("END")?;
1725 Some(self.block(&["WHEN"])?)
1726 } else {
1727 None
1728 };
1729 let mut whens = Vec::new();
1730 while self.accept_word("WHEN") {
1731 let cond = self.cond()?;
1732 let body = self.block(&["WHEN", "END-SEARCH"])?;
1733 whens.push((cond, body));
1734 }
1735 if whens.is_empty() {
1736 return Err(self.error("WHEN"));
1737 }
1738 self.accept_word("END-SEARCH");
1739 Ok(Search { table, all, varying, at_end, whens, pos })
1740 }
1741
1742 fn set(&mut self) -> R<SetStmt> {
1743 if self.is_word("ENVIRONMENT") {
1744 return Err(Error::at(self.pos(), "SET ENVIRONMENT is GnuCOBOL's, not Enterprise COBOL's"));
1745 }
1746 if self.is_word("ADDRESS") && self.word_at(1) == Some("OF") {
1747 let mut targets = Vec::new();
1748 while self.is_word("ADDRESS") && self.word_at(1) == Some("OF") {
1749 self.at += 2;
1750 targets.push(self.reference()?);
1751 }
1752 self.expect_word("TO")?;
1753 return Ok(SetStmt::AddressOf { targets, value: self.operand()? });
1754 }
1755 let targets = self.refs()?;
1756 if self.accept_word("TO") {
1757 if self.accept_word("TRUE") {
1758 return Ok(SetStmt::ConditionTrue(targets));
1759 }
1760 if self.accept_word("FALSE") {
1761 return Ok(SetStmt::ConditionFalse(targets));
1762 }
1763 return Ok(SetStmt::To { targets, value: self.operand()? });
1764 }
1765 match self.accept_any(&["UP", "DOWN"]).as_deref() {
1766 Some(direction) => {
1767 self.expect_word("BY")?;
1768 Ok(SetStmt::UpDown { targets, down: direction == "DOWN", by: self.expr()? })
1769 }
1770 None => Err(self.error("TO, UP BY or DOWN BY")),
1771 }
1772 }
1773
1774 fn proc_name(&mut self) -> R<ProcName> {
1775 let name = self.name("a procedure name")?;
1776 let section = if self.accept_any(&["OF", "IN"]).is_some() { Some(self.name("a section name")?) } else { None };
1777 Ok(ProcName { name, section })
1778 }
1779
1780 fn evaluate(&mut self, pos: Pos) -> R<Stmt> {
1781 let mut subjects = vec![self.subject()?];
1782 while self.accept_word("ALSO") {
1783 subjects.push(self.subject()?);
1784 }
1785 let (mut whens, mut other) = (Vec::new(), Vec::new());
1786 while self.is_word("WHEN") {
1787 if self.word_at(1) == Some("OTHER") {
1788 self.at += 2;
1789 other = self.block(&["END-EVALUATE"])?;
1790 break;
1791 }
1792 let mut alternatives = Vec::new();
1793 while self.is_word("WHEN") && self.word_at(1) != Some("OTHER") {
1794 self.at += 1;
1795 let mut objects = Vec::new();
1796 for (k, subject) in subjects.iter().enumerate() {
1797 if k > 0 {
1798 self.expect_word("ALSO")?;
1799 }
1800 objects.push(self.object(subject)?);
1801 }
1802 alternatives.push(objects);
1803 }
1804 let body = self.block(&["WHEN", "END-EVALUATE"])?;
1805 whens.push(When { alternatives, body });
1806 }
1807 self.accept_word("END-EVALUATE");
1808 Ok(Stmt::Evaluate { subjects, whens, other, pos })
1809 }
1810
1811 fn subject(&mut self) -> R<Subject> {
1812 if let Some(b) = self.accept_any(&["TRUE", "FALSE"]) {
1813 return Ok(Subject::Bool(b == "TRUE"));
1814 }
1815 let save = self.at;
1816 self.expr()?;
1817 let conditional = self.relop_ahead(0) || self.is_word("IS") || self.is_word("NOT")
1818 || self.word().is_some_and(|w| matches!(w, "NUMERIC" | "ALPHABETIC" | "POSITIVE" | "NEGATIVE" | "ZERO"));
1819 self.at = save;
1820 Ok(if conditional { Subject::Cond(self.cond()?) } else { Subject::Expr(self.expr()?) })
1821 }
1822
1823 fn object(&mut self, subject: &Subject) -> R<Object> {
1824 if self.accept_word("ANY") {
1825 return Ok(Object::Any);
1826 }
1827 if let Some(b) = self.accept_any(&["TRUE", "FALSE"]) {
1828 return Ok(Object::Bool(b == "TRUE"));
1829 }
1830 if !matches!(subject, Subject::Expr(_)) {
1831 return Ok(Object::Cond(self.cond()?));
1832 }
1833 let not = self.accept_word("NOT");
1834 let from = self.expr()?;
1835 let thru = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.expr()?) } else { None };
1836 Ok(Object::Value { not, from, thru })
1837 }
1838
1839 fn repeat(&mut self) -> R<Loop> {
1840 let mut test_after = false;
1841 if self.accept_word("WITH") || self.is_word("TEST") {
1842 self.expect_word("TEST")?;
1843 test_after = self.accept_any(&["BEFORE", "AFTER"]).as_deref() == Some("AFTER");
1844 }
1845 if self.accept_word("UNTIL") {
1846 return Ok(Loop::Until { cond: self.cond()?, test_after });
1847 }
1848 if self.accept_word("VARYING") {
1849 let varying = Box::new(self.varying()?);
1850 let mut after = Vec::new();
1851 while self.is_word("AFTER") {
1852 if after.len() == 6 {
1853 return Err(self.error("the end of the PERFORM: Enterprise COBOL takes at most six AFTER phrases"));
1854 }
1855 self.at += 1;
1856 after.push(self.varying()?);
1857 }
1858 return Ok(Loop::Varying { varying, after, test_after });
1859 }
1860 if self.starts_operand() && self.word_at(1) == Some("TIMES") || matches!(self.peek(), Some(Tok::Number(_))) {
1861 let count = self.expr()?;
1862 self.expect_word("TIMES")?;
1863 return Ok(Loop::Times(count));
1864 }
1865 Ok(Loop::Once)
1866 }
1867
1868 fn varying(&mut self) -> R<Varying> {
1870 let var = self.reference()?;
1871 self.expect_word("FROM")?;
1872 let from = self.expr()?;
1873 self.expect_word("BY")?;
1874 let by = self.expr()?;
1875 self.expect_word("UNTIL")?;
1876 Ok(Varying { var, from, by, until: self.cond()? })
1877 }
1878
1879 fn starts_ref(&self) -> bool {
1880 self.word().is_some_and(|w| !VERBS.contains(&w) && !PHRASE_WORDS.contains(&w) && figurative(w).is_none() && w != "FUNCTION")
1881 && !self.paragraph_header()
1882 }
1883
1884 fn starts_operand(&self) -> bool {
1885 match self.peek() {
1886 Some(Tok::Alnum(_) | Tok::Hex(_) | Tok::National(_) | Tok::Number(_)) => true,
1887 Some(Tok::Word(w)) => {
1888 (figurative(w).is_some() || matches!(w.as_str(), "ALL" | "FUNCTION" | "LENGTH" | "ADDRESS" | "DFHRESP" | "DFHVALUE") || self.starts_ref()) && !self.paragraph_header()
1889 }
1890 _ => false,
1891 }
1892 }
1893
1894 fn operand(&mut self) -> R<Operand> {
1895 let pos = self.pos();
1896 match self.peek() {
1897 Some(Tok::Word(w)) if w == "FUNCTION" => {
1898 self.at += 1;
1899 let name = self.name("a function name")?;
1900 let (mut args, mut modifier) = (Vec::new(), None);
1901 if self.peek() == Some(&Tok::LParen) && !self.refmod_ahead() {
1902 self.at += 1;
1903 while !self.accept(&Tok::RParen) {
1904 if let Some(m) = self.accept_any(&["LEADING", "TRAILING"]) {
1905 modifier = Some(m);
1906 continue;
1907 }
1908 args.push(self.expr()?);
1909 }
1910 }
1911 let refmod = self.refmod()?;
1912 Ok(Operand::Function(FunctionCall { name, args, modifier, refmod, pos }))
1913 }
1914 Some(Tok::Word(w)) if w == "LENGTH" && self.word_at(1) == Some("OF") => {
1915 self.at += 2;
1916 Ok(Operand::LengthOf(self.reference()?))
1917 }
1918 Some(Tok::Word(w)) if w == "DFHRESP" && self.peek_at(1) == Some(&Tok::LParen) => {
1919 self.at += 2;
1920 let condition = self.name("a CICS condition")?;
1921 self.expect(&Tok::RParen, "')'")?;
1922 let code = crate::system::resp_code(&condition).ok_or_else(|| Error::at(pos, format!("DFHRESP({condition}): not a CICS condition ironwork for COBOL knows")))?;
1923 Ok(Operand::Literal(Literal::Number(code.to_string())))
1924 }
1925 Some(Tok::Word(w)) if w == "DFHVALUE" && self.peek_at(1) == Some(&Tok::LParen) => {
1926 self.at += 2;
1927 let name = self.name("a CVDA value")?;
1928 self.expect(&Tok::RParen, "')'")?;
1929 let value = rt::cics_tables::cvda(&name).ok_or_else(|| Error::at(pos, format!("DFHVALUE({name}): not a CVDA ironwork for COBOL knows")))?;
1930 Ok(Operand::Literal(Literal::Number(value.to_string())))
1931 }
1932 Some(Tok::Word(w)) if w == "ADDRESS" && self.word_at(1) == Some("OF") => {
1933 self.at += 2;
1934 Ok(Operand::AddressOf(self.reference()?))
1935 }
1936 Some(Tok::Word(w)) if figurative(w).is_some() || w == "ALL" => Ok(Operand::Literal(self.literal()?)),
1937 Some(Tok::Word(_)) => Ok(Operand::Ref(self.reference()?)),
1938 Some(Tok::Alnum(_) | Tok::Hex(_) | Tok::National(_) | Tok::Number(_)) => Ok(Operand::Literal(self.literal()?)),
1939 _ => Err(self.error("an operand")),
1940 }
1941 }
1942
1943 fn refmod_ahead(&self) -> bool {
1945 let mut depth = 0;
1946 for t in &self.tokens[self.at..] {
1947 match t.tok {
1948 Tok::LParen => depth += 1,
1949 Tok::RParen => {
1950 depth -= 1;
1951 if depth == 0 {
1952 return false;
1953 }
1954 }
1955 Tok::Colon if depth == 1 => return true,
1956 Tok::Period => return false,
1957 _ => {}
1958 }
1959 }
1960 false
1961 }
1962
1963 fn refmod(&mut self) -> R<Option<RefMod>> {
1964 if self.peek() != Some(&Tok::LParen) || !self.refmod_ahead() {
1965 return Ok(None);
1966 }
1967 self.at += 1;
1968 let start = Box::new(self.expr()?);
1969 self.expect(&Tok::Colon, "':'")?;
1970 let length = if self.peek() == Some(&Tok::RParen) { None } else { Some(Box::new(self.expr()?)) };
1971 self.expect(&Tok::RParen, "')'")?;
1972 Ok(Some(RefMod { start, length }))
1973 }
1974
1975 fn reference(&mut self) -> R<Ref> {
1976 let pos = self.pos();
1977 let name = self.name("a data name")?;
1978 let mut qualifiers = Vec::new();
1979 while self.accept_any(&["OF", "IN"]).is_some() {
1980 qualifiers.push(self.name("a qualifier")?);
1981 }
1982 let mut subscripts = Vec::new();
1983 if self.peek() == Some(&Tok::LParen) && !self.refmod_ahead() {
1984 self.at += 1;
1985 while !self.accept(&Tok::RParen) {
1986 subscripts.push(self.expr()?);
1987 }
1988 }
1989 let refmod = self.refmod()?;
1990 Ok(Ref { name, qualifiers, subscripts, refmod, pos })
1991 }
1992
1993 fn expr(&mut self) -> R<Expr> {
1994 let mut left = self.term()?;
1995 loop {
1996 let op = match self.peek() {
1997 Some(Tok::Plus) => BinOp::Add,
1998 Some(Tok::Minus) => BinOp::Sub,
1999 _ => return Ok(left),
2000 };
2001 self.at += 1;
2002 left = Expr::Bin(Box::new(left), op, Box::new(self.term()?));
2003 }
2004 }
2005
2006 fn term(&mut self) -> R<Expr> {
2007 let mut left = self.power()?;
2008 loop {
2009 let op = match self.peek() {
2010 Some(Tok::Star) => BinOp::Mul,
2011 Some(Tok::Slash) => BinOp::Div,
2012 _ => return Ok(left),
2013 };
2014 self.at += 1;
2015 left = Expr::Bin(Box::new(left), op, Box::new(self.power()?));
2016 }
2017 }
2018
2019 fn power(&mut self) -> R<Expr> {
2020 let mut left = self.unary()?;
2021 while self.accept(&Tok::Power) {
2022 left = Expr::Bin(Box::new(left), BinOp::Pow, Box::new(self.unary()?));
2023 }
2024 Ok(left)
2025 }
2026
2027 fn unary(&mut self) -> R<Expr> {
2028 if self.accept(&Tok::Minus) {
2029 return Ok(Expr::Neg(Box::new(self.unary()?)));
2030 }
2031 self.accept(&Tok::Plus);
2032 if self.accept(&Tok::LParen) {
2033 let e = self.expr()?;
2034 self.expect(&Tok::RParen, "')'")?;
2035 return Ok(e);
2036 }
2037 Ok(Expr::Operand(self.operand()?))
2038 }
2039
2040 fn cond(&mut self) -> R<Cond> {
2041 let mut last = None;
2042 self.or_cond(&mut last)
2043 }
2044
2045 fn or_cond(&mut self, last: &mut Option<(Expr, RelOp)>) -> R<Cond> {
2046 let mut left = self.and_cond(last)?;
2047 while self.accept_word("OR") {
2048 left = Cond::Or(Box::new(left), Box::new(self.and_cond(last)?));
2049 }
2050 Ok(left)
2051 }
2052
2053 fn and_cond(&mut self, last: &mut Option<(Expr, RelOp)>) -> R<Cond> {
2054 let mut left = self.not_cond(last)?;
2055 while self.accept_word("AND") {
2056 left = Cond::And(Box::new(left), Box::new(self.not_cond(last)?));
2057 }
2058 Ok(left)
2059 }
2060
2061 fn not_cond(&mut self, last: &mut Option<(Expr, RelOp)>) -> R<Cond> {
2062 if self.is_word("NOT") && !self.relop_ahead(1) {
2063 self.at += 1;
2064 return Ok(Cond::Not(Box::new(self.not_cond(last)?)));
2065 }
2066 self.primary_cond(last)
2067 }
2068
2069 fn relop_ahead(&self, ahead: usize) -> bool {
2070 matches!(self.peek_at(ahead), Some(Tok::Eq | Tok::Lt | Tok::Gt | Tok::Le | Tok::Ge))
2071 || matches!(self.word_at(ahead), Some("EQUAL" | "GREATER" | "LESS"))
2072 }
2073
2074 fn primary_cond(&mut self, last: &mut Option<(Expr, RelOp)>) -> R<Cond> {
2075 if self.peek() == Some(&Tok::LParen) {
2076 let save = self.at;
2077 self.at += 1;
2078 let mut inner_last = None;
2079 if let Ok(c) = self.or_cond(&mut inner_last)
2080 && self.accept(&Tok::RParen)
2081 && !matches!(self.peek(), Some(Tok::Plus | Tok::Minus | Tok::Star | Tok::Slash | Tok::Power))
2082 && !self.relop_ahead(0)
2083 && !self.is_word("IS")
2084 {
2085 return Ok(c);
2086 }
2087 self.at = save;
2088 }
2089 let left = self.expr()?;
2090 self.accept_word("IS");
2091 let negated = self.is_word("NOT") && {
2092 self.at += 1;
2093 true
2094 };
2095 let wrap = |c: Cond| if negated { Cond::Not(Box::new(c)) } else { c };
2096 if let Some(op) = self.relop()? {
2097 let right = self.expr()?;
2098 *last = Some((left.clone(), op));
2099 return Ok(wrap(Cond::Rel(left, op, right)));
2100 }
2101 if let Some(class) = self.accept_any(&["NUMERIC", "ALPHABETIC", "POSITIVE", "NEGATIVE", "ZERO"]) {
2102 let class = match class.as_str() {
2103 "NUMERIC" => Class::Numeric,
2104 "ALPHABETIC" => Class::Alphabetic,
2105 "POSITIVE" => Class::Positive,
2106 "NEGATIVE" => Class::Negative,
2107 _ => Class::Zero,
2108 };
2109 return Ok(wrap(Cond::Class(left, class)));
2110 }
2111 if negated {
2112 return Err(self.error("a relational operator or class after NOT"));
2113 }
2114 match (left, last.clone()) {
2115 (Expr::Operand(Operand::Ref(name)), Some((subject, op))) if self.abbreviation_context() => Ok(Cond::NameOrRel { subject, op, name }),
2116 (right, Some((subject, op))) if !matches!(&right, Expr::Operand(Operand::Ref(_))) => Ok(Cond::Rel(subject, op, right)),
2117 (Expr::Operand(Operand::Ref(r)), _) => Ok(Cond::Name(r)),
2118 (_, _) => Err(self.error("a relational operator")),
2119 }
2120 }
2121
2122 fn abbreviation_context(&self) -> bool {
2124 self.at > 1 && matches!(self.tokens.get(self.at.saturating_sub(2)).map(|t| &t.tok), Some(Tok::Word(w)) if w == "OR" || w == "AND")
2125 }
2126
2127 fn relop(&mut self) -> R<Option<RelOp>> {
2128 if self.peek() == Some(&Tok::Lt) && self.peek_at(1) == Some(&Tok::Gt) {
2129 return Err(Error::at(self.pos(), "<> is not an Enterprise COBOL relational operator: it writes NOT ="));
2130 }
2131 let op = match self.peek() {
2132 Some(Tok::Eq) => RelOp::Eq,
2133 Some(Tok::Lt) => RelOp::Lt,
2134 Some(Tok::Gt) => RelOp::Gt,
2135 Some(Tok::Le) => RelOp::Le,
2136 Some(Tok::Ge) => RelOp::Ge,
2137 Some(Tok::Word(w)) if w == "EQUAL" => {
2138 self.at += 1;
2139 self.accept_word("TO");
2140 return Ok(Some(RelOp::Eq));
2141 }
2142 Some(Tok::Word(w)) if w == "GREATER" || w == "LESS" => {
2143 let greater = w == "GREATER";
2144 self.at += 1;
2145 self.accept_word("THAN");
2146 let or_equal = self.accept_word("OR");
2147 if or_equal {
2148 self.expect_word("EQUAL")?;
2149 self.accept_word("TO");
2150 }
2151 return Ok(Some(match (greater, or_equal) {
2152 (true, false) => RelOp::Gt,
2153 (true, true) => RelOp::Ge,
2154 (false, false) => RelOp::Lt,
2155 (false, true) => RelOp::Le,
2156 }));
2157 }
2158 _ => return Ok(None),
2159 };
2160 self.at += 1;
2161 Ok(Some(op))
2162 }
2163}
2164
2165fn host_variables(sql: &str, pos: Pos) -> Vec<Ref> {
2167 let chars: Vec<char> = sql.chars().collect();
2168 let (mut out, mut i, mut quote) = (Vec::new(), 0, None);
2169 while i < chars.len() {
2170 let c = chars[i];
2171 match quote {
2172 Some(q) if c == q => quote = None,
2173 Some(_) => {}
2174 None if c == '\'' || c == '"' => quote = Some(c),
2175 None if c == ':' && chars.get(i + 1).is_some_and(|n| n.is_ascii_alphanumeric()) => {
2176 let start = i + 1;
2177 let mut end = start;
2178 while end < chars.len() && (chars[end].is_ascii_alphanumeric() || matches!(chars[end], '-' | '_' | '.')) {
2179 end += 1;
2180 }
2181 let path: String = chars[start..end].iter().collect::<String>().to_ascii_uppercase();
2182 let mut parts: Vec<String> = path.trim_end_matches('.').split('.').map(str::to_owned).collect();
2183 let name = parts.pop().unwrap_or_default();
2184 parts.reverse();
2185 out.push(Ref { name, qualifiers: parts, subscripts: Vec::new(), refmod: None, pos });
2186 i = end;
2187 continue;
2188 }
2189 None => {}
2190 }
2191 i += 1;
2192 }
2193 out
2194}
2195
2196fn cics_options(body: &str) -> Vec<(String, Option<ExecArg>)> {
2198 let chars: Vec<char> = body.chars().collect();
2199 let (mut out, mut i) = (Vec::new(), 0);
2200 while i < chars.len() {
2201 if chars[i].is_whitespace() {
2202 i += 1;
2203 continue;
2204 }
2205 let start = i;
2206 while i < chars.len() && !chars[i].is_whitespace() && chars[i] != '(' {
2207 i += 1;
2208 }
2209 let name: String = chars[start..i].iter().collect::<String>().to_ascii_uppercase();
2210 let mut j = i;
2211 while j < chars.len() && chars[j].is_whitespace() {
2212 j += 1;
2213 }
2214 if j < chars.len() && chars[j] == '(' {
2215 let (mut depth, mut quote, mut k) = (0, None, j);
2216 while k < chars.len() {
2217 match (quote, chars[k]) {
2218 (Some(q), c) if c == q => quote = None,
2219 (Some(_), _) => {}
2220 (None, '\'' | '"') => quote = Some(chars[k]),
2221 (None, '(') => depth += 1,
2222 (None, ')') => {
2223 depth -= 1;
2224 if depth == 0 {
2225 break;
2226 }
2227 }
2228 _ => {}
2229 }
2230 k += 1;
2231 }
2232 let arg: String = chars[j + 1..k.min(chars.len())].iter().collect();
2233 out.push((name, Some(ExecArg::Text(arg.trim().to_owned()))));
2234 i = (k + 1).min(chars.len());
2235 } else {
2236 out.push((name, None));
2237 }
2238 }
2239 out
2240}
2241
2242fn operand_of(text: &str, pos: Pos) -> Option<Operand> {
2244 let source = crate::source::Source { text: text.to_owned(), positions: vec![pos; text.chars().count()], options: Vec::new(), debugging: None };
2245 let tokens = crate::lexer::lex(&source).ok()?;
2246 let mut p = Parser { tokens: &tokens, at: 0, exec_declarations: Vec::new(), cics: false, sql: SqlState::default(), mnemonics: Vec::new(), debugging: false };
2247 let op = p.operand().ok()?;
2248 (p.at == tokens.len()).then_some(op)
2249}
2250
2251fn system_entries(member: &str) -> R<Vec<DataEntry>> {
2253 system_text_entries(&crate::system::member(member).unwrap_or_default())
2254}
2255
2256fn system_text_entries(text: &str) -> R<Vec<DataEntry>> {
2257 let source = crate::source::read(text)?;
2258 let tokens = crate::lexer::lex(&source)?;
2259 Parser { tokens: &tokens, at: 0, exec_declarations: Vec::new(), cics: false, sql: SqlState::default(), mnemonics: Vec::new(), debugging: false }.data_entries()
2260}
2261
2262const SELECT_CLAUSES: &[&str] = &[
2264 "ASSIGN", "ORGANIZATION", "ACCESS", "FILE", "STATUS", "RECORD", "ALTERNATE", "RELATIVE", "LINE", "SEQUENTIAL", "INDEXED", "RESERVE",
2265 "PADDING", "LOCK", "SHARING",
2266];
2267
2268const FD_WORDS: &[&str] = &[
2270 "RECORDING", "RECORD", "BLOCK", "LABEL", "DATA", "VALUE", "CODE-SET", "LINAGE", "REPORT", "REPORTS", "IS", "EXTERNAL", "GLOBAL", "STYLE",
2271];
2272
2273fn is_clause_word(w: &str) -> bool {
2274 matches!(
2275 w,
2276 "PIC" | "PICTURE" | "USAGE" | "VALUE" | "VALUES" | "REDEFINES" | "OCCURS" | "SIGN" | "LEADING" | "TRAILING" | "JUSTIFIED"
2277 | "JUST" | "SYNC" | "SYNCHRONIZED" | "GLOBAL" | "EXTERNAL" | "BLANK"
2278 ) || usage_word(w).is_some()
2279}
2280
2281#[cfg(test)]
2282mod tests {
2283 use super::*;
2284
2285 fn program(body: &str) -> Program {
2286 let text = format!(
2287 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n DATA DIVISION.\n WORKING-STORAGE SECTION.\n{body}"
2288 );
2289 crate::parse(&text).unwrap_or_else(|e| panic!("{e}"))
2290 }
2291
2292 #[test]
2293 fn dfhresp_and_dfhvalue_fold_to_the_numbers_in_ibms_tables() {
2294 let body = |operand: &str| format!(" 01 A PIC S9(8) COMP.\n PROCEDURE DIVISION.\n IF A = {operand}\n GOBACK\n END-IF.\n");
2295 let folds_to = |operand: &str, n: i32| {
2296 let p = program(&body(operand));
2297 assert!(format!("{:?}", p.paragraphs[0].statements[0]).contains(&format!("Number(\"{n}\")")), "{operand}");
2298 };
2299 folds_to("DFHRESP(NOTFINISHED)", 113);
2300 folds_to("DFHRESP(DSIDERR)", 12);
2301 folds_to("DFHRESP(FILENOTFOUND)", 12);
2302 assert_eq!(rt::cics_tables::cvda("ENABLED"), Some(23));
2303 folds_to("DFHVALUE(ENABLED)", 23);
2304 for (operand, message) in [("DFHRESP(NOSUCH)", "DFHRESP(NOSUCH): not a CICS condition"), ("DFHVALUE(NOSUCH)", "DFHVALUE(NOSUCH): not a CVDA")] {
2305 let err = crate::parse(&format!(" IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n DATA DIVISION.\n WORKING-STORAGE SECTION.\n{}", body(operand))).unwrap_err();
2306 assert!(err.to_string().contains(message), "{err}");
2307 }
2308 }
2309
2310 #[test]
2311 fn an_exec_cics_program_compares_with_dfhresp_notfinished() {
2312 program(" 01 WS-RESP PIC S9(8) COMP.\n PROCEDURE DIVISION.\n EXEC CICS RETURN END-EXEC\n IF WS-RESP = DFHRESP(NOTFINISHED)\n GOBACK\n END-IF.\n");
2313 }
2314
2315 #[test]
2316 fn data_entries_with_clauses_in_any_order() {
2317 let p = program(
2318 " 01 G.\n 05 A PIC S9(3)V99 COMP-3 VALUE -1.5.\n 05 B REDEFINES A PIC X(3).\n 05 FILLER PIC X(2) VALUE SPACES.\n 05 T PIC 9 OCCURS 3 TIMES.\n PROCEDURE DIVISION.\n GOBACK.\n",
2319 );
2320 let ws = &p.working_storage;
2321 assert_eq!(ws.len(), 5);
2322 assert_eq!((ws[1].picture.as_deref(), ws[1].usage, &ws[1].value), (Some("S9(3)V99"), Some(Usage::Packed), &Some(Literal::Number("-1.5".into()))));
2323 assert_eq!(ws[2].redefines.as_deref(), Some("A"));
2324 assert_eq!((ws[3].name.as_deref(), &ws[3].value), (None, &Some(Literal::Figurative(Figurative::Space))));
2325 assert_eq!(ws[4].occurs, Some(3));
2326 }
2327
2328 #[test]
2329 fn paragraphs_and_nested_if() {
2330 let p = program(
2331 " 01 A PIC X.\n PROCEDURE DIVISION.\n MAIN-LINE.\n PERFORM P2\n GOBACK.\n P2.\n IF A < 'B'\n MOVE 'L' TO A\n ELSE\n IF A = 'B'\n MOVE 'E' TO A\n ELSE\n MOVE 'G' TO A\n END-IF\n END-IF.\n",
2332 );
2333 assert_eq!(p.paragraphs.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(), ["MAIN-LINE", "P2"]);
2334 let Stmt::If { otherwise, .. } = &p.paragraphs[1].statements[0] else { panic!() };
2335 assert!(matches!(otherwise[0], Stmt::If { .. }));
2336 }
2337
2338 #[test]
2339 fn a_program_names_the_programs_it_directly_contains() {
2340 let program = |id: &str, inner: &str| format!(" IDENTIFICATION DIVISION.\n PROGRAM-ID. {id}.\n PROCEDURE DIVISION.\n GOBACK.\n{inner} END PROGRAM {id}.\n");
2341 let text = program("OUTER", &[program("A", &program("A1", "")), program("B", "")].concat());
2342 let all = crate::parse_all_with(&text, &Default::default()).unwrap_or_else(|e| panic!("{e}"));
2343 let contained: Vec<(&str, &[String])> = all.iter().map(|p| (p.id.as_str(), p.nested.as_slice())).collect();
2344 assert_eq!(contained, [("OUTER", &["A".to_owned(), "B".to_owned()][..]), ("A", &["A1".to_owned()][..]), ("A1", &[][..]), ("B", &[][..])]);
2345 }
2346
2347 #[test]
2348 fn inline_perform_varying_with_a_compound_condition() {
2349 let p = program(
2350 " 01 I PIC 9(4) COMP.\n 01 J PIC 9(4) COMP.\n PROCEDURE DIVISION.\n PERFORM VARYING J FROM 0 BY 1\n UNTIL J > 31 OR I + J > 64\n CONTINUE\n END-PERFORM.\n",
2351 );
2352 let Stmt::PerformInline { repeat: Loop::Varying { varying, .. }, .. } = &p.paragraphs[0].statements[0] else { panic!() };
2353 assert!(matches!(varying.until, Cond::Or(..)));
2354 }
2355
2356 #[test]
2357 fn functions_reference_modification_and_length_of() {
2358 let p = program(
2359 " 01 X PIC X(9).\n 01 N PIC 9(4) COMP.\n PROCEDURE DIVISION.\n COMPUTE N = FUNCTION ORD(X(N + 1:1)) - 1\n MOVE FUNCTION NATIONAL-OF(X, 1047) TO X\n MOVE LENGTH OF X TO N.\n",
2360 );
2361 let Stmt::Compute { expr: Expr::Bin(left, BinOp::Sub, _), .. } = &p.paragraphs[0].statements[0] else { panic!() };
2362 let Expr::Operand(Operand::Function(f)) = left.as_ref() else { panic!() };
2363 let Expr::Operand(Operand::Ref(r)) = &f.args[0] else { panic!() };
2364 assert!(r.refmod.is_some());
2365 assert!(matches!(&p.paragraphs[0].statements[2], Stmt::Move { from: Operand::LengthOf(_), .. }));
2366 }
2367
2368 #[test]
2369 fn divide_giving_remainder_and_add_to() {
2370 let p = program(
2371 " 01 N PIC 9(4) COMP.\n 01 H PIC 9(4) COMP.\n 01 L PIC 9(4) COMP.\n PROCEDURE DIVISION.\n DIVIDE N BY 16 GIVING H REMAINDER L\n ADD 1 TO H ROUNDED.\n",
2372 );
2373 let Stmt::Arith(a) = &p.paragraphs[0].statements[0] else { panic!() };
2374 assert_eq!(a.verb, ArithVerb::Divide);
2375 assert!(a.remainder.is_some());
2376 let Stmt::Arith(add) = &p.paragraphs[0].statements[1] else { panic!() };
2377 assert!(add.computations[0].0.rounded);
2378 }
2379
2380 #[test]
2381 fn abbreviated_combined_relation() {
2382 let p = program(" 01 A PIC 9.\n PROCEDURE DIVISION.\n IF A = 1 OR 2 CONTINUE END-IF.\n");
2383 let Stmt::If { cond: Cond::Or(_, right), .. } = &p.paragraphs[0].statements[0] else { panic!() };
2384 assert!(matches!(right.as_ref(), Cond::Rel(_, RelOp::Eq, _)));
2385 }
2386
2387 #[test]
2388 fn indexed_select_clauses_and_keyed_statements() {
2389 let text = [
2390 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n ENVIRONMENT DIVISION.\n INPUT-OUTPUT SECTION.\n FILE-CONTROL.\n",
2391 " SELECT K ASSIGN TO KDD ORGANIZATION IS INDEXED\n",
2392 " ACCESS MODE IS DYNAMIC RECORD KEY IS K-ID\n",
2393 " ALTERNATE KEY K-ALT WITH DUPLICATES.\n",
2394 " SELECT N ASSIGN TO NDD STATUS N-FS N-VS ORGANIZATION\n",
2395 " INDEXED FILE STATUS IS N-FS RECORD N-ID.\n",
2396 " DATA DIVISION.\n FILE SECTION.\n FD K.\n 01 K-REC.\n 05 K-ID PIC XX.\n 05 K-ALT PIC XX.\n",
2397 " FD N.\n 01 N-ID PIC XX.\n WORKING-STORAGE SECTION.\n 01 N-FS PIC XX.\n 01 N-VS PIC X(6).\n",
2398 " PROCEDURE DIVISION.\n",
2399 " READ K NEXT RECORD AT END CONTINUE\n",
2400 " NOT AT END CONTINUE END-READ\n",
2401 " READ K KEY IS K-ALT INVALID KEY CONTINUE END-READ\n",
2402 " START K KEY IS NOT LESS THAN K-ID\n",
2403 " INVALID KEY CONTINUE NOT INVALID KEY CONTINUE\n",
2404 " END-START\n",
2405 " REWRITE K-REC INVALID KEY CONTINUE END-REWRITE\n",
2406 " DELETE K RECORD END-DELETE.\n",
2407 ]
2408 .concat();
2409 let p = crate::parse(&text).unwrap_or_else(|e| panic!("{e}"));
2410 let f = &p.files[0];
2411 assert_eq!((f.organization, f.access), (Organization::Indexed, Access::Dynamic));
2412 assert_eq!(f.record_key.as_ref().map(|r| r.name.as_str()), Some("K-ID"));
2413 assert_eq!(f.alternate_keys.iter().map(|(r, d)| (r.name.as_str(), *d)).collect::<Vec<_>>(), [("K-ALT", true)]);
2414 let n = &p.files[1];
2415 assert_eq!((n.organization, n.record_key.as_ref().map(|r| r.name.as_str())), (Organization::Indexed, Some("N-ID")));
2416 let s = &p.paragraphs[0].statements;
2417 let Stmt::Read(r) = &s[0] else { panic!() };
2418 assert!(r.next && !r.previous && r.at_end.on.is_some() && r.at_end.not_on.is_some());
2419 let Stmt::Read(r) = &s[1] else { panic!() };
2420 assert!(r.key.is_some() && r.invalid.on.is_some() && !r.next);
2421 let Stmt::Start { key: Some((RelOp::Ge, _)), invalid, .. } = &s[2] else { panic!() };
2422 assert!(invalid.on.is_some() && invalid.not_on.is_some());
2423 assert!(matches!(&s[3], Stmt::Rewrite { invalid, .. } if invalid.on.is_some()));
2424 assert!(matches!(&s[4], Stmt::Delete { .. }));
2425 }
2426
2427 #[test]
2428 fn entry_alter_the_go_to_forms_and_section_priorities() {
2429 let p = program(
2430 " 01 D PIC 9.\n LINKAGE SECTION.\n 01 L PIC X.\n PROCEDURE DIVISION.\n S SECTION 50.\n P1.\n ENTRY 'Alt' USING BY VALUE L.\n ALTER P2 TO PROCEED TO P1 P3 TO P1\n GO TO P1 P2 DEPENDING ON D.\n P2.\n GO TO.\n P3.\n GO TO P1.\n T SECTION.\n",
2431 );
2432 let priorities: Vec<(&str, u8)> = p.paragraphs.iter().map(|q| (q.name.as_str(), q.priority)).collect();
2433 assert_eq!(priorities, [("S", 50), ("P1", 50), ("P2", 50), ("P3", 50), ("T", 0)]);
2434 let s = &p.paragraphs[1].statements;
2435 assert!(matches!(&s[0], Stmt::Entry { name, using, .. } if name == "ALT" && using == &[Param { by_value: true, name: "L".into() }]));
2436 assert!(matches!(&s[2], Stmt::Alter { pairs, .. } if pairs.len() == 2 && pairs[1].0.name == "P3"));
2437 assert!(matches!(&s[3], Stmt::GoToDepending { targets, on, .. } if targets.len() == 2 && on.name == "D"));
2438 assert!(matches!(&p.paragraphs[2].statements[0], Stmt::GoTo { target: None, .. }));
2439 assert!(matches!(&p.paragraphs[3].statements[0], Stmt::GoTo { target: Some(t), .. } if t.name == "P1"));
2440 }
2441
2442 #[test]
2443 fn unsupported_statements_are_named() {
2444 let text = " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n PROCEDURE DIVISION.\n MOVE CORRESPONDING A TO B.\n";
2445 assert!(crate::parse(text).unwrap_err().message.contains("MOVE CORRESPONDING"));
2446 }
2447
2448 #[test]
2449 fn advancing_mnemonic_names_reach_contained_programs_and_linage_is_noted() {
2450 let text = [
2451 " IDENTIFICATION DIVISION.\n PROGRAM-ID. OUTER.\n ENVIRONMENT DIVISION.\n",
2452 " CONFIGURATION SECTION.\n SPECIAL-NAMES.\n C01 IS TOP-OF-PAGE CSP NO-SPACE\n",
2453 " AFP-5A IS PAGE-MODE UPSI-0 IS SWITCH-0 ON STATUS IS SW-ON.\n",
2454 " INPUT-OUTPUT SECTION.\n FILE-CONTROL.\n SELECT P ASSIGN TO PDD.\n",
2455 " DATA DIVISION.\n FILE SECTION.\n FD P LINAGE IS 60.\n 01 P-REC PIC X.\n",
2456 " PROCEDURE DIVISION.\n WRITE P-REC AFTER TOP-OF-PAGE\n",
2457 " WRITE P-REC BEFORE ADVANCING NO-SPACE\n WRITE P-REC AFTER ADVANCING PAGE-COUNT LINES.\n",
2458 " IDENTIFICATION DIVISION.\n PROGRAM-ID. INNER.\n PROCEDURE DIVISION.\n",
2459 " WRITE P-REC AFTER ADVANCING PAGE-MODE.\n END PROGRAM INNER.\n END PROGRAM OUTER.\n",
2460 ]
2461 .concat();
2462 let programs = crate::parse_all_with(&text, &crate::copy::Libraries::default()).unwrap_or_else(|e| panic!("{e}"));
2463 let (outer, inner) = (&programs[0], &programs[1]);
2464 let pairs = |p: &Program| p.environment.mnemonics.iter().map(|(n, e)| format!("{n}={e}")).collect::<Vec<_>>();
2465 assert_eq!(pairs(outer), ["TOP-OF-PAGE=C01", "NO-SPACE=CSP", "PAGE-MODE=AFP-5A"]);
2466 assert_eq!(outer.files[0].linage.as_ref().map(|l| &l.lines), Some(&LinageValue::Integer("60".into())));
2467 let advancing = |p: &Program, i: usize| match &p.paragraphs[0].statements[i] {
2468 Stmt::Write { advancing: Some(a), .. } => a.clone(),
2469 other => panic!("{other:?}"),
2470 };
2471 assert!(matches!(advancing(outer, 0), Advancing::Mnemonic { before: false, environment, .. } if environment == "C01"));
2472 assert!(matches!(advancing(outer, 1), Advancing::Mnemonic { before: true, environment, .. } if environment == "CSP"));
2473 assert!(matches!(advancing(outer, 2), Advancing::Lines { before: false, .. }));
2474 assert!(matches!(advancing(inner, 0), Advancing::Mnemonic { environment, .. } if environment == "AFP-5A"));
2475 }
2476
2477 #[test]
2478 fn exit_section_opening_a_paragraph_is_a_statement_not_a_section() {
2479 let p = program(" PROCEDURE DIVISION.\n MAIN-LINE SECTION.\n SKIPPED.\n EXIT SECTION.\n NEVER.\n GOBACK.\n");
2480 let names: Vec<&str> = p.paragraphs.iter().map(|q| q.name.as_str()).collect();
2481 assert_eq!(names, ["MAIN-LINE", "SKIPPED", "NEVER"]);
2482 assert!(matches!(p.paragraphs[1].statements[..], [Stmt::Exit { kind: ExitKind::Section, .. }, Stmt::SentenceEnd]), "{:?}", p.paragraphs[1].statements);
2483 }
2484
2485 fn linage_program(fds: &str, procedure: &str) -> String {
2486 [
2487 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n ENVIRONMENT DIVISION.\n INPUT-OUTPUT SECTION.\n FILE-CONTROL.\n",
2488 " SELECT P ASSIGN TO PDD.\n SELECT S ASSIGN TO SDD.\n SELECT Q ASSIGN TO QDD.\n",
2489 " DATA DIVISION.\n FILE SECTION.\n",
2490 fds,
2491 " WORKING-STORAGE SECTION.\n 01 SIZES.\n 05 BODY PIC 99.\n 01 T-M PIC 9.\n PROCEDURE DIVISION.\n",
2492 procedure,
2493 ]
2494 .concat()
2495 }
2496
2497 #[test]
2498 fn linage_phrases_come_in_any_order_and_an_sds_is_dropped() {
2499 let fds = [
2500 " FD P LINAGE IS BODY OF SIZES LINES LINES AT BOTTOM 6\n WITH FOOTING AT 45 TOP T-M.\n 01 P-REC PIC X.\n",
2501 " SD S LINAGE 10.\n 01 S-REC PIC X.\n",
2502 " FD Q LABEL RECORDS STANDARD LINAGE 5 RECORDING MODE F.\n 01 Q-REC PIC X.\n",
2503 ]
2504 .concat();
2505 let p = crate::parse(&linage_program(&fds, " GOBACK.\n")).unwrap_or_else(|e| panic!("{e}"));
2506 let l = p.files[0].linage.as_ref().unwrap();
2507 assert!(matches!(&l.lines, LinageValue::Data(r) if r.name == "BODY" && r.qualifiers == ["SIZES"]));
2508 assert_eq!((&l.footing, &l.bottom), (&Some(LinageValue::Integer("45".into())), &Some(LinageValue::Integer("6".into()))));
2509 assert!(matches!(&l.top, Some(LinageValue::Data(r)) if r.name == "T-M"));
2510 assert_eq!(p.files[1].linage, None);
2511 assert_eq!((p.files[2].linage.as_ref().map(|l| &l.lines), p.files[2].recording), (Some(&LinageValue::Integer("5".into())), Some('F')));
2512 for (fd, expected) in [("LINAGE 5 TOP 1 TOP 2", "TOP is given twice"), ("LINAGE 5.5", "not an unsigned integer"), ("LINAGE 5 FOOTING", "after FOOTING")] {
2513 let text = linage_program(&format!(" FD P {fd}.\n 01 P-REC PIC X.\n"), " GOBACK.\n");
2514 let e = crate::parse(&text).unwrap_err();
2515 assert!(e.message.contains(expected), "{fd}: {}", e.message);
2516 }
2517 }
2518
2519 #[test]
2520 fn end_of_page_phrases_leave_a_read_its_not_at_end() {
2521 let procedure = [
2522 " READ Q AT END WRITE P-REC\n",
2523 " NOT AT END WRITE P-REC AT EOP CONTINUE END-WRITE\n END-READ\n",
2524 " WRITE P-REC BEFORE ADVANCING 2 LINES END-OF-PAGE CONTINUE\n",
2525 " NOT AT END-OF-PAGE CONTINUE\n END-WRITE\n",
2526 " WRITE P-REC INVALID KEY CONTINUE NOT EOP CONTINUE.\n",
2527 ]
2528 .concat();
2529 let fds = " FD P LINAGE 5.\n 01 P-REC PIC X.\n FD Q.\n 01 Q-REC PIC X.\n";
2530 let p = crate::parse(&linage_program(fds, &procedure)).unwrap_or_else(|e| panic!("{e}"));
2531 let s = &p.paragraphs[0].statements;
2532 let Stmt::Read(r) = &s[0] else { panic!("{:?}", s[0]) };
2533 let (Some(on), Some(not_on)) = (&r.at_end.on, &r.at_end.not_on) else { panic!("{r:?}") };
2534 assert!(matches!(&on[0], Stmt::Write { end_of_page, .. } if *end_of_page == Handlers::default()));
2535 assert!(matches!(¬_on[0], Stmt::Write { end_of_page, .. } if end_of_page.on.is_some() && end_of_page.not_on.is_none()));
2536 assert!(matches!(&s[1], Stmt::Write { advancing: Some(Advancing::Lines { before: true, .. }), end_of_page, .. } if end_of_page.on.is_some() && end_of_page.not_on.is_some()));
2537 assert!(matches!(&s[2], Stmt::Write { invalid, end_of_page, .. } if invalid.on.is_some() && end_of_page.not_on.is_some()));
2538 }
2539
2540 #[test]
2541 fn decimal_point_is_comma_reaches_contained_programs_and_other_currency_signs_are_refused() {
2542 let text = [
2543 " IDENTIFICATION DIVISION.\n PROGRAM-ID. OUTER.\n ENVIRONMENT DIVISION.\n",
2544 " CONFIGURATION SECTION.\n SPECIAL-NAMES.\n CURRENCY SIGN IS '$'\n DECIMAL-POINT IS COMMA.\n",
2545 " DATA DIVISION.\n WORKING-STORAGE SECTION.\n 01 A PIC 9V9 VALUE 1,5.\n",
2546 " PROCEDURE DIVISION.\n GOBACK.\n",
2547 " IDENTIFICATION DIVISION.\n PROGRAM-ID. INNER.\n PROCEDURE DIVISION.\n GOBACK.\n",
2548 " END PROGRAM INNER.\n END PROGRAM OUTER.\n",
2549 ]
2550 .concat();
2551 let programs = crate::parse_all_with(&text, &crate::copy::Libraries::default()).unwrap_or_else(|e| panic!("{e}"));
2552 assert!(programs.iter().all(|p| p.environment.decimal_point_comma));
2553 assert_eq!(programs[0].working_storage[0].value, Some(Literal::Number("1.5".into())));
2554 let other = text.replace("'$'", "'W'");
2555 assert!(crate::parse(&other).unwrap_err().message.contains("CURRENCY SIGN"));
2556 }
2557
2558 #[test]
2559 fn renames_and_condition_names_with_a_false_value() {
2560 let p = program(concat!(
2561 " 01 R.\n 05 A PIC X.\n 05 B PIC X.\n 88 B-ON VALUE 'Y' FALSE 'N'.\n",
2562 " 88 B-OFF VALUES 'N' 'X' WHEN SET TO FALSE IS SPACE.\n",
2563 " 66 AB RENAMES A THRU B.\n 66 BB RENAMES B OF R.\n",
2564 " PROCEDURE DIVISION.\n SET B-ON B-OFF TO FALSE.\n",
2565 ));
2566 let ws = &p.working_storage;
2567 assert_eq!((ws[3].false_value.as_ref(), ws[3].condition_values.len()), (Some(&Literal::Alnum("N".into())), 1));
2568 assert_eq!((ws[4].false_value.as_ref(), ws[4].condition_values.len()), (Some(&Literal::Figurative(Figurative::Space)), 2));
2569 let (first, last) = ws[5].renames.as_ref().unwrap();
2570 assert_eq!((ws[5].level, first.name.as_str(), last.as_ref().map(|r| r.name.as_str())), (66, "A", Some("B")));
2571 assert_eq!(ws[6].renames.as_ref().unwrap().0.qualifiers, ["R"]);
2572 assert!(matches!(&p.paragraphs[0].statements[0], Stmt::Set { set: SetStmt::ConditionFalse(t), .. } if t.len() == 2));
2573 }
2574}