mathlex/
lib.rs

1// Allow approx_constant - tests parse "3.14" which should give 3.14, not π
2#![allow(clippy::approx_constant)]
3// Allow large error variants - boxing would be a breaking API change
4#![allow(clippy::result_large_err)]
5// Allow unnecessary cast - swift-bridge macro generates these
6#![allow(clippy::unnecessary_cast)]
7
8//! # mathlex
9//!
10//! A mathematical expression parser for LaTeX and plain text notation,
11//! producing a language-agnostic Abstract Syntax Tree (AST).
12//!
13//! ## Overview
14//!
15//! mathlex is a pure parsing library that converts mathematical expressions
16//! in LaTeX or plain text format into a well-defined AST. The library does
17//! NOT perform any evaluation or mathematical operations - interpretation
18//! of the AST is entirely the responsibility of consuming libraries.
19//!
20//! ## Features
21//!
22//! - **LaTeX Parsing**: Parse mathematical LaTeX notation
23//! - **Plain Text Parsing**: Parse standard mathematical expressions
24//! - **Rich AST**: Comprehensive AST for algebra, calculus, linear algebra
25//! - **Utilities**: Variable extraction, substitution, string conversion
26//!
27//! ## Quick Start
28//!
29//! ```
30//! use mathlex::{parse, parse_latex, Expression, BinaryOp};
31//!
32//! // Parse plain text
33//! let expr = parse("2*x + sin(y)").unwrap();
34//!
35//! // Parse LaTeX
36//! let expr = parse_latex(r"\frac{1}{2}").unwrap();
37//!
38//! // Verify the parsed structure
39//! if let Expression::Binary { op: BinaryOp::Div, .. } = expr {
40//!     println!("Parsed as division");
41//! }
42//! ```
43//!
44//! ## Configuration
45//!
46//! For advanced parsing options, use [`ParserConfig`]:
47//!
48//! ```
49//! use mathlex::{parse_with_config, ParserConfig};
50//!
51//! let config = ParserConfig {
52//!     implicit_multiplication: true,
53//! };
54//!
55//! // Parse with custom configuration (config reserved for future use)
56//! let expr = parse_with_config("2*x + 3", &config).unwrap();
57//! ```
58//!
59//! ## Design Philosophy
60//!
61//! mathlex follows the principle of single responsibility:
62//!
63//! - **Parse** text into AST
64//! - **Convert** AST back to text (plain or LaTeX)
65//! - **Query** AST (find variables, functions)
66//! - **Transform** AST (substitution)
67//!
68//! What mathlex does NOT do:
69//!
70//! - Evaluate expressions
71//! - Simplify expressions
72//! - Solve equations
73//! - Perform any mathematical computation
74//!
75//! This design allows mathlex to serve as a shared foundation for both
76//! symbolic computation systems (like thales) and numerical libraries
77//! (like NumericSwift) without creating dependencies between them.
78
79#![warn(missing_docs)]
80#![warn(clippy::all)]
81
82// Modules will be added as development progresses
83pub mod ast;
84pub mod display;
85pub mod error;
86pub mod latex;
87pub mod parser;
88pub mod util;
89
90#[cfg(feature = "ffi")]
91pub mod ffi;
92
93// Re-export key types at crate root for convenience
94pub use ast::{
95    BinaryOp, Direction, Expression, InequalityOp, IntegralBounds, MathConstant, UnaryOp,
96};
97pub use error::{ParseError, ParseErrorKind, ParseResult, Position, Span};
98pub use latex::ToLatex;
99
100// Re-export parser functions
101pub use parser::{parse, parse_latex};
102
103/// Configuration options for the mathematical expression parser.
104///
105/// This struct controls various parsing behaviors. Currently supports
106/// configuring implicit multiplication handling.
107///
108/// # Examples
109///
110/// ```
111/// use mathlex::ParserConfig;
112///
113/// // Use default configuration
114/// let config = ParserConfig::default();
115/// assert_eq!(config.implicit_multiplication, true);
116///
117/// // Create custom configuration
118/// let config = ParserConfig {
119///     implicit_multiplication: false,
120/// };
121/// ```
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct ParserConfig {
124    /// Enable implicit multiplication (e.g., `2x` means `2*x`).
125    ///
126    /// When `true`, expressions like `2x` or `(a)(b)` are interpreted
127    /// as multiplication. When `false`, such expressions may result in
128    /// parse errors.
129    ///
130    /// Default: `true`
131    pub implicit_multiplication: bool,
132}
133
134impl Default for ParserConfig {
135    fn default() -> Self {
136        Self {
137            implicit_multiplication: true,
138        }
139    }
140}
141
142/// Parses a plain text mathematical expression with custom configuration.
143///
144/// This function allows parsing with custom configuration options.
145///
146/// # Arguments
147///
148/// * `input` - The mathematical expression string to parse
149/// * `config` - Parser configuration options
150///
151/// # Returns
152///
153/// A `ParseResult<Expression>` containing the parsed AST or an error.
154///
155/// # Examples
156///
157/// ```
158/// use mathlex::{parse_with_config, ParserConfig};
159///
160/// let config = ParserConfig::default();
161/// let expr = parse_with_config("sin(x) + 2", &config).unwrap();
162/// ```
163///
164/// ```
165/// use mathlex::{parse_with_config, ParserConfig, Expression, BinaryOp};
166///
167/// let config = ParserConfig {
168///     implicit_multiplication: true,
169/// };
170///
171/// // Parse expression with implicit multiplication
172/// let expr = parse_with_config("2x", &config).unwrap();
173/// match expr {
174///     Expression::Binary { op: BinaryOp::Mul, .. } => println!("Parsed as multiplication"),
175///     _ => panic!("Unexpected expression type"),
176/// }
177/// ```
178pub fn parse_with_config(input: &str, config: &ParserConfig) -> ParseResult<Expression> {
179    parser::parse_with_config(input, config)
180}
181
182/// Placeholder for library version
183pub const VERSION: &str = env!("CARGO_PKG_VERSION");
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_version() {
191        assert_eq!(VERSION, "0.1.0");
192    }
193
194    #[test]
195    fn test_parser_config_default() {
196        let config = ParserConfig::default();
197        assert!(config.implicit_multiplication);
198    }
199
200    #[test]
201    fn test_parser_config_custom() {
202        let config = ParserConfig {
203            implicit_multiplication: false,
204        };
205        assert!(!config.implicit_multiplication);
206    }
207
208    #[test]
209    fn test_parser_config_equality() {
210        let config1 = ParserConfig::default();
211        let config2 = ParserConfig {
212            implicit_multiplication: true,
213        };
214        let config3 = ParserConfig {
215            implicit_multiplication: false,
216        };
217
218        assert_eq!(config1, config2);
219        assert_ne!(config1, config3);
220    }
221
222    #[test]
223    fn test_parser_config_clone() {
224        let config = ParserConfig::default();
225        let cloned = config;
226        assert_eq!(config, cloned);
227    }
228
229    #[test]
230    fn test_parse_simple() {
231        let expr = parse("2 + 3").unwrap();
232        assert!(matches!(
233            expr,
234            Expression::Binary {
235                op: BinaryOp::Add,
236                ..
237            }
238        ));
239    }
240
241    #[test]
242    fn test_parse_latex_simple() {
243        let expr = parse_latex(r"\frac{1}{2}").unwrap();
244        assert!(matches!(
245            expr,
246            Expression::Binary {
247                op: BinaryOp::Div,
248                ..
249            }
250        ));
251    }
252
253    #[test]
254    fn test_parse_with_config_default() {
255        let config = ParserConfig::default();
256        let expr = parse_with_config("sin(x) + 2", &config).unwrap();
257        assert!(matches!(
258            expr,
259            Expression::Binary {
260                op: BinaryOp::Add,
261                ..
262            }
263        ));
264    }
265
266    #[test]
267    fn test_parse_with_config_custom() {
268        let config = ParserConfig {
269            implicit_multiplication: false,
270        };
271        let expr = parse_with_config("2 + 3", &config).unwrap();
272        assert!(matches!(
273            expr,
274            Expression::Binary {
275                op: BinaryOp::Add,
276                ..
277            }
278        ));
279    }
280
281    #[test]
282    fn test_all_type_exports() {
283        // Ensure all required types are exported
284        let _expr: Expression = Expression::Integer(42);
285        let _op: BinaryOp = BinaryOp::Add;
286        let _unary: UnaryOp = UnaryOp::Neg;
287        let _const: MathConstant = MathConstant::Pi;
288        let _dir: Direction = Direction::Both;
289        let _ineq: InequalityOp = InequalityOp::Lt;
290        let _bounds: IntegralBounds = IntegralBounds {
291            lower: Box::new(Expression::Integer(0)),
292            upper: Box::new(Expression::Integer(1)),
293        };
294        let _pos: Position = Position::start();
295        let _span: Span = Span::start();
296        let _err: ParseError = ParseError::empty_expression(None);
297    }
298}