aboutsummaryrefslogtreecommitdiff
path: root/src/z/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/z/js')
-rw-r--r--src/z/js/parser/ast.zig674
-rw-r--r--src/z/js/parser/lexical_grammar.zig1099
-rw-r--r--src/z/js/parser/parser.zig117
-rw-r--r--src/z/js/parser/result.zig3
-rw-r--r--src/z/js/parser/root.zig93
-rw-r--r--src/z/js/parser/syntactic_grammar.zig1907
6 files changed, 3893 insertions, 0 deletions
diff --git a/src/z/js/parser/ast.zig b/src/z/js/parser/ast.zig
new file mode 100644
index 0000000..1fb8797
--- /dev/null
+++ b/src/z/js/parser/ast.zig
@@ -0,0 +1,674 @@
+pub const Loc = struct {
+ start: usize,
+ end: usize,
+};
+
+pub const Node = union(enum) {
+ program: Program,
+
+ stmt: Stmt,
+ expr: Expr,
+ pat: Pat,
+
+ decl: Decl,
+ literal: Lit,
+ property: Prop,
+ template: Template,
+ spread: Spread,
+ decorator: Decorator,
+
+ import_specifier: ImportSpecifier,
+ export_specifier: ExportSpecifier,
+ import_attr: ImportAttribute,
+ catch_clause: CatchClause,
+ switch_case: SwitchCase,
+};
+
+pub const Program = struct {
+ body: []const Node,
+ loc: Loc,
+};
+
+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,
+ using: UsingStmt,
+ import: ImportDecl,
+ @"export": ExportDecl,
+};
+
+pub const Stmt = struct {
+ loc: Loc,
+ data: StmtType,
+};
+
+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 const PatType = union(enum) {
+ ident: []const u8,
+ object: ObjectPat,
+ array: ArrayPat,
+ assign: AssignPat,
+ rest: RestPat,
+};
+
+pub const Pat = struct {
+ loc: Loc,
+ data: PatType,
+};
+
+pub const DeclType = union(enum) {
+ @"fn": FnDecl,
+ class: ClassDecl,
+ @"var": VarDecl,
+ lexical: LexicalDecl,
+ 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 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 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 const PropSet = struct {
+ key: PropKey,
+ param: Pat,
+ body: BlockStmt,
+};
+
+pub const SpreadElem = struct {
+ loc: Loc,
+ arg: *const Expr,
+};
+
+pub const BlockStmt = struct {
+ loc: Loc,
+ body: []const Stmt,
+};
+
+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 const WhileStmt = struct {
+ loc: Loc,
+ condition: *const Expr,
+ body: *const Stmt,
+};
+
+pub const DoWhileStmt = struct {
+ loc: Loc,
+ body: *const Stmt,
+ condition: *const Expr,
+};
+
+pub const ForStmt = struct {
+ loc: Loc,
+ init: ?union(enum) {
+ expr: *const Expr,
+ decl: *const Decl,
+ },
+ condition: ?*const Expr,
+ update: ?*const Expr,
+ body: *const Stmt,
+};
+
+pub const ForInStmt = struct {
+ loc: Loc,
+ left: union(enum) {
+ expr: *const Expr,
+ decl: *const Decl,
+ },
+ right: *const Expr,
+ body: *const Stmt,
+};
+
+pub const ForOfStmt = struct {
+ loc: Loc,
+ await_token: bool,
+ left: union(enum) {
+ expr: *const Expr,
+ decl: *const Decl,
+ },
+ right: *const Expr,
+ body: *const Stmt,
+};
+
+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 const SwitchCase = struct {
+ loc: Loc,
+ condition: ?*const Expr,
+ consequent: []const Stmt,
+};
+
+pub const ThrowStmt = struct {
+ loc: Loc,
+ arg: *const Expr,
+};
+
+pub const TryStmt = struct {
+ loc: Loc,
+ block: BlockStmt,
+ handler: ?CatchClause,
+ finalizer: ?BlockStmt,
+};
+
+pub const CatchClause = struct {
+ loc: Loc,
+ param: ?Pat,
+ body: BlockStmt,
+};
+
+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 const UsingStmt = struct {
+ loc: Loc,
+ decls: []const VarDeclarator,
+};
+
+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 const UsingDecl = struct {
+ loc: Loc,
+ is_await: bool,
+ decls: []const VarDeclarator,
+};
+
+pub const VarDeclarator = struct {
+ loc: Loc,
+ id: Pat,
+ init: ?*const Expr,
+};
+
+pub const ObjectPat = struct {
+ loc: Loc,
+ props: []const PatProp,
+ rest: ?*const Pat,
+};
+
+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 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 const FnExpr = struct {
+ loc: Loc,
+ fn_type: FnType,
+ id: ?[]const u8,
+ params: []const Pat,
+ body: BlockStmt,
+};
+
+pub const ArrowFn = struct {
+ loc: Loc,
+ async_token: bool,
+ params: []const Pat,
+ body: union(enum) {
+ block: BlockStmt,
+ expr: *const Expr,
+ },
+};
+
+pub const ClassDecl = struct {
+ loc: Loc,
+ id: ?[]const u8,
+ super_class: ?*const Expr,
+ body: []const ClassElem,
+};
+
+pub const ClassExpr = struct {
+ loc: Loc,
+ id: ?[]const u8,
+ super_class: ?*const Expr,
+ body: []const ClassElem,
+};
+
+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 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 const ExportDecl = struct {
+ loc: Loc,
+ declaration: ?*const Decl,
+ specifiers: ?[]const ExportSpecifier,
+ source: ?[]const u8,
+};
+
+pub const ExportSpecifier = struct {
+ loc: Loc,
+ exported: []const u8,
+ local: []const u8,
+};
+
+pub const ExportDefault = struct {
+ loc: Loc,
+ declaration: *const Decl,
+};
+
+pub const ArrayExpr = struct {
+ loc: Loc,
+ elems: []const ?Expr,
+};
+
+pub const ObjectExpr = struct {
+ loc: Loc,
+ props: []const Prop,
+};
+
+pub const MemberExpr = struct {
+ loc: Loc,
+ obj: *const Expr,
+ prop: union(enum) {
+ ident: []const u8,
+ private: []const u8,
+ },
+};
+
+pub const ComputedMember = struct {
+ loc: Loc,
+ obj: *const Expr,
+ expr: *const Expr,
+};
+
+pub const CallExpr = struct {
+ loc: Loc,
+ callee: *const Expr,
+ args: []const Expr,
+ optional: bool,
+};
+
+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 SpreadElem = struct {
+ loc: Loc,
+ arg: *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 const TemplateElem = struct {
+ loc: Loc,
+ value: []const u8,
+ tail: bool,
+};
+
+pub const TaggedTemplate = struct {
+ loc: Loc,
+ tag: *const Expr,
+ quasi: TemplateLit,
+};
+
+pub const Decorator = struct {
+ loc: Loc,
+ expr: *const Expr,
+};
diff --git a/src/z/js/parser/lexical_grammar.zig b/src/z/js/parser/lexical_grammar.zig
new file mode 100644
index 0000000..bd54e8f
--- /dev/null
+++ b/src/z/js/parser/lexical_grammar.zig
@@ -0,0 +1,1099 @@
+const std = @import("std");
+
+pub const Loc = struct {
+ start: usize,
+ end: usize,
+};
+
+pub const TokenType = enum {
+ // Keywords
+ await,
+ @"break",
+ case,
+ @"catch",
+ class,
+ @"const",
+ @"continue",
+ debugger,
+ default,
+ delete,
+ do,
+ @"else",
+ @"enum",
+ @"export",
+ extends,
+ false,
+ finally,
+ @"for",
+ function,
+ @"if",
+ import,
+ in,
+ instanceof,
+ let,
+ new,
+ null,
+ @"return",
+ super,
+ @"switch",
+ this,
+ throw,
+ true,
+ @"try",
+ typeof,
+ @"var",
+ void,
+ @"while",
+ with,
+ yield,
+ using,
+
+ // Identifiers and literals
+ identifier,
+ private_identifier,
+ number,
+ string,
+ bigint,
+ regex,
+ template_head,
+ template_middle,
+ template_tail,
+ no_sub_template,
+
+ // Punctuators
+ lbrace,
+ rbrace,
+ lparen,
+ rparen,
+ lbracket,
+ rbracket,
+ dot,
+ semicolon,
+ comma,
+ @"...",
+ @"<",
+ @">",
+ @"<=",
+ @">=",
+ @"==",
+ @"!=",
+ @"===",
+ @"!==",
+ @"+",
+ @"-",
+ @"*",
+ @"%",
+ @"**",
+ @"++",
+ @"--",
+ @"<<",
+ @">>",
+ @">>>",
+ @"&",
+ @"|",
+ @"^",
+ @"!",
+ @"~",
+ @"&&",
+ @"||",
+ @"??",
+ @"?",
+ @":",
+ @"=",
+ @"+=",
+ @"-=",
+ @"*=",
+ @"%=",
+ @"**=",
+ @"<<=",
+ @">>=",
+ @">>>=",
+ @"&=",
+ @"|=",
+ @"^=",
+ @"&&=",
+ @"||=",
+ @"??=",
+ @"=>",
+ @"/",
+ @"/=",
+ @"?.",
+ @".",
+ @"#",
+
+ // Special
+ eof,
+ unknown,
+
+ pub fn isKeyword(self: TokenType) bool {
+ return switch (self) {
+ .await,
+ .@"break",
+ .case,
+ .@"catch",
+ .class,
+ .@"const",
+ .@"continue",
+ .debugger,
+ .default,
+ .delete,
+ .do,
+ .@"else",
+ .@"enum",
+ .@"export",
+ .extends,
+ .false,
+ .finally,
+ .@"for",
+ .function,
+ .@"if",
+ .import,
+ .in,
+ .instanceof,
+ .let,
+ .new,
+ .null,
+ .@"return",
+ .super,
+ .@"switch",
+ .this,
+ .throw,
+ .true,
+ .@"try",
+ .typeof,
+ .@"var",
+ .void,
+ .@"while",
+ .with,
+ .yield,
+ .using,
+ => true,
+ else => false,
+ };
+ }
+
+ pub fn isIdentifier(self: TokenType) bool {
+ return self == .identifier or self.isKeyword();
+ }
+};
+
+pub const Token = struct {
+ kind: TokenType,
+ loc: Loc,
+ slice: []const u8,
+};
+
+pub const Lexer = struct {
+ source: []const u8,
+ pos: usize,
+
+ const Self = @This();
+
+ pub fn init(source: []const u8) Self {
+ return .{ .source = source, .pos = 0 };
+ }
+
+ pub fn tokenize(source: []const u8) []const Token {
+ var lexer = Self.init(source);
+ var tokens: [4096]Token = undefined;
+ var count: usize = 0;
+
+ while (true) {
+ const tok = lexer.next();
+ tokens[count] = tok;
+ count += 1;
+ if (tok.kind == .eof) break;
+ if (count >= tokens.len - 1) {
+ tokens[count] = .{ .kind = .eof, .loc = .{ .start = lexer.pos, .end = lexer.pos }, .slice = "" };
+ break;
+ }
+ }
+
+ return tokens[0..count];
+ }
+
+ fn ch(self: Self) ?u8 {
+ if (self.pos >= self.source.len) return null;
+ return self.source[self.pos];
+ }
+
+ fn chAt(self: Self, offset: usize) ?u8 {
+ const idx = self.pos + offset;
+ if (idx >= self.source.len) return null;
+ return self.source[idx];
+ }
+
+ fn advance(self: *Self) void {
+ if (self.pos < self.source.len) self.pos += 1;
+ }
+
+ fn isWhiteSpace(c: u8) bool {
+ return switch (c) {
+ ' ', '\t', 0x0B, 0x0C => true,
+ else => false,
+ };
+ }
+
+ fn isLineTerminator(c: u8) bool {
+ return switch (c) {
+ '\n', '\r' => true,
+ 0x2028, 0x2029 => true,
+ else => false,
+ };
+ }
+
+ fn isDigit(c: u8) bool {
+ return c >= '0' and c <= '9';
+ }
+
+ fn isHexDigit(c: u8) bool {
+ return switch (c) {
+ '0'...'9', 'a'...'f', 'A'...'F' => true,
+ else => false,
+ };
+ }
+
+ fn isOctalDigit(c: u8) bool {
+ return c >= '0' and c <= '7';
+ }
+
+ fn isIdStart(c: u8) bool {
+ return switch (c) {
+ 'a'...'z', 'A'...'Z', '_', '$' => true,
+ else => c > 0x7F,
+ };
+ }
+
+ fn isIdContinue(c: u8) bool {
+ return isIdStart(c) or isDigit(c) or c == 0x200C or c == 0x200D;
+ }
+
+ fn skipWhiteSpace(self: *Self) void {
+ while (self.ch()) |c| {
+ if (isWhiteSpace(c) or isLineTerminator(c)) {
+ self.advance();
+ } else {
+ break;
+ }
+ }
+ }
+
+ fn skipSingleLineComment(self: *Self) void {
+ self.advance();
+ self.advance();
+ while (self.ch()) |c| {
+ if (isLineTerminator(c)) break;
+ self.advance();
+ }
+ }
+
+ fn skipMultiLineComment(self: *Self) void {
+ self.advance();
+ self.advance();
+ while (self.ch()) |c| {
+ if (c == '*' and self.chAt(1)) |n| {
+ if (n == '/') {
+ self.advance();
+ self.advance();
+ return;
+ }
+ }
+ self.advance();
+ }
+ }
+
+ fn skipHashbang(self: *Self) void {
+ if (self.ch()) |c| {
+ if (c == '#') {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '!') {
+ self.advance();
+ while (self.ch()) |ch| {
+ if (isLineTerminator(ch)) break;
+ self.advance();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn scanString(self: *Self, quote: u8) Token {
+ const start = self.pos;
+ self.advance();
+
+ while (self.ch()) |c| {
+ if (c == quote) {
+ self.advance();
+ return .{
+ .kind = .string,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start + 1 .. self.pos - 1],
+ };
+ }
+ if (c == '\\') {
+ self.advance();
+ if (self.ch()) |_| self.advance();
+ } else if (isLineTerminator(c) and c != 0x2028 and c != 0x2029) {
+ break;
+ } else {
+ self.advance();
+ }
+ }
+
+ return .{
+ .kind = .string,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start + 1 .. self.pos],
+ };
+ }
+
+ fn scanTemplate(self: *Self, start_kind: TokenType) Token {
+ const start = self.pos;
+ if (start_kind == .template_middle or start_kind == .template_head) {
+ self.advance();
+ }
+ return self.scanTemplateRest(start);
+ }
+
+ fn scanTemplateRest(self: *Self, start: usize) Token {
+ while (self.ch()) |c| {
+ if (c == '`') {
+ self.advance();
+ return .{
+ .kind = .no_sub_template,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ if (c == '$' and self.chAt(1)) |n| {
+ if (n == '{') {
+ self.advance();
+ self.advance();
+ return .{
+ .kind = .template_head,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 2],
+ };
+ }
+ }
+ if (c == '\\') {
+ self.advance();
+ if (self.ch()) |_| self.advance();
+ } else {
+ self.advance();
+ }
+ }
+
+ return .{
+ .kind = .no_sub_template,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanTemplateMiddleOrTail(self: *Self) Token {
+ const start = self.pos;
+ self.advance();
+
+ while (self.ch()) |c| {
+ if (c == '`') {
+ self.advance();
+ return .{
+ .kind = .template_tail,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ if (c == '$' and self.chAt(1)) |n| {
+ if (n == '{') {
+ self.advance();
+ self.advance();
+ return .{
+ .kind = .template_middle,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 2],
+ };
+ }
+ }
+ if (c == '\\') {
+ self.advance();
+ if (self.ch()) |_| self.advance();
+ } else {
+ self.advance();
+ }
+ }
+
+ return .{
+ .kind = .template_tail,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanNumber(self: *Self) Token {
+ const start = self.pos;
+
+ if (self.ch()) |c| {
+ if (c == '0') {
+ self.advance();
+ if (self.ch()) |n| {
+ switch (n) {
+ 'x', 'X' => return self.scanHex(start),
+ 'b', 'B' => return self.scanBinary(start),
+ 'o', 'O' => return self.scanOctal(start),
+ 'n' => {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ },
+ 'e', 'E' => return self.scanDecimalAfterInt(start),
+ '.', '0'...'9' => return self.scanDecimalAfterInt(start),
+ else => {},
+ }
+ }
+ }
+ }
+
+ return self.scanDecimal(start);
+ }
+
+ fn scanDecimal(self: *Self, start: usize) Token {
+ while (self.ch()) |c| {
+ if (!isDigit(c) and c != '_') break;
+ self.advance();
+ }
+
+ if (self.ch()) |c| {
+ if (c == '.') {
+ self.advance();
+ while (self.ch()) |c2| {
+ if (!isDigit(c2) and c2 != '_') break;
+ self.advance();
+ }
+ }
+ if (self.ch()) |c2| {
+ if (c2 == 'e' or c2 == 'E') {
+ return self.scanExponent(start);
+ }
+ }
+ if (self.ch()) |c2| {
+ if (c2 == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ }
+
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanDecimalAfterInt(self: *Self, start: usize) Token {
+ while (self.ch()) |c| {
+ if (!isDigit(c) and c != '_') break;
+ self.advance();
+ }
+ if (self.ch()) |c| {
+ if (c == '.') {
+ self.advance();
+ while (self.ch()) |c2| {
+ if (!isDigit(c2) and c2 != '_') break;
+ self.advance();
+ }
+ }
+ if (self.ch()) |c2| {
+ if (c2 == 'e' or c2 == 'E') {
+ return self.scanExponent(start);
+ }
+ }
+ if (self.ch()) |c2| {
+ if (c2 == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanExponent(self: *Self, start: usize) Token {
+ self.advance();
+ if (self.ch()) |c| {
+ if (c == '+' or c == '-') self.advance();
+ }
+ while (self.ch()) |c| {
+ if (!isDigit(c) and c != '_') break;
+ self.advance();
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanHex(self: *Self, start: usize) Token {
+ self.advance();
+ while (self.ch()) |c| {
+ if (!isHexDigit(c) and c != '_') break;
+ self.advance();
+ }
+ if (self.ch()) |c| {
+ if (c == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanBinary(self: *Self, start: usize) Token {
+ self.advance();
+ while (self.ch()) |c| {
+ if (c != '0' and c != '1' and c != '_') break;
+ self.advance();
+ }
+ if (self.ch()) |c| {
+ if (c == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanOctal(self: *Self, start: usize) Token {
+ self.advance();
+ while (self.ch()) |c| {
+ if (!isOctalDigit(c) and c != '_') break;
+ self.advance();
+ }
+ if (self.ch()) |c| {
+ if (c == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanIdentifierOrKeyword(self: *Self) Token {
+ const start = self.pos;
+ while (self.ch()) |c| {
+ if (!isIdContinue(c)) break;
+ self.advance();
+ }
+
+ const slice = self.source[start..self.pos];
+ const kind = keywordFromString(slice);
+
+ return .{
+ .kind = kind,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = slice,
+ };
+ }
+
+ fn scanPrivateIdentifier(self: *Self) Token {
+ const start = self.pos;
+ self.advance();
+ while (self.ch()) |c| {
+ if (!isIdContinue(c)) break;
+ self.advance();
+ }
+ return .{
+ .kind = .private_identifier,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanRegex(self: *Self) Token {
+ const start = self.pos;
+ self.advance();
+
+ var in_class = false;
+ while (self.ch()) |c| {
+ if (c == '\\') {
+ self.advance();
+ if (self.ch()) |_| self.advance();
+ } else if (c == '[') {
+ in_class = true;
+ self.advance();
+ } else if (c == ']') {
+ in_class = false;
+ self.advance();
+ } else if (c == '/' and !in_class) {
+ self.advance();
+ break;
+ } else if (isLineTerminator(c)) {
+ break;
+ } else {
+ self.advance();
+ }
+ }
+
+ while (self.ch()) |c| {
+ if (!isIdContinue(c)) break;
+ self.advance();
+ }
+
+ return .{
+ .kind = .regex,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanPunctuator(self: *Self) Token {
+ const start = self.pos;
+ const c = self.ch().?;
+
+ switch (c) {
+ '{' => {
+ self.advance();
+ return simple(.lbrace, start);
+ },
+ '}' => {
+ self.advance();
+ return simple(.rbrace, start);
+ },
+ '(' => {
+ self.advance();
+ return simple(.lparen, start);
+ },
+ ')' => {
+ self.advance();
+ return simple(.rparen, start);
+ },
+ '[' => {
+ self.advance();
+ return simple(.lbracket, start);
+ },
+ ']' => {
+ self.advance();
+ return simple(.rbracket, start);
+ },
+ ';' => {
+ self.advance();
+ return simple(.semicolon, start);
+ },
+ ',' => {
+ self.advance();
+ return simple(.comma, start);
+ },
+ ':' => {
+ self.advance();
+ return simple(.@":", start);
+ },
+ '?' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '?') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"??=", start);
+ }
+ }
+ return simple(.@"??", start);
+ }
+ if (n == '.') {
+ if (self.chAt(1)) |n2| {
+ if (!isDigit(n2)) {
+ self.advance();
+ return simple(.@"?.", start);
+ }
+ } else {
+ self.advance();
+ return simple(.@"?.", start);
+ }
+ }
+ }
+ return simple(.@"?", start);
+ },
+ '~' => {
+ self.advance();
+ return simple(.@"~", start);
+ },
+ '.' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '.' and self.chAt(1)) |n2| {
+ if (n2 == '.') {
+ self.advance();
+ self.advance();
+ return simple(.@"...", start);
+ }
+ }
+ }
+ return simple(.@".", start);
+ },
+ '#' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (isIdStart(n)) {
+ return self.scanPrivateIdentifier();
+ }
+ }
+ return simple(.@"#", start);
+ },
+ '+' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '+') {
+ self.advance();
+ return simple(.@"++", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"+=", start);
+ }
+ }
+ return simple(.@"+", start);
+ },
+ '-' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '-') {
+ self.advance();
+ return simple(.@"--", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"-=", start);
+ }
+ }
+ return simple(.@"-", start);
+ },
+ '*' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '*') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"**=", start);
+ }
+ }
+ return simple(.@"**", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"*=", start);
+ }
+ }
+ return simple(.@"*", start);
+ },
+ '%' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '=') {
+ self.advance();
+ return simple(.@"%=", start);
+ }
+ }
+ return simple(.@"%", start);
+ },
+ '=' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '=') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"===", start);
+ }
+ }
+ return simple(.@"==", start);
+ }
+ if (n == '>') {
+ self.advance();
+ return simple(.@"=>", start);
+ }
+ }
+ return simple(.@"=", start);
+ },
+ '!' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '=') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"!==", start);
+ }
+ }
+ return simple(.@"!=", start);
+ }
+ }
+ return simple(.@"!", start);
+ },
+ '<' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '<') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"<<=", start);
+ }
+ }
+ return simple(.@"<<", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"<=", start);
+ }
+ }
+ return simple(.@"<", start);
+ },
+ '>' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '>') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '>') {
+ self.advance();
+ if (self.ch()) |n3| {
+ if (n3 == '=') {
+ self.advance();
+ return simple(.@">>>=", start);
+ }
+ }
+ return simple(.@">>>", start);
+ }
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@">>=", start);
+ }
+ }
+ return simple(.@">>", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@">=", start);
+ }
+ }
+ return simple(.@">", start);
+ },
+ '&' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '&') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"&&=", start);
+ }
+ }
+ return simple(.@"&&", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"&=", start);
+ }
+ }
+ return simple(.@"&", start);
+ },
+ '|' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '|') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"||=", start);
+ }
+ }
+ return simple(.@"||", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"|=", start);
+ }
+ }
+ return simple(.@"|", start);
+ },
+ '^' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '=') {
+ self.advance();
+ return simple(.@"^=", start);
+ }
+ }
+ return simple(.@"^", start);
+ },
+ '`' => {
+ self.advance();
+ return self.scanTemplateRest(start);
+ },
+ '\'' => return self.scanString('\''),
+ '"' => return self.scanString('"'),
+ '/' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '/') {
+ self.skipSingleLineComment();
+ return self.next();
+ }
+ if (n == '*') {
+ self.skipMultiLineComment();
+ return self.next();
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"/=", start);
+ }
+ }
+ return simple(.@"/", start);
+ },
+ else => {
+ if (isLineTerminator(c) or isWhiteSpace(c)) {
+ self.skipWhiteSpace();
+ return self.next();
+ }
+ if (isDigit(c)) return self.scanNumber();
+ if (isIdStart(c)) return self.scanIdentifierOrKeyword();
+ if (c == '\\') {
+ self.advance();
+ return self.scanIdentifierOrKeyword();
+ }
+ self.advance();
+ return simple(.unknown, start);
+ },
+ }
+ }
+
+ fn simple(kind: TokenType, start: usize) Token {
+ _ = start;
+ return .{ .kind = kind, .loc = undefined, .slice = "" };
+ }
+
+ fn simpleWithSlice(comptime kind: TokenType, start: usize, slice: []const u8) Token {
+ return .{ .kind = kind, .loc = undefined, .slice = slice };
+ }
+
+ pub fn next(self: *Self) Token {
+ const start = self.pos;
+ if (self.ch()) |c| {
+ _ = c;
+ // Handle hashbang at beginning
+ if (self.pos == 0 and self.ch()) |c2| {
+ if (c2 == '#') {
+ self.skipHashbang();
+ return self.next();
+ }
+ }
+ return self.scanPunctuator();
+ }
+ return simple(.eof, self.pos);
+ }
+
+ fn keywordFromString(s: []const u8) TokenType {
+ const keywords = std.ComptimeStringMap(TokenType, .{
+ .{ "await", .await },
+ .{ "break", .@"break" },
+ .{ "case", .case },
+ .{ "catch", .@"catch" },
+ .{ "class", .class },
+ .{ "const", .@"const" },
+ .{ "continue", .@"continue" },
+ .{ "debugger", .debugger },
+ .{ "default", .default },
+ .{ "delete", .delete },
+ .{ "do", .do },
+ .{ "else", .@"else" },
+ .{ "enum", .@"enum" },
+ .{ "export", .@"export" },
+ .{ "extends", .extends },
+ .{ "false", .false },
+ .{ "finally", .finally },
+ .{ "for", .@"for" },
+ .{ "function", .function },
+ .{ "if", .@"if" },
+ .{ "import", .import },
+ .{ "in", .in },
+ .{ "instanceof", .instanceof },
+ .{ "let", .let },
+ .{ "new", .new },
+ .{ "null", .null },
+ .{ "return", .@"return" },
+ .{ "super", .super },
+ .{ "switch", .@"switch" },
+ .{ "this", .this },
+ .{ "throw", .throw },
+ .{ "true", .true },
+ .{ "try", .@"try" },
+ .{ "typeof", .typeof },
+ .{ "var", .@"var" },
+ .{ "void", .void },
+ .{ "while", .@"while" },
+ .{ "with", .with },
+ .{ "yield", .yield },
+ .{ "using", .using },
+ });
+
+ if (keywords.get(s)) |k| return k;
+ return .identifier;
+ }
+
+ pub fn tokenizeComptime(comptime source: []const u8) []const Token {
+ comptime {
+ return tokenize(source);
+ }
+ }
+};
diff --git a/src/z/js/parser/parser.zig b/src/z/js/parser/parser.zig
new file mode 100644
index 0000000..f269401
--- /dev/null
+++ b/src/z/js/parser/parser.zig
@@ -0,0 +1,117 @@
+const std = @import("std");
+
+const mod = @import("root.zig");
+const Result = mod.Result;
+
+pub fn Parser(T: type) type {
+ const R = Result(T);
+
+ return struct {
+ parse: *const fn ([]const u8) anyerror!R,
+
+ pub fn parseAll(self: *const @This(), buffer: []const u8) !T {
+ const value, const next = try self.parse(buffer);
+
+ if (next.len > 0) {
+ return error.NotExhaustive;
+ }
+
+ return value;
+ }
+
+ pub fn fromType(P: type) @This() {
+ return .{ .parse = P.parse };
+ }
+
+ pub fn variants(V: type) @This() {
+ if (@typeInfo(T) != .@"union") {
+ @compileError("variants is only allowed with an union as result type");
+ }
+
+ return .{
+ .parse = struct {
+ fn parse(buffer: []const u8) !R {
+ inline for (std.meta.fields(T)) |field| {
+ if (@field(V, field.name).parse(buffer)) |result| {
+ const value, const next = result;
+ return .{
+ @unionInit(T, field.name, value),
+ next,
+ };
+ } else |_| {}
+ }
+
+ return error.Variants;
+ }
+ }.parse,
+ };
+ }
+
+ pub fn any(P: type, comptime parsers: []const struct { Parser(P), T }) @This() {
+ return .{
+ .parse = struct {
+ fn parse(buffer: []const u8) !R {
+ inline for (parsers) |pair| {
+ const parser, const value = pair;
+
+ if (parser.parse(buffer)) |result| {
+ _, const next = result;
+ return .{ value, next };
+ } else |_| {}
+ }
+ return error.Any;
+ }
+ }.parse,
+ };
+ }
+ };
+}
+
+test "variants" {
+ const Char = union(enum) {
+ upper: u8,
+ lower: u8,
+ space: void,
+ };
+
+ const parser: Parser(Char) = .variants(struct {
+ const upper: Parser(u8) = .any(void, &.{
+ .{ mod.builtin.literal("A"), 'A' },
+ .{ mod.builtin.literal("B"), 'B' },
+ });
+
+ const lower: Parser(u8) = .any(void, &.{
+ .{ mod.builtin.literal("a"), 'a' },
+ .{ mod.builtin.literal("b"), 'b' },
+ });
+
+ const space: Parser(void) = mod.builtin.literal(" ");
+ });
+
+ {
+ const result = try parser.parseAll("a");
+ try std.testing.expect(result.lower == 'a');
+ }
+
+ {
+ const result = try parser.parseAll("b");
+ try std.testing.expect(result.lower == 'b');
+ }
+
+ {
+ const result = try parser.parseAll("A");
+ try std.testing.expect(result.upper == 'A');
+ }
+
+ {
+ const result = try parser.parseAll("B");
+ try std.testing.expect(result.upper == 'B');
+ }
+
+ {
+ const result = try parser.parseAll(" ");
+ try std.testing.expect(result == .space);
+ }
+
+ try std.testing.expect(parser.parseAll("c") == error.Variants);
+}
diff --git a/src/z/js/parser/result.zig b/src/z/js/parser/result.zig
new file mode 100644
index 0000000..106dabe
--- /dev/null
+++ b/src/z/js/parser/result.zig
@@ -0,0 +1,3 @@
+pub fn Result(T: type) type {
+ return struct { T, []const u8 };
+}
diff --git a/src/z/js/parser/root.zig b/src/z/js/parser/root.zig
new file mode 100644
index 0000000..6d41c7b
--- /dev/null
+++ b/src/z/js/parser/root.zig
@@ -0,0 +1,93 @@
+const std = @import("std");
+
+pub const lexical_grammar = @import("lexical_grammar.zig");
+pub const ast = @import("ast.zig");
+pub const syntactic_grammar = @import("syntactic_grammar.zig");
+pub const Result = @import("result.zig").Result;
+pub const Parser = @import("parser.zig").Parser;
+
+test "simple literal" {
+ const source = "42;";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+ try std.testing.expect(result.body[0].data == .expr);
+}
+
+test "variable declaration" {
+ const source = "var x = 1;";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+ try std.testing.expect(result.body[0].data == .variable);
+}
+
+test "function declaration" {
+ const source = "function foo() { return 1; }";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+ try std.testing.expect(result.body[0].data == .function);
+}
+
+test "if statement" {
+ const source = "if (x > 0) { x = 1; } else { x = 2; }";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+ try std.testing.expect(result.body[0].data == .@"if");
+}
+
+test "binary expression" {
+ const source = "a + b * c;";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+}
+
+test "arrow function" {
+ const source = "const f = (x) => x + 1;";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+}
+
+test "object literal" {
+ const source = "var obj = { a: 1, b: 2 };";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+}
+
+test "class declaration" {
+ const source = "class Foo { constructor() { } }";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+}
+
+test "for loop" {
+ const source = "for (var i = 0; i < 10; i++) { break; }";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+}
+
+test "try catch" {
+ const source = "try { x; } catch (e) { y; }";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+}
+
+test "multiple statements" {
+ const source = "var a = 1; var b = 2; var c = a + b;";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 3);
+}
+
+test "array literal with spread" {
+ const source = "var arr = [1, ...rest, 3];";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+}
+
+test "template literal" {
+ const source = "const s = `hello ${name} world`;";
+ const result = try syntactic_grammar.parse(source);
+ try std.testing.expect(result.body.len == 1);
+}
+
+test {
+ _ = std.testing.refAllDecls(@This());
+}
diff --git a/src/z/js/parser/syntactic_grammar.zig b/src/z/js/parser/syntactic_grammar.zig
new file mode 100644
index 0000000..065e827
--- /dev/null
+++ b/src/z/js/parser/syntactic_grammar.zig
@@ -0,0 +1,1907 @@
+const std = @import("std");
+
+const lex = @import("lexical_grammar.zig");
+const Token = lex.Token;
+const TokenType = lex.TokenType;
+const Loc = lex.Loc;
+
+const ast = @import("ast.zig");
+const Expr = ast.Expr;
+const ExprType = ast.ExprType;
+const Stmt = ast.Stmt;
+const StmtType = ast.StmtType;
+const Pat = ast.Pat;
+const PatType = ast.PatType;
+const Decl = ast.Decl;
+const DeclType = ast.DeclType;
+const Lit = ast.Lit;
+const LitType = ast.LitType;
+
+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()) Token {
+ const t = self.tokens[self.pos];
+ self.pos += 1;
+ return t;
+ }
+
+ fn expect(self: *@This(), kind: TokenType) ParseError!Token {
+ if (self.peek() != kind) return ParseError.ExpectedToken;
+ return self.advance();
+ }
+
+ fn match(self: *@This(), kind: TokenType) bool {
+ if (self.peek() == kind) {
+ self.advance();
+ return true;
+ }
+ return false;
+ }
+
+ fn matchKeyword(self: *@This(), kw: TokenType) bool {
+ if (self.peek() == kw) {
+ self.advance();
+ 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];
+ // Check if there's a line terminator between prev and next tokens
+ 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', 0x2028, 0x2029 => return true,
+ else => {},
+ }
+ }
+ return false;
+ }
+
+ fn as_identifier(self: *@This(), tok: Token) []const u8 {
+ return self.source[tok.loc.start..tok.loc.end];
+ }
+
+ fn tokenSlice(self: *@This(), tok: Token) []const u8 {
+ return self.source[tok.loc.start..tok.loc.end];
+ }
+
+ pub fn parseProgram(self: *@This()) ParseError!ast.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.advance();
+ 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.advance();
+ 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.advance().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.expect(.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.advance().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.advance().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.advance().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.advance().loc.start;
+ try self.expect(.lparen);
+
+ var init: ?union(enum) { expr: *const Expr, decl: *const Decl } = 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();
+ init = .{ .decl = decl };
+ } else {
+ const expr = try self.parseExpr(0);
+ 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 = 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 = 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 = init, .condition = condition, .update = update, .body = body } },
+ };
+ }
+
+ fn parseForDeclaration(self: *@This()) ParseError!*const Decl {
+ const kind_tok = self.advance();
+ var decls: [128]ast.VarDeclarator = undefined;
+ var count: usize = 0;
+
+ while (true) {
+ const pat = try self.parseBindingPattern();
+ var init: ?*const Expr = null;
+ if (self.match(.@"=")) {
+ init = try self.parseExpr(0);
+ }
+ decls[count] = .{ .loc = pat.loc, .id = pat, .init = 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 });
+ }
+
+ fn parseContinue(self: *@This()) ParseError!Stmt {
+ const start = self.advance().loc.start;
+ var label: ?[]const u8 = null;
+ if (self.peek() == .identifier and !self.isLineTerminatorBeforeNext()) {
+ const t = self.advance();
+ 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.advance().loc.start;
+ var label: ?[]const u8 = null;
+ if (self.peek() == .identifier and !self.isLineTerminatorBeforeNext()) {
+ const t = self.advance();
+ 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.advance().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.advance().loc.start;
+ try self.expect(.lparen);
+ const discriminant = try self.parseExpr(0);
+ try self.expect(.rparen);
+ try self.expect(.lbrace);
+
+ var cases: [256]ast.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.expect(.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.advance().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.advance().loc.start;
+ const block = try self.parseBlockStmt();
+ var handler: ?ast.CatchClause = null;
+ var finalizer: ?ast.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.advance().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!ast.BlockStmt {
+ const start = try self.expect(.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.expect(.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.advance();
+ var decls: [128]ast.VarDeclarator = undefined;
+ var count: usize = 0;
+
+ while (true) {
+ const pat = try self.parseBindingPattern();
+ var init: ?*const Expr = null;
+ if (self.match(.@"=")) {
+ init = try self.parseExpr(0);
+ }
+ decls[count] = .{ .loc = pat.loc, .id = pat, .init = init };
+ count += 1;
+ if (!self.match(.comma)) break;
+ }
+
+ self.semicolon();
+ const kind: ast.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.advance();
+ var decls: [128]ast.VarDeclarator = undefined;
+ var count: usize = 0;
+
+ while (true) {
+ const pat = try self.parseBindingPattern();
+ var init: ?*const Expr = null;
+ if (kind_tok.kind == .@"const") {
+ if (!self.match(.@"=")) return ParseError.ExpectedToken;
+ init = try self.parseExpr(0);
+ } else if (self.match(.@"=")) {
+ init = try self.parseExpr(0);
+ }
+ decls[count] = .{ .loc = pat.loc, .id = pat, .init = init };
+ count += 1;
+ if (!self.match(.comma)) break;
+ }
+
+ self.semicolon();
+ const kind: ast.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.advance().loc.start;
+ var decls: [128]ast.VarDeclarator = undefined;
+ var count: usize = 0;
+
+ while (true) {
+ const id_tok = try self.expect(.identifier);
+ const id = self.tokenSlice(id_tok);
+ var init: ?*const Expr = null;
+ if (self.match(.@"=")) {
+ init = try self.parseExpr(0);
+ }
+ decls[count] = .{ .loc = .{ .start = id_tok.loc.start, .end = if (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 = 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: ast.FnType, mode: enum { stmt, expr }) ParseError!?Stmt {
+ const start = self.tok().loc.start;
+
+ if (fn_type == .async) {
+ _ = self.advance(); // async
+ } else if (fn_type == .async_generator) {
+ _ = self.advance(); // async
+ _ = self.advance(); // *
+ }
+
+ _ = self.advance(); // 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.advance();
+ 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.advance().loc.start;
+
+ var fn_type: ast.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.advance();
+ 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!ast.BlockStmt {
+ return self.parseBlockStmt();
+ }
+
+ fn parseClassDecl(self: *@This(), mode: enum { stmt, expr }) ParseError!?Stmt {
+ const start = self.advance().loc.start;
+
+ var id: ?[]const u8 = null;
+ if (self.peek() == .identifier) {
+ const id_tok = self.advance();
+ 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]ast.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.expect(.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.advance().loc.start;
+
+ var id: ?[]const u8 = null;
+ if (self.peek() == .identifier) {
+ const id_tok = self.advance();
+ 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]ast.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.expect(.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!ast.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 ast.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 ast.ClassElem{ .static_get = .{ .key = key, .body = body } };
+ return ast.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 ast.ClassElem{ .static_set = .{ .key = key, .param = param, .body = body } };
+ return ast.ClassElem{ .set = .{ .key = key, .param = param, .body = body } };
+ }
+
+ if (self.match(.@"*")) {
+ const key = try self.parsePropKey();
+ try self.expect(.lparen);
+ const params = try self.parseParams();
+ const body = try self.parseFunctionBody();
+ const fn_expr = ast.FnExpr{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = .generator, .id = null, .params = params, .body = body };
+ if (is_static) return ast.ClassElem{ .static_method = fn_expr };
+ return ast.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 = ast.FnExpr{ .loc = .{ .start = start, .end = body.loc.end }, .fn_type = .normal, .id = null, .params = params, .body = body };
+ if (is_static) return ast.ClassElem{ .static_method = fn_expr };
+ return ast.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 = ast.FieldDef{ .loc = .{ .start = start, .end = if (value) |v| v.loc.end else self.tok().loc.start }, .key = key, .value = value };
+ if (is_static) return ast.ClassElem{ .static_field = field };
+ return ast.ClassElem{ .field = field };
+ }
+
+ fn parseImport(self: *@This()) ParseError!Stmt {
+ const start = self.advance().loc.start;
+ var specifiers: [32]ast.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.advance();
+ 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.expect(.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.expect(.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]ast.ImportAttribute = undefined;
+ var attr_count: usize = 0;
+
+ if (self.matchKeyword(.from)) {
+ const str_tok = try self.expect(.string);
+ source = self.tokenSlice(str_tok);
+ } else if (self.peek() == .string) {
+ const str_tok = self.advance();
+ 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.advance();
+ const key = self.tokenSlice(key_tok);
+ try self.expect(.@":");
+ const val_tok = try self.expect(.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.advance().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.expect(.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.advance();
+ _ = self.match(.identifier); // as
+ if (!self.match(.comma)) break;
+ }
+ try self.expect(.rbrace);
+ if (self.matchKeyword(.from)) {
+ _ = self.advance();
+ }
+ 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;
+ }
+
+ // Expression parsing with precedence climbing
+ fn parseExpr(self: *@This(), min_bp: u32) ParseError!*const Expr {
+ var left = try self.parsePrimaryExpr();
+
+ while (true) {
+ const bp = self.bp(self.peek());
+ if (bp <= min_bp) break;
+
+ switch (self.peek()) {
+ .@"=",
+ .@"+=",
+ .@"-=",
+ .@"*=",
+ .@"/=",
+ .@"%=",
+ .@"**=",
+ .@"<<=",
+ .@">>=",
+ .@">>>=",
+ .@"&=",
+ .@"|=",
+ .@"^=",
+ .@"&&=",
+ .@"||=",
+ .@"??=",
+ => {
+ self.advance();
+ const op = self.assignOpFromToken();
+ const right = try self.parseExpr(BP.assign);
+ const 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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .start = left.loc.start, .end = right.loc.end };
+ left = self.arena.exprPtr(.{ .binary = .{ .loc = loc, .op = .@"**", .left = left, .right = right } }, loc);
+ },
+
+ .lparen => {
+ // Call expression
+ 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 = .{ .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) {
+ // optional call
+ 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 = .{ .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 = .{ .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.advance();
+ const prop = self.tokenSlice(prop_tok);
+ const 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.advance();
+ const prop = self.tokenSlice(prop_tok);
+ const loc = .{ .start = left.loc.start, .end = prop_tok.loc.end };
+ left = self.arena.exprPtr(.{ .member = .{ .loc = loc, .obj = left, .prop = .{ .private = prop } } }, loc);
+ }
+ },
+
+ .@"[" => {
+ self.advance();
+ const expr = try self.parseExpr(0);
+ try self.expect(.rbracket);
+ const 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.advance();
+ const prop = self.tokenSlice(prop_tok);
+ const 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.expect(.identifier);
+ const prop = self.tokenSlice(prop_tok);
+ const 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 = .{ .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 = .{ .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 = .{ .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 = .{ .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.advance();
+ return self.arena.exprPtr(.{ .this = {} }, .{ .start = start, .end = t.loc.end });
+ },
+ .super => {
+ const t = self.advance();
+ return self.arena.exprPtr(.{ .super = {} }, .{ .start = start, .end = t.loc.end });
+ },
+ .null => {
+ const t = self.advance();
+ return self.arena.exprPtr(.{ .null = {} }, .{ .start = start, .end = t.loc.end });
+ },
+ .true => {
+ const t = self.advance();
+ return self.arena.exprPtr(.{ .bool = true }, .{ .start = start, .end = t.loc.end });
+ },
+ .false => {
+ const t = self.advance();
+ return self.arena.exprPtr(.{ .bool = false }, .{ .start = start, .end = t.loc.end });
+ },
+ .number => {
+ const t = self.advance();
+ 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.advance();
+ return self.arena.exprPtr(.{ .string = self.tokenSlice(t) }, .{ .start = start, .end = t.loc.end });
+ },
+ .bigint => {
+ const t = self.advance();
+ const slice = self.tokenSlice(t);
+ return self.arena.exprPtr(.{ .number = 0 }, .{ .start = start, .end = t.loc.end });
+ },
+ .regex => {
+ const t = self.advance();
+ 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.advance();
+ const name = self.tokenSlice(t);
+ return self.arena.exprPtr(.{ .identifier = name }, .{ .start = start, .end = t.loc.end });
+ },
+ .private_identifier => {
+ const t = self.advance();
+ 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 = .{ .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();
+ }
+ // async arrow function
+ const id_tok = try self.expect(.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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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 = .{ .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);
+ const source = 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.expect(.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.advance();
+ 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 {
+ _ = params_info;
+ 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.advance().loc.start;
+ var props: [128]ast.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() == .@"(") {
+ 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() == .@"," 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.advance().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!ast.TemplateLit {
+ const start = self.tok().loc.start;
+ var quasis: [64]ast.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.advance();
+ 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.advance();
+ 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.advance();
+ 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.advance();
+ 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!ast.PropKey {
+ switch (self.peek()) {
+ .identifier, .let, .static, .get, .set, .async => {
+ const t = self.advance();
+ return ast.PropKey{ .ident = self.tokenSlice(t) };
+ },
+ .string => {
+ const t = self.advance();
+ return ast.PropKey{ .string = self.tokenSlice(t) };
+ },
+ .number => {
+ const t = self.advance();
+ const val = std.fmt.parseFloat(f64, self.tokenSlice(t)) catch 0.0;
+ return ast.PropKey{ .number = val };
+ },
+ .lbracket => {
+ self.advance();
+ const expr = try self.parseExpr(0);
+ try self.expect(.rbracket);
+ return ast.PropKey{ .computed = expr };
+ },
+ .private_identifier => {
+ const t = self.advance();
+ return ast.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.advance();
+ const name = self.tokenSlice(t);
+ return Pat{ .loc = .{ .start = start, .end = t.loc.end }, .data = .{ .ident = name } };
+ },
+ .lbrace => {
+ self.advance();
+ var props: [128]ast.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(self: *@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,
+ .@"[", .@".", .@"?." => BP.member,
+ .@"++", .@"--" => BP.update,
+ .template_head, .no_sub_template => BP.member,
+ else => 0,
+ };
+ }
+
+ fn unaryOpFromToken(self: *@This()) ast.UnaryOp {
+ return switch (self.tok().kind) {
+ .@"+" => .@"+",
+ .@"-" => .@"-",
+ .@"!" => .@"!",
+ .@"~" => .@"~",
+ .typeof => .typeof,
+ .void => .void,
+ .delete => .delete,
+ else => .@"+",
+ };
+ }
+
+ fn assignOpFromToken(self: *@This()) ast.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 fn parse(comptime source: []const u8) ParseError!ast.Program {
+ comptime {
+ const tokens = lex.Lexer.tokenize(source);
+ var arena: NodeArena = .{};
+ var parser = Parser.init(tokens, source, &arena);
+ const result = try parser.parseProgram();
+ return result;
+ }
+}
+
+pub fn parseTokens(comptime source: []const u8) ParseError![]const Token {
+ comptime {
+ return lex.Lexer.tokenize(source);
+ }
+}
+
+pub fn tokenType(kind: TokenType) []const u8 {
+ return @tagName(kind);
+}