1use alloc::string::String;
2use core::fmt::{self, Write};
3use core::ops::Range;
4
5use kstring::KString;
6use strum_macros::IntoStaticStr;
7
8use crate::environments::Env;
9use crate::html_utils::{escape_double_quoted_html_attribute, escape_html_content};
10use crate::token::EndToken;
11use crate::{MathDisplay, token::LimitsKind};
12
13#[derive(Debug, Clone)]
15pub struct LatexError(pub Range<usize>, pub(crate) LatexErrKind);
16
17#[derive(Debug, Clone)]
18pub(crate) enum LatexErrKind {
19 UnclosedGroup(EndToken),
20 UnmatchedClose(EndToken),
21 ExpectedArgumentGotClose,
22 ExpectedArgumentGotEOI,
23 ExpectedDelimiter(DelimiterModifier),
24 DisallowedChar(char),
25 UnknownEnvironment(KString),
26 UnknownCommand(KString),
27 UnknownColor(KString),
28 MismatchedEnvironment {
29 expected: Env,
30 got: Env,
31 },
32 CannotBeUsedHere {
33 got: LimitedUsabilityToken,
34 correct_place: Place,
35 },
36 ExpectedRelation,
37 ExpectedLargeOp,
38 ExpectedAtMostOneToken,
39 ExpectedExactlyOneToken,
40 BoundFollowedByBound,
41 DuplicateSubOrSup,
42 CannotBeUsedAsArgument,
43 ExpectedAscii,
44 ExpectedLength(KString),
45 IllegalUnit {
46 unit: KString,
47 math_unit_expected: bool,
48 },
49 InvalidUnit(KString),
50 ExpectedColSpec(KString),
51 ExpectedStyle,
52 NotValidInTextMode,
53 NotValidInMathMode,
54 NestedMathModeUnimplemented,
56 UnexpectedDollar,
58 CouldNotExtractText,
59 MoreThanOneLabel,
60 MoreThanOneInfixCmd,
61 InvalidMacroName(String),
62 ExpectedCommandName,
64 CommandAlreadyDefined,
66 CommandNotDefined,
68 InvalidParameterNumber,
69 ParameterNumberOutOfRange {
70 n: u8,
71 actual: u8,
72 },
73 DelimitedParameters,
75 UnexpectedParameterNumber {
77 expected: u8,
78 actual: u8,
79 },
80 MacroParameterOutsideCustomCommand,
81 ExpectedParamNumberGotEOI,
82 HardLimitExceeded,
83 TooManyExpansions,
84 Internal,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
88pub enum DelimiterModifier {
89 #[strum(serialize = r"\left")]
90 Left,
91 #[strum(serialize = r"\right")]
92 Right,
93 #[strum(serialize = r"\middle")]
94 Middle,
95 #[strum(serialize = r"\big, \Big, ...")]
96 Big,
97 #[strum(serialize = r"\genfrac")]
98 Genfrac,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
102#[repr(u32)] pub enum Place {
104 #[strum(serialize = r"after \int, \sum, ...")]
105 AfterBigOp,
106 #[strum(serialize = r"in a table-like environment")]
107 TableEnv,
108 #[strum(serialize = r"in a numbered equation environment")]
109 NumberedEnv,
110 #[strum(serialize = r"directly after a `\\` or at the beginning of an array or matrix")]
111 ArrayRowStart,
112 #[strum(serialize = r"directly before \let or \def")]
113 BeforeDefinition,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
117pub enum LimitedUsabilityToken {
118 #[strum(serialize = "&")]
119 Ampersand,
120 #[strum(serialize = r"\tag")]
121 Tag,
122 #[strum(serialize = r"\tag*")]
123 TagStar,
124 #[strum(serialize = r"\label")]
125 Label,
126 #[strum(serialize = r"\limits")]
127 Limits,
128 #[strum(serialize = r"\nolimits")]
129 NoLimits,
130 #[strum(serialize = r"\displaylimits")]
131 DisplayLimits,
132 #[strum(serialize = r"\hline")]
133 HLine,
134 #[strum(serialize = r"\hdashline")]
135 HDashLine,
136 #[strum(serialize = r"\global")]
137 Global,
138}
139
140impl From<LimitsKind> for LimitedUsabilityToken {
141 fn from(kind: LimitsKind) -> Self {
142 match kind {
143 LimitsKind::Always => LimitedUsabilityToken::Limits,
144 LimitsKind::Never => LimitedUsabilityToken::NoLimits,
145 LimitsKind::Display => LimitedUsabilityToken::DisplayLimits,
146 }
147 }
148}
149
150impl LatexErrKind {
151 fn write_msg(&self, s: &mut String) -> core::fmt::Result {
153 match self {
154 LatexErrKind::UnclosedGroup(expected) => {
155 write!(
156 s,
157 "Expected closing token \"{}\", but reached end of input.",
158 <&str>::from(expected)
159 )?;
160 }
161 LatexErrKind::UnmatchedClose(got) => {
162 write!(s, "Unmatched closing token: \"{}\".", <&str>::from(got))?;
163 }
164 LatexErrKind::ExpectedArgumentGotClose => {
165 write!(
166 s,
167 r"Expected argument but got closing token (`}}`, `\end`, `\right`)."
168 )?;
169 }
170 LatexErrKind::ExpectedArgumentGotEOI => {
171 write!(s, "Expected argument but reached end of input.")?;
172 }
173 LatexErrKind::ExpectedDelimiter(location) => {
174 write!(
175 s,
176 "There must be a parenthesis after \"{}\", but not found.",
177 <&str>::from(*location)
178 )?;
179 }
180 LatexErrKind::ExpectedStyle => {
181 write!(
182 s,
183 r"Expected one of `\displaystyle`, `\textstyle`, `\scriptstyle`, or `\scriptscriptstyle`"
184 )?;
185 }
186 LatexErrKind::DisallowedChar(got) => {
187 write!(s, "Disallowed character in text group: '{got}'.")?;
188 }
189 LatexErrKind::UnknownEnvironment(environment) => {
190 write!(s, "Unknown environment \"{environment}\".")?;
191 }
192 LatexErrKind::UnknownCommand(cmd) => {
193 write!(s, "Unknown command \"\\{cmd}\".")?;
194 }
195 LatexErrKind::UnknownColor(color) => {
196 write!(s, "Unknown color \"{color}\".")?;
197 }
198 LatexErrKind::MismatchedEnvironment { expected, got } => {
199 write!(
200 s,
201 "Expected \"\\end{{{}}}\", but found \"\\end{{{}}}\".",
202 expected.as_str(),
203 got.as_str()
204 )?;
205 }
206 LatexErrKind::CannotBeUsedHere { got, correct_place } => {
207 write!(
208 s,
209 "Found \"{}\", which may only appear {}.",
210 <&str>::from(got),
211 <&str>::from(correct_place)
212 )?;
213 }
214 LatexErrKind::ExpectedRelation => {
215 write!(s, "Expected a relation after \\not.")?;
216 }
217 LatexErrKind::ExpectedLargeOp => {
218 write!(s, "Expected a large operator.")?;
219 }
220 LatexErrKind::ExpectedAtMostOneToken => {
221 write!(s, "Expected at most one token as argument.")?;
222 }
223 LatexErrKind::ExpectedExactlyOneToken => {
224 write!(s, "Expected exactly one token as argument.")?;
225 }
226 LatexErrKind::BoundFollowedByBound => {
227 write!(s, "'^' or '_' directly followed by '^', '_' or prime.")?;
228 }
229 LatexErrKind::DuplicateSubOrSup => {
230 write!(s, "Duplicate subscript or superscript.")?;
231 }
232 LatexErrKind::CannotBeUsedAsArgument => {
233 write!(s, "Switch-like commands cannot be used as arguments.")?;
234 }
235 LatexErrKind::ExpectedAscii => {
236 write!(
237 s,
238 "Expected non-special ASCII characters in string literal."
239 )?;
240 }
241 LatexErrKind::ExpectedLength(got) => {
242 write!(s, "Expected length with units, found \"{got}\".")?;
243 }
244 LatexErrKind::IllegalUnit {
245 unit,
246 math_unit_expected,
247 } => {
248 if *math_unit_expected {
249 write!(
250 s,
251 "Text unit \"{unit}\" cannot be used with \\mkern/\\mskip/\\mspace."
252 )?;
253 } else {
254 write!(
255 s,
256 "Math unit \"{unit}\" cannot be used with \\kern/\\hskip/\\hspace."
257 )?;
258 }
259 }
260 LatexErrKind::InvalidUnit(unit) => {
261 write!(s, "Found invalid unit \"{unit}\".")?;
262 }
263 LatexErrKind::ExpectedColSpec(got) => {
264 write!(s, "Expected column specification, found \"{got}\".")?;
265 }
266 LatexErrKind::NotValidInTextMode => {
267 write!(s, "Not valid in text mode.")?;
268 }
269 LatexErrKind::NotValidInMathMode => {
270 write!(s, "Not valid in math mode.")?;
271 }
272 LatexErrKind::NestedMathModeUnimplemented => {
273 write!(s, "Math mode within text mode is not implemented yet.")?;
274 }
275 LatexErrKind::UnexpectedDollar => {
276 write!(s, "Unexpected \"$\".")?;
277 }
278 LatexErrKind::CouldNotExtractText => {
279 write!(s, "Could not extract text from the given macro.")?;
280 }
281 LatexErrKind::MoreThanOneLabel => {
282 write!(s, "Found more than one label in a row.")?;
283 }
284 LatexErrKind::MoreThanOneInfixCmd => {
285 write!(s, "Found more than one infix fraction in a group.")?;
286 }
287 LatexErrKind::InvalidMacroName(name) => {
288 write!(s, "Invalid macro name: \"\\{name}\".")?;
289 }
290 LatexErrKind::ExpectedCommandName => {
291 write!(s, "Expected the name of a command.")?;
292 }
293 LatexErrKind::CommandAlreadyDefined => {
294 write!(s, "This command is already defined.")?;
295 }
296 LatexErrKind::CommandNotDefined => {
297 write!(s, "This command is not defined.")?;
298 }
299 LatexErrKind::InvalidParameterNumber => {
300 write!(s, "Invalid parameter number. Must be 1-9.")?;
301 }
302 LatexErrKind::ParameterNumberOutOfRange { n, actual } => {
303 write!(
304 s,
305 "Parameter number {actual} is out of range. Expected a number of at most {n}."
306 )?;
307 }
308 LatexErrKind::DelimitedParameters => {
309 write!(
310 s,
311 "Delimited parameters are not supported. Expected \"#n\" or \"{{\" here."
312 )?;
313 }
314 LatexErrKind::UnexpectedParameterNumber { expected, actual } => {
315 write!(
316 s,
317 "Expected parameter #{expected}, found #{actual}. Parameters must be numbered consecutively, starting at 1."
318 )?;
319 }
320 LatexErrKind::MacroParameterOutsideCustomCommand => {
321 write!(
322 s,
323 "Macro parameter found outside of custom command definition."
324 )?;
325 }
326 LatexErrKind::ExpectedParamNumberGotEOI => {
327 write!(
328 s,
329 "Expected parameter number after '#', but reached end of input."
330 )?;
331 }
332 LatexErrKind::HardLimitExceeded => {
333 write!(s, "Hard limit exceeded. Please simplify your equation.")?;
334 }
335 LatexErrKind::TooManyExpansions => {
336 write!(
337 s,
338 "Too many expansions of custom commands. A command may be expanding to itself."
339 )?;
340 }
341 LatexErrKind::Internal => {
342 write!(
343 s,
344 "Internal parser error. Please report this bug at https://github.com/tmke8/math-core/issues"
345 )?;
346 }
347 }
348 Ok(())
349 }
350}
351
352impl LatexError {
353 pub fn to_html(&self, latex: &str, display: MathDisplay, css_class: Option<&str>) -> String {
361 let mut output = String::new();
362 let tag = if matches!(display, MathDisplay::Block) {
363 "p"
364 } else {
365 "span"
366 };
367 let css_class = css_class.unwrap_or("math-core-error");
368 let _ = write!(output, r#"<{tag} class="{css_class}" title=""#);
369 let mut err_msg = String::new();
370 self.to_message(&mut err_msg, latex);
371 escape_double_quoted_html_attribute(&mut output, &err_msg);
372 output.push_str(r#""><code>"#);
373 escape_html_content(&mut output, latex);
374 let _ = write!(output, "</code></{tag}>");
375 output
376 }
377
378 pub fn error_message(&self) -> String {
380 let mut s = String::new();
381 let _ = self.1.write_msg(&mut s);
382 s
383 }
384
385 pub fn to_message(&self, s: &mut String, input: &str) {
392 let loc = input.floor_char_boundary(self.0.start);
393 let codepoint_offset = input[..loc].chars().count();
394 let _ = write!(s, "{codepoint_offset}: ");
395 let _ = self.1.write_msg(s);
396 }
397
398 pub fn label(&self) -> &'static str {
400 match &self.1 {
401 LatexErrKind::UnclosedGroup(_) => "a group was never closed",
402 LatexErrKind::UnmatchedClose(_) => "no matching opening for this",
403 LatexErrKind::ExpectedArgumentGotClose | LatexErrKind::ExpectedArgumentGotEOI => {
404 "expected an argument here"
405 }
406 LatexErrKind::ExpectedDelimiter(_) => "expected a delimiter here",
407 LatexErrKind::DisallowedChar(_) => "disallowed character",
408 LatexErrKind::UnknownEnvironment(_) => "unknown environment",
409 LatexErrKind::UnknownCommand(_) => "unknown command",
410 LatexErrKind::UnknownColor(_) => "unknown color",
411 LatexErrKind::MismatchedEnvironment { .. } => {
412 "expected a different environment name here"
413 }
414 LatexErrKind::CannotBeUsedHere { .. } => "cannot be used here",
415 LatexErrKind::ExpectedRelation => "expected a relation",
416 LatexErrKind::ExpectedLargeOp => "expected a large operator",
417 LatexErrKind::ExpectedStyle => "expected a style",
418 LatexErrKind::ExpectedAtMostOneToken => "expected at most one token here",
419 LatexErrKind::ExpectedExactlyOneToken => "expected exactly one token here",
420 LatexErrKind::BoundFollowedByBound => "unexpected bound",
421 LatexErrKind::DuplicateSubOrSup => "duplicate",
422 LatexErrKind::CannotBeUsedAsArgument => "used as argument",
423 LatexErrKind::ExpectedAscii => "special or not ASCII",
424 LatexErrKind::ExpectedLength(_) => "expected length here",
425 LatexErrKind::IllegalUnit { .. } => "illegal unit here",
426 LatexErrKind::InvalidUnit(_) => "invalid unit here",
427 LatexErrKind::ExpectedColSpec(_) => "expected a column spec here",
428 LatexErrKind::NotValidInTextMode => "this is not valid in text mode",
429 LatexErrKind::NotValidInMathMode => "this is not valid in math mode",
430 LatexErrKind::NestedMathModeUnimplemented => "cannot switch to math mode here",
431 LatexErrKind::UnexpectedDollar => "unexpected dollar sign",
432 LatexErrKind::CouldNotExtractText => "could not extract text from this",
433 LatexErrKind::MoreThanOneLabel => "duplicate label",
434 LatexErrKind::MoreThanOneInfixCmd => "duplicate infix frac",
435 LatexErrKind::InvalidMacroName(_) => "invalid name here",
436 LatexErrKind::ExpectedCommandName => "expected a command name here",
437 LatexErrKind::CommandAlreadyDefined => "already defined",
438 LatexErrKind::CommandNotDefined => "not defined",
439 LatexErrKind::InvalidParameterNumber => "must be 1-9",
440 LatexErrKind::ParameterNumberOutOfRange { .. } => "parameter number out of range",
441 LatexErrKind::DelimitedParameters => "unsupported delimiter",
442 LatexErrKind::UnexpectedParameterNumber { .. } => "unexpected parameter number",
443 LatexErrKind::MacroParameterOutsideCustomCommand => "unexpected macro parameter",
444 LatexErrKind::ExpectedParamNumberGotEOI => "expected parameter number",
445 LatexErrKind::HardLimitExceeded => "limit exceeded",
446 LatexErrKind::TooManyExpansions => "expansion limit exceeded",
447 LatexErrKind::Internal => "internal error",
448 }
449 }
450}
451
452#[cfg(feature = "ariadne")]
453impl LatexError {
454 pub fn to_report<'name>(
456 &self,
457 source_name: &'name str,
458 with_color: bool,
459 ) -> ariadne::Report<'static, (&'name str, Range<usize>)> {
460 use ariadne::{Label, Report, ReportKind};
461
462 let label_msg = self.label();
463
464 let mut config = ariadne::Config::default().with_index_type(ariadne::IndexType::Byte);
465 if !with_color {
466 config = config.with_color(false);
467 }
468 Report::build(ReportKind::Error, (source_name, self.0.start..self.0.start))
469 .with_config(config)
470 .with_message(self.error_message())
471 .with_label(Label::new((source_name, self.0.clone())).with_message(label_msg))
472 .finish()
473 }
474}
475
476impl fmt::Display for LatexError {
477 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478 write!(f, "{}", self.error_message())
479 }
480}
481
482impl core::error::Error for LatexError {}
483
484pub trait GetUnwrap {
485 fn get_unwrap(&self, range: core::ops::Range<usize>) -> &str;
487}
488
489impl GetUnwrap for str {
490 #[cfg(target_arch = "wasm32")]
491 #[inline]
492 fn get_unwrap(&self, range: core::ops::Range<usize>) -> &str {
493 unsafe { self.get_unchecked(range) }
496 }
497 #[cfg(not(target_arch = "wasm32"))]
498 #[inline]
499 fn get_unwrap(&self, range: core::ops::Range<usize>) -> &str {
500 self.get(range).expect("valid range")
501 }
502}