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, 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#[derive(Debug)]
44enum EvalSource<'str> {
45    Source(Cow<'str, Source<'str>>),
46    File(PathBuf),
47}
48
49#[derive(Debug)]
50pub struct Eval<'str> {
51    source: EvalSource<'str>,
52    table: Option<SymTable>,
53}
54
55impl<'str> Eval<'str> {
56    pub fn new(string: &'str str) -> Self {
57        let source = Source::new(string);
58        Self {
59            source: EvalSource::Source(Cow::Owned(source)),
60            table: None,
61        }
62    }
63
64    pub fn new_from_source(source: &'str Source<'str>) -> Self {
65        Self {
66            source: EvalSource::Source(Cow::Borrowed(source)),
67            table: None,
68        }
69    }
70
71    pub fn new_from_file(path: PathBuf) -> Self {
72        Self {
73            source: EvalSource::File(path),
74            table: None,
75        }
76    }
77
78    pub fn with_table(&mut self, table: SymTable) {
79        self.table = Some(table);
80    }
81
82    pub fn run(&mut self) -> Result<Decimal, String> {
83        let program = self.build_program()?;
84        Vm::default().run(&program).map_err(|err| err.to_string())
85    }
86
87    pub fn compile_to_file(&mut self, path: &PathBuf) -> Result<(), String> {
88        let program = self.build_program()?;
89        let binary_data = program.compile().map_err(|err| err.to_string())?;
90        fs::write(path, binary_data).map_err(|err| err.to_string())
91    }
92
93    pub fn build_program(&mut self) -> Result<Program<'_>, String> {
94        let table = self.table.get_or_insert_with(SymTable::stdlib);
95        match &self.source {
96            EvalSource::Source(source) => {
97                let mut parser = Parser::new(source);
98                let mut ast: Expr = match parser
99                    .parse()
100                    .map_err(|err| Self::error_with_source(&err, source))?
101                {
102                    Some(ast) => ast,
103                    None => return Ok(Program::default()),
104                };
105                Sema::new(table)
106                    .visit(&mut ast)
107                    .map_err(|err| Self::error_with_source(&err, source))?;
108                IrBuilder::new().build(&ast).map_err(|err| err.to_string())
109            }
110            EvalSource::File(path) => {
111                let binary_data = fs::read(path).map_err(|err| err.to_string())?;
112                Program::load(&binary_data, table).map_err(|err| err.to_string())
113            }
114        }
115    }
116
117    fn error_with_source<T: SpanError>(error: &T, source: &Source) -> String {
118        format!("{}\n{}", error, source.highlight(&error.span()))
119    }
120}