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); } } };