Skip to main content

ironwork_compile/
oo.rs

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