1pub mod any_length;
5pub mod arith;
6pub mod cics_bind;
7mod crt_status;
8mod classes;
9pub mod constructs;
10pub mod collating;
11mod corresponding;
12pub use corresponding::is_alphabetic;
13pub use operands::as_characters;
14pub mod declaratives;
15pub mod facts;
16pub mod function;
17mod initcheck;
18pub mod layout;
19pub mod linage;
20pub mod lower;
21pub mod markup;
22pub mod numcheck;
23mod omitted;
24pub mod oo;
25mod operands;
26pub mod picture;
27pub mod printer;
28pub mod report;
29mod registers;
30mod reserved;
31mod scope;
32mod screens;
33pub mod sort;
34pub mod sql;
35pub mod statements;
36mod switches;
37pub mod values;
38
39use layout::Layout;
40use numeric::{Options, Vlr};
41use rt::lir::{CompileTime, TimeSource};
42use rt::storage::literal_fixed;
43use syntax::ast::*;
44use syntax::messages::{IWC0003, IWC0004, Message};
45use syntax::{Error, Pos, Severity};
46
47pub struct Compiled {
48 pub program: Program,
49 pub when_compiled: CompileTime,
51 pub layout: Layout,
52 pub options: Options,
53 pub ssrange: bool,
54 pub report_writer: report::Writer,
55 pub collating: collating::Sequence,
57 pub carriage: Vec<Option<printer::Carriage>>,
59 pub diagnostics: Vec<Error>,
61 pub entries: Vec<EntryPoint>,
63 pub declaratives: declaratives::Table,
65 pub functions: Vec<function::Udf>,
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct EntryPoint {
73 pub name: String,
74 pub paragraph: usize,
75 pub statement: usize,
76 pub using: Vec<Param>,
77 pub pos: Pos,
78}
79
80pub fn entry_points(program: &Program) -> Vec<EntryPoint> {
82 let mut out = Vec::new();
83 for (paragraph, p) in program.paragraphs.iter().enumerate() {
84 for (k, s) in p.statements.iter().enumerate() {
85 if let Stmt::Entry { name, using, pos } = s {
86 out.push(EntryPoint { name: name.clone(), paragraph, statement: k + 1, using: using.clone(), pos: *pos });
87 }
88 }
89 }
90 out
91}
92
93pub fn variable_records(file: &FileDecl, layout: &Layout, k: usize) -> bool {
98 let undeclared = file.recording.is_none() && file.record_min.is_none() && file.record_max.is_none() && !file.record_varying;
99 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)
100}
101
102pub fn read_lengths(file: &FileDecl, layout: &Layout, k: usize, vlr: Vlr) -> (u32, u32) {
108 match vlr {
109 Vlr::Compat if file.record_varying => varying_lengths(file, layout, k),
110 _ => record_lengths(layout, k),
111 }
112}
113
114pub fn varying_lengths(file: &FileDecl, layout: &Layout, k: usize) -> (u32, u32) {
117 let (shortest, longest) = record_lengths(layout, k);
118 (file.record_min.unwrap_or(shortest), file.record_max.unwrap_or(longest))
119}
120
121fn record_lengths(layout: &Layout, k: usize) -> (u32, u32) {
122 layout.record_lengths[k].unwrap_or((0, layout.file_areas[k].1))
123}
124
125const FUNCTIONS: &[&str] = rt::intrinsic::FIRST;
126
127pub 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)";
128pub const INTO_WITHOUT_COLON: &str = "an INTO name written without its colon (Db2 13 for z/OS requires the colon before every host variable)";
129
130pub fn compile(program: Program, flags: &[String]) -> Result<Compiled, Vec<Error>> {
135 let at = compile_time().map_err(|message| vec![syntax::messages::IWO0004.at(Pos::default(), message)])?;
136 compile_at(program, flags, at)
137}
138
139pub fn compile_at(program: Program, flags: &[String], at: CompileTime) -> Result<Compiled, Vec<Error>> {
141 if program.oo.as_ref().is_some_and(|o| o.class().is_some()) {
142 return oo::compile_class_definition(program, flags, at);
143 }
144 compile_program(program, flags, true, at)
145}
146
147pub fn compile_time() -> Result<CompileTime, String> {
150 let clock = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
151 compile_time_from(std::env::var_os("SOURCE_DATE_EPOCH").as_deref(), clock)
152}
153
154fn compile_time_from(epoch: Option<&std::ffi::OsStr>, clock: std::time::Duration) -> Result<CompileTime, String> {
155 let Some(epoch) = epoch else {
156 return Ok(CompileTime { seconds: clock.as_secs() as i64, hundredths: clock.subsec_millis() / 10, source: TimeSource::Clock });
157 };
158 let text = epoch.to_string_lossy();
159 match text.parse::<i64>() {
160 Ok(seconds) if text.bytes().all(|b| b.is_ascii_digit()) && seconds <= CompileTime::LATEST => Ok(CompileTime { seconds, hundredths: 0, source: TimeSource::SourceDateEpoch }),
161 _ => Err(format!("SOURCE_DATE_EPOCH={text}: not a whole number of seconds from 0 to {}", CompileTime::LATEST)),
162 }
163}
164
165pub(crate) fn compile_program(mut program: Program, flags: &[String], whole: bool, when_compiled: CompileTime) -> Result<Compiled, Vec<Error>> {
168 let mut errors = std::mem::take(&mut program.messages);
169 reserved::check(&program, &mut errors);
170 let mut declared = program.working_storage.len();
171 let mut program = declaratives::with_debug_item(markup::with_special_registers(sort::with_special_registers(registers::with_when_compiled(program, when_compiled))));
172 switches::declare(&mut program, &mut errors);
173 qualify_in_own_section(&mut program);
174 let mut options = Options::default();
175 let mut ssrange = false;
176 for flag in flags {
179 if let Err(e) = options.apply_flag(flag) {
180 errors.push(syntax::messages::IWO0005.at(Pos::default(), e.to_string()));
181 }
182 }
183 for option in &program.options {
184 if let Some(on) = numeric::options::switch(option, "SSRANGE") {
185 ssrange = on;
186 }
187 if let Err(e) = options.apply(option) {
188 errors.push(syntax::messages::IWO0001.at(Pos::default(), format!("CBL {option}: {e}")).graded(option_severity(&e)));
189 }
190 }
191 if options.compliance == numeric::Compliance::Extended {
192 let inserted = top_level_tables(&mut program.working_storage, &mut errors);
193 declared += inserted.iter().filter(|&&at| at < declared).count();
194 top_level_tables(&mut program.local_storage, &mut errors);
195 let Program { working_storage, local_storage, linkage, files, .. } = &mut program;
196 for entries in [working_storage, local_storage, linkage].into_iter().chain(files.iter_mut().map(|f| &mut f.records)) {
197 renames_inside_records(entries, &mut errors);
198 }
199 declared = based_items(&mut program, declared, &mut errors);
200 }
201 screens::expand(&mut program, options.compliance == numeric::Compliance::Extended, &mut errors);
202 let page = options.code_page();
203 if let Err((message, m)) = syntax::parser::decode_currency(&mut program.environment, |bytes| page.decode(bytes)) {
204 errors.push(message.at(Pos::default(), m));
205 }
206 default_currency(&mut program, &mut options, &mut errors);
207 national_symbols(&mut program, &mut options, &mut errors);
208 if options.intdate == numeric::IntDate::Lilian {
209 program.paragraphs.iter_mut().for_each(|p| ceecbldy_to_ceedays(&mut p.statements, &mut errors));
210 }
211 for (name, alphabet) in &program.environment.alphabets {
212 if program.environment.collating_sequence.as_ref() != Some(name)
213 && let Err((message, m)) = collating::Sequence::of(alphabet, options.code_page(), options.quote)
214 {
215 errors.push(message.at(Pos::default(), format!("ALPHABET {name}: {m}")));
216 }
217 }
218 let collating = collating::Sequence::program(&program.environment, options.code_page(), options.quote).unwrap_or_else(|(message, m)| {
219 errors.push(message.at(Pos::default(), m));
220 let mut native = collating::Sequence::native();
221 native.quote = options.quote;
222 native
223 });
224 digit_limits(&program, options.arith, &mut errors);
225 binary_chars(&program, &options, &mut errors);
226 forever_names(&program, &mut errors);
227 micro_focus_binaries(&program, &options, &mut errors);
228 let drafts = report::prepare(&mut program, options.adv, options.qualify, &mut errors);
229 let linage_counters = linage::add_counters(&mut program, options.qualify);
230 if whole {
231 oo::option_rules(&program, &options, &mut errors);
232 }
233 options.initial &= !options.thread;
234 if whole && program.oo.as_deref().and_then(Oo::method).is_none() {
235 program.initial |= options.initial;
236 }
237 scope::rules(&program, &mut errors);
238 if whole {
239 program_names(&program, &options, &mut errors);
240 }
241 let inherited = scope::inherit(&mut program);
242 let own_linkage = scope::own_linkage(&program);
243 omitted::rewrite(&mut program, &inherited.entries, options.compliance == numeric::Compliance::Extended, &mut errors);
244 any_length::rewrite(&mut program, &mut errors);
245 if options.compliance == numeric::Compliance::Extended {
246 crt_status::rewrite(&mut program, &inherited.entries);
247 }
248 declare_assign_items(&mut program, &inherited.entries);
249 let linkage: Vec<DataEntry> = program.linkage.iter().chain(&inherited.entries).cloned().collect();
250 let files: Vec<(&[DataEntry], Option<u32>)> = program.files.iter().map(|f| (f.records.as_slice(), f.record_max)).collect();
251 let shared = layout::record_area_owners(&program.files, &program.environment).unwrap_or_else(|e| {
252 errors.push(e);
253 (0..files.len()).collect()
254 });
255 let mut layout = match layout::build(&program.working_storage, &files, &shared, &linkage, program.linkage.len(), &program.local_storage, crate::picture::Notation::of(&program.environment), options.qualify, options.parmcheck.map(|p| (declared, p.bytes.into()))) {
256 Ok(l) => l,
257 Err(e) => {
258 errors.push(e);
259 return Err(errors.into_iter().map(|e| e.in_files(&program.sources)).collect());
260 }
261 };
262 scope::bind(&mut layout, own_linkage, inherited, &program.files);
263 layout.classes = classes::sets(&program.environment.classes, page, &mut errors);
264 let counter_item = |entry: usize| program.working_storage[..entry].iter().filter(|e| e.level != 88).count();
265 layout.name_files(&program.files, linage_counters.iter().map(|c| c.map(counter_item)).collect());
266 assign_items(&mut program, &layout, &options, &mut errors);
267 corresponding::expand(&mut program, &layout, &mut errors);
268 condition_subjects(&mut program, &layout);
269 dbcs_values(&layout, &mut errors);
270 numeric_values(&layout, &mut errors);
271 edited_values(&layout, options.compliance, &mut errors);
272 for item in &layout.items {
273 if let Some(object) = &item.depending_on {
274 match layout.resolve(&object.name, &object.qualifiers, object.pos) {
275 Ok(layout::Resolved::Item(i)) if !layout.items[i].moved_by.is_empty() => errors.push(syntax::messages::IWC0048.at(
276 object.pos,
277 format!("OCCURS DEPENDING ON {}: the object cannot follow an OCCURS DEPENDING ON table in its record", object.name),
278 )),
279 Ok(layout::Resolved::Item(i)) if layout.items[i].kind.is_numeric() => {}
280 Ok(_) => errors.push(syntax::messages::IWC0049.at(object.pos, format!("OCCURS DEPENDING ON {}: not a numeric data item", object.name))),
281 Err(e) => errors.push(e),
282 }
283 }
284 }
285 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));
286 for param in &program.using {
287 if !is_record(¶m.name) {
288 errors.push(syntax::messages::IWC0050.at(Pos::default(), format!("PROCEDURE DIVISION USING {}: not an 01 or 77 item of the LINKAGE SECTION", param.name)));
289 }
290 }
291 if let Some(name) = &program.returning
293 && program.function.is_none()
294 && program.oo.as_deref().and_then(Oo::method).is_none()
295 && !is_record(name)
296 {
297 errors.push(syntax::messages::IWC0051.at(Pos::default(), format!("PROCEDURE DIVISION RETURNING {name}: not an 01 or 77 item of the LINKAGE SECTION")));
298 }
299 let report_writer = report::resolve(&program, &layout, drafts, &mut errors);
300 let carriage = printer::carriages(&program, &layout, options.adv);
301 let declaratives = declaratives::resolve(&program, &layout, &options, &mut errors);
302 let debugging = declaratives::debugging_sections(&program);
303 let functions = function::functions(&program, options.qualify, &mut errors);
304 let alphabetic: Vec<Pos> = [&program.working_storage, &program.local_storage, &program.linkage]
305 .into_iter()
306 .flatten()
307 .chain(program.files.iter().flat_map(|f| &f.records))
308 .filter(|e| e.picture.as_deref().is_some_and(is_alphabetic))
309 .map(|e| e.pos)
310 .collect();
311 let mut check = Check {
312 layout: &layout,
313 program: &program,
314 errors: &mut errors,
315 debugging: false,
316 max_digits: options.arith.max_picture_digits(),
317 inline_performs: 0,
318 functions: Some(&functions),
319 alphabetic: &alphabetic,
320 at: Pos::default(),
321 paragraph: 0,
322 extended: options.compliance == numeric::Compliance::Extended,
323 environment_named: None,
324 };
325 for k in 0..program.files.len() {
326 check.file_keys(k);
327 check.record_depending(k);
328 check.label_records(k);
329 check.recording_mode(k);
330 check.passwords(k);
331 linage::check_file(check.program, check.layout, k, check.errors);
332 }
333 for block in &program.exec_declarations {
334 check.exec_block(block);
335 }
336 for (i, p) in program.paragraphs.iter().enumerate() {
337 check.debugging = debugging.iter().any(|&(first, last)| (first..=last).contains(&i));
338 check.paragraph = i;
339 check.statements(&p.statements);
340 }
341 let entries = entry_points(&program);
342 procedure_rules(&program, &layout, &entries, &options, &mut errors);
343 if whole && !program.oo.as_deref().is_some_and(|o| o.method().is_some()) && !program.is_prototype() {
344 program_end(&program, &options, &mut errors);
345 }
346 oo::check(&layout, &program, &mut errors);
347 if let Some(mode) = options.initcheck {
348 errors.extend(initcheck::check(&program, &layout, declared, mode));
349 }
350 scope::check(&program, &layout, &mut errors);
351 layout.numcheck = numcheck::facts(&program, &layout, declared, &options, &collating, &mut errors);
352 let errors: Vec<Error> = errors.into_iter().map(|e| e.in_files(&program.sources)).collect();
353 if refused(&errors, &options) {
354 Err(errors)
355 } else {
356 Ok(Compiled { program, when_compiled, layout, options, ssrange, report_writer, collating, carriage, diagnostics: errors, entries, declaratives, functions })
357 }
358}
359
360fn declare_assign_items(program: &mut Program, inherited: &[DataEntry]) {
369 let mut missing: Vec<(String, Pos)> = Vec::new();
370 for a in program.files.iter().filter_map(|f| f.assign_item.as_ref()).filter(|a| a.declared_if_missing) {
371 let name = &a.reference.name;
372 let declared = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(inherited).chain(program.files.iter().flat_map(|f| &f.records)).any(|e| e.name.as_ref() == Some(name));
373 if !declared && !missing.iter().any(|(m, _)| m == name) {
374 missing.push((name.clone(), a.reference.pos));
375 }
376 }
377 for (name, pos) in missing {
378 program.working_storage.push(report::entry(1, Some(name), Some("X(4095)".into()), None, pos));
379 }
380}
381
382fn assign_items(program: &mut Program, layout: &layout::Layout, options: &Options, errors: &mut Vec<Error>) {
383 let extended = options.compliance == numeric::Compliance::Extended;
384 let mut all = Vec::new();
385 program.paragraphs.iter().for_each(|p| inner_statements(&p.statements, &mut all));
386 let sorted: Vec<&str> = all
387 .iter()
388 .filter_map(|s| match s {
389 Stmt::Sorting(sorting) => match sorting.as_ref() {
390 Sorting::Sort(s) => Some(s),
391 _ => None,
392 },
393 _ => None,
394 })
395 .flat_map(|s| [&s.input, &s.output])
396 .filter_map(|io| if let Some(SortIo::Files(names)) = io { Some(names) } else { None })
397 .flatten()
398 .map(String::as_str)
399 .collect();
400 let mut kept = Vec::new();
401 for (k, f) in program.files.iter().enumerate() {
402 let Some(a) = &f.assign_item else { continue };
403 let (name, pos) = (&a.reference.name, a.reference.pos);
404 if !extended {
405 if a.explicit {
406 errors.push(syntax::messages::IWC0052.at(pos, format!("ASSIGN USING or DYNAMIC {name}: a Micro Focus and GnuCOBOL form; --compliance extended reads it")));
407 }
408 continue;
409 }
410 let item = match layout.resolve(name, &a.reference.qualifiers, pos) {
411 Ok(layout::Resolved::Item(i)) => i,
412 Ok(layout::Resolved::Condition(_)) | Err(_) if a.explicit => {
413 errors.push(syntax::messages::IWC0053.at(pos, format!("ASSIGN {name}: not a data item")));
414 continue;
415 }
416 _ => continue,
417 };
418 if !matches!(layout.items[item].kind, rt::storage::Kind::Group | rt::storage::Kind::Alnum { .. }) {
419 errors.push(syntax::messages::IWC0054.at(pos, format!("ASSIGN {name}: the item holding the file's name must be alphanumeric or a group")));
420 } else if f.sort || sorted.contains(&f.name.as_str()) {
421 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")));
422 } else {
423 errors.push(syntax::messages::IWX0007.at(pos, format!("{}: each OPEN of {} takes its DD name from {name}", syntax::extended::ASSIGN_ITEM, f.name)));
424 kept.push(k);
425 }
426 }
427 for (k, f) in program.files.iter_mut().enumerate() {
428 if !kept.contains(&k) {
429 f.assign_item = None;
430 }
431 }
432}
433
434fn option_severity(e: &numeric::options::OptionError) -> Severity {
440 use numeric::options::OptionError;
441 match e {
442 OptionError::BadSuboption { .. } => Severity::Error,
443 OptionError::Removed { .. } | OptionError::NoEffect { warning: true, .. } => Severity::Warning,
444 OptionError::NoEffect { warning: false, .. } => Severity::Informational,
445 OptionError::UnsupportedCodePage(_) | OptionError::UnknownFlag(_) => Severity::Severe,
446 }
447}
448
449fn default_currency(program: &mut Program, options: &mut Options, errors: &mut Vec<Error>) {
455 match options.currency_symbol() {
456 Some(Ok(symbol)) if program.environment.currency.is_empty() => program.environment.currency.push(CurrencySign { value: symbol.to_string(), symbol, hex: None }),
457 Some(Err(c)) => {
458 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));
459 options.currency = None;
460 }
461 _ => {}
462 }
463}
464
465fn national_symbols(program: &mut Program, options: &mut Options, errors: &mut Vec<Error>) {
470 if options.nsymbol == numeric::Nsymbol::National {
471 if !options.dbcs && program.options.iter().any(|o| numeric::options::switch(o, "NSYMBOL").is_some()) {
472 errors.push(syntax::messages::IWO0003.at(Pos::default(), "CBL NODBCS: NSYMBOL(NATIONAL) requires DBCS, which is in effect"));
473 options.dbcs = true;
474 }
475 return;
476 }
477 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, '(' | ')'));
478 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));
479 for entries in lists {
480 let mut groups: Vec<(u8, bool)> = Vec::new();
481 for e in entries.iter_mut().filter(|e| !matches!(e.level, 66 | 88)) {
482 let level = if e.level == 77 { 1 } else { e.level };
483 while groups.last().is_some_and(|&(l, _)| l >= level) {
484 groups.pop();
485 }
486 let usage = e.usage.is_some() || groups.iter().any(|&(_, u)| u);
487 groups.push((level, e.usage.is_some()));
488 if !usage && e.picture.as_deref().is_some_and(only_n) {
489 e.usage = Some(Usage::Dbcs);
490 }
491 }
492 }
493}
494
495fn program_end(program: &Program, options: &Options, errors: &mut Vec<Error>) {
499 use numeric::CicsReturnWarning;
500 let mut all = Vec::new();
501 program.paragraphs.iter().for_each(|p| inner_statements(&p.statements, &mut all));
502 if all.iter().any(|s| matches!(s, Stmt::StopRun { .. } | Stmt::Goback { .. } | Stmt::ExitProgram { .. })) {
503 return;
504 }
505 let cics_end = all.iter().find_map(|s| match s {
506 Stmt::Exec(b) if b.kind == ExecKind::Cics && matches!(b.command.as_str(), "RETURN" | "XCTL") => Some(b.command.as_str()),
507 _ => None,
508 });
509 match (cics_end, options.cics_return_warning) {
510 (Some(_), CicsReturnWarning::Never) => {}
511 (Some(command), CicsReturnWarning::Once) => errors.push(syntax::messages::IWP0002.at(Pos::default(), format!(
512 "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"
513 )).graded(Severity::Informational)),
514 (None, _) | (Some(_), CicsReturnWarning::Always) => {
515 errors.push(syntax::messages::IWC0055.at(Pos::default(), "no STOP RUN, GOBACK or EXIT PROGRAM in the program: check that it ends"));
516 }
517 }
518}
519
520fn ceecbldy_to_ceedays(stmts: &mut [Stmt], errors: &mut Vec<Error>) {
525 for s in stmts {
526 if let Stmt::Call(c) = s
527 && let Operand::Literal(Literal::Alnum(name)) = &mut c.target
528 && name.trim().eq_ignore_ascii_case("CEECBLDY")
529 {
530 *name = "CEEDAYS".into();
531 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"));
532 }
533 for body in oo::bodies_mut(s) {
534 ceecbldy_to_ceedays(body, errors);
535 }
536 }
537}
538
539pub fn refused(messages: &[Error], options: &Options) -> bool {
543 let stops_at = options.object_code().stops_at().min(12);
544 stops_at == 0 || messages.iter().any(|m| m.severity.return_code() >= stops_at)
545}
546
547fn procedure_rules(program: &Program, layout: &Layout, entries: &[EntryPoint], options: &Options, errors: &mut Vec<Error>) {
550 let method = program.oo.as_ref().is_some_and(|o| matches!(o.unit, OoUnit::Method(_)));
551 for (k, e) in entries.iter().enumerate() {
552 if program.returning.is_some() {
553 errors.push(syntax::messages::IWC0057.at(e.pos, format!("ENTRY '{}': a program with PROCEDURE DIVISION RETURNING cannot have ENTRY statements", e.name)));
554 }
555 if e.name.eq_ignore_ascii_case(&program.id) || entries[..k].iter().any(|f| f.name == e.name) {
556 errors.push(syntax::messages::IWC0058.at(e.pos, format!("ENTRY '{}': the name is already the program's or another ENTRY's", e.name)));
557 }
558 for param in &e.using {
559 if !layout.linkage_roots.iter().enumerate().any(|(o, &i)| layout.is_argument(o) && layout.items[i].name.as_deref() == Some(param.name.as_str())) {
560 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)));
561 }
562 }
563 }
564 let why_no_alter = if program.recursive {
565 Some("a RECURSIVE program")
566 } else if options.thread {
567 Some("a program compiled with THREAD")
568 } else {
569 method.then_some("a method")
570 };
571 for p in &program.paragraphs {
572 for s in &p.statements {
573 let mut inner = Vec::new();
574 oo::bodies(s).into_iter().for_each(|body| inner_statements(body, &mut inner));
575 for (t, nested) in std::iter::once((s, false)).chain(inner.into_iter().map(|t| (t, true))) {
576 match t {
577 Stmt::Entry { name, pos, .. } if nested => {
578 errors.push(syntax::messages::IWC0060.at(*pos, format!("ENTRY '{name}' must be a sentence of its own, not inside another statement")));
579 }
580 Stmt::GoTo { target: None, pos } => {
581 if let Some(why) = why_no_alter {
582 errors.push(syntax::messages::IWC0061.at(*pos, format!("a GO TO with no procedure-name cannot be used in {why}")));
583 }
584 if nested || !lone_go_to(p) {
585 errors.push(syntax::messages::IWC0062.at(*pos, "a GO TO with no procedure-name must be its paragraph's only sentence"));
586 }
587 }
588 Stmt::Alter { pairs, pos } => {
589 if let Some(why) = why_no_alter {
590 errors.push(syntax::messages::IWC0063.at(*pos, format!("ALTER cannot be used in {why}")));
591 }
592 for (from, to) in pairs {
593 altered_paragraph(program, from, *pos, errors);
594 if let Err(m) = procedure(program, to) {
595 errors.push(m.at(*pos));
596 }
597 }
598 }
599 _ => {}
600 }
601 }
602 }
603 }
604}
605
606fn program_names(program: &Program, options: &Options, errors: &mut Vec<Error>) {
611 let strict = options.program_scope == numeric::ProgramScope::Strict;
612 if strict {
613 for (name, pos) in &program.duplicates {
614 let text = format!("{name}: two programs of {} have this name, and the programs of a separately compiled program each need their own (--program-scope=flexible allows it)", program.id);
615 errors.push(syntax::messages::IWC0295.at(*pos, text));
616 }
617 }
618 if options.unresolved_calls != numeric::UnresolvedCalls::Fail || options.dynam {
619 return;
620 }
621 let reached = |name: &str| {
622 let contained = program.callable.iter().chain(program.hidden.iter().filter(|_| !strict));
623 program.linked.iter().chain(contained).any(|n| n.eq_ignore_ascii_case(name)) || rt::le::provides(&name.to_ascii_uppercase())
624 };
625 let mut all = Vec::new();
626 program.paragraphs.iter().for_each(|p| inner_statements(&p.statements, &mut all));
627 for s in all {
628 if let Stmt::Call(c) = s
629 && let Operand::Literal(Literal::Alnum(name)) = &c.target
630 && !reached(name.trim_end())
631 {
632 let text = format!("CALL '{}': no program of the compilation has this name, and --unresolved-calls=fail refuses a static CALL the binder could not resolve", name.trim_end());
633 errors.push(syntax::messages::IWC0296.at(c.pos, text));
634 }
635 }
636}
637
638fn inner_statements<'s>(stmts: &'s [Stmt], out: &mut Vec<&'s Stmt>) {
640 for s in stmts {
641 out.push(s);
642 oo::bodies(s).into_iter().for_each(|body| inner_statements(body, out));
643 }
644}
645
646fn altered_paragraph(program: &Program, name: &ProcName, pos: Pos, errors: &mut Vec<Error>) {
648 match procedure(program, name) {
649 Err(m) => errors.push(m.at(pos)),
650 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))),
651 Ok((i, _)) if !lone_go_to(&program.paragraphs[i]) => {
652 errors.push(syntax::messages::IWC0065.at(pos, format!("ALTER {}: the paragraph must hold one sentence, a GO TO without DEPENDING ON", name.name)));
653 }
654 Ok(_) => {}
655 }
656}
657
658fn lone_go_to(p: &Paragraph) -> bool {
659 matches!(p.statements.as_slice(), [Stmt::GoTo { .. }] | [Stmt::GoTo { .. }, Stmt::SentenceEnd])
660}
661
662pub fn section_end(program: &Program, i: usize) -> usize {
665 let paragraphs = &program.paragraphs;
666 let declaratives = program.report_writer.procedure_start;
667 let (floor, ceiling) = if i < declaratives { (0, declaratives) } else { (declaratives, paragraphs.len()) };
668 let Some(header) = (floor..=i).rev().find(|&j| paragraphs[j].is_section) else { return i };
669 (header + 1..ceiling).take_while(|&j| !paragraphs[j].is_section).last().unwrap_or(header)
670}
671
672fn qualify_in_own_section(program: &mut Program) {
676 for i in 0..program.paragraphs.len() {
677 let Some(section) = program.paragraphs[i].section.clone() else { continue };
678 let mut statements = std::mem::take(&mut program.paragraphs[i].statements);
679 oo::each_mut(&mut statements, &mut |s| {
680 for name in procedure_names_mut(s) {
681 if name.section.is_none() && names_own_paragraph(&program.paragraphs, &name.name, §ion) {
682 name.section = Some(section.clone());
683 }
684 }
685 });
686 program.paragraphs[i].statements = statements;
687 }
688}
689
690fn names_own_paragraph(paragraphs: &[Paragraph], name: &str, section: &str) -> bool {
693 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))
694}
695
696pub fn procedure_from(program: &Program, p: &ProcName, from: usize) -> Result<(usize, usize), Unnamed> {
700 match program.paragraphs.get(from).and_then(|q| q.section.as_deref()) {
701 Some(section) if p.section.is_none() && names_own_paragraph(&program.paragraphs, &p.name, section) => {
702 procedure(program, &ProcName { name: p.name.clone(), section: Some(section.to_owned()) })
703 }
704 _ => procedure(program, p),
705 }
706}
707
708fn dbcs_values(layout: &Layout, errors: &mut Vec<Error>) {
714 let dbcs = |i: usize| matches!(layout.items[i].kind, rt::storage::Kind::Dbcs { .. });
715 let is_dbcs = |l: &Literal| matches!(l, Literal::Dbcs(_)) || matches!(l, Literal::All(inner) if matches!(**inner, Literal::Dbcs(_)));
716 let fits = |l: &Literal, i: usize| match l {
717 Literal::Dbcs(s) => s.chars().count() <= layout.items[i].size as usize / 2,
718 Literal::Figurative(syntax::ast::Figurative::Space) => true,
719 l => is_dbcs(l),
720 };
721 let values = layout.items.iter().enumerate().filter_map(|(i, it)| it.value.as_ref().map(|v| (i, v, it.pos)));
722 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)));
723 for (i, value, pos) in values.chain(conditions) {
724 let name = layout.items[i].name.as_deref().unwrap_or("FILLER");
725 if dbcs(i) && !fits(value, i) {
726 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)));
727 } else if !dbcs(i) && is_dbcs(value) {
728 errors.push(syntax::messages::IWC0067.at(pos, format!("{name}: a DBCS literal can be the VALUE of a DBCS item only")));
729 }
730 }
731}
732
733const DISPLAY_DEVICES: &[&str] = &["SYSOUT", "SYSLIST", "SYSLST", "SYSPUNCH", "SYSPCH", "CONSOLE"];
736
737fn numeric_values(layout: &Layout, errors: &mut Vec<Error>) {
740 for item in layout.items.iter().filter(|i| i.children.is_empty() && i.kind.is_numeric()) {
741 let what = match item.value {
742 Some(Literal::Alnum(_) | Literal::Hex(_)) => "an alphanumeric literal",
743 Some(Literal::National(_)) => "a national literal",
744 _ => continue,
745 };
746 let name = item.name.as_deref().unwrap_or("FILLER");
747 errors.push(syntax::messages::IWC0068.at(item.pos, format!("VALUE of {name}: {what}, where a numeric item's VALUE literal must be numeric")));
748 }
749}
750
751fn edited_values(layout: &Layout, compliance: numeric::Compliance, errors: &mut Vec<Error>) {
755 for item in layout.items.iter().filter(|i| matches!(i.kind, rt::storage::Kind::NumericEdited { .. }) && matches!(i.value, Some(Literal::Number(_)))) {
756 let name = item.name.as_deref().unwrap_or("FILLER");
757 errors.push(if compliance == numeric::Compliance::Extended {
758 syntax::messages::IWX0012.at(item.pos, format!("a numeric literal as a numeric-edited item's VALUE (Micro Focus and GnuCOBOL; Enterprise COBOL takes an alphanumeric literal in edited form): {name} starts as the literal moved to it"))
759 } else {
760 syntax::messages::IWC0292.at(item.pos, format!("VALUE of {name}: a numeric literal, where a numeric-edited item's VALUE is an alphanumeric literal or a figurative constant written in edited form; --compliance extended edits the number into it"))
761 });
762 }
763}
764
765fn condition_subjects(program: &mut Program, layout: &Layout) {
766 let is_condition = |r: &Ref| matches!(layout.resolve(&r.name, &r.qualifiers, r.pos), Ok(layout::Resolved::Condition(_)));
767 for p in &mut program.paragraphs {
768 oo::each_mut(&mut p.statements, &mut |s| {
769 let Stmt::Evaluate { subjects, whens, .. } = s else { return };
770 for (k, subject) in subjects.iter_mut().enumerate() {
771 let Subject::Expr(Expr::Operand(Operand::Ref(r))) = subject else { continue };
772 if !is_condition(r) {
773 continue;
774 }
775 *subject = Subject::Cond(Cond::Name(r.clone()));
776 for alternative in whens.iter_mut().flat_map(|w| w.alternatives.iter_mut()) {
777 let cond = match alternative.get(k) {
778 Some(Object::Value { not, from: Expr::Operand(Operand::Ref(o)), thru: None }) => {
779 let c = Cond::Name(o.clone());
780 if *not { Cond::Not(Box::new(c)) } else { c }
781 }
782 _ => continue,
783 };
784 alternative[k] = Object::Cond(cond);
785 }
786 }
787 });
788 }
789}
790
791fn procedure_names_mut(s: &mut Stmt) -> Vec<&mut ProcName> {
793 match s {
794 Stmt::PerformProc { from, thru, .. } => std::iter::once(from).chain(thru.as_mut()).collect(),
795 Stmt::GoTo { target, .. } => target.iter_mut().collect(),
796 Stmt::GoToDepending { targets, .. } => targets.iter_mut().collect(),
797 Stmt::Alter { pairs, .. } => pairs.iter_mut().flat_map(|(from, to)| [from, to]).collect(),
798 Stmt::XmlParse(x) => std::iter::once(&mut x.procedure).chain(x.thru.as_mut()).collect(),
799 Stmt::Sorting(so) => match &mut **so {
800 Sorting::Sort(st) => [st.input.as_mut(), st.output.as_mut()]
801 .into_iter()
802 .flatten()
803 .flat_map(|io| match io {
804 SortIo::Procedure { from, thru } => std::iter::once(from).chain(thru.as_mut()).collect(),
805 SortIo::Files(_) => Vec::new(),
806 })
807 .collect(),
808 _ => Vec::new(),
809 },
810 _ => Vec::new(),
811 }
812}
813
814#[derive(Clone, Debug, PartialEq, Eq)]
816pub struct Unnamed {
817 pub message: Message,
818 pub text: String,
819}
820
821impl Unnamed {
822 pub fn at(self, pos: Pos) -> Error {
823 self.message.at(pos, self.text)
824 }
825}
826
827impl std::fmt::Display for Unnamed {
828 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
829 f.write_str(&self.text)
830 }
831}
832
833impl From<Unnamed> for String {
834 fn from(u: Unnamed) -> Self {
835 u.text
836 }
837}
838
839pub fn procedure(program: &Program, p: &ProcName) -> Result<(usize, usize), Unnamed> {
841 let found: Vec<usize> = program
842 .paragraphs
843 .iter()
844 .enumerate()
845 .filter(|(_, q)| q.name == p.name && (p.section.is_none() || (q.section == p.section && !q.is_section)))
846 .map(|(i, _)| i)
847 .collect();
848 match found.as_slice() {
849 [i] if program.paragraphs[*i].is_section => Ok((*i, section_end(program, *i))),
850 [i] => Ok((*i, *i)),
851 [] => Err(Unnamed { message: IWC0003, text: format!("no paragraph or section named {}", p.name) }),
852 _ => Err(Unnamed { message: IWC0004, text: format!("{} names more than one paragraph; qualify it with OF and its section", p.name) }),
853 }
854}
855
856fn top_level_tables(entries: &mut Vec<DataEntry>, errors: &mut Vec<Error>) -> Vec<usize> {
863 let mut inserted = Vec::new();
864 let mut at = 0;
865 while at < entries.len() {
866 let e = &entries[at];
867 let end = at + 1 + entries[at + 1..].iter().take_while(|d| (2..=49).contains(&d.level) || d.level == 88).count();
868 let movable = matches!(e.level, 1 | 77) && e.occurs.is_some() && e.redefines.is_none() && !e.external && !e.global && entries[at + 1..end].iter().all(|d| d.level != 49);
869 if !movable {
870 at = end.max(at + 1);
871 continue;
872 }
873 let name = e.name.clone().unwrap_or_else(|| "FILLER".into());
874 errors.push(syntax::messages::IWX0019.at(e.pos, format!("OCCURS at level {:02} (Micro Focus and GnuCOBOL; Enterprise COBOL takes OCCURS only at levels 02 to 49): {name} is read as a table in a record of its own", e.level)));
875 let record = DataEntry { level: 1, name: None, spelled: None, picture: None, usage: None, value: None, redefines: None, occurs: None, occurs_min: None, depending_on: None, sign: None, justified: false, sync: false, blank_when_zero: false, indexed_by: Vec::new(), keys: Vec::new(), condition_values: Vec::new(), false_value: None, renames: None, object_class: None, external: false, global: false, any_length: false, based: false, pos: e.pos };
876 for d in &mut entries[at..end] {
877 if d.level != 88 {
878 d.level = if d.level == 77 { 2 } else { d.level + 1 };
879 }
880 }
881 entries.insert(at, record);
882 inserted.push(at - inserted.len());
883 at = end + 1;
884 }
885 inserted
886}
887
888fn renames_inside_records(entries: &mut [DataEntry], errors: &mut Vec<Error>) {
894 let mut at = 0;
895 while at < entries.len() {
896 if entries[at].level != 66 {
897 at += 1;
898 continue;
899 }
900 let run = entries[at..].iter().take_while(|e| e.level == 66).count();
901 let next = at + run;
902 if !entries.get(next).is_some_and(|e| (2..=49).contains(&e.level)) {
903 at = next;
904 continue;
905 }
906 let record = entries[..at].iter().rev().find(|e| e.level == 1).and_then(|e| e.name.clone()).unwrap_or_else(|| "FILLER".into());
907 let end = next + entries[next..].iter().take_while(|e| !matches!(e.level, 1 | 66 | 77)).count();
908 for e in &entries[at..next] {
909 let name = e.name.as_deref().unwrap_or("FILLER");
910 errors.push(syntax::messages::IWX0032.at(e.pos, format!("a level-66 entry before the end of its record (GnuCOBOL's IBM and Micro Focus dialects; Enterprise COBOL writes a record's RENAMES entries after its last entry): {name} is read as following {record}'s last entry")));
911 }
912 entries[at..end].rotate_left(run);
913 at = end - run;
914 }
915}
916
917fn forever_names(program: &Program, errors: &mut Vec<Error>) {
920 let mut endless = false;
921 for p in &program.paragraphs {
922 oo::each(&p.statements, &mut |s| endless |= matches!(s, Stmt::PerformInline { repeat: Loop::Forever, .. } | Stmt::PerformProc { repeat: Loop::Forever, .. }));
923 }
924 if !endless {
925 return;
926 }
927 for p in program.paragraphs.iter().filter(|p| p.name == "FOREVER") {
928 let what = if p.is_section { "SECTION" } else { "paragraph" };
929 errors.push(syntax::messages::IWC0304.at(p.pos, format!("{what} FOREVER: under --compliance extended PERFORM FOREVER is Micro Focus's and GnuCOBOL's endless loop, not a PERFORM of it; compile the program under strict")));
930 }
931}
932
933fn based_items(program: &mut Program, declared: usize, errors: &mut Vec<Error>) -> usize {
938 let mut moved = Vec::new();
939 let mut declared_left = declared;
940 for (entries, storage) in [(&mut program.working_storage, true), (&mut program.local_storage, false)] {
941 let mut kept = Vec::with_capacity(entries.len());
942 let mut taking = false;
943 for (k, e) in std::mem::take(entries).into_iter().enumerate() {
944 if e.based && !matches!(e.level, 1 | 77) {
945 errors.push(syntax::messages::IWR0077.at(e.pos, format!("BASED on {}: ironwork reads BASED on an 01 or 77 entry", e.name.as_deref().unwrap_or("FILLER"))));
946 }
947 if matches!(e.level, 1 | 77) {
948 taking = e.based;
949 if taking {
950 let name = e.name.as_deref().unwrap_or("FILLER");
951 errors.push(syntax::messages::IWX0046.at(e.pos, format!("BASED (GnuCOBOL and Micro Focus; Enterprise COBOL describes such an item in the LINKAGE SECTION): {name} has no storage until SET ADDRESS OF gives it some")));
952 }
953 }
954 if taking {
955 declared_left -= usize::from(storage && k < declared);
956 moved.push(e);
957 } else {
958 kept.push(e);
959 }
960 }
961 *entries = kept;
962 }
963 program.linkage.extend(moved);
964 declared_left
965}
966
967fn binary_chars(program: &Program, options: &Options, errors: &mut Vec<Error>) {
970 let entries = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(program.files.iter().flat_map(|f| &f.records));
971 for e in entries {
972 let Some(Usage::BinaryChar { signed }) = e.usage else { continue };
973 if options.compliance == numeric::Compliance::Strict {
974 errors.push(syntax::messages::IWC0293.at(e.pos, "BINARY-CHAR: Micro Focus's and GnuCOBOL's one-byte binary, not Enterprise COBOL's; --compliance extended reads it"));
975 continue;
976 }
977 let name = e.name.as_deref().unwrap_or("FILLER");
978 let range = if signed { "-128 to 127" } else { "0 to 255" };
979 errors.push(syntax::messages::IWX0016.at(e.pos, format!("BINARY-CHAR (Micro Focus and GnuCOBOL; Enterprise COBOL's binary items are two, four or eight bytes): {name} is one byte of binary, {range}")));
980 }
981}
982
983fn micro_focus_binaries(program: &Program, options: &Options, errors: &mut Vec<Error>) {
987 let entries = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(program.files.iter().flat_map(|f| &f.records));
988 for e in entries {
989 let Some(usage @ (Usage::CompX | Usage::NativeBinary)) = e.usage else { continue };
990 let Some(pic) = e.picture.as_deref().and_then(|p| picture::analyse(p).ok()) else { continue };
991 let (native, digits, signed) = match (usage, pic.category) {
992 (Usage::CompX, picture::Category::Numeric) if pic.digits <= 18 => (numeric::Native::CompX, pic.digits as u8, pic.signed),
993 (Usage::CompX, picture::Category::Alphanumeric) if pic.size <= 8 => (numeric::Native::CompX, numeric::binary::alphanumeric_digits(pic.size), false),
994 (Usage::NativeBinary, picture::Category::Alphanumeric) if pic.size <= 8 => (numeric::Native::Comp5Bytes, numeric::binary::alphanumeric_digits(pic.size), false),
995 _ => continue,
996 };
997 if options.compliance == numeric::Compliance::Strict {
998 errors.push(if native == numeric::Native::CompX {
999 syntax::messages::IWC0301.at(e.pos, "COMP-X: Micro Focus's binary in the fewest bytes its digits need, not Enterprise COBOL's; --compliance extended reads it")
1000 } else {
1001 syntax::messages::IWC0302.at(e.pos, "PIC X(n) COMP-5: Micro Focus's and GnuCOBOL's binary of n bytes, where Enterprise COBOL's COMP-5 takes a numeric PICTURE; --compliance extended reads it")
1002 });
1003 continue;
1004 }
1005 let name = e.name.as_deref().unwrap_or("FILLER");
1006 let bytes = numeric::binary::Binary { digits, signed, native }.bytes();
1007 let bits = 8 * bytes as u32;
1008 let range = if signed { format!("-{} to {}", 1u64 << (bits - 1), (1u64 << (bits - 1)) - 1) } else { format!("0 to {}", u64::MAX >> (64 - bits)) };
1009 let plural = if bytes == 1 { "" } else { "s" };
1010 errors.push(if native == numeric::Native::CompX {
1011 syntax::messages::IWX0025.at(e.pos, format!("COMP-X (Micro Focus; Enterprise COBOL's binary items are two, four or eight bytes): {name} is {bytes} byte{plural} of binary, {range}, shown in {digits} digits"))
1012 } else {
1013 syntax::messages::IWX0026.at(e.pos, format!("PIC X(n) COMP-5 (Micro Focus and GnuCOBOL; Enterprise COBOL's COMP-5 takes a numeric PICTURE): {name} is {bytes} byte{plural} of binary, {range}"))
1014 });
1015 }
1016}
1017
1018fn digit_limits(program: &Program, arith: numeric::options::Arith, errors: &mut Vec<Error>) {
1022 let max = arith.max_picture_digits();
1023 let option = match arith {
1024 numeric::options::Arith::Compat => "ARITH(COMPAT)",
1025 numeric::options::Arith::Extend => "ARITH(EXTEND)",
1026 };
1027 let entries = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(program.files.iter().flat_map(|f| &f.records));
1028 for e in entries {
1029 if let Some(p) = e.picture.as_deref()
1030 && let Ok(pic) = picture::analyse_with(p, crate::picture::Notation::of(&program.environment))
1031 && matches!(pic.category, picture::Category::Numeric | picture::Category::NumericEdited)
1032 {
1033 let positions = pic.digits + pic.scaling + pic.scale.saturating_sub(pic.digits);
1034 if positions > max {
1035 errors.push(syntax::messages::IWC0069.at(e.pos, format!("PICTURE {p}: {positions} digit positions, more than the {max} {option} allows")));
1036 }
1037 }
1038 let values = e.value.iter().chain(e.condition_values.iter().flat_map(|(low, high)| std::iter::once(low).chain(high))).chain(&e.false_value);
1039 for v in values {
1040 if let Literal::Number(t) = v
1041 && literal_digits(t) > max as usize
1042 {
1043 errors.push(syntax::messages::IWC0070.at(e.pos, format!("the literal {t} has more than the {max} digits {option} allows")));
1044 }
1045 }
1046 }
1047}
1048
1049fn literal_digits(t: &str) -> usize {
1050 t.chars().filter(char::is_ascii_digit).count()
1051}
1052
1053#[derive(Clone, Copy, PartialEq, Eq)]
1055enum CharUsage {
1056 Display,
1057 National,
1058 Dbcs,
1059}
1060
1061impl CharUsage {
1062 fn of(kind: rt::storage::Kind) -> Self {
1063 match kind {
1064 rt::storage::Kind::National => Self::National,
1065 rt::storage::Kind::Dbcs { .. } => Self::Dbcs,
1066 _ => Self::Display,
1067 }
1068 }
1069}
1070
1071impl std::fmt::Display for CharUsage {
1072 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1073 f.write_str(match self {
1074 Self::Display => "DISPLAY",
1075 Self::National => "national",
1076 Self::Dbcs => "DBCS",
1077 })
1078 }
1079}
1080
1081fn inspected_literal(l: &Literal) -> Option<(&'static str, CharUsage)> {
1084 match l {
1085 Literal::Figurative(_) => None,
1086 Literal::All(inner) => inspected_literal(inner),
1087 Literal::National(_) => Some(("a national literal", CharUsage::National)),
1088 Literal::Dbcs(_) => Some(("a DBCS literal", CharUsage::Dbcs)),
1089 Literal::Number(_) => Some(("a numeric literal", CharUsage::Display)),
1090 Literal::Alnum(_) | Literal::Hex(_) => Some(("an alphanumeric literal", CharUsage::Display)),
1091 }
1092}
1093
1094fn flag_conditions(flag: &Flag) -> Vec<&Ref> {
1097 match flag {
1098 Flag::Condition(c) => vec![c],
1099 Flag::Conditions(on, off) => vec![on, off],
1100 Flag::Literals(..) => Vec::new(),
1101 }
1102}
1103
1104struct Check<'a> {
1105 layout: &'a Layout,
1106 program: &'a Program,
1107 errors: &'a mut Vec<Error>,
1108 debugging: bool,
1110 max_digits: u32,
1112 inline_performs: usize,
1114 functions: Option<&'a [function::Udf]>,
1116 alphabetic: &'a [Pos],
1118 at: Pos,
1120 paragraph: usize,
1122 extended: bool,
1124 environment_named: Option<Pos>,
1127}
1128
1129impl Check<'_> {
1130 fn statements(&mut self, stmts: &[Stmt]) {
1131 for s in stmts {
1132 self.statement(s);
1133 }
1134 }
1135
1136 fn statement(&mut self, s: &Stmt) {
1137 linage::check_receivers(self.layout, s, self.errors);
1138 if let Stmt::If { pos, .. } | Stmt::Evaluate { pos, .. } | Stmt::PerformInline { pos, .. } | Stmt::PerformProc { pos, .. } = s {
1139 self.at = *pos;
1140 }
1141 if let Stmt::Search(se) = s {
1142 self.at = se.pos;
1143 }
1144 match s {
1145 Stmt::Move { from, to, .. } => {
1146 self.operand(from);
1147 if let Operand::Function(f) = from {
1148 self.moved_function(f);
1149 }
1150 to.iter().for_each(|r| self.reference(r));
1151 }
1152 Stmt::Compute { targets, expr, size_error, .. } => {
1153 for t in targets {
1154 self.reference(&t.r);
1155 self.arithmetic_receiver("COMPUTE", &t.r);
1156 }
1157 self.expr(expr);
1158 self.size_error(size_error.as_ref());
1159 }
1160 Stmt::Arith(a) => {
1161 let verb = match a.verb {
1162 ArithVerb::Add => "ADD",
1163 ArithVerb::Subtract => "SUBTRACT",
1164 ArithVerb::Multiply => "MULTIPLY",
1165 ArithVerb::Divide => "DIVIDE",
1166 };
1167 for (t, e) in &a.computations {
1168 self.reference(&t.r);
1169 self.arithmetic_receiver(verb, &t.r);
1170 self.expr(e);
1171 }
1172 if let Some((t, x, y)) = &a.remainder {
1173 self.reference(&t.r);
1174 self.arithmetic_receiver(verb, &t.r);
1175 self.expr(x);
1176 self.expr(y);
1177 }
1178 self.size_error(a.size_error.as_ref());
1179 }
1180 Stmt::If { cond, then, otherwise, .. } => {
1181 self.cond(cond);
1182 self.statements(then);
1183 self.statements(otherwise);
1184 }
1185 Stmt::PerformInline { body, repeat, .. } => {
1186 self.repeat(repeat);
1187 self.inline_performs += 1;
1188 self.statements(body);
1189 self.inline_performs -= 1;
1190 }
1191 Stmt::PerformProc { from, thru, repeat, pos } => {
1192 self.procedure(from, *pos);
1193 if let Some(t) = thru {
1194 self.procedure(t, *pos);
1195 }
1196 self.repeat(repeat);
1197 }
1198 Stmt::Evaluate { subjects, whens, other, pos } => {
1199 for subject in subjects {
1200 match subject {
1201 Subject::Expr(e) => self.expr(e),
1202 Subject::Cond(c) => self.cond(c),
1203 Subject::Bool(_) => {}
1204 }
1205 }
1206 for w in whens {
1207 for alternative in &w.alternatives {
1208 for (subject, object) in subjects.iter().zip(alternative) {
1209 match object {
1210 Object::Any => {}
1211 Object::Bool(_) | Object::Cond(_) if matches!(subject, Subject::Expr(_)) => {
1212 self.errors.push(syntax::messages::IWC0071.at(*pos, "a condition as the WHEN object of a value subject"));
1213 }
1214 Object::Bool(_) => {}
1215 Object::Cond(c) => self.cond(c),
1216 Object::Value { .. } if !matches!(subject, Subject::Expr(_)) => {
1217 self.errors.push(syntax::messages::IWC0072.at(*pos, "a value as the WHEN object of a TRUE, FALSE or condition subject"));
1218 }
1219 Object::Value { from, thru, .. } => {
1220 self.expr(from);
1221 if let Some(t) = thru {
1222 self.expr(t);
1223 }
1224 if let Subject::Expr(e) = subject {
1225 std::iter::once(from).chain(thru).for_each(|o| self.comparison(e, o));
1226 }
1227 }
1228 }
1229 }
1230 }
1231 self.statements(&w.body);
1232 }
1233 self.statements(other);
1234 }
1235 Stmt::Display { items, upon, screen, pos, .. } => {
1236 for o in items {
1237 self.operand(o);
1238 if let Operand::Function(f) = o {
1239 self.displayed_function(f);
1240 }
1241 }
1242 if let Some(screen) = screen.as_deref().filter(|s| s.screen.is_none()) {
1243 self.screen("DISPLAY", screen);
1244 }
1245 if let Some(upon) = upon
1246 && matches!(upon.device.as_str(), "ENVIRONMENT-NAME" | "ENVIRONMENT-VALUE")
1247 {
1248 self.environment_display(upon, *pos);
1249 } else if let Some(upon) = upon
1250 && upon.device == "ARGUMENT-NUMBER"
1251 {
1252 self.argument_number(items, *pos);
1253 } else if let Some(upon) = upon
1254 && upon.device == "SYSERR"
1255 && self.extended
1256 {
1257 self.errors.push(syntax::messages::IWX0056.at(*pos, "DISPLAY UPON SYSERR (GnuCOBOL and Micro Focus; Enterprise COBOL has no such device): the line is written to the run's standard error"));
1258 } else if let Some(upon) = upon
1259 && !DISPLAY_DEVICES.contains(&upon.device.as_str())
1260 {
1261 let why = if upon.name == upon.device {
1262 "neither an environment-name DISPLAY writes to, SYSOUT, SYSLIST, SYSLST, SYSPUNCH, SYSPCH or CONSOLE, nor a mnemonic-name for one".to_owned()
1263 } else {
1264 format!("a mnemonic-name for {}, which DISPLAY does not write to", upon.device)
1265 };
1266 self.errors.push(syntax::messages::IWC0073.at(*pos, format!("DISPLAY UPON {}: {why}", upon.name)));
1267 }
1268 }
1269 Stmt::Open { files, pos } => files.iter().for_each(|(_, f)| self.file(f, *pos)),
1270 Stmt::DeleteFile { files, pos } => files.iter().for_each(|name| self.file(name, *pos)),
1271 Stmt::Close { files, pos } => {
1272 for (name, closing) in files {
1273 self.file(name, *pos);
1274 let keyed = self.program.files.iter().any(|f| f.name == *name && matches!(f.organization, Organization::Indexed | Organization::Relative));
1275 if keyed && matches!(closing, Some(Closing::Volume | Closing::NoRewind)) {
1276 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")));
1277 }
1278 }
1279 }
1280 Stmt::Read(r) => {
1281 self.file(&r.file, r.pos);
1282 if r.next {
1283 self.not_random(&r.file, "READ NEXT", r.pos);
1284 }
1285 if let Some(into) = &r.into {
1286 self.reference(into);
1287 }
1288 if let Some(key) = &r.key {
1289 self.key_of(&r.file, key, false);
1290 }
1291 self.handlers(&r.at_end);
1292 self.handlers(&r.invalid);
1293 }
1294 Stmt::Write { record, from, invalid, pos, .. } | Stmt::Rewrite { record, from, invalid, pos } => {
1295 let verb = if matches!(s, Stmt::Write { .. }) { "WRITE" } else { "REWRITE" };
1296 self.reference(record);
1297 if let Ok(layout::Resolved::Item(i)) = self.layout.resolve(&record.name, &record.qualifiers, record.pos)
1298 && self.layout.items[i].file.is_none()
1299 {
1300 self.errors.push(syntax::messages::IWC0075.at(*pos, format!("{verb} {}: not a record of a file", record.name)));
1301 }
1302 if let Some(op) = from {
1303 self.operand(op);
1304 }
1305 if let Stmt::Write { advancing, end_of_page, .. } = s {
1306 if let Some(a) = advancing {
1307 if let Advancing::Lines { count, .. } = a {
1308 self.expr(count);
1309 }
1310 printer::check_write(self.program, self.layout, record, a, self.extended, *pos, self.errors);
1311 }
1312 linage::check_write(self.program, self.layout, record, advancing.as_ref(), end_of_page, *pos, self.errors);
1313 self.handlers(end_of_page);
1314 }
1315 self.handlers(invalid);
1316 }
1317 Stmt::Delete { file, invalid, pos } => {
1318 self.keyed_file(file, "DELETE", *pos);
1319 self.handlers(invalid);
1320 }
1321 Stmt::Start { file, key, invalid, pos } => {
1322 self.keyed_file(file, "START", *pos);
1323 self.not_random(file, "START", *pos);
1324 if let Some((op, r)) = key {
1325 if self.extended && matches!(op, RelOp::Lt | RelOp::Le) {
1326 let written = if *op == RelOp::Lt { "<" } else { "NOT > or <=" };
1327 self.errors.push(syntax::messages::IWX0036.at(*pos, format!("START KEY {written} (Micro Focus and GnuCOBOL; Enterprise COBOL's START takes =, >, NOT < or >=): the file is positioned at the last record whose key is {written} the value, which READ NEXT or READ PREVIOUS reads first")));
1328 } else if !matches!(op, RelOp::Eq | RelOp::Gt | RelOp::Ge) {
1329 self.errors.push(syntax::messages::IWC0076.at(*pos, "START KEY takes =, >, NOT < or >="));
1330 }
1331 self.key_of(file, r, true);
1332 }
1333 self.handlers(invalid);
1334 }
1335 Stmt::Initialize { targets, with, pos } => {
1336 for r in targets {
1337 self.reference(r);
1338 if self.item(r).is_some_and(|i| self.layout.items[i].level == 66) {
1339 self.errors.push(syntax::messages::IWC0077.at(*pos, format!("INITIALIZE {}: a level-66 RENAMES item cannot be initialized", r.name)));
1340 }
1341 let items = &self.layout.items;
1342 if self.item(r).is_some_and(|i| !items[i].moved_by.is_empty() || items[i].odo.iter().any(|&t| items[t].followed)) {
1343 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)));
1344 }
1345 }
1346 if let Some(with) = with {
1347 with.replacing.iter().for_each(|(_, by)| self.operand(by));
1348 self.initialize_incompatible(targets, with, *pos);
1349 }
1350 }
1351 Stmt::GoTo { target: Some(target), pos } => self.procedure(target, *pos),
1352 Stmt::GoToDepending { targets, on, pos } => {
1353 targets.iter().for_each(|t| self.procedure(t, *pos));
1354 self.reference(on);
1355 }
1356 Stmt::GoTo { target: None, .. } | Stmt::Alter { .. } | Stmt::Entry { .. } => {}
1357 Stmt::Call(c) => {
1358 self.operand(&c.target);
1359 for arg in &c.using {
1360 if let Some(op) = &arg.value {
1361 self.operand(op);
1362 }
1363 }
1364 if let Some(r) = &c.returning {
1365 self.reference(r);
1366 }
1367 self.statements(c.on_exception.as_deref().unwrap_or_default());
1368 self.statements(c.not_on_exception.as_deref().unwrap_or_default());
1369 }
1370 Stmt::Cancel { targets, .. } => targets.iter().for_each(|t| self.operand(t)),
1371 Stmt::Set { set, .. } => match set {
1372 SetStmt::ConditionTrue(targets) => {
1373 for r in targets {
1374 self.reference_or_condition(r);
1375 if let Ok(layout::Resolved::Condition(c)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
1376 && let Some(e) = switches::without_variable(self.layout, c, r)
1377 {
1378 self.errors.push(e);
1379 }
1380 }
1381 }
1382 SetStmt::ConditionFalse(targets) => {
1383 for r in targets {
1384 self.reference_or_condition(r);
1385 if let Ok(layout::Resolved::Condition(c)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
1386 && self.layout.conditions[c].false_value.is_none()
1387 {
1388 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)));
1389 }
1390 }
1391 }
1392 SetStmt::To { targets, value } | SetStmt::Entry { targets, entry: value } => {
1393 targets.iter().for_each(|r| self.reference(r));
1394 self.operand(value);
1395 }
1396 SetStmt::AddressOf { targets, value } => {
1397 targets.iter().for_each(|r| self.reference(r));
1398 targets.iter().for_each(|r| scope::set_address(self.layout, r, self.errors));
1399 self.operand(value);
1400 }
1401 SetStmt::UpDown { targets, by, .. } => {
1402 targets.iter().for_each(|r| self.reference(r));
1403 self.expr(by);
1404 }
1405 SetStmt::Switches(_) => {}
1406 },
1407 Stmt::Accept { target, from, exception, screen, pos } => {
1408 match screen {
1409 Some(screen) if screen.screen.is_some() => {
1410 for i in &screen.inputs {
1411 self.reference(&i.field);
1412 self.reference(&i.target);
1413 }
1414 }
1415 Some(screen) => {
1416 self.reference(target);
1417 self.screen("ACCEPT", screen);
1418 }
1419 None => {
1420 self.reference(target);
1421 self.accept_source(*from, exception, target, *pos);
1422 }
1423 }
1424 self.statements(exception.on.as_deref().unwrap_or_default());
1425 self.statements(exception.not_on.as_deref().unwrap_or_default());
1426 }
1427 Stmt::String(st) => {
1428 for (op, delimiter) in &st.sources {
1429 self.operand(op);
1430 if let Delimiter::By(d) = delimiter {
1431 self.operand(d);
1432 }
1433 }
1434 self.reference(&st.into);
1435 if let Some(p) = &st.pointer {
1436 self.reference(p);
1437 }
1438 self.statements(st.on_overflow.as_deref().unwrap_or_default());
1439 self.statements(st.not_on_overflow.as_deref().unwrap_or_default());
1440 }
1441 Stmt::Unstring(u) => {
1442 self.reference(&u.source);
1443 u.delimiters.iter().for_each(|(_, d)| self.operand(d));
1444 for into in &u.into {
1445 self.reference(&into.target);
1446 into.delimiter_in.iter().chain(&into.count_in).for_each(|r| self.reference(r));
1447 }
1448 u.pointer.iter().chain(&u.tallying).for_each(|r| self.reference(r));
1449 self.statements(u.on_overflow.as_deref().unwrap_or_default());
1450 self.statements(u.not_on_overflow.as_deref().unwrap_or_default());
1451 }
1452 Stmt::Inspect(i) => {
1453 self.operand(&i.target);
1454 match &i.target {
1455 Operand::Function(f) => self.inspected_function(f, i),
1456 Operand::Ref(r) => self.inspected_usage(r, i),
1457 _ => {}
1458 }
1459 for p in i.tallying.iter().chain(&i.replacing) {
1460 p.pattern.iter().chain(&p.by).for_each(|o| self.operand(o));
1461 if let Some(c) = &p.counter {
1462 self.reference(c);
1463 }
1464 p.bounds.iter().for_each(|b| self.operand(&b.value));
1465 }
1466 if let Some((from, to, bounds)) = &i.converting {
1467 self.operand(from);
1468 self.operand(to);
1469 bounds.iter().for_each(|b| self.operand(&b.value));
1470 }
1471 }
1472 Stmt::Search(se) => {
1473 self.reference_unsubscripted(&se.table);
1474 if let Some(v) = &se.varying {
1475 self.reference(v);
1476 self.search_varying(se, v);
1477 }
1478 self.statements(se.at_end.as_deref().unwrap_or_default());
1479 for (cond, body) in &se.whens {
1480 self.cond(cond);
1481 self.statements(body);
1482 }
1483 }
1484 Stmt::Exec(block) => self.exec_block(block),
1485 Stmt::Report(r) => report::check_statement(self.program, r, self.errors),
1486 Stmt::Invoke(i) => self.invoke(i),
1487 Stmt::XmlParse(x) => {
1488 self.reference(&x.document);
1489 if let Some(op) = &x.encoding {
1490 self.operand(op);
1491 }
1492 self.procedure(&x.procedure, x.pos);
1493 if let Some(t) = &x.thru {
1494 self.procedure(t, x.pos);
1495 }
1496 self.statements(x.on_exception.as_deref().unwrap_or_default());
1497 self.statements(x.not_on_exception.as_deref().unwrap_or_default());
1498 }
1499 Stmt::JsonParse(j) => {
1500 self.reference(&j.source);
1501 self.whole_table_reference(&j.into);
1502 let mut named: Vec<&Ref> = j.names.iter().map(|(r, _)| r).chain(&j.suppress).chain(j.ignoring.iter().flatten()).collect();
1503 for (item, flag, indicator) in &j.indicating {
1504 named.push(item);
1505 named.extend(indicator);
1506 named.extend(flag_conditions(flag));
1507 }
1508 for (item, conversion) in &j.converting {
1509 named.push(item);
1510 if let ParseConversion::Boolean(flag) = conversion {
1511 named.extend(flag_conditions(flag));
1512 }
1513 }
1514 named.into_iter().for_each(|r| self.reference_unsubscripted(r));
1515 if let Some(Encoding::Ccsid(op)) = &j.encoding {
1516 self.operand(op);
1517 }
1518 self.statements(j.on_exception.as_deref().unwrap_or_default());
1519 self.statements(j.not_on_exception.as_deref().unwrap_or_default());
1520 }
1521 Stmt::XmlGenerate(x) => {
1522 for r in [&x.receiver, &x.from].into_iter().chain(&x.count) {
1523 self.reference(r);
1524 }
1525 let named = x.names.iter().map(|(r, _)| r).chain(x.types.iter().map(|(r, _)| r));
1526 let suppressed = x.suppress.iter().filter_map(|s| if let Suppression::Item { item, .. } = s { Some(item) } else { None });
1527 named.chain(suppressed).for_each(|r| self.reference_unsubscripted(r));
1528 for op in [&x.encoding, &x.namespace, &x.prefix].into_iter().flatten() {
1529 self.operand(op);
1530 }
1531 self.statements(x.on_exception.as_deref().unwrap_or_default());
1532 self.statements(x.not_on_exception.as_deref().unwrap_or_default());
1533 }
1534 Stmt::JsonGenerate(g) => {
1535 for r in [&g.receiver].into_iter().chain(&g.count) {
1536 self.reference(r);
1537 }
1538 self.whole_table_reference(&g.from);
1539 let mut named: Vec<&Ref> = g.names.iter().map(|(r, _)| r).collect();
1540 for s in &g.suppress {
1541 if let Suppression::Item { item, .. } = s {
1542 named.push(item);
1543 }
1544 }
1545 for (item, conversion) in &g.converting {
1546 named.push(item);
1547 if let JsonConversion::Boolean(Marker::Condition(c)) = conversion {
1548 named.push(c);
1549 }
1550 }
1551 for i in &g.indicating {
1552 named.push(&i.item);
1553 named.extend(&i.indicator);
1554 if let Marker::Condition(c) = &i.marker {
1555 named.push(c);
1556 }
1557 }
1558 named.into_iter().for_each(|r| self.reference_unsubscripted(r));
1559 if let Some(Encoding::Ccsid(op)) = &g.encoding {
1560 self.operand(op);
1561 }
1562 self.statements(g.on_exception.as_deref().unwrap_or_default());
1563 self.statements(g.not_on_exception.as_deref().unwrap_or_default());
1564 }
1565 Stmt::Sorting(s) => self.sorting(s),
1566 Stmt::Exit { kind: kind @ (ExitKind::Perform | ExitKind::PerformCycle), pos } if self.inline_performs == 0 => {
1568 let exit = if *kind == ExitKind::Perform { "EXIT PERFORM" } else { "EXIT PERFORM CYCLE" };
1569 self.errors.push(syntax::messages::IWC0080.at(*pos, format!("{exit} must be inside an inline PERFORM")));
1570 }
1571 Stmt::Goback { .. } | Stmt::StopRun { .. } | Stmt::ExitProgram { .. } | Stmt::ExitMethod { .. } | Stmt::Continue { .. } | Stmt::Exit { .. } | Stmt::NextSentence | Stmt::SentenceEnd => {}
1572 Stmt::Corresponding(_) => unreachable!("CORRESPONDING is expanded before Check"),
1573 }
1574 }
1575
1576 fn size_error(&mut self, se: Option<&SizeError>) {
1577 if let Some(se) = se {
1578 self.statements(&se.on);
1579 self.statements(&se.not_on);
1580 }
1581 }
1582
1583 fn repeat(&mut self, repeat: &Loop) {
1584 match repeat {
1585 Loop::Once | Loop::Forever => {}
1586 Loop::Times(e) => self.expr(e),
1587 Loop::Until { cond, .. } => self.cond(cond),
1588 Loop::Varying { varying, after, .. } => {
1589 for v in std::iter::once(&**varying).chain(after) {
1590 self.reference(&v.var);
1591 self.expr(&v.from);
1592 self.expr(&v.by);
1593 self.cond(&v.until);
1594 self.varying(v);
1595 }
1596 }
1597 }
1598 }
1599
1600 fn varying(&mut self, v: &Varying) {
1603 use rt::storage::Kind;
1604 if let Some(i) = self.item(&v.var)
1605 && !matches!(self.layout.items[i].kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::Index)
1606 {
1607 self.errors.push(syntax::messages::IWC0081.at(self.at, format!("PERFORM VARYING {}: not a numeric elementary item or an index-name", v.var.name)));
1608 }
1609 for (phrase, e) in [("FROM", &v.from), ("BY", &v.by)] {
1610 if !matches!(e, Expr::Operand(_)) {
1611 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)));
1612 }
1613 }
1614 }
1615
1616 fn file(&mut self, name: &str, pos: Pos) {
1617 if !self.program.files.iter().any(|f| f.name == name) {
1618 self.errors.push(syntax::messages::IWC0083.at(pos, format!("no file named {name}")));
1619 }
1620 }
1621
1622 fn keyed_file(&mut self, name: &str, verb: &str, pos: Pos) {
1623 match self.program.files.iter().find(|f| f.name == name) {
1624 None => self.errors.push(syntax::messages::IWC0083.at(pos, format!("no file named {name}"))),
1625 Some(f) if !matches!(f.organization, Organization::Indexed | Organization::Relative) => {
1626 self.errors.push(syntax::messages::IWC0084.at(pos, format!("{verb} {name}: not an indexed or relative file")));
1627 }
1628 Some(_) => {}
1629 }
1630 }
1631
1632 fn not_random(&mut self, name: &str, verb: &str, pos: Pos) {
1633 if self.program.files.iter().any(|f| f.name == name && f.access == Access::Random) {
1634 self.errors.push(syntax::messages::IWC0085.at(pos, format!("{verb} {name}: the file's ACCESS MODE is RANDOM")));
1635 }
1636 }
1637
1638 fn handlers(&mut self, h: &Handlers) {
1639 self.statements(h.on.as_deref().unwrap_or_default());
1640 self.statements(h.not_on.as_deref().unwrap_or_default());
1641 }
1642
1643 fn class_name_subject(&mut self, e: &Expr, name: &str) {
1646 let Expr::Operand(Operand::Ref(r)) = e else {
1647 self.errors.push(syntax::messages::IWC0086.at(self.at, format!("class-name {name} tests a data item, not an expression")));
1648 return;
1649 };
1650 let Some(i) = self.item(r) else { return };
1651 if !matches!(self.layout.items[i].kind, layout::Kind::Group | layout::Kind::Alnum { .. } | layout::Kind::AlnumEdited { .. } | layout::Kind::NumericEdited { .. } | layout::Kind::Zoned { .. }) {
1652 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)));
1653 }
1654 }
1655
1656 fn item(&self, r: &Ref) -> Option<usize> {
1657 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1658 Ok(layout::Resolved::Item(i)) => Some(i),
1659 _ => None,
1660 }
1661 }
1662
1663 fn key_of(&mut self, file: &str, key: &Ref, partial: bool) {
1666 let Some(f) = self.program.files.iter().find(|f| f.name == file) else { return self.reference(key) };
1667 if key.qualifiers.is_empty() && f.split_key(&key.name).is_some() {
1668 return;
1669 }
1670 self.reference(key);
1671 if f.organization != Organization::Indexed {
1672 return;
1673 }
1674 let Some(item) = self.item(key).map(|i| &self.layout.items[i]) else { return };
1675 let fits = |r: &Ref| {
1676 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))
1677 };
1678 let named = f.record_key.iter().chain(f.alternate_keys.iter().map(|(r, _)| r)).any(fits);
1679 if item.file.is_none() || !named {
1680 self.errors.push(syntax::messages::IWC0088.at(key.pos, format!("{}: not a key of {file}", key.name)));
1681 }
1682 }
1683
1684 fn file_keys(&mut self, k: usize) {
1686 let f = &self.program.files[k];
1687 let in_records = |c: &Self, r: &Ref| c.item(r).is_some_and(|i| c.layout.items[i].file == Some(k as u16));
1688 match f.organization {
1689 Organization::Indexed => {
1690 if f.record_key.is_none() {
1691 self.errors.push(syntax::messages::IWC0089.at(f.pos, format!("{}: an indexed file needs a RECORD KEY", f.name)));
1692 }
1693 let keys = f.record_key.iter().chain(f.alternate_keys.iter().map(|(r, _)| r));
1694 let items: Vec<Ref> = keys.flat_map(|r| f.split_key(&r.name).map_or_else(|| vec![r.clone()], <[Ref]>::to_vec)).collect();
1695 for r in &items {
1696 self.reference(r);
1697 if self.item(r).is_some() && !in_records(self, r) {
1698 self.errors.push(syntax::messages::IWC0090.at(r.pos, format!("{}: a key of {} must be in its records", r.name, f.name)));
1699 }
1700 }
1701 }
1702 Organization::Relative => {
1703 if let Some(r) = &f.relative_key {
1704 self.reference(r);
1705 if in_records(self, r) {
1706 self.errors.push(syntax::messages::IWC0091.at(r.pos, format!("{}: the RELATIVE KEY of {} must not be in its records", r.name, f.name)));
1707 }
1708 } else if f.access != Access::Sequential {
1709 self.errors.push(syntax::messages::IWC0092.at(f.pos, format!("{}: random or dynamic access needs a RELATIVE KEY", f.name)));
1710 }
1711 }
1712 _ => {}
1713 }
1714 }
1715
1716 fn record_depending(&mut self, k: usize) {
1719 let f = &self.program.files[k];
1720 let Some(r) = &f.record_depending else { return };
1721 self.reference(r);
1722 if self.item(r).is_some() && linage::unsigned_integer(self.layout, r).is_none() {
1723 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)));
1724 }
1725 }
1726
1727 fn label_records(&mut self, k: usize) {
1730 let f = &self.program.files[k];
1731 for r in &f.label_records {
1732 if self.layout.resolve(&r.name, &r.qualifiers, r.pos).is_err() {
1733 self.errors.push(syntax::messages::IWC0324.at(r.pos, format!("LABEL RECORDS {}: not defined as a data-name", r.name)));
1734 }
1735 }
1736 }
1737
1738 fn recording_mode(&mut self, k: usize) {
1742 let f = &self.program.files[k];
1743 if f.recording != Some('F') {
1744 return;
1745 }
1746 let declared = (f.record_min, f.record_max);
1747 let lengths = match declared {
1748 (Some(min), Some(max)) if f.record_varying || min != max => Some((min, max)),
1749 _ => self.layout.record_lengths[k].filter(|(shortest, longest)| shortest != longest),
1750 };
1751 if let Some((shortest, longest)) = lengths {
1752 self.errors.push(syntax::messages::IWC0325.at(f.pos, format!("FD {}: RECORDING MODE F, but its records are {shortest} to {longest} bytes", f.name)));
1753 }
1754 }
1755
1756 fn passwords(&mut self, k: usize) {
1759 let f = &self.program.files[k];
1760 for r in &f.passwords {
1761 self.reference(r);
1762 let Some(i) = self.item(r) else { continue };
1763 let item = &self.layout.items[i];
1764 let working = !item.local && item.file.is_none() && item.linkage.is_none();
1765 if !working || !matches!(item.kind, rt::storage::Kind::Alnum { .. } | rt::storage::Kind::AlnumEdited { .. } | rt::storage::Kind::Group) {
1766 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)));
1767 }
1768 }
1769 }
1770
1771 fn procedure(&mut self, p: &ProcName, pos: Pos) {
1772 if let Err(m) = procedure(self.program, p) {
1773 self.errors.push(m.at(pos));
1774 }
1775 }
1776
1777 fn reference(&mut self, r: &Ref) {
1780 self.reference_or_condition(r);
1781 if let Ok(layout::Resolved::Condition(_)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1782 self.errors.push(syntax::messages::IWC0095.at(r.pos, format!("{} is a condition-name, not a data item", r.name)));
1783 }
1784 }
1785
1786 fn arithmetic_receiver(&mut self, verb: &str, r: &Ref) {
1791 let Some(i) = self.item(r) else { return };
1792 let kind = self.layout.items[i].kind;
1793 if kind.is_numeric() || matches!(kind, rt::storage::Kind::NumericEdited { .. }) {
1794 return;
1795 }
1796 let class = match kind {
1797 rt::storage::Kind::Group => "a group",
1798 rt::storage::Kind::Alnum { .. } => "alphanumeric",
1799 rt::storage::Kind::AlnumEdited { .. } => "alphanumeric-edited",
1800 rt::storage::Kind::National => "national",
1801 rt::storage::Kind::Pointer | rt::storage::Kind::ProgramPointer => "a pointer",
1802 _ => "not numeric",
1803 };
1804 self.errors.push(syntax::messages::IWC0323.at(r.pos, format!("{verb} {}: a receiving operand of an arithmetic statement must be numeric or numeric-edited, and {} is {class}", r.name, r.name)));
1805 }
1806
1807 fn reference_or_condition(&mut self, r: &Ref) {
1809 if r.name == "RETURN-CODE" && r.qualifiers.is_empty() && self.layout.resolve(&r.name, &r.qualifiers, r.pos).is_err() {
1810 return;
1811 }
1812 if oo::special_register(self.layout, r) || markup::xml_register(self.layout, r) {
1813 return;
1814 }
1815 if !self.debugging && !self.program.declaratives.debugging.is_empty() && declaratives::DEBUG_ITEM_NAMES.contains(&r.name.as_str()) {
1816 self.errors.push(syntax::messages::IWC0096.at(r.pos, format!("{}: only a debugging section may reference DEBUG-ITEM", r.name)));
1817 return;
1818 }
1819 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1820 Err(e) => self.errors.push(e),
1821 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)),
1822 Ok(layout::Resolved::Item(i)) if self.layout.items[i].dims.len() != r.subscripts.len() => self.errors.push(syntax::messages::IWC0097.at(
1823 r.pos,
1824 format!("{} takes {} subscripts, not {}", r.name, self.layout.items[i].dims.len(), r.subscripts.len()),
1825 )),
1826 Ok(_) => {}
1827 }
1828 r.subscripts.iter().for_each(|e| self.expr(e));
1829 if let Some(rm) = &r.refmod {
1830 self.expr(&rm.start);
1831 if let Some(l) = &rm.length {
1832 self.expr(l);
1833 }
1834 }
1835 }
1836
1837 fn exec_block(&mut self, block: &ExecBlock) {
1839 if block.kind == ExecKind::Dli {
1840 self.dli_block(block);
1841 }
1842 if block.kind == ExecKind::Cics && matches!(block.command.as_str(), "HANDLE CONDITION" | "HANDLE AID" | "HANDLE ABEND") {
1843 self.handle_labels(block);
1844 }
1845 if let Some(syntax::sql::Sql { statement: syntax::sql::Statement::Malformed(why), .. }) = &block.sql {
1846 self.errors.push(syntax::messages::IWP0003.at(block.pos, format!("EXEC SQL {}: {why}", block.command)));
1847 }
1848 for r in block.sql.iter().flat_map(|sql| sql.statement.colonless_into()) {
1851 if self.extended {
1852 self.errors.push(syntax::messages::IWX0011.at(r.pos, format!("{INTO_WITHOUT_COLON}: {} is read as a host variable", r.name)));
1853 } else {
1854 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)));
1855 }
1856 }
1857 for r in &block.host_variables {
1858 if r.subscripts.is_empty() {
1859 self.reference_unsubscripted(r);
1860 } else {
1861 self.reference(r);
1862 }
1863 }
1864 for (_, arg) in &block.options {
1865 if let Some(ExecArg::Operand(op)) = arg {
1866 self.operand(op);
1867 }
1868 }
1869 }
1870
1871 fn initialize_incompatible(&mut self, targets: &[Ref], with: &InitializeWith, pos: Pos) {
1874 if !with.value.is_empty() || with.default {
1875 return;
1876 }
1877 for r in targets {
1878 let Some(i) = self.item(r) else { continue };
1879 let initialized = match r.refmod {
1880 Some(_) => with.initial_value(Some(self.layout.refmod_category(Some(i), self.layout.items[i].kind)), false).is_some(),
1881 None => self.layout.initialize_receivers(i, with.filler).iter().any(|&(e, _)| with.initial_value(self.layout.category(e), false).is_some()),
1882 };
1883 if !initialized {
1884 let categories: Vec<&str> = with.replacing.iter().map(|(c, _)| c.word()).collect();
1885 self.errors.push(syntax::messages::IWC0098.at(
1886 pos,
1887 format!("INITIALIZE {}: none of its items is of a category REPLACING names ({}), so it is not initialized", r.name, categories.join(", ")),
1888 ));
1889 }
1890 }
1891 }
1892
1893 fn whole_table_reference(&mut self, r: &Ref) {
1896 if let Ok(layout::Resolved::Item(i)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
1897 && self.layout.items[i].table
1898 && self.layout.items[i].dims.len() == r.subscripts.len() + 1
1899 {
1900 r.subscripts.iter().for_each(|e| self.expr(e));
1901 return;
1902 }
1903 self.reference(r);
1904 }
1905
1906 fn dli_block(&mut self, block: &ExecBlock) {
1908 let Some(command) = syntax::dli::find(&block.command) else {
1909 self.errors.push(syntax::messages::IWP0004.at(block.pos, format!("EXEC DLI {} is not an EXEC DLI command", block.command)));
1910 return;
1911 };
1912 for (name, arg) in &block.options {
1913 if command.options.is_some_and(|options| !options.contains(&name.as_str())) {
1914 self.errors.push(syntax::messages::IWP0005.at(block.pos, format!("EXEC DLI {}: {name} is not one of its options", command.name)));
1915 } else if let (true, Some(ExecArg::Text(t))) = (name == "WHERE", arg)
1916 && let Err(why) = syntax::dli::qualification(t)
1917 {
1918 self.errors.push(syntax::messages::IWP0006.at(block.pos, format!("EXEC DLI {} WHERE({t}): {why}", command.name)));
1919 }
1920 }
1921 }
1922
1923 fn inspected_function(&mut self, f: &FunctionCall, i: &Inspect) {
1927 let name = f.name.as_str();
1928 let known = self.intrinsic(name);
1929 let numeric_udf = self.functions.into_iter().flatten().any(|u| u.name == name && !u.character_valued());
1930 if known && !rt::intrinsic::CHARACTER_VALUED.contains(&name) || numeric_udf {
1931 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")));
1932 }
1933 let stores = if !i.replacing.is_empty() {
1934 Some("REPLACING")
1935 } else {
1936 i.converting.as_ref().map(|_| "CONVERTING")
1937 };
1938 if let Some(phrase) = stores {
1939 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")));
1940 }
1941 }
1942
1943 fn displayed_function(&mut self, f: &FunctionCall) {
1948 let name = f.name.as_str();
1949 let numeric = !rt::intrinsic::CHARACTER_VALUED.contains(&name) || self.numeric_max_or_min(f);
1950 if self.intrinsic(name) && numeric {
1951 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")));
1952 }
1953 }
1954
1955 fn screen(&mut self, statement: &str, phrases: &ScreenPhrases) {
1958 let mut placed = Vec::new();
1959 match &phrases.at {
1960 Some(ScreenAt::Combined(o)) => placed.push(o),
1961 Some(ScreenAt::LineColumn { line, column }) => placed.extend(line.iter().chain(column)),
1962 None => {}
1963 }
1964 for o in placed {
1965 self.operand(o);
1966 }
1967 if !self.extended {
1968 self.errors.push(syntax::messages::IWC0298.at(phrases.pos, format!("{statement} on the screen: Micro Focus's and GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it")));
1969 return;
1970 }
1971 let at = match &phrases.at {
1972 Some(ScreenAt::Combined(_)) => "the line and column AT gives",
1973 Some(ScreenAt::LineColumn { line: Some(_), column: Some(_) }) => "the line and column given",
1974 Some(ScreenAt::LineColumn { line: Some(_), column: None }) => "column 1 of the line given",
1975 Some(ScreenAt::LineColumn { line: None, .. }) => "the column given on the cursor's line",
1976 None => "the cursor",
1977 };
1978 self.errors.push(syntax::messages::IWX0020.at(phrases.pos, format!("{statement} on the screen (Micro Focus and GnuCOBOL; Enterprise COBOL has none): at {at}")));
1979 }
1980
1981 fn accept_source(&mut self, from: AcceptFrom, exception: &Handlers, target: &Ref, pos: Pos) {
1985 if (exception.on.is_some() || exception.not_on.is_some()) && !matches!(from, AcceptFrom::ArgumentValue | AcceptFrom::EnvironmentValue) {
1986 self.errors.push(syntax::messages::IWC0256.at(pos, "ACCEPT ... ON EXCEPTION: of the ACCEPT statements, only ACCEPT ... FROM ARGUMENT-VALUE and the environment under --compliance extended have an exception"));
1987 }
1988 if from == AcceptFrom::EnvironmentValue {
1989 let named = self.environment_named.take() == Some(pos);
1990 let written = if named { "ACCEPT ... FROM ENVIRONMENT" } else { "ACCEPT ... FROM ENVIRONMENT-VALUE" };
1991 if self.extended {
1992 self.errors.push(syntax::messages::IWX0021.at(pos, format!("{written} (Micro Focus and GnuCOBOL; Enterprise COBOL reads and sets no environment variable): {} receives the value of the environment variable named, or spaces and the exception when it is not set", target.name)));
1993 } else if named {
1994 self.errors.push(syntax::messages::IWS0055.at(pos, "ACCEPT ... FROM ENVIRONMENT is GnuCOBOL's, not Enterprise COBOL's"));
1995 } else {
1996 self.errors.push(syntax::messages::IWS0060.at(pos, "ACCEPT ... FROM ENVIRONMENT-VALUE: GnuCOBOL's, not Enterprise COBOL's"));
1997 }
1998 return;
1999 }
2000 let (name, what) = match from {
2001 AcceptFrom::CommandLine => ("COMMAND-LINE", "the job step's PARM program arguments"),
2002 AcceptFrom::ArgumentNumber => ("ARGUMENT-NUMBER", "how many words the job step's PARM program arguments hold"),
2003 AcceptFrom::ArgumentValue => ("ARGUMENT-VALUE", "the next word of the job step's PARM program arguments"),
2004 _ => return,
2005 };
2006 if self.extended {
2007 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)));
2008 } else {
2009 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")));
2010 }
2011 }
2012
2013 fn environment_display(&mut self, upon: &Upon, pos: Pos) {
2018 let value = upon.device == "ENVIRONMENT-VALUE";
2019 match upon.name.as_str() {
2020 "ENVIRONMENT" => self.environment_named = Some(pos),
2021 "SET ENVIRONMENT" if !value => {}
2022 "SET ENVIRONMENT" if self.extended => self.errors.push(syntax::messages::IWX0021.at(pos, "SET ENVIRONMENT (Micro Focus and GnuCOBOL; Enterprise COBOL reads and sets no environment variable): the environment variable named takes the value")),
2023 "SET ENVIRONMENT" => self.errors.push(syntax::messages::IWS0061.at(pos, "SET ENVIRONMENT is GnuCOBOL's, not Enterprise COBOL's")),
2024 _ if self.extended => {
2025 let what = if value { "the environment variable ENVIRONMENT-NAME last named takes the value" } else { "names the environment variable ENVIRONMENT-VALUE reads and sets next" };
2026 self.errors.push(syntax::messages::IWX0021.at(pos, format!("DISPLAY UPON {} (Micro Focus and GnuCOBOL; Enterprise COBOL reads and sets no environment variable): {what}", upon.device)));
2027 }
2028 _ => self.errors.push(syntax::messages::IWC0073.at(pos, format!("DISPLAY UPON {}: Micro Focus's and GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it", upon.name))),
2029 }
2030 }
2031
2032 fn argument_number(&mut self, items: &[Operand], pos: Pos) {
2036 if !self.extended {
2037 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"));
2038 return;
2039 }
2040 let numeric = match items {
2041 [Operand::Literal(Literal::Number(_))] => true,
2042 [Operand::Ref(r)] => self.item(r).is_some_and(|i| self.layout.items[i].kind.is_numeric()),
2043 _ => false,
2044 };
2045 if numeric {
2046 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"));
2047 } else {
2048 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"));
2049 }
2050 }
2051
2052 fn moved_function(&mut self, f: &FunctionCall) {
2058 let name = f.name.as_str();
2059 let numeric = !rt::intrinsic::CHARACTER_VALUED.contains(&name) || self.numeric_max_or_min(f);
2060 if !self.intrinsic(name) || !numeric {
2061 return;
2062 }
2063 if self.extended {
2064 self.errors.push(syntax::messages::IWX0008.at(f.pos, format!("{NUMERIC_FUNCTION_MOVED}: FUNCTION {name} is moved as its value")));
2065 } else {
2066 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")));
2067 }
2068 }
2069
2070 fn inspected_usage(&mut self, target: &Ref, i: &Inspect) {
2075 let Some(t) = self.item(target) else { return };
2076 let usage = CharUsage::of(self.layout.items[t].kind);
2077 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)));
2078 let converting = i.converting.iter().flat_map(|(from, to, bounds)| [from, to].into_iter().chain(bounds.iter().map(|b| &b.value)));
2079 for op in phrases.chain(converting) {
2080 let (operand, operand_usage, pos) = match op {
2081 Operand::Literal(l) => match inspected_literal(l) {
2082 Some((what, operand_usage)) => (what.to_owned(), operand_usage, i.pos),
2083 None => continue,
2084 },
2085 Operand::Ref(r) => match self.item(r) {
2086 Some(k) => (r.name.clone(), CharUsage::of(self.layout.items[k].kind), r.pos),
2087 None => continue,
2088 },
2089 _ => continue,
2090 };
2091 let name = &target.name;
2092 let why = match (usage, operand_usage) {
2093 (u, o) if u == o => continue,
2094 (CharUsage::Display, o) => format!("{name} is not {o}, and an operand can be {o} only when the inspected item is"),
2095 (u, _) => format!("{name} is {u} and every operand but the count field must be {u} too"),
2096 };
2097 self.errors.push(syntax::messages::IWC0103.at(pos, format!("INSPECT {name}: {operand} cannot be an operand here, since {why}")));
2098 }
2099 }
2100
2101 fn handle_labels(&mut self, block: &ExecBlock) {
2104 let abend = block.command == "HANDLE ABEND";
2105 for (option, arg) in &block.options {
2106 let Some(ExecArg::Text(label)) = arg else { continue };
2107 let label = label.trim();
2108 if label.is_empty() || matches!(option.as_str(), "RESP" | "RESP2" | "NOHANDLE") || abend && option != "LABEL" {
2109 continue;
2110 }
2111 let name = ProcName { name: label.to_ascii_uppercase(), section: None };
2112 if let Err(m) = procedure_from(self.program, &name, self.paragraph) {
2113 self.errors.push(syntax::messages::IWP0007.at(block.pos, format!("EXEC CICS {} {option}({label}): {m}", block.command)));
2114 }
2115 }
2116 }
2117
2118 fn search_varying(&mut self, se: &Search, v: &Ref) {
2121 use rt::storage::Kind;
2122 let Some(i) = self.item(v) else { return };
2123 if !matches!(self.layout.items[i].kind, Kind::Index | Kind::Zoned { scale: 0, .. } | Kind::Packed { scale: 0, .. } | Kind::Binary { scale: 0, .. }) {
2124 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)));
2125 }
2126 }
2127
2128 fn reference_unsubscripted(&mut self, r: &Ref) {
2130 if let Err(e) = self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
2131 self.errors.push(e);
2132 }
2133 }
2134
2135 fn intrinsic(&self, name: &str) -> bool {
2138 (FUNCTIONS.contains(&name) || rt::intrinsic::FUNCTIONS.contains(&name)) && !self.functions.into_iter().flatten().any(|u| u.name == name)
2139 }
2140
2141 fn operand(&mut self, op: &Operand) {
2142 match op {
2143 Operand::LengthOf(r) => self.reference(&self.layout.length_of_ref(r)),
2144 Operand::Ref(r) | Operand::AddressOf(r) => self.reference(r),
2145 Operand::Literal(Literal::Number(t)) if literal_fixed(t).is_none() || literal_digits(t) > self.max_digits as usize => {
2146 self.errors.push(syntax::messages::IWC0105.at(Pos::default(), format!("the literal {t} has more than {} digits", self.max_digits.min(31))));
2147 }
2148 Operand::Literal(_) => {}
2149 Operand::Function(f) => {
2150 if f.name == "MODULE-CALLER-ID" && self.intrinsic(&f.name) {
2151 self.errors.push(if self.extended {
2152 syntax::messages::IWX0031.at(f.pos, "FUNCTION MODULE-CALLER-ID (GnuCOBOL; Enterprise COBOL has no such function): the PROGRAM-ID of the program that called this one, empty in the main program")
2153 } else {
2154 syntax::messages::IWC0305.at(f.pos, "FUNCTION MODULE-CALLER-ID: GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it")
2155 });
2156 }
2157 if f.name == "CONCATENATE" && self.intrinsic(&f.name) {
2158 self.errors.push(if self.extended {
2159 syntax::messages::IWX0053.at(f.pos, "FUNCTION CONCATENATE (GnuCOBOL; Enterprise COBOL has no such function): its arguments' characters joined, a number's as its digits")
2160 } else {
2161 syntax::messages::IWC0319.at(f.pos, "FUNCTION CONCATENATE: GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it")
2162 });
2163 }
2164 if f.name == "STORED-CHAR-LENGTH" && self.intrinsic(&f.name) {
2165 self.errors.push(if self.extended {
2166 syntax::messages::IWX0042.at(f.pos, "FUNCTION STORED-CHAR-LENGTH (GnuCOBOL; Enterprise COBOL has no such function): the argument's length in characters without its trailing spaces")
2167 } else {
2168 syntax::messages::IWC0312.at(f.pos, "FUNCTION STORED-CHAR-LENGTH: GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it")
2169 });
2170 }
2171 if !self.intrinsic(&f.name) {
2172 match self.functions.map(|all| all.iter().find(|u| u.name == f.name)) {
2173 Some(Some(udf)) => function::check_invocation(udf, f, self.layout, self.alphabetic, self.program.environment.decimal_point_comma, self.errors),
2174 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))),
2175 None => self.errors.push(syntax::messages::IWR0075.at(f.pos, format!("FUNCTION {}: a user-defined function is not supported here yet", f.name))),
2176 }
2177 }
2178 self.function_arguments(f);
2179 f.args.iter().for_each(|a| self.expr(a));
2180 }
2181 }
2182 }
2183
2184 fn expr(&mut self, e: &Expr) {
2185 match e {
2186 Expr::Operand(op) => self.operand(op),
2187 Expr::Neg(inner) => self.expr(inner),
2188 Expr::Bin(a, _, b) => {
2189 self.expr(a);
2190 self.expr(b);
2191 }
2192 }
2193 }
2194
2195 fn cond(&mut self, c: &Cond) {
2196 match c {
2197 Cond::Rel(a, _, b) => {
2198 self.expr(a);
2199 self.expr(b);
2200 self.comparison(a, b);
2201 }
2202 Cond::Class(e, class) => {
2203 self.expr(e);
2204 if let Class::Named(name) = class {
2205 self.class_name_subject(e, name);
2206 }
2207 }
2208 Cond::Name(r) => self.reference_or_condition(r),
2209 Cond::NameOrRel { subject, name, .. } => {
2210 self.expr(subject);
2211 self.reference_or_condition(name);
2212 if self.item(name).is_some() {
2213 self.comparison(subject, &Expr::Operand(Operand::Ref(name.clone())));
2214 }
2215 }
2216 Cond::Not(inner) => self.cond(inner),
2217 Cond::And(a, b) | Cond::Or(a, b) => {
2218 self.cond(a);
2219 self.cond(b);
2220 }
2221 }
2222 }
2223}
2224
2225#[cfg(test)]
2226mod tests {
2227 use super::*;
2228 use std::ffi::OsStr;
2229 use std::time::Duration;
2230
2231 #[test]
2232 fn source_date_epoch_decides_the_compile_time_and_the_clock_stands_in_for_it() {
2233 let clock = Duration::from_millis(1_790_510_400_428);
2234 let from = |epoch: Option<&str>| compile_time_from(epoch.map(OsStr::new), clock);
2235 assert_eq!(from(None), Ok(CompileTime { seconds: 1_790_510_400, hundredths: 42, source: TimeSource::Clock }));
2236 assert_eq!(from(Some("315532800")), Ok(CompileTime { seconds: 315_532_800, hundredths: 0, source: TimeSource::SourceDateEpoch }));
2237 assert_eq!(from(Some("253402300799")).map(|t| t.seconds), Ok(CompileTime::LATEST));
2238 for bad in ["", "-1", "+5", "1.5", " 7", "253402300800"] {
2239 assert!(from(Some(bad)).unwrap_err().starts_with(&format!("SOURCE_DATE_EPOCH={bad}: ")), "{bad}");
2240 }
2241 }
2242}