diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-07-30 10:24:19 +0200 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-07-30 10:24:19 +0200 |
| commit | 00920ddd36a8ebb851b52c3c048c0017753176bd (patch) | |
| tree | 8498e65ca6c055333aa95b6c3a0d28c8370cb0ef /src/z/parser/js/ast/expr.zig | |
| parent | e8259360516078517a2fbeddbf781f701f35d0d0 (diff) | |
complete js parser + minifierjs-parser
Diffstat (limited to 'src/z/parser/js/ast/expr.zig')
| -rw-r--r-- | src/z/parser/js/ast/expr.zig | 1245 |
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); + } +}; |