math_core/latex_parser/
error.rs

1use std::fmt;
2
3use strum_macros::IntoStaticStr;
4
5// use no_panic::no_panic;
6
7use super::token::Token;
8
9/// Represents an error that occurred during LaTeX parsing or rendering.
10#[derive(Debug)]
11pub struct LatexError<'source>(pub usize, pub LatexErrKind<'source>);
12
13#[derive(Debug)]
14pub enum LatexErrKind<'source> {
15    UnexpectedToken {
16        expected: &'static Token<'static>,
17        got: Token<'source>,
18    },
19    UnclosedGroup(Token<'source>),
20    UnexpectedClose(Token<'source>),
21    UnexpectedEOF,
22    MissingParenthesis {
23        location: &'static Token<'static>,
24        got: Token<'source>,
25    },
26    DisallowedChars,
27    UnknownEnvironment(&'source str),
28    UnknownCommand(&'source str),
29    UnknownColor(&'source str),
30    MismatchedEnvironment {
31        expected: &'source str,
32        got: &'source str,
33    },
34    CannotBeUsedHere {
35        got: Token<'source>,
36        correct_place: Place,
37    },
38    ExpectedText(&'static str),
39    ExpectedLength(&'source str),
40    ExpectedColSpec(&'source str),
41    RenderError,
42    NotValidInTextMode(Token<'source>),
43    InvalidMacroName(&'source str),
44}
45
46#[derive(Debug, IntoStaticStr)]
47#[repr(u32)] // A different value here somehow increases code size on WASM enormously.
48pub enum Place {
49    #[strum(serialize = r"after \int, \sum, ...")]
50    AfterBigOp,
51    #[strum(serialize = r"before supported operators")]
52    BeforeSomeOps,
53    #[strum(serialize = r"after an identifier or operator")]
54    AfterOpOrIdent,
55}
56
57impl LatexErrKind<'_> {
58    /// Returns the error message as a string.
59    ///
60    /// This serves the same purpose as the `Display` implementation,
61    /// but produces more compact WASM code.
62    pub fn string(&self) -> String {
63        match self {
64            LatexErrKind::UnexpectedToken { expected, got } => {
65                "Expected token \"".to_string()
66                    + <&str>::from(*expected)
67                    + "\", but found token \""
68                    + <&str>::from(got)
69                    + "\"."
70            }
71            LatexErrKind::UnclosedGroup(expected) => {
72                "Expected token \"".to_string() + <&str>::from(expected) + "\", but not found."
73            }
74            LatexErrKind::UnexpectedClose(got) => {
75                "Unexpected closing token: \"".to_string() + <&str>::from(got) + "\"."
76            }
77            LatexErrKind::UnexpectedEOF => "Unexpected end of file.".to_string(),
78            LatexErrKind::MissingParenthesis { location, got } => {
79                "There must be a parenthesis after \"".to_string()
80                    + <&str>::from(*location)
81                    + "\", but not found. Instead, \""
82                    + <&str>::from(got)
83                    + "\" was found."
84            }
85            LatexErrKind::DisallowedChars => "Disallowed characters in text group.".to_string(),
86            LatexErrKind::UnknownEnvironment(environment) => {
87                "Unknown environment \"".to_string() + environment + "\"."
88            }
89            LatexErrKind::UnknownCommand(cmd) => "Unknown command \"\\".to_string() + cmd + "\".",
90            LatexErrKind::UnknownColor(color) => "Unknown color \"".to_string() + color + "\".",
91            LatexErrKind::MismatchedEnvironment { expected, got } => {
92                "Expected \"\\end{".to_string() + expected + "}\", but got \"\\end{" + got + "}\"."
93            }
94            LatexErrKind::CannotBeUsedHere { got, correct_place } => {
95                "Got \"".to_string()
96                    + <&str>::from(got)
97                    + "\", which may only appear "
98                    + <&str>::from(correct_place)
99                    + "."
100            }
101            LatexErrKind::ExpectedText(place) => "Expected text in ".to_string() + place + ".",
102            LatexErrKind::ExpectedLength(got) => {
103                "Expected length with units, got \"".to_string() + got + "\"."
104            }
105            LatexErrKind::ExpectedColSpec(got) => {
106                "Expected column specification, got \"".to_string() + got + "\"."
107            }
108            LatexErrKind::RenderError => "Render error".to_string(),
109            LatexErrKind::NotValidInTextMode(got) => {
110                "Got \"".to_string() + <&str>::from(got) + "\", which is not valid in text mode."
111            }
112            LatexErrKind::InvalidMacroName(name) => {
113                "Invalid macro name: \"\\".to_string() + name + "\"."
114            }
115        }
116    }
117}
118
119impl fmt::Display for LatexError<'_> {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        write!(f, "{}: {}", self.0, self.1.string())
122    }
123}
124
125impl std::error::Error for LatexError<'_> {}
126
127pub trait GetUnwrap {
128    /// `str::get` with `Option::unwrap`.
129    fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str;
130}
131
132impl GetUnwrap for str {
133    #[cfg(target_arch = "wasm32")]
134    #[inline]
135    fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str {
136        // On WASM, panics are really expensive in terms of code size,
137        // so we use an unchecked get here.
138        unsafe { self.get_unchecked(range) }
139    }
140    #[cfg(not(target_arch = "wasm32"))]
141    #[inline]
142    fn get_unwrap(&self, range: std::ops::Range<usize>) -> &str {
143        self.get(range).expect("valid range")
144    }
145}
146
147/* fn itoa(val: u64, buf: &mut [u8; 20]) -> &str {
148    let start = if val == 0 {
149        buf[19] = b'0';
150        19
151    } else {
152        let mut val = val;
153        let mut i = 20;
154
155        // The `i > 0` check is technically redundant but it allows the compiler to
156        // prove that `buf` is always validly indexed.
157        while val != 0 && i > 0 {
158            i -= 1;
159            buf[i] = (val % 10) as u8 + b'0';
160            val /= 10;
161        }
162        i
163    };
164
165    // This is safe because we know the buffer contains valid ASCII.
166    // This unsafe block wouldn't be necessary if the `ascii_char` feature were stable.
167    unsafe { std::str::from_utf8_unchecked(&buf[start..]) }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn itoa_test() {
176        let mut buf = [0u8; 20];
177        assert_eq!(itoa(0, &mut buf), "0");
178        assert_eq!(itoa(1, &mut buf), "1");
179        assert_eq!(itoa(10, &mut buf), "10");
180        assert_eq!(itoa(1234567890, &mut buf), "1234567890");
181        assert_eq!(itoa(u64::MAX, &mut buf), "18446744073709551615");
182    }
183} */