lexer_rs/
fmt_context.rs

1//a Imports
2use crate::UserPosn;
3
4//a FmtContext
5//tt FmtContext
6/// This trait is provided by types that wish to support context for
7/// (e.g.) error messages
8///
9/// It requires the type to have the ability to map from a line number
10/// to a position within the file/stream/text of the type, and to
11/// provide the length of any specific line nummber.e
12///
13/// With those supplied methods, the trait provides the 'fmt_context'
14/// method, which outputs to a formatter (which can be an &mut String
15/// even) the lines of the text ahead of a provided span of start and
16/// end positions within the stream.
17///
18/// Currently the format of the context is fixed - the number of lines
19/// ahead is fixed a a maximum of four, the lines are always numbered
20/// with aa line number of up to 4 digits, and so on.
21pub trait FmtContext<P> {
22    /// Return the length of the specified line
23    fn line_length(&self, line: usize) -> usize;
24
25    /// Format the line of text (potentially with coloring and so on).
26    ///
27    /// This formatting must preserve the columnn numbers of characters
28    /// if context markers are to line up correctly
29    fn fmt_line(&self, f: &mut dyn std::fmt::Write, line: usize) -> std::fmt::Result;
30
31    /// Format a line of text with highlight on certain columns
32    fn fmt_context_single_line(
33        &self,
34        f: &mut dyn std::fmt::Write,
35        start: &P,
36        num_cols: usize, // number of columns to highlight
37    ) -> std::fmt::Result
38    where
39        P: UserPosn,
40    {
41        let line = start.line();
42        let first_col = start.column();
43        if line > 1 {
44            write!(f, "    |  ")?;
45            self.fmt_line(f, line - 1)?;
46            writeln!(f)?;
47        }
48        write!(f, "{:4}|  ", line)?;
49        self.fmt_line(f, line)?;
50        writeln!(f)?;
51        write!(f, "    |  ")?;
52        for _ in 1..(first_col) {
53            f.write_char(' ')?;
54        }
55        if num_cols == 0 {
56            f.write_char('^')?;
57        } else {
58            for _ in 0..num_cols {
59                f.write_char('^')?;
60            }
61        }
62        writeln!(f)?;
63        writeln!(f, "    |  ")
64    }
65
66    /// Format multiple lines of text, highlighting certain lines
67    fn fmt_context_multiple_lines(
68        &self,
69        f: &mut dyn std::fmt::Write,
70        start: &P,
71        end: &P,
72    ) -> std::fmt::Result
73    where
74        P: UserPosn,
75    {
76        let first_line = start.line();
77        let first_line = if first_line > 1 {
78            first_line - 1
79        } else {
80            first_line
81        };
82        let last_line = end.line() + {
83            if end.column() == 0 {
84                0
85            } else {
86                1
87            }
88        };
89        // Change to a Range
90        let num_lines = {
91            if last_line <= first_line {
92                1
93            } else {
94                last_line + 1 - first_line
95            }
96        };
97
98        let (start_skip, end_skip) = {
99            if num_lines > 4 {
100                (4, num_lines - 4)
101            } else {
102                (1, 0)
103            }
104        };
105
106        let mut ellipses_output = false;
107        for i in 0..num_lines {
108            let l = first_line + i;
109            if i >= start_skip && i <= end_skip {
110                if !ellipses_output {
111                    writeln!(f, "    |...")?;
112                    ellipses_output = true;
113                }
114                continue;
115            }
116            if l >= start.line() && l <= end.line() {
117                write!(f, "{:4}|  ", l)?;
118            } else {
119                write!(f, "    |  ")?;
120            }
121            self.fmt_line(f, l)?;
122            writeln!(f)?;
123        }
124        Ok(())
125    }
126
127    /// Format text with highlighting between start and end
128    ///
129    /// This is the main method used by clients of the trait
130    fn fmt_context(&self, fmt: &mut dyn std::fmt::Write, start: &P, end: &P) -> std::fmt::Result
131    where
132        P: UserPosn,
133    {
134        if start.line() == end.line() || (start.line() + 1 == end.line() && end.column() == 0) {
135            let num_cols = {
136                if start.line() == end.line() {
137                    end.column() - start.column()
138                } else {
139                    self.line_length(start.line())
140                }
141            };
142            self.fmt_context_single_line(fmt, start, num_cols)
143        } else {
144            self.fmt_context_multiple_lines(fmt, start, end)
145        }
146    }
147}