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    // 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>;