Skip to main content

asciimath_parser/
lib.rs

1//! A fast extensible memory-efficient asciimath parser
2//!
3//! This parser produces a parsed tree representation rooted as an
4//! [`Expression`][tree::Expression]. The parsed structure keeps references to the underlying string
5//! in order to avoid copies, but these strings must still be interpreted as the correct tokens to
6//! use the structure.
7//!
8//! ## Usage
9//!
10//! ```sh
11//! cargo add asciimath-parser
12//! ```
13//!
14//! then
15//!
16//! ```
17//! asciimath_parser::parse("x / y");
18//! ```
19//!
20//! ### Performance
21//!
22//! This library is meant to be a fast extensible parser. Most other rust libraries parse and
23//! format, or parse and evaluate, but don't expose their underlying parsing logic. This parser
24//! produces a relatively simple parse tree whose tokens are slices of the input, so it does no
25//! extra string allocation. The `parse` bench measures parsing a corpus of hand-written examples
26//! and a batch of random expressions:
27//!
28//! ```txt
29//! test example ... bench:       2,232 ns/iter (+/- 115)
30//! test random  ... bench:     113,775 ns/iter (+/- 6,033)
31//! ```
32//!
33//! ## Dialect
34//!
35//! Asciimath is a loose standard that aims for fault-tolerant parsing while looking close to what
36//! you might type in ascii if you were trying. However, other than the [current buggy
37//! implementation](https://github.com/asciimath/asciimathml/blob/master/ASCIIMathML.js), there's
38//! no parse standard.
39//!
40//! The parsing is written manually, so it doesn't quite conform to this grammar, (which is also
41//! very ambiguous), but this grammar is close to the way asciimath actually interprets strings. In
42//! asciimath, left-right brackets have the highest precedence and almost any argument can be
43//! [missing][tree::Simple::Missing], save the first.
44//!
45//! ```txt
46//! v ::= any char | greek letters | numbers | ... | missing
47//! u ::= sqrt | text | bb | ...               unary symbols for font commands
48//! f ::= sin | cos | ...                      function symbols
49//! b ::= frac | root | stackrel | ...         binary symbols
50//! l ::= ( | [ | { | (: | {: | ...            left brackets
51//! r ::= ) | ] | } | :) | :} | ...            right brackets
52//! d ::= '|' | '||'                           left-right brackets
53//! R ::= E | E,R                              Matrix row expression
54//! M ::= lRr | lRr,M                          Matrix expression
55//! S ::= v | lEr | uS | fS | bSS | dEd | lMr  Simple expression
56//! P ::= _S | ^S | _S^S                       Power expression
57//! I ::= fP?I | SP?                           Intermediate expression
58//! E ::= IE | I/I                             Expression
59//! ```
60//!
61//! Left-right brackets are closed greedily, and must match the same string on both sides. If they
62//! can't be matched they'll be parsed as a symbol. This is particularly useful for probability
63//! conditioning, e.g. "p(x|y)". For matrices, all left brackets must match, all right brackets
64//! must match, the number of separators (,) in each row must match, and there needs to be more
65//! than one element. This is more narrow than asciimath, but prevents need to have hardcoded rules
66//! for the difference between a set and a matrix.
67//!
68//! This dialect results in many ways to parse things that conceptually might have the same
69//! meaning. `"raw test"` and `text(raw text)` might seem to have the same meaning, but the first
70//! is actually parsed as raw text, and the second is parsed as a unary function "text" with an
71//! argument. Similarly `1 / 2` and `frac 1 2` both represent the same thing, but the first is a
72//! high level [`Frac`][tree::Frac] construct, while the later is a binary operator called "frac".
73//!
74//! ### Differences with Asciimath
75//!
76//! Asciimaths parsing of left-right brackets is confusing, in particular the default way they
77//! handle expressions like ||x||. This library tokenizes "||" as one token and tries to match it
78//! that way, which produces different results than asciimath. Additionally, asciimath will
79//! sometimes put a phantom empty open brace if an expression ends on a "|". This proved difficult
80//! to support and seems like an unuseful edgecase as it could always be substituted with
81//! "{: ...  :|".
82//!
83//! ### Extensions to Asciimath
84//!
85//! This parser is meant to be extensible, so if there are parts that don't function as desired,
86//! they can be tweaked.
87//!
88//! 1. [`parse`][crate::parse()] uses the default tokenizer, but
89//!    [`parse_tokens`] can be used to parse an iterator of tuples `(&str,
90//!    Token)` for any custom tokenization you write.
91//! 2. Custom tokenizer options can be used by creating an alternate [`Tokenizer`] using
92//!    [`with_tokens`][Tokenizer::with_tokens].
93//!    ```
94//!    use asciimath_parser::{parse_tokens, Tokenizer, ASCIIMATH_TOKENS};
95//!    use asciimath_parser::prefix_map::HashPrefixMap;
96//!
97//!    let token_map = HashPrefixMap::from_iter(ASCIIMATH_TOKENS);
98//!    let parsed = parse_tokens(Tokenizer::with_tokens("...", &token_map, false));
99//!    ```
100//! 3. Nonstandard tokens can be used instead by creating custom token maps:
101//!    ```
102//!    use asciimath_parser::{parse_tokens, Tokenizer, Token};
103//!    use asciimath_parser::prefix_map::HashPrefixMap;
104//!
105//!    let token_map = HashPrefixMap::from_iter([
106//!        ("@", Token::Symbol),
107//!        // ...
108//!    ]);
109//!    let parsed = parse_tokens(Tokenizer::with_tokens("...", &token_map, true));
110//!    ```
111//!
112//! ## Design
113//!
114//! This parser tries to balance a few different goals which mediate it's design:
115//! 1. simple - The "standard" asciimath parser is complicated, makes several passes, is relatively
116//!    difficult to tweak or modify, is error-prone, and produces somewhat inconsistent results. By
117//!    making this parser as simple as possible all of those should be relatively easy.
118//! 2. extensible - Asciimath isn't a standard and there's a lot about it that you might want to
119//!    change, or add to suit a particular usecase.
120//! 3. efficient - Fast and with as little memory as possible. Because the asciimath parse trees
121//!    are trees, some heap allocation is necessary to store the recursive structure.
122//!
123//! As a result, this parser produces a parsed representation, but doesn't attach any meanings to
124//! the tokens in the parsed tree. The default parser treats both "*" and "cdot" as tokens, but
125//! doesn't say anywhere that they should be rendered the same. This choice was made so that you
126//! could easily add or remove tokens, or even change their meaning, and this library doesn't have
127//! to know.
128//!
129//! If you want to consume this output and make sure the tokens are parsed correctly, you can use
130//! the exported const version of the tokens uses to parse. By default [`parse`][crate::parse()]
131//! uses [`crate::ASCIIMATH_TOKENS`]
132//!
133//! ## Tree Structure
134//!
135//! The parsed representation is a tree like structure that has a hierarchy of types that roughly
136//! follows [`Expression`][tree::Expression] -> [`Intermediate`][tree::Intermediate] ->
137//! [`Frac`][tree::Frac] -> [`ScriptFunc`][tree::ScriptFunc] ->
138//! [`SimpleScript`][tree::SimpleScript] / [`Func`][tree::Func] -> [`Simple`][tree::Simple]. The
139//! exceptions to this hierarchy are [`Group`][tree::Group] and [`Matrix`][tree::Matrix] that are
140//! both "simple" structures, but contain nested expressions. All of these types implement `From`
141//! from their singleton children, allowing promoting simple types to more complex ones with
142//! minimal overhead. All of their members are public allowing destructuring, especially with the
143//! `box_patterns` feature. See [`tree`] for more details.
144//!
145//! ```
146//! use asciimath_parser::tree::{Expression, Simple};
147//!
148//! let expr = Expression::from_iter([Simple::Ident("x")]);
149//! ```
150//!
151//! ### Manual creation
152//!
153//! Most the tree structures implement [From] any of their singular upstream components, and most
154//! constructors support anything implementing [Into], meaning that you only need only need to
155//! construct the lowest level argument, and it will get upcast to a higher tree structure as you
156//! need it.
157//!
158//! For example:
159//! ```
160//! # use asciimath_parser::tree::{Expression, Simple};
161//! let expr = Expression::from_iter([Simple::Ident("x")]);
162//! ```
163#![forbid(unsafe_code)]
164#![warn(clippy::pedantic, missing_docs)]
165
166mod parse;
167pub mod prefix_map;
168mod tokenizer;
169pub mod tree;
170
171pub use parse::{parse, parse_tokens};
172pub use tokenizer::{ASCIIMATH_TOKENS, Token, Tokenizer};