1use std::fmt::{self, Write};
2use std::ops::Range;
3
4use strum_macros::IntoStaticStr;
5
6use crate::MathDisplay;
7use crate::environments::Env;
8use crate::html_utils::{escape_double_quoted_html_attribute, escape_html_content};
9use crate::token::EndToken;
10
11#[derive(Debug, Clone)]
13pub struct LatexError(pub Range<usize>, pub(crate) LatexErrKind);
14
15#[derive(Debug, Clone)]
16pub(crate) enum LatexErrKind {
17 UnclosedGroup(EndToken),
18 UnmatchedClose(EndToken),
19 ExpectedArgumentGotClose,
20 ExpectedArgumentGotEOF,
21 ExpectedDelimiter(DelimiterModifier),
22 DisallowedChar(char),
23 UnknownEnvironment(Box<str>),
24 UnknownCommand(Box<str>),
25 UnknownColor(Box<str>),
26 MismatchedEnvironment {
27 expected: Env,
28 got: Env,
29 },
30 CannotBeUsedHere {
31 got: LimitedUsabilityToken,
32 correct_place: Place,
33 },
34 ExpectedRelation,
35 BoundFollowedByBound,
36 DuplicateSubOrSup,
37 ExpectedText(&'static str),
38 ExpectedLength(Box<str>),
39 ExpectedColSpec(Box<str>),
40 ExpectedNumber(Box<str>),
41 NotValidInTextMode,
42 InvalidMacroName(String),
43 InvalidParameterNumber,
44 MacroParameterOutsideCustomCommand,
45 ExpectedParamNumberGotEOF,
46 HardLimitExceeded,
47 Internal,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
51pub enum DelimiterModifier {
52 #[strum(serialize = r"\left")]
53 Left,
54 #[strum(serialize = r"\right")]
55 Right,
56 #[strum(serialize = r"\middle")]
57 Middle,
58 #[strum(serialize = r"\big, \Big, ...")]
59 Big,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
63#[repr(u32)] pub enum Place {
65 #[strum(serialize = r"after \int, \sum, ...")]
66 AfterBigOp,
67 #[strum(serialize = r"in a table-like environment")]
68 TableEnv,
69 #[strum(serialize = r"in a numbered equation environment")]
70 NumberedEnv,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
74pub enum LimitedUsabilityToken {
75 #[strum(serialize = "&")]
76 Ampersand,
77 #[strum(serialize = r"\tag")]
78 Tag,
79 #[strum(serialize = r"\limits")]
80 Limits,
81}
82
83impl LatexErrKind {
84 fn write_msg(&self, s: &mut String) -> std::fmt::Result {
86 match self {
87 LatexErrKind::UnclosedGroup(expected) => {
88 write!(
89 s,
90 "Expected token \"{}\", but not found.",
91 <&str>::from(expected)
92 )?;
93 }
94 LatexErrKind::UnmatchedClose(got) => {
95 write!(s, "Unmatched closing token: \"{}\".", <&str>::from(got))?;
96 }
97 LatexErrKind::ExpectedArgumentGotClose => {
98 write!(
99 s,
100 r"Expected argument but got closing token (`}}`, `\end`, `\right`)."
101 )?;
102 }
103 LatexErrKind::ExpectedArgumentGotEOF => {
104 write!(s, "Expected argument but reached end of input.")?;
105 }
106 LatexErrKind::ExpectedDelimiter(location) => {
107 write!(
108 s,
109 "There must be a parenthesis after \"{}\", but not found.",
110 <&str>::from(*location)
111 )?;
112 }
113 LatexErrKind::DisallowedChar(got) => {
114 write!(s, "Disallowed character in text group: '{}'.", got)?;
115 }
116 LatexErrKind::UnknownEnvironment(environment) => {
117 write!(s, "Unknown environment \"{}\".", environment)?;
118 }
119 LatexErrKind::UnknownCommand(cmd) => {
120 write!(s, "Unknown command \"\\{}\".", cmd)?;
121 }
122 LatexErrKind::UnknownColor(color) => {
123 write!(s, "Unknown color \"{}\".", color)?;
124 }
125 LatexErrKind::MismatchedEnvironment { expected, got } => {
126 write!(
127 s,
128 "Expected \"\\end{{{}}}\", but got \"\\end{{{}}}\".",
129 expected.as_str(),
130 got.as_str()
131 )?;
132 }
133 LatexErrKind::CannotBeUsedHere { got, correct_place } => {
134 write!(
135 s,
136 "Got \"{}\", which may only appear {}.",
137 <&str>::from(got),
138 <&str>::from(correct_place)
139 )?;
140 }
141 LatexErrKind::ExpectedRelation => {
142 write!(s, "Expected a relation after \\not.")?;
143 }
144 LatexErrKind::BoundFollowedByBound => {
145 write!(s, "'^' or '_' directly followed by '^', '_' or prime.")?;
146 }
147 LatexErrKind::DuplicateSubOrSup => {
148 write!(s, "Duplicate subscript or superscript.")?;
149 }
150 LatexErrKind::ExpectedText(place) => {
151 write!(s, "Expected text in {}.", place)?;
152 }
153 LatexErrKind::ExpectedLength(got) => {
154 write!(s, "Expected length with units, got \"{}\".", got)?;
155 }
156 LatexErrKind::ExpectedNumber(got) => {
157 write!(s, "Expected a number, got \"{}\".", got)?;
158 }
159 LatexErrKind::ExpectedColSpec(got) => {
160 write!(s, "Expected column specification, got \"{}\".", got)?;
161 }
162 LatexErrKind::NotValidInTextMode => {
163 write!(s, "Not valid in text mode.")?;
164 }
165 LatexErrKind::InvalidMacroName(name) => {
166 write!(s, "Invalid macro name: \"\\{}\".", name)?;
167 }
168 LatexErrKind::InvalidParameterNumber => {
169 write!(s, "Invalid parameter number. Must be 1-9.")?;
170 }
171 LatexErrKind::MacroParameterOutsideCustomCommand => {
172 write!(
173 s,
174 "Macro parameter found outside of custom command definition."
175 )?;
176 }
177 LatexErrKind::ExpectedParamNumberGotEOF => {
178 write!(
179 s,
180 "Expected parameter number after '#', but got end of input."
181 )?;
182 }
183 LatexErrKind::HardLimitExceeded => {
184 write!(s, "Hard limit exceeded. Please simplify your equation.")?;
185 }
186 LatexErrKind::Internal => {
187 write!(
188 s,
189 "Internal parser error. Please report this bug at https://github.com/tmke8/math-core/issues"
190 )?;
191 }
192 }
193 Ok(())
194 }
195}
196
197impl LatexError {
198 pub fn to_html(&self, latex: &str, display: MathDisplay, css_class: Option<&str>) -> String {
206 let mut output = String::new();
207 let tag = if matches!(display, MathDisplay::Block) {
208 "p"
209 } else {
210 "span"
211 };
212 let css_class = css_class.unwrap_or("math-core-error");
213 let _ = write!(output, r#"<{} class="{}" title=""#, tag, css_class);
214 let mut err_msg = String::new();
215 self.to_message(&mut err_msg, latex);
216 escape_double_quoted_html_attribute(&mut output, &err_msg);
217 output.push_str(r#""><code>"#);
218 escape_html_content(&mut output, latex);
219 let _ = write!(output, "</code></{tag}>");
220 output
221 }
222
223 pub fn error_message(&self) -> String {
224 let mut s = String::new();
225 let _ = self.1.write_msg(&mut s);
226 s
227 }
228
229 pub fn to_message(&self, s: &mut String, input: &str) {
236 let loc = input.floor_char_boundary(self.0.start);
237 let codepoint_offset = input[..loc].chars().count();
238 let _ = write!(s, "{}: ", codepoint_offset);
239 let _ = self.1.write_msg(s);
240 }
241}
242
243#[cfg(feature = "ariadne")]
244impl LatexError {
245 pub fn to_report<'name>(
247 &self,
248 source_name: &'name str,
249 with_color: bool,
250 ) -> ariadne::Report<'static, (&'name str, Range<usize>)> {
251 use std::borrow::Cow;
252
253 use ariadne::{Label, Report, ReportKind};
254
255 let label_msg: Cow<'_, str> = match &self.1 {
256 LatexErrKind::UnclosedGroup(expected) => format!(
257 "expected \"{}\" to close this group",
258 <&str>::from(expected)
259 )
260 .into(),
261 LatexErrKind::UnmatchedClose(got) => {
262 format!("unmatched \"{}\"", <&str>::from(got)).into()
263 }
264 LatexErrKind::ExpectedArgumentGotClose => "expected an argument here".into(),
265 LatexErrKind::ExpectedArgumentGotEOF => "expected an argument here".into(),
266 LatexErrKind::ExpectedDelimiter(modifier) => {
267 format!("expected a delimiter after \"{}\"", <&str>::from(*modifier)).into()
268 }
269 LatexErrKind::DisallowedChar(_) => "disallowed character".into(),
270 LatexErrKind::UnknownEnvironment(_) => "unknown environment".into(),
271 LatexErrKind::UnknownCommand(_) => "unknown command".into(),
272 LatexErrKind::UnknownColor(_) => "unknown color".into(),
273 LatexErrKind::MismatchedEnvironment { expected, .. } => {
274 format!("expected \"\\end{{{}}}\" here", expected.as_str()).into()
275 }
276 LatexErrKind::CannotBeUsedHere { correct_place, .. } => {
277 format!("may only appear {}", <&str>::from(correct_place)).into()
278 }
279 LatexErrKind::ExpectedRelation => "expected a relation".into(),
280 LatexErrKind::BoundFollowedByBound => "unexpected bound".into(),
281 LatexErrKind::DuplicateSubOrSup => "duplicate".into(),
282 LatexErrKind::ExpectedText(place) => format!("expected text in {place}").into(),
283 LatexErrKind::ExpectedLength(_) => "expected length here".into(),
284 LatexErrKind::ExpectedNumber(_) => "expected a number here".into(),
285 LatexErrKind::ExpectedColSpec(_) => "expected a column spec here".into(),
286 LatexErrKind::NotValidInTextMode => "not valid in text mode".into(),
287 LatexErrKind::InvalidMacroName(_) => "invalid name here".into(),
288 LatexErrKind::InvalidParameterNumber => "must be 1-9".into(),
289 LatexErrKind::MacroParameterOutsideCustomCommand => "unexpected macro parameter".into(),
290 LatexErrKind::ExpectedParamNumberGotEOF => "expected parameter number".into(),
291 LatexErrKind::HardLimitExceeded => "limit exceeded".into(),
292 LatexErrKind::Internal => "internal error".into(),
293 };
294
295 let mut config = ariadne::Config::default().with_index_type(ariadne::IndexType::Byte);
296 if !with_color {
297 config = config.with_color(false);
298 }
299 Report::build(ReportKind::Error, (source_name, self.0.start..self.0.start))
300 .with_config(config)
301 .with_message(self.error_message())
302 .with_label(Label::new((source_name, self.0.clone())).with_message(label_msg))
303 .finish()
304 }
305}
306
307impl fmt::Display for LatexError {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 write!(f, "{}", self.error_message())
310 }
311}
312
313impl std::error::Error for LatexError {}
314
315pub trait GetUnwrap {
316 fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str;
318}
319
320impl GetUnwrap for str {
321 #[cfg(target_arch = "wasm32")]
322 #[inline]
323 fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str {
324 unsafe { self.get_unchecked(range) }
327 }
328 #[cfg(not(target_arch = "wasm32"))]
329 #[inline]
330 fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str {
331 self.get(range).expect("valid range")
332 }
333}