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