Skip to main content

ironwork_rt/
linage.rs

1//! LINAGE's logical page at run time: where the printer is in the page body, and how far each
2//! WRITE moves the paper past the footing area and the margins
3//! ([`numeric::assumptions::LINAGE_PAGE_MOVEMENT`]).
4
5/// One logical page, in lines.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct Geometry {
8    pub body: u64,
9    /// The first line of the footing area. Without FOOTING only a page overflow raises the
10    /// end-of-page condition (Language Reference SC27-8713-03, p. 475).
11    pub footing: Option<u64>,
12    pub top: u64,
13    pub bottom: u64,
14}
15
16impl Geometry {
17    /// The page the clause's values give, or why they give none: the page body is at least a line
18    /// and the footing starts within it (Language Reference SC27-8713-03, p. 189).
19    pub fn new(body: i64, footing: Option<i64>, top: i64, bottom: i64) -> Result<Self, String> {
20        if body < 1 {
21            return Err(format!("LINAGE gives a page body of {body} lines, and it needs at least 1"));
22        }
23        if let Some(f) = footing.filter(|f| !(1..=body).contains(f)) {
24            return Err(format!("LINAGE puts the footing at line {f}, outside the page body of {body} lines"));
25        }
26        if top < 0 || bottom < 0 {
27            return Err(format!("LINAGE gives margins of {top} and {bottom} lines"));
28        }
29        Ok(Self { body: body as u64, footing: footing.map(|f| f as u64), top: top as u64, bottom: bottom as u64 })
30    }
31}
32
33/// Where the printer is on a LINAGE file's current page.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct Page {
36    pub geometry: Geometry,
37    /// LINAGE-COUNTER: the line of the page body the printer is at.
38    pub counter: u64,
39    /// Lines the paper has yet to move to reach that line: the first page's top margin, until the
40    /// first WRITE.
41    owed: u64,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum Motion {
46    Lines(u64),
47    Page,
48}
49
50/// What one WRITE does: the lines the paper moves before the record's line and after it, and
51/// whether the end-of-page condition holds once it is written.
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct Step {
54    pub ahead: u64,
55    pub behind: u64,
56    pub end_of_page: bool,
57}
58
59impl Page {
60    pub fn opened(geometry: Geometry) -> Self {
61        Self { geometry, counter: 1, owed: geometry.top }
62    }
63
64    /// A WRITE BEFORE (`before`) or AFTER ADVANCING `motion`. A WRITE that would pass the page
65    /// body, or that advances a page, goes to the first line of the next page, whose geometry
66    /// `next` gives (Language Reference SC27-8713-03, pp. 474-475).
67    pub fn write<E>(&mut self, before: bool, motion: Motion, next: impl FnOnce() -> Result<Geometry, E>) -> Result<Step, E> {
68        let current = self.geometry;
69        let (moved, overflow) = match motion {
70            Motion::Lines(n) if self.counter.saturating_add(n) <= current.body => {
71                self.counter += n;
72                (n, false)
73            }
74            _ => {
75                let following = next()?;
76                let rest = current.body.saturating_sub(self.counter).saturating_add(current.bottom);
77                self.geometry = following;
78                self.counter = 1;
79                (rest.saturating_add(following.top).saturating_add(1), motion != Motion::Page)
80            }
81        };
82        let end_of_page = overflow || self.geometry.footing.is_some_and(|f| self.counter >= f);
83        let owed = std::mem::take(&mut self.owed);
84        Ok(if before { Step { ahead: owed, behind: moved, end_of_page } } else { Step { ahead: owed.saturating_add(moved), behind: 0, end_of_page } })
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    fn page(body: i64, footing: Option<i64>, top: i64, bottom: i64) -> Page {
93        Page::opened(Geometry::new(body, footing, top, bottom).unwrap())
94    }
95
96    fn step(p: &mut Page, before: bool, motion: Motion, next: Geometry) -> (u64, u64, bool, u64) {
97        let s = p.write(before, motion, || Ok::<_, ()>(next)).unwrap();
98        (s.ahead, s.behind, s.end_of_page, p.counter)
99    }
100
101    #[test]
102    fn the_first_write_moves_past_the_top_margin_and_counts_from_line_one() {
103        let g = Geometry::new(5, Some(4), 2, 3).unwrap();
104        let mut p = Page::opened(g);
105        assert_eq!(p.counter, 1);
106        assert_eq!(step(&mut p, false, Motion::Lines(1), g), (3, 0, false, 2));
107        assert_eq!(step(&mut p, false, Motion::Lines(1), g), (1, 0, false, 3));
108        assert_eq!(step(&mut p, false, Motion::Lines(1), g), (1, 0, true, 4));
109        assert_eq!(step(&mut p, false, Motion::Lines(0), g), (0, 0, true, 4));
110        assert_eq!(step(&mut p, false, Motion::Lines(2), g), (1 + 3 + 2 + 1, 0, true, 1));
111    }
112
113    #[test]
114    fn before_advancing_prints_then_moves_and_overflow_leaves_the_printer_on_the_next_page() {
115        let g = Geometry::new(3, Some(3), 1, 1).unwrap();
116        let mut p = Page::opened(g);
117        assert_eq!(step(&mut p, true, Motion::Lines(1), g), (1, 1, false, 2));
118        assert_eq!(step(&mut p, true, Motion::Lines(1), g), (0, 1, true, 3));
119        assert_eq!(step(&mut p, true, Motion::Lines(1), g), (0, 1 + 1 + 1, true, 1));
120    }
121
122    #[test]
123    fn without_footing_only_an_overflow_is_the_end_of_the_page() {
124        let g = Geometry::new(2, None, 0, 0).unwrap();
125        let mut p = Page::opened(g);
126        assert_eq!(step(&mut p, false, Motion::Lines(1), g), (1, 0, false, 2));
127        assert_eq!(step(&mut p, false, Motion::Lines(0), g), (0, 0, false, 2));
128        assert_eq!(step(&mut p, false, Motion::Lines(1), g), (1, 0, true, 1));
129        assert_eq!(step(&mut p, false, Motion::Page, g), (2, 0, false, 1));
130    }
131
132    #[test]
133    fn advancing_page_takes_the_next_geometry_and_raises_end_of_page_only_at_a_first_line_footing() {
134        let mut p = page(10, Some(8), 0, 0);
135        let next = Geometry::new(4, Some(2), 3, 0).unwrap();
136        assert_eq!(step(&mut p, false, Motion::Lines(5), next), (5, 0, false, 6));
137        assert_eq!(step(&mut p, false, Motion::Page, next), (4 + 3 + 1, 0, false, 1));
138        assert_eq!(p.geometry, next);
139        let at_one = Geometry::new(4, Some(1), 0, 0).unwrap();
140        assert_eq!(step(&mut p, true, Motion::Page, at_one), (0, 3 + 1, true, 1));
141    }
142
143    #[test]
144    fn a_page_the_values_cannot_make_is_refused() {
145        assert!(Geometry::new(0, None, 0, 0).is_err());
146        assert!(Geometry::new(5, Some(6), 0, 0).is_err());
147        assert!(Geometry::new(5, Some(0), 0, 0).is_err());
148        assert!(Geometry::new(5, None, -1, 0).is_err());
149        assert_eq!(Geometry::new(5, None, 0, 0).unwrap().footing, None);
150    }
151}