1use crate::layout::{Layout, Resolved};
6use numeric::Options;
7use syntax::ast::*;
8use syntax::{Error, Pos};
9
10pub(crate) type Span = (usize, usize);
12
13const 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
31pub(crate) const DEBUG_LINE: (usize, usize) = (0, 6);
33pub(crate) const DEBUG_NAME: (usize, usize) = (7, 30);
34pub(crate) const DEBUG_CONTENTS: (usize, usize) = (56, 30);
35
36pub(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#[derive(Debug, Default)]
41pub struct Table {
42 pub(crate) files: Vec<Option<Span>>,
44 pub(crate) modes: [Option<Span>; 4],
46 pub(crate) triggers: Vec<Option<(Span, String)>>,
49 pub(crate) debug_item: Option<usize>,
51 pub(crate) declarative_alters: Vec<Pos>,
53}
54
55pub(crate) fn mode_index(mode: OpenMode) -> usize {
56 match mode {
57 OpenMode::Input => 0,
58 OpenMode::Output => 1,
59 OpenMode::InputOutput => 2,
60 OpenMode::Extend => 3,
61 }
62}
63
64fn mode_name(mode: OpenMode) -> &'static str {
65 ["INPUT", "OUTPUT", "I-O", "EXTEND"][mode_index(mode)]
66}
67
68pub(crate) fn with_debug_item(mut program: Program) -> Program {
70 if program.declaratives.debugging.is_empty() || program.working_storage.iter().any(|e| e.name.as_deref() == Some("DEBUG-ITEM")) {
71 return program;
72 }
73 let Ok(registers) = syntax::parse(DEBUG_ITEM) else { return program };
74 for mut entry in registers.working_storage {
75 entry.pos = Pos::default();
76 program.working_storage.push(entry);
77 }
78 program
79}
80
81pub(crate) fn debugging_sections(program: &Program) -> Vec<Span> {
83 program.declaratives.debugging.iter().map(|u| (u.section, crate::section_end(program, u.section))).collect()
84}
85
86pub(crate) fn resolve(program: &Program, layout: &Layout, options: &Options, errors: &mut Vec<Error>) -> Table {
88 let span = |section: usize| (section, crate::section_end(program, section));
89 let mut table = Table { files: vec![None; program.files.len()], ..Table::default() };
90 for u in &program.declaratives.errors {
91 match &u.on {
92 ErrorUse::Files(names) => {
93 for name in names {
94 let error = |why: &str| Error::at(u.pos, format!("USE AFTER EXCEPTION/ERROR ON {name}: {why}"));
95 match program.files.iter().position(|f| f.name == *name) {
96 None => errors.push(error("no file has that name")),
97 Some(k) if program.files[k].sort => errors.push(error("a sort or merge file takes no EXCEPTION/ERROR procedure")),
98 Some(k) if table.files[k].is_some() => errors.push(error("the file has another EXCEPTION/ERROR procedure")),
99 Some(k) => table.files[k] = Some(span(u.section)),
100 }
101 }
102 }
103 ErrorUse::Mode(mode) => match &mut table.modes[mode_index(*mode)] {
104 Some(_) => errors.push(Error::at(u.pos, format!("USE AFTER EXCEPTION/ERROR ON {}: another procedure is for the same open mode", mode_name(*mode)))),
105 free => *free = Some(span(u.section)),
106 },
107 }
108 }
109 let debugging = debugging_sections(program);
110 let in_debugging = |i: usize| debugging.iter().any(|&(first, last)| (first..=last).contains(&i));
111 let uses = &program.declaratives.debugging;
112 if let Some(u) = uses.first().filter(|_| options.thread) {
113 errors.push(Error::at(u.pos, "USE FOR DEBUGGING is not allowed in a program compiled with THREAD"));
114 }
115 let every = uses.iter().filter(|u| u.procedures.is_empty()).count();
116 if let Some(u) = uses.iter().find(|u| u.procedures.is_empty()).filter(|_| every > 1 || every < uses.len()) {
117 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"));
118 }
119 let mut triggers: Vec<Option<(Span, String)>> = vec![None; program.paragraphs.len()];
120 for (u, §ion) in uses.iter().zip(&debugging) {
121 if u.procedures.is_empty() {
122 for (i, p) in program.paragraphs.iter().enumerate() {
123 if !p.name.is_empty() && !in_debugging(i) {
124 triggers[i] = Some((section, p.name.clone()));
125 }
126 }
127 continue;
128 }
129 for name in &u.procedures {
130 let error = |why: &str| Error::at(u.pos, format!("USE FOR DEBUGGING ON {}: {why}", name.name));
131 match crate::procedure(program, name) {
132 Err(m) => errors.push(error(&m)),
133 Ok((i, _)) if in_debugging(i) => errors.push(error("the procedure is in a debugging section")),
134 Ok((i, _)) if triggers[i].is_some() => errors.push(error("the procedure is named in another USE FOR DEBUGGING, or twice in this one")),
135 Ok((i, _)) => triggers[i] = Some((section, debug_name(name))),
136 }
137 }
138 }
139 references(program, &debugging, errors);
140 if options.debug && triggers.iter().any(Option::is_some) {
141 table.triggers = triggers;
142 if every > 0 {
143 for p in program.paragraphs.iter().take(program.report_writer.procedure_start) {
144 alters(&p.statements, &mut table.declarative_alters);
145 }
146 }
147 }
148 table.debug_item = match layout.resolve("DEBUG-ITEM", &[], Pos::default()) {
149 Ok(Resolved::Item(i)) if !uses.is_empty() => Some(i),
150 _ => None,
151 };
152 table
153}
154
155pub(crate) fn debug_name(name: &ProcName) -> String {
157 name.section.as_ref().map_or_else(|| name.name.clone(), |s| format!("{} OF {s}", name.name))
158}
159
160fn alters(stmts: &[Stmt], out: &mut Vec<Pos>) {
162 for s in stmts {
163 if let Stmt::Alter { pos, .. } = s {
164 out.push(*pos);
165 }
166 for body in crate::oo::bodies(s) {
167 alters(body, out);
168 }
169 }
170}
171
172fn procedure_references<'a>(stmts: &'a [Stmt], out: &mut Vec<(&'a ProcName, Option<&'a ProcName>, bool, Pos)>) {
175 for s in stmts {
176 match s {
177 Stmt::PerformProc { from, thru, pos, .. } => out.push((from, thru.as_ref(), true, *pos)),
178 Stmt::GoTo { target: Some(target), pos } => out.push((target, None, false, *pos)),
179 Stmt::GoToDepending { targets, pos, .. } => out.extend(targets.iter().map(|t| (t, None, false, *pos))),
180 Stmt::Alter { pairs, pos } => out.extend(pairs.iter().flat_map(|(from, to)| [from, to]).map(|t| (t, None, false, *pos))),
181 Stmt::Sorting(sorting) => {
182 if let Sorting::Sort(st) = &**sorting {
183 for io in [&st.input, &st.output].into_iter().flatten() {
184 if let SortIo::Procedure { from, thru } = io {
185 out.push((from, thru.as_ref(), false, st.pos));
186 }
187 }
188 }
189 }
190 _ => {}
191 }
192 for body in crate::oo::bodies(s) {
193 procedure_references(body, out);
194 }
195 }
196}
197
198fn references(program: &Program, debugging: &[Span], errors: &mut Vec<Error>) {
203 let declaratives_end = program.report_writer.procedure_start;
204 if declaratives_end == 0 {
205 return;
206 }
207 let debugging_section =|i: usize| debugging.iter().position(|&(first, last)| (first..=last).contains(&i));
208 let section_of = |i: usize| (0..=i).rev().find(|&j| program.paragraphs[j].is_section);
209 for (i, p) in program.paragraphs.iter().enumerate() {
210 let mut named = Vec::new();
211 procedure_references(&p.statements, &mut named);
212 for (from, thru, perform, pos) in named {
213 let first = |n: &ProcName| crate::procedure(program, n).ok().map(|(at, _)| at);
214 let (Some(a), b) = (first(from), thru.and_then(first)) else { continue };
215 for (target, name) in std::iter::once((a, from)).chain(b.zip(thru)) {
216 if debugging_section(i).is_some() && target >= declaratives_end {
217 errors.push(Error::at(pos, format!("{}: a debugging section may refer only to declarative procedures", name.name)));
218 } else if debugging_section(i).is_none() && debugging_section(target).is_some() {
219 errors.push(Error::at(pos, format!("{}: only a debugging section may refer to a procedure in a debugging section", name.name)));
220 }
221 }
222 if let Some(b) = b.filter(|&b| perform && (a < declaratives_end || b < declaratives_end))
223 && (b >= declaratives_end || a >= declaratives_end || section_of(a) != section_of(b))
224 {
225 errors.push(Error::at(pos, "PERFORM ... THRU: a declarative procedure and the other end of the range must be in the same declarative section"));
226 }
227 }
228 }
229}