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;