Skip to main content

ironwork_compile/
declaratives.rs

1//! USE AFTER EXCEPTION/ERROR and USE FOR DEBUGGING procedures at compile time: the files, open
2//! mode or procedures each serves, and the rules the Language Reference (SC27-8713-03) sets for
3//! them. They run in `machine::declaratives`.
4
5use crate::layout::{Layout, Resolved};
6use numeric::Options;
7use syntax::ast::*;
8use syntax::{Error, Pos};
9
10/// A section's paragraphs, first to last.
11pub type Span = (usize, usize);
12
13/// DEBUG-ITEM as the Language Reference describes it (p. 19), with a DEBUG-CONTENTS of 30
14/// characters ([`numeric::assumptions::DEBUG_CONTENTS_LENGTH`]).
15const DEBUG_ITEM: &str = concat!(
16    "       IDENTIFICATION DIVISION.\n       PROGRAM-ID. REGISTERS.\n       DATA DIVISION.\n       WORKING-STORAGE SECTION.\n",
17    "       01  DEBUG-ITEM.\n",
18    "           02  DEBUG-LINE PIC X(6).\n",
19    "           02  FILLER PIC X VALUE SPACE.\n",
20    "           02  DEBUG-NAME PIC X(30).\n",
21    "           02  FILLER PIC X VALUE SPACE.\n",
22    "           02  DEBUG-SUB-1 PIC S9999 SIGN IS LEADING SEPARATE CHARACTER.\n",
23    "           02  FILLER PIC X VALUE SPACE.\n",
24    "           02  DEBUG-SUB-2 PIC S9999 SIGN IS LEADING SEPARATE CHARACTER.\n",
25    "           02  FILLER PIC X VALUE SPACE.\n",
26    "           02  DEBUG-SUB-3 PIC S9999 SIGN IS LEADING SEPARATE CHARACTER.\n",
27    "           02  FILLER PIC X VALUE SPACE.\n",
28    "           02  DEBUG-CONTENTS PIC X(30).\n",
29);
30
31/// Where each field of DEBUG-ITEM starts, and its length.
32pub const DEBUG_LINE: (usize, usize) = (0, 6);
33pub const DEBUG_NAME: (usize, usize) = (7, 30);
34pub const DEBUG_CONTENTS: (usize, usize) = (56, 30);
35
36/// DEBUG-ITEM and its fields, which only a debugging section can reference (p. 771).
37pub(crate) const DEBUG_ITEM_NAMES: [&str; 7] = ["DEBUG-ITEM", "DEBUG-LINE", "DEBUG-NAME", "DEBUG-SUB-1", "DEBUG-SUB-2", "DEBUG-SUB-3", "DEBUG-CONTENTS"];
38
39/// Where each declarative runs.
40#[derive(Debug, Default)]
41pub struct Table {
42    /// Each file's own EXCEPTION/ERROR procedure.
43    pub files: Vec<Option<Span>>,
44    /// The EXCEPTION/ERROR procedures for files open INPUT, OUTPUT, I-O and EXTEND.
45    pub modes: [Option<Span>; 4],
46    /// Those of `files` and `modes` declared GLOBAL, which serve the programs this one contains.
47    pub global_files: Vec<Option<Span>>,
48    pub global_modes: [Option<Span>; 4],
49    /// Under the DEBUG runtime option: for each paragraph, the debugging section that runs before
50    /// it and the name DEBUG-NAME gives. Empty otherwise.
51    pub triggers: Vec<Option<(Span, String)>>,
52    /// DEBUG-ITEM's item in the layout.
53    pub debug_item: Option<usize>,
54    /// Under ALL PROCEDURES, where each ALTER in the declaratives is: those run no debugging section.
55    pub declarative_alters: Vec<Pos>,
56}
57
58pub fn mode_index(mode: OpenMode) -> usize {
59    match mode {
60        OpenMode::Input => 0,
61        OpenMode::Output => 1,
62        OpenMode::InputOutput => 2,
63        OpenMode::Extend => 3,
64    }
65}
66
67fn mode_name(mode: OpenMode) -> &'static str {
68    ["INPUT", "OUTPUT", "I-O", "EXTEND"][mode_index(mode)]
69}
70
71/// A program with debugging sections gets DEBUG-ITEM after its own WORKING-STORAGE.
72pub(crate) fn with_debug_item(mut program: Program) -> Program {
73    if program.declaratives.debugging.is_empty() || program.working_storage.iter().any(|e| e.name.as_deref() == Some("DEBUG-ITEM")) {
74        return program;
75    }
76    let Ok(registers) = syntax::parse(DEBUG_ITEM) else { return program };
77    for mut entry in registers.working_storage {
78        entry.pos = Pos::default();
79        program.working_storage.push(entry);
80    }
81    program
82}
83
84/// The debugging sections, as spans of paragraphs.
85pub(crate) fn debugging_sections(program: &Program) -> Vec<Span> {
86    program.declaratives.debugging.iter().map(|u| (u.section, crate::section_end(program, u.section))).collect()
87}
88
89/// Resolves every USE AFTER EXCEPTION/ERROR and USE FOR DEBUGGING, with the rules of pp. 714-716.
90pub(crate) fn resolve(program: &Program, layout: &Layout, options: &Options, errors: &mut Vec<Error>) -> Table {
91    let span = |section: usize| (section, crate::section_end(program, section));
92    let mut table = Table { files: vec![None; program.files.len()], global_files: vec![None; program.files.len()], ..Table::default() };
93    for u in &program.declaratives.errors {
94        match &u.on {
95            ErrorUse::Files(names) => {
96                for name in names {
97                    let error = |why: &str| Error::at(u.pos, format!("USE AFTER EXCEPTION/ERROR ON {name}: {why}"));
98                    match program.files.iter().position(|f| f.name == *name) {
99                        None => errors.push(error("no file has that name")),
100                        Some(k) if program.files[k].sort => errors.push(error("a sort or merge file takes no EXCEPTION/ERROR procedure")),
101                        Some(k) if table.files[k].is_some() => errors.push(error("the file has another EXCEPTION/ERROR procedure")),
102                        Some(k) => {
103                            table.files[k] = Some(span(u.section));
104                            if u.global {
105                                table.global_files[k] = Some(span(u.section));
106                            }
107                        }
108                    }
109                }
110            }
111            ErrorUse::Mode(mode) => match &mut table.modes[mode_index(*mode)] {
112                Some(_) => errors.push(Error::at(u.pos, format!("USE AFTER EXCEPTION/ERROR ON {}: another procedure is for the same open mode", mode_name(*mode)))),
113                free => {
114                    *free = Some(span(u.section));
115                    if u.global {
116                        table.global_modes[mode_index(*mode)] = Some(span(u.section));
117                    }
118                }
119            },
120        }
121    }
122    let debugging = debugging_sections(program);
123    let in_debugging = |i: usize| debugging.iter().any(|&(first, last)| (first..=last).contains(&i));
124    let uses = &program.declaratives.debugging;
125    if let Some(u) = uses.first().filter(|_| options.thread) {
126        errors.push(Error::at(u.pos, "USE FOR DEBUGGING is not allowed in a program compiled with THREAD"));
127    }
128    let every = uses.iter().filter(|u| u.procedures.is_empty()).count();
129    if let Some(u) = uses.iter().find(|u| u.procedures.is_empty()).filter(|_| every > 1 || every < uses.len()) {
130        errors.push(Error::at(u.pos, "USE FOR DEBUGGING ON ALL PROCEDURES: it may be written once, and no other USE FOR DEBUGGING may name a procedure"));
131    }
132    let mut triggers: Vec<Option<(Span, String)>> = vec![None; program.paragraphs.len()];
133    for (u, &section) in uses.iter().zip(&debugging) {
134        if u.procedures.is_empty() {
135            for (i, p) in program.paragraphs.iter().enumerate() {
136                if !p.name.is_empty() && !in_debugging(i) {
137                    triggers[i] = Some((section, p.name.clone()));
138                }
139            }
140            continue;
141        }
142        for name in &u.procedures {
143            let error = |why: &str| Error::at(u.pos, format!("USE FOR DEBUGGING ON {}: {why}", name.name));
144            match crate::procedure_from(program, name, section.0) {
145                Err(m) => errors.push(error(&m)),
146                Ok((i, _)) if in_debugging(i) => errors.push(error("the procedure is in a debugging section")),
147                Ok((i, _)) if triggers[i].is_some() => errors.push(error("the procedure is named in another USE FOR DEBUGGING, or twice in this one")),
148                Ok((i, _)) => triggers[i] = Some((section, debug_name(name))),
149            }
150        }
151    }
152    references(program, &debugging, errors);
153    if options.debug && triggers.iter().any(Option::is_some) {
154        table.triggers = triggers;
155        if every > 0 {
156            for p in program.paragraphs.iter().take(program.report_writer.procedure_start) {
157                alters(&p.statements, &mut table.declarative_alters);
158            }
159        }
160    }
161    table.debug_item = match layout.resolve("DEBUG-ITEM", &[], Pos::default()) {
162        Ok(Resolved::Item(i)) if !uses.is_empty() => Some(i),
163        _ => None,
164    };
165    table
166}
167
168/// A procedure-name as DEBUG-NAME and DEBUG-CONTENTS give it, a qualifier after OF (p. 19).
169pub fn debug_name(name: &ProcName) -> String {
170    name.section.as_ref().map_or_else(|| name.name.clone(), |s| format!("{} OF {s}", name.name))
171}
172
173/// Where each ALTER in `stmts` is.
174fn alters(stmts: &[Stmt], out: &mut Vec<Pos>) {
175    for s in stmts {
176        if let Stmt::Alter { pos, .. } = s {
177            out.push(*pos);
178        }
179        for body in crate::oo::bodies(s) {
180            alters(body, out);
181        }
182    }
183}
184
185/// Each PERFORM, GO TO, ALTER and SORT or MERGE procedure in `stmts`: the procedures it names,
186/// whether it is a PERFORM, and where it is.
187fn procedure_references<'a>(stmts: &'a [Stmt], out: &mut Vec<(&'a ProcName, Option<&'a ProcName>, bool, Pos)>) {
188    for s in stmts {
189        match s {
190            Stmt::PerformProc { from, thru, pos, .. } => out.push((from, thru.as_ref(), true, *pos)),
191            Stmt::GoTo { target: Some(target), pos } => out.push((target, None, false, *pos)),
192            Stmt::GoToDepending { targets, pos, .. } => out.extend(targets.iter().map(|t| (t, None, false, *pos))),
193            Stmt::Alter { pairs, pos } => out.extend(pairs.iter().flat_map(|(from, to)| [from, to]).map(|t| (t, None, false, *pos))),
194            Stmt::Sorting(sorting) => {
195                if let Sorting::Sort(st) = &**sorting {
196                    for io in [&st.input, &st.output].into_iter().flatten() {
197                        if let SortIo::Procedure { from, thru } = io {
198                            out.push((from, thru.as_ref(), false, st.pos));
199                        }
200                    }
201                }
202            }
203            _ => {}
204        }
205        for body in crate::oo::bodies(s) {
206            procedure_references(body, out);
207        }
208    }
209}
210
211/// A debugging section refers to no nondeclarative procedure (p. 716), and nothing outside the
212/// debugging sections refers into one (p. 771). A PERFORM ... THRU that names a declarative
213/// procedure names two in the same declarative section (p. 418). See
214/// [`numeric::assumptions::DEBUGGING_SECTION_REFERENCES`].
215fn references(program: &Program, debugging: &[Span], errors: &mut Vec<Error>) {
216    let declaratives_end = program.report_writer.procedure_start;
217    if declaratives_end == 0 {
218        return;
219    }
220    let debugging_section =|i: usize| debugging.iter().position(|&(first, last)| (first..=last).contains(&i));
221    let section_of = |i: usize| (0..=i).rev().find(|&j| program.paragraphs[j].is_section);
222    for (i, p) in program.paragraphs.iter().enumerate() {
223        let mut named = Vec::new();
224        procedure_references(&p.statements, &mut named);
225        for (from, thru, perform, pos) in named {
226            let first = |n: &ProcName| crate::procedure(program, n).ok().map(|(at, _)| at);
227            let (Some(a), b) = (first(from), thru.and_then(first)) else { continue };
228            for (target, name) in std::iter::once((a, from)).chain(b.zip(thru)) {
229                if debugging_section(i).is_some() && target >= declaratives_end {
230                    errors.push(Error::at(pos, format!("{}: a debugging section may refer only to declarative procedures", name.name)));
231                } else if debugging_section(i).is_none() && debugging_section(target).is_some() {
232                    errors.push(Error::at(pos, format!("{}: only a debugging section may refer to a procedure in a debugging section", name.name)));
233                }
234            }
235            if let Some(b) = b.filter(|&b| perform && (a < declaratives_end || b < declaratives_end))
236                && (b >= declaratives_end || a >= declaratives_end || section_of(a) != section_of(b))
237            {
238                errors.push(Error::at(pos, "PERFORM ... THRU: a declarative procedure and the other end of the range must be in the same declarative section"));
239            }
240        }
241    }
242}