1 //! Code to compute example inputs given a backtrace.
2
3 use ascii_canvas::AsciiView;
4 use grammar::repr::*;
5 use message::builder::InlineBuilder;
6 use message::Content;
7 use std::fmt::{Debug, Error, Formatter};
8 use style::Style;
9 use tls::Tls;
10
11 #[cfg(test)]
12 mod test;
13
14 /// An "example" input and the way it was derived. This can be
15 /// serialized into useful text. For example, it might represent
16 /// something like this:
17 ///
18 /// ```
19 /// Looking at
20 /// |
21 /// v
22 /// Ty "->" Ty "->" Ty
23 /// | | |
24 /// +-Ty-----+ |
25 /// | |
26 /// +-Ty-------------+
27 /// ```
28 ///
29 /// The top-line is the `symbols` vector. The groupings below are
30 /// stored in the `reductions` vector, in order from smallest to
31 /// largest (they are always properly nested). The `cursor` field
32 /// indicates the current lookahead token.
33 ///
34 /// The `symbols` vector is actually `Option<Symbol>` to account
35 /// for empty reductions:
36 ///
37 /// ```
38 /// A B
39 /// | | | |
40 /// | +-Y-+ |
41 /// +-Z-----+
42 /// ```
43 ///
44 /// The "empty space" between A and B would be represented as `None`.
45 #[derive(Clone, Debug)]
46 pub struct Example {
47 pub symbols: Vec<ExampleSymbol>,
48 pub cursor: usize,
49 pub reductions: Vec<Reduction>,
50 }
51
52 #[derive(Clone, Debug, PartialEq, Eq)]
53 pub enum ExampleSymbol {
54 Symbol(Symbol),
55 Epsilon,
56 }
57
58 #[derive(Copy, Clone, Default)]
59 pub struct ExampleStyles {
60 pub before_cursor: Style,
61 pub on_cursor: Style,
62 pub after_cursor: Style,
63 }
64
65 #[derive(Clone, Debug)]
66 pub struct Reduction {
67 pub start: usize,
68 pub end: usize,
69 pub nonterminal: NonterminalString,
70 }
71
72 impl Example {
73 /// Length of each symbol. Each will need *at least* that amount
74 /// of space. :) Measure in characters, under the assumption of a
75 /// mono-spaced font. Also add a final `0` marker which will serve
76 /// as the end position.
lengths(&self) -> Vec<usize>77 fn lengths(&self) -> Vec<usize> {
78 self.symbols
79 .iter()
80 .map(|s| match *s {
81 ExampleSymbol::Symbol(ref s) => format!("{}", s).chars().count(),
82 ExampleSymbol::Epsilon => 1, // display as " "
83 })
84 .chain(Some(0))
85 .collect()
86 }
87
88 /// Extract a prefix of the list of symbols from this `Example`
89 /// and make a styled list of them, like:
90 ///
91 /// Ty "->" Ty -> "Ty"
to_symbol_list(&self, length: usize, styles: ExampleStyles) -> Box<dyn Content>92 pub fn to_symbol_list(&self, length: usize, styles: ExampleStyles) -> Box<dyn Content> {
93 let mut builder = InlineBuilder::new().begin_spaced();
94
95 for (index, symbol) in self.symbols[..length].iter().enumerate() {
96 let style = if index < self.cursor {
97 styles.before_cursor
98 } else if index > self.cursor {
99 styles.after_cursor
100 } else {
101 match *symbol {
102 ExampleSymbol::Symbol(Symbol::Terminal(_)) => styles.on_cursor,
103 ExampleSymbol::Symbol(Symbol::Nonterminal(_)) => styles.after_cursor,
104 ExampleSymbol::Epsilon => styles.after_cursor,
105 }
106 };
107
108 if let ExampleSymbol::Symbol(ref s) = symbol {
109 builder = builder.push(s.clone()).styled(style);
110 }
111 }
112
113 builder.end().indented().end()
114 }
115
116 /// Render the example into a styled diagram suitable for
117 /// embedding in an error message.
into_picture(self, styles: ExampleStyles) -> Box<dyn Content>118 pub fn into_picture(self, styles: ExampleStyles) -> Box<dyn Content> {
119 let lengths = self.lengths();
120 let positions = self.positions(&lengths);
121 InlineBuilder::new()
122 .push(Box::new(ExamplePicture {
123 example: self,
124 positions,
125 styles,
126 }))
127 .indented()
128 .end()
129 }
130
starting_positions(&self, lengths: &[usize]) -> Vec<usize>131 fn starting_positions(&self, lengths: &[usize]) -> Vec<usize> {
132 lengths
133 .iter()
134 .scan(0, |counter, &len| {
135 let start = *counter;
136
137 // Leave space for "NT " (if "NT" is the name
138 // of the nonterminal).
139 *counter = start + len + 1;
140
141 Some(start)
142 })
143 .collect()
144 }
145
146 /// Start index where each symbol in the example should appear,
147 /// measured in characters. These are spaced to leave enough room
148 /// for the reductions below.
positions(&self, lengths: &[usize]) -> Vec<usize>149 fn positions(&self, lengths: &[usize]) -> Vec<usize> {
150 // Initially, position each symbol with one space in between,
151 // like:
152 //
153 // X Y Z
154 let mut positions = self.starting_positions(lengths);
155
156 // Adjust spacing to account for the nonterminal labels
157 // we will have to add. It will display
158 // like this:
159 //
160 // A1 B2 C3 D4 E5 F6
161 // | |
162 // +-Label---+
163 //
164 // But if the label is long we may have to adjust the spacing
165 // of the covered items (here, we changed them to two spaces,
166 // except the first gap, which got 3 spaces):
167 //
168 // A1 B2 C3 D4 E5 F6
169 // | |
170 // +-LongLabel22-+
171 for &Reduction {
172 start,
173 end,
174 ref nonterminal,
175 } in &self.reductions
176 {
177 let nt_len = format!("{}", nonterminal).chars().count();
178
179 // Number of symbols we are reducing. This should always
180 // be non-zero because even in the case of a \epsilon
181 // rule, we ought to be have a `None` entry in the symbol array.
182 let num_syms = end - start;
183 assert!(num_syms > 0);
184
185 // Let's use the expansion from above as our running example.
186 // We start out with positions like this:
187 //
188 // A1 B2 C3 D4 E5 F6
189 // | |
190 // +-LongLabel22-+
191 //
192 // But we want LongLabel to end at D4. No good.
193
194 // Start of first symbol to be reduced. Here, 0.
195 //
196 // A1 B2 C3 D4
197 // ^ here
198 let start_position = positions[start];
199
200 // End of last symbol to be reduced. Here, 11.
201 //
202 // A1 B2 C3 D4 E5
203 // ^ positions[end]
204 // ^ here -- positions[end] - 1
205 let end_position = positions[end] - 1;
206
207 // We need space to draw `+-Label-+` between
208 // start_position and end_position.
209 let required_len = nt_len + 4; // here, 15
210 let actual_len = end_position - start_position; // here, 10
211 if required_len < actual_len {
212 continue; // Got enough space, all set.
213 }
214
215 // Have to add `difference` characters altogether.
216 let difference = required_len - actual_len; // here, 4
217
218 // Increment over everything that is not part of this nonterminal.
219 // In the example above, that is E5 and F6.
220 shift(&mut positions[end..], difference);
221
222 if num_syms > 1 {
223 // If there is just one symbol being reduced here,
224 // then we have shifted over the things that follow
225 // it, and we are done. This would be a case like:
226 //
227 // X Y Z
228 // | |
229 // +-Label-+
230 //
231 // (which maybe ought to be rendered slightly
232 // differently).
233 //
234 // But if there are multiple symbols, we're not quite
235 // done, because there would be an unsightly gap:
236 //
237 // (gaps)
238 // | | |
239 // v v v
240 // A1 B2 C3 D4 E5 F6
241 // | |
242 // +-LongLabel22-+
243 //
244 // we'd like to make things line up, so we have to
245 // distribute that extra space internally by
246 // increasing the "gaps" (marked above) as evenly as
247 // possible (basically, full justification).
248 //
249 // We do this by dividing up the spaces evenly and
250 // then taking the remainder `N` and distributing 1
251 // extra to the first N.
252 let num_gaps = num_syms - 1; // number of gaps we can adjust. Here, 3.
253 let amount = difference / num_gaps; // what to add to each gap. Here, 1.
254 let extra = difference % num_gaps; // the remainder. Here, 1.
255
256 // For the first `extra` symbols, give them amount + 1
257 // extra space. After that, just amount. (O(n^2). Sue me.)
258 for i in 0..extra {
259 shift(&mut positions[start + 1 + i..end], amount + 1);
260 }
261 for i in extra..num_gaps {
262 shift(&mut positions[start + 1 + i..end], amount);
263 }
264 }
265 }
266
267 positions
268 }
269
270 #[cfg(test)]
paint_unstyled(&self) -> Vec<::ascii_canvas::Row>271 pub fn paint_unstyled(&self) -> Vec<::ascii_canvas::Row> {
272 let this = self.clone();
273 let content = this.into_picture(ExampleStyles::default());
274 let min_width = content.min_width();
275 let canvas = content.emit_to_canvas(min_width);
276 canvas.to_strings()
277 }
278
paint_on(&self, styles: &ExampleStyles, positions: &[usize], view: &mut dyn AsciiView)279 fn paint_on(&self, styles: &ExampleStyles, positions: &[usize], view: &mut dyn AsciiView) {
280 // Draw the brackets for each reduction:
281 for (index, reduction) in self.reductions.iter().enumerate() {
282 let start_column = positions[reduction.start];
283 let end_column = positions[reduction.end] - 1;
284 let row = 1 + index;
285 view.draw_vertical_line(0..row + 1, start_column);
286 view.draw_vertical_line(0..row + 1, end_column - 1);
287 view.draw_horizontal_line(row, start_column..end_column);
288 }
289
290 // Write the labels for each reduction. Do this after the
291 // brackets so that ascii canvas can convert `|` to `+`
292 // without interfering with the text (in case of weird overlap).
293 let session = Tls::session();
294 for (index, reduction) in self.reductions.iter().enumerate() {
295 let column = positions[reduction.start] + 2;
296 let row = 1 + index;
297 view.write_chars(
298 row,
299 column,
300 reduction.nonterminal.to_string().chars(),
301 session.nonterminal_symbol,
302 );
303 }
304
305 // Write the labels on top:
306 // A1 B2 C3 D4 E5 F6
307 self.paint_symbols_on(&self.symbols, &positions, styles, view);
308 }
309
paint_symbols_on( &self, symbols: &[ExampleSymbol], positions: &[usize], styles: &ExampleStyles, view: &mut dyn AsciiView, )310 fn paint_symbols_on(
311 &self,
312 symbols: &[ExampleSymbol],
313 positions: &[usize],
314 styles: &ExampleStyles,
315 view: &mut dyn AsciiView,
316 ) {
317 let session = Tls::session();
318 for (index, ex_symbol) in symbols.iter().enumerate() {
319 let style = if index < self.cursor {
320 styles.before_cursor
321 } else if index == self.cursor {
322 // Only display actual terminals in the "on-cursor"
323 // font, because it might be misleading to show a
324 // nonterminal that way. Really it'd be nice to expand
325 // so that the cursor is always a terminal.
326 match *ex_symbol {
327 ExampleSymbol::Symbol(Symbol::Terminal(_)) => styles.on_cursor,
328 _ => styles.after_cursor,
329 }
330 } else {
331 styles.after_cursor
332 };
333
334 let column = positions[index];
335 match *ex_symbol {
336 ExampleSymbol::Symbol(Symbol::Terminal(ref term)) => {
337 view.write_chars(
338 0,
339 column,
340 term.to_string().chars(),
341 style.with(session.terminal_symbol),
342 );
343 }
344 ExampleSymbol::Symbol(Symbol::Nonterminal(ref nt)) => {
345 view.write_chars(
346 0,
347 column,
348 nt.to_string().chars(),
349 style.with(session.nonterminal_symbol),
350 );
351 }
352 ExampleSymbol::Epsilon => {}
353 }
354 }
355 }
356 }
357
358 struct ExamplePicture {
359 example: Example,
360 positions: Vec<usize>,
361 styles: ExampleStyles,
362 }
363
364 impl Content for ExamplePicture {
min_width(&self) -> usize365 fn min_width(&self) -> usize {
366 *self.positions.last().unwrap()
367 }
368
emit(&self, view: &mut dyn AsciiView)369 fn emit(&self, view: &mut dyn AsciiView) {
370 self.example.paint_on(&self.styles, &self.positions, view);
371 }
372
into_wrap_items(self: Box<Self>, wrap_items: &mut Vec<Box<dyn Content>>)373 fn into_wrap_items(self: Box<Self>, wrap_items: &mut Vec<Box<dyn Content>>) {
374 wrap_items.push(self);
375 }
376 }
377
378 impl Debug for ExamplePicture {
fmt(&self, fmt: &mut Formatter) -> Result<(), Error>379 fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
380 Debug::fmt(&self.example, fmt)
381 }
382 }
383
shift(positions: &mut [usize], amount: usize)384 fn shift(positions: &mut [usize], amount: usize) {
385 for position in positions {
386 *position += amount;
387 }
388 }
389
390 impl ExampleStyles {
ambig() -> Self391 pub fn ambig() -> Self {
392 let session = Tls::session();
393 ExampleStyles {
394 before_cursor: session.ambig_symbols,
395 on_cursor: session.ambig_symbols,
396 after_cursor: session.ambig_symbols,
397 }
398 }
399
new() -> Self400 pub fn new() -> Self {
401 let session = Tls::session();
402 ExampleStyles {
403 before_cursor: session.observed_symbols,
404 on_cursor: session.cursor_symbol,
405 after_cursor: session.unobserved_symbols,
406 }
407 }
408 }
409