ironwork_compile/
printer.rs1use crate::layout::{Layout, Resolved};
9use syntax::Error;
10use syntax::ast::*;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct Carriage {
15 pub machine: bool,
17 pub reserved: bool,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Space {
24 Lines(u64),
26 Channel(u8),
28 PageMode,
30}
31
32pub fn mnemonic_space(environment: &str) -> Option<Space> {
34 match environment {
35 "CSP" => Some(Space::Lines(0)),
36 "AFP-5A" => Some(Space::PageMode),
37 c if c.len() == 3 && c.starts_with('C') => c[1..].parse().ok().filter(|n| (1..=12).contains(n)).map(Space::Channel),
38 _ => None,
39 }
40}
41
42pub(crate) fn carriages(program: &Program, layout: &Layout, adv: bool) -> Vec<Option<Carriage>> {
44 let mut uses = vec![(false, false); program.files.len()];
45 let mut pending: Vec<&[Stmt]> = program.paragraphs.iter().map(|p| p.statements.as_slice()).collect();
46 while let Some(stmts) = pending.pop() {
47 for s in stmts {
48 pending.extend(crate::oo::bodies(s));
49 if let Stmt::Write { record, advancing: Some(a), .. } = s
50 && let Some(k) = file_of(layout, record)
51 {
52 uses[k] = (true, uses[k].1 || a.before());
53 }
54 }
55 }
56 program
57 .files
58 .iter()
59 .zip(uses)
60 .map(|(f, (advancing, before))| {
61 let print = f.organization == Organization::Sequential && !f.sort && (advancing || f.linage.is_some() || !f.reports.is_empty());
62 print.then_some(Carriage { machine: before, reserved: reserves_first_byte(f, adv) })
63 })
64 .collect()
65}
66
67pub(crate) fn reserves_first_byte(f: &FileDecl, adv: bool) -> bool {
70 !adv && f.linage.is_none() && f.organization == Organization::Sequential && !f.sort
71}
72
73fn file_of(layout: &Layout, record: &Ref) -> Option<usize> {
74 match layout.resolve(&record.name, &record.qualifiers, record.pos) {
75 Ok(Resolved::Item(i)) => layout.items[i].file.map(usize::from),
76 _ => None,
77 }
78}
79
80pub(crate) fn check_write(program: &Program, layout: &Layout, record: &Ref, advancing: &Advancing, extended: bool, pos: syntax::Pos, errors: &mut Vec<Error>) {
83 if let Advancing::Mnemonic { name, environment, .. } = advancing
84 && mnemonic_space(environment).is_none()
85 {
86 errors.push(syntax::messages::IWR0013.at(pos, format!("ADVANCING {name}: stacker selection ({environment}) on a card punch is not supported yet")));
87 }
88 let Some(f) = file_of(layout, record).map(|k| &program.files[k]) else { return };
89 match f.organization {
90 Organization::Indexed | Organization::Relative => errors.push(syntax::messages::IWC0144.at(pos, format!("WRITE ... ADVANCING: {} is not a sequential file", f.name))),
91 Organization::LineSequential if extended && advancing.before() && !matches!(advancing, Advancing::Mnemonic { .. }) => {
92 errors.push(syntax::messages::IWX0030.at(pos, format!("WRITE ... BEFORE ADVANCING on the line-sequential file {} (GnuCOBOL and Micro Focus; Enterprise COBOL allows only AFTER there): the line, then the lines or page it names", f.name)));
93 }
94 Organization::LineSequential if advancing.before() || matches!(advancing, Advancing::Mnemonic { .. }) => {
95 errors.push(syntax::messages::IWC0145.at(pos, format!("WRITE ... BEFORE ADVANCING, or ADVANCING a mnemonic-name, is not allowed for the line-sequential file {}", f.name)));
96 }
97 _ => {}
98 }
99}