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}