diff options
Diffstat (limited to 'src/z/parser/js/ast/stmt.zig')
| -rw-r--r-- | src/z/parser/js/ast/stmt.zig | 865 |
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); + } + } +}; |