1use super::EffectOp;
10use super::effects::EffectOpcode;
11use super::nav::Nav;
12
13pub mod cols {
15 pub const INDENT: usize = 2;
17 pub const GAP: usize = 1;
19 pub const SYMBOL: usize = 5;
21 pub const TOTAL_WIDTH: usize = 44;
23}
24
25#[derive(Clone, Copy, Debug)]
31pub struct Symbol {
32 pub left: &'static str,
35 pub center: &'static str,
38 pub right: &'static str,
41}
42
43impl Default for Symbol {
44 fn default() -> Self {
45 Self::EMPTY
46 }
47}
48
49impl Symbol {
50 pub const fn new(left: &'static str, center: &'static str, right: &'static str) -> Self {
52 Self {
53 left,
54 center,
55 right,
56 }
57 }
58
59 pub const EMPTY: Symbol = Symbol::new(" ", " ", " ");
61
62 pub const EPSILON: Symbol = Symbol::new(" ", "ε", " ");
64
65 pub fn format(&self) -> String {
67 format!("{}{}{}", self.left, self.center, self.right)
68 }
69}
70
71pub fn nav_symbol(nav: Nav) -> Symbol {
88 match nav {
89 Nav::Epsilon => Symbol::EPSILON,
90 Nav::Stay => Symbol::EMPTY,
91 Nav::StayExact => Symbol::new(" ", "!", " "),
92 Nav::Down => Symbol::new(" ", "▽", " "),
93 Nav::DownSkip => Symbol::new(" !", "▽", " "),
94 Nav::DownExact => Symbol::new("!!", "▽", " "),
95 Nav::Next => Symbol::new(" ", "▷", " "),
96 Nav::NextSkip => Symbol::new(" !", "▷", " "),
97 Nav::NextExact => Symbol::new("!!", "▷", " "),
98 Nav::Up(n) => Symbol::new(" ", "△", superscript_suffix(n)),
99 Nav::UpSkipTrivia(n) => Symbol::new(" !", "△", superscript_suffix(n)),
100 Nav::UpExact(n) => Symbol::new("!!", "△", superscript_suffix(n)),
101 }
102}
103
104pub mod trace {
106 use super::Symbol;
107
108 pub const NAV_DOWN: Symbol = Symbol::new(" ", "▽", " ");
110 pub const NAV_NEXT: Symbol = Symbol::new(" ", "▷", " ");
112 pub const NAV_UP: Symbol = Symbol::new(" ", "△", " ");
114
115 pub const MATCH_SUCCESS: Symbol = Symbol::new(" ", "●", " ");
117 pub const MATCH_FAILURE: Symbol = Symbol::new(" ", "○", " ");
119
120 pub const EFFECT: Symbol = Symbol::new(" ", "⬥", " ");
122 pub const EFFECT_SUPPRESSED: Symbol = Symbol::new(" ", "⬦", " ");
124
125 pub const CALL: Symbol = Symbol::new(" ", "▶", " ");
127 pub const RETURN: Symbol = Symbol::new(" ", "◀", " ");
129
130 pub const BACKTRACK: Symbol = Symbol::new(" ", "❮❮❮", " ");
132}
133
134const SUPERSCRIPT_DIGITS: &[char] = &['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
135
136pub fn superscript(n: u8) -> String {
138 if n < 10 {
139 SUPERSCRIPT_DIGITS[n as usize].to_string()
140 } else {
141 n.to_string()
142 .chars()
143 .map(|c| SUPERSCRIPT_DIGITS[c.to_digit(10).unwrap() as usize])
144 .collect()
145 }
146}
147
148fn superscript_suffix(n: u8) -> &'static str {
151 match n {
152 1 => " ",
153 2 => "² ",
154 3 => "³ ",
155 4 => "⁴ ",
156 5 => "⁵ ",
157 6 => "⁶ ",
158 7 => "⁷ ",
159 8 => "⁸ ",
160 9 => "⁹ ",
161 _ => "ⁿ ",
163 }
164}
165
166pub fn width_for_count(count: usize) -> usize {
168 if count <= 1 {
169 1
170 } else {
171 ((count - 1) as f64).log10().floor() as usize + 1
172 }
173}
174
175pub fn truncate_text(s: &str, max_len: usize) -> String {
179 if s.chars().count() <= max_len {
180 s.to_string()
181 } else {
182 let truncated: String = s.chars().take(max_len - 1).collect();
183 format!("{}…", truncated)
184 }
185}
186
187pub struct LineBuilder {
192 step_width: usize,
193}
194
195impl LineBuilder {
196 pub fn new(step_width: usize) -> Self {
198 Self { step_width }
199 }
200
201 pub fn instruction_prefix(&self, step: u16, symbol: Symbol) -> String {
203 format!(
204 "{:indent$}{:0sw$} {} ",
205 "",
206 step,
207 symbol.format(),
208 indent = cols::INDENT,
209 sw = self.step_width,
210 )
211 }
212
213 pub fn subline_prefix(&self, symbol: Symbol) -> String {
215 let step_area = cols::INDENT + self.step_width + cols::GAP;
216 format!("{:step_area$}{} ", "", symbol.format())
217 }
218
219 pub fn backtrack_line(&self, step: u16) -> String {
221 format!(
222 "{:indent$}{:0sw$} {}",
223 "",
224 step,
225 trace::BACKTRACK.format(),
226 indent = cols::INDENT,
227 sw = self.step_width,
228 )
229 }
230
231 pub fn pad_successors(&self, base: String, successors: &str) -> String {
235 let padding = cols::TOTAL_WIDTH
236 .saturating_sub(display_width(&base))
237 .max(2);
238 format!("{base}{:padding$}{successors}", "")
239 }
240}
241
242fn display_width(s: &str) -> usize {
246 let mut width = 0;
247 let mut in_escape = false;
248
249 for c in s.chars() {
250 if in_escape {
251 if c == 'm' {
252 in_escape = false;
253 }
254 } else if c == '\x1b' {
255 in_escape = true;
256 } else {
257 width += 1;
258 }
259 }
260
261 width
262}
263
264pub fn format_effect(effect: &EffectOp) -> String {
266 match effect.opcode {
267 EffectOpcode::Node => "Node".to_string(),
268 EffectOpcode::Arr => "Arr".to_string(),
269 EffectOpcode::Push => "Push".to_string(),
270 EffectOpcode::EndArr => "EndArr".to_string(),
271 EffectOpcode::Obj => "Obj".to_string(),
272 EffectOpcode::EndObj => "EndObj".to_string(),
273 EffectOpcode::Set => format!("Set(M{})", effect.payload),
274 EffectOpcode::Enum => format!("Enum(M{})", effect.payload),
275 EffectOpcode::EndEnum => "EndEnum".to_string(),
276 EffectOpcode::Text => "Text".to_string(),
277 EffectOpcode::Clear => "Clear".to_string(),
278 EffectOpcode::Null => "Null".to_string(),
279 EffectOpcode::SuppressBegin => "SuppressBegin".to_string(),
280 EffectOpcode::SuppressEnd => "SuppressEnd".to_string(),
281 }
282}