Skip to main content

farben_core/
errors.rs

1//! Error types for farben markup tokenization and registry operations.
2//!
3//! [`LexError`] covers failures during tokenization of markup strings like `[bold red]text[/]`.
4//! [`RegistryError`] covers failures in registry operations such as `set_prefix` and `insert_style`.
5//! [`LexErrorDisplay`] wraps a [`LexError`] with the original input to produce compiler-style
6//! diagnostic output with a caret pointing at the offending byte offset.
7
8/// Errors produced by registry operations (`set_prefix`, `insert_style`).
9///
10/// These errors carry no source position because registry calls happen outside of markup
11/// parsing, where no input string is available to point into.
12#[derive(Debug)]
13pub enum RegistryError {
14    /// The `prefix!` macro was called with a style name that has not been registered.
15    UnknownStyle(String),
16    /// A style name was registered that contains `[` or `]`, which are reserved as tag delimiters.
17    InvalidName(String),
18}
19
20impl std::fmt::Display for RegistryError {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        match self {
23            Self::UnknownStyle(s) => write!(f, "unknown style: '{s}' has not been registered"),
24            Self::InvalidName(s) => write!(
25                f,
26                "invalid style name '{s}': '[' and ']' are not allowed in style names"
27            ),
28        }
29    }
30}
31
32/// Diagnostic formatter that renders a [`LexError`] alongside the original markup input.
33///
34/// Formats output in the style of the Rust compiler: the input string on one line, followed by
35/// a caret (`^`) on the next line aligned to the byte offset stored in the error variant.
36///
37/// # Example
38///
39/// ```text
40///    | [bold unknown]text[/]
41///    |  ^^^^ invalid tag: 'bold unknown'
42/// ```
43pub struct LexErrorDisplay<'a> {
44    /// The error to render.
45    pub error: &'a LexError,
46    /// The full markup string that was being tokenized when the error occurred.
47    pub input: &'a str,
48}
49
50impl std::fmt::Display for LexErrorDisplay<'_> {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        writeln!(f, "   | {}", self.input)?;
53        match self.error {
54            LexError::UnclosedTag(position) => {
55                write!(f, "   | {}^ unclosed tag", " ".repeat(*position))
56            }
57            LexError::InvalidTag {
58                tag_content,
59                position,
60            } => {
61                write!(
62                    f,
63                    "   | {}{} invalid tag: '{tag_content}'",
64                    " ".repeat(*position + 1),
65                    "^".repeat(tag_content.len())
66                )
67            }
68            LexError::UnclosedValue(position) => {
69                write!(
70                    f,
71                    "   | {}^ unclosed parentheses for color value",
72                    " ".repeat(*position + 1)
73                )
74            }
75            LexError::InvalidArgumentCount {
76                expected,
77                got,
78                position,
79            } => {
80                write!(
81                    f,
82                    "   | {}^ expected {expected} arguments, got {got}",
83                    " ".repeat(*position + 1)
84                )
85            }
86            LexError::InvalidValue { value, position } => {
87                write!(
88                    f,
89                    "   | {}{} invalid value: '{value}'",
90                    " ".repeat(*position + 1),
91                    "^".repeat(value.len())
92                )
93            }
94            LexError::InvalidResetTarget(position) => {
95                write!(
96                    f,
97                    "   | {}^ reset target must be a color or emphasis tag",
98                    " ".repeat(*position + 1)
99                )
100            }
101        }
102    }
103}
104
105/// Errors produced during tokenization of a farben markup string.
106#[derive(Debug)]
107pub enum LexError {
108    /// A `[` was found with no matching `]`.
109    UnclosedTag(usize),
110    /// The tag name is not a recognized keyword or color form.
111    InvalidTag {
112        /// The raw text inside the brackets, e.g. `"floob"` for `[floob]`.
113        tag_content: String,
114        /// Byte offset of the opening `[` in the original input.
115        position: usize,
116    },
117    /// A color value function (e.g. `rgb(` or `ansi(`) was opened but never closed.
118    UnclosedValue(usize),
119    /// A color function received the wrong number of arguments.
120    InvalidArgumentCount {
121        /// How many arguments the function requires (e.g. 3 for `rgb`).
122        expected: usize,
123        /// How many arguments were actually provided.
124        got: usize,
125        /// Byte offset of the function call in the original input.
126        position: usize,
127    },
128    /// An argument could not be parsed into the expected numeric type.
129    InvalidValue {
130        /// The offending argument as written.
131        value: String,
132        /// Byte offset of the function call in the original input.
133        position: usize,
134    },
135    /// A reset tag was given a target that cannot be reset (e.g. `[/reset]` or `[/prefix]`).
136    InvalidResetTarget(usize),
137}
138
139impl LexError {
140    /// Returns a helper that formats this error with a caret pointing at the
141    /// offending byte in the original input.
142    ///
143    /// # Example
144    ///
145    /// ```
146    /// use farben_core::errors::LexError;
147    /// use farben_core::lexer::tokenize;
148    ///
149    /// let input = "[bold red unkn]oops";
150    /// match tokenize(input) {
151    ///     Ok(_) => {}
152    ///     Err(e) => eprintln!("{}", e.display(input)),
153    /// }
154    /// ```
155    #[must_use]
156    pub fn display<'a>(&'a self, input: &'a str) -> LexErrorDisplay<'a> {
157        LexErrorDisplay { error: self, input }
158    }
159}
160
161impl std::fmt::Display for LexError {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        match self {
164            LexError::UnclosedTag(pos) => write!(f, "unclosed tag at position {pos}"),
165            LexError::InvalidTag {
166                tag_content,
167                position,
168            } => write!(f, "invalid tag '{tag_content}' at position {position}"),
169            LexError::UnclosedValue(pos) => {
170                write!(f, "unclosed parentheses for color value at position {pos}")
171            }
172            LexError::InvalidArgumentCount {
173                expected,
174                got,
175                position,
176            } => {
177                write!(
178                    f,
179                    "expected {expected} arguments, got {got} at position {position}"
180                )
181            }
182            LexError::InvalidValue { value, position } => {
183                write!(f, "invalid value '{value}' at position {position}")
184            }
185            LexError::InvalidResetTarget(pos) => write!(
186                f,
187                "reset target must be a color or emphasis tag at position {pos}"
188            ),
189        }
190    }
191}
192
193impl std::error::Error for LexError {}