Exprimo
Exprimo is a reliable and JavaScript-compliant expression evaluator written in Rust, designed for rule-based engines and dynamic expression evaluation. Inspired by angular-expressions, it's built to be simple, fast, and production-ready.
Description
Exprimo parses and evaluates JavaScript expressions efficiently and securely. It utilizes the power of Rust and its excellent memory safety guarantees to provide a reliable and fast JavaScript expression evaluator with proper error handling and JavaScript semantics compliance.
Perfect for:
- Rule-based engines
- Dynamic configuration
- Conditional logic evaluation
- Template expressions
- Business rule processing
Features
✅ JavaScript-Compliant - Follows JavaScript semantics for intuitive expression writing
✅ Robust Error Handling - Gracefully handles edge cases (division by zero, NaN, Infinity)
✅ Type Coercion - Supports both loose (==) and strict (===) equality with proper type coercion
✅ Rich Type Support - Numbers, strings, booleans, arrays, objects, null, NaN, Infinity
✅ Custom Functions - Extend with your own Rust functions
✅ Built-in Methods - Array and object methods (.length, .includes(), .hasOwnProperty())
✅ String Escapes - Proper handling of escape sequences (\n, \t, \\, etc.)
✅ Production-Ready - Comprehensive test coverage (43+ tests)
Installation
Add Exprimo to your Cargo.toml:
[]
= "*"
Or install via cargo:
Optional Logging
Enable trace logging for debugging:
[]
= { = "*", = ["logging"] }
This will install Scribe Rust. Set LOG_LEVEL=TRACE in environment variables for logs to output.
Quick Start
use Evaluator;
use Value;
use HashMap;
// Set up context with variables
let mut context = new;
context.insert;
context.insert;
context.insert;
// Create evaluator
let evaluator = new;
// Evaluate expressions
let result = evaluator.evaluate.unwrap;
assert_eq!;
// Ternary operator
let result = evaluator.evaluate.unwrap;
assert_eq!;
// Type coercion with ==
let result = evaluator.evaluate.unwrap;
assert_eq!; // true == 1 with type coercion
Supported Operations
Arithmetic Operators
a + b // Addition (also string concatenation)
a - b // Subtraction
a * b // Multiplication
a / b // Division (returns Infinity for division by zero)
a % b // Modulo
-a // Unary negation
+a // Unary plus
Comparison Operators
a == b // Loose equality (with type coercion)
a != b // Loose inequality
a === b // Strict equality (no type coercion)
a !== b // Strict inequality
a > b // Greater than
a < b // Less than
a >= b // Greater than or equal
a <= b // Less than or equal
Logical Operators
a && b // Logical AND
a || b // Logical OR
!a // Logical NOT
Ternary Operator
condition ? valueIfTrue : valueIfFalse
Grouping
* c
Type Coercion
Exprimo implements JavaScript-compliant type coercion for the == operator:
// String to number coercion
evaluator.evaluate.unwrap; // true
evaluator.evaluate.unwrap; // false (strict)
// Boolean to number coercion
evaluator.evaluate.unwrap; // true
evaluator.evaluate.unwrap; // true
// Empty string to number
evaluator.evaluate.unwrap; // true
Truthiness Rules
Exprimo follows JavaScript truthiness semantics:
| Value | Truthy/Falsy | Example |
|---|---|---|
true |
Truthy | true ? 'yes' : 'no' → 'yes' |
false |
Falsy | false ? 'yes' : 'no' → 'no' |
null |
Falsy | null ? 'yes' : 'no' → 'no' |
0 |
Falsy | 0 ? 'yes' : 'no' → 'no' |
NaN |
Falsy | NaN ? 'yes' : 'no' → 'no' |
"" (empty string) |
Falsy | "" ? 'yes' : 'no' → 'no' |
| Non-zero numbers | Truthy | 42 ? 'yes' : 'no' → 'yes' |
| Non-empty strings | Truthy | "hello" ? 'yes' : 'no' → 'yes' |
| All arrays | Truthy | [] ? 'yes' : 'no' → 'yes' |
| All objects | Truthy | {} ? 'yes' : 'no' → 'yes' |
Note: Arrays and objects are always truthy, even if empty (JavaScript standard behavior).
Special Values
Infinity and NaN
Exprimo properly handles Infinity and NaN:
// Division by zero returns Infinity
evaluator.evaluate.unwrap; // Infinity (no error!)
evaluator.evaluate.unwrap; // -Infinity
// Invalid conversions return NaN
evaluator.evaluate.unwrap; // NaN (no error!)
// NaN comparisons
evaluator.evaluate.unwrap; // false (JavaScript behavior)
evaluator.evaluate.unwrap; // false
// Infinity identifier
evaluator.evaluate.unwrap; // true
Note: Due to serde_json::Number limitations, NaN and Infinity are represented as best-effort approximations. For production use with heavy NaN/Infinity usage, consider a custom Value type.
Undefined
The undefined identifier returns null (closest JSON equivalent):
evaluator.evaluate.unwrap; // null
String Escape Sequences
Exprimo processes common escape sequences:
evaluator.evaluate.unwrap; // "line1\nline2"
evaluator.evaluate.unwrap; // "col1\tcol2"
evaluator.evaluate.unwrap; // "quote: 'hello'"
evaluator.evaluate.unwrap; // "quote: \"hello\""
evaluator.evaluate.unwrap; // "path\to\file"
Supported escapes: \n, \t, \r, \\, \', \", \0
Type Conversions
String to Number
evaluator.evaluate.unwrap; // 84
evaluator.evaluate.unwrap; // NaN
evaluator.evaluate.unwrap; // 0 (empty string → 0)
evaluator.evaluate.unwrap; // true
Array to Number
evaluator.evaluate.unwrap; // 0 (empty array → 0)
evaluator.evaluate.unwrap; // 84 (single element)
evaluator.evaluate.unwrap; // NaN (multiple elements)
Object to Number
evaluator.evaluate.unwrap; // NaN (objects → NaN)
Built-in Properties and Methods
Arrays
Arrays are represented by serde_json::Value::Array.
.length
Returns the number of elements in an array.
let mut context = new;
context.insert;
let evaluator = new;
evaluator.evaluate.unwrap; // 3
.includes(valueToFind)
Checks if an array contains valueToFind using SameValueZero comparison (strict equality where NaN equals NaN).
evaluator.evaluate.unwrap; // true
evaluator.evaluate.unwrap; // false
evaluator.evaluate.unwrap; // true (special case)
Objects
Objects are represented by serde_json::Value::Object.
.hasOwnProperty(key)
Checks if an object contains the specified key as its own direct property.
let mut context = new;
let mut obj = new;
obj.insert;
obj.insert;
context.insert;
let evaluator = new;
evaluator.evaluate.unwrap; // true
evaluator.evaluate.unwrap; // false
evaluator.evaluate.unwrap; // false (coerced to "123")
Custom Functions
Extend Exprimo with your own Rust functions by implementing the CustomFunction trait.
Example: toUpperCase Function
use ;
use Value;
use HashMap;
use Arc;
;
Custom Function Requirements
- Implement
exprimo::CustomFunctiontrait (also requiresstd::fmt::Debug) - Implement the
callmethod with signature:fn call(&self, args: &[Value]) -> Result<Value, CustomFuncError> - Handle argument validation (count and types)
- Return
Ok(Value)on success orErr(CustomFuncError)on failure - Wrap in
Arc::new()before inserting into the custom functions map
Real-World Example: Rule Engine
use Evaluator;
use Value;
use HashMap;
// Rule engine context
let mut context = new;
context.insert;
context.insert;
context.insert;
context.insert;
context.insert;
let evaluator = new;
// Rule 1: Eligibility check
let eligible = evaluator.evaluate.unwrap;
assert_eq!;
// Rule 2: Discount calculation
let discount = evaluator.evaluate.unwrap;
assert_eq!;
// Rule 3: Complex condition
let approved = evaluator.evaluate.unwrap;
assert_eq!;
Error Handling
Exprimo provides detailed error types:
use EvaluationError;
let result = evaluator.evaluate;
match result
Known Limitations
-
serde_json::Number Constraints
NaNandInfinitydon't serialize perfectly to JSON- Workarounds are in place, but consider a custom
Valuetype for production
-
Complex Literals
- Only empty array
[]and empty object{}literals are supported - Complex literals like
[1, 2, 3]or{a: 1, b: 2}are not yet implemented - Workaround: Pass complex structures via context
- Only empty array
-
Object Literal Ambiguity
{}in expression context is parsed as a block statement (JavaScript quirk)- Workaround: Use variables or wrap in parentheses (future support)
Testing
Run the test suite:
Run with logging:
LOG_LEVEL=TRACE
Run examples:
LOG_LEVEL=TRACE
Performance
Exprimo is designed for performance:
- Minimal allocations
- Efficient AST traversal
- Zero-copy where possible
- Rust's memory safety without runtime overhead
Changelog
See CHANGELOG.md for version history.
Recent Improvements (Latest Version)
- ✅ Division by zero returns
Infinity/NaNinstead of errors - ✅ Invalid type conversions return
NaNinstead of errors - ✅ Proper NaN comparison semantics (
NaN != NaN) - ✅ Arrays and objects are now truthy (JavaScript standard)
- ✅ Type coercion for
==operator - ✅ Separate strict equality
===without coercion - ✅
Infinity,NaN,undefinedidentifiers - ✅ String escape sequence processing
- ✅ Array to number conversion
- ✅ SameValueZero for
Array.includes()
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
Exprimo is licensed under the MIT license. See the LICENSE file for details.
Credits
Inspired by angular-expressions.
Built with ❤️ using Rust and rslint_parser.