Skip to main content

nodejs/
ast.rs

1//! JavaScript abstract syntax tree.
2//!
3//! Every node here has a direct lowering in `compiler.rs`. JS is
4//! statement-oriented with brace-delimited blocks, so the tree separates `Stmt`
5//! (blocks of these form a program/function body) from `Expr`. Numbers are all
6//! IEEE-754 `f64`, matching JavaScript's single number type.
7
8/// A binary operator (`a <op> b`). `&&`/`||`/`??` are `LogicalOp` because they
9/// short-circuit and yield an operand value, not a coerced boolean.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum BinOp {
12    Add,
13    Sub,
14    Mul,
15    Div,
16    Mod,
17    Pow, // **
18    // Comparison
19    Lt,
20    Le,
21    Gt,
22    Ge,
23    EqEqEq, // ===
24    NeEqEq, // !==
25    EqEq,   // ==  (loose, coercing)
26    NeEq,   // !=
27    // Bitwise / shift
28    BitAnd,
29    BitOr,
30    BitXor,
31    Shl,  // <<
32    Shr,  // >>
33    UShr, // >>>
34    // `in` / `instanceof`
35    In,
36    InstanceOf,
37}
38
39/// A short-circuiting logical operator.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum LogicalOp {
42    And,     // &&
43    Or,      // ||
44    Nullish, // ??
45}
46
47/// The operator carried by a COMPOUND assignment (`a += b`, `a ??= b`).
48///
49/// It rides on the [`Expr::Assign`] node rather than being desugared away at
50/// parse time. The parser used to rewrite `a op= b` into `a = a op b`, which
51/// duplicates the target expression — so every side effect in the target ran
52/// TWICE. `o[k()] += 1` called `k` once in node and twice here, and the same
53/// held for the logical forms even when they short-circuited and never wrote.
54/// Duplication cannot be undone later by inspecting the tree, because
55/// `o[k()] = o[k()] + 1` is a DIFFERENT program that legitimately calls `k`
56/// twice and is structurally identical after the rewrite.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum AssignOp {
59    /// `+= -= *= /= %= **= &= |= ^= <<= >>= >>>=` — always reads, computes, writes.
60    Binary(BinOp),
61    /// `&&= ||= ??=` — reads, and writes only if the read did not short-circuit.
62    Logical(LogicalOp),
63}
64
65/// A unary prefix operator.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum UnOp {
68    Neg,    // -x
69    Pos,    // +x
70    Not,    // !x
71    BitNot, // ~x
72    TypeOf, // typeof x
73    Void,   // void x
74    Delete, // delete x
75}
76
77/// The kind of a variable declaration.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum DeclKind {
80    Var,
81    Let,
82    Const,
83}
84
85/// The update (increment/decrement) operator, prefix or postfix.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum UpdateOp {
88    Inc, // ++
89    Dec, // --
90}
91
92/// A property of an object literal.
93#[derive(Debug, Clone, PartialEq)]
94pub enum Prop {
95    /// `key: value` — `computed` marks `[expr]: value`.
96    KeyValue {
97        key: Expr,
98        value: Expr,
99        computed: bool,
100    },
101    /// `...spread`.
102    Spread(Expr),
103    /// `get key() {}` / `set key(v) {}` — an accessor property.
104    Accessor {
105        key: Expr,
106        computed: bool,
107        /// `true` for a getter, `false` for a setter.
108        is_getter: bool,
109        /// The accessor function (an `Expr::Function`).
110        func: Expr,
111    },
112}
113
114/// A JavaScript expression.
115#[derive(Debug, Clone, PartialEq)]
116pub enum Expr {
117    Null,
118    Undefined,
119    True,
120    False,
121    Number(f64),
122    /// A `BigInt` literal as its canonical decimal digit string (`"123"` for
123    /// `123n`). Lowered to a heap `JsObj::BigInt`.
124    BigInt(String),
125    /// A regex literal: `(pattern, flags)`. Lowered to a `JsObj::RegExp`.
126    Regex(String, String),
127    Str(String),
128    /// A template literal: alternating literal quasis and interpolated exprs.
129    /// `quasis.len() == exprs.len() + 1`.
130    Template {
131        quasis: Vec<String>,
132        exprs: Vec<Expr>,
133    },
134    /// A tagged template `` tag`a${x}b` ``: calls `tag(strings, ...values)` where
135    /// `strings` is the cooked-quasi array carrying a `.raw` array of `raws`.
136    TaggedTemplate {
137        tag: Box<Expr>,
138        quasis: Vec<String>,
139        raws: Vec<String>,
140        exprs: Vec<Expr>,
141    },
142
143    /// A bare identifier (`x`); the compiler resolves scope at runtime.
144    Ident(String),
145    /// `this`.
146    This,
147    /// `super` (only valid as `super(...)` call callee or `super.x` object).
148    Super,
149    /// `new.target`.
150    NewTarget,
151    /// `yield expr` / `yield* expr` / `yield` (generator).
152    Yield {
153        arg: Option<Box<Expr>>,
154        delegate: bool,
155    },
156    /// `await expr` (async function).
157    Await(Box<Expr>),
158    /// A `class` expression.
159    Class(Box<ClassNode>),
160
161    Array(Vec<Expr>),
162    /// An elided array-literal element (`[1,,3]`) — a HOLE, which reads back as
163    /// `undefined` but is not an own property. Distinct from `Expr::Undefined`
164    /// so `compile_array` can record it and `destructure_array` can skip it.
165    Hole,
166    Object(Vec<Prop>),
167    /// `...expr` — a spread element (array/call).
168    Spread(Box<Expr>),
169
170    Logical(LogicalOp, Box<Expr>, Box<Expr>),
171    Unary(UnOp, Box<Expr>),
172    Binary(BinOp, Box<Expr>, Box<Expr>),
173
174    /// `test ? cons : alt`.
175    Conditional {
176        test: Box<Expr>,
177        cons: Box<Expr>,
178        alt: Box<Expr>,
179    },
180
181    /// `target = value`, or a compound `target op= value` when `op` is `Some`.
182    ///
183    /// A compound assignment evaluates the target reference ONCE: for
184    /// `o[k()] += 1` the object and the key are computed a single time, the
185    /// old value is read through them, and the result is written back through
186    /// the same reference (ECMA-262 13.15.2).
187    Assign {
188        target: Box<Expr>,
189        op: Option<AssignOp>,
190        value: Box<Expr>,
191    },
192    /// `++x` / `x++` / `--x` / `x--`.
193    Update {
194        op: UpdateOp,
195        prefix: bool,
196        target: Box<Expr>,
197    },
198
199    /// A call `func(args)`. `optional` marks `?.(`.
200    Call {
201        func: Box<Expr>,
202        args: Vec<Expr>,
203        optional: bool,
204    },
205    /// `new Ctor(args)`.
206    New {
207        callee: Box<Expr>,
208        args: Vec<Expr>,
209    },
210    /// `value.name` — `optional` marks `?.name`.
211    Member {
212        object: Box<Expr>,
213        property: String,
214        optional: bool,
215    },
216    /// `value[expr]` — `optional` marks `?.[expr]`.
217    Index {
218        object: Box<Expr>,
219        index: Box<Expr>,
220        optional: bool,
221    },
222
223    /// A function expression / arrow function.
224    Function {
225        params: Vec<Param>,
226        body: FnBody,
227        is_arrow: bool,
228        name: Option<String>,
229        is_generator: bool,
230        is_async: bool,
231        /// True for a MethodDefinition (`{ m(){} }`, `{ get x(){} }`) rather
232        /// than an ordinary function expression. A non-generator method owns no
233        /// `prototype` property (10.2.5 runs only for ordinary functions).
234        is_method: bool,
235    },
236
237    /// `,`-sequence expression: evaluate all, yield the last.
238    Sequence(Vec<Expr>),
239}
240
241/// A `class` declaration/expression body.
242#[derive(Debug, Clone, PartialEq)]
243pub struct ClassNode {
244    pub name: Option<String>,
245    /// The `extends` expression, if any.
246    pub parent: Option<Box<Expr>>,
247    pub members: Vec<ClassMember>,
248}
249
250/// One member of a class body: a method, accessor, or field, on the instance or
251/// static side.
252#[derive(Debug, Clone, PartialEq)]
253pub struct ClassMember {
254    /// The property key (an `Expr::Str` for a plain name, or any expr when
255    /// `computed`).
256    pub key: Expr,
257    pub computed: bool,
258    pub kind: MemberKind,
259    pub is_static: bool,
260    pub is_generator: bool,
261    pub is_async: bool,
262    /// Params + body for a method/accessor/constructor.
263    pub params: Vec<Param>,
264    pub body: Vec<Stmt>,
265    /// Initializer expression for a field (`x = expr;`).
266    pub field_init: Option<Expr>,
267}
268
269/// The kind of a class member.
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum MemberKind {
272    Constructor,
273    Method,
274    Get,
275    Set,
276    Field,
277    /// A `static { … }` initialization block (ES2022). It has no key and no
278    /// parameters: only `body` is meaningful, and it runs once at class-definition
279    /// time with `this` bound to the constructor.
280    StaticBlock,
281}
282
283/// A function/arrow body: either a brace-delimited statement list or (arrow) a
284/// single expression whose value is returned.
285#[derive(Debug, Clone, PartialEq)]
286pub enum FnBody {
287    Block(Vec<Stmt>),
288    Expr(Box<Expr>),
289}
290
291/// A formal parameter.
292#[derive(Debug, Clone, PartialEq)]
293pub struct Param {
294    /// The binding target — an `Ident`, or an array/object pattern (also an
295    /// `Expr::Array`/`Expr::Object` used as a destructuring target).
296    pub pattern: Expr,
297    /// `= default`.
298    pub default: Option<Expr>,
299    /// `...rest`.
300    pub rest: bool,
301}
302
303/// One `case`/`default` clause of a `switch`.
304#[derive(Debug, Clone, PartialEq)]
305pub struct SwitchCase {
306    /// `None` for the `default:` clause.
307    pub test: Option<Expr>,
308    pub body: Vec<Stmt>,
309}
310
311/// A single declarator inside a `var`/`let`/`const`.
312#[derive(Debug, Clone, PartialEq)]
313pub struct Declarator {
314    pub target: Expr,
315    pub init: Option<Expr>,
316}
317
318/// A JavaScript statement.
319#[derive(Debug, Clone, PartialEq)]
320pub enum StmtKind {
321    /// An expression evaluated for effect (value discarded).
322    Expr(Expr),
323    /// `var`/`let`/`const` declaration list.
324    Decl {
325        kind: DeclKind,
326        decls: Vec<Declarator>,
327    },
328    /// `{ ... }` block.
329    Block(Vec<Stmt>),
330    /// `function name(params) { body }`.
331    FuncDecl {
332        name: String,
333        params: Vec<Param>,
334        body: Vec<Stmt>,
335        is_generator: bool,
336        is_async: bool,
337    },
338    /// `class Name … { … }`.
339    ClassDecl(ClassNode),
340
341    If {
342        test: Expr,
343        cons: Box<Stmt>,
344        alt: Option<Box<Stmt>>,
345    },
346    While {
347        test: Expr,
348        body: Box<Stmt>,
349    },
350    DoWhile {
351        body: Box<Stmt>,
352        test: Expr,
353    },
354    /// C-style `for (init; test; update) body`.
355    For {
356        init: Option<Box<Stmt>>,
357        test: Option<Expr>,
358        update: Option<Expr>,
359        body: Box<Stmt>,
360    },
361    /// `for (decl of iterable) body`. `is_await` marks `for await (…)`.
362    ForOf {
363        decl_kind: Option<DeclKind>,
364        target: Expr,
365        iter: Expr,
366        body: Box<Stmt>,
367        is_await: bool,
368    },
369    /// `for (decl in object) body`.
370    ForIn {
371        decl_kind: Option<DeclKind>,
372        target: Expr,
373        object: Expr,
374        body: Box<Stmt>,
375    },
376    Switch {
377        disc: Expr,
378        cases: Vec<SwitchCase>,
379    },
380
381    /// `label: stmt` — a labeled statement (typically a loop), targetable by
382    /// `break label` / `continue label`.
383    Labeled {
384        label: String,
385        body: Box<Stmt>,
386    },
387
388    Return(Option<Expr>),
389    Break(Option<String>),
390    Continue(Option<String>),
391    Throw(Expr),
392    Try {
393        block: Vec<Stmt>,
394        handler: Option<(Option<Expr>, Vec<Stmt>)>, // (param pattern, body)
395        finalizer: Option<Vec<Stmt>>,
396    },
397
398    Empty,
399}
400
401/// A statement plus its 1-based source line.
402#[derive(Debug, Clone, PartialEq)]
403pub struct Stmt {
404    pub kind: StmtKind,
405    pub line: u32,
406}
407
408impl Stmt {
409    pub fn new(kind: StmtKind, line: u32) -> Stmt {
410        Stmt { kind, line }
411    }
412}
413
414impl From<StmtKind> for Stmt {
415    /// Wrap a `StmtKind` as a synthetic statement (line 0).
416    fn from(kind: StmtKind) -> Stmt {
417        Stmt { kind, line: 0 }
418    }
419}