const std = @import("std"); pub const Loc = struct { start: usize, end: usize, }; const lex = @import("lexical_grammar.zig"); const Token = lex.Token; const TokenType = lex.TokenType; pub const FormatOptions = struct { indent_size: usize = 4, }; pub const Formatter = struct { buf: []u8, pos: usize, indent_level: usize, options: FormatOptions, pub fn write(self: *@This(), s: []const u8) void { @memcpy(self.buf[self.pos..][0..s.len], s); self.pos += s.len; } pub fn writeChar(self: *@This(), c: u8) void { self.buf[self.pos] = c; self.pos += 1; } pub fn writeIndent(self: *@This()) void { const spaces = self.indent_level * self.options.indent_size; var i: usize = 0; while (i < spaces) : (i += 1) { self.writeChar(' '); } } pub fn newline(self: *@This()) void { self.writeChar('\n'); } }; pub const ParseError = error{ UnexpectedToken, ExpectedToken, ExpectedExpression, ExpectedStatement, ExpectedIdentifier, UnclosedString, InvalidNumber, InvalidEscape, UnterminatedComment, ExpectedParameterName, ExpectedPropertyName, UnterminatedTemplate, InvalidAssignmentTarget, TooManyTokens, }; const BP = struct { const comma = 1; const assign = 2; const yield = 3; const conditional = 4; const coalesce = 5; const log_or = 6; const log_and = 7; const bit_or = 8; const bit_xor = 9; const bit_and = 10; const equality = 11; const relational = 12; const shift = 13; const additive = 14; const multiplicative = 15; const exponentiation = 16; const unary = 17; const update = 18; const call = 19; const member = 20; }; pub const NodeArena = struct { exprs: [8192]Expr = undefined, expr_count: usize = 0, stmts: [8192]Stmt = undefined, stmt_count: usize = 0, decls: [1024]Decl = undefined, decl_count: usize = 0, pats: [1024]Pat = undefined, pat_count: usize = 0, lits: [1024]Lit = undefined, lit_count: usize = 0, string_buf: [65536]u8 = undefined, string_pos: usize = 0, fn exprPtr(self: *NodeArena, data: ExprType, loc: Loc) *const Expr { const idx = self.expr_count; self.exprs[idx] = .{ .loc = loc, .data = data }; self.expr_count += 1; return &self.exprs[idx]; } fn stmtPtr(self: *NodeArena, data: StmtType, loc: Loc) *const Stmt { const idx = self.stmt_count; self.stmts[idx] = .{ .loc = loc, .data = data }; self.stmt_count += 1; return &self.stmts[idx]; } fn declPtr(self: *NodeArena, data: DeclType, loc: Loc) *const Decl { const idx = self.decl_count; self.decls[idx] = .{ .loc = loc, .data = data }; self.decl_count += 1; return &self.decls[idx]; } fn patPtr(self: *NodeArena, data: PatType, loc: Loc) *const Pat { const idx = self.pat_count; self.pats[idx] = .{ .loc = loc, .data = data }; self.pat_count += 1; return &self.pats[idx]; } fn litPtr(self: *NodeArena, data: LitType, loc: Loc) *const Lit { const idx = self.lit_count; self.lits[idx] = .{ .loc = loc, .data = data }; self.lit_count += 1; return &self.lits[idx]; } }; pub const Parser = struct { tokens: []const Token, pos: usize, source: []const u8, arena: *NodeArena, pub fn init(tokens: []const Token, source: []const u8, arena: *NodeArena) @This() { return .{ .tokens = tokens, .pos = 0, .source = source, .arena = arena }; } fn tok(self: *@This()) Token { return self.tokens[self.pos]; } fn peek(self: *@This()) TokenType { return self.tokens[self.pos].kind; } fn peekAt(self: *@This(), offset: usize) TokenType { const idx = self.pos + offset; if (idx >= self.tokens.len) return .eof; return self.tokens[idx].kind; } fn advance(self: *@This()) void { self.pos += 1; } fn advanceTok(self: *@This()) Token { const t = self.tokens[self.pos]; self.pos += 1; return t; } fn expect(self: *@This(), kind: TokenType) ParseError!void { if (self.peek() != kind) return ParseError.ExpectedToken; self.advance(); } fn expectTok(self: *@This(), kind: TokenType) ParseError!Token { if (self.peek() != kind) return ParseError.ExpectedToken; return self.advanceTok(); } fn match(self: *@This(), kind: TokenType) bool { if (self.peek() == kind) { _ = self.advanceTok(); return true; } return false; } fn matchKeyword(self: *@This(), kw: TokenType) bool { if (self.peek() == kw) { _ = self.advanceTok(); return true; } return false; } fn isLineTerminatorBeforeNext(self: *@This()) bool { if (self.pos == 0) return false; const prev_token = self.tokens[self.pos - 1]; const next_token = self.tokens[self.pos]; var i = prev_token.loc.end; while (i < next_token.loc.start) : (i += 1) { if (i >= self.source.len) break; const c = self.source[i]; switch (c) { '\n', '\r' => return true, else => {}, } } return false; } fn as_identifier(self: *@This(), t: Token) []const u8 { return self.source[t.loc.start..t.loc.end]; } fn tokenSlice(self: *@This(), t: Token) []const u8 { return self.source[t.loc.start..t.loc.end]; } fn parseForDeclaration(self: *@This()) ParseError!*const Decl { const kind_tok = self.advanceTok(); var decls: [128]VarDeclarator = undefined; var count: usize = 0; while (true) { const pat = try Pat.parse(self); var decl_init: ?*const Expr = null; if (self.match(.@"=")) { decl_init = try Expr.parse(self, 0); } decls[count] = .{ .loc = pat.loc, .id = pat, .init = decl_init }; count += 1; if (!self.match(.comma)) break; } const kind: VarKind = switch (kind_tok.kind) { .@"var" => .@"var", .let => .let, .@"const" => .@"const", else => return ParseError.UnexpectedToken, }; return self.arena.declPtr(.{ .@"var" = .{ .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, .kind = kind, .decls = decls[0..count] } }, .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }); } fn parseParams(self: *@This()) ParseError![]const Pat { try self.expect(.lparen); var params: [128]Pat = undefined; var count: usize = 0; if (self.peek() != .rparen) { while (true) { if (self.peek() == .@"...") { self.advance(); const pat = try Pat.parse(self); params[count] = Pat{ .loc = pat.loc, .data = .{ .rest = .{ .loc = pat.loc, .arg = self.arena.patPtr(pat.data, pat.loc) } } }; count += 1; break; } const pat = try Pat.parse(self); if (self.match(.@"=")) { const expr = try Expr.parse(self, 0); params[count] = Pat{ .loc = .{ .start = pat.loc.start, .end = expr.loc.end }, .data = .{ .assign = .{ .loc = .{ .start = pat.loc.start, .end = expr.loc.end }, .left = self.arena.patPtr(pat.data, pat.loc), .right = expr } } }; } else { params[count] = pat; } count += 1; if (!self.match(.comma)) break; if (self.peek() == .@"...") { self.advance(); const rest_pat = try Pat.parse(self); params[count] = Pat{ .loc = rest_pat.loc, .data = .{ .rest = .{ .loc = rest_pat.loc, .arg = self.arena.patPtr(rest_pat.data, rest_pat.loc) } } }; count += 1; break; } } } try self.expect(.rparen); return params[0..count]; } fn parseFunctionBody(self: *@This()) ParseError!BlockStmt { return BlockStmt.parse(self); } fn parsePrimaryExpr(self: *@This()) ParseError!*const Expr { const start = self.tok().loc.start; switch (self.peek()) { .this => { const t = self.advanceTok(); return self.arena.exprPtr(.{ .this = {} }, .{ .start = start, .end = t.loc.end }); }, .super => { const t = self.advanceTok(); return self.arena.exprPtr(.{ .super = {} }, .{ .start = start, .end = t.loc.end }); }, .null => { const t = self.advanceTok(); return self.arena.exprPtr(.{ .null = {} }, .{ .start = start, .end = t.loc.end }); }, .true => { const t = self.advanceTok(); return self.arena.exprPtr(.{ .bool = true }, .{ .start = start, .end = t.loc.end }); }, .false => { const t = self.advanceTok(); return self.arena.exprPtr(.{ .bool = false }, .{ .start = start, .end = t.loc.end }); }, .number => { const t = self.advanceTok(); const val = std.fmt.parseFloat(f64, self.tokenSlice(t)) catch 0.0; return self.arena.exprPtr(.{ .number = val }, .{ .start = start, .end = t.loc.end }); }, .string => { const t = self.advanceTok(); return self.arena.exprPtr(.{ .string = self.tokenSlice(t) }, .{ .start = start, .end = t.loc.end }); }, .bigint => { const t = self.advanceTok(); return self.arena.exprPtr(.{ .number = 0 }, .{ .start = start, .end = t.loc.end }); }, .regex => { const t = self.advanceTok(); const slice = self.tokenSlice(t); const slash_pos = std.mem.indexOfScalar(u8, slice[1..], '/') orelse 0; const pattern = slice[1 .. slash_pos + 1]; const flags = slice[slash_pos + 2 ..]; return self.arena.exprPtr( .{ .regex = .{ .pattern = pattern, .flags = flags } }, .{ .start = start, .end = t.loc.end }, ); }, .identifier => { const t = self.advanceTok(); const name = self.tokenSlice(t); return self.arena.exprPtr(.{ .identifier = name }, .{ .start = start, .end = t.loc.end }); }, .private_identifier => { const t = self.advanceTok(); const name = self.tokenSlice(t); return self.arena.exprPtr(.{ .private_ident = name }, .{ .start = start, .end = t.loc.end }); }, .lbrace => return ObjectExpr.parse(self), .lbracket => return ArrayExpr.parse(self), .lparen => { self.advance(); if (self.peek() == .rparen) { self.advance(); if (self.peek() == .@"=>") { return ArrowFn.parse(self, .{ .loc = .{ .start = start, .end = self.tok().loc.start }, .params = &.{} }); } return self.arena.exprPtr(.{ .identifier = "" }, .{ .start = start, .end = self.tok().loc.start }); } if (self.peek() == .@"...") { self.advance(); const arg = try Expr.parse(self, 0); try self.expect(.rparen); if (self.peek() == .@"=>") { return ArrowFn.parse(self, .{ .loc = .{ .start = start, .end = arg.loc.end }, .params = &.{} }); } const loc: Loc = .{ .start = start, .end = self.tok().loc.start }; return self.arena.exprPtr(.{ .spread = .{ .loc = loc, .arg = arg } }, loc); } const expr = try Expr.parse(self, 0); if (self.peek() == .comma) { var params: [128]Pat = undefined; var count: usize = 0; params[count] = Pat{ .loc = expr.loc, .data = .{ .ident = expr.data.identifier } }; count += 1; while (self.match(.comma)) { const p = try Pat.parse(self); params[count] = p; count += 1; } try self.expect(.rparen); if (self.peek() == .@"=>") { return ArrowFn.parse(self, .{ .loc = .{ .start = start, .end = self.tok().loc.start }, .params = params[0..count] }); } return self.arena.exprPtr(.{ .identifier = "" }, .{ .start = start, .end = self.tok().loc.start }); } try self.expect(.rparen); if (self.peek() == .@"=>") { const pat = Pat{ .loc = expr.loc, .data = .{ .ident = expr.data.identifier } }; return ArrowFn.parse(self, .{ .loc = .{ .start = start, .end = self.tok().loc.start }, .params = &.{pat} }); } return self.arena.exprPtr(.{ .parenthesized = expr }, .{ .start = start, .end = self.tok().loc.start }); }, .function => return FnExpr.parse(self), .class => return ClassExpr.parse(self), .async => { self.advance(); if (self.peek() == .function) { return FnExpr.parse(self); } const id_tok = try self.expectTok(.identifier); const name = self.tokenSlice(id_tok); if (self.peek() == .@"=>") { const pat = Pat{ .loc = .{ .start = start, .end = id_tok.loc.end }, .data = .{ .ident = name } }; return ArrowFn.parse(self, .{ .loc = .{ .start = start, .end = id_tok.loc.end }, .params = &.{pat} }); } return self.arena.exprPtr(.{ .identifier = name }, .{ .start = start, .end = id_tok.loc.end }); }, .new => { self.advance(); const callee = try Expr.parse(self, BP.member); var args: [256]Expr = undefined; var arg_count: usize = 0; if (self.peek() == .lparen) { self.advance(); if (self.peek() != .rparen) { while (true) { const arg = try Expr.parse(self, 0); args[arg_count] = arg.*; arg_count += 1; if (!self.match(.comma)) break; } } try self.expect(.rparen); } const loc: Loc = .{ .start = start, .end = if (arg_count > 0) args[arg_count - 1].loc.end else callee.loc.end }; return self.arena.exprPtr(.{ .new = .{ .loc = loc, .callee = callee, .args = args[0..arg_count] } }, loc); }, .delete, .typeof, .void, .@"!", .@"~", .@"+", .@"-" => { const op = self.unaryOpFromToken(); self.advance(); const arg = try Expr.parse(self, BP.unary); const loc: Loc = .{ .start = start, .end = arg.loc.end }; return self.arena.exprPtr(.{ .unary = .{ .loc = loc, .op = op, .arg = arg, .prefix = true } }, loc); }, .@"++" => { self.advance(); const arg = try Expr.parse(self, BP.update); const loc: Loc = .{ .start = start, .end = arg.loc.end }; return self.arena.exprPtr(.{ .update = .{ .loc = loc, .op = .@"++", .arg = arg, .prefix = true } }, loc); }, .@"--" => { self.advance(); const arg = try Expr.parse(self, BP.update); const loc: Loc = .{ .start = start, .end = arg.loc.end }; return self.arena.exprPtr(.{ .update = .{ .loc = loc, .op = .@"--", .arg = arg, .prefix = true } }, loc); }, .await => { self.advance(); const arg = try Expr.parse(self, BP.unary); const loc: Loc = .{ .start = start, .end = arg.loc.end }; return self.arena.exprPtr(.{ .await = .{ .loc = loc, .arg = arg } }, loc); }, .yield => { self.advance(); var delegate = false; var arg: ?*const Expr = null; if (!self.isLineTerminatorBeforeNext()) { if (self.match(.@"*")) { delegate = true; } if (self.peek() != .rbrace and self.peek() != .rparen and self.peek() != .rbracket and self.peek() != .semicolon and self.peek() != .comma and self.peek() != .@":" and self.peek() != .eof) { arg = try Expr.parse(self, 0); } } const loc: Loc = .{ .start = start, .end = if (arg) |a| a.loc.end else start + 5 }; return self.arena.exprPtr(.{ .yield = .{ .loc = loc, .arg = arg, .delegate = delegate } }, loc); }, .template_head, .no_sub_template => return self.arena.exprPtr(.{ .template = try TemplateLit.parse(self) }, .{ .start = start, .end = self.tok().loc.start }), .import => { self.advance(); try self.expect(.lparen); _ = try Expr.parse(self, 0); try self.expect(.rparen); return self.arena.exprPtr(.{ .identifier = "import" }, .{ .start = start, .end = self.tok().loc.start }); }, .@"#" => { self.advance(); const id_tok = try self.expectTok(.identifier); return self.arena.exprPtr(.{ .private_ident = self.tokenSlice(id_tok) }, .{ .start = start, .end = id_tok.loc.end }); }, else => { if (self.peek().isIdentifier()) { const t = self.advanceTok(); const name = self.tokenSlice(t); if (self.peek() == .@"=>") { const pat = Pat{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .data = .{ .ident = name } }; return ArrowFn.parse(self, .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .params = &.{pat} }); } return self.arena.exprPtr(.{ .identifier = name }, .{ .start = t.loc.start, .end = t.loc.end }); } return ParseError.ExpectedExpression; }, } } fn parsePropKey(self: *@This()) ParseError!PropKey { switch (self.peek()) { .identifier, .let, .static, .get, .set, .async => { const t = self.advanceTok(); return PropKey{ .ident = self.tokenSlice(t) }; }, .string => { const t = self.advanceTok(); return PropKey{ .string = self.tokenSlice(t) }; }, .number => { const t = self.advanceTok(); const val = std.fmt.parseFloat(f64, self.tokenSlice(t)) catch 0.0; return PropKey{ .number = val }; }, .lbracket => { self.advance(); const expr = try Expr.parse(self, 0); try self.expect(.rbracket); return PropKey{ .computed = expr }; }, .private_identifier => { const t = self.advanceTok(); return PropKey{ .private = self.tokenSlice(t) }; }, else => return ParseError.ExpectedPropertyName, } } fn bp(_: *@This(), kind: TokenType) u32 { return switch (kind) { .comma => BP.comma, .@"=", .@"+=", .@"-=", .@"*=", .@"/=", .@"%=", .@"**=", .@"<<=", .@">>=", .@">>>=", .@"&=", .@"|=", .@"^=", .@"&&=", .@"||=", .@"??=" => BP.assign, .@"?" => BP.conditional, .@"??" => BP.coalesce, .@"||" => BP.log_or, .@"&&" => BP.log_and, .@"|" => BP.bit_or, .@"^" => BP.bit_xor, .@"&" => BP.bit_and, .@"==", .@"!=", .@"===", .@"!==" => BP.equality, .@"<", .@">", .@"<=", .@">=", .in, .instanceof => BP.relational, .@"<<", .@">>", .@">>>" => BP.shift, .@"+", .@"-" => BP.additive, .@"*", .@"/", .@"%" => BP.multiplicative, .@"**" => BP.exponentiation, .lparen => BP.call, .lbracket, .@".", .@"?." => BP.member, .@"++", .@"--" => BP.update, .template_head, .no_sub_template => BP.member, else => 0, }; } fn unaryOpFromToken(self: *@This()) UnaryOp { return switch (self.tok().kind) { .@"+" => .@"+", .@"-" => .@"-", .@"!" => .@"!", .@"~" => .@"~", .typeof => .typeof, .void => .void, .delete => .delete, else => .@"+", }; } fn assignOpFromToken(self: *@This()) AssignOp { return switch (self.tokens[self.pos - 1].kind) { .@"=" => .@"=", .@"+=" => .@"+=", .@"-=" => .@"-=", .@"*=" => .@"*=", .@"/=" => .@"/=", .@"%=" => .@"%=", .@"**=" => .@"**=", .@"<<=" => .@"<<=", .@">>=" => .@">>=", .@">>>=" => .@">>>=", .@"&=" => .@"&=", .@"|=" => .@"|=", .@"^=" => .@"^=", .@"&&=" => .@"&&=", .@"||=" => .@"||=", .@"??=" => .@"??=", else => .@"=", }; } fn semicolon(self: *@This()) void { if (self.peek() == .semicolon) { self.advance(); } } fn statementPointer(self: *@This()) ParseError!*const Stmt { const s = try Stmt.parse(self); return self.arena.stmtPtr(s.data, s.loc); } }; pub const Node = union(enum) { program: Program, stmt: Stmt, expr: Expr, pat: Pat, decl: Decl, literal: Lit, property: Prop, template: TemplateLit, spread: SpreadElem, decorator: Decorator, import_specifier: ImportSpecifier, export_specifier: ExportSpecifier, import_attr: ImportAttribute, catch_clause: CatchClause, switch_case: SwitchCase, }; pub const Program = struct { body: []const Stmt, loc: Loc, pub fn parse(comptime source: []const u8) ParseError!Program { return comptime blk: { var token_buf: [4096]Token = undefined; const tokens = lex.Lexer.tokenizeBuf(source, &token_buf); var arena: NodeArena = .{}; var parser = Parser.init(tokens, source, &arena); var stmts: [2048]Stmt = undefined; var count: usize = 0; while (parser.peek() != .eof) { const item = try Stmt.parse(&parser); stmts[count] = item; count += 1; if (count >= stmts.len) return ParseError.TooManyTokens; } const body = stmts[0..count]; const loc: Loc = if (body.len > 0) .{ .start = body[0].loc.start, .end = body[body.len - 1].loc.end } else .{ .start = 0, .end = 0 }; break :blk .{ .body = body, .loc = loc }; }; } pub fn fmt(self: @This(), f: *Formatter) void { @setEvalBranchQuota(100000); for (self.body, 0..) |stmt, i| { if (i > 0) f.newline(); stmt.fmt(f); } } }; pub const StmtType = union(enum) { block: BlockStmt, empty: void, debugger: void, expr: ExprStmt, @"if": IfStmt, @"while": WhileStmt, do_while: DoWhileStmt, @"for": ForStmt, for_in: ForInStmt, for_of: ForOfStmt, @"continue": ContinueStmt, @"break": BreakStmt, @"return": ReturnStmt, @"switch": SwitchStmt, throw: ThrowStmt, @"try": TryStmt, labelled: LabelledStmt, with: WithStmt, variable: VarStmt, lexical: LexicalDecl, function: FnDecl, class: ClassDecl, using: UsingStmt, import: ImportDecl, @"export": ExportDecl, }; pub const Stmt = struct { loc: Loc, data: StmtType, pub fn parse(p: *Parser) ParseError!Stmt { switch (p.peek()) { .lbrace => { const b = try BlockStmt.parse(p); return Stmt{ .loc = b.loc, .data = .{ .block = b } }; }, .semicolon => { const t = p.advanceTok(); return Stmt{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .data = .{ .empty = {} } }; }, .@"if" => { const s = try IfStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .@"if" = s } }; }, .@"while" => { const s = try WhileStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .@"while" = s } }; }, .do => { const s = try DoWhileStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .do_while = s } }; }, .@"for" => { const start = p.advanceTok().loc.start; try p.expect(.lparen); var for_init: ?ForInit = null; if (p.peek() != .semicolon) { if (p.peek() == .@"var" or p.peek() == .let or p.peek() == .@"const") { const decl = try p.parseForDeclaration(); for_init = .{ .decl = decl }; } else { const expr = try Expr.parse(p, 0); for_init = .{ .expr = expr }; } } if (p.matchKeyword(.in)) { const s = try ForInStmt.parse(p, start, for_init); return Stmt{ .loc = s.loc, .data = .{ .for_in = s } }; } if (p.matchKeyword(.of)) { const s = try ForOfStmt.parse(p, start, for_init); return Stmt{ .loc = s.loc, .data = .{ .for_of = s } }; } const s = try ForStmt.parse(p, start, for_init); return Stmt{ .loc = s.loc, .data = .{ .@"for" = s } }; }, .@"continue" => { const s = try ContinueStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .@"continue" = s } }; }, .@"break" => { const s = try BreakStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .@"break" = s } }; }, .@"return" => { const s = try ReturnStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .@"return" = s } }; }, .@"switch" => { const s = try SwitchStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .@"switch" = s } }; }, .throw => { const s = try ThrowStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .throw = s } }; }, .@"try" => { const s = try TryStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .@"try" = s } }; }, .debugger => { const t = p.advanceTok(); p.semicolon(); return Stmt{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .data = .{ .debugger = {} } }; }, .with => { const s = try WithStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .with = s } }; }, .function => return (try FnDecl.parse(p, .normal, .stmt)).?, .async => { if (p.peekAt(1) == .function) { return (try FnDecl.parse(p, .async, .stmt)).?; } if (p.peekAt(1) == .@"*" and p.peekAt(2) == .function) { return (try FnDecl.parse(p, .async_generator, .stmt)).?; } return Stmt.parse(p); }, .class => return (try ClassDecl.parse(p, .stmt)).?, .@"const", .let => { const d = try LexicalDecl.parse(p); return Stmt{ .loc = d.loc, .data = .{ .lexical = d } }; }, .@"var" => { const s = try VarStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .variable = s } }; }, .using => { const s = try UsingStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .using = s } }; }, .import => { const d = try ImportDecl.parse(p); return Stmt{ .loc = d.loc, .data = .{ .import = d } }; }, .@"export" => { const d = try ExportDecl.parse(p); return Stmt{ .loc = d.loc, .data = .{ .@"export" = d } }; }, else => { const s = try ExprStmt.parse(p); return Stmt{ .loc = s.loc, .data = .{ .expr = s } }; }, } } pub fn fmt(self: @This(), f: *Formatter) void { switch (self.data) { .block => |b| b.fmt(f), .empty => f.writeChar(';'), .debugger => f.write("debugger;"), .expr => |e| { e.expr.*.fmt(f); f.writeChar(';'); }, .@"if" => |s| s.fmt(f), .@"while" => |w| w.fmt(f), .do_while => |d| d.fmt(f), .@"for" => |fo| fo.fmt(f), .for_in => |fi| fi.fmt(f), .for_of => |fo| fo.fmt(f), .@"continue" => |c| { f.write("continue"); if (c.label) |l| { f.writeChar(' '); f.write(l); } f.writeChar(';'); }, .@"break" => |b| { f.write("break"); if (b.label) |l| { f.writeChar(' '); f.write(l); } f.writeChar(';'); }, .@"return" => |r| { f.write("return"); if (r.arg) |a| { f.writeChar(' '); a.*.fmt(f); } f.writeChar(';'); }, .@"switch" => |s| s.fmt(f), .throw => |t| { f.write("throw "); t.arg.*.fmt(f); f.writeChar(';'); }, .@"try" => |t| t.fmt(f), .labelled => |l| { f.write(l.label); f.write(": "); l.body.*.fmt(f); }, .with => |w| { f.write("with ("); w.obj.*.fmt(f); f.write(") "); w.body.*.fmt(f); }, .variable => |v| v.fmt(f), .lexical => |l| l.fmt(f), .using => |u| { f.write("using "); for (u.decls, 0..) |d, i| { if (i > 0) f.write(", "); d.fmt(f); } f.writeChar(';'); }, .import => |i| i.fmt(f), .@"export" => |e| e.fmt(f), .class => |c| c.fmt(f), .function => |d| d.fmt(f), } } }; 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.writeChar(' '); f.write(@tagName(b.op)); f.writeChar(' '); 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.writeChar(' '); f.write(@tagName(a.op)); f.writeChar(' '); 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.writeChar(' '); 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 PatType = union(enum) { ident: []const u8, object: ObjectPat, array: ArrayPat, assign: AssignPat, rest: RestPat, }; pub const Pat = struct { loc: Loc, data: PatType, pub fn parse(p: *Parser) ParseError!Pat { const start = p.tok().loc.start; switch (p.peek()) { .identifier, .await, .yield => { const t = p.advanceTok(); const name = p.tokenSlice(t); return Pat{ .loc = .{ .start = start, .end = t.loc.end }, .data = .{ .ident = name } }; }, .lbrace => { p.advance(); var props: [128]PatProp = undefined; var count: usize = 0; var rest: ?*const Pat = null; while (p.peek() != .rbrace and p.peek() != .eof) { if (p.peek() == .@"...") { p.advance(); const pat = try Pat.parse(p); rest = p.arena.patPtr(pat.data, pat.loc); break; } if (p.peek() == .identifier) { const name_tok = p.tok(); const name = p.tokenSlice(name_tok); p.advance(); if (p.peek() == .@":") { p.advance(); const val_pat = try Pat.parse(p); props[count] = .{ .key_value = .{ .key = .{ .ident = name }, .value = p.arena.patPtr(val_pat.data, val_pat.loc) } }; count += 1; } else { props[count] = .{ .shorthand = name }; count += 1; } } else if (p.peek() == .lbracket) { const key = try p.parsePropKey(); try p.expect(.@":"); const val_pat = try Pat.parse(p); props[count] = .{ .key_value = .{ .key = key, .value = p.arena.patPtr(val_pat.data, val_pat.loc) } }; count += 1; } else { break; } if (!p.match(.comma)) break; } try p.expect(.rbrace); return Pat{ .loc = .{ .start = start, .end = p.tok().loc.start }, .data = .{ .object = .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .props = props[0..count], .rest = rest } } }; }, .lbracket => { p.advance(); var elems: [128]?Pat = undefined; var count: usize = 0; var rest: ?*const Pat = null; while (p.peek() != .rbracket and p.peek() != .eof) { if (p.match(.comma)) { elems[count] = null; count += 1; } else if (p.peek() == .@"...") { p.advance(); const pat = try Pat.parse(p); rest = p.arena.patPtr(pat.data, pat.loc); break; } else { const pat = try Pat.parse(p); elems[count] = pat; count += 1; if (!p.match(.comma)) break; } } try p.expect(.rbracket); return Pat{ .loc = .{ .start = start, .end = p.tok().loc.start }, .data = .{ .array = .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .elems = elems[0..count], .rest = rest } } }; }, else => return ParseError.ExpectedParameterName, } } pub fn fmt(self: @This(), f: *Formatter) void { switch (self.data) { .ident => |name| f.write(name), .object => |o| o.fmt(f), .array => |a| a.fmt(f), .assign => |a| { a.left.*.fmt(f); f.write(" = "); a.right.*.fmt(f); }, .rest => |r| { f.write("..."); r.arg.*.fmt(f); }, } } }; pub const DeclType = union(enum) { @"fn": FnDecl, class: ClassDecl, @"var": VarDecl, using: UsingDecl, import: ImportDecl, @"export": ExportDecl, export_default: ExportDefault, }; pub const Decl = struct { loc: Loc, data: DeclType, pub fn fmt(self: @This(), f: *Formatter) void { switch (self.data) { .@"fn" => |d| d.fmt(f), .class => |c| c.fmt(f), .@"var" => |v| { f.write(@tagName(v.kind)); f.writeChar(' '); for (v.decls, 0..) |d, i| { if (i > 0) f.write(", "); d.fmt(f); } }, .using => |u| { f.write("using "); for (u.decls, 0..) |d, i| { if (i > 0) f.write(", "); d.fmt(f); } }, .import => |i| i.fmt(f), .@"export" => |e| e.fmt(f), .export_default => |e| e.fmt(f), } } pub fn parse(p: *Parser) ParseError!Decl { switch (p.peek()) { .@"var" => { const s = try VarStmt.parse(p); return Decl{ .loc = s.loc, .data = .{ .@"var" = .{ .loc = s.loc, .kind = s.kind, .decls = s.decls } } }; }, else => return ParseError.ExpectedStatement, } } }; pub const LitType = union(enum) { null: void, bool: bool, number: f64, string: []const u8, regex: Regex, bigint: []const u8, }; pub const Lit = struct { loc: Loc, data: LitType, pub fn fmt(self: @This(), f: *Formatter) void { switch (self.data) { .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); }, .bigint => |s| { f.write(s); f.writeChar('n'); }, } } }; pub const Regex = struct { pattern: []const u8, flags: []const u8, }; 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 BlockStmt = struct { loc: Loc, body: []const Stmt, pub fn parse(p: *Parser) ParseError!BlockStmt { const start = try p.expectTok(.lbrace); var stmts: [1024]Stmt = undefined; var count: usize = 0; while (p.peek() != .rbrace and p.peek() != .eof) { const s = try Stmt.parse(p); stmts[count] = s; count += 1; if (count >= stmts.len) return ParseError.TooManyTokens; } const end = try p.expectTok(.rbrace); return .{ .loc = .{ .start = start.loc.start, .end = end.loc.end }, .body = stmts[0..count] }; } pub fn fmt(self: @This(), f: *Formatter) void { f.writeChar('{'); if (self.body.len > 0) { f.newline(); f.indent_level += 1; for (self.body) |s| { f.writeIndent(); s.fmt(f); f.newline(); } f.indent_level -= 1; f.writeIndent(); } f.writeChar('}'); } }; pub const ExprStmt = struct { loc: Loc, expr: *const Expr, pub fn parse(p: *Parser) ParseError!ExprStmt { const expr = try Expr.parse(p, 0); p.semicolon(); return .{ .loc = expr.loc, .expr = expr }; } }; pub const IfStmt = struct { loc: Loc, condition: *const Expr, consequent: *const Stmt, alternate: ?*const Stmt, pub fn parse(p: *Parser) ParseError!IfStmt { const start = p.advanceTok().loc.start; try p.expect(.lparen); const condition = try Expr.parse(p, 0); try p.expect(.rparen); const consequent_stmt = try Stmt.parse(p); const consequent = p.arena.stmtPtr(consequent_stmt.data, consequent_stmt.loc); var alternate: ?*const Stmt = null; if (p.matchKeyword(.@"else")) { const alt_stmt = try Stmt.parse(p); alternate = p.arena.stmtPtr(alt_stmt.data, alt_stmt.loc); } const end = if (alternate) |a| a.loc.end else consequent.loc.end; return .{ .loc = .{ .start = start, .end = end }, .condition = condition, .consequent = consequent, .alternate = alternate }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("if ("); self.condition.*.fmt(f); f.write(") "); self.consequent.*.fmt(f); if (self.alternate) |alt| { f.write(" else "); alt.*.fmt(f); } } }; pub const WhileStmt = struct { loc: Loc, condition: *const Expr, body: *const Stmt, pub fn parse(p: *Parser) ParseError!WhileStmt { const start = p.advanceTok().loc.start; try p.expect(.lparen); const condition = try Expr.parse(p, 0); try p.expect(.rparen); const body = blk: { const s = try Stmt.parse(p); break :blk p.arena.stmtPtr(s.data, s.loc); }; return .{ .loc = .{ .start = start, .end = body.loc.end }, .condition = condition, .body = body }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("while ("); self.condition.*.fmt(f); f.write(") "); self.body.*.fmt(f); } }; pub const DoWhileStmt = struct { loc: Loc, body: *const Stmt, condition: *const Expr, pub fn parse(p: *Parser) ParseError!DoWhileStmt { const start = p.advanceTok().loc.start; const body = blk: { const s = try Stmt.parse(p); break :blk p.arena.stmtPtr(s.data, s.loc); }; try p.expect(.@"while"); try p.expect(.lparen); const condition = try Expr.parse(p, 0); try p.expect(.rparen); p.semicolon(); return .{ .loc = .{ .start = start, .end = condition.loc.end }, .body = body, .condition = condition }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("do "); self.body.*.fmt(f); f.write(" while ("); self.condition.*.fmt(f); f.write(");"); } }; pub const ForInit = union(enum) { expr: *const Expr, decl: *const Decl, }; pub const ForStmt = struct { loc: Loc, init: ?ForInit, condition: ?*const Expr, update: ?*const Expr, body: *const Stmt, pub fn parse(p: *Parser, start: usize, for_init: ?ForInit) ParseError!ForStmt { var condition: ?*const Expr = null; var update: ?*const Expr = null; try p.expect(.semicolon); if (p.peek() != .semicolon and p.peek() != .rparen) { condition = try Expr.parse(p, 0); } try p.expect(.semicolon); if (p.peek() != .rparen) { update = try Expr.parse(p, 0); } try p.expect(.rparen); const body = blk: { const s = try Stmt.parse(p); break :blk p.arena.stmtPtr(s.data, s.loc); }; return .{ .loc = .{ .start = start, .end = body.loc.end }, .init = for_init, .condition = condition, .update = update, .body = body }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("for ("); if (self.init) |init| { switch (init) { .expr => |e| e.*.fmt(f), .decl => |d| d.*.fmt(f), } } f.writeChar(';'); if (self.condition) |c| { f.writeChar(' '); c.*.fmt(f); } f.writeChar(';'); if (self.update) |u| { f.writeChar(' '); u.*.fmt(f); } f.write(") "); self.body.*.fmt(f); } }; pub const ForInStmt = struct { loc: Loc, left: ForInit, right: *const Expr, body: *const Stmt, pub fn parse(p: *Parser, start: usize, for_init: ?ForInit) ParseError!ForInStmt { const right = try Expr.parse(p, 0); try p.expect(.rparen); const body = blk: { const s = try Stmt.parse(p); break :blk p.arena.stmtPtr(s.data, s.loc); }; return .{ .loc = .{ .start = start, .end = body.loc.end }, .left = for_init.?, .right = right, .body = body }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("for ("); switch (self.left) { .expr => |e| e.*.fmt(f), .decl => |d| d.*.fmt(f), } f.write(" in "); self.right.*.fmt(f); f.write(") "); self.body.*.fmt(f); } }; pub const ForOfStmt = struct { loc: Loc, await_token: bool, left: ForInit, right: *const Expr, body: *const Stmt, pub fn parse(p: *Parser, start: usize, for_init: ?ForInit) ParseError!ForOfStmt { const right = try Expr.parse(p, 0); try p.expect(.rparen); const body = blk: { const s = try Stmt.parse(p); break :blk p.arena.stmtPtr(s.data, s.loc); }; return .{ .loc = .{ .start = start, .end = body.loc.end }, .await_token = false, .left = for_init.?, .right = right, .body = body }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("for"); if (self.await_token) f.write(" await"); f.write(" ("); switch (self.left) { .expr => |e| e.*.fmt(f), .decl => |d| d.*.fmt(f), } f.write(" of "); self.right.*.fmt(f); f.write(") "); self.body.*.fmt(f); } }; pub const ContinueStmt = struct { loc: Loc, label: ?[]const u8, pub fn parse(p: *Parser) ParseError!ContinueStmt { const start = p.advanceTok().loc.start; var label: ?[]const u8 = null; if (p.peek() == .identifier and !p.isLineTerminatorBeforeNext()) { const t = p.advanceTok(); label = p.tokenSlice(t); } p.semicolon(); return .{ .loc = .{ .start = start, .end = start + 8 }, .label = label }; } }; pub const BreakStmt = struct { loc: Loc, label: ?[]const u8, pub fn parse(p: *Parser) ParseError!BreakStmt { const start = p.advanceTok().loc.start; var label: ?[]const u8 = null; if (p.peek() == .identifier and !p.isLineTerminatorBeforeNext()) { const t = p.advanceTok(); label = p.tokenSlice(t); } p.semicolon(); return .{ .loc = .{ .start = start, .end = start + 5 }, .label = label }; } }; pub const ReturnStmt = struct { loc: Loc, arg: ?*const Expr, pub fn parse(p: *Parser) ParseError!ReturnStmt { const start = p.advanceTok().loc.start; var arg: ?*const Expr = null; if (p.peek() != .semicolon and p.peek() != .rbrace and p.peek() != .eof and !p.isLineTerminatorBeforeNext()) { arg = try Expr.parse(p, 0); } p.semicolon(); return .{ .loc = .{ .start = start, .end = if (arg) |a| a.loc.end else start + 6 }, .arg = arg }; } }; pub const SwitchStmt = struct { loc: Loc, discriminant: *const Expr, cases: []const SwitchCase, pub fn parse(p: *Parser) ParseError!SwitchStmt { const start = p.advanceTok().loc.start; try p.expect(.lparen); const discriminant = try Expr.parse(p, 0); try p.expect(.rparen); try p.expect(.lbrace); var cases: [256]SwitchCase = undefined; var case_count: usize = 0; while (p.peek() == .case or p.peek() == .default) { const case_start = p.tok().loc.start; if (p.matchKeyword(.case)) { const case_test = try Expr.parse(p, 0); try p.expect(.@":"); var consequent: [128]Stmt = undefined; var cons_count: usize = 0; while (p.peek() != .case and p.peek() != .default and p.peek() != .rbrace and p.peek() != .eof) { const s = try Stmt.parse(p); consequent[cons_count] = s; cons_count += 1; } cases[case_count] = .{ .loc = .{ .start = case_start, .end = p.tok().loc.start }, .condition = case_test, .consequent = consequent[0..cons_count] }; case_count += 1; } else if (p.matchKeyword(.default)) { try p.expect(.@":"); var consequent: [128]Stmt = undefined; var cons_count: usize = 0; while (p.peek() != .case and p.peek() != .default and p.peek() != .rbrace and p.peek() != .eof) { const s = try Stmt.parse(p); consequent[cons_count] = s; cons_count += 1; } cases[case_count] = .{ .loc = .{ .start = case_start, .end = p.tok().loc.start }, .condition = null, .consequent = consequent[0..cons_count] }; case_count += 1; } } const end_tok = try p.expectTok(.rbrace); return .{ .loc = .{ .start = start, .end = end_tok.loc.end }, .discriminant = discriminant, .cases = cases[0..case_count] }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("switch ("); self.discriminant.*.fmt(f); f.write(") {"); f.newline(); f.indent_level += 1; for (self.cases) |c| { f.writeIndent(); c.fmt(f); f.newline(); } f.indent_level -= 1; f.writeIndent(); f.writeChar('}'); } }; pub const SwitchCase = struct { loc: Loc, condition: ?*const Expr, consequent: []const Stmt, pub fn fmt(self: @This(), f: *Formatter) void { if (self.condition) |c| { f.write("case "); c.*.fmt(f); } else { f.write("default"); } f.writeChar(':'); for (self.consequent) |s| { f.writeChar(' '); s.fmt(f); } } }; pub const ThrowStmt = struct { loc: Loc, arg: *const Expr, pub fn parse(p: *Parser) ParseError!ThrowStmt { const start = p.advanceTok().loc.start; if (p.isLineTerminatorBeforeNext()) return ParseError.ExpectedExpression; const arg = try Expr.parse(p, 0); p.semicolon(); return .{ .loc = .{ .start = start, .end = arg.loc.end }, .arg = arg }; } }; pub const TryStmt = struct { loc: Loc, block: BlockStmt, handler: ?CatchClause, finalizer: ?BlockStmt, pub fn parse(p: *Parser) ParseError!TryStmt { const start = p.advanceTok().loc.start; const block = try p.parseFunctionBody(); var handler: ?CatchClause = null; var finalizer: ?BlockStmt = null; if (p.matchKeyword(.@"catch")) { var param: ?Pat = null; if (p.match(.lparen)) { const pat = try Pat.parse(p); param = pat; try p.expect(.rparen); } const catch_body = try p.parseFunctionBody(); handler = .{ .loc = .{ .start = block.loc.start, .end = catch_body.loc.end }, .param = param, .body = catch_body }; } if (p.matchKeyword(.finally)) { const finally_body = try p.parseFunctionBody(); finalizer = finally_body; } const end = if (finalizer) |f| f.loc.end else if (handler) |h| h.loc.end else block.loc.end; return .{ .loc = .{ .start = start, .end = end }, .block = block, .handler = handler, .finalizer = finalizer }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("try "); self.block.fmt(f); if (self.handler) |h| { f.writeChar(' '); h.fmt(f); } if (self.finalizer) |fin| { f.write(" finally "); fin.fmt(f); } } }; pub const CatchClause = struct { loc: Loc, param: ?Pat, body: BlockStmt, pub fn fmt(self: @This(), f: *Formatter) void { f.write("catch"); if (self.param) |p| { f.write(" ("); p.fmt(f); f.write(") "); } else { f.writeChar(' '); } self.body.fmt(f); } }; pub const LabelledStmt = struct { loc: Loc, label: []const u8, body: *const Stmt, }; pub const WithStmt = struct { loc: Loc, obj: *const Expr, body: *const Stmt, pub fn parse(p: *Parser) ParseError!WithStmt { const start = p.advanceTok().loc.start; try p.expect(.lparen); const obj = try Expr.parse(p, 0); try p.expect(.rparen); const body = blk: { const s = try Stmt.parse(p); break :blk p.arena.stmtPtr(s.data, s.loc); }; return .{ .loc = .{ .start = start, .end = body.loc.end }, .obj = obj, .body = body }; } }; pub const VarStmt = struct { loc: Loc, kind: VarKind, decls: []const VarDeclarator, pub fn parse(p: *Parser) ParseError!VarStmt { const kind_tok = p.advanceTok(); var decls: [128]VarDeclarator = undefined; var count: usize = 0; while (true) { const pat = try Pat.parse(p); var decl_init: ?*const Expr = null; if (p.match(.@"=")) { decl_init = try Expr.parse(p, 0); } decls[count] = .{ .loc = pat.loc, .id = pat, .init = decl_init }; count += 1; if (!p.match(.comma)) break; } p.semicolon(); return .{ .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, .kind = .@"var", .decls = decls[0..count] }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write(@tagName(self.kind)); f.writeChar(' '); for (self.decls, 0..) |d, i| { if (i > 0) f.write(", "); d.fmt(f); } f.writeChar(';'); } }; pub const UsingStmt = struct { loc: Loc, decls: []const VarDeclarator, pub fn parse(p: *Parser) ParseError!UsingStmt { const start = p.advanceTok().loc.start; var decls: [128]VarDeclarator = undefined; var count: usize = 0; while (true) { const id_tok = try p.expectTok(.identifier); const id = p.tokenSlice(id_tok); var decl_init: ?*const Expr = null; if (p.match(.@"=")) { decl_init = try Expr.parse(p, 0); } decls[count] = .{ .loc = .{ .start = id_tok.loc.start, .end = if (decl_init) |e| e.loc.end else id_tok.loc.end }, .id = Pat{ .loc = .{ .start = id_tok.loc.start, .end = id_tok.loc.end }, .data = .{ .ident = id } }, .init = decl_init }; count += 1; if (!p.match(.comma)) break; } p.semicolon(); return .{ .loc = .{ .start = start, .end = decls[count - 1].loc.end }, .decls = decls[0..count] }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("using "); for (self.decls, 0..) |d, i| { if (i > 0) f.write(", "); d.fmt(f); } } }; pub const VarKind = enum { @"var", let, @"const", }; pub const VarDecl = struct { loc: Loc, kind: VarKind, decls: []const VarDeclarator, }; pub const LexicalDecl = struct { loc: Loc, kind: VarKind, decls: []const VarDeclarator, pub fn parse(p: *Parser) ParseError!LexicalDecl { const kind_tok = p.advanceTok(); var decls: [128]VarDeclarator = undefined; var count: usize = 0; while (true) { const pat = try Pat.parse(p); var decl_init: ?*const Expr = null; if (kind_tok.kind == .@"const") { if (!p.match(.@"=")) return ParseError.ExpectedToken; decl_init = try Expr.parse(p, 0); } else if (p.match(.@"=")) { decl_init = try Expr.parse(p, 0); } decls[count] = .{ .loc = pat.loc, .id = pat, .init = decl_init }; count += 1; if (!p.match(.comma)) break; } p.semicolon(); const kind: VarKind = switch (kind_tok.kind) { .let => .let, .@"const" => .@"const", else => return ParseError.UnexpectedToken, }; return .{ .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, .kind = kind, .decls = decls[0..count] }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write(@tagName(self.kind)); f.writeChar(' '); for (self.decls, 0..) |d, i| { if (i > 0) f.write(", "); d.fmt(f); } f.writeChar(';'); } }; pub const UsingDecl = struct { loc: Loc, is_await: bool, decls: []const VarDeclarator, }; pub const VarDeclarator = struct { loc: Loc, id: Pat, init: ?*const Expr, pub fn fmt(self: @This(), f: *Formatter) void { self.id.fmt(f); if (self.init) |init| { f.write(" = "); init.*.fmt(f); } } }; pub const ObjectPat = struct { loc: Loc, props: []const PatProp, rest: ?*const Pat, pub fn fmt(self: @This(), f: *Formatter) void { f.writeChar('{'); var first = true; for (self.props) |prop| { if (!first) f.write(", "); first = false; switch (prop) { .key_value => |kv| { kv.key.fmt(f); f.write(": "); kv.value.*.fmt(f); }, .shorthand => |name| f.write(name), .rest => |r| { f.write("..."); r.*.fmt(f); }, } } if (self.rest) |rest| { if (!first) f.write(", "); f.write("..."); rest.*.fmt(f); } f.writeChar('}'); } }; pub const PatProp = union(enum) { key_value: struct { key: PropKey, value: *const Pat, }, shorthand: []const u8, rest: *const Pat, }; pub const ArrayPat = struct { loc: Loc, elems: []const ?Pat, rest: ?*const Pat, pub fn fmt(self: @This(), f: *Formatter) void { f.writeChar('['); for (self.elems, 0..) |elem, i| { if (i > 0) f.write(", "); if (elem) |pat| pat.fmt(f); } if (self.rest) |rest| { if (self.elems.len > 0) f.write(", "); f.write("..."); rest.*.fmt(f); } f.writeChar(']'); } }; pub const AssignPat = struct { loc: Loc, left: *const Pat, right: *const Expr, }; pub const RestPat = struct { loc: Loc, arg: *const Pat, }; pub const FnType = union(enum) { normal: void, generator: void, async: void, async_generator: void, }; pub const FnDecl = struct { loc: Loc, fn_type: FnType, id: ?[]const u8, params: []const Pat, body: BlockStmt, pub fn parse(p: *Parser, fn_type_arg: FnType, mode: enum { stmt, expr }) ParseError!?Stmt { const start = p.tok().loc.start; var fn_type = fn_type_arg; if (fn_type == .async) { _ = p.advanceTok(); } else if (fn_type == .async_generator) { _ = p.advanceTok(); _ = p.advanceTok(); } _ = p.advanceTok(); if (fn_type == .normal and p.peek() == .@"*") { p.advance(); fn_type = .generator; } var id: ?[]const u8 = null; if (p.peek().isIdentifier()) { const id_tok = p.advanceTok(); id = p.tokenSlice(id_tok); } const params = try p.parseParams(); const body = try p.parseFunctionBody(); const end = body.loc.end; if (mode == .stmt) { return Stmt{ .loc = .{ .start = start, .end = end }, .data = .{ .function = .{ .loc = .{ .start = start, .end = end }, .fn_type = fn_type, .id = id, .params = params, .body = body } }, }; } return null; } 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.writeChar(' '); 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 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.writeChar(' '); 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 ClassDecl = struct { loc: Loc, id: ?[]const u8, super_class: ?*const Expr, body: []const ClassElem, pub fn parse(p: *Parser, mode: enum { stmt, expr }) ParseError!?Stmt { 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; if (elem_count >= elems.len) return ParseError.TooManyTokens; } const end_tok = try p.expectTok(.rbrace); if (mode == .stmt) { return Stmt{ .loc = .{ .start = start, .end = end_tok.loc.end }, .data = .{ .class = .{ .loc = .{ .start = start, .end = end_tok.loc.end }, .id = id, .super_class = super_class, .body = elems[0..elem_count] } }, }; } return null; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("class"); if (self.id) |id| { f.writeChar(' '); 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 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.writeChar(' '); 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 ImportDecl = struct { loc: Loc, specifiers: []const ImportSpecifier, source: []const u8, attributes: []const ImportAttribute, pub fn parse(p: *Parser) ParseError!ImportDecl { const start = p.advanceTok().loc.start; var specifiers: [32]ImportSpecifier = undefined; var spec_count: usize = 0; if (p.peek() != .string and p.peek() != .lparen) { if (p.peek() == .identifier or p.peek().isKeyword()) { const local_tok = p.advanceTok(); const local = p.tokenSlice(local_tok); specifiers[spec_count] = .{ .default = local }; spec_count += 1; if (p.match(.comma)) {} } if (p.match(.@"*")) { try p.expect(.identifier); const ns_tok = try p.expectTok(.identifier); const ns = p.tokenSlice(ns_tok); specifiers[spec_count] = .{ .namespace = ns }; spec_count += 1; } else if (p.peek() == .lbrace) { p.advance(); if (p.peek() != .rbrace) { while (true) { const imported_tok = try p.expectTok(.identifier); const imported = p.tokenSlice(imported_tok); var local = imported; if (p.match(.identifier)) { local = p.tokenSlice(p.tokens[p.pos - 1]); } specifiers[spec_count] = .{ .named = .{ .imported = imported, .local = local } }; spec_count += 1; if (!p.match(.comma)) break; } } try p.expect(.rbrace); } } var source: []const u8 = ""; var attrs: [8]ImportAttribute = undefined; var attr_count: usize = 0; if (p.matchKeyword(.from)) { const str_tok = try p.expectTok(.string); source = p.tokenSlice(str_tok); } else if (p.peek() == .string) { const str_tok = p.advanceTok(); source = p.tokenSlice(str_tok); } if (p.matchKeyword(.with)) { try p.expect(.lbrace); while (p.peek() != .rbrace and p.peek() != .eof) { const key_tok = p.advanceTok(); const key = p.tokenSlice(key_tok); try p.expect(.@":"); const val_tok = try p.expectTok(.string); const val = p.tokenSlice(val_tok); attrs[attr_count] = .{ .key = key, .value = val }; attr_count += 1; _ = p.match(.comma); } try p.expect(.rbrace); } try p.expect(.semicolon); return .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .specifiers = specifiers[0..spec_count], .source = source, .attributes = attrs[0..attr_count] }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("import "); if (self.specifiers.len > 0) { for (self.specifiers, 0..) |spec, j| { if (j > 0) f.write(", "); spec.fmt(f); } f.write(" from "); } f.writeChar('"'); f.write(self.source); f.writeChar('"'); for (self.attributes) |a| { f.write(" /* "); a.fmt(f); f.write(" */"); } f.writeChar(';'); } }; pub const ImportSpecifier = union(enum) { default: []const u8, namespace: []const u8, named: struct { imported: []const u8, local: []const u8 }, }; pub const ImportAttribute = struct { key: []const u8, value: []const u8, pub fn fmt(self: @This(), f: *Formatter) void { f.write(self.key); f.write(": \""); f.write(self.value); f.writeChar('"'); } }; pub const ExportDecl = struct { loc: Loc, declaration: ?*const Decl, specifiers: ?[]const ExportSpecifier, source: ?[]const u8, pub fn parse(p: *Parser) ParseError!ExportDecl { const start = p.advanceTok().loc.start; if (p.matchKeyword(.default)) { if (p.peek() == .function or p.peek() == .async or p.peek() == .class) { const decl = try Stmt.parse(p); p.semicolon(); return .{ .loc = .{ .start = start, .end = decl.loc.end }, .declaration = null, .specifiers = null, .source = null }; } const expr = try Expr.parse(p, 0); p.semicolon(); return .{ .loc = .{ .start = start, .end = expr.loc.end }, .declaration = null, .specifiers = null, .source = null }; } if (p.peek() == .@"*") { p.advance(); try p.expect(.identifier); const source_tok = try p.expectTok(.string); const src = p.tokenSlice(source_tok); try p.expect(.semicolon); return .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .declaration = null, .specifiers = null, .source = src }; } if (p.peek() == .lbrace) { p.advance(); var specs: [32]ExportSpecifier = undefined; var spec_count: usize = 0; while (p.peek() != .rbrace and p.peek() != .eof) { const exported_tok = p.advanceTok(); const exported = p.tokenSlice(exported_tok); var local = exported; if (p.match(.identifier)) { local = p.tokenSlice(p.tokens[p.pos - 1]); } specs[spec_count] = .{ .loc = .{ .start = exported_tok.loc.start, .end = p.tok().loc.start }, .exported = exported, .local = local }; spec_count += 1; if (!p.match(.comma)) break; } try p.expect(.rbrace); var source: ?[]const u8 = null; if (p.matchKeyword(.from)) { const src_tok = try p.expectTok(.string); source = p.tokenSlice(src_tok); } try p.expect(.semicolon); return .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .declaration = null, .specifiers = specs[0..spec_count], .source = source }; } const decl = try Stmt.parse(p); p.semicolon(); return .{ .loc = .{ .start = start, .end = decl.loc.end }, .declaration = null, .specifiers = null, .source = null }; } pub fn fmt(self: @This(), f: *Formatter) void { f.write("export "); if (self.declaration) |d| { d.*.fmt(f); } else if (self.specifiers) |s| { f.writeChar('{'); for (s, 0..) |sp, i| { if (i > 0) f.write(", "); sp.fmt(f); } f.writeChar('}'); if (self.source) |src| { f.write(" from \""); f.write(src); f.writeChar('"'); } f.writeChar(';'); } else if (self.source) |src| { f.write("* from \""); f.write(src); f.writeChar("\";"); } } }; pub const ExportSpecifier = struct { loc: Loc, exported: []const u8, local: []const u8, pub fn fmt(self: @This(), f: *Formatter) void { f.write(self.local); if (!std.mem.eql(u8, self.local, self.exported)) { f.write(" as "); f.write(self.exported); } } }; pub const ExportDefault = struct { loc: Loc, declaration: *const Decl, pub fn fmt(self: @This(), f: *Formatter) void { f.write("export default "); self.declaration.*.fmt(f); } }; 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 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); } };