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::new(tokens);
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", "JSON", "XML",
30];
31
32fn relation(subject: Expr, op: RelOp, negated: bool, object: Expr) -> Cond {
35 let c = Cond::Rel(subject, op, object);
36 if negated { Cond::Not(Box::new(c)) } else { c }
37}
38
39fn digits(t: &Tok) -> bool {
41 matches!(t, Tok::Number(n) if n.bytes().all(|b| b.is_ascii_digit()))
42}
43
44const PHRASE_WORDS: &[&str] = &[
45 "ELSE", "END-IF", "END-PERFORM", "END-COMPUTE", "END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE", "END-DISPLAY", "WHEN",
46 "TO", "FROM", "BY", "INTO", "GIVING", "REMAINDER", "ROUNDED", "ON", "NOT", "SIZE", "UNTIL", "VARYING", "TIMES", "THRU", "THROUGH",
47 "AND", "OR", "THEN", "UPON", "WITH", "IS", "END-EVALUATE", "ALSO", "OTHER", "OF", "IN", "AT", "END", "END-READ", "END-WRITE",
48 "BEFORE", "AFTER", "ADVANCING", "INPUT", "OUTPUT", "EXTEND", "I-O", "REVERSED", "USING", "RETURNING", "EXCEPTION", "OVERFLOW",
49 "END-CALL", "OMITTED", "CONTENT", "REFERENCE", "VALUE", "UP", "DOWN", "DELIMITED", "DELIMITER", "COUNT", "POINTER", "TALLYING",
50 "REPLACING", "CONVERTING", "INITIAL", "FOR", "CHARACTERS", "LEADING", "FIRST", "ALL", "END-STRING", "END-UNSTRING", "END-SEARCH",
51 "NEXT", "INVALID", "KEY", "END-REWRITE", "END-DELETE", "END-START", "END-INVOKE", "END-RETURN", "END-OF-PAGE", "EOP", "END-JSON", "END-XML",
52];
53
54const JSON_PHRASES: &[&str] = &["COUNT", "INDICATING", "ENCODING", "NAME", "SUPPRESS", "CONVERTING", "IGNORING", "WITH", "DETAIL", "ON", "NOT", "EXCEPTION", "END-JSON", "ALSO"];
57
58const XML_PHRASES: &[&str] =
60 &["COUNT", "WITH", "ENCODING", "XML-DECLARATION", "ATTRIBUTES", "NAMESPACE", "NAMESPACE-PREFIX", "NAME", "TYPE", "SUPPRESS", "EVERY", "ON", "NOT", "EXCEPTION", "END-XML"];
61
62fn advancing_environment_name(word: &str) -> bool {
65 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));
66 matches!(word, "CSP" | "AFP-5A") || numbered('C', 12) || numbered('S', 5)
67}
68
69fn figurative(word: &str) -> Option<Figurative> {
70 Some(match word {
71 "ZERO" | "ZEROS" | "ZEROES" => Figurative::Zero,
72 "SPACE" | "SPACES" => Figurative::Space,
73 "HIGH-VALUE" | "HIGH-VALUES" => Figurative::HighValue,
74 "LOW-VALUE" | "LOW-VALUES" => Figurative::LowValue,
75 "QUOTE" | "QUOTES" => Figurative::Quote,
76 "NULL" | "NULLS" => Figurative::Null,
77 _ => return None,
78 })
79}
80
81fn currency_symbol(c: char) -> bool {
84 u32::from(c) < 256 && !c.is_ascii_digit() && !"ABCDEGNPRSUVXZabcdegnprsuvxz +-,.*/;()\"='".contains(c)
85}
86
87fn share_configuration(outer: &Environment, inner: &mut Environment) {
91 inner.debugging_mode |= outer.debugging_mode;
92 inner.decimal_point_comma |= outer.decimal_point_comma;
93 if inner.currency.is_empty() {
94 inner.currency.clone_from(&outer.currency);
95 }
96 if inner.collating_sequence.is_none() {
97 inner.collating_sequence.clone_from(&outer.collating_sequence);
98 }
99 for (name, alphabet) in &outer.alphabets {
100 if !inner.alphabets.iter().any(|(n, _)| n == name) {
101 inner.alphabets.push((name.clone(), alphabet.clone()));
102 }
103 }
104}
105
106fn usage_word(word: &str) -> Option<Usage> {
107 Some(match word {
108 "DISPLAY" => Usage::Display,
109 "BINARY" | "COMP" | "COMPUTATIONAL" | "COMP-4" | "COMPUTATIONAL-4" => Usage::Binary,
110 "COMP-5" | "COMPUTATIONAL-5" => Usage::NativeBinary,
111 "PACKED-DECIMAL" | "COMP-3" | "COMPUTATIONAL-3" => Usage::Packed,
112 "COMP-1" | "COMPUTATIONAL-1" => Usage::Float1,
113 "COMP-2" | "COMPUTATIONAL-2" => Usage::Float2,
114 "NATIONAL" => Usage::National,
115 "POINTER" => Usage::Pointer,
116 "INDEX" => Usage::Index,
117 "FUNCTION-POINTER" | "PROCEDURE-POINTER" => Usage::ProgramPointer,
118 _ => return None,
119 })
120}
121
122struct Parser<'a> {
123 tokens: &'a [Token],
124 at: usize,
125 exec_declarations: Vec<ExecBlock>,
127 cics: bool,
129 dli: bool,
131 mnemonics: Vec<(String, String)>,
134 sql: SqlState,
135 debugging: bool,
137 messages: Vec<Error>,
139 reported: Vec<bool>,
142}
143
144#[derive(Default)]
147struct SqlState {
148 whenever: crate::sql::Whenever,
149 blocks: u32,
150 cursors: crate::sql::Cursors,
151}
152
153type R<T> = Result<T, Error>;
154
155impl<'a> Parser<'a> {
156 fn new(tokens: &'a [Token]) -> Self {
157 Self {
158 tokens,
159 at: 0,
160 exec_declarations: Vec::new(),
161 cics: false,
162 dli: false,
163 sql: SqlState::default(),
164 mnemonics: Vec::new(),
165 debugging: false,
166 messages: Vec::new(),
167 reported: vec![false; tokens.len()],
168 }
169 }
170}
171
172impl Parser<'_> {
173 fn peek(&self) -> Option<&Tok> {
174 self.tokens.get(self.at).map(|t| &t.tok)
175 }
176
177 fn peek_at(&self, ahead: usize) -> Option<&Tok> {
178 self.tokens.get(self.at + ahead).map(|t| &t.tok)
179 }
180
181 fn qualifying_paren_at(&self, at: usize) -> bool {
184 self.tokens.get(at).is_some_and(|t| t.tok == Tok::LParen && !t.after_comma)
185 }
186
187 fn pos(&self) -> Pos {
188 self.tokens.get(self.at).or(self.tokens.last()).map(|t| t.pos).unwrap_or_default()
189 }
190
191 fn word(&self) -> Option<&str> {
192 match self.peek() {
193 Some(Tok::Word(w)) => Some(w),
194 _ => None,
195 }
196 }
197
198 fn word_at(&self, ahead: usize) -> Option<&str> {
199 match self.peek_at(ahead) {
200 Some(Tok::Word(w)) => Some(w),
201 _ => None,
202 }
203 }
204
205 fn is_word(&self, w: &str) -> bool {
206 self.word() == Some(w)
207 }
208
209 fn accept_word(&mut self, w: &str) -> bool {
210 let yes = self.is_word(w);
211 if yes {
212 self.at += 1;
213 }
214 yes
215 }
216
217 fn accept_any(&mut self, words: &[&str]) -> Option<String> {
218 let w = self.word().filter(|w| words.contains(w))?.to_owned();
219 self.at += 1;
220 Some(w)
221 }
222
223 fn expect_word(&mut self, w: &str) -> R<()> {
224 if self.accept_word(w) { Ok(()) } else { Err(self.error(format!("expected {w}"))) }
225 }
226
227 fn accept(&mut self, tok: &Tok) -> bool {
228 let yes = self.peek() == Some(tok);
229 if yes {
230 self.at += 1;
231 }
232 yes
233 }
234
235 fn expect(&mut self, tok: &Tok, what: &str) -> R<()> {
236 if self.accept(tok) { Ok(()) } else { Err(self.error(format!("expected {what}"))) }
237 }
238
239 fn error(&self, message: impl Into<String>) -> Error {
240 let found = match self.peek() {
241 None => "end of source".to_owned(),
242 Some(Tok::Word(w)) => w.clone(),
243 Some(t) => format!("{t:?}"),
244 };
245 Error::at(self.pos(), format!("{}, found {found}", message.into()))
246 }
247
248 fn name(&mut self, what: &str) -> R<String> {
249 match self.peek() {
250 Some(Tok::Word(w)) => {
251 let w = w.clone();
252 self.at += 1;
253 Ok(w)
254 }
255 _ => Err(self.error(format!("expected {what}"))),
256 }
257 }
258
259 fn at_division(&self, names: &[&str]) -> bool {
260 self.word().is_some_and(|w| names.contains(&w)) && self.word_at(1) == Some("DIVISION")
261 }
262
263 fn program(&mut self, options: &[String], out: &mut Vec<Program>) -> R<()> {
264 let (start, first) = (self.at, out.len());
265 let outer = (std::mem::take(&mut self.exec_declarations), std::mem::take(&mut self.cics), std::mem::take(&mut self.dli), std::mem::take(&mut self.sql.blocks), self.mnemonics.clone(), self.debugging);
266 let outer_messages = std::mem::take(&mut self.messages);
267 let parsed = self.one_program(options, out);
268 (self.exec_declarations, self.cics, self.dli, self.sql.blocks, self.mnemonics, self.debugging) = outer;
269 let own = std::mem::replace(&mut self.messages, outer_messages);
270 parsed?;
271 let mut messages = Vec::new();
272 for i in start..self.at {
273 if !std::mem::replace(&mut self.reported[i], true) {
274 messages.extend(self.tokens[i].messages.iter().cloned());
275 }
276 }
277 messages.extend(own);
278 if let Some(p) = out.get_mut(first) {
279 p.messages = messages;
280 }
281 Ok(())
282 }
283
284 fn one_program(&mut self, options: &[String], out: &mut Vec<Program>) -> R<()> {
285 if !self.accept_word("IDENTIFICATION") {
286 self.expect_word("ID")?;
287 }
288 self.expect_word("DIVISION")?;
289 self.expect(&Tok::Period, "a period")?;
290 if self.is_word("CLASS-ID") {
291 return self.class_definition(options, out);
292 }
293 self.expect_word("PROGRAM-ID")?;
294 self.accept(&Tok::Period);
295 let id = match self.peek() {
296 Some(Tok::Alnum(s)) => {
297 let s = s.clone();
298 self.at += 1;
299 s
300 }
301 _ => self.name("a program name")?,
302 };
303 let (mut initial, mut recursive) = (false, false);
304 while let Some(t) = self.peek() {
305 if *t == Tok::Period {
306 self.at += 1;
307 break;
308 }
309 initial |= self.is_word("INITIAL");
310 recursive |= self.is_word("RECURSIVE");
311 self.at += 1;
312 }
313 self.program_body(id, initial, recursive, options, out, false)
314 }
315
316 fn program_body(&mut self, id: String, initial: bool, recursive: bool, options: &[String], out: &mut Vec<Program>, method: bool) -> R<()> {
319 while self.peek().is_some() && !self.at_division(&["ENVIRONMENT", "DATA", "PROCEDURE", "IDENTIFICATION", "ID"]) && !self.at_end_program() {
320 self.at += 1;
321 }
322 let (mut files, mut repository, mut environment) = (Vec::new(), Vec::new(), Environment::default());
323 if self.at_division(&["ENVIRONMENT"]) {
324 (files, repository) = self.environment(&mut environment)?;
325 }
326 self.mnemonics.splice(0..0, environment.mnemonics.iter().cloned());
327 self.debugging |= environment.debugging_mode;
328 let (mut working_storage, mut local_storage, mut linkage) = (Vec::new(), Vec::new(), Vec::new());
329 let mut report_writer = crate::report::ReportWriter::default();
330 let mut declaratives = Declaratives::default();
331 if self.at_division(&["DATA"]) {
332 self.at += 2;
333 self.expect(&Tok::Period, "a period")?;
334 while !self.at_division(&["PROCEDURE", "IDENTIFICATION", "ID"]) && !self.at_end_program() && self.peek().is_some() {
335 if self.data_exec()? {
336 continue;
337 }
338 let section = self.name("a DATA DIVISION section")?;
339 self.expect_word("SECTION")?;
340 self.expect(&Tok::Period, "a period")?;
341 match section.as_str() {
342 "WORKING-STORAGE" => working_storage = self.data_entries()?,
343 "LINKAGE" => linkage = self.data_entries()?,
344 "LOCAL-STORAGE" => local_storage = self.data_entries()?,
345 "FILE" => self.file_section(&mut files)?,
346 "REPORT" => report_writer.reports.extend(self.report_section()?),
347 other => return Err(self.error(format!("the {other} SECTION is not supported yet"))),
348 }
349 }
350 }
351 let (mut using, mut returning) = (Vec::new(), None);
352 let paragraphs = if self.at_division(&["PROCEDURE"]) {
353 self.at += 2;
354 if self.accept_word("USING") {
355 using = self.parameters()?;
356 }
357 if self.accept_word("RETURNING") {
358 returning = Some(self.name("a RETURNING item")?);
359 }
360 self.expect(&Tok::Period, "a period after the PROCEDURE DIVISION header")?;
361 self.procedure_paragraphs(&mut report_writer, &mut declaratives)?
362 } else {
363 Vec::new()
364 };
365 declaratives::debugging_sections_allowed(&declaratives, recursive, method)?;
366 if let Some(f) = files.iter().find(|f| f.assign.is_empty()) {
367 return Err(Error::at(f.pos, format!("{} has no SELECT ... ASSIGN", f.name)));
368 }
369 if self.cics {
370 self.translator_additions(&mut linkage, &mut using)?;
371 }
372 let declares = |entries: &[DataEntry], name: &str| entries.iter().any(|e| e.name.as_deref() == Some(name));
373 if self.dli && !declares(&working_storage, "DIBSTAT") && !declares(&linkage, "DIBSTAT") {
374 working_storage.splice(0..0, system_entries("DLZDIB")?);
375 }
376 let exec_declarations = std::mem::take(&mut self.exec_declarations);
377 let (mut nested, mut contained) = (Vec::new(), Vec::new());
378 while self.at_division(&["IDENTIFICATION", "ID"]) {
379 let first = nested.len();
380 self.program(options, &mut nested)?;
381 contained.extend(nested.get(first).map(|p: &Program| p.id.clone()));
382 }
383 let entry = nested.iter().flat_map(|p: &Program| &p.paragraphs).flat_map(|p| &p.statements).find_map(|s| match s {
384 Stmt::Entry { pos, .. } => Some(*pos),
385 _ => None,
386 });
387 if let Some(pos) = entry {
388 return Err(Error::at(pos, "ENTRY cannot be used in a nested program"));
389 }
390 oo::share_repository(&repository, &mut nested)?;
391 for inner in &mut nested {
392 share_configuration(&environment, &mut inner.environment);
393 }
394 declaratives::contained_programs(&declaratives, &report_writer, &nested)?;
395 if !method && self.at_end_program() && self.word_at(1) == Some("PROGRAM") {
396 self.at += 2;
397 if self.word().is_some() || matches!(self.peek(), Some(Tok::Alnum(_))) {
398 self.at += 1;
399 }
400 self.accept(&Tok::Period);
401 }
402 out.push(Program {
403 id,
404 options: options.to_vec(),
405 initial,
406 recursive,
407 working_storage,
408 local_storage,
409 linkage,
410 using,
411 returning,
412 paragraphs,
413 files,
414 exec_declarations,
415 report_writer,
416 declaratives,
417 oo: oo::program_oo(repository),
418 environment,
419 nested: contained,
420 ..Program::default()
421 });
422 out.extend(nested);
423 Ok(())
424 }
425
426 fn parameters(&mut self) -> R<Vec<Param>> {
428 let (mut using, mut by_value) = (Vec::new(), false);
429 loop {
430 if self.accept_word("BY") {
431 by_value = self.accept_any(&["REFERENCE", "VALUE"]).as_deref() == Some("VALUE");
432 continue;
433 }
434 if !self.starts_ref() {
435 return Ok(using);
436 }
437 using.push(Param { by_value, name: self.name("a LINKAGE item")? });
438 }
439 }
440
441 fn at_end_program(&self) -> bool {
442 self.is_word("END") && matches!(self.word_at(1), Some("PROGRAM" | "METHOD"))
443 }
444
445 fn environment(&mut self, clauses: &mut Environment) -> R<(Vec<FileDecl>, Vec<ClassEntry>)> {
448 let (mut files, mut repository) = (Vec::new(), Vec::new());
449 while self.peek().is_some() && !self.at_division(&["DATA", "PROCEDURE"]) {
450 if self.accept_word("DECIMAL-POINT") {
451 self.accept_word("IS");
452 self.expect_word("COMMA")?;
453 clauses.decimal_point_comma = true;
454 continue;
455 }
456 if self.is_word("CURRENCY") {
457 let pos = self.pos();
458 self.at += 1;
459 let sign = self.currency_sign()?;
460 if clauses.currency.iter().any(|c| c.symbol == sign.symbol) {
461 return Err(Error::at(pos, format!("a second CURRENCY SIGN clause for the currency symbol {:?}", sign.symbol)));
462 }
463 clauses.currency.push(sign);
464 continue;
465 }
466 if self.environment_clause(clauses)? {
467 continue;
468 }
469 if self.is_word("DEBUGGING") && self.word_at(1) == Some("MODE") {
470 self.at += 2;
471 clauses.debugging_mode = true;
472 continue;
473 }
474 if let Some(environment) = self.word().filter(|w| advancing_environment_name(w)).map(str::to_owned) {
475 let at_name = if self.word_at(1) == Some("IS") { 2 } else { 1 };
476 if let Some(name) = self.word_at(at_name).map(str::to_owned) {
477 self.at += at_name + 1;
478 clauses.mnemonics.push((name, environment));
479 continue;
480 }
481 }
482 if self.accept_word("SELECT") {
483 files.push(self.select()?);
484 continue;
485 }
486 if self.accept_word("REPOSITORY") {
487 repository = self.repository()?;
488 continue;
489 }
490 self.at += 1;
491 }
492 Ok((files, repository))
493 }
494
495 fn currency_sign(&mut self) -> R<CurrencySign> {
498 self.accept_word("SIGN");
499 self.accept_word("IS");
500 let pos = self.pos();
501 let value = match self.literal()? {
502 Literal::Alnum(v) if !v.is_empty() => v,
503 Literal::Hex(_) => return Err(Error::at(pos, "a hexadecimal CURRENCY SIGN literal is not supported yet")),
504 _ => return Err(Error::at(pos, "CURRENCY SIGN needs a nonempty alphanumeric literal")),
505 };
506 let with = self.accept_word("WITH");
507 if !(self.is_word("PICTURE") && self.word_at(1) == Some("SYMBOL")) {
508 if with {
509 return Err(self.error("expected PICTURE SYMBOL after WITH"));
510 }
511 let mut chars = value.chars();
512 return match (chars.next(), chars.next()) {
513 (Some(symbol), None) if currency_symbol(symbol) => Ok(CurrencySign { value, symbol }),
514 _ => Err(Error::at(pos, format!("CURRENCY SIGN {value:?} is not one character that can be a PICTURE currency symbol"))),
515 };
516 }
517 self.at += 2;
518 if value.chars().any(|c| c.is_ascii_digit() || matches!(c, '+' | '-' | '.' | ',')) {
519 return Err(Error::at(pos, format!("CURRENCY SIGN {value:?} contains a digit, +, -, . or ,")));
520 }
521 let pos = self.pos();
522 let symbol = match self.literal()? {
523 Literal::Alnum(s) => s,
524 _ => String::new(),
525 };
526 let mut chars = symbol.chars();
527 match (chars.next(), chars.next()) {
528 (Some(symbol), None) if currency_symbol(symbol) => Ok(CurrencySign { value, symbol }),
529 _ => Err(Error::at(pos, format!("PICTURE SYMBOL {symbol:?} is not one character that can be a PICTURE currency symbol"))),
530 }
531 }
532
533 fn select(&mut self) -> R<FileDecl> {
534 let pos = self.pos();
535 let optional = self.accept_word("OPTIONAL");
536 let name = self.name("a file name")?;
537 let mut f = FileDecl {
538 name,
539 assign: String::new(),
540 organization: Organization::Sequential,
541 access: Access::Sequential,
542 record_key: None,
543 alternate_keys: Vec::new(),
544 relative_key: None,
545 optional,
546 status: None,
547 recording: None,
548 record_min: None,
549 record_max: None,
550 record_varying: false,
551 records: Vec::new(),
552 reports: Vec::new(),
553 linage: None,
554 sort: false,
555 pos,
556 };
557 while !self.accept(&Tok::Period) {
558 let clause = self.name("a SELECT clause or a period")?;
559 match clause.as_str() {
560 "ASSIGN" => {
561 self.accept_word("TO");
562 let target = match self.peek().cloned() {
563 Some(Tok::Word(w)) | Some(Tok::Alnum(w)) => w,
564 _ => return Err(self.error("a DD name after ASSIGN")),
565 };
566 self.at += 1;
567 let target = target.to_ascii_uppercase();
568 f.assign = target.rsplit('-').next().filter(|_| target.contains("-S-") || target.starts_with("S-") || target.starts_with("AS-")).unwrap_or(&target).to_owned();
569 }
570 "RECORD" if self.accept_word("DELIMITER") => {
572 self.accept_word("IS");
573 self.name("STANDARD-1 or an assignment-name")?;
574 }
575 "RECORD" if !self.is_word("SEQUENTIAL") => {
576 self.accept_word("KEY");
577 self.accept_word("IS");
578 f.record_key = Some(self.reference()?);
579 }
580 "RELATIVE" if self.is_word("KEY") || self.is_word("IS") || self.starts_ref() && !self.word().is_some_and(|w| SELECT_CLAUSES.contains(&w)) => {
581 self.accept_word("KEY");
582 self.accept_word("IS");
583 f.relative_key = Some(self.reference()?);
584 }
585 "ALTERNATE" => {
586 self.accept_word("RECORD");
587 self.accept_word("KEY");
588 self.accept_word("IS");
589 let key = self.reference()?;
590 let duplicates = self.accept_word("WITH") | self.is_word("DUPLICATES");
591 if duplicates {
592 self.expect_word("DUPLICATES")?;
593 }
594 f.alternate_keys.push((key, duplicates));
595 }
596 "ORGANIZATION" | "LINE" | "RECORD" | "SEQUENTIAL" | "INDEXED" | "RELATIVE" => {
597 if clause == "ORGANIZATION" {
598 self.accept_word("IS");
599 }
600 let first = if clause == "ORGANIZATION" { self.name("an organization")? } else { clause.clone() };
601 f.organization = match first.as_str() {
602 "LINE" => {
603 self.expect_word("SEQUENTIAL")?;
604 Organization::LineSequential
605 }
606 "RECORD" => {
607 self.expect_word("SEQUENTIAL")?;
608 Organization::Sequential
609 }
610 "SEQUENTIAL" => Organization::Sequential,
611 "INDEXED" => Organization::Indexed,
612 "RELATIVE" => Organization::Relative,
613 other => return Err(self.error(format!("ORGANIZATION {other} is not supported yet"))),
614 };
615 }
616 "ACCESS" => {
617 self.accept_word("MODE");
618 self.accept_word("IS");
619 f.access = match self.name("an access mode")?.as_str() {
620 "SEQUENTIAL" => Access::Sequential,
621 "RANDOM" => Access::Random,
622 "DYNAMIC" => Access::Dynamic,
623 other => return Err(self.error(format!("ACCESS MODE {other} is not an access mode"))),
624 };
625 }
626 "FILE" | "STATUS" => {
627 if clause == "FILE" {
628 self.expect_word("STATUS")?;
629 }
630 self.accept_word("IS");
631 f.status = Some(self.reference()?);
632 if self.starts_ref() && !self.word().is_some_and(|w| SELECT_CLAUSES.contains(&w)) {
633 self.reference()?;
634 }
635 }
636 "RESERVE" | "PADDING" => {
637 while self.peek().is_some() && self.peek() != Some(&Tok::Period) && !self.word().is_some_and(|w| SELECT_CLAUSES.contains(&w)) {
638 self.at += 1;
639 }
640 }
641 other => return Err(self.error(format!("{other} is not a SELECT clause ironwork for COBOL supports yet"))),
642 }
643 }
644 Ok(f)
645 }
646
647 fn file_section(&mut self, files: &mut [FileDecl]) -> R<()> {
650 while self.is_word("FD") || self.is_word("SD") {
651 let indicator = self.name("FD or SD")?;
652 let pos = self.pos();
653 let name = self.name("a file name")?;
654 let Some(index) = files.iter().position(|f| f.name == name) else {
655 return Err(Error::at(pos, format!("{indicator} {name} has no SELECT")));
656 };
657 files[index].sort = indicator == "SD";
658 while !self.accept(&Tok::Period) {
659 match self.name("an FD clause or a period")?.as_str() {
660 "RECORDING" => {
661 self.accept_word("MODE");
662 self.accept_word("IS");
663 let mode = self.name("F, V, U or S")?;
664 files[index].recording = mode.chars().next();
665 }
666 "RECORD" => {
667 self.accept_word("CONTAINS");
668 self.accept_word("IS");
669 let varying = self.accept_word("VARYING");
670 if varying {
671 files[index].recording.get_or_insert('V');
672 self.accept_word("IN");
673 self.accept_word("SIZE");
674 self.accept_word("FROM");
675 }
676 let number = |p: &mut Self| -> R<Option<u32>> {
677 match p.peek() {
678 Some(Tok::Number(n)) => {
679 let v = n.parse().map_err(|_| p.error("a record length"))?;
680 p.at += 1;
681 Ok(Some(v))
682 }
683 _ => Ok(None),
684 }
685 };
686 let first = number(self)?;
687 let second = if self.accept_word("TO") { number(self)? } else { None };
688 if varying {
689 files[index].record_varying = true;
690 (files[index].record_min, files[index].record_max) = (first, second);
691 } else if first.is_some() {
692 files[index].record_min = first;
693 files[index].record_max = second.or(first);
694 }
695 self.accept_word("CHARACTERS");
696 if self.accept_word("DEPENDING") {
697 self.accept_word("ON");
698 self.reference()?;
699 }
700 }
701 "REPORT" | "REPORTS" if files[index].sort => return Err(Error::at(pos, format!("SD {name}: a sort or merge file takes no REPORT clause"))),
702 "REPORT" | "REPORTS" => {
703 let names = self.report_names()?;
704 files[index].reports.extend(names);
705 }
706 "LINAGE" => {
707 let linage = self.linage()?;
708 if files[index].linage.is_some() {
709 return Err(Error::at(pos, format!("{indicator} {name}: LINAGE is given twice")));
710 }
711 files[index].linage = (indicator == "FD").then_some(linage);
712 }
713 _ => {
714 while self.peek().is_some() && self.peek() != Some(&Tok::Period) && !self.word().is_some_and(|w| FD_WORDS.contains(&w)) {
715 self.at += 1;
716 }
717 }
718 }
719 }
720 files[index].records = self.data_entries()?;
721 }
722 Ok(())
723 }
724
725 fn linage(&mut self) -> R<Linage> {
727 self.accept_word("IS");
728 let lines = self.linage_value("LINAGE")?;
729 self.accept_word("LINES");
730 let mut linage = Linage { lines, footing: None, top: None, bottom: None };
731 loop {
732 let start = self.at;
733 self.accept_word("WITH");
734 self.accept_word("LINES");
735 self.accept_word("AT");
736 let Some(phrase) = self.accept_any(&["FOOTING", "TOP", "BOTTOM"]) else {
737 self.at = start;
738 return Ok(linage);
739 };
740 if phrase == "FOOTING" {
741 self.accept_word("AT");
742 }
743 let value = Some(self.linage_value(&phrase)?);
744 let slot = match phrase.as_str() {
745 "FOOTING" => &mut linage.footing,
746 "TOP" => &mut linage.top,
747 _ => &mut linage.bottom,
748 };
749 if slot.is_some() {
750 return Err(self.error(format!("LINAGE: {phrase} is given twice")));
751 }
752 *slot = value;
753 }
754 }
755
756 fn linage_value(&mut self, phrase: &str) -> R<LinageValue> {
758 match self.peek() {
759 Some(Tok::Number(n)) if n.bytes().all(|b| b.is_ascii_digit()) => {
760 let n = n.clone();
761 self.at += 1;
762 Ok(LinageValue::Integer(n))
763 }
764 Some(Tok::Number(n)) => Err(self.error(format!("LINAGE: {phrase} {n} is not an unsigned integer"))),
765 Some(Tok::Word(_)) if self.starts_ref() => {
766 let r = self.reference()?;
767 if !r.subscripts.is_empty() || r.refmod.is_some() {
768 return Err(Error::at(r.pos, format!("LINAGE: {phrase} {} takes no subscript or reference modification", r.name)));
769 }
770 Ok(LinageValue::Data(r))
771 }
772 _ => Err(self.error(format!("an integer or a data-name after {phrase}"))),
773 }
774 }
775
776 fn data_exec(&mut self) -> R<bool> {
778 let Some(Tok::Exec(text)) = self.peek().cloned() else { return Ok(false) };
779 let block = self.exec_block(&text, self.pos());
780 self.at += 1;
781 self.accept(&Tok::Period);
782 self.exec_declarations.push(block);
783 Ok(true)
784 }
785
786 fn translator_additions(&mut self, linkage: &mut Vec<DataEntry>, using: &mut Vec<Param>) -> R<()> {
789 let defined = |l: &[DataEntry], n: &str| l.iter().any(|e| e.level == 1 && e.name.as_deref() == Some(n));
790 let mut added = Vec::new();
791 if !defined(linkage, "DFHEIBLK") {
792 added.extend(system_entries("DFHEIBLK")?);
793 }
794 if !defined(linkage, "DFHCOMMAREA") {
795 added.extend(system_text_entries(" 01 DFHCOMMAREA PIC X(1).\n")?);
796 }
797 linkage.splice(0..0, added);
798 for (i, name) in ["DFHEIBLK", "DFHCOMMAREA"].into_iter().enumerate() {
799 if using.get(i).map(|p| p.name.as_str()) != Some(name) {
800 using.insert(i, Param { by_value: false, name: name.into() });
801 }
802 }
803 Ok(())
804 }
805
806 fn exec_block(&mut self, text: &str, pos: Pos) -> ExecBlock {
807 let (kind_word, body) = text.split_once(' ').unwrap_or((text, ""));
808 let kind = match kind_word.to_ascii_uppercase().as_str() {
809 "SQL" => ExecKind::Sql,
810 "CICS" => ExecKind::Cics,
811 "DLI" => ExecKind::Dli,
812 _ => ExecKind::Other,
813 };
814 let words: Vec<String> = body.split_whitespace().map(|w| w.to_ascii_uppercase()).collect();
815 let word = |i: usize| words.get(i).map(String::as_str).unwrap_or("");
816 let mut block = ExecBlock { kind, command: word(0).to_owned(), options: Vec::new(), host_variables: Vec::new(), sql: None, text: text.to_owned(), pos };
817 match kind {
818 ExecKind::Sql => {
819 block.command = match (word(0), word(1), word(2)) {
820 ("DECLARE", _, "CURSOR") => "DECLARE CURSOR".into(),
821 ("DECLARE", _, "TABLE") => "DECLARE TABLE".into(),
822 ("DECLARE", _, "STATEMENT") => "DECLARE STATEMENT".into(),
823 ("BEGIN" | "END", "DECLARE", "SECTION") => format!("{} DECLARE SECTION", word(0)),
824 (first, _, _) => first.into(),
825 };
826 let statement = self.sql.cursors.resolve(crate::sql::parse(body, pos));
827 if let crate::sql::Statement::Whenever { condition, action } = &statement {
828 self.sql.whenever.set(*condition, action.clone());
829 }
830 block.host_variables = match &statement {
831 crate::sql::Statement::Unsupported(_)
832 | crate::sql::Statement::Malformed(_)
833 | crate::sql::Statement::Declaration
834 | crate::sql::Statement::DeclareUnsupported { .. } => host_variables(body, pos),
835 typed => typed.references().into_iter().cloned().collect(),
836 };
837 self.sql.blocks += 1;
838 block.sql = Some(crate::sql::Sql { statement, ordinal: self.sql.blocks, whenever: self.sql.whenever.clone() });
839 }
840 ExecKind::Cics => {
841 self.cics = true;
842 block.options = cics_options(body);
843 if let Some((first, None)) = block.options.first().cloned() {
844 block.command = first.clone();
845 block.options.remove(0);
846 if let Some((second, None)) = block.options.first().cloned()
847 && crate::system::cics_two_word(&first, &second)
848 {
849 block.command = format!("{first} {second}");
850 block.options.remove(0);
851 }
852 }
853 let labels = block.command.starts_with("HANDLE");
854 for (_, arg) in &mut block.options {
855 if let Some(ExecArg::Text(t)) = arg
856 && !labels
857 && let Some(op) = operand_of(t, pos)
858 {
859 *arg = Some(ExecArg::Operand(op));
860 }
861 }
862 }
863 ExecKind::Dli => {
864 self.dli = true;
865 let words: Vec<&str> = body.split_whitespace().collect();
866 let rest = match crate::dli::command(&words) {
867 Some((command, n)) => {
868 block.command = command.name.to_owned();
869 let mut rest = body.trim_start();
870 for _ in 0..n {
871 rest = rest.trim_start().split_once(char::is_whitespace).map_or("", |(_, r)| r);
872 }
873 rest
874 }
875 None => body.trim_start().split_once(char::is_whitespace).map_or("", |(_, r)| r),
876 };
877 block.options = cics_options(rest).into_iter().filter(|(name, arg)| !(name == "USING" && arg.is_none())).collect();
878 for (name, arg) in &mut block.options {
879 let Some(ExecArg::Text(t)) = arg else { continue };
880 if name == "WHERE" {
881 if let Ok(comparisons) = crate::dli::qualification(t) {
882 for (_, _, value) in comparisons {
883 if let Some(Operand::Ref(r)) = operand_of(&value, pos) {
884 block.host_variables.push(r);
885 }
886 }
887 }
888 continue;
889 }
890 let area = t.strip_prefix('(').and_then(|a| a.strip_suffix(')'));
891 let data = match area {
892 Some(area) if crate::dli::NAMED.contains(&name.as_str()) => area.trim(),
893 _ if crate::dli::NAMED.contains(&name.as_str()) => continue,
894 _ => t.as_str(),
895 };
896 if let Some(op) = operand_of(data, pos) {
897 *arg = Some(ExecArg::Operand(op));
898 }
899 }
900 }
901 _ => {}
902 }
903 block
904 }
905
906 fn data_entries(&mut self) -> R<Vec<DataEntry>> {
907 let mut entries = Vec::new();
908 loop {
909 if self.data_exec()? {
910 continue;
911 }
912 let Some((level, pos)) = self.level_number()? else { break };
913 entries.push(self.data_entry(level, pos)?);
914 }
915 Ok(entries)
916 }
917
918 fn level_number(&mut self) -> R<Option<(u8, Pos)>> {
919 let Some(Tok::Number(level)) = self.peek() else { return Ok(None) };
920 let pos = self.pos();
921 let level = level.parse().map_err(|_| self.error("a level number"))?;
922 self.at += 1;
923 Ok(Some((level, pos)))
924 }
925
926 fn picture(&mut self) -> R<String> {
928 self.accept_word("IS");
929 match self.peek() {
930 Some(Tok::Pic(p)) => {
931 let p = p.clone();
932 self.at += 1;
933 Ok(p)
934 }
935 _ => Err(self.error("a PICTURE character-string")),
936 }
937 }
938
939 fn sign_clause(&mut self, first: &str) -> R<SignClause> {
941 let side = if first == "SIGN" {
942 self.accept_word("IS");
943 self.name("LEADING or TRAILING")?
944 } else {
945 first.to_owned()
946 };
947 let position = match side.as_str() {
948 "LEADING" => SignPosition::Leading,
949 "TRAILING" => SignPosition::Trailing,
950 _ => return Err(self.error("LEADING or TRAILING")),
951 };
952 let separate = self.accept_word("SEPARATE");
953 if separate {
954 self.accept_word("CHARACTER");
955 }
956 Ok(SignClause { position, separate })
957 }
958
959 fn blank_when_zero(&mut self) -> R<()> {
961 self.accept_word("WHEN");
962 if self.accept_any(&["ZERO", "ZEROS", "ZEROES"]).is_none() {
963 return Err(self.error("ZERO after BLANK WHEN"));
964 }
965 Ok(())
966 }
967
968 fn data_entry(&mut self, level: u8, pos: Pos) -> R<DataEntry> {
969 let mut e = DataEntry {
970 level,
971 name: None,
972 spelled: None,
973 picture: None,
974 usage: None,
975 value: None,
976 redefines: None,
977 occurs: None,
978 occurs_min: None,
979 depending_on: None,
980 sign: None,
981 justified: false,
982 sync: false,
983 blank_when_zero: false,
984 indexed_by: Vec::new(),
985 keys: Vec::new(),
986 condition_values: Vec::new(),
987 false_value: None,
988 renames: None,
989 object_class: None,
990 pos,
991 };
992 if let Some(w) = self.word()
993 && !is_clause_word(w)
994 {
995 if w != "FILLER" {
996 e.name = Some(w.to_owned());
997 e.spelled = self.tokens[self.at].spelled.clone();
998 }
999 self.at += 1;
1000 }
1001 while !self.accept(&Tok::Period) {
1002 let clause = self.name("a data description clause or a period")?;
1003 match clause.as_str() {
1004 "PIC" | "PICTURE" => e.picture = Some(self.picture()?),
1005 "USAGE" => {
1006 self.accept_word("IS");
1007 let w = self.name("a usage")?;
1008 if w == "OBJECT" {
1009 self.object_reference(&mut e)?;
1010 } else {
1011 e.usage = Some(usage_word(&w).ok_or_else(|| Error::at(pos, format!("USAGE {w} is not supported yet")))?);
1012 }
1013 }
1014 "OBJECT" => self.object_reference(&mut e)?,
1015 "VALUE" | "VALUES" => {
1016 self.accept_word("IS");
1017 self.accept_word("ARE");
1018 if level == 88 {
1019 while self.peek().is_some() && self.peek() != Some(&Tok::Period) && !self.is_word("WHEN") && !self.is_word("FALSE") {
1020 let low = self.literal()?;
1021 let high = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.literal()?) } else { None };
1022 e.condition_values.push((low, high));
1023 }
1024 if self.accept_word("WHEN") {
1025 self.accept_word("SET");
1026 self.accept_word("TO");
1027 self.expect_word("FALSE")?;
1028 self.accept_word("IS");
1029 e.false_value = Some(self.literal()?);
1030 } else if self.accept_word("FALSE") {
1031 self.accept_word("IS");
1032 e.false_value = Some(self.literal()?);
1033 }
1034 } else {
1035 e.value = Some(self.literal()?);
1036 }
1037 }
1038 "REDEFINES" => e.redefines = Some(self.name("the item redefined")?),
1039 "RENAMES" => {
1040 let first = self.reference()?;
1041 let last = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.reference()?) } else { None };
1042 e.renames = Some((first, last));
1043 }
1044 "OCCURS" => {
1045 let count = |p: &mut Self| -> R<u32> {
1046 let n = match p.peek() {
1047 Some(Tok::Number(n)) => n.parse().map_err(|_| p.error("an OCCURS count"))?,
1048 _ => return Err(p.error("an OCCURS count")),
1049 };
1050 p.at += 1;
1051 Ok(n)
1052 };
1053 let mut most = count(self)?;
1054 let mut least = 1;
1055 if self.accept_word("TO") {
1056 least = most;
1057 most = count(self)?;
1058 }
1059 e.occurs = Some(most);
1060 self.accept_word("TIMES");
1061 if self.accept_word("DEPENDING") {
1062 self.accept_word("ON");
1063 e.depending_on = Some(self.reference()?);
1064 e.occurs_min = Some(least);
1065 }
1066 loop {
1067 if let Some(order) = self.accept_any(&["ASCENDING", "DESCENDING"]) {
1068 self.accept_word("KEY");
1069 self.accept_word("IS");
1070 while self.word().is_some_and(|w| !is_clause_word(w) && !matches!(w, "INDEXED" | "ASCENDING" | "DESCENDING")) {
1071 e.keys.push((order == "ASCENDING", self.reference()?));
1072 }
1073 } else if self.accept_word("INDEXED") {
1074 self.accept_word("BY");
1075 while self.word().is_some_and(|w| !is_clause_word(w) && !matches!(w, "ASCENDING" | "DESCENDING")) {
1076 e.indexed_by.push(self.name("an index name")?);
1077 }
1078 } else {
1079 break;
1080 }
1081 }
1082 }
1083 "SIGN" | "LEADING" | "TRAILING" => e.sign = Some(self.sign_clause(&clause)?),
1084 "JUSTIFIED" | "JUST" => {
1085 self.accept_word("RIGHT");
1086 e.justified = true;
1087 }
1088 "SYNC" | "SYNCHRONIZED" => {
1089 self.accept_any(&["LEFT", "RIGHT"]);
1090 e.sync = true;
1091 }
1092 "BLANK" => {
1093 self.blank_when_zero()?;
1094 e.blank_when_zero = true;
1095 }
1096 "GLOBAL" | "EXTERNAL" => {}
1097 other => match usage_word(other) {
1098 Some(u) => e.usage = Some(u),
1099 None => return Err(Error::at(self.tokens[self.at - 1].pos, format!("{other} is not a data description clause ironwork for COBOL supports yet"))),
1100 },
1101 }
1102 }
1103 Ok(e)
1104 }
1105
1106 fn literal(&mut self) -> R<Literal> {
1107 let lit = match self.peek().cloned() {
1108 Some(Tok::Alnum(s)) => Literal::Alnum(s),
1109 Some(Tok::Hex(b)) => Literal::Hex(b),
1110 Some(Tok::National(s)) => Literal::National(s),
1111 Some(Tok::Number(n)) => Literal::Number(n),
1112 Some(Tok::Word(w)) if w == "ALL" => {
1113 self.at += 1;
1114 return Ok(Literal::All(Box::new(self.literal()?)));
1115 }
1116 Some(Tok::Word(w)) => Literal::Figurative(figurative(&w).ok_or_else(|| self.error("a literal"))?),
1117 _ => return Err(self.error("a literal")),
1118 };
1119 self.at += 1;
1120 Ok(lit)
1121 }
1122
1123 fn paragraph_header(&self) -> bool {
1124 self.tokens.get(self.at).is_some_and(|t| t.area_a && (matches!(t.tok, Tok::Word(_)) || digits(&t.tok))) && self.peek_at(1) == Some(&Tok::Period)
1125 }
1126
1127 fn section_header(&self) -> bool {
1128 self.tokens.get(self.at).is_some_and(|t| matches!(t.tok, Tok::Word(_)) || digits(&t.tok)) && self.word_at(0) != Some("EXIT") && self.word_at(1) == Some("SECTION")
1130 }
1131
1132 fn paragraphs(&mut self) -> R<Vec<Paragraph>> {
1133 let mut paragraphs = Vec::new();
1134 while self.peek().is_some() && !self.at_end_program() && !self.at_division(&["IDENTIFICATION", "ID"]) {
1135 if self.is_word("DECLARATIVES") {
1136 return Err(self.error("DECLARATIVES must begin the PROCEDURE DIVISION"));
1137 }
1138 self.procedure_item(&mut paragraphs)?;
1139 }
1140 Ok(paragraphs)
1141 }
1142
1143 fn procedure_item(&mut self, paragraphs: &mut Vec<Paragraph>) -> R<bool> {
1145 if self.section_header() {
1146 let pos = self.pos();
1147 let name = self.procedure_word("a section name")?;
1148 self.at += 1;
1149 let mut priority = 0;
1150 if let Some(Tok::Number(n)) = self.peek() {
1151 priority = n.trim_start_matches('+').parse().ok().filter(|p| *p <= 99).ok_or_else(|| self.error("a priority-number from 0 to 99"))?;
1152 self.at += 1;
1153 }
1154 self.expect(&Tok::Period, "a period after the section header")?;
1155 paragraphs.push(Paragraph { section: Some(name.clone()), name, statements: Vec::new(), is_section: true, priority, pos });
1156 return Ok(true);
1157 }
1158 if self.paragraph_header() {
1159 let pos = self.pos();
1160 let name = self.procedure_word("a paragraph name")?;
1161 self.at += 1;
1162 let (section, priority) = paragraphs.last().map_or((None, 0), |p| (p.section.clone(), p.priority));
1163 paragraphs.push(Paragraph { name, statements: Vec::new(), section, is_section: false, priority, pos });
1164 return Ok(false);
1165 }
1166 if self.accept(&Tok::Period) {
1167 if let Some(p) = paragraphs.last_mut()
1168 && p.statements.last().is_some_and(|s| *s != Stmt::SentenceEnd)
1169 {
1170 p.statements.push(Stmt::SentenceEnd);
1171 }
1172 return Ok(false);
1173 }
1174 let block = self.block(&[])?;
1175 if block.is_empty() {
1176 return Err(self.error("a statement"));
1177 }
1178 if paragraphs.is_empty() {
1179 paragraphs.push(Paragraph { name: String::new(), statements: Vec::new(), section: None, is_section: false, priority: 0, pos: self.pos() });
1180 }
1181 paragraphs.last_mut().unwrap().statements.extend(block);
1182 Ok(false)
1183 }
1184
1185 fn block(&mut self, stops: &[&str]) -> R<Vec<Stmt>> {
1187 let mut out = Vec::new();
1188 while let Some(tok) = self.peek() {
1189 if *tok == Tok::Period || self.paragraph_header() || self.section_header() || self.word().is_some_and(|w| stops.contains(&w)) {
1190 break;
1191 }
1192 if let Some(Tok::Exec(text)) = self.peek().cloned() {
1193 let block = self.exec_block(&text, self.pos());
1194 self.at += 1;
1195 out.push(Stmt::Exec(Box::new(block)));
1196 continue;
1197 }
1198 if !self.word().is_some_and(|w| VERBS.contains(&w)) {
1199 break;
1200 }
1201 out.push(self.statement()?);
1202 }
1203 Ok(out)
1204 }
1205
1206 fn statement(&mut self) -> R<Stmt> {
1207 let pos = self.pos();
1208 let verb = self.name("a statement")?;
1209 Ok(match verb.as_str() {
1210 "MOVE" => {
1211 if self.accept_any(&["CORRESPONDING", "CORR"]).is_some() {
1212 return self.corresponding(CorrespondingVerb::Move, pos);
1213 }
1214 let from = self.operand()?;
1215 self.expect_word("TO")?;
1216 let to = self.refs()?;
1217 Stmt::Move { from, to, pos }
1218 }
1219 "COMPUTE" => {
1220 let targets = self.targets()?;
1221 if !self.accept(&Tok::Eq) {
1222 self.expect_word("EQUAL")?;
1223 }
1224 let expr = self.expr()?;
1225 let size_error = self.size_error()?;
1226 self.accept_word("END-COMPUTE");
1227 Stmt::Compute { targets, expr, size_error, pos }
1228 }
1229 "ADD" if self.accept_any(&["CORRESPONDING", "CORR"]).is_some() => self.corresponding(CorrespondingVerb::Add, pos)?,
1230 "SUBTRACT" if self.accept_any(&["CORRESPONDING", "CORR"]).is_some() => self.corresponding(CorrespondingVerb::Subtract, pos)?,
1231 "ADD" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" => Stmt::Arith(Box::new(self.arith(&verb, pos)?)),
1232 "IF" => {
1233 let cond = self.cond()?;
1234 self.accept_word("THEN");
1235 let then = self.block(&["ELSE", "END-IF"])?;
1236 let otherwise = if self.accept_word("ELSE") { self.block(&["END-IF"])? } else { Vec::new() };
1237 self.accept_word("END-IF");
1238 Stmt::If { cond, then, otherwise, pos }
1239 }
1240 "PERFORM" => self.perform(pos)?,
1241 "DISPLAY" => {
1242 let mut items = Vec::new();
1243 let no_advancing_ahead = |p: &Self| p.is_word("NO") && p.word_at(1) == Some("ADVANCING");
1244 while self.starts_operand() && !no_advancing_ahead(self) {
1245 items.push(self.operand()?);
1246 }
1247 if self.accept_word("UPON") {
1248 self.name("a mnemonic name")?;
1249 }
1250 let no_advancing = self.accept_word("WITH") | no_advancing_ahead(self);
1251 if no_advancing {
1252 self.expect_word("NO")?;
1253 self.expect_word("ADVANCING")?;
1254 if self.is_word("UPON") {
1255 return Err(self.error("the end of DISPLAY: Enterprise COBOL takes UPON before WITH NO ADVANCING"));
1256 }
1257 }
1258 self.accept_word("END-DISPLAY");
1259 Stmt::Display { items, no_advancing, pos }
1260 }
1261 "INITIALIZE" => Stmt::Initialize { targets: self.refs()?, pos },
1262 "CALL" => Stmt::Call(Box::new(self.call(pos)?)),
1263 "INVOKE" => Stmt::Invoke(Box::new(self.invoke(pos)?)),
1264 "JSON" if self.accept_word("GENERATE") => Stmt::JsonGenerate(Box::new(self.json_generate(pos)?)),
1265 "JSON" if self.accept_word("PARSE") => Stmt::JsonParse(Box::new(self.json_parse(pos)?)),
1266 "XML" if self.accept_word("PARSE") => Stmt::XmlParse(Box::new(self.xml_parse(pos)?)),
1267 "XML" if self.accept_word("GENERATE") => Stmt::XmlGenerate(Box::new(self.xml_generate(pos)?)),
1268 "CANCEL" => {
1269 let mut targets = Vec::new();
1270 while self.starts_operand() {
1271 targets.push(self.operand()?);
1272 }
1273 if targets.is_empty() {
1274 return Err(self.error("a program to CANCEL"));
1275 }
1276 Stmt::Cancel { targets, pos }
1277 }
1278 "SET" => Stmt::Set { set: self.set()?, pos },
1279 "STRING" => Stmt::String(Box::new(self.string(pos)?)),
1280 "UNSTRING" => Stmt::Unstring(Box::new(self.unstring(pos)?)),
1281 "INSPECT" => Stmt::Inspect(Box::new(self.inspect(pos)?)),
1282 "SEARCH" => Stmt::Search(Box::new(self.search(pos)?)),
1283 "SORT" | "MERGE" | "RELEASE" | "RETURN" => Stmt::Sorting(Box::new(self.sorting(&verb, pos)?)),
1284 "NEXT" => {
1285 self.expect_word("SENTENCE")?;
1286 Stmt::NextSentence
1287 }
1288 "ACCEPT" => {
1289 let target = self.reference()?;
1290 if self.is_word("FROM") && self.word_at(1) == Some("ENVIRONMENT") {
1291 return Err(Error::at(pos, "ACCEPT ... FROM ENVIRONMENT is GnuCOBOL's, not Enterprise COBOL's"));
1292 }
1293 let from = if self.accept_word("FROM") {
1294 match self.name("SYSIN, DATE, DAY, DAY-OF-WEEK or TIME")?.as_str() {
1295 "DATE" => AcceptFrom::Date { four_digit_year: self.accept_word("YYYYMMDD") },
1296 "DAY" => AcceptFrom::Day { four_digit_year: self.accept_word("YYYYDDD") },
1297 "DAY-OF-WEEK" => AcceptFrom::DayOfWeek,
1298 "TIME" => AcceptFrom::Time,
1299 _ => AcceptFrom::Sysin,
1300 }
1301 } else {
1302 AcceptFrom::Sysin
1303 };
1304 self.accept_word("END-ACCEPT");
1305 Stmt::Accept { target, from, pos }
1306 }
1307 "OPEN" => {
1308 let mut files = Vec::new();
1309 while let Some(mode) = self.accept_any(&["INPUT", "OUTPUT", "EXTEND", "I-O"]) {
1310 let mode = match mode.as_str() {
1311 "INPUT" => OpenMode::Input,
1312 "OUTPUT" => OpenMode::Output,
1313 "EXTEND" => OpenMode::Extend,
1314 _ => OpenMode::InputOutput,
1315 };
1316 while self.starts_ref() {
1317 files.push((mode, self.name("a file name")?));
1318 self.accept_any(&["REVERSED"]);
1319 if self.accept_word("WITH") {
1320 self.expect_word("NO")?;
1321 self.expect_word("REWIND")?;
1322 }
1323 }
1324 }
1325 if files.is_empty() {
1326 return Err(self.error("INPUT, OUTPUT, EXTEND or I-O and a file"));
1327 }
1328 Stmt::Open { files, pos }
1329 }
1330 "CLOSE" => {
1331 let mut files = Vec::new();
1332 while self.starts_ref() {
1333 let name = self.name("a file name")?;
1334 let closing = if self.accept_any(&["REEL", "UNIT"]).is_some() {
1335 if self.accept_word("FOR") {
1336 self.expect_word("REMOVAL")?;
1337 } else if self.accept_word("WITH") || self.is_word("NO") {
1338 self.expect_word("NO")?;
1339 self.expect_word("REWIND")?;
1340 }
1341 Some(Closing::Volume)
1342 } else {
1343 self.accept_word("WITH");
1344 if self.accept_word("LOCK") {
1345 Some(Closing::Lock)
1346 } else if self.accept_word("NO") {
1347 self.expect_word("REWIND")?;
1348 Some(Closing::NoRewind)
1349 } else {
1350 None
1351 }
1352 };
1353 files.push((name, closing));
1354 }
1355 Stmt::Close { files, pos }
1356 }
1357 "READ" => {
1358 let file = self.name("a file name")?;
1359 let previous = self.accept_word("PREVIOUS");
1360 let next = previous || self.accept_word("NEXT");
1361 self.accept_word("RECORD");
1362 let into = if self.accept_word("INTO") { Some(self.reference()?) } else { None };
1363 let key = if self.accept_word("KEY") {
1364 self.accept_word("IS");
1365 Some(self.reference()?)
1366 } else {
1367 None
1368 };
1369 let [at_end, invalid] = self.on_phrases(&["AT", "END", "INVALID"], &["END-READ"], |p| {
1370 if p.accept_word("INVALID") {
1371 p.accept_word("KEY");
1372 return Ok(1);
1373 }
1374 p.accept_word("AT");
1375 p.expect_word("END").map(|()| 0)
1376 })?;
1377 self.accept_word("END-READ");
1378 Stmt::Read(Box::new(ReadStmt { file, next, previous, into, key, at_end, invalid, pos }))
1379 }
1380 "REWRITE" => {
1381 let record = self.reference()?;
1382 let from = if self.accept_word("FROM") { Some(self.operand()?) } else { None };
1383 let invalid = self.invalid_key("END-REWRITE")?;
1384 Stmt::Rewrite { record, from, invalid, pos }
1385 }
1386 "DELETE" => {
1387 let file = self.name("a file name")?;
1388 self.accept_word("RECORD");
1389 let invalid = self.invalid_key("END-DELETE")?;
1390 Stmt::Delete { file, invalid, pos }
1391 }
1392 "START" => {
1393 let file = self.name("a file name")?;
1394 let key = if self.accept_word("KEY") {
1395 self.accept_word("IS");
1396 let op = if self.accept_word("NOT") {
1397 match self.relop()? {
1398 Some(RelOp::Lt) => RelOp::Ge,
1399 _ => return Err(self.error("NOT < in START KEY")),
1400 }
1401 } else {
1402 self.relop()?.ok_or_else(|| self.error("a relation after START KEY"))?
1403 };
1404 Some((op, self.reference()?))
1405 } else {
1406 None
1407 };
1408 let invalid = self.invalid_key("END-START")?;
1409 Stmt::Start { file, key, invalid, pos }
1410 }
1411 "WRITE" => {
1412 let record = self.reference()?;
1413 let from = if self.accept_word("FROM") { Some(self.operand()?) } else { None };
1414 let mut advancing = None;
1415 if let Some(side) = self.accept_any(&["BEFORE", "AFTER"]) {
1416 let before = side == "BEFORE";
1417 self.accept_word("ADVANCING");
1418 let mnemonic = self.word().and_then(|w| self.mnemonics.iter().find(|(name, _)| name == w)).cloned();
1419 advancing = Some(if self.accept_word("PAGE") {
1420 Advancing::Page { before }
1421 } else if let Some((name, environment)) = mnemonic {
1422 self.at += 1;
1423 Advancing::Mnemonic { before, name, environment }
1424 } else {
1425 let count = self.expr()?;
1426 self.accept_any(&["LINE", "LINES"]);
1427 Advancing::Lines { before, count }
1428 });
1429 }
1430 let eop_ahead = |p: &Self, i: usize| {
1431 let at = usize::from(p.word_at(i) == Some("AT"));
1432 matches!(p.word_at(i + at), Some("END-OF-PAGE" | "EOP"))
1433 };
1434 let [end_of_page, invalid] = self.phrases_opening(|p, i| eop_ahead(p, i) || p.word_at(i) == Some("INVALID"), &["END-WRITE"], |p| {
1435 if p.accept_word("INVALID") {
1436 p.accept_word("KEY");
1437 return Ok(1);
1438 }
1439 p.accept_word("AT");
1440 p.at += 1;
1441 Ok(0)
1442 })?;
1443 self.accept_word("END-WRITE");
1444 Stmt::Write { record, from, advancing, invalid, end_of_page, pos }
1445 }
1446 "GO" => {
1447 self.accept_word("TO");
1448 if self.peek() == Some(&Tok::Period) {
1449 return Ok(Stmt::GoTo { target: None, pos });
1450 }
1451 let target = self.proc_name()?;
1452 if !self.starts_proc_name() && !self.is_word("DEPENDING") {
1453 return Ok(Stmt::GoTo { target: Some(target), pos });
1454 }
1455 let mut targets = vec![target];
1456 while self.starts_proc_name() && !self.is_word("DEPENDING") {
1457 targets.push(self.proc_name()?);
1458 }
1459 self.expect_word("DEPENDING")?;
1460 self.accept_word("ON");
1461 Stmt::GoToDepending { targets, on: self.reference()?, pos }
1462 }
1463 "ALTER" => {
1464 let mut pairs = Vec::new();
1465 loop {
1466 let paragraph = self.proc_name()?;
1467 self.expect_word("TO")?;
1468 if self.accept_word("PROCEED") {
1469 self.expect_word("TO")?;
1470 }
1471 pairs.push((paragraph, self.proc_name()?));
1472 if !self.starts_proc_name() {
1473 break;
1474 }
1475 }
1476 Stmt::Alter { pairs, pos }
1477 }
1478 "ENTRY" => {
1479 let Some(Tok::Alnum(name)) = self.peek().cloned() else {
1480 return Err(self.error("an alphanumeric literal naming the entry point"));
1481 };
1482 self.at += 1;
1483 let using = if self.accept_word("USING") { self.parameters()? } else { Vec::new() };
1484 Stmt::Entry { name: name.to_ascii_uppercase(), using, pos }
1485 }
1486 "EVALUATE" => self.evaluate(pos)?,
1487 "INITIATE" | "GENERATE" | "TERMINATE" | "SUPPRESS" => Stmt::Report(Box::new(self.report_statement(&verb, pos)?)),
1488 "GOBACK" => Stmt::Goback { pos },
1489 "STOP" if self.accept_word("RUN") => Stmt::StopRun { pos },
1490 "STOP" if self.starts_operand() && !self.starts_ref() => Stmt::Display { items: vec![self.operand()?], no_advancing: false, pos },
1492 "STOP" => return Err(self.error("RUN or a literal after STOP")),
1493 "CONTINUE" => Stmt::Continue,
1494 "EXIT" => match self.accept_any(&["PROGRAM", "PARAGRAPH", "SECTION", "PERFORM", "METHOD"]).as_deref() {
1495 Some("PROGRAM") => Stmt::ExitProgram { pos },
1496 Some("METHOD") => Stmt::ExitMethod { pos },
1497 Some("PARAGRAPH") => Stmt::Exit { kind: ExitKind::Paragraph, pos },
1498 Some("SECTION") => Stmt::Exit { kind: ExitKind::Section, pos },
1499 Some(_) if self.accept_word("CYCLE") => Stmt::Exit { kind: ExitKind::PerformCycle, pos },
1500 Some(_) => Stmt::Exit { kind: ExitKind::Perform, pos },
1501 None => Stmt::Exit { kind: ExitKind::Plain, pos },
1502 },
1503 other => return Err(Error::at(pos, format!("{other} is not a statement ironwork for COBOL supports yet"))),
1504 })
1505 }
1506
1507 fn size_error(&mut self) -> R<Option<SizeError>> {
1508 let [h] = self.on_phrases(&["ON", "SIZE"], &["END-COMPUTE", "END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE"], |p| {
1509 p.accept_word("ON");
1510 p.expect_word("SIZE")?;
1511 p.expect_word("ERROR").map(|()| 0)
1512 })?;
1513 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() }))
1514 }
1515
1516 fn corresponding(&mut self, verb: CorrespondingVerb, pos: Pos) -> R<Stmt> {
1519 let from = self.reference()?;
1520 self.expect_word(if verb == CorrespondingVerb::Subtract { "FROM" } else { "TO" })?;
1521 let to = self.reference()?;
1522 let (rounded, size_error) = match verb {
1523 CorrespondingVerb::Move => (false, None),
1524 _ => (self.accept_word("ROUNDED"), self.size_error()?),
1525 };
1526 if self.starts_ref() {
1527 return Err(Error::at(self.pos(), "CORRESPONDING takes one receiving group"));
1528 }
1529 self.accept_any(&["END-ADD", "END-SUBTRACT"]);
1530 Ok(Stmt::Corresponding(Box::new(Corresponding { verb, from, to, rounded, size_error, pos })))
1531 }
1532
1533 fn targets(&mut self) -> R<Vec<Target>> {
1534 let mut out = Vec::new();
1535 while self.starts_ref() {
1536 let r = self.reference()?;
1537 out.push(Target { r, rounded: self.accept_word("ROUNDED") });
1538 }
1539 if out.is_empty() {
1540 return Err(self.error("a receiving item"));
1541 }
1542 Ok(out)
1543 }
1544
1545 fn refs(&mut self) -> R<Vec<Ref>> {
1546 let mut out = Vec::new();
1547 while self.starts_ref() {
1548 out.push(self.reference()?);
1549 }
1550 if out.is_empty() {
1551 return Err(self.error("a data name"));
1552 }
1553 Ok(out)
1554 }
1555
1556 fn operands_until(&mut self, stops: &[&str]) -> R<Vec<Expr>> {
1557 let mut out = Vec::new();
1558 while self.starts_operand() && !self.word().is_some_and(|w| stops.contains(&w)) {
1559 out.push(Expr::Operand(self.operand()?));
1560 }
1561 if out.is_empty() {
1562 return Err(self.error("an operand"));
1563 }
1564 Ok(out)
1565 }
1566
1567 fn arith(&mut self, verb: &str, pos: Pos) -> R<Arith> {
1568 let sum = |mut es: Vec<Expr>| {
1569 let first = es.remove(0);
1570 es.into_iter().fold(first, |acc, e| Expr::Bin(Box::new(acc), BinOp::Add, Box::new(e)))
1571 };
1572 let of = |t: &Target| Expr::Operand(Operand::Ref(t.r.clone()));
1573 let bin = |a: Expr, op: BinOp, b: Expr| Expr::Bin(Box::new(a), op, Box::new(b));
1574 let mut remainder = None;
1575 let (verb, computations) = match verb {
1576 "ADD" => {
1577 let addends = sum(self.operands_until(&["TO", "GIVING"])?);
1578 if self.accept_word("TO") {
1579 if self.words_ahead_include("GIVING") {
1580 let to = sum(self.operands_until(&["GIVING"])?);
1581 self.expect_word("GIVING")?;
1582 let targets = self.targets()?;
1583 let total = bin(addends, BinOp::Add, to);
1584 (ArithVerb::Add, targets.into_iter().map(|t| (t, total.clone())).collect())
1585 } else {
1586 let targets = self.targets()?;
1587 (ArithVerb::Add, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Add, addends.clone()))).collect())
1588 }
1589 } else {
1590 self.expect_word("GIVING")?;
1591 let targets = self.targets()?;
1592 (ArithVerb::Add, targets.into_iter().map(|t| (t, addends.clone())).collect())
1593 }
1594 }
1595 "SUBTRACT" => {
1596 let subtrahend = sum(self.operands_until(&["FROM"])?);
1597 self.expect_word("FROM")?;
1598 if self.words_ahead_include("GIVING") {
1599 let minuend = sum(self.operands_until(&["GIVING"])?);
1600 self.expect_word("GIVING")?;
1601 let targets = self.targets()?;
1602 let diff = bin(minuend, BinOp::Sub, subtrahend);
1603 (ArithVerb::Subtract, targets.into_iter().map(|t| (t, diff.clone())).collect())
1604 } else {
1605 let targets = self.targets()?;
1606 (ArithVerb::Subtract, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Sub, subtrahend.clone()))).collect())
1607 }
1608 }
1609 "MULTIPLY" => {
1610 let a = Expr::Operand(self.operand()?);
1611 self.expect_word("BY")?;
1612 if self.words_ahead_include("GIVING") {
1613 let b = Expr::Operand(self.operand()?);
1614 self.expect_word("GIVING")?;
1615 let targets = self.targets()?;
1616 (ArithVerb::Multiply, targets.into_iter().map(|t| (t, bin(a.clone(), BinOp::Mul, b.clone()))).collect())
1617 } else {
1618 let targets = self.targets()?;
1619 (ArithVerb::Multiply, targets.into_iter().map(|t| (t.clone(), bin(a.clone(), BinOp::Mul, of(&t)))).collect())
1620 }
1621 }
1622 _ => {
1623 let first = Expr::Operand(self.operand()?);
1624 let into = match self.accept_any(&["INTO", "BY"]).as_deref() {
1625 Some("INTO") => true,
1626 Some(_) => false,
1627 None => return Err(self.error("INTO or BY")),
1628 };
1629 if !into || self.words_ahead_include("GIVING") {
1630 let second = Expr::Operand(self.operand()?);
1631 let (dividend, divisor) = if into { (second, first) } else { (first, second) };
1632 self.expect_word("GIVING")?;
1633 let targets = self.targets()?;
1634 if self.accept_word("REMAINDER") {
1635 let r = self.reference()?;
1636 remainder = Some((Target { r, rounded: false }, dividend.clone(), divisor.clone()));
1637 }
1638 (ArithVerb::Divide, targets.into_iter().map(|t| (t, bin(dividend.clone(), BinOp::Div, divisor.clone()))).collect())
1639 } else {
1640 let targets = self.targets()?;
1641 (ArithVerb::Divide, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Div, first.clone()))).collect())
1642 }
1643 }
1644 };
1645 let size_error = self.size_error()?;
1646 self.accept_any(&["END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE"]);
1647 Ok(Arith { verb, computations, remainder, size_error, pos })
1648 }
1649
1650 fn words_ahead_include(&self, word: &str) -> bool {
1652 self.tokens[self.at..]
1653 .iter()
1654 .take_while(|t| t.tok != Tok::Period && !matches!(&t.tok, Tok::Word(w) if VERBS.contains(&w.as_str()) && w != word))
1655 .any(|t| matches!(&t.tok, Tok::Word(w) if w == word))
1656 }
1657
1658 fn perform(&mut self, pos: Pos) -> R<Stmt> {
1659 let named = (self.word().is_some_and(|w| !VERBS.contains(&w) && !PHRASE_WORDS.contains(&w) && w != "TEST") || self.peek().is_some_and(digits))
1660 && !self.times_ahead();
1661 if named {
1662 let from = self.proc_name()?;
1663 let thru = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.proc_name()?) } else { None };
1664 let repeat = self.repeat()?;
1665 return Ok(Stmt::PerformProc { from, thru, repeat, pos });
1666 }
1667 let repeat = self.repeat()?;
1668 if matches!(&repeat, Loop::Varying { after, .. } if !after.is_empty()) {
1669 return Err(Error::at(pos, "an inline PERFORM cannot have AFTER phrases: Enterprise COBOL takes them only when PERFORM names a procedure"));
1670 }
1671 let body = self.block(&["END-PERFORM"])?;
1672 self.expect_word("END-PERFORM")?;
1673 Ok(Stmt::PerformInline { body, repeat, pos })
1674 }
1675
1676 fn on_phrases<const N: usize>(&mut self, starts: &[&str], ends: &[&str], head: impl Fn(&mut Self) -> R<usize>) -> R<[Handlers; N]> {
1678 let opens = |p: &Self, i: usize| match p.word_at(i) {
1680 Some(w @ ("ON" | "AT")) if starts.contains(&w) => p.word_at(i + 1).is_some_and(|k| k != w && starts.contains(&k)),
1681 Some(w) => starts.contains(&w),
1682 None => false,
1683 };
1684 self.phrases_opening(opens, ends, head)
1685 }
1686
1687 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]> {
1689 let stops = [&["NOT"][..], ends].concat();
1690 let mut handlers = std::array::from_fn(|_| Handlers::default());
1691 loop {
1692 let negated = self.is_word("NOT") && opens(self, 1);
1693 if !negated && !opens(self, 0) {
1694 return Ok(handlers);
1695 }
1696 self.at += usize::from(negated);
1697 let h = &mut handlers[head(self)?];
1698 let body = self.block(&stops)?;
1699 if negated { h.not_on = Some(body) } else { h.on = Some(body) }
1700 }
1701 }
1702
1703 fn invalid_key(&mut self, end: &str) -> R<Handlers> {
1705 let [h] = self.on_phrases(&["INVALID"], &[end], |p| {
1706 p.at += 1;
1707 p.accept_word("KEY");
1708 Ok(0)
1709 })?;
1710 self.accept_word(end);
1711 Ok(h)
1712 }
1713
1714 fn json_operand_follows(&self) -> bool {
1715 self.starts_ref() && !self.word().is_some_and(|w| JSON_PHRASES.contains(&w))
1716 }
1717
1718 fn figurative_list(&mut self) -> R<Vec<Figurative>> {
1719 let mut out = Vec::new();
1720 loop {
1721 let word = self.name("a figurative constant")?;
1722 out.push(figurative(&word).filter(|f| !matches!(f, Figurative::Quote | Figurative::Null)).ok_or_else(|| self.error("ZERO, SPACE, LOW-VALUE or HIGH-VALUE"))?);
1723 if !self.accept_word("OR") {
1724 return Ok(out);
1725 }
1726 }
1727 }
1728
1729 fn marker(&mut self, inside: bool) -> R<(Marker, Option<Ref>)> {
1731 if self.starts_ref() {
1732 return Ok((Marker::Condition(self.reference()?), None));
1733 }
1734 let literal = self.literal()?;
1735 let item = if inside {
1736 self.expect_word("IN")?;
1737 Some(self.reference()?)
1738 } else {
1739 None
1740 };
1741 Ok((Marker::Literal(literal), item))
1742 }
1743
1744 fn json_generate(&mut self, pos: Pos) -> R<JsonGenerate> {
1745 let receiver = self.reference()?;
1746 self.expect_word("FROM")?;
1747 let from = self.reference()?;
1748 let mut g = JsonGenerate { receiver, from, count: None, names: Vec::new(), suppress: Vec::new(), converting: Vec::new(), indicating: Vec::new(), encoding: None, on_exception: None, not_on_exception: None, pos };
1749 loop {
1750 if self.accept_word("COUNT") {
1751 self.accept_word("IN");
1752 g.count = Some(self.reference()?);
1753 } else if self.accept_word("INDICATING") {
1754 loop {
1755 let item = self.reference()?;
1756 self.accept_word("IS");
1757 self.accept_word("JSON");
1758 self.expect_word("NULL")?;
1759 self.expect_word("USING")?;
1760 let (marker, indicator) = self.marker(true)?;
1761 g.indicating.push(NullIndicator { item, marker, indicator });
1762 if !self.accept_word("ALSO") {
1763 break;
1764 }
1765 }
1766 } else if self.accept_word("ENCODING") {
1767 g.encoding = Some(if self.accept_word("FROM") {
1768 self.expect_word("CODEPAGE")?;
1769 Encoding::FromCodepage
1770 } else {
1771 Encoding::Ccsid(self.operand()?)
1772 });
1773 } else if self.accept_word("NAME") {
1774 self.accept_word("OF");
1775 loop {
1776 let item = self.reference()?;
1777 self.accept_word("IS");
1778 let name = if self.accept_word("OMITTED") { None } else { Some(self.literal()?) };
1779 g.names.push((item, name));
1780 if !self.json_operand_follows() {
1781 break;
1782 }
1783 }
1784 } else if self.accept_word("SUPPRESS") {
1785 loop {
1786 if self.accept_word("EVERY") {
1787 let numeric = match self.accept_any(&["NUMERIC", "NONNUMERIC"]).as_deref() {
1788 Some("NUMERIC") => Some(true),
1789 Some(_) => Some(false),
1790 None => None,
1791 };
1792 self.expect_word("WHEN")?;
1793 g.suppress.push(Suppression::Every { numeric, form: None, when: self.figurative_list()? });
1794 } else {
1795 let item = self.reference()?;
1796 let when = if self.accept_word("WHEN") { self.figurative_list()? } else { Vec::new() };
1797 g.suppress.push(Suppression::Item { item, when });
1798 }
1799 if !(self.is_word("EVERY") || self.json_operand_follows()) {
1800 break;
1801 }
1802 }
1803 } else if self.accept_word("CONVERTING") {
1804 loop {
1805 let item = self.reference()?;
1806 self.expect_word("TO")?;
1807 self.accept_word("JSON");
1808 let conversion = if self.accept_word("NULL") {
1809 self.expect_word("USING")?;
1810 let [f] = self.figurative_list()?[..] else { return Err(self.error("one figurative constant after USING")) };
1811 JsonConversion::Null(f)
1812 } else {
1813 if self.accept_any(&["BOOLEAN", "BOOL"]).is_none() {
1814 return Err(self.error("BOOLEAN or NULL"));
1815 }
1816 self.expect_word("USING")?;
1817 JsonConversion::Boolean(self.marker(false)?.0)
1818 };
1819 g.converting.push((item, conversion));
1820 if !self.accept_word("ALSO") {
1821 break;
1822 }
1823 }
1824 } else {
1825 break;
1826 }
1827 }
1828 let [h] = self.on_phrases(&["ON", "EXCEPTION"], &["END-JSON"], |p| {
1829 p.accept_word("ON");
1830 p.expect_word("EXCEPTION")?;
1831 Ok(0)
1832 })?;
1833 self.accept_word("END-JSON");
1834 (g.on_exception, g.not_on_exception) = (h.on, h.not_on);
1835 Ok(g)
1836 }
1837
1838 fn flag(&mut self, inside: bool) -> R<(Flag, Option<Ref>)> {
1841 self.expect_word("USING")?;
1842 if self.starts_ref() {
1843 let first = self.reference()?;
1844 if self.accept_word("AND") {
1845 return Ok((Flag::Conditions(first, self.reference()?), None));
1846 }
1847 return Ok((Flag::Condition(first), None));
1848 }
1849 let on = self.literal()?;
1850 self.expect_word("AND")?;
1851 let off = self.literal()?;
1852 let item = if inside {
1853 self.expect_word("IN")?;
1854 Some(self.reference()?)
1855 } else {
1856 None
1857 };
1858 Ok((Flag::Literals(on, off), item))
1859 }
1860
1861 fn json_parse(&mut self, pos: Pos) -> R<JsonParse> {
1862 let source = self.reference()?;
1863 self.expect_word("INTO")?;
1864 let into = self.reference()?;
1865 let mut j = JsonParse {
1866 source,
1867 into,
1868 detail: false,
1869 ignoring: Vec::new(),
1870 indicating: Vec::new(),
1871 encoding: None,
1872 names: Vec::new(),
1873 suppress: Vec::new(),
1874 converting: Vec::new(),
1875 on_exception: None,
1876 not_on_exception: None,
1877 pos,
1878 };
1879 loop {
1880 if self.accept_word("WITH") || self.is_word("DETAIL") {
1881 self.expect_word("DETAIL")?;
1882 j.detail = true;
1883 } else if self.accept_word("IGNORING") {
1884 loop {
1885 self.accept_word("JSON");
1886 self.expect_word("NULL")?;
1887 self.expect_word("FOR")?;
1888 j.ignoring.push(if self.accept_word("ALL") { None } else { Some(self.reference()?) });
1889 if !self.accept_word("ALSO") {
1890 break;
1891 }
1892 }
1893 } else if self.accept_word("INDICATING") {
1894 loop {
1895 let item = self.reference()?;
1896 self.accept_word("IS");
1897 self.accept_word("JSON");
1898 self.expect_word("NULL")?;
1899 let (flag, indicator) = self.flag(true)?;
1900 j.indicating.push((item, flag, indicator));
1901 if !self.accept_word("ALSO") {
1902 break;
1903 }
1904 }
1905 } else if self.accept_word("ENCODING") {
1906 j.encoding = Some(if self.accept_word("FROM") {
1907 self.expect_word("CODEPAGE")?;
1908 Encoding::FromCodepage
1909 } else {
1910 Encoding::Ccsid(self.operand()?)
1911 });
1912 } else if self.accept_word("NAME") {
1913 self.accept_word("OF");
1914 loop {
1915 let item = self.reference()?;
1916 self.accept_word("IS");
1917 let name = if self.accept_word("OMITTED") { None } else { Some(self.literal()?) };
1918 j.names.push((item, name));
1919 if !self.json_operand_follows() {
1920 break;
1921 }
1922 }
1923 } else if self.accept_word("SUPPRESS") {
1924 loop {
1925 j.suppress.push(self.reference()?);
1926 if !self.json_operand_follows() {
1927 break;
1928 }
1929 }
1930 } else if self.accept_word("CONVERTING") {
1931 loop {
1932 let item = self.reference()?;
1933 self.expect_word("FROM")?;
1934 self.accept_word("JSON");
1935 let conversion = if self.accept_word("NULL") {
1936 self.expect_word("USING")?;
1937 let [f] = self.figurative_list()?[..] else { return Err(self.error("one figurative constant after USING")) };
1938 ParseConversion::Null(f)
1939 } else {
1940 if self.accept_any(&["BOOLEAN", "BOOL"]).is_none() {
1941 return Err(self.error("BOOLEAN or NULL"));
1942 }
1943 ParseConversion::Boolean(Box::new(self.flag(false)?.0))
1944 };
1945 j.converting.push((item, conversion));
1946 if !self.accept_word("ALSO") {
1947 break;
1948 }
1949 }
1950 } else {
1951 break;
1952 }
1953 }
1954 let [h] = self.on_phrases(&["ON", "EXCEPTION"], &["END-JSON"], |p| {
1955 p.accept_word("ON");
1956 p.expect_word("EXCEPTION")?;
1957 Ok(0)
1958 })?;
1959 self.accept_word("END-JSON");
1960 (j.on_exception, j.not_on_exception) = (h.on, h.not_on);
1961 Ok(j)
1962 }
1963
1964 fn xml_operand_follows(&self) -> bool {
1965 self.starts_ref() && !self.word().is_some_and(|w| XML_PHRASES.contains(&w))
1966 }
1967
1968 fn xml_form(&mut self) -> R<Option<XmlForm>> {
1969 Ok(match self.accept_any(&["ATTRIBUTE", "ELEMENT", "CONTENT"]).as_deref() {
1970 Some("ATTRIBUTE") => Some(XmlForm::Attribute),
1971 Some("ELEMENT") => Some(XmlForm::Element),
1972 Some(_) => Some(XmlForm::Content),
1973 None => None,
1974 })
1975 }
1976
1977 fn xml_generate(&mut self, pos: Pos) -> R<XmlGenerate> {
1978 let receiver = self.reference()?;
1979 self.expect_word("FROM")?;
1980 let from = self.reference()?;
1981 let mut x = XmlGenerate {
1982 receiver,
1983 from,
1984 count: None,
1985 encoding: None,
1986 declaration: false,
1987 attributes: false,
1988 namespace: None,
1989 prefix: None,
1990 names: Vec::new(),
1991 types: Vec::new(),
1992 suppress: Vec::new(),
1993 on_exception: None,
1994 not_on_exception: None,
1995 pos,
1996 };
1997 loop {
1998 if self.accept_word("COUNT") {
1999 self.accept_word("IN");
2000 x.count = Some(self.reference()?);
2001 continue;
2002 }
2003 let with = self.accept_word("WITH");
2004 if self.accept_word("ENCODING") {
2005 x.encoding = Some(self.operand()?);
2006 } else if self.accept_word("XML-DECLARATION") {
2007 x.declaration = true;
2008 } else if self.accept_word("ATTRIBUTES") {
2009 x.attributes = true;
2010 } else if with {
2011 return Err(self.error("ENCODING, XML-DECLARATION or ATTRIBUTES"));
2012 } else if self.accept_word("NAMESPACE") {
2013 self.accept_word("IS");
2014 x.namespace = Some(self.operand()?);
2015 if self.accept_word("NAMESPACE-PREFIX") {
2016 self.accept_word("IS");
2017 x.prefix = Some(self.operand()?);
2018 }
2019 } else if self.accept_word("NAME") {
2020 self.accept_word("OF");
2021 loop {
2022 let item = self.reference()?;
2023 self.accept_word("IS");
2024 x.names.push((item, self.literal()?));
2025 if !self.xml_operand_follows() {
2026 break;
2027 }
2028 }
2029 } else if self.accept_word("TYPE") {
2030 self.accept_word("OF");
2031 loop {
2032 let item = self.reference()?;
2033 self.accept_word("IS");
2034 let form = self.xml_form()?.ok_or_else(|| self.error("ATTRIBUTE, ELEMENT or CONTENT"))?;
2035 x.types.push((item, form));
2036 if !self.xml_operand_follows() {
2037 break;
2038 }
2039 }
2040 } else if self.accept_word("SUPPRESS") {
2041 loop {
2042 if self.accept_word("EVERY") {
2043 let numeric = match self.accept_any(&["NUMERIC", "NONNUMERIC"]).as_deref() {
2044 Some("NUMERIC") => Some(true),
2045 Some(_) => Some(false),
2046 None => None,
2047 };
2048 let form = self.xml_form()?;
2049 if numeric.is_none() && form.is_none() {
2050 return Err(self.error("NUMERIC, NONNUMERIC, ATTRIBUTE, CONTENT or ELEMENT"));
2051 }
2052 self.expect_word("WHEN")?;
2053 x.suppress.push(Suppression::Every { numeric, form, when: self.figurative_list()? });
2054 } else {
2055 let item = self.reference()?;
2056 let when = if self.accept_word("WHEN") { self.figurative_list()? } else { Vec::new() };
2057 x.suppress.push(Suppression::Item { item, when });
2058 }
2059 if !(self.is_word("EVERY") || self.xml_operand_follows()) {
2060 break;
2061 }
2062 }
2063 } else {
2064 break;
2065 }
2066 }
2067 let [h] = self.on_phrases(&["ON", "EXCEPTION"], &["END-XML"], |p| {
2068 p.accept_word("ON");
2069 p.expect_word("EXCEPTION")?;
2070 Ok(0)
2071 })?;
2072 self.accept_word("END-XML");
2073 (x.on_exception, x.not_on_exception) = (h.on, h.not_on);
2074 Ok(x)
2075 }
2076
2077 fn xml_parse(&mut self, pos: Pos) -> R<XmlParse> {
2078 let document = self.reference()?;
2079 let mut x = XmlParse { document, encoding: None, returning_national: false, procedure: ProcName { name: String::new(), section: None }, thru: None, on_exception: None, not_on_exception: None, pos };
2080 loop {
2081 if self.accept_word("WITH") || self.is_word("ENCODING") {
2082 self.expect_word("ENCODING")?;
2083 x.encoding = Some(self.operand()?);
2084 } else if self.accept_word("RETURNING") {
2085 self.expect_word("NATIONAL")?;
2086 x.returning_national = true;
2087 } else if self.is_word("VALIDATING") {
2088 return Err(self.error("PROCESSING PROCEDURE: XML PARSE VALIDATING is not supported yet"));
2089 } else {
2090 break;
2091 }
2092 }
2093 self.expect_word("PROCESSING")?;
2094 self.expect_word("PROCEDURE")?;
2095 self.accept_word("IS");
2096 x.procedure = self.proc_name()?;
2097 if self.accept_any(&["THRU", "THROUGH"]).is_some() {
2098 x.thru = Some(self.proc_name()?);
2099 }
2100 let [h] = self.on_phrases(&["ON", "EXCEPTION"], &["END-XML"], |p| {
2101 p.accept_word("ON");
2102 p.expect_word("EXCEPTION")?;
2103 Ok(0)
2104 })?;
2105 self.accept_word("END-XML");
2106 (x.on_exception, x.not_on_exception) = (h.on, h.not_on);
2107 Ok(x)
2108 }
2109
2110 fn call(&mut self, pos: Pos) -> R<Call> {
2111 let target = self.operand()?;
2112 let mut using = Vec::new();
2113 if self.accept_word("USING") {
2114 let mut mode = ArgMode::Reference;
2115 loop {
2116 let by = self.accept_word("BY");
2117 if let Some(m) = self.accept_any(&["REFERENCE", "CONTENT", "VALUE"]) {
2118 mode = match m.as_str() {
2119 "CONTENT" => ArgMode::Content,
2120 "VALUE" => ArgMode::Value,
2121 _ => ArgMode::Reference,
2122 };
2123 } else if by {
2124 return Err(self.error("REFERENCE, CONTENT or VALUE after BY"));
2125 } else if self.accept_word("OMITTED") {
2126 using.push(Arg { mode, value: None });
2127 } else if self.starts_operand() {
2128 using.push(Arg { mode, value: Some(self.operand()?) });
2129 } else {
2130 break;
2131 }
2132 }
2133 }
2134 let returning = if self.accept_word("RETURNING") { Some(self.reference()?) } else { None };
2135 let [exception] = self.on_phrases(&["ON", "EXCEPTION", "OVERFLOW"], &["END-CALL"], |p| {
2136 p.accept_word("ON");
2137 p.accept_any(&["EXCEPTION", "OVERFLOW"]).map(|_| 0).ok_or_else(|| p.error("EXCEPTION or OVERFLOW"))
2138 })?;
2139 self.accept_word("END-CALL");
2140 Ok(Call { target, using, returning, on_exception: exception.on, not_on_exception: exception.not_on, pos })
2141 }
2142
2143 fn overflow(&mut self, end: &str) -> R<Handlers> {
2145 let [h] = self.on_phrases(&["ON", "OVERFLOW"], &[end], |p| {
2146 p.accept_word("ON");
2147 p.expect_word("OVERFLOW").map(|()| 0)
2148 })?;
2149 self.accept_word(end);
2150 Ok(h)
2151 }
2152
2153 fn string(&mut self, pos: Pos) -> R<StringStmt> {
2154 let mut sources = Vec::new();
2155 while !self.is_word("INTO") {
2156 let mut group = Vec::new();
2157 while self.starts_operand() && !self.is_word("DELIMITED") {
2158 group.push(self.operand()?);
2159 }
2160 if group.is_empty() {
2161 return Err(self.error("a sending item"));
2162 }
2163 let delimiter = if self.accept_word("DELIMITED") {
2164 self.accept_word("BY");
2165 if self.accept_word("SIZE") { Delimiter::Size } else { Delimiter::By(self.operand()?) }
2166 } else {
2167 Delimiter::Size
2168 };
2169 sources.extend(group.into_iter().map(|op| (op, delimiter.clone())));
2170 }
2171 self.expect_word("INTO")?;
2172 let into = self.reference()?;
2173 let pointer = if self.accept_word("WITH") || self.is_word("POINTER") {
2174 self.expect_word("POINTER")?;
2175 Some(self.reference()?)
2176 } else {
2177 None
2178 };
2179 let Handlers { on: on_overflow, not_on: not_on_overflow } = self.overflow("END-STRING")?;
2180 Ok(StringStmt { sources, into, pointer, on_overflow, not_on_overflow, pos })
2181 }
2182
2183 fn unstring(&mut self, pos: Pos) -> R<Unstring> {
2184 let source = self.reference()?;
2185 let mut delimiters = Vec::new();
2186 if self.accept_word("DELIMITED") {
2187 self.accept_word("BY");
2188 loop {
2189 let all = self.accept_word("ALL");
2190 delimiters.push((all, self.operand()?));
2191 if !self.accept_word("OR") {
2192 break;
2193 }
2194 }
2195 }
2196 self.expect_word("INTO")?;
2197 let mut into = Vec::new();
2198 while self.starts_ref() {
2199 let target = self.reference()?;
2200 let delimiter_in = if self.accept_word("DELIMITER") {
2201 self.accept_word("IN");
2202 Some(self.reference()?)
2203 } else {
2204 None
2205 };
2206 let count_in = if self.accept_word("COUNT") {
2207 self.accept_word("IN");
2208 Some(self.reference()?)
2209 } else {
2210 None
2211 };
2212 into.push(UnstringInto { target, delimiter_in, count_in });
2213 }
2214 if into.is_empty() {
2215 return Err(self.error("a receiving item after INTO"));
2216 }
2217 let pointer = if self.accept_word("WITH") || self.is_word("POINTER") {
2218 self.expect_word("POINTER")?;
2219 Some(self.reference()?)
2220 } else {
2221 None
2222 };
2223 let tallying = if self.accept_word("TALLYING") {
2224 self.accept_word("IN");
2225 Some(self.reference()?)
2226 } else {
2227 None
2228 };
2229 let Handlers { on: on_overflow, not_on: not_on_overflow } = self.overflow("END-UNSTRING")?;
2230 Ok(Unstring { source, delimiters, into, pointer, tallying, on_overflow, not_on_overflow, pos })
2231 }
2232
2233 fn bounds(&mut self) -> R<Vec<Bound>> {
2234 let mut bounds = Vec::new();
2235 while let Some(side) = self.accept_any(&["BEFORE", "AFTER"]) {
2236 self.accept_word("INITIAL");
2237 bounds.push(Bound { after: side == "AFTER", value: self.operand()? });
2238 }
2239 Ok(bounds)
2240 }
2241
2242 fn inspect(&mut self, pos: Pos) -> R<Inspect> {
2243 let target = if self.is_word("FUNCTION") { self.operand()? } else { Operand::Ref(self.reference()?) };
2244 let (mut tallying, mut replacing, mut converting) = (Vec::new(), Vec::new(), None);
2245 if self.accept_word("TALLYING") {
2246 while self.starts_ref() && self.word_at(1) == Some("FOR") || self.starts_ref() && !self.is_word("REPLACING") && self.tally_counter_ahead() {
2247 let counter = self.reference()?;
2248 self.expect_word("FOR")?;
2249 loop {
2250 if self.accept_word("CHARACTERS") {
2251 tallying.push(InspectPhrase { mode: InspectMode::Characters, pattern: None, by: None, counter: Some(counter.clone()), bounds: self.bounds()? });
2252 } else if let Some(mode) = self.accept_any(&["ALL", "LEADING"]) {
2253 let mode = if mode == "ALL" { InspectMode::All } else { InspectMode::Leading };
2254 loop {
2255 let pattern = self.operand()?;
2256 tallying.push(InspectPhrase { mode, pattern: Some(pattern), by: None, counter: Some(counter.clone()), bounds: self.bounds()? });
2257 if !self.starts_operand() || self.word_at(1) == Some("FOR") || self.is_word("ALL") || self.is_word("LEADING") {
2258 break;
2259 }
2260 }
2261 } else {
2262 break;
2263 }
2264 }
2265 }
2266 }
2267 if self.accept_word("REPLACING") {
2268 loop {
2269 if self.accept_word("CHARACTERS") {
2270 self.expect_word("BY")?;
2271 let by = self.operand()?;
2272 replacing.push(InspectPhrase { mode: InspectMode::Characters, pattern: None, by: Some(by), counter: None, bounds: self.bounds()? });
2273 } else if let Some(mode) = self.accept_any(&["ALL", "LEADING", "FIRST"]) {
2274 let mode = match mode.as_str() {
2275 "ALL" => InspectMode::All,
2276 "LEADING" => InspectMode::Leading,
2277 _ => InspectMode::First,
2278 };
2279 loop {
2280 let pattern = self.operand()?;
2281 self.expect_word("BY")?;
2282 let by = self.operand()?;
2283 replacing.push(InspectPhrase { mode, pattern: Some(pattern), by: Some(by), counter: None, bounds: self.bounds()? });
2284 if !self.starts_operand() || self.is_word("ALL") || self.is_word("LEADING") || self.is_word("FIRST") {
2285 break;
2286 }
2287 }
2288 } else {
2289 break;
2290 }
2291 }
2292 }
2293 if self.accept_word("CONVERTING") {
2294 let from = self.operand()?;
2295 self.expect_word("TO")?;
2296 let to = self.operand()?;
2297 converting = Some((from, to, self.bounds()?));
2298 }
2299 if tallying.is_empty() && replacing.is_empty() && converting.is_none() {
2300 return Err(self.error("TALLYING, REPLACING or CONVERTING"));
2301 }
2302 Ok(Inspect { target, tallying, replacing, converting, pos })
2303 }
2304
2305 fn tally_counter_ahead(&self) -> bool {
2307 let mut i = self.at + 1;
2308 if self.qualifying_paren_at(i) {
2309 let mut depth = 0;
2310 while let Some(t) = self.tokens.get(i) {
2311 match t.tok {
2312 Tok::LParen => depth += 1,
2313 Tok::RParen => {
2314 depth -= 1;
2315 if depth == 0 {
2316 i += 1;
2317 break;
2318 }
2319 }
2320 _ => {}
2321 }
2322 i += 1;
2323 }
2324 }
2325 matches!(self.tokens.get(i).map(|t| &t.tok), Some(Tok::Word(w)) if w == "FOR")
2326 }
2327
2328 fn search(&mut self, pos: Pos) -> R<Search> {
2329 let all = self.accept_word("ALL");
2330 let table = self.reference()?;
2331 let varying = if self.accept_word("VARYING") { Some(self.reference()?) } else { None };
2332 let at_end = if self.accept_word("AT") || self.is_word("END") {
2333 self.expect_word("END")?;
2334 Some(self.block(&["WHEN"])?)
2335 } else {
2336 None
2337 };
2338 let mut whens = Vec::new();
2339 while self.accept_word("WHEN") {
2340 let cond = self.cond()?;
2341 let body = self.block(&["WHEN", "END-SEARCH"])?;
2342 whens.push((cond, body));
2343 }
2344 if whens.is_empty() {
2345 return Err(self.error("WHEN"));
2346 }
2347 self.accept_word("END-SEARCH");
2348 Ok(Search { table, all, varying, at_end, whens, pos })
2349 }
2350
2351 fn set(&mut self) -> R<SetStmt> {
2352 if self.is_word("ENVIRONMENT") {
2353 return Err(Error::at(self.pos(), "SET ENVIRONMENT is GnuCOBOL's, not Enterprise COBOL's"));
2354 }
2355 if self.is_word("ADDRESS") && self.word_at(1) == Some("OF") {
2356 let mut targets = Vec::new();
2357 while self.is_word("ADDRESS") && self.word_at(1) == Some("OF") {
2358 self.at += 2;
2359 targets.push(self.reference()?);
2360 }
2361 self.expect_word("TO")?;
2362 return Ok(SetStmt::AddressOf { targets, value: self.operand()? });
2363 }
2364 let targets = self.refs()?;
2365 if self.accept_word("TO") {
2366 if self.accept_word("TRUE") {
2367 return Ok(SetStmt::ConditionTrue(targets));
2368 }
2369 if self.accept_word("FALSE") {
2370 return Ok(SetStmt::ConditionFalse(targets));
2371 }
2372 return Ok(SetStmt::To { targets, value: self.operand()? });
2373 }
2374 match self.accept_any(&["UP", "DOWN"]).as_deref() {
2375 Some(direction) => {
2376 self.expect_word("BY")?;
2377 Ok(SetStmt::UpDown { targets, down: direction == "DOWN", by: self.expr()? })
2378 }
2379 None => Err(self.error("TO, UP BY or DOWN BY")),
2380 }
2381 }
2382
2383 fn proc_name(&mut self) -> R<ProcName> {
2384 let name = self.procedure_word("a procedure name")?;
2385 let section = if self.accept_any(&["OF", "IN"]).is_some() { Some(self.procedure_word("a section name")?) } else { None };
2386 Ok(ProcName { name, section })
2387 }
2388
2389 fn procedure_word(&mut self, what: &str) -> R<String> {
2391 match self.peek() {
2392 Some(t) if digits(t) => {
2393 let Tok::Number(n) = t.clone() else { unreachable!() };
2394 self.at += 1;
2395 Ok(n)
2396 }
2397 _ => self.name(what),
2398 }
2399 }
2400
2401 fn starts_proc_name(&self) -> bool {
2402 self.starts_ref() || self.peek().is_some_and(digits)
2403 }
2404
2405 fn times_ahead(&mut self) -> bool {
2407 let at = self.at;
2408 let times = self.starts_operand() && self.expr().is_ok() && self.is_word("TIMES");
2409 self.at = at;
2410 times
2411 }
2412
2413 fn evaluate(&mut self, pos: Pos) -> R<Stmt> {
2414 let mut subjects = vec![self.subject()?];
2415 while self.accept_word("ALSO") {
2416 subjects.push(self.subject()?);
2417 }
2418 let (mut whens, mut other) = (Vec::new(), Vec::new());
2419 while self.is_word("WHEN") {
2420 if self.word_at(1) == Some("OTHER") {
2421 self.at += 2;
2422 other = self.block(&["END-EVALUATE"])?;
2423 break;
2424 }
2425 let mut alternatives = Vec::new();
2426 while self.is_word("WHEN") && self.word_at(1) != Some("OTHER") {
2427 self.at += 1;
2428 let mut objects = Vec::new();
2429 for (k, subject) in subjects.iter().enumerate() {
2430 if k > 0 {
2431 self.expect_word("ALSO")?;
2432 }
2433 objects.push(self.object(subject)?);
2434 }
2435 alternatives.push(objects);
2436 }
2437 let body = self.block(&["WHEN", "END-EVALUATE"])?;
2438 whens.push(When { alternatives, body });
2439 }
2440 self.accept_word("END-EVALUATE");
2441 Ok(Stmt::Evaluate { subjects, whens, other, pos })
2442 }
2443
2444 fn subject(&mut self) -> R<Subject> {
2445 if let Some(b) = self.accept_any(&["TRUE", "FALSE"]) {
2446 return Ok(Subject::Bool(b == "TRUE"));
2447 }
2448 let save = self.at;
2449 self.expr()?;
2450 let conditional = self.relop_ahead(0) || self.is_word("IS") || self.is_word("NOT")
2451 || self.word().is_some_and(|w| matches!(w, "NUMERIC" | "ALPHABETIC" | "POSITIVE" | "NEGATIVE" | "ZERO"));
2452 self.at = save;
2453 Ok(if conditional { Subject::Cond(self.cond()?) } else { Subject::Expr(self.expr()?) })
2454 }
2455
2456 fn object(&mut self, subject: &Subject) -> R<Object> {
2457 if self.accept_word("ANY") {
2458 return Ok(Object::Any);
2459 }
2460 if let Some(b) = self.accept_any(&["TRUE", "FALSE"]) {
2461 return Ok(Object::Bool(b == "TRUE"));
2462 }
2463 if !matches!(subject, Subject::Expr(_)) {
2464 return Ok(Object::Cond(self.cond()?));
2465 }
2466 let not = self.accept_word("NOT");
2467 let from = self.expr()?;
2468 let thru = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.expr()?) } else { None };
2469 Ok(Object::Value { not, from, thru })
2470 }
2471
2472 fn repeat(&mut self) -> R<Loop> {
2473 let mut test_after = false;
2474 if self.accept_word("WITH") || self.is_word("TEST") {
2475 self.expect_word("TEST")?;
2476 test_after = self.accept_any(&["BEFORE", "AFTER"]).as_deref() == Some("AFTER");
2477 }
2478 if self.accept_word("UNTIL") {
2479 return Ok(Loop::Until { cond: self.cond()?, test_after });
2480 }
2481 if self.accept_word("VARYING") {
2482 let varying = Box::new(self.varying()?);
2483 let mut after = Vec::new();
2484 while self.is_word("AFTER") {
2485 if after.len() == 6 {
2486 return Err(self.error("the end of the PERFORM: Enterprise COBOL takes at most six AFTER phrases"));
2487 }
2488 self.at += 1;
2489 after.push(self.varying()?);
2490 }
2491 return Ok(Loop::Varying { varying, after, test_after });
2492 }
2493 if self.times_ahead() {
2494 let count = self.expr()?;
2495 self.expect_word("TIMES")?;
2496 return Ok(Loop::Times(count));
2497 }
2498 Ok(Loop::Once)
2499 }
2500
2501 fn varying(&mut self) -> R<Varying> {
2503 let var = self.reference()?;
2504 self.expect_word("FROM")?;
2505 let from = self.expr()?;
2506 self.expect_word("BY")?;
2507 let by = self.expr()?;
2508 self.expect_word("UNTIL")?;
2509 Ok(Varying { var, from, by, until: self.cond()? })
2510 }
2511
2512 fn starts_ref(&self) -> bool {
2513 self.word().is_some_and(|w| !VERBS.contains(&w) && !PHRASE_WORDS.contains(&w) && figurative(w).is_none() && w != "FUNCTION")
2514 && !self.paragraph_header()
2515 }
2516
2517 fn starts_operand(&self) -> bool {
2518 match self.peek() {
2519 Some(Tok::Alnum(_) | Tok::Hex(_) | Tok::National(_) | Tok::Number(_)) => true,
2520 Some(Tok::Word(w)) => {
2521 (figurative(w).is_some() || matches!(w.as_str(), "ALL" | "FUNCTION" | "LENGTH" | "ADDRESS" | "DFHRESP" | "DFHVALUE") || self.starts_ref()) && !self.paragraph_header()
2522 }
2523 _ => false,
2524 }
2525 }
2526
2527 fn operand(&mut self) -> R<Operand> {
2528 let pos = self.pos();
2529 match self.peek() {
2530 Some(Tok::Word(w)) if w == "FUNCTION" => {
2531 self.at += 1;
2532 let name = self.name("a function name")?;
2533 let (mut args, mut modifier, mut all_subscripts) = (Vec::new(), None, Vec::new());
2534 if self.qualifying_paren_at(self.at) && !self.refmod_ahead() {
2535 self.at += 1;
2536 while !self.accept(&Tok::RParen) {
2537 if let Some(m) = self.accept_any(&["LEADING", "TRAILING"]) {
2538 modifier = Some(m);
2539 continue;
2540 }
2541 if self.all_subscript_ahead() {
2542 let (table, all) = self.table_with_all()?;
2543 all_subscripts.push((args.len(), all));
2544 args.push(Expr::Operand(Operand::Ref(table)));
2545 continue;
2546 }
2547 args.push(self.expr()?);
2548 }
2549 }
2550 let refmod = self.refmod()?;
2551 Ok(Operand::Function(FunctionCall { name, args, modifier, refmod, all_subscripts, pos }))
2552 }
2553 Some(Tok::Word(w)) if w == "LENGTH" && self.word_at(1) == Some("OF") => {
2554 self.at += 2;
2555 Ok(Operand::LengthOf(self.reference()?))
2556 }
2557 Some(Tok::Word(w)) if w == "DFHRESP" && self.peek_at(1) == Some(&Tok::LParen) => {
2558 self.at += 2;
2559 let condition = self.name("a CICS condition")?;
2560 self.expect(&Tok::RParen, "')'")?;
2561 let code = crate::system::resp_code(&condition).ok_or_else(|| Error::at(pos, format!("DFHRESP({condition}): not a CICS condition ironwork for COBOL knows")))?;
2562 Ok(Operand::Literal(Literal::Number(code.to_string())))
2563 }
2564 Some(Tok::Word(w)) if w == "DFHVALUE" && self.peek_at(1) == Some(&Tok::LParen) => {
2565 self.at += 2;
2566 let name = self.name("a CVDA value")?;
2567 self.expect(&Tok::RParen, "')'")?;
2568 let value = rt::cics_tables::cvda(&name).ok_or_else(|| Error::at(pos, format!("DFHVALUE({name}): not a CVDA ironwork for COBOL knows")))?;
2569 Ok(Operand::Literal(Literal::Number(value.to_string())))
2570 }
2571 Some(Tok::Word(w)) if w == "ADDRESS" && self.word_at(1) == Some("OF") => {
2572 self.at += 2;
2573 Ok(Operand::AddressOf(self.reference()?))
2574 }
2575 Some(Tok::Word(w)) if figurative(w).is_some() || w == "ALL" => Ok(Operand::Literal(self.literal()?)),
2576 Some(Tok::Word(_)) => Ok(Operand::Ref(self.reference()?)),
2577 Some(Tok::Alnum(_) | Tok::Hex(_) | Tok::National(_) | Tok::Number(_)) => Ok(Operand::Literal(self.literal()?)),
2578 _ => Err(self.error("an operand")),
2579 }
2580 }
2581
2582 fn refmod_ahead(&self) -> bool {
2584 let mut depth = 0;
2585 for t in &self.tokens[self.at..] {
2586 match t.tok {
2587 Tok::LParen => depth += 1,
2588 Tok::RParen => {
2589 depth -= 1;
2590 if depth == 0 {
2591 return false;
2592 }
2593 }
2594 Tok::Colon if depth == 1 => return true,
2595 Tok::Period => return false,
2596 _ => {}
2597 }
2598 }
2599 false
2600 }
2601
2602 fn refmod(&mut self) -> R<Option<RefMod>> {
2603 if !self.qualifying_paren_at(self.at) || !self.refmod_ahead() {
2604 return Ok(None);
2605 }
2606 self.at += 1;
2607 let start = Box::new(self.expr()?);
2608 self.expect(&Tok::Colon, "':'")?;
2609 let length = if self.peek() == Some(&Tok::RParen) { None } else { Some(Box::new(self.expr()?)) };
2610 self.expect(&Tok::RParen, "')'")?;
2611 Ok(Some(RefMod { start, length }))
2612 }
2613
2614 fn all_subscript_ahead(&self) -> bool {
2616 if !self.starts_ref() {
2617 return false;
2618 }
2619 let mut at = self.at + 1;
2620 while matches!(self.tokens.get(at).map(|t| &t.tok), Some(Tok::Word(w)) if w == "OF" || w == "IN") {
2621 at += 2;
2622 }
2623 if !self.qualifying_paren_at(at) {
2624 return false;
2625 }
2626 let mut depth = 0;
2627 for t in &self.tokens[at..] {
2628 match &t.tok {
2629 Tok::LParen => depth += 1,
2630 Tok::RParen if depth == 1 => return false,
2631 Tok::RParen => depth -= 1,
2632 Tok::Word(w) if depth == 1 && w == "ALL" => return true,
2633 Tok::Period => return false,
2634 _ => {}
2635 }
2636 }
2637 false
2638 }
2639
2640 fn table_with_all(&mut self) -> R<(Ref, Vec<usize>)> {
2643 let pos = self.pos();
2644 let name = self.name("a data name")?;
2645 let mut qualifiers = Vec::new();
2646 while self.accept_any(&["OF", "IN"]).is_some() {
2647 qualifiers.push(self.name("a qualifier")?);
2648 }
2649 self.expect(&Tok::LParen, "'('")?;
2650 let (mut subscripts, mut all) = (Vec::new(), Vec::new());
2651 while !self.accept(&Tok::RParen) {
2652 if self.accept_any(&["ALL"]).is_some() {
2653 all.push(subscripts.len());
2654 subscripts.push(Expr::Operand(Operand::Literal(Literal::Number("1".into()))));
2655 } else {
2656 subscripts.push(self.expr()?);
2657 }
2658 }
2659 let refmod = self.refmod()?;
2660 Ok((Ref { name, qualifiers, subscripts, refmod, pos }, all))
2661 }
2662
2663 fn reference(&mut self) -> R<Ref> {
2664 let pos = self.pos();
2665 let name = self.name("a data name")?;
2666 let mut qualifiers = Vec::new();
2667 while self.accept_any(&["OF", "IN"]).is_some() {
2668 qualifiers.push(self.name("a qualifier")?);
2669 }
2670 let mut subscripts = Vec::new();
2671 if self.qualifying_paren_at(self.at) && !self.refmod_ahead() {
2672 self.at += 1;
2673 while !self.accept(&Tok::RParen) {
2674 subscripts.push(self.expr()?);
2675 }
2676 }
2677 let refmod = self.refmod()?;
2678 Ok(Ref { name, qualifiers, subscripts, refmod, pos })
2679 }
2680
2681 fn expr(&mut self) -> R<Expr> {
2682 let mut left = self.term()?;
2683 loop {
2684 let op = match self.peek() {
2685 Some(Tok::Plus) => BinOp::Add,
2686 Some(Tok::Minus) => BinOp::Sub,
2687 _ => return Ok(left),
2688 };
2689 self.at += 1;
2690 left = Expr::Bin(Box::new(left), op, Box::new(self.term()?));
2691 }
2692 }
2693
2694 fn term(&mut self) -> R<Expr> {
2695 let mut left = self.power()?;
2696 loop {
2697 let op = match self.peek() {
2698 Some(Tok::Star) => BinOp::Mul,
2699 Some(Tok::Slash) => BinOp::Div,
2700 _ => return Ok(left),
2701 };
2702 self.at += 1;
2703 left = Expr::Bin(Box::new(left), op, Box::new(self.power()?));
2704 }
2705 }
2706
2707 fn power(&mut self) -> R<Expr> {
2708 let mut left = self.unary()?;
2709 while self.accept(&Tok::Power) {
2710 left = Expr::Bin(Box::new(left), BinOp::Pow, Box::new(self.unary()?));
2711 }
2712 Ok(left)
2713 }
2714
2715 fn unary(&mut self) -> R<Expr> {
2716 if self.accept(&Tok::Minus) {
2717 return Ok(Expr::Neg(Box::new(self.unary()?)));
2718 }
2719 self.accept(&Tok::Plus);
2720 if self.accept(&Tok::LParen) {
2721 let e = self.expr()?;
2722 self.expect(&Tok::RParen, "')'")?;
2723 return Ok(e);
2724 }
2725 Ok(Expr::Operand(self.operand()?))
2726 }
2727
2728 fn cond(&mut self) -> R<Cond> {
2729 let mut last = None;
2730 self.or_cond(&mut last)
2731 }
2732
2733 fn or_cond(&mut self, last: &mut Option<(Expr, RelOp, bool)>) -> R<Cond> {
2734 let mut left = self.and_cond(last)?;
2735 while self.accept_word("OR") {
2736 left = Cond::Or(Box::new(left), Box::new(self.and_cond(last)?));
2737 }
2738 Ok(left)
2739 }
2740
2741 fn and_cond(&mut self, last: &mut Option<(Expr, RelOp, bool)>) -> R<Cond> {
2742 let mut left = self.not_cond(last)?;
2743 while self.accept_word("AND") {
2744 left = Cond::And(Box::new(left), Box::new(self.not_cond(last)?));
2745 }
2746 Ok(left)
2747 }
2748
2749 fn not_cond(&mut self, last: &mut Option<(Expr, RelOp, bool)>) -> R<Cond> {
2750 if self.is_word("NOT") && !self.relop_ahead(1) {
2751 self.at += 1;
2752 return Ok(Cond::Not(Box::new(self.not_cond(last)?)));
2753 }
2754 self.primary_cond(last)
2755 }
2756
2757 fn relop_ahead(&self, ahead: usize) -> bool {
2758 matches!(self.peek_at(ahead), Some(Tok::Eq | Tok::Lt | Tok::Gt | Tok::Le | Tok::Ge))
2759 || matches!(self.word_at(ahead), Some("EQUAL" | "GREATER" | "LESS"))
2760 }
2761
2762 fn primary_cond(&mut self, last: &mut Option<(Expr, RelOp, bool)>) -> R<Cond> {
2763 if let Some((subject, ..)) = last.clone()
2764 && (self.relop_ahead(0) || self.is_word("NOT") && self.relop_ahead(1))
2765 {
2766 let negated = self.accept_word("NOT");
2767 let op = self.relop()?.ok_or_else(|| self.error("a relational operator"))?;
2768 return self.objects(subject, op, negated, last);
2769 }
2770 if self.peek() == Some(&Tok::LParen) {
2771 let save = self.at;
2772 self.at += 1;
2773 let mut inner_last = None;
2774 if let Ok(c) = self.or_cond(&mut inner_last)
2775 && self.accept(&Tok::RParen)
2776 && !matches!(self.peek(), Some(Tok::Plus | Tok::Minus | Tok::Star | Tok::Slash | Tok::Power))
2777 && !self.relop_ahead(0)
2778 && !self.is_word("IS")
2779 {
2780 return Ok(c);
2781 }
2782 self.at = save;
2783 }
2784 let left = self.expr()?;
2785 self.accept_word("IS");
2786 let negated = self.is_word("NOT") && {
2787 self.at += 1;
2788 true
2789 };
2790 let wrap = |c: Cond| if negated { Cond::Not(Box::new(c)) } else { c };
2791 if let Some(op) = self.relop()? {
2792 return self.objects(left, op, negated, last);
2793 }
2794 if let Some(class) = self.accept_any(&["NUMERIC", "ALPHABETIC", "POSITIVE", "NEGATIVE", "ZERO"]) {
2795 let class = match class.as_str() {
2796 "NUMERIC" => Class::Numeric,
2797 "ALPHABETIC" => Class::Alphabetic,
2798 "POSITIVE" => Class::Positive,
2799 "NEGATIVE" => Class::Negative,
2800 _ => Class::Zero,
2801 };
2802 return Ok(wrap(Cond::Class(left, class)));
2803 }
2804 if negated {
2805 return Err(self.error("a relational operator or class after NOT"));
2806 }
2807 match (left, last.clone()) {
2808 (Expr::Operand(Operand::Ref(name)), Some((subject, op, negated))) if self.abbreviation_context() => Ok(Cond::NameOrRel { subject, op, negated, name }),
2809 (right, Some((subject, op, negated))) if !matches!(&right, Expr::Operand(Operand::Ref(_))) => Ok(relation(subject, op, negated, right)),
2810 (Expr::Operand(Operand::Ref(r)), _) => Ok(Cond::Name(r)),
2811 (_, _) => Err(self.error("a relational operator")),
2812 }
2813 }
2814
2815 fn objects(&mut self, subject: Expr, op: RelOp, negated: bool, last: &mut Option<(Expr, RelOp, bool)>) -> R<Cond> {
2818 *last = Some((subject.clone(), op, negated));
2819 let start = self.at;
2820 match self.distributed(&subject, op, negated) {
2821 Ok(Some(c)) => return Ok(c),
2822 _ => self.at = start,
2823 }
2824 let object = self.expr()?;
2825 Ok(relation(subject, op, negated, object))
2826 }
2827
2828 fn distributed(&mut self, subject: &Expr, op: RelOp, negated: bool) -> R<Option<Cond>> {
2831 let start = self.at;
2832 if !self.accept(&Tok::LParen) {
2833 return Ok(None);
2834 }
2835 let mut logical = false;
2836 let mut any = self.distributed_all(subject, op, negated, &mut logical)?;
2837 while self.accept_word("OR") {
2838 logical = true;
2839 any = Cond::Or(Box::new(any), Box::new(self.distributed_all(subject, op, negated, &mut logical)?));
2840 }
2841 if !logical || !self.accept(&Tok::RParen) {
2842 self.at = start;
2843 return Ok(None);
2844 }
2845 Ok(Some(any))
2846 }
2847
2848 fn distributed_all(&mut self, subject: &Expr, op: RelOp, negated: bool, logical: &mut bool) -> R<Cond> {
2849 let mut all = self.distributed_object(subject, op, negated)?;
2850 while self.accept_word("AND") {
2851 *logical = true;
2852 all = Cond::And(Box::new(all), Box::new(self.distributed_object(subject, op, negated)?));
2853 }
2854 Ok(all)
2855 }
2856
2857 fn distributed_object(&mut self, subject: &Expr, op: RelOp, negated: bool) -> R<Cond> {
2858 if self.accept_word("NOT") {
2859 return Ok(Cond::Not(Box::new(self.distributed_object(subject, op, negated)?)));
2860 }
2861 let start = self.at;
2862 match self.distributed(subject, op, negated) {
2863 Ok(Some(group)) => return Ok(group),
2864 _ => self.at = start,
2865 }
2866 let object = self.expr()?;
2867 Ok(relation(subject.clone(), op, negated, object))
2868 }
2869
2870 fn abbreviation_context(&self) -> bool {
2872 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")
2873 }
2874
2875 fn relop(&mut self) -> R<Option<RelOp>> {
2876 if self.peek() == Some(&Tok::Lt) && self.peek_at(1) == Some(&Tok::Gt) {
2877 return Err(Error::at(self.pos(), "<> is not an Enterprise COBOL relational operator: it writes NOT ="));
2878 }
2879 let op = match self.peek() {
2880 Some(Tok::Eq) => RelOp::Eq,
2881 Some(Tok::Lt) => RelOp::Lt,
2882 Some(Tok::Gt) => RelOp::Gt,
2883 Some(Tok::Le) => RelOp::Le,
2884 Some(Tok::Ge) => RelOp::Ge,
2885 Some(Tok::Word(w)) if w == "EQUAL" => {
2886 self.at += 1;
2887 self.accept_word("TO");
2888 return Ok(Some(RelOp::Eq));
2889 }
2890 Some(Tok::Word(w)) if w == "GREATER" || w == "LESS" => {
2891 let greater = w == "GREATER";
2892 self.at += 1;
2893 self.accept_word("THAN");
2894 let or_equal = self.accept_word("OR");
2895 if or_equal {
2896 self.expect_word("EQUAL")?;
2897 self.accept_word("TO");
2898 }
2899 return Ok(Some(match (greater, or_equal) {
2900 (true, false) => RelOp::Gt,
2901 (true, true) => RelOp::Ge,
2902 (false, false) => RelOp::Lt,
2903 (false, true) => RelOp::Le,
2904 }));
2905 }
2906 _ => return Ok(None),
2907 };
2908 self.at += 1;
2909 Ok(Some(op))
2910 }
2911}
2912
2913fn host_variables(sql: &str, pos: Pos) -> Vec<Ref> {
2915 let chars: Vec<char> = sql.chars().collect();
2916 let (mut out, mut i, mut quote) = (Vec::new(), 0, None);
2917 while i < chars.len() {
2918 let c = chars[i];
2919 match quote {
2920 Some(q) if c == q => quote = None,
2921 Some(_) => {}
2922 None if c == '\'' || c == '"' => quote = Some(c),
2923 None if c == ':' && chars.get(i + 1).is_some_and(|n| n.is_ascii_alphanumeric()) => {
2924 let start = i + 1;
2925 let mut end = start;
2926 while end < chars.len() && (chars[end].is_ascii_alphanumeric() || matches!(chars[end], '-' | '_' | '.')) {
2927 end += 1;
2928 }
2929 let path: String = chars[start..end].iter().collect::<String>().to_ascii_uppercase();
2930 let mut parts: Vec<String> = path.trim_end_matches('.').split('.').map(str::to_owned).collect();
2931 let name = parts.pop().unwrap_or_default();
2932 parts.reverse();
2933 out.push(Ref { name, qualifiers: parts, subscripts: Vec::new(), refmod: None, pos });
2934 i = end;
2935 continue;
2936 }
2937 None => {}
2938 }
2939 i += 1;
2940 }
2941 out
2942}
2943
2944fn cics_options(body: &str) -> Vec<(String, Option<ExecArg>)> {
2946 let chars: Vec<char> = body.chars().collect();
2947 let (mut out, mut i) = (Vec::new(), 0);
2948 while i < chars.len() {
2949 if chars[i].is_whitespace() {
2950 i += 1;
2951 continue;
2952 }
2953 let start = i;
2954 while i < chars.len() && !chars[i].is_whitespace() && chars[i] != '(' {
2955 i += 1;
2956 }
2957 let name: String = chars[start..i].iter().collect::<String>().to_ascii_uppercase();
2958 let mut j = i;
2959 while j < chars.len() && chars[j].is_whitespace() {
2960 j += 1;
2961 }
2962 if j < chars.len() && chars[j] == '(' {
2963 let (mut depth, mut quote, mut k) = (0, None, j);
2964 while k < chars.len() {
2965 match (quote, chars[k]) {
2966 (Some(q), c) if c == q => quote = None,
2967 (Some(_), _) => {}
2968 (None, '\'' | '"') => quote = Some(chars[k]),
2969 (None, '(') => depth += 1,
2970 (None, ')') => {
2971 depth -= 1;
2972 if depth == 0 {
2973 break;
2974 }
2975 }
2976 _ => {}
2977 }
2978 k += 1;
2979 }
2980 let arg: String = chars[j + 1..k.min(chars.len())].iter().collect();
2981 out.push((name, Some(ExecArg::Text(arg.trim().to_owned()))));
2982 i = (k + 1).min(chars.len());
2983 } else {
2984 out.push((name, None));
2985 }
2986 }
2987 out
2988}
2989
2990fn operand_of(text: &str, pos: Pos) -> Option<Operand> {
2992 let source = crate::source::Source { text: text.to_owned(), positions: vec![pos; text.chars().count()], options: Vec::new(), debugging: None };
2993 let tokens = crate::lexer::lex(&source).ok()?;
2994 let mut p = Parser::new(&tokens);
2995 let op = p.operand().ok()?;
2996 (p.at == tokens.len()).then_some(op)
2997}
2998
2999fn system_entries(member: &str) -> R<Vec<DataEntry>> {
3001 system_text_entries(&crate::system::member(member).unwrap_or_default())
3002}
3003
3004fn system_text_entries(text: &str) -> R<Vec<DataEntry>> {
3005 let source = crate::source::read(text)?;
3006 let tokens = crate::lexer::lex(&source)?;
3007 Parser::new(&tokens).data_entries()
3008}
3009
3010const SELECT_CLAUSES: &[&str] = &[
3012 "ASSIGN", "ORGANIZATION", "ACCESS", "FILE", "STATUS", "RECORD", "ALTERNATE", "RELATIVE", "LINE", "SEQUENTIAL", "INDEXED", "RESERVE",
3013 "PADDING", "LOCK", "SHARING",
3014];
3015
3016const FD_WORDS: &[&str] = &[
3018 "RECORDING", "RECORD", "BLOCK", "LABEL", "DATA", "VALUE", "CODE-SET", "LINAGE", "REPORT", "REPORTS", "IS", "EXTERNAL", "GLOBAL", "STYLE",
3019];
3020
3021fn is_clause_word(w: &str) -> bool {
3022 matches!(
3023 w,
3024 "PIC" | "PICTURE" | "USAGE" | "VALUE" | "VALUES" | "REDEFINES" | "OCCURS" | "SIGN" | "LEADING" | "TRAILING" | "JUSTIFIED"
3025 | "JUST" | "SYNC" | "SYNCHRONIZED" | "GLOBAL" | "EXTERNAL" | "BLANK"
3026 ) || usage_word(w).is_some()
3027}
3028
3029#[cfg(test)]
3030mod tests {
3031 use super::*;
3032
3033 fn program(body: &str) -> Program {
3034 let text = format!(
3035 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n DATA DIVISION.\n WORKING-STORAGE SECTION.\n{body}"
3036 );
3037 crate::parse(&text).unwrap_or_else(|e| panic!("{e}"))
3038 }
3039
3040 #[test]
3041 fn dfhresp_and_dfhvalue_fold_to_the_numbers_in_ibms_tables() {
3042 let body = |operand: &str| format!(" 01 A PIC S9(8) COMP.\n PROCEDURE DIVISION.\n IF A = {operand}\n GOBACK\n END-IF.\n");
3043 let folds_to = |operand: &str, n: i32| {
3044 let p = program(&body(operand));
3045 assert!(format!("{:?}", p.paragraphs[0].statements[0]).contains(&format!("Number(\"{n}\")")), "{operand}");
3046 };
3047 folds_to("DFHRESP(NOTFINISHED)", 113);
3048 folds_to("DFHRESP(DSIDERR)", 12);
3049 folds_to("DFHRESP(FILENOTFOUND)", 12);
3050 assert_eq!(rt::cics_tables::cvda("ENABLED"), Some(23));
3051 folds_to("DFHVALUE(ENABLED)", 23);
3052 for (operand, message) in [("DFHRESP(NOSUCH)", "DFHRESP(NOSUCH): not a CICS condition"), ("DFHVALUE(NOSUCH)", "DFHVALUE(NOSUCH): not a CVDA")] {
3053 let err = crate::parse(&format!(" IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n DATA DIVISION.\n WORKING-STORAGE SECTION.\n{}", body(operand))).unwrap_err();
3054 assert!(err.to_string().contains(message), "{err}");
3055 }
3056 }
3057
3058 #[test]
3059 fn an_exec_cics_program_compares_with_dfhresp_notfinished() {
3060 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");
3061 }
3062
3063 #[test]
3064 fn data_entries_with_clauses_in_any_order() {
3065 let p = program(
3066 " 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",
3067 );
3068 let ws = &p.working_storage;
3069 assert_eq!(ws.len(), 5);
3070 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()))));
3071 assert_eq!(ws[2].redefines.as_deref(), Some("A"));
3072 assert_eq!((ws[3].name.as_deref(), &ws[3].value), (None, &Some(Literal::Figurative(Figurative::Space))));
3073 assert_eq!(ws[4].occurs, Some(3));
3074 }
3075
3076 #[test]
3077 fn paragraphs_and_nested_if() {
3078 let p = program(
3079 " 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",
3080 );
3081 assert_eq!(p.paragraphs.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(), ["MAIN-LINE", "P2"]);
3082 let Stmt::If { otherwise, .. } = &p.paragraphs[1].statements[0] else { panic!() };
3083 assert!(matches!(otherwise[0], Stmt::If { .. }));
3084 }
3085
3086 #[test]
3087 fn a_program_names_the_programs_it_directly_contains() {
3088 let program = |id: &str, inner: &str| format!(" IDENTIFICATION DIVISION.\n PROGRAM-ID. {id}.\n PROCEDURE DIVISION.\n GOBACK.\n{inner} END PROGRAM {id}.\n");
3089 let text = program("OUTER", &[program("A", &program("A1", "")), program("B", "")].concat());
3090 let all = crate::parse_all_with(&text, &Default::default()).unwrap_or_else(|e| panic!("{e}"));
3091 let contained: Vec<(&str, &[String])> = all.iter().map(|p| (p.id.as_str(), p.nested.as_slice())).collect();
3092 assert_eq!(contained, [("OUTER", &["A".to_owned(), "B".to_owned()][..]), ("A", &["A1".to_owned()][..]), ("A1", &[][..]), ("B", &[][..])]);
3093 }
3094
3095 #[test]
3096 fn each_program_carries_the_messages_of_its_own_source_and_not_its_contained_programs() {
3097 let program = |id: &str, name: &str, inner: &str| {
3098 format!(" IDENTIFICATION DIVISION.\n PROGRAM-ID. {id}.\n PROCEDURE DIVISION.\n DISPLAY {name}\n GOBACK.\n{inner} END PROGRAM {id}.\n")
3099 };
3100 let text = [program("O#1", "O1", &[program("A", "A@1", ""), program("B", "B1", "")].concat()), program("NEXT", "N%1", "")].concat();
3101 let all = crate::parse_all_with(&text, &Default::default()).unwrap_or_else(|e| panic!("{e}"));
3102 let lines: Vec<(&str, Vec<u32>)> = all.iter().map(|p| (p.id.as_str(), p.messages.iter().map(|m| m.pos.line).collect())).collect();
3103 assert_eq!(lines, [("O#1", vec![2, 18]), ("A", vec![9]), ("B", vec![]), ("NEXT", vec![22])]);
3104 }
3105
3106 #[test]
3107 fn inline_perform_varying_with_a_compound_condition() {
3108 let p = program(
3109 " 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",
3110 );
3111 let Stmt::PerformInline { repeat: Loop::Varying { varying, .. }, .. } = &p.paragraphs[0].statements[0] else { panic!() };
3112 assert!(matches!(varying.until, Cond::Or(..)));
3113 }
3114
3115 #[test]
3116 fn functions_reference_modification_and_length_of() {
3117 let p = program(
3118 " 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",
3119 );
3120 let Stmt::Compute { expr: Expr::Bin(left, BinOp::Sub, _), .. } = &p.paragraphs[0].statements[0] else { panic!() };
3121 let Expr::Operand(Operand::Function(f)) = left.as_ref() else { panic!() };
3122 let Expr::Operand(Operand::Ref(r)) = &f.args[0] else { panic!() };
3123 assert!(r.refmod.is_some());
3124 assert!(matches!(&p.paragraphs[0].statements[2], Stmt::Move { from: Operand::LengthOf(_), .. }));
3125 }
3126
3127 #[test]
3128 fn divide_giving_remainder_and_add_to() {
3129 let p = program(
3130 " 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",
3131 );
3132 let Stmt::Arith(a) = &p.paragraphs[0].statements[0] else { panic!() };
3133 assert_eq!(a.verb, ArithVerb::Divide);
3134 assert!(a.remainder.is_some());
3135 let Stmt::Arith(add) = &p.paragraphs[0].statements[1] else { panic!() };
3136 assert!(add.computations[0].0.rounded);
3137 }
3138
3139 #[test]
3140 fn abbreviated_combined_relation() {
3141 let p = program(" 01 A PIC 9.\n PROCEDURE DIVISION.\n IF A = 1 OR 2 CONTINUE END-IF.\n");
3142 let Stmt::If { cond: Cond::Or(_, right), .. } = &p.paragraphs[0].statements[0] else { panic!() };
3143 assert!(matches!(right.as_ref(), Cond::Rel(_, RelOp::Eq, _)));
3144 }
3145
3146 #[test]
3147 fn indexed_select_clauses_and_keyed_statements() {
3148 let text = [
3149 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n ENVIRONMENT DIVISION.\n INPUT-OUTPUT SECTION.\n FILE-CONTROL.\n",
3150 " SELECT K ASSIGN TO KDD ORGANIZATION IS INDEXED\n",
3151 " ACCESS MODE IS DYNAMIC RECORD KEY IS K-ID\n",
3152 " ALTERNATE KEY K-ALT WITH DUPLICATES.\n",
3153 " SELECT N ASSIGN TO NDD STATUS N-FS N-VS ORGANIZATION\n",
3154 " INDEXED FILE STATUS IS N-FS RECORD N-ID.\n",
3155 " 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",
3156 " 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",
3157 " PROCEDURE DIVISION.\n",
3158 " READ K NEXT RECORD AT END CONTINUE\n",
3159 " NOT AT END CONTINUE END-READ\n",
3160 " READ K KEY IS K-ALT INVALID KEY CONTINUE END-READ\n",
3161 " START K KEY IS NOT LESS THAN K-ID\n",
3162 " INVALID KEY CONTINUE NOT INVALID KEY CONTINUE\n",
3163 " END-START\n",
3164 " REWRITE K-REC INVALID KEY CONTINUE END-REWRITE\n",
3165 " DELETE K RECORD END-DELETE.\n",
3166 ]
3167 .concat();
3168 let p = crate::parse(&text).unwrap_or_else(|e| panic!("{e}"));
3169 let f = &p.files[0];
3170 assert_eq!((f.organization, f.access), (Organization::Indexed, Access::Dynamic));
3171 assert_eq!(f.record_key.as_ref().map(|r| r.name.as_str()), Some("K-ID"));
3172 assert_eq!(f.alternate_keys.iter().map(|(r, d)| (r.name.as_str(), *d)).collect::<Vec<_>>(), [("K-ALT", true)]);
3173 let n = &p.files[1];
3174 assert_eq!((n.organization, n.record_key.as_ref().map(|r| r.name.as_str())), (Organization::Indexed, Some("N-ID")));
3175 let s = &p.paragraphs[0].statements;
3176 let Stmt::Read(r) = &s[0] else { panic!() };
3177 assert!(r.next && !r.previous && r.at_end.on.is_some() && r.at_end.not_on.is_some());
3178 let Stmt::Read(r) = &s[1] else { panic!() };
3179 assert!(r.key.is_some() && r.invalid.on.is_some() && !r.next);
3180 let Stmt::Start { key: Some((RelOp::Ge, _)), invalid, .. } = &s[2] else { panic!() };
3181 assert!(invalid.on.is_some() && invalid.not_on.is_some());
3182 assert!(matches!(&s[3], Stmt::Rewrite { invalid, .. } if invalid.on.is_some()));
3183 assert!(matches!(&s[4], Stmt::Delete { .. }));
3184 }
3185
3186 #[test]
3187 fn entry_alter_the_go_to_forms_and_section_priorities() {
3188 let p = program(
3189 " 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",
3190 );
3191 let priorities: Vec<(&str, u8)> = p.paragraphs.iter().map(|q| (q.name.as_str(), q.priority)).collect();
3192 assert_eq!(priorities, [("S", 50), ("P1", 50), ("P2", 50), ("P3", 50), ("T", 0)]);
3193 let s = &p.paragraphs[1].statements;
3194 assert!(matches!(&s[0], Stmt::Entry { name, using, .. } if name == "ALT" && using == &[Param { by_value: true, name: "L".into() }]));
3195 assert!(matches!(&s[2], Stmt::Alter { pairs, .. } if pairs.len() == 2 && pairs[1].0.name == "P3"));
3196 assert!(matches!(&s[3], Stmt::GoToDepending { targets, on, .. } if targets.len() == 2 && on.name == "D"));
3197 assert!(matches!(&p.paragraphs[2].statements[0], Stmt::GoTo { target: None, .. }));
3198 assert!(matches!(&p.paragraphs[3].statements[0], Stmt::GoTo { target: Some(t), .. } if t.name == "P1"));
3199 }
3200
3201 #[test]
3202 fn by_left_out_stop_literal_and_a_phrase_that_belongs_to_the_outer_statement() {
3203 let p = program(
3204 " 01 A PIC 9.\n PROCEDURE DIVISION.\n CALL 'P' USING CONTENT A REFERENCE A VALUE A A\n ON EXCEPTION ADD 1 TO A\n NOT ON EXCEPTION ADD 2 TO A\n END-CALL\n STOP 'OPERATOR'\n STOP ZERO\n STOP RUN.\n",
3205 );
3206 let s = &p.paragraphs[0].statements;
3207 let Stmt::Call(c) = &s[0] else { panic!("{:?}", s[0]) };
3208 let modes: Vec<ArgMode> = c.using.iter().map(|a| a.mode).collect();
3209 assert_eq!(modes, [ArgMode::Content, ArgMode::Reference, ArgMode::Value, ArgMode::Value]);
3210 assert!(c.on_exception.as_ref().is_some_and(|b| matches!(&b[0], Stmt::Arith(a) if a.size_error.is_none())) && c.not_on_exception.is_some());
3211 assert!(matches!(&s[1], Stmt::Display { items, .. } if items == &[Operand::Literal(Literal::Alnum("OPERATOR".into()))]));
3212 assert!(matches!(&s[2], Stmt::Display { .. }) && matches!(&s[3], Stmt::StopRun { .. }));
3213 }
3214
3215 #[test]
3216 fn select_clauses_with_their_optional_words_left_out() {
3217 let text = [
3218 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n ENVIRONMENT DIVISION.\n INPUT-OUTPUT SECTION.\n FILE-CONTROL.\n",
3219 " SELECT R ASSIGN RDD ORGANIZATION RELATIVE ACCESS RANDOM\n RELATIVE R-KEY.\n",
3220 " SELECT Q ASSIGN QDD RELATIVE ACCESS DYNAMIC\n RELATIVE IS Q-KEY.\n",
3221 " SELECT X ASSIGN XDD ORGANIZATION INDEXED\n RECORD X-KEY ALTERNATE RECORD X-ALT\n ALTERNATE RECORD IS X-ALT2 WITH DUPLICATES.\n",
3222 " SELECT S ASSIGN SDD RECORD DELIMITER IS STANDARD-1.\n",
3223 " DATA DIVISION.\n FILE SECTION.\n FD R.\n 01 R-REC PIC X.\n FD Q.\n 01 Q-REC PIC X.\n",
3224 " FD X.\n 01 X-REC.\n 05 X-KEY PIC X.\n 05 X-ALT PIC X.\n 05 X-ALT2 PIC X.\n FD S.\n 01 S-REC PIC X.\n",
3225 " WORKING-STORAGE SECTION.\n 01 R-KEY PIC 9.\n 01 Q-KEY PIC 9.\n PROCEDURE DIVISION.\n GOBACK.\n",
3226 ]
3227 .concat();
3228 let p = crate::parse(&text).unwrap_or_else(|e| panic!("{e}"));
3229 let f = &p.files;
3230 assert!(f[0].organization == Organization::Relative && f[0].relative_key.as_ref().is_some_and(|k| k.name == "R-KEY"));
3231 assert!(f[1].organization == Organization::Relative && f[1].relative_key.as_ref().is_some_and(|k| k.name == "Q-KEY"));
3232 let alternates: Vec<(&str, bool)> = f[2].alternate_keys.iter().map(|(k, d)| (k.name.as_str(), *d)).collect();
3233 assert_eq!(alternates, [("X-ALT", false), ("X-ALT2", true)]);
3234 assert!(f[3].record_key.is_none() && f[3].organization == Organization::Sequential);
3235 }
3236
3237 #[test]
3238 fn procedure_names_of_digits_and_a_subscripted_times_count() {
3239 let p = program(
3240 " 01 T.\n 05 N PIC 9 OCCURS 2.\n 01 I PIC 9.\n PROCEDURE DIVISION.\n 00 SECTION 00.\n 10.\n PERFORM 20 N (I) TIMES\n PERFORM 20 THRU 30\n PERFORM N (1) TIMES CONTINUE END-PERFORM\n PERFORM TEST BEFORE UNTIL I > 1 ADD 1 TO I END-PERFORM\n GO TO 20 30 DEPENDING ON I\n ALTER 30 TO PROCEED TO 20 40 TO 20.\n 20.\n PERFORM 00.\n 30.\n GO TO 20.\n 40.\n GO TO 20.\n",
3241 );
3242 let names: Vec<&str> = p.paragraphs.iter().map(|q| q.name.as_str()).collect();
3243 assert_eq!(names, ["00", "10", "20", "30", "40"]);
3244 let s = &p.paragraphs[1].statements;
3245 assert!(matches!(&s[0], Stmt::PerformProc { from, repeat: Loop::Times(Expr::Operand(Operand::Ref(r))), .. } if from.name == "20" && r.name == "N" && r.subscripts.len() == 1));
3246 assert!(matches!(&s[1], Stmt::PerformProc { from, thru: Some(t), repeat: Loop::Once, .. } if from.name == "20" && t.name == "30"));
3247 assert!(matches!(&s[2], Stmt::PerformInline { repeat: Loop::Times(_), .. }));
3248 assert!(matches!(&s[3], Stmt::PerformInline { repeat: Loop::Until { test_after: false, .. }, .. }));
3249 assert!(matches!(&s[4], Stmt::GoToDepending { targets, .. } if targets.len() == 2 && targets[1].name == "30"));
3250 assert!(matches!(&s[5], Stmt::Alter { pairs, .. } if pairs.len() == 2 && pairs[1].0.name == "40"));
3251 assert!(matches!(&p.paragraphs[2].statements[0], Stmt::PerformProc { from, .. } if from.name == "00"));
3252 }
3253
3254 #[test]
3255 fn corresponding_forms_take_one_receiving_group() {
3256 let p = program(
3257 " PROCEDURE DIVISION.\n MOVE CORR A TO B(1)\n ADD CORRESPONDING A TO B ROUNDED\n ON SIZE ERROR CONTINUE END-ADD\n SUBTRACT CORR A FROM B.\n",
3258 );
3259 let s = &p.paragraphs[0].statements;
3260 let Stmt::Corresponding(m) = &s[0] else { panic!("{:?}", s[0]) };
3261 assert_eq!((m.verb, m.from.name.as_str(), m.to.name.as_str(), m.to.subscripts.len()), (CorrespondingVerb::Move, "A", "B", 1));
3262 let Stmt::Corresponding(a) = &s[1] else { panic!() };
3263 assert!(a.verb == CorrespondingVerb::Add && a.rounded && a.size_error.is_some());
3264 assert!(matches!(&s[2], Stmt::Corresponding(c) if c.verb == CorrespondingVerb::Subtract && !c.rounded));
3265 let text = " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n PROCEDURE DIVISION.\n MOVE CORRESPONDING A TO B C.\n";
3266 assert!(crate::parse(text).unwrap_err().message.contains("one receiving group"));
3267 }
3268
3269 #[test]
3270 fn advancing_mnemonic_names_reach_contained_programs_and_linage_is_noted() {
3271 let text = [
3272 " IDENTIFICATION DIVISION.\n PROGRAM-ID. OUTER.\n ENVIRONMENT DIVISION.\n",
3273 " CONFIGURATION SECTION.\n SPECIAL-NAMES.\n C01 IS TOP-OF-PAGE CSP NO-SPACE\n",
3274 " AFP-5A IS PAGE-MODE UPSI-0 IS SWITCH-0 ON STATUS IS SW-ON.\n",
3275 " INPUT-OUTPUT SECTION.\n FILE-CONTROL.\n SELECT P ASSIGN TO PDD.\n",
3276 " DATA DIVISION.\n FILE SECTION.\n FD P LINAGE IS 60.\n 01 P-REC PIC X.\n",
3277 " PROCEDURE DIVISION.\n WRITE P-REC AFTER TOP-OF-PAGE\n",
3278 " WRITE P-REC BEFORE ADVANCING NO-SPACE\n WRITE P-REC AFTER ADVANCING PAGE-COUNT LINES.\n",
3279 " IDENTIFICATION DIVISION.\n PROGRAM-ID. INNER.\n PROCEDURE DIVISION.\n",
3280 " WRITE P-REC AFTER ADVANCING PAGE-MODE.\n END PROGRAM INNER.\n END PROGRAM OUTER.\n",
3281 ]
3282 .concat();
3283 let programs = crate::parse_all_with(&text, &crate::copy::Libraries::default()).unwrap_or_else(|e| panic!("{e}"));
3284 let (outer, inner) = (&programs[0], &programs[1]);
3285 let pairs = |p: &Program| p.environment.mnemonics.iter().map(|(n, e)| format!("{n}={e}")).collect::<Vec<_>>();
3286 assert_eq!(pairs(outer), ["TOP-OF-PAGE=C01", "NO-SPACE=CSP", "PAGE-MODE=AFP-5A"]);
3287 assert_eq!(outer.files[0].linage.as_ref().map(|l| &l.lines), Some(&LinageValue::Integer("60".into())));
3288 let advancing = |p: &Program, i: usize| match &p.paragraphs[0].statements[i] {
3289 Stmt::Write { advancing: Some(a), .. } => a.clone(),
3290 other => panic!("{other:?}"),
3291 };
3292 assert!(matches!(advancing(outer, 0), Advancing::Mnemonic { before: false, environment, .. } if environment == "C01"));
3293 assert!(matches!(advancing(outer, 1), Advancing::Mnemonic { before: true, environment, .. } if environment == "CSP"));
3294 assert!(matches!(advancing(outer, 2), Advancing::Lines { before: false, .. }));
3295 assert!(matches!(advancing(inner, 0), Advancing::Mnemonic { environment, .. } if environment == "AFP-5A"));
3296 }
3297
3298 #[test]
3299 fn exit_section_opening_a_paragraph_is_a_statement_not_a_section() {
3300 let p = program(" PROCEDURE DIVISION.\n MAIN-LINE SECTION.\n SKIPPED.\n EXIT SECTION.\n NEVER.\n GOBACK.\n");
3301 let names: Vec<&str> = p.paragraphs.iter().map(|q| q.name.as_str()).collect();
3302 assert_eq!(names, ["MAIN-LINE", "SKIPPED", "NEVER"]);
3303 assert!(matches!(p.paragraphs[1].statements[..], [Stmt::Exit { kind: ExitKind::Section, .. }, Stmt::SentenceEnd]), "{:?}", p.paragraphs[1].statements);
3304 }
3305
3306 fn linage_program(fds: &str, procedure: &str) -> String {
3307 [
3308 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n ENVIRONMENT DIVISION.\n INPUT-OUTPUT SECTION.\n FILE-CONTROL.\n",
3309 " SELECT P ASSIGN TO PDD.\n SELECT S ASSIGN TO SDD.\n SELECT Q ASSIGN TO QDD.\n",
3310 " DATA DIVISION.\n FILE SECTION.\n",
3311 fds,
3312 " WORKING-STORAGE SECTION.\n 01 SIZES.\n 05 BODY PIC 99.\n 01 T-M PIC 9.\n PROCEDURE DIVISION.\n",
3313 procedure,
3314 ]
3315 .concat()
3316 }
3317
3318 #[test]
3319 fn linage_phrases_come_in_any_order_and_an_sds_is_dropped() {
3320 let fds = [
3321 " 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",
3322 " SD S LINAGE 10.\n 01 S-REC PIC X.\n",
3323 " FD Q LABEL RECORDS STANDARD LINAGE 5 RECORDING MODE F.\n 01 Q-REC PIC X.\n",
3324 ]
3325 .concat();
3326 let p = crate::parse(&linage_program(&fds, " GOBACK.\n")).unwrap_or_else(|e| panic!("{e}"));
3327 let l = p.files[0].linage.as_ref().unwrap();
3328 assert!(matches!(&l.lines, LinageValue::Data(r) if r.name == "BODY" && r.qualifiers == ["SIZES"]));
3329 assert_eq!((&l.footing, &l.bottom), (&Some(LinageValue::Integer("45".into())), &Some(LinageValue::Integer("6".into()))));
3330 assert!(matches!(&l.top, Some(LinageValue::Data(r)) if r.name == "T-M"));
3331 assert_eq!(p.files[1].linage, None);
3332 assert_eq!((p.files[2].linage.as_ref().map(|l| &l.lines), p.files[2].recording), (Some(&LinageValue::Integer("5".into())), Some('F')));
3333 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")] {
3334 let text = linage_program(&format!(" FD P {fd}.\n 01 P-REC PIC X.\n"), " GOBACK.\n");
3335 let e = crate::parse(&text).unwrap_err();
3336 assert!(e.message.contains(expected), "{fd}: {}", e.message);
3337 }
3338 }
3339
3340 #[test]
3341 fn record_varying_keeps_from_and_to_apart_and_an_odo_table_its_fewest_occurrences() {
3342 let fds = [
3343 " FD P RECORD IS VARYING IN SIZE TO 80 CHARACTERS.\n 01 P-REC PIC X.\n",
3344 " FD S RECORD VARYING FROM 10.\n 01 S-REC.\n 05 N PIC 9.\n 05 T PIC X OCCURS 9 DEPENDING ON N.\n",
3345 " FD Q RECORD CONTAINS 10 TO 80.\n 01 Q-REC.\n 05 U PIC X OCCURS 2 TO 9 TIMES DEPENDING ON N.\n",
3346 ]
3347 .concat();
3348 let p = crate::parse(&linage_program(&fds, " GOBACK.\n")).unwrap_or_else(|e| panic!("{e}"));
3349 let bounds = |k: usize| (p.files[k].record_varying, p.files[k].record_min, p.files[k].record_max);
3350 assert_eq!([bounds(0), bounds(1), bounds(2)], [(true, None, Some(80)), (true, Some(10), None), (false, Some(10), Some(80))]);
3351 assert_eq!((p.files[1].records[2].occurs, p.files[1].records[2].occurs_min), (Some(9), Some(1)));
3352 assert_eq!((p.files[2].records[1].occurs, p.files[2].records[1].occurs_min), (Some(9), Some(2)));
3353 }
3354
3355 #[test]
3356 fn end_of_page_phrases_leave_a_read_its_not_at_end() {
3357 let procedure = [
3358 " READ Q AT END WRITE P-REC\n",
3359 " NOT AT END WRITE P-REC AT EOP CONTINUE END-WRITE\n END-READ\n",
3360 " WRITE P-REC BEFORE ADVANCING 2 LINES END-OF-PAGE CONTINUE\n",
3361 " NOT AT END-OF-PAGE CONTINUE\n END-WRITE\n",
3362 " WRITE P-REC INVALID KEY CONTINUE NOT EOP CONTINUE.\n",
3363 ]
3364 .concat();
3365 let fds = " FD P LINAGE 5.\n 01 P-REC PIC X.\n FD Q.\n 01 Q-REC PIC X.\n";
3366 let p = crate::parse(&linage_program(fds, &procedure)).unwrap_or_else(|e| panic!("{e}"));
3367 let s = &p.paragraphs[0].statements;
3368 let Stmt::Read(r) = &s[0] else { panic!("{:?}", s[0]) };
3369 let (Some(on), Some(not_on)) = (&r.at_end.on, &r.at_end.not_on) else { panic!("{r:?}") };
3370 assert!(matches!(&on[0], Stmt::Write { end_of_page, .. } if *end_of_page == Handlers::default()));
3371 assert!(matches!(¬_on[0], Stmt::Write { end_of_page, .. } if end_of_page.on.is_some() && end_of_page.not_on.is_none()));
3372 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()));
3373 assert!(matches!(&s[2], Stmt::Write { invalid, end_of_page, .. } if invalid.on.is_some() && end_of_page.not_on.is_some()));
3374 }
3375
3376 #[test]
3377 fn decimal_point_is_comma_and_currency_signs_reach_contained_programs() {
3378 let text = [
3379 " IDENTIFICATION DIVISION.\n PROGRAM-ID. OUTER.\n ENVIRONMENT DIVISION.\n",
3380 " CONFIGURATION SECTION.\n SPECIAL-NAMES.\n CURRENCY SIGN IS 'W'\n",
3381 " CURRENCY 'EUR ' WITH PICTURE SYMBOL 'y'\n DECIMAL-POINT IS COMMA.\n",
3382 " DATA DIVISION.\n WORKING-STORAGE SECTION.\n 01 A PIC 9V9 VALUE 1,5.\n",
3383 " PROCEDURE DIVISION.\n GOBACK.\n",
3384 " IDENTIFICATION DIVISION.\n PROGRAM-ID. INNER.\n PROCEDURE DIVISION.\n GOBACK.\n",
3385 " END PROGRAM INNER.\n END PROGRAM OUTER.\n",
3386 ]
3387 .concat();
3388 let programs = crate::parse_all_with(&text, &crate::copy::Libraries::default()).unwrap_or_else(|e| panic!("{e}"));
3389 assert!(programs.iter().all(|p| p.environment.decimal_point_comma));
3390 assert_eq!(programs[0].working_storage[0].value, Some(Literal::Number("1.5".into())));
3391 let signs = [CurrencySign { value: "W".into(), symbol: 'W' }, CurrencySign { value: "EUR ".into(), symbol: 'y' }];
3392 assert!(programs.iter().all(|p| p.environment.currency == signs));
3393 for (clause, why) in [
3394 ("'E'", "one character"),
3395 ("'EUR'", "one character"),
3396 ("'E9' PICTURE SYMBOL 'Y'", "a digit"),
3397 ("'EUR' PICTURE SYMBOL 'Z'", "PICTURE SYMBOL"),
3398 ("X'9F' PICTURE SYMBOL 'Y'", "hexadecimal"),
3399 ("'EUR' WITH 'Y'", "PICTURE SYMBOL after WITH"),
3400 ("'W'\n CURRENCY 'WON' PICTURE SYMBOL 'W'", "a second CURRENCY SIGN"),
3401 ] {
3402 let bad = text.replace("CURRENCY SIGN IS 'W'", &format!("CURRENCY SIGN IS {clause}"));
3403 let message = crate::parse(&bad).unwrap_err().message;
3404 assert!(message.contains(why), "{clause}: {message}");
3405 }
3406 }
3407
3408 #[test]
3409 fn a_picture_keeps_the_case_of_a_currency_symbol_and_picture_symbol_is_no_picture_string() {
3410 let text = concat!(
3411 " IDENTIFICATION DIVISION.\n PROGRAM-ID. T.\n ENVIRONMENT DIVISION.\n CONFIGURATION SECTION.\n",
3412 " SPECIAL-NAMES.\n CURRENCY SIGN 'CHF ' WITH PICTURE SYMBOL 'f'.\n DATA DIVISION.\n",
3413 " WORKING-STORAGE SECTION.\n 01 A PIC fff9v99.\n 01 B PIC zz9.\n",
3414 );
3415 let p = crate::parse(text).unwrap_or_else(|e| panic!("{e}"));
3416 assert_eq!(p.environment.currency, [CurrencySign { value: "CHF ".into(), symbol: 'f' }]);
3417 assert_eq!((p.working_storage[0].picture.as_deref(), p.working_storage[1].picture.as_deref()), (Some("fff9V99"), Some("ZZ9")));
3418 }
3419
3420 #[test]
3421 fn renames_and_condition_names_with_a_false_value() {
3422 let p = program(concat!(
3423 " 01 R.\n 05 A PIC X.\n 05 B PIC X.\n 88 B-ON VALUE 'Y' FALSE 'N'.\n",
3424 " 88 B-OFF VALUES 'N' 'X' WHEN SET TO FALSE IS SPACE.\n",
3425 " 66 AB RENAMES A THRU B.\n 66 BB RENAMES B OF R.\n",
3426 " PROCEDURE DIVISION.\n SET B-ON B-OFF TO FALSE.\n",
3427 ));
3428 let ws = &p.working_storage;
3429 assert_eq!((ws[3].false_value.as_ref(), ws[3].condition_values.len()), (Some(&Literal::Alnum("N".into())), 1));
3430 assert_eq!((ws[4].false_value.as_ref(), ws[4].condition_values.len()), (Some(&Literal::Figurative(Figurative::Space)), 2));
3431 let (first, last) = ws[5].renames.as_ref().unwrap();
3432 assert_eq!((ws[5].level, first.name.as_str(), last.as_ref().map(|r| r.name.as_str())), (66, "A", Some("B")));
3433 assert_eq!(ws[6].renames.as_ref().unwrap().0.qualifiers, ["R"]);
3434 assert!(matches!(&p.paragraphs[0].statements[0], Stmt::Set { set: SetStmt::ConditionFalse(t), .. } if t.len() == 2));
3435 }
3436}