1use 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
48fn 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
468pub 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}