plotnik_bytecode/bytecode/
format.rs1use 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 const PADDING: Symbol = Symbol::new(" ", "...", " ");
67
68 pub fn format(&self) -> String {
70 format!("{}{}{}", self.left, self.center, self.right)
71 }
72}
73
74pub fn nav_symbol(nav: Nav) -> Symbol {
91 match nav {
92 Nav::Epsilon => Symbol::EPSILON,
93 Nav::Stay => Symbol::EMPTY,
94 Nav::StayExact => Symbol::new(" ", "!", " "),
95 Nav::Down => Symbol::new(" ", "▽", " "),
96 Nav::DownSkip => Symbol::new(" !", "▽", " "),
97 Nav::DownExact => Symbol::new("!!", "▽", " "),
98 Nav::Next => Symbol::new(" ", "▷", " "),
99 Nav::NextSkip => Symbol::new(" !", "▷", " "),
100 Nav::NextExact => Symbol::new("!!", "▷", " "),
101 Nav::Up(n) => Symbol::new(" ", "△", superscript_suffix(n)),
102 Nav::UpSkipTrivia(n) => Symbol::new(" !", "△", superscript_suffix(n)),
103 Nav::UpExact(n) => Symbol::new("!!", "△", superscript_suffix(n)),
104 }
105}
106
107pub mod trace {
109 use super::Symbol;
110
111 pub const NAV_DOWN: Symbol = Symbol::new(" ", "▽", " ");
113 pub const NAV_NEXT: Symbol = Symbol::new(" ", "▷", " ");
115 pub const NAV_UP: Symbol = Symbol::new(" ", "△", " ");
117
118 pub const MATCH_SUCCESS: Symbol = Symbol::new(" ", "●", " ");
120 pub const MATCH_FAILURE: Symbol = Symbol::new(" ", "○", " ");
122
123 pub const EFFECT: Symbol = Symbol::new(" ", "⬥", " ");
125 pub const EFFECT_SUPPRESSED: Symbol = Symbol::new(" ", "⬦", " ");
127
128 pub const CALL: Symbol = Symbol::new(" ", "▶", " ");
130 pub const RETURN: Symbol = Symbol::new(" ", "◀", " ");
132
133 pub const BACKTRACK: Symbol = Symbol::new(" ", "❮❮❮", " ");
135}
136
137const SUPERSCRIPT_DIGITS: &[char] = &['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
138
139pub fn superscript(n: u8) -> String {
141 if n < 10 {
142 SUPERSCRIPT_DIGITS[n as usize].to_string()
143 } else {
144 n.to_string()
145 .chars()
146 .map(|c| SUPERSCRIPT_DIGITS[c.to_digit(10).unwrap() as usize])
147 .collect()
148 }
149}
150
151fn superscript_suffix(n: u8) -> &'static str {
154 match n {
155 1 => " ",
156 2 => "² ",
157 3 => "³ ",
158 4 => "⁴ ",
159 5 => "⁵ ",
160 6 => "⁶ ",
161 7 => "⁷ ",
162 8 => "⁸ ",
163 9 => "⁹ ",
164 _ => "ⁿ ",
166 }
167}
168
169pub fn width_for_count(count: usize) -> usize {
171 if count <= 1 {
172 1
173 } else {
174 ((count - 1) as f64).log10().floor() as usize + 1
175 }
176}
177
178pub fn truncate_text(s: &str, max_len: usize) -> String {
182 if s.chars().count() <= max_len {
183 s.to_string()
184 } else {
185 let truncated: String = s.chars().take(max_len - 1).collect();
186 format!("{}…", truncated)
187 }
188}
189
190pub struct LineBuilder {
195 step_width: usize,
196}
197
198impl LineBuilder {
199 pub fn new(step_width: usize) -> Self {
201 Self { step_width }
202 }
203
204 pub fn instruction_prefix(&self, step: u16, symbol: Symbol) -> String {
206 format!(
207 "{:indent$}{:0sw$} {} ",
208 "",
209 step,
210 symbol.format(),
211 indent = cols::INDENT,
212 sw = self.step_width,
213 )
214 }
215
216 pub fn subline_prefix(&self, symbol: Symbol) -> String {
218 let step_area = cols::INDENT + self.step_width + cols::GAP;
219 format!("{:step_area$}{} ", "", symbol.format())
220 }
221
222 pub fn backtrack_line(&self, step: u16) -> String {
224 format!(
225 "{:indent$}{:0sw$} {}",
226 "",
227 step,
228 trace::BACKTRACK.format(),
229 indent = cols::INDENT,
230 sw = self.step_width,
231 )
232 }
233
234 pub fn pad_successors(&self, base: String, successors: &str) -> String {
238 let padding = cols::TOTAL_WIDTH
239 .saturating_sub(display_width(&base))
240 .max(2);
241 format!("{base}{:padding$}{successors}", "")
242 }
243}
244
245fn display_width(s: &str) -> usize {
249 let mut width = 0;
250 let mut in_escape = false;
251
252 for c in s.chars() {
253 if in_escape {
254 if c == 'm' {
255 in_escape = false;
256 }
257 } else if c == '\x1b' {
258 in_escape = true;
259 } else {
260 width += 1;
261 }
262 }
263
264 width
265}
266
267pub fn format_effect(effect: &EffectOp) -> String {
269 match effect.opcode {
270 EffectOpcode::Node => "Node".to_string(),
271 EffectOpcode::Arr => "Arr".to_string(),
272 EffectOpcode::Push => "Push".to_string(),
273 EffectOpcode::EndArr => "EndArr".to_string(),
274 EffectOpcode::Obj => "Obj".to_string(),
275 EffectOpcode::EndObj => "EndObj".to_string(),
276 EffectOpcode::Set => format!("Set(M{})", effect.payload),
277 EffectOpcode::Enum => format!("Enum(M{})", effect.payload),
278 EffectOpcode::EndEnum => "EndEnum".to_string(),
279 EffectOpcode::Text => "Text".to_string(),
280 EffectOpcode::Clear => "Clear".to_string(),
281 EffectOpcode::Null => "Null".to_string(),
282 EffectOpcode::SuppressBegin => "SuppressBegin".to_string(),
283 EffectOpcode::SuppressEnd => "SuppressEnd".to_string(),
284 }
285}