chrisgve/mathlex
A mathematical expression parser for LaTeX and plain text notation, producing a language-agnostic Abstract Syntax Tree (AST).
Features
- LaTeX Parsing - Parse mathematical LaTeX notation (
\frac{1}{2},\int_0^1,\sum_{i=1}^n) - Plain Text Parsing - Parse standard math notation (
2*x + 3,sin(x)) - Rich AST - Comprehensive AST supporting:
- Algebra and calculus expressions - Linear algebra (vectors and matrices) - Vector calculus (gradient, divergence, curl, Laplacian) - Set theory (unions, intersections, quantifiers) - Logic (connectives, quantifiers) - Multiple integrals (double, triple, closed path) - Quaternion algebra
- Vector Notation - Multiple styles: bold (
\mathbf{v}), arrow (\vec{v}), hat (\hat{n}) - Context-Aware - Smart handling of
e,i,j,kbased on number system context - No Evaluation - Pure parsing library, interpretation is client responsibility
- Cross-Platform - Native support for Rust and Swift (iOS/macOS)
Installation
Rust
Add to your Cargo.toml:
[dependencies]
mathlex = "0.4.0"Swift
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/ChrisGVE/mathlex.git", from: "0.4.0")
]Or in Xcode: File → Add Package Dependencies → Enter https://github.com/ChrisGVE/mathlex.git
Quick Start
Rust
Parse Plain Text
use mathlex::{parse, Expression};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let expr = parse("2*x + sin(y)")?;
// Find all variables
let vars = expr.find_variables();
println!("Variables: {:?}", vars); // {"x", "y"}
// Convert back to string
println!("{}", expr); // "2 * x + sin(y)"
Ok(())
}Parse LaTeX
use mathlex::{parse_latex, Expression};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let expr = parse_latex(r"\frac{1}{2} + \sqrt{x}")?;
// Convert to LaTeX string
println!("{}", expr.to_latex()); // "\frac{1}{2} + \sqrt{x}"
Ok(())
}Swift
import MathLex
do {
// Parse plain text
let expr = try MathExpression.parse("2*x + sin(y)")
print(expr.variables) // ["x", "y"]
print(expr.description) // "2 * x + sin(y)"
// Parse LaTeX
let latex = try MathExpression.parseLatex(#"\frac{1}{2}"#)
print(latex.latex) // "\frac{1}{2}"
} catch {
print("Parse error: \(error)")
}Supported Notation
Literals
- Integers:
42,-17 - Floats:
3.14,2.5e-3 - Rationals: LaTeX
\frac{1}{2} - Complex: via construction
- Quaternions: via construction with basis vectors
i,j,k
Symbols
- Variables:
x,y,theta - Greek letters:
\alpha,\beta,\Gamma - Constants:
\pi(π),e,\infty(∞) - Imaginary/quaternion units:
i,j,k(context-aware)
Operations
- Binary:
+,-,,/,^,*,%,\pm,\mp - Unary:
-x,x! - Functions:
sin,cos,tan,log,ln,exp,sqrt,abs,floor,ceil,det - Vector products:
\cdot(dot),\times(cross)
Calculus (Representation Only)
- Derivatives (LaTeX):
- Standard notation: \frac{d}{dx}f, \frac{\partial}{\partial x}f - Higher order: \frac{d^2}{dx^2}f, \frac{\partial^2}{\partial x^2}f
- Derivatives (plain text):
- Leibniz: dy/dx, d2y/dx2, d3y/dx3 - Prime: y', y'', y''' - Functional: diff(y, x), diff(y, x, 2)
- Partial derivatives (plain text):
- partial(f, x), partial(f, x, 2) (second order) - partial(f, x, y) (mixed partial ∂²f/∂x∂y)
- Integrals:
\int,\int_a^b - Multiple integrals:
\iint(double),\iiint(triple) - Closed integrals:
\oint(line),\oiint(surface) - Limits:
\lim_{x \to a} - Sums:
\sum_{i=1}^{n} - Products:
\prod_{i=1}^{n} - Subscripts: Supports expression subscripts like
x_{i+1},a_{n-1}(flattened to variable names)
Vector Calculus
- Gradient:
\nabla f(LaTeX),grad(f),nabla(f),∇f(plain text) - Divergence:
\nabla \cdot \mathbf{F}(LaTeX),div(f)(plain text) - Curl:
\nabla \times \mathbf{F}(LaTeX),curl(f)(plain text) - Laplacian:
\nabla^2 f(LaTeX),laplacian(f)(plain text) - Vector notation styles:
- Bold: \mathbf{v} - Arrow: \vec{v} - Hat (unit vectors): \hat{n}
Set Theory
- Operations:
\cup(union),\cap(intersection),\setminus(difference) - Relations:
\in,\notin,\subset,\subseteq,\supset,\supseteq - Special sets:
\emptysetor\varnothing(empty set) - Number sets:
\mathbb{N},\mathbb{Z},\mathbb{Q},\mathbb{R},\mathbb{C},\mathbb{H}(quaternions) - Power set:
\mathcal{P}(A) - Quantifiers:
\forall(for all),\exists(there exists)
Logic
- Connectives:
\land(and),\lor(or),\lnot(not) - Implications:
\implies,\iff(if and only if)
Structures
- Vectors:
\begin{pmatrix} a \\ b \end{pmatrix} - Matrices:
\begin{bmatrix} a & b \\ c & d \end{bmatrix} - Equations:
x = y - Inequalities:
x < y,\leq,\geq,\neq
Advanced Examples
Vector Calculus
use mathlex::parse_latex;
// Maxwell's equations components
let gauss = parse_latex(r"\nabla \cdot \mathbf{E}").unwrap();
let faraday = parse_latex(r"\nabla \times \mathbf{E}").unwrap();
// Laplacian operator
let poisson = parse_latex(r"\nabla^2 \phi").unwrap();
// Vector identities
let div_curl = parse_latex(r"\nabla \cdot (\nabla \times \mathbf{F})").unwrap();
let curl_grad = parse_latex(r"\nabla \times (\nabla f)").unwrap();Multiple Integrals
use mathlex::parse_latex;
// Surface area integral
let surface = parse_latex(r"\iint_R f(x,y) dA").unwrap();
// Volume integral
let volume = parse_latex(r"\iiint_V \rho dV").unwrap();
// Closed line integral (circulation)
let circulation = parse_latex(r"\oint_C \mathbf{F} \cdot d\mathbf{r}").unwrap();Set Theory and Logic
use mathlex::parse_latex;
// Set operations
let union = parse_latex(r"A \cup B \cap C").unwrap();
let membership = parse_latex(r"x \in A \cup B").unwrap();
// Logical statements
let forall = parse_latex(r"\forall x \in \mathbb{R} (x^2 \geq 0)").unwrap();
let exists = parse_latex(r"\exists x (P(x) \land Q(x))").unwrap();
let implication = parse_latex(r"P \implies Q").unwrap();Quaternions
use mathlex::{Expression, ExprKind};
// Quaternion basis vectors satisfy:
// i² = j² = k² = ijk = -1
// ij = k, jk = i, ki = j
// ji = -k, kj = -i, ik = -j
let quaternion: Expression = ExprKind::Quaternion {
real: Box::new(Expression::integer(1)),
i: Box::new(Expression::integer(2)),
j: Box::new(Expression::integer(3)),
k: Box::new(Expression::integer(4)),
}.into();Context-Aware Parsing
use mathlex::{parse_latex, Expression, MathConstant};
// 'e' is parsed as Euler's constant
let euler = parse_latex(r"e^x").unwrap();
// 'i' requires explicit marking to be treated as imaginary unit
let complex = parse_latex(r"3 + 4\mathrm{i}").unwrap();
// Quaternion basis vectors (when marked)
let quat = parse_latex(r"\mathbf{i} + \mathbf{j} + \mathbf{k}").unwrap();Integration with Evaluation Libraries
mathlex does not evaluate expressions, but it produces a well-defined AST that downstream libraries can consume. For Swift consumers such as numerical or CAS frameworks, the AST is accessible as JSON via the toJSON() and toJSONPretty() methods on MathExpression:
import MathLex
let expr = try MathExpression.parse("sin(x)^2 + cos(x)^2")
let json = try expr.toJSON()
// Decode json into your own Decodable types and evaluateThe serde feature must be enabled on the Rust side (it is included in the default XCFramework build).
- Wire format reference:
docs/WIRE-FORMAT.md— complete reference for every node type and its JSON representation - Integration example:
examples/numericswift-integration/— SwiftDecodabletypes and a numeric evaluator that consumes the JSON AST
Design Philosophy
mathlex is a pure parsing library. It converts text to AST and back - nothing more.
- No evaluation - mathlex does not compute values
- No simplification - mathlex does not transform expressions
- No dependencies on consumers - can be used by any library
This design allows different libraries to interpret the AST according to their capabilities:
- A CAS library can perform symbolic differentiation on
Derivativenodes - A numerical library can evaluate
Functionnodes numerically - An educational tool can render step-by-step explanations
Serialization
Enable the serde feature to serialize and deserialize the Expression AST as JSON:
[dependencies]
mathlex = { version = "0.4.0", features = ["serde"] }use mathlex::parse;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let expr = parse("sin(x)^2 + cos(x)^2")?;
let json = serde_json::to_string_pretty(&expr)?;
println!("{}", json);
// {
// "kind": "Binary",
// "value": {
// "op": { "kind": "Add" },
// "left": { "kind": "Binary", "value": { ... } },
// "right": { "kind": "Binary", "value": { ... } }
// }
// }
let roundtrip: mathlex::Expression = serde_json::from_str(&json)?;
assert_eq!(expr, roundtrip);
Ok(())
}Every node serializes as { "kind": "<VariantName>", "value": <payload> }. Each node also carries an AnnotationSet — a string-to-string metadata map used by downstream consumers such as thales. The annotations field is omitted when empty, which is always the case for nodes produced directly by the parsers.
See docs/WIRE-FORMAT.md for the full schema with one JSON example per variant.
Optional Features
Rust
[dependencies]
mathlex = { version = "0.4.0", features = ["serde"] }serde- Enable serialization/deserialization of AST typesffi- Enable Swift FFI bindings (for building XCFramework)
Documentation
- Rust: docs.rs/mathlex
- Swift: Swift Package Index
License
Apache License 2.0 - see LICENSE for details.
Links
- Crates.io: crates.io/crates/mathlex
- Swift Package Index: swiftpackageindex.com/ChrisGVE/mathlex
- Documentation: docs.rs/mathlex
- Repository: github.com/ChrisGVE/mathlex
- Issues: Report bugs
Package Metadata
Repository: chrisgve/mathlex
Default branch: main
README: README.md