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::{SymTable, Program};
44//! use rust_decimal_macros::dec;
45//!
46//! // Compile expression to bytecode
47//! let program = Program::new_from_source("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    eval_with_table(expression, SymTable::stdlib())
113}
114
115/// Evaluates an expression string with a custom symbol table.
116///
117/// # Examples
118///
119/// ```
120/// use expr_solver::{eval_with_table, SymTable};
121/// use rust_decimal_macros::dec;
122///
123/// let mut table = SymTable::stdlib();
124/// table.add_const("x", dec!(42)).unwrap();
125///
126/// let result = eval_with_table("x * 2", table).unwrap();
127/// assert_eq!(result, dec!(84));
128/// ```
129pub fn eval_with_table(expression: &str, table: SymTable) -> Result<Decimal, String> {
130    Program::new_from_source(expression)
131        .map_err(|err| err.to_string())?
132        .link(table)
133        .map_err(|err| err.to_string())?
134        .execute()
135        .map_err(|err| err.to_string())
136}
137
138/// Evaluates an expression from a binary file with the standard library.
139///
140/// # Examples
141///
142/// ```no_run
143/// use expr_solver::eval_file;
144///
145/// let result = eval_file("expr.bin").unwrap();
146/// ```
147pub fn eval_file(path: impl AsRef<str>) -> Result<Decimal, String> {
148    eval_file_with_table(path, SymTable::stdlib())
149}
150
151/// Evaluates an expression from a binary file with a custom symbol table.
152///
153/// # Examples
154///
155/// ```no_run
156/// use expr_solver::{eval_file_with_table, SymTable};
157///
158/// let result = eval_file_with_table("expr.bin", SymTable::stdlib()).unwrap();
159/// ```
160pub fn eval_file_with_table(path: impl AsRef<str>, table: SymTable) -> Result<Decimal, String> {
161    let program = Program::new_from_file(path.as_ref()).map_err(|err| err.to_string())?;
162    let linked = program.link(table).map_err(|err| err.to_string())?;
163    linked.execute().map_err(|err| err.to_string())
164}