diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-07-30 07:46:37 +0200 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-07-30 08:10:47 +0200 |
| commit | 4d064042e8a6ebf874fecbc23f13073f39fa1ebe (patch) | |
| tree | 55cb0fc9d92dd2edd998bb910985de5e3b9ed012 /src/z/js/parser | |
| parent | d1776161dcc57b600ce7b18c5e7179757491fbc2 (diff) | |
first version of js parser
Diffstat (limited to 'src/z/js/parser')
| -rw-r--r-- | src/z/js/parser/ast.zig | 674 | ||||
| -rw-r--r-- | src/z/js/parser/lexical_grammar.zig | 1099 | ||||
| -rw-r--r-- | src/z/js/parser/parser.zig | 117 | ||||
| -rw-r--r-- | src/z/js/parser/result.zig | 3 | ||||
| -rw-r--r-- | src/z/js/parser/root.zig | 93 | ||||
| -rw-r--r-- | src/z/js/parser/syntactic_grammar.zig | 1907 |
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); +} |