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); } };