Skip to main content

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    // Python-style conditional: `then_ if cond else else_`.
102    // Short-circuits — only the taken branch is evaluated.
103    IfElse {
104        cond:  Box<Expr>,
105        then_: Box<Expr>,
106        else_: Box<Expr>,
107    },
108
109    // Global function calls
110    GlobalCall {
111        name: String,
112        args: Vec<Arg>,
113    },
114
115    // Type cast: `expr as int` / `expr as string` etc.  Add-on sugar on top
116    // of existing `.to_int()` / `.to_string()` methods — semantics identical.
117    Cast {
118        expr: Box<Expr>,
119        ty:   CastType,
120    },
121
122    // Declarative patch block: `patch root { path1: val1, path2: val2, ... }`.
123    // Returns a new document with the listed paths rewritten in order.
124    // COW semantics — unchanged subtrees are shared.
125    Patch {
126        root: Box<Expr>,
127        ops:  Vec<PatchOp>,
128    },
129
130    // Sentinel: `DELETE` inside a patch-field value removes the key.
131    // Only meaningful at the leaf of a patch path; outside patch it errors.
132    DeleteMark,
133}
134
135// ── Patch ─────────────────────────────────────────────────────────────────────
136
137#[derive(Debug, Clone)]
138pub struct PatchOp {
139    pub path: Vec<PathStep>,
140    pub val:  Expr,
141    pub cond: Option<Expr>,
142}
143
144#[derive(Debug, Clone)]
145pub enum PathStep {
146    Field(String),
147    Index(i64),
148    DynIndex(Expr),
149    Wildcard,
150    WildcardFilter(Box<Expr>),
151    Descendant(String),
152}
153
154// ── Cast type ─────────────────────────────────────────────────────────────────
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum CastType {
158    Int, Float, Number, Str, Bool, Array, Object, Null,
159}
160
161// ── Pipeline step ─────────────────────────────────────────────────────────────
162
163#[derive(Debug, Clone)]
164pub enum PipeStep {
165    /// `| expr` — forward current value as new context
166    Forward(Expr),
167    /// `-> target` — label current value, pass through unchanged
168    Bind(BindTarget),
169}
170
171#[derive(Debug, Clone)]
172pub enum BindTarget {
173    Name(String),
174    Obj { fields: Vec<String>, rest: Option<String> },
175    Arr(Vec<String>),
176}
177
178// ── F-string parts ────────────────────────────────────────────────────────────
179
180#[derive(Debug, Clone)]
181pub enum FStringPart {
182    Lit(String),
183    Interp { expr: Expr, fmt: Option<FmtSpec> },
184}
185
186#[derive(Debug, Clone)]
187pub enum FmtSpec {
188    Spec(String),   // :.2f  :>10  etc.
189    Pipe(String),   // |upper  |trim  etc.
190}
191
192// ── Array element ─────────────────────────────────────────────────────────────
193
194#[derive(Debug, Clone)]
195pub enum ArrayElem {
196    Expr(Expr),
197    Spread(Expr),
198}
199
200// ── Postfix steps ─────────────────────────────────────────────────────────────
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum QuantifierKind {
204    /// `?` — first element or null
205    First,
206    /// `!` — exactly one element (error if 0 or >1)
207    One,
208}
209
210#[derive(Debug, Clone)]
211pub enum Step {
212    Field(String),                     // .field
213    OptField(String),                  // ?.field
214    Descendant(String),                // ..field
215    DescendAll,                        // ..  (all descendants)
216    Index(i64),                        // [n]
217    DynIndex(Box<Expr>),               // [expr]
218    Slice(Option<i64>, Option<i64>),   // [n:m]
219    Method(String, Vec<Arg>),          // .method(args)
220    OptMethod(String, Vec<Arg>),       // ?.method(args)
221    InlineFilter(Box<Expr>),           // {pred}
222    Quantifier(QuantifierKind),        // ? or !
223}
224
225// ── Function arguments ────────────────────────────────────────────────────────
226
227#[derive(Debug, Clone)]
228pub enum Arg {
229    Pos(Expr),
230    Named(String, Expr),
231}
232
233// ── Object field ─────────────────────────────────────────────────────────────
234
235#[derive(Debug, Clone)]
236pub enum ObjField {
237    /// `key: expr` (optionally `key: expr when cond`)
238    Kv { key: String, val: Expr, optional: bool, cond: Option<Expr> },
239    /// `key` — shorthand for `key: key`
240    Short(String),
241    /// `[expr]: expr` — dynamic key
242    Dynamic { key: Expr, val: Expr },
243    /// `...expr` — spread object (shallow)
244    Spread(Expr),
245    /// `...**expr` — deep-merge spread: recurse into nested objects,
246    /// concatenate arrays, rhs wins for scalars
247    SpreadDeep(Expr),
248}
249
250// ── Binary operators ──────────────────────────────────────────────────────────
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum BinOp {
254    Add, Sub, Mul, Div, Mod,
255    Eq, Neq, Lt, Lte, Gt, Gte,
256    Fuzzy,
257    And, Or,
258}
259
260// ── Kind types ────────────────────────────────────────────────────────────────
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum KindType {
264    Null, Bool, Number, Str, Array, Object,
265}
266
267// ── Sort key ──────────────────────────────────────────────────────────────────
268
269#[derive(Debug, Clone)]
270pub struct SortKey {
271    pub expr: Expr,
272    pub desc: bool,
273}
274
275impl Expr {
276    /// Wrap in a Chain if steps is non-empty, otherwise return self.
277    pub fn maybe_chain(self, steps: Vec<Step>) -> Self {
278        if steps.is_empty() { self } else { Expr::Chain(Box::new(self), steps) }
279    }
280}
281
282/// Shared reference to an expression (for lambdas stored in closures).
283pub type ExprRef = Arc<Expr>;