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}