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 {}