1use crate::layout::{Kind, Layout, Resolved};
8use crate::{Check, Compiled};
9use numeric::Options;
10use rt::lir::CompileTime;
11use numeric::assumptions::{INITIAL_UNDER_THREAD, OO_OPTIONS_REQUIRED, OO_OPTIONS_SEVERITY};
12use std::collections::HashSet;
13use std::rc::Rc;
14use syntax::ast::*;
15use syntax::{Error, Pos};
16
17pub use rt::oo::JAVA_LANG_OBJECT;
18
19pub type ClassCode = rt::oo::ClassCode<Rc<Compiled>>;
21pub type Part = rt::oo::Part<Rc<Compiled>>;
23pub type MethodCode = rt::oo::MethodCode<Rc<Compiled>>;
24
25pub(crate) fn compile_class_definition(program: Program, flags: &[String], at: CompileTime) -> Result<Compiled, Vec<Error>> {
27 let mut shell = program.clone();
28 shell.oo = None;
29 let mut compiled = crate::compile_program(shell, flags, false, at)?;
30 let (_, diagnostics) = class_code(&program, flags, at)?;
31 compiled.diagnostics.extend(diagnostics);
32 compiled.program = program;
33 Ok(compiled)
34}
35
36fn object_oriented(program: &Program) -> bool {
39 let files = program.files.iter().flat_map(|f| f.records.iter());
40 let mut data = program.working_storage.iter().chain(&program.local_storage).chain(&program.linkage).chain(files);
41 let mut invoke = false;
42 for p in &program.paragraphs {
43 each(&p.statements, &mut |s| invoke |= matches!(s, Stmt::Invoke(_)));
44 }
45 program.oo.as_deref().is_some_and(|o| o.class().is_some()) || invoke || data.any(|e| e.usage == Some(Usage::ObjectReference))
46}
47
48pub(crate) fn option_rules(program: &Program, options: &Options, errors: &mut Vec<Error>) {
55 let oo = program.oo.as_deref();
56 let method = oo.and_then(Oo::method).is_some();
57 let who = match oo.and_then(Oo::class) {
58 Some(c) => format!("class {}", c.name),
59 None => format!("program {}", program.id),
60 };
61 if !method {
62 let forcing: Vec<&str> = [(options.thread, "THREAD"), (options.dll, "DLL")].into_iter().filter(|(on, _)| *on).map(|(_, o)| o).collect();
63 if !options.rent && !forcing.is_empty() {
64 errors.push(Error::warning(Pos::default(), format!("NORENT conflicts with {}, which IBM compiles only as RENT (see {OO_OPTIONS_REQUIRED})", forcing.join(" and "))));
65 }
66 if object_oriented(program) {
67 let missing: Vec<&str> = [(options.thread, "THREAD"), (options.dll, "DLL"), (options.rent || !forcing.is_empty(), "RENT"), (options.dbcs, "DBCS")]
68 .into_iter()
69 .filter(|(on, _)| !*on)
70 .map(|(_, o)| o)
71 .collect();
72 if !missing.is_empty() {
73 errors.push(Error::warning(
74 Pos::default(),
75 format!(
76 "{who} uses object-oriented syntax, which IBM compiles only with THREAD, DLL, RENT and DBCS: {} missing from its CBL or PROCESS cards (see {OO_OPTIONS_REQUIRED} and {OO_OPTIONS_SEVERITY})",
77 missing.join(", ")
78 ),
79 ));
80 }
81 }
82 }
83 if !options.thread {
84 return;
85 }
86 if !method && options.initial {
87 errors.push(Error::warning(Pos::default(), format!("INITIAL conflicts with THREAD, which IBM compiles only as NOINITIAL (see {INITIAL_UNDER_THREAD})")));
88 }
89 if !method && oo.and_then(Oo::class).is_none() {
90 if !program.recursive {
91 errors.push(Error::at(Pos::default(), format!("{who} is compiled with THREAD, which requires RECURSIVE in its PROGRAM-ID paragraph")));
92 }
93 if program.initial {
94 errors.push(Error::at(Pos::default(), format!("{who} is INITIAL, which THREAD does not allow")));
95 }
96 if let Some(inner) = program.nested.first() {
97 errors.push(Error::at(Pos::default(), format!("{who} contains program {inner}, and THREAD does not allow nested programs")));
98 }
99 }
100 for p in &program.paragraphs {
101 each(&p.statements, &mut |s| {
102 if let Stmt::Sorting(so) = s
103 && let Sorting::Sort(st) = &**so
104 && (st.merge || program.files.iter().any(|f| f.name == st.subject.name))
105 {
106 let verb = if st.merge { "MERGE" } else { "SORT of a file" };
107 errors.push(Error::at(st.pos, format!("{verb} is not allowed in a program compiled with THREAD")));
108 }
109 });
110 }
111}
112
113pub fn defined_class(program: &Program) -> Option<String> {
115 let oo = program.oo.as_ref()?;
116 let class = oo.class()?;
117 Some(oo.external(&class.name).map_or_else(|| external_class_name(&class.name), str::to_owned))
118}
119
120fn records(entries: &[DataEntry]) -> usize {
121 entries.iter().filter(|e| e.level == 1 || e.level == 77).count()
122}
123
124fn declared_names(program: &Program) -> HashSet<String> {
126 let files = program.files.iter().flat_map(|f| f.records.iter());
127 program
128 .working_storage
129 .iter()
130 .chain(&program.local_storage)
131 .chain(&program.linkage)
132 .chain(files)
133 .flat_map(|e| e.name.iter().chain(&e.indexed_by))
134 .cloned()
135 .collect()
136}
137
138pub fn class_code(program: &Program, flags: &[String], at: CompileTime) -> Result<(ClassCode, Vec<Error>), Vec<Error>> {
141 let Some(oo) = program.oo.as_deref() else { return Err(vec![Error::at(Pos::default(), "not a class definition")]) };
142 let Some(def) = oo.class() else { return Err(vec![Error::at(Pos::default(), "not a class definition")]) };
143 let mut errors = Vec::new();
144 let mut options = Options::default();
145 for flag in flags {
146 options.apply_flag(flag).ok();
147 }
148 for option in &program.options {
149 options.apply(option).ok();
150 }
151 option_rules(program, &options, &mut errors);
152 let external = defined_class(program).unwrap_or_default();
153 let parent = match oo.external(&def.inherits) {
154 Some(e) => e.to_owned(),
155 None => {
156 errors.push(Error::at(def.pos, format!("{}: the class a class INHERITS must be named in its REPOSITORY paragraph", def.inherits)));
157 String::new()
158 }
159 };
160 if def.inherits == def.name || parent == external {
161 errors.push(Error::at(def.pos, format!("class {} cannot inherit from itself", def.name)));
162 }
163 let mut base = program.clone();
164 base.oo = None;
165 let mut code = ClassCode { parent, factory: None, object: None, methods: Vec::new() };
166 for (factory, part) in [(true, &def.factory), (false, &def.object)] {
167 let Some(part) = part else { continue };
168 if let Some(e) = part.working_storage.iter().find(|e| !e.indexed_by.is_empty()) {
169 errors.push(Error::at(e.pos, "INDEXED BY in FACTORY or OBJECT data is not supported yet"));
170 continue;
171 }
172 let mut data = base.clone();
173 data.working_storage = part.working_storage.clone();
174 data.oo = Some(Box::new(Oo { repository: oo.repository.clone(), unit: OoUnit::Program }));
175 match crate::compile_program(data, flags, false, at) {
176 Ok(c) => {
177 let offsets = c.layout.items.iter().filter(|i| i.parent.is_none()).take(records(&part.working_storage)).map(|i| i.offset).collect();
178 let compiled = Part { data: Rc::new(c), records: offsets };
179 if factory { code.factory = Some(compiled) } else { code.object = Some(compiled) }
180 }
181 Err(e) => errors.extend(e),
182 }
183 for m in &part.methods {
184 match method_code(program, m, part, factory, flags, at) {
185 Ok(mc) => {
186 errors.extend(mc.code.diagnostics.iter().cloned());
187 code.methods.push(mc);
188 }
189 Err(e) => errors.extend(e),
190 }
191 }
192 }
193 for (k, m) in code.methods.iter().enumerate() {
194 if let Some(twin) = code.methods[..k].iter().find(|o| o.name == m.name && o.params == m.params) {
195 let kind = |f: bool| if f { "factory" } else { "instance" };
196 let pos = m.code.program.oo.as_deref().and_then(Oo::method).map_or(def.pos, |m| m.pos);
197 errors.push(Error::at(pos, format!("{} method \"{}\" has the same parameter types as {} method \"{}\"", kind(m.factory), m.name, kind(twin.factory), twin.name)));
198 }
199 }
200 if crate::refused(&errors, &options) { Err(errors) } else { Ok((code, errors)) }
201}
202
203fn method_code(class: &Program, method: &Program, part: &ClassPart, factory: bool, flags: &[String], at: CompileTime) -> Result<MethodCode, Vec<Error>> {
206 let mut p = method.clone();
207 p.sources = class.sources.clone();
208 p.options = class.options.clone();
209 let own_records = records(&p.linkage);
210 let hidden = declared_names(&p);
211 let hide = |n: &mut String| {
212 if hidden.contains(n) {
213 n.insert(0, ' ');
214 }
215 };
216 if let Some(e) = part.working_storage.iter().find(|e| e.external) {
217 return Err(vec![Error::at(e.pos, format!("{}: EXTERNAL in FACTORY or OBJECT WORKING-STORAGE is not supported yet", e.name.as_deref().unwrap_or("FILLER")))]);
218 }
219 for e in &part.working_storage {
220 let mut e = e.clone();
221 e.value = None;
222 e.global = false;
223 e.name.iter_mut().for_each(hide);
224 e.redefines.iter_mut().for_each(hide);
225 e.depending_on.iter_mut().for_each(|r| hide(&mut r.name));
226 p.linkage.push(e);
227 }
228 let pos = method.oo.as_deref().and_then(Oo::method).map_or(part.pos, |m| m.pos);
229 let name = method.id.clone();
230 let compiled = crate::compile_at(p, flags, at)?;
231 let mut errors = Vec::new();
232 let layout = &compiled.layout;
233 let oo = compiled.program.oo.as_deref();
234 let own = |n: &str| layout.linkage_roots.iter().take(own_records).copied().find(|&i| layout.items[i].name.as_deref() == Some(n));
235 let mut params = Vec::new();
236 for param in &compiled.program.using {
237 if !param.by_value {
238 errors.push(Error::at(pos, format!("method \"{name}\" receives {} BY REFERENCE: a method's parameters are BY VALUE", param.name)));
239 }
240 match own(¶m.name).map(|i| item_type(layout, oo, i)) {
241 Some(Ok(t)) => params.push(t),
242 Some(Err(m)) => errors.push(Error::at(pos, format!("method \"{name}\" parameter {}: {m}", param.name))),
243 None => errors.push(Error::at(pos, format!("method \"{name}\" parameter {}: not a record of the method's own LINKAGE SECTION", param.name))),
244 }
245 }
246 let mut shared = Vec::new();
247 for paragraph in &compiled.program.paragraphs {
248 each(¶graph.statements, &mut |s| {
249 if let Stmt::Set { set: SetStmt::AddressOf { targets, .. }, pos } = s {
250 for r in targets {
251 if let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos)
252 && layout.items[i].linkage.is_some_and(|l| l as usize >= own_records)
253 {
254 shared.push(Error::at(*pos, format!("SET ADDRESS OF {}: FACTORY and OBJECT data is WORKING-STORAGE, not LINKAGE", r.name)));
255 }
256 }
257 }
258 });
259 }
260 errors.extend(shared);
261 let returns = match &compiled.program.returning {
262 None => None,
263 Some(r) => match own(r).map(|i| item_type(layout, oo, i)) {
264 Some(Ok(t)) => Some(t),
265 Some(Err(m)) => {
266 errors.push(Error::at(pos, format!("method \"{name}\" RETURNING {r}: {m}")));
267 None
268 }
269 None => {
270 errors.push(Error::at(pos, format!("method \"{name}\" RETURNING {r}: not a record of the method's own LINKAGE SECTION")));
271 None
272 }
273 },
274 };
275 if !errors.is_empty() {
276 return Err(errors.into_iter().map(|e| e.in_files(&class.sources)).collect());
277 }
278 Ok(MethodCode { name, factory, params, returns, code: Rc::new(compiled), own_records })
279}
280
281pub(crate) fn class_type(external: &str) -> String {
283 match external {
284 "jstring" | "java.lang.String" => "Ljava/lang/String;".into(),
285 "jbooleanArray" => "[Z".into(),
286 "jbyteArray" => "[B".into(),
287 "jshortArray" => "[S".into(),
288 "jintArray" => "[I".into(),
289 "jlongArray" => "[J".into(),
290 "jcharArray" => "[C".into(),
291 "jfloatArray" => "[F".into(),
292 "jdoubleArray" => "[D".into(),
293 "jobjectArray" => "[Ljava/lang/Object;".into(),
294 e => match e.strip_prefix("jobjectArray:") {
295 Some(element) => format!("[L{};", element.replace('.', "/")),
296 None => format!("L{};", e.replace('.', "/")),
297 },
298 }
299}
300
301fn boolean(layout: &Layout, item: usize) -> bool {
303 let byte = |l: &Literal| match l {
304 Literal::Hex(b) if b.len() == 1 => Some(b[0]),
305 Literal::Figurative(Figurative::LowValue) => Some(0x00),
306 Literal::Figurative(Figurative::HighValue) => Some(0xFF),
307 _ => None,
308 };
309 let conditions: Vec<_> = layout.conditions.iter().filter(|c| c.item == item).collect();
310 let is = |low: u8, high: Option<u8>| {
311 conditions.iter().any(|c| matches!(c.values.as_slice(), [(l, h)] if byte(l) == Some(low) && h.as_ref().and_then(byte) == high))
312 };
313 conditions.len() == 2 && is(0x00, None) && is(0x01, Some(0xFF))
314}
315
316pub fn item_type(layout: &Layout, oo: Option<&Oo>, item: usize) -> Result<String, String> {
319 let it = &layout.items[item];
320 Ok(match it.kind {
321 Kind::Alnum { .. } if it.size == 1 => (if boolean(layout, item) { "Z" } else { "B" }).into(),
322 Kind::Binary { digits, scale: 0, signed: true, .. } => (match digits {
323 1..=4 => "S",
324 5..=9 => "I",
325 _ => "J",
326 })
327 .into(),
328 Kind::Float(zarch::hfp::Precision::Short) => "F".into(),
329 Kind::Float(_) => "D".into(),
330 Kind::National if it.size == 2 => "C".into(),
331 Kind::ObjectReference => match &it.object_class {
332 None => format!("L{};", JAVA_LANG_OBJECT.replace('.', "/")),
333 Some(c) => class_type(oo.and_then(|o| o.external(c)).ok_or_else(|| format!("class {c} is not named in the REPOSITORY paragraph"))?),
334 },
335 _ => return Err("not a type Java shares with COBOL: PIC X for byte or boolean, a signed binary integer, COMP-1, COMP-2, PIC N for char, or an object reference".into()),
336 })
337}
338
339pub fn operand_type(layout: &Layout, oo: Option<&Oo>, op: &Operand) -> Result<String, String> {
342 match op {
343 Operand::Ref(r) => {
344 let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos) else { return Err(format!("{} is not a data item", r.name)) };
345 match &r.refmod {
346 None => item_type(layout, oo, i),
347 Some(_) if layout.items[i].kind == Kind::National => Err("a reference-modified national argument is not supported yet".into()),
348 Some(rm) if matches!(rm.length.as_deref(), Some(Expr::Operand(Operand::Literal(Literal::Number(n)))) if n == "1") => Ok("C".into()),
349 Some(_) => Err("a reference-modified argument must be one character long".into()),
350 }
351 }
352 Operand::LengthOf(_) => Ok("I".into()),
353 Operand::Literal(Literal::Number(t)) if !t.contains('.') && t.trim_start_matches(['+', '-']).len() <= 9 => Ok("I".into()),
354 Operand::Literal(Literal::Figurative(Figurative::Zero)) => Ok("I".into()),
355 Operand::Literal(Literal::Figurative(Figurative::Space | Figurative::Quote | Figurative::HighValue | Figurative::LowValue)) => Ok("B".into()),
356 Operand::Literal(Literal::Alnum(s)) if s.chars().count() == 1 => Ok("B".into()),
357 Operand::Literal(Literal::National(s)) if s.chars().count() == 1 => Ok("C".into()),
358 _ => Err("not an argument Java takes: an item of a type Java shares, LENGTH OF, an integer literal of up to nine digits, or a one-character literal".into()),
359 }
360}
361
362fn is_named(r: &Ref, name: &str) -> bool {
363 r.name == name && r.qualifiers.is_empty() && r.subscripts.is_empty() && r.refmod.is_none()
364}
365
366pub(crate) fn special_register(layout: &Layout, r: &Ref) -> bool {
368 (is_named(r, "SELF") || is_named(r, "JNIENVPTR")) && layout.resolve(&r.name, &r.qualifiers, r.pos).is_err()
369}
370
371fn class_name<'o>(layout: &Layout, oo: Option<&'o Oo>, r: &Ref) -> Option<&'o str> {
373 let external = oo?.external(&r.name)?;
374 (r.qualifiers.is_empty() && r.subscripts.is_empty() && r.refmod.is_none() && layout.resolve(&r.name, &r.qualifiers, r.pos).is_err()).then_some(external)
375}
376
377impl Check<'_> {
378 pub(crate) fn invoke(&mut self, i: &Invoke) {
379 let oo = self.program.oo.as_deref();
380 let in_method = oo.and_then(Oo::method).is_some();
381 let err = |m: String| Error::at(i.pos, m);
382 let special = is_named(&i.target, "SELF") || is_named(&i.target, "SUPER");
383 let class = class_name(self.layout, oo, &i.target).is_some();
384 let mut typed = false;
385 if special && self.layout.resolve(&i.target.name, &[], i.pos).is_err() {
386 if !in_method {
387 self.errors.push(err(format!("INVOKE {}: SELF and SUPER can be used only in a method", i.target.name)));
388 }
389 } else if !class {
390 self.reference(&i.target);
391 match self.layout.resolve(&i.target.name, &i.target.qualifiers, i.target.pos) {
392 Ok(Resolved::Item(k)) if self.layout.items[k].kind == Kind::ObjectReference => typed = self.layout.items[k].object_class.is_some(),
393 Ok(_) => self.errors.push(err(format!("INVOKE {}: not an object reference or a class named in the REPOSITORY paragraph", i.target.name))),
394 Err(_) => {}
395 }
396 }
397 match &i.method {
398 InvokeMethod::New => {
399 if !class {
400 self.errors.push(err(format!("INVOKE {} NEW: NEW takes a class-name from the REPOSITORY paragraph", i.target.name)));
401 }
402 match &i.returning {
403 None => self.errors.push(err("INVOKE ... NEW needs RETURNING an object reference".into())),
404 Some(r) if !matches!(self.layout.resolve(&r.name, &r.qualifiers, r.pos), Ok(Resolved::Item(k)) if self.layout.items[k].kind == Kind::ObjectReference) => {
405 self.errors.push(err(format!("INVOKE ... NEW RETURNING {}: not an object reference", r.name)));
406 }
407 Some(_) => {}
408 }
409 }
410 InvokeMethod::Named(name) if name.is_empty() => self.errors.push(err("INVOKE with an empty method name".into())),
411 InvokeMethod::Named(_) => {}
412 InvokeMethod::Identifier(r) => {
413 self.reference(r);
414 if let Ok(Resolved::Item(k)) = self.layout.resolve(&r.name, &r.qualifiers, r.pos)
415 && !matches!(self.layout.items[k].kind, Kind::Alnum { .. } | Kind::National | Kind::Group)
416 {
417 self.errors.push(err(format!("INVOKE ... {}: a method name is held in an alphanumeric or national item", r.name)));
418 }
419 if typed {
420 self.errors.push(err(format!("INVOKE {} {}: a method named by a data item is invoked on a universal object reference", i.target.name, r.name)));
421 }
422 }
423 }
424 for op in &i.using {
425 self.operand(op);
426 if let Err(m) = operand_type(self.layout, oo, op) {
427 self.errors.push(err(format!("INVOKE argument: {m}")));
428 }
429 }
430 if let Some(r) = &i.returning {
431 self.reference(r);
432 if r.refmod.is_some() {
433 self.errors.push(err(format!("INVOKE ... RETURNING {}: not reference-modified", r.name)));
434 }
435 if let (false, Ok(Resolved::Item(k))) = (i.method == InvokeMethod::New, self.layout.resolve(&r.name, &r.qualifiers, r.pos))
436 && let Err(m) = item_type(self.layout, oo, k)
437 {
438 self.errors.push(err(format!("INVOKE ... RETURNING {}: {m}", r.name)));
439 }
440 }
441 self.statements(i.on_exception.as_deref().unwrap_or_default());
442 self.statements(i.not_on_exception.as_deref().unwrap_or_default());
443 }
444}
445
446#[derive(Clone, Copy, PartialEq, Eq)]
447enum Side {
448 Object,
449 ProgramPointer,
450 DataPointer,
452 Null,
453 Other,
454}
455
456struct Rules<'a> {
459 layout: &'a Layout,
460 method: bool,
461 uses_oo: bool,
462 errors: &'a mut Vec<Error>,
463}
464
465pub(crate) fn check(layout: &Layout, program: &Program, errors: &mut Vec<Error>) {
466 let oo = program.oo.as_deref();
467 let method = oo.and_then(Oo::method).is_some();
468 let mut rules = Rules { layout, method, uses_oo: oo.is_some_and(|o| !o.repository.is_empty()), errors };
469 for item in &layout.items {
470 if item.kind == Kind::ObjectReference {
471 rules.uses_oo = true;
472 }
473 if let Some(c) = &item.object_class
474 && oo.and_then(|o| o.external(c)).is_none()
475 {
476 rules.errors.push(Error::at(item.pos, format!("OBJECT REFERENCE {c}: the class must be named in the REPOSITORY paragraph")));
477 }
478 }
479 if method && let Some(f) = program.files.first() {
480 rules.errors.push(Error::at(f.pos, "a method's FILE SECTION can define only EXTERNAL files, which ironwork for COBOL does not support yet"));
481 }
482 for p in &program.paragraphs {
483 rules.statements(&p.statements);
484 }
485 for report in &program.report_writer.reports {
487 report.controls.iter().for_each(|c| rules.plain(&Operand::Ref(c.clone()), c.pos));
488 for e in report.groups.iter().flat_map(|g| &g.entries) {
489 if let Some(syntax::report::Content::Source(x)) = &e.content {
490 rules.expr(x, e.pos);
491 }
492 }
493 }
494 let exec = program.exec_declarations.iter().map(|b| (b.kind, b.pos)).chain(execs(&program.paragraphs));
495 for (kind, pos) in exec {
496 if method {
497 rules.errors.push(Error::at(pos, "a class definition cannot contain EXEC statements"));
498 } else if kind == ExecKind::Cics && rules.uses_oo {
499 rules.errors.push(Error::at(pos, "a program that uses object-oriented syntax cannot contain EXEC CICS"));
500 }
501 }
502}
503
504fn execs(paragraphs: &[Paragraph]) -> Vec<(ExecKind, Pos)> {
505 let mut out = Vec::new();
506 for p in paragraphs {
507 each(&p.statements, &mut |s| {
508 if let Stmt::Exec(b) = s {
509 out.push((b.kind, b.pos));
510 }
511 });
512 }
513 out
514}
515
516fn each(stmts: &[Stmt], f: &mut dyn FnMut(&Stmt)) {
518 for s in stmts {
519 f(s);
520 for body in bodies(s) {
521 each(body, f);
522 }
523 }
524}
525
526pub fn each_mut(stmts: &mut [Stmt], f: &mut dyn FnMut(&mut Stmt)) {
528 for s in stmts {
529 f(s);
530 for body in bodies_mut(s) {
531 each_mut(body, f);
532 }
533 }
534}
535
536fn opt(o: &Option<Vec<Stmt>>) -> &[Stmt] {
537 o.as_deref().unwrap_or_default()
538}
539
540fn handlers(h: &Handlers) -> [&[Stmt]; 2] {
541 [opt(&h.on), opt(&h.not_on)]
542}
543
544pub fn bodies(s: &Stmt) -> Vec<&[Stmt]> {
545 match s {
546 Stmt::If { then, otherwise, .. } => vec![then, otherwise],
547 Stmt::PerformInline { body, .. } => vec![body],
548 Stmt::Evaluate { whens, other, .. } => whens.iter().map(|w| w.body.as_slice()).chain([other.as_slice()]).collect(),
549 Stmt::Compute { size_error: Some(se), .. } => vec![&se.on, &se.not_on],
550 Stmt::Arith(a) => a.size_error.iter().flat_map(|se| [se.on.as_slice(), se.not_on.as_slice()]).collect(),
551 Stmt::Corresponding(c) => c.size_error.iter().flat_map(|se| [se.on.as_slice(), se.not_on.as_slice()]).collect(),
552 Stmt::Read(r) => handlers(&r.at_end).into_iter().chain(handlers(&r.invalid)).collect(),
553 Stmt::Write { invalid, end_of_page, .. } => handlers(invalid).into_iter().chain(handlers(end_of_page)).collect(),
554 Stmt::Rewrite { invalid, .. } | Stmt::Delete { invalid, .. } | Stmt::Start { invalid, .. } => handlers(invalid).to_vec(),
555 Stmt::Call(c) => vec![opt(&c.on_exception), opt(&c.not_on_exception)],
556 Stmt::Invoke(i) => vec![opt(&i.on_exception), opt(&i.not_on_exception)],
557 Stmt::JsonGenerate(g) => vec![opt(&g.on_exception), opt(&g.not_on_exception)],
558 Stmt::JsonParse(j) => vec![opt(&j.on_exception), opt(&j.not_on_exception)],
559 Stmt::XmlParse(x) => vec![opt(&x.on_exception), opt(&x.not_on_exception)],
560 Stmt::XmlGenerate(x) => vec![opt(&x.on_exception), opt(&x.not_on_exception)],
561 Stmt::String(st) => vec![opt(&st.on_overflow), opt(&st.not_on_overflow)],
562 Stmt::Unstring(u) => vec![opt(&u.on_overflow), opt(&u.not_on_overflow)],
563 Stmt::Search(se) => se.whens.iter().map(|(_, b)| b.as_slice()).chain([opt(&se.at_end)]).collect(),
564 Stmt::Sorting(so) => match &**so {
565 Sorting::Return { at_end, .. } => handlers(at_end).to_vec(),
566 _ => Vec::new(),
567 },
568 _ => Vec::new(),
569 }
570}
571
572pub fn bodies_mut(s: &mut Stmt) -> Vec<&mut Vec<Stmt>> {
574 fn handlers(h: &mut Handlers) -> impl Iterator<Item = &mut Vec<Stmt>> {
575 h.on.iter_mut().chain(h.not_on.iter_mut())
576 }
577 fn pair<'s>(a: &'s mut Option<Vec<Stmt>>, b: &'s mut Option<Vec<Stmt>>) -> Vec<&'s mut Vec<Stmt>> {
578 a.iter_mut().chain(b.iter_mut()).collect()
579 }
580 match s {
581 Stmt::If { then, otherwise, .. } => vec![then, otherwise],
582 Stmt::PerformInline { body, .. } => vec![body],
583 Stmt::Evaluate { whens, other, .. } => whens.iter_mut().map(|w| &mut w.body).chain([other]).collect(),
584 Stmt::Compute { size_error: Some(se), .. } => vec![&mut se.on, &mut se.not_on],
585 Stmt::Arith(a) => a.size_error.iter_mut().flat_map(|se| [&mut se.on, &mut se.not_on]).collect(),
586 Stmt::Corresponding(c) => c.size_error.iter_mut().flat_map(|se| [&mut se.on, &mut se.not_on]).collect(),
587 Stmt::Read(r) => handlers(&mut r.at_end).chain(handlers(&mut r.invalid)).collect(),
588 Stmt::Write { invalid, end_of_page, .. } => handlers(invalid).chain(handlers(end_of_page)).collect(),
589 Stmt::Rewrite { invalid, .. } | Stmt::Delete { invalid, .. } | Stmt::Start { invalid, .. } => handlers(invalid).collect(),
590 Stmt::Call(c) => pair(&mut c.on_exception, &mut c.not_on_exception),
591 Stmt::Invoke(i) => pair(&mut i.on_exception, &mut i.not_on_exception),
592 Stmt::JsonGenerate(g) => pair(&mut g.on_exception, &mut g.not_on_exception),
593 Stmt::JsonParse(j) => pair(&mut j.on_exception, &mut j.not_on_exception),
594 Stmt::XmlParse(x) => pair(&mut x.on_exception, &mut x.not_on_exception),
595 Stmt::XmlGenerate(x) => pair(&mut x.on_exception, &mut x.not_on_exception),
596 Stmt::String(st) => pair(&mut st.on_overflow, &mut st.not_on_overflow),
597 Stmt::Unstring(u) => pair(&mut u.on_overflow, &mut u.not_on_overflow),
598 Stmt::Search(se) => se.whens.iter_mut().map(|(_, b)| b).chain(se.at_end.iter_mut()).collect(),
599 Stmt::Sorting(so) => match &mut **so {
600 Sorting::Return { at_end, .. } => handlers(at_end).collect(),
601 _ => Vec::new(),
602 },
603 _ => Vec::new(),
604 }
605}
606
607impl Rules<'_> {
608 fn statements(&mut self, stmts: &[Stmt]) {
609 for s in stmts {
610 self.statement(s);
611 for body in bodies(s) {
612 self.statements(body);
613 }
614 }
615 }
616
617 fn statement(&mut self, s: &Stmt) {
618 match s {
619 Stmt::Move { from, to, pos } => {
620 self.plain(from, *pos);
621 to.iter().for_each(|r| self.receiver(r, *pos));
622 }
623 Stmt::Display { items, pos, .. } => items.iter().for_each(|o| self.plain(o, *pos)),
624 Stmt::Compute { targets, expr, pos, .. } => {
625 targets.iter().for_each(|t| self.receiver(&t.r, *pos));
626 self.expr(expr, *pos);
627 }
628 Stmt::Arith(a) => {
629 for (t, e) in &a.computations {
630 self.receiver(&t.r, a.pos);
631 self.expr(e, a.pos);
632 }
633 }
634 Stmt::Initialize { targets, pos, .. } | Stmt::Set { set: SetStmt::UpDown { targets, .. }, pos } => targets.iter().for_each(|r| self.receiver(r, *pos)),
635 Stmt::Accept { target, pos, .. } => self.receiver(target, *pos),
636 Stmt::String(st) => {
637 st.sources.iter().for_each(|(o, _)| self.plain(o, st.pos));
638 self.receiver(&st.into, st.pos);
639 }
640 Stmt::Unstring(u) => {
641 self.plain(&Operand::Ref(u.source.clone()), u.pos);
642 u.into.iter().for_each(|i| self.receiver(&i.target, u.pos));
643 }
644 Stmt::Inspect(i) => match &i.target {
645 Operand::Ref(r) => self.receiver(r, i.pos),
646 subject => self.plain(subject, i.pos),
647 },
648 Stmt::If { cond, pos, .. } => self.cond(cond, *pos),
649 Stmt::PerformInline { repeat, pos, .. } | Stmt::PerformProc { repeat, pos, .. } => match repeat {
650 Loop::Until { cond, .. } => self.cond(cond, *pos),
651 Loop::Varying { varying, after, .. } => std::iter::once(&**varying).chain(after).for_each(|v| self.cond(&v.until, *pos)),
652 _ => {}
653 },
654 Stmt::Evaluate { subjects, whens, pos, .. } => {
655 for subject in subjects {
656 match subject {
657 Subject::Cond(c) => self.cond(c, *pos),
658 Subject::Expr(e) => self.expr(e, *pos),
659 Subject::Bool(_) => {}
660 }
661 }
662 for w in whens {
663 for object in w.alternatives.iter().flatten() {
664 match object {
665 Object::Cond(c) => self.cond(c, *pos),
666 Object::Value { from, thru, .. } => thru.iter().chain([from]).for_each(|e| self.expr(e, *pos)),
667 _ => {}
668 }
669 }
670 }
671 }
672 Stmt::Search(se) => se.whens.iter().for_each(|(c, _)| self.cond(c, se.pos)),
673 Stmt::Set { set, pos } => self.set(set, *pos),
674 Stmt::ExitMethod { pos } if !self.method => self.errors.push(Error::at(*pos, "EXIT METHOD can be used only in a method")),
675 Stmt::ExitProgram { pos } if self.method => self.errors.push(Error::at(*pos, "EXIT PROGRAM cannot be used in a method: use EXIT METHOD or GOBACK")),
676 Stmt::Invoke(_) => self.uses_oo = true,
677 Stmt::Sorting(so) => match &**so {
678 Sorting::Release { from: Some(op), pos, .. } => self.plain(op, *pos),
679 Sorting::Return { into: Some(r), pos, .. } => self.receiver(r, *pos),
680 _ => {}
681 },
682 _ => {}
683 }
684 }
685
686 fn is_self(&self, r: &Ref) -> bool {
687 is_named(r, "SELF") && self.layout.resolve(&r.name, &[], r.pos).is_err()
688 }
689
690 fn side_of_ref(&mut self, r: &Ref, pos: Pos) -> Side {
691 if self.is_self(r) {
692 if !self.method {
693 self.errors.push(Error::at(pos, "SELF can be used only in a method"));
694 }
695 return Side::Object;
696 }
697 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
698 Ok(Resolved::Item(i)) if self.layout.items[i].kind == Kind::ObjectReference => Side::Object,
699 Ok(Resolved::Item(i)) if self.layout.items[i].kind == Kind::ProgramPointer => Side::ProgramPointer,
700 _ => Side::Other,
701 }
702 }
703
704 fn side(&mut self, op: &Operand, pos: Pos) -> Side {
705 match op {
706 Operand::Ref(r) => self.side_of_ref(r, pos),
707 Operand::Literal(Literal::Figurative(Figurative::Null)) => Side::Null,
708 _ => Side::Other,
709 }
710 }
711
712 fn plain(&mut self, op: &Operand, pos: Pos) {
714 let what = match self.side(op, pos) {
715 Side::Object => "an object reference",
716 Side::ProgramPointer => "a function-pointer or procedure-pointer",
717 _ => return,
718 };
719 let name = if let Operand::Ref(r) = op { r.name.as_str() } else { "" };
720 self.errors.push(Error::at(pos, format!("{name} is {what}: it can be used only in SET, INVOKE, CALL and a relation condition")));
721 }
722
723 fn receiver(&mut self, r: &Ref, pos: Pos) {
724 if is_named(r, "JNIENVPTR") && self.layout.resolve(&r.name, &[], r.pos).is_err() {
725 self.errors.push(Error::at(pos, "JNIENVPTR cannot receive a value"));
726 } else if self.is_self(r) {
727 self.errors.push(Error::at(pos, "SELF cannot receive a value"));
728 } else {
729 self.plain(&Operand::Ref(r.clone()), pos);
730 }
731 }
732
733 fn expr(&mut self, e: &Expr, pos: Pos) {
734 match e {
735 Expr::Operand(op) => self.plain(op, pos),
736 Expr::Neg(inner) => self.expr(inner, pos),
737 Expr::Bin(a, _, b) => {
738 self.expr(a, pos);
739 self.expr(b, pos);
740 }
741 }
742 }
743
744 fn cond(&mut self, c: &Cond, pos: Pos) {
745 match c {
746 Cond::Rel(a, op, b) => {
747 let side = |r: &mut Self, e: &Expr| match e {
748 Expr::Operand(o) => r.side(o, pos),
749 _ => Side::Other,
750 };
751 let (x, y) = (side(self, a), side(self, b));
752 let handle = |s: Side| matches!(s, Side::Object | Side::ProgramPointer);
753 if !handle(x) && !handle(y) {
754 self.expr(a, pos);
755 self.expr(b, pos);
756 return;
757 }
758 if !matches!(op, RelOp::Eq | RelOp::Ne) {
759 self.errors.push(Error::at(pos, "object references and function-pointers compare only as equal or not equal"));
760 }
761 let fits = |s: Side, other: Side| s == other || s == Side::Null || other == Side::Null;
762 if !fits(x, y) {
763 self.errors.push(Error::at(pos, "an object reference compares with another object reference, SELF or NULL; a function-pointer with another or NULL"));
764 }
765 }
766 Cond::Class(e, _) => self.expr(e, pos),
767 Cond::Not(inner) => self.cond(inner, pos),
768 Cond::And(a, b) | Cond::Or(a, b) => {
769 self.cond(a, pos);
770 self.cond(b, pos);
771 }
772 Cond::Name(_) | Cond::NameOrRel { .. } => {}
773 }
774 }
775
776 fn kind_of(&self, r: &Ref) -> Option<Kind> {
777 match self.layout.resolve(&r.name, &r.qualifiers, r.pos) {
778 Ok(Resolved::Item(i)) => Some(self.layout.items[i].kind),
779 _ => None,
780 }
781 }
782
783 fn set_entry(&mut self, targets: &[Ref], entry: &Operand, pos: Pos) {
786 for r in targets {
787 if self.side_of_ref(r, pos) != Side::ProgramPointer {
788 self.errors.push(Error::at(pos, format!("SET {} TO ENTRY: the receiver must be a procedure-pointer or function-pointer", r.name)));
789 }
790 }
791 let fault = match entry {
792 Operand::Literal(Literal::Alnum(name)) if name.trim().is_empty() => Some("the literal names no entry".to_owned()),
793 Operand::Literal(Literal::Alnum(_) | Literal::Hex(_)) => None,
794 Operand::Literal(_) => Some("the entry literal must be alphanumeric".to_owned()),
795 Operand::Ref(r) => match self.kind_of(r) {
796 Some(Kind::Alnum { .. } | Kind::Group) | None => None,
797 Some(_) => Some(format!("{} must be an alphanumeric or alphabetic item", r.name)),
798 },
799 Operand::Function(f) if crate::FUNCTIONS.contains(&f.name.as_str()) || rt::intrinsic::FUNCTIONS.contains(&f.name.as_str()) => Some(format!("FUNCTION {} is an intrinsic function, not a user-defined function returning a pointer", f.name)),
800 Operand::Function(_) => None,
801 Operand::LengthOf(_) | Operand::AddressOf(_) => Some("the entry must be a literal or identifier".to_owned()),
802 };
803 if let Some(fault) = fault {
804 let names: Vec<&str> = targets.iter().map(|r| r.name.as_str()).collect();
805 self.errors.push(Error::at(pos, format!("SET {} TO ENTRY: {fault}", names.join(" "))));
806 }
807 }
808
809 fn set(&mut self, set: &SetStmt, pos: Pos) {
810 if let SetStmt::Entry { targets, entry } = set {
811 return self.set_entry(targets, entry, pos);
812 }
813 let SetStmt::To { targets, value } = set else { return };
814 let value_side = match value {
815 Operand::Ref(r) if self.kind_of(r) == Some(Kind::Pointer) => Side::DataPointer,
816 _ => self.side(value, pos),
817 };
818 for r in targets {
819 if is_named(r, "JNIENVPTR") && self.layout.resolve(&r.name, &[], r.pos).is_err() {
820 self.errors.push(Error::at(pos, "JNIENVPTR cannot receive a value"));
821 continue;
822 }
823 if self.is_self(r) {
824 self.errors.push(Error::at(pos, "SELF cannot receive a value"));
825 continue;
826 }
827 let message = match (self.side_of_ref(r, pos), value_side) {
828 (Side::Object, Side::Object | Side::Null) | (Side::ProgramPointer, Side::ProgramPointer | Side::DataPointer | Side::Null) => continue,
829 (Side::Object, _) => "an object reference takes another object reference, SELF or NULL",
830 (Side::ProgramPointer, _) => "a function-pointer or procedure-pointer takes another, a pointer, ENTRY or NULL",
831 (_, Side::Object | Side::ProgramPointer) => "an object reference or function-pointer can be set only into its own kind",
832 _ => continue,
833 };
834 self.errors.push(Error::at(pos, format!("SET {} TO: {message}", r.name)));
835 }
836 }
837}