Crate expr_solver

Crate expr_solver 

Source
Expand description

A mathematical expression evaluator library with bytecode compilation.

This library provides a complete compiler pipeline for mathematical expressions, from parsing to bytecode execution on a stack-based virtual machine.

§Features

  • Flexible numeric types - Choose between f64 (default) or 128-bit Decimal precision
  • Rich error messages - Parse errors with syntax highlighting
  • Bytecode compilation - Compile once, execute many times
  • Custom symbols - Add your own constants and functions
  • Local constants - letthen syntax for declaring scoped constants
  • Control flow - Conditional expressions with if(condition, then, else)
  • Serialization - Save/load compiled programs (requires serialization feature)

§Numeric Type Selection

The library supports two numeric backends via feature flags:

  • f64-floats (default) - Standard f64 floating-point arithmetic. Faster and simpler, suitable for most use cases. Allows Inf and NaN results.
  • decimal-precision - 128-bit Decimal arithmetic for high precision. No floating-point errors, checked arithmetic with overflow detection. Use for financial calculations or when exact decimal representation is required.

Note: Only one numeric backend can be enabled at a time.

§Quick Start

use expr_solver::{eval, num};

let result = eval("2 + 3 * 4").unwrap();
assert_eq!(result, num!(14));

§Working with Numbers

Use the num! macro to create numeric values that work with both f64 and Decimal backends:

use expr_solver::num;

let x = num!(42);      // Works with any backend
let y = num!(3.14);    // Supports decimals
let z = num!(-10);     // And negative numbers

The macro ensures your code works regardless of which numeric backend is enabled.

§Custom Symbols

use expr_solver::{num, eval_with_table, SymTable};

let mut table = SymTable::stdlib();
table.add_const("x", num!(10), false).unwrap();

let result = eval_with_table("x * 2", table).unwrap();
assert_eq!(result, num!(20));

§Advanced: Type-State Pattern

The Program type uses the type-state pattern to enforce correct usage:

use expr_solver::{num, SymTable, Program};

// Compile expression to bytecode
let program = Program::new_from_source("x + y").unwrap();

// Link with symbol table (validated at link time)
let mut table = SymTable::new();
table.add_const("x", num!(10), false).unwrap();
table.add_const("y", num!(5), false).unwrap();

let mut linked = program.link(table).unwrap();

// Execute
let result = linked.execute().unwrap();
assert_eq!(result, num!(15));

§Local Constants with LET THEN

Declare local constants using letthen syntax:

use expr_solver::{eval, num};

// Single declaration
let result = eval("let x = 10 then x * 2").unwrap();
assert_eq!(result, num!(20));

// Multiple declarations
let result = eval("let x = 5, y = 3 then x + y").unwrap();
assert_eq!(result, num!(8));

// Reference previous declarations
let result = eval("let x = 2, y = x * 3, z = y + 1 then z").unwrap();
assert_eq!(result, num!(7));

// Use with globals and functions
let result = eval("let r = 5, area = pi * r ^ 2 then area").unwrap();
assert!((result - num!(78.53981633974483)).abs() < num!(0.0001));

Notes:

  • Local constants are evaluated left-to-right
  • Can reference previously declared locals and globals
  • Cannot reference forward (e.g., let x = y, y = 1 then x is an error)
  • Cannot shadow global constants (e.g., let pi = 3 then pi is an error)
  • Duplicate names in the same let block are errors

§Supported Operators

  • Arithmetic: +, -, *, /, ^ (power), ! (factorial), unary -
  • Comparison: ==, !=, <, <=, >, >= (return 1 or 0)
  • Grouping: ( )
  • Control flow: if(condition, then_value, else_value)

§Built-in Functions

See SymTable::stdlib() for the complete list of built-in functions and constants.

§Architecture

The library uses a multi-stage compilation pipeline:

  1. Parsing (Parser) - Source code → Abstract Syntax Tree (AST)
  2. IR Generation ([IrBuilder]) - AST → Bytecode + Symbol metadata
  3. Linking ([Linker]) - Bytecode + SymTable → Linked bytecode
  4. Execution (Vm) - Linked bytecode → Result

Each stage has its own error type (ParseError, IrError, LinkerError, VmError) which automatically converts to ProgramError for convenience.

Macros§

num
Macro for creating numeric literals in a type-neutral way.

Structs§

Compiled
Compiled state - bytecode ready for linking.
Expr
Expression node in the AST with source location.
Linked
Linked state - ready to execute.
Parser
Recursive descent parser for mathematical expressions.
Program
Type-state program using Rust’s type system to enforce correct usage.
SymTable
Symbol table containing constants and functions.
SymbolMetadata
Metadata about a symbol required by compiled bytecode.
Vm
Stack-based virtual machine for executing bytecode programs.

Enums§

BinOp
Binary operators: arithmetic and comparison.
ExprKind
Expression kind representing different types of expressions.
IrError
Errors that can occur during IR (bytecode) generation.
LinkerError
Errors that can occur during the linking process.
ParseError
Errors that can occur during parsing.
ProgramError
Errors that can occur during program operations.
ProgramOrigin
Origin of a compiled program.
Symbol
A symbol representing either a constant or function.
SymbolError
Errors that can occur during symbol table operations.
SymbolKind
The kind of symbol (constant or function) with its requirements.
UnOp
Unary operators: negation and factorial.
VmError
Virtual machine runtime errors.

Traits§

ParseNumber
Helper trait for parsing numbers from strings.

Functions§

eval
Evaluates an expression string with the standard library.
eval_with_table
Evaluates an expression string with a custom symbol table.

Type Aliases§

Number