Skip to main content

ironwork_compile/
printer.rs

1//! The printer control character ([`numeric::assumptions::PRINT_CONTROL_CHARACTER`]). A
2//! sequential file that a WRITE ... ADVANCING of the program names, whose FD has LINAGE, or that
3//! holds a report is a print file: every record written to it carries a control character, an ASA
4//! character when every WRITE ... ADVANCING of the file says AFTER, a machine code when one says
5//! BEFORE. Under ADV the character is a byte before the record; under NOADV it is the record's
6//! first byte.
7
8use crate::layout::{Layout, Resolved};
9use syntax::Error;
10use syntax::ast::*;
11
12/// How a print file's records carry the control character.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct Carriage {
15    /// Machine codes rather than ASA characters.
16    pub machine: bool,
17    /// The character is the record's own first byte (NOADV), not a byte added before it.
18    pub reserved: bool,
19}
20
21/// How far a WRITE moves the paper.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum Space {
24    /// Lines; 0 suppresses spacing.
25    Lines(u64),
26    /// A skip to channel 1 to 12. ADVANCING PAGE is channel 1.
27    Channel(u8),
28    /// AFP-5A: the record is page mode data for the Print Services Facility.
29    PageMode,
30}
31
32/// The environment-name a mnemonic-name stands for, as a movement; None for the punch pockets.
33pub 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
42/// Each file's control character: None for a file that is not a print file.
43pub(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
67/// Whether a print file's control character is its records' own first byte: under NOADV, unless
68/// LINAGE makes the file ADV.
69pub(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
80/// What the Language Reference allows of a WRITE's ADVANCING phrase beyond its operand.
81pub(crate) fn check_write(program: &Program, layout: &Layout, record: &Ref, advancing: &Advancing, pos: syntax::Pos, errors: &mut Vec<Error>) {
82    if let Advancing::Mnemonic { name, environment, .. } = advancing
83        && mnemonic_space(environment).is_none()
84    {
85        errors.push(Error::at(pos, format!("ADVANCING {name}: stacker selection ({environment}) on a card punch is not supported yet")));
86    }
87    let Some(f) = file_of(layout, record).map(|k| &program.files[k]) else { return };
88    match f.organization {
89        Organization::Indexed | Organization::Relative => errors.push(Error::at(pos, format!("WRITE ... ADVANCING: {} is not a sequential file", f.name))),
90        Organization::LineSequential if advancing.before() || matches!(advancing, Advancing::Mnemonic { .. }) => {
91            errors.push(Error::at(pos, format!("WRITE ... BEFORE ADVANCING, or ADVANCING a mnemonic-name, is not allowed for the line-sequential file {}", f.name)));
92        }
93        _ => {}
94    }
95}