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