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