aboutsummaryrefslogtreecommitdiff
path: root/src/z/parser/js/ast/stmt.zig
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2026-07-30 10:24:19 +0200
committerNathan Reiner <nathan@nathanreiner.xyz>2026-07-30 10:24:19 +0200
commit00920ddd36a8ebb851b52c3c048c0017753176bd (patch)
tree8498e65ca6c055333aa95b6c3a0d28c8370cb0ef /src/z/parser/js/ast/stmt.zig
parente8259360516078517a2fbeddbf781f701f35d0d0 (diff)
complete js parser + minifierjs-parser
Diffstat (limited to 'src/z/parser/js/ast/stmt.zig')
-rw-r--r--src/z/parser/js/ast/stmt.zig865
1 files changed, 865 insertions, 0 deletions
diff --git a/src/z/parser/js/ast/stmt.zig b/src/z/parser/js/ast/stmt.zig
new file mode 100644
index 0000000..c15b1f5
--- /dev/null
+++ b/src/z/parser/js/ast/stmt.zig
@@ -0,0 +1,865 @@
+const std = @import("std");
+const Loc = @import("loc.zig").Loc;
+const Expr = @import("expr.zig").Expr;
+const Pat = @import("pat.zig").Pat;
+const Decl = @import("decl.zig").Decl;
+const FnDecl = @import("decl.zig").FnDecl;
+const ClassDecl = @import("decl.zig").ClassDecl;
+const ImportDecl = @import("decl.zig").ImportDecl;
+const ExportDecl = @import("decl.zig").ExportDecl;
+const Parser = @import("../parser.zig").Parser;
+const Formatter = @import("../formatter.zig").Formatter;
+const ParseError = @import("../parser.zig").ParseError;
+
+pub const StmtType = union(enum) {
+ block: BlockStmt,
+ empty: void,
+ debugger: void,
+ expr: ExprStmt,
+ @"if": IfStmt,
+ @"while": WhileStmt,
+ do_while: DoWhileStmt,
+ @"for": ForStmt,
+ for_in: ForInStmt,
+ for_of: ForOfStmt,
+ @"continue": ContinueStmt,
+ @"break": BreakStmt,
+ @"return": ReturnStmt,
+ @"switch": SwitchStmt,
+ throw: ThrowStmt,
+ @"try": TryStmt,
+ labelled: LabelledStmt,
+ with: WithStmt,
+ variable: VarStmt,
+ lexical: LexicalDecl,
+ function: FnDecl,
+ class: ClassDecl,
+ using: UsingStmt,
+ import: ImportDecl,
+ @"export": ExportDecl,
+};
+
+pub const Stmt = struct {
+ loc: Loc,
+ data: StmtType,
+
+ pub fn parse(p: *Parser) ParseError!Stmt {
+ switch (p.peek()) {
+ .lbrace => {
+ const b = try BlockStmt.parse(p);
+ return Stmt{ .loc = b.loc, .data = .{ .block = b } };
+ },
+ .semicolon => {
+ const t = p.advanceTok();
+ return Stmt{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .data = .{ .empty = {} } };
+ },
+ .@"if" => {
+ const s = try IfStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .@"if" = s } };
+ },
+ .@"while" => {
+ const s = try WhileStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .@"while" = s } };
+ },
+ .do => {
+ const s = try DoWhileStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .do_while = s } };
+ },
+ .@"for" => {
+ const start = p.advanceTok().loc.start;
+ try p.expect(.lparen);
+ var for_init: ?ForInit = null;
+ if (p.peek() != .semicolon) {
+ if (p.peek() == .@"var" or p.peek() == .let or p.peek() == .@"const") {
+ const decl = try p.parseForDeclaration();
+ for_init = .{ .decl = decl };
+ } else {
+ const expr = try Expr.parse(p, 0);
+ for_init = .{ .expr = expr };
+ }
+ }
+ if (p.matchKeyword(.in)) {
+ const s = try ForInStmt.parse(p, start, for_init);
+ return Stmt{ .loc = s.loc, .data = .{ .for_in = s } };
+ }
+ if (p.matchKeyword(.of)) {
+ const s = try ForOfStmt.parse(p, start, for_init);
+ return Stmt{ .loc = s.loc, .data = .{ .for_of = s } };
+ }
+ const s = try ForStmt.parse(p, start, for_init);
+ return Stmt{ .loc = s.loc, .data = .{ .@"for" = s } };
+ },
+ .@"continue" => {
+ const s = try ContinueStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .@"continue" = s } };
+ },
+ .@"break" => {
+ const s = try BreakStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .@"break" = s } };
+ },
+ .@"return" => {
+ const s = try ReturnStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .@"return" = s } };
+ },
+ .@"switch" => {
+ const s = try SwitchStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .@"switch" = s } };
+ },
+ .throw => {
+ const s = try ThrowStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .throw = s } };
+ },
+ .@"try" => {
+ const s = try TryStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .@"try" = s } };
+ },
+ .debugger => {
+ const t = p.advanceTok();
+ p.semicolon();
+ return Stmt{ .loc = .{ .start = t.loc.start, .end = t.loc.end }, .data = .{ .debugger = {} } };
+ },
+ .with => {
+ const s = try WithStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .with = s } };
+ },
+ .function => return (try FnDecl.parse(p, .normal, .stmt)).?,
+ .async => {
+ if (p.peekAt(1) == .function) {
+ return (try FnDecl.parse(p, .async, .stmt)).?;
+ }
+ if (p.peekAt(1) == .@"*" and p.peekAt(2) == .function) {
+ return (try FnDecl.parse(p, .async_generator, .stmt)).?;
+ }
+ return Stmt.parse(p);
+ },
+ .class => return (try ClassDecl.parse(p, .stmt)).?,
+ .@"const", .let => {
+ const d = try LexicalDecl.parse(p);
+ return Stmt{ .loc = d.loc, .data = .{ .lexical = d } };
+ },
+ .@"var" => {
+ const s = try VarStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .variable = s } };
+ },
+ .using => {
+ const s = try UsingStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .using = s } };
+ },
+ .import => {
+ const d = try ImportDecl.parse(p);
+ return Stmt{ .loc = d.loc, .data = .{ .import = d } };
+ },
+ .@"export" => {
+ const d = try ExportDecl.parse(p);
+ return Stmt{ .loc = d.loc, .data = .{ .@"export" = d } };
+ },
+ else => {
+ const s = try ExprStmt.parse(p);
+ return Stmt{ .loc = s.loc, .data = .{ .expr = s } };
+ },
+ }
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ switch (self.data) {
+ .block => |b| b.fmt(f),
+ .empty => f.writeChar(';'),
+ .debugger => f.write("debugger;"),
+ .expr => |e| {
+ e.expr.*.fmt(f);
+ f.writeChar(';');
+ },
+ .@"if" => |s| s.fmt(f),
+ .@"while" => |w| w.fmt(f),
+ .do_while => |d| d.fmt(f),
+ .@"for" => |fo| fo.fmt(f),
+ .for_in => |fi| fi.fmt(f),
+ .for_of => |fo| fo.fmt(f),
+ .@"continue" => |c| {
+ f.write("continue");
+ if (c.label) |l| {
+ f.writeSpace();
+ f.write(l);
+ }
+ f.writeChar(';');
+ },
+ .@"break" => |b| {
+ f.write("break");
+ if (b.label) |l| {
+ f.writeSpace();
+ f.write(l);
+ }
+ f.writeChar(';');
+ },
+ .@"return" => |r| {
+ f.write("return");
+ if (r.arg) |a| {
+ f.writeSpace();
+ a.*.fmt(f);
+ }
+ f.writeChar(';');
+ },
+ .@"switch" => |s| s.fmt(f),
+ .throw => |t| {
+ f.write("throw ");
+ t.arg.*.fmt(f);
+ f.writeChar(';');
+ },
+ .@"try" => |t| t.fmt(f),
+ .labelled => |l| {
+ f.write(l.label);
+ f.write(": ");
+ l.body.*.fmt(f);
+ },
+ .with => |w| {
+ f.write("with (");
+ w.obj.*.fmt(f);
+ f.write(") ");
+ w.body.*.fmt(f);
+ },
+ .variable => |v| v.fmt(f),
+ .lexical => |l| l.fmt(f),
+ .using => |u| {
+ f.write("using ");
+ for (u.decls, 0..) |d, i| {
+ if (i > 0) f.write(", ");
+ d.fmt(f);
+ }
+ f.writeChar(';');
+ },
+ .import => |i| i.fmt(f),
+ .@"export" => |e| e.fmt(f),
+ .class => |c| c.fmt(f),
+ .function => |d| d.fmt(f),
+ }
+ }
+};
+
+pub const ForInit = union(enum) {
+ expr: *const Expr,
+ decl: *const Decl,
+};
+
+pub const BlockStmt = struct {
+ loc: Loc,
+ body: []const Stmt,
+
+ pub fn parse(p: *Parser) ParseError!BlockStmt {
+ const start = try p.expectTok(.lbrace);
+ var stmts: [1024]Stmt = undefined;
+ var count: usize = 0;
+
+ while (p.peek() != .rbrace and p.peek() != .eof) {
+ const s = try Stmt.parse(p);
+ stmts[count] = s;
+ count += 1;
+ if (count >= stmts.len) return ParseError.TooManyTokens;
+ }
+
+ const end = try p.expectTok(.rbrace);
+ return .{ .loc = .{ .start = start.loc.start, .end = end.loc.end }, .body = stmts[0..count] };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.writeChar('{');
+ if (self.body.len > 0) {
+ f.newline();
+ f.indent_level += 1;
+ for (self.body) |s| {
+ f.writeIndent();
+ s.fmt(f);
+ f.newline();
+ }
+ f.indent_level -= 1;
+ f.writeIndent();
+ }
+ f.writeChar('}');
+ }
+};
+
+pub const ExprStmt = struct {
+ loc: Loc,
+ expr: *const Expr,
+
+ pub fn parse(p: *Parser) ParseError!ExprStmt {
+ const expr = try Expr.parse(p, 0);
+ p.semicolon();
+ return .{ .loc = expr.loc, .expr = expr };
+ }
+};
+
+pub const IfStmt = struct {
+ loc: Loc,
+ condition: *const Expr,
+ consequent: *const Stmt,
+ alternate: ?*const Stmt,
+
+ pub fn parse(p: *Parser) ParseError!IfStmt {
+ const start = p.advanceTok().loc.start;
+ try p.expect(.lparen);
+ const condition = try Expr.parse(p, 0);
+ try p.expect(.rparen);
+ const consequent_stmt = try Stmt.parse(p);
+ const consequent = p.arena.stmtPtr(consequent_stmt.data, consequent_stmt.loc);
+ var alternate: ?*const Stmt = null;
+ if (p.matchKeyword(.@"else")) {
+ const alt_stmt = try Stmt.parse(p);
+ alternate = p.arena.stmtPtr(alt_stmt.data, alt_stmt.loc);
+ }
+ const end = if (alternate) |a| a.loc.end else consequent.loc.end;
+ return .{ .loc = .{ .start = start, .end = end }, .condition = condition, .consequent = consequent, .alternate = alternate };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("if (");
+ self.condition.*.fmt(f);
+ f.write(") ");
+ self.consequent.*.fmt(f);
+ if (self.alternate) |alt| {
+ f.write(" else ");
+ alt.*.fmt(f);
+ }
+ }
+};
+
+pub const WhileStmt = struct {
+ loc: Loc,
+ condition: *const Expr,
+ body: *const Stmt,
+
+ pub fn parse(p: *Parser) ParseError!WhileStmt {
+ const start = p.advanceTok().loc.start;
+ try p.expect(.lparen);
+ const condition = try Expr.parse(p, 0);
+ try p.expect(.rparen);
+ const body = blk: {
+ const s = try Stmt.parse(p);
+ break :blk p.arena.stmtPtr(s.data, s.loc);
+ };
+ return .{ .loc = .{ .start = start, .end = body.loc.end }, .condition = condition, .body = body };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("while (");
+ self.condition.*.fmt(f);
+ f.write(") ");
+ self.body.*.fmt(f);
+ }
+};
+
+pub const DoWhileStmt = struct {
+ loc: Loc,
+ body: *const Stmt,
+ condition: *const Expr,
+
+ pub fn parse(p: *Parser) ParseError!DoWhileStmt {
+ const start = p.advanceTok().loc.start;
+ const body = blk: {
+ const s = try Stmt.parse(p);
+ break :blk p.arena.stmtPtr(s.data, s.loc);
+ };
+ try p.expect(.@"while");
+ try p.expect(.lparen);
+ const condition = try Expr.parse(p, 0);
+ try p.expect(.rparen);
+ p.semicolon();
+ return .{ .loc = .{ .start = start, .end = condition.loc.end }, .body = body, .condition = condition };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("do ");
+ self.body.*.fmt(f);
+ f.write(" while (");
+ self.condition.*.fmt(f);
+ f.write(");");
+ }
+};
+
+pub const ForStmt = struct {
+ loc: Loc,
+ init: ?ForInit,
+ condition: ?*const Expr,
+ update: ?*const Expr,
+ body: *const Stmt,
+
+ pub fn parse(p: *Parser, start: usize, for_init: ?ForInit) ParseError!ForStmt {
+ var condition: ?*const Expr = null;
+ var update: ?*const Expr = null;
+
+ try p.expect(.semicolon);
+ if (p.peek() != .semicolon and p.peek() != .rparen) {
+ condition = try Expr.parse(p, 0);
+ }
+ try p.expect(.semicolon);
+ if (p.peek() != .rparen) {
+ update = try Expr.parse(p, 0);
+ }
+ try p.expect(.rparen);
+ const body = blk: {
+ const s = try Stmt.parse(p);
+ break :blk p.arena.stmtPtr(s.data, s.loc);
+ };
+ return .{ .loc = .{ .start = start, .end = body.loc.end }, .init = for_init, .condition = condition, .update = update, .body = body };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("for (");
+ if (self.init) |init| {
+ switch (init) {
+ .expr => |e| e.*.fmt(f),
+ .decl => |d| d.*.fmt(f),
+ }
+ }
+ f.writeChar(';');
+ if (self.condition) |c| {
+ f.writeSpace();
+ c.*.fmt(f);
+ }
+ f.writeChar(';');
+ if (self.update) |u| {
+ f.writeSpace();
+ u.*.fmt(f);
+ }
+ f.write(") ");
+ self.body.*.fmt(f);
+ }
+};
+
+pub const ForInStmt = struct {
+ loc: Loc,
+ left: ForInit,
+ right: *const Expr,
+ body: *const Stmt,
+
+ pub fn parse(p: *Parser, start: usize, for_init: ?ForInit) ParseError!ForInStmt {
+ const right = try Expr.parse(p, 0);
+ try p.expect(.rparen);
+ const body = blk: {
+ const s = try Stmt.parse(p);
+ break :blk p.arena.stmtPtr(s.data, s.loc);
+ };
+ return .{ .loc = .{ .start = start, .end = body.loc.end }, .left = for_init.?, .right = right, .body = body };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("for (");
+ switch (self.left) {
+ .expr => |e| e.*.fmt(f),
+ .decl => |d| d.*.fmt(f),
+ }
+ f.write(" in ");
+ self.right.*.fmt(f);
+ f.write(") ");
+ self.body.*.fmt(f);
+ }
+};
+
+pub const ForOfStmt = struct {
+ loc: Loc,
+ await_token: bool,
+ left: ForInit,
+ right: *const Expr,
+ body: *const Stmt,
+
+ pub fn parse(p: *Parser, start: usize, for_init: ?ForInit) ParseError!ForOfStmt {
+ const right = try Expr.parse(p, 0);
+ try p.expect(.rparen);
+ const body = blk: {
+ const s = try Stmt.parse(p);
+ break :blk p.arena.stmtPtr(s.data, s.loc);
+ };
+ return .{ .loc = .{ .start = start, .end = body.loc.end }, .await_token = false, .left = for_init.?, .right = right, .body = body };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("for");
+ if (self.await_token) f.write(" await");
+ f.write(" (");
+ switch (self.left) {
+ .expr => |e| e.*.fmt(f),
+ .decl => |d| d.*.fmt(f),
+ }
+ f.write(" of ");
+ self.right.*.fmt(f);
+ f.write(") ");
+ self.body.*.fmt(f);
+ }
+};
+
+pub const ContinueStmt = struct {
+ loc: Loc,
+ label: ?[]const u8,
+
+ pub fn parse(p: *Parser) ParseError!ContinueStmt {
+ const start = p.advanceTok().loc.start;
+ var label: ?[]const u8 = null;
+ if (p.peek() == .identifier and !p.isLineTerminatorBeforeNext()) {
+ const t = p.advanceTok();
+ label = p.tokenSlice(t);
+ }
+ p.semicolon();
+ return .{ .loc = .{ .start = start, .end = start + 8 }, .label = label };
+ }
+};
+
+pub const BreakStmt = struct {
+ loc: Loc,
+ label: ?[]const u8,
+
+ pub fn parse(p: *Parser) ParseError!BreakStmt {
+ const start = p.advanceTok().loc.start;
+ var label: ?[]const u8 = null;
+ if (p.peek() == .identifier and !p.isLineTerminatorBeforeNext()) {
+ const t = p.advanceTok();
+ label = p.tokenSlice(t);
+ }
+ p.semicolon();
+ return .{ .loc = .{ .start = start, .end = start + 5 }, .label = label };
+ }
+};
+
+pub const ReturnStmt = struct {
+ loc: Loc,
+ arg: ?*const Expr,
+
+ pub fn parse(p: *Parser) ParseError!ReturnStmt {
+ const start = p.advanceTok().loc.start;
+ var arg: ?*const Expr = null;
+ if (p.peek() != .semicolon and p.peek() != .rbrace and p.peek() != .eof and !p.isLineTerminatorBeforeNext()) {
+ arg = try Expr.parse(p, 0);
+ }
+ p.semicolon();
+ return .{ .loc = .{ .start = start, .end = if (arg) |a| a.loc.end else start + 6 }, .arg = arg };
+ }
+};
+
+pub const SwitchStmt = struct {
+ loc: Loc,
+ discriminant: *const Expr,
+ cases: []const SwitchCase,
+
+ pub fn parse(p: *Parser) ParseError!SwitchStmt {
+ const start = p.advanceTok().loc.start;
+ try p.expect(.lparen);
+ const discriminant = try Expr.parse(p, 0);
+ try p.expect(.rparen);
+ try p.expect(.lbrace);
+
+ var cases: [256]SwitchCase = undefined;
+ var case_count: usize = 0;
+
+ while (p.peek() == .case or p.peek() == .default) {
+ const case_start = p.tok().loc.start;
+ if (p.matchKeyword(.case)) {
+ const case_test = try Expr.parse(p, 0);
+ try p.expect(.@":");
+ var consequent: [128]Stmt = undefined;
+ var cons_count: usize = 0;
+ while (p.peek() != .case and p.peek() != .default and p.peek() != .rbrace and p.peek() != .eof) {
+ const s = try Stmt.parse(p);
+ consequent[cons_count] = s;
+ cons_count += 1;
+ }
+ cases[case_count] = .{ .loc = .{ .start = case_start, .end = p.tok().loc.start }, .condition = case_test, .consequent = consequent[0..cons_count] };
+ case_count += 1;
+ } else if (p.matchKeyword(.default)) {
+ try p.expect(.@":");
+ var consequent: [128]Stmt = undefined;
+ var cons_count: usize = 0;
+ while (p.peek() != .case and p.peek() != .default and p.peek() != .rbrace and p.peek() != .eof) {
+ const s = try Stmt.parse(p);
+ consequent[cons_count] = s;
+ cons_count += 1;
+ }
+ cases[case_count] = .{ .loc = .{ .start = case_start, .end = p.tok().loc.start }, .condition = null, .consequent = consequent[0..cons_count] };
+ case_count += 1;
+ }
+ }
+
+ const end_tok = try p.expectTok(.rbrace);
+ return .{ .loc = .{ .start = start, .end = end_tok.loc.end }, .discriminant = discriminant, .cases = cases[0..case_count] };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("switch (");
+ self.discriminant.*.fmt(f);
+ f.write(") {");
+ f.newline();
+ f.indent_level += 1;
+ for (self.cases) |c| {
+ f.writeIndent();
+ c.fmt(f);
+ f.newline();
+ }
+ f.indent_level -= 1;
+ f.writeIndent();
+ f.writeChar('}');
+ }
+};
+
+pub const SwitchCase = struct {
+ loc: Loc,
+ condition: ?*const Expr,
+ consequent: []const Stmt,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ if (self.condition) |c| {
+ f.write("case ");
+ c.*.fmt(f);
+ } else {
+ f.write("default");
+ }
+ f.writeChar(':');
+ for (self.consequent) |s| {
+ f.writeSpace();
+ s.fmt(f);
+ }
+ }
+};
+
+pub const ThrowStmt = struct {
+ loc: Loc,
+ arg: *const Expr,
+
+ pub fn parse(p: *Parser) ParseError!ThrowStmt {
+ const start = p.advanceTok().loc.start;
+ if (p.isLineTerminatorBeforeNext()) return ParseError.ExpectedExpression;
+ const arg = try Expr.parse(p, 0);
+ p.semicolon();
+ return .{ .loc = .{ .start = start, .end = arg.loc.end }, .arg = arg };
+ }
+};
+
+pub const TryStmt = struct {
+ loc: Loc,
+ block: BlockStmt,
+ handler: ?CatchClause,
+ finalizer: ?BlockStmt,
+
+ pub fn parse(p: *Parser) ParseError!TryStmt {
+ const start = p.advanceTok().loc.start;
+ const block = try p.parseFunctionBody();
+ var handler: ?CatchClause = null;
+ var finalizer: ?BlockStmt = null;
+
+ if (p.matchKeyword(.@"catch")) {
+ var param: ?Pat = null;
+ if (p.match(.lparen)) {
+ const pat = try Pat.parse(p);
+ param = pat;
+ try p.expect(.rparen);
+ }
+ const catch_body = try p.parseFunctionBody();
+ handler = .{ .loc = .{ .start = block.loc.start, .end = catch_body.loc.end }, .param = param, .body = catch_body };
+ }
+
+ if (p.matchKeyword(.finally)) {
+ const finally_body = try p.parseFunctionBody();
+ finalizer = finally_body;
+ }
+
+ const end = if (finalizer) |f| f.loc.end else if (handler) |h| h.loc.end else block.loc.end;
+ return .{ .loc = .{ .start = start, .end = end }, .block = block, .handler = handler, .finalizer = finalizer };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("try ");
+ self.block.fmt(f);
+ if (self.handler) |h| {
+ f.writeSpace();
+ h.fmt(f);
+ }
+ if (self.finalizer) |fin| {
+ f.write(" finally ");
+ fin.fmt(f);
+ }
+ }
+};
+
+pub const CatchClause = struct {
+ loc: Loc,
+ param: ?Pat,
+ body: BlockStmt,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("catch");
+ if (self.param) |p| {
+ f.write(" (");
+ p.fmt(f);
+ f.write(") ");
+ } else {
+ f.writeSpace();
+ }
+ self.body.fmt(f);
+ }
+};
+
+pub const LabelledStmt = struct {
+ loc: Loc,
+ label: []const u8,
+ body: *const Stmt,
+};
+
+pub const WithStmt = struct {
+ loc: Loc,
+ obj: *const Expr,
+ body: *const Stmt,
+
+ pub fn parse(p: *Parser) ParseError!WithStmt {
+ const start = p.advanceTok().loc.start;
+ try p.expect(.lparen);
+ const obj = try Expr.parse(p, 0);
+ try p.expect(.rparen);
+ const body = blk: {
+ const s = try Stmt.parse(p);
+ break :blk p.arena.stmtPtr(s.data, s.loc);
+ };
+ return .{ .loc = .{ .start = start, .end = body.loc.end }, .obj = obj, .body = body };
+ }
+};
+
+pub const VarStmt = struct {
+ loc: Loc,
+ kind: VarKind,
+ decls: []const VarDeclarator,
+
+ pub fn parse(p: *Parser) ParseError!VarStmt {
+ const kind_tok = p.advanceTok();
+ var decls: [128]VarDeclarator = undefined;
+ var count: usize = 0;
+
+ while (true) {
+ const pat = try Pat.parse(p);
+ var decl_init: ?*const Expr = null;
+ if (p.match(.@"=")) {
+ decl_init = try Expr.parse(p, 0);
+ }
+ decls[count] = .{ .loc = pat.loc, .id = pat, .init = decl_init };
+ count += 1;
+ if (!p.match(.comma)) break;
+ }
+
+ p.semicolon();
+ return .{ .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, .kind = .@"var", .decls = decls[0..count] };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write(@tagName(self.kind));
+ f.writeSpace();
+ for (self.decls, 0..) |d, i| {
+ if (i > 0) f.write(", ");
+ d.fmt(f);
+ }
+ f.writeChar(';');
+ }
+};
+
+pub const UsingStmt = struct {
+ loc: Loc,
+ decls: []const VarDeclarator,
+
+ pub fn parse(p: *Parser) ParseError!UsingStmt {
+ const start = p.advanceTok().loc.start;
+ var decls: [128]VarDeclarator = undefined;
+ var count: usize = 0;
+
+ while (true) {
+ const id_tok = try p.expectTok(.identifier);
+ const id = p.tokenSlice(id_tok);
+ var decl_init: ?*const Expr = null;
+ if (p.match(.@"=")) {
+ decl_init = try Expr.parse(p, 0);
+ }
+ decls[count] = .{ .loc = .{ .start = id_tok.loc.start, .end = if (decl_init) |e| e.loc.end else id_tok.loc.end }, .id = Pat{ .loc = .{ .start = id_tok.loc.start, .end = id_tok.loc.end }, .data = .{ .ident = id } }, .init = decl_init };
+ count += 1;
+ if (!p.match(.comma)) break;
+ }
+
+ p.semicolon();
+ return .{ .loc = .{ .start = start, .end = decls[count - 1].loc.end }, .decls = decls[0..count] };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write("using ");
+ for (self.decls, 0..) |d, i| {
+ if (i > 0) f.write(", ");
+ d.fmt(f);
+ }
+ }
+};
+
+pub const VarKind = enum {
+ @"var",
+ let,
+ @"const",
+};
+
+pub const VarDecl = struct {
+ loc: Loc,
+ kind: VarKind,
+ decls: []const VarDeclarator,
+};
+
+pub const LexicalDecl = struct {
+ loc: Loc,
+ kind: VarKind,
+ decls: []const VarDeclarator,
+
+ pub fn parse(p: *Parser) ParseError!LexicalDecl {
+ const kind_tok = p.advanceTok();
+ var decls: [128]VarDeclarator = undefined;
+ var count: usize = 0;
+
+ while (true) {
+ const pat = try Pat.parse(p);
+ var decl_init: ?*const Expr = null;
+ if (kind_tok.kind == .@"const") {
+ if (!p.match(.@"=")) return ParseError.ExpectedToken;
+ decl_init = try Expr.parse(p, 0);
+ } else if (p.match(.@"=")) {
+ decl_init = try Expr.parse(p, 0);
+ }
+ decls[count] = .{ .loc = pat.loc, .id = pat, .init = decl_init };
+ count += 1;
+ if (!p.match(.comma)) break;
+ }
+
+ p.semicolon();
+ const kind: VarKind = switch (kind_tok.kind) {
+ .let => .let,
+ .@"const" => .@"const",
+ else => return ParseError.UnexpectedToken,
+ };
+ return .{ .loc = .{ .start = kind_tok.loc.start, .end = decls[count - 1].loc.end }, .kind = kind, .decls = decls[0..count] };
+ }
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ f.write(@tagName(self.kind));
+ f.writeSpace();
+ for (self.decls, 0..) |d, i| {
+ if (i > 0) f.write(", ");
+ d.fmt(f);
+ }
+ f.writeChar(';');
+ }
+};
+
+pub const UsingDecl = struct {
+ loc: Loc,
+ is_await: bool,
+ decls: []const VarDeclarator,
+};
+
+pub const VarDeclarator = struct {
+ loc: Loc,
+ id: Pat,
+ init: ?*const Expr,
+
+ pub fn fmt(self: @This(), f: *Formatter) void {
+ self.id.fmt(f);
+ if (self.init) |init| {
+ f.write(" = ");
+ init.*.fmt(f);
+ }
+ }
+};