/// MathLex - Swift wrapper for the mathlex mathematical expression parser
///
/// This module provides a Swift-friendly API for parsing mathematical expressions
/// in LaTeX and plain text formats. It wraps the Rust-based mathlex library.
///
/// ## Overview
///
/// MathLex is a pure parsing library that converts mathematical notation into
/// an Abstract Syntax Tree (AST). It does NOT evaluate expressions or perform
/// any mathematical computations.
///
/// ## Quick Start
///
/// ```swift
/// import MathLex
///
/// // Parse plain text expression
/// let expr = try MathExpression.parse("2*x + sin(y)")
///
/// // Parse LaTeX expression
/// let latexExpr = try MathExpression.parseLatex(#"\frac{1}{2}"#)
///
/// // Get variables used in expression
/// let variables = expr.variables
///
/// // Convert to LaTeX
/// let latex = expr.latex
/// ```
import Foundation
import MathLexRust
// Disambiguate from Foundation.Expression (macOS 15+)
private typealias RustExpression = MathLexRust.Expression
/// Errors that can occur during mathematical expression parsing
public enum MathLexError: Error {
/// A parsing error occurred with the given message
case parseError(String)
/// An internal error occurred
case internalError(String)
}
extension MathLexError: LocalizedError {
public var errorDescription: String? {
switch self {
case .parseError(let message):
return "Parse error: \(message)"
case .internalError(let message):
return "Internal error: \(message)"
}
}
}
/// A mathematical expression represented as an Abstract Syntax Tree (AST)
///
/// This type provides a Swift-friendly wrapper around the Rust-based mathlex parser.
/// It supports parsing both plain text and LaTeX mathematical notation.
///
/// ## Parsing
///
/// ```swift
/// // Parse plain text
/// let expr1 = try MathExpression.parse("x^2 + 2*x + 1")
///
/// // Parse LaTeX
/// let expr2 = try MathExpression.parseLatex(#"x^2 + 2x + 1"#)
/// ```
///
/// ## Querying
///
/// ```swift
/// let expr = try MathExpression.parse("sin(x) + cos(y)")
///
/// // Get all variables
/// let vars = expr.variables // {"x", "y"}
///
/// // Get all functions
/// let funcs = expr.functions // {"sin", "cos"}
///
/// // Get tree depth
/// let depth = expr.depth // Tree depth
///
/// // Get node count
/// let count = expr.nodeCount // Total nodes
/// ```
///
/// ## Conversion
///
/// ```swift
/// let expr = try MathExpression.parse("1/2")
///
/// // Convert to string (plain text)
/// let text = expr.description // "1 / 2"
///
/// // Convert to LaTeX
/// let latex = expr.latex // "\\frac{1}{2}"
/// ```
public struct MathExpression {
// The underlying Rust expression
private let inner: RustExpression
/// Internal initializer from Rust expression
private init(inner: RustExpression) {
self.inner = inner
}
// MARK: - Parsing
/// Parses a plain text mathematical expression
///
/// This method parses standard mathematical notation using common operators
/// and function syntax.
///
/// ## Supported Notation
///
/// - Binary operators: `+`, `-`, `*`, `/`, `^`, `%`
/// - Functions: `sin(x)`, `cos(x)`, `log(x)`, etc.
/// - Variables: `x`, `y`, `theta`, etc.
/// - Constants: `pi`, `e`, `i`
/// - Parentheses for grouping
///
/// ## Examples
///
/// ```swift
/// let simple = try MathExpression.parse("2 + 3")
/// let complex = try MathExpression.parse("sin(x)^2 + cos(x)^2")
/// let calculus = try MathExpression.parse("d/dx(x^2)")
/// ```
///
/// - Parameter input: The mathematical expression string to parse
/// - Returns: A parsed `MathExpression`
/// - Throws: `MathLexError.parseError` if the input is invalid
public static func parse(_ input: String) throws -> MathExpression {
do {
let expr = try MathLexRust.parseText(input)
return MathExpression(inner: expr)
} catch let error as RustString {
throw MathLexError.parseError(error.toString())
}
}
/// Parses a LaTeX mathematical expression
///
/// This method parses LaTeX mathematical notation, supporting common
/// LaTeX commands for mathematical expressions.
///
/// ## Supported LaTeX Commands
///
/// - Fractions: `\frac{a}{b}`
/// - Powers: `x^2` or `x^{2n}`
/// - Roots: `\sqrt{x}`, `\sqrt[n]{x}`
/// - Functions: `\sin{x}`, `\cos{x}`, etc.
/// - Greek letters: `\pi`, `\theta`, `\alpha`, etc.
/// - Derivatives: `\frac{d}{dx}`, `\frac{\partial}{\partial x}`
/// - Integrals: `\int`, `\int_a^b`
/// - Summations: `\sum_{i=1}^{n}`
/// - Matrices: `\begin{pmatrix}...\end{pmatrix}`
///
/// ## Examples
///
/// ```swift
/// let frac = try MathExpression.parseLatex(#"\frac{1}{2}"#)
/// let integral = try MathExpression.parseLatex(#"\int_0^1 x^2 dx"#)
/// let sum = try MathExpression.parseLatex(#"\sum_{i=1}^{n} i"#)
/// ```
///
/// - Parameter input: The LaTeX expression string to parse
/// - Returns: A parsed `MathExpression`
/// - Throws: `MathLexError.parseError` if the input is invalid
public static func parseLatex(_ input: String) throws -> MathExpression {
do {
let expr = try MathLexRust.parseLatex(input)
return MathExpression(inner: expr)
} catch let error as RustString {
throw MathLexError.parseError(error.toString())
}
}
// MARK: - Conversion
/// A string representation of the expression in plain text format
///
/// This converts the AST back to a human-readable mathematical expression
/// using standard notation.
///
/// ## Example
///
/// ```swift
/// let expr = try MathExpression.parseLatex(#"\frac{1}{2}"#)
/// print(expr.description) // "1 / 2"
/// ```
public var description: String {
MathLexRust.toString(inner).toString()
}
/// A LaTeX representation of the expression
///
/// This converts the AST to LaTeX notation, suitable for rendering
/// with LaTeX engines or display in documentation.
///
/// ## Example
///
/// ```swift
/// let expr = try MathExpression.parse("1/2")
/// print(expr.latex) // "\\frac{1}{2}"
/// ```
public var latex: String {
MathLexRust.toLatex(inner).toString()
}
/// A compact JSON representation of the AST.
///
/// Serializes the full Abstract Syntax Tree to a single-line JSON string.
/// This is the recommended format for passing the AST across language boundaries
/// for evaluation or further processing.
///
/// ## Example
///
/// ```swift
/// let expr = try MathExpression.parse("x + 1")
/// let json = try expr.toJSON()
/// // {"Binary":{"op":"Add","left":{"Variable":"x"},"right":{"Integer":1}}}
/// ```
///
/// - Returns: A compact JSON string representing the AST
/// - Throws: `MathLexError.internalError` if serialization fails
public func toJSON() throws -> String {
do {
return try MathLexRust.toJSON(inner).toString()
} catch let error as RustString {
throw MathLexError.internalError(error.toString())
}
}
/// A pretty-printed JSON representation of the AST.
///
/// Serializes the full Abstract Syntax Tree to an indented, multi-line JSON
/// string. Useful for debugging and human inspection of the parsed structure.
///
/// ## Example
///
/// ```swift
/// let expr = try MathExpression.parse("x + 1")
/// let json = try expr.toJSONPretty()
/// // {
/// // "Binary": {
/// // "op": "Add",
/// // ...
/// // }
/// // }
/// ```
///
/// - Returns: An indented JSON string representing the AST
/// - Throws: `MathLexError.internalError` if serialization fails
public func toJSONPretty() throws -> String {
do {
return try MathLexRust.toJSONPretty(inner).toString()
} catch let error as RustString {
throw MathLexError.internalError(error.toString())
}
}
// MARK: - Querying
/// All unique variable names used in the expression
///
/// Returns a set of all variable identifiers found in the expression,
/// including index variables from summations and integration variables.
///
/// ## Example
///
/// ```swift
/// let expr = try MathExpression.parse("x + y + x")
/// print(expr.variables) // {"x", "y"}
/// ```
public var variables: Set<String> {
let rustVec = MathLexRust.findVariables(inner)
var result = Set<String>()
for i in 0..<rustVec.len() {
if let rustStr = rustVec.get(index: UInt(i)) {
result.insert(rustStr.as_str().toString())
}
}
return result
}
/// All unique function names used in the expression
///
/// Returns a set of all function identifiers found in the expression.
///
/// ## Example
///
/// ```swift
/// let expr = try MathExpression.parse("sin(x) + cos(y)")
/// print(expr.functions) // {"sin", "cos"}
/// ```
public var functions: Set<String> {
let rustVec = MathLexRust.findFunctions(inner)
var result = Set<String>()
for i in 0..<rustVec.len() {
if let rustStr = rustVec.get(index: UInt(i)) {
result.insert(rustStr.as_str().toString())
}
}
return result
}
/// All mathematical constants used in the expression
///
/// Returns a set of constant identifiers (π, e, i, ∞) found in the expression.
///
/// ## Example
///
/// ```swift
/// let expr = try MathExpression.parse("2*pi + e")
/// print(expr.constants) // {"pi", "e"}
/// ```
public var constants: Set<String> {
let rustVec = MathLexRust.findConstants(inner)
var result = Set<String>()
for i in 0..<rustVec.len() {
if let rustStr = rustVec.get(index: UInt(i)) {
result.insert(rustStr.as_str().toString())
}
}
return result
}
/// The maximum depth of the expression tree
///
/// This measures the longest path from the root to any leaf node.
/// Leaf nodes (literals, variables, constants) have depth 1.
///
/// ## Example
///
/// ```swift
/// let simple = try MathExpression.parse("x")
/// print(simple.depth) // 1
///
/// let nested = try MathExpression.parse("(x + y) * z")
/// print(nested.depth) // 3
/// ```
public var depth: Int {
Int(MathLexRust.depth(inner))
}
/// The total number of nodes in the expression tree
///
/// This counts all AST nodes, providing a measure of expression complexity.
///
/// ## Example
///
/// ```swift
/// let expr = try MathExpression.parse("x + y")
/// print(expr.nodeCount) // 3 (Add + x + y)
/// ```
public var nodeCount: Int {
Int(MathLexRust.nodeCount(inner))
}
}
// MARK: - CustomStringConvertible
extension MathExpression: CustomStringConvertible {
// Uses the description property defined above
}
// MARK: - Equatable
extension MathExpression: Equatable {
public static func == (lhs: MathExpression, rhs: MathExpression) -> Bool {
// Compare based on string representation
// This ensures structural equality of the AST
lhs.description == rhs.description
}
}
// MARK: - Hashable
extension MathExpression: Hashable {
public func hash(into hasher: inout Hasher) {
// Hash based on string representation for consistency with Equatable
hasher.combine(description)
}
}