From 00920ddd36a8ebb851b52c3c048c0017753176bd Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Thu, 30 Jul 2026 10:24:19 +0200 Subject: complete js parser + minifier --- src/z/parser/js/ast.zig | 3368 ----------------------------------- src/z/parser/js/ast.zig.bak | 3334 ---------------------------------- src/z/parser/js/ast/core.zig | 162 ++ src/z/parser/js/ast/decl.zig | 432 +++++ src/z/parser/js/ast/expr.zig | 1245 +++++++++++++ src/z/parser/js/ast/lit.zig | 48 + src/z/parser/js/ast/loc.zig | 4 + src/z/parser/js/ast/pat.zig | 190 ++ src/z/parser/js/ast/stmt.zig | 865 +++++++++ src/z/parser/js/formatter.zig | 67 + src/z/parser/js/lexical_grammar.zig | 3 +- src/z/parser/js/parser.zig | 553 ++++++ src/z/parser/js/root.zig | 14 +- 13 files changed, 3580 insertions(+), 6705 deletions(-) delete mode 100644 src/z/parser/js/ast.zig delete mode 100644 src/z/parser/js/ast.zig.bak create mode 100644 src/z/parser/js/ast/core.zig create mode 100644 src/z/parser/js/ast/decl.zig create mode 100644 src/z/parser/js/ast/expr.zig create mode 100644 src/z/parser/js/ast/lit.zig create mode 100644 src/z/parser/js/ast/loc.zig create mode 100644 src/z/parser/js/ast/pat.zig create mode 100644 src/z/parser/js/ast/stmt.zig create mode 100644 src/z/parser/js/formatter.zig create mode 100644 src/z/parser/js/parser.zig (limited to 'src') diff --git a/src/z/parser/js/ast.zig b/src/z/parser/js/ast.zig deleted file mode 100644 index 3f11e8a..0000000 --- a/src/z/parser/js/ast.zig +++ /dev/null @@ -1,3368 +0,0 @@ -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); - } -}; diff --git a/src/z/parser/js/ast.zig.bak b/src/z/parser/js/ast.zig.bak deleted file mode 100644 index 3dc29a3..0000000 --- a/src/z/parser/js/ast.zig.bak +++ /dev/null @@ -1,3334 +0,0 @@ -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]; - } - - pub fn parseProgram(self: *@This()) ParseError!Program { - var stmts: [2048]Stmt = undefined; - var count: usize = 0; - - while (self.peek() != .eof) { - const item = try self.parseStatementListItem(); - 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 }; - - return .{ .body = body, .loc = loc }; - } - - fn parseStatementListItem(self: *@This()) ParseError!Stmt { - switch (self.peek()) { - .function => return (try self.parseFunctionDecl(.normal, .stmt)).?, - .async => { - if (self.peekAt(1) == .function) { - return (try self.parseFunctionDecl(.async, .stmt)).?; - } - if (self.peekAt(1) == .@"*" and self.peekAt(2) == .function) { - return (try self.parseFunctionDecl(.async_generator, .stmt)).?; - } - return self.parseStatement(); - }, - .class => return (try self.parseClassDecl(.stmt)).?, - .@"const", .let => return self.parseLexicalDecl(), - .@"var" => return self.parseVarStmt(), - .using => return self.parseUsingStmt(), - .import => return try self.parseImport(), - .@"export" => return try self.parseExport(), - else => return self.parseStatement(), - } - } - - fn parseStatement(self: *@This()) ParseError!Stmt { - switch (self.peek()) { - .lbrace => return self.parseBlock(), - .semicolon => { - const t = self.advanceTok(); - return Stmt{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .data = .{ .empty = {} } }; - }, - .@"if" => return self.parseIf(), - .@"while" => return self.parseWhile(), - .do => return self.parseDoWhile(), - .@"for" => return self.parseFor(), - .@"continue" => return self.parseContinue(), - .@"break" => return self.parseBreak(), - .@"return" => return self.parseReturn(), - .@"switch" => return self.parseSwitch(), - .throw => return self.parseThrow(), - .@"try" => return self.parseTry(), - .debugger => { - const t = self.advanceTok(); - self.semicolon(); - return Stmt{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .data = .{ .debugger = {} } }; - }, - .with => return self.parseWith(), - .function => return (try self.parseFunctionDecl(.normal, .stmt)).?, - .async => { - if (self.peekAt(1) == .function) { - return (try self.parseFunctionDecl(.async, .stmt)).?; - } - if (self.peekAt(1) == .@"*") return self.parseExpressionStatement(); - if (self.peekAt(1).isIdentifier()) { - return self.parseExpressionStatement(); - } - return self.parseExpressionStatement(); - }, - .class => return (try self.parseClassDecl(.stmt)).?, - .@"const", .let => return self.parseLexicalDecl(), - .@"var" => return self.parseVarStmt(), - .import => return try self.parseImport(), - .@"export" => return try self.parseExport(), - else => return self.parseExpressionStatement(), - } - } - - fn parseBlock(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - var stmts: [1024]Stmt = undefined; - var count: usize = 0; - - while (self.peek() != .rbrace and self.peek() != .eof) { - const s = try self.parseStatementListItem(); - stmts[count] = s; - count += 1; - if (count >= stmts.len) return ParseError.TooManyTokens; - } - - const end_tok = try self.expectTok(.rbrace); - const end = end_tok.loc.end; - - const block = Stmt{ - .loc = .{ .start = start, .end = end }, - .data = .{ .block = .{ .loc = .{ .start = start, .end = end }, .body = stmts[0..count] } }, - }; - return block; - } - - fn parseIf(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - try self.expect(.lparen); - const condition = try self.parseExpr(0); - try self.expect(.rparen); - const consequent = try self.statementPointer(); - var alternate: ?*const Stmt = null; - if (self.matchKeyword(.@"else")) { - alternate = try self.statementPointer(); - } - const end = if (alternate) |a| a.loc.end else consequent.loc.end; - return Stmt{ - .loc = .{ .start = start, .end = end }, - .data = .{ .@"if" = .{ .loc = .{ .start = start, .end = end }, .condition = condition, .consequent = consequent, .alternate = alternate } }, - }; - } - - fn parseWhile(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - try self.expect(.lparen); - const condition = try self.parseExpr(0); - try self.expect(.rparen); - const body = try self.statementPointer(); - return Stmt{ - .loc = .{ .start = start, .end = body.loc.end }, - .data = .{ .@"while" = .{ .loc = .{ .start = start, .end = body.loc.end }, .condition = condition, .body = body } }, - }; - } - - fn parseDoWhile(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - const body = try self.statementPointer(); - try self.expect(.@"while"); - try self.expect(.lparen); - const condition = try self.parseExpr(0); - try self.expect(.rparen); - self.semicolon(); - return Stmt{ - .loc = .{ .start = start, .end = condition.loc.end }, - .data = .{ .do_while = .{ .loc = .{ .start = start, .end = condition.loc.end }, .body = body, .condition = condition } }, - }; - } - - fn parseFor(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - try self.expect(.lparen); - - var for_init: ?ForInit = null; - var condition: ?*const Expr = null; - var update: ?*const Expr = null; - - if (self.peek() != .semicolon) { - if (self.peek() == .@"var" or self.peek() == .let or self.peek() == .@"const") { - const decl = try self.parseForDeclaration(); - for_init = .{ .decl = decl }; - } else { - const expr = try self.parseExpr(0); - for_init = .{ .expr = expr }; - } - } - - if (self.matchKeyword(.in)) { - const right = try self.parseExpr(0); - try self.expect(.rparen); - const body = try self.statementPointer(); - return Stmt{ - .loc = .{ .start = start, .end = body.loc.end }, - .data = .{ .for_in = .{ .loc = .{ .start = start, .end = body.loc.end }, .left = for_init.?, .right = right, .body = body } }, - }; - } - - if (self.matchKeyword(.of)) { - const right = try self.parseExpr(0); - try self.expect(.rparen); - const body = try self.statementPointer(); - return Stmt{ - .loc = .{ .start = start, .end = body.loc.end }, - .data = .{ .for_of = .{ .loc = .{ .start = start, .end = body.loc.end }, .await_token = false, .left = for_init.?, .right = right, .body = body } }, - }; - } - - try self.expect(.semicolon); - if (self.peek() != .semicolon and self.peek() != .rparen) { - condition = try self.parseExpr(0); - } - try self.expect(.semicolon); - if (self.peek() != .rparen) { - update = try self.parseExpr(0); - } - try self.expect(.rparen); - const body = try self.statementPointer(); - return Stmt{ - .loc = .{ .start = start, .end = body.loc.end }, - .data = .{ .@"for" = .{ .loc = .{ .start = start, .end = body.loc.end }, .init = for_init, .condition = condition, .update = update, .body = body } }, - }; - } - - 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 self.parseBindingPattern(); - var decl_init: ?*const Expr = null; - if (self.match(.@"=")) { - decl_init = try self.parseExpr(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 parseContinue(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - var label: ?[]const u8 = null; - if (self.peek() == .identifier and !self.isLineTerminatorBeforeNext()) { - const t = self.advanceTok(); - label = self.tokenSlice(t); - } - self.semicolon(); - return Stmt{ .loc = .{ .start = start, .end = start + 8 }, .data = .{ .@"continue" = .{ .loc = .{ .start = start, .end = start + 8 }, .label = label } } }; - } - - fn parseBreak(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - var label: ?[]const u8 = null; - if (self.peek() == .identifier and !self.isLineTerminatorBeforeNext()) { - const t = self.advanceTok(); - label = self.tokenSlice(t); - } - self.semicolon(); - return Stmt{ .loc = .{ .start = start, .end = start + 5 }, .data = .{ .@"break" = .{ .loc = .{ .start = start, .end = start + 5 }, .label = label } } }; - } - - fn parseReturn(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - var arg: ?*const Expr = null; - if (self.peek() != .semicolon and self.peek() != .rbrace and self.peek() != .eof and !self.isLineTerminatorBeforeNext()) { - arg = try self.parseExpr(0); - } - self.semicolon(); - return Stmt{ .loc = .{ .start = start, .end = arg.?.loc.end }, .data = .{ .@"return" = .{ .loc = .{ .start = start, .end = if (arg) |a| a.loc.end else start + 6 }, .arg = arg } } }; - } - - fn parseSwitch(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - try self.expect(.lparen); - const discriminant = try self.parseExpr(0); - try self.expect(.rparen); - try self.expect(.lbrace); - - var cases: [256]SwitchCase = undefined; - var case_count: usize = 0; - - while (self.peek() == .case or self.peek() == .default) { - const case_start = self.tok().loc.start; - if (self.matchKeyword(.case)) { - const case_test = try self.parseExpr(0); - try self.expect(.@":"); - var consequent: [128]Stmt = undefined; - var cons_count: usize = 0; - while (self.peek() != .case and self.peek() != .default and self.peek() != .rbrace and self.peek() != .eof) { - const s = try self.parseStatementListItem(); - consequent[cons_count] = s; - cons_count += 1; - } - cases[case_count] = .{ .loc = .{ .start = case_start, .end = self.tok().loc.start }, .condition = case_test, .consequent = consequent[0..cons_count] }; - case_count += 1; - } else if (self.matchKeyword(.default)) { - try self.expect(.@":"); - var consequent: [128]Stmt = undefined; - var cons_count: usize = 0; - while (self.peek() != .case and self.peek() != .default and self.peek() != .rbrace and self.peek() != .eof) { - const s = try self.parseStatementListItem(); - consequent[cons_count] = s; - cons_count += 1; - } - cases[case_count] = .{ .loc = .{ .start = case_start, .end = self.tok().loc.start }, .condition = null, .consequent = consequent[0..cons_count] }; - case_count += 1; - } - } - - const end_tok = try self.expectTok(.rbrace); - return Stmt{ - .loc = .{ .start = start, .end = end_tok.loc.end }, - .data = .{ .@"switch" = .{ .loc = .{ .start = start, .end = end_tok.loc.end }, .discriminant = discriminant, .cases = cases[0..case_count] } }, - }; - } - - fn parseThrow(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - if (self.isLineTerminatorBeforeNext()) return ParseError.ExpectedExpression; - const arg = try self.parseExpr(0); - self.semicolon(); - return Stmt{ .loc = .{ .start = start, .end = arg.loc.end }, .data = .{ .throw = .{ .loc = .{ .start = start, .end = arg.loc.end }, .arg = arg } } }; - } - - fn parseTry(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - const block = try self.parseBlockStmt(); - var handler: ?CatchClause = null; - var finalizer: ?BlockStmt = null; - - if (self.matchKeyword(.@"catch")) { - var param: ?Pat = null; - if (self.match(.lparen)) { - const pat = try self.parseBindingPattern(); - param = pat; - try self.expect(.rparen); - } - const catch_body = try self.parseBlockStmt(); - handler = .{ .loc = .{ .start = block.loc.start, .end = catch_body.loc.end }, .param = param, .body = catch_body }; - } - - if (self.matchKeyword(.finally)) { - const finally_body = try self.parseBlockStmt(); - finalizer = finally_body; - } - - const end = if (finalizer) |f| f.loc.end else if (handler) |h| h.loc.end else block.loc.end; - return Stmt{ - .loc = .{ .start = start, .end = end }, - .data = .{ .@"try" = .{ .loc = .{ .start = start, .end = end }, .block = block, .handler = handler, .finalizer = finalizer } }, - }; - } - - fn parseWith(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - try self.expect(.lparen); - const obj = try self.parseExpr(0); - try self.expect(.rparen); - const body = try self.statementPointer(); - return Stmt{ - .loc = .{ .start = start, .end = body.loc.end }, - .data = .{ .with = .{ .loc = .{ .start = start, .end = body.loc.end }, .obj = obj, .body = body } }, - }; - } - - fn parseBlockStmt(self: *@This()) ParseError!BlockStmt { - const start = try self.expectTok(.lbrace); - var stmts: [1024]Stmt = undefined; - var count: usize = 0; - - while (self.peek() != .rbrace and self.peek() != .eof) { - const s = try self.parseStatementListItem(); - stmts[count] = s; - count += 1; - if (count >= stmts.len) return ParseError.TooManyTokens; - } - - const end = try self.expectTok(.rbrace); - return .{ .loc = .{ .start = start.loc.start, .end = end.loc.end }, .body = stmts[0..count] }; - } - - fn parseVarStmt(self: *@This()) ParseError!Stmt { - const kind_tok = self.advanceTok(); - var decls: [128]VarDeclarator = undefined; - var count: usize = 0; - - while (true) { - const pat = try self.parseBindingPattern(); - var decl_init: ?*const Expr = null; - if (self.match(.@"=")) { - decl_init = try self.parseExpr(0); - } - decls[count] = .{ .loc = pat.loc, .id = pat, .init = decl_init }; - count += 1; - if (!self.match(.comma)) break; - } - - self.semicolon(); - const kind: VarKind = .@"var"; - return Stmt{ - .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, - .data = .{ .variable = .{ .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, .kind = kind, .decls = decls[0..count] } }, - }; - } - - fn parseLexicalDecl(self: *@This()) ParseError!Stmt { - const kind_tok = self.advanceTok(); - var decls: [128]VarDeclarator = undefined; - var count: usize = 0; - - while (true) { - const pat = try self.parseBindingPattern(); - var decl_init: ?*const Expr = null; - if (kind_tok.kind == .@"const") { - if (!self.match(.@"=")) return ParseError.ExpectedToken; - decl_init = try self.parseExpr(0); - } else if (self.match(.@"=")) { - decl_init = try self.parseExpr(0); - } - decls[count] = .{ .loc = pat.loc, .id = pat, .init = decl_init }; - count += 1; - if (!self.match(.comma)) break; - } - - self.semicolon(); - const kind: VarKind = switch (kind_tok.kind) { - .let => .let, - .@"const" => .@"const", - else => return ParseError.UnexpectedToken, - }; - return Stmt{ - .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, - .data = .{ .lexical = .{ .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, .kind = kind, .decls = decls[0..count] } }, - }; - } - - fn parseUsingStmt(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - var decls: [128]VarDeclarator = undefined; - var count: usize = 0; - - while (true) { - const id_tok = try self.expectTok(.identifier); - const id = self.tokenSlice(id_tok); - var decl_init: ?*const Expr = null; - if (self.match(.@"=")) { - decl_init = try self.parseExpr(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 (!self.match(.comma)) break; - } - - self.semicolon(); - return Stmt{ - .loc = .{ .start = start, .end = decls[count - 1].loc.end }, - .data = .{ .using = .{ .loc = .{ .start = start, .end = decls[count - 1].loc.end }, .decls = decls[0..count] } }, - }; - } - - fn parseExpressionStatement(self: *@This()) ParseError!Stmt { - const expr = try self.parseExpr(0); - self.semicolon(); - return Stmt{ .loc = expr.loc, .data = .{ .expr = .{ .loc = expr.loc, .expr = expr } } }; - } - - fn parseFunctionDecl(self: *@This(), fn_type_arg: FnType, mode: enum { stmt, expr }) ParseError!?Stmt { - const start = self.tok().loc.start; - var fn_type = fn_type_arg; - - if (fn_type == .async) { - _ = self.advanceTok(); // async - } else if (fn_type == .async_generator) { - _ = self.advanceTok(); // async - _ = self.advanceTok(); // * - } - - _ = self.advanceTok(); // function - - if (fn_type == .normal and self.peek() == .@"*") { - self.advance(); - fn_type = .generator; - } - - var id: ?[]const u8 = null; - if (self.peek().isIdentifier()) { - const id_tok = self.advanceTok(); - id = self.tokenSlice(id_tok); - } - - const params = try self.parseParams(); - const body = try self.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; - } - - fn parseFnExpr(self: *@This()) ParseError!*const Expr { - const start = self.advanceTok().loc.start; - - var fn_type: FnType = .normal; - if (self.peek() == .@"*") { - fn_type = .generator; - self.advance(); - } - - var id: ?[]const u8 = null; - if (self.peek() == .identifier or self.peek().isKeywordButNotLet()) { - const id_tok = self.advanceTok(); - id = self.tokenSlice(id_tok); - } - - const params = try self.parseParams(); - const body = try self.parseFunctionBody(); - - return self.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 }, - ); - } - - 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 self.parseBindingPattern(); - 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 self.parseBindingPattern(); - if (self.match(.@"=")) { - const expr = try self.parseExpr(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 self.parseBindingPattern(); - 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 self.parseBlockStmt(); - } - - fn parseClassDecl(self: *@This(), mode: enum { stmt, expr }) ParseError!?Stmt { - const start = self.advanceTok().loc.start; - - var id: ?[]const u8 = null; - if (self.peek() == .identifier) { - const id_tok = self.advanceTok(); - id = self.tokenSlice(id_tok); - } - - var super_class: ?*const Expr = null; - if (self.matchKeyword(.extends)) { - super_class = try self.parseExpr(0); - } - - try self.expect(.lbrace); - var elems: [128]ClassElem = undefined; - var elem_count: usize = 0; - - while (self.peek() != .rbrace and self.peek() != .eof) { - const elem = try self.parseClassElement(); - elems[elem_count] = elem; - elem_count += 1; - if (elem_count >= elems.len) return ParseError.TooManyTokens; - } - - const end_tok = try self.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; - } - - fn parseClassExpr(self: *@This()) ParseError!*const Expr { - const start = self.advanceTok().loc.start; - - var id: ?[]const u8 = null; - if (self.peek() == .identifier) { - const id_tok = self.advanceTok(); - id = self.tokenSlice(id_tok); - } - - var super_class: ?*const Expr = null; - if (self.matchKeyword(.extends)) { - super_class = try self.parseExpr(0); - } - - try self.expect(.lbrace); - var elems: [128]ClassElem = undefined; - var elem_count: usize = 0; - - while (self.peek() != .rbrace and self.peek() != .eof) { - const elem = try self.parseClassElement(); - elems[elem_count] = elem; - elem_count += 1; - } - - const end_tok = try self.expectTok(.rbrace); - return self.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 }, - ); - } - - fn parseClassElement(self: *@This()) ParseError!ClassElem { - const start = self.tok().loc.start; - const is_static = self.matchKeyword(.static); - - if (is_static and self.peek() == .lbrace) { - const body = try self.parseBlockStmt(); - return ClassElem{ .static_block = body }; - } - - if (self.matchKeyword(.get)) { - const key = try self.parsePropKey(); - try self.expect(.lparen); - try self.expect(.rparen); - const body = try self.parseFunctionBody(); - if (is_static) return ClassElem{ .static_get = .{ .key = key, .body = body } }; - return ClassElem{ .get = .{ .key = key, .body = body } }; - } - - if (self.matchKeyword(.set)) { - const key = try self.parsePropKey(); - try self.expect(.lparen); - const param = try self.parseBindingPattern(); - try self.expect(.rparen); - const body = try self.parseFunctionBody(); - if (is_static) return ClassElem{ .static_set = .{ .key = key, .param = param, .body = body } }; - return ClassElem{ .set = .{ .key = key, .param = param, .body = body } }; - } - - if (self.match(.@"*")) { - _ = try self.parsePropKey(); - try self.expect(.lparen); - const params = try self.parseParams(); - const body = try self.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 self.parsePropKey(); - - if (self.peek() == .lparen) { - const params = try self.parseParams(); - const body = try self.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 (self.match(.@"=")) { - value = try self.parseExpr(0); - } - if (self.peek() == .semicolon) self.advance(); - - const field = FieldDef{ .loc = .{ .start = start, .end = if (value) |v| v.loc.end else self.tok().loc.start }, .key = key, .value = value }; - if (is_static) return ClassElem{ .static_field = field }; - return ClassElem{ .field = field }; - } - - fn parseImport(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - var specifiers: [32]ImportSpecifier = undefined; - var spec_count: usize = 0; - - if (self.peek() != .string and self.peek() != .lparen) { - if (self.peek() == .identifier or self.peek().isKeyword()) { - const local_tok = self.advanceTok(); - const local = self.tokenSlice(local_tok); - specifiers[spec_count] = .{ .default = local }; - spec_count += 1; - if (self.match(.comma)) {} - } - - if (self.match(.@"*")) { - try self.expect(.identifier); // as - const ns_tok = try self.expectTok(.identifier); - const ns = self.tokenSlice(ns_tok); - specifiers[spec_count] = .{ .namespace = ns }; - spec_count += 1; - } else if (self.peek() == .lbrace) { - self.advance(); - if (self.peek() != .rbrace) { - while (true) { - const imported_tok = try self.expectTok(.identifier); - const imported = self.tokenSlice(imported_tok); - var local = imported; - if (self.match(.identifier)) { - local = self.tokenSlice(self.tokens[self.pos - 1]); - } - specifiers[spec_count] = .{ .named = .{ .imported = imported, .local = local } }; - spec_count += 1; - if (!self.match(.comma)) break; - } - } - try self.expect(.rbrace); - } - } - - var source: []const u8 = ""; - var attrs: [8]ImportAttribute = undefined; - var attr_count: usize = 0; - - if (self.matchKeyword(.from)) { - const str_tok = try self.expectTok(.string); - source = self.tokenSlice(str_tok); - } else if (self.peek() == .string) { - const str_tok = self.advanceTok(); - source = self.tokenSlice(str_tok); - } - - if (self.matchKeyword(.with)) { - try self.expect(.lbrace); - while (self.peek() != .rbrace and self.peek() != .eof) { - const key_tok = self.advanceTok(); - const key = self.tokenSlice(key_tok); - try self.expect(.@":"); - const val_tok = try self.expectTok(.string); - const val = self.tokenSlice(val_tok); - attrs[attr_count] = .{ .key = key, .value = val }; - attr_count += 1; - _ = self.match(.comma); - } - try self.expect(.rbrace); - } - - try self.expect(.semicolon); - return Stmt{ - .loc = .{ .start = start, .end = self.tok().loc.start }, - .data = .{ .import = .{ .loc = .{ .start = start, .end = self.tok().loc.start }, .specifiers = specifiers[0..spec_count], .source = source, .attributes = attrs[0..attr_count] } }, - }; - } - - fn parseExport(self: *@This()) ParseError!Stmt { - const start = self.advanceTok().loc.start; - - if (self.matchKeyword(.default)) { - if (self.peek() == .function or self.peek() == .async or self.peek() == .class) { - const decl = try self.parseStatementListItem(); - self.semicolon(); - return Stmt{ .loc = .{ .start = start, .end = decl.loc.end }, .data = decl.data }; - } - const expr = try self.parseExpr(0); - self.semicolon(); - return Stmt{ .loc = .{ .start = start, .end = expr.loc.end }, .data = .{ .expr = .{ .loc = expr.loc, .expr = expr } } }; - } - - if (self.peek() == .@"*") { - self.advance(); - try self.expect(.identifier); // from - const source_tok = try self.expectTok(.string); - _ = self.tokenSlice(source_tok); - try self.expect(.semicolon); - return Stmt{ .loc = .{ .start = start, .end = self.tok().loc.start }, .data = .{ .empty = {} } }; - } - - if (self.peek() == .lbrace) { - self.advance(); - while (self.peek() != .rbrace and self.peek() != .eof) { - _ = self.advanceTok(); - _ = self.match(.identifier); // as - if (!self.match(.comma)) break; - } - try self.expect(.rbrace); - if (self.matchKeyword(.from)) { - _ = self.advanceTok(); - } - try self.expect(.semicolon); - return Stmt{ .loc = .{ .start = start, .end = self.tok().loc.start }, .data = .{ .empty = {} } }; - } - - const decl = try self.parseStatementListItem(); - self.semicolon(); - return decl; - } - - fn parseExpr(self: *@This(), min_bp: u32) ParseError!*const Expr { - var left = try self.parsePrimaryExpr(); - - while (true) { - const cur_bp = self.bp(self.peek()); - if (cur_bp <= min_bp) break; - - switch (self.peek()) { - .@"=", - .@"+=", - .@"-=", - .@"*=", - .@"/=", - .@"%=", - .@"**=", - .@"<<=", - .@">>=", - .@">>>=", - .@"&=", - .@"|=", - .@"^=", - .@"&&=", - .@"||=", - .@"??=", - => { - self.advance(); - const op = self.assignOpFromToken(); - const right = try self.parseExpr(BP.assign); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .assign = .{ .loc = loc, .op = op, .left = left, .right = right } }, loc); - }, - - .@"?" => { - self.advance(); - const consequent = try self.parseExpr(0); - try self.expect(.@":"); - const alternate = try self.parseExpr(BP.conditional); - const loc: Loc = .{ .start = left.loc.start, .end = alternate.loc.end }; - left = self.arena.exprPtr(.{ .conditional = .{ .loc = loc, .condition = left, .consequent = consequent, .alternate = alternate } }, loc); - }, - - .@"??" => { - self.advance(); - const right = try self.parseExpr(BP.coalesce); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"??", .left = left, .right = right } }, loc); - }, - - .@"||" => { - self.advance(); - const right = try self.parseExpr(BP.log_or); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"||", .left = left, .right = right } }, loc); - }, - - .@"&&" => { - self.advance(); - const right = try self.parseExpr(BP.log_and); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"&&", .left = left, .right = right } }, loc); - }, - - .@"|" => { - self.advance(); - const right = try self.parseExpr(BP.bit_or); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"|", .left = left, .right = right } }, loc); - }, - - .@"^" => { - self.advance(); - const right = try self.parseExpr(BP.bit_xor); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"^", .left = left, .right = right } }, loc); - }, - - .@"&" => { - self.advance(); - const right = try self.parseExpr(BP.bit_and); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"&", .left = left, .right = right } }, loc); - }, - - .@"==" => { - self.advance(); - const right = try self.parseExpr(BP.equality); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"==", .left = left, .right = right } }, loc); - }, - - .@"!=" => { - self.advance(); - const right = try self.parseExpr(BP.equality); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"!=", .left = left, .right = right } }, loc); - }, - - .@"===" => { - self.advance(); - const right = try self.parseExpr(BP.equality); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"===", .left = left, .right = right } }, loc); - }, - - .@"!==" => { - self.advance(); - const right = try self.parseExpr(BP.equality); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"!==", .left = left, .right = right } }, loc); - }, - - .@"<" => { - self.advance(); - const right = try self.parseExpr(BP.relational); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<", .left = left, .right = right } }, loc); - }, - - .@">" => { - self.advance(); - const right = try self.parseExpr(BP.relational); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">", .left = left, .right = right } }, loc); - }, - - .@"<=" => { - self.advance(); - const right = try self.parseExpr(BP.relational); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<=", .left = left, .right = right } }, loc); - }, - - .@">=" => { - self.advance(); - const right = try self.parseExpr(BP.relational); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">=", .left = left, .right = right } }, loc); - }, - - .in => { - self.advance(); - const right = try self.parseExpr(BP.relational); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .in, .left = left, .right = right } }, loc); - }, - - .instanceof => { - self.advance(); - const right = try self.parseExpr(BP.relational); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .instanceof, .left = left, .right = right } }, loc); - }, - - .@"<<" => { - self.advance(); - const right = try self.parseExpr(BP.shift); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<<", .left = left, .right = right } }, loc); - }, - - .@">>" => { - self.advance(); - const right = try self.parseExpr(BP.shift); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">>", .left = left, .right = right } }, loc); - }, - - .@">>>" => { - self.advance(); - const right = try self.parseExpr(BP.shift); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">>>", .left = left, .right = right } }, loc); - }, - - .@"+" => { - self.advance(); - const right = try self.parseExpr(BP.additive); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"+", .left = left, .right = right } }, loc); - }, - - .@"-" => { - self.advance(); - const right = try self.parseExpr(BP.additive); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"-", .left = left, .right = right } }, loc); - }, - - .@"*" => { - self.advance(); - const right = try self.parseExpr(BP.multiplicative); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"*", .left = left, .right = right } }, loc); - }, - - .@"/" => { - self.advance(); - const right = try self.parseExpr(BP.multiplicative); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"/", .left = left, .right = right } }, loc); - }, - - .@"%" => { - self.advance(); - const right = try self.parseExpr(BP.multiplicative); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"%", .left = left, .right = right } }, loc); - }, - - .@"**" => { - self.advance(); - const right = try self.parseExpr(BP.exponentiation - 1); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"**", .left = left, .right = right } }, loc); - }, - - .lparen => { - self.advance(); - var args: [256]Expr = undefined; - var arg_count: usize = 0; - - if (self.peek() != .rparen) { - while (true) { - if (self.peek() == .@"...") { - self.advance(); - const arg = try self.parseExpr(0); - args[arg_count] = Expr{ .loc = arg.loc, .data = .{ .spread = .{ .loc = arg.loc, .arg = self.arena.exprPtr(arg.data, arg.loc) } } }; - arg_count += 1; - } else { - const arg = try self.parseExpr(0); - args[arg_count] = arg.*; - arg_count += 1; - } - if (!self.match(.comma)) break; - } - } - - try self.expect(.rparen); - const loc: Loc = .{ .start = left.loc.start, .end = self.tok().loc.start }; - left = self.arena.exprPtr(.{ .call = .{ .loc = loc, .callee = left, .args = args[0..arg_count], .optional = false } }, loc); - }, - - .@"?." => { - self.advance(); - if (self.peek() == .lparen) { - self.advance(); - var args: [256]Expr = undefined; - var arg_count: usize = 0; - if (self.peek() != .rparen) { - while (true) { - const arg = try self.parseExpr(0); - args[arg_count] = arg.*; - arg_count += 1; - if (!self.match(.comma)) break; - } - } - try self.expect(.rparen); - const loc: Loc = .{ .start = left.loc.start, .end = self.tok().loc.start }; - left = self.arena.exprPtr(.{ .call = .{ .loc = loc, .callee = left, .args = args[0..arg_count], .optional = true } }, loc); - } else if (self.peek() == .lbracket) { - self.advance(); - const expr = try self.parseExpr(0); - try self.expect(.rbracket); - const loc: Loc = .{ .start = left.loc.start, .end = self.tok().loc.start }; - left = self.arena.exprPtr(.{ .computed_member = .{ .loc = loc, .obj = left, .expr = expr } }, loc); - } else if (self.peek() == .identifier or self.peek().isKeyword()) { - const prop_tok = self.advanceTok(); - const prop = self.tokenSlice(prop_tok); - const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end }; - left = self.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .ident = prop } } }, loc); - } else if (self.peek() == .private_identifier) { - const prop_tok = self.advanceTok(); - const prop = self.tokenSlice(prop_tok); - const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end }; - left = self.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .private = prop } } }, loc); - } - }, - - .lbracket => { - self.advance(); - const expr = try self.parseExpr(0); - try self.expect(.rbracket); - const loc: Loc = .{ .start = left.loc.start, .end = self.tok().loc.start }; - left = self.arena.exprPtr(.{ .computed_member = .{ .loc = loc, .obj = left, .expr = expr } }, loc); - }, - - .@"." => { - self.advance(); - if (self.peek() == .private_identifier) { - const prop_tok = self.advanceTok(); - const prop = self.tokenSlice(prop_tok); - const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end }; - left = self.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .private = prop } } }, loc); - } else { - const prop_tok = try self.expectTok(.identifier); - const prop = self.tokenSlice(prop_tok); - const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end }; - left = self.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .ident = prop } } }, loc); - } - }, - - .template_head, .no_sub_template => { - const template = try self.parseTemplateLit(); - const loc: Loc = .{ .start = left.loc.start, .end = template.loc.end }; - left = self.arena.exprPtr(.{ .tagged_template = .{ .loc = loc, .tag = left, .quasi = template } }, loc); - }, - - .@"++" => { - if (!self.isLineTerminatorBeforeNext()) { - self.advance(); - const loc: Loc = .{ .start = left.loc.start, .end = left.loc.end + 2 }; - left = self.arena.exprPtr(.{ .update = .{ .loc = loc, .op = .@"++", .arg = left, .prefix = false } }, loc); - } else break; - }, - - .@"--" => { - if (!self.isLineTerminatorBeforeNext()) { - self.advance(); - const loc: Loc = .{ .start = left.loc.start, .end = left.loc.end + 2 }; - left = self.arena.exprPtr(.{ .update = .{ .loc = loc, .op = .@"--", .arg = left, .prefix = false } }, loc); - } else break; - }, - - .comma => { - self.advance(); - const right = try self.parseExpr(BP.comma); - const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; - var exprs: [2]Expr = .{ left.*, right.* }; - left = self.arena.exprPtr(.{ .sequence = .{ .loc = loc, .exprs = &exprs } }, loc); - }, - - else => break, - } - } - - return left; - } - - 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 self.parseObjLiteral(), - .lbracket => return self.parseArrayLiteral(), - .lparen => { - self.advance(); - if (self.peek() == .rparen) { - self.advance(); - if (self.peek() == .@"=>") { - return self.parseArrowExpr(.{ .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 self.parseExpr(0); - try self.expect(.rparen); - if (self.peek() == .@"=>") { - return self.parseArrowExpr(.{ .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 self.parseExpr(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 self.parseBindingPattern(); - params[count] = p; - count += 1; - } - try self.expect(.rparen); - if (self.peek() == .@"=>") { - return self.parseArrowExpr(.{ .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 self.parseArrowExpr(.{ .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 self.parseFnExpr(), - .class => return self.parseClassExpr(), - .async => { - self.advance(); - if (self.peek() == .function) { - return self.parseFnExpr(); - } - 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 self.parseArrowExpr(.{ .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 self.parseExpr(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 self.parseExpr(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 self.parseExpr(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 self.parseExpr(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 self.parseExpr(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 self.parseExpr(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 self.parseExpr(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 self.parseTemplateLit() }, .{ .start = start, .end = self.tok().loc.start }), - .import => { - self.advance(); - try self.expect(.lparen); - _ = try self.parseExpr(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 self.parseArrowExpr(.{ .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 parseArrowExpr(self: *@This(), params_info: struct { loc: Loc, params: []const Pat }) ParseError!*const Expr { - try self.expect(.@"=>"); - const async_token = false; - - if (self.peek() == .lbrace) { - const body = try self.parseFunctionBody(); - return self.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 self.parseExpr(0); - return self.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 }, - ); - } - - fn parseObjLiteral(self: *@This()) ParseError!*const Expr { - const start = self.advanceTok().loc.start; - var props: [128]Prop = undefined; - var count: usize = 0; - - while (self.peek() != .rbrace and self.peek() != .eof) { - if (self.peek() == .@"...") { - self.advance(); - const arg = try self.parseExpr(0); - props[count] = .{ .loc = arg.loc, .key = .{ .ident = "" }, .data = .{ .spread = .{ .loc = arg.loc, .arg = arg } } }; - count += 1; - } else if (self.peek() == .identifier) { - const name_tok = self.tok(); - const name = self.tokenSlice(name_tok); - self.advance(); - if (self.peek() == .@":") { - self.advance(); - const value = try self.parseExpr(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 (self.peek() == .lparen) { - const params = try self.parseParams(); - const body = try self.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 (self.peek() == .@"=") { - self.advance(); - const value = try self.parseExpr(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 (self.peek() == .comma or self.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 (self.peek() == .number or self.peek() == .string) { - const key = try self.parsePropKey(); - try self.expect(.@":"); - const value = try self.parseExpr(0); - props[count] = .{ .loc = .{ .start = start, .end = value.loc.end }, .key = key, .data = .{ .init = .{ .key = key, .value = value } } }; - count += 1; - } else if (self.peek() == .lbracket) { - const key = try self.parsePropKey(); - try self.expect(.@":"); - const value = try self.parseExpr(0); - props[count] = .{ .loc = .{ .start = start, .end = value.loc.end }, .key = key, .data = .{ .init = .{ .key = key, .value = value } } }; - count += 1; - } else if (self.matchKeyword(.get)) { - const key = try self.parsePropKey(); - try self.expect(.lparen); - try self.expect(.rparen); - const body = try self.parseFunctionBody(); - props[count] = .{ .loc = .{ .start = start, .end = body.loc.end }, .key = key, .data = .{ .get = .{ .key = key, .body = body } } }; - count += 1; - } else if (self.matchKeyword(.set)) { - const key = try self.parsePropKey(); - try self.expect(.lparen); - const param = try self.parseBindingPattern(); - try self.expect(.rparen); - const body = try self.parseFunctionBody(); - props[count] = .{ .loc = .{ .start = start, .end = body.loc.end }, .key = key, .data = .{ .set = .{ .key = key, .param = param, .body = body } } }; - count += 1; - } else if (self.match(.@"*")) { - const key = try self.parsePropKey(); - const params = try self.parseParams(); - const body = try self.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 (!self.match(.comma)) break; - } - - try self.expect(.rbrace); - return self.arena.exprPtr(.{ .object = .{ .loc = .{ .start = start, .end = self.tok().loc.start }, .props = props[0..count] } }, .{ .start = start, .end = self.tok().loc.start }); - } - - fn parseArrayLiteral(self: *@This()) ParseError!*const Expr { - const start = self.advanceTok().loc.start; - var elems: [256]?Expr = undefined; - var count: usize = 0; - - while (self.peek() != .rbracket and self.peek() != .eof) { - if (self.match(.comma)) { - elems[count] = null; - count += 1; - } else if (self.peek() == .@"...") { - self.advance(); - const arg = try self.parseExpr(0); - elems[count] = Expr{ .loc = arg.loc, .data = .{ .spread = .{ .loc = arg.loc, .arg = arg } } }; - count += 1; - if (!self.match(.comma)) break; - } else { - const expr = try self.parseExpr(0); - elems[count] = expr.*; - count += 1; - if (!self.match(.comma)) break; - } - } - - try self.expect(.rbracket); - return self.arena.exprPtr(.{ .array = .{ .loc = .{ .start = start, .end = self.tok().loc.start }, .elems = elems[0..count] } }, .{ .start = start, .end = self.tok().loc.start }); - } - - fn parseTemplateLit(self: *@This()) ParseError!TemplateLit { - const start = self.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 (self.peek()) { - .no_sub_template => { - const t = self.advanceTok(); - const slice = self.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 = self.advanceTok(); - const slice = self.tokenSlice(t); - quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = false }; - q_count += 1; - const expr = try self.parseExpr(0); - exprs[e_count] = expr.*; - e_count += 1; - try self.expect(.rbrace); - }, - .template_middle => { - const t = self.advanceTok(); - const slice = self.tokenSlice(t); - quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = false }; - q_count += 1; - const expr = try self.parseExpr(0); - exprs[e_count] = expr.*; - e_count += 1; - try self.expect(.rbrace); - }, - .template_tail => { - const t = self.advanceTok(); - const slice = self.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 = self.tok().loc.start }, .quasis = quasis[0..q_count], .exprs = exprs[0..e_count] }; - } - - 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 self.parseExpr(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 parseBindingPattern(self: *@This()) ParseError!Pat { - const start = self.tok().loc.start; - - switch (self.peek()) { - .identifier, .await, .yield => { - const t = self.advanceTok(); - const name = self.tokenSlice(t); - return Pat{ .loc = .{ .start = start, .end = t.loc.end }, .data = .{ .ident = name } }; - }, - .lbrace => { - self.advance(); - var props: [128]PatProp = undefined; - var count: usize = 0; - var rest: ?*const Pat = null; - - while (self.peek() != .rbrace and self.peek() != .eof) { - if (self.peek() == .@"...") { - self.advance(); - const pat = try self.parseBindingPattern(); - rest = self.arena.patPtr(pat.data, pat.loc); - break; - } - if (self.peek() == .identifier) { - const name_tok = self.tok(); - const name = self.tokenSlice(name_tok); - self.advance(); - if (self.peek() == .@":") { - self.advance(); - const val_pat = try self.parseBindingPattern(); - props[count] = .{ .key_value = .{ .key = .{ .ident = name }, .value = self.arena.patPtr(val_pat.data, val_pat.loc) } }; - count += 1; - } else { - props[count] = .{ .shorthand = name }; - count += 1; - } - } else if (self.peek() == .lbracket) { - const key = try self.parsePropKey(); - try self.expect(.@":"); - const val_pat = try self.parseBindingPattern(); - props[count] = .{ .key_value = .{ .key = key, .value = self.arena.patPtr(val_pat.data, val_pat.loc) } }; - count += 1; - } else { - break; - } - if (!self.match(.comma)) break; - } - - try self.expect(.rbrace); - return Pat{ .loc = .{ .start = start, .end = self.tok().loc.start }, .data = .{ .object = .{ .loc = .{ .start = start, .end = self.tok().loc.start }, .props = props[0..count], .rest = rest } } }; - }, - .lbracket => { - self.advance(); - var elems: [128]?Pat = undefined; - var count: usize = 0; - var rest: ?*const Pat = null; - - while (self.peek() != .rbracket and self.peek() != .eof) { - if (self.match(.comma)) { - elems[count] = null; - count += 1; - } else if (self.peek() == .@"...") { - self.advance(); - const pat = try self.parseBindingPattern(); - rest = self.arena.patPtr(pat.data, pat.loc); - break; - } else { - const pat = try self.parseBindingPattern(); - elems[count] = pat; - count += 1; - if (!self.match(.comma)) break; - } - } - - try self.expect(.rbracket); - return Pat{ .loc = .{ .start = start, .end = self.tok().loc.start }, .data = .{ .array = .{ .loc = .{ .start = start, .end = self.tok().loc.start }, .elems = elems[0..count], .rest = rest } } }; - }, - else => return ParseError.ExpectedParameterName, - } - } - - 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 self.parseStatement(); - 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); - break :blk try parser.parseProgram(); - }; - } - - 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 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 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 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 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 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 const IfStmt = struct { - loc: Loc, - condition: *const Expr, - consequent: *const Stmt, - alternate: ?*const Stmt, - - 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 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 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 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 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 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 const BreakStmt = struct { - loc: Loc, - label: ?[]const u8, -}; - -pub const ReturnStmt = struct { - loc: Loc, - arg: ?*const Expr, -}; - -pub const SwitchStmt = struct { - loc: Loc, - discriminant: *const Expr, - cases: []const SwitchCase, - - 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 const TryStmt = struct { - loc: Loc, - block: BlockStmt, - handler: ?CatchClause, - finalizer: ?BlockStmt, - - 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 const VarStmt = struct { - loc: Loc, - kind: VarKind, - decls: []const VarDeclarator, - - 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 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 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 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 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 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 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 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 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 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 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 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 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 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); - } -}; diff --git a/src/z/parser/js/ast/core.zig b/src/z/parser/js/ast/core.zig new file mode 100644 index 0000000..a278912 --- /dev/null +++ b/src/z/parser/js/ast/core.zig @@ -0,0 +1,162 @@ +const std = @import("std"); +const lex = @import("../lexical_grammar.zig"); +const Token = lex.Token; + +pub const Loc = @import("loc.zig").Loc; +pub const FormatOptions = @import("../formatter.zig").FormatOptions; +pub const Formatter = @import("../formatter.zig").Formatter; +pub const Parser = @import("../parser.zig").Parser; +pub const ParseError = @import("../parser.zig").ParseError; +pub const NodeArena = @import("../parser.zig").NodeArena; + +pub const stmt = @import("stmt.zig"); +pub const expr = @import("expr.zig"); +pub const pat = @import("pat.zig"); +pub const decl = @import("decl.zig"); +pub const lit = @import("lit.zig"); + +pub const StmtType = stmt.StmtType; +pub const Stmt = stmt.Stmt; +pub const BlockStmt = stmt.BlockStmt; +pub const ExprStmt = stmt.ExprStmt; +pub const IfStmt = stmt.IfStmt; +pub const WhileStmt = stmt.WhileStmt; +pub const DoWhileStmt = stmt.DoWhileStmt; +pub const ForStmt = stmt.ForStmt; +pub const ForInStmt = stmt.ForInStmt; +pub const ForOfStmt = stmt.ForOfStmt; +pub const ContinueStmt = stmt.ContinueStmt; +pub const BreakStmt = stmt.BreakStmt; +pub const ReturnStmt = stmt.ReturnStmt; +pub const SwitchStmt = stmt.SwitchStmt; +pub const SwitchCase = stmt.SwitchCase; +pub const ThrowStmt = stmt.ThrowStmt; +pub const TryStmt = stmt.TryStmt; +pub const CatchClause = stmt.CatchClause; +pub const LabelledStmt = stmt.LabelledStmt; +pub const WithStmt = stmt.WithStmt; +pub const VarStmt = stmt.VarStmt; +pub const UsingStmt = stmt.UsingStmt; +pub const VarKind = stmt.VarKind; +pub const VarDecl = stmt.VarDecl; +pub const LexicalDecl = stmt.LexicalDecl; +pub const UsingDecl = stmt.UsingDecl; +pub const VarDeclarator = stmt.VarDeclarator; +pub const ForInit = stmt.ForInit; + +pub const ExprType = expr.ExprType; +pub const Expr = expr.Expr; +pub const FnType = expr.FnType; +pub const FnExpr = expr.FnExpr; +pub const ArrowFn = expr.ArrowFn; +pub const ClassExpr = expr.ClassExpr; +pub const ClassElem = expr.ClassElem; +pub const FieldDef = expr.FieldDef; +pub const ArrayExpr = expr.ArrayExpr; +pub const ObjectExpr = expr.ObjectExpr; +pub const PropType = expr.PropType; +pub const Prop = expr.Prop; +pub const PropKey = expr.PropKey; +pub const PropInit = expr.PropInit; +pub const PropGet = expr.PropGet; +pub const PropSet = expr.PropSet; +pub const SpreadElem = expr.SpreadElem; +pub const MemberExpr = expr.MemberExpr; +pub const ComputedMember = expr.ComputedMember; +pub const CallExpr = expr.CallExpr; +pub const NewExpr = expr.NewExpr; +pub const ChainExpr = expr.ChainExpr; +pub const UnaryOp = expr.UnaryOp; +pub const UnaryExpr = expr.UnaryExpr; +pub const BinaryOp = expr.BinaryOp; +pub const BinaryExpr = expr.BinaryExpr; +pub const UpdateOp = expr.UpdateOp; +pub const UpdateExpr = expr.UpdateExpr; +pub const IfExpr = expr.IfExpr; +pub const AssignOp = expr.AssignOp; +pub const AssignExpr = expr.AssignExpr; +pub const SeqExpr = expr.SeqExpr; +pub const YieldExpr = expr.YieldExpr; +pub const AwaitExpr = expr.AwaitExpr; +pub const MetaProp = expr.MetaProp; +pub const TemplateLit = expr.TemplateLit; +pub const TemplateElem = expr.TemplateElem; +pub const TaggedTemplate = expr.TaggedTemplate; +pub const Decorator = expr.Decorator; + +pub const PatType = pat.PatType; +pub const Pat = pat.Pat; +pub const ObjectPat = pat.ObjectPat; +pub const PatProp = pat.PatProp; +pub const ArrayPat = pat.ArrayPat; +pub const AssignPat = pat.AssignPat; +pub const RestPat = pat.RestPat; + +pub const DeclType = decl.DeclType; +pub const Decl = decl.Decl; +pub const FnDecl = decl.FnDecl; +pub const ClassDecl = decl.ClassDecl; +pub const ImportDecl = decl.ImportDecl; +pub const ImportSpecifier = decl.ImportSpecifier; +pub const ImportAttribute = decl.ImportAttribute; +pub const ExportDecl = decl.ExportDecl; +pub const ExportSpecifier = decl.ExportSpecifier; +pub const ExportDefault = decl.ExportDefault; + +pub const LitType = lit.LitType; +pub const Lit = lit.Lit; +pub const Regex = lit.Regex; + +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..) |s, i| { + if (i > 0) f.newline(); + s.fmt(f); + } + } +}; diff --git a/src/z/parser/js/ast/decl.zig b/src/z/parser/js/ast/decl.zig new file mode 100644 index 0000000..a9a6117 --- /dev/null +++ b/src/z/parser/js/ast/decl.zig @@ -0,0 +1,432 @@ +const std = @import("std"); +const Loc = @import("loc.zig").Loc; +const Expr = @import("expr.zig").Expr; +const Pat = @import("pat.zig").Pat; +const Stmt = @import("stmt.zig").Stmt; +const BlockStmt = @import("stmt.zig").BlockStmt; +const VarStmt = @import("stmt.zig").VarStmt; +const VarDeclarator = @import("stmt.zig").VarDeclarator; +const VarKind = @import("stmt.zig").VarKind; +const FnType = @import("expr.zig").FnType; +const ClassElem = @import("expr.zig").ClassElem; +const Parser = @import("../parser.zig").Parser; +const Formatter = @import("../formatter.zig").Formatter; +const ParseError = @import("../parser.zig").ParseError; + +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.writeSpace(); + 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 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.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 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.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 VarDecl = struct { + loc: Loc, + kind: VarKind, + decls: []const VarDeclarator, +}; + +pub const UsingDecl = struct { + loc: Loc, + is_await: bool, + decls: []const VarDeclarator, +}; + +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); + } +}; diff --git a/src/z/parser/js/ast/expr.zig b/src/z/parser/js/ast/expr.zig new file mode 100644 index 0000000..2443b80 --- /dev/null +++ b/src/z/parser/js/ast/expr.zig @@ -0,0 +1,1245 @@ +const std = @import("std"); +const Loc = @import("loc.zig").Loc; +const BlockStmt = @import("stmt.zig").BlockStmt; +const Pat = @import("pat.zig").Pat; +const Regex = @import("lit.zig").Regex; +const Parser = @import("../parser.zig").Parser; +const Formatter = @import("../formatter.zig").Formatter; +const ParseError = @import("../parser.zig").ParseError; +const BP = @import("../parser.zig").BP; + +pub const ExprType = union(enum) { + identifier: []const u8, + this: void, + super: void, + null: void, + bool: bool, + number: f64, + string: []const u8, + regex: Regex, + array: ArrayExpr, + object: ObjectExpr, + func: FnExpr, + arrow: ArrowFn, + class: ClassExpr, + template: TemplateLit, + tagged_template: TaggedTemplate, + member: MemberExpr, + computed_member: ComputedMember, + call: CallExpr, + new: NewExpr, + chain: ChainExpr, + unary: UnaryExpr, + binary: BinaryExpr, + update: UpdateExpr, + conditional: IfExpr, + assign: AssignExpr, + sequence: SeqExpr, + spread: SpreadElem, + yield: YieldExpr, + await: AwaitExpr, + meta_prop: MetaProp, + parenthesized: *const Expr, + private_ident: []const u8, +}; + +pub const Expr = struct { + loc: Loc, + data: ExprType, + + pub fn parse(p: *Parser, min_bp: u32) ParseError!*const Expr { + var left = try p.parsePrimaryExpr(); + + while (true) { + const cur_bp = p.bp(p.peek()); + if (cur_bp <= min_bp) break; + + switch (p.peek()) { + .@"=", + .@"+=", + .@"-=", + .@"*=", + .@"/=", + .@"%=", + .@"**=", + .@"<<=", + .@">>=", + .@">>>=", + .@"&=", + .@"|=", + .@"^=", + .@"&&=", + .@"||=", + .@"??=", + => { + p.advance(); + const op = p.assignOpFromToken(); + const right = try Expr.parse(p, BP.assign); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .assign = .{ .loc = loc, .op = op, .left = left, .right = right } }, loc); + }, + + .@"?" => { + p.advance(); + const consequent = try Expr.parse(p, 0); + try p.expect(.@":"); + const alternate = try Expr.parse(p, BP.conditional); + const loc: Loc = .{ .start = left.loc.start, .end = alternate.loc.end }; + left = p.arena.exprPtr(.{ .conditional = .{ .loc = loc, .condition = left, .consequent = consequent, .alternate = alternate } }, loc); + }, + + .@"??" => { + p.advance(); + const right = try Expr.parse(p, BP.coalesce); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"??", .left = left, .right = right } }, loc); + }, + + .@"||" => { + p.advance(); + const right = try Expr.parse(p, BP.log_or); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"||", .left = left, .right = right } }, loc); + }, + + .@"&&" => { + p.advance(); + const right = try Expr.parse(p, BP.log_and); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"&&", .left = left, .right = right } }, loc); + }, + + .@"|" => { + p.advance(); + const right = try Expr.parse(p, BP.bit_or); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"|", .left = left, .right = right } }, loc); + }, + + .@"^" => { + p.advance(); + const right = try Expr.parse(p, BP.bit_xor); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"^", .left = left, .right = right } }, loc); + }, + + .@"&" => { + p.advance(); + const right = try Expr.parse(p, BP.bit_and); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"&", .left = left, .right = right } }, loc); + }, + + .@"==" => { + p.advance(); + const right = try Expr.parse(p, BP.equality); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"==", .left = left, .right = right } }, loc); + }, + + .@"!=" => { + p.advance(); + const right = try Expr.parse(p, BP.equality); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"!=", .left = left, .right = right } }, loc); + }, + + .@"===" => { + p.advance(); + const right = try Expr.parse(p, BP.equality); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"===", .left = left, .right = right } }, loc); + }, + + .@"!==" => { + p.advance(); + const right = try Expr.parse(p, BP.equality); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"!==", .left = left, .right = right } }, loc); + }, + + .@"<" => { + p.advance(); + const right = try Expr.parse(p, BP.relational); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<", .left = left, .right = right } }, loc); + }, + + .@">" => { + p.advance(); + const right = try Expr.parse(p, BP.relational); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">", .left = left, .right = right } }, loc); + }, + + .@"<=" => { + p.advance(); + const right = try Expr.parse(p, BP.relational); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<=", .left = left, .right = right } }, loc); + }, + + .@">=" => { + p.advance(); + const right = try Expr.parse(p, BP.relational); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">=", .left = left, .right = right } }, loc); + }, + + .in => { + p.advance(); + const right = try Expr.parse(p, BP.relational); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .in, .left = left, .right = right } }, loc); + }, + + .instanceof => { + p.advance(); + const right = try Expr.parse(p, BP.relational); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .instanceof, .left = left, .right = right } }, loc); + }, + + .@"<<" => { + p.advance(); + const right = try Expr.parse(p, BP.shift); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"<<", .left = left, .right = right } }, loc); + }, + + .@">>" => { + p.advance(); + const right = try Expr.parse(p, BP.shift); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">>", .left = left, .right = right } }, loc); + }, + + .@">>>" => { + p.advance(); + const right = try Expr.parse(p, BP.shift); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@">>>", .left = left, .right = right } }, loc); + }, + + .@"+" => { + p.advance(); + const right = try Expr.parse(p, BP.additive); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"+", .left = left, .right = right } }, loc); + }, + + .@"-" => { + p.advance(); + const right = try Expr.parse(p, BP.additive); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"-", .left = left, .right = right } }, loc); + }, + + .@"*" => { + p.advance(); + const right = try Expr.parse(p, BP.multiplicative); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"*", .left = left, .right = right } }, loc); + }, + + .@"/" => { + p.advance(); + const right = try Expr.parse(p, BP.multiplicative); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"/", .left = left, .right = right } }, loc); + }, + + .@"%" => { + p.advance(); + const right = try Expr.parse(p, BP.multiplicative); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"%", .left = left, .right = right } }, loc); + }, + + .@"**" => { + p.advance(); + const right = try Expr.parse(p, BP.exponentiation - 1); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + left = p.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"**", .left = left, .right = right } }, loc); + }, + + .lparen => { + p.advance(); + var args: [256]Expr = undefined; + var arg_count: usize = 0; + + if (p.peek() != .rparen) { + while (true) { + if (p.peek() == .@"...") { + p.advance(); + const arg = try Expr.parse(p, 0); + args[arg_count] = Expr{ .loc = arg.loc, .data = .{ .spread = .{ .loc = arg.loc, .arg = p.arena.exprPtr(arg.data, arg.loc) } } }; + arg_count += 1; + } else { + const arg = try Expr.parse(p, 0); + args[arg_count] = arg.*; + arg_count += 1; + } + if (!p.match(.comma)) break; + } + } + + try p.expect(.rparen); + const loc: Loc = .{ .start = left.loc.start, .end = p.tok().loc.start }; + left = p.arena.exprPtr(.{ .call = .{ .loc = loc, .callee = left, .args = args[0..arg_count], .optional = false } }, loc); + }, + + .@"?." => { + p.advance(); + if (p.peek() == .lparen) { + p.advance(); + var args: [256]Expr = undefined; + var arg_count: usize = 0; + if (p.peek() != .rparen) { + while (true) { + const arg = try Expr.parse(p, 0); + args[arg_count] = arg.*; + arg_count += 1; + if (!p.match(.comma)) break; + } + } + try p.expect(.rparen); + const loc: Loc = .{ .start = left.loc.start, .end = p.tok().loc.start }; + left = p.arena.exprPtr(.{ .call = .{ .loc = loc, .callee = left, .args = args[0..arg_count], .optional = true } }, loc); + } else if (p.peek() == .lbracket) { + p.advance(); + const expr = try Expr.parse(p, 0); + try p.expect(.rbracket); + const loc: Loc = .{ .start = left.loc.start, .end = p.tok().loc.start }; + left = p.arena.exprPtr(.{ .computed_member = .{ .loc = loc, .obj = left, .expr = expr } }, loc); + } else if (p.peek() == .identifier or p.peek().isKeyword()) { + const prop_tok = p.advanceTok(); + const prop = p.tokenSlice(prop_tok); + const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end }; + left = p.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .ident = prop } } }, loc); + } else if (p.peek() == .private_identifier) { + const prop_tok = p.advanceTok(); + const prop = p.tokenSlice(prop_tok); + const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end }; + left = p.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .private = prop } } }, loc); + } + }, + + .lbracket => { + p.advance(); + const expr = try Expr.parse(p, 0); + try p.expect(.rbracket); + const loc: Loc = .{ .start = left.loc.start, .end = p.tok().loc.start }; + left = p.arena.exprPtr(.{ .computed_member = .{ .loc = loc, .obj = left, .expr = expr } }, loc); + }, + + .@"." => { + p.advance(); + if (p.peek() == .private_identifier) { + const prop_tok = p.advanceTok(); + const prop = p.tokenSlice(prop_tok); + const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end }; + left = p.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .private = prop } } }, loc); + } else { + const prop_tok = try p.expectTok(.identifier); + const prop = p.tokenSlice(prop_tok); + const loc: Loc = .{ .start = left.loc.start, .end = prop_tok.loc.end }; + left = p.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .ident = prop } } }, loc); + } + }, + + .template_head, .no_sub_template => { + const template = try TemplateLit.parse(p); + const loc: Loc = .{ .start = left.loc.start, .end = template.loc.end }; + left = p.arena.exprPtr(.{ .tagged_template = .{ .loc = loc, .tag = left, .quasi = template } }, loc); + }, + + .@"++" => { + if (!p.isLineTerminatorBeforeNext()) { + p.advance(); + const loc: Loc = .{ .start = left.loc.start, .end = left.loc.end + 2 }; + left = p.arena.exprPtr(.{ .update = .{ .loc = loc, .op = .@"++", .arg = left, .prefix = false } }, loc); + } else break; + }, + + .@"--" => { + if (!p.isLineTerminatorBeforeNext()) { + p.advance(); + const loc: Loc = .{ .start = left.loc.start, .end = left.loc.end + 2 }; + left = p.arena.exprPtr(.{ .update = .{ .loc = loc, .op = .@"--", .arg = left, .prefix = false } }, loc); + } else break; + }, + + .comma => { + p.advance(); + const right = try Expr.parse(p, BP.comma); + const loc: Loc = .{ .start = left.loc.start, .end = right.loc.end }; + var exprs: [2]Expr = .{ left.*, right.* }; + left = p.arena.exprPtr(.{ .sequence = .{ .loc = loc, .exprs = &exprs } }, loc); + }, + + else => break, + } + } + + return left; + } + + pub fn fmt(self: @This(), f: *Formatter) void { + switch (self.data) { + .identifier => |name| f.write(name), + .this => f.write("this"), + .super => f.write("super"), + .null => f.write("null"), + .bool => |b| f.write(if (b) "true" else "false"), + .number => |n| { + var buf: [64]u8 = undefined; + f.write(std.fmt.bufPrint(&buf, "{d}", .{n}) catch "0"); + }, + .string => |s| { + f.writeChar('"'); + f.write(s); + f.writeChar('"'); + }, + .regex => |r| { + f.writeChar('/'); + f.write(r.pattern); + f.writeChar('/'); + f.write(r.flags); + }, + .array => |a| a.fmt(f), + .object => |o| o.fmt(f), + .func => |fn_expr| fn_expr.fmt(f), + .arrow => |arrow| arrow.fmt(f), + .class => |c| c.fmt(f), + .template => |t| t.fmt(f), + .tagged_template => |tt| tt.fmt(f), + .member => |m| m.fmt(f), + .computed_member => |cm| cm.fmt(f), + .call => |call| call.fmt(f), + .new => |n| { + f.write("new "); + n.callee.*.fmt(f); + f.writeChar('('); + for (n.args, 0..) |a, i| { + if (i > 0) f.write(", "); + a.fmt(f); + } + f.writeChar(')'); + }, + .chain => |c| c.expr.*.fmt(f), + .unary => |u| { + if (u.prefix) { + f.write(@tagName(u.op)); + u.arg.*.fmt(f); + } else { + u.arg.*.fmt(f); + f.write(@tagName(u.op)); + } + }, + .binary => |b| { + f.writeChar('('); + b.left.*.fmt(f); + f.writeSpace(); + f.write(@tagName(b.op)); + f.writeSpace(); + b.right.*.fmt(f); + f.writeChar(')'); + }, + .update => |u| { + const s = if (u.op == .@"++") "++" else "--"; + if (u.prefix) { + f.write(s); + u.arg.*.fmt(f); + } else { + u.arg.*.fmt(f); + f.write(s); + } + }, + .conditional => |c| { + c.condition.*.fmt(f); + f.write(" ? "); + c.consequent.*.fmt(f); + f.write(" : "); + c.alternate.*.fmt(f); + }, + .assign => |a| { + a.left.*.fmt(f); + f.writeSpace(); + f.write(@tagName(a.op)); + f.writeSpace(); + a.right.*.fmt(f); + }, + .sequence => |s| { + for (s.exprs, 0..) |e, i| { + if (i > 0) f.write(", "); + e.fmt(f); + } + }, + .spread => |s| { + f.write("..."); + s.arg.*.fmt(f); + }, + .yield => |y| { + f.write("yield"); + if (y.delegate) f.write("*"); + if (y.arg) |a| { + f.writeSpace(); + a.*.fmt(f); + } + }, + .await => |a| { + f.write("await "); + a.arg.*.fmt(f); + }, + .meta_prop => |m| { + f.write(m.meta); + f.writeChar('.'); + f.write(m.prop); + }, + .parenthesized => |p| { + f.writeChar('('); + p.*.fmt(f); + f.writeChar(')'); + }, + .private_ident => |name| f.write(name), + } + } +}; + +pub const FnType = union(enum) { + normal: void, + generator: void, + async: void, + async_generator: void, +}; + +pub const FnExpr = struct { + loc: Loc, + fn_type: FnType, + id: ?[]const u8, + params: []const Pat, + body: BlockStmt, + + pub fn parse(p: *Parser) ParseError!*const Expr { + const start = p.advanceTok().loc.start; + + var fn_type: FnType = .normal; + if (p.peek() == .@"*") { + fn_type = .generator; + p.advance(); + } + + var id: ?[]const u8 = null; + if (p.peek() == .identifier or p.peek().isKeywordButNotLet()) { + const id_tok = p.advanceTok(); + id = p.tokenSlice(id_tok); + } + + const params = try p.parseParams(); + const body = try p.parseFunctionBody(); + + return p.arena.exprPtr( + .{ .func = .{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = fn_type, .id = id, .params = params, .body = body } }, + .{ .start = start, .end = body.loc.end }, + ); + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("function"); + if (self.fn_type == .generator or self.fn_type == .async_generator) { + f.write("*"); + } + if (self.fn_type == .async or self.fn_type == .async_generator) { + f.write(" async"); + } + if (self.id) |id| { + f.writeSpace(); + f.write(id); + } + f.writeChar('('); + for (self.params, 0..) |p, i| { + if (i > 0) f.write(", "); + p.fmt(f); + } + f.write(") "); + self.body.fmt(f); + } +}; + +pub const ArrowFn = struct { + loc: Loc, + async_token: bool, + params: []const Pat, + body: union(enum) { + block: BlockStmt, + expr: *const Expr, + }, + + pub fn parse(p: *Parser, params_info: struct { loc: Loc, params: []const Pat }) ParseError!*const Expr { + try p.expect(.@"=>"); + const async_token = false; + + if (p.peek() == .lbrace) { + const body = try p.parseFunctionBody(); + return p.arena.exprPtr( + .{ .arrow = .{ .loc = .{ .start = params_info.loc.start, .end = body.loc.end }, .async_token = async_token, .params = params_info.params, .body = .{ .block = body } } }, + .{ .start = params_info.loc.start, .end = body.loc.end }, + ); + } + + const expr = try Expr.parse(p, 0); + return p.arena.exprPtr( + .{ .arrow = .{ .loc = .{ .start = params_info.loc.start, .end = expr.loc.end }, .async_token = async_token, .params = params_info.params, .body = .{ .expr = expr } } }, + .{ .start = params_info.loc.start, .end = expr.loc.end }, + ); + } + + pub fn fmt(self: @This(), f: *Formatter) void { + if (self.async_token) f.write("async "); + f.writeChar('('); + for (self.params, 0..) |p, i| { + if (i > 0) f.write(", "); + p.fmt(f); + } + f.write(") => "); + switch (self.body) { + .block => |b| b.fmt(f), + .expr => |e| e.*.fmt(f), + } + } +}; + +pub const ClassExpr = struct { + loc: Loc, + id: ?[]const u8, + super_class: ?*const Expr, + body: []const ClassElem, + + pub fn parse(p: *Parser) ParseError!*const Expr { + const start = p.advanceTok().loc.start; + + var id: ?[]const u8 = null; + if (p.peek() == .identifier) { + const id_tok = p.advanceTok(); + id = p.tokenSlice(id_tok); + } + + var super_class: ?*const Expr = null; + if (p.matchKeyword(.extends)) { + super_class = try Expr.parse(p, 0); + } + + try p.expect(.lbrace); + var elems: [128]ClassElem = undefined; + var elem_count: usize = 0; + + while (p.peek() != .rbrace and p.peek() != .eof) { + const elem = try ClassElem.parse(p); + elems[elem_count] = elem; + elem_count += 1; + } + + const end_tok = try p.expectTok(.rbrace); + return p.arena.exprPtr( + .{ .class = .{ .loc = .{ .start = start, .end = end_tok.loc.end }, .id = id, .super_class = super_class, .body = elems[0..elem_count] } }, + .{ .start = start, .end = end_tok.loc.end }, + ); + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("class"); + if (self.id) |id| { + f.writeSpace(); + f.write(id); + } + if (self.super_class) |sc| { + f.write(" extends "); + sc.*.fmt(f); + } + f.write(" {"); + f.newline(); + f.indent_level += 1; + for (self.body) |elem| { + f.writeIndent(); + elem.fmt(f); + f.newline(); + } + f.indent_level -= 1; + f.writeIndent(); + f.writeChar('}'); + } +}; + +pub const ClassElem = union(enum) { + method: FnExpr, + get: PropGet, + set: PropSet, + field: FieldDef, + static_block: BlockStmt, + static_method: FnExpr, + static_get: PropGet, + static_set: PropSet, + static_field: FieldDef, + + pub fn parse(p: *Parser) ParseError!ClassElem { + const start = p.tok().loc.start; + const is_static = p.matchKeyword(.static); + + if (is_static and p.peek() == .lbrace) { + const body = try p.parseFunctionBody(); + return ClassElem{ .static_block = body }; + } + + if (p.matchKeyword(.get)) { + const key = try p.parsePropKey(); + try p.expect(.lparen); + try p.expect(.rparen); + const body = try p.parseFunctionBody(); + if (is_static) return ClassElem{ .static_get = .{ .key = key, .body = body } }; + return ClassElem{ .get = .{ .key = key, .body = body } }; + } + + if (p.matchKeyword(.set)) { + const key = try p.parsePropKey(); + try p.expect(.lparen); + const param = try Pat.parse(p); + try p.expect(.rparen); + const body = try p.parseFunctionBody(); + if (is_static) return ClassElem{ .static_set = .{ .key = key, .param = param, .body = body } }; + return ClassElem{ .set = .{ .key = key, .param = param, .body = body } }; + } + + if (p.match(.@"*")) { + _ = try p.parsePropKey(); + try p.expect(.lparen); + const params = try p.parseParams(); + const body = try p.parseFunctionBody(); + const fn_expr = FnExpr{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = .generator, .id = null, .params = params, .body = body }; + if (is_static) return ClassElem{ .static_method = fn_expr }; + return ClassElem{ .method = fn_expr }; + } + + const key = try p.parsePropKey(); + + if (p.peek() == .lparen) { + const params = try p.parseParams(); + const body = try p.parseFunctionBody(); + const fn_expr = FnExpr{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = .normal, .id = null, .params = params, .body = body }; + if (is_static) return ClassElem{ .static_method = fn_expr }; + return ClassElem{ .method = fn_expr }; + } + + var value: ?*const Expr = null; + if (p.match(.@"=")) { + value = try Expr.parse(p, 0); + } + if (p.peek() == .semicolon) p.advance(); + + const field = FieldDef{ .loc = .{ .start = start, .end = if (value) |v| v.loc.end else p.tok().loc.start }, .key = key, .value = value }; + if (is_static) return ClassElem{ .static_field = field }; + return ClassElem{ .field = field }; + } +}; + +pub const FieldDef = struct { + loc: Loc, + key: PropKey, + value: ?*const Expr, +}; + +pub const ArrayExpr = struct { + loc: Loc, + elems: []const ?Expr, + + pub fn parse(p: *Parser) ParseError!*const Expr { + const start = p.advanceTok().loc.start; + var elems: [256]?Expr = undefined; + var count: usize = 0; + + while (p.peek() != .rbracket and p.peek() != .eof) { + if (p.match(.comma)) { + elems[count] = null; + count += 1; + } else if (p.peek() == .@"...") { + p.advance(); + const arg = try Expr.parse(p, 0); + elems[count] = Expr{ .loc = arg.loc, .data = .{ .spread = .{ .loc = arg.loc, .arg = arg } } }; + count += 1; + if (!p.match(.comma)) break; + } else { + const expr = try Expr.parse(p, 0); + elems[count] = expr.*; + count += 1; + if (!p.match(.comma)) break; + } + } + + try p.expect(.rbracket); + return p.arena.exprPtr(.{ .array = .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .elems = elems[0..count] } }, .{ .start = start, .end = p.tok().loc.start }); + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.writeChar('['); + for (self.elems, 0..) |elem, i| { + if (i > 0) f.write(", "); + if (elem) |e| e.fmt(f); + } + f.writeChar(']'); + } +}; + +pub const ObjectExpr = struct { + loc: Loc, + props: []const Prop, + + pub fn parse(p: *Parser) ParseError!*const Expr { + const start = p.advanceTok().loc.start; + var props: [128]Prop = undefined; + var count: usize = 0; + + while (p.peek() != .rbrace and p.peek() != .eof) { + if (p.peek() == .@"...") { + p.advance(); + const arg = try Expr.parse(p, 0); + props[count] = .{ .loc = arg.loc, .key = .{ .ident = "" }, .data = .{ .spread = .{ .loc = arg.loc, .arg = arg } } }; + count += 1; + } else if (p.peek() == .identifier) { + const name_tok = p.tok(); + const name = p.tokenSlice(name_tok); + p.advance(); + if (p.peek() == .@":") { + p.advance(); + const value = try Expr.parse(p, 0); + props[count] = .{ .loc = .{ .start = name_tok.loc.start, .end = value.loc.end }, .key = .{ .ident = name }, .data = .{ .init = .{ .key = .{ .ident = name }, .value = value } } }; + count += 1; + } else if (p.peek() == .lparen) { + const params = try p.parseParams(); + const body = try p.parseFunctionBody(); + props[count] = .{ .loc = .{ .start = name_tok.loc.start, .end = body.loc.end }, .key = .{ .ident = name }, .data = .{ .method = .{ .loc = .{ .start = name_tok.loc.start, .end = body.loc.end }, .fn_type = .normal, .id = null, .params = params, .body = body } } }; + count += 1; + } else if (p.peek() == .@"=") { + p.advance(); + const value = try Expr.parse(p, 0); + props[count] = .{ .loc = .{ .start = name_tok.loc.start, .end = value.loc.end }, .key = .{ .ident = name }, .data = .{ .init = .{ .key = .{ .ident = name }, .value = value } } }; + count += 1; + } else if (p.peek() == .comma or p.peek() == .rbrace) { + props[count] = .{ .loc = .{ .start = name_tok.loc.start, .end = name_tok.loc.end }, .key = .{ .ident = name }, .data = .{ .shorthand = name } }; + count += 1; + } + } else if (p.peek() == .number or p.peek() == .string) { + const key = try p.parsePropKey(); + try p.expect(.@":"); + const value = try Expr.parse(p, 0); + props[count] = .{ .loc = .{ .start = start, .end = value.loc.end }, .key = key, .data = .{ .init = .{ .key = key, .value = value } } }; + count += 1; + } else if (p.peek() == .lbracket) { + const key = try p.parsePropKey(); + try p.expect(.@":"); + const value = try Expr.parse(p, 0); + props[count] = .{ .loc = .{ .start = start, .end = value.loc.end }, .key = key, .data = .{ .init = .{ .key = key, .value = value } } }; + count += 1; + } else if (p.matchKeyword(.get)) { + const key = try p.parsePropKey(); + try p.expect(.lparen); + try p.expect(.rparen); + const body = try p.parseFunctionBody(); + props[count] = .{ .loc = .{ .start = start, .end = body.loc.end }, .key = key, .data = .{ .get = .{ .key = key, .body = body } } }; + count += 1; + } else if (p.matchKeyword(.set)) { + const key = try p.parsePropKey(); + try p.expect(.lparen); + const param = try Pat.parse(p); + try p.expect(.rparen); + const body = try p.parseFunctionBody(); + props[count] = .{ .loc = .{ .start = start, .end = body.loc.end }, .key = key, .data = .{ .set = .{ .key = key, .param = param, .body = body } } }; + count += 1; + } else if (p.match(.@"*")) { + const key = try p.parsePropKey(); + const params = try p.parseParams(); + const body = try p.parseFunctionBody(); + props[count] = .{ .loc = .{ .start = start, .end = body.loc.end }, .key = key, .data = .{ .method = .{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = .generator, .id = null, .params = params, .body = body } } }; + count += 1; + } else { + break; + } + + if (!p.match(.comma)) break; + } + + try p.expect(.rbrace); + return p.arena.exprPtr(.{ .object = .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .props = props[0..count] } }, .{ .start = start, .end = p.tok().loc.start }); + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.writeChar('{'); + for (self.props, 0..) |p, i| { + if (i > 0) f.write(", "); + p.fmt(f); + } + f.writeChar('}'); + } +}; + +pub const PropType = union(enum) { + init: PropInit, + get: PropGet, + set: PropSet, + shorthand: []const u8, + spread: SpreadElem, + method: FnExpr, +}; + +pub const Prop = struct { + loc: Loc, + key: PropKey, + data: PropType, + + pub fn fmt(self: @This(), f: *Formatter) void { + switch (self.data) { + .init => |p_init| { + self.key.fmt(f); + f.write(": "); + p_init.value.*.fmt(f); + }, + .get => |g| g.fmt(f), + .set => |s| s.fmt(f), + .shorthand => |name| f.write(name), + .spread => |sp| { + f.write("..."); + sp.arg.*.fmt(f); + }, + .method => |m| m.fmt(f), + } + } +}; + +pub const PropKey = union(enum) { + ident: []const u8, + string: []const u8, + number: f64, + computed: *const Expr, + private: []const u8, +}; + +pub const PropInit = struct { + key: PropKey, + value: *const Expr, +}; + +pub const PropGet = struct { + key: PropKey, + body: BlockStmt, + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("get "); + self.key.fmt(f); + f.write("() "); + self.body.fmt(f); + } +}; + +pub const PropSet = struct { + key: PropKey, + param: Pat, + body: BlockStmt, + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("set "); + self.key.fmt(f); + f.writeChar('('); + self.param.fmt(f); + f.write(") "); + self.body.fmt(f); + } +}; + +pub const SpreadElem = struct { + loc: Loc, + arg: *const Expr, +}; + +pub const MemberExpr = struct { + loc: Loc, + obj: *const Expr, + prop: union(enum) { + ident: []const u8, + private: []const u8, + }, + + pub fn fmt(self: @This(), f: *Formatter) void { + self.obj.*.fmt(f); + switch (self.prop) { + .ident => |name| { + f.writeChar('.'); + f.write(name); + }, + .private => |name| { + f.writeChar('.'); + f.write(name); + }, + } + } +}; + +pub const ComputedMember = struct { + loc: Loc, + obj: *const Expr, + expr: *const Expr, + + pub fn fmt(self: @This(), f: *Formatter) void { + self.obj.*.fmt(f); + f.writeChar('['); + self.expr.*.fmt(f); + f.writeChar(']'); + } +}; + +pub const CallExpr = struct { + loc: Loc, + callee: *const Expr, + args: []const Expr, + optional: bool, + + pub fn fmt(self: @This(), f: *Formatter) void { + self.callee.*.fmt(f); + if (self.optional) { + f.write("?.("); + } else { + f.writeChar('('); + } + for (self.args, 0..) |a, i| { + if (i > 0) f.write(", "); + a.fmt(f); + } + f.writeChar(')'); + } +}; + +pub const NewExpr = struct { + loc: Loc, + callee: *const Expr, + args: []const Expr, +}; + +pub const ChainExpr = struct { + loc: Loc, + expr: *const Expr, +}; + +pub const UnaryOp = enum { + @"-", + @"+", + @"!", + @"~", + typeof, + void, + delete, +}; + +pub const UnaryExpr = struct { + loc: Loc, + op: UnaryOp, + arg: *const Expr, + prefix: bool, +}; + +pub const BinaryOp = enum { + @"==", + @"!=", + @"===", + @"!==", + @"<", + @"<=", + @">", + @">=", + @"<<", + @">>", + @">>>", + @"+", + @"-", + @"*", + @"/", + @"%", + @"**", + @"|", + @"^", + @"&", + in, + instanceof, + @"||", + @"&&", + @"??", +}; + +pub const BinaryExpr = struct { + loc: Loc, + op: BinaryOp, + left: *const Expr, + right: *const Expr, +}; + +pub const UpdateOp = enum { + @"++", + @"--", +}; + +pub const UpdateExpr = struct { + loc: Loc, + op: UpdateOp, + arg: *const Expr, + prefix: bool, +}; + +pub const IfExpr = struct { + loc: Loc, + condition: *const Expr, + consequent: *const Expr, + alternate: *const Expr, +}; + +pub const AssignOp = enum { + @"=", + @"+=", + @"-=", + @"*=", + @"/=", + @"%=", + @"**=", + @"<<=", + @">>=", + @">>>=", + @"|=", + @"^=", + @"&=", + @"||=", + @"&&=", + @"??=", +}; + +pub const AssignExpr = struct { + loc: Loc, + op: AssignOp, + left: *const Expr, + right: *const Expr, +}; + +pub const SeqExpr = struct { + loc: Loc, + exprs: []const Expr, +}; + +pub const YieldExpr = struct { + loc: Loc, + arg: ?*const Expr, + delegate: bool, +}; + +pub const AwaitExpr = struct { + loc: Loc, + arg: *const Expr, +}; + +pub const MetaProp = struct { + loc: Loc, + meta: []const u8, + prop: []const u8, +}; + +pub const TemplateLit = struct { + loc: Loc, + quasis: []const TemplateElem, + exprs: []const Expr, + + pub fn parse(p: *Parser) ParseError!TemplateLit { + const start = p.tok().loc.start; + var quasis: [64]TemplateElem = undefined; + var exprs: [64]Expr = undefined; + var q_count: usize = 0; + var e_count: usize = 0; + + while (true) { + switch (p.peek()) { + .no_sub_template => { + const t = p.advanceTok(); + const slice = p.tokenSlice(t); + quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = true }; + q_count += 1; + break; + }, + .template_head => { + const t = p.advanceTok(); + const slice = p.tokenSlice(t); + quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = false }; + q_count += 1; + const expr = try Expr.parse(p, 0); + exprs[e_count] = expr.*; + e_count += 1; + try p.expect(.rbrace); + }, + .template_middle => { + const t = p.advanceTok(); + const slice = p.tokenSlice(t); + quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = false }; + q_count += 1; + const expr = try Expr.parse(p, 0); + exprs[e_count] = expr.*; + e_count += 1; + try p.expect(.rbrace); + }, + .template_tail => { + const t = p.advanceTok(); + const slice = p.tokenSlice(t); + quasis[q_count] = .{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .value = slice, .tail = true }; + q_count += 1; + break; + }, + else => break, + } + } + + return .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .quasis = quasis[0..q_count], .exprs = exprs[0..e_count] }; + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.writeChar('`'); + var ei: usize = 0; + for (self.quasis, 0..) |q, i| { + if (i > 0 and ei < self.exprs.len) { + f.write("${"); + self.exprs[ei].fmt(f); + f.writeChar('}'); + ei += 1; + } + f.write(q.value); + } + f.writeChar('`'); + } +}; + +pub const TemplateElem = struct { + loc: Loc, + value: []const u8, + tail: bool, +}; + +pub const TaggedTemplate = struct { + loc: Loc, + tag: *const Expr, + quasi: TemplateLit, + + pub fn fmt(self: @This(), f: *Formatter) void { + self.tag.*.fmt(f); + self.quasi.fmt(f); + } +}; + +pub const Decorator = struct { + loc: Loc, + expr: *const Expr, + + pub fn fmt(self: @This(), f: *Formatter) void { + f.writeChar('@'); + self.expr.*.fmt(f); + } +}; diff --git a/src/z/parser/js/ast/lit.zig b/src/z/parser/js/ast/lit.zig new file mode 100644 index 0000000..3a5149f --- /dev/null +++ b/src/z/parser/js/ast/lit.zig @@ -0,0 +1,48 @@ +const std = @import("std"); +const Loc = @import("loc.zig").Loc; +const Formatter = @import("../formatter.zig").Formatter; + +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, +}; diff --git a/src/z/parser/js/ast/loc.zig b/src/z/parser/js/ast/loc.zig new file mode 100644 index 0000000..276d05c --- /dev/null +++ b/src/z/parser/js/ast/loc.zig @@ -0,0 +1,4 @@ +pub const Loc = struct { + start: usize, + end: usize, +}; diff --git a/src/z/parser/js/ast/pat.zig b/src/z/parser/js/ast/pat.zig new file mode 100644 index 0000000..fdc8f55 --- /dev/null +++ b/src/z/parser/js/ast/pat.zig @@ -0,0 +1,190 @@ +const std = @import("std"); +const Loc = @import("loc.zig").Loc; +const Expr = @import("expr.zig").Expr; +const PropKey = @import("expr.zig").PropKey; +const Parser = @import("../parser.zig").Parser; +const Formatter = @import("../formatter.zig").Formatter; +const ParseError = @import("../parser.zig").ParseError; + +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 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, +}; diff --git a/src/z/parser/js/ast/stmt.zig b/src/z/parser/js/ast/stmt.zig new file mode 100644 index 0000000..c15b1f5 --- /dev/null +++ b/src/z/parser/js/ast/stmt.zig @@ -0,0 +1,865 @@ +const std = @import("std"); +const Loc = @import("loc.zig").Loc; +const Expr = @import("expr.zig").Expr; +const Pat = @import("pat.zig").Pat; +const Decl = @import("decl.zig").Decl; +const FnDecl = @import("decl.zig").FnDecl; +const ClassDecl = @import("decl.zig").ClassDecl; +const ImportDecl = @import("decl.zig").ImportDecl; +const ExportDecl = @import("decl.zig").ExportDecl; +const Parser = @import("../parser.zig").Parser; +const Formatter = @import("../formatter.zig").Formatter; +const ParseError = @import("../parser.zig").ParseError; + +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.writeSpace(); + f.write(l); + } + f.writeChar(';'); + }, + .@"break" => |b| { + f.write("break"); + if (b.label) |l| { + f.writeSpace(); + f.write(l); + } + f.writeChar(';'); + }, + .@"return" => |r| { + f.write("return"); + if (r.arg) |a| { + f.writeSpace(); + 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 ForInit = union(enum) { + expr: *const Expr, + decl: *const Decl, +}; + +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 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.writeSpace(); + c.*.fmt(f); + } + f.writeChar(';'); + if (self.update) |u| { + f.writeSpace(); + 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.writeSpace(); + 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.writeSpace(); + 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.writeSpace(); + } + 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.writeSpace(); + 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.writeSpace(); + 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); + } + } +}; diff --git a/src/z/parser/js/formatter.zig b/src/z/parser/js/formatter.zig new file mode 100644 index 0000000..fbea7bc --- /dev/null +++ b/src/z/parser/js/formatter.zig @@ -0,0 +1,67 @@ +const std = @import("std"); + +pub const FormatOptions = struct { + indent_size: usize = 4, + minify: bool = false, +}; + +pub const Formatter = struct { + buf: []u8, + pos: usize, + indent_level: usize, + options: FormatOptions, + last_char: u8 = 0, + + pub fn write(self: *@This(), s: []const u8) void { + if (self.options.minify) { + if (s.len == 0) return; + var start: usize = 0; + while (start < s.len and s[start] == ' ') start += 1; + var end = s.len; + while (end > start and s[end - 1] == ' ') end -= 1; + if (start >= end) return; + const trimmed = s[start..end]; + if (self.last_char != 0 and isWordChar(self.last_char) and isWordChar(trimmed[0])) { + self.buf[self.pos] = ' '; + self.pos += 1; + } + @memcpy(self.buf[self.pos..][0..trimmed.len], trimmed); + self.pos += trimmed.len; + self.last_char = trimmed[trimmed.len - 1]; + } else { + @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; + self.last_char = c; + } + + pub fn writeIndent(self: *@This()) void { + if (self.options.minify) return; + 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 { + if (self.options.minify) return; + self.writeChar('\n'); + } + + pub fn writeSpace(self: *@This()) void { + if (!self.options.minify) self.writeChar(' '); + } + + fn isWordChar(c: u8) bool { + return switch (c) { + 'a'...'z', 'A'...'Z', '0'...'9', '_', '$' => true, + else => false, + }; + } +}; diff --git a/src/z/parser/js/lexical_grammar.zig b/src/z/parser/js/lexical_grammar.zig index 2211fbe..35d1a4a 100644 --- a/src/z/parser/js/lexical_grammar.zig +++ b/src/z/parser/js/lexical_grammar.zig @@ -1,7 +1,6 @@ const std = @import("std"); -const ast = @import("ast.zig"); -pub const Loc = ast.Loc; +pub const Loc = @import("ast/loc.zig").Loc; pub const TokenType = enum { // Keywords diff --git a/src/z/parser/js/parser.zig b/src/z/parser/js/parser.zig new file mode 100644 index 0000000..09118dd --- /dev/null +++ b/src/z/parser/js/parser.zig @@ -0,0 +1,553 @@ +const std = @import("std"); +const lex = @import("lexical_grammar.zig"); +const Token = lex.Token; +const TokenType = lex.TokenType; +const ast = @import("ast/core.zig"); + +pub const ParseError = error{ + UnexpectedToken, + ExpectedToken, + ExpectedExpression, + ExpectedStatement, + ExpectedIdentifier, + UnclosedString, + InvalidNumber, + InvalidEscape, + UnterminatedComment, + ExpectedParameterName, + ExpectedPropertyName, + UnterminatedTemplate, + InvalidAssignmentTarget, + TooManyTokens, +}; + +pub const BP = struct { + pub const comma = 1; + pub const assign = 2; + pub const yield = 3; + pub const conditional = 4; + pub const coalesce = 5; + pub const log_or = 6; + pub const log_and = 7; + pub const bit_or = 8; + pub const bit_xor = 9; + pub const bit_and = 10; + pub const equality = 11; + pub const relational = 12; + pub const shift = 13; + pub const additive = 14; + pub const multiplicative = 15; + pub const exponentiation = 16; + pub const unary = 17; + pub const update = 18; + pub const call = 19; + pub const member = 20; +}; + +pub const NodeArena = struct { + exprs: [8192]ast.Expr = undefined, + expr_count: usize = 0, + stmts: [8192]ast.Stmt = undefined, + stmt_count: usize = 0, + decls: [1024]ast.Decl = undefined, + decl_count: usize = 0, + pats: [1024]ast.Pat = undefined, + pat_count: usize = 0, + lits: [1024]ast.Lit = undefined, + lit_count: usize = 0, + string_buf: [65536]u8 = undefined, + string_pos: usize = 0, + + pub fn exprPtr(self: *NodeArena, data: ast.ExprType, loc: ast.Loc) *const ast.Expr { + const idx = self.expr_count; + self.exprs[idx] = .{ .loc = loc, .data = data }; + self.expr_count += 1; + return &self.exprs[idx]; + } + + pub fn stmtPtr(self: *NodeArena, data: ast.StmtType, loc: ast.Loc) *const ast.Stmt { + const idx = self.stmt_count; + self.stmts[idx] = .{ .loc = loc, .data = data }; + self.stmt_count += 1; + return &self.stmts[idx]; + } + + pub fn declPtr(self: *NodeArena, data: ast.DeclType, loc: ast.Loc) *const ast.Decl { + const idx = self.decl_count; + self.decls[idx] = .{ .loc = loc, .data = data }; + self.decl_count += 1; + return &self.decls[idx]; + } + + pub fn patPtr(self: *NodeArena, data: ast.PatType, loc: ast.Loc) *const ast.Pat { + const idx = self.pat_count; + self.pats[idx] = .{ .loc = loc, .data = data }; + self.pat_count += 1; + return &self.pats[idx]; + } + + pub fn litPtr(self: *NodeArena, data: ast.LitType, loc: ast.Loc) *const ast.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 }; + } + + pub fn tok(self: *@This()) Token { + return self.tokens[self.pos]; + } + + pub fn peek(self: *@This()) TokenType { + return self.tokens[self.pos].kind; + } + + pub fn peekAt(self: *@This(), offset: usize) TokenType { + const idx = self.pos + offset; + if (idx >= self.tokens.len) return .eof; + return self.tokens[idx].kind; + } + + pub fn advance(self: *@This()) void { + self.pos += 1; + } + + pub fn advanceTok(self: *@This()) Token { + const t = self.tokens[self.pos]; + self.pos += 1; + return t; + } + + pub fn expect(self: *@This(), kind: TokenType) ParseError!void { + if (self.peek() != kind) return ParseError.ExpectedToken; + self.advance(); + } + + pub fn expectTok(self: *@This(), kind: TokenType) ParseError!Token { + if (self.peek() != kind) return ParseError.ExpectedToken; + return self.advanceTok(); + } + + pub fn match(self: *@This(), kind: TokenType) bool { + if (self.peek() == kind) { + _ = self.advanceTok(); + return true; + } + return false; + } + + pub fn matchKeyword(self: *@This(), kw: TokenType) bool { + if (self.peek() == kw) { + _ = self.advanceTok(); + return true; + } + return false; + } + + pub 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; + } + + pub fn as_identifier(self: *@This(), t: Token) []const u8 { + return self.source[t.loc.start..t.loc.end]; + } + + pub fn tokenSlice(self: *@This(), t: Token) []const u8 { + return self.source[t.loc.start..t.loc.end]; + } + + pub fn parseForDeclaration(self: *@This()) ParseError!*const ast.Decl { + const kind_tok = self.advanceTok(); + var decls: [128]ast.VarDeclarator = undefined; + var count: usize = 0; + + while (true) { + const pat = try ast.Pat.parse(self); + var decl_init: ?*const ast.Expr = null; + if (self.match(.@"=")) { + decl_init = try ast.Expr.parse(self, 0); + } + decls[count] = .{ .loc = pat.loc, .id = pat, .init = decl_init }; + count += 1; + if (!self.match(.comma)) break; + } + + const kind: ast.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 }); + } + + pub fn parseParams(self: *@This()) ParseError![]const ast.Pat { + try self.expect(.lparen); + var params: [128]ast.Pat = undefined; + var count: usize = 0; + + if (self.peek() != .rparen) { + while (true) { + if (self.peek() == .@"...") { + self.advance(); + const pat = try ast.Pat.parse(self); + params[count] = ast.Pat{ .loc = pat.loc, .data = .{ .rest = .{ .loc = pat.loc, .arg = self.arena.patPtr(pat.data, pat.loc) } } }; + count += 1; + break; + } + const pat = try ast.Pat.parse(self); + if (self.match(.@"=")) { + const expr = try ast.Expr.parse(self, 0); + params[count] = ast.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 ast.Pat.parse(self); + params[count] = ast.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]; + } + + pub fn parseFunctionBody(self: *@This()) ParseError!ast.BlockStmt { + return ast.BlockStmt.parse(self); + } + + pub fn parsePrimaryExpr(self: *@This()) ParseError!*const ast.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 ast.ObjectExpr.parse(self), + .lbracket => return ast.ArrayExpr.parse(self), + .lparen => { + self.advance(); + if (self.peek() == .rparen) { + self.advance(); + if (self.peek() == .@"=>") { + return ast.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 ast.Expr.parse(self, 0); + try self.expect(.rparen); + if (self.peek() == .@"=>") { + return ast.ArrowFn.parse(self, .{ .loc = .{ .start = start, .end = arg.loc.end }, .params = &.{} }); + } + const loc: ast.Loc = .{ .start = start, .end = self.tok().loc.start }; + return self.arena.exprPtr(.{ .spread = .{ .loc = loc, .arg = arg } }, loc); + } + const expr = try ast.Expr.parse(self, 0); + if (self.peek() == .comma) { + var params: [128]ast.Pat = undefined; + var count: usize = 0; + params[count] = ast.Pat{ .loc = expr.loc, .data = .{ .ident = expr.data.identifier } }; + count += 1; + while (self.match(.comma)) { + const p = try ast.Pat.parse(self); + params[count] = p; + count += 1; + } + try self.expect(.rparen); + if (self.peek() == .@"=>") { + return ast.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 = ast.Pat{ .loc = expr.loc, .data = .{ .ident = expr.data.identifier } }; + return ast.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 ast.FnExpr.parse(self), + .class => return ast.ClassExpr.parse(self), + .async => { + self.advance(); + if (self.peek() == .function) { + return ast.FnExpr.parse(self); + } + const id_tok = try self.expectTok(.identifier); + const name = self.tokenSlice(id_tok); + if (self.peek() == .@"=>") { + const pat = ast.Pat{ .loc = .{ .start = start, .end = id_tok.loc.end }, .data = .{ .ident = name } }; + return ast.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 ast.Expr.parse(self, BP.member); + var args: [256]ast.Expr = undefined; + var arg_count: usize = 0; + if (self.peek() == .lparen) { + self.advance(); + if (self.peek() != .rparen) { + while (true) { + const arg = try ast.Expr.parse(self, 0); + args[arg_count] = arg.*; + arg_count += 1; + if (!self.match(.comma)) break; + } + } + try self.expect(.rparen); + } + const loc: ast.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 ast.Expr.parse(self, BP.unary); + const loc: ast.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 ast.Expr.parse(self, BP.update); + const loc: ast.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 ast.Expr.parse(self, BP.update); + const loc: ast.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 ast.Expr.parse(self, BP.unary); + const loc: ast.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 ast.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 ast.Expr.parse(self, 0); + } + } + const loc: ast.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 ast.TemplateLit.parse(self) }, .{ .start = start, .end = self.tok().loc.start }), + .import => { + self.advance(); + try self.expect(.lparen); + _ = try ast.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 = ast.Pat{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .data = .{ .ident = name } }; + return ast.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; + }, + } + } + + pub fn parsePropKey(self: *@This()) ParseError!ast.PropKey { + switch (self.peek()) { + .identifier, .let, .static, .get, .set, .async => { + const t = self.advanceTok(); + return ast.PropKey{ .ident = self.tokenSlice(t) }; + }, + .string => { + const t = self.advanceTok(); + return ast.PropKey{ .string = self.tokenSlice(t) }; + }, + .number => { + const t = self.advanceTok(); + const val = std.fmt.parseFloat(f64, self.tokenSlice(t)) catch 0.0; + return ast.PropKey{ .number = val }; + }, + .lbracket => { + self.advance(); + const expr = try ast.Expr.parse(self, 0); + try self.expect(.rbracket); + return ast.PropKey{ .computed = expr }; + }, + .private_identifier => { + const t = self.advanceTok(); + return ast.PropKey{ .private = self.tokenSlice(t) }; + }, + else => return ParseError.ExpectedPropertyName, + } + } + + pub 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, + }; + } + + pub fn unaryOpFromToken(self: *@This()) ast.UnaryOp { + return switch (self.tok().kind) { + .@"+" => .@"+", + .@"-" => .@"-", + .@"!" => .@"!", + .@"~" => .@"~", + .typeof => .typeof, + .void => .void, + .delete => .delete, + else => .@"+", + }; + } + + pub fn assignOpFromToken(self: *@This()) ast.AssignOp { + return switch (self.tokens[self.pos - 1].kind) { + .@"=" => .@"=", + .@"+=" => .@"+=", + .@"-=" => .@"-=", + .@"*=" => .@"*=", + .@"/=" => .@"/=", + .@"%=" => .@"%=", + .@"**=" => .@"**=", + .@"<<=" => .@"<<=", + .@">>=" => .@">>=", + .@">>>=" => .@">>>=", + .@"&=" => .@"&=", + .@"|=" => .@"|=", + .@"^=" => .@"^=", + .@"&&=" => .@"&&=", + .@"||=" => .@"||=", + .@"??=" => .@"??=", + else => .@"=", + }; + } + + pub fn semicolon(self: *@This()) void { + if (self.peek() == .semicolon) { + self.advance(); + } + } + + pub fn statementPointer(self: *@This()) ParseError!*const ast.Stmt { + const s = try ast.Stmt.parse(self); + return self.arena.stmtPtr(s.data, s.loc); + } +}; diff --git a/src/z/parser/js/root.zig b/src/z/parser/js/root.zig index 565d572..6b2d5c7 100644 --- a/src/z/parser/js/root.zig +++ b/src/z/parser/js/root.zig @@ -1,7 +1,7 @@ const std = @import("std"); pub const lexical_grammar = @import("lexical_grammar.zig"); -pub const ast = @import("ast.zig"); +pub const ast = @import("ast/core.zig"); test "simple literal" { comptime { @@ -80,6 +80,18 @@ test "format fixture" { } } +test "minify" { + comptime { + var buf: [65536]u8 = undefined; + const ast1 = try ast.Program.parse("function foo() { var x = 1; return x; }"); + var f = ast.Formatter{ .buf = buf[0..], .pos = 0, .indent_level = 0, .options = .{ .minify = true } }; + ast1.fmt(&f); + const minified = buf[0..f.pos]; + try std.testing.expect(std.mem.indexOf(u8, minified, "\n") == null); + try std.testing.expect(minified.len < 50); + } +} + test { _ = std.testing.refAllDecls(@This()); } -- cgit v1.2.3-70-g09d2