diff options
Diffstat (limited to 'src/z/parser/js/ast/decl.zig')
| -rw-r--r-- | src/z/parser/js/ast/decl.zig | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/src/z/parser/js/ast/decl.zig b/src/z/parser/js/ast/decl.zig new file mode 100644 index 0000000..a9a6117 --- /dev/null +++ b/src/z/parser/js/ast/decl.zig @@ -0,0 +1,432 @@ +const std = @import("std"); +const Loc = @import("loc.zig").Loc; +const Expr = @import("expr.zig").Expr; +const Pat = @import("pat.zig").Pat; +const Stmt = @import("stmt.zig").Stmt; +const BlockStmt = @import("stmt.zig").BlockStmt; +const VarStmt = @import("stmt.zig").VarStmt; +const VarDeclarator = @import("stmt.zig").VarDeclarator; +const VarKind = @import("stmt.zig").VarKind; +const FnType = @import("expr.zig").FnType; +const ClassElem = @import("expr.zig").ClassElem; +const Parser = @import("../parser.zig").Parser; +const Formatter = @import("../formatter.zig").Formatter; +const ParseError = @import("../parser.zig").ParseError; + +pub const DeclType = union(enum) { + @"fn": FnDecl, + class: ClassDecl, + @"var": VarDecl, + using: UsingDecl, + import: ImportDecl, + @"export": ExportDecl, + export_default: ExportDefault, +}; + +pub const Decl = struct { + loc: Loc, + data: DeclType, + + pub fn fmt(self: @This(), f: *Formatter) void { + switch (self.data) { + .@"fn" => |d| d.fmt(f), + .class => |c| c.fmt(f), + .@"var" => |v| { + f.write(@tagName(v.kind)); + f.writeSpace(); + for (v.decls, 0..) |d, i| { + if (i > 0) f.write(", "); + d.fmt(f); + } + }, + .using => |u| { + f.write("using "); + for (u.decls, 0..) |d, i| { + if (i > 0) f.write(", "); + d.fmt(f); + } + }, + .import => |i| i.fmt(f), + .@"export" => |e| e.fmt(f), + .export_default => |e| e.fmt(f), + } + } + + pub fn parse(p: *Parser) ParseError!Decl { + switch (p.peek()) { + .@"var" => { + const s = try VarStmt.parse(p); + return Decl{ .loc = s.loc, .data = .{ .@"var" = .{ .loc = s.loc, .kind = s.kind, .decls = s.decls } } }; + }, + else => return ParseError.ExpectedStatement, + } + } +}; + +pub const FnDecl = struct { + loc: Loc, + fn_type: FnType, + id: ?[]const u8, + params: []const Pat, + body: BlockStmt, + + pub fn parse(p: *Parser, fn_type_arg: FnType, mode: enum { stmt, expr }) ParseError!?Stmt { + const start = p.tok().loc.start; + var fn_type = fn_type_arg; + + if (fn_type == .async) { + _ = p.advanceTok(); + } else if (fn_type == .async_generator) { + _ = p.advanceTok(); + _ = p.advanceTok(); + } + + _ = p.advanceTok(); + + if (fn_type == .normal and p.peek() == .@"*") { + p.advance(); + fn_type = .generator; + } + + var id: ?[]const u8 = null; + if (p.peek().isIdentifier()) { + const id_tok = p.advanceTok(); + id = p.tokenSlice(id_tok); + } + + const params = try p.parseParams(); + const body = try p.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; + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("function"); + if (self.fn_type == .generator or self.fn_type == .async_generator) { + f.write("*"); + } + if (self.fn_type == .async or self.fn_type == .async_generator) { + f.write(" async"); + } + if (self.id) |id| { + f.writeSpace(); + f.write(id); + } + f.writeChar('('); + for (self.params, 0..) |p, i| { + if (i > 0) f.write(", "); + p.fmt(f); + } + f.write(") "); + self.body.fmt(f); + } +}; + +pub const ClassDecl = struct { + loc: Loc, + id: ?[]const u8, + super_class: ?*const Expr, + body: []const ClassElem, + + pub fn parse(p: *Parser, mode: enum { stmt, expr }) ParseError!?Stmt { + const start = p.advanceTok().loc.start; + + var id: ?[]const u8 = null; + if (p.peek() == .identifier) { + const id_tok = p.advanceTok(); + id = p.tokenSlice(id_tok); + } + + var super_class: ?*const Expr = null; + if (p.matchKeyword(.extends)) { + super_class = try Expr.parse(p, 0); + } + + try p.expect(.lbrace); + var elems: [128]ClassElem = undefined; + var elem_count: usize = 0; + + while (p.peek() != .rbrace and p.peek() != .eof) { + const elem = try ClassElem.parse(p); + elems[elem_count] = elem; + elem_count += 1; + if (elem_count >= elems.len) return ParseError.TooManyTokens; + } + + const end_tok = try p.expectTok(.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; + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("class"); + if (self.id) |id| { + f.writeSpace(); + f.write(id); + } + if (self.super_class) |sc| { + f.write(" extends "); + sc.*.fmt(f); + } + f.write(" {"); + f.newline(); + f.indent_level += 1; + for (self.body) |elem| { + f.writeIndent(); + elem.fmt(f); + f.newline(); + } + f.indent_level -= 1; + f.writeIndent(); + f.writeChar('}'); + } +}; + +pub const VarDecl = struct { + loc: Loc, + kind: VarKind, + decls: []const VarDeclarator, +}; + +pub const UsingDecl = struct { + loc: Loc, + is_await: bool, + decls: []const VarDeclarator, +}; + +pub const ImportDecl = struct { + loc: Loc, + specifiers: []const ImportSpecifier, + source: []const u8, + attributes: []const ImportAttribute, + + pub fn parse(p: *Parser) ParseError!ImportDecl { + const start = p.advanceTok().loc.start; + var specifiers: [32]ImportSpecifier = undefined; + var spec_count: usize = 0; + + if (p.peek() != .string and p.peek() != .lparen) { + if (p.peek() == .identifier or p.peek().isKeyword()) { + const local_tok = p.advanceTok(); + const local = p.tokenSlice(local_tok); + specifiers[spec_count] = .{ .default = local }; + spec_count += 1; + if (p.match(.comma)) {} + } + + if (p.match(.@"*")) { + try p.expect(.identifier); + const ns_tok = try p.expectTok(.identifier); + const ns = p.tokenSlice(ns_tok); + specifiers[spec_count] = .{ .namespace = ns }; + spec_count += 1; + } else if (p.peek() == .lbrace) { + p.advance(); + if (p.peek() != .rbrace) { + while (true) { + const imported_tok = try p.expectTok(.identifier); + const imported = p.tokenSlice(imported_tok); + var local = imported; + if (p.match(.identifier)) { + local = p.tokenSlice(p.tokens[p.pos - 1]); + } + specifiers[spec_count] = .{ .named = .{ .imported = imported, .local = local } }; + spec_count += 1; + if (!p.match(.comma)) break; + } + } + try p.expect(.rbrace); + } + } + + var source: []const u8 = ""; + var attrs: [8]ImportAttribute = undefined; + var attr_count: usize = 0; + + if (p.matchKeyword(.from)) { + const str_tok = try p.expectTok(.string); + source = p.tokenSlice(str_tok); + } else if (p.peek() == .string) { + const str_tok = p.advanceTok(); + source = p.tokenSlice(str_tok); + } + + if (p.matchKeyword(.with)) { + try p.expect(.lbrace); + while (p.peek() != .rbrace and p.peek() != .eof) { + const key_tok = p.advanceTok(); + const key = p.tokenSlice(key_tok); + try p.expect(.@":"); + const val_tok = try p.expectTok(.string); + const val = p.tokenSlice(val_tok); + attrs[attr_count] = .{ .key = key, .value = val }; + attr_count += 1; + _ = p.match(.comma); + } + try p.expect(.rbrace); + } + + try p.expect(.semicolon); + return .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .specifiers = specifiers[0..spec_count], .source = source, .attributes = attrs[0..attr_count] }; + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("import "); + if (self.specifiers.len > 0) { + for (self.specifiers, 0..) |spec, j| { + if (j > 0) f.write(", "); + spec.fmt(f); + } + f.write(" from "); + } + f.writeChar('"'); + f.write(self.source); + f.writeChar('"'); + for (self.attributes) |a| { + f.write(" /* "); + a.fmt(f); + f.write(" */"); + } + f.writeChar(';'); + } +}; + +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 fn fmt(self: @This(), f: *Formatter) void { + f.write(self.key); + f.write(": \""); + f.write(self.value); + f.writeChar('"'); + } +}; + +pub const ExportDecl = struct { + loc: Loc, + declaration: ?*const Decl, + specifiers: ?[]const ExportSpecifier, + source: ?[]const u8, + + pub fn parse(p: *Parser) ParseError!ExportDecl { + const start = p.advanceTok().loc.start; + + if (p.matchKeyword(.default)) { + if (p.peek() == .function or p.peek() == .async or p.peek() == .class) { + const decl = try Stmt.parse(p); + p.semicolon(); + return .{ .loc = .{ .start = start, .end = decl.loc.end }, .declaration = null, .specifiers = null, .source = null }; + } + const expr = try Expr.parse(p, 0); + p.semicolon(); + return .{ .loc = .{ .start = start, .end = expr.loc.end }, .declaration = null, .specifiers = null, .source = null }; + } + + if (p.peek() == .@"*") { + p.advance(); + try p.expect(.identifier); + const source_tok = try p.expectTok(.string); + const src = p.tokenSlice(source_tok); + try p.expect(.semicolon); + return .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .declaration = null, .specifiers = null, .source = src }; + } + + if (p.peek() == .lbrace) { + p.advance(); + var specs: [32]ExportSpecifier = undefined; + var spec_count: usize = 0; + while (p.peek() != .rbrace and p.peek() != .eof) { + const exported_tok = p.advanceTok(); + const exported = p.tokenSlice(exported_tok); + var local = exported; + if (p.match(.identifier)) { + local = p.tokenSlice(p.tokens[p.pos - 1]); + } + specs[spec_count] = .{ .loc = .{ .start = exported_tok.loc.start, .end = p.tok().loc.start }, .exported = exported, .local = local }; + spec_count += 1; + if (!p.match(.comma)) break; + } + try p.expect(.rbrace); + var source: ?[]const u8 = null; + if (p.matchKeyword(.from)) { + const src_tok = try p.expectTok(.string); + source = p.tokenSlice(src_tok); + } + try p.expect(.semicolon); + return .{ .loc = .{ .start = start, .end = p.tok().loc.start }, .declaration = null, .specifiers = specs[0..spec_count], .source = source }; + } + + const decl = try Stmt.parse(p); + p.semicolon(); + return .{ .loc = .{ .start = start, .end = decl.loc.end }, .declaration = null, .specifiers = null, .source = null }; + } + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("export "); + if (self.declaration) |d| { + d.*.fmt(f); + } else if (self.specifiers) |s| { + f.writeChar('{'); + for (s, 0..) |sp, i| { + if (i > 0) f.write(", "); + sp.fmt(f); + } + f.writeChar('}'); + if (self.source) |src| { + f.write(" from \""); + f.write(src); + f.writeChar('"'); + } + f.writeChar(';'); + } else if (self.source) |src| { + f.write("* from \""); + f.write(src); + f.writeChar("\";"); + } + } +}; + +pub const ExportSpecifier = struct { + loc: Loc, + exported: []const u8, + local: []const u8, + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write(self.local); + if (!std.mem.eql(u8, self.local, self.exported)) { + f.write(" as "); + f.write(self.exported); + } + } +}; + +pub const ExportDefault = struct { + loc: Loc, + declaration: *const Decl, + + pub fn fmt(self: @This(), f: *Formatter) void { + f.write("export default "); + self.declaration.*.fmt(f); + } +}; |