elegance/
core.rs

1//! The core implementation of the printer.
2//!
3//! The algorithm is mostly based on the Haskell implementation in the paper
4//! "Linear, bounded, functional pretty-printing" by O. Chitil.
5
6use std::{
7    collections::VecDeque,
8    ops::{AddAssign, Sub},
9};
10
11use crate::{render::Render, string::CowString};
12
13#[derive(Clone, Copy)]
14struct Position(pub usize);
15
16impl AddAssign<usize> for Position {
17    fn add_assign(&mut self, rhs: usize) {
18        self.0 += rhs
19    }
20}
21
22impl Sub<Position> for Position {
23    type Output = usize;
24
25    fn sub(self, rhs: Position) -> Self::Output {
26        debug_assert!(self.0 >= rhs.0);
27        self.0 - rhs.0
28    }
29}
30
31enum Token<'a, S: AsRef<str>> {
32    Text(CowString<'a, S>),
33    Break { indent: usize },
34    Group(OutGroup<'a, S>),
35}
36
37struct OutGroup<'a, S: AsRef<str>> {
38    tokens: Vec<(Token<'a, S>, usize)>,
39    consistent: bool,
40}
41
42#[derive(Clone, Copy)]
43enum RenderFrame {
44    Fits,
45    Break { consistent: bool },
46}
47
48/// The `Printer` is a pretty printing engine. It takes a sequence of layout elements and
49/// produces a pretty printed representation of the elements.
50pub struct Printer<'a, R: Render = String, S: AsRef<str> = String> {
51    // common
52    line_width: usize,
53
54    // scanner
55    position: Position,
56    indent: Vec<isize>,
57    dq: VecDeque<(Position, OutGroup<'a, S>)>,
58
59    // renderer
60    renderer: R,
61    remaining: usize,
62    render_stack: Vec<RenderFrame>,
63    pending_indent: usize,
64}
65
66impl<R: Render> Printer<'_, R> {
67    /// Create a new printer.
68    ///
69    /// # Panics
70    ///
71    /// If line width is not between 1 and 65536.
72    pub fn new(renderer: R, line_width: usize) -> Self {
73        Self::new_with(renderer, line_width)
74    }
75}
76
77impl<'a, S: AsRef<str>, R: Render> Printer<'a, R, S> {
78    /// Create a new printer (with custom string type).
79    ///
80    /// # Panics
81    ///
82    /// If line width is not between 1 and 65536.
83    pub fn new_with(renderer: R, line_width: usize) -> Self {
84        assert!(
85            line_width > 0 && line_width <= Self::MAX_WIDTH,
86            "line width must be between 1 and {}",
87            Self::MAX_WIDTH
88        );
89        let mut pp = Self {
90            line_width,
91            position: Position(0),
92            indent: vec![0],
93            dq: VecDeque::new(),
94            renderer,
95            remaining: line_width,
96            render_stack: Vec::new(),
97            pending_indent: 0,
98        };
99        pp.scan_begin(0, false);
100        pp
101    }
102
103    /// Maximum line width.
104    pub const MAX_WIDTH: usize = 65536;
105
106    /// Write a text element.
107    pub fn scan_text(&mut self, text: CowString<'a, S>, width: usize) -> Result<(), R::Error> {
108        self.scan(width, Token::Text(text))
109    }
110
111    /// Write a break element.
112    ///
113    /// A break is `size` spaces if there is enough space, or a new line if not.
114    ///
115    /// After line break, the indent is increased by `indent`. The value can be
116    /// negative, in which case the indent is decreased.
117    ///
118    /// # Panics
119    ///
120    /// If the total indent is negative.
121    pub fn scan_break(&mut self, size: usize, indent: isize) -> Result<(), R::Error> {
122        let indent = (self.indent() + indent)
123            .try_into()
124            .expect("indent must >= 0");
125        self.scan(size, Token::Break { indent })
126    }
127
128    /// Begin a group.
129    pub fn scan_begin(&mut self, indent: isize, consistent: bool) {
130        self.indent.push(self.indent() + indent);
131        self.dq.push_back((
132            self.position,
133            OutGroup {
134                tokens: Vec::with_capacity(12),
135                consistent,
136            },
137        ));
138    }
139
140    /// End a group.
141    pub fn scan_end(&mut self) -> Result<(), R::Error> {
142        self.indent.pop();
143        if let Some((s, grp1)) = self.dq.pop_back() {
144            let width = self.position - s;
145            if let Some((_, grp2)) = self.dq.back_mut() {
146                grp2.tokens.push((Token::Group(grp1), width));
147            } else {
148                self.render_begin(grp1, width)?;
149                self.render_end()?;
150            }
151        } else {
152            self.render_end()?;
153        }
154        Ok(())
155    }
156
157    /// Finish the printer and return the result.
158    ///
159    /// # Panics
160    ///
161    /// If there is an unclosed group.
162    pub fn finish(mut self) -> Result<R, R::Error> {
163        self.scan_end()?;
164        assert!(self.dq.is_empty(), "unclosed group");
165        Ok(self.renderer)
166    }
167
168    fn indent(&self) -> isize {
169        *self.indent.last().unwrap()
170    }
171
172    fn scan(&mut self, width: usize, out: Token<'a, S>) -> Result<(), R::Error> {
173        self.position += width;
174        if let Some((_, grp)) = self.dq.back_mut() {
175            grp.tokens.push((out, width));
176            self.prune()?;
177        } else {
178            self.render_token(out, width)?;
179        }
180        Ok(())
181    }
182
183    fn prune(&mut self) -> Result<(), R::Error> {
184        while self
185            .dq
186            .front()
187            .is_some_and(|&(s, _)| self.position - s > self.remaining)
188        {
189            let (_, grp) = self.dq.pop_front().unwrap();
190            self.render_begin(grp, Self::MAX_WIDTH)?;
191        }
192        Ok(())
193    }
194
195    fn render_token(&mut self, token: Token<'a, S>, width: usize) -> Result<(), R::Error> {
196        match token {
197            Token::Text(text) => self.render_text(text, width),
198            Token::Break { indent } => self.render_break(indent, width),
199            Token::Group(group) => {
200                self.render_begin(group, width)?;
201                self.render_end()
202            }
203        }
204    }
205
206    fn render_text(&mut self, text: CowString<'a, S>, width: usize) -> Result<(), R::Error> {
207        if self.pending_indent > 0 {
208            self.renderer.write_spaces(self.pending_indent)?;
209            self.pending_indent = 0;
210        }
211        self.renderer.write_str(&text)?;
212        self.remaining = self.remaining.saturating_sub(width);
213        Ok(())
214    }
215
216    fn render_break(&mut self, indent: usize, width: usize) -> Result<(), R::Error> {
217        let frame = self
218            .render_stack
219            .last()
220            .copied()
221            .unwrap_or(RenderFrame::Break { consistent: false });
222        let fits = match frame {
223            RenderFrame::Fits => true,
224            RenderFrame::Break { consistent, .. } => !consistent && width < self.remaining,
225        };
226        if fits {
227            self.renderer.write_spaces(width)?;
228            self.remaining = self.remaining.saturating_sub(width);
229        } else {
230            self.renderer.write_str("\n")?;
231            self.pending_indent = indent;
232            self.remaining = self.line_width.saturating_sub(indent);
233        }
234        Ok(())
235    }
236
237    fn render_begin(&mut self, group: OutGroup<'a, S>, width: usize) -> Result<(), R::Error> {
238        self.render_stack.push(if width <= self.remaining {
239            RenderFrame::Fits
240        } else {
241            RenderFrame::Break {
242                consistent: group.consistent,
243            }
244        });
245        for (out, width) in group.tokens {
246            self.render_token(out, width)?;
247        }
248        Ok(())
249    }
250
251    fn render_end(&mut self) -> Result<(), R::Error> {
252        self.render_stack.pop();
253        Ok(())
254    }
255}