1use std::{error::Error, fmt::Display};
5
6use super::SpanStack;
7use crate::event::GroupingKind;
8
9#[derive(Debug)]
15pub struct ParserError {
16 inner: Box<Inner>,
17}
18
19#[derive(Debug)]
20struct Inner {
21 error: ErrorKind,
22 context: Box<str>,
23}
24
25impl ParserError {
26 pub(super) fn new(error: ErrorKind, place: *const u8, span_stack: &mut SpanStack) -> Self {
27 const CONTEXT_SIZE: usize = 12;
28 const CONTEXT_PREFIX: &str = "╭─► context:\n";
29 const EXPANSION_PREFIX: &str = "─► which was expanded from:\n";
30
31 let index = span_stack.reach_original_call_site(place);
32 let mut context = String::from(CONTEXT_PREFIX);
33
34 let first_string = span_stack
35 .expansions
36 .last()
37 .map(|exp| exp.full_expansion)
38 .unwrap_or(span_stack.input);
39
40 let (mut lower_bound, mut upper_bound) = (
41 floor_char_boundary(first_string, index.saturating_sub(CONTEXT_SIZE)),
42 floor_char_boundary(first_string, index + CONTEXT_SIZE),
43 );
44
45 for (index, expansion) in span_stack.expansions.iter().rev().enumerate() {
46 let next_string = (span_stack.expansions.len() - 1)
47 .checked_sub(index + 1)
48 .map(|index| span_stack.expansions[index].full_expansion)
49 .unwrap_or(span_stack.input);
50
51 if lower_bound > expansion.expansion_length {
52 lower_bound = floor_char_boundary(
53 next_string,
54 lower_bound + expansion.call_site_in_origin.start,
55 );
56 upper_bound = floor_char_boundary(
57 next_string,
58 (expansion.call_site_in_origin.end + upper_bound).min(next_string.len()),
59 );
60
61 continue;
62 }
63
64 let context_str = &expansion.full_expansion[lower_bound..upper_bound];
65 write_context_str(context_str, &mut context, false, lower_bound > 0);
66 context.push_str(EXPANSION_PREFIX);
67
68 lower_bound = floor_char_boundary(
69 next_string,
70 expansion
71 .call_site_in_origin
72 .start
73 .saturating_sub(CONTEXT_SIZE),
74 );
75 upper_bound = floor_char_boundary(
76 next_string,
77 expansion.call_site_in_origin.end + CONTEXT_SIZE,
78 );
79 }
80 write_context_str(
81 &span_stack.input[lower_bound..upper_bound],
82 &mut context,
83 true,
84 lower_bound > 0,
85 );
86 context.shrink_to_fit();
87
88 Self {
89 inner: Box::new(Inner {
90 error,
91 context: context.into_boxed_str(),
92 }),
93 }
94 }
95}
96
97fn write_context_str(context: &str, out: &mut String, last: bool, has_previous_content: bool) {
98 out.push_str("│\n");
99 let mut lines = context.lines();
100 if let Some(line) = lines.next() {
101 out.push('│');
102 if has_previous_content {
103 out.push('…');
104 } else {
105 out.push(' ');
106 }
107 out.push_str(line);
108 out.push('\n');
109 }
110
111 lines.for_each(|line| {
112 out.push_str("│ ");
113 out.push_str(line);
114 out.push('\n');
115 });
116 let last_line_len = context.lines().last().unwrap_or_default().len();
117 out.push_str("│ ");
118 (0..last_line_len).for_each(|_| out.push('^'));
119 out.push('\n');
120 if last {
121 out.push_str("╰─");
122 (0..last_line_len).for_each(|_| out.push('─'));
123 } else {
124 out.push('├');
125 }
126}
127
128impl Error for ParserError {
129 fn source(&self) -> Option<&(dyn Error + 'static)> {
130 Some(&self.inner.error)
131 }
132}
133
134impl Display for ParserError {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 f.write_str("parsing error: ")?;
137 self.inner.error.fmt(f)?;
138 f.write_str("\n")?;
139 f.write_str(&self.inner.context)?;
140 Ok(())
141 }
142}
143
144pub(crate) type InnerResult<T> = std::result::Result<T, ErrorKind>;
145
146#[derive(Debug)]
147pub(crate) enum ErrorKind {
148 UnbalancedGroup(Option<GroupingKind>),
149 Environment,
150 MathShift,
151 HashSign,
152 DimensionArgument,
153 DimensionUnit,
154 MathUnit,
155 Delimiter,
156 ControlSequence,
157 Number,
158 CharacterNumber,
159 Argument,
160 GroupArgument,
161 DoubleSubscript,
162 DoubleSuperscript,
163 UnknownPrimitive,
164 ControlSequenceAsArgument,
165 ScriptAsArgument,
166 EmptyControlSequence,
167 UnknownColor,
168 InvalidCharNumber,
169 Relax,
170 BracesInParamText,
171 CommentInParamText,
172 IncorrectMacroParams(u8, u8),
173 IncorrectReplacementParams(u8, u8),
174 TooManyParams,
175 StandaloneHashSign,
176 IncorrectMacroPrefix,
177 MacroSuffixNotFound,
178 MacroAlreadyDefined,
179 MacroNotDefined,
180 Alignment,
181 NewLine,
182 ArrayNoColumns,
183 MissingExpansion,
184 MacroRecursionLimit,
185 Token,
186}
187
188impl Display for ErrorKind {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 match self {
191 ErrorKind::UnbalancedGroup(Some(missing)) => {
192 write!(f, "unbalanced group found, expected it to be closed with `{}`", missing.closing_str())
193 },
194 ErrorKind::UnbalancedGroup(None) => f.write_str("unbalanced group found, unexpected group closing found"),
195 ErrorKind::Environment => f.write_str("unkown mathematical environment found"),
196 ErrorKind::MathShift => f.write_str(
197 "unexpected math `$` (math shift) character - this character cannot be used inside math mode"),
198 ErrorKind::HashSign => f.write_str(
199 "unexpected hash sign `#` character - this character can only be used in macro definitions"
200 ),
201 ErrorKind::MathUnit => f.write_str("expected mathematical units (mu) in dimension specification"),
202 ErrorKind::Delimiter => f.write_str("expected a delimiter token"),
203 ErrorKind::ControlSequence => f.write_str("expected a control sequence"),
204 ErrorKind::Number => f.write_str("expected a number"),
205 ErrorKind::CharacterNumber => f.write_str("expected a character representing a number after '`'. found a non ascii character"),
206 ErrorKind::Argument => f.write_str("expected an argument"),
207 ErrorKind::GroupArgument => f.write_str("expected an argument delimited by `{{}}`"),
208 ErrorKind::DoubleSubscript => f.write_str("trying to add a subscript twice to the same element"),
209 ErrorKind::DoubleSuperscript => f.write_str("trying to add a superscript twice to the same element"),
210 ErrorKind::UnknownPrimitive => f.write_str("unknown primitive command found"),
211 ErrorKind::ControlSequenceAsArgument => f.write_str("control sequence found as argument to a command that does not support them"),
212 ErrorKind::ScriptAsArgument => f.write_str("subscript and/or superscript found as argument to a command"),
213 ErrorKind::EmptyControlSequence => f.write_str("empty control sequence"),
214 ErrorKind::UnknownColor => f.write_str("unkown color. colors must either be predefined or in the form `#RRGGBB`"),
215 ErrorKind::InvalidCharNumber => f.write_str("expected a number in the range 0..=255 for it to be translated into a character"),
216 ErrorKind::Relax => f.write_str("cannot use the `\\relax` command in this context"),
217 ErrorKind::BracesInParamText => f.write_str("macro definition of parameters contains '{{' or '}}'"),
218 ErrorKind::CommentInParamText => f.write_str("macro definition of parameters contains a (`%`) comment"),
219 ErrorKind::IncorrectMacroParams(found, expected) => {
220 write!(f, "macro definition found parameter #{} but expected #{}", found, expected)
221 }
222 ErrorKind::IncorrectReplacementParams(found, expected) => {
223 write!(f, "macro definition found parameter #{} but expected a parameter in the range [1, {}]", found, expected)
224 }
225 ErrorKind::TooManyParams => f.write_str("macro definition contains too many parameters, the maximum is 9"),
226 ErrorKind::StandaloneHashSign => f.write_str("macro definition contains a standalone '#'"),
227 ErrorKind::IncorrectMacroPrefix => f.write_str("macro use does not match its definition, expected it to begin with a prefix string as specified in the definition"),
228 ErrorKind::MacroSuffixNotFound => f.write_str("macro use does not match its definition, expected its argument(s) to end with a suffix string as specified in the definition"),
229 ErrorKind::MacroAlreadyDefined => f.write_str("macro already defined"),
230 ErrorKind::MacroNotDefined => f.write_str("macro not defined"),
231 ErrorKind::DimensionArgument => f.write_str("expected a dimension or glue argument"),
232 ErrorKind::DimensionUnit => f.write_str("expected a dimensional unit"),
233 ErrorKind::Alignment => f.write_str("alignment not allowed in current environment"),
234 ErrorKind::NewLine => f.write_str("new line command not allowed in current environment"),
235 ErrorKind::ArrayNoColumns => f.write_str("array must have at least one column of the type `c`, `l` or `r`"),
236 ErrorKind::MissingExpansion => f.write_str("The macro definition is missing an expansion"),
237 ErrorKind::MacroRecursionLimit => f.write_str("macro expansion depth limit exceeded (possible infinite recursion)"),
238 ErrorKind::Token => f.write_str("expected a token"),
239 }
240 }
241}
242
243impl Error for ErrorKind {}
244
245fn floor_char_boundary(str: &str, index: usize) -> usize {
246 if index >= str.len() {
247 str.len()
248 } else {
249 let lower_bound = index.saturating_sub(3);
250 let new_index = str.as_bytes()[lower_bound..=index].iter().rposition(|b| {
251 (*b as i8) >= -0x40
253 });
254
255 unsafe { lower_bound + new_index.unwrap_unchecked() }
257 }
258}