expr_solver/
lib.rs

1//! A simple expression solver library
2//!
3//! Parses and evaluates mathematical expressions with built-in functions and constants.
4//!
5//! # Features
6//!
7//! - Mathematical operators: `+`, `-`, `*`, `/`, `^`, unary `-`, `!` (factorial)
8//! - Comparison operators: `==`, `!=`, `<`, `<=`, `>`, `>=` (return 1.0 or 0.0)
9//! - Built-in constants: `pi`, `e`, `tau`, `ln2`, `ln10`, `sqrt2`
10//! - Basic math functions: `abs`, `floor`, `ceil`, `round`, `trunc`, `fract`
11//! - Variadic functions: `min`, `max`, `sum`, `avg`
12//! - 128-bit decimal arithmetic (no floating-point representation errors!)
13//! - Error handling with source location information
14
15mod ast;
16mod ir;
17mod lexer;
18mod parser;
19mod program;
20
21mod sema;
22mod source;
23mod span;
24mod symbol;
25mod token;
26mod vm;
27
28use std::{borrow::Cow, fmt, fs, path::PathBuf};
29
30// Public API
31pub use ir::IrBuilder;
32pub use parser::Parser;
33pub use program::Program;
34
35use crate::ast::Expr;
36use crate::span::SpanError;
37use rust_decimal::Decimal;
38pub use sema::Sema;
39pub use source::Source;
40pub use symbol::{SymTable, Symbol, SymbolError};
41pub use vm::{Vm, VmError};
42
43/// A wrapper that formats errors with source code highlighting
44struct FormattedError {
45    message: String,
46}
47
48impl fmt::Display for FormattedError {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(f, "{}", self.message)
51    }
52}
53
54impl<T: SpanError> From<(&T, &Source<'_>)> for FormattedError {
55    fn from((error, source): (&T, &Source<'_>)) -> Self {
56        Self {
57            message: format!("{}\n{}", error, source.highlight(&error.span())),
58        }
59    }
60}
61
62#[derive(Debug)]
63enum EvalSource<'str> {
64    Source(Cow<'str, Source<'str>>),
65    File(PathBuf),
66}
67
68/// Expression evaluator with support for custom symbols and bytecode compilation.
69///
70/// `Eval` is the main entry point for evaluating mathematical expressions. It supports
71/// both quick one-off evaluations and reusable evaluators with custom symbol tables.
72///
73/// # Examples
74///
75/// ```
76/// use expr_solver::Eval;
77///
78/// // Quick evaluation
79/// let result = Eval::evaluate("2 + 3 * 4").unwrap();
80/// assert_eq!(result.to_string(), "14");
81///
82/// // Reusable evaluator
83/// let mut eval = Eval::new("sqrt(16) + pi");
84/// let result = eval.run().unwrap();
85/// ```
86#[derive(Debug)]
87pub struct Eval<'str> {
88    source: EvalSource<'str>,
89    table: SymTable,
90}
91
92impl<'str> Eval<'str> {
93    /// Quick evaluation of an expression with the standard library.
94    ///
95    /// This is a convenience method for one-off evaluations.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use expr_solver::Eval;
101    ///
102    /// let result = Eval::evaluate("2^8").unwrap();
103    /// assert_eq!(result.to_string(), "256");
104    /// ```
105    pub fn evaluate(expression: &'str str) -> Result<Decimal, String> {
106        Self::new(expression).run()
107    }
108
109    /// Quick evaluation of an expression with a custom symbol table.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use expr_solver::{Eval, SymTable};
115    /// use rust_decimal_macros::dec;
116    ///
117    /// let mut table = SymTable::stdlib();
118    /// table.add_const("x", dec!(42)).unwrap();
119    ///
120    /// let result = Eval::evaluate_with_table("x * 2", table).unwrap();
121    /// assert_eq!(result, dec!(84));
122    /// ```
123    pub fn evaluate_with_table(expression: &'str str, table: SymTable) -> Result<Decimal, String> {
124        Self::with_table(expression, table).run()
125    }
126
127    /// Creates a new evaluator with the standard library.
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// use expr_solver::Eval;
133    ///
134    /// let mut eval = Eval::new("sin(pi/2)");
135    /// let result = eval.run().unwrap();
136    /// ```
137    pub fn new(string: &'str str) -> Self {
138        Self::with_table(string, SymTable::stdlib())
139    }
140
141    /// Creates a new evaluator with a custom symbol table.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use expr_solver::{Eval, SymTable};
147    /// use rust_decimal_macros::dec;
148    ///
149    /// let mut table = SymTable::stdlib();
150    /// table.add_const("x", dec!(42)).unwrap();
151    ///
152    /// let mut eval = Eval::with_table("x * 2", table);
153    /// let result = eval.run().unwrap();
154    /// assert_eq!(result, dec!(84));
155    /// ```
156    pub fn with_table(string: &'str str, table: SymTable) -> Self {
157        let source = Source::new(string);
158        Self {
159            source: EvalSource::Source(Cow::Owned(source)),
160            table,
161        }
162    }
163
164    /// Creates a new evaluator from a [`Source`] reference.
165    pub fn new_from_source(source: &'str Source<'str>) -> Self {
166        Self::from_source_with_table(source, SymTable::stdlib())
167    }
168
169    /// Creates a new evaluator from a [`Source`] reference with a custom symbol table.
170    pub fn from_source_with_table(source: &'str Source<'str>, table: SymTable) -> Self {
171        Self {
172            source: EvalSource::Source(Cow::Borrowed(source)),
173            table,
174        }
175    }
176
177    /// Creates a new evaluator from a compiled binary file.
178    ///
179    /// The file must have been created using [`compile_to_file`](Self::compile_to_file).
180    pub fn new_from_file(path: PathBuf) -> Self {
181        Self::from_file_with_table(path, SymTable::stdlib())
182    }
183
184    /// Creates a new evaluator from a compiled binary file with a custom symbol table.
185    pub fn from_file_with_table(path: PathBuf, table: SymTable) -> Self {
186        Self {
187            source: EvalSource::File(path),
188            table,
189        }
190    }
191
192    /// Evaluates the expression and returns the result.
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// use expr_solver::Eval;
198    ///
199    /// let mut eval = Eval::new("2 + 3");
200    /// assert_eq!(eval.run().unwrap().to_string(), "5");
201    /// ```
202    pub fn run(&mut self) -> Result<Decimal, String> {
203        let program = self.build_program()?;
204        Vm::default().run(&program).map_err(|err| err.to_string())
205    }
206
207    /// Compiles the expression to a binary file.
208    ///
209    /// The compiled bytecode can later be loaded with [`new_from_file`](Self::new_from_file).
210    ///
211    /// # Examples
212    ///
213    /// ```no_run
214    /// use expr_solver::Eval;
215    /// use std::path::PathBuf;
216    ///
217    /// let mut eval = Eval::new("2 + 3 * 4");
218    /// eval.compile_to_file(&PathBuf::from("expr.bin")).unwrap();
219    /// ```
220    pub fn compile_to_file(&mut self, path: &PathBuf) -> Result<(), String> {
221        let program = self.build_program()?;
222        let binary_data = program.compile().map_err(|err| err.to_string())?;
223        fs::write(path, binary_data).map_err(|err| err.to_string())
224    }
225
226    /// Returns a human-readable assembly representation of the compiled expression.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use expr_solver::Eval;
232    ///
233    /// let mut eval = Eval::new("2 + 3");
234    /// let assembly = eval.get_assembly().unwrap();
235    /// assert!(assembly.contains("PUSH"));
236    /// assert!(assembly.contains("ADD"));
237    /// ```
238    pub fn get_assembly(&mut self) -> Result<String, String> {
239        let program = self.build_program()?;
240        Ok(program.get_assembly())
241    }
242
243    fn build_program(&mut self) -> Result<Program<'_>, String> {
244        match &self.source {
245            EvalSource::Source(source) => {
246                let mut parser = Parser::new(source);
247                let mut ast: Expr = match parser
248                    .parse()
249                    .map_err(|err| FormattedError::from((&err, source.as_ref())).to_string())?
250                {
251                    Some(ast) => ast,
252                    None => return Ok(Program::default()),
253                };
254                Sema::new(&self.table)
255                    .visit(&mut ast)
256                    .map_err(|err| FormattedError::from((&err, source.as_ref())).to_string())?;
257                IrBuilder::new().build(&ast).map_err(|err| err.to_string())
258            }
259            EvalSource::File(path) => {
260                let binary_data = fs::read(path).map_err(|err| err.to_string())?;
261                Program::load(&binary_data, &self.table).map_err(|err| err.to_string())
262            }
263        }
264    }
265}