use ascii_canvas::AsciiView;
use crate::grammar::repr::*;
use crate::message::builder::InlineBuilder;
use crate::message::Content;
use std::fmt::{Debug, Error, Formatter};
use crate::style::Style;
use crate::tls::Tls;
#[cfg(test)]
mod test;
#[derive(Clone, Debug)]
pub struct Example {
pub symbols: Vec<ExampleSymbol>,
pub cursor: usize,
pub reductions: Vec<Reduction>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExampleSymbol {
Symbol(Symbol),
Epsilon,
}
#[derive(Copy, Clone, Default)]
pub struct ExampleStyles {
pub before_cursor: Style,
pub on_cursor: Style,
pub after_cursor: Style,
}
#[derive(Clone, Debug)]
pub struct Reduction {
pub start: usize,
pub end: usize,
pub nonterminal: NonterminalString,
}
impl Example {
fn lengths(&self) -> Vec<usize> {
self.symbols
.iter()
.map(|s| match *s {
ExampleSymbol::Symbol(ref s) => format!("{}", s).chars().count(),
ExampleSymbol::Epsilon => 1, })
.chain(Some(0))
.collect()
}
pub fn to_symbol_list(&self, length: usize, styles: ExampleStyles) -> Box<dyn Content> {
let mut builder = InlineBuilder::new().begin_spaced();
for (index, symbol) in self.symbols[..length].iter().enumerate() {
let style = if index < self.cursor {
styles.before_cursor
} else if index > self.cursor {
styles.after_cursor
} else {
match *symbol {
ExampleSymbol::Symbol(Symbol::Terminal(_)) => styles.on_cursor,
ExampleSymbol::Symbol(Symbol::Nonterminal(_)) => styles.after_cursor,
ExampleSymbol::Epsilon => styles.after_cursor,
}
};
if let ExampleSymbol::Symbol(ref s) = symbol {
builder = builder.push(s.clone()).styled(style);
}
}
builder.end().indented().end()
}
pub fn into_picture(self, styles: ExampleStyles) -> Box<dyn Content> {
let lengths = self.lengths();
let positions = self.positions(&lengths);
InlineBuilder::new()
.push(Box::new(ExamplePicture {
example: self,
positions,
styles,
}))
.indented()
.end()
}
fn starting_positions(&self, lengths: &[usize]) -> Vec<usize> {
lengths
.iter()
.scan(0, |counter, &len| {
let start = *counter;
*counter = start + len + 1;
Some(start)
})
.collect()
}
fn positions(&self, lengths: &[usize]) -> Vec<usize> {
let mut positions = self.starting_positions(lengths);
for &Reduction {
start,
end,
ref nonterminal,
} in &self.reductions
{
let nt_len = format!("{}", nonterminal).chars().count();
let num_syms = end - start;
assert!(num_syms > 0);
let start_position = positions[start];
let end_position = positions[end] - 1;
let required_len = nt_len + 4; let actual_len = end_position - start_position; if required_len < actual_len {
continue; }
let difference = required_len - actual_len;
shift(&mut positions[end..], difference);
if num_syms > 1 {
let num_gaps = num_syms - 1; let amount = difference / num_gaps; let extra = difference % num_gaps;
for i in 0..extra {
shift(&mut positions[start + 1 + i..end], amount + 1);
}
for i in extra..num_gaps {
shift(&mut positions[start + 1 + i..end], amount);
}
}
}
positions
}
#[cfg(test)]
pub fn paint_unstyled(&self) -> Vec<::ascii_canvas::Row> {
let this = self.clone();
let content = this.into_picture(ExampleStyles::default());
let min_width = content.min_width();
let canvas = content.emit_to_canvas(min_width);
canvas.to_strings()
}
fn paint_on(&self, styles: &ExampleStyles, positions: &[usize], view: &mut dyn AsciiView) {
for (index, reduction) in self.reductions.iter().enumerate() {
let start_column = positions[reduction.start];
let end_column = positions[reduction.end] - 1;
let row = 1 + index;
view.draw_vertical_line(0..row + 1, start_column);
view.draw_vertical_line(0..row + 1, end_column - 1);
view.draw_horizontal_line(row, start_column..end_column);
}
let session = Tls::session();
for (index, reduction) in self.reductions.iter().enumerate() {
let column = positions[reduction.start] + 2;
let row = 1 + index;
view.write_chars(
row,
column,
reduction.nonterminal.to_string().chars(),
session.nonterminal_symbol,
);
}
self.paint_symbols_on(&self.symbols, &positions, styles, view);
}
fn paint_symbols_on(
&self,
symbols: &[ExampleSymbol],
positions: &[usize],
styles: &ExampleStyles,
view: &mut dyn AsciiView,
) {
let session = Tls::session();
for (index, ex_symbol) in symbols.iter().enumerate() {
let style = if index < self.cursor {
styles.before_cursor
} else if index == self.cursor {
match *ex_symbol {
ExampleSymbol::Symbol(Symbol::Terminal(_)) => styles.on_cursor,
_ => styles.after_cursor,
}
} else {
styles.after_cursor
};
let column = positions[index];
match *ex_symbol {
ExampleSymbol::Symbol(Symbol::Terminal(ref term)) => {
view.write_chars(
0,
column,
term.to_string().chars(),
style.with(session.terminal_symbol),
);
}
ExampleSymbol::Symbol(Symbol::Nonterminal(ref nt)) => {
view.write_chars(
0,
column,
nt.to_string().chars(),
style.with(session.nonterminal_symbol),
);
}
ExampleSymbol::Epsilon => {}
}
}
}
}
struct ExamplePicture {
example: Example,
positions: Vec<usize>,
styles: ExampleStyles,
}
impl Content for ExamplePicture {
fn min_width(&self) -> usize {
*self.positions.last().unwrap()
}
fn emit(&self, view: &mut dyn AsciiView) {
self.example.paint_on(&self.styles, &self.positions, view);
}
fn into_wrap_items(self: Box<Self>, wrap_items: &mut Vec<Box<dyn Content>>) {
wrap_items.push(self);
}
}
impl Debug for ExamplePicture {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
Debug::fmt(&self.example, fmt)
}
}
fn shift(positions: &mut [usize], amount: usize) {
for position in positions {
*position += amount;
}
}
impl ExampleStyles {
pub fn ambig() -> Self {
let session = Tls::session();
ExampleStyles {
before_cursor: session.ambig_symbols,
on_cursor: session.ambig_symbols,
after_cursor: session.ambig_symbols,
}
}
pub fn new() -> Self {
let session = Tls::session();
ExampleStyles {
before_cursor: session.observed_symbols,
on_cursor: session.cursor_symbol,
after_cursor: session.unobserved_symbols,
}
}
}