expr_solver/
lib.rs

1//! A mathematical expression evaluator library with bytecode compilation.
2//!
3//! This library provides a complete compiler pipeline for mathematical expressions,
4//! from parsing to bytecode execution on a stack-based virtual machine.
5//!
6//! # Features
7//!
8//! - **Type-safe compilation** - Uses Rust's type system to enforce correct pipeline order
9//! - **128-bit decimal precision** - No floating-point errors using `rust_decimal`
10//! - **Rich error messages** - Parse errors with syntax highlighting
11//! - **Bytecode compilation** - Compile once, execute many times
12//! - **Custom symbols** - Add your own constants and functions
13//! - **Serialization** - Save/load compiled programs to/from disk
14//!
15//! # Quick Start
16//!
17//! ```
18//! use expr_solver::eval;
19//!
20//! // Simple evaluation
21//! let result = eval("2 + 3 * 4").unwrap();
22//! assert_eq!(result.to_string(), "14");
23//! ```
24//!
25//! # Custom Symbols
26//!
27//! ```
28//! use expr_solver::{eval_with_table, SymTable};
29//! use rust_decimal_macros::dec;
30//!
31//! let mut table = SymTable::stdlib();
32//! table.add_const("x", dec!(10)).unwrap();
33//!
34//! let result = eval_with_table("x * 2", table).unwrap();
35//! assert_eq!(result, dec!(20));
36//! ```
37//!
38//! # Advanced: Type-State Pattern
39//!
40//! The `Program` type uses the type-state pattern to enforce correct usage:
41//!
42//! ```
43//! use expr_solver::{load, SymTable};
44//! use rust_decimal_macros::dec;
45//!
46//! // Compile expression to bytecode
47//! let program = load("x + y").unwrap();
48//!
49//! // Link with symbol table (validated at link time)
50//! let mut table = SymTable::new();
51//! table.add_const("x", dec!(10)).unwrap();
52//! table.add_const("y", dec!(5)).unwrap();
53//!
54//! let linked = program.link(table).unwrap();
55//!
56//! // Execute
57//! let result = linked.execute().unwrap();
58//! assert_eq!(result, dec!(15));
59//! ```
60//!
61//! # Supported Operators
62//!
63//! - Arithmetic: `+`, `-`, `*`, `/`, `^` (power), `!` (factorial), unary `-`
64//! - Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=` (return 1 or 0)
65//! - Grouping: `(` `)`
66//!
67//! # Built-in Functions
68//!
69//! See [`SymTable::stdlib()`] for the complete list of built-in functions and constants.
70
71// Core types (shared)
72mod ir;
73mod span;
74mod symbol;
75mod token;
76mod vm;
77
78// Expression solver implementation
79mod ast;
80mod error;
81mod lexer;
82mod metadata;
83mod parser;
84mod program;
85
86use rust_decimal::Decimal;
87
88// Public API
89pub use ast::{BinOp, Expr, ExprKind, UnOp};
90pub use error::{LinkError, ParseError, ProgramError};
91pub use metadata::{SymbolKind, SymbolMetadata};
92pub use parser::Parser;
93pub use program::{Compiled, Linked, Program, ProgramOrigin};
94pub use symbol::{SymTable, Symbol, SymbolError};
95pub use vm::{Vm, VmError};
96
97// ============================================================================
98// Helper functions for evaluating expressions
99// ============================================================================
100
101/// Evaluates an expression string with the standard library.
102///
103/// # Examples
104///
105/// ```
106/// use expr_solver::eval;
107///
108/// let result = eval("2 + 3 * 4").unwrap();
109/// assert_eq!(result.to_string(), "14");
110/// ```
111pub fn eval(expression: &str) -> Result<Decimal, String> {
112    let program = load_with_table(expression, SymTable::stdlib())?;
113    program.execute().map_err(|err| err.to_string())
114}
115
116/// Evaluates an expression string with a custom symbol table.
117///
118/// # Examples
119///
120/// ```
121/// use expr_solver::{eval_with_table, SymTable};
122/// use rust_decimal_macros::dec;
123///
124/// let mut table = SymTable::stdlib();
125/// table.add_const("x", dec!(42)).unwrap();
126///
127/// let result = eval_with_table("x * 2", table).unwrap();
128/// assert_eq!(result, dec!(84));
129/// ```
130pub fn eval_with_table(expression: &str, table: SymTable) -> Result<Decimal, String> {
131    let program = load_with_table(expression, table)?;
132    program.execute().map_err(|err| err.to_string())
133}
134
135/// Evaluates an expression from a binary file with the standard library.
136///
137/// # Examples
138///
139/// ```no_run
140/// use expr_solver::eval_file;
141///
142/// let result = eval_file("expr.bin").unwrap();
143/// ```
144pub fn eval_file(path: impl AsRef<str>) -> Result<Decimal, String> {
145    eval_file_with_table(path, SymTable::stdlib())
146}
147
148/// Evaluates an expression from a binary file with a custom symbol table.
149///
150/// # Examples
151///
152/// ```no_run
153/// use expr_solver::{eval_file_with_table, SymTable};
154///
155/// let result = eval_file_with_table("expr.bin", SymTable::stdlib()).unwrap();
156/// ```
157pub fn eval_file_with_table(path: impl AsRef<str>, table: SymTable) -> Result<Decimal, String> {
158    let program = Program::new_from_file(path.as_ref()).map_err(|err| err.to_string())?;
159    let linked = program.link(table).map_err(|err| err.to_string())?;
160    linked.execute().map_err(|err| err.to_string())
161}
162
163/// Loads and compiles an expression, returning a compiled program.
164///
165/// # Examples
166///
167/// ```
168/// use expr_solver::{load, SymTable};
169///
170/// let program = load("2 + 3 * 4").unwrap();
171/// let linked = program.link(SymTable::stdlib()).unwrap();
172/// let result = linked.execute().unwrap();
173/// assert_eq!(result.to_string(), "14");
174/// ```
175pub fn load(expression: &str) -> Result<Program<'_, Compiled>, String> {
176    Program::new_from_source(expression).map_err(|err| err.to_string())
177}
178
179/// Loads, compiles, and links an expression, returning a ready-to-execute program.
180///
181/// # Examples
182///
183/// ```
184/// use expr_solver::{load_with_table, SymTable};
185///
186/// let program = load_with_table("sin(pi/2)", SymTable::stdlib()).unwrap();
187/// let result = program.execute().unwrap();
188/// ```
189pub fn load_with_table(expression: &str, table: SymTable) -> Result<Program<'_, Linked>, String> {
190    let program = load(expression)?;
191    program.link(table).map_err(|err| err.to_string())
192}