Skip to main content

ironwork_rt/
printer.rs

1//! The control characters a WRITE to a print file puts out; which files are print files, and how,
2//! is `compile::printer`.
3
4use crate::files::Move;
5use crate::lir::Spacing as Space;
6
7const ASA_LINES: [u8; 4] = [0x4E, 0x40, 0xF0, 0x60];
8const ASA_CHANNELS: [u8; 12] = [0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xC1, 0xC2, 0xC3];
9const PAGE_MODE: u8 = 0x5A;
10const PRINT_THEN_SPACE: [u8; 4] = [0x01, 0x09, 0x11, 0x19];
11const SPACE_NOW: [u8; 4] = [0x01, 0x0B, 0x13, 0x1B];
12
13fn print_then_skip(channel: u8) -> u8 {
14    0x89 + 8 * (channel.clamp(1, 12) - 1)
15}
16
17fn skip_now(channel: u8) -> u8 {
18    0x8B + 8 * (channel.clamp(1, 12) - 1)
19}
20
21/// Records written only to move the paper: `full` of them with `byte`, then one with `rest`.
22#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
23pub struct Spacing {
24    pub byte: u8,
25    pub full: u64,
26    pub rest: Option<u8>,
27}
28
29impl Spacing {
30    /// Machine-code spacing of `lines` lines without printing, three at a time.
31    fn machine(lines: u64) -> Self {
32        let rest = (lines % 3) as usize;
33        Self { byte: SPACE_NOW[3], full: lines / 3, rest: (rest > 0).then(|| SPACE_NOW[rest]) }
34    }
35
36    fn bytes(self) -> impl Iterator<Item = u8> {
37        std::iter::repeat_n(self.byte, self.full as usize).chain(self.rest)
38    }
39}
40
41/// The records one WRITE puts out, in order: spacing records, the record holding the line with
42/// `data` as its control character, spacing records
43/// ([`numeric::assumptions::PRINT_SPACING_RECORDS`] where one character cannot say the movement).
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct Controls {
46    pub lead: Spacing,
47    pub data: u8,
48    pub trail: Spacing,
49}
50
51impl Controls {
52    /// Each record's control character, and whether it is the record holding the line.
53    pub fn records(self) -> impl Iterator<Item = (u8, bool)> {
54        self.lead.bytes().map(|b| (b, false)).chain([(self.data, true)]).chain(self.trail.bytes().map(|b| (b, false)))
55    }
56}
57
58/// The control characters of a WRITE BEFORE (`before`) or AFTER ADVANCING `space`. An ASA file
59/// has no WRITE BEFORE, since one makes the file's characters machine codes.
60pub fn controls(machine: bool, before: bool, space: Space) -> Controls {
61    let only = |data| Controls { lead: Spacing::default(), data, trail: Spacing::default() };
62    match (machine, before, space) {
63        (_, _, Space::PageMode) => only(PAGE_MODE),
64        (false, _, Space::Lines(n)) => {
65            let full = n.saturating_sub(1) / 3;
66            Controls { lead: Spacing { byte: ASA_LINES[3], full, rest: None }, data: ASA_LINES[(n - 3 * full) as usize], trail: Spacing::default() }
67        }
68        (false, _, Space::Channel(c)) => only(ASA_CHANNELS[usize::from(c.clamp(1, 12) - 1)]),
69        (true, true, Space::Lines(n)) => Controls { lead: Spacing::default(), data: PRINT_THEN_SPACE[n.min(3) as usize], trail: Spacing::machine(n.saturating_sub(3)) },
70        (true, true, Space::Channel(c)) => only(print_then_skip(c)),
71        (true, false, Space::Lines(n)) => Controls { lead: Spacing::machine(n), data: PRINT_THEN_SPACE[0], trail: Spacing::default() },
72        (true, false, Space::Channel(c)) => Controls { lead: Spacing { byte: skip_now(c), full: 1, rest: None }, data: PRINT_THEN_SPACE[0], trail: Spacing::default() },
73    }
74}
75
76/// The control characters of a WRITE to a LINAGE file, which moves the paper `ahead` lines before
77/// its line and `behind` lines after it. An ASA file has no WRITE BEFORE, so moves nothing after.
78pub fn moving(machine: bool, ahead: u64, behind: u64) -> Controls {
79    if !machine {
80        return controls(false, false, Space::Lines(ahead));
81    }
82    Controls { lead: Spacing::machine(ahead), data: PRINT_THEN_SPACE[behind.min(3) as usize], trail: Spacing::machine(behind.saturating_sub(3)) }
83}
84
85/// How a text DD moves the paper before and after a record's line: as the control characters of
86/// a print file say, and for any other file a line of its own
87/// ([`numeric::assumptions::TEXT_PRINT_LINES`]).
88pub fn text_motion(before: bool, space: Space) -> (Option<Move>, Option<Move>) {
89    let movement = match space {
90        Space::Lines(n) => Move::Lines(n),
91        Space::Channel(1) => Move::Page,
92        Space::Channel(_) | Space::PageMode => Move::Lines(1),
93    };
94    if before { (None, Some(movement)) } else { (Some(movement), None) }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    fn bytes(machine: bool, before: bool, space: Space) -> Vec<(u8, bool)> {
102        controls(machine, before, space).records().collect()
103    }
104
105    #[test]
106    fn asa_characters_for_after_advancing() {
107        let data = |n| bytes(false, false, Space::Lines(n));
108        assert_eq!([data(0), data(1), data(2), data(3)], [[(0x4E, true)], [(0x40, true)], [(0xF0, true)], [(0x60, true)]]);
109        assert_eq!(data(5), [(0x60, false), (0xF0, true)]);
110        assert_eq!(data(7), [(0x60, false), (0x60, false), (0x40, true)]);
111        assert_eq!(bytes(false, false, Space::Channel(1)), [(0xF1, true)]);
112        assert_eq!(bytes(false, false, Space::Channel(10)), [(0xC1, true)]);
113        assert_eq!(bytes(false, false, Space::Channel(12)), [(0xC3, true)]);
114        assert_eq!(bytes(false, false, Space::PageMode), [(0x5A, true)]);
115    }
116
117    #[test]
118    fn machine_codes_print_then_act_or_act_first() {
119        let before = |n| bytes(true, true, Space::Lines(n));
120        assert_eq!([before(0), before(1), before(2), before(3)], [[(0x01, true)], [(0x09, true)], [(0x11, true)], [(0x19, true)]]);
121        assert_eq!(before(5), [(0x19, true), (0x13, false)]);
122        assert_eq!(before(7), [(0x19, true), (0x1B, false), (0x0B, false)]);
123        assert_eq!(bytes(true, true, Space::Channel(1)), [(0x89, true)]);
124        assert_eq!(bytes(true, true, Space::Channel(12)), [(0xE1, true)]);
125        assert_eq!(bytes(true, false, Space::Lines(0)), [(0x01, true)]);
126        assert_eq!(bytes(true, false, Space::Lines(1)), [(0x0B, false), (0x01, true)]);
127        assert_eq!(bytes(true, false, Space::Lines(4)), [(0x1B, false), (0x0B, false), (0x01, true)]);
128        assert_eq!(bytes(true, false, Space::Channel(1)), [(0x8B, false), (0x01, true)]);
129        assert_eq!(bytes(true, false, Space::Channel(2)), [(0x93, false), (0x01, true)]);
130    }
131
132    #[test]
133    fn a_linage_movement_spaces_before_and_after_the_line() {
134        let records = |machine, ahead, behind| moving(machine, ahead, behind).records().collect::<Vec<_>>();
135        assert_eq!(records(false, 5, 0), [(0x60, false), (0xF0, true)]);
136        assert_eq!(records(false, 0, 0), [(0x4E, true)]);
137        assert_eq!(records(true, 0, 5), [(0x19, true), (0x13, false)]);
138        assert_eq!(records(true, 4, 1), [(0x1B, false), (0x0B, false), (0x09, true)]);
139        assert_eq!(records(true, 2, 0), [(0x13, false), (0x01, true)]);
140    }
141}