aboutsummaryrefslogtreecommitdiff
path: root/src/z/parser/js/ast/expr.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/z/parser/js/ast/expr.zig')
-rw-r--r--src/z/parser/js/ast/expr.zig1245
1 files changed, 1245 insertions, 0 deletions
diff --git a/src/z/parser/js/ast/expr.zig b/src/z/parser/js/ast/expr.zig
new file mode 100644
index 0000000..2443b80
--- /dev/null
+++ b/src/z/parser/js/ast/expr.zig
@@ -0,0 +1,1245 @@
+const std = @import("std");
+const Loc = @import("loc.zig").Loc;
+const BlockStmt = @import("stmt.zig").BlockStmt;
+const Pat = @import("pat.zig").Pat;
+const Regex = @import("lit.zig").Regex;
+const Parser = @import("../parser.zig").Parser;
+const Formatter = @import("../formatter.zig").Formatter;
+const ParseError = @import("../parser.zig").ParseError;
+const BP = @import("../parser.zig").BP;
+
+pub const ExprType = union(enum) {
+ identifier: []const u8,
+ this: void,
+ super: void,
+ null: void,
+ bool: bool,
+ number: f64,
+ string: []const u8,
+ regex: Regex,
+ array: ArrayExpr,
+ object: ObjectExpr,
+ func: FnExpr,
+ arrow: ArrowFn,
+ class: ClassExpr,
+ template: TemplateLit,
+ tagged_template: TaggedTemplate,
+ member: MemberExpr,
+ computed_member: ComputedMember,
+ call: CallExpr,
+ new: NewExpr,
+ chain: ChainExpr,
+ unary: UnaryExpr,
+ binary: BinaryExpr,
+ update: UpdateExpr,
+ conditional: IfExpr,
+ assign: AssignExpr,
+ sequence: SeqExpr,
+ spread: SpreadElem,
+ yield: YieldExpr,
+ await: AwaitExpr,
+ meta_prop: MetaProp,
+ parenthesized: *const Expr,
+ private_ident: []const u8,
+};
+
+pub const Expr = struct {
+ loc: Loc,
+ data: ExprType,
+
+ pub fn parse(p: *Parser, min_bp: u32) ParseError!*const Expr {
+ var left = try p.parsePrimaryExpr();
+
+ while (true) {
+ const cur_bp = p.bp(p.peek());
+ if (cur_bp <= min_bp) break;
+
+ switch (p.peek()) {
+ .@"=",
+ .@"+=",
+ .@"-=",
+ .@"*=",
+ .@"/=",
+ .@"%=",
+ .@"**=",
+ .@"<<=",
+ .@">>=",
+ .@">>>=",
+ .@"&=",
+ .@"|=",
+ .@"^=",
+ .@"&&=",
+ .@"||=",
+ .@"??=",
+ => {
+ p.advance();
+ const op = p.assignOpFromToken();
+ const right = try Expr.parse(p, BP.assign);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .assign = .{ .loc = loc, .op = op, .left = left, .right = right } }, loc);
+ },
+
+ .@"?" => {
+ p.advance();
+ const consequent = try Expr.parse(p, 0);
+ try p.expect(.@":");
+ const alternate = try Expr.parse(p, BP.conditional);
+ const loc: Loc = .{ .start = left.loc.start, .end = alternate.loc.end };
+ left = p.arena.exprPtr(.{ .conditional = .{ .loc = loc, .condition = left, .consequent = consequent, .alternate = alternate } }, loc);
+ },
+
+ .@"??" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.coalesce);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"??", .left = left, .right = right } }, loc);
+ },
+
+ .@"||" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.log_or);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"||", .left = left, .right = right } }, loc);
+ },
+
+ .@"&&" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.log_and);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"&&", .left = left, .right = right } }, loc);
+ },
+
+ .@"|" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.bit_or);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"|", .left = left, .right = right } }, loc);
+ },
+
+ .@"^" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.bit_xor);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"^", .left = left, .right = right } }, loc);
+ },
+
+ .@"&" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.bit_and);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"&", .left = left, .right = right } }, loc);
+ },
+
+ .@"==" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.equality);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"==", .left = left, .right = right } }, loc);
+ },
+
+ .@"!=" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.equality);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"!=", .left = left, .right = right } }, loc);
+ },
+
+ .@"===" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.equality);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"===", .left = left, .right = right } }, loc);
+ },
+
+ .@"!==" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.equality);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"!==", .left = left, .right = right } }, loc);
+ },
+
+ .@"<" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.relational);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<", .left = left, .right = right } }, loc);
+ },
+
+ .@">" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.relational);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">", .left = left, .right = right } }, loc);
+ },
+
+ .@"<=" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.relational);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<=", .left = left, .right = right } }, loc);
+ },
+
+ .@">=" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.relational);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">=", .left = left, .right = right } }, loc);
+ },
+
+ .in => {
+ p.advance();
+ const right = try Expr.parse(p, BP.relational);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .in, .left = left, .right = right } }, loc);
+ },
+
+ .instanceof => {
+ p.advance();
+ const right = try Expr.parse(p, BP.relational);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .instanceof, .left = left, .right = right } }, loc);
+ },
+
+ .@"<<" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.shift);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<<", .left = left, .right = right } }, loc);
+ },
+
+ .@">>" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.shift);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">>", .left = left, .right = right } }, loc);
+ },
+
+ .@">>>" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.shift);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">>>", .left = left, .right = right } }, loc);
+ },
+
+ .@"+" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.additive);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"+", .left = left, .right = right } }, loc);
+ },
+
+ .@"-" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.additive);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"-", .left = left, .right = right } }, loc);
+ },
+
+ .@"*" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.multiplicative);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"*", .left = left, .right = right } }, loc);
+ },
+
+ .@"/" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.multiplicative);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"/", .left = left, .right = right } }, loc);
+ },
+
+ .@"%" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.multiplicative);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"%", .left = left, .right = right } }, loc);
+ },
+
+ .@"**" => {
+ p.advance();
+ const right = try Expr.parse(p, BP.exponentiation - 1);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"**", .left = left, .right = right } }, loc);
+ },
+
+ .lparen => {
+ p.advance();
+ var args: [256]Expr = undefined;
+ var arg_count: usize = 0;
+
+ if (p.peek() != .rparen) {
+ while (true) {
+ if (p.peek() == .@"...") {
+ p.advance();
+ const arg = try Expr.parse(p, 0);
+ args[arg_count] = Expr{ .loc = arg.loc, .data = .{ .spread = .{ .loc = arg.loc, .arg = p.arena.exprPtr(arg.data, arg.loc) } } };
+ arg_count += 1;
+ } else {
+ const arg = try Expr.parse(p, 0);
+ args[arg_count] = arg.*;
+ arg_count += 1;
+ }
+ if (!p.match(.comma)) break;
+ }
+ }
+
+ try p.expect(.rparen);
+ const loc: Loc = .{ .start = left.loc.start, .end = p.tok().loc.start };
+ left = p.arena.exprPtr(.{ .call = .{ .loc = loc, .callee = left, .args = args[0..arg_count], .optional = false } }, loc);
+ },
+
+ .@"?." => {
+ p.advance();
+ if (p.peek() == .lparen) {
+ p.advance();
+ var args: [256]Expr = undefined;
+ var arg_count: usize = 0;
+ if (p.peek() != .rparen) {
+ while (true) {
+ const arg = try Expr.parse(p, 0);
+ args[arg_count] = arg.*;
+ arg_count += 1;
+ if (!p.match(.comma)) break;
+ }
+ }
+ try p.expect(.rparen);
+ const loc: Loc = .{ .start = left.loc.start, .end = p.tok().loc.start };
+ left = p.arena.exprPtr(.{ .call = .{ .loc = loc, .callee = left, .args = args[0..arg_count], .optional = true } }, loc);
+ } else if (p.peek() == .lbracket) {
+ p.advance();
+ const expr = try Expr.parse(p, 0);
+ try p.expect(.rbracket);
+ const loc: Loc = .{ .start = left.loc.start, .end = p.tok().loc.start };
+ left = p.arena.exprPtr(.{ .computed_member = .{ .loc = loc, .obj = left, .expr = expr } }, loc);
+ } else if (p.peek() == .identifier or p.peek().isKeyword()) {
+ const prop_tok = p.advanceTok();
+ const prop = p.tokenSlice(prop_tok);
+ const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end };
+ left = p.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .ident = prop } } }, loc);
+ } else if (p.peek() == .private_identifier) {
+ const prop_tok = p.advanceTok();
+ const prop = p.tokenSlice(prop_tok);
+ const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end };
+ left = p.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .private = prop } } }, loc);
+ }
+ },
+
+ .lbracket => {
+ p.advance();
+ const expr = try Expr.parse(p, 0);
+ try p.expect(.rbracket);
+ const loc: Loc = .{ .start = left.loc.start, .end = p.tok().loc.start };
+ left = p.arena.exprPtr(.{ .computed_member = .{ .loc = loc, .obj = left, .expr = expr } }, loc);
+ },
+
+ .@"." => {
+ p.advance();
+ if (p.peek() == .private_identifier) {
+ const prop_tok = p.advanceTok();
+ const prop = p.tokenSlice(prop_tok);
+ const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end };
+ left = p.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .private = prop } } }, loc);
+ } else {
+ const prop_tok = try p.expectTok(.identifier);
+ const prop = p.tokenSlice(prop_tok);
+ const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end };
+ left = p.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .ident = prop } } }, loc);
+ }
+ },
+
+ .template_head, .no_sub_template => {
+ const template = try TemplateLit.parse(p);
+ const loc: Loc = .{ .start = left.loc.start, .end = template.loc.end };
+ left = p.arena.exprPtr(.{ .tagged_template = .{ .loc = loc, .tag = left, .quasi = template } }, loc);
+ },
+
+ .@"++" => {
+ if (!p.isLineTerminatorBeforeNext()) {
+ p.advance();
+ const loc: Loc = .{ .start = left.loc.start, .end = left.loc.end + 2 };
+ left = p.arena.exprPtr(.{ .update = .{ .loc = loc, .op = .@"++", .arg = left, .prefix = false } }, loc);
+ } else break;
+ },
+
+ .@"--" => {
+ if (!p.isLineTerminatorBeforeNext()) {
+ p.advance();
+ const loc: Loc = .{ .start = left.loc.start, .end = left.loc.end + 2 };
+ left = p.arena.exprPtr(.{ .update = .{ .loc = loc, .op = .@"--", .arg = left, .prefix = false } }, loc);
+ } else break;
+ },
+
+ .comma => {
+ p.advance();
+ const right = try Expr.parse(p, BP.comma);
+ const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end };
+ var exprs: [2]Expr = .{ left.*, right.* };
+ left = p.arena.exprPtr(.{ .sequence = .{ .loc = loc, .exprs = &exprs } }, loc);
+ },
+
+ else => break,
+ }
+ }
+
+ return left;
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ switch (self.data) {
+ .identifier => |name| f.write(name),
+ .this => f.write("this"),
+ .super => f.write("super"),
+ .null => f.write("null"),
+ .bool => |b| f.write(if (b) "true" else "false"),
+ .number => |n| {
+ var buf: [64]u8 = undefined;
+ f.write(std.fmt.bufPrint(&buf, "{d}", .{n}) catch "0");
+ },
+ .string => |s| {
+ f.writeChar('"');
+ f.write(s);
+ f.writeChar('"');
+ },
+ .regex => |r| {
+ f.writeChar('/');
+ f.write(r.pattern);
+ f.writeChar('/');
+ f.write(r.flags);
+ },
+ .array => |a| a.fmt(f),
+ .object => |o| o.fmt(f),
+ .func => |fn_expr| fn_expr.fmt(f),
+ .arrow => |arrow| arrow.fmt(f),
+ .class => |c| c.fmt(f),
+ .template => |t| t.fmt(f),
+ .tagged_template => |tt| tt.fmt(f),
+ .member => |m| m.fmt(f),
+ .computed_member => |cm| cm.fmt(f),
+ .call => |call| call.fmt(f),
+ .new => |n| {
+ f.write("new ");
+ n.callee.*.fmt(f);
+ f.writeChar('(');
+ for (n.args, 0..) |a, i| {
+ if (i > 0) f.write(", ");
+ a.fmt(f);
+ }
+ f.writeChar(')');
+ },
+ .chain => |c| c.expr.*.fmt(f),
+ .unary => |u| {
+ if (u.prefix) {
+ f.write(@tagName(u.op));
+ u.arg.*.fmt(f);
+ } else {
+ u.arg.*.fmt(f);
+ f.write(@tagName(u.op));
+ }
+ },
+ .binary => |b| {
+ f.writeChar('(');
+ b.left.*.fmt(f);
+ f.writeSpace();
+ f.write(@tagName(b.op));
+ f.writeSpace();
+ b.right.*.fmt(f);
+ f.writeChar(')');
+ },
+ .update => |u| {
+ const s = if (u.op == .@"++") "++" else "--";
+ if (u.prefix) {
+ f.write(s);
+ u.arg.*.fmt(f);
+ } else {
+ u.arg.*.fmt(f);
+ f.write(s);
+ }
+ },
+ .conditional => |c| {
+ c.condition.*.fmt(f);
+ f.write(" ? ");
+ c.consequent.*.fmt(f);
+ f.write(" : ");
+ c.alternate.*.fmt(f);
+ },
+ .assign => |a| {
+ a.left.*.fmt(f);
+ f.writeSpace();
+ f.write(@tagName(a.op));
+ f.writeSpace();
+ a.right.*.fmt(f);
+ },
+ .sequence => |s| {
+ for (s.exprs, 0..) |e, i| {
+ if (i > 0) f.write(", ");
+ e.fmt(f);
+ }
+ },
+ .spread => |s| {
+ f.write("...");
+ s.arg.*.fmt(f);
+ },
+ .yield => |y| {
+ f.write("yield");
+ if (y.delegate) f.write("*");
+ if (y.arg) |a| {
+ f.writeSpace();
+ a.*.fmt(f);
+ }
+ },
+ .await => |a| {
+ f.write("await ");
+ a.arg.*.fmt(f);
+ },
+ .meta_prop => |m| {
+ f.write(m.meta);
+ f.writeChar('.');
+ f.write(m.prop);
+ },
+ .parenthesized => |p| {
+ f.writeChar('(');
+ p.*.fmt(f);
+ f.writeChar(')');
+ },
+ .private_ident => |name| f.write(name),
+ }
+ }
+};
+
+pub const FnType = union(enum) {
+ normal: void,
+ generator: void,
+ async: void,
+ async_generator: void,
+};
+
+pub const FnExpr = struct {
+ loc: Loc,
+ fn_type: FnType,
+ id: ?[]const u8,
+ params: []const Pat,
+ body: BlockStmt,
+
+ pub fn parse(p: *Parser) ParseError!*const Expr {
+ const start = p.advanceTok().loc.start;
+
+ var fn_type: FnType = .normal;
+ if (p.peek() == .@"*") {
+ fn_type = .generator;
+ p.advance();
+ }
+
+ var id: ?[]const u8 = null;
+ if (p.peek() == .identifier or p.peek().isKeywordButNotLet()) {
+ const id_tok = p.advanceTok();
+ id = p.tokenSlice(id_tok);
+ }
+
+ const params = try p.parseParams();
+ const body = try p.parseFunctionBody();
+
+ return p.arena.exprPtr(
+ .{ .func = .{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = fn_type, .id = id, .params = params, .body = body } },
+ .{ .start = start, .end = body.loc.end },
+ );
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("function");
+ if (self.fn_type == .generator or self.fn_type == .async_generator) {
+ f.write("*");
+ }
+ if (self.fn_type == .async or self.fn_type == .async_generator) {
+ f.write(" async");
+ }
+ if (self.id) |id| {
+ f.writeSpace();
+ f.write(id);
+ }
+ f.writeChar('(');
+ for (self.params, 0..) |p, i| {
+ if (i > 0) f.write(", ");
+ p.fmt(f);
+ }
+ f.write(") ");
+ self.body.fmt(f);
+ }
+};
+
+pub const ArrowFn = struct {
+ loc: Loc,
+ async_token: bool,
+ params: []const Pat,
+ body: union(enum) {
+ block: BlockStmt,
+ expr: *const Expr,
+ },
+
+ pub fn parse(p: *Parser, params_info: struct { loc: Loc, params: []const Pat }) ParseError!*const Expr {
+ try p.expect(.@"=>");
+ const async_token = false;
+
+ if (p.peek() == .lbrace) {
+ const body = try p.parseFunctionBody();
+ return p.arena.exprPtr(
+ .{ .arrow = .{ .loc = .{ .start = params_info.loc.start, .end = body.loc.end }, .async_token = async_token, .params = params_info.params, .body = .{ .block = body } } },
+ .{ .start = params_info.loc.start, .end = body.loc.end },
+ );
+ }
+
+ const expr = try Expr.parse(p, 0);
+ return p.arena.exprPtr(
+ .{ .arrow = .{ .loc = .{ .start = params_info.loc.start, .end = expr.loc.end }, .async_token = async_token, .params = params_info.params, .body = .{ .expr = expr } } },
+ .{ .start = params_info.loc.start, .end = expr.loc.end },
+ );
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ if (self.async_token) f.write("async ");
+ f.writeChar('(');
+ for (self.params, 0..) |p, i| {
+ if (i > 0) f.write(", ");
+ p.fmt(f);
+ }
+ f.write(") => ");
+ switch (self.body) {
+ .block => |b| b.fmt(f),
+ .expr => |e| e.*.fmt(f),
+ }
+ }
+};
+
+pub const ClassExpr = struct {
+ loc: Loc,
+ id: ?[]const u8,
+ super_class: ?*const Expr,
+ body: []const ClassElem,
+
+ pub fn parse(p: *Parser) ParseError!*const Expr {
+ const start = p.advanceTok().loc.start;
+
+ var id: ?[]const u8 = null;
+ if (p.peek() == .identifier) {
+ const id_tok = p.advanceTok();
+ id = p.tokenSlice(id_tok);
+ }
+
+ var super_class: ?*const Expr = null;
+ if (p.matchKeyword(.extends)) {
+ super_class = try Expr.parse(p, 0);
+ }
+
+ try p.expect(.lbrace);
+ var elems: [128]ClassElem = undefined;
+ var elem_count: usize = 0;
+
+ while (p.peek() != .rbrace and p.peek() != .eof) {
+ const elem = try ClassElem.parse(p);
+ elems[elem_count] = elem;
+ elem_count += 1;
+ }
+
+ const end_tok = try p.expectTok(.rbrace);
+ return p.arena.exprPtr(
+ .{ .class = .{ .loc = .{ .start = start, .end = end_tok.loc.end }, .id = id, .super_class = super_class, .body = elems[0..elem_count] } },
+ .{ .start = start, .end = end_tok.loc.end },
+ );
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("class");
+ if (self.id) |id| {
+ f.writeSpace();
+ f.write(id);
+ }
+ if (self.super_class) |sc| {
+ f.write(" extends ");
+ sc.*.fmt(f);
+ }
+ f.write(" {");
+ f.newline();
+ f.indent_level += 1;
+ for (self.body) |elem| {
+ f.writeIndent();
+ elem.fmt(f);
+ f.newline();
+ }
+ f.indent_level -= 1;
+ f.writeIndent();
+ f.writeChar('}');
+ }
+};
+
+pub const ClassElem = union(enum) {
+ method: FnExpr,
+ get: PropGet,
+ set: PropSet,
+ field: FieldDef,
+ static_block: BlockStmt,
+ static_method: FnExpr,
+ static_get: PropGet,
+ static_set: PropSet,
+ static_field: FieldDef,
+
+ pub fn parse(p: *Parser) ParseError!ClassElem {
+ const start = p.tok().loc.start;
+ const is_static = p.matchKeyword(.static);
+
+ if (is_static and p.peek() == .lbrace) {
+ const body = try p.parseFunctionBody();
+ return ClassElem{ .static_block = body };
+ }
+
+ if (p.matchKeyword(.get)) {
+ const key = try p.parsePropKey();
+ try p.expect(.lparen);
+ try p.expect(.rparen);
+ const body = try p.parseFunctionBody();
+ if (is_static) return ClassElem{ .static_get = .{ .key = key, .body = body } };
+ return ClassElem{ .get = .{ .key = key, .body = body } };
+ }
+
+ if (p.matchKeyword(.set)) {
+ const key = try p.parsePropKey();
+ try p.expect(.lparen);
+ const param = try Pat.parse(p);
+ try p.expect(.rparen);
+ const body = try p.parseFunctionBody();
+ if (is_static) return ClassElem{ .static_set = .{ .key = key, .param = param, .body = body } };
+ return ClassElem{ .set = .{ .key = key, .param = param, .body = body } };
+ }
+
+ if (p.match(.@"*")) {
+ _ = try p.parsePropKey();
+ try p.expect(.lparen);
+ const params = try p.parseParams();
+ const body = try p.parseFunctionBody();
+ const fn_expr = FnExpr{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = .generator, .id = null, .params = params, .body = body };
+ if (is_static) return ClassElem{ .static_method = fn_expr };
+ return ClassElem{ .method = fn_expr };
+ }
+
+ const key = try p.parsePropKey();
+
+ if (p.peek() == .lparen) {
+ const params = try p.parseParams();
+ const body = try p.parseFunctionBody();
+ const fn_expr = FnExpr{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = .normal, .id = null, .params = params, .body = body };
+ if (is_static) return ClassElem{ .static_method = fn_expr };
+ return ClassElem{ .method = fn_expr };
+ }
+
+ var value: ?*const Expr = null;
+ if (p.match(.@"=")) {
+ value = try Expr.parse(p, 0);
+ }
+ if (p.peek() == .semicolon) p.advance();
+
+ const field = FieldDef{ .loc = .{ .start = start, .end = if (value) |v| v.loc.end else p.tok().loc.start }, .key = key, .value = value };
+ if (is_static) return ClassElem{ .static_field = field };
+ return ClassElem{ .field = field };
+ }
+};
+
+pub const FieldDef = struct {
+ loc: Loc,
+ key: PropKey,
+ value: ?*const Expr,
+};
+
+pub const ArrayExpr = struct {
+ loc: Loc,
+ elems: []const ?Expr,
+
+ pub fn parse(p: *Parser) ParseError!*const Expr {
+ const start = p.advanceTok().loc.start;
+ var elems: [256]?Expr = undefined;
+ var count: usize = 0;
+
+ while (p.peek() != .rbracket and p.peek() != .eof) {
+ if (p.match(.comma)) {
+ elems[count] = null;
+ count += 1;
+ } else if (p.peek() == .@"...") {
+ p.advance();
+ const arg = try Expr.parse(p, 0);
+ elems[count] = Expr{ .loc = arg.loc, .data = .{ .spread = .{ .loc = arg.loc, .arg = arg } } };
+ count += 1;
+ if (!p.match(.comma)) break;
+ } else {
+ const expr = try Expr.parse(p, 0);
+ elems[count] = expr.*;
+ count += 1;
+ if (!p.match(.comma)) break;
+ }
+ }
+
+ try p.expect(.rbracket);
+ return p.arena.exprPtr(.{ .array = .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .elems = elems[0..count] } }, .{ .start = start, .end = p.tok().loc.start });
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.writeChar('[');
+ for (self.elems, 0..) |elem, i| {
+ if (i > 0) f.write(", ");
+ if (elem) |e| e.fmt(f);
+ }
+ f.writeChar(']');
+ }
+};
+
+pub const ObjectExpr = struct {
+ loc: Loc,
+ props: []const Prop,
+
+ pub fn parse(p: *Parser) ParseError!*const Expr {
+ const start = p.advanceTok().loc.start;
+ var props: [128]Prop = undefined;
+ var count: usize = 0;
+
+ while (p.peek() != .rbrace and p.peek() != .eof) {
+ if (p.peek() == .@"...") {
+ p.advance();
+ const arg = try Expr.parse(p, 0);
+ props[count] = .{ .loc = arg.loc, .key = .{ .ident = "" }, .data = .{ .spread = .{ .loc = arg.loc, .arg = arg } } };
+ count += 1;
+ } else if (p.peek() == .identifier) {
+ const name_tok = p.tok();
+ const name = p.tokenSlice(name_tok);
+ p.advance();
+ if (p.peek() == .@":") {
+ p.advance();
+ const value = try Expr.parse(p, 0);
+ props[count] = .{ .loc = .{ .start = name_tok.loc.start, .end = value.loc.end }, .key = .{ .ident = name }, .data = .{ .init = .{ .key = .{ .ident = name }, .value = value } } };
+ count += 1;
+ } else if (p.peek() == .lparen) {
+ const params = try p.parseParams();
+ const body = try p.parseFunctionBody();
+ props[count] = .{ .loc = .{ .start = name_tok.loc.start, .end = body.loc.end }, .key = .{ .ident = name }, .data = .{ .method = .{ .loc = .{ .start = name_tok.loc.start, .end = body.loc.end }, .fn_type = .normal, .id = null, .params = params, .body = body } } };
+ count += 1;
+ } else if (p.peek() == .@"=") {
+ p.advance();
+ const value = try Expr.parse(p, 0);
+ props[count] = .{ .loc = .{ .start = name_tok.loc.start, .end = value.loc.end }, .key = .{ .ident = name }, .data = .{ .init = .{ .key = .{ .ident = name }, .value = value } } };
+ count += 1;
+ } else if (p.peek() == .comma or p.peek() == .rbrace) {
+ props[count] = .{ .loc = .{ .start = name_tok.loc.start, .end = name_tok.loc.end }, .key = .{ .ident = name }, .data = .{ .shorthand = name } };
+ count += 1;
+ }
+ } else if (p.peek() == .number or p.peek() == .string) {
+ const key = try p.parsePropKey();
+ try p.expect(.@":");
+ const value = try Expr.parse(p, 0);
+ props[count] = .{ .loc = .{ .start = start, .end = value.loc.end }, .key = key, .data = .{ .init = .{ .key = key, .value = value } } };
+ count += 1;
+ } else if (p.peek() == .lbracket) {
+ const key = try p.parsePropKey();
+ try p.expect(.@":");
+ const value = try Expr.parse(p, 0);
+ props[count] = .{ .loc = .{ .start = start, .end = value.loc.end }, .key = key, .data = .{ .init = .{ .key = key, .value = value } } };
+ count += 1;
+ } else if (p.matchKeyword(.get)) {
+ const key = try p.parsePropKey();
+ try p.expect(.lparen);
+ try p.expect(.rparen);
+ const body = try p.parseFunctionBody();
+ props[count] = .{ .loc = .{ .start = start, .end = body.loc.end }, .key = key, .data = .{ .get = .{ .key = key, .body = body } } };
+ count += 1;
+ } else if (p.matchKeyword(.set)) {
+ const key = try p.parsePropKey();
+ try p.expect(.lparen);
+ const param = try Pat.parse(p);
+ try p.expect(.rparen);
+ const body = try p.parseFunctionBody();
+ props[count] = .{ .loc = .{ .start = start, .end = body.loc.end }, .key = key, .data = .{ .set = .{ .key = key, .param = param, .body = body } } };
+ count += 1;
+ } else if (p.match(.@"*")) {
+ const key = try p.parsePropKey();
+ const params = try p.parseParams();
+ const body = try p.parseFunctionBody();
+ props[count] = .{ .loc = .{ .start = start, .end = body.loc.end }, .key = key, .data = .{ .method = .{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = .generator, .id = null, .params = params, .body = body } } };
+ count += 1;
+ } else {
+ break;
+ }
+
+ if (!p.match(.comma)) break;
+ }
+
+ try p.expect(.rbrace);
+ return p.arena.exprPtr(.{ .object = .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .props = props[0..count] } }, .{ .start = start, .end = p.tok().loc.start });
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.writeChar('{');
+ for (self.props, 0..) |p, i| {
+ if (i > 0) f.write(", ");
+ p.fmt(f);
+ }
+ f.writeChar('}');
+ }
+};
+
+pub const PropType = union(enum) {
+ init: PropInit,
+ get: PropGet,
+ set: PropSet,
+ shorthand: []const u8,
+ spread: SpreadElem,
+ method: FnExpr,
+};
+
+pub const Prop = struct {
+ loc: Loc,
+ key: PropKey,
+ data: PropType,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ switch (self.data) {
+ .init => |p_init| {
+ self.key.fmt(f);
+ f.write(": ");
+ p_init.value.*.fmt(f);
+ },
+ .get => |g| g.fmt(f),
+ .set => |s| s.fmt(f),
+ .shorthand => |name| f.write(name),
+ .spread => |sp| {
+ f.write("...");
+ sp.arg.*.fmt(f);
+ },
+ .method => |m| m.fmt(f),
+ }
+ }
+};
+
+pub const PropKey = union(enum) {
+ ident: []const u8,
+ string: []const u8,
+ number: f64,
+ computed: *const Expr,
+ private: []const u8,
+};
+
+pub const PropInit = struct {
+ key: PropKey,
+ value: *const Expr,
+};
+
+pub const PropGet = struct {
+ key: PropKey,
+ body: BlockStmt,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("get ");
+ self.key.fmt(f);
+ f.write("() ");
+ self.body.fmt(f);
+ }
+};
+
+pub const PropSet = struct {
+ key: PropKey,
+ param: Pat,
+ body: BlockStmt,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("set ");
+ self.key.fmt(f);
+ f.writeChar('(');
+ self.param.fmt(f);
+ f.write(") ");
+ self.body.fmt(f);
+ }
+};
+
+pub const SpreadElem = struct {
+ loc: Loc,
+ arg: *const Expr,
+};
+
+pub const MemberExpr = struct {
+ loc: Loc,
+ obj: *const Expr,
+ prop: union(enum) {
+ ident: []const u8,
+ private: []const u8,
+ },
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ self.obj.*.fmt(f);
+ switch (self.prop) {
+ .ident => |name| {
+ f.writeChar('.');
+ f.write(name);
+ },
+ .private => |name| {
+ f.writeChar('.');
+ f.write(name);
+ },
+ }
+ }
+};
+
+pub const ComputedMember = struct {
+ loc: Loc,
+ obj: *const Expr,
+ expr: *const Expr,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ self.obj.*.fmt(f);
+ f.writeChar('[');
+ self.expr.*.fmt(f);
+ f.writeChar(']');
+ }
+};
+
+pub const CallExpr = struct {
+ loc: Loc,
+ callee: *const Expr,
+ args: []const Expr,
+ optional: bool,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ self.callee.*.fmt(f);
+ if (self.optional) {
+ f.write("?.(");
+ } else {
+ f.writeChar('(');
+ }
+ for (self.args, 0..) |a, i| {
+ if (i > 0) f.write(", ");
+ a.fmt(f);
+ }
+ f.writeChar(')');
+ }
+};
+
+pub const NewExpr = struct {
+ loc: Loc,
+ callee: *const Expr,
+ args: []const Expr,
+};
+
+pub const ChainExpr = struct {
+ loc: Loc,
+ expr: *const Expr,
+};
+
+pub const UnaryOp = enum {
+ @"-",
+ @"+",
+ @"!",
+ @"~",
+ typeof,
+ void,
+ delete,
+};
+
+pub const UnaryExpr = struct {
+ loc: Loc,
+ op: UnaryOp,
+ arg: *const Expr,
+ prefix: bool,
+};
+
+pub const BinaryOp = enum {
+ @"==",
+ @"!=",
+ @"===",
+ @"!==",
+ @"<",
+ @"<=",
+ @">",
+ @">=",
+ @"<<",
+ @">>",
+ @">>>",
+ @"+",
+ @"-",
+ @"*",
+ @"/",
+ @"%",
+ @"**",
+ @"|",
+ @"^",
+ @"&",
+ in,
+ instanceof,
+ @"||",
+ @"&&",
+ @"??",
+};
+
+pub const BinaryExpr = struct {
+ loc: Loc,
+ op: BinaryOp,
+ left: *const Expr,
+ right: *const Expr,
+};
+
+pub const UpdateOp = enum {
+ @"++",
+ @"--",
+};
+
+pub const UpdateExpr = struct {
+ loc: Loc,
+ op: UpdateOp,
+ arg: *const Expr,
+ prefix: bool,
+};
+
+pub const IfExpr = struct {
+ loc: Loc,
+ condition: *const Expr,
+ consequent: *const Expr,
+ alternate: *const Expr,
+};
+
+pub const AssignOp = enum {
+ @"=",
+ @"+=",
+ @"-=",
+ @"*=",
+ @"/=",
+ @"%=",
+ @"**=",
+ @"<<=",
+ @">>=",
+ @">>>=",
+ @"|=",
+ @"^=",
+ @"&=",
+ @"||=",
+ @"&&=",
+ @"??=",
+};
+
+pub const AssignExpr = struct {
+ loc: Loc,
+ op: AssignOp,
+ left: *const Expr,
+ right: *const Expr,
+};
+
+pub const SeqExpr = struct {
+ loc: Loc,
+ exprs: []const Expr,
+};
+
+pub const YieldExpr = struct {
+ loc: Loc,
+ arg: ?*const Expr,
+ delegate: bool,
+};
+
+pub const AwaitExpr = struct {
+ loc: Loc,
+ arg: *const Expr,
+};
+
+pub const MetaProp = struct {
+ loc: Loc,
+ meta: []const u8,
+ prop: []const u8,
+};
+
+pub const TemplateLit = struct {
+ loc: Loc,
+ quasis: []const TemplateElem,
+ exprs: []const Expr,
+
+ pub fn parse(p: *Parser) ParseError!TemplateLit {
+ const start = p.tok().loc.start;
+ var quasis: [64]TemplateElem = undefined;
+ var exprs: [64]Expr = undefined;
+ var q_count: usize = 0;
+ var e_count: usize = 0;
+
+ while (true) {
+ switch (p.peek()) {
+ .no_sub_template => {
+ const t = p.advanceTok();
+ const slice = p.tokenSlice(t);
+ quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = true };
+ q_count += 1;
+ break;
+ },
+ .template_head => {
+ const t = p.advanceTok();
+ const slice = p.tokenSlice(t);
+ quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = false };
+ q_count += 1;
+ const expr = try Expr.parse(p, 0);
+ exprs[e_count] = expr.*;
+ e_count += 1;
+ try p.expect(.rbrace);
+ },
+ .template_middle => {
+ const t = p.advanceTok();
+ const slice = p.tokenSlice(t);
+ quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = false };
+ q_count += 1;
+ const expr = try Expr.parse(p, 0);
+ exprs[e_count] = expr.*;
+ e_count += 1;
+ try p.expect(.rbrace);
+ },
+ .template_tail => {
+ const t = p.advanceTok();
+ const slice = p.tokenSlice(t);
+ quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = true };
+ q_count += 1;
+ break;
+ },
+ else => break,
+ }
+ }
+
+ return .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .quasis = quasis[0..q_count], .exprs = exprs[0..e_count] };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.writeChar('`');
+ var ei: usize = 0;
+ for (self.quasis, 0..) |q, i| {
+ if (i > 0 and ei < self.exprs.len) {
+ f.write("${");
+ self.exprs[ei].fmt(f);
+ f.writeChar('}');
+ ei += 1;
+ }
+ f.write(q.value);
+ }
+ f.writeChar('`');
+ }
+};
+
+pub const TemplateElem = struct {
+ loc: Loc,
+ value: []const u8,
+ tail: bool,
+};
+
+pub const TaggedTemplate = struct {
+ loc: Loc,
+ tag: *const Expr,
+ quasi: TemplateLit,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ self.tag.*.fmt(f);
+ self.quasi.fmt(f);
+ }
+};
+
+pub const Decorator = struct {
+ loc: Loc,
+ expr: *const Expr,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.writeChar('@');
+ self.expr.*.fmt(f);
+ }
+};