1pub mod collating;
5mod corresponding;
6pub use corresponding::is_alphabetic;
7pub mod declaratives;
8pub mod function;
9mod initcheck;
10pub mod layout;
11pub mod linage;
12pub mod markup;
13pub mod numcheck;
14pub mod oo;
15mod operands;
16pub mod picture;
17pub mod printer;
18pub mod report;
19mod reserved;
20mod scope;
21pub mod sort;
22pub mod sql;
23
24use layout::Layout;
25use numeric::{Options, Vlr};
26use rt::lir::{CompileTime, TimeSource};
27use rt::storage::literal_fixed;
28use syntax::ast::*;
29use syntax::{Error, Pos, Severity};
30
31pub struct Compiled {
32 pub program: Program,
33 pub when_compiled: CompileTime,
35 pub layout: Layout,
36 pub options: Options,
37 pub ssrange: bool,
38 pub report_writer: report::Writer,
39 pub collating: collating::Sequence,
41 pub carriage: Vec<Option<printer::Carriage>>,
43 pub diagnostics: Vec<Error>,
45 pub entries: Vec<EntryPoint>,
47 pub declaratives: declaratives::Table,
49 pub functions: Vec<function::Udf>,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct EntryPoint {
57 pub name: String,
58 pub paragraph: usize,
59 pub statement: usize,
60 pub using: Vec<Param>,
61 pub pos: Pos,
62}
63
64pub fn entry_points(program: &Program) -> Vec<EntryPoint> {
66 let mut out = Vec::new();
67 for (paragraph, p) in program.paragraphs.iter().enumerate() {
68 for (k, s) in p.statements.iter().enumerate() {
69 if let Stmt::Entry { name, using, pos } = s {
70 out.push(EntryPoint { name: name.clone(), paragraph, statement: k + 1, using: using.clone(), pos: *pos });
71 }
72 }
73 }
74 out
75}
76
77pub fn variable_records(file: &FileDecl, layout: &Layout, k: usize) -> bool {
82 let undeclared = file.recording.is_none() && file.record_min.is_none() && file.record_max.is_none() && !file.record_varying;
83 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)
84}
85
86pub fn read_lengths(file: &FileDecl, layout: &Layout, k: usize, vlr: Vlr) -> (u32, u32) {
92 match vlr {
93 Vlr::Compat if file.record_varying => varying_lengths(file, layout, k),
94 _ => record_lengths(layout, k),
95 }
96}
97
98pub fn varying_lengths(file: &FileDecl, layout: &Layout, k: usize) -> (u32, u32) {
101 let (shortest, longest) = record_lengths(layout, k);
102 (file.record_min.unwrap_or(shortest), file.record_max.unwrap_or(longest))
103}
104
105fn record_lengths(layout: &Layout, k: usize) -> (u32, u32) {
106 layout.record_lengths[k].unwrap_or((0, layout.file_areas[k].1))
107}
108
109const FUNCTIONS: &[&str] = rt::intrinsic::FIRST;
110
111pub fn compile(program: Program, flags: &[String]) -> Result<Compiled, Vec<Error>> {
116 let at = compile_time().map_err(|message| vec![Error::at(Pos::default(), message)])?;
117 compile_at(program, flags, at)
118}
119
120pub fn compile_at(program: Program, flags: &[String], at: CompileTime) -> Result<Compiled, Vec<Error>> {
122 if program.oo.as_ref().is_some_and(|o| o.class().is_some()) {
123 return oo::compile_class_definition(program, flags, at);
124 }
125 compile_program(program, flags, true, at)
126}
127
128pub fn compile_time() -> Result<CompileTime, String> {
131 let clock = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
132 compile_time_from(std::env::var_os("SOURCE_DATE_EPOCH").as_deref(), clock)
133}
134
135fn compile_time_from(epoch: Option<&std::ffi::OsStr>, clock: std::time::Duration) -> Result<CompileTime, String> {
136 let Some(epoch) = epoch else {
137 return Ok(CompileTime { seconds: clock.as_secs() as i64, hundredths: clock.subsec_millis() / 10, source: TimeSource::Clock });
138 };
139 let text = epoch.to_string_lossy();
140 match text.parse::<i64>() {
141 Ok(seconds) if text.bytes().all(|b| b.is_ascii_digit()) && seconds <= CompileTime::LATEST => Ok(CompileTime { seconds, hundredths: 0, source: TimeSource::SourceDateEpoch }),
142 _ => Err(format!("SOURCE_DATE_EPOCH={text}: not a whole number of seconds from 0 to {}", CompileTime::LATEST)),
143 }
144}
145
146pub(crate) fn compile_program(mut program: Program, flags: &[String], whole: bool, when_compiled: CompileTime) -> Result<Compiled, Vec<Error>> {
149 let mut errors = std::mem::take(&mut program.messages);
150 reserved::check(&program, &mut errors);
151 let declared = program.working_storage.len();
152 let mut program = declaratives::with_debug_item(markup::with_special_registers(sort::with_special_registers(program)));
153 qualify_in_own_section(&mut program);
154 let mut options = Options::default();
155 let mut ssrange = false;
156 for flag in flags {
159 if let Err(e) = options.apply_flag(flag) {
160 errors.push(Error::at(Pos::default(), e.to_string()));
161 }
162 }
163 for option in &program.options {
164 if let Some(on) = numeric::options::switch(option, "SSRANGE") {
165 ssrange = on;
166 }
167 if let Err(e) = options.apply(option) {
168 errors.push(Error::at(Pos::default(), format!("CBL {option}: {e}")).graded(option_severity(&e)));
169 }
170 }
171 let page = options.code_page();
172 if let Err(m) = syntax::parser::decode_currency(&mut program.environment, |bytes| page.decode(bytes)) {
173 errors.push(Error::at(Pos::default(), m));
174 }
175 default_currency(&mut program, &mut options, &mut errors);
176 national_symbols(&mut program, &mut options, &mut errors);
177 if options.intdate == numeric::IntDate::Lilian {
178 program.paragraphs.iter_mut().for_each(|p| ceecbldy_to_ceedays(&mut p.statements, &mut errors));
179 }
180 for (name, alphabet) in &program.environment.alphabets {
181 if program.environment.collating_sequence.as_ref() != Some(name)
182 && let Err(m) = collating::Sequence::of(alphabet, options.code_page(), options.quote)
183 {
184 errors.push(Error::at(Pos::default(), format!("ALPHABET {name}: {m}")));
185 }
186 }
187 let collating = collating::Sequence::program(&program.environment, options.code_page(), options.quote).unwrap_or_else(|m| {
188 errors.push(Error::at(Pos::default(), m));
189 let mut native = collating::Sequence::native();
190 native.quote = options.quote;
191 native
192 });
193 digit_limits(&program, options.arith, &mut errors);
194 let drafts = report::prepare(&mut program, options.adv, options.qualify, &mut errors);
195 let linage_counters = linage::add_counters(&mut program, options.qualify);
196 if whole {
197 oo::option_rules(&program, &options, &mut errors);
198 }
199 options.initial &= !options.thread;
200 if whole && program.oo.as_deref().and_then(Oo::method).is_none() {
201 program.initial |= options.initial;
202 }
203 scope::rules(&program, &mut errors);
204 let inherited = scope::inherit(&mut program);
205 let own_linkage = scope::own_linkage(&program);
206 let linkage: Vec<DataEntry> = program.linkage.iter().chain(&inherited.entries).cloned().collect();
207 let files: Vec<(&[DataEntry], Option<u32>)> = program.files.iter().map(|f| (f.records.as_slice(), f.record_max)).collect();
208 let shared = layout::record_area_owners(&program.files, &program.environment).unwrap_or_else(|e| {
209 errors.push(e);
210 (0..files.len()).collect()
211 });
212 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()))) {
213 Ok(l) => l,
214 Err(e) => {
215 errors.push(e);
216 return Err(errors.into_iter().map(|e| e.in_files(&program.sources)).collect());
217 }
218 };
219 scope::bind(&mut layout, own_linkage, inherited, &program.files);
220 let counter_item = |entry: usize| program.working_storage[..entry].iter().filter(|e| e.level != 88).count();
221 layout.name_files(&program.files, linage_counters.iter().map(|c| c.map(counter_item)).collect());
222 corresponding::expand(&mut program, &layout, &mut errors);
223 condition_subjects(&mut program, &layout);
224 dbcs_values(&layout, &mut errors);
225 numeric_values(&layout, &mut errors);
226 for item in &layout.items {
227 if let Some(object) = &item.depending_on {
228 match layout.resolve(&object.name, &object.qualifiers, object.pos) {
229 Ok(layout::Resolved::Item(i)) if !layout.items[i].moved_by.is_empty() => errors.push(Error::at(
230 object.pos,
231 format!("OCCURS DEPENDING ON {}: the object cannot follow an OCCURS DEPENDING ON table in its record", object.name),
232 )),
233 Ok(layout::Resolved::Item(i)) if layout.items[i].kind.is_numeric() => {}
234 Ok(_) => errors.push(Error::at(object.pos, format!("OCCURS DEPENDING ON {}: not a numeric data item", object.name))),
235 Err(e) => errors.push(e),
236 }
237 }
238 }
239 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));
240 for param in &program.using {
241 if !is_record(¶m.name) {
242 errors.push(Error::at(Pos::default(), format!("PROCEDURE DIVISION USING {}: not an 01 or 77 item of the LINKAGE SECTION", param.name)));
243 }
244 }
245 if let Some(name) = &program.returning
247 && program.function.is_none()
248 && program.oo.as_deref().and_then(Oo::method).is_none()
249 && !is_record(name)
250 {
251 errors.push(Error::at(Pos::default(), format!("PROCEDURE DIVISION RETURNING {name}: not an 01 or 77 item of the LINKAGE SECTION")));
252 }
253 let report_writer = report::resolve(&program, &layout, drafts, &mut errors);
254 let carriage = printer::carriages(&program, &layout, options.adv);
255 let declaratives = declaratives::resolve(&program, &layout, &options, &mut errors);
256 let debugging = declaratives::debugging_sections(&program);
257 let functions = function::functions(&program, options.qualify, &mut errors);
258 let alphabetic: Vec<Pos> = [&program.working_storage, &program.local_storage, &program.linkage]
259 .into_iter()
260 .flatten()
261 .chain(program.files.iter().flat_map(|f| &f.records))
262 .filter(|e| e.picture.as_deref().is_some_and(is_alphabetic))
263 .map(|e| e.pos)
264 .collect();
265 let mut check = Check {
266 layout: &layout,
267 program: &program,
268 errors: &mut errors,
269 debugging: false,
270 max_digits: options.arith.max_picture_digits(),
271 inline_performs: 0,
272 functions: Some(&functions),
273 alphabetic: &alphabetic,
274 at: Pos::default(),
275 paragraph: 0,
276 };
277 for k in 0..program.files.len() {
278 check.file_keys(k);
279 check.record_depending(k);
280 check.passwords(k);
281 linage::check_file(check.program, check.layout, k, check.errors);
282 }
283 for block in &program.exec_declarations {
284 check.exec_block(block);
285 }
286 for (i, p) in program.paragraphs.iter().enumerate() {
287 check.debugging = debugging.iter().any(|&(first, last)| (first..=last).contains(&i));
288 check.paragraph = i;
289 check.statements(&p.statements);
290 }
291 let entries = entry_points(&program);
292 procedure_rules(&program, &layout, &entries, &options, &mut errors);
293 if whole && !program.oo.as_deref().is_some_and(|o| o.method().is_some()) && !program.is_prototype() {
294 program_end(&program, &options, &mut errors);
295 }
296 oo::check(&layout, &program, &mut errors);
297 if let Some(mode) = options.initcheck {
298 errors.extend(initcheck::check(&program, &layout, declared, mode));
299 }
300 scope::check(&program, &layout, &mut errors);
301 layout.numcheck = numcheck::facts(&program, &layout, declared, &options, &collating, &mut errors);
302 let errors: Vec<Error> = errors.into_iter().map(|e| e.in_files(&program.sources)).collect();
303 if refused(&errors, &options) {
304 Err(errors)
305 } else {
306 Ok(Compiled { program, when_compiled, layout, options, ssrange, report_writer, collating, carriage, diagnostics: errors, entries, declaratives, functions })
307 }
308}
309
310fn option_severity(e: &numeric::options::OptionError) -> Severity {
316 use numeric::options::OptionError;
317 match e {
318 OptionError::BadSuboption { .. } => Severity::Error,
319 OptionError::Removed { .. } | OptionError::NoEffect { warning: true, .. } => Severity::Warning,
320 OptionError::NoEffect { warning: false, .. } => Severity::Informational,
321 OptionError::UnsupportedCodePage(_) | OptionError::UnknownFlag(_) => Severity::Severe,
322 }
323}
324
325fn default_currency(program: &mut Program, options: &mut Options, errors: &mut Vec<Error>) {
331 match options.currency_symbol() {
332 Some(Ok(symbol)) if program.environment.currency.is_empty() => program.environment.currency.push(CurrencySign { value: symbol.to_string(), symbol, hex: None }),
333 Some(Err(c)) => {
334 errors.push(Error::at(Pos::default(), format!("CBL CURRENCY: code page {} reads its byte as {c:?}, which cannot be a currency symbol", options.codepage)).graded(Severity::Error));
335 options.currency = None;
336 }
337 _ => {}
338 }
339}
340
341fn national_symbols(program: &mut Program, options: &mut Options, errors: &mut Vec<Error>) {
346 if options.nsymbol == numeric::Nsymbol::National {
347 if !options.dbcs && program.options.iter().any(|o| numeric::options::switch(o, "NSYMBOL").is_some()) {
348 errors.push(Error::warning(Pos::default(), "CBL NODBCS: NSYMBOL(NATIONAL) requires DBCS, which is in effect"));
349 options.dbcs = true;
350 }
351 return;
352 }
353 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, '(' | ')'));
354 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));
355 for entries in lists {
356 let mut groups: Vec<(u8, bool)> = Vec::new();
357 for e in entries.iter_mut().filter(|e| !matches!(e.level, 66 | 88)) {
358 let level = if e.level == 77 { 1 } else { e.level };
359 while groups.last().is_some_and(|&(l, _)| l >= level) {
360 groups.pop();
361 }
362 let usage = e.usage.is_some() || groups.iter().any(|&(_, u)| u);
363 groups.push((level, e.usage.is_some()));
364 if !usage && e.picture.as_deref().is_some_and(only_n) {
365 e.usage = Some(Usage::Dbcs);
366 }
367 }
368 }
369}
370
371fn program_end(program: &Program, options: &Options, errors: &mut Vec<Error>) {
375 use numeric::CicsReturnWarning;
376 let mut all = Vec::new();
377 program.paragraphs.iter().for_each(|p| inner_statements(&p.statements, &mut all));
378 if all.iter().any(|s| matches!(s, Stmt::StopRun { .. } | Stmt::Goback { .. } | Stmt::ExitProgram { .. })) {
379 return;
380 }
381 let cics_end = all.iter().find_map(|s| match s {
382 Stmt::Exec(b) if b.kind == ExecKind::Cics && matches!(b.command.as_str(), "RETURN" | "XCTL") => Some(b.command.as_str()),
383 _ => None,
384 });
385 match (cics_end, options.cics_return_warning) {
386 (Some(_), CicsReturnWarning::Never) => {}
387 (Some(command), CicsReturnWarning::Once) => errors.push(Error::at(Pos::default(), format!(
388 "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"
389 )).graded(Severity::Informational)),
390 (None, _) | (Some(_), CicsReturnWarning::Always) => {
391 errors.push(Error::warning(Pos::default(), "no STOP RUN, GOBACK or EXIT PROGRAM in the program: check that it ends"));
392 }
393 }
394}
395
396fn ceecbldy_to_ceedays(stmts: &mut [Stmt], errors: &mut Vec<Error>) {
401 for s in stmts {
402 if let Stmt::Call(c) = s
403 && let Operand::Literal(Literal::Alnum(name)) = &mut c.target
404 && name.trim().eq_ignore_ascii_case("CEECBLDY")
405 {
406 *name = "CEEDAYS".into();
407 errors.push(Error::warning(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"));
408 }
409 for body in oo::bodies_mut(s) {
410 ceecbldy_to_ceedays(body, errors);
411 }
412 }
413}
414
415pub fn refused(messages: &[Error], options: &Options) -> bool {
419 let stops_at = options.object_code().stops_at().min(12);
420 stops_at == 0 || messages.iter().any(|m| m.severity.return_code() >= stops_at)
421}
422
423fn procedure_rules(program: &Program, layout: &Layout, entries: &[EntryPoint], options: &Options, errors: &mut Vec<Error>) {
426 let method = program.oo.as_ref().is_some_and(|o| matches!(o.unit, OoUnit::Method(_)));
427 for (k, e) in entries.iter().enumerate() {
428 if program.returning.is_some() {
429 errors.push(Error::at(e.pos, format!("ENTRY '{}': a program with PROCEDURE DIVISION RETURNING cannot have ENTRY statements", e.name)));
430 }
431 if e.name.eq_ignore_ascii_case(&program.id) || entries[..k].iter().any(|f| f.name == e.name) {
432 errors.push(Error::at(e.pos, format!("ENTRY '{}': the name is already the program's or another ENTRY's", e.name)));
433 }
434 for param in &e.using {
435 if !layout.linkage_roots.iter().enumerate().any(|(o, &i)| layout.is_argument(o) && layout.items[i].name.as_deref() == Some(param.name.as_str())) {
436 errors.push(Error::at(e.pos, format!("ENTRY '{}' USING {}: not an 01 or 77 item of the LINKAGE SECTION", e.name, param.name)));
437 }
438 }
439 }
440 let why_no_alter = if program.recursive {
441 Some("a RECURSIVE program")
442 } else if options.thread {
443 Some("a program compiled with THREAD")
444 } else {
445 method.then_some("a method")
446 };
447 for p in &program.paragraphs {
448 for s in &p.statements {
449 let mut inner = Vec::new();
450 oo::bodies(s).into_iter().for_each(|body| inner_statements(body, &mut inner));
451 for (t, nested) in std::iter::once((s, false)).chain(inner.into_iter().map(|t| (t, true))) {
452 match t {
453 Stmt::Entry { name, pos, .. } if nested => {
454 errors.push(Error::at(*pos, format!("ENTRY '{name}' must be a sentence of its own, not inside another statement")));
455 }
456 Stmt::GoTo { target: None, pos } => {
457 if let Some(why) = why_no_alter {
458 errors.push(Error::at(*pos, format!("a GO TO with no procedure-name cannot be used in {why}")));
459 }
460 if nested || !lone_go_to(p) {
461 errors.push(Error::at(*pos, "a GO TO with no procedure-name must be its paragraph's only sentence"));
462 }
463 }
464 Stmt::Alter { pairs, pos } => {
465 if let Some(why) = why_no_alter {
466 errors.push(Error::at(*pos, format!("ALTER cannot be used in {why}")));
467 }
468 for (from, to) in pairs {
469 altered_paragraph(program, from, *pos, errors);
470 if let Err(m) = procedure(program, to) {
471 errors.push(Error::at(*pos, m));
472 }
473 }
474 }
475 _ => {}
476 }
477 }
478 }
479 }
480}
481
482fn inner_statements<'s>(stmts: &'s [Stmt], out: &mut Vec<&'s Stmt>) {
484 for s in stmts {
485 out.push(s);
486 oo::bodies(s).into_iter().for_each(|body| inner_statements(body, out));
487 }
488}
489
490fn altered_paragraph(program: &Program, name: &ProcName, pos: Pos, errors: &mut Vec<Error>) {
492 match procedure(program, name) {
493 Err(m) => errors.push(Error::at(pos, m)),
494 Ok((i, _)) if program.paragraphs[i].is_section => errors.push(Error::at(pos, format!("ALTER {}: a section, where ALTER names a paragraph", name.name))),
495 Ok((i, _)) if !lone_go_to(&program.paragraphs[i]) => {
496 errors.push(Error::at(pos, format!("ALTER {}: the paragraph must hold one sentence, a GO TO without DEPENDING ON", name.name)));
497 }
498 Ok(_) => {}
499 }
500}
501
502fn lone_go_to(p: &Paragraph) -> bool {
503 matches!(p.statements.as_slice(), [Stmt::GoTo { .. }] | [Stmt::GoTo { .. }, Stmt::SentenceEnd])
504}
505
506pub fn section_end(program: &Program, i: usize) -> usize {
509 let paragraphs = &program.paragraphs;
510 let declaratives = program.report_writer.procedure_start;
511 let (floor, ceiling) = if i < declaratives { (0, declaratives) } else { (declaratives, paragraphs.len()) };
512 let Some(header) = (floor..=i).rev().find(|&j| paragraphs[j].is_section) else { return i };
513 (header + 1..ceiling).take_while(|&j| !paragraphs[j].is_section).last().unwrap_or(header)
514}
515
516fn qualify_in_own_section(program: &mut Program) {
520 for i in 0..program.paragraphs.len() {
521 let Some(section) = program.paragraphs[i].section.clone() else { continue };
522 let mut statements = std::mem::take(&mut program.paragraphs[i].statements);
523 oo::each_mut(&mut statements, &mut |s| {
524 for name in procedure_names_mut(s) {
525 if name.section.is_none() && names_own_paragraph(&program.paragraphs, &name.name, §ion) {
526 name.section = Some(section.clone());
527 }
528 }
529 });
530 program.paragraphs[i].statements = statements;
531 }
532}
533
534fn names_own_paragraph(paragraphs: &[Paragraph], name: &str, section: &str) -> bool {
537 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))
538}
539
540pub fn procedure_from(program: &Program, p: &ProcName, from: usize) -> Result<(usize, usize), String> {
544 match program.paragraphs.get(from).and_then(|q| q.section.as_deref()) {
545 Some(section) if p.section.is_none() && names_own_paragraph(&program.paragraphs, &p.name, section) => {
546 procedure(program, &ProcName { name: p.name.clone(), section: Some(section.to_owned()) })
547 }
548 _ => procedure(program, p),
549 }
550}
551
552fn dbcs_values(layout: &Layout, errors: &mut Vec<Error>) {
558 let dbcs = |i: usize| matches!(layout.items[i].kind, rt::storage::Kind::Dbcs { .. });
559 let is_dbcs = |l: &Literal| matches!(l, Literal::Dbcs(_)) || matches!(l, Literal::All(inner) if matches!(**inner, Literal::Dbcs(_)));
560 let fits = |l: &Literal, i: usize| match l {
561 Literal::Dbcs(s) => s.chars().count() <= layout.items[i].size as usize / 2,
562 Literal::Figurative(syntax::ast::Figurative::Space) => true,
563 l => is_dbcs(l),
564 };
565 let values = layout.items.iter().enumerate().filter_map(|(i, it)| it.value.as_ref().map(|v| (i, v, it.pos)));
566 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)));
567 for (i, value, pos) in values.chain(conditions) {
568 let name = layout.items[i].name.as_deref().unwrap_or("FILLER");
569 if dbcs(i) && !fits(value, i) {
570 errors.push(Error::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)));
571 } else if !dbcs(i) && is_dbcs(value) {
572 errors.push(Error::at(pos, format!("{name}: a DBCS literal can be the VALUE of a DBCS item only")));
573 }
574 }
575}
576
577const DISPLAY_DEVICES: &[&str] = &["SYSOUT", "SYSLIST", "SYSLST", "SYSPUNCH", "SYSPCH", "CONSOLE"];
580
581fn numeric_values(layout: &Layout, errors: &mut Vec<Error>) {
584 for item in layout.items.iter().filter(|i| i.children.is_empty() && i.kind.is_numeric()) {
585 let what = match item.value {
586 Some(Literal::Alnum(_) | Literal::Hex(_)) => "an alphanumeric literal",
587 Some(Literal::National(_)) => "a national literal",
588 _ => continue,
589 };
590 let name = item.name.as_deref().unwrap_or("FILLER");
591 errors.push(Error::at(item.pos, format!("VALUE of {name}: {what}, where a numeric item's VALUE literal must be numeric")));
592 }
593}
594
595fn condition_subjects(program: &mut Program, layout: &Layout) {
596 let is_condition = |r: &Ref| matches!(layout.resolve(&r.name, &r.qualifiers, r.pos), Ok(layout::Resolved::Condition(_)));
597 for p in &mut program.paragraphs {
598 oo::each_mut(&mut p.statements, &mut |s| {
599 let Stmt::Evaluate { subjects, whens, .. } = s else { return };
600 for (k, subject) in subjects.iter_mut().enumerate() {
601 let Subject::Expr(Expr::Operand(Operand::Ref(r))) = subject else { continue };
602 if !is_condition(r) {
603 continue;
604 }
605 *subject = Subject::Cond(Cond::Name(r.clone()));
606 for alternative in whens.iter_mut().flat_map(|w| w.alternatives.iter_mut()) {
607 let cond = match alternative.get(k) {
608 Some(Object::Value { not, from: Expr::Operand(Operand::Ref(o)), thru: None }) => {
609 let c = Cond::Name(o.clone());
610 if *not { Cond::Not(Box::new(c)) } else { c }
611 }
612 _ => continue,
613 };
614 alternative[k] = Object::Cond(cond);
615 }
616 }
617 });
618 }
619}
620
621fn procedure_names_mut(s: &mut Stmt) -> Vec<&mut ProcName> {
623 match s {
624 Stmt::PerformProc { from, thru, .. } => std::iter::once(from).chain(thru.as_mut()).collect(),
625 Stmt::GoTo { target, .. } => target.iter_mut().collect(),
626 Stmt::GoToDepending { targets, .. } => targets.iter_mut().collect(),
627 Stmt::Alter { pairs, .. } => pairs.iter_mut().flat_map(|(from, to)| [from, to]).collect(),
628 Stmt::XmlParse(x) => std::iter::once(&mut x.procedure).chain(x.thru.as_mut()).collect(),
629 Stmt::Sorting(so) => match &mut **so {
630 Sorting::Sort(st) => [st.input.as_mut(), st.output.as_mut()]
631 .into_iter()
632 .flatten()
633 .flat_map(|io| match io {
634 SortIo::Procedure { from, thru } => std::iter::once(from).chain(thru.as_mut()).collect(),
635 SortIo::Files(_) => Vec::new(),
636 })
637 .collect(),
638 _ => Vec::new(),
639 },
640 _ => Vec::new(),
641 }
642}
643
644pub fn procedure(program: &Program, p: &ProcName) -> Result<(usize, usize), String> {
646 let found: Vec<usize> = program
647 .paragraphs
648 .iter()
649 .enumerate()
650 .filter(|(_, q)| q.name == p.name && (p.section.is_none() || (q.section == p.section && !q.is_section)))
651 .map(|(i, _)| i)
652 .collect();
653 match found.as_slice() {
654 [i] if program.paragraphs[*i].is_section => Ok((*i, section_end(program, *i))),
655 [i] => Ok((*i, *i)),
656 [] => Err(format!("no paragraph or section named {}", p.name)),
657 _ => Err(format!("{} names more than one paragraph; qualify it with OF and its section", p.name)),
658 }
659}
660
661fn digit_limits(program: &Program, arith: numeric::options::Arith, errors: &mut Vec<Error>) {
665 let max = arith.max_picture_digits();
666 let option = match arith {
667 numeric::options::Arith::Compat => "ARITH(COMPAT)",
668 numeric::options::Arith::Extend => "ARITH(EXTEND)",
669 };
670 let entries = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(program.files.iter().flat_map(|f| &f.records));
671 for e in entries {
672 if let Some(p) = e.picture.as_deref()
673 && let Ok(pic) = picture::analyse_with(p, crate::picture::Notation::of(&program.environment))
674 && matches!(pic.category, picture::Category::Numeric | picture::Category::NumericEdited)
675 {
676 let positions = pic.digits + pic.scaling + pic.scale.saturating_sub(pic.digits);
677 if positions > max {
678 errors.push(Error::at(e.pos, format!("PICTURE {p}: {positions} digit positions, more than the {max} {option} allows")));
679 }
680 }
681 let values = e.value.iter().chain(e.condition_values.iter().flat_map(|(low, high)| std::iter::once(low).chain(high))).chain(&e.false_value);
682 for v in values {
683 if let Literal::Number(t) = v
684 && literal_digits(t) > max as usize
685 {
686 errors.push(Error::at(e.pos, format!("the literal {t} has more than the {max} digits {option} allows")));
687 }
688 }
689 }
690}
691
692fn literal_digits(t: &str) -> usize {
693 t.chars().filter(char::is_ascii_digit).count()
694}
695
696#[derive(Clone, Copy, PartialEq, Eq)]
698enum CharUsage {
699 Display,
700 National,
701 Dbcs,
702}
703
704impl CharUsage {
705 fn of(kind: rt::storage::Kind) -> Self {
706 match kind {
707 rt::storage::Kind::National => Self::National,
708 rt::storage::Kind::Dbcs { .. } => Self::Dbcs,
709 _ => Self::Display,
710 }
711 }
712}
713
714impl std::fmt::Display for CharUsage {
715 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
716 f.write_str(match self {
717 Self::Display => "DISPLAY",
718 Self::National => "national",
719 Self::Dbcs => "DBCS",
720 })
721 }
722}
723
724fn inspected_literal(l: &Literal) -> Option<(&'static str, CharUsage)> {
727 match l {
728 Literal::Figurative(_) => None,
729 Literal::All(inner) => inspected_literal(inner),
730 Literal::National(_) => Some(("a national literal", CharUsage::National)),
731 Literal::Dbcs(_) => Some(("a DBCS literal", CharUsage::Dbcs)),
732 Literal::Number(_) => Some(("a numeric literal", CharUsage::Display)),
733 Literal::Alnum(_) | Literal::Hex(_) => Some(("an alphanumeric literal", CharUsage::Display)),
734 }
735}
736
737fn flag_conditions(flag: &Flag) -> Vec<&Ref> {
740 match flag {
741 Flag::Condition(c) => vec![c],
742 Flag::Conditions(on, off) => vec![on, off],
743 Flag::Literals(..) => Vec::new(),
744 }
745}
746
747struct Check<'a> {
748 layout: &'a Layout,
749 program: &'a Program,
750 errors: &'a mut Vec<Error>,
751 debugging: bool,
753 max_digits: u32,
755 inline_performs: usize,
757 functions: Option<&'a [function::Udf]>,
759 alphabetic: &'a [Pos],
761 at: Pos,
763 paragraph: usize,
765}
766
767impl Check<'_> {
768 fn statements(&mut self, stmts: &[Stmt]) {
769 for s in stmts {
770 self.statement(s);
771 }
772 }
773
774 fn statement(&mut self, s: &Stmt) {
775 linage::check_receivers(self.layout, s, self.errors);
776 if let Stmt::If { pos, .. } | Stmt::Evaluate { pos, .. } | Stmt::PerformInline { pos, .. } | Stmt::PerformProc { pos, .. } = s {
777 self.at = *pos;
778 }
779 if let Stmt::Search(se) = s {
780 self.at = se.pos;
781 }
782 match s {
783 Stmt::Move { from, to, .. } => {
784 self.operand(from);
785 to.iter().for_each(|r| self.reference(r));
786 }
787 Stmt::Compute { targets, expr, size_error, .. } => {
788 targets.iter().for_each(|t| self.reference(&t.r));
789 self.expr(expr);
790 self.size_error(size_error.as_ref());
791 }
792 Stmt::Arith(a) => {
793 for (t, e) in &a.computations {
794 self.reference(&t.r);
795 self.expr(e);
796 }
797 if let Some((t, x, y)) = &a.remainder {
798 self.reference(&t.r);
799 self.expr(x);
800 self.expr(y);
801 }
802 self.size_error(a.size_error.as_ref());
803 }
804 Stmt::If { cond, then, otherwise, .. } => {
805 self.cond(cond);
806 self.statements(then);
807 self.statements(otherwise);
808 }
809 Stmt::PerformInline { body, repeat, .. } => {
810 self.repeat(repeat);
811 self.inline_performs += 1;
812 self.statements(body);
813 self.inline_performs -= 1;
814 }
815 Stmt::PerformProc { from, thru, repeat, pos } => {
816 self.procedure(from, *pos);
817 if let Some(t) = thru {
818 self.procedure(t, *pos);
819 }
820 self.repeat(repeat);
821 }
822 Stmt::Evaluate { subjects, whens, other, pos } => {
823 for subject in subjects {
824 match subject {
825 Subject::Expr(e) => self.expr(e),
826 Subject::Cond(c) => self.cond(c),
827 Subject::Bool(_) => {}
828 }
829 }
830 for w in whens {
831 for alternative in &w.alternatives {
832 for (subject, object) in subjects.iter().zip(alternative) {
833 match object {
834 Object::Any => {}
835 Object::Bool(_) | Object::Cond(_) if matches!(subject, Subject::Expr(_)) => {
836 self.errors.push(Error::at(*pos, "a condition as the WHEN object of a value subject"));
837 }
838 Object::Bool(_) => {}
839 Object::Cond(c) => self.cond(c),
840 Object::Value { .. } if !matches!(subject, Subject::Expr(_)) => {
841 self.errors.push(Error::at(*pos, "a value as the WHEN object of a TRUE, FALSE or condition subject"));
842 }
843 Object::Value { from, thru, .. } => {
844 self.expr(from);
845 if let Some(t) = thru {
846 self.expr(t);
847 }
848 if let Subject::Expr(e) = subject {
849 std::iter::once(from).chain(thru).for_each(|o| self.comparison(e, o));
850 }
851 }
852 }
853 }
854 }
855 self.statements(&w.body);
856 }
857 self.statements(other);
858 }
859 Stmt::Display { items, upon, pos, .. } => {
860 for o in items {
861 self.operand(o);
862 if let Operand::Function(f) = o {
863 self.displayed_function(f);
864 }
865 }
866 if let Some(upon) = upon
867 && !DISPLAY_DEVICES.contains(&upon.device.as_str())
868 {
869 let why = if upon.name == upon.device {
870 "neither an environment-name DISPLAY writes to, SYSOUT, SYSLIST, SYSLST, SYSPUNCH, SYSPCH or CONSOLE, nor a mnemonic-name for one".to_owned()
871 } else {
872 format!("a mnemonic-name for {}, which DISPLAY does not write to", upon.device)
873 };
874 self.errors.push(Error::at(*pos, format!("DISPLAY UPON {}: {why}", upon.name)));
875 }
876 }
877 Stmt::Open { files, pos } => files.iter().for_each(|(_, f)| self.file(f, *pos)),
878 Stmt::Close { files, pos } => {
879 for (name, closing) in files {
880 self.file(name, *pos);
881 let keyed = self.program.files.iter().any(|f| f.name == *name && matches!(f.organization, Organization::Indexed | Organization::Relative));
882 if keyed && matches!(closing, Some(Closing::Volume | Closing::NoRewind)) {
883 self.errors.push(Error::at(*pos, format!("CLOSE {name}: REEL, UNIT and NO REWIND are not valid for an indexed or relative file")));
884 }
885 }
886 }
887 Stmt::Read(r) => {
888 self.file(&r.file, r.pos);
889 if r.next {
890 self.not_random(&r.file, "READ NEXT", r.pos);
891 }
892 if let Some(into) = &r.into {
893 self.reference(into);
894 }
895 if let Some(key) = &r.key {
896 self.reference(key);
897 self.key_of(&r.file, key, false);
898 }
899 self.handlers(&r.at_end);
900 self.handlers(&r.invalid);
901 }
902 Stmt::Write { record, from, invalid, pos, .. } | Stmt::Rewrite { record, from, invalid, pos } => {
903 let verb = if matches!(s, Stmt::Write { .. }) { "WRITE" } else { "REWRITE" };
904 self.reference(record);
905 if let Ok(layout::Resolved::Item(i)) = self.layout.resolve(&record.name, &record.qualifiers, record.pos)
906 && self.layout.items[i].file.is_none()
907 {
908 self.errors.push(Error::at(*pos, format!("{verb} {}: not a record of a file", record.name)));
909 }
910 if let Some(op) = from {
911 self.operand(op);
912 }
913 if let Stmt::Write { advancing, end_of_page, .. } = s {
914 if let Some(a) = advancing {
915 if let Advancing::Lines { count, .. } = a {
916 self.expr(count);
917 }
918 printer::check_write(self.program, self.layout, record, a, *pos, self.errors);
919 }
920 linage::check_write(self.program, self.layout, record, advancing.as_ref(), end_of_page, *pos, self.errors);
921 self.handlers(end_of_page);
922 }
923 self.handlers(invalid);
924 }
925 Stmt::Delete { file, invalid, pos } => {
926 self.keyed_file(file, "DELETE", *pos);
927 self.handlers(invalid);
928 }
929 Stmt::Start { file, key, invalid, pos } => {
930 self.keyed_file(file, "START", *pos);
931 self.not_random(file, "START", *pos);
932 if let Some((op, r)) = key {
933 if !matches!(op, RelOp::Eq | RelOp::Gt | RelOp::Ge) {
934 self.errors.push(Error::at(*pos, "START KEY takes =, >, NOT < or >="));
935 }
936 self.reference(r);
937 self.key_of(file, r, true);
938 }
939 self.handlers(invalid);
940 }
941 Stmt::Initialize { targets, with, pos } => {
942 for r in targets {
943 self.reference(r);
944 if self.item(r).is_some_and(|i| self.layout.items[i].level == 66) {
945 self.errors.push(Error::at(*pos, format!("INITIALIZE {}: a level-66 RENAMES item cannot be initialized", r.name)));
946 }
947 let items = &self.layout.items;
948 if self.item(r).is_some_and(|i| !items[i].moved_by.is_empty() || items[i].odo.iter().any(|&t| items[t].followed)) {
949 self.errors.push(Error::at(*pos, format!("INITIALIZE {}: a variably located item, or a group holding one, cannot be initialized (Language Reference p. 351)", r.name)));
950 }
951 }
952 if let Some(with) = with {
953 with.replacing.iter().for_each(|(_, by)| self.operand(by));
954 self.initialize_incompatible(targets, with, *pos);
955 }
956 }
957 Stmt::GoTo { target: Some(target), pos } => self.procedure(target, *pos),
958 Stmt::GoToDepending { targets, on, pos } => {
959 targets.iter().for_each(|t| self.procedure(t, *pos));
960 self.reference(on);
961 }
962 Stmt::GoTo { target: None, .. } | Stmt::Alter { .. } | Stmt::Entry { .. } => {}
963 Stmt::Call(c) => {
964 self.operand(&c.target);
965 for arg in &c.using {
966 if let Some(op) = &arg.value {
967 self.operand(op);
968 }
969 }
970 if let Some(r) = &c.returning {
971 self.reference(r);
972 }
973 self.statements(c.on_exception.as_deref().unwrap_or_default());
974 self.statements(c.not_on_exception.as_deref().unwrap_or_default());
975 }
976 Stmt::Cancel { targets, .. } => targets.iter().for_each(|t| self.operand(t)),
977 Stmt::Set { set, .. } => match set {
978 SetStmt::ConditionTrue(targets) => targets.iter().for_each(|r| self.reference_or_condition(r)),
979 SetStmt::ConditionFalse(targets) => {
980 for r in targets {
981 self.reference_or_condition(r);
982 if let Ok(layout::Resolved::Condition(c)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
983 && self.layout.conditions[c].false_value.is_none()
984 {
985 self.errors.push(Error::at(r.pos, format!("SET {} TO FALSE: the condition-name has no WHEN SET TO FALSE value", r.name)));
986 }
987 }
988 }
989 SetStmt::To { targets, value } | SetStmt::Entry { targets, entry: value } => {
990 targets.iter().for_each(|r| self.reference(r));
991 self.operand(value);
992 }
993 SetStmt::AddressOf { targets, value } => {
994 targets.iter().for_each(|r| self.reference(r));
995 targets.iter().for_each(|r| scope::set_address(self.layout, r, self.errors));
996 self.operand(value);
997 }
998 SetStmt::UpDown { targets, by, .. } => {
999 targets.iter().for_each(|r| self.reference(r));
1000 self.expr(by);
1001 }
1002 },
1003 Stmt::Accept { target, .. } => self.reference(target),
1004 Stmt::String(st) => {
1005 for (op, delimiter) in &st.sources {
1006 self.operand(op);
1007 if let Delimiter::By(d) = delimiter {
1008 self.operand(d);
1009 }
1010 }
1011 self.reference(&st.into);
1012 if let Some(p) = &st.pointer {
1013 self.reference(p);
1014 }
1015 self.statements(st.on_overflow.as_deref().unwrap_or_default());
1016 self.statements(st.not_on_overflow.as_deref().unwrap_or_default());
1017 }
1018 Stmt::Unstring(u) => {
1019 self.reference(&u.source);
1020 u.delimiters.iter().for_each(|(_, d)| self.operand(d));
1021 for into in &u.into {
1022 self.reference(&into.target);
1023 into.delimiter_in.iter().chain(&into.count_in).for_each(|r| self.reference(r));
1024 }
1025 u.pointer.iter().chain(&u.tallying).for_each(|r| self.reference(r));
1026 self.statements(u.on_overflow.as_deref().unwrap_or_default());
1027 self.statements(u.not_on_overflow.as_deref().unwrap_or_default());
1028 }
1029 Stmt::Inspect(i) => {
1030 self.operand(&i.target);
1031 match &i.target {
1032 Operand::Function(f) => self.inspected_function(f, i),
1033 Operand::Ref(r) => self.inspected_usage(r, i),
1034 _ => {}
1035 }
1036 for p in i.tallying.iter().chain(&i.replacing) {
1037 p.pattern.iter().chain(&p.by).for_each(|o| self.operand(o));
1038 if let Some(c) = &p.counter {
1039 self.reference(c);
1040 }
1041 p.bounds.iter().for_each(|b| self.operand(&b.value));
1042 }
1043 if let Some((from, to, bounds)) = &i.converting {
1044 self.operand(from);
1045 self.operand(to);
1046 bounds.iter().for_each(|b| self.operand(&b.value));
1047 }
1048 }
1049 Stmt::Search(se) => {
1050 self.reference_unsubscripted(&se.table);
1051 if let Some(v) = &se.varying {
1052 self.reference(v);
1053 self.search_varying(se, v);
1054 }
1055 self.statements(se.at_end.as_deref().unwrap_or_default());
1056 for (cond, body) in &se.whens {
1057 self.cond(cond);
1058 self.statements(body);
1059 }
1060 }
1061 Stmt::Exec(block) => self.exec_block(block),
1062 Stmt::Report(r) => report::check_statement(self.program, r, self.errors),
1063 Stmt::Invoke(i) => self.invoke(i),
1064 Stmt::XmlParse(x) => {
1065 self.reference(&x.document);
1066 if let Some(op) = &x.encoding {
1067 self.operand(op);
1068 }
1069 self.procedure(&x.procedure, x.pos);
1070 if let Some(t) = &x.thru {
1071 self.procedure(t, x.pos);
1072 }
1073 self.statements(x.on_exception.as_deref().unwrap_or_default());
1074 self.statements(x.not_on_exception.as_deref().unwrap_or_default());
1075 }
1076 Stmt::JsonParse(j) => {
1077 self.reference(&j.source);
1078 self.whole_table_reference(&j.into);
1079 let mut named: Vec<&Ref> = j.names.iter().map(|(r, _)| r).chain(&j.suppress).chain(j.ignoring.iter().flatten()).collect();
1080 for (item, flag, indicator) in &j.indicating {
1081 named.push(item);
1082 named.extend(indicator);
1083 named.extend(flag_conditions(flag));
1084 }
1085 for (item, conversion) in &j.converting {
1086 named.push(item);
1087 if let ParseConversion::Boolean(flag) = conversion {
1088 named.extend(flag_conditions(flag));
1089 }
1090 }
1091 named.into_iter().for_each(|r| self.reference_unsubscripted(r));
1092 if let Some(Encoding::Ccsid(op)) = &j.encoding {
1093 self.operand(op);
1094 }
1095 self.statements(j.on_exception.as_deref().unwrap_or_default());
1096 self.statements(j.not_on_exception.as_deref().unwrap_or_default());
1097 }
1098 Stmt::XmlGenerate(x) => {
1099 for r in [&x.receiver, &x.from].into_iter().chain(&x.count) {
1100 self.reference(r);
1101 }
1102 let named = x.names.iter().map(|(r, _)| r).chain(x.types.iter().map(|(r, _)| r));
1103 let suppressed = x.suppress.iter().filter_map(|s| if let Suppression::Item { item, .. } = s { Some(item) } else { None });
1104 named.chain(suppressed).for_each(|r| self.reference_unsubscripted(r));
1105 for op in [&x.encoding, &x.namespace, &x.prefix].into_iter().flatten() {
1106 self.operand(op);
1107 }
1108 self.statements(x.on_exception.as_deref().unwrap_or_default());
1109 self.statements(x.not_on_exception.as_deref().unwrap_or_default());
1110 }
1111 Stmt::JsonGenerate(g) => {
1112 for r in [&g.receiver].into_iter().chain(&g.count) {
1113 self.reference(r);
1114 }
1115 self.whole_table_reference(&g.from);
1116 let mut named: Vec<&Ref> = g.names.iter().map(|(r, _)| r).collect();
1117 for s in &g.suppress {
1118 if let Suppression::Item { item, .. } = s {
1119 named.push(item);
1120 }
1121 }
1122 for (item, conversion) in &g.converting {
1123 named.push(item);
1124 if let JsonConversion::Boolean(Marker::Condition(c)) = conversion {
1125 named.push(c);
1126 }
1127 }
1128 for i in &g.indicating {
1129 named.push(&i.item);
1130 named.extend(&i.indicator);
1131 if let Marker::Condition(c) = &i.marker {
1132 named.push(c);
1133 }
1134 }
1135 named.into_iter().for_each(|r| self.reference_unsubscripted(r));
1136 if let Some(Encoding::Ccsid(op)) = &g.encoding {
1137 self.operand(op);
1138 }
1139 self.statements(g.on_exception.as_deref().unwrap_or_default());
1140 self.statements(g.not_on_exception.as_deref().unwrap_or_default());
1141 }
1142 Stmt::Sorting(s) => self.sorting(s),
1143 Stmt::Exit { kind: kind @ (ExitKind::Perform | ExitKind::PerformCycle), pos } if self.inline_performs == 0 => {
1145 let exit = if *kind == ExitKind::Perform { "EXIT PERFORM" } else { "EXIT PERFORM CYCLE" };
1146 self.errors.push(Error::at(*pos, format!("{exit} must be inside an inline PERFORM")));
1147 }
1148 Stmt::Goback { .. } | Stmt::StopRun { .. } | Stmt::ExitProgram { .. } | Stmt::ExitMethod { .. } | Stmt::Continue | Stmt::Exit { .. } | Stmt::NextSentence | Stmt::SentenceEnd => {}
1149 Stmt::Corresponding(_) => unreachable!("CORRESPONDING is expanded before Check"),
1150 }
1151 }
1152
1153 fn size_error(&mut self, se: Option<&SizeError>) {
1154 if let Some(se) = se {
1155 self.statements(&se.on);
1156 self.statements(&se.not_on);
1157 }
1158 }
1159
1160 fn repeat(&mut self, repeat: &Loop) {
1161 match repeat {
1162 Loop::Once => {}
1163 Loop::Times(e) => self.expr(e),
1164 Loop::Until { cond, .. } => self.cond(cond),
1165 Loop::Varying { varying, after, .. } => {
1166 for v in std::iter::once(&**varying).chain(after) {
1167 self.reference(&v.var);
1168 self.expr(&v.from);
1169 self.expr(&v.by);
1170 self.cond(&v.until);
1171 self.varying(v);
1172 }
1173 }
1174 }
1175 }
1176
1177 fn varying(&mut self, v: &Varying) {
1180 use rt::storage::Kind;
1181 if let Some(i) = self.item(&v.var)
1182 && !matches!(self.layout.items[i].kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::Index)
1183 {
1184 self.errors.push(Error::at(self.at, format!("PERFORM VARYING {}: not a numeric elementary item or an index-name", v.var.name)));
1185 }
1186 for (phrase, e) in [("FROM", &v.from), ("BY", &v.by)] {
1187 if !matches!(e, Expr::Operand(_)) {
1188 self.errors.push(Error::at(self.at, format!("PERFORM VARYING {} {phrase}: an arithmetic expression, where {phrase} takes an identifier, index-name or literal", v.var.name)));
1189 }
1190 }
1191 }
1192
1193 fn file(&mut self, name: &str, pos: Pos) {
1194 if !self.program.files.iter().any(|f| f.name == name) {
1195 self.errors.push(Error::at(pos, format!("no file named {name}")));
1196 }
1197 }
1198
1199 fn keyed_file(&mut self, name: &str, verb: &str, pos: Pos) {
1200 match self.program.files.iter().find(|f| f.name == name) {
1201 None => self.errors.push(Error::at(pos, format!("no file named {name}"))),
1202 Some(f) if !matches!(f.organization, Organization::Indexed | Organization::Relative) => {
1203 self.errors.push(Error::at(pos, format!("{verb} {name}: not an indexed or relative file")));
1204 }
1205 Some(_) => {}
1206 }
1207 }
1208
1209 fn not_random(&mut self, name: &str, verb: &str, pos: Pos) {
1210 if self.program.files.iter().any(|f| f.name == name && f.access == Access::Random) {
1211 self.errors.push(Error::at(pos, format!("{verb} {name}: the file's ACCESS MODE is RANDOM")));
1212 }
1213 }
1214
1215 fn handlers(&mut self, h: &Handlers) {
1216 self.statements(h.on.as_deref().unwrap_or_default());
1217 self.statements(h.not_on.as_deref().unwrap_or_default());
1218 }
1219
1220 fn item(&self, r: &Ref) -> Option<usize> {
1221 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1222 Ok(layout::Resolved::Item(i)) => Some(i),
1223 _ => None,
1224 }
1225 }
1226
1227 fn key_of(&mut self, file: &str, key: &Ref, partial: bool) {
1230 let Some(f) = self.program.files.iter().find(|f| f.name == file) else { return };
1231 if f.organization != Organization::Indexed {
1232 return;
1233 }
1234 let Some(item) = self.item(key).map(|i| &self.layout.items[i]) else { return };
1235 let fits = |r: &Ref| {
1236 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))
1237 };
1238 let named = f.record_key.iter().chain(f.alternate_keys.iter().map(|(r, _)| r)).any(fits);
1239 if item.file.is_none() || !named {
1240 self.errors.push(Error::at(key.pos, format!("{}: not a key of {file}", key.name)));
1241 }
1242 }
1243
1244 fn file_keys(&mut self, k: usize) {
1246 let f = &self.program.files[k];
1247 let in_records = |c: &Self, r: &Ref| c.item(r).is_some_and(|i| c.layout.items[i].file == Some(k as u16));
1248 match f.organization {
1249 Organization::Indexed => {
1250 if f.record_key.is_none() {
1251 self.errors.push(Error::at(f.pos, format!("{}: an indexed file needs a RECORD KEY", f.name)));
1252 }
1253 for r in f.record_key.iter().chain(f.alternate_keys.iter().map(|(r, _)| r)) {
1254 self.reference(r);
1255 if self.item(r).is_some() && !in_records(self, r) {
1256 self.errors.push(Error::at(r.pos, format!("{}: a key of {} must be in its records", r.name, f.name)));
1257 }
1258 }
1259 }
1260 Organization::Relative => {
1261 if let Some(r) = &f.relative_key {
1262 self.reference(r);
1263 if in_records(self, r) {
1264 self.errors.push(Error::at(r.pos, format!("{}: the RELATIVE KEY of {} must not be in its records", r.name, f.name)));
1265 }
1266 } else if f.access != Access::Sequential {
1267 self.errors.push(Error::at(f.pos, format!("{}: random or dynamic access needs a RELATIVE KEY", f.name)));
1268 }
1269 }
1270 _ => {}
1271 }
1272 }
1273
1274 fn record_depending(&mut self, k: usize) {
1277 let f = &self.program.files[k];
1278 let Some(r) = &f.record_depending else { return };
1279 self.reference(r);
1280 if self.item(r).is_some() && linage::unsigned_integer(self.layout, r).is_none() {
1281 self.errors.push(Error::at(r.pos, format!("{}: the DEPENDING ON item of {} must be an elementary unsigned integer", r.name, f.name)));
1282 }
1283 }
1284
1285 fn passwords(&mut self, k: usize) {
1288 let f = &self.program.files[k];
1289 for r in &f.passwords {
1290 self.reference(r);
1291 let Some(i) = self.item(r) else { continue };
1292 let item = &self.layout.items[i];
1293 let working = !item.local && item.file.is_none() && item.linkage.is_none();
1294 if !working || !matches!(item.kind, rt::storage::Kind::Alnum { .. } | rt::storage::Kind::AlnumEdited { .. } | rt::storage::Kind::Group) {
1295 self.errors.push(Error::at(r.pos, format!("{}: the PASSWORD of {} must be an alphabetic, alphanumeric or alphanumeric-edited item of WORKING-STORAGE", r.name, f.name)));
1296 }
1297 }
1298 }
1299
1300 fn procedure(&mut self, p: &ProcName, pos: Pos) {
1301 if let Err(m) = procedure(self.program, p) {
1302 self.errors.push(Error::at(pos, m));
1303 }
1304 }
1305
1306 fn reference(&mut self, r: &Ref) {
1309 self.reference_or_condition(r);
1310 if let Ok(layout::Resolved::Condition(_)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1311 self.errors.push(Error::at(r.pos, format!("{} is a condition-name, not a data item", r.name)));
1312 }
1313 }
1314
1315 fn reference_or_condition(&mut self, r: &Ref) {
1317 if r.name == "RETURN-CODE" && r.qualifiers.is_empty() && self.layout.resolve(&r.name, &r.qualifiers, r.pos).is_err() {
1318 return;
1319 }
1320 if oo::special_register(self.layout, r) || markup::xml_register(self.layout, r) {
1321 return;
1322 }
1323 if !self.debugging && !self.program.declaratives.debugging.is_empty() && declaratives::DEBUG_ITEM_NAMES.contains(&r.name.as_str()) {
1324 self.errors.push(Error::at(r.pos, format!("{}: only a debugging section may reference DEBUG-ITEM", r.name)));
1325 return;
1326 }
1327 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1328 Err(e) => self.errors.push(e),
1329 Ok(layout::Resolved::Item(i)) if self.layout.items[i].dims.len() != r.subscripts.len() => self.errors.push(Error::at(
1330 r.pos,
1331 format!("{} takes {} subscripts, not {}", r.name, self.layout.items[i].dims.len(), r.subscripts.len()),
1332 )),
1333 Ok(_) => {}
1334 }
1335 r.subscripts.iter().for_each(|e| self.expr(e));
1336 if let Some(rm) = &r.refmod {
1337 self.expr(&rm.start);
1338 if let Some(l) = &rm.length {
1339 self.expr(l);
1340 }
1341 }
1342 }
1343
1344 fn exec_block(&mut self, block: &ExecBlock) {
1346 if block.kind == ExecKind::Dli {
1347 self.dli_block(block);
1348 }
1349 if block.kind == ExecKind::Cics && matches!(block.command.as_str(), "HANDLE CONDITION" | "HANDLE AID" | "HANDLE ABEND") {
1350 self.handle_labels(block);
1351 }
1352 if let Some(syntax::sql::Sql { statement: syntax::sql::Statement::Malformed(why), .. }) = &block.sql {
1353 self.errors.push(Error::at(block.pos, format!("EXEC SQL {}: {why}", block.command)));
1354 }
1355 for r in &block.host_variables {
1356 if r.subscripts.is_empty() {
1357 self.reference_unsubscripted(r);
1358 } else {
1359 self.reference(r);
1360 }
1361 }
1362 for (_, arg) in &block.options {
1363 if let Some(ExecArg::Operand(op)) = arg {
1364 self.operand(op);
1365 }
1366 }
1367 }
1368
1369 fn initialize_incompatible(&mut self, targets: &[Ref], with: &InitializeWith, pos: Pos) {
1372 if !with.value.is_empty() || with.default {
1373 return;
1374 }
1375 for r in targets {
1376 let Some(i) = self.item(r) else { continue };
1377 let initialized = match r.refmod {
1378 Some(_) => with.initial_value(Some(self.layout.refmod_category(Some(i), self.layout.items[i].kind)), false).is_some(),
1379 None => self.layout.initialize_receivers(i, with.filler).iter().any(|&(e, _)| with.initial_value(self.layout.category(e), false).is_some()),
1380 };
1381 if !initialized {
1382 let categories: Vec<&str> = with.replacing.iter().map(|(c, _)| c.word()).collect();
1383 self.errors.push(Error::warning(
1384 pos,
1385 format!("INITIALIZE {}: none of its items is of a category REPLACING names ({}), so it is not initialized", r.name, categories.join(", ")),
1386 ));
1387 }
1388 }
1389 }
1390
1391 fn whole_table_reference(&mut self, r: &Ref) {
1394 if let Ok(layout::Resolved::Item(i)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
1395 && self.layout.items[i].table
1396 && self.layout.items[i].dims.len() == r.subscripts.len() + 1
1397 {
1398 r.subscripts.iter().for_each(|e| self.expr(e));
1399 return;
1400 }
1401 self.reference(r);
1402 }
1403
1404 fn dli_block(&mut self, block: &ExecBlock) {
1406 let Some(command) = syntax::dli::find(&block.command) else {
1407 self.errors.push(Error::at(block.pos, format!("EXEC DLI {} is not an EXEC DLI command", block.command)));
1408 return;
1409 };
1410 for (name, arg) in &block.options {
1411 if command.options.is_some_and(|options| !options.contains(&name.as_str())) {
1412 self.errors.push(Error::at(block.pos, format!("EXEC DLI {}: {name} is not one of its options", command.name)));
1413 } else if let (true, Some(ExecArg::Text(t))) = (name == "WHERE", arg)
1414 && let Err(why) = syntax::dli::qualification(t)
1415 {
1416 self.errors.push(Error::at(block.pos, format!("EXEC DLI {} WHERE({t}): {why}", command.name)));
1417 }
1418 }
1419 }
1420
1421 fn inspected_function(&mut self, f: &FunctionCall, i: &Inspect) {
1425 let name = f.name.as_str();
1426 let known = FUNCTIONS.contains(&name) || rt::intrinsic::FUNCTIONS.contains(&name);
1427 let numeric_udf = self.functions.into_iter().flatten().any(|u| u.name == name && !u.character_valued());
1428 if known && !rt::intrinsic::CHARACTER_VALUED.contains(&name) || numeric_udf {
1429 self.errors.push(Error::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")));
1430 }
1431 let stores = if !i.replacing.is_empty() {
1432 Some("REPLACING")
1433 } else {
1434 i.converting.as_ref().map(|_| "CONVERTING")
1435 };
1436 if let Some(phrase) = stores {
1437 self.errors.push(Error::at(f.pos, format!("INSPECT FUNCTION {name} {phrase}: {phrase} stores into the inspected item, and a function-identifier cannot be a receiving operand")));
1438 }
1439 }
1440
1441 fn displayed_function(&mut self, f: &FunctionCall) {
1446 let name = f.name.as_str();
1447 let intrinsic = FUNCTIONS.contains(&name) || rt::intrinsic::FUNCTIONS.contains(&name);
1448 let user_defined = self.functions.into_iter().flatten().any(|u| u.name == name);
1449 let numeric = !rt::intrinsic::CHARACTER_VALUED.contains(&name) || self.numeric_max_or_min(f);
1450 if intrinsic && !user_defined && numeric {
1451 self.errors.push(Error::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")));
1452 }
1453 }
1454
1455 fn inspected_usage(&mut self, target: &Ref, i: &Inspect) {
1460 let Some(t) = self.item(target) else { return };
1461 let usage = CharUsage::of(self.layout.items[t].kind);
1462 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)));
1463 let converting = i.converting.iter().flat_map(|(from, to, bounds)| [from, to].into_iter().chain(bounds.iter().map(|b| &b.value)));
1464 for op in phrases.chain(converting) {
1465 let (operand, operand_usage, pos) = match op {
1466 Operand::Literal(l) => match inspected_literal(l) {
1467 Some((what, operand_usage)) => (what.to_owned(), operand_usage, i.pos),
1468 None => continue,
1469 },
1470 Operand::Ref(r) => match self.item(r) {
1471 Some(k) => (r.name.clone(), CharUsage::of(self.layout.items[k].kind), r.pos),
1472 None => continue,
1473 },
1474 _ => continue,
1475 };
1476 let name = &target.name;
1477 let why = match (usage, operand_usage) {
1478 (u, o) if u == o => continue,
1479 (CharUsage::Display, o) => format!("{name} is not {o}, and an operand can be {o} only when the inspected item is"),
1480 (u, _) => format!("{name} is {u} and every operand but the count field must be {u} too"),
1481 };
1482 self.errors.push(Error::at(pos, format!("INSPECT {name}: {operand} cannot be an operand here, since {why}")));
1483 }
1484 }
1485
1486 fn handle_labels(&mut self, block: &ExecBlock) {
1489 let abend = block.command == "HANDLE ABEND";
1490 for (option, arg) in &block.options {
1491 let Some(ExecArg::Text(label)) = arg else { continue };
1492 let label = label.trim();
1493 if label.is_empty() || matches!(option.as_str(), "RESP" | "RESP2" | "NOHANDLE") || abend && option != "LABEL" {
1494 continue;
1495 }
1496 let name = ProcName { name: label.to_ascii_uppercase(), section: None };
1497 if let Err(m) = procedure_from(self.program, &name, self.paragraph) {
1498 self.errors.push(Error::at(block.pos, format!("EXEC CICS {} {option}({label}): {m}", block.command)));
1499 }
1500 }
1501 }
1502
1503 fn search_varying(&mut self, se: &Search, v: &Ref) {
1506 use rt::storage::Kind;
1507 let Some(i) = self.item(v) else { return };
1508 if !matches!(self.layout.items[i].kind, Kind::Index | Kind::Zoned { scale: 0, .. } | Kind::Packed { scale: 0, .. } | Kind::Binary { scale: 0, .. }) {
1509 self.errors.push(Error::at(v.pos, format!("SEARCH {} VARYING {}: not an index-name, an index data item or an elementary integer item", se.table.name, v.name)));
1510 }
1511 }
1512
1513 fn reference_unsubscripted(&mut self, r: &Ref) {
1515 if let Err(e) = self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
1516 self.errors.push(e);
1517 }
1518 }
1519
1520 fn operand(&mut self, op: &Operand) {
1521 match op {
1522 Operand::LengthOf(r) => self.reference(&self.layout.length_of_ref(r)),
1523 Operand::Ref(r) | Operand::AddressOf(r) => self.reference(r),
1524 Operand::Literal(Literal::Number(t)) if literal_fixed(t).is_none() || literal_digits(t) > self.max_digits as usize => {
1525 self.errors.push(Error::at(Pos::default(), format!("the literal {t} has more than {} digits", self.max_digits.min(31))));
1526 }
1527 Operand::Literal(_) => {}
1528 Operand::Function(f) => {
1529 if !FUNCTIONS.contains(&f.name.as_str()) && !rt::intrinsic::FUNCTIONS.contains(&f.name.as_str()) {
1530 match self.functions.map(|all| all.iter().find(|u| u.name == f.name)) {
1531 Some(Some(udf)) => function::check_invocation(udf, f, self.layout, self.alphabetic, self.program.environment.decimal_point_comma, self.errors),
1532 Some(None) => self.errors.push(Error::at(f.pos, format!("FUNCTION {}: neither an intrinsic function nor a user-defined function defined or prototyped before this program", f.name))),
1533 None => self.errors.push(Error::at(f.pos, format!("FUNCTION {}: a user-defined function is not supported here yet", f.name))),
1534 }
1535 }
1536 self.function_arguments(f);
1537 f.args.iter().for_each(|a| self.expr(a));
1538 }
1539 }
1540 }
1541
1542 fn expr(&mut self, e: &Expr) {
1543 match e {
1544 Expr::Operand(op) => self.operand(op),
1545 Expr::Neg(inner) => self.expr(inner),
1546 Expr::Bin(a, _, b) => {
1547 self.expr(a);
1548 self.expr(b);
1549 }
1550 }
1551 }
1552
1553 fn cond(&mut self, c: &Cond) {
1554 match c {
1555 Cond::Rel(a, _, b) => {
1556 self.expr(a);
1557 self.expr(b);
1558 self.comparison(a, b);
1559 }
1560 Cond::Class(e, _) => self.expr(e),
1561 Cond::Name(r) => self.reference_or_condition(r),
1562 Cond::NameOrRel { subject, name, .. } => {
1563 self.expr(subject);
1564 self.reference_or_condition(name);
1565 if self.item(name).is_some() {
1566 self.comparison(subject, &Expr::Operand(Operand::Ref(name.clone())));
1567 }
1568 }
1569 Cond::Not(inner) => self.cond(inner),
1570 Cond::And(a, b) | Cond::Or(a, b) => {
1571 self.cond(a);
1572 self.cond(b);
1573 }
1574 }
1575 }
1576}
1577
1578#[cfg(test)]
1579mod tests {
1580 use super::*;
1581 use std::ffi::OsStr;
1582 use std::time::Duration;
1583
1584 #[test]
1585 fn source_date_epoch_decides_the_compile_time_and_the_clock_stands_in_for_it() {
1586 let clock = Duration::from_millis(1_790_510_400_428);
1587 let from = |epoch: Option<&str>| compile_time_from(epoch.map(OsStr::new), clock);
1588 assert_eq!(from(None), Ok(CompileTime { seconds: 1_790_510_400, hundredths: 42, source: TimeSource::Clock }));
1589 assert_eq!(from(Some("315532800")), Ok(CompileTime { seconds: 315_532_800, hundredths: 0, source: TimeSource::SourceDateEpoch }));
1590 assert_eq!(from(Some("253402300799")).map(|t| t.seconds), Ok(CompileTime::LATEST));
1591 for bad in ["", "-1", "+5", "1.5", " 7", "253402300800"] {
1592 assert!(from(Some(bad)).unwrap_err().starts_with(&format!("SOURCE_DATE_EPOCH={bad}: ")), "{bad}");
1593 }
1594 }
1595}