diff options
-rw-r--r-- | src/ast/base.zig | 4 | ||||
-rw-r--r-- | src/js_ast.zig | 123 | ||||
-rw-r--r-- | src/js_parser.zig | 207 | ||||
-rw-r--r-- | src/js_printer.zig | 3 | ||||
-rw-r--r-- | src/logger.zig | 8 | ||||
-rw-r--r-- | src/main.zig | 28 | ||||
-rw-r--r-- | src/panic_handler.zig | 58 |
7 files changed, 371 insertions, 60 deletions
diff --git a/src/ast/base.zig b/src/ast/base.zig index 1b2e3f087..3889cd743 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -44,6 +44,10 @@ pub const Ref = packed struct { pub fn eql(ref: Ref, b: Ref) bool { return ref.inner_index == b.inner_index and ref.source_index == b.source_index; } + + pub fn jsonStringify(self: *const Ref, options: anytype, writer: anytype) !void { + return try std.json.stringify([2]u32{ self.source_index, self.inner_index }, options, writer); + } }; // This is kind of the wrong place, but it's shared between files diff --git a/src/js_ast.zig b/src/js_ast.zig index 1f0f542b4..42138301b 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -93,6 +93,17 @@ pub const Binding = struct { loc: logger.Loc, data: B, + const Serializable = struct { + @"type": Tag, + object: string, + value: B, + loc: logger.Loc, + }; + + pub fn jsonStringify(self: *const @This(), options: anytype, writer: anytype) !void { + return try std.json.stringify(Serializable{ .@"type" = std.meta.activeTag(self.data), .object = "binding", .value = self.data, .loc = self.loc }, options, writer); + } + pub fn ToExpr(comptime expr_type: type, comptime func_type: anytype) type { const ExprType = expr_type; return struct { @@ -801,12 +812,25 @@ pub const E = struct { children: ExprNodeList, }; - pub const Missing = struct {}; + pub const Missing = struct { + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(null, opts, o); + } + }; - pub const Number = struct { value: f64 }; + pub const Number = struct { + value: f64, + pub fn jsonStringify(self: *const Number, opts: anytype, o: anytype) !void { + return try std.json.stringify(self.value, opts, o); + } + }; pub const BigInt = struct { value: string, + + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(self.value, opts, o); + } }; pub const Object = struct { @@ -826,6 +850,20 @@ pub const E = struct { pub fn string(s: *String, allocator: *std.mem.Allocator) !string { return try std.unicode.utf16leToUtf8Alloc(allocator, s.value); } + + pub fn jsonStringify(s: *const String, options: anytype, writer: anytype) !void { + var buf = [_]u8{0} ** 4096; + var i: usize = 0; + for (s.value) |char| { + buf[i] = @intCast(u8, char); + i += 1; + if (i >= 4096) { + break; + } + } + + return try std.json.stringify(buf[0..i], options, writer); + } }; // value is in the Node @@ -846,11 +884,17 @@ pub const E = struct { pub const RegExp = struct { value: string, + + pub fn jsonStringify(self: *const RegExp, opts: anytype, o: anytype) !void { + return try std.json.stringify(self.value, opts, o); + } }; pub const Class = G.Class; - pub const Await = struct { value: ExprNodeIndex }; + pub const Await = struct { + value: ExprNodeIndex, + }; pub const Yield = struct { value: ?ExprNodeIndex = null, @@ -891,6 +935,17 @@ pub const Stmt = struct { loc: logger.Loc, data: Data, + const Serializable = struct { + @"type": Tag, + object: string, + value: Data, + loc: logger.Loc, + }; + + pub fn jsonStringify(self: *const Stmt, options: anytype, writer: anytype) !void { + return try std.json.stringify(Serializable{ .@"type" = std.meta.activeTag(self.data), .object = "stmt", .value = self.data, .loc = self.loc }, options, writer); + } + pub fn isTypeScript(self: *Stmt) bool { return @as(Stmt.Tag, self.data) == .s_type_script; } @@ -1223,6 +1278,54 @@ pub const Expr = struct { pub const EFlags = enum { none, ts_decorator }; + const Serializable = struct { + @"type": Tag, + object: string, + value: Data, + loc: logger.Loc, + }; + + pub fn isMissing(a: *const Expr) bool { + return std.meta.activeTag(a.data) == Expr.Tag.e_missing; + } + + pub fn joinWithComma(a: Expr, b: Expr, allocator: *std.mem.Allocator) Expr { + if (a.isMissing()) { + return b; + } + + if (b.isMissing()) { + return a; + } + + return Expr.alloc(allocator, E.Binary{ .op = .bin_comma, .left = a, .right = b }, a.loc); + } + + pub fn joinAllWithComma(all: []Expr, allocator: *std.mem.Allocator) Expr { + std.debug.assert(all.len > 0); + switch (all.len) { + 1 => { + return all[0]; + }, + 2 => { + return Expr.joinWithComma(all[0], all[1], allocator); + }, + else => { + var expr = Expr.joinWithComma(all[0], all[1], allocator); + var _all = all[2 .. all.len - 1]; + for (_all) |right| { + expr = Expr.joinWithComma(expr, right, allocator); + } + + return expr; + }, + } + } + + pub fn jsonStringify(self: *const @This(), options: anytype, writer: anytype) !void { + return try std.json.stringify(Serializable{ .@"type" = std.meta.activeTag(self.data), .object = "expr", .value = self.data, .loc = self.loc }, options, writer); + } + pub fn extractNumericValues(left: Expr.Data, right: Expr.Data) ?[2]f64 { if (!(@as(Expr.Tag, left) == .e_number and @as(Expr.Tag, right) == .e_number)) { return null; @@ -2598,6 +2701,10 @@ pub const Op = struct { }; } + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { + return try std.json.stringify(self.text, opts, o); + } + pub const TableType: std.EnumArray(Op.Code, Op); pub const Table = comptime { var table = std.EnumArray(Op.Code, Op).initUndefined(); @@ -2722,6 +2829,13 @@ pub const Ast = struct { .parts = parts, }; } + + pub fn toJSON(self: *Ast, allocator: *std.mem.Allocator, stream: anytype) !void { + const opts = std.json.StringifyOptions{ .whitespace = std.json.StringifyOptions.Whitespace{ + .separator = true, + } }; + try std.json.stringify(self.parts, opts, stream); + } }; pub const Span = struct { @@ -2885,6 +2999,9 @@ pub const Part = struct { // algorithm. is_live: bool = false, pub const SymbolUseMap = std.AutoHashMap(Ref, Symbol.Use); + pub fn jsonStringify(self: *const Part, options: std.json.StringifyOptions, writer: anytype) !void { + return std.json.stringify(self.stmts, options, writer); + } }; pub const Result = struct { diff --git a/src/js_parser.zig b/src/js_parser.zig index b8bf09793..f8c787f3b 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -146,9 +146,9 @@ pub const ImportScanner = struct { // user is expecting the output to be as small as possible. So we // should omit unused imports. // - const keep_unused_imports = !p.options.trim_unused_imports; + // const keep_unused_imports = !p.options.trim_unused_imports; var did_remove_star_loc = false; - // const keep_unused_imports = true; + const keep_unused_imports = true; // TypeScript always trims unused imports. This is important for // correctness since some imports might be fake (only in the type @@ -2319,6 +2319,8 @@ pub const P = struct { // } } + // This assumes the "function" token has already been parsed + pub fn parseFnStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, asyncRange: ?logger.Range) !Stmt { const isGenerator = p.lexer.token == T.t_asterisk; const isAsync = asyncRange != null; @@ -2352,9 +2354,11 @@ pub const P = struct { var nameLoc = p.lexer.loc(); nameText = p.lexer.identifier; p.lexer.expect(T.t_identifier); + // Difference + const ref = try p.newSymbol(Symbol.Kind.other, nameText); name = js_ast.LocRef{ .loc = nameLoc, - .ref = null, + .ref = ref, }; } @@ -2397,10 +2401,7 @@ pub const P = struct { return p.s(S.TypeScript{}, loc); } - // Balance the fake block scope introduced above - if (hasIfScope) { - p.popScope(); - } + p.popScope(); // Only declare the function after we know if it had a body or not. Otherwise // TypeScript code such as this will double-declare the symbol: @@ -2418,6 +2419,11 @@ pub const P = struct { func.flags.is_export = opts.is_export; + // Balance the fake block scope introduced above + if (hasIfScope) { + p.popScope(); + } + return p.s(S.Function{ .func = func, }, func.open_parens_loc); @@ -2617,7 +2623,26 @@ pub const P = struct { // TODO: pub fn parseTypeScriptDecorators(p: *P) []ExprNodeIndex { - notimpl(); + if (!p.options.ts) { + return &([_]ExprNodeIndex{}); + } + + var decorators = List(ExprNodeIndex).init(p.allocator); + while (p.lexer.token == T.t_at) { + p.lexer.next(); + + // Parse a new/call expression with "exprFlagTSDecorator" so we ignore + // EIndex expressions, since they may be part of a computed property: + // + // class Foo { + // @foo ['computed']() {} + // } + // + // This matches the behavior of the TypeScript compiler. + decorators.append(p.parseExprWithFlags(.new, Expr.EFlags.ts_decorator)) catch unreachable; + } + + return decorators.toOwnedSlice(); } // TODO: @@ -3457,6 +3482,7 @@ pub const P = struct { update = p.parseExpr(.lowest); } + p.lexer.expect(.t_close_paren); var stmtOpts = ParseStatementOptions{}; const body = p.parseStmt(&stmtOpts) catch unreachable; return p.s( @@ -3516,13 +3542,18 @@ pub const P = struct { p.lexer.expectContextualKeyword("from"); }, .t_open_brace => { - // "import * as ns from 'path'" + // "import {item1, item2} from 'path'" if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) { p.lexer.unexpected(); fail(); } var importClause = try p.parseImportClause(); - stmt = S.Import{ .namespace_ref = undefined, .import_record_index = std.math.maxInt(u32), .items = importClause.items, .is_single_line = importClause.is_single_line }; + stmt = S.Import{ + .namespace_ref = undefined, + .import_record_index = std.math.maxInt(u32), + .items = importClause.items, + .is_single_line = importClause.is_single_line, + }; p.lexer.expectContextualKeyword("from"); }, .t_identifier => { @@ -3733,7 +3764,9 @@ pub const P = struct { p.lexer.expectOrInsertSemicolon(); return stmt; }, - else => {}, + .expr => |_expr| { + expr = _expr; + }, } } @@ -4484,10 +4517,6 @@ pub const P = struct { }, p.lexer.loc())); } - if (p.lexer.token == eend) { - break :run; - } - var stmt = p.parseStmt(opts) catch break :run; // Skip TypeScript types entirely @@ -4562,6 +4591,10 @@ pub const P = struct { returnWithoutSemicolonStart = -1; } } + + if (p.lexer.token == eend) { + break :run; + } } return stmts.toOwnedSlice(); @@ -4813,12 +4846,12 @@ pub const P = struct { pub fn declareBinding(p: *P, kind: Symbol.Kind, binding: BindingNodeIndex, opts: *ParseStatementOptions) !void { switch (binding.data) { + .b_missing => {}, .b_identifier => |bind| { if (!opts.is_typescript_declare or (opts.is_namespace_scope and opts.is_export)) { bind.ref = try p.declareSymbol(kind, binding.loc, p.loadNameFromRef(bind.ref)); } }, - .b_missing => |*bind| {}, .b_array => |bind| { for (bind.items) |item| { @@ -5175,7 +5208,7 @@ pub const P = struct { } } - p.lexer.expect(.t_close_brace); + p.lexer.expect(.t_close_bracket); key = expr; }, .t_asterisk => { @@ -5231,7 +5264,7 @@ pub const P = struct { } }, .p_async => { - if (!opts.is_async and strings.eql(raw, name)) { + if (!opts.is_async and strings.eql(raw, name) and !p.lexer.has_newline_before) { opts.is_async = true; opts.async_range = name_range; @@ -5240,7 +5273,7 @@ pub const P = struct { } }, .p_static => { - if (!opts.is_static and !opts.is_async and !opts.is_class and strings.eql(raw, name)) { + if (!opts.is_static and !opts.is_async and opts.is_class and strings.eql(raw, name)) { opts.is_static = true; return p.parseProperty(kind, opts, null); } @@ -5429,11 +5462,11 @@ pub const P = struct { }, .set => { if (func.args.len != 1) { - var r = js_lexer.rangeOfIdentifier(&p.source, func.args[0].binding.loc); + var r = js_lexer.rangeOfIdentifier(&p.source, if (func.args.len > 0) func.args[0].binding.loc else loc); if (func.args.len > 1) { r = js_lexer.rangeOfIdentifier(&p.source, func.args[1].binding.loc); } - p.log.addRangeErrorFmt(p.source, r, p.allocator, "Setter {s} must have exactly 1 argument", .{p.keyNameForError(key)}) catch unreachable; + p.log.addRangeErrorFmt(p.source, r, p.allocator, "Setter {s} must have exactly 1 argument (there are {d})", .{ p.keyNameForError(key), func.args.len }) catch unreachable; } }, else => {}, @@ -5516,17 +5549,17 @@ pub const P = struct { if (p.lexer.token == .t_extends) { p.lexer.next(); extends = p.parseExpr(.new); - } - // TypeScript's type argument parser inside expressions backtracks if the - // first token after the end of the type parameter list is "{", so the - // parsed expression above will have backtracked if there are any type - // arguments. This means we have to re-parse for any type arguments here. - // This seems kind of wasteful to me but it's what the official compiler - // does and it probably doesn't have that high of a performance overhead - // because "extends" clauses aren't that frequent, so it should be ok. - if (p.options.ts) { - p.skipTypeScriptTypeArguments(false); // isInsideJSXElement + // TypeScript's type argument parser inside expressions backtracks if the + // first token after the end of the type parameter list is "{", so the + // parsed expression above will have backtracked if there are any type + // arguments. This means we have to re-parse for any type arguments here. + // This seems kind of wasteful to me but it's what the official compiler + // does and it probably doesn't have that high of a performance overhead + // because "extends" clauses aren't that frequent, so it should be ok. + if (p.options.ts) { + p.skipTypeScriptTypeArguments(false); // isInsideJSXElement + } } if (p.options.ts and p.lexer.isContextualKeyword("implements")) { @@ -5555,8 +5588,7 @@ pub const P = struct { const scopeIndex = p.pushScopeForParsePass(.class_body, body_loc) catch unreachable; var opts = PropertyOpts{ .is_class = true, .allow_ts_decorators = class_opts.allow_ts_decorators, .class_has_extends = extends != null }; - - while (p.lexer.token != .t_close_brace) { + while (p.lexer.token != T.t_close_brace) { if (p.lexer.token == .t_semicolon) { p.lexer.next(); continue; @@ -5597,6 +5629,8 @@ pub const P = struct { p.allow_in = old_allow_in; p.allow_private_identifiers = old_allow_private_identifiers; + p.lexer.expect(.t_close_brace); + return G.Class{ .class_name = name, .extends = extends, @@ -6474,6 +6508,7 @@ pub const P = struct { pub fn _parsePrefix(p: *P, level: Level, errors: *DeferredErrors, flags: Expr.EFlags) Expr { const loc = p.lexer.loc(); const l = @enumToInt(level); + std.debug.print("Parse Prefix {s}:{s} @{s} ", .{ p.lexer.token, p.lexer.raw(), @tagName(level) }); switch (p.lexer.token) { .t_super => { @@ -6499,7 +6534,7 @@ pub const P = struct { p.lexer.next(); // Arrow functions aren't allowed in the middle of expressions - if (l > @enumToInt(Level.assign)) { + if (level.gt(.assign)) { const oldAllowIn = p.allow_in; p.allow_in = true; @@ -6885,7 +6920,7 @@ pub const P = struct { const old_allow_in = p.allow_in; p.allow_in = true; - while (p.lexer.token != .t_close_brace and p.lexer.token != .t_end_of_file) { + while (p.lexer.token != .t_close_brace) { if (p.lexer.token == .t_dot_dot_dot) { p.lexer.next(); properties.append(G.Property{ .kind = .spread, .value = p.parseExpr(.comma) }) catch unreachable; @@ -7312,6 +7347,7 @@ pub const P = struct { } p.pushScopeForVisitPass(.function_args, open_parens_loc) catch unreachable; + defer p.popScope(); p.visitArgs( func.args, VisitArgsOpts{ @@ -7320,13 +7356,17 @@ pub const P = struct { .is_unique_formal_parameters = true, }, ); - defer p.popScope(); - const body = func.body orelse p.panic("Expected visitFunc to have body {s}", .{func}); + + var body = func.body orelse p.panic("Expected visitFunc to have body {s}", .{func}); p.pushScopeForVisitPass(.function_body, body.loc) catch unreachable; + defer p.popScope(); var stmts = List(Stmt).fromOwnedSlice(p.allocator, body.stmts); var temp_opts = PrependTempRefsOpts{ .kind = StmtsKind.fn_body, .fn_body_loc = body.loc }; p.visitStmtsAndPrependTempRefs(&stmts, &temp_opts) catch unreachable; - func.body.?.stmts = stmts.toOwnedSlice(); + + body.stmts = stmts.toOwnedSlice(); + + func.body = body; } pub fn maybeKeepExprSymbolName(p: *P, expr: Expr, original_name: string, was_anonymous_named_expr: bool) Expr { @@ -7382,7 +7422,7 @@ pub const P = struct { }, .e_import_meta => |exp| { - const is_delete_target = exp == p.delete_target.e_import_meta; + const is_delete_target = std.meta.activeTag(p.delete_target) == .e_import_meta and exp == p.delete_target.e_import_meta; if (p.define.dots.get("meta")) |meta| { for (meta) |define| { @@ -7540,8 +7580,8 @@ pub const P = struct { else => {}, } - const is_call_target = e_ == p.call_target.e_binary; - const is_stmt_expr = e_ == p.stmt_expr_value.e_binary; + const is_call_target = @as(Expr.Tag, p.call_target) == .e_binary and e_ == p.call_target.e_binary; + const is_stmt_expr = @as(Expr.Tag, p.stmt_expr_value) == .e_binary and e_ == p.stmt_expr_value.e_binary; const was_anonymous_named_expr = p.isAnonymousNamedExpr(e_.right); e_.left = p.visitExprInOut(e_.left, ExprIn{ @@ -7841,8 +7881,8 @@ pub const P = struct { } }, .e_index => |e_| { - const is_call_target = e_ == p.call_target.e_index; - const is_delete_target = e_ == p.delete_target.e_index; + const is_call_target = std.meta.activeTag(p.call_target) == .e_index and e_ == p.call_target.e_index; + const is_delete_target = std.meta.activeTag(p.delete_target) == .e_index and e_ == p.delete_target.e_index; const target = p.visitExprInOut(e_.target, ExprIn{ // this is awkward due to a zig compiler bug @@ -8003,7 +8043,7 @@ pub const P = struct { } }, .e_if => |e_| { - const is_call_target = e_ == p.call_target.e_if; + const is_call_target = (p.call_target) == .e_if and e_ == p.call_target.e_if; e_.test_ = p.visitExpr(e_.test_); @@ -9331,7 +9371,18 @@ pub const P = struct { } pub fn lowerClass(p: *P, stmtorexpr: js_ast.StmtOrExpr, ref: Ref) []Stmt { - notimpl(); + switch (stmtorexpr) { + .stmt => |stmt| { + var stmts = p.allocator.alloc(Stmt, 1) catch unreachable; + stmts[0] = stmt; + return stmts; + }, + .expr => |expr| { + var stmts = p.allocator.alloc(Stmt, 1) catch unreachable; + stmts[0] = p.s(S.SExpr{ .value = expr }, expr.loc); + return stmts; + }, + } } pub fn visitForLoopInit(p: *P, stmt: Stmt, is_in_or_of: bool) Stmt { @@ -9651,7 +9702,7 @@ pub const P = struct { // are not allowed to assign to this symbol (it throws a TypeError). const name = p.symbols.items[class_name_ref.inner_index].original_name; var identifier = p.allocator.alloc(u8, name.len + 1) catch unreachable; - std.mem.copy(u8, identifier[1 .. identifier.len - 1], name); + std.mem.copy(u8, identifier[1..identifier.len], name); identifier[0] = '_'; shadow_ref = p.newSymbol(Symbol.Kind.cconst, identifier) catch unreachable; p.recordDeclaredSymbol(shadow_ref) catch unreachable; @@ -9844,7 +9895,7 @@ pub const P = struct { // values that introduce new scopes and declare new symbols. If this is an // arrow function, then those new scopes will need to be parented under the // scope of the arrow function itself. - const scopeIndex = p.pushScopeForParsePass(.function_args, loc); + const scopeIndex = try p.pushScopeForParsePass(.function_args, loc); // Allow "in" inside parentheses var oldAllowIn = p.allow_in; @@ -9952,7 +10003,67 @@ pub const P = struct { } } - return p.e(E.Missing{}, loc); + // If we get here, it's not an arrow function so undo the pushing of the + // scope we did earlier. This needs to flatten any child scopes into the + // parent scope as if the scope was never pushed in the first place. + p.popAndFlattenScope(scopeIndex); + + // If this isn't an arrow function, then types aren't allowed + if (type_colon_range.len > 0) { + try p.log.addRangeError(p.source, type_colon_range, "Unexpected \":\""); + p.panic("", .{}); + } + + // Are these arguments for a call to a function named "async"? + if (opts.is_async) { + p.logExprErrors(&errors); + const async_expr = p.e(E.Identifier{ .ref = try p.storeNameInRef("async") }, loc); + return p.e(E.Call{ .target = async_expr, .args = items }, loc); + } + + // Is this a chain of expressions and comma operators? + + if (items.len > 0) { + p.logExprErrors(&errors); + if (spread_range.len > 0) { + try p.log.addRangeError(p.source, type_colon_range, "Unexpected \"...\""); + p.panic("", .{}); + } + + var value = Expr.joinAllWithComma(items, p.allocator); + p.markExprAsParenthesized(&value); + return value; + } + + // Indicate that we expected an arrow function + p.lexer.expected(.t_equals_greater_than); + p.panic("", .{}); + } + + pub fn popAndFlattenScope(p: *P, scope_index: usize) void { + // Move up to the parent scope + var to_flatten = p.current_scope; + var parent = to_flatten.parent.?; + p.current_scope = parent; + var scopes_in_order = p.scopes_in_order.toOwnedSlice(); + // Erase this scope from the order. This will shift over the indices of all + // the scopes that were created after us. However, we shouldn't have to + // worry about other code with outstanding scope indices for these scopes. + // These scopes were all created in between this scope's push and pop + // operations, so they should all be child scopes and should all be popped + // by the time we get here. + std.mem.copy(ScopeOrder, scopes_in_order[scope_index..scopes_in_order.len], scopes_in_order[scope_index + 1 .. scopes_in_order.len]); + p.scopes_in_order = @TypeOf(p.scopes_in_order).fromOwnedSlice(p.allocator, scopes_in_order); + + // Remove the last child from the parent scope + const last = parent.children.items.len - 1; + assert(parent.children.items[last] == to_flatten); + _ = parent.children.popOrNull(); + + for (to_flatten.children.items) |item| { + item.parent = parent; + parent.children.append(item) catch unreachable; + } } pub fn maybeCommaSpreadError(p: *P, _comma_after_spread: ?logger.Loc) void { diff --git a/src/js_printer.zig b/src/js_printer.zig index beed8162d..50114e670 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -1941,6 +1941,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type { } const name = s.func.name orelse std.debug.panic("Internal error: expected func to have a name ref\n{s}", .{s}); const nameRef = name.ref orelse std.debug.panic("Internal error: expected func to have a name\n{s}", .{s}); + p.printSpace(); p.printSymbol(nameRef); p.printFunc(s.func); p.printNewline(); @@ -1951,7 +1952,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type { if (s.is_export) { p.print("export "); } - p.print("class"); + p.print("class "); p.printSymbol(s.class.class_name.?.ref.?); p.printClass(s.class); p.printNewline(); diff --git a/src/logger.zig b/src/logger.zig index d735469c3..7736e5d62 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -43,6 +43,10 @@ pub const Loc = packed struct { pub fn eql(loc: *Loc, other: Loc) bool { return loc.start == other.start; } + + pub fn jsonStringify(self: *const Loc, options: anytype, writer: anytype) !void { + return try std.json.stringify(self.start, options, writer); + } }; pub const Location = struct { @@ -142,6 +146,10 @@ pub const Range = packed struct { pub fn endI(self: *const Range) usize { return std.math.lossyCast(usize, self.loc.start + self.len); } + + pub fn jsonStringify(self: *const Range, options: anytype, writer: anytype) !void { + return try std.json.stringify([2]i32{ self.loc.start, self.len + self.loc.start }, options, writer); + } }; pub const Log = struct { diff --git a/src/main.zig b/src/main.zig index b1250fc56..d67ba6167 100644 --- a/src/main.zig +++ b/src/main.zig @@ -10,9 +10,24 @@ const js_ast = @import("js_ast.zig"); const linker = @import("linker.zig"); usingnamespace @import("ast/base.zig"); usingnamespace @import("defines.zig"); +const panicky = @import("panic_handler.zig"); + +const MainPanicHandler = panicky.NewPanicHandler(panicky.default_panic); + +pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn { + if (MainPanicHandler.Singleton) |singleton| { + MainPanicHandler.handle_panic(msg, error_return_trace); + } else { + panicky.default_panic(msg, error_return_trace); + } +} pub fn main() anyerror!void { try alloc.setup(std.heap.page_allocator); + var log = logger.Log.init(alloc.dynamic); + var panicker = MainPanicHandler.init(&log); + panicker.skip_next_panic = true; + MainPanicHandler.Singleton = &panicker; const args = try std.process.argsAlloc(alloc.dynamic); const stdout = std.io.getStdOut(); @@ -30,7 +45,7 @@ pub fn main() anyerror!void { const code = try file.readToEndAlloc(alloc.dynamic, stat.size); const opts = try options.TransformOptions.initUncached(alloc.dynamic, entryPointName, code); - var log = logger.Log.init(alloc.dynamic); + var source = logger.Source.initFile(opts.entry_point, alloc.dynamic); var ast: js_ast.Ast = undefined; var raw_defines = RawDefines.init(alloc.static); @@ -79,13 +94,10 @@ pub fn main() anyerror!void { ); if (std.builtin.mode == std.builtin.Mode.Debug) { - std.debug.print("\n--AST DEBUG--:\n", .{}); - std.debug.print("Lines: {d}\n", .{ast.approximate_line_count}); - std.debug.print("Parts: {d}\n{s}\n", .{ ast.parts.len, ast.parts }); - std.debug.print("Symbols: {d}\n{s}\n", .{ ast.symbols.len, ast.symbols }); - std.debug.print("Imports: {d}\n{s}\n", .{ ast.named_exports.count(), ast.named_imports }); - std.debug.print("Exports: {d}\n{s}\n", .{ ast.named_imports.count(), ast.named_exports }); - std.debug.print("\n--AST DEBUG--:\n", .{}); + var fixed_buffer = [_]u8{0} ** 512000; + var buf_stream = std.io.fixedBufferStream(&fixed_buffer); + + try ast.toJSON(alloc.dynamic, stderr.writer()); } _ = try stdout.write(printed.js); diff --git a/src/panic_handler.zig b/src/panic_handler.zig new file mode 100644 index 000000000..475d55514 --- /dev/null +++ b/src/panic_handler.zig @@ -0,0 +1,58 @@ +const std = @import("std"); +const logger = @import("logger.zig"); +const root = @import("root"); + +const USERLAND_PANIC_MESSAGE = "iNtErNaL sErVeR eRrOr"; + +/// This function is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub fn default_panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn { + @setCold(true); + if (@hasDecl(root, "os") and @hasDecl(root.os, "panic")) { + root.os.panic(msg, error_return_trace); + unreachable; + } + switch (std.builtin.os.tag) { + .freestanding => { + while (true) { + @breakpoint(); + } + }, + .wasi => { + std.debug.warn("{s}", .{msg}); + std.os.abort(); + }, + .uefi => { + // TODO look into using the debug info and logging helpful messages + std.os.abort(); + }, + else => { + const first_trace_addr = @returnAddress(); + std.debug.panicExtra(error_return_trace, first_trace_addr, "{s}", .{msg}); + }, + } +} + +pub fn NewPanicHandler(panic_func: fn handle_panic(msg: []const u8, error_return_type: ?*std.builtin.StackTrace) noreturn) type { + return struct { + panic_count: usize = 0, + skip_next_panic: bool = false, + log: *logger.Log, + + pub var Singleton: ?*Handler = null; + const Handler = @This(); + + pub fn init(log: *logger.Log) Handler { + return Handler{ + .log = log, + }; + } + pub fn handle_panic(msg: []const u8, error_return_type: ?*std.builtin.StackTrace) callconv(.Inline) noreturn { + if (@This().Singleton) |singleton| { + singleton.panic_count += 1; + } + + panic_func(msg, error_return_type); + } + }; +} |