rulesxp/lib.rs
1//! RulesXP - Multi-language rules expression evaluator
2//!
3//! This crate provides a minimalistic expression evaluator that supports both Scheme syntax
4//! and JSONLogic operations with strict typing. It implements a proper subset of both
5//! languages designed for reliable rule evaluation with predictable behavior.
6//!
7//! ## Dual Language Support
8//!
9//! The evaluator accepts expressions in Scheme or JSONLogic syntax:
10//!
11//! ```scheme
12//! ;; Scheme syntax
13//! (+ 1 2 3) ; arithmetic
14//! (if #t "yes" "no") ; conditionals
15//! (and #t #f) ; boolean logic
16//! (car '(1 2 3)) ; list operations
17//! ```
18//!
19//! The same operations can be represented in JSONLogic:
20//! ```json
21//! {"+": [1, 2, 3]}
22//! {"if": [true, "yes", "no"]}
23//! {"and": [true, false]}
24//! {"car": [[1, 2, 3]]}
25//! ```
26//!
27//! ## Strict Typing
28//!
29//! This interpreter implements stricter semantics than standard Scheme or JSONLogic:
30//! - No type coercion (numbers don't become strings, etc.)
31//! - Boolean operations require actual boolean values (no "truthiness")
32//! - Arithmetic overflow detection and error reporting
33//! - Strict arity checking for all functions
34//!
35//! Any program accepted by this interpreter will give identical results in standard
36//! Scheme R7RS-small or JSONLogic interpreters, but the converse is not true due to
37//! our additional type safety requirements.
38//!
39//! ## Modules
40//!
41//! - `scheme`: S-expression parsing from text
42//! - `evaluator`: Core expression evaluation engine
43//! - `builtinops`: Built-in operations with dual-language mapping
44//! - `jsonlogic`: JSONLogic format conversion and integration
45
46use std::fmt;
47
48/// Maximum parsing depth to prevent stack overflow attacks
49/// This limits deeply nested structures in both S-expression and JSONLogic parsers
50pub const MAX_PARSE_DEPTH: usize = 32;
51
52/// Maximum evaluation depth to prevent stack overflow in recursive evaluation
53/// This limits deeply nested function calls and expressions during evaluation
54/// Set higher than parse depth to allow for nested function applications
55pub const MAX_EVAL_DEPTH: usize = 64;
56
57/// Error types for the interpreter
58#[derive(Debug, Clone, PartialEq)]
59pub enum Error {
60 ParseError(String),
61 EvalError(String),
62 TypeError(String),
63 UnboundVariable(String),
64 ArityError {
65 expected: usize,
66 got: usize,
67 expression: Option<String>, // Optional expression context
68 },
69}
70
71impl Error {
72 /// Create an ArityError without expression context
73 pub fn arity_error(expected: usize, got: usize) -> Self {
74 Error::ArityError {
75 expected,
76 got,
77 expression: None,
78 }
79 }
80
81 /// Create an ArityError with expression context
82 pub fn arity_error_with_expr(expected: usize, got: usize, expression: String) -> Self {
83 Error::ArityError {
84 expected,
85 got,
86 expression: Some(expression),
87 }
88 }
89}
90
91impl fmt::Display for Error {
92 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93 match self {
94 Error::ParseError(msg) => write!(f, "Parse error: {msg}"),
95 Error::EvalError(msg) => write!(f, "Evaluation error: {msg}"),
96 Error::TypeError(msg) => write!(f, "Type error: {msg}"),
97 Error::UnboundVariable(var) => write!(f, "Unbound variable: {var}"),
98 Error::ArityError {
99 expected,
100 got,
101 expression,
102 } => match expression {
103 Some(expr) => write!(
104 f,
105 "Arity error in expression {expr}: expected {expected} arguments, got {got}"
106 ),
107 None => write!(f, "Arity error: expected {expected} arguments, got {got}"),
108 },
109 }
110 }
111}
112
113pub mod ast;
114pub mod builtinops;
115pub mod evaluator;
116
117#[cfg(feature = "jsonlogic")]
118pub mod jsonlogic;
119
120#[cfg(feature = "scheme")]
121pub mod scheme;