expr_solver/
source.rs

1use crate::span::Span;
2use colored::Colorize;
3use unicode_width::UnicodeWidthStr;
4
5/// Source code container with input validation.
6#[derive(Debug, Clone)]
7pub struct Source<'str> {
8    pub input: &'str str,
9}
10
11impl<'str> Source<'str> {
12    /// Create a new source from input string.
13    ///
14    /// Validates input length and trims whitespace.
15    pub fn new(input: &'str str) -> Self {
16        let trimmed = input.trim();
17        Self { input: trimmed }
18    }
19
20    /// Return source string with a highlighted section
21    pub fn highlight(&self, span: &Span) -> String {
22        let input = &self.input;
23        let pre = Self::escape(&input[..span.start]);
24        let tok = Self::escape(&input[span.start..span.end]);
25        let post = Self::escape(&input[span.end..]);
26        let line = format!("{}{}{}", pre, tok.red().bold(), post);
27
28        let caret = "^".green().bold();
29        let squiggly_len = UnicodeWidthStr::width(tok.as_str());
30        let caret_offset = UnicodeWidthStr::width(pre.as_str()) + caret.len();
31
32        format!(
33            "1 | {0}\n  | {1: >2$}{3}",
34            line,
35            caret,
36            caret_offset,
37            "~".repeat(squiggly_len.saturating_sub(1)).green()
38        )
39    }
40
41    fn escape(s: &str) -> String {
42        let mut out = String::with_capacity(s.len());
43        for c in s.chars() {
44            match c {
45                '\n' => out.push_str("\\n"),
46                '\r' => out.push_str("\\r"),
47                other => out.push(other),
48            }
49        }
50        out
51    }
52}