Skip to main content

ironwork_syntax/
parser.rs

1use crate::ast::*;
2use crate::lexer::{Tok, Token};
3use crate::{Error, Pos};
4
5/// Every program in the source, first to last, with nested programs after the one containing them.
6pub fn parse(tokens: &[Token], options: Vec<String>) -> Result<Vec<Program>, Error> {
7    let mut parser = Parser { tokens, at: 0, exec_declarations: Vec::new(), cics: false };
8    let mut programs = Vec::new();
9    parser.program(&options, &mut programs)?;
10    while parser.peek().is_some() {
11        if !parser.at_division(&["IDENTIFICATION", "ID"]) {
12            return Err(parser.error("another program, or the end of the source"));
13        }
14        parser.program(&options, &mut programs)?;
15    }
16    Ok(programs)
17}
18
19/// Words that begin a statement: a list of operands ends at any of them.
20const VERBS: &[&str] = &[
21    "MOVE", "COMPUTE", "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "IF", "PERFORM", "DISPLAY", "INITIALIZE", "GO", "GOBACK", "STOP",
22    "CONTINUE", "EXIT", "EVALUATE", "SET", "CALL", "ACCEPT", "STRING", "UNSTRING", "INSPECT", "READ", "WRITE", "OPEN", "CLOSE",
23    "REWRITE", "DELETE", "START", "SEARCH", "SORT", "MERGE", "RETURN", "RELEASE", "CANCEL", "EXEC", "NEXT",
24];
25
26/// Words that end a phrase or a nested block.
27const PHRASE_WORDS: &[&str] = &[
28    "ELSE", "END-IF", "END-PERFORM", "END-COMPUTE", "END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE", "END-DISPLAY", "WHEN",
29    "TO", "FROM", "BY", "INTO", "GIVING", "REMAINDER", "ROUNDED", "ON", "NOT", "SIZE", "UNTIL", "VARYING", "TIMES", "THRU", "THROUGH",
30    "AND", "OR", "THEN", "UPON", "WITH", "IS", "END-EVALUATE", "ALSO", "OTHER", "OF", "IN", "AT", "END", "END-READ", "END-WRITE",
31    "BEFORE", "AFTER", "ADVANCING", "INPUT", "OUTPUT", "EXTEND", "I-O", "REVERSED", "USING", "RETURNING", "EXCEPTION", "OVERFLOW",
32    "END-CALL", "OMITTED", "CONTENT", "REFERENCE", "VALUE", "UP", "DOWN", "DELIMITED", "DELIMITER", "COUNT", "POINTER", "TALLYING",
33    "REPLACING", "CONVERTING", "INITIAL", "FOR", "CHARACTERS", "LEADING", "FIRST", "ALL", "END-STRING", "END-UNSTRING", "END-SEARCH",
34    "NEXT", "INVALID", "KEY", "END-REWRITE", "END-DELETE", "END-START",
35];
36
37fn figurative(word: &str) -> Option<Figurative> {
38    Some(match word {
39        "ZERO" | "ZEROS" | "ZEROES" => Figurative::Zero,
40        "SPACE" | "SPACES" => Figurative::Space,
41        "HIGH-VALUE" | "HIGH-VALUES" => Figurative::HighValue,
42        "LOW-VALUE" | "LOW-VALUES" => Figurative::LowValue,
43        "QUOTE" | "QUOTES" => Figurative::Quote,
44        "NULL" | "NULLS" => Figurative::Null,
45        _ => return None,
46    })
47}
48
49fn usage_word(word: &str) -> Option<Usage> {
50    Some(match word {
51        "DISPLAY" => Usage::Display,
52        "BINARY" | "COMP" | "COMPUTATIONAL" | "COMP-4" | "COMPUTATIONAL-4" => Usage::Binary,
53        "COMP-5" | "COMPUTATIONAL-5" => Usage::NativeBinary,
54        "PACKED-DECIMAL" | "COMP-3" | "COMPUTATIONAL-3" => Usage::Packed,
55        "COMP-1" | "COMPUTATIONAL-1" => Usage::Float1,
56        "COMP-2" | "COMPUTATIONAL-2" => Usage::Float2,
57        "NATIONAL" => Usage::National,
58        "POINTER" => Usage::Pointer,
59        "INDEX" => Usage::Index,
60        _ => return None,
61    })
62}
63
64struct Parser<'a> {
65    tokens: &'a [Token],
66    at: usize,
67    /// DATA DIVISION EXEC blocks of the program being parsed.
68    exec_declarations: Vec<ExecBlock>,
69    /// Whether the program being parsed has EXEC CICS, so the translator's additions apply.
70    cics: bool,
71}
72
73type R<T> = Result<T, Error>;
74
75/// The statements of an ON phrase and of its NOT ON phrase.
76type OnPhrases = (Option<Vec<Stmt>>, Option<Vec<Stmt>>);
77
78impl Parser<'_> {
79    fn peek(&self) -> Option<&Tok> {
80        self.tokens.get(self.at).map(|t| &t.tok)
81    }
82
83    fn peek_at(&self, ahead: usize) -> Option<&Tok> {
84        self.tokens.get(self.at + ahead).map(|t| &t.tok)
85    }
86
87    fn pos(&self) -> Pos {
88        self.tokens.get(self.at).or(self.tokens.last()).map(|t| t.pos).unwrap_or_default()
89    }
90
91    fn word(&self) -> Option<&str> {
92        match self.peek() {
93            Some(Tok::Word(w)) => Some(w),
94            _ => None,
95        }
96    }
97
98    fn word_at(&self, ahead: usize) -> Option<&str> {
99        match self.peek_at(ahead) {
100            Some(Tok::Word(w)) => Some(w),
101            _ => None,
102        }
103    }
104
105    fn is_word(&self, w: &str) -> bool {
106        self.word() == Some(w)
107    }
108
109    fn accept_word(&mut self, w: &str) -> bool {
110        let yes = self.is_word(w);
111        if yes {
112            self.at += 1;
113        }
114        yes
115    }
116
117    fn accept_any(&mut self, words: &[&str]) -> Option<String> {
118        let w = self.word().filter(|w| words.contains(w))?.to_owned();
119        self.at += 1;
120        Some(w)
121    }
122
123    fn expect_word(&mut self, w: &str) -> R<()> {
124        if self.accept_word(w) { Ok(()) } else { Err(self.error(format!("expected {w}"))) }
125    }
126
127    fn accept(&mut self, tok: &Tok) -> bool {
128        let yes = self.peek() == Some(tok);
129        if yes {
130            self.at += 1;
131        }
132        yes
133    }
134
135    fn expect(&mut self, tok: &Tok, what: &str) -> R<()> {
136        if self.accept(tok) { Ok(()) } else { Err(self.error(format!("expected {what}"))) }
137    }
138
139    fn error(&self, message: impl Into<String>) -> Error {
140        let found = match self.peek() {
141            None => "end of source".to_owned(),
142            Some(Tok::Word(w)) => w.clone(),
143            Some(t) => format!("{t:?}"),
144        };
145        Error::at(self.pos(), format!("{}, found {found}", message.into()))
146    }
147
148    fn name(&mut self, what: &str) -> R<String> {
149        match self.peek() {
150            Some(Tok::Word(w)) => {
151                let w = w.clone();
152                self.at += 1;
153                Ok(w)
154            }
155            _ => Err(self.error(format!("expected {what}"))),
156        }
157    }
158
159    fn at_division(&self, names: &[&str]) -> bool {
160        self.word().is_some_and(|w| names.contains(&w)) && self.word_at(1) == Some("DIVISION")
161    }
162
163    fn program(&mut self, options: &[String], out: &mut Vec<Program>) -> R<()> {
164        let outer = (std::mem::take(&mut self.exec_declarations), std::mem::take(&mut self.cics));
165        let parsed = self.one_program(options, out);
166        (self.exec_declarations, self.cics) = outer;
167        parsed
168    }
169
170    fn one_program(&mut self, options: &[String], out: &mut Vec<Program>) -> R<()> {
171        if !self.accept_word("IDENTIFICATION") {
172            self.expect_word("ID")?;
173        }
174        self.expect_word("DIVISION")?;
175        self.expect(&Tok::Period, "a period")?;
176        self.expect_word("PROGRAM-ID")?;
177        self.accept(&Tok::Period);
178        let id = match self.peek() {
179            Some(Tok::Alnum(s)) => {
180                let s = s.clone();
181                self.at += 1;
182                s
183            }
184            _ => self.name("a program name")?,
185        };
186        let (mut initial, mut recursive) = (false, false);
187        while let Some(t) = self.peek() {
188            if *t == Tok::Period {
189                self.at += 1;
190                break;
191            }
192            initial |= self.is_word("INITIAL");
193            recursive |= self.is_word("RECURSIVE");
194            self.at += 1;
195        }
196        while self.peek().is_some() && !self.at_division(&["ENVIRONMENT", "DATA", "PROCEDURE", "IDENTIFICATION", "ID"]) && !self.at_end_program() {
197            self.at += 1;
198        }
199        let mut files = Vec::new();
200        if self.at_division(&["ENVIRONMENT"]) {
201            files = self.environment()?;
202        }
203        let (mut working_storage, mut local_storage, mut linkage) = (Vec::new(), Vec::new(), Vec::new());
204        if self.at_division(&["DATA"]) {
205            self.at += 2;
206            self.expect(&Tok::Period, "a period")?;
207            while !self.at_division(&["PROCEDURE", "IDENTIFICATION", "ID"]) && !self.at_end_program() && self.peek().is_some() {
208                if self.data_exec()? {
209                    continue;
210                }
211                let section = self.name("a DATA DIVISION section")?;
212                self.expect_word("SECTION")?;
213                self.expect(&Tok::Period, "a period")?;
214                match section.as_str() {
215                    "WORKING-STORAGE" => working_storage = self.data_entries()?,
216                    "LINKAGE" => linkage = self.data_entries()?,
217                    "LOCAL-STORAGE" => local_storage = self.data_entries()?,
218                    "FILE" => self.file_section(&mut files)?,
219                    other => return Err(self.error(format!("the {other} SECTION is not supported yet"))),
220                }
221            }
222        }
223        let (mut using, mut returning) = (Vec::new(), None);
224        let paragraphs = if self.at_division(&["PROCEDURE"]) {
225            self.at += 2;
226            if self.accept_word("USING") {
227                let mut by_value = false;
228                loop {
229                    if self.accept_word("BY") {
230                        by_value = self.accept_any(&["REFERENCE", "VALUE"]).as_deref() == Some("VALUE");
231                        continue;
232                    }
233                    if !self.starts_ref() {
234                        break;
235                    }
236                    using.push(Param { by_value, name: self.name("a LINKAGE item")? });
237                }
238            }
239            if self.accept_word("RETURNING") {
240                returning = Some(self.name("a RETURNING item")?);
241            }
242            self.expect(&Tok::Period, "a period after the PROCEDURE DIVISION header")?;
243            self.paragraphs()?
244        } else {
245            Vec::new()
246        };
247        if let Some(f) = files.iter().find(|f| f.assign.is_empty()) {
248            return Err(Error::at(f.pos, format!("{} has no SELECT ... ASSIGN", f.name)));
249        }
250        if self.cics {
251            self.translator_additions(&mut linkage, &mut using)?;
252        }
253        let exec_declarations = std::mem::take(&mut self.exec_declarations);
254        let mut nested = Vec::new();
255        while self.at_division(&["IDENTIFICATION", "ID"]) {
256            self.program(options, &mut nested)?;
257        }
258        if self.at_end_program() {
259            self.at += 2;
260            if self.word().is_some() || matches!(self.peek(), Some(Tok::Alnum(_))) {
261                self.at += 1;
262            }
263            self.accept(&Tok::Period);
264        }
265        out.push(Program {
266            id,
267            options: options.to_vec(),
268            initial,
269            recursive,
270            working_storage,
271            local_storage,
272            linkage,
273            using,
274            returning,
275            paragraphs,
276            files,
277            sources: Vec::new(),
278            exec_declarations,
279        });
280        out.extend(nested);
281        Ok(())
282    }
283
284    fn at_end_program(&self) -> bool {
285        self.is_word("END") && self.word_at(1) == Some("PROGRAM")
286    }
287
288    /// The ENVIRONMENT DIVISION: SELECT entries of FILE-CONTROL; everything else is skipped, except
289    /// what would change the meaning of the rest of the program.
290    fn environment(&mut self) -> R<Vec<FileDecl>> {
291        let mut files = Vec::new();
292        while self.peek().is_some() && !self.at_division(&["DATA", "PROCEDURE"]) {
293            if self.is_word("DECIMAL-POINT") {
294                return Err(self.error("DECIMAL-POINT IS COMMA is not supported yet"));
295            }
296            if self.accept_word("SELECT") {
297                files.push(self.select()?);
298                continue;
299            }
300            self.at += 1;
301        }
302        Ok(files)
303    }
304
305    fn select(&mut self) -> R<FileDecl> {
306        let pos = self.pos();
307        let optional = self.accept_word("OPTIONAL");
308        let name = self.name("a file name")?;
309        let mut f = FileDecl {
310            name,
311            assign: String::new(),
312            organization: Organization::Sequential,
313            access: Access::Sequential,
314            record_key: None,
315            alternate_keys: Vec::new(),
316            relative_key: None,
317            optional,
318            status: None,
319            recording: None,
320            record_min: None,
321            record_max: None,
322            records: Vec::new(),
323            pos,
324        };
325        while !self.accept(&Tok::Period) {
326            let clause = self.name("a SELECT clause or a period")?;
327            match clause.as_str() {
328                "ASSIGN" => {
329                    self.accept_word("TO");
330                    let target = match self.peek().cloned() {
331                        Some(Tok::Word(w)) | Some(Tok::Alnum(w)) => w,
332                        _ => return Err(self.error("a DD name after ASSIGN")),
333                    };
334                    self.at += 1;
335                    let target = target.to_ascii_uppercase();
336                    f.assign = target.rsplit('-').next().filter(|_| target.contains("-S-") || target.starts_with("S-") || target.starts_with("AS-")).unwrap_or(&target).to_owned();
337                }
338                "RECORD" if !self.is_word("SEQUENTIAL") => {
339                    self.accept_word("KEY");
340                    self.accept_word("IS");
341                    f.record_key = Some(self.reference()?);
342                }
343                "RELATIVE" if self.is_word("KEY") => {
344                    self.at += 1;
345                    self.accept_word("IS");
346                    f.relative_key = Some(self.reference()?);
347                }
348                "ALTERNATE" => {
349                    self.accept_word("RECORD");
350                    self.expect_word("KEY")?;
351                    self.accept_word("IS");
352                    let key = self.reference()?;
353                    let duplicates = self.accept_word("WITH") | self.is_word("DUPLICATES");
354                    if duplicates {
355                        self.expect_word("DUPLICATES")?;
356                    }
357                    f.alternate_keys.push((key, duplicates));
358                }
359                "ORGANIZATION" | "LINE" | "RECORD" | "SEQUENTIAL" | "INDEXED" | "RELATIVE" => {
360                    if clause == "ORGANIZATION" {
361                        self.accept_word("IS");
362                    }
363                    let first = if clause == "ORGANIZATION" { self.name("an organization")? } else { clause.clone() };
364                    f.organization = match first.as_str() {
365                        "LINE" => {
366                            self.expect_word("SEQUENTIAL")?;
367                            Organization::LineSequential
368                        }
369                        "RECORD" => {
370                            self.expect_word("SEQUENTIAL")?;
371                            Organization::Sequential
372                        }
373                        "SEQUENTIAL" => Organization::Sequential,
374                        "INDEXED" => Organization::Indexed,
375                        "RELATIVE" => Organization::Relative,
376                        other => return Err(self.error(format!("ORGANIZATION {other} is not supported yet"))),
377                    };
378                }
379                "ACCESS" => {
380                    self.accept_word("MODE");
381                    self.accept_word("IS");
382                    f.access = match self.name("an access mode")?.as_str() {
383                        "SEQUENTIAL" => Access::Sequential,
384                        "RANDOM" => Access::Random,
385                        "DYNAMIC" => Access::Dynamic,
386                        other => return Err(self.error(format!("ACCESS MODE {other} is not an access mode"))),
387                    };
388                }
389                "FILE" | "STATUS" => {
390                    if clause == "FILE" {
391                        self.expect_word("STATUS")?;
392                    }
393                    self.accept_word("IS");
394                    f.status = Some(self.reference()?);
395                    if self.starts_ref() && !self.word().is_some_and(|w| SELECT_CLAUSES.contains(&w)) {
396                        self.reference()?;
397                    }
398                }
399                "RESERVE" | "PADDING" => {
400                    while self.peek().is_some() && self.peek() != Some(&Tok::Period) && !self.word().is_some_and(|w| SELECT_CLAUSES.contains(&w)) {
401                        self.at += 1;
402                    }
403                }
404                other => return Err(self.error(format!("{other} is not a SELECT clause ironwork for COBOL supports yet"))),
405            }
406        }
407        Ok(f)
408    }
409
410    /// FD entries: the clauses that shape records are kept, the rest skipped; then each file's
411    /// record descriptions.
412    fn file_section(&mut self, files: &mut [FileDecl]) -> R<()> {
413        while self.is_word("FD") || self.is_word("SD") {
414            if self.is_word("SD") {
415                return Err(self.error("SD (sort files) is not supported yet"));
416            }
417            self.at += 1;
418            let pos = self.pos();
419            let name = self.name("a file name")?;
420            let Some(index) = files.iter().position(|f| f.name == name) else {
421                return Err(Error::at(pos, format!("FD {name} has no SELECT")));
422            };
423            while !self.accept(&Tok::Period) {
424                match self.name("an FD clause or a period")?.as_str() {
425                    "RECORDING" => {
426                        self.accept_word("MODE");
427                        self.accept_word("IS");
428                        let mode = self.name("F, V, U or S")?;
429                        files[index].recording = mode.chars().next();
430                    }
431                    "RECORD" => {
432                        self.accept_word("CONTAINS");
433                        self.accept_word("IS");
434                        if self.accept_word("VARYING") {
435                            self.accept_word("IN");
436                            self.accept_word("SIZE");
437                            self.accept_word("FROM");
438                        }
439                        let number = |p: &mut Self| -> R<Option<u32>> {
440                            match p.peek() {
441                                Some(Tok::Number(n)) => {
442                                    let v = n.parse().map_err(|_| p.error("a record length"))?;
443                                    p.at += 1;
444                                    Ok(Some(v))
445                                }
446                                _ => Ok(None),
447                            }
448                        };
449                        let first = number(self)?;
450                        let second = if self.accept_word("TO") { number(self)? } else { None };
451                        files[index].record_min = first;
452                        files[index].record_max = second.or(first);
453                        self.accept_word("CHARACTERS");
454                        if self.accept_word("DEPENDING") {
455                            self.accept_word("ON");
456                            self.reference()?;
457                        }
458                    }
459                    _ => {
460                        while self.peek().is_some() && self.peek() != Some(&Tok::Period)
461                            && !self.word().is_some_and(|w| matches!(w, "RECORDING" | "RECORD" | "BLOCK" | "LABEL" | "DATA" | "VALUE"))
462                        {
463                            self.at += 1;
464                        }
465                    }
466                }
467            }
468            files[index].records = self.data_entries()?;
469        }
470        Ok(())
471    }
472
473    /// An EXEC block among DATA DIVISION entries, kept as a declaration with its period.
474    fn data_exec(&mut self) -> R<bool> {
475        let Some(Tok::Exec(text)) = self.peek().cloned() else { return Ok(false) };
476        let block = self.exec_block(&text, self.pos());
477        self.at += 1;
478        self.accept(&Tok::Period);
479        self.exec_declarations.push(block);
480        Ok(true)
481    }
482
483    /// What the CICS translator adds: the EXEC interface block, a one-byte DFHCOMMAREA when the
484    /// program declares none, and both at the head of PROCEDURE DIVISION USING.
485    fn translator_additions(&mut self, linkage: &mut Vec<DataEntry>, using: &mut Vec<Param>) -> R<()> {
486        let defined = |l: &[DataEntry], n: &str| l.iter().any(|e| e.level == 1 && e.name.as_deref() == Some(n));
487        let mut added = Vec::new();
488        if !defined(linkage, "DFHEIBLK") {
489            added.extend(system_entries("DFHEIBLK")?);
490        }
491        if !defined(linkage, "DFHCOMMAREA") {
492            added.extend(system_text_entries("       01  DFHCOMMAREA PIC X(1).\n")?);
493        }
494        linkage.splice(0..0, added);
495        for (i, name) in ["DFHEIBLK", "DFHCOMMAREA"].into_iter().enumerate() {
496            if using.get(i).map(|p| p.name.as_str()) != Some(name) {
497                using.insert(i, Param { by_value: false, name: name.into() });
498            }
499        }
500        Ok(())
501    }
502
503    fn exec_block(&mut self, text: &str, pos: Pos) -> ExecBlock {
504        let (kind_word, body) = text.split_once(' ').unwrap_or((text, ""));
505        let kind = match kind_word.to_ascii_uppercase().as_str() {
506            "SQL" => ExecKind::Sql,
507            "CICS" => ExecKind::Cics,
508            "DLI" => ExecKind::Dli,
509            _ => ExecKind::Other,
510        };
511        let words: Vec<String> = body.split_whitespace().map(|w| w.to_ascii_uppercase()).collect();
512        let word = |i: usize| words.get(i).map(String::as_str).unwrap_or("");
513        let mut block = ExecBlock { kind, command: word(0).to_owned(), options: Vec::new(), host_variables: Vec::new(), text: text.to_owned(), pos };
514        match kind {
515            ExecKind::Sql => {
516                block.command = match (word(0), word(1), word(2)) {
517                    ("DECLARE", _, "CURSOR") => "DECLARE CURSOR".into(),
518                    ("DECLARE", _, "TABLE") => "DECLARE TABLE".into(),
519                    ("DECLARE", _, "STATEMENT") => "DECLARE STATEMENT".into(),
520                    ("BEGIN" | "END", "DECLARE", "SECTION") => format!("{} DECLARE SECTION", word(0)),
521                    (first, _, _) => first.into(),
522                };
523                block.host_variables = host_variables(body, pos);
524            }
525            ExecKind::Cics => {
526                self.cics = true;
527                block.options = cics_options(body);
528                if let Some((first, None)) = block.options.first().cloned() {
529                    block.command = first.clone();
530                    block.options.remove(0);
531                    if let Some((second, None)) = block.options.first().cloned()
532                        && crate::system::cics_two_word(&first, &second)
533                    {
534                        block.command = format!("{first} {second}");
535                        block.options.remove(0);
536                    }
537                }
538                let labels = block.command.starts_with("HANDLE");
539                for (_, arg) in &mut block.options {
540                    if let Some(ExecArg::Text(t)) = arg
541                        && !labels
542                        && let Some(op) = operand_of(t, pos)
543                    {
544                        *arg = Some(ExecArg::Operand(op));
545                    }
546                }
547            }
548            _ => {}
549        }
550        block
551    }
552
553    fn data_entries(&mut self) -> R<Vec<DataEntry>> {
554        let mut entries = Vec::new();
555        loop {
556            if self.data_exec()? {
557                continue;
558            }
559            let Some(Tok::Number(level)) = self.peek() else { break };
560            let pos = self.pos();
561            let level: u8 = level.parse().map_err(|_| self.error("a level number"))?;
562            self.at += 1;
563            entries.push(self.data_entry(level, pos)?);
564        }
565        Ok(entries)
566    }
567
568    fn data_entry(&mut self, level: u8, pos: Pos) -> R<DataEntry> {
569        let mut e = DataEntry {
570            level,
571            name: None,
572            picture: None,
573            usage: None,
574            value: None,
575            redefines: None,
576            occurs: None,
577            depending_on: None,
578            sign: None,
579            justified: false,
580            sync: false,
581            blank_when_zero: false,
582            indexed_by: Vec::new(),
583            keys: Vec::new(),
584            condition_values: Vec::new(),
585            pos,
586        };
587        if let Some(w) = self.word()
588            && !is_clause_word(w)
589        {
590            if w != "FILLER" {
591                e.name = Some(w.to_owned());
592            }
593            self.at += 1;
594        }
595        while !self.accept(&Tok::Period) {
596            let clause = self.name("a data description clause or a period")?;
597            match clause.as_str() {
598                "PIC" | "PICTURE" => {
599                    self.accept_word("IS");
600                    match self.peek() {
601                        Some(Tok::Pic(p)) => {
602                            e.picture = Some(p.clone());
603                            self.at += 1;
604                        }
605                        _ => return Err(self.error("a PICTURE character-string")),
606                    }
607                }
608                "USAGE" => {
609                    self.accept_word("IS");
610                    let w = self.name("a usage")?;
611                    e.usage = Some(usage_word(&w).ok_or_else(|| Error::at(pos, format!("USAGE {w} is not supported yet")))?);
612                }
613                "VALUE" | "VALUES" => {
614                    self.accept_word("IS");
615                    self.accept_word("ARE");
616                    if level == 88 {
617                        while self.peek().is_some() && self.peek() != Some(&Tok::Period) {
618                            let low = self.literal()?;
619                            let high = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.literal()?) } else { None };
620                            e.condition_values.push((low, high));
621                        }
622                    } else {
623                        e.value = Some(self.literal()?);
624                    }
625                }
626                "REDEFINES" => e.redefines = Some(self.name("the item redefined")?),
627                "OCCURS" => {
628                    let count = |p: &mut Self| -> R<u32> {
629                        let n = match p.peek() {
630                            Some(Tok::Number(n)) => n.parse().map_err(|_| p.error("an OCCURS count"))?,
631                            _ => return Err(p.error("an OCCURS count")),
632                        };
633                        p.at += 1;
634                        Ok(n)
635                    };
636                    let mut most = count(self)?;
637                    if self.accept_word("TO") {
638                        most = count(self)?;
639                    }
640                    e.occurs = Some(most);
641                    self.accept_word("TIMES");
642                    if self.accept_word("DEPENDING") {
643                        self.accept_word("ON");
644                        e.depending_on = Some(self.reference()?);
645                    }
646                    loop {
647                        if let Some(order) = self.accept_any(&["ASCENDING", "DESCENDING"]) {
648                            self.accept_word("KEY");
649                            self.accept_word("IS");
650                            while self.word().is_some_and(|w| !is_clause_word(w) && !matches!(w, "INDEXED" | "ASCENDING" | "DESCENDING")) {
651                                e.keys.push((order == "ASCENDING", self.reference()?));
652                            }
653                        } else if self.accept_word("INDEXED") {
654                            self.accept_word("BY");
655                            while self.word().is_some_and(|w| !is_clause_word(w) && !matches!(w, "ASCENDING" | "DESCENDING")) {
656                                e.indexed_by.push(self.name("an index name")?);
657                            }
658                        } else {
659                            break;
660                        }
661                    }
662                }
663                "SIGN" | "LEADING" | "TRAILING" => {
664                    if clause == "SIGN" {
665                        self.accept_word("IS");
666                    }
667                    let side = if clause == "SIGN" { self.name("LEADING or TRAILING")? } else { clause.clone() };
668                    let position = match side.as_str() {
669                        "LEADING" => SignPosition::Leading,
670                        "TRAILING" => SignPosition::Trailing,
671                        _ => return Err(self.error("LEADING or TRAILING")),
672                    };
673                    let separate = self.accept_word("SEPARATE");
674                    if separate {
675                        self.accept_word("CHARACTER");
676                    }
677                    e.sign = Some(SignClause { position, separate });
678                }
679                "JUSTIFIED" | "JUST" => {
680                    self.accept_word("RIGHT");
681                    e.justified = true;
682                }
683                "SYNC" | "SYNCHRONIZED" => {
684                    self.accept_any(&["LEFT", "RIGHT"]);
685                    e.sync = true;
686                }
687                "BLANK" => {
688                    self.accept_word("WHEN");
689                    if self.accept_any(&["ZERO", "ZEROS", "ZEROES"]).is_none() {
690                        return Err(self.error("ZERO after BLANK WHEN"));
691                    }
692                    e.blank_when_zero = true;
693                }
694                "GLOBAL" | "EXTERNAL" => {}
695                other => match usage_word(other) {
696                    Some(u) => e.usage = Some(u),
697                    None => return Err(Error::at(self.tokens[self.at - 1].pos, format!("{other} is not a data description clause ironwork for COBOL supports yet"))),
698                },
699            }
700        }
701        Ok(e)
702    }
703
704    fn literal(&mut self) -> R<Literal> {
705        let lit = match self.peek().cloned() {
706            Some(Tok::Alnum(s)) => Literal::Alnum(s),
707            Some(Tok::Hex(b)) => Literal::Hex(b),
708            Some(Tok::National(s)) => Literal::National(s),
709            Some(Tok::Number(n)) => Literal::Number(n),
710            Some(Tok::Word(w)) if w == "ALL" => {
711                self.at += 1;
712                return Ok(Literal::All(Box::new(self.literal()?)));
713            }
714            Some(Tok::Word(w)) => Literal::Figurative(figurative(&w).ok_or_else(|| self.error("a literal"))?),
715            _ => return Err(self.error("a literal")),
716        };
717        self.at += 1;
718        Ok(lit)
719    }
720
721    fn paragraph_header(&self) -> bool {
722        self.tokens.get(self.at).is_some_and(|t| t.area_a && matches!(t.tok, Tok::Word(_))) && self.peek_at(1) == Some(&Tok::Period)
723    }
724
725    fn section_header(&self) -> bool {
726        self.tokens.get(self.at).is_some_and(|t| matches!(t.tok, Tok::Word(_))) && self.word_at(1) == Some("SECTION")
727    }
728
729    fn paragraphs(&mut self) -> R<Vec<Paragraph>> {
730        let mut paragraphs: Vec<Paragraph> = Vec::new();
731        let mut section: Option<String> = None;
732        loop {
733            if self.peek().is_none() || self.at_end_program() || self.at_division(&["IDENTIFICATION", "ID"]) {
734                break;
735            }
736            if self.is_word("DECLARATIVES") {
737                return Err(self.error("DECLARATIVES are not supported yet"));
738            }
739            if self.section_header() {
740                let pos = self.pos();
741                let name = self.name("a section name")?;
742                self.at += 1;
743                if matches!(self.peek(), Some(Tok::Number(_))) {
744                    self.at += 1;
745                }
746                self.expect(&Tok::Period, "a period after the section header")?;
747                section = Some(name.clone());
748                paragraphs.push(Paragraph { name, statements: Vec::new(), section: section.clone(), is_section: true, pos });
749                continue;
750            }
751            if self.paragraph_header() {
752                let pos = self.pos();
753                let name = self.name("a paragraph name")?;
754                self.at += 1;
755                paragraphs.push(Paragraph { name, statements: Vec::new(), section: section.clone(), is_section: false, pos });
756                continue;
757            }
758            if self.accept(&Tok::Period) {
759                if let Some(p) = paragraphs.last_mut()
760                    && p.statements.last().is_some_and(|s| *s != Stmt::SentenceEnd)
761                {
762                    p.statements.push(Stmt::SentenceEnd);
763                }
764                continue;
765            }
766            let block = self.block(&[])?;
767            if block.is_empty() {
768                return Err(self.error("a statement"));
769            }
770            if paragraphs.is_empty() {
771                paragraphs.push(Paragraph { name: String::new(), statements: Vec::new(), section: None, is_section: false, pos: self.pos() });
772            }
773            paragraphs.last_mut().unwrap().statements.extend(block);
774        }
775        Ok(paragraphs)
776    }
777
778    /// Statements up to a period, a paragraph header, or one of `stops`, none of them consumed.
779    fn block(&mut self, stops: &[&str]) -> R<Vec<Stmt>> {
780        let mut out = Vec::new();
781        while let Some(tok) = self.peek() {
782            if *tok == Tok::Period || self.paragraph_header() || self.section_header() || self.word().is_some_and(|w| stops.contains(&w)) {
783                break;
784            }
785            if let Some(Tok::Exec(text)) = self.peek().cloned() {
786                let block = self.exec_block(&text, self.pos());
787                self.at += 1;
788                out.push(Stmt::Exec(Box::new(block)));
789                continue;
790            }
791            if !self.word().is_some_and(|w| VERBS.contains(&w)) {
792                break;
793            }
794            out.push(self.statement()?);
795        }
796        Ok(out)
797    }
798
799    fn statement(&mut self) -> R<Stmt> {
800        let pos = self.pos();
801        let verb = self.name("a statement")?;
802        Ok(match verb.as_str() {
803            "MOVE" => {
804                if self.accept_any(&["CORRESPONDING", "CORR"]).is_some() {
805                    return Err(Error::at(pos, "MOVE CORRESPONDING is not supported yet"));
806                }
807                let from = self.operand()?;
808                self.expect_word("TO")?;
809                let to = self.refs()?;
810                Stmt::Move { from, to, pos }
811            }
812            "COMPUTE" => {
813                let targets = self.targets()?;
814                if !self.accept(&Tok::Eq) {
815                    self.expect_word("EQUAL")?;
816                }
817                let expr = self.expr()?;
818                let size_error = self.size_error()?;
819                self.accept_word("END-COMPUTE");
820                Stmt::Compute { targets, expr, size_error, pos }
821            }
822            "ADD" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" => Stmt::Arith(Box::new(self.arith(&verb, pos)?)),
823            "IF" => {
824                let cond = self.cond()?;
825                self.accept_word("THEN");
826                let then = self.block(&["ELSE", "END-IF"])?;
827                let otherwise = if self.accept_word("ELSE") { self.block(&["END-IF"])? } else { Vec::new() };
828                self.accept_word("END-IF");
829                Stmt::If { cond, then, otherwise, pos }
830            }
831            "PERFORM" => self.perform(pos)?,
832            "DISPLAY" => {
833                let mut items = Vec::new();
834                while self.starts_operand() {
835                    items.push(self.operand()?);
836                }
837                if self.accept_word("UPON") {
838                    self.name("a mnemonic name")?;
839                }
840                let no_advancing = self.accept_word("WITH") | self.is_word("NO");
841                if no_advancing {
842                    self.expect_word("NO")?;
843                    self.expect_word("ADVANCING")?;
844                }
845                self.accept_word("END-DISPLAY");
846                Stmt::Display { items, no_advancing, pos }
847            }
848            "INITIALIZE" => Stmt::Initialize { targets: self.refs()?, pos },
849            "CALL" => Stmt::Call(Box::new(self.call(pos)?)),
850            "CANCEL" => {
851                let mut targets = Vec::new();
852                while self.starts_operand() {
853                    targets.push(self.operand()?);
854                }
855                if targets.is_empty() {
856                    return Err(self.error("a program to CANCEL"));
857                }
858                Stmt::Cancel { targets, pos }
859            }
860            "SET" => Stmt::Set { set: self.set()?, pos },
861            "STRING" => Stmt::String(Box::new(self.string(pos)?)),
862            "UNSTRING" => Stmt::Unstring(Box::new(self.unstring(pos)?)),
863            "INSPECT" => Stmt::Inspect(Box::new(self.inspect(pos)?)),
864            "SEARCH" => Stmt::Search(Box::new(self.search(pos)?)),
865            "NEXT" => {
866                self.expect_word("SENTENCE")?;
867                Stmt::NextSentence
868            }
869            "ACCEPT" => {
870                let target = self.reference()?;
871                let from = if self.accept_word("FROM") {
872                    match self.name("SYSIN, DATE, DAY, DAY-OF-WEEK or TIME")?.as_str() {
873                        "DATE" => AcceptFrom::Date { four_digit_year: self.accept_word("YYYYMMDD") },
874                        "DAY" => AcceptFrom::Day { four_digit_year: self.accept_word("YYYYDDD") },
875                        "DAY-OF-WEEK" => AcceptFrom::DayOfWeek,
876                        "TIME" => AcceptFrom::Time,
877                        _ => AcceptFrom::Sysin,
878                    }
879                } else {
880                    AcceptFrom::Sysin
881                };
882                self.accept_word("END-ACCEPT");
883                Stmt::Accept { target, from, pos }
884            }
885            "OPEN" => {
886                let mut files = Vec::new();
887                while let Some(mode) = self.accept_any(&["INPUT", "OUTPUT", "EXTEND", "I-O"]) {
888                    let mode = match mode.as_str() {
889                        "INPUT" => OpenMode::Input,
890                        "OUTPUT" => OpenMode::Output,
891                        "EXTEND" => OpenMode::Extend,
892                        _ => OpenMode::InputOutput,
893                    };
894                    while self.starts_ref() {
895                        files.push((mode, self.name("a file name")?));
896                        self.accept_any(&["REVERSED"]);
897                        if self.accept_word("WITH") {
898                            self.expect_word("NO")?;
899                            self.expect_word("REWIND")?;
900                        }
901                    }
902                }
903                if files.is_empty() {
904                    return Err(self.error("INPUT, OUTPUT, EXTEND or I-O and a file"));
905                }
906                Stmt::Open { files, pos }
907            }
908            "CLOSE" => {
909                let mut files = Vec::new();
910                while self.starts_ref() {
911                    files.push(self.name("a file name")?);
912                    if self.accept_word("WITH") {
913                        self.accept_any(&["LOCK", "NO"]);
914                        self.accept_word("REWIND");
915                    }
916                }
917                Stmt::Close { files, pos }
918            }
919            "READ" => {
920                let file = self.name("a file name")?;
921                let previous = self.accept_word("PREVIOUS");
922                let next = previous || self.accept_word("NEXT");
923                self.accept_word("RECORD");
924                let into = if self.accept_word("INTO") { Some(self.reference()?) } else { None };
925                let key = if self.accept_word("KEY") {
926                    self.accept_word("IS");
927                    Some(self.reference()?)
928                } else {
929                    None
930                };
931                let (mut at_end, mut invalid) = (Handlers::default(), Handlers::default());
932                loop {
933                    let negated = self.is_word("NOT") && matches!(self.word_at(1), Some("AT" | "END" | "INVALID"));
934                    let start = if negated { 1 } else { 0 };
935                    let word = self.word_at(start).unwrap_or("").to_owned();
936                    if !matches!(word.as_str(), "AT" | "END" | "INVALID") {
937                        break;
938                    }
939                    self.at += start;
940                    let target = if word == "INVALID" {
941                        self.at += 1;
942                        self.accept_word("KEY");
943                        &mut invalid
944                    } else {
945                        self.accept_word("AT");
946                        self.expect_word("END")?;
947                        &mut at_end
948                    };
949                    let body = self.block(&["NOT", "END-READ"])?;
950                    if negated { target.not_on = Some(body) } else { target.on = Some(body) }
951                }
952                self.accept_word("END-READ");
953                Stmt::Read(Box::new(ReadStmt { file, next, previous, into, key, at_end, invalid, pos }))
954            }
955            "REWRITE" => {
956                let record = self.reference()?;
957                let from = if self.accept_word("FROM") { Some(self.operand()?) } else { None };
958                let invalid = self.invalid_key("END-REWRITE")?;
959                Stmt::Rewrite { record, from, invalid, pos }
960            }
961            "DELETE" => {
962                let file = self.name("a file name")?;
963                self.accept_word("RECORD");
964                let invalid = self.invalid_key("END-DELETE")?;
965                Stmt::Delete { file, invalid, pos }
966            }
967            "START" => {
968                let file = self.name("a file name")?;
969                let key = if self.accept_word("KEY") {
970                    self.accept_word("IS");
971                    let op = if self.accept_word("NOT") {
972                        match self.relop()? {
973                            Some(RelOp::Lt) => RelOp::Ge,
974                            _ => return Err(self.error("NOT < in START KEY")),
975                        }
976                    } else {
977                        self.relop()?.ok_or_else(|| self.error("a relation after START KEY"))?
978                    };
979                    Some((op, self.reference()?))
980                } else {
981                    None
982                };
983                let invalid = self.invalid_key("END-START")?;
984                Stmt::Start { file, key, invalid, pos }
985            }
986            "WRITE" => {
987                let record = self.reference()?;
988                let from = if self.accept_word("FROM") { Some(self.operand()?) } else { None };
989                let mut advancing = None;
990                if let Some(side) = self.accept_any(&["BEFORE", "AFTER"]) {
991                    let before = side == "BEFORE";
992                    self.accept_word("ADVANCING");
993                    advancing = Some(if self.accept_word("PAGE") {
994                        Advancing::Page { before }
995                    } else {
996                        let count = self.expr()?;
997                        self.accept_any(&["LINE", "LINES"]);
998                        Advancing::Lines { before, count }
999                    });
1000                }
1001                let invalid = self.invalid_key("END-WRITE")?;
1002                Stmt::Write { record, from, advancing, invalid, pos }
1003            }
1004            "GO" => {
1005                self.accept_word("TO");
1006                Stmt::GoTo { target: self.proc_name()?, pos }
1007            }
1008            "EVALUATE" => self.evaluate(pos)?,
1009            "GOBACK" => Stmt::Goback { pos },
1010            "STOP" => {
1011                self.expect_word("RUN")?;
1012                Stmt::StopRun { pos }
1013            }
1014            "CONTINUE" => Stmt::Continue,
1015            "EXIT" => match self.accept_any(&["PROGRAM", "PARAGRAPH", "SECTION", "PERFORM"]).as_deref() {
1016                Some("PROGRAM") => Stmt::ExitProgram { pos },
1017                Some("PARAGRAPH") => Stmt::Exit(ExitKind::Paragraph),
1018                Some("SECTION") => Stmt::Exit(ExitKind::Section),
1019                Some(_) if self.accept_word("CYCLE") => Stmt::Exit(ExitKind::PerformCycle),
1020                Some(_) => Stmt::Exit(ExitKind::Perform),
1021                None => Stmt::Exit(ExitKind::Plain),
1022            },
1023            other => return Err(Error::at(pos, format!("{other} is not a statement ironwork for COBOL supports yet"))),
1024        })
1025    }
1026
1027    fn size_error(&mut self) -> R<Option<SizeError>> {
1028        let mut found = None;
1029        loop {
1030            let negated = self.is_word("NOT") && matches!(self.word_at(1), Some("ON" | "SIZE"));
1031            let plain = self.is_word("ON") || self.is_word("SIZE");
1032            if !negated && !plain {
1033                return Ok(found);
1034            }
1035            if negated {
1036                self.at += 1;
1037            }
1038            self.accept_word("ON");
1039            self.expect_word("SIZE")?;
1040            self.expect_word("ERROR")?;
1041            let body = self.block(&["NOT", "END-COMPUTE", "END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE"])?;
1042            let se = found.get_or_insert(SizeError { on: Vec::new(), not_on: Vec::new() });
1043            if negated { se.not_on = body } else { se.on = body }
1044        }
1045    }
1046
1047    fn targets(&mut self) -> R<Vec<Target>> {
1048        let mut out = Vec::new();
1049        while self.starts_ref() {
1050            let r = self.reference()?;
1051            out.push(Target { r, rounded: self.accept_word("ROUNDED") });
1052        }
1053        if out.is_empty() {
1054            return Err(self.error("a receiving item"));
1055        }
1056        Ok(out)
1057    }
1058
1059    fn refs(&mut self) -> R<Vec<Ref>> {
1060        let mut out = Vec::new();
1061        while self.starts_ref() {
1062            out.push(self.reference()?);
1063        }
1064        if out.is_empty() {
1065            return Err(self.error("a data name"));
1066        }
1067        Ok(out)
1068    }
1069
1070    fn operands_until(&mut self, stops: &[&str]) -> R<Vec<Expr>> {
1071        let mut out = Vec::new();
1072        while self.starts_operand() && !self.word().is_some_and(|w| stops.contains(&w)) {
1073            out.push(Expr::Operand(self.operand()?));
1074        }
1075        if out.is_empty() {
1076            return Err(self.error("an operand"));
1077        }
1078        Ok(out)
1079    }
1080
1081    fn arith(&mut self, verb: &str, pos: Pos) -> R<Arith> {
1082        let sum = |mut es: Vec<Expr>| {
1083            let first = es.remove(0);
1084            es.into_iter().fold(first, |acc, e| Expr::Bin(Box::new(acc), BinOp::Add, Box::new(e)))
1085        };
1086        let of = |t: &Target| Expr::Operand(Operand::Ref(t.r.clone()));
1087        let bin = |a: Expr, op: BinOp, b: Expr| Expr::Bin(Box::new(a), op, Box::new(b));
1088        let mut remainder = None;
1089        let (verb, computations) = match verb {
1090            "ADD" => {
1091                let addends = sum(self.operands_until(&["TO", "GIVING"])?);
1092                if self.accept_word("TO") {
1093                    if self.words_ahead_include("GIVING") {
1094                        let to = sum(self.operands_until(&["GIVING"])?);
1095                        self.expect_word("GIVING")?;
1096                        let targets = self.targets()?;
1097                        let total = bin(addends, BinOp::Add, to);
1098                        (ArithVerb::Add, targets.into_iter().map(|t| (t, total.clone())).collect())
1099                    } else {
1100                        let targets = self.targets()?;
1101                        (ArithVerb::Add, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Add, addends.clone()))).collect())
1102                    }
1103                } else {
1104                    self.expect_word("GIVING")?;
1105                    let targets = self.targets()?;
1106                    (ArithVerb::Add, targets.into_iter().map(|t| (t, addends.clone())).collect())
1107                }
1108            }
1109            "SUBTRACT" => {
1110                let subtrahend = sum(self.operands_until(&["FROM"])?);
1111                self.expect_word("FROM")?;
1112                if self.words_ahead_include("GIVING") {
1113                    let minuend = sum(self.operands_until(&["GIVING"])?);
1114                    self.expect_word("GIVING")?;
1115                    let targets = self.targets()?;
1116                    let diff = bin(minuend, BinOp::Sub, subtrahend);
1117                    (ArithVerb::Subtract, targets.into_iter().map(|t| (t, diff.clone())).collect())
1118                } else {
1119                    let targets = self.targets()?;
1120                    (ArithVerb::Subtract, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Sub, subtrahend.clone()))).collect())
1121                }
1122            }
1123            "MULTIPLY" => {
1124                let a = Expr::Operand(self.operand()?);
1125                self.expect_word("BY")?;
1126                if self.words_ahead_include("GIVING") {
1127                    let b = Expr::Operand(self.operand()?);
1128                    self.expect_word("GIVING")?;
1129                    let targets = self.targets()?;
1130                    (ArithVerb::Multiply, targets.into_iter().map(|t| (t, bin(a.clone(), BinOp::Mul, b.clone()))).collect())
1131                } else {
1132                    let targets = self.targets()?;
1133                    (ArithVerb::Multiply, targets.into_iter().map(|t| (t.clone(), bin(a.clone(), BinOp::Mul, of(&t)))).collect())
1134                }
1135            }
1136            _ => {
1137                let first = Expr::Operand(self.operand()?);
1138                let into = match self.accept_any(&["INTO", "BY"]).as_deref() {
1139                    Some("INTO") => true,
1140                    Some(_) => false,
1141                    None => return Err(self.error("INTO or BY")),
1142                };
1143                if !into || self.words_ahead_include("GIVING") {
1144                    let second = Expr::Operand(self.operand()?);
1145                    let (dividend, divisor) = if into { (second, first) } else { (first, second) };
1146                    self.expect_word("GIVING")?;
1147                    let targets = self.targets()?;
1148                    if self.accept_word("REMAINDER") {
1149                        let r = self.reference()?;
1150                        remainder = Some((Target { r, rounded: false }, dividend.clone(), divisor.clone()));
1151                    }
1152                    (ArithVerb::Divide, targets.into_iter().map(|t| (t, bin(dividend.clone(), BinOp::Div, divisor.clone()))).collect())
1153                } else {
1154                    let targets = self.targets()?;
1155                    (ArithVerb::Divide, targets.into_iter().map(|t| (t.clone(), bin(of(&t), BinOp::Div, first.clone()))).collect())
1156                }
1157            }
1158        };
1159        let size_error = self.size_error()?;
1160        self.accept_any(&["END-ADD", "END-SUBTRACT", "END-MULTIPLY", "END-DIVIDE"]);
1161        Ok(Arith { verb, computations, remainder, size_error, pos })
1162    }
1163
1164    /// Whether `word` appears before the statement ends.
1165    fn words_ahead_include(&self, word: &str) -> bool {
1166        self.tokens[self.at..]
1167            .iter()
1168            .take_while(|t| t.tok != Tok::Period && !matches!(&t.tok, Tok::Word(w) if VERBS.contains(&w.as_str()) && w != word))
1169            .any(|t| matches!(&t.tok, Tok::Word(w) if w == word))
1170    }
1171
1172    fn perform(&mut self, pos: Pos) -> R<Stmt> {
1173        let named = self.word().is_some_and(|w| !VERBS.contains(&w) && !PHRASE_WORDS.contains(&w))
1174            && self.word_at(1) != Some("TIMES")
1175            && !matches!(self.peek(), Some(Tok::Number(_)));
1176        if named {
1177            let from = self.proc_name()?;
1178            let thru = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.proc_name()?) } else { None };
1179            let repeat = self.repeat()?;
1180            return Ok(Stmt::PerformProc { from, thru, repeat, pos });
1181        }
1182        let repeat = self.repeat()?;
1183        let body = self.block(&["END-PERFORM"])?;
1184        self.expect_word("END-PERFORM")?;
1185        Ok(Stmt::PerformInline { body, repeat, pos })
1186    }
1187
1188    /// INVALID KEY and NOT INVALID KEY phrases, then the scope terminator.
1189    fn invalid_key(&mut self, end: &str) -> R<Handlers> {
1190        let mut h = Handlers::default();
1191        loop {
1192            let negated = self.is_word("NOT") && self.word_at(1) == Some("INVALID");
1193            if !negated && !self.is_word("INVALID") {
1194                break;
1195            }
1196            self.at += if negated { 2 } else { 1 };
1197            self.accept_word("KEY");
1198            let body = self.block(&["NOT", end])?;
1199            if negated { h.not_on = Some(body) } else { h.on = Some(body) }
1200        }
1201        self.accept_word(end);
1202        Ok(h)
1203    }
1204
1205    fn call(&mut self, pos: Pos) -> R<Call> {
1206        let target = self.operand()?;
1207        let mut using = Vec::new();
1208        if self.accept_word("USING") {
1209            let mut mode = ArgMode::Reference;
1210            loop {
1211                if self.accept_word("BY") {
1212                    mode = match self.accept_any(&["REFERENCE", "CONTENT", "VALUE"]).as_deref() {
1213                        Some("CONTENT") => ArgMode::Content,
1214                        Some("VALUE") => ArgMode::Value,
1215                        Some(_) => ArgMode::Reference,
1216                        None => return Err(self.error("REFERENCE, CONTENT or VALUE after BY")),
1217                    };
1218                } else if self.accept_word("OMITTED") {
1219                    using.push(Arg { mode, value: None });
1220                } else if self.starts_operand() {
1221                    using.push(Arg { mode, value: Some(self.operand()?) });
1222                } else {
1223                    break;
1224                }
1225            }
1226        }
1227        let returning = if self.accept_word("RETURNING") { Some(self.reference()?) } else { None };
1228        let (mut on_exception, mut not_on_exception) = (None, None);
1229        loop {
1230            let negated = self.is_word("NOT") && matches!(self.word_at(1), Some("ON" | "EXCEPTION" | "OVERFLOW"));
1231            if !negated && !(self.is_word("ON") || self.is_word("EXCEPTION") || self.is_word("OVERFLOW")) {
1232                break;
1233            }
1234            if negated {
1235                self.at += 1;
1236            }
1237            self.accept_word("ON");
1238            if self.accept_any(&["EXCEPTION", "OVERFLOW"]).is_none() {
1239                return Err(self.error("EXCEPTION or OVERFLOW"));
1240            }
1241            let body = self.block(&["NOT", "END-CALL"])?;
1242            if negated { not_on_exception = Some(body) } else { on_exception = Some(body) }
1243        }
1244        self.accept_word("END-CALL");
1245        Ok(Call { target, using, returning, on_exception, not_on_exception, pos })
1246    }
1247
1248    /// ON OVERFLOW and NOT ON OVERFLOW phrases, in either order.
1249    fn overflow(&mut self, end: &str) -> R<OnPhrases> {
1250        let (mut on, mut not_on) = (None, None);
1251        loop {
1252            let negated = self.is_word("NOT") && matches!(self.word_at(1), Some("ON" | "OVERFLOW"));
1253            if !negated && !(self.is_word("ON") || self.is_word("OVERFLOW")) {
1254                break;
1255            }
1256            if negated {
1257                self.at += 1;
1258            }
1259            self.accept_word("ON");
1260            self.expect_word("OVERFLOW")?;
1261            let body = self.block(&["NOT", end])?;
1262            if negated { not_on = Some(body) } else { on = Some(body) }
1263        }
1264        self.accept_word(end);
1265        Ok((on, not_on))
1266    }
1267
1268    fn string(&mut self, pos: Pos) -> R<StringStmt> {
1269        let mut sources = Vec::new();
1270        while !self.is_word("INTO") {
1271            let mut group = Vec::new();
1272            while self.starts_operand() && !self.is_word("DELIMITED") {
1273                group.push(self.operand()?);
1274            }
1275            if group.is_empty() {
1276                return Err(self.error("a sending item"));
1277            }
1278            let delimiter = if self.accept_word("DELIMITED") {
1279                self.accept_word("BY");
1280                if self.accept_word("SIZE") { Delimiter::Size } else { Delimiter::By(self.operand()?) }
1281            } else {
1282                Delimiter::Size
1283            };
1284            sources.extend(group.into_iter().map(|op| (op, delimiter.clone())));
1285        }
1286        self.expect_word("INTO")?;
1287        let into = self.reference()?;
1288        let pointer = if self.accept_word("WITH") || self.is_word("POINTER") {
1289            self.expect_word("POINTER")?;
1290            Some(self.reference()?)
1291        } else {
1292            None
1293        };
1294        let (on_overflow, not_on_overflow) = self.overflow("END-STRING")?;
1295        Ok(StringStmt { sources, into, pointer, on_overflow, not_on_overflow, pos })
1296    }
1297
1298    fn unstring(&mut self, pos: Pos) -> R<Unstring> {
1299        let source = self.reference()?;
1300        let mut delimiters = Vec::new();
1301        if self.accept_word("DELIMITED") {
1302            self.accept_word("BY");
1303            loop {
1304                let all = self.accept_word("ALL");
1305                delimiters.push((all, self.operand()?));
1306                if !self.accept_word("OR") {
1307                    break;
1308                }
1309            }
1310        }
1311        self.expect_word("INTO")?;
1312        let mut into = Vec::new();
1313        while self.starts_ref() {
1314            let target = self.reference()?;
1315            let delimiter_in = if self.accept_word("DELIMITER") {
1316                self.accept_word("IN");
1317                Some(self.reference()?)
1318            } else {
1319                None
1320            };
1321            let count_in = if self.accept_word("COUNT") {
1322                self.accept_word("IN");
1323                Some(self.reference()?)
1324            } else {
1325                None
1326            };
1327            into.push(UnstringInto { target, delimiter_in, count_in });
1328        }
1329        if into.is_empty() {
1330            return Err(self.error("a receiving item after INTO"));
1331        }
1332        let pointer = if self.accept_word("WITH") || self.is_word("POINTER") {
1333            self.expect_word("POINTER")?;
1334            Some(self.reference()?)
1335        } else {
1336            None
1337        };
1338        let tallying = if self.accept_word("TALLYING") {
1339            self.accept_word("IN");
1340            Some(self.reference()?)
1341        } else {
1342            None
1343        };
1344        let (on_overflow, not_on_overflow) = self.overflow("END-UNSTRING")?;
1345        Ok(Unstring { source, delimiters, into, pointer, tallying, on_overflow, not_on_overflow, pos })
1346    }
1347
1348    fn bounds(&mut self) -> R<Vec<Bound>> {
1349        let mut bounds = Vec::new();
1350        while let Some(side) = self.accept_any(&["BEFORE", "AFTER"]) {
1351            self.accept_word("INITIAL");
1352            bounds.push(Bound { after: side == "AFTER", value: self.operand()? });
1353        }
1354        Ok(bounds)
1355    }
1356
1357    fn inspect(&mut self, pos: Pos) -> R<Inspect> {
1358        let target = self.reference()?;
1359        let (mut tallying, mut replacing, mut converting) = (Vec::new(), Vec::new(), None);
1360        if self.accept_word("TALLYING") {
1361            while self.starts_ref() && self.word_at(1) == Some("FOR") || self.starts_ref() && !self.is_word("REPLACING") && self.tally_counter_ahead() {
1362                let counter = self.reference()?;
1363                self.expect_word("FOR")?;
1364                loop {
1365                    if self.accept_word("CHARACTERS") {
1366                        tallying.push(InspectPhrase { mode: InspectMode::Characters, pattern: None, by: None, counter: Some(counter.clone()), bounds: self.bounds()? });
1367                    } else if let Some(mode) = self.accept_any(&["ALL", "LEADING"]) {
1368                        let mode = if mode == "ALL" { InspectMode::All } else { InspectMode::Leading };
1369                        loop {
1370                            let pattern = self.operand()?;
1371                            tallying.push(InspectPhrase { mode, pattern: Some(pattern), by: None, counter: Some(counter.clone()), bounds: self.bounds()? });
1372                            if !self.starts_operand() || self.word_at(1) == Some("FOR") || self.is_word("ALL") || self.is_word("LEADING") {
1373                                break;
1374                            }
1375                        }
1376                    } else {
1377                        break;
1378                    }
1379                }
1380            }
1381        }
1382        if self.accept_word("REPLACING") {
1383            loop {
1384                if self.accept_word("CHARACTERS") {
1385                    self.expect_word("BY")?;
1386                    let by = self.operand()?;
1387                    replacing.push(InspectPhrase { mode: InspectMode::Characters, pattern: None, by: Some(by), counter: None, bounds: self.bounds()? });
1388                } else if let Some(mode) = self.accept_any(&["ALL", "LEADING", "FIRST"]) {
1389                    let mode = match mode.as_str() {
1390                        "ALL" => InspectMode::All,
1391                        "LEADING" => InspectMode::Leading,
1392                        _ => InspectMode::First,
1393                    };
1394                    loop {
1395                        let pattern = self.operand()?;
1396                        self.expect_word("BY")?;
1397                        let by = self.operand()?;
1398                        replacing.push(InspectPhrase { mode, pattern: Some(pattern), by: Some(by), counter: None, bounds: self.bounds()? });
1399                        if !self.starts_operand() || self.is_word("ALL") || self.is_word("LEADING") || self.is_word("FIRST") {
1400                            break;
1401                        }
1402                    }
1403                } else {
1404                    break;
1405                }
1406            }
1407        }
1408        if self.accept_word("CONVERTING") {
1409            let from = self.operand()?;
1410            self.expect_word("TO")?;
1411            let to = self.operand()?;
1412            converting = Some((from, to, self.bounds()?));
1413        }
1414        if tallying.is_empty() && replacing.is_empty() && converting.is_none() {
1415            return Err(self.error("TALLYING, REPLACING or CONVERTING"));
1416        }
1417        Ok(Inspect { target, tallying, replacing, converting, pos })
1418    }
1419
1420    /// Whether a counter and FOR follow, past any subscripts on the counter.
1421    fn tally_counter_ahead(&self) -> bool {
1422        let mut i = self.at + 1;
1423        if self.tokens.get(i).map(|t| &t.tok) == Some(&Tok::LParen) {
1424            let mut depth = 0;
1425            while let Some(t) = self.tokens.get(i) {
1426                match t.tok {
1427                    Tok::LParen => depth += 1,
1428                    Tok::RParen => {
1429                        depth -= 1;
1430                        if depth == 0 {
1431                            i += 1;
1432                            break;
1433                        }
1434                    }
1435                    _ => {}
1436                }
1437                i += 1;
1438            }
1439        }
1440        matches!(self.tokens.get(i).map(|t| &t.tok), Some(Tok::Word(w)) if w == "FOR")
1441    }
1442
1443    fn search(&mut self, pos: Pos) -> R<Search> {
1444        let all = self.accept_word("ALL");
1445        let table = self.reference()?;
1446        let varying = if self.accept_word("VARYING") { Some(self.reference()?) } else { None };
1447        let at_end = if self.accept_word("AT") || self.is_word("END") {
1448            self.expect_word("END")?;
1449            Some(self.block(&["WHEN"])?)
1450        } else {
1451            None
1452        };
1453        let mut whens = Vec::new();
1454        while self.accept_word("WHEN") {
1455            let cond = self.cond()?;
1456            let body = self.block(&["WHEN", "END-SEARCH"])?;
1457            whens.push((cond, body));
1458        }
1459        if whens.is_empty() {
1460            return Err(self.error("WHEN"));
1461        }
1462        self.accept_word("END-SEARCH");
1463        Ok(Search { table, all, varying, at_end, whens, pos })
1464    }
1465
1466    fn set(&mut self) -> R<SetStmt> {
1467        if self.is_word("ADDRESS") && self.word_at(1) == Some("OF") {
1468            let mut targets = Vec::new();
1469            while self.is_word("ADDRESS") && self.word_at(1) == Some("OF") {
1470                self.at += 2;
1471                targets.push(self.reference()?);
1472            }
1473            self.expect_word("TO")?;
1474            return Ok(SetStmt::AddressOf { targets, value: self.operand()? });
1475        }
1476        let targets = self.refs()?;
1477        if self.accept_word("TO") {
1478            if self.accept_word("TRUE") {
1479                return Ok(SetStmt::ConditionTrue(targets));
1480            }
1481            return Ok(SetStmt::To { targets, value: self.operand()? });
1482        }
1483        match self.accept_any(&["UP", "DOWN"]).as_deref() {
1484            Some(direction) => {
1485                self.expect_word("BY")?;
1486                Ok(SetStmt::UpDown { targets, down: direction == "DOWN", by: self.expr()? })
1487            }
1488            None => Err(self.error("TO, UP BY or DOWN BY")),
1489        }
1490    }
1491
1492    fn proc_name(&mut self) -> R<ProcName> {
1493        let name = self.name("a procedure name")?;
1494        let section = if self.accept_any(&["OF", "IN"]).is_some() { Some(self.name("a section name")?) } else { None };
1495        Ok(ProcName { name, section })
1496    }
1497
1498    fn evaluate(&mut self, pos: Pos) -> R<Stmt> {
1499        let mut subjects = vec![self.subject()?];
1500        while self.accept_word("ALSO") {
1501            subjects.push(self.subject()?);
1502        }
1503        let (mut whens, mut other) = (Vec::new(), Vec::new());
1504        while self.is_word("WHEN") {
1505            if self.word_at(1) == Some("OTHER") {
1506                self.at += 2;
1507                other = self.block(&["END-EVALUATE"])?;
1508                break;
1509            }
1510            let mut alternatives = Vec::new();
1511            while self.is_word("WHEN") && self.word_at(1) != Some("OTHER") {
1512                self.at += 1;
1513                let mut objects = Vec::new();
1514                for (k, subject) in subjects.iter().enumerate() {
1515                    if k > 0 {
1516                        self.expect_word("ALSO")?;
1517                    }
1518                    objects.push(self.object(subject)?);
1519                }
1520                alternatives.push(objects);
1521            }
1522            let body = self.block(&["WHEN", "END-EVALUATE"])?;
1523            whens.push(When { alternatives, body });
1524        }
1525        self.accept_word("END-EVALUATE");
1526        Ok(Stmt::Evaluate { subjects, whens, other, pos })
1527    }
1528
1529    fn subject(&mut self) -> R<Subject> {
1530        if let Some(b) = self.accept_any(&["TRUE", "FALSE"]) {
1531            return Ok(Subject::Bool(b == "TRUE"));
1532        }
1533        let save = self.at;
1534        self.expr()?;
1535        let conditional = self.relop_ahead(0) || self.is_word("IS") || self.is_word("NOT")
1536            || self.word().is_some_and(|w| matches!(w, "NUMERIC" | "ALPHABETIC" | "POSITIVE" | "NEGATIVE" | "ZERO"));
1537        self.at = save;
1538        Ok(if conditional { Subject::Cond(self.cond()?) } else { Subject::Expr(self.expr()?) })
1539    }
1540
1541    fn object(&mut self, subject: &Subject) -> R<Object> {
1542        if self.accept_word("ANY") {
1543            return Ok(Object::Any);
1544        }
1545        if let Some(b) = self.accept_any(&["TRUE", "FALSE"]) {
1546            return Ok(Object::Bool(b == "TRUE"));
1547        }
1548        if !matches!(subject, Subject::Expr(_)) {
1549            return Ok(Object::Cond(self.cond()?));
1550        }
1551        let not = self.accept_word("NOT");
1552        let from = self.expr()?;
1553        let thru = if self.accept_any(&["THRU", "THROUGH"]).is_some() { Some(self.expr()?) } else { None };
1554        Ok(Object::Value { not, from, thru })
1555    }
1556
1557    fn repeat(&mut self) -> R<Loop> {
1558        let mut test_after = false;
1559        if self.accept_word("WITH") || self.is_word("TEST") {
1560            self.expect_word("TEST")?;
1561            test_after = self.accept_any(&["BEFORE", "AFTER"]).as_deref() == Some("AFTER");
1562        }
1563        if self.accept_word("UNTIL") {
1564            return Ok(Loop::Until { cond: self.cond()?, test_after });
1565        }
1566        if self.accept_word("VARYING") {
1567            let var = self.reference()?;
1568            self.expect_word("FROM")?;
1569            let from = self.expr()?;
1570            self.expect_word("BY")?;
1571            let by = self.expr()?;
1572            self.expect_word("UNTIL")?;
1573            let until = self.cond()?;
1574            if self.is_word("AFTER") {
1575                return Err(self.error("PERFORM VARYING ... AFTER is not supported yet"));
1576            }
1577            return Ok(Loop::Varying { varying: Box::new(Varying { var, from, by, until }), test_after });
1578        }
1579        if self.starts_operand() && self.word_at(1) == Some("TIMES") || matches!(self.peek(), Some(Tok::Number(_))) {
1580            let count = self.expr()?;
1581            self.expect_word("TIMES")?;
1582            return Ok(Loop::Times(count));
1583        }
1584        Ok(Loop::Once)
1585    }
1586
1587    fn starts_ref(&self) -> bool {
1588        self.word().is_some_and(|w| !VERBS.contains(&w) && !PHRASE_WORDS.contains(&w) && figurative(w).is_none() && w != "FUNCTION")
1589            && !self.paragraph_header()
1590    }
1591
1592    fn starts_operand(&self) -> bool {
1593        match self.peek() {
1594            Some(Tok::Alnum(_) | Tok::Hex(_) | Tok::National(_) | Tok::Number(_)) => true,
1595            Some(Tok::Word(w)) => {
1596                (figurative(w).is_some() || matches!(w.as_str(), "ALL" | "FUNCTION" | "LENGTH" | "ADDRESS" | "DFHRESP") || self.starts_ref()) && !self.paragraph_header()
1597            }
1598            _ => false,
1599        }
1600    }
1601
1602    fn operand(&mut self) -> R<Operand> {
1603        let pos = self.pos();
1604        match self.peek() {
1605            Some(Tok::Word(w)) if w == "FUNCTION" => {
1606                self.at += 1;
1607                let name = self.name("a function name")?;
1608                let (mut args, mut modifier) = (Vec::new(), None);
1609                if self.peek() == Some(&Tok::LParen) && !self.refmod_ahead() {
1610                    self.at += 1;
1611                    while !self.accept(&Tok::RParen) {
1612                        if let Some(m) = self.accept_any(&["LEADING", "TRAILING"]) {
1613                            modifier = Some(m);
1614                            continue;
1615                        }
1616                        args.push(self.expr()?);
1617                    }
1618                }
1619                let refmod = self.refmod()?;
1620                Ok(Operand::Function(FunctionCall { name, args, modifier, refmod, pos }))
1621            }
1622            Some(Tok::Word(w)) if w == "LENGTH" && self.word_at(1) == Some("OF") => {
1623                self.at += 2;
1624                Ok(Operand::LengthOf(self.reference()?))
1625            }
1626            Some(Tok::Word(w)) if w == "DFHRESP" && self.peek_at(1) == Some(&Tok::LParen) => {
1627                self.at += 2;
1628                let condition = self.name("a CICS condition")?;
1629                self.expect(&Tok::RParen, "')'")?;
1630                let code = crate::system::resp_code(&condition).ok_or_else(|| Error::at(pos, format!("DFHRESP({condition}): not a CICS condition ironwork for COBOL knows")))?;
1631                Ok(Operand::Literal(Literal::Number(code.to_string())))
1632            }
1633            Some(Tok::Word(w)) if w == "ADDRESS" && self.word_at(1) == Some("OF") => {
1634                self.at += 2;
1635                Ok(Operand::AddressOf(self.reference()?))
1636            }
1637            Some(Tok::Word(w)) if figurative(w).is_some() || w == "ALL" => Ok(Operand::Literal(self.literal()?)),
1638            Some(Tok::Word(_)) => Ok(Operand::Ref(self.reference()?)),
1639            Some(Tok::Alnum(_) | Tok::Hex(_) | Tok::National(_) | Tok::Number(_)) => Ok(Operand::Literal(self.literal()?)),
1640            _ => Err(self.error("an operand")),
1641        }
1642    }
1643
1644    /// Whether the parenthesis at the cursor opens a reference modification `(start:length)`.
1645    fn refmod_ahead(&self) -> bool {
1646        let mut depth = 0;
1647        for t in &self.tokens[self.at..] {
1648            match t.tok {
1649                Tok::LParen => depth += 1,
1650                Tok::RParen => {
1651                    depth -= 1;
1652                    if depth == 0 {
1653                        return false;
1654                    }
1655                }
1656                Tok::Colon if depth == 1 => return true,
1657                Tok::Period => return false,
1658                _ => {}
1659            }
1660        }
1661        false
1662    }
1663
1664    fn refmod(&mut self) -> R<Option<RefMod>> {
1665        if self.peek() != Some(&Tok::LParen) || !self.refmod_ahead() {
1666            return Ok(None);
1667        }
1668        self.at += 1;
1669        let start = Box::new(self.expr()?);
1670        self.expect(&Tok::Colon, "':'")?;
1671        let length = if self.peek() == Some(&Tok::RParen) { None } else { Some(Box::new(self.expr()?)) };
1672        self.expect(&Tok::RParen, "')'")?;
1673        Ok(Some(RefMod { start, length }))
1674    }
1675
1676    fn reference(&mut self) -> R<Ref> {
1677        let pos = self.pos();
1678        let name = self.name("a data name")?;
1679        let mut qualifiers = Vec::new();
1680        while self.accept_any(&["OF", "IN"]).is_some() {
1681            qualifiers.push(self.name("a qualifier")?);
1682        }
1683        let mut subscripts = Vec::new();
1684        if self.peek() == Some(&Tok::LParen) && !self.refmod_ahead() {
1685            self.at += 1;
1686            while !self.accept(&Tok::RParen) {
1687                subscripts.push(self.expr()?);
1688            }
1689        }
1690        let refmod = self.refmod()?;
1691        Ok(Ref { name, qualifiers, subscripts, refmod, pos })
1692    }
1693
1694    fn expr(&mut self) -> R<Expr> {
1695        let mut left = self.term()?;
1696        loop {
1697            let op = match self.peek() {
1698                Some(Tok::Plus) => BinOp::Add,
1699                Some(Tok::Minus) => BinOp::Sub,
1700                _ => return Ok(left),
1701            };
1702            self.at += 1;
1703            left = Expr::Bin(Box::new(left), op, Box::new(self.term()?));
1704        }
1705    }
1706
1707    fn term(&mut self) -> R<Expr> {
1708        let mut left = self.power()?;
1709        loop {
1710            let op = match self.peek() {
1711                Some(Tok::Star) => BinOp::Mul,
1712                Some(Tok::Slash) => BinOp::Div,
1713                _ => return Ok(left),
1714            };
1715            self.at += 1;
1716            left = Expr::Bin(Box::new(left), op, Box::new(self.power()?));
1717        }
1718    }
1719
1720    fn power(&mut self) -> R<Expr> {
1721        let mut left = self.unary()?;
1722        while self.accept(&Tok::Power) {
1723            left = Expr::Bin(Box::new(left), BinOp::Pow, Box::new(self.unary()?));
1724        }
1725        Ok(left)
1726    }
1727
1728    fn unary(&mut self) -> R<Expr> {
1729        if self.accept(&Tok::Minus) {
1730            return Ok(Expr::Neg(Box::new(self.unary()?)));
1731        }
1732        self.accept(&Tok::Plus);
1733        if self.accept(&Tok::LParen) {
1734            let e = self.expr()?;
1735            self.expect(&Tok::RParen, "')'")?;
1736            return Ok(e);
1737        }
1738        Ok(Expr::Operand(self.operand()?))
1739    }
1740
1741    fn cond(&mut self) -> R<Cond> {
1742        let mut last = None;
1743        self.or_cond(&mut last)
1744    }
1745
1746    fn or_cond(&mut self, last: &mut Option<(Expr, RelOp)>) -> R<Cond> {
1747        let mut left = self.and_cond(last)?;
1748        while self.accept_word("OR") {
1749            left = Cond::Or(Box::new(left), Box::new(self.and_cond(last)?));
1750        }
1751        Ok(left)
1752    }
1753
1754    fn and_cond(&mut self, last: &mut Option<(Expr, RelOp)>) -> R<Cond> {
1755        let mut left = self.not_cond(last)?;
1756        while self.accept_word("AND") {
1757            left = Cond::And(Box::new(left), Box::new(self.not_cond(last)?));
1758        }
1759        Ok(left)
1760    }
1761
1762    fn not_cond(&mut self, last: &mut Option<(Expr, RelOp)>) -> R<Cond> {
1763        if self.is_word("NOT") && !self.relop_ahead(1) {
1764            self.at += 1;
1765            return Ok(Cond::Not(Box::new(self.not_cond(last)?)));
1766        }
1767        self.primary_cond(last)
1768    }
1769
1770    fn relop_ahead(&self, ahead: usize) -> bool {
1771        matches!(self.peek_at(ahead), Some(Tok::Eq | Tok::Lt | Tok::Gt | Tok::Le | Tok::Ge))
1772            || matches!(self.word_at(ahead), Some("EQUAL" | "GREATER" | "LESS"))
1773    }
1774
1775    fn primary_cond(&mut self, last: &mut Option<(Expr, RelOp)>) -> R<Cond> {
1776        if self.peek() == Some(&Tok::LParen) {
1777            let save = self.at;
1778            self.at += 1;
1779            let mut inner_last = None;
1780            if let Ok(c) = self.or_cond(&mut inner_last)
1781                && self.accept(&Tok::RParen)
1782                && !matches!(self.peek(), Some(Tok::Plus | Tok::Minus | Tok::Star | Tok::Slash | Tok::Power))
1783                && !self.relop_ahead(0)
1784                && !self.is_word("IS")
1785            {
1786                return Ok(c);
1787            }
1788            self.at = save;
1789        }
1790        let left = self.expr()?;
1791        self.accept_word("IS");
1792        let negated = self.is_word("NOT") && {
1793            self.at += 1;
1794            true
1795        };
1796        let wrap = |c: Cond| if negated { Cond::Not(Box::new(c)) } else { c };
1797        if let Some(op) = self.relop()? {
1798            let right = self.expr()?;
1799            *last = Some((left.clone(), op));
1800            return Ok(wrap(Cond::Rel(left, op, right)));
1801        }
1802        if let Some(class) = self.accept_any(&["NUMERIC", "ALPHABETIC", "POSITIVE", "NEGATIVE", "ZERO"]) {
1803            let class = match class.as_str() {
1804                "NUMERIC" => Class::Numeric,
1805                "ALPHABETIC" => Class::Alphabetic,
1806                "POSITIVE" => Class::Positive,
1807                "NEGATIVE" => Class::Negative,
1808                _ => Class::Zero,
1809            };
1810            return Ok(wrap(Cond::Class(left, class)));
1811        }
1812        if negated {
1813            return Err(self.error("a relational operator or class after NOT"));
1814        }
1815        match (left, last.clone()) {
1816            (Expr::Operand(Operand::Ref(name)), Some((subject, op))) if self.abbreviation_context() => Ok(Cond::NameOrRel { subject, op, name }),
1817            (right, Some((subject, op))) if !matches!(&right, Expr::Operand(Operand::Ref(_))) => Ok(Cond::Rel(subject, op, right)),
1818            (Expr::Operand(Operand::Ref(r)), _) => Ok(Cond::Name(r)),
1819            (_, _) => Err(self.error("a relational operator")),
1820        }
1821    }
1822
1823    /// After AND or OR, an operand with no operator of its own continues an abbreviated relation.
1824    fn abbreviation_context(&self) -> bool {
1825        self.at > 1 && matches!(self.tokens.get(self.at.saturating_sub(2)).map(|t| &t.tok), Some(Tok::Word(w)) if w == "OR" || w == "AND")
1826    }
1827
1828    fn relop(&mut self) -> R<Option<RelOp>> {
1829        let op = match self.peek() {
1830            Some(Tok::Eq) => RelOp::Eq,
1831            Some(Tok::Lt) => RelOp::Lt,
1832            Some(Tok::Gt) => RelOp::Gt,
1833            Some(Tok::Le) => RelOp::Le,
1834            Some(Tok::Ge) => RelOp::Ge,
1835            Some(Tok::Word(w)) if w == "EQUAL" => {
1836                self.at += 1;
1837                self.accept_word("TO");
1838                return Ok(Some(RelOp::Eq));
1839            }
1840            Some(Tok::Word(w)) if w == "GREATER" || w == "LESS" => {
1841                let greater = w == "GREATER";
1842                self.at += 1;
1843                self.accept_word("THAN");
1844                let or_equal = self.accept_word("OR");
1845                if or_equal {
1846                    self.expect_word("EQUAL")?;
1847                    self.accept_word("TO");
1848                }
1849                return Ok(Some(match (greater, or_equal) {
1850                    (true, false) => RelOp::Gt,
1851                    (true, true) => RelOp::Ge,
1852                    (false, false) => RelOp::Lt,
1853                    (false, true) => RelOp::Le,
1854                }));
1855            }
1856            _ => return Ok(None),
1857        };
1858        self.at += 1;
1859        Ok(Some(op))
1860    }
1861}
1862
1863/// SQL host variables: `:NAME`, `:GROUP.NAME` and indicator variables, outside quoted strings.
1864fn host_variables(sql: &str, pos: Pos) -> Vec<Ref> {
1865    let chars: Vec<char> = sql.chars().collect();
1866    let (mut out, mut i, mut quote) = (Vec::new(), 0, None);
1867    while i < chars.len() {
1868        let c = chars[i];
1869        match quote {
1870            Some(q) if c == q => quote = None,
1871            Some(_) => {}
1872            None if c == '\'' || c == '"' => quote = Some(c),
1873            None if c == ':' && chars.get(i + 1).is_some_and(|n| n.is_ascii_alphanumeric()) => {
1874                let start = i + 1;
1875                let mut end = start;
1876                while end < chars.len() && (chars[end].is_ascii_alphanumeric() || matches!(chars[end], '-' | '_' | '.')) {
1877                    end += 1;
1878                }
1879                let path: String = chars[start..end].iter().collect::<String>().to_ascii_uppercase();
1880                let mut parts: Vec<String> = path.trim_end_matches('.').split('.').map(str::to_owned).collect();
1881                let name = parts.pop().unwrap_or_default();
1882                parts.reverse();
1883                out.push(Ref { name, qualifiers: parts, subscripts: Vec::new(), refmod: None, pos });
1884                i = end;
1885                continue;
1886            }
1887            None => {}
1888        }
1889        i += 1;
1890    }
1891    out
1892}
1893
1894/// CICS command words and options: `NAME` or `NAME(argument)`, the argument kept as written.
1895fn cics_options(body: &str) -> Vec<(String, Option<ExecArg>)> {
1896    let chars: Vec<char> = body.chars().collect();
1897    let (mut out, mut i) = (Vec::new(), 0);
1898    while i < chars.len() {
1899        if chars[i].is_whitespace() {
1900            i += 1;
1901            continue;
1902        }
1903        let start = i;
1904        while i < chars.len() && !chars[i].is_whitespace() && chars[i] != '(' {
1905            i += 1;
1906        }
1907        let name: String = chars[start..i].iter().collect::<String>().to_ascii_uppercase();
1908        let mut j = i;
1909        while j < chars.len() && chars[j].is_whitespace() {
1910            j += 1;
1911        }
1912        if j < chars.len() && chars[j] == '(' {
1913            let (mut depth, mut quote, mut k) = (0, None, j);
1914            while k < chars.len() {
1915                match (quote, chars[k]) {
1916                    (Some(q), c) if c == q => quote = None,
1917                    (Some(_), _) => {}
1918                    (None, '\'' | '"') => quote = Some(chars[k]),
1919                    (None, '(') => depth += 1,
1920                    (None, ')') => {
1921                        depth -= 1;
1922                        if depth == 0 {
1923                            break;
1924                        }
1925                    }
1926                    _ => {}
1927                }
1928                k += 1;
1929            }
1930            let arg: String = chars[j + 1..k.min(chars.len())].iter().collect();
1931            out.push((name, Some(ExecArg::Text(arg.trim().to_owned()))));
1932            i = (k + 1).min(chars.len());
1933        } else {
1934            out.push((name, None));
1935        }
1936    }
1937    out
1938}
1939
1940/// An argument as the COBOL operand it names, through ironwork's own lexer and parser.
1941fn operand_of(text: &str, pos: Pos) -> Option<Operand> {
1942    let source = crate::source::Source { text: text.to_owned(), positions: vec![pos; text.chars().count()], options: Vec::new() };
1943    let tokens = crate::lexer::lex(&source).ok()?;
1944    let mut p = Parser { tokens: &tokens, at: 0, exec_declarations: Vec::new(), cics: false };
1945    let op = p.operand().ok()?;
1946    (p.at == tokens.len()).then_some(op)
1947}
1948
1949/// DATA DIVISION entries from the text of a system member.
1950fn system_entries(member: &str) -> R<Vec<DataEntry>> {
1951    system_text_entries(&crate::system::member(member).unwrap_or_default())
1952}
1953
1954fn system_text_entries(text: &str) -> R<Vec<DataEntry>> {
1955    let source = crate::source::read(text)?;
1956    let tokens = crate::lexer::lex(&source)?;
1957    Parser { tokens: &tokens, at: 0, exec_declarations: Vec::new(), cics: false }.data_entries()
1958}
1959
1960/// Words that begin a SELECT clause, and so end the one before.
1961const SELECT_CLAUSES: &[&str] = &[
1962    "ASSIGN", "ORGANIZATION", "ACCESS", "FILE", "STATUS", "RECORD", "ALTERNATE", "RELATIVE", "LINE", "SEQUENTIAL", "INDEXED", "RESERVE",
1963    "PADDING", "LOCK", "SHARING",
1964];
1965
1966fn is_clause_word(w: &str) -> bool {
1967    matches!(
1968        w,
1969        "PIC" | "PICTURE" | "USAGE" | "VALUE" | "VALUES" | "REDEFINES" | "OCCURS" | "SIGN" | "LEADING" | "TRAILING" | "JUSTIFIED"
1970            | "JUST" | "SYNC" | "SYNCHRONIZED" | "GLOBAL" | "EXTERNAL" | "BLANK"
1971    ) || usage_word(w).is_some()
1972}
1973
1974#[cfg(test)]
1975mod tests {
1976    use super::*;
1977
1978    fn program(body: &str) -> Program {
1979        let text = format!(
1980            "       IDENTIFICATION DIVISION.\n       PROGRAM-ID. T.\n       DATA DIVISION.\n       WORKING-STORAGE SECTION.\n{body}"
1981        );
1982        crate::parse(&text).unwrap_or_else(|e| panic!("{e}"))
1983    }
1984
1985    #[test]
1986    fn data_entries_with_clauses_in_any_order() {
1987        let p = program(
1988            "       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",
1989        );
1990        let ws = &p.working_storage;
1991        assert_eq!(ws.len(), 5);
1992        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()))));
1993        assert_eq!(ws[2].redefines.as_deref(), Some("A"));
1994        assert_eq!((ws[3].name.as_deref(), &ws[3].value), (None, &Some(Literal::Figurative(Figurative::Space))));
1995        assert_eq!(ws[4].occurs, Some(3));
1996    }
1997
1998    #[test]
1999    fn paragraphs_and_nested_if() {
2000        let p = program(
2001            "       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",
2002        );
2003        assert_eq!(p.paragraphs.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(), ["MAIN-LINE", "P2"]);
2004        let Stmt::If { otherwise, .. } = &p.paragraphs[1].statements[0] else { panic!() };
2005        assert!(matches!(otherwise[0], Stmt::If { .. }));
2006    }
2007
2008    #[test]
2009    fn inline_perform_varying_with_a_compound_condition() {
2010        let p = program(
2011            "       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",
2012        );
2013        let Stmt::PerformInline { repeat: Loop::Varying { varying, .. }, .. } = &p.paragraphs[0].statements[0] else { panic!() };
2014        assert!(matches!(varying.until, Cond::Or(..)));
2015    }
2016
2017    #[test]
2018    fn functions_reference_modification_and_length_of() {
2019        let p = program(
2020            "       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",
2021        );
2022        let Stmt::Compute { expr: Expr::Bin(left, BinOp::Sub, _), .. } = &p.paragraphs[0].statements[0] else { panic!() };
2023        let Expr::Operand(Operand::Function(f)) = left.as_ref() else { panic!() };
2024        let Expr::Operand(Operand::Ref(r)) = &f.args[0] else { panic!() };
2025        assert!(r.refmod.is_some());
2026        assert!(matches!(&p.paragraphs[0].statements[2], Stmt::Move { from: Operand::LengthOf(_), .. }));
2027    }
2028
2029    #[test]
2030    fn divide_giving_remainder_and_add_to() {
2031        let p = program(
2032            "       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",
2033        );
2034        let Stmt::Arith(a) = &p.paragraphs[0].statements[0] else { panic!() };
2035        assert_eq!(a.verb, ArithVerb::Divide);
2036        assert!(a.remainder.is_some());
2037        let Stmt::Arith(add) = &p.paragraphs[0].statements[1] else { panic!() };
2038        assert!(add.computations[0].0.rounded);
2039    }
2040
2041    #[test]
2042    fn abbreviated_combined_relation() {
2043        let p = program("       01  A PIC 9.\n       PROCEDURE DIVISION.\n           IF A = 1 OR 2 CONTINUE END-IF.\n");
2044        let Stmt::If { cond: Cond::Or(_, right), .. } = &p.paragraphs[0].statements[0] else { panic!() };
2045        assert!(matches!(right.as_ref(), Cond::Rel(_, RelOp::Eq, _)));
2046    }
2047
2048    #[test]
2049    fn indexed_select_clauses_and_keyed_statements() {
2050        let text = [
2051            "       IDENTIFICATION DIVISION.\n       PROGRAM-ID. T.\n       ENVIRONMENT DIVISION.\n       INPUT-OUTPUT SECTION.\n       FILE-CONTROL.\n",
2052            "           SELECT K ASSIGN TO KDD ORGANIZATION IS INDEXED\n",
2053            "               ACCESS MODE IS DYNAMIC RECORD KEY IS K-ID\n",
2054            "               ALTERNATE KEY K-ALT WITH DUPLICATES.\n",
2055            "           SELECT N ASSIGN TO NDD STATUS N-FS N-VS ORGANIZATION\n",
2056            "               INDEXED FILE STATUS IS N-FS RECORD N-ID.\n",
2057            "       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",
2058            "       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",
2059            "       PROCEDURE DIVISION.\n",
2060            "           READ K NEXT RECORD AT END CONTINUE\n",
2061            "               NOT AT END CONTINUE END-READ\n",
2062            "           READ K KEY IS K-ALT INVALID KEY CONTINUE END-READ\n",
2063            "           START K KEY IS NOT LESS THAN K-ID\n",
2064            "               INVALID KEY CONTINUE NOT INVALID KEY CONTINUE\n",
2065            "           END-START\n",
2066            "           REWRITE K-REC INVALID KEY CONTINUE END-REWRITE\n",
2067            "           DELETE K RECORD END-DELETE.\n",
2068        ]
2069        .concat();
2070        let p = crate::parse(&text).unwrap_or_else(|e| panic!("{e}"));
2071        let f = &p.files[0];
2072        assert_eq!((f.organization, f.access), (Organization::Indexed, Access::Dynamic));
2073        assert_eq!(f.record_key.as_ref().map(|r| r.name.as_str()), Some("K-ID"));
2074        assert_eq!(f.alternate_keys.iter().map(|(r, d)| (r.name.as_str(), *d)).collect::<Vec<_>>(), [("K-ALT", true)]);
2075        let n = &p.files[1];
2076        assert_eq!((n.organization, n.record_key.as_ref().map(|r| r.name.as_str())), (Organization::Indexed, Some("N-ID")));
2077        let s = &p.paragraphs[0].statements;
2078        let Stmt::Read(r) = &s[0] else { panic!() };
2079        assert!(r.next && !r.previous && r.at_end.on.is_some() && r.at_end.not_on.is_some());
2080        let Stmt::Read(r) = &s[1] else { panic!() };
2081        assert!(r.key.is_some() && r.invalid.on.is_some() && !r.next);
2082        let Stmt::Start { key: Some((RelOp::Ge, _)), invalid, .. } = &s[2] else { panic!() };
2083        assert!(invalid.on.is_some() && invalid.not_on.is_some());
2084        assert!(matches!(&s[3], Stmt::Rewrite { invalid, .. } if invalid.on.is_some()));
2085        assert!(matches!(&s[4], Stmt::Delete { .. }));
2086    }
2087
2088    #[test]
2089    fn unsupported_statements_are_named() {
2090        let text = "       IDENTIFICATION DIVISION.\n       PROGRAM-ID. T.\n       PROCEDURE DIVISION.\n           SORT F ON ASCENDING KEY K USING A GIVING B.\n";
2091        assert!(crate::parse(text).unwrap_err().message.contains("SORT"));
2092    }
2093}