1mod call;
13mod cics;
14mod class;
15mod cond;
16mod data;
17mod file;
18mod flow;
19mod function;
20mod markup;
21mod plans;
22mod report;
23mod search;
24mod set;
25mod sort;
26mod sql;
27mod text;
28mod verify;
29
30#[cfg(test)]
31mod tests;
32
33pub use verify::verify;
34
35use crate::Compiled;
36use crate::layout::{Binding, Layout, Resolved};
37use crate::machine::Machine;
38use crate::unit::{AddProgram, Clock, Library, RunUnit};
39use rt::abend::AbendCode;
40use rt::lir::{self, AbendId, BlockId, ConstId, DebugId, PlaceId, RangeId, SymId};
41use std::collections::{BTreeSet, HashMap};
42use std::fmt;
43use syntax::Pos;
44use syntax::ast;
45use zarch::ebcdic::CodePage;
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub enum LowerError {
50 Unsupported(&'static str, Pos),
52 Exceeds(&'static str, Pos),
54 Invalid(String),
56}
57
58impl LowerError {
59 pub fn pos(&self) -> Pos {
60 match self {
61 Self::Unsupported(_, pos) | Self::Exceeds(_, pos) => *pos,
62 Self::Invalid(_) => Pos::default(),
63 }
64 }
65}
66
67impl fmt::Display for LowerError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 Self::Unsupported(what, _) => write!(f, "lowering: {what} is not lowered yet"),
71 Self::Exceeds(what, _) => write!(f, "lowering: {what} exceeds the LIR's limit"),
72 Self::Invalid(why) => write!(f, "lowering: the lowered program is invalid: {why}"),
73 }
74 }
75}
76
77impl From<LowerError> for syntax::Error {
78 fn from(e: LowerError) -> Self {
79 syntax::Error::at(e.pos(), e.to_string())
80 }
81}
82
83type R<T> = Result<T, LowerError>;
84
85fn unsupported<T>(what: &'static str, pos: Pos) -> R<T> {
86 Err(LowerError::Unsupported(what, pos))
87}
88
89fn next_id<T>(table: &[T], what: &'static str) -> R<u32> {
91 u32::try_from(table.len()).map_err(|_| LowerError::Exceeds(what, Pos::default()))
92}
93
94fn push<T>(table: &mut Vec<T>, value: T, what: &'static str) -> R<u32> {
95 let id = next_id(table, what)?;
96 table.push(value);
97 Ok(id)
98}
99
100pub fn lower(compiled: &Compiled) -> Result<lir::Program, LowerError> {
103 if compiled.options.numcheck.is_some() {
105 return unsupported("NUMCHECK or ZONECHECK", Pos::default());
106 }
107 if compiled.options.parmcheck.is_some() {
108 return unsupported("PARMCHECK", Pos::default());
109 }
110 let mut l = Lower::new(compiled);
111 l.refuse_shared_storage()?;
112 let id = l.sym(&compiled.program.id);
113 let sources = compiled.program.sources.iter().map(|s| l.sym(s)).collect();
114 let storage = l.storage()?;
115 let items = l.items()?;
116 l.services.files = l.files()?;
117 l.services.declaratives = l.declaratives()?;
118 l.services.report = l.report_writer()?;
119 (l.sql, l.services.sqlca) = l.sql_table()?;
120 let paragraphs = l.procedure()?;
121 l.services.entries = l.entry_points()?;
122 l.services.class = l.class_definition()?;
123 let procedure_start = compiled.program.report_writer.procedure_start.min(compiled.program.paragraphs.len());
124 let (blocks, debug) = l.blocks.finish()?;
125 let program = lir::Program {
126 id,
127 options: lir::ProgramOptions {
128 options: compiled.options,
129 ssrange: compiled.ssrange,
130 cards: compiled.program.options.clone(),
131 collating: collating(&compiled.collating),
132 decimal_point_comma: compiled.program.environment.decimal_point_comma,
133 numval_currency: crate::machine::numval_currency(&compiled.program.environment.currency),
134 when_compiled: l.plans.function.iter().any(|f| f.func == lir::Func::WhenCompiled).then_some(compiled.when_compiled),
135 },
136 initial: compiled.program.initial,
137 recursive: compiled.program.recursive,
138 storage,
139 items,
140 paragraphs,
141 procedure_start: procedure_start as u32,
142 ranges: l.ranges,
143 blocks,
144 places: l.places,
145 exprs: l.exprs,
146 conds: l.conds,
147 consts: l.consts,
148 plans: l.plans,
149 services: l.services,
150 sql: l.sql,
151 abends: l.abends,
152 edits: edits(&compiled.layout)?,
153 symbols: l.symbols,
154 debug: lir::Debug { sources, positions: l.positions, ops: debug.ops, statements: debug.statements },
155 };
156 if cfg!(debug_assertions) {
157 verify(&program).map_err(LowerError::Invalid)?;
158 }
159 Ok(program)
160}
161
162struct Lower<'c> {
164 c: &'c Compiled,
165 layout: &'c Layout,
166 program: &'c ast::Program,
167 page: &'static CodePage,
168 symbols: Vec<String>,
169 symbol_ids: HashMap<String, SymId>,
170 positions: Vec<Pos>,
171 position_ids: HashMap<(u16, u32, u32), DebugId>,
172 places: Vec<lir::Place>,
173 place_items: Vec<Option<usize>>,
175 place_ids: HashMap<String, PlaceId>,
176 exprs: Vec<lir::Expr>,
177 conds: Vec<lir::Cond>,
178 consts: Vec<lir::Const>,
179 const_ids: HashMap<String, ConstId>,
180 abends: Vec<lir::AbendText>,
181 abend_ids: HashMap<String, AbendId>,
182 plans: lir::Plans,
183 services: lir::Services,
184 sql: Vec<lir::SqlEntry>,
186 ranges: Vec<lir::Range>,
187 range_ids: HashMap<(u32, u32, lir::RangeKind), RangeId>,
188 blocks: flow::Blocks,
189 temps: u16,
190 entries: Vec<BlockId>,
192 sentences: HashMap<(usize, usize), BlockId>,
194 entry_blocks: HashMap<(usize, usize), BlockId>,
196 altered: BTreeSet<usize>,
198 segments: bool,
201 debugging: bool,
203}
204
205impl<'c> Lower<'c> {
206 fn new(c: &'c Compiled) -> Self {
207 Self {
208 c,
209 layout: &c.layout,
210 program: &c.program,
211 page: c.options.code_page(),
212 symbols: Vec::new(),
213 symbol_ids: HashMap::new(),
214 positions: Vec::new(),
215 position_ids: HashMap::new(),
216 places: Vec::new(),
217 place_items: Vec::new(),
218 place_ids: HashMap::new(),
219 exprs: Vec::new(),
220 conds: Vec::new(),
221 consts: Vec::new(),
222 const_ids: HashMap::new(),
223 abends: Vec::new(),
224 abend_ids: HashMap::new(),
225 plans: lir::Plans::default(),
226 services: lir::Services::default(),
227 sql: Vec::new(),
228 ranges: Vec::new(),
229 range_ids: HashMap::new(),
230 blocks: flow::Blocks::default(),
231 temps: 0,
232 entries: Vec::new(),
233 sentences: HashMap::new(),
234 entry_blocks: HashMap::new(),
235 altered: BTreeSet::new(),
236 segments: false,
237 debugging: !c.declaratives.triggers.is_empty(),
238 }
239 }
240
241 fn refuse_shared_storage(&self) -> R<()> {
243 let program = self.program;
244 if self.layout.bindings.iter().any(|b| *b != Binding::Argument) || program.files.iter().any(|f| f.external || f.declared_in.is_some()) {
245 return unsupported("EXTERNAL data and files, and GLOBAL names of a containing program", Pos::default());
246 }
247 if !program.containers.is_empty() && !program.files.is_empty() {
248 return unsupported("the files of a contained program, which a containing program's GLOBAL declaratives may serve", Pos::default());
249 }
250 let entries = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage);
251 let declares_global = entries.clone().any(|e| e.global) || program.files.iter().any(|f| f.global) || program.declaratives.errors.iter().any(|u| u.global);
252 if !program.nested.is_empty() && declares_global {
253 return unsupported("GLOBAL names and declaratives of a program that contains others", Pos::default());
254 }
255 Ok(())
256 }
257
258 fn sym(&mut self, text: &str) -> SymId {
259 if let Some(&id) = self.symbol_ids.get(text) {
260 return id;
261 }
262 let id = self.symbols.len() as SymId;
263 self.symbols.push(text.to_owned());
264 self.symbol_ids.insert(text.to_owned(), id);
265 id
266 }
267
268 fn at(&mut self, pos: Pos) -> DebugId {
269 let key = (pos.file, pos.line, pos.col);
270 if let Some(&id) = self.position_ids.get(&key) {
271 return id;
272 }
273 let id = self.positions.len() as DebugId;
274 self.positions.push(pos);
275 self.position_ids.insert(key, id);
276 id
277 }
278
279 fn abend(&mut self, code: AbendCode, message: &str, pos: Option<Pos>) -> R<AbendId> {
281 let at = pos.map(|p| self.at(p));
282 let key = format!("{code:?}{at:?}{message}");
283 if let Some(&id) = self.abend_ids.get(&key) {
284 return Ok(id);
285 }
286 let text = lir::AbendText { code, message: self.sym(message), at };
287 let id = push(&mut self.abends, text, "abend messages")?;
288 self.abend_ids.insert(key, id);
289 Ok(id)
290 }
291
292 fn ironwork(&mut self, message: &str) -> R<AbendId> {
293 self.abend(AbendCode::Ironwork, message, None)
294 }
295
296 fn storage(&mut self) -> R<lir::Storage> {
299 let layout = self.layout;
300 let (size, local) = (layout.size as usize, layout.local_size as usize);
301 let (mut out, mut err) = (Vec::new(), Vec::new());
302 let (image, local_image, abend) = {
303 let mut unit = RunUnit::new(Library::default(), crate::files::Dds::default(), None, Clock::Fixed(0, 0), &mut out, &mut err);
304 let me = unit.add(None, self.program, size);
305 let abend = Machine::activation(self.c, me, &mut unit, true).err();
306 let base = unit.programs[me].base;
307 let image = unit.mem[base..base + size].to_vec();
308 let local_image = if local > 0 { unit.mem[unit.mem.len() - local..].to_vec() } else { Vec::new() };
309 (image, local_image, abend)
310 };
311 let init_reports = String::from_utf8_lossy(&err).lines().map(|l| self.sym(l)).collect();
312 let init_abend = match abend {
313 Some(a) => Some(self.abend(a.code.clone(), &a.message, Some(a.pos))?),
314 None => None,
315 };
316 let root = |name: &str| layout.linkage_roots.iter().position(|&i| layout.items[i].name.as_deref() == Some(name));
317 let mut using = Vec::new();
318 for param in &self.program.using {
319 match root(¶m.name).map(u16::try_from) {
320 Some(Ok(ordinal)) => using.push(ordinal),
321 Some(Err(_)) => return Err(LowerError::Exceeds("LINKAGE records", Pos::default())),
322 None => return unsupported("PROCEDURE DIVISION USING an item that is not a LINKAGE record", Pos::default()),
323 }
324 }
325 let returning = match &self.program.returning {
326 None => None,
327 Some(name) => match root(name).map(u16::try_from) {
328 Some(Ok(ordinal)) => Some(ordinal),
329 Some(Err(_)) => return Err(LowerError::Exceeds("LINKAGE records", Pos::default())),
330 None => return unsupported("RETURNING an item that is not a LINKAGE record", Pos::default()),
331 },
332 };
333 Ok(lir::Storage {
334 size: layout.size,
335 image,
336 local_image,
337 init_reports,
338 init_abend,
339 linkage: layout.linkage_roots.iter().map(|&i| layout.items[i].size).collect(),
340 using,
341 returning,
342 file_areas: layout.file_areas.clone(),
343 })
344 }
345
346 fn items(&mut self) -> R<Vec<lir::Item>> {
348 let layout = self.layout;
349 let mut items = Vec::with_capacity(layout.items.len());
350 for item in &layout.items {
351 let item_of = |r: &ast::Ref| match layout.resolve(&r.name, &r.qualifiers, r.pos) {
352 Ok(Resolved::Item(i)) => Ok(i as u32),
353 _ => unsupported("an OCCURS DEPENDING ON or KEY that names no data item", r.pos),
354 };
355 let depending_on = item.depending_on.as_ref().map(item_of).transpose()?;
356 let keys = item.keys.iter().map(|(ascending, r)| item_of(r).map(|i| (*ascending, i))).collect::<R<_>>()?;
357 items.push(lir::Item {
358 name: item.name.as_deref().map(|n| self.sym(n)),
359 level: item.level,
360 parent: item.parent.map(|p| p as u32),
361 offset: item.offset,
362 size: item.size,
363 occurs: item.occurs,
364 dims: item.dims.clone(),
365 kind: item.kind,
366 local: item.local,
367 linkage: item.linkage,
368 redefines: item.redefines.as_deref().map(|n| self.sym(n)),
369 depending_on,
370 keys,
371 at: self.at(item.pos),
372 });
373 }
374 Ok(items)
375 }
376}
377
378fn collating(sequence: &crate::collating::Sequence) -> lir::Collating {
381 if sequence.is_native() {
382 return lir::Collating::Native;
383 }
384 let characters = (1..=sequence.count() as i64).filter_map(|k| sequence.character(k)).collect();
385 lir::Collating::Sequence(lir::Sequence {
386 positions: Box::new(sequence.positions()),
387 characters,
388 high_value: sequence.high_value,
389 low_value: sequence.low_value,
390 })
391}
392
393fn edits(layout: &Layout) -> R<Vec<lir::Edit>> {
395 if layout.edits.len() != layout.currencies.len() {
396 return Err(LowerError::Invalid(format!("{} edited PICTUREs with {} currency values", layout.edits.len(), layout.currencies.len())));
397 }
398 Ok(layout.edits.iter().zip(&layout.currencies).map(|(syms, currency)| lir::Edit { syms: syms.clone(), currency: currency.clone() }).collect())
399}
400
401fn is_static(place: &lir::Place) -> bool {
404 matches!(place.base, lir::Base::Program | lir::Base::Local | lir::Base::ReturnCode) && place.subscripts.is_empty() && place.odo.is_none() && place.refmod.is_none()
405}