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}