Skip to main content

jsonata_core/
ast.rs

1// Abstract Syntax Tree definitions
2// Mirrors the AST structure from jsonata.js
3
4use serde::{Deserialize, Serialize};
5
6/// Stage types that can be attached to path steps
7///
8/// In JSONata, predicates following path segments become "stages" that are applied
9/// during the extraction process, not as separate steps.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum Stage {
12    /// Filter/predicate stage [expr]
13    Filter(Box<AstNode>),
14}
15
16/// A step in a path expression with optional stages
17///
18/// Stages are operations (like predicates) that apply during the step evaluation,
19/// not after all steps are complete.
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct PathStep {
22    /// The main step node (field name, wildcard, etc.)
23    pub node: AstNode,
24    /// Stages to apply during this step (e.g., predicates)
25    pub stages: Vec<Stage>,
26}
27
28/// AST Node types
29///
30/// This enum represents all possible node types in a JSONata expression AST.
31/// The structure closely mirrors the JavaScript implementation to facilitate
32/// maintenance and upstream synchronization.
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub enum AstNode {
35    /// String literal (e.g., "hello", 'world')
36    String(String),
37
38    /// Field/property name in path expressions (e.g., foo in foo.bar)
39    /// This is distinct from String: Name is a field access, String is a literal value
40    Name(String),
41
42    /// Number literal
43    Number(f64),
44
45    /// Boolean literal
46    Boolean(bool),
47
48    /// Null literal
49    Null,
50
51    /// Undefined literal (distinct from null in JavaScript semantics)
52    /// In JSONata, undefined represents "no value" and propagates through expressions
53    Undefined,
54
55    /// Placeholder for partial application (?)
56    /// When used as a function argument, creates a partially applied function
57    Placeholder,
58
59    /// Regex literal (e.g., /pattern/flags)
60    Regex { pattern: String, flags: String },
61
62    /// Variable reference (e.g., $var)
63    Variable(String),
64
65    /// Parent variable reference (e.g., $$)
66    ParentVariable(String),
67
68    /// Path expression (e.g., foo.bar)
69    /// Each step can have stages (like predicates) attached
70    Path { steps: Vec<PathStep> },
71
72    /// Binary operation
73    Binary {
74        op: BinaryOp,
75        lhs: Box<AstNode>,
76        rhs: Box<AstNode>,
77    },
78
79    /// Unary operation
80    Unary { op: UnaryOp, operand: Box<AstNode> },
81
82    /// Function call by name
83    Function {
84        name: String,
85        args: Vec<AstNode>,
86        /// Whether this was called with $ prefix (built-in function)
87        /// True for $string(x), false for string(x)
88        is_builtin: bool,
89    },
90
91    /// Call an arbitrary expression as a function
92    /// Used for IIFE patterns like `(function($x){...})(5)` or chained calls
93    /// The procedure can be any expression that evaluates to a function
94    Call {
95        procedure: Box<AstNode>,
96        args: Vec<AstNode>,
97    },
98
99    /// Lambda function definition
100    Lambda {
101        params: Vec<String>,
102        body: Box<AstNode>,
103        /// Optional signature for type checking (e.g., "<n-n:n>")
104        signature: Option<String>,
105        /// Whether this lambda's body is a thunk (contains tail call that should be optimized)
106        /// A thunk wraps a tail-position function call for TCO
107        #[serde(default)]
108        thunk: bool,
109    },
110
111    /// Array constructor
112    Array(Vec<AstNode>),
113
114    /// Object constructor
115    Object(Vec<(AstNode, AstNode)>),
116
117    /// Object transform (postfix object constructor): expr{key: value}
118    /// Transforms the input using the object pattern
119    ObjectTransform {
120        input: Box<AstNode>,
121        pattern: Vec<(AstNode, AstNode)>,
122    },
123
124    /// Block expression
125    Block(Vec<AstNode>),
126
127    /// Conditional expression (? :)
128    Conditional {
129        condition: Box<AstNode>,
130        then_branch: Box<AstNode>,
131        else_branch: Option<Box<AstNode>>,
132    },
133
134    /// Wildcard operator (*) in path expressions
135    Wildcard,
136
137    /// Descendant operator (**) in path expressions
138    Descendant,
139
140    /// Array filter/predicate [condition]
141    /// Can be an index (number) or a predicate (boolean expression)
142    Predicate(Box<AstNode>),
143
144    /// Array grouping in path expression .[expr]
145    /// Like Array but doesn't flatten when used in paths
146    ArrayGroup(Vec<AstNode>),
147
148    /// Function application in path expression .(expr)
149    /// Maps expr over the current value, with $ referring to each element
150    FunctionApplication(Box<AstNode>),
151
152    /// Sort operator in path expression ^(expr)
153    /// Sorts the current value by evaluating expr for each element
154    /// expr can be prefixed with < (ascending, default) or > (descending)
155    Sort {
156        /// The input expression to sort
157        input: Box<AstNode>,
158        /// Sort terms - list of (expression, ascending) tuples
159        terms: Vec<(AstNode, bool)>,
160    },
161
162    /// Index binding operator #$var
163    /// Binds the current array index to the specified variable during path traversal
164    /// For example: arr#$i.field binds the index to $i for each element
165    IndexBind {
166        /// The input expression being indexed
167        input: Box<AstNode>,
168        /// The variable name to bind the index to (without the $ prefix)
169        variable: String,
170    },
171
172    /// Transform operator |location|update[,delete]|
173    /// Creates a function that transforms objects by:
174    /// 1. Evaluating location to find objects to modify
175    /// 2. Applying update (object constructor) to each matched object
176    /// 3. Optionally deleting fields specified in delete array
177    Transform {
178        /// Expression to locate objects to transform
179        location: Box<AstNode>,
180        /// Object constructor expression for updates
181        update: Box<AstNode>,
182        /// Optional array of field names to delete
183        delete: Option<Box<AstNode>>,
184    },
185}
186
187/// Binary operators
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
189pub enum BinaryOp {
190    // Arithmetic
191    Add,
192    Subtract,
193    Multiply,
194    Divide,
195    Modulo,
196
197    // Comparison
198    Equal,
199    NotEqual,
200    LessThan,
201    LessThanOrEqual,
202    GreaterThan,
203    GreaterThanOrEqual,
204
205    // Logical
206    And,
207    Or,
208
209    // String
210    Concatenate,
211
212    // Range
213    Range,
214
215    // Other
216    In,
217
218    // Variable binding
219    ColonEqual, // :=
220
221    // Coalescing
222    Coalesce, // ??
223
224    // Default
225    Default, // ?:
226
227    // Function chaining/piping
228    ChainPipe, // ~>
229}
230
231/// Unary operators
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
233pub enum UnaryOp {
234    /// Negation (-)
235    Negate,
236
237    /// Logical NOT
238    Not,
239}
240
241impl PathStep {
242    /// Create a path step from a node without stages
243    pub fn new(node: AstNode) -> Self {
244        PathStep {
245            node,
246            stages: Vec::new(),
247        }
248    }
249
250    /// Create a path step with stages
251    pub fn with_stages(node: AstNode, stages: Vec<Stage>) -> Self {
252        PathStep { node, stages }
253    }
254}
255
256impl AstNode {
257    /// Create a string literal node
258    pub fn string(s: impl Into<String>) -> Self {
259        AstNode::String(s.into())
260    }
261
262    /// Create a number literal node
263    pub fn number(n: f64) -> Self {
264        AstNode::Number(n)
265    }
266
267    /// Create a boolean literal node
268    pub fn boolean(b: bool) -> Self {
269        AstNode::Boolean(b)
270    }
271
272    /// Create a null literal node
273    pub fn null() -> Self {
274        AstNode::Null
275    }
276
277    /// Create an undefined literal node
278    pub fn undefined() -> Self {
279        AstNode::Undefined
280    }
281
282    /// Create a variable reference node
283    pub fn variable(name: impl Into<String>) -> Self {
284        AstNode::Variable(name.into())
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn test_ast_node_creation() {
294        let str_node = AstNode::string("hello");
295        assert!(matches!(str_node, AstNode::String(_)));
296
297        let num_node = AstNode::number(42.0);
298        assert!(matches!(num_node, AstNode::Number(_)));
299
300        let bool_node = AstNode::boolean(true);
301        assert!(matches!(bool_node, AstNode::Boolean(_)));
302
303        let null_node = AstNode::null();
304        assert!(matches!(null_node, AstNode::Null));
305    }
306
307    #[test]
308    fn test_binary_op() {
309        let node = AstNode::Binary {
310            op: BinaryOp::Add,
311            lhs: Box::new(AstNode::number(1.0)),
312            rhs: Box::new(AstNode::number(2.0)),
313        };
314        assert!(matches!(node, AstNode::Binary { .. }));
315    }
316}