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