use crate::layout::{Binding, Layout, Resolved, Section};
use syntax::ast::*;
use syntax::Error;
#[derive(Default)]
pub(crate) struct Inherited {
pub entries: Vec<DataEntry>,
records: Vec<(Binding, u8, Option<usize>)>,
}
pub(crate) fn rules(program: &Program, errors: &mut Vec<Error>) {
let contains = !program.nested.is_empty();
let sections: [(&str, &[DataEntry]); 3] = [("WORKING-STORAGE", &program.working_storage), ("LOCAL-STORAGE", &program.local_storage), ("LINKAGE", &program.linkage)];
let mut externals: Vec<&str> = Vec::new();
let mut globals: Vec<&str> = Vec::new();
for (section, entries) in sections {
let mut record: Option<&DataEntry> = None;
for e in entries {
if matches!(e.level, 1 | 77) {
record = Some(e);
}
let name = e.name.as_deref().unwrap_or("FILLER");
if e.external {
if section != "WORKING-STORAGE" {
errors.push(Error::at(e.pos, format!("{name}: EXTERNAL is not allowed in the {section} SECTION")));
} else if e.level != 1 {
errors.push(Error::at(e.pos, format!("{name}: EXTERNAL goes on a level-01 entry")));
} else if e.redefines.is_some() {
errors.push(Error::at(e.pos, format!("{name}: EXTERNAL and REDEFINES cannot be in the same entry")));
} else if e.name.is_none() {
errors.push(Error::at(e.pos, "an EXTERNAL record needs a data-name, not FILLER"));
} else if externals.contains(&name) {
errors.push(Error::at(e.pos, format!("{name}: another EXTERNAL record of the program has the same name")));
} else {
externals.push(name);
}
}
if e.global {
if e.level != 1 {
errors.push(Error::at(e.pos, format!("{name}: GLOBAL goes on a level-01 entry")));
} else if e.name.is_none() {
errors.push(Error::at(e.pos, "a GLOBAL record needs a data-name, not FILLER"));
} else if globals.contains(&name) {
errors.push(Error::at(e.pos, format!("{name}: another GLOBAL record of the DATA DIVISION has the same name")));
} else {
globals.push(name);
}
}
if let Some(r) = record.filter(|r| r.external && e.level != 88 && e.value.is_some()) {
errors.push(Error::at(e.pos, format!("{name}: an item of EXTERNAL record {} takes no VALUE clause", r.name.as_deref().unwrap_or_default())));
}
if contains && !e.indexed_by.is_empty() && record.is_some_and(|r| r.global) {
errors.push(Error::at(e.pos, format!("{name}: INDEXED BY in a GLOBAL record, in a program that contains others, is not supported yet")));
}
}
}
for f in &program.files {
if (f.external || f.global) && f.records.iter().any(|e| e.level == 1 && e.name.is_none()) {
errors.push(Error::at(f.pos, format!("FD {}: a record of an EXTERNAL or GLOBAL file needs a data-name, not FILLER", f.name)));
}
if f.external && f.linage.is_some() {
errors.push(Error::at(f.pos, format!("FD {}: LINAGE on an EXTERNAL file is not supported yet", f.name)));
}
if f.external && !f.reports.is_empty() {
errors.push(Error::at(f.pos, format!("FD {}: REPORT on an EXTERNAL file is not supported yet", f.name)));
}
if contains && f.global && (f.linage.is_some() || !f.reports.is_empty()) {
errors.push(Error::at(f.pos, format!("FD {}: LINAGE or REPORT on a GLOBAL file, in a program that contains others, is not supported yet", f.name)));
}
if contains && !f.global && let Some(e) = f.records.iter().find(|e| e.global) {
errors.push(Error::at(e.pos, format!("{}: a GLOBAL record of FD {}, which is not GLOBAL, in a program that contains others, is not supported yet", e.name.as_deref().unwrap_or("FILLER"), f.name)));
}
}
}
pub(crate) fn inherit(program: &mut Program) -> Inherited {
let mut inherited = Inherited::default();
let containers = std::mem::take(&mut program.containers);
for (n, c) in containers.iter().enumerate() {
let depth = u8::try_from(n + 1).unwrap_or(u8::MAX);
let sections = [(Section::WorkingStorage, &c.working_storage), (Section::LocalStorage, &c.local_storage), (Section::Linkage, &c.linkage)];
for (section, entries) in sections {
for e in entries {
if matches!(e.level, 1 | 77) {
let record = e.name.clone().unwrap_or_default();
let binding = if e.external { Binding::External { name: record, size: 0 } } else { Binding::Global { program: c.id.clone(), record, section: section.clone() } };
inherited.records.push((binding, depth, None));
}
inherited.entries.push(e.clone());
}
}
for f in &c.files {
if program.files.iter().any(|own| own.name == f.name) {
continue;
}
let k = program.files.len();
let mut copy = f.clone();
copy.declared_in = Some(c.id.clone());
copy.linage = None;
copy.reports.clear();
for e in std::mem::take(&mut copy.records) {
if matches!(e.level, 1 | 77) {
let binding = if f.external { Binding::ExternalFile(k as u16) } else { Binding::Global { program: c.id.clone(), record: String::new(), section: Section::File(f.name.clone()) } };
inherited.records.push((binding, depth, Some(k)));
}
inherited.entries.push(e);
}
program.files.push(copy);
}
}
program.containers = containers;
inherited
}
pub(crate) fn bind(layout: &mut Layout, own: usize, inherited: Inherited, files: &[FileDecl]) {
for (n, (binding, depth, file)) in inherited.records.into_iter().enumerate() {
let ordinal = own + n;
let Some(&root) = layout.linkage_roots.get(ordinal) else { continue };
let size = layout.items[root].size;
layout.bindings[ordinal] = match binding {
Binding::External { name, .. } => Binding::External { name, size },
other => other,
};
layout.depths[ordinal] = depth;
let Some(k) = file else { continue };
for i in 0..layout.items.len() {
if layout.items[i].linkage == Some(ordinal as u16) {
layout.items[i].file = Some(k as u16);
}
}
layout.bound_areas[k].get_or_insert(ordinal as u16);
let area = layout.file_areas[k].1.max(size).max(files[k].record_max.unwrap_or(0));
layout.file_areas[k] = (0, area);
}
}
pub(crate) fn check(program: &Program, layout: &Layout, errors: &mut Vec<Error>) {
for (&root, binding) in layout.linkage_roots.iter().zip(&layout.bindings) {
let item = &layout.items[root];
if let Binding::External { name, size } = binding
&& item.redefines.is_some()
&& item.size > *size
{
errors.push(Error::at(item.pos, format!("{}: {} bytes, larger than the EXTERNAL record {name} it redefines", item.name.as_deref().unwrap_or_default(), item.size)));
}
}
for f in &program.files {
let Some(declarer) = &f.declared_in else { continue };
let refs = f.status.iter().map(|r| ("FILE STATUS", r)).chain(f.record_key.iter().map(|r| ("RECORD KEY", r))).chain(f.alternate_keys.iter().map(|(r, _)| ("ALTERNATE RECORD KEY", r))).chain(f.relative_key.iter().map(|r| ("RELATIVE KEY", r)));
for (clause, r) in refs {
if !declared_by(layout, r, declarer) {
errors.push(Error::at(r.pos, format!("{}, a GLOBAL file of {declarer}: its {clause} {} is not a GLOBAL name of {declarer}, which is not supported yet", f.name, r.name)));
}
}
}
}
fn declared_by(layout: &Layout, r: &Ref, program: &str) -> bool {
let Ok(Resolved::Item(mut i)) = layout.resolve(&r.name, &r.qualifiers, r.pos) else { return false };
while let Some(p) = layout.items[i].parent {
i = p;
}
let binding = layout.items[i].linkage.and_then(|l| layout.bindings.get(l as usize));
matches!(binding, Some(Binding::Global { program: p, .. }) if p == program)
}
pub(crate) fn set_address(layout: &Layout, r: &Ref, errors: &mut Vec<Error>) {
let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos) else { return };
let Some(l) = layout.items[i].linkage.filter(|_| layout.items[i].parent.is_none()) else { return };
match &layout.bindings[l as usize] {
Binding::Argument => {}
Binding::Global { section: Section::Linkage, program, .. } => {
errors.push(Error::at(r.pos, format!("SET ADDRESS OF {}, a GLOBAL LINKAGE record of {program}, in a program it contains is not supported yet", r.name)));
}
_ => errors.push(Error::at(r.pos, format!("SET ADDRESS OF {}: an EXTERNAL or GLOBAL record is not a LINKAGE record of the program", r.name))),
}
}
pub(crate) fn own_linkage(program: &Program) -> usize {
program.linkage.iter().filter(|e| matches!(e.level, 1 | 77)).count()
}