jetro_core/ast.rs
1//! Abstract syntax tree for Jetro v2 expressions.
2//!
3//! The AST is the contract between the parser and every other v2 layer —
4//! the compiler lowers it to opcodes, analyses inspect it for ident-use
5//! / purity, and tests build it directly. Because every component
6//! observes `Expr`, its variants are kept deliberately orthogonal:
7//! each is a language concept, not an implementation shortcut.
8//!
9//! `Arc<str>` is used for every identifier so that cloning a name into
10//! an opcode is a refcount bump rather than a byte copy. Recursive
11//! sub-expressions are `Box<Expr>` — owned, not shared — since the
12//! compiler rewrites the AST in-place (see `reorder_and_operands`).
13
14use std::sync::Arc;
15
16// ── AST nodes ─────────────────────────────────────────────────────────────────
17
18#[derive(Debug, Clone)]
19pub enum Expr {
20 // Literals
21 Null,
22 Bool(bool),
23 Int(i64),
24 Float(f64),
25 Str(String),
26 FString(Vec<FStringPart>),
27
28 // References
29 Root, // $
30 Current, // @
31 Ident(String), // variable or implicit current-item field
32
33 // Navigation chain: base followed by postfix steps
34 Chain(Box<Expr>, Vec<Step>),
35
36 // Binary operations
37 BinOp(Box<Expr>, BinOp, Box<Expr>),
38 UnaryNeg(Box<Expr>),
39 Not(Box<Expr>),
40
41 // Kind check: expr kind [not] type
42 Kind {
43 expr: Box<Expr>,
44 ty: KindType,
45 negate: bool,
46 },
47
48 // Null-coalesce: lhs ?| rhs
49 Coalesce(Box<Expr>, Box<Expr>),
50
51 // Object / array construction
52 Object(Vec<ObjField>),
53 Array(Vec<ArrayElem>),
54
55 // Pipeline: base | step1 | step2 or base -> name | ...
56 Pipeline {
57 base: Box<Expr>,
58 steps: Vec<PipeStep>,
59 },
60
61 // Comprehensions
62 ListComp {
63 expr: Box<Expr>,
64 vars: Vec<String>,
65 iter: Box<Expr>,
66 cond: Option<Box<Expr>>,
67 },
68 DictComp {
69 key: Box<Expr>,
70 val: Box<Expr>,
71 vars: Vec<String>,
72 iter: Box<Expr>,
73 cond: Option<Box<Expr>>,
74 },
75 SetComp {
76 expr: Box<Expr>,
77 vars: Vec<String>,
78 iter: Box<Expr>,
79 cond: Option<Box<Expr>>,
80 },
81 GenComp {
82 expr: Box<Expr>,
83 vars: Vec<String>,
84 iter: Box<Expr>,
85 cond: Option<Box<Expr>>,
86 },
87
88 // Lambda: lambda x, y: body
89 Lambda {
90 params: Vec<String>,
91 body: Box<Expr>,
92 },
93
94 // Let binding: let x = init in body
95 Let {
96 name: String,
97 init: Box<Expr>,
98 body: Box<Expr>,
99 },
100
101 // Global function calls
102 GlobalCall {
103 name: String,
104 args: Vec<Arg>,
105 },
106
107 // Type cast: `expr as int` / `expr as string` etc. Add-on sugar on top
108 // of existing `.to_int()` / `.to_string()` methods — semantics identical.
109 Cast {
110 expr: Box<Expr>,
111 ty: CastType,
112 },
113
114 // Declarative patch block: `patch root { path1: val1, path2: val2, ... }`.
115 // Returns a new document with the listed paths rewritten in order.
116 // COW semantics — unchanged subtrees are shared.
117 Patch {
118 root: Box<Expr>,
119 ops: Vec<PatchOp>,
120 },
121
122 // Sentinel: `DELETE` inside a patch-field value removes the key.
123 // Only meaningful at the leaf of a patch path; outside patch it errors.
124 DeleteMark,
125}
126
127// ── Patch ─────────────────────────────────────────────────────────────────────
128
129#[derive(Debug, Clone)]
130pub struct PatchOp {
131 pub path: Vec<PathStep>,
132 pub val: Expr,
133 pub cond: Option<Expr>,
134}
135
136#[derive(Debug, Clone)]
137pub enum PathStep {
138 Field(String),
139 Index(i64),
140 Wildcard,
141 WildcardFilter(Box<Expr>),
142 Descendant(String),
143}
144
145// ── Cast type ─────────────────────────────────────────────────────────────────
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum CastType {
149 Int, Float, Number, Str, Bool, Array, Object, Null,
150}
151
152// ── Pipeline step ─────────────────────────────────────────────────────────────
153
154#[derive(Debug, Clone)]
155pub enum PipeStep {
156 /// `| expr` — forward current value as new context
157 Forward(Expr),
158 /// `-> target` — label current value, pass through unchanged
159 Bind(BindTarget),
160}
161
162#[derive(Debug, Clone)]
163pub enum BindTarget {
164 Name(String),
165 Obj { fields: Vec<String>, rest: Option<String> },
166 Arr(Vec<String>),
167}
168
169// ── F-string parts ────────────────────────────────────────────────────────────
170
171#[derive(Debug, Clone)]
172pub enum FStringPart {
173 Lit(String),
174 Interp { expr: Expr, fmt: Option<FmtSpec> },
175}
176
177#[derive(Debug, Clone)]
178pub enum FmtSpec {
179 Spec(String), // :.2f :>10 etc.
180 Pipe(String), // |upper |trim etc.
181}
182
183// ── Array element ─────────────────────────────────────────────────────────────
184
185#[derive(Debug, Clone)]
186pub enum ArrayElem {
187 Expr(Expr),
188 Spread(Expr),
189}
190
191// ── Postfix steps ─────────────────────────────────────────────────────────────
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum QuantifierKind {
195 /// `?` — first element or null
196 First,
197 /// `!` — exactly one element (error if 0 or >1)
198 One,
199}
200
201#[derive(Debug, Clone)]
202pub enum Step {
203 Field(String), // .field
204 OptField(String), // ?.field
205 Descendant(String), // ..field
206 DescendAll, // .. (all descendants)
207 Index(i64), // [n]
208 DynIndex(Box<Expr>), // [expr]
209 Slice(Option<i64>, Option<i64>), // [n:m]
210 Method(String, Vec<Arg>), // .method(args)
211 OptMethod(String, Vec<Arg>), // ?.method(args)
212 InlineFilter(Box<Expr>), // {pred}
213 Quantifier(QuantifierKind), // ? or !
214}
215
216// ── Function arguments ────────────────────────────────────────────────────────
217
218#[derive(Debug, Clone)]
219pub enum Arg {
220 Pos(Expr),
221 Named(String, Expr),
222}
223
224// ── Object field ─────────────────────────────────────────────────────────────
225
226#[derive(Debug, Clone)]
227pub enum ObjField {
228 /// `key: expr` (optionally `key: expr when cond`)
229 Kv { key: String, val: Expr, optional: bool, cond: Option<Expr> },
230 /// `key` — shorthand for `key: key`
231 Short(String),
232 /// `[expr]: expr` — dynamic key
233 Dynamic { key: Expr, val: Expr },
234 /// `...expr` — spread object (shallow)
235 Spread(Expr),
236 /// `...**expr` — deep-merge spread: recurse into nested objects,
237 /// concatenate arrays, rhs wins for scalars
238 SpreadDeep(Expr),
239}
240
241// ── Binary operators ──────────────────────────────────────────────────────────
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub enum BinOp {
245 Add, Sub, Mul, Div, Mod,
246 Eq, Neq, Lt, Lte, Gt, Gte,
247 Fuzzy,
248 And, Or,
249}
250
251// ── Kind types ────────────────────────────────────────────────────────────────
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254pub enum KindType {
255 Null, Bool, Number, Str, Array, Object,
256}
257
258// ── Sort key ──────────────────────────────────────────────────────────────────
259
260#[derive(Debug, Clone)]
261pub struct SortKey {
262 pub expr: Expr,
263 pub desc: bool,
264}
265
266impl Expr {
267 /// Wrap in a Chain if steps is non-empty, otherwise return self.
268 pub fn maybe_chain(self, steps: Vec<Step>) -> Self {
269 if steps.is_empty() { self } else { Expr::Chain(Box::new(self), steps) }
270 }
271}
272
273/// Shared reference to an expression (for lambdas stored in closures).
274pub type ExprRef = Arc<Expr>;