expr_solver/
source.rs

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