Skip to main content

ironwork_numeric/
governs.rs

1//! What an assumption of the register governs, and what a run holds of it: the statement kinds and
2//! data usages of each program it entered, with the options in force for it. An assumption governs
3//! a run when every trigger of one of its conjunctions is among one program's facts, so the ids a
4//! run names are those its outcome could have rested on, at program granularity: an
5//! over-approximation.
6
7use crate::options::{DispSign, IntDate, Qualify, Quote, Trunc};
8use crate::{Compliance, Numproc, Options};
9
10macro_rules! vocabulary {
11    ($(#[$doc:meta])* $name:ident { $($variant:ident => $word:literal,)* }) => {
12        $(#[$doc])*
13        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14        pub enum $name {
15            $($variant,)*
16        }
17
18        impl $name {
19            pub const ALL: &'static [Self] = &[$(Self::$variant,)*];
20
21            /// The word a load module and `ironwork assumptions --json` give it.
22            pub const fn name(self) -> &'static str {
23                match self {
24                    $(Self::$variant => $word,)*
25                }
26            }
27
28            pub fn named(word: &str) -> Option<Self> {
29                Self::ALL.iter().copied().find(|k| k.name() == word)
30            }
31
32            const fn bit(self) -> u64 {
33                1 << self as u32
34            }
35        }
36    };
37}
38
39vocabulary! {
40    /// A statement, or a construct of the PROCEDURE DIVISION, a program holds.
41    Statement {
42        Move => "move",
43        Arithmetic => "arithmetic",
44        Exponentiation => "exponentiation",
45        Corresponding => "corresponding",
46        Display => "display",
47        Accept => "accept",
48        Call => "call",
49        Cancel => "cancel",
50        Entry => "entry",
51        Stop => "stop-run",
52        Alter => "alter",
53        Perform => "perform",
54        Condition => "condition",
55        Initialize => "initialize",
56        Inspect => "inspect",
57        Set => "set",
58        Sort => "sort",
59        Merge => "merge",
60        TableSort => "table-sort",
61        FileIo => "file-io",
62        WriteAdvancing => "write-advancing",
63        UseProcedure => "use-procedure",
64        Debugging => "debugging",
65        Function => "intrinsic-function",
66        UserFunction => "user-function",
67        CopyMember => "copy-member",
68        Sql => "exec-sql",
69        Cics => "exec-cics",
70        Dli => "exec-dli",
71        Xml => "xml",
72        Json => "json",
73        Invoke => "invoke",
74        ReportWriter => "report-writer",
75        Screen => "screen",
76    }
77}
78
79vocabulary! {
80    /// A usage, or a file organization or data description clause, a program's data holds.
81    Usage {
82        Zoned => "zoned",
83        Packed => "packed",
84        Binary => "binary",
85        NativeBinary => "native-binary",
86        Float => "float",
87        National => "national",
88        Dbcs => "dbcs",
89        NumericEdited => "numeric-edited",
90        ObjectReference => "object-reference",
91        ProgramPointer => "program-pointer",
92        IndexedFile => "indexed-file",
93        RelativeFile => "relative-file",
94        LinageFile => "linage-file",
95        Synchronized => "synchronized",
96        OccursDepending => "occurs-depending",
97        External => "external",
98        Global => "global",
99        Alphabet => "alphabet",
100        DecimalComma => "decimal-point-comma",
101        Upsi => "upsi",
102    }
103}
104
105vocabulary! {
106    /// A compiler option a program was compiled with, or a way the run was made.
107    OptionFact {
108        TruncOpt => "TRUNC(OPT)",
109        NumprocPfd => "NUMPROC(PFD)",
110        Ssrange => "SSRANGE",
111        Fastsrt => "FASTSRT",
112        Thread => "THREAD",
113        Invdata => "INVDATA",
114        Numcheck => "NUMCHECK",
115        Initcheck => "INITCHECK",
116        Parmcheck => "PARMCHECK",
117        IntdateLilian => "INTDATE(LILIAN)",
118        QualifyExtend => "QUALIFY(EXTEND)",
119        DispsignSep => "DISPSIGN(SEP)",
120        Apost => "APOST",
121        Currency => "CURRENCY",
122        Cards => "cbl-card",
123        MixedCodepage => "mixed-codepage",
124        Extended => "compliance-extended",
125        CicsTask => "cics-task",
126        JobStep => "job-step",
127        Parm => "parm",
128        StatementLimit => "statement-limit",
129    }
130}
131
132const _: () = assert!(Statement::ALL.len() <= 64 && Usage::ALL.len() <= 64 && OptionFact::ALL.len() <= 64);
133
134#[derive(Clone, Copy, Debug, PartialEq, Eq)]
135pub enum Trigger {
136    Statement(Statement),
137    Usage(Usage),
138    Option(OptionFact),
139    /// The claim holds for every program: storage layout, code page tables, how source is read.
140    Always,
141}
142
143impl Trigger {
144    /// `statement:sort`, `usage:packed`, `option:TRUNC(OPT)` or `always`.
145    pub fn name(self) -> String {
146        match self {
147            Self::Statement(s) => format!("statement:{}", s.name()),
148            Self::Usage(u) => format!("usage:{}", u.name()),
149            Self::Option(o) => format!("option:{}", o.name()),
150            Self::Always => "always".into(),
151        }
152    }
153}
154
155/// Any of the conjunctions, each met when all of its triggers are.
156pub type Governs = &'static [&'static [Trigger]];
157
158/// The statement kinds, usages and options a program holds, or a run's programs together.
159#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
160pub struct Facts {
161    statements: u64,
162    usages: u64,
163    options: u64,
164}
165
166impl Facts {
167    /// Every statement kind and usage, for a program whose own are not known; no option.
168    pub fn every_construct() -> Self {
169        let all = |bits: &mut u64, n: usize| *bits = if n == 64 { u64::MAX } else { (1 << n) - 1 };
170        let mut facts = Self::default();
171        all(&mut facts.statements, Statement::ALL.len());
172        all(&mut facts.usages, Usage::ALL.len());
173        facts
174    }
175
176    pub fn insert(&mut self, t: Trigger) {
177        match t {
178            Trigger::Statement(s) => self.statements |= s.bit(),
179            Trigger::Usage(u) => self.usages |= u.bit(),
180            Trigger::Option(o) => self.options |= o.bit(),
181            Trigger::Always => {}
182        }
183    }
184
185    pub fn statement(&mut self, s: Statement) {
186        self.insert(Trigger::Statement(s));
187    }
188
189    pub fn usage(&mut self, u: Usage) {
190        self.insert(Trigger::Usage(u));
191    }
192
193    pub fn option(&mut self, o: OptionFact) {
194        self.insert(Trigger::Option(o));
195    }
196
197    pub fn has(&self, t: Trigger) -> bool {
198        match t {
199            Trigger::Statement(s) => self.statements & s.bit() != 0,
200            Trigger::Usage(u) => self.usages & u.bit() != 0,
201            Trigger::Option(o) => self.options & o.bit() != 0,
202            Trigger::Always => true,
203        }
204    }
205
206    pub fn union(&mut self, other: Self) {
207        self.statements |= other.statements;
208        self.usages |= other.usages;
209        self.options |= other.options;
210    }
211
212    /// Whether any conjunction of `governs` is met.
213    pub fn meet(&self, governs: Governs) -> bool {
214        governs.iter().any(|all| all.iter().all(|&t| self.has(t)))
215    }
216
217    pub fn statements(&self) -> impl Iterator<Item = Statement> + '_ {
218        Statement::ALL.iter().copied().filter(|s| self.statements & s.bit() != 0)
219    }
220
221    pub fn usages(&self) -> impl Iterator<Item = Usage> + '_ {
222        Usage::ALL.iter().copied().filter(|u| self.usages & u.bit() != 0)
223    }
224
225    pub fn options(&self) -> impl Iterator<Item = OptionFact> + '_ {
226        OptionFact::ALL.iter().copied().filter(|o| self.options & o.bit() != 0)
227    }
228
229    /// The options a program compiled with `options` holds, SSRANGE and its CBL or PROCESS cards
230    /// being kept beside them.
231    pub fn of_options(options: &Options, ssrange: bool, cards: bool) -> Self {
232        let mut facts = Self::default();
233        let held = [
234            (options.trunc == Trunc::Opt, OptionFact::TruncOpt),
235            (options.numproc == Numproc::Pfd, OptionFact::NumprocPfd),
236            (ssrange, OptionFact::Ssrange),
237            (options.fastsrt, OptionFact::Fastsrt),
238            (options.thread, OptionFact::Thread),
239            (options.invdata.is_some(), OptionFact::Invdata),
240            (options.numcheck.is_some(), OptionFact::Numcheck),
241            (options.initcheck.is_some(), OptionFact::Initcheck),
242            (options.parmcheck.is_some(), OptionFact::Parmcheck),
243            (options.intdate == IntDate::Lilian, OptionFact::IntdateLilian),
244            (options.qualify == Qualify::Extend, OptionFact::QualifyExtend),
245            (options.dispsign == DispSign::Sep, OptionFact::DispsignSep),
246            (options.quote == Quote::Apost, OptionFact::Apost),
247            (options.currency.is_some(), OptionFact::Currency),
248            (cards, OptionFact::Cards),
249            (zarch::ebcdic::CodePage::by_ccsid(options.codepage).is_some_and(|p| p.dbcs().is_some()), OptionFact::MixedCodepage),
250            (options.compliance == Compliance::Extended, OptionFact::Extended),
251        ];
252        for (on, fact) in held {
253            if on {
254                facts.option(fact);
255            }
256        }
257        facts
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn each_word_names_one_fact_and_reads_back() {
267        for s in Statement::ALL {
268            assert_eq!(Statement::named(s.name()), Some(*s));
269        }
270        for u in Usage::ALL {
271            assert_eq!(Usage::named(u.name()), Some(*u));
272        }
273        for o in OptionFact::ALL {
274            assert_eq!(OptionFact::named(o.name()), Some(*o));
275        }
276        assert_eq!(Statement::named("packed"), None);
277    }
278
279    #[test]
280    fn a_conjunction_needs_every_trigger_and_any_one_suffices() {
281        let mut facts = Facts::default();
282        facts.usage(Usage::Binary);
283        let governs: Governs = &[&[Trigger::Option(OptionFact::TruncOpt), Trigger::Usage(Usage::Binary)], &[Trigger::Statement(Statement::Sort)]];
284        assert!(!facts.meet(governs));
285        facts.option(OptionFact::TruncOpt);
286        assert!(facts.meet(governs));
287        assert!(Facts::default().meet(&[&[Trigger::Always]]));
288        assert!(!Facts::default().meet(&[]));
289    }
290
291    #[test]
292    fn every_construct_holds_each_statement_and_usage_and_no_option() {
293        let every = Facts::every_construct();
294        assert_eq!(every.statements().count(), Statement::ALL.len());
295        assert_eq!(every.usages().count(), Usage::ALL.len());
296        assert_eq!(every.options().count(), 0);
297    }
298
299    #[test]
300    fn options_are_read_from_what_the_compile_was_given() {
301        let mut o = Options::default();
302        assert_eq!(Facts::of_options(&o, false, false).options().count(), 0);
303        o.trunc = Trunc::Opt;
304        o.codepage = 930;
305        let facts = Facts::of_options(&o, true, true);
306        assert_eq!(facts.options().collect::<Vec<_>>(), [OptionFact::TruncOpt, OptionFact::Ssrange, OptionFact::Cards, OptionFact::MixedCodepage]);
307    }
308}