diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bundler.zig | 4 | ||||
-rw-r--r-- | src/js_ast.zig | 93 | ||||
-rw-r--r-- | src/js_lexer.zig | 86 | ||||
-rw-r--r-- | src/js_parser.zig | 12 | ||||
-rw-r--r-- | src/js_printer.zig | 448 | ||||
-rw-r--r-- | src/json_parser.zig | 138 | ||||
-rw-r--r-- | src/logger.zig | 25 | ||||
-rw-r--r-- | src/main.zig | 5 | ||||
-rw-r--r-- | src/renamer.zig | 22 | ||||
-rw-r--r-- | src/string_immutable.zig | 46 | ||||
-rw-r--r-- | src/string_mutable.zig | 34 |
11 files changed, 799 insertions, 114 deletions
diff --git a/src/bundler.zig b/src/bundler.zig index d1a825a70..62d663c7c 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -10,7 +10,7 @@ pub const Bundler = struct { result: ?options.TransformResult = null, pub fn init(options: options.TransformOptions, allocator: *std.mem.Allocator) Bundler { - var log = logger.Log{ .msgs = ArrayList(Msg).init(allocator) }; + var log = logger.Log.init(allocator); return Bundler{ .options = options, @@ -25,7 +25,5 @@ pub const Bundler = struct { var result = self.result; var source = logger.Source.initFile(self.options.entry_point, self.allocator); - - } }; diff --git a/src/js_ast.zig b/src/js_ast.zig index 7e81b77b5..e387c6138 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -68,6 +68,10 @@ pub const Ref = packed struct { pub fn isSourceNull(self: *const Ref) bool { return self.source_index == std.math.maxInt(Ref.Int); } + + pub fn eql(ref: *Ref, b: Ref) bool { + return ref.inner_index == b.inner_index and ref.source_index == b.source_index; + } }; pub const ImportItemStatus = packed enum { @@ -550,6 +554,22 @@ pub const Symbol = struct { var symbols_for_source: [][]Symbol = try allocator.alloc([]Symbol, sourceCount); return Map{ .symbols_for_source = symbols_for_source }; } + + pub fn follow(symbols: *Map, ref: Ref) Ref { + if (symbols.get(ref)) |*symbol| { + if (symbol.link) |link| { + if (!link.eql(ref)) { + symbol.link = ref; + } + + return link; + } else { + return symbol; + } + } else { + return ref; + } + } }; pub fn isKindPrivate(kind: Symbol.Kind) bool { @@ -767,6 +787,10 @@ pub const E = struct { value: JavascriptString, legacy_octal_loc: ?logger.Loc = null, prefer_template: bool = false, + + pub fn string(s: *String, allocator: *std.mem.Allocator) !string { + return try std.unicode.utf16leToUtf8Alloc(allocator, s.value); + } }; // value is in the Node @@ -804,8 +828,12 @@ pub const E = struct { no: ExprNodeIndex, }; + pub const Require = struct { + import_record_index: u32 = 0, + }; + pub const RequireOrRequireResolve = struct { - import_record_index: u32, + import_record_index: u32 = 0, }; pub const Import = struct { @@ -1376,18 +1404,26 @@ pub const Expr = struct { .data = Data{ .e_if = exp }, }; }, - *E.RequireOrRequireResolve => { + + *E.Import => { return Expr{ .loc = loc, - .data = Data{ .e_require_or_require_resolve = exp }, + .data = Data{ .e_import = exp }, }; }, - *E.Import => { + *E.Require => { return Expr{ .loc = loc, - .data = Data{ .e_import = exp }, + .data = Data{ .e_require = exp }, + }; + }, + *E.RequireOrRequireResolve => { + return Expr{ + .loc = loc, + .data = Data{ .e_require_or_require_resolve = exp }, }; }, + else => { @compileError("Expr.init needs a pointer to E.*"); }, @@ -1571,6 +1607,12 @@ pub const Expr = struct { dat.* = st; return Expr{ .loc = loc, .data = Data{ .e_import = dat } }; }, + E.Require => { + var dat = allocator.create(E.Require) catch unreachable; + dat.* = st; + return Expr{ .loc = loc, .data = Data{ .e_require = dat } }; + }, + else => { @compileError("Invalid type passed to Expr.init"); }, @@ -1614,6 +1656,8 @@ pub const Expr = struct { e_this, e_class, + e_require, + pub fn isArray(self: Tag) bool { switch (self) { .e_array => { @@ -2099,6 +2143,7 @@ pub const Expr = struct { e_await: *E.Await, e_yield: *E.Yield, e_if: *E.If, + e_require: *E.Require, e_require_or_require_resolve: *E.RequireOrRequireResolve, e_import: *E.Import, @@ -2540,26 +2585,32 @@ pub const Ast = struct { // These are stored at the AST level instead of on individual AST nodes so // they can be manipulated efficiently without a full AST traversal - import_records: ?[]ImportRecord = null, + import_records: []ImportRecord = &([_]ImportRecord{}), hashbang: ?string = null, directive: ?string = null, url_for_css: ?string = null, - parts: std.ArrayList(Part), - symbols: std.ArrayList(Symbol), - module_scope: ?Scope, + parts: []Part, + symbols: []Symbol = &([_]Symbol{}), + module_scope: ?Scope = null, // char_freq: *CharFreq, - exports_ref: ?Ref, - module_ref: ?Ref, - wrapper_ref: ?Ref, + exports_ref: ?Ref = null, + module_ref: ?Ref = null, + wrapper_ref: ?Ref = null, // These are used when bundling. They are filled in during the parser pass // since we already have to traverse the AST then anyway and the parser pass // is conveniently fully parallelized. - named_imports: std.AutoHashMap(Ref, NamedImport), - named_exports: std.StringHashMap(NamedExport), - top_level_symbol_to_parts: std.AutoHashMap(Ref, []u32), - export_star_import_records: std.ArrayList(u32), + named_imports: std.AutoHashMap(Ref, NamedImport) = undefined, + named_exports: std.StringHashMap(NamedExport) = undefined, + top_level_symbol_to_parts: std.AutoHashMap(Ref, []u32) = undefined, + export_star_import_records: std.ArrayList(u32) = undefined, + + pub fn initTest(parts: []Part) Ast { + return Ast{ + .parts = parts, + }; + } }; pub const Span = struct { @@ -2691,23 +2742,23 @@ pub const AstData = struct { // splitting. pub const Part = struct { stmts: []Stmt, - scopes: []*Scope, + scopes: []*Scope = &([_]*Scope{}), // Each is an index into the file-level import record list - import_record_indices: std.ArrayList(u32), + import_record_indices: std.ArrayList(u32) = undefined, // All symbols that are declared in this part. Note that a given symbol may // have multiple declarations, and so may end up being declared in multiple // parts (e.g. multiple "var" declarations with the same name). Also note // that this list isn't deduplicated and may contain duplicates. - declared_symbols: std.ArrayList(DeclaredSymbol), + declared_symbols: std.ArrayList(DeclaredSymbol) = undefined, // An estimate of the number of uses of all symbols used within this part. - symbol_uses: std.AutoHashMap(Ref, Symbol.Use), + symbol_uses: std.AutoHashMap(Ref, Symbol.Use) = undefined, // The indices of the other parts in this file that are needed if this part // is needed. - dependencies: std.ArrayList(Dependency), + dependencies: std.ArrayList(Dependency) = undefined, // If true, this part can be removed if none of the declared symbols are // used. If the file containing this part is imported, then all parts that diff --git a/src/js_lexer.zig b/src/js_lexer.zig index fa95341a5..a2652dfa8 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -35,11 +35,12 @@ pub fn NewLexerType(comptime jsonOptions: ?JSONOptions) type { // }; // err: ?@This().Error, - log: logger.Log, + log: *logger.Log, source: logger.Source, current: usize = 0, start: usize = 0, end: usize = 0, + did_panic: bool = false, approximate_newline_count: i32 = 0, legacy_octal_loc: logger.Loc = logger.Loc.Empty, previous_backslash_quote_in_jsx: logger.Range = logger.Range.None, @@ -92,8 +93,7 @@ pub fn NewLexerType(comptime jsonOptions: ?JSONOptions) type { return; } - const errorMessage = std.fmt.allocPrint(self.allocator, format, args) catch unreachable; - self.log.addError(self.source, __loc, errorMessage) catch unreachable; + self.log.addErrorFmt(self.source, __loc, self.allocator, format, args) catch unreachable; self.prev_error_loc = __loc; var msg = self.log.msgs.items[self.log.msgs.items.len - 1]; msg.formatNoWriter(std.debug.panic); @@ -114,7 +114,11 @@ pub fn NewLexerType(comptime jsonOptions: ?JSONOptions) type { } fn doPanic(self: *@This(), content: []const u8) void { - std.debug.panic("{s}", .{content}); + if (@import("builtin").is_test) { + self.did_panic = true; + } else { + std.debug.panic("{s}", .{content}); + } } pub fn codePointEql(self: *@This(), a: u8) bool { @@ -324,7 +328,7 @@ pub fn NewLexerType(comptime jsonOptions: ?JSONOptions) type { pub fn next(lexer: *@This()) void { lexer.has_newline_before = lexer.end == 0; - lex: while (lexer.log.errors == 0) { + lex: while (true) { lexer.start = lexer.end; lexer.token = T.t_end_of_file; @@ -841,11 +845,7 @@ pub fn NewLexerType(comptime jsonOptions: ?JSONOptions) type { } else { const contents = lexer.raw(); lexer.identifier = contents; - if (Keywords.get(contents)) |keyword| { - lexer.token = keyword; - } else { - lexer.token = T.t_identifier; - } + lexer.token = Keywords.get(contents) orelse T.t_identifier; } }, @@ -960,11 +960,11 @@ pub fn NewLexerType(comptime jsonOptions: ?JSONOptions) type { }; } - pub fn init(log: logger.Log, source: logger.Source, allocator: *std.mem.Allocator) !@This() { + pub fn init(log: *logger.Log, source: *logger.Source, allocator: *std.mem.Allocator) !@This() { var empty_string_literal: JavascriptString = undefined; var lex = @This(){ .log = log, - .source = source, + .source = source.*, .string_literal = empty_string_literal, .prev_error_loc = logger.Loc.Empty, .allocator = allocator, @@ -1437,7 +1437,7 @@ pub const Lexer = NewLexerType(null); pub const JSONLexer = NewLexerType(JSONOptions{ .allow_comments = false, .allow_trailing_commas = false }); pub const TSConfigJSONLexer = NewLexerType(JSONOptions{ .allow_comments = true, .allow_trailing_commas = true }); -fn isIdentifierStart(codepoint: CodePoint) bool { +pub fn isIdentifierStart(codepoint: CodePoint) bool { switch (codepoint) { 'a'...'z', 'A'...'Z', '_', '$' => { return true; @@ -1447,11 +1447,14 @@ fn isIdentifierStart(codepoint: CodePoint) bool { }, } } -fn isIdentifierContinue(codepoint: CodePoint) bool { +pub fn isIdentifierContinue(codepoint: CodePoint) bool { switch (codepoint) { '_', '$', '0'...'9', 'a'...'z', 'A'...'Z' => { return true; }, + -1 => { + return false; + }, else => {}, } @@ -1468,7 +1471,7 @@ fn isIdentifierContinue(codepoint: CodePoint) bool { return false; } -fn isWhitespace(codepoint: CodePoint) bool { +pub fn isWhitespace(codepoint: CodePoint) bool { switch (codepoint) { 0x000B, // line tabulation 0x0009, // character tabulation @@ -1528,18 +1531,15 @@ fn float64(num: anytype) callconv(.Inline) f64 { return @intToFloat(f64, num); } -fn test_lexer(contents: []const u8) @This() { +fn test_lexer(contents: []const u8) Lexer { alloc.setup(std.heap.page_allocator) catch unreachable; - const msgs = std.ArrayList(logger.Msg).init(alloc.dynamic); - const log = logger.Log{ - .msgs = msgs, - }; - - const source = logger.Source.initPathString( + var log = alloc.dynamic.create(logger.Log) catch unreachable; + log.* = logger.Log.init(alloc.dynamic); + var source = logger.Source.initPathString( "index.js", contents, ); - return @This().init(log, source, alloc.dynamic) catch unreachable; + return Lexer.init(log, &source, alloc.dynamic) catch unreachable; } // test "@This().next()" { @@ -1554,35 +1554,49 @@ fn test_lexer(contents: []const u8) @This() { // lex.next(); // } +fn expectStr(lexer: *Lexer, expected: string, actual: string) void { + if (lexer.log.errors > 0 or lexer.log.warnings > 0) { + std.debug.panic("{s}", .{lexer.log.msgs.items}); + // const msg: logger.Msg = lexer.log.msgs.items[0]; + // msg.formatNoWriter(std.debug.panic); + } + std.testing.expectEqual(lexer.log.errors, 0); + std.testing.expectEqual(lexer.log.warnings, 0); + std.testing.expectEqual(false, lexer.did_panic); + std.testing.expectEqual(@as(usize, 0), lexer.log.errors); + std.testing.expectEqual(@as(usize, 0), lexer.log.warnings); + std.testing.expectEqualStrings(expected, actual); +} + test "Lexer.next() simple" { var lex = test_lexer("for (let i = 0; i < 100; i++) { }"); - std.testing.expectEqualStrings("\"for\"", tokenToString.get(lex.token)); + expectStr(&lex, "\"for\"", tokenToString.get(lex.token)); lex.next(); - std.testing.expectEqualStrings("\"(\"", tokenToString.get(lex.token)); + expectStr(&lex, "\"(\"", tokenToString.get(lex.token)); lex.next(); - std.testing.expectEqualStrings("let", lex.raw()); + expectStr(&lex, "let", lex.raw()); lex.next(); - std.testing.expectEqualStrings("i", lex.raw()); + expectStr(&lex, "i", lex.raw()); lex.next(); - std.testing.expectEqualStrings("=", lex.raw()); + expectStr(&lex, "=", lex.raw()); lex.next(); - std.testing.expectEqualStrings("0", lex.raw()); + expectStr(&lex, "0", lex.raw()); lex.next(); - std.testing.expectEqualStrings(";", lex.raw()); + expectStr(&lex, ";", lex.raw()); lex.next(); - std.testing.expectEqualStrings("i", lex.raw()); + expectStr(&lex, "i", lex.raw()); lex.next(); std.testing.expect(lex.number == 0.0); - std.testing.expectEqualStrings("<", lex.raw()); + expectStr(&lex, "<", lex.raw()); lex.next(); std.testing.expect(lex.number == 100.0); - std.testing.expectEqualStrings("100", lex.raw()); + expectStr(&lex, "100", lex.raw()); lex.next(); } pub fn test_stringLiteralEquals(expected: string, source_text: string) void { - const msgs = std.ArrayList(logger.Msg).init(std.testing.allocator); - const log = logger.Log{ + var msgs = std.ArrayList(logger.Msg).init(std.testing.allocator); + var log = logger.Log{ .msgs = msgs, }; @@ -1593,7 +1607,7 @@ pub fn test_stringLiteralEquals(expected: string, source_text: string) void { source_text, ); - var lex = try @This().init(log, source, std.heap.page_allocator); + var lex = try Lexer.init(&log, &source, std.heap.page_allocator); while (!lex.token.isString() and lex.token != .t_end_of_file) { lex.next(); } diff --git a/src/js_parser.zig b/src/js_parser.zig index 460e0d275..e32adc859 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -344,7 +344,7 @@ const PropertyOpts = struct { pub const Parser = struct { options: Options, lexer: js_lexer.Lexer, - log: logger.Log, + log: *logger.Log, source: logger.Source, allocator: *std.mem.Allocator, p: ?*P, @@ -412,9 +412,7 @@ pub const Parser = struct { return result; } - pub fn init(transform: options.TransformOptions, allocator: *std.mem.Allocator) !Parser { - const log = logger.Log{ .msgs = List(logger.Msg).init(allocator) }; - const source = logger.Source.initFile(transform.entry_point, allocator); + pub fn init(transform: options.TransformOptions, log: *logger.Log, source: *logger.Source, allocator: *std.mem.Allocator) !Parser { const lexer = try js_lexer.Lexer.init(log, source, allocator); return Parser{ .options = Options{ @@ -427,7 +425,7 @@ pub const Parser = struct { }, .allocator = allocator, .lexer = lexer, - .source = source, + .source = source.*, .log = log, .p = null, }; @@ -471,7 +469,7 @@ const ParseStatementOptions = struct { const P = struct { allocator: *std.mem.Allocator, options: Parser.Options, - log: logger.Log, + log: *logger.Log, source: logger.Source, lexer: js_lexer.Lexer, allow_in: bool = false, @@ -6249,7 +6247,7 @@ const P = struct { return p.e(E.Missing{}, loc); } - pub fn init(allocator: *std.mem.Allocator, log: logger.Log, source: logger.Source, lexer: js_lexer.Lexer, opts: Parser.Options) !*P { + pub fn init(allocator: *std.mem.Allocator, log: *logger.Log, source: logger.Source, lexer: js_lexer.Lexer, opts: Parser.Options) !*P { var parser = try allocator.create(P); parser.allocated_names = @TypeOf(parser.allocated_names).init(allocator); parser.scopes_for_current_part = @TypeOf(parser.scopes_for_current_part).init(allocator); diff --git a/src/js_printer.zig b/src/js_printer.zig index e69de29bb..7cd61b634 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -0,0 +1,448 @@ +const std = @import("std"); +const logger = @import("logger.zig"); +const js_lexer = @import("js_lexer.zig"); +const importRecord = @import("import_record.zig"); +const js_ast = @import("js_ast.zig"); +const options = @import("options.zig"); +const alloc = @import("alloc.zig"); +const rename = @import("renamer.zig"); + +const fs = @import("fs.zig"); +usingnamespace @import("strings.zig"); +usingnamespace @import("ast/base.zig"); +usingnamespace js_ast.G; + +const expect = std.testing.expect; +const ImportKind = importRecord.ImportKind; +const BindingNodeIndex = js_ast.BindingNodeIndex; + +const Ref = js_ast.Ref; +const LocRef = js_ast.LocRef; +const S = js_ast.S; +const B = js_ast.B; +const G = js_ast.G; +const T = js_lexer.T; +const E = js_ast.E; +const Stmt = js_ast.Stmt; +const Expr = js_ast.Expr; +const Binding = js_ast.Binding; +const Symbol = js_ast.Symbol; +const Level = js_ast.Op.Level; +const Op = js_ast.Op; +const Scope = js_ast.Scope; +const locModuleScope = logger.Loc.Empty; +const Ast = js_ast.Ast; + +fn notimpl() void { + std.debug.panic("Not implemented yet!", .{}); +} + +pub const SourceMapChunk = struct { + buffer: MutableString, + end_state: State = State{}, + final_generated_column: usize = 0, + should_ignore: bool = false, + + // Coordinates in source maps are stored using relative offsets for size + // reasons. When joining together chunks of a source map that were emitted + // in parallel for different parts of a file, we need to fix up the first + // segment of each chunk to be relative to the end of the previous chunk. + pub const State = struct { + // This isn't stored in the source map. It's only used by the bundler to join + // source map chunks together correctly. + generated_line: i32 = 0, + + // These are stored in the source map in VLQ format. + generated_column: i32 = 0, + source_index: i32 = 0, + original_line: i32 = 0, + original_column: i32 = 0, + }; +}; + +pub const Options = struct { + to_module_ref: js_ast.Ref, + indent: usize = 0, + // If we're writing out a source map, this table of line start indices lets + // us do binary search on to figure out what line a given AST node came from + // line_offset_tables: []LineOffsetTable +}; + +pub const PrintResult = struct { js: string, source_map: ?SourceMapChunk = null }; + +const ExprFlag = enum { + forbid_call, + forbid_in, + has_non_optional_chain_parent, + expr_result_is_unused, +}; + +pub fn NewPrinter(comptime ascii_only: bool) type { + // comptime const comptime_buf_len = 64; + // comptime var comptime_buf = [comptime_buf_len]u8{}; + // comptime var comptime_buf_i: usize = 0; + + return struct { + symbols: Symbol.Map, + import_records: []importRecord.ImportRecord, + + js: MutableString, + + needs_semicolon: bool = false, + stmt_start: i32 = -1, + options: Options, + export_default_start: i32 = -1, + arrow_expr_start: i32 = -1, + for_of_init_start: i32 = -1, + prev_op: Op.Code = Op.Code.bin_add, + prev_op_end: i32 = -1, + prev_num_end: i32 = -1, + prev_reg_exp_end: i32 = -1, + call_target: ?Expr.Data = null, + int_to_bytes_buffer: [64]u8 = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + allocator: *std.mem.Allocator, + + const Printer = @This(); + pub fn comptime_flush(p: *Printer) void {} + // pub fn comptime_flush(p: *Printer) callconv(.Inline) void { + // const result = comptime { + // if (comptime_buf_i > 0) { + // return comptime_buf[0..comptime_buf_i]; + // } else { + // return ""; + // } + // }; + + // if (result.len) { + // p.print(result); + // comptime { + // if (comptime_buf_i > 0) { + // comptime_buf_i = 0; + // while (comptime_buf_i < comptime_buf_i) { + // comptime_buf[comptime_buf_i] = 0; + // comptime_buf_i += 1; + // } + // comptime_buf_i = 0; + // } + // } + // } + // } + // pub fn comptime_print(p: *Printer, str: comptime []const u8) callconv(.Inline) void { + // comptime const needsFlush = (str.len + comptime_buf_i >= comptime_buf_len - 1); + // if (needsFlush) { + // p.comptime_flush(); + // } + + // comptime { + // if (str.len > 63) { + // @compileError("comptime_print buffer overflow"); + // return; + // } + // } + + // comptime { + // comptime str_i = 0; + // while (str_i < str.len) { + // comptime_buf[comptime_buf_i] = str[str_i]; + // comptime_buf_i += 1; + // str_i += 1; + // } + // } + // } + + pub fn print(p: *Printer, str: string) void { + p.js.append(str) catch unreachable; + } + + pub fn unsafePrint(p: *Printer, str: string) void { + p.js.appendAssumeCapacity(str); + } + + pub fn printIndent(p: *Printer) void { + comptime_flush(p); + + if (p.options.indent == 0) { + return; + } + + p.js.growBy(p.options.indent * " ".len) catch unreachable; + while (p.options.indent > 0) { + p.unsafePrint(" "); + p.options.indent -= 1; + } + } + + pub fn printSpace(p: *Printer) void { + p.print(" "); + } + pub fn printNewline(p: *Printer) void { + notimpl(); + } + pub fn printSemicolonAfterStatement(p: *Printer) void { + p.print(";\n"); + } + pub fn printSemicolonIfNeeded(p: *Printer) void { + notimpl(); + } + pub fn printSpaceBeforeIdentifier(p: *Printer) void { + const n = p.js.len(); + if (n > 0 and (js_lexer.isIdentifierContinue(p.js.list.items[n - 1]) or n == p.prev_reg_exp_end)) { + p.print(" "); + } + } + pub fn printDotThenPrefix(p: *Printer) Level { + return .lowest; + } + + pub fn printUndefined(level: Level) void { + notimpl(); + } + + pub fn printBody(stmt: Stmt) void { + notimpl(); + } + pub fn printBlock(loc: logger.Loc, stmts: []Stmt) void { + notimpl(); + } + pub fn printDecls(keyword: string, decls: []G.Decl, flags: ExprFlag) void { + notimpl(); + } + + // noop for now + pub fn addSourceMapping(p: *Printer, loc: logger.Loc) void {} + + pub fn printSymbol(p: *Printer, ref: Ref) void { + notimpl(); + } + pub fn printClauseAlias(p: *Printer, alias: string) void { + notimpl(); + } + pub fn printFunc(p: *Printer, func: G.Fn) void { + notimpl(); + } + pub fn printClass(p: *Printer, class: G.Class) void { + notimpl(); + } + pub fn printExpr(p: *Printer, expr: Expr, level: Level, flags: ExprFlag) void { + p.addSourceMapping(expr.loc); + + switch (expr.data) { + .e_missing => |e| { + notimpl(); + }, + .e_undefined => |e| { + notimpl(); + }, + .e_super => |e| { + notimpl(); + }, + .e_null => |e| { + notimpl(); + }, + .e_this => |e| { + notimpl(); + }, + .e_spread => |e| { + notimpl(); + }, + .e_new_target => |e| { + notimpl(); + }, + .e_import_meta => |e| { + notimpl(); + }, + .e_new => |e| { + notimpl(); + }, + .e_call => |e| { + notimpl(); + }, + .e_require => |e| { + notimpl(); + }, + .e_require_or_require_resolve => |e| { + notimpl(); + }, + .e_import => |e| { + notimpl(); + }, + .e_dot => |e| { + notimpl(); + }, + .e_index => |e| { + notimpl(); + }, + .e_if => |e| { + notimpl(); + }, + .e_arrow => |e| { + notimpl(); + }, + .e_function => |e| { + notimpl(); + }, + .e_class => |e| { + notimpl(); + }, + .e_array => |e| { + notimpl(); + }, + .e_object => |e| { + notimpl(); + }, + .e_boolean => |e| { + p.printSpaceBeforeIdentifier(); + p.print(if (e.value) "true" else "false"); + }, + .e_string => |e| { + notimpl(); + }, + .e_template => |e| { + notimpl(); + }, + .e_reg_exp => |e| { + notimpl(); + }, + .e_big_int => |e| { + notimpl(); + }, + .e_number => |e| { + notimpl(); + }, + .e_identifier => |e| { + notimpl(); + }, + .e_import_identifier => |e| { + notimpl(); + }, + .e_await => |e| { + notimpl(); + }, + .e_yield => |e| { + notimpl(); + }, + .e_unary => |e| { + notimpl(); + }, + .e_binary => |e| { + notimpl(); + }, + else => { + std.debug.panic("Unexpected expression of type {s}", .{expr.data}); + }, + } + } + + pub fn printProperty(p: *Printer, prop: G.Property) void { + notimpl(); + } + pub fn printBinding(p: *Printer, binding: Binding) void { + notimpl(); + } + pub fn printStmt(p: *Printer, stmt: Stmt) !void { + p.comptime_flush(); + + p.addSourceMapping(stmt.loc); + + switch (stmt.data) { + .s_comment => |s| { + p.printIndentedComment(s.text); + }, + .s_function => |s| {}, + .s_class => |s| {}, + .s_empty => |s| {}, + .s_export_default => |s| {}, + .s_export_star => |s| {}, + .s_export_clause => |s| {}, + .s_export_from => |s| {}, + .s_local => |s| {}, + .s_if => |s| {}, + .s_do_while => |s| {}, + .s_for_in => |s| {}, + .s_for_of => |s| {}, + .s_while => |s| {}, + .s_with => |s| {}, + .s_label => |s| {}, + .s_try => |s| {}, + .s_for => |s| {}, + .s_switch => |s| {}, + .s_import => |s| {}, + .s_block => |s| {}, + .s_debugger => |s| {}, + .s_directive => |s| {}, + .s_break => |s| {}, + .s_continue => |s| {}, + .s_return => |s| {}, + .s_throw => |s| {}, + .s_expr => |s| { + p.printIndent(); + p.stmt_start = p.js.lenI(); + p.printExpr(s.value, .lowest, .expr_result_is_unused); + p.printSemicolonAfterStatement(); + }, + else => { + std.debug.panic("Unexpected statement of type {s}", .{@TypeOf(stmt)}); + }, + } + } + + pub fn printIndentedComment(p: *Printer, _text: string) void { + var text = _text; + if (strings.startsWith(text, "/*")) { + // Re-indent multi-line comments + while (strings.indexOfChar(text, '\n')) |newline_index| { + p.printIndent(); + p.print(text[0 .. newline_index + 1]); + text = text[newline_index + 1 ..]; + } + p.printIndent(); + p.print(text); + p.printNewline(); + } else { + // Print a mandatory newline after single-line comments + p.printIndent(); + p.print(text); + p.print("\n"); + } + } + + pub fn init(allocator: *std.mem.Allocator, tree: Ast, symbols: Symbol.Map, opts: Options) !Printer { + return Printer{ + .allocator = allocator, + .import_records = tree.import_records, + .options = opts, + .symbols = symbols, + .js = try MutableString.init(allocator, 1024), + }; + } + }; +} + +const UnicodePrinter = NewPrinter(false); +const AsciiPrinter = NewPrinter(true); + +pub fn printAst(allocator: *std.mem.Allocator, tree: Ast, symbols: js_ast.Symbol.Map, ascii_only: bool, opts: Options) !PrintResult { + if (ascii_only) { + var printer = try AsciiPrinter.init(allocator, tree, symbols, opts); + for (tree.parts) |part| { + for (part.stmts) |stmt| { + try printer.printStmt(stmt); + } + } + + return PrintResult{ + .js = printer.js.toOwnedSlice(), + }; + } else { + var printer = try UnicodePrinter.init(allocator, tree, symbols, opts); + for (tree.parts) |part| { + for (part.stmts) |stmt| { + try printer.printStmt(stmt); + } + } + + return PrintResult{ + .js = printer.js.toOwnedSlice(), + }; + } +} diff --git a/src/json_parser.zig b/src/json_parser.zig index 8a025864f..afd7c8046 100644 --- a/src/json_parser.zig +++ b/src/json_parser.zig @@ -11,6 +11,7 @@ usingnamespace @import("strings.zig"); usingnamespace @import("ast/base.zig"); usingnamespace js_ast.G; +const expect = std.testing.expect; const ImportKind = importRecord.ImportKind; const BindingNodeIndex = js_ast.BindingNodeIndex; @@ -41,13 +42,13 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { const Lexer = if (opts.allow_comments) js_lexer.TSConfigJSONLexer else js_lexer.JSONLexer; return struct { lexer: Lexer, - source: logger.Source, - log: logger.Log, + source: *logger.Source, + log: *logger.Log, allocator: *std.mem.Allocator, - pub fn init(allocator: *std.mem.Allocator, source: logger.Source, log: logger.Log) Parser { + pub fn init(allocator: *std.mem.Allocator, source: *logger.Source, log: *logger.Log) !Parser { return Parser{ - .lexer = Lexer.init(log, source, allocator), + .lexer = try Lexer.init(log, source, allocator), .allocator = allocator, .log = log, .source = source, @@ -63,7 +64,7 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { return Expr.alloc(p.allocator, t, loc); } } - pub fn parseExpr(p: *Parser) ?Expr { + pub fn parseExpr(p: *Parser) Expr { const loc = p.lexer.loc(); switch (p.lexer.token) { @@ -104,7 +105,7 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { .t_open_bracket => { p.lexer.next(); var is_single_line = !p.lexer.has_newline_before; - var exprs = List(Expr).init(p.allocator); + var exprs = std.ArrayList(Expr).init(p.allocator); while (p.lexer.token != .t_close_bracket) { if (exprs.items.len > 0) { @@ -121,11 +122,7 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { } } - if (p.parseExpr()) |expr| { - try exprs.append(expr); - } else { - break; - } + exprs.append(p.parseExpr()) catch unreachable; } if (p.lexer.has_newline_before) { @@ -137,8 +134,8 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { .t_open_brace => { p.lexer.next(); var is_single_line = !p.lexer.has_newline_before; - var properties = List(G.Property).init(p.allocator); - var duplicates = std.StringHashMap(u0).init(p.allocator); + var properties = std.ArrayList(G.Property).init(p.allocator); + var duplicates = std.StringHashMap(u1).init(p.allocator); while (p.lexer.token != .t_close_brace) { if (properties.items.len > 0) { @@ -153,17 +150,17 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { var key_range = p.lexer.range(); var key = p.e(E.String{ .value = key_string }, key_range.loc); p.lexer.expect(.t_string_literal); - var key_text = p.lexer.utf16ToString(); + var key_text = p.lexer.utf16ToString(key_string); // Warn about duplicate keys - if (duplicates.contains(key_text)) { - p.log.addRangeWarningFmt(p.source, r, "Duplicate key \"{s}\" in object literal", .{key_text}) catch unreachable; - } else { - duplicates.put(key_text, 0) catch unreachable; + + const entry = duplicates.getOrPut(key_text) catch unreachable; + if (entry.found_existing) { + p.log.addRangeWarningFmt(p.source.*, key_range, p.allocator, "Duplicate key \"{s}\" in object literal", .{key_text}) catch unreachable; } p.lexer.expect(.t_colon); - var value = p.parseExpr() orelse return null; - try properties.append(G.Property{ .key = key, .value = value }); + var value = p.parseExpr(); + properties.append(G.Property{ .key = key, .value = value }) catch unreachable; } is_single_line = if (p.lexer.has_newline_before) false else is_single_line; @@ -175,7 +172,7 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { }, else => { p.lexer.unexpected(); - return null; + return p.e(E.Missing{}, loc); }, } } @@ -186,7 +183,7 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { if (p.lexer.token == closer) { if (!opts.allow_trailing_commas) { - p.log.addRangeError(p.source, comma_range, "JSON does not support trailing commas") catch unreachable; + p.log.addRangeError(p.source.*, comma_range, "JSON does not support trailing commas") catch unreachable; } return false; } @@ -199,14 +196,99 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type { const JSONParser = JSONLikeParser(js_lexer.JSONOptions{}); const TSConfigParser = JSONLikeParser(js_lexer.JSONOptions{ .allow_comments = true, .allow_trailing_commas = true }); -pub fn ParseJSON(log: logger.Log, source: logger.Source) !?Expr { - var parser = JSONParser.init(allocator, log, source); +pub fn ParseJSON(source: *logger.Source, log: *logger.Log, allocator: *std.mem.Allocator) !Expr { + var parser = try JSONParser.init(allocator, source, log); + + return parser.parseExpr(); +} + +pub fn ParseTSConfig(log: logger.Loc, source: logger.Source, allocator: *std.mem.Allocator) !Expr { + var parser = try TSConfigParser.init(allocator, log, source); + + return parser.parseExpr(); +} + +const duplicateKeyJson = "{ \"name\": \"valid\", \"name\": \"invalid\" }"; + +fn expectPrintedJSON(_contents: string, expected: string) void { + if (alloc.dynamic_manager == null) { + alloc.setup(std.heap.page_allocator) catch unreachable; + } + + var contents = alloc.dynamic.alloc(u8, _contents.len + 1) catch unreachable; + std.mem.copy(u8, contents, _contents); + contents[contents.len - 1] = ';'; + var log = logger.Log.init(alloc.dynamic); + const js_printer = @import("js_printer.zig"); + const renamer = @import("renamer.zig"); + + var source = logger.Source.initPathString( + "source.json", + contents, + ); + const expr = try ParseJSON(&source, &log, alloc.dynamic); + var stmt = Stmt.alloc(std.heap.page_allocator, S.SExpr{ .value = expr }, logger.Loc{ .start = 0 }); + + var part = js_ast.Part{ + .stmts = &([_]Stmt{stmt}), + }; + const tree = js_ast.Ast.initTest(&([_]js_ast.Part{part})); + var symbol_map = Symbol.Map{}; + if (log.msgs.items.len > 0) { + std.debug.panic("--FAIL--\nExpr {s}\nLog: {s}\n--FAIL--", .{ expr, log.msgs.items[0].data.text }); + } + + const result = js_printer.printAst(std.heap.page_allocator, tree, symbol_map, true, js_printer.Options{ .to_module_ref = Ref{ .inner_index = 0 } }) catch unreachable; + + var js = result.js; + + if (js.len > 1) { + while (js[js.len - 1] == '\n') { + js = js[0 .. js.len - 1]; + } + + if (js[js.len - 1] == ';') { + js = js[0 .. js.len - 1]; + } + } + + std.testing.expectEqualStrings(expected, js); +} - return try parser.parseExpr(); +test "ParseJSON" { + expectPrintedJSON("true", "true"); + expectPrintedJSON("false", "false"); } -pub fn ParseTSConfig(log: logger.Loc, source: logger.Source) !?Expr { - var parser = TSConfigParser.init(allocator, log, source); +test "ParseJSON DuplicateKey warning" { + alloc.setup(std.heap.page_allocator) catch unreachable; + var log = logger.Log.init(alloc.dynamic); + + var source = logger.Source.initPathString( + "package.json", + duplicateKeyJson, + ); + const expr = try ParseJSON(&source, &log, alloc.dynamic); + + const tag = @as(Expr.Tag, expr.data); + expect(tag == .e_object); + const object = expr.data.e_object; + std.testing.expectEqual(@as(usize, 2), object.properties.len); + const name1 = object.properties[0]; + expect(name1.key != null); + expect(name1.value != null); + expect(Expr.Tag.e_string == @as(Expr.Tag, name1.value.?.data)); + expect(Expr.Tag.e_string == @as(Expr.Tag, name1.key.?.data)); + expect(strings.eqlUtf16("name", name1.key.?.data.e_string.value)); + expect(strings.eqlUtf16("valid", name1.value.?.data.e_string.value)); + + const name2 = object.properties[1]; + expect(name2.key != null); + expect(name2.value != null); + expect(Expr.Tag.e_string == @as(Expr.Tag, name2.value.?.data)); + expect(Expr.Tag.e_string == @as(Expr.Tag, name2.key.?.data)); + expect(strings.eqlUtf16("name", name2.key.?.data.e_string.value)); + std.testing.expectEqualStrings("invalid", try name2.value.?.data.e_string.string(alloc.dynamic)); - return try parser.parseExpr(); + std.testing.expectEqual(@as(usize, 1), log.msgs.items.len); } diff --git a/src/logger.zig b/src/logger.zig index bf7bcb21c..0aa37bdc8 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -103,7 +103,7 @@ pub const Msg = struct { kind: Kind = Kind.err, data: Data, notes: ?[]Data = null, - pub fn format(msg: *const Msg, to: anytype, formatterFunc: @TypeOf(std.fmt.format)) !void { + pub fn doFormat(msg: *const Msg, to: anytype, formatterFunc: @TypeOf(std.fmt.format)) !void { try formatterFunc(to, "\n\n{s}: {s}\n{s}\n{s}:{}:{}", .{ msg.kind.string(), msg.data.text, msg.data.location.?.line_text, msg.data.location.?.file, msg.data.location.?.line, msg.data.location.?.column }); } @@ -127,10 +127,16 @@ pub const Range = packed struct { pub const Log = struct { debug: bool = false, - warnings: u8 = 0, - errors: u8 = 0, + warnings: usize = 0, + errors: usize = 0, msgs: ArrayList(Msg), + pub fn init(allocator: *std.mem.Allocator) Log { + return Log{ + .msgs = ArrayList(Msg).init(allocator), + }; + } + pub fn addVerbose(log: *Log, source: ?Source, loc: Loc, text: string) !void { try log.addMsg(Msg{ .kind = .verbose, @@ -179,7 +185,7 @@ pub const Log = struct { } pub fn addWarningFmt(log: *Log, source: ?Source, l: Loc, allocator: *std.mem.Allocator, comptime text: string, args: anytype) !void { - log.errors += 1; + log.warnings += 1; try log.addMsg(Msg{ .kind = .err, .data = rangeData(source, Range{ .loc = l }, std.fmt.allocPrint(allocator, text, args) catch unreachable), @@ -187,9 +193,9 @@ pub const Log = struct { } pub fn addRangeWarningFmt(log: *Log, source: ?Source, r: Range, allocator: *std.mem.Allocator, comptime text: string, args: anytype) !void { - log.errors += 1; + log.warnings += 1; try log.addMsg(Msg{ - .kind = .err, + .kind = .warn, .data = rangeData(source, r, std.fmt.allocPrint(allocator, text, args) catch unreachable), }); } @@ -241,7 +247,7 @@ pub const Log = struct { // TODO: pub fn print(self: *Log, to: anytype) !void { for (self.msgs.items) |msg| { - try msg.format(to, std.fmt.format); + try msg.doFormat(to, std.fmt.format); } } }; @@ -406,7 +412,8 @@ pub fn rangeData(source: ?Source, r: Range, text: string) Data { } test "print msg" { - var log = Log{ .msgs = ArrayList(Msg).init(std.testing.allocator) }; + var msgs = ArrayList(Msg).init(std.testing.allocator); + var log = Log{ .msgs = msgs }; defer log.msgs.deinit(); var filename = "test.js".*; var syntax = "for(i = 0;)".*; @@ -420,5 +427,5 @@ test "print msg" { const stdout = std.io.getStdOut().writer(); - try log.print(stdout); + // try log.print(stdout); } diff --git a/src/main.zig b/src/main.zig index 73f03f9ad..6bdd34cb4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -20,7 +20,10 @@ pub fn main() anyerror!void { const entryPointName = "/var/foo/index.js"; const code = "for (let i = 0; i < 100; i++) { console.log('hi') aposkdpoaskdpokasdpokasdpokasdpokasdpoaksdpoaksdpoaskdpoaksdpoaksdpoaskdpoaskdpoasdk; "; - var parser = try js_parser.Parser.init(try options.TransformOptions.initUncached(alloc.dynamic, entryPointName, code), alloc.dynamic); + var log = logger.Log.init(alloc.dynamic); + const opts = try options.TransformOptions.initUncached(alloc.dynamic, entryPointName, code); + var source = logger.Source.initFile(opts.entry_point, alloc.dynamic); + var parser = try js_parser.Parser.init(opts, &log, &source, alloc.dynamic); var res = try parser.parse(); std.debug.print("{s}", .{res}); diff --git a/src/renamer.zig b/src/renamer.zig new file mode 100644 index 000000000..ed811bd1c --- /dev/null +++ b/src/renamer.zig @@ -0,0 +1,22 @@ +const js_ast = @import("js_ast.zig"); + +pub const Renamer = struct { + symbols: js_ast.Symbol.Map, + pub fn init(symbols: js_ast.Symbol.Map) Renamer { + return Renamer{ .symbols = symbols }; + } + + pub fn nameForSymbol(renamer: *Renamer, ref: js_ast.Ref) string { + const resolved = renamer.symbols.follow(ref); + const symbol = renamer.symbols.get(resolved) orelse std.debug.panic("Internal error: symbol not found for ref: {s}", .{resolved}); + + return symbol.original_name; + } +}; + +pub const DisabledRenamer = struct { + pub fn init(symbols: js_ast.Symbol.Map) DisabledRenamer {} + pub fn nameForSymbol(renamer: *Renamer, ref: js_ast.Ref) callconv(.Inline) string { + @compileError("DisabledRunner called"); + } +}; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index f9d485df6..abfeec072 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -25,6 +25,52 @@ pub fn indexOf(self: string, str: u8) ?usize { return std.mem.indexOf(u8, self, str); } +pub fn startsWith(self: string, str: string) bool { + if (str.len > self.len) { + return false; + } + + var i: usize = 0; + while (i < str.len) { + if (str[i] != self[i]) { + return false; + } + i += 1; + } + + return true; +} + +pub fn endsWithAny(self: string, str: string) bool { + const end = self[self.len - 1]; + for (str) |char| { + if (char == end) { + return true; + } + } + + return false; +} + +pub fn lastNonwhitespace(self: string, str: string) bool { + +} + +pub fn endsWithAnyComptime(self: string, comptime str: string) bool { + if (str.len < 10) { + const last = self[self.len - 1]; + inline while (str) |char| { + if (char == last) { + return true; + } + } + + return false; + } else { + return endsWithAny(self, str); + } +} + pub fn eql(self: string, other: anytype) bool { return std.mem.eql(u8, self, other); } diff --git a/src/string_mutable.zig b/src/string_mutable.zig index f6cf12799..8c9c9e99e 100644 --- a/src/string_mutable.zig +++ b/src/string_mutable.zig @@ -7,6 +7,18 @@ pub const MutableString = struct { allocator: *std.mem.Allocator, list: std.ArrayListUnmanaged(u8), + pub const Writer = std.io.Writer(@This(), anyerror, MutableString.writeAll); + pub fn writer(self: *MutableString) Writer { + return Writer{ + .context = self, + }; + } + + pub fn writeAll(self: *MutableString, bytes: []u8) !usize { + try self.list.appendSlice(self.allocator, bytes); + return self.list.items.len; + } + pub fn init(allocator: *std.mem.Allocator, capacity: usize) !MutableString { return MutableString{ .allocator = allocator, .list = try std.ArrayListUnmanaged(u8).initCapacity(allocator, capacity) }; } @@ -61,25 +73,29 @@ pub const MutableString = struct { try self.list.replaceRange(self.allocator, 0, std.mem.len(str[0..]), str[0..]); } } + pub fn growBy(self: *MutableString, amount: usize) callconv(.Inline) !void { + try self.list.ensureCapacity(self.allocator, self.list.capacity + amount); + } pub fn deinit(self: *MutableString) !void { self.list.deinit(self.allocator); } - - pub fn appendChar(self: *MutableString, char: u8) !void { + pub fn appendChar(self: *MutableString, char: u8) callconv(.Inline) !void { try self.list.append(self.allocator, char); } - - pub fn appendCharAssumeCapacity(self: *MutableString, char: u8) void { + pub fn appendCharAssumeCapacity(self: *MutableString, char: u8) callconv(.Inline) void { self.list.appendAssumeCapacity(char); } - - pub fn append(self: *MutableString, char: []const u8) !void { + pub fn append(self: *MutableString, char: []const u8) callconv(.Inline) !void { try self.list.appendSlice(self.allocator, char); } - - pub fn appendAssumeCapacity(self: *MutableString, char: []const u8) !void { - try self.list.appendSliceAssumeCapacity(self.allocator, char); + pub fn appendAssumeCapacity(self: *MutableString, char: []const u8) callconv(.Inline) void { + self.list.appendSliceAssumeCapacity( + char, + ); + } + pub fn lenI(self: *MutableString) callconv(.Inline) i32 { + return @intCast(i32, self.list.items.len); } pub fn toOwnedSlice(self: *MutableString) string { |