1use 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 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 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 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 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 Always,
141}
142
143impl Trigger {
144 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
155pub type Governs = &'static [&'static [Trigger]];
157
158#[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 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 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 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}