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