use super::EffectOp;
use super::effects::EffectOpcode;
use super::nav::Nav;
pub mod cols {
pub const INDENT: usize = 2;
pub const GAP: usize = 1;
pub const SYMBOL: usize = 5;
pub const TOTAL_WIDTH: usize = 44;
}
#[derive(Clone, Copy, Debug)]
pub struct Symbol {
pub left: &'static str,
pub center: &'static str,
pub right: &'static str,
}
impl Default for Symbol {
fn default() -> Self {
Self::EMPTY
}
}
impl Symbol {
pub const fn new(left: &'static str, center: &'static str, right: &'static str) -> Self {
Self {
left,
center,
right,
}
}
pub const EMPTY: Symbol = Symbol::new(" ", " ", " ");
pub const EPSILON: Symbol = Symbol::new(" ", "ε", " ");
pub const PADDING: Symbol = Symbol::new(" ", "...", " ");
pub fn format(&self) -> String {
format!("{}{}{}", self.left, self.center, self.right)
}
}
pub fn nav_symbol(nav: Nav) -> Symbol {
match nav {
Nav::Epsilon => Symbol::EPSILON,
Nav::Stay => Symbol::EMPTY,
Nav::StayExact => Symbol::new(" ", "!", " "),
Nav::Down => Symbol::new(" ", "▽", " "),
Nav::DownSkip => Symbol::new(" !", "▽", " "),
Nav::DownExact => Symbol::new("!!", "▽", " "),
Nav::Next => Symbol::new(" ", "▷", " "),
Nav::NextSkip => Symbol::new(" !", "▷", " "),
Nav::NextExact => Symbol::new("!!", "▷", " "),
Nav::Up(n) => Symbol::new(" ", "△", superscript_suffix(n)),
Nav::UpSkipTrivia(n) => Symbol::new(" !", "△", superscript_suffix(n)),
Nav::UpExact(n) => Symbol::new("!!", "△", superscript_suffix(n)),
}
}
pub mod trace {
use super::Symbol;
pub const NAV_DOWN: Symbol = Symbol::new(" ", "▽", " ");
pub const NAV_NEXT: Symbol = Symbol::new(" ", "▷", " ");
pub const NAV_UP: Symbol = Symbol::new(" ", "△", " ");
pub const MATCH_SUCCESS: Symbol = Symbol::new(" ", "●", " ");
pub const MATCH_FAILURE: Symbol = Symbol::new(" ", "○", " ");
pub const EFFECT: Symbol = Symbol::new(" ", "⬥", " ");
pub const EFFECT_SUPPRESSED: Symbol = Symbol::new(" ", "⬦", " ");
pub const CALL: Symbol = Symbol::new(" ", "▶", " ");
pub const RETURN: Symbol = Symbol::new(" ", "◀", " ");
pub const BACKTRACK: Symbol = Symbol::new(" ", "❮❮❮", " ");
}
const SUPERSCRIPT_DIGITS: &[char] = &['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
pub fn superscript(n: u8) -> String {
if n < 10 {
SUPERSCRIPT_DIGITS[n as usize].to_string()
} else {
n.to_string()
.chars()
.map(|c| SUPERSCRIPT_DIGITS[c.to_digit(10).unwrap() as usize])
.collect()
}
}
fn superscript_suffix(n: u8) -> &'static str {
match n {
1 => " ",
2 => "² ",
3 => "³ ",
4 => "⁴ ",
5 => "⁵ ",
6 => "⁶ ",
7 => "⁷ ",
8 => "⁸ ",
9 => "⁹ ",
_ => "ⁿ ",
}
}
pub fn width_for_count(count: usize) -> usize {
if count <= 1 {
1
} else {
((count - 1) as f64).log10().floor() as usize + 1
}
}
pub fn truncate_text(s: &str, max_len: usize) -> String {
if s.chars().count() <= max_len {
s.to_string()
} else {
let truncated: String = s.chars().take(max_len - 1).collect();
format!("{}…", truncated)
}
}
pub struct LineBuilder {
step_width: usize,
}
impl LineBuilder {
pub fn new(step_width: usize) -> Self {
Self { step_width }
}
pub fn instruction_prefix(&self, step: u16, symbol: Symbol) -> String {
format!(
"{:indent$}{:0sw$} {} ",
"",
step,
symbol.format(),
indent = cols::INDENT,
sw = self.step_width,
)
}
pub fn subline_prefix(&self, symbol: Symbol) -> String {
let step_area = cols::INDENT + self.step_width + cols::GAP;
format!("{:step_area$}{} ", "", symbol.format())
}
pub fn backtrack_line(&self, step: u16) -> String {
format!(
"{:indent$}{:0sw$} {}",
"",
step,
trace::BACKTRACK.format(),
indent = cols::INDENT,
sw = self.step_width,
)
}
pub fn pad_successors(&self, base: String, successors: &str) -> String {
let padding = cols::TOTAL_WIDTH
.saturating_sub(display_width(&base))
.max(2);
format!("{base}{:padding$}{successors}", "")
}
}
fn display_width(s: &str) -> usize {
let mut width = 0;
let mut in_escape = false;
for c in s.chars() {
if in_escape {
if c == 'm' {
in_escape = false;
}
} else if c == '\x1b' {
in_escape = true;
} else {
width += 1;
}
}
width
}
pub fn format_effect(effect: &EffectOp) -> String {
match effect.opcode {
EffectOpcode::Node => "Node".to_string(),
EffectOpcode::Arr => "Arr".to_string(),
EffectOpcode::Push => "Push".to_string(),
EffectOpcode::EndArr => "EndArr".to_string(),
EffectOpcode::Obj => "Obj".to_string(),
EffectOpcode::EndObj => "EndObj".to_string(),
EffectOpcode::Set => format!("Set(M{})", effect.payload),
EffectOpcode::Enum => format!("Enum(M{})", effect.payload),
EffectOpcode::EndEnum => "EndEnum".to_string(),
EffectOpcode::Text => "Text".to_string(),
EffectOpcode::Clear => "Clear".to_string(),
EffectOpcode::Null => "Null".to_string(),
EffectOpcode::SuppressBegin => "SuppressBegin".to_string(),
EffectOpcode::SuppressEnd => "SuppressEnd".to_string(),
}
}