Skip to main content

ironwork_compile/
constructs.rs

1//! The statement kinds, data usages and options a compiled program holds, which decide the
2//! assumptions of the register a run of it names (`numeric::governs`). Both executors take them
3//! from here, and a load module carries them.
4
5use crate::Compiled;
6use numeric::governs::{Facts, OptionFact, Statement as S, Usage as U};
7use rt::storage::Kind;
8use syntax::ast::{
9    Advancing, Cond, Encoding, ExecArg, ExecKind, Expr, InvokeMethod, Literal, Loop, Object, Operand, Organization, Program, Ref, ScreenAt, ScreenPhrases, SetStmt, Sorting, Stmt, Subject,
10    Varying,
11};
12use syntax::ast::{BinOp, Delimiter};
13
14pub fn of(c: &Compiled) -> Facts {
15    let program = &c.program;
16    let mut facts = Facts::of_options(&c.options, c.ssrange, !program.options.is_empty());
17    data(c, &mut facts);
18    let mut walk = Walk { program, facts };
19    for p in &program.paragraphs {
20        crate::oo::each(&p.statements, &mut |s| walk.statement(s));
21    }
22    let mut facts = walk.facts;
23    let declaratives = &program.declaratives;
24    if !declaratives.errors.is_empty() {
25        facts.statement(S::UseProcedure);
26    }
27    if !declaratives.debugging.is_empty() || program.environment.debugging_mode {
28        facts.statement(S::Debugging);
29    }
30    if !program.report_writer.reports.is_empty() {
31        facts.statement(S::ReportWriter);
32    }
33    if program.function.is_some() {
34        facts.statement(S::UserFunction);
35    }
36    if program.oo.as_deref().is_some_and(|o| !matches!(o.unit, syntax::ast::OoUnit::Program)) {
37        facts.statement(S::Invoke);
38    }
39    if program.sources.len() > 1 {
40        facts.statement(S::CopyMember);
41    }
42    if !program.screens.is_empty() {
43        facts.statement(S::Screen);
44    }
45    facts
46}
47
48/// The usages of the program's items, its files' organizations and its data clauses.
49fn data(c: &Compiled, facts: &mut Facts) {
50    for item in &c.layout.items {
51        let usage = match item.kind {
52            Kind::Zoned { .. } => U::Zoned,
53            Kind::Packed { .. } => U::Packed,
54            Kind::Binary { native, .. } => {
55                if native != numeric::Native::No {
56                    facts.usage(U::NativeBinary);
57                }
58                U::Binary
59            }
60            Kind::Float(_) => U::Float,
61            Kind::National => U::National,
62            Kind::Dbcs { .. } => U::Dbcs,
63            Kind::NumericEdited { .. } => U::NumericEdited,
64            Kind::ObjectReference => U::ObjectReference,
65            Kind::ProgramPointer => U::ProgramPointer,
66            Kind::Group | Kind::Alnum { .. } | Kind::AlnumEdited { .. } | Kind::Pointer | Kind::Index => continue,
67        };
68        facts.usage(usage);
69    }
70    let program = &c.program;
71    let containers = program.containers.iter();
72    for file in program.files.iter().chain(containers.clone().flat_map(|k| &k.files)) {
73        match file.organization {
74            Organization::Indexed => facts.usage(U::IndexedFile),
75            Organization::Relative => facts.usage(U::RelativeFile),
76            Organization::Sequential | Organization::LineSequential => {}
77        }
78        if file.linage.is_some() {
79            facts.usage(U::LinageFile);
80        }
81        if file.external {
82            facts.usage(U::External);
83        }
84        if file.global || file.declared_in.is_some() {
85            facts.usage(U::Global);
86        }
87    }
88    let records = program.files.iter().flat_map(|f| &f.records);
89    let containers = containers.flat_map(|k| k.working_storage.iter().chain(&k.local_storage).chain(&k.linkage).chain(k.files.iter().flat_map(|f| &f.records)));
90    for entry in program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(records).chain(containers.clone()) {
91        if entry.sync {
92            facts.usage(U::Synchronized);
93        }
94        if entry.depending_on.is_some() {
95            facts.usage(U::OccursDepending);
96        }
97        if entry.external {
98            facts.usage(U::External);
99        }
100        if entry.global {
101            facts.usage(U::Global);
102        }
103    }
104    if containers.count() > 0 {
105        facts.usage(U::Global);
106    }
107    let environment = &program.environment;
108    if !environment.alphabets.is_empty() || environment.collating_sequence.is_some() {
109        facts.usage(U::Alphabet);
110    }
111    if environment.decimal_point_comma {
112        facts.usage(U::DecimalComma);
113    }
114    if !environment.switches.is_empty() {
115        facts.usage(U::Upsi);
116    }
117}
118
119struct Walk<'p> {
120    program: &'p Program,
121    facts: Facts,
122}
123
124impl Walk<'_> {
125    fn statement(&mut self, s: &Stmt) {
126        let f = &mut self.facts;
127        match s {
128            Stmt::Move { from, to, .. } => {
129                f.statement(S::Move);
130                self.operand(from);
131                self.refs(to);
132            }
133            Stmt::Compute { targets, expr, .. } => {
134                f.statement(S::Arithmetic);
135                targets.iter().for_each(|t| self.reference(&t.r));
136                self.expr(expr);
137            }
138            Stmt::Arith(a) => {
139                f.statement(S::Arithmetic);
140                for (t, e) in &a.computations {
141                    self.reference(&t.r);
142                    self.expr(e);
143                }
144                if let Some((t, q, r)) = &a.remainder {
145                    self.reference(&t.r);
146                    self.expr(q);
147                    self.expr(r);
148                }
149            }
150            Stmt::Corresponding(c) => {
151                f.statement(S::Corresponding);
152                f.statement(if c.verb == syntax::ast::CorrespondingVerb::Move { S::Move } else { S::Arithmetic });
153                self.reference(&c.from);
154                self.reference(&c.to);
155            }
156            Stmt::If { cond, .. } => self.cond(cond),
157            Stmt::PerformInline { repeat, .. } | Stmt::PerformProc { repeat, .. } => {
158                f.statement(S::Perform);
159                self.repeat(repeat);
160            }
161            Stmt::Evaluate { subjects, whens, .. } => {
162                f.statement(S::Condition);
163                for subject in subjects {
164                    match subject {
165                        Subject::Bool(_) => {}
166                        Subject::Expr(e) => self.expr(e),
167                        Subject::Cond(c) => self.cond(c),
168                    }
169                }
170                for object in whens.iter().flat_map(|w| w.alternatives.iter().flatten()) {
171                    match object {
172                        Object::Any | Object::Bool(_) => {}
173                        Object::Cond(c) => self.cond(c),
174                        Object::Value { from, thru, .. } => {
175                            self.expr(from);
176                            thru.iter().for_each(|e| self.expr(e));
177                        }
178                    }
179                }
180            }
181            Stmt::Display { items, screen, .. } => {
182                f.statement(S::Display);
183                items.iter().for_each(|o| self.operand(o));
184                self.screen(screen.as_deref());
185            }
186            Stmt::Accept { target, screen, .. } => {
187                f.statement(S::Accept);
188                self.reference(target);
189                self.screen(screen.as_deref());
190            }
191            Stmt::Open { .. } | Stmt::Close { .. } | Stmt::Delete { .. } | Stmt::DeleteFile { .. } => f.statement(S::FileIo),
192            Stmt::Read(r) => {
193                f.statement(S::FileIo);
194                r.into.iter().chain(&r.key).for_each(|x| self.reference(x));
195            }
196            Stmt::Write { record, from, advancing, .. } => {
197                f.statement(S::FileIo);
198                if let Some(advancing) = advancing {
199                    f.statement(S::WriteAdvancing);
200                    if let Advancing::Lines { count, .. } = advancing {
201                        self.expr(count);
202                    }
203                }
204                self.reference(record);
205                from.iter().for_each(|o| self.operand(o));
206            }
207            Stmt::Rewrite { record, from, .. } => {
208                f.statement(S::FileIo);
209                self.reference(record);
210                from.iter().for_each(|o| self.operand(o));
211            }
212            Stmt::Start { key, .. } => {
213                f.statement(S::FileIo);
214                key.iter().for_each(|(_, r)| self.reference(r));
215            }
216            Stmt::Initialize { targets, with, .. } => {
217                f.statement(S::Initialize);
218                self.refs(targets);
219                with.iter().flat_map(|w| &w.replacing).for_each(|(_, o)| self.operand(o));
220            }
221            Stmt::GoTo { target: None, .. } | Stmt::Alter { .. } => f.statement(S::Alter),
222            Stmt::GoToDepending { on, .. } => self.reference(on),
223            Stmt::Entry { .. } => f.statement(S::Entry),
224            Stmt::Call(c) => {
225                f.statement(S::Call);
226                self.operand(&c.target);
227                c.using.iter().filter_map(|a| a.value.as_ref()).for_each(|o| self.operand(o));
228                c.returning.iter().for_each(|r| self.reference(r));
229            }
230            Stmt::Cancel { targets, .. } => {
231                f.statement(S::Cancel);
232                targets.iter().for_each(|o| self.operand(o));
233            }
234            Stmt::Set { set, .. } => {
235                f.statement(S::Set);
236                match set {
237                    SetStmt::ConditionTrue(refs) | SetStmt::ConditionFalse(refs) => self.refs(refs),
238                    SetStmt::To { targets, value } | SetStmt::Entry { targets, entry: value } | SetStmt::AddressOf { targets, value } => {
239                        self.refs(targets);
240                        self.operand(value);
241                    }
242                    SetStmt::UpDown { targets, by, .. } => {
243                        self.refs(targets);
244                        self.expr(by);
245                    }
246                    SetStmt::Switches(groups) => groups.iter().for_each(|(refs, _)| self.refs(refs)),
247                }
248            }
249            Stmt::String(st) => {
250                for (o, d) in &st.sources {
251                    self.operand(o);
252                    if let Delimiter::By(d) = d {
253                        self.operand(d);
254                    }
255                }
256                self.reference(&st.into);
257                st.pointer.iter().for_each(|r| self.reference(r));
258            }
259            Stmt::Unstring(u) => {
260                self.reference(&u.source);
261                u.delimiters.iter().for_each(|(_, o)| self.operand(o));
262                for into in &u.into {
263                    [Some(&into.target), into.delimiter_in.as_ref(), into.count_in.as_ref()].into_iter().flatten().for_each(|r| self.reference(r));
264                }
265                u.pointer.iter().chain(&u.tallying).for_each(|r| self.reference(r));
266            }
267            Stmt::Inspect(i) => {
268                f.statement(S::Inspect);
269                self.operand(&i.target);
270                for p in i.tallying.iter().chain(&i.replacing) {
271                    p.pattern.iter().chain(&p.by).chain(p.bounds.iter().map(|b| &b.value)).for_each(|o| self.operand(o));
272                    p.counter.iter().for_each(|r| self.reference(r));
273                }
274                if let Some((from, to, bounds)) = &i.converting {
275                    [from, to].into_iter().chain(bounds.iter().map(|b| &b.value)).for_each(|o| self.operand(o));
276                }
277            }
278            Stmt::Search(se) => {
279                f.statement(S::Condition);
280                self.reference(&se.table);
281                se.varying.iter().for_each(|r| self.reference(r));
282                se.whens.iter().for_each(|(c, _)| self.cond(c));
283            }
284            Stmt::Exec(b) => {
285                match b.kind {
286                    ExecKind::Sql => f.statement(S::Sql),
287                    ExecKind::Cics => f.statement(S::Cics),
288                    ExecKind::Dli => f.statement(S::Dli),
289                    ExecKind::Other => {}
290                }
291                for (_, arg) in &b.options {
292                    if let Some(ExecArg::Operand(o)) = arg {
293                        self.operand(o);
294                    }
295                }
296                self.refs(&b.host_variables);
297            }
298            Stmt::Report(_) => f.statement(S::ReportWriter),
299            Stmt::Invoke(i) => {
300                f.statement(S::Invoke);
301                self.reference(&i.target);
302                if let InvokeMethod::Identifier(r) = &i.method {
303                    self.reference(r);
304                }
305                i.using.iter().for_each(|o| self.operand(o));
306                i.returning.iter().for_each(|r| self.reference(r));
307            }
308            Stmt::JsonGenerate(j) => {
309                f.statement(S::Json);
310                self.encoding(j.encoding.as_ref());
311            }
312            Stmt::JsonParse(j) => {
313                f.statement(S::Json);
314                self.encoding(j.encoding.as_ref());
315            }
316            Stmt::XmlGenerate(x) => {
317                f.statement(S::Xml);
318                x.encoding.iter().chain(&x.namespace).chain(&x.prefix).for_each(|o| self.operand(o));
319            }
320            Stmt::XmlParse(x) => {
321                f.statement(S::Xml);
322                x.encoding.iter().for_each(|o| self.operand(o));
323            }
324            Stmt::Sorting(so) => match &**so {
325                Sorting::Sort(sort) => {
326                    let file = self.program.files.iter().any(|f| f.sort && f.name.eq_ignore_ascii_case(&sort.subject.name));
327                    self.facts.statement(match (sort.merge, file) {
328                        (true, _) => S::Merge,
329                        (false, true) => S::Sort,
330                        (false, false) => S::TableSort,
331                    });
332                    if sort.collating.is_some() {
333                        self.facts.usage(U::Alphabet);
334                    }
335                    sort.keys.iter().for_each(|(_, r)| self.reference(r));
336                }
337                Sorting::Release { record, from, .. } => {
338                    self.facts.statement(S::Sort);
339                    self.reference(record);
340                    from.iter().for_each(|o| self.operand(o));
341                }
342                Sorting::Return { into, .. } => {
343                    self.facts.statement(S::Sort);
344                    into.iter().for_each(|r| self.reference(r));
345                }
346            },
347            Stmt::StopRun { .. } => f.statement(S::Stop),
348            Stmt::GoTo { target: Some(_), .. }
349            | Stmt::Goback { .. }
350            | Stmt::ExitProgram { .. }
351            | Stmt::NextSentence
352            | Stmt::SentenceEnd
353            | Stmt::ExitMethod { .. }
354            | Stmt::Continue { .. }
355            | Stmt::Exit { .. } => {}
356        }
357    }
358
359    fn repeat(&mut self, repeat: &Loop) {
360        match repeat {
361            Loop::Once | Loop::Forever => {}
362            Loop::Times(e) => self.expr(e),
363            Loop::Until { cond, .. } => self.cond(cond),
364            Loop::Varying { varying, after, .. } => std::iter::once(&**varying).chain(after).for_each(|v| self.varying(v)),
365        }
366    }
367
368    fn varying(&mut self, v: &Varying) {
369        self.facts.statement(S::Arithmetic);
370        self.reference(&v.var);
371        self.expr(&v.from);
372        self.expr(&v.by);
373        self.cond(&v.until);
374    }
375
376    fn screen(&mut self, screen: Option<&ScreenPhrases>) {
377        let Some(screen) = screen else { return };
378        self.facts.statement(S::Screen);
379        match &screen.at {
380            Some(ScreenAt::Combined(o)) => self.operand(o),
381            Some(ScreenAt::LineColumn { line, column }) => line.iter().chain(column).for_each(|o| self.operand(o)),
382            None => {}
383        }
384    }
385
386    fn encoding(&mut self, encoding: Option<&Encoding>) {
387        if let Some(Encoding::Ccsid(o)) = encoding {
388            self.operand(o);
389        }
390    }
391
392    fn cond(&mut self, c: &Cond) {
393        self.facts.statement(S::Condition);
394        match c {
395            Cond::Rel(a, _, b) => {
396                self.expr(a);
397                self.expr(b);
398            }
399            Cond::Class(e, _) => self.expr(e),
400            Cond::Name(r) => self.reference(r),
401            Cond::NameOrRel { subject, name, .. } => {
402                self.expr(subject);
403                self.reference(name);
404            }
405            Cond::Not(inner) => self.cond(inner),
406            Cond::And(a, b) | Cond::Or(a, b) => {
407                self.cond(a);
408                self.cond(b);
409            }
410        }
411    }
412
413    fn expr(&mut self, e: &Expr) {
414        match e {
415            Expr::Operand(o) => self.operand(o),
416            Expr::Neg(inner) => {
417                self.facts.statement(S::Arithmetic);
418                self.expr(inner);
419            }
420            Expr::Bin(a, op, b) => {
421                self.facts.statement(S::Arithmetic);
422                if *op == BinOp::Pow {
423                    self.facts.statement(S::Exponentiation);
424                }
425                self.expr(a);
426                self.expr(b);
427            }
428        }
429    }
430
431    fn operand(&mut self, o: &Operand) {
432        match o {
433            Operand::Ref(r) | Operand::LengthOf(r) | Operand::AddressOf(r) => self.reference(r),
434            Operand::Literal(l) => self.literal(l),
435            Operand::Function(call) => {
436                self.facts.statement(if self.program.intrinsic(&call.name) { S::Function } else { S::UserFunction });
437                call.args.iter().for_each(|a| self.expr(a));
438                if let Some(m) = &call.refmod {
439                    self.expr(&m.start);
440                    m.length.iter().for_each(|l| self.expr(l));
441                }
442            }
443        }
444    }
445
446    fn literal(&mut self, l: &Literal) {
447        match l {
448            Literal::National(_) => self.facts.usage(U::National),
449            Literal::Dbcs(_) => self.facts.usage(U::Dbcs),
450            Literal::All(inner) => self.literal(inner),
451            Literal::Alnum(_) | Literal::Hex(_) | Literal::Number(_) | Literal::Figurative(_) => {}
452        }
453    }
454
455    fn refs(&mut self, refs: &[Ref]) {
456        refs.iter().for_each(|r| self.reference(r));
457    }
458
459    fn reference(&mut self, r: &Ref) {
460        r.subscripts.iter().for_each(|e| self.expr(e));
461        if let Some(m) = &r.refmod {
462            self.expr(&m.start);
463            m.length.iter().for_each(|l| self.expr(l));
464        }
465    }
466}
467
468/// A run's own facts beside its programs': how it was made.
469pub fn of_run(cics: bool, job: bool, parm: bool, statement_limit: bool) -> Facts {
470    let mut facts = Facts::default();
471    for (on, fact) in [(cics, OptionFact::CicsTask), (job, OptionFact::JobStep), (parm, OptionFact::Parm), (statement_limit, OptionFact::StatementLimit)] {
472        if on {
473            facts.option(fact);
474        }
475    }
476    facts
477}
478
479#[cfg(test)]
480mod tests {
481    use super::*;
482    use numeric::governs::Trigger;
483
484    fn facts_of(card: &str, data: &[&str], procedure: &[&str]) -> Facts {
485        let lines = ["IDENTIFICATION DIVISION.", "PROGRAM-ID. FACTS.", "ENVIRONMENT DIVISION.", "INPUT-OUTPUT SECTION.", "FILE-CONTROL.", "    SELECT KEYED ASSIGN TO KEYED ORGANIZATION INDEXED", "        RECORD KEY K-KEY.", "DATA DIVISION.", "FILE SECTION.", "FD  KEYED.", "01  K-REC.", "    05 K-KEY PIC X(4).", "WORKING-STORAGE SECTION."]
486            .into_iter()
487            .chain(data.iter().copied())
488            .chain(["PROCEDURE DIVISION."])
489            .chain(procedure.iter().copied())
490            .chain(["    GOBACK."]);
491        let source: String = std::iter::once(card.to_owned()).chain(lines.map(|l| format!("       {l}"))).map(|l| l + "\n").collect();
492        of(&crate::compile(syntax::parse(&source).unwrap(), &[]).unwrap_or_else(|e| panic!("{e:?}")))
493    }
494
495    #[test]
496    fn a_program_holds_the_statements_usages_and_options_it_is_written_with() {
497        let data = ["01  P PIC S9(5) COMP-3 VALUE 1.", "01  F COMP-2.", "01  T.", "    05 E PIC X OCCURS 3.", "01  N PIC N(2) VALUE N'AB'."];
498        let procedure = ["    COMPUTE P = P ** 2", "    IF FUNCTION LENGTH(N) > 0 DISPLAY E(P + 1) END-IF", "    SORT E ASCENDING", "    OPEN INPUT KEYED"];
499        let facts = facts_of("       CBL TRUNC(OPT)", &data, &procedure);
500        let statements: Vec<_> = facts.statements().collect();
501        assert_eq!(statements, [S::Arithmetic, S::Exponentiation, S::Display, S::Condition, S::TableSort, S::FileIo, S::Function]);
502        for usage in [U::Packed, U::Float, U::National, U::IndexedFile] {
503            assert!(facts.has(Trigger::Usage(usage)), "{usage:?}");
504        }
505        assert!(!facts.has(Trigger::Usage(U::Binary)));
506        assert_eq!(facts.options().collect::<Vec<_>>(), [OptionFact::TruncOpt, OptionFact::Cards]);
507        assert!(!facts.has(Trigger::Statement(S::Sort)));
508    }
509
510    #[test]
511    fn a_run_holds_how_it_was_made() {
512        let facts = of_run(true, false, true, false);
513        assert_eq!(facts.options().collect::<Vec<_>>(), [OptionFact::CicsTask, OptionFact::Parm]);
514        assert_eq!(facts.statements().count() + facts.usages().count(), 0);
515    }
516}