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}