1use std::fmt::{self, Write};
2use std::ops::Range;
3
4use strum_macros::IntoStaticStr;
5
6use crate::MathDisplay;
9use crate::environments::Env;
10use crate::html_utils::{escape_double_quoted_html_attribute, escape_html_content};
11use crate::token::EndToken;
12
13#[derive(Debug, Clone)]
15pub struct LatexError(pub Range<usize>, pub LatexErrKind);
16
17#[derive(Debug, Clone)]
18#[non_exhaustive]
19pub enum LatexErrKind {
20 UnclosedGroup(EndToken),
21 UnmatchedClose(EndToken),
22 ExpectedArgumentGotClose,
23 ExpectedArgumentGotEOF,
24 ExpectedDelimiter(DelimiterModifier),
25 DisallowedChar(char),
26 UnknownEnvironment(Box<str>),
27 UnknownCommand(Box<str>),
28 UnknownColor(Box<str>),
29 MismatchedEnvironment {
30 expected: Env,
31 got: Env,
32 },
33 CannotBeUsedHere {
34 got: LimitedUsabilityToken,
35 correct_place: Place,
36 },
37 ExpectedRelation,
38 BoundFollowedByBound,
39 DuplicateSubOrSup,
40 ExpectedText(&'static str),
41 ExpectedLength(Box<str>),
42 ExpectedColSpec(Box<str>),
43 ExpectedNumber(Box<str>),
44 NotValidInTextMode,
45 InvalidMacroName(String),
46 InvalidParameterNumber,
47 MacroParameterOutsideCustomCommand,
48 ExpectedParamNumberGotEOF,
49 HardLimitExceeded,
50 Internal,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
54pub enum DelimiterModifier {
55 #[strum(serialize = r"\left")]
56 Left,
57 #[strum(serialize = r"\right")]
58 Right,
59 #[strum(serialize = r"\middle")]
60 Middle,
61 #[strum(serialize = r"\big, \Big, ...")]
62 Big,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
66#[repr(u32)] pub enum Place {
68 #[strum(serialize = r"after \int, \sum, ...")]
69 AfterBigOp,
70 #[strum(serialize = r"in a table-like environment")]
71 TableEnv,
72 #[strum(serialize = r"in a numbered equation environment")]
73 NumberedEnv,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
77pub enum LimitedUsabilityToken {
78 #[strum(serialize = "&")]
79 Ampersand,
80 #[strum(serialize = r"\tag")]
81 Tag,
82 #[strum(serialize = r"\limits")]
83 Limits,
84}
85
86impl LatexErrKind {
87 pub fn string(&self) -> String {
92 match self {
93 LatexErrKind::UnclosedGroup(expected) => {
94 "Expected token \"".to_string() + <&str>::from(expected) + "\", but not found."
95 }
96 LatexErrKind::UnmatchedClose(got) => {
97 "Unmatched closing token: \"".to_string() + <&str>::from(got) + "\"."
98 }
99 LatexErrKind::ExpectedArgumentGotClose => {
100 r"Expected argument but got closing token (`}`, `\end`, `\right`).".to_string()
101 }
102 LatexErrKind::ExpectedArgumentGotEOF => "Expected argument but reached end of input.".to_string(),
103 LatexErrKind::ExpectedDelimiter(location) => {
104 "There must be a parenthesis after \"".to_string()
105 + <&str>::from(*location)
106 + "\", but not found."
107 }
108 LatexErrKind::DisallowedChar(got) => {
109 let mut text = "Disallowed character in text group: '".to_string();
110 text.push(*got);
111 text += "'.";
112 text
113 }
114 LatexErrKind::UnknownEnvironment(environment) => {
115 "Unknown environment \"".to_string() + environment + "\"."
116 }
117 LatexErrKind::UnknownCommand(cmd) => "Unknown command \"\\".to_string() + cmd + "\".",
118 LatexErrKind::UnknownColor(color) => "Unknown color \"".to_string() + color + "\".",
119 LatexErrKind::MismatchedEnvironment { expected, got } => {
120 "Expected \"\\end{".to_string()
121 + expected.as_str()
122 + "}\", but got \"\\end{"
123 + got.as_str()
124 + "}\"."
125 }
126 LatexErrKind::CannotBeUsedHere { got, correct_place } => {
127 "Got \"".to_string()
128 + <&str>::from(got)
129 + "\", which may only appear "
130 + <&str>::from(correct_place)
131 + "."
132 }
133 LatexErrKind::ExpectedRelation => {
134 "Expected a relation after \\not.".to_string()
135 }
136 LatexErrKind::BoundFollowedByBound => {
137 "'^' or '_' directly followed by '^', '_' or prime.".to_string()
138 }
139 LatexErrKind::DuplicateSubOrSup => {
140 "Duplicate subscript or superscript.".to_string()
141 }
142 LatexErrKind::ExpectedText(place) => "Expected text in ".to_string() + place + ".",
143 LatexErrKind::ExpectedLength(got) => {
144 "Expected length with units, got \"".to_string() + got + "\"."
145 }
146 LatexErrKind::ExpectedNumber(got) => "Expected a number, got \"".to_string() + got + "\".",
147 LatexErrKind::ExpectedColSpec(got) => {
148 "Expected column specification, got \"".to_string() + got + "\"."
149 }
150 LatexErrKind::NotValidInTextMode => {
151 "Not valid in text mode.".to_string()
152 }
153 LatexErrKind::InvalidMacroName(name) => {
154 "Invalid macro name: \"\\".to_string() + name + "\"."
155 }
156 LatexErrKind::InvalidParameterNumber => {
157 "Invalid parameter number. Must be 1-9.".to_string()
158 }
159 LatexErrKind::MacroParameterOutsideCustomCommand => {
160 "Macro parameter found outside of custom command definition.".to_string()
161 }
162 LatexErrKind::ExpectedParamNumberGotEOF => {
163 "Expected parameter number after '#', but got end of input.".to_string()
164 }
165 LatexErrKind::HardLimitExceeded => {
166 "Hard limit exceeded. Please simplify your equation.".to_string()
167 }
168 LatexErrKind::Internal => {
169 "Internal parser error. Please report this bug at https://github.com/tmke8/math-core/issues".to_string()
170 },
171 }
172 }
173}
174
175impl LatexError {
176 pub fn to_html(&self, latex: &str, display: MathDisplay, css_class: Option<&str>) -> String {
184 let mut output = String::new();
185 let tag = if matches!(display, MathDisplay::Block) {
186 "p"
187 } else {
188 "span"
189 };
190 let css_class = css_class.unwrap_or("math-core-error");
191 let _ = write!(
192 output,
193 r#"<{} class="{}" title="{}: "#,
194 tag, css_class, self.0.start
195 );
196 escape_double_quoted_html_attribute(&mut output, &self.1.string());
197 output.push_str(r#""><code>"#);
198 escape_html_content(&mut output, latex);
199 let _ = write!(output, "</code></{tag}>");
200 output
201 }
202}
203
204impl fmt::Display for LatexError {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(f, "{}: {}", self.0.start, self.1.string())
207 }
208}
209
210impl std::error::Error for LatexError {}
211
212pub trait GetUnwrap {
213 fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str;
215}
216
217impl GetUnwrap for str {
218 #[cfg(target_arch = "wasm32")]
219 #[inline]
220 fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str {
221 unsafe { self.get_unchecked(range) }
224 }
225 #[cfg(not(target_arch = "wasm32"))]
226 #[inline]
227 fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str {
228 self.get(range).expect("valid range")
229 }
230}
231
232