Skip to main content

geo_aid_figure/
math_string.rs

1use num_derive::{FromPrimitive, ToPrimitive};
2use num_traits::FromPrimitive;
3use serde::de::{Error, Visitor};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::fmt::{Display, Formatter};
6use std::mem;
7use std::ops::{Deref, DerefMut};
8use std::str::FromStr;
9
10/// Special math characters as their string representatives
11pub const SPECIAL_MATH: [&str; 49] = [
12    "alpha", "Alpha", "beta", "Beta", "gamma", "Gamma", "delta", "Delta", "epsilon", "Epsilon",
13    "zeta", "Zeta", "eta", "Eta", "theta", "Theta", "iota", "Iota", "kappa", "Kappa", "lambda",
14    "Lambda", "mu", "Mu", "nu", "Nu", "xi", "Xi", "omicron", "Omicron", "phi", "Phi", "rho", "Rho",
15    "sigma", "Sigma", "tau", "Tau", "upsilon", "Upsilon", "phi", "Phi", "chi", "Chi", "psi", "Psi",
16    "omega", "Omega", "quote",
17];
18
19/// A span in a string. Does not account for lines
20#[derive(Debug, Clone, Copy, Default)]
21pub struct StringSpan {
22    /// The first position in the span
23    pub start: usize,
24    /// The first position NOT in the span
25    pub end: usize,
26}
27
28impl Display for StringSpan {
29    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30        write!(f, "({}:{})", self.start, self.end)
31    }
32}
33
34/// An error while parsing math string
35#[derive(Debug, Clone)]
36pub struct ParseError {
37    /// Span of the error
38    pub span: StringSpan,
39    /// Kind of the error
40    pub kind: ParseErrorKind,
41}
42
43impl Display for ParseError {
44    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45        write!(f, "error at {}: {}", self.span, self.kind)
46    }
47}
48
49impl std::error::Error for ParseError {}
50
51/// The parsing error kind
52#[derive(Debug, Clone)]
53pub enum ParseErrorKind {
54    /// A special character code was not recognised
55    SpecialNotRecognised(String),
56    /// A nested index (a `_{...}` inside a `_{...}`). This is not valid
57    NestedIndex,
58    /// A special tag (`[`) was left unclosed
59    UnclosedSpecialTag(String),
60}
61
62impl Display for ParseErrorKind {
63    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
64        match self {
65            Self::SpecialNotRecognised(special) => write!(f, "special '{special}' not recognised"),
66            Self::NestedIndex => write!(f, "nested index is not valid"),
67            Self::UnclosedSpecialTag(special) => {
68                write!(f, "unclosed special tag. Parsed '{special}'")
69            }
70        }
71    }
72}
73
74/// Normal/lower index in math text.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
76pub enum MathIndex {
77    /// Set the current indexing to normal text
78    Normal,
79    /// Set the current indexing to lower (subscript)
80    Lower,
81}
82
83impl Display for MathIndex {
84    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85        match self {
86            Self::Normal => write!(f, "}}"),
87            Self::Lower => write!(f, "_{{"),
88        }
89    }
90}
91
92/// A math character is either just an ASCII character or a special character.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
94pub enum MathChar {
95    /// A standard ASCII character.
96    Ascii(char),
97    /// Special character denoted by a string.
98    Special(MathSpecial),
99    /// Starts lower index.
100    SetIndex(MathIndex),
101    /// Prime (a tick)
102    Prime,
103}
104
105impl Display for MathChar {
106    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
107        match self {
108            Self::Ascii(c) => write!(f, "{c}"),
109            Self::Special(c) => write!(f, "[{c}]"),
110            Self::SetIndex(x) => write!(f, "{x}"),
111            Self::Prime => write!(f, "'"),
112        }
113    }
114}
115
116/// A special character
117#[derive(Debug, Clone, Copy, FromPrimitive, ToPrimitive, PartialEq, Eq, Serialize)]
118pub enum MathSpecial {
119    /// &alpha;
120    Alpha,
121    /// &Alpha;
122    AlphaUpper,
123    /// &beta;
124    Beta,
125    /// &Beta;
126    BetaUpper,
127    /// &gamma;
128    Gamma,
129    /// &Gamma;
130    GammaUpper,
131    /// &delta;
132    Delta,
133    /// &Delta;
134    DeltaUpper,
135    /// &epsilon;
136    Epsilon,
137    /// &Epsilon;
138    EpsilonUpper,
139    /// &zeta;
140    Zeta,
141    /// &Zeta;
142    ZetaUpper,
143    /// &eta;
144    Eta,
145    /// &Eta;
146    EtaUpper,
147    /// &theta;
148    Theta,
149    /// &Theta;
150    ThetaUpper,
151    /// &iota;
152    Iota,
153    /// &Iota;
154    IotaUpper,
155    /// &kappa;
156    Kappa,
157    /// &Kappa;
158    KappaUpper,
159    /// &lambda;
160    Lambda,
161    /// &Lambda;
162    LambdaUpper,
163    /// &mu;
164    Mu,
165    /// &Mu;
166    MuUpper,
167    /// &nu;
168    Nu,
169    /// &Nu;
170    NuUpper,
171    /// &xi;
172    Xi,
173    /// &Xi;
174    XiUpper,
175    /// &omicron;
176    Omicron,
177    /// &Omicron;
178    OmicronUpper,
179    /// &pi;
180    Pi,
181    /// &Pi;
182    PiUpper,
183    /// &rho;
184    Rho,
185    /// &Rho;
186    RhoUpper,
187    /// &sigme;
188    Sigma,
189    /// &Sigme;
190    SigmaUpper,
191    /// &tau;
192    Tau,
193    /// &Tau;
194    TauUpper,
195    /// &upsilon;
196    Upsilon,
197    /// &Upsilon;
198    UpsilonUpper,
199    /// &phi;
200    Phi,
201    /// &Phi;
202    PhiUpper,
203    /// &chi;
204    Chi,
205    /// &Chi;
206    ChiUpper,
207    /// &psi;
208    Psi,
209    /// &Psi;
210    PsiUpper,
211    /// &omega;
212    Omega,
213    /// &Omega;
214    OmegaUpper,
215    /// "
216    Quote,
217}
218
219impl MathSpecial {
220    /// Whether this special is an alphabetic one
221    #[must_use]
222    pub fn is_alphabetic(self) -> bool {
223        self != Self::Quote
224    }
225
226    /// If `char_code` is a valid special math character, returns it in a `Some`. `None` otherwise.
227    ///
228    /// # Panics
229    /// Any panic in this function are a bug.
230    #[must_use]
231    pub fn parse(char_code: &str) -> Option<Self> {
232        SPECIAL_MATH
233            .iter()
234            .enumerate()
235            .find(|x| *x.1 == char_code)
236            .map(|x| MathSpecial::from_usize(x.0).unwrap())
237    }
238}
239
240impl Display for MathSpecial {
241    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
242        write!(f, "{}", SPECIAL_MATH[*self as usize])
243    }
244}
245
246/// A series of math characters.
247#[derive(Debug, Clone, Default)]
248pub struct MathString(Vec<MathChar>);
249
250impl MathString {
251    /// Creates a new, empty math string
252    #[must_use]
253    pub fn new() -> Self {
254        Self(Vec::new())
255    }
256
257    /// Checks if the math string is empty
258    #[must_use]
259    pub fn is_empty(&self) -> bool {
260        self.0.is_empty()
261    }
262
263    /// Creates a new string from a `&str`, treating each character literally
264    #[must_use]
265    pub fn raw(source: &str) -> Self {
266        Self(source.chars().map(MathChar::Ascii).collect())
267    }
268}
269
270impl Deref for MathString {
271    type Target = Vec<MathChar>;
272
273    fn deref(&self) -> &Self::Target {
274        &self.0
275    }
276}
277
278impl DerefMut for MathString {
279    fn deref_mut(&mut self) -> &mut Self::Target {
280        &mut self.0
281    }
282}
283
284impl Display for MathString {
285    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
286        for c in &self.0 {
287            write!(f, "{c}")?;
288        }
289
290        Ok(())
291    }
292}
293
294impl FromStr for MathString {
295    type Err = ParseError;
296
297    fn from_str(content: &str) -> Result<Self, Self::Err> {
298        let mut ignore_next = false;
299        let mut math_string = Vec::new();
300        let mut indexed = false;
301        let mut collect_special = false;
302        let mut special = String::new();
303        let mut index_delimited = false;
304
305        let mut special_start = 0;
306
307        for (char_count, c) in content.chars().enumerate() {
308            if collect_special {
309                if ignore_next || (c != ']' && c != '\\') {
310                    special.push(c);
311                    ignore_next = false;
312                } else if c == ']' {
313                    math_string.push(MathChar::Special(MathSpecial::parse(&special).ok_or_else(
314                        || ParseError {
315                            span: StringSpan {
316                                start: special_start + 1,
317                                end: char_count,
318                            },
319                            kind: ParseErrorKind::SpecialNotRecognised(mem::take(&mut special)),
320                        },
321                    )?));
322
323                    // special.clear();
324                    collect_special = false;
325                } else {
326                    ignore_next = true;
327                }
328            } else if ignore_next {
329                math_string.push(MathChar::Ascii(c));
330                ignore_next = false;
331            } else if c == '\\' {
332                ignore_next = true;
333            } else if c == '_' {
334                if indexed {
335                    return Err(ParseError {
336                        span: StringSpan {
337                            start: 0,
338                            end: content.len(),
339                        },
340                        kind: ParseErrorKind::NestedIndex,
341                    });
342                }
343
344                math_string.push(MathChar::SetIndex(MathIndex::Lower));
345                indexed = true;
346            } else if c == '[' {
347                special_start = char_count;
348                collect_special = true;
349            } else if c == ' ' {
350                if indexed && !index_delimited {
351                    indexed = false;
352                    math_string.push(MathChar::SetIndex(MathIndex::Normal));
353                }
354
355                math_string.push(MathChar::Ascii(c));
356            } else if c == '{'
357                && math_string.last().copied() == Some(MathChar::SetIndex(MathIndex::Lower))
358            {
359                index_delimited = true;
360            } else if c == '}' && index_delimited {
361                indexed = false;
362                index_delimited = false;
363                math_string.push(MathChar::SetIndex(MathIndex::Normal));
364            } else if c == '\'' {
365                math_string.push(MathChar::Prime);
366            } else {
367                math_string.push(MathChar::Ascii(c));
368            }
369        }
370
371        if indexed {
372            math_string.push(MathChar::SetIndex(MathIndex::Normal));
373        }
374
375        if collect_special {
376            // Special tag was not closed
377            return Err(ParseError {
378                span: StringSpan {
379                    start: special_start,
380                    end: content.len(),
381                },
382                kind: ParseErrorKind::UnclosedSpecialTag(special),
383            });
384        }
385
386        Ok(Self(math_string))
387    }
388}
389
390impl Serialize for MathString {
391    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
392    where
393        S: Serializer,
394    {
395        let mut s = String::new();
396
397        for c in &self.0 {
398            match c {
399                MathChar::Prime => s.push('c'),
400                MathChar::SetIndex(MathIndex::Normal) => s.push('}'),
401                MathChar::SetIndex(MathIndex::Lower) => s += "_{",
402                MathChar::Ascii(c) => s.extend(['\\', *c]),
403                MathChar::Special(c) => {
404                    s.push('[');
405                    s += SPECIAL_MATH[*c as usize];
406                    s.push(']');
407                }
408            }
409        }
410
411        serializer.serialize_str(&self.to_string())
412    }
413}
414
415impl<'de> Deserialize<'de> for MathString {
416    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
417    where
418        D: Deserializer<'de>,
419    {
420        deserializer.deserialize_str(MathStringVisitor)
421    }
422}
423
424struct MathStringVisitor;
425
426impl Visitor<'_> for MathStringVisitor {
427    type Value = MathString;
428
429    fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
430        formatter.write_str("a valid math string")
431    }
432
433    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
434    where
435        E: Error,
436    {
437        MathString::from_str(v).map_err(|err| E::custom(err))
438    }
439}