1mod classes;
5pub mod collating;
6mod corresponding;
7pub use corresponding::is_alphabetic;
8pub mod declaratives;
9pub mod function;
10mod initcheck;
11pub mod layout;
12pub mod linage;
13pub mod markup;
14pub mod numcheck;
15pub mod oo;
16mod operands;
17pub mod picture;
18pub mod printer;
19pub mod report;
20mod reserved;
21mod scope;
22pub mod sort;
23pub mod sql;
24mod switches;
25
26use layout::Layout;
27use numeric::{Options, Vlr};
28use rt::lir::{CompileTime, TimeSource};
29use rt::storage::literal_fixed;
30use syntax::ast::*;
31use syntax::messages::{IWC0003, IWC0004, Message};
32use syntax::{Error, Pos, Severity};
33
34pub struct Compiled {
35 pub program: Program,
36 pub when_compiled: CompileTime,
38 pub layout: Layout,
39 pub options: Options,
40 pub ssrange: bool,
41 pub report_writer: report::Writer,
42 pub collating: collating::Sequence,
44 pub carriage: Vec<Option<printer::Carriage>>,
46 pub diagnostics: Vec<Error>,
48 pub entries: Vec<EntryPoint>,
50 pub declaratives: declaratives::Table,
52 pub functions: Vec<function::Udf>,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct EntryPoint {
60 pub name: String,
61 pub paragraph: usize,
62 pub statement: usize,
63 pub using: Vec<Param>,
64 pub pos: Pos,
65}
66
67pub fn entry_points(program: &Program) -> Vec<EntryPoint> {
69 let mut out = Vec::new();
70 for (paragraph, p) in program.paragraphs.iter().enumerate() {
71 for (k, s) in p.statements.iter().enumerate() {
72 if let Stmt::Entry { name, using, pos } = s {
73 out.push(EntryPoint { name: name.clone(), paragraph, statement: k + 1, using: using.clone(), pos: *pos });
74 }
75 }
76 }
77 out
78}
79
80pub fn variable_records(file: &FileDecl, layout: &Layout, k: usize) -> bool {
85 let undeclared = file.recording.is_none() && file.record_min.is_none() && file.record_max.is_none() && !file.record_varying;
86 file.recording == Some('V') || file.record_varying || file.record_min != file.record_max || undeclared && layout.record_lengths[k].is_some_and(|(shortest, longest)| shortest != longest)
87}
88
89pub fn read_lengths(file: &FileDecl, layout: &Layout, k: usize, vlr: Vlr) -> (u32, u32) {
95 match vlr {
96 Vlr::Compat if file.record_varying => varying_lengths(file, layout, k),
97 _ => record_lengths(layout, k),
98 }
99}
100
101pub fn varying_lengths(file: &FileDecl, layout: &Layout, k: usize) -> (u32, u32) {
104 let (shortest, longest) = record_lengths(layout, k);
105 (file.record_min.unwrap_or(shortest), file.record_max.unwrap_or(longest))
106}
107
108fn record_lengths(layout: &Layout, k: usize) -> (u32, u32) {
109 layout.record_lengths[k].unwrap_or((0, layout.file_areas[k].1))
110}
111
112const FUNCTIONS: &[&str] = rt::intrinsic::FIRST;
113
114pub const NUMERIC_FUNCTION_MOVED: &str = "an integer or numeric function as a MOVE's sender (GnuCOBOL; Enterprise COBOL takes one only where an arithmetic expression can be)";
115pub const INTO_WITHOUT_COLON: &str = "an INTO name written without its colon (Db2 13 for z/OS requires the colon before every host variable)";
116
117pub fn compile(program: Program, flags: &[String]) -> Result<Compiled, Vec<Error>> {
122 let at = compile_time().map_err(|message| vec![syntax::messages::IWO0004.at(Pos::default(), message)])?;
123 compile_at(program, flags, at)
124}
125
126pub fn compile_at(program: Program, flags: &[String], at: CompileTime) -> Result<Compiled, Vec<Error>> {
128 if program.oo.as_ref().is_some_and(|o| o.class().is_some()) {
129 return oo::compile_class_definition(program, flags, at);
130 }
131 compile_program(program, flags, true, at)
132}
133
134pub fn compile_time() -> Result<CompileTime, String> {
137 let clock = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
138 compile_time_from(std::env::var_os("SOURCE_DATE_EPOCH").as_deref(), clock)
139}
140
141fn compile_time_from(epoch: Option<&std::ffi::OsStr>, clock: std::time::Duration) -> Result<CompileTime, String> {
142 let Some(epoch) = epoch else {
143 return Ok(CompileTime { seconds: clock.as_secs() as i64, hundredths: clock.subsec_millis() / 10, source: TimeSource::Clock });
144 };
145 let text = epoch.to_string_lossy();
146 match text.parse::<i64>() {
147 Ok(seconds) if text.bytes().all(|b| b.is_ascii_digit()) && seconds <= CompileTime::LATEST => Ok(CompileTime { seconds, hundredths: 0, source: TimeSource::SourceDateEpoch }),
148 _ => Err(format!("SOURCE_DATE_EPOCH={text}: not a whole number of seconds from 0 to {}", CompileTime::LATEST)),
149 }
150}
151
152pub(crate) fn compile_program(mut program: Program, flags: &[String], whole: bool, when_compiled: CompileTime) -> Result<Compiled, Vec<Error>> {
155 let mut errors = std::mem::take(&mut program.messages);
156 reserved::check(&program, &mut errors);
157 let declared = program.working_storage.len();
158 let mut program = declaratives::with_debug_item(markup::with_special_registers(sort::with_special_registers(program)));
159 switches::declare(&mut program, &mut errors);
160 qualify_in_own_section(&mut program);
161 let mut options = Options::default();
162 let mut ssrange = false;
163 for flag in flags {
166 if let Err(e) = options.apply_flag(flag) {
167 errors.push(syntax::messages::IWO0005.at(Pos::default(), e.to_string()));
168 }
169 }
170 for option in &program.options {
171 if let Some(on) = numeric::options::switch(option, "SSRANGE") {
172 ssrange = on;
173 }
174 if let Err(e) = options.apply(option) {
175 errors.push(syntax::messages::IWO0001.at(Pos::default(), format!("CBL {option}: {e}")).graded(option_severity(&e)));
176 }
177 }
178 let page = options.code_page();
179 if let Err((message, m)) = syntax::parser::decode_currency(&mut program.environment, |bytes| page.decode(bytes)) {
180 errors.push(message.at(Pos::default(), m));
181 }
182 default_currency(&mut program, &mut options, &mut errors);
183 national_symbols(&mut program, &mut options, &mut errors);
184 if options.intdate == numeric::IntDate::Lilian {
185 program.paragraphs.iter_mut().for_each(|p| ceecbldy_to_ceedays(&mut p.statements, &mut errors));
186 }
187 for (name, alphabet) in &program.environment.alphabets {
188 if program.environment.collating_sequence.as_ref() != Some(name)
189 && let Err((message, m)) = collating::Sequence::of(alphabet, options.code_page(), options.quote)
190 {
191 errors.push(message.at(Pos::default(), format!("ALPHABET {name}: {m}")));
192 }
193 }
194 let collating = collating::Sequence::program(&program.environment, options.code_page(), options.quote).unwrap_or_else(|(message, m)| {
195 errors.push(message.at(Pos::default(), m));
196 let mut native = collating::Sequence::native();
197 native.quote = options.quote;
198 native
199 });
200 digit_limits(&program, options.arith, &mut errors);
201 let drafts = report::prepare(&mut program, options.adv, options.qualify, &mut errors);
202 let linage_counters = linage::add_counters(&mut program, options.qualify);
203 if whole {
204 oo::option_rules(&program, &options, &mut errors);
205 }
206 options.initial &= !options.thread;
207 if whole && program.oo.as_deref().and_then(Oo::method).is_none() {
208 program.initial |= options.initial;
209 }
210 scope::rules(&program, &mut errors);
211 let inherited = scope::inherit(&mut program);
212 let own_linkage = scope::own_linkage(&program);
213 let linkage: Vec<DataEntry> = program.linkage.iter().chain(&inherited.entries).cloned().collect();
214 let files: Vec<(&[DataEntry], Option<u32>)> = program.files.iter().map(|f| (f.records.as_slice(), f.record_max)).collect();
215 let shared = layout::record_area_owners(&program.files, &program.environment).unwrap_or_else(|e| {
216 errors.push(e);
217 (0..files.len()).collect()
218 });
219 let mut layout = match layout::build(&program.working_storage, &files, &shared, &linkage, &program.local_storage, crate::picture::Notation::of(&program.environment), options.qualify, options.parmcheck.map(|p| (declared, p.bytes.into()))) {
220 Ok(l) => l,
221 Err(e) => {
222 errors.push(e);
223 return Err(errors.into_iter().map(|e| e.in_files(&program.sources)).collect());
224 }
225 };
226 scope::bind(&mut layout, own_linkage, inherited, &program.files);
227 layout.classes = classes::sets(&program.environment.classes, page, &mut errors);
228 let counter_item = |entry: usize| program.working_storage[..entry].iter().filter(|e| e.level != 88).count();
229 layout.name_files(&program.files, linage_counters.iter().map(|c| c.map(counter_item)).collect());
230 assign_items(&mut program, &layout, &options, &mut errors);
231 corresponding::expand(&mut program, &layout, &mut errors);
232 condition_subjects(&mut program, &layout);
233 dbcs_values(&layout, &mut errors);
234 numeric_values(&layout, &mut errors);
235 for item in &layout.items {
236 if let Some(object) = &item.depending_on {
237 match layout.resolve(&object.name, &object.qualifiers, object.pos) {
238 Ok(layout::Resolved::Item(i)) if !layout.items[i].moved_by.is_empty() => errors.push(syntax::messages::IWC0048.at(
239 object.pos,
240 format!("OCCURS DEPENDING ON {}: the object cannot follow an OCCURS DEPENDING ON table in its record", object.name),
241 )),
242 Ok(layout::Resolved::Item(i)) if layout.items[i].kind.is_numeric() => {}
243 Ok(_) => errors.push(syntax::messages::IWC0049.at(object.pos, format!("OCCURS DEPENDING ON {}: not a numeric data item", object.name))),
244 Err(e) => errors.push(e),
245 }
246 }
247 }
248 let is_record = |name: &str| layout.linkage_roots.iter().enumerate().any(|(o, &i)| layout.is_argument(o) && layout.items[i].name.as_deref() == Some(name));
249 for param in &program.using {
250 if !is_record(¶m.name) {
251 errors.push(syntax::messages::IWC0050.at(Pos::default(), format!("PROCEDURE DIVISION USING {}: not an 01 or 77 item of the LINKAGE SECTION", param.name)));
252 }
253 }
254 if let Some(name) = &program.returning
256 && program.function.is_none()
257 && program.oo.as_deref().and_then(Oo::method).is_none()
258 && !is_record(name)
259 {
260 errors.push(syntax::messages::IWC0051.at(Pos::default(), format!("PROCEDURE DIVISION RETURNING {name}: not an 01 or 77 item of the LINKAGE SECTION")));
261 }
262 let report_writer = report::resolve(&program, &layout, drafts, &mut errors);
263 let carriage = printer::carriages(&program, &layout, options.adv);
264 let declaratives = declaratives::resolve(&program, &layout, &options, &mut errors);
265 let debugging = declaratives::debugging_sections(&program);
266 let functions = function::functions(&program, options.qualify, &mut errors);
267 let alphabetic: Vec<Pos> = [&program.working_storage, &program.local_storage, &program.linkage]
268 .into_iter()
269 .flatten()
270 .chain(program.files.iter().flat_map(|f| &f.records))
271 .filter(|e| e.picture.as_deref().is_some_and(is_alphabetic))
272 .map(|e| e.pos)
273 .collect();
274 let mut check = Check {
275 layout: &layout,
276 program: &program,
277 errors: &mut errors,
278 debugging: false,
279 max_digits: options.arith.max_picture_digits(),
280 inline_performs: 0,
281 functions: Some(&functions),
282 alphabetic: &alphabetic,
283 at: Pos::default(),
284 paragraph: 0,
285 extended: options.compliance == numeric::Compliance::Extended,
286 };
287 for k in 0..program.files.len() {
288 check.file_keys(k);
289 check.record_depending(k);
290 check.passwords(k);
291 linage::check_file(check.program, check.layout, k, check.errors);
292 }
293 for block in &program.exec_declarations {
294 check.exec_block(block);
295 }
296 for (i, p) in program.paragraphs.iter().enumerate() {
297 check.debugging = debugging.iter().any(|&(first, last)| (first..=last).contains(&i));
298 check.paragraph = i;
299 check.statements(&p.statements);
300 }
301 let entries = entry_points(&program);
302 procedure_rules(&program, &layout, &entries, &options, &mut errors);
303 if whole && !program.oo.as_deref().is_some_and(|o| o.method().is_some()) && !program.is_prototype() {
304 program_end(&program, &options, &mut errors);
305 }
306 oo::check(&layout, &program, &mut errors);
307 if let Some(mode) = options.initcheck {
308 errors.extend(initcheck::check(&program, &layout, declared, mode));
309 }
310 scope::check(&program, &layout, &mut errors);
311 layout.numcheck = numcheck::facts(&program, &layout, declared, &options, &collating, &mut errors);
312 let errors: Vec<Error> = errors.into_iter().map(|e| e.in_files(&program.sources)).collect();
313 if refused(&errors, &options) {
314 Err(errors)
315 } else {
316 Ok(Compiled { program, when_compiled, layout, options, ssrange, report_writer, collating, carriage, diagnostics: errors, entries, declaratives, functions })
317 }
318}
319
320fn assign_items(program: &mut Program, layout: &layout::Layout, options: &Options, errors: &mut Vec<Error>) {
326 let extended = options.compliance == numeric::Compliance::Extended;
327 let mut all = Vec::new();
328 program.paragraphs.iter().for_each(|p| inner_statements(&p.statements, &mut all));
329 let sorted: Vec<&str> = all
330 .iter()
331 .filter_map(|s| match s {
332 Stmt::Sorting(sorting) => match sorting.as_ref() {
333 Sorting::Sort(s) => Some(s),
334 _ => None,
335 },
336 _ => None,
337 })
338 .flat_map(|s| [&s.input, &s.output])
339 .filter_map(|io| if let Some(SortIo::Files(names)) = io { Some(names) } else { None })
340 .flatten()
341 .map(String::as_str)
342 .collect();
343 let mut kept = Vec::new();
344 for (k, f) in program.files.iter().enumerate() {
345 let Some(a) = &f.assign_item else { continue };
346 let (name, pos) = (&a.reference.name, a.reference.pos);
347 if !extended {
348 if a.explicit {
349 errors.push(syntax::messages::IWC0052.at(pos, format!("ASSIGN USING or DYNAMIC {name}: a Micro Focus and GnuCOBOL form; --compliance extended reads it")));
350 }
351 continue;
352 }
353 let item = match layout.resolve(name, &a.reference.qualifiers, pos) {
354 Ok(layout::Resolved::Item(i)) => i,
355 Ok(layout::Resolved::Condition(_)) | Err(_) if a.explicit => {
356 errors.push(syntax::messages::IWC0053.at(pos, format!("ASSIGN {name}: not a data item")));
357 continue;
358 }
359 _ => continue,
360 };
361 if !matches!(layout.items[item].kind, rt::storage::Kind::Group | rt::storage::Kind::Alnum { .. }) {
362 errors.push(syntax::messages::IWC0054.at(pos, format!("ASSIGN {name}: the item holding the file's name must be alphanumeric or a group")));
363 } else if f.sort || sorted.contains(&f.name.as_str()) {
364 errors.push(syntax::messages::IWR0022.at(pos, format!("ASSIGN {name}: a file SORT or MERGE reads, writes or describes taking its name from a data item is not supported yet")));
365 } else {
366 errors.push(syntax::messages::IWX0007.at(pos, format!("{}: each OPEN of {} takes its DD name from {name}", syntax::extended::ASSIGN_ITEM, f.name)));
367 kept.push(k);
368 }
369 }
370 for (k, f) in program.files.iter_mut().enumerate() {
371 if !kept.contains(&k) {
372 f.assign_item = None;
373 }
374 }
375}
376
377fn option_severity(e: &numeric::options::OptionError) -> Severity {
383 use numeric::options::OptionError;
384 match e {
385 OptionError::BadSuboption { .. } => Severity::Error,
386 OptionError::Removed { .. } | OptionError::NoEffect { warning: true, .. } => Severity::Warning,
387 OptionError::NoEffect { warning: false, .. } => Severity::Informational,
388 OptionError::UnsupportedCodePage(_) | OptionError::UnknownFlag(_) => Severity::Severe,
389 }
390}
391
392fn default_currency(program: &mut Program, options: &mut Options, errors: &mut Vec<Error>) {
398 match options.currency_symbol() {
399 Some(Ok(symbol)) if program.environment.currency.is_empty() => program.environment.currency.push(CurrencySign { value: symbol.to_string(), symbol, hex: None }),
400 Some(Err(c)) => {
401 errors.push(syntax::messages::IWO0002.at(Pos::default(), format!("CBL CURRENCY: code page {} reads its byte as {c:?}, which cannot be a currency symbol", options.codepage)).graded(Severity::Error));
402 options.currency = None;
403 }
404 _ => {}
405 }
406}
407
408fn national_symbols(program: &mut Program, options: &mut Options, errors: &mut Vec<Error>) {
413 if options.nsymbol == numeric::Nsymbol::National {
414 if !options.dbcs && program.options.iter().any(|o| numeric::options::switch(o, "NSYMBOL").is_some()) {
415 errors.push(syntax::messages::IWO0003.at(Pos::default(), "CBL NODBCS: NSYMBOL(NATIONAL) requires DBCS, which is in effect"));
416 options.dbcs = true;
417 }
418 return;
419 }
420 let only_n = |p: &str| p.chars().any(|c| c.eq_ignore_ascii_case(&'N')) && p.chars().all(|c| c.eq_ignore_ascii_case(&'N') || c.eq_ignore_ascii_case(&'B') || c.is_ascii_digit() || matches!(c, '(' | ')'));
421 let lists = [&mut program.working_storage, &mut program.local_storage, &mut program.linkage].into_iter().chain(program.files.iter_mut().map(|f| &mut f.records));
422 for entries in lists {
423 let mut groups: Vec<(u8, bool)> = Vec::new();
424 for e in entries.iter_mut().filter(|e| !matches!(e.level, 66 | 88)) {
425 let level = if e.level == 77 { 1 } else { e.level };
426 while groups.last().is_some_and(|&(l, _)| l >= level) {
427 groups.pop();
428 }
429 let usage = e.usage.is_some() || groups.iter().any(|&(_, u)| u);
430 groups.push((level, e.usage.is_some()));
431 if !usage && e.picture.as_deref().is_some_and(only_n) {
432 e.usage = Some(Usage::Dbcs);
433 }
434 }
435 }
436}
437
438fn program_end(program: &Program, options: &Options, errors: &mut Vec<Error>) {
442 use numeric::CicsReturnWarning;
443 let mut all = Vec::new();
444 program.paragraphs.iter().for_each(|p| inner_statements(&p.statements, &mut all));
445 if all.iter().any(|s| matches!(s, Stmt::StopRun { .. } | Stmt::Goback { .. } | Stmt::ExitProgram { .. })) {
446 return;
447 }
448 let cics_end = all.iter().find_map(|s| match s {
449 Stmt::Exec(b) if b.kind == ExecKind::Cics && matches!(b.command.as_str(), "RETURN" | "XCTL") => Some(b.command.as_str()),
450 _ => None,
451 });
452 match (cics_end, options.cics_return_warning) {
453 (Some(_), CicsReturnWarning::Never) => {}
454 (Some(command), CicsReturnWarning::Once) => errors.push(syntax::messages::IWP0002.at(Pos::default(), format!(
455 "IGYPS2091-W not given: the program ends with EXEC CICS {command}, which the CICS translator turns into a CALL; --cics-return-warning=always gives the warning, =never drops this note"
456 )).graded(Severity::Informational)),
457 (None, _) | (Some(_), CicsReturnWarning::Always) => {
458 errors.push(syntax::messages::IWC0055.at(Pos::default(), "no STOP RUN, GOBACK or EXIT PROGRAM in the program: check that it ends"));
459 }
460 }
461}
462
463fn ceecbldy_to_ceedays(stmts: &mut [Stmt], errors: &mut Vec<Error>) {
468 for s in stmts {
469 if let Stmt::Call(c) = s
470 && let Operand::Literal(Literal::Alnum(name)) = &mut c.target
471 && name.trim().eq_ignore_ascii_case("CEECBLDY")
472 {
473 *name = "CEEDAYS".into();
474 errors.push(syntax::messages::IWC0056.at(c.pos, "CALL 'CEECBLDY' under INTDATE(LILIAN): CEECBLDY gives an ANSI integer date, which nothing can use under LILIAN, so the CALL is to CEEDAYS"));
475 }
476 for body in oo::bodies_mut(s) {
477 ceecbldy_to_ceedays(body, errors);
478 }
479 }
480}
481
482pub fn refused(messages: &[Error], options: &Options) -> bool {
486 let stops_at = options.object_code().stops_at().min(12);
487 stops_at == 0 || messages.iter().any(|m| m.severity.return_code() >= stops_at)
488}
489
490fn procedure_rules(program: &Program, layout: &Layout, entries: &[EntryPoint], options: &Options, errors: &mut Vec<Error>) {
493 let method = program.oo.as_ref().is_some_and(|o| matches!(o.unit, OoUnit::Method(_)));
494 for (k, e) in entries.iter().enumerate() {
495 if program.returning.is_some() {
496 errors.push(syntax::messages::IWC0057.at(e.pos, format!("ENTRY '{}': a program with PROCEDURE DIVISION RETURNING cannot have ENTRY statements", e.name)));
497 }
498 if e.name.eq_ignore_ascii_case(&program.id) || entries[..k].iter().any(|f| f.name == e.name) {
499 errors.push(syntax::messages::IWC0058.at(e.pos, format!("ENTRY '{}': the name is already the program's or another ENTRY's", e.name)));
500 }
501 for param in &e.using {
502 if !layout.linkage_roots.iter().enumerate().any(|(o, &i)| layout.is_argument(o) && layout.items[i].name.as_deref() == Some(param.name.as_str())) {
503 errors.push(syntax::messages::IWC0059.at(e.pos, format!("ENTRY '{}' USING {}: not an 01 or 77 item of the LINKAGE SECTION", e.name, param.name)));
504 }
505 }
506 }
507 let why_no_alter = if program.recursive {
508 Some("a RECURSIVE program")
509 } else if options.thread {
510 Some("a program compiled with THREAD")
511 } else {
512 method.then_some("a method")
513 };
514 for p in &program.paragraphs {
515 for s in &p.statements {
516 let mut inner = Vec::new();
517 oo::bodies(s).into_iter().for_each(|body| inner_statements(body, &mut inner));
518 for (t, nested) in std::iter::once((s, false)).chain(inner.into_iter().map(|t| (t, true))) {
519 match t {
520 Stmt::Entry { name, pos, .. } if nested => {
521 errors.push(syntax::messages::IWC0060.at(*pos, format!("ENTRY '{name}' must be a sentence of its own, not inside another statement")));
522 }
523 Stmt::GoTo { target: None, pos } => {
524 if let Some(why) = why_no_alter {
525 errors.push(syntax::messages::IWC0061.at(*pos, format!("a GO TO with no procedure-name cannot be used in {why}")));
526 }
527 if nested || !lone_go_to(p) {
528 errors.push(syntax::messages::IWC0062.at(*pos, "a GO TO with no procedure-name must be its paragraph's only sentence"));
529 }
530 }
531 Stmt::Alter { pairs, pos } => {
532 if let Some(why) = why_no_alter {
533 errors.push(syntax::messages::IWC0063.at(*pos, format!("ALTER cannot be used in {why}")));
534 }
535 for (from, to) in pairs {
536 altered_paragraph(program, from, *pos, errors);
537 if let Err(m) = procedure(program, to) {
538 errors.push(m.at(*pos));
539 }
540 }
541 }
542 _ => {}
543 }
544 }
545 }
546 }
547}
548
549fn inner_statements<'s>(stmts: &'s [Stmt], out: &mut Vec<&'s Stmt>) {
551 for s in stmts {
552 out.push(s);
553 oo::bodies(s).into_iter().for_each(|body| inner_statements(body, out));
554 }
555}
556
557fn altered_paragraph(program: &Program, name: &ProcName, pos: Pos, errors: &mut Vec<Error>) {
559 match procedure(program, name) {
560 Err(m) => errors.push(m.at(pos)),
561 Ok((i, _)) if program.paragraphs[i].is_section => errors.push(syntax::messages::IWC0064.at(pos, format!("ALTER {}: a section, where ALTER names a paragraph", name.name))),
562 Ok((i, _)) if !lone_go_to(&program.paragraphs[i]) => {
563 errors.push(syntax::messages::IWC0065.at(pos, format!("ALTER {}: the paragraph must hold one sentence, a GO TO without DEPENDING ON", name.name)));
564 }
565 Ok(_) => {}
566 }
567}
568
569fn lone_go_to(p: &Paragraph) -> bool {
570 matches!(p.statements.as_slice(), [Stmt::GoTo { .. }] | [Stmt::GoTo { .. }, Stmt::SentenceEnd])
571}
572
573pub fn section_end(program: &Program, i: usize) -> usize {
576 let paragraphs = &program.paragraphs;
577 let declaratives = program.report_writer.procedure_start;
578 let (floor, ceiling) = if i < declaratives { (0, declaratives) } else { (declaratives, paragraphs.len()) };
579 let Some(header) = (floor..=i).rev().find(|&j| paragraphs[j].is_section) else { return i };
580 (header + 1..ceiling).take_while(|&j| !paragraphs[j].is_section).last().unwrap_or(header)
581}
582
583fn qualify_in_own_section(program: &mut Program) {
587 for i in 0..program.paragraphs.len() {
588 let Some(section) = program.paragraphs[i].section.clone() else { continue };
589 let mut statements = std::mem::take(&mut program.paragraphs[i].statements);
590 oo::each_mut(&mut statements, &mut |s| {
591 for name in procedure_names_mut(s) {
592 if name.section.is_none() && names_own_paragraph(&program.paragraphs, &name.name, §ion) {
593 name.section = Some(section.clone());
594 }
595 }
596 });
597 program.paragraphs[i].statements = statements;
598 }
599}
600
601fn names_own_paragraph(paragraphs: &[Paragraph], name: &str, section: &str) -> bool {
604 paragraphs.iter().filter(|p| p.name == name).count() > 1 && paragraphs.iter().any(|p| p.name == name && !p.is_section && p.section.as_deref() == Some(section))
605}
606
607pub fn procedure_from(program: &Program, p: &ProcName, from: usize) -> Result<(usize, usize), Unnamed> {
611 match program.paragraphs.get(from).and_then(|q| q.section.as_deref()) {
612 Some(section) if p.section.is_none() && names_own_paragraph(&program.paragraphs, &p.name, section) => {
613 procedure(program, &ProcName { name: p.name.clone(), section: Some(section.to_owned()) })
614 }
615 _ => procedure(program, p),
616 }
617}
618
619fn dbcs_values(layout: &Layout, errors: &mut Vec<Error>) {
625 let dbcs = |i: usize| matches!(layout.items[i].kind, rt::storage::Kind::Dbcs { .. });
626 let is_dbcs = |l: &Literal| matches!(l, Literal::Dbcs(_)) || matches!(l, Literal::All(inner) if matches!(**inner, Literal::Dbcs(_)));
627 let fits = |l: &Literal, i: usize| match l {
628 Literal::Dbcs(s) => s.chars().count() <= layout.items[i].size as usize / 2,
629 Literal::Figurative(syntax::ast::Figurative::Space) => true,
630 l => is_dbcs(l),
631 };
632 let values = layout.items.iter().enumerate().filter_map(|(i, it)| it.value.as_ref().map(|v| (i, v, it.pos)));
633 let conditions = layout.conditions.iter().flat_map(|c| c.values.iter().flat_map(|(low, high)| std::iter::once(low).chain(high)).chain(&c.false_value).map(move |v| (c.item, v, layout.items[c.item].pos)));
634 for (i, value, pos) in values.chain(conditions) {
635 let name = layout.items[i].name.as_deref().unwrap_or("FILLER");
636 if dbcs(i) && !fits(value, i) {
637 errors.push(syntax::messages::IWC0066.at(pos, format!("{name}: a DBCS item's VALUE is a DBCS literal of at most {} characters, SPACE or ALL with a DBCS literal", layout.items[i].size / 2)));
638 } else if !dbcs(i) && is_dbcs(value) {
639 errors.push(syntax::messages::IWC0067.at(pos, format!("{name}: a DBCS literal can be the VALUE of a DBCS item only")));
640 }
641 }
642}
643
644const DISPLAY_DEVICES: &[&str] = &["SYSOUT", "SYSLIST", "SYSLST", "SYSPUNCH", "SYSPCH", "CONSOLE"];
647
648fn numeric_values(layout: &Layout, errors: &mut Vec<Error>) {
651 for item in layout.items.iter().filter(|i| i.children.is_empty() && i.kind.is_numeric()) {
652 let what = match item.value {
653 Some(Literal::Alnum(_) | Literal::Hex(_)) => "an alphanumeric literal",
654 Some(Literal::National(_)) => "a national literal",
655 _ => continue,
656 };
657 let name = item.name.as_deref().unwrap_or("FILLER");
658 errors.push(syntax::messages::IWC0068.at(item.pos, format!("VALUE of {name}: {what}, where a numeric item's VALUE literal must be numeric")));
659 }
660}
661
662fn condition_subjects(program: &mut Program, layout: &Layout) {
663 let is_condition = |r: &Ref| matches!(layout.resolve(&r.name, &r.qualifiers, r.pos), Ok(layout::Resolved::Condition(_)));
664 for p in &mut program.paragraphs {
665 oo::each_mut(&mut p.statements, &mut |s| {
666 let Stmt::Evaluate { subjects, whens, .. } = s else { return };
667 for (k, subject) in subjects.iter_mut().enumerate() {
668 let Subject::Expr(Expr::Operand(Operand::Ref(r))) = subject else { continue };
669 if !is_condition(r) {
670 continue;
671 }
672 *subject = Subject::Cond(Cond::Name(r.clone()));
673 for alternative in whens.iter_mut().flat_map(|w| w.alternatives.iter_mut()) {
674 let cond = match alternative.get(k) {
675 Some(Object::Value { not, from: Expr::Operand(Operand::Ref(o)), thru: None }) => {
676 let c = Cond::Name(o.clone());
677 if *not { Cond::Not(Box::new(c)) } else { c }
678 }
679 _ => continue,
680 };
681 alternative[k] = Object::Cond(cond);
682 }
683 }
684 });
685 }
686}
687
688fn procedure_names_mut(s: &mut Stmt) -> Vec<&mut ProcName> {
690 match s {
691 Stmt::PerformProc { from, thru, .. } => std::iter::once(from).chain(thru.as_mut()).collect(),
692 Stmt::GoTo { target, .. } => target.iter_mut().collect(),
693 Stmt::GoToDepending { targets, .. } => targets.iter_mut().collect(),
694 Stmt::Alter { pairs, .. } => pairs.iter_mut().flat_map(|(from, to)| [from, to]).collect(),
695 Stmt::XmlParse(x) => std::iter::once(&mut x.procedure).chain(x.thru.as_mut()).collect(),
696 Stmt::Sorting(so) => match &mut **so {
697 Sorting::Sort(st) => [st.input.as_mut(), st.output.as_mut()]
698 .into_iter()
699 .flatten()
700 .flat_map(|io| match io {
701 SortIo::Procedure { from, thru } => std::iter::once(from).chain(thru.as_mut()).collect(),
702 SortIo::Files(_) => Vec::new(),
703 })
704 .collect(),
705 _ => Vec::new(),
706 },
707 _ => Vec::new(),
708 }
709}
710
711#[derive(Clone, Debug, PartialEq, Eq)]
713pub struct Unnamed {
714 pub message: Message,
715 pub text: String,
716}
717
718impl Unnamed {
719 pub fn at(self, pos: Pos) -> Error {
720 self.message.at(pos, self.text)
721 }
722}
723
724impl std::fmt::Display for Unnamed {
725 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
726 f.write_str(&self.text)
727 }
728}
729
730impl From<Unnamed> for String {
731 fn from(u: Unnamed) -> Self {
732 u.text
733 }
734}
735
736pub fn procedure(program: &Program, p: &ProcName) -> Result<(usize, usize), Unnamed> {
738 let found: Vec<usize> = program
739 .paragraphs
740 .iter()
741 .enumerate()
742 .filter(|(_, q)| q.name == p.name && (p.section.is_none() || (q.section == p.section && !q.is_section)))
743 .map(|(i, _)| i)
744 .collect();
745 match found.as_slice() {
746 [i] if program.paragraphs[*i].is_section => Ok((*i, section_end(program, *i))),
747 [i] => Ok((*i, *i)),
748 [] => Err(Unnamed { message: IWC0003, text: format!("no paragraph or section named {}", p.name) }),
749 _ => Err(Unnamed { message: IWC0004, text: format!("{} names more than one paragraph; qualify it with OF and its section", p.name) }),
750 }
751}
752
753fn digit_limits(program: &Program, arith: numeric::options::Arith, errors: &mut Vec<Error>) {
757 let max = arith.max_picture_digits();
758 let option = match arith {
759 numeric::options::Arith::Compat => "ARITH(COMPAT)",
760 numeric::options::Arith::Extend => "ARITH(EXTEND)",
761 };
762 let entries = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(program.files.iter().flat_map(|f| &f.records));
763 for e in entries {
764 if let Some(p) = e.picture.as_deref()
765 && let Ok(pic) = picture::analyse_with(p, crate::picture::Notation::of(&program.environment))
766 && matches!(pic.category, picture::Category::Numeric | picture::Category::NumericEdited)
767 {
768 let positions = pic.digits + pic.scaling + pic.scale.saturating_sub(pic.digits);
769 if positions > max {
770 errors.push(syntax::messages::IWC0069.at(e.pos, format!("PICTURE {p}: {positions} digit positions, more than the {max} {option} allows")));
771 }
772 }
773 let values = e.value.iter().chain(e.condition_values.iter().flat_map(|(low, high)| std::iter::once(low).chain(high))).chain(&e.false_value);
774 for v in values {
775 if let Literal::Number(t) = v
776 && literal_digits(t) > max as usize
777 {
778 errors.push(syntax::messages::IWC0070.at(e.pos, format!("the literal {t} has more than the {max} digits {option} allows")));
779 }
780 }
781 }
782}
783
784fn literal_digits(t: &str) -> usize {
785 t.chars().filter(char::is_ascii_digit).count()
786}
787
788#[derive(Clone, Copy, PartialEq, Eq)]
790enum CharUsage {
791 Display,
792 National,
793 Dbcs,
794}
795
796impl CharUsage {
797 fn of(kind: rt::storage::Kind) -> Self {
798 match kind {
799 rt::storage::Kind::National => Self::National,
800 rt::storage::Kind::Dbcs { .. } => Self::Dbcs,
801 _ => Self::Display,
802 }
803 }
804}
805
806impl std::fmt::Display for CharUsage {
807 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
808 f.write_str(match self {
809 Self::Display => "DISPLAY",
810 Self::National => "national",
811 Self::Dbcs => "DBCS",
812 })
813 }
814}
815
816fn inspected_literal(l: &Literal) -> Option<(&'static str, CharUsage)> {
819 match l {
820 Literal::Figurative(_) => None,
821 Literal::All(inner) => inspected_literal(inner),
822 Literal::National(_) => Some(("a national literal", CharUsage::National)),
823 Literal::Dbcs(_) => Some(("a DBCS literal", CharUsage::Dbcs)),
824 Literal::Number(_) => Some(("a numeric literal", CharUsage::Display)),
825 Literal::Alnum(_) | Literal::Hex(_) => Some(("an alphanumeric literal", CharUsage::Display)),
826 }
827}
828
829fn flag_conditions(flag: &Flag) -> Vec<&Ref> {
832 match flag {
833 Flag::Condition(c) => vec![c],
834 Flag::Conditions(on, off) => vec![on, off],
835 Flag::Literals(..) => Vec::new(),
836 }
837}
838
839struct Check<'a> {
840 layout: &'a Layout,
841 program: &'a Program,
842 errors: &'a mut Vec<Error>,
843 debugging: bool,
845 max_digits: u32,
847 inline_performs: usize,
849 functions: Option<&'a [function::Udf]>,
851 alphabetic: &'a [Pos],
853 at: Pos,
855 paragraph: usize,
857 extended: bool,
859}
860
861impl Check<'_> {
862 fn statements(&mut self, stmts: &[Stmt]) {
863 for s in stmts {
864 self.statement(s);
865 }
866 }
867
868 fn statement(&mut self, s: &Stmt) {
869 linage::check_receivers(self.layout, s, self.errors);
870 if let Stmt::If { pos, .. } | Stmt::Evaluate { pos, .. } | Stmt::PerformInline { pos, .. } | Stmt::PerformProc { pos, .. } = s {
871 self.at = *pos;
872 }
873 if let Stmt::Search(se) = s {
874 self.at = se.pos;
875 }
876 match s {
877 Stmt::Move { from, to, .. } => {
878 self.operand(from);
879 if let Operand::Function(f) = from {
880 self.moved_function(f);
881 }
882 to.iter().for_each(|r| self.reference(r));
883 }
884 Stmt::Compute { targets, expr, size_error, .. } => {
885 targets.iter().for_each(|t| self.reference(&t.r));
886 self.expr(expr);
887 self.size_error(size_error.as_ref());
888 }
889 Stmt::Arith(a) => {
890 for (t, e) in &a.computations {
891 self.reference(&t.r);
892 self.expr(e);
893 }
894 if let Some((t, x, y)) = &a.remainder {
895 self.reference(&t.r);
896 self.expr(x);
897 self.expr(y);
898 }
899 self.size_error(a.size_error.as_ref());
900 }
901 Stmt::If { cond, then, otherwise, .. } => {
902 self.cond(cond);
903 self.statements(then);
904 self.statements(otherwise);
905 }
906 Stmt::PerformInline { body, repeat, .. } => {
907 self.repeat(repeat);
908 self.inline_performs += 1;
909 self.statements(body);
910 self.inline_performs -= 1;
911 }
912 Stmt::PerformProc { from, thru, repeat, pos } => {
913 self.procedure(from, *pos);
914 if let Some(t) = thru {
915 self.procedure(t, *pos);
916 }
917 self.repeat(repeat);
918 }
919 Stmt::Evaluate { subjects, whens, other, pos } => {
920 for subject in subjects {
921 match subject {
922 Subject::Expr(e) => self.expr(e),
923 Subject::Cond(c) => self.cond(c),
924 Subject::Bool(_) => {}
925 }
926 }
927 for w in whens {
928 for alternative in &w.alternatives {
929 for (subject, object) in subjects.iter().zip(alternative) {
930 match object {
931 Object::Any => {}
932 Object::Bool(_) | Object::Cond(_) if matches!(subject, Subject::Expr(_)) => {
933 self.errors.push(syntax::messages::IWC0071.at(*pos, "a condition as the WHEN object of a value subject"));
934 }
935 Object::Bool(_) => {}
936 Object::Cond(c) => self.cond(c),
937 Object::Value { .. } if !matches!(subject, Subject::Expr(_)) => {
938 self.errors.push(syntax::messages::IWC0072.at(*pos, "a value as the WHEN object of a TRUE, FALSE or condition subject"));
939 }
940 Object::Value { from, thru, .. } => {
941 self.expr(from);
942 if let Some(t) = thru {
943 self.expr(t);
944 }
945 if let Subject::Expr(e) = subject {
946 std::iter::once(from).chain(thru).for_each(|o| self.comparison(e, o));
947 }
948 }
949 }
950 }
951 }
952 self.statements(&w.body);
953 }
954 self.statements(other);
955 }
956 Stmt::Display { items, upon, pos, .. } => {
957 for o in items {
958 self.operand(o);
959 if let Operand::Function(f) = o {
960 self.displayed_function(f);
961 }
962 }
963 if let Some(upon) = upon
964 && upon.device == "ARGUMENT-NUMBER"
965 {
966 self.argument_number(items, *pos);
967 } else if let Some(upon) = upon
968 && !DISPLAY_DEVICES.contains(&upon.device.as_str())
969 {
970 let why = if upon.name == upon.device {
971 "neither an environment-name DISPLAY writes to, SYSOUT, SYSLIST, SYSLST, SYSPUNCH, SYSPCH or CONSOLE, nor a mnemonic-name for one".to_owned()
972 } else {
973 format!("a mnemonic-name for {}, which DISPLAY does not write to", upon.device)
974 };
975 self.errors.push(syntax::messages::IWC0073.at(*pos, format!("DISPLAY UPON {}: {why}", upon.name)));
976 }
977 }
978 Stmt::Open { files, pos } => files.iter().for_each(|(_, f)| self.file(f, *pos)),
979 Stmt::Close { files, pos } => {
980 for (name, closing) in files {
981 self.file(name, *pos);
982 let keyed = self.program.files.iter().any(|f| f.name == *name && matches!(f.organization, Organization::Indexed | Organization::Relative));
983 if keyed && matches!(closing, Some(Closing::Volume | Closing::NoRewind)) {
984 self.errors.push(syntax::messages::IWC0074.at(*pos, format!("CLOSE {name}: REEL, UNIT and NO REWIND are not valid for an indexed or relative file")));
985 }
986 }
987 }
988 Stmt::Read(r) => {
989 self.file(&r.file, r.pos);
990 if r.next {
991 self.not_random(&r.file, "READ NEXT", r.pos);
992 }
993 if let Some(into) = &r.into {
994 self.reference(into);
995 }
996 if let Some(key) = &r.key {
997 self.reference(key);
998 self.key_of(&r.file, key, false);
999 }
1000 self.handlers(&r.at_end);
1001 self.handlers(&r.invalid);
1002 }
1003 Stmt::Write { record, from, invalid, pos, .. } | Stmt::Rewrite { record, from, invalid, pos } => {
1004 let verb = if matches!(s, Stmt::Write { .. }) { "WRITE" } else { "REWRITE" };
1005 self.reference(record);
1006 if let Ok(layout::Resolved::Item(i)) = self.layout.resolve(&record.name, &record.qualifiers, record.pos)
1007 && self.layout.items[i].file.is_none()
1008 {
1009 self.errors.push(syntax::messages::IWC0075.at(*pos, format!("{verb} {}: not a record of a file", record.name)));
1010 }
1011 if let Some(op) = from {
1012 self.operand(op);
1013 }
1014 if let Stmt::Write { advancing, end_of_page, .. } = s {
1015 if let Some(a) = advancing {
1016 if let Advancing::Lines { count, .. } = a {
1017 self.expr(count);
1018 }
1019 printer::check_write(self.program, self.layout, record, a, *pos, self.errors);
1020 }
1021 linage::check_write(self.program, self.layout, record, advancing.as_ref(), end_of_page, *pos, self.errors);
1022 self.handlers(end_of_page);
1023 }
1024 self.handlers(invalid);
1025 }
1026 Stmt::Delete { file, invalid, pos } => {
1027 self.keyed_file(file, "DELETE", *pos);
1028 self.handlers(invalid);
1029 }
1030 Stmt::Start { file, key, invalid, pos } => {
1031 self.keyed_file(file, "START", *pos);
1032 self.not_random(file, "START", *pos);
1033 if let Some((op, r)) = key {
1034 if !matches!(op, RelOp::Eq | RelOp::Gt | RelOp::Ge) {
1035 self.errors.push(syntax::messages::IWC0076.at(*pos, "START KEY takes =, >, NOT < or >="));
1036 }
1037 self.reference(r);
1038 self.key_of(file, r, true);
1039 }
1040 self.handlers(invalid);
1041 }
1042 Stmt::Initialize { targets, with, pos } => {
1043 for r in targets {
1044 self.reference(r);
1045 if self.item(r).is_some_and(|i| self.layout.items[i].level == 66) {
1046 self.errors.push(syntax::messages::IWC0077.at(*pos, format!("INITIALIZE {}: a level-66 RENAMES item cannot be initialized", r.name)));
1047 }
1048 let items = &self.layout.items;
1049 if self.item(r).is_some_and(|i| !items[i].moved_by.is_empty() || items[i].odo.iter().any(|&t| items[t].followed)) {
1050 self.errors.push(syntax::messages::IWC0078.at(*pos, format!("INITIALIZE {}: a variably located item, or a group holding one, cannot be initialized (Language Reference p. 351)", r.name)));
1051 }
1052 }
1053 if let Some(with) = with {
1054 with.replacing.iter().for_each(|(_, by)| self.operand(by));
1055 self.initialize_incompatible(targets, with, *pos);
1056 }
1057 }
1058 Stmt::GoTo { target: Some(target), pos } => self.procedure(target, *pos),
1059 Stmt::GoToDepending { targets, on, pos } => {
1060 targets.iter().for_each(|t| self.procedure(t, *pos));
1061 self.reference(on);
1062 }
1063 Stmt::GoTo { target: None, .. } | Stmt::Alter { .. } | Stmt::Entry { .. } => {}
1064 Stmt::Call(c) => {
1065 self.operand(&c.target);
1066 for arg in &c.using {
1067 if let Some(op) = &arg.value {
1068 self.operand(op);
1069 }
1070 }
1071 if let Some(r) = &c.returning {
1072 self.reference(r);
1073 }
1074 self.statements(c.on_exception.as_deref().unwrap_or_default());
1075 self.statements(c.not_on_exception.as_deref().unwrap_or_default());
1076 }
1077 Stmt::Cancel { targets, .. } => targets.iter().for_each(|t| self.operand(t)),
1078 Stmt::Set { set, .. } => match set {
1079 SetStmt::ConditionTrue(targets) => {
1080 for r in targets {
1081 self.reference_or_condition(r);
1082 if let Ok(layout::Resolved::Condition(c)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
1083 && let Some(e) = switches::without_variable(self.layout, c, r)
1084 {
1085 self.errors.push(e);
1086 }
1087 }
1088 }
1089 SetStmt::ConditionFalse(targets) => {
1090 for r in targets {
1091 self.reference_or_condition(r);
1092 if let Ok(layout::Resolved::Condition(c)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
1093 && self.layout.conditions[c].false_value.is_none()
1094 {
1095 self.errors.push(syntax::messages::IWC0079.at(r.pos, format!("SET {} TO FALSE: the condition-name has no WHEN SET TO FALSE value", r.name)));
1096 }
1097 }
1098 }
1099 SetStmt::To { targets, value } | SetStmt::Entry { targets, entry: value } => {
1100 targets.iter().for_each(|r| self.reference(r));
1101 self.operand(value);
1102 }
1103 SetStmt::AddressOf { targets, value } => {
1104 targets.iter().for_each(|r| self.reference(r));
1105 targets.iter().for_each(|r| scope::set_address(self.layout, r, self.errors));
1106 self.operand(value);
1107 }
1108 SetStmt::UpDown { targets, by, .. } => {
1109 targets.iter().for_each(|r| self.reference(r));
1110 self.expr(by);
1111 }
1112 SetStmt::Switches(_) => {}
1113 },
1114 Stmt::Accept { target, from, exception, pos } => {
1115 self.reference(target);
1116 self.accept_source(*from, exception, target, *pos);
1117 self.statements(exception.on.as_deref().unwrap_or_default());
1118 self.statements(exception.not_on.as_deref().unwrap_or_default());
1119 }
1120 Stmt::String(st) => {
1121 for (op, delimiter) in &st.sources {
1122 self.operand(op);
1123 if let Delimiter::By(d) = delimiter {
1124 self.operand(d);
1125 }
1126 }
1127 self.reference(&st.into);
1128 if let Some(p) = &st.pointer {
1129 self.reference(p);
1130 }
1131 self.statements(st.on_overflow.as_deref().unwrap_or_default());
1132 self.statements(st.not_on_overflow.as_deref().unwrap_or_default());
1133 }
1134 Stmt::Unstring(u) => {
1135 self.reference(&u.source);
1136 u.delimiters.iter().for_each(|(_, d)| self.operand(d));
1137 for into in &u.into {
1138 self.reference(&into.target);
1139 into.delimiter_in.iter().chain(&into.count_in).for_each(|r| self.reference(r));
1140 }
1141 u.pointer.iter().chain(&u.tallying).for_each(|r| self.reference(r));
1142 self.statements(u.on_overflow.as_deref().unwrap_or_default());
1143 self.statements(u.not_on_overflow.as_deref().unwrap_or_default());
1144 }
1145 Stmt::Inspect(i) => {
1146 self.operand(&i.target);
1147 match &i.target {
1148 Operand::Function(f) => self.inspected_function(f, i),
1149 Operand::Ref(r) => self.inspected_usage(r, i),
1150 _ => {}
1151 }
1152 for p in i.tallying.iter().chain(&i.replacing) {
1153 p.pattern.iter().chain(&p.by).for_each(|o| self.operand(o));
1154 if let Some(c) = &p.counter {
1155 self.reference(c);
1156 }
1157 p.bounds.iter().for_each(|b| self.operand(&b.value));
1158 }
1159 if let Some((from, to, bounds)) = &i.converting {
1160 self.operand(from);
1161 self.operand(to);
1162 bounds.iter().for_each(|b| self.operand(&b.value));
1163 }
1164 }
1165 Stmt::Search(se) => {
1166 self.reference_unsubscripted(&se.table);
1167 if let Some(v) = &se.varying {
1168 self.reference(v);
1169 self.search_varying(se, v);
1170 }
1171 self.statements(se.at_end.as_deref().unwrap_or_default());
1172 for (cond, body) in &se.whens {
1173 self.cond(cond);
1174 self.statements(body);
1175 }
1176 }
1177 Stmt::Exec(block) => self.exec_block(block),
1178 Stmt::Report(r) => report::check_statement(self.program, r, self.errors),
1179 Stmt::Invoke(i) => self.invoke(i),
1180 Stmt::XmlParse(x) => {
1181 self.reference(&x.document);
1182 if let Some(op) = &x.encoding {
1183 self.operand(op);
1184 }
1185 self.procedure(&x.procedure, x.pos);
1186 if let Some(t) = &x.thru {
1187 self.procedure(t, x.pos);
1188 }
1189 self.statements(x.on_exception.as_deref().unwrap_or_default());
1190 self.statements(x.not_on_exception.as_deref().unwrap_or_default());
1191 }
1192 Stmt::JsonParse(j) => {
1193 self.reference(&j.source);
1194 self.whole_table_reference(&j.into);
1195 let mut named: Vec<&Ref> = j.names.iter().map(|(r, _)| r).chain(&j.suppress).chain(j.ignoring.iter().flatten()).collect();
1196 for (item, flag, indicator) in &j.indicating {
1197 named.push(item);
1198 named.extend(indicator);
1199 named.extend(flag_conditions(flag));
1200 }
1201 for (item, conversion) in &j.converting {
1202 named.push(item);
1203 if let ParseConversion::Boolean(flag) = conversion {
1204 named.extend(flag_conditions(flag));
1205 }
1206 }
1207 named.into_iter().for_each(|r| self.reference_unsubscripted(r));
1208 if let Some(Encoding::Ccsid(op)) = &j.encoding {
1209 self.operand(op);
1210 }
1211 self.statements(j.on_exception.as_deref().unwrap_or_default());
1212 self.statements(j.not_on_exception.as_deref().unwrap_or_default());
1213 }
1214 Stmt::XmlGenerate(x) => {
1215 for r in [&x.receiver, &x.from].into_iter().chain(&x.count) {
1216 self.reference(r);
1217 }
1218 let named = x.names.iter().map(|(r, _)| r).chain(x.types.iter().map(|(r, _)| r));
1219 let suppressed = x.suppress.iter().filter_map(|s| if let Suppression::Item { item, .. } = s { Some(item) } else { None });
1220 named.chain(suppressed).for_each(|r| self.reference_unsubscripted(r));
1221 for op in [&x.encoding, &x.namespace, &x.prefix].into_iter().flatten() {
1222 self.operand(op);
1223 }
1224 self.statements(x.on_exception.as_deref().unwrap_or_default());
1225 self.statements(x.not_on_exception.as_deref().unwrap_or_default());
1226 }
1227 Stmt::JsonGenerate(g) => {
1228 for r in [&g.receiver].into_iter().chain(&g.count) {
1229 self.reference(r);
1230 }
1231 self.whole_table_reference(&g.from);
1232 let mut named: Vec<&Ref> = g.names.iter().map(|(r, _)| r).collect();
1233 for s in &g.suppress {
1234 if let Suppression::Item { item, .. } = s {
1235 named.push(item);
1236 }
1237 }
1238 for (item, conversion) in &g.converting {
1239 named.push(item);
1240 if let JsonConversion::Boolean(Marker::Condition(c)) = conversion {
1241 named.push(c);
1242 }
1243 }
1244 for i in &g.indicating {
1245 named.push(&i.item);
1246 named.extend(&i.indicator);
1247 if let Marker::Condition(c) = &i.marker {
1248 named.push(c);
1249 }
1250 }
1251 named.into_iter().for_each(|r| self.reference_unsubscripted(r));
1252 if let Some(Encoding::Ccsid(op)) = &g.encoding {
1253 self.operand(op);
1254 }
1255 self.statements(g.on_exception.as_deref().unwrap_or_default());
1256 self.statements(g.not_on_exception.as_deref().unwrap_or_default());
1257 }
1258 Stmt::Sorting(s) => self.sorting(s),
1259 Stmt::Exit { kind: kind @ (ExitKind::Perform | ExitKind::PerformCycle), pos } if self.inline_performs == 0 => {
1261 let exit = if *kind == ExitKind::Perform { "EXIT PERFORM" } else { "EXIT PERFORM CYCLE" };
1262 self.errors.push(syntax::messages::IWC0080.at(*pos, format!("{exit} must be inside an inline PERFORM")));
1263 }
1264 Stmt::Goback { .. } | Stmt::StopRun { .. } | Stmt::ExitProgram { .. } | Stmt::ExitMethod { .. } | Stmt::Continue | Stmt::Exit { .. } | Stmt::NextSentence | Stmt::SentenceEnd => {}
1265 Stmt::Corresponding(_) => unreachable!("CORRESPONDING is expanded before Check"),
1266 }
1267 }
1268
1269 fn size_error(&mut self, se: Option<&SizeError>) {
1270 if let Some(se) = se {
1271 self.statements(&se.on);
1272 self.statements(&se.not_on);
1273 }
1274 }
1275
1276 fn repeat(&mut self, repeat: &Loop) {
1277 match repeat {
1278 Loop::Once => {}
1279 Loop::Times(e) => self.expr(e),
1280 Loop::Until { cond, .. } => self.cond(cond),
1281 Loop::Varying { varying, after, .. } => {
1282 for v in std::iter::once(&**varying).chain(after) {
1283 self.reference(&v.var);
1284 self.expr(&v.from);
1285 self.expr(&v.by);
1286 self.cond(&v.until);
1287 self.varying(v);
1288 }
1289 }
1290 }
1291 }
1292
1293 fn varying(&mut self, v: &Varying) {
1296 use rt::storage::Kind;
1297 if let Some(i) = self.item(&v.var)
1298 && !matches!(self.layout.items[i].kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::Index)
1299 {
1300 self.errors.push(syntax::messages::IWC0081.at(self.at, format!("PERFORM VARYING {}: not a numeric elementary item or an index-name", v.var.name)));
1301 }
1302 for (phrase, e) in [("FROM", &v.from), ("BY", &v.by)] {
1303 if !matches!(e, Expr::Operand(_)) {
1304 self.errors.push(syntax::messages::IWC0082.at(self.at, format!("PERFORM VARYING {} {phrase}: an arithmetic expression, where {phrase} takes an identifier, index-name or literal", v.var.name)));
1305 }
1306 }
1307 }
1308
1309 fn file(&mut self, name: &str, pos: Pos) {
1310 if !self.program.files.iter().any(|f| f.name == name) {
1311 self.errors.push(syntax::messages::IWC0083.at(pos, format!("no file named {name}")));
1312 }
1313 }
1314
1315 fn keyed_file(&mut self, name: &str, verb: &str, pos: Pos) {
1316 match self.program.files.iter().find(|f| f.name == name) {
1317 None => self.errors.push(syntax::messages::IWC0083.at(pos, format!("no file named {name}"))),
1318 Some(f) if !matches!(f.organization, Organization::Indexed | Organization::Relative) => {
1319 self.errors.push(syntax::messages::IWC0084.at(pos, format!("{verb} {name}: not an indexed or relative file")));
1320 }
1321 Some(_) => {}
1322 }
1323 }
1324
1325 fn not_random(&mut self, name: &str, verb: &str, pos: Pos) {
1326 if self.program.files.iter().any(|f| f.name == name && f.access == Access::Random) {
1327 self.errors.push(syntax::messages::IWC0085.at(pos, format!("{verb} {name}: the file's ACCESS MODE is RANDOM")));
1328 }
1329 }
1330
1331 fn handlers(&mut self, h: &Handlers) {
1332 self.statements(h.on.as_deref().unwrap_or_default());
1333 self.statements(h.not_on.as_deref().unwrap_or_default());
1334 }
1335
1336 fn class_name_subject(&mut self, e: &Expr, name: &str) {
1339 let Expr::Operand(Operand::Ref(r)) = e else {
1340 self.errors.push(syntax::messages::IWC0086.at(self.at, format!("class-name {name} tests a data item, not an expression")));
1341 return;
1342 };
1343 let Some(i) = self.item(r) else { return };
1344 if !matches!(self.layout.items[i].kind, layout::Kind::Group | layout::Kind::Alnum { .. } | layout::Kind::AlnumEdited { .. } | layout::Kind::NumericEdited { .. } | layout::Kind::Zoned { .. }) {
1345 self.errors.push(syntax::messages::IWC0087.at(r.pos, format!("class-name {name} tests a data item of USAGE DISPLAY, and {} is not one", r.name)));
1346 }
1347 }
1348
1349 fn item(&self, r: &Ref) -> Option<usize> {
1350 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1351 Ok(layout::Resolved::Item(i)) => Some(i),
1352 _ => None,
1353 }
1354 }
1355
1356 fn key_of(&mut self, file: &str, key: &Ref, partial: bool) {
1359 let Some(f) = self.program.files.iter().find(|f| f.name == file) else { return };
1360 if f.organization != Organization::Indexed {
1361 return;
1362 }
1363 let Some(item) = self.item(key).map(|i| &self.layout.items[i]) else { return };
1364 let fits = |r: &Ref| {
1365 self.item(r).map(|i| &self.layout.items[i]).is_some_and(|k| k.offset == item.offset && (k.size == item.size || partial && item.size < k.size))
1366 };
1367 let named = f.record_key.iter().chain(f.alternate_keys.iter().map(|(r, _)| r)).any(fits);
1368 if item.file.is_none() || !named {
1369 self.errors.push(syntax::messages::IWC0088.at(key.pos, format!("{}: not a key of {file}", key.name)));
1370 }
1371 }
1372
1373 fn file_keys(&mut self, k: usize) {
1375 let f = &self.program.files[k];
1376 let in_records = |c: &Self, r: &Ref| c.item(r).is_some_and(|i| c.layout.items[i].file == Some(k as u16));
1377 match f.organization {
1378 Organization::Indexed => {
1379 if f.record_key.is_none() {
1380 self.errors.push(syntax::messages::IWC0089.at(f.pos, format!("{}: an indexed file needs a RECORD KEY", f.name)));
1381 }
1382 for r in f.record_key.iter().chain(f.alternate_keys.iter().map(|(r, _)| r)) {
1383 self.reference(r);
1384 if self.item(r).is_some() && !in_records(self, r) {
1385 self.errors.push(syntax::messages::IWC0090.at(r.pos, format!("{}: a key of {} must be in its records", r.name, f.name)));
1386 }
1387 }
1388 }
1389 Organization::Relative => {
1390 if let Some(r) = &f.relative_key {
1391 self.reference(r);
1392 if in_records(self, r) {
1393 self.errors.push(syntax::messages::IWC0091.at(r.pos, format!("{}: the RELATIVE KEY of {} must not be in its records", r.name, f.name)));
1394 }
1395 } else if f.access != Access::Sequential {
1396 self.errors.push(syntax::messages::IWC0092.at(f.pos, format!("{}: random or dynamic access needs a RELATIVE KEY", f.name)));
1397 }
1398 }
1399 _ => {}
1400 }
1401 }
1402
1403 fn record_depending(&mut self, k: usize) {
1406 let f = &self.program.files[k];
1407 let Some(r) = &f.record_depending else { return };
1408 self.reference(r);
1409 if self.item(r).is_some() && linage::unsigned_integer(self.layout, r).is_none() {
1410 self.errors.push(syntax::messages::IWC0093.at(r.pos, format!("{}: the DEPENDING ON item of {} must be an elementary unsigned integer", r.name, f.name)));
1411 }
1412 }
1413
1414 fn passwords(&mut self, k: usize) {
1417 let f = &self.program.files[k];
1418 for r in &f.passwords {
1419 self.reference(r);
1420 let Some(i) = self.item(r) else { continue };
1421 let item = &self.layout.items[i];
1422 let working = !item.local && item.file.is_none() && item.linkage.is_none();
1423 if !working || !matches!(item.kind, rt::storage::Kind::Alnum { .. } | rt::storage::Kind::AlnumEdited { .. } | rt::storage::Kind::Group) {
1424 self.errors.push(syntax::messages::IWC0094.at(r.pos, format!("{}: the PASSWORD of {} must be an alphabetic, alphanumeric or alphanumeric-edited item of WORKING-STORAGE", r.name, f.name)));
1425 }
1426 }
1427 }
1428
1429 fn procedure(&mut self, p: &ProcName, pos: Pos) {
1430 if let Err(m) = procedure(self.program, p) {
1431 self.errors.push(m.at(pos));
1432 }
1433 }
1434
1435 fn reference(&mut self, r: &Ref) {
1438 self.reference_or_condition(r);
1439 if let Ok(layout::Resolved::Condition(_)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1440 self.errors.push(syntax::messages::IWC0095.at(r.pos, format!("{} is a condition-name, not a data item", r.name)));
1441 }
1442 }
1443
1444 fn reference_or_condition(&mut self, r: &Ref) {
1446 if r.name == "RETURN-CODE" && r.qualifiers.is_empty() && self.layout.resolve(&r.name, &r.qualifiers, r.pos).is_err() {
1447 return;
1448 }
1449 if oo::special_register(self.layout, r) || markup::xml_register(self.layout, r) {
1450 return;
1451 }
1452 if !self.debugging && !self.program.declaratives.debugging.is_empty() && declaratives::DEBUG_ITEM_NAMES.contains(&r.name.as_str()) {
1453 self.errors.push(syntax::messages::IWC0096.at(r.pos, format!("{}: only a debugging section may reference DEBUG-ITEM", r.name)));
1454 return;
1455 }
1456 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1457 Err(e) => self.errors.push(e),
1458 Ok(layout::Resolved::Item(i)) if switches::switch_of(self.layout, i).is_some() => self.errors.push(switches::mnemonic_as_data(r, self.layout, i)),
1459 Ok(layout::Resolved::Item(i)) if self.layout.items[i].dims.len() != r.subscripts.len() => self.errors.push(syntax::messages::IWC0097.at(
1460 r.pos,
1461 format!("{} takes {} subscripts, not {}", r.name, self.layout.items[i].dims.len(), r.subscripts.len()),
1462 )),
1463 Ok(_) => {}
1464 }
1465 r.subscripts.iter().for_each(|e| self.expr(e));
1466 if let Some(rm) = &r.refmod {
1467 self.expr(&rm.start);
1468 if let Some(l) = &rm.length {
1469 self.expr(l);
1470 }
1471 }
1472 }
1473
1474 fn exec_block(&mut self, block: &ExecBlock) {
1476 if block.kind == ExecKind::Dli {
1477 self.dli_block(block);
1478 }
1479 if block.kind == ExecKind::Cics && matches!(block.command.as_str(), "HANDLE CONDITION" | "HANDLE AID" | "HANDLE ABEND") {
1480 self.handle_labels(block);
1481 }
1482 if let Some(syntax::sql::Sql { statement: syntax::sql::Statement::Malformed(why), .. }) = &block.sql {
1483 self.errors.push(syntax::messages::IWP0003.at(block.pos, format!("EXEC SQL {}: {why}", block.command)));
1484 }
1485 for r in block.sql.iter().flat_map(|sql| sql.statement.colonless_into()) {
1488 if self.extended {
1489 self.errors.push(syntax::messages::IWX0011.at(r.pos, format!("{INTO_WITHOUT_COLON}: {} is read as a host variable", r.name)));
1490 } else {
1491 self.errors.push(syntax::messages::IWP0046.at(r.pos, format!("EXEC SQL {}: {} in the INTO list has no colon, which Db2 requires before every host variable", block.command, r.name)));
1492 }
1493 }
1494 for r in &block.host_variables {
1495 if r.subscripts.is_empty() {
1496 self.reference_unsubscripted(r);
1497 } else {
1498 self.reference(r);
1499 }
1500 }
1501 for (_, arg) in &block.options {
1502 if let Some(ExecArg::Operand(op)) = arg {
1503 self.operand(op);
1504 }
1505 }
1506 }
1507
1508 fn initialize_incompatible(&mut self, targets: &[Ref], with: &InitializeWith, pos: Pos) {
1511 if !with.value.is_empty() || with.default {
1512 return;
1513 }
1514 for r in targets {
1515 let Some(i) = self.item(r) else { continue };
1516 let initialized = match r.refmod {
1517 Some(_) => with.initial_value(Some(self.layout.refmod_category(Some(i), self.layout.items[i].kind)), false).is_some(),
1518 None => self.layout.initialize_receivers(i, with.filler).iter().any(|&(e, _)| with.initial_value(self.layout.category(e), false).is_some()),
1519 };
1520 if !initialized {
1521 let categories: Vec<&str> = with.replacing.iter().map(|(c, _)| c.word()).collect();
1522 self.errors.push(syntax::messages::IWC0098.at(
1523 pos,
1524 format!("INITIALIZE {}: none of its items is of a category REPLACING names ({}), so it is not initialized", r.name, categories.join(", ")),
1525 ));
1526 }
1527 }
1528 }
1529
1530 fn whole_table_reference(&mut self, r: &Ref) {
1533 if let Ok(layout::Resolved::Item(i)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
1534 && self.layout.items[i].table
1535 && self.layout.items[i].dims.len() == r.subscripts.len() + 1
1536 {
1537 r.subscripts.iter().for_each(|e| self.expr(e));
1538 return;
1539 }
1540 self.reference(r);
1541 }
1542
1543 fn dli_block(&mut self, block: &ExecBlock) {
1545 let Some(command) = syntax::dli::find(&block.command) else {
1546 self.errors.push(syntax::messages::IWP0004.at(block.pos, format!("EXEC DLI {} is not an EXEC DLI command", block.command)));
1547 return;
1548 };
1549 for (name, arg) in &block.options {
1550 if command.options.is_some_and(|options| !options.contains(&name.as_str())) {
1551 self.errors.push(syntax::messages::IWP0005.at(block.pos, format!("EXEC DLI {}: {name} is not one of its options", command.name)));
1552 } else if let (true, Some(ExecArg::Text(t))) = (name == "WHERE", arg)
1553 && let Err(why) = syntax::dli::qualification(t)
1554 {
1555 self.errors.push(syntax::messages::IWP0006.at(block.pos, format!("EXEC DLI {} WHERE({t}): {why}", command.name)));
1556 }
1557 }
1558 }
1559
1560 fn inspected_function(&mut self, f: &FunctionCall, i: &Inspect) {
1564 let name = f.name.as_str();
1565 let known = FUNCTIONS.contains(&name) || rt::intrinsic::FUNCTIONS.contains(&name);
1566 let numeric_udf = self.functions.into_iter().flatten().any(|u| u.name == name && !u.character_valued());
1567 if known && !rt::intrinsic::CHARACTER_VALUED.contains(&name) || numeric_udf {
1568 self.errors.push(syntax::messages::IWC0099.at(f.pos, format!("INSPECT FUNCTION {name}: an integer or numeric function can be used only where an arithmetic expression can, not as the inspected item")));
1569 }
1570 let stores = if !i.replacing.is_empty() {
1571 Some("REPLACING")
1572 } else {
1573 i.converting.as_ref().map(|_| "CONVERTING")
1574 };
1575 if let Some(phrase) = stores {
1576 self.errors.push(syntax::messages::IWC0100.at(f.pos, format!("INSPECT FUNCTION {name} {phrase}: {phrase} stores into the inspected item, and a function-identifier cannot be a receiving operand")));
1577 }
1578 }
1579
1580 fn displayed_function(&mut self, f: &FunctionCall) {
1585 let name = f.name.as_str();
1586 let intrinsic = FUNCTIONS.contains(&name) || rt::intrinsic::FUNCTIONS.contains(&name);
1587 let user_defined = self.functions.into_iter().flatten().any(|u| u.name == name);
1588 let numeric = !rt::intrinsic::CHARACTER_VALUED.contains(&name) || self.numeric_max_or_min(f);
1589 if intrinsic && !user_defined && numeric {
1590 self.errors.push(syntax::messages::IWC0101.at(f.pos, format!("DISPLAY FUNCTION {name}: an integer or numeric function can be used only where an arithmetic expression can, and DISPLAY takes none")));
1591 }
1592 }
1593
1594 fn accept_source(&mut self, from: AcceptFrom, exception: &Handlers, target: &Ref, pos: Pos) {
1598 if (exception.on.is_some() || exception.not_on.is_some()) && from != AcceptFrom::ArgumentValue {
1599 self.errors.push(syntax::messages::IWC0256.at(pos, "ACCEPT ... ON EXCEPTION: of the ACCEPT statements, only ACCEPT ... FROM ARGUMENT-VALUE under --compliance extended has an exception"));
1600 }
1601 let (name, what) = match from {
1602 AcceptFrom::CommandLine => ("COMMAND-LINE", "the job step's PARM program arguments"),
1603 AcceptFrom::ArgumentNumber => ("ARGUMENT-NUMBER", "how many words the job step's PARM program arguments hold"),
1604 AcceptFrom::ArgumentValue => ("ARGUMENT-VALUE", "the next word of the job step's PARM program arguments"),
1605 _ => return,
1606 };
1607 if self.extended {
1608 self.errors.push(syntax::messages::IWX0010.at(pos, format!("ACCEPT ... FROM {name} (Micro Focus and GnuCOBOL; Enterprise COBOL reads no command line): {} receives {what}", target.name)));
1609 } else {
1610 self.errors.push(syntax::messages::IWC0257.at(pos, format!("ACCEPT ... FROM {name}: Micro Focus's and GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it from the job step's PARM")));
1611 }
1612 }
1613
1614 fn argument_number(&mut self, items: &[Operand], pos: Pos) {
1618 if !self.extended {
1619 self.errors.push(syntax::messages::IWC0258.at(pos, "DISPLAY UPON ARGUMENT-NUMBER: Micro Focus's and GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it"));
1620 return;
1621 }
1622 let numeric = match items {
1623 [Operand::Literal(Literal::Number(_))] => true,
1624 [Operand::Ref(r)] => self.item(r).is_some_and(|i| self.layout.items[i].kind.is_numeric()),
1625 _ => false,
1626 };
1627 if numeric {
1628 self.errors.push(syntax::messages::IWX0010.at(pos, "DISPLAY ... UPON ARGUMENT-NUMBER (Micro Focus and GnuCOBOL; Enterprise COBOL reads no command line): the next ACCEPT ... FROM ARGUMENT-VALUE takes the job step's PARM word it numbers"));
1629 } else {
1630 self.errors.push(syntax::messages::IWC0259.at(pos, "DISPLAY UPON ARGUMENT-NUMBER: it shows one numeric item or literal, the number of the argument the next ACCEPT ... FROM ARGUMENT-VALUE takes"));
1631 }
1632 }
1633
1634 fn moved_function(&mut self, f: &FunctionCall) {
1640 let name = f.name.as_str();
1641 let intrinsic = FUNCTIONS.contains(&name) || rt::intrinsic::FUNCTIONS.contains(&name);
1642 let user_defined = self.functions.into_iter().flatten().any(|u| u.name == name);
1643 let numeric = !rt::intrinsic::CHARACTER_VALUED.contains(&name) || self.numeric_max_or_min(f);
1644 if !intrinsic || user_defined || !numeric {
1645 return;
1646 }
1647 if self.extended {
1648 self.errors.push(syntax::messages::IWX0008.at(f.pos, format!("{NUMERIC_FUNCTION_MOVED}: FUNCTION {name} is moved as its value")));
1649 } else {
1650 self.errors.push(syntax::messages::IWC0102.at(f.pos, format!("MOVE FUNCTION {name}: an integer or numeric function can be used only where an arithmetic expression can, not as a MOVE's sender")));
1651 }
1652 }
1653
1654 fn inspected_usage(&mut self, target: &Ref, i: &Inspect) {
1659 let Some(t) = self.item(target) else { return };
1660 let usage = CharUsage::of(self.layout.items[t].kind);
1661 let phrases = i.tallying.iter().chain(&i.replacing).flat_map(|p| p.pattern.iter().chain(&p.by).chain(p.bounds.iter().map(|b| &b.value)));
1662 let converting = i.converting.iter().flat_map(|(from, to, bounds)| [from, to].into_iter().chain(bounds.iter().map(|b| &b.value)));
1663 for op in phrases.chain(converting) {
1664 let (operand, operand_usage, pos) = match op {
1665 Operand::Literal(l) => match inspected_literal(l) {
1666 Some((what, operand_usage)) => (what.to_owned(), operand_usage, i.pos),
1667 None => continue,
1668 },
1669 Operand::Ref(r) => match self.item(r) {
1670 Some(k) => (r.name.clone(), CharUsage::of(self.layout.items[k].kind), r.pos),
1671 None => continue,
1672 },
1673 _ => continue,
1674 };
1675 let name = &target.name;
1676 let why = match (usage, operand_usage) {
1677 (u, o) if u == o => continue,
1678 (CharUsage::Display, o) => format!("{name} is not {o}, and an operand can be {o} only when the inspected item is"),
1679 (u, _) => format!("{name} is {u} and every operand but the count field must be {u} too"),
1680 };
1681 self.errors.push(syntax::messages::IWC0103.at(pos, format!("INSPECT {name}: {operand} cannot be an operand here, since {why}")));
1682 }
1683 }
1684
1685 fn handle_labels(&mut self, block: &ExecBlock) {
1688 let abend = block.command == "HANDLE ABEND";
1689 for (option, arg) in &block.options {
1690 let Some(ExecArg::Text(label)) = arg else { continue };
1691 let label = label.trim();
1692 if label.is_empty() || matches!(option.as_str(), "RESP" | "RESP2" | "NOHANDLE") || abend && option != "LABEL" {
1693 continue;
1694 }
1695 let name = ProcName { name: label.to_ascii_uppercase(), section: None };
1696 if let Err(m) = procedure_from(self.program, &name, self.paragraph) {
1697 self.errors.push(syntax::messages::IWP0007.at(block.pos, format!("EXEC CICS {} {option}({label}): {m}", block.command)));
1698 }
1699 }
1700 }
1701
1702 fn search_varying(&mut self, se: &Search, v: &Ref) {
1705 use rt::storage::Kind;
1706 let Some(i) = self.item(v) else { return };
1707 if !matches!(self.layout.items[i].kind, Kind::Index | Kind::Zoned { scale: 0, .. } | Kind::Packed { scale: 0, .. } | Kind::Binary { scale: 0, .. }) {
1708 self.errors.push(syntax::messages::IWC0104.at(v.pos, format!("SEARCH {} VARYING {}: not an index-name, an index data item or an elementary integer item", se.table.name, v.name)));
1709 }
1710 }
1711
1712 fn reference_unsubscripted(&mut self, r: &Ref) {
1714 if let Err(e) = self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1715 self.errors.push(e);
1716 }
1717 }
1718
1719 fn operand(&mut self, op: &Operand) {
1720 match op {
1721 Operand::LengthOf(r) => self.reference(&self.layout.length_of_ref(r)),
1722 Operand::Ref(r) | Operand::AddressOf(r) => self.reference(r),
1723 Operand::Literal(Literal::Number(t)) if literal_fixed(t).is_none() || literal_digits(t) > self.max_digits as usize => {
1724 self.errors.push(syntax::messages::IWC0105.at(Pos::default(), format!("the literal {t} has more than {} digits", self.max_digits.min(31))));
1725 }
1726 Operand::Literal(_) => {}
1727 Operand::Function(f) => {
1728 if !FUNCTIONS.contains(&f.name.as_str()) && !rt::intrinsic::FUNCTIONS.contains(&f.name.as_str()) {
1729 match self.functions.map(|all| all.iter().find(|u| u.name == f.name)) {
1730 Some(Some(udf)) => function::check_invocation(udf, f, self.layout, self.alphabetic, self.program.environment.decimal_point_comma, self.errors),
1731 Some(None) => self.errors.push(syntax::messages::IWC0106.at(f.pos, format!("FUNCTION {}: neither an intrinsic function nor a user-defined function defined or prototyped before this program", f.name))),
1732 None => self.errors.push(syntax::messages::IWC0107.at(f.pos, format!("FUNCTION {}: a user-defined function is not supported here yet", f.name))),
1733 }
1734 }
1735 self.function_arguments(f);
1736 f.args.iter().for_each(|a| self.expr(a));
1737 }
1738 }
1739 }
1740
1741 fn expr(&mut self, e: &Expr) {
1742 match e {
1743 Expr::Operand(op) => self.operand(op),
1744 Expr::Neg(inner) => self.expr(inner),
1745 Expr::Bin(a, _, b) => {
1746 self.expr(a);
1747 self.expr(b);
1748 }
1749 }
1750 }
1751
1752 fn cond(&mut self, c: &Cond) {
1753 match c {
1754 Cond::Rel(a, _, b) => {
1755 self.expr(a);
1756 self.expr(b);
1757 self.comparison(a, b);
1758 }
1759 Cond::Class(e, class) => {
1760 self.expr(e);
1761 if let Class::Named(name) = class {
1762 self.class_name_subject(e, name);
1763 }
1764 }
1765 Cond::Name(r) => self.reference_or_condition(r),
1766 Cond::NameOrRel { subject, name, .. } => {
1767 self.expr(subject);
1768 self.reference_or_condition(name);
1769 if self.item(name).is_some() {
1770 self.comparison(subject, &Expr::Operand(Operand::Ref(name.clone())));
1771 }
1772 }
1773 Cond::Not(inner) => self.cond(inner),
1774 Cond::And(a, b) | Cond::Or(a, b) => {
1775 self.cond(a);
1776 self.cond(b);
1777 }
1778 }
1779 }
1780}
1781
1782#[cfg(test)]
1783mod tests {
1784 use super::*;
1785 use std::ffi::OsStr;
1786 use std::time::Duration;
1787
1788 #[test]
1789 fn source_date_epoch_decides_the_compile_time_and_the_clock_stands_in_for_it() {
1790 let clock = Duration::from_millis(1_790_510_400_428);
1791 let from = |epoch: Option<&str>| compile_time_from(epoch.map(OsStr::new), clock);
1792 assert_eq!(from(None), Ok(CompileTime { seconds: 1_790_510_400, hundredths: 42, source: TimeSource::Clock }));
1793 assert_eq!(from(Some("315532800")), Ok(CompileTime { seconds: 315_532_800, hundredths: 0, source: TimeSource::SourceDateEpoch }));
1794 assert_eq!(from(Some("253402300799")).map(|t| t.seconds), Ok(CompileTime::LATEST));
1795 for bad in ["", "-1", "+5", "1.5", " 7", "253402300800"] {
1796 assert!(from(Some(bad)).unwrap_err().starts_with(&format!("SOURCE_DATE_EPOCH={bad}: ")), "{bad}");
1797 }
1798 }
1799}