aboutsummaryrefslogtreecommitdiff
path: root/src/js_printer.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/js_printer.zig')
-rw-r--r--src/js_printer.zig368
1 files changed, 217 insertions, 151 deletions
diff --git a/src/js_printer.zig b/src/js_printer.zig
index c0ba6ce55..97940ff71 100644
--- a/src/js_printer.zig
+++ b/src/js_printer.zig
@@ -118,7 +118,7 @@ const ExprFlag = packed struct {
}
};
-pub fn NewPrinter(comptime ascii_only: bool) type {
+pub fn NewPrinter(comptime ascii_only: bool, comptime Writer: type) type {
// comptime const comptime_buf_len = 64;
// comptime var comptime_buf = [comptime_buf_len]u8{};
// comptime var comptime_buf_i: usize = 0;
@@ -127,7 +127,6 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
symbols: Symbol.Map,
import_records: []importRecord.ImportRecord,
linker: ?*Linker,
- js: MutableString,
needs_semicolon: bool = false,
stmt_start: i32 = -1,
@@ -140,8 +139,8 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
prev_num_end: i32 = -1,
prev_reg_exp_end: i32 = -1,
call_target: ?Expr.Data = null,
- writer: MutableString.Writer,
- allocator: *std.mem.Allocator,
+ writer: Writer,
+
renamer: rename.Renamer,
prev_stmt_tag: Stmt.Tag = .s_empty,
@@ -200,60 +199,17 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
pub fn print(p: *Printer, str: anytype) void {
switch (@TypeOf(str)) {
- comptime_int => {
- p.js.appendChar(str) catch unreachable;
- },
- string => {
- if (FeatureFlags.disable_printing_null) {
- if (str.len > 0 and str[0] == 0 or (str[0] == '\\' and str[1] == '0')) {
- Global.panic("Attempted to print null char", .{});
- }
- }
-
- p.js.append(str) catch unreachable;
- },
- u8 => {
- if (FeatureFlags.disable_printing_null) {
- if (str == 0) {
- Global.panic("Attempted to print null char", .{});
- }
- }
- p.js.appendChar(str) catch unreachable;
- },
- u16 => {
- if (FeatureFlags.disable_printing_null) {
- if (str == 0) {
- Global.panic("Attempted to print null char", .{});
- }
- }
- p.js.appendChar(@intCast(u8, str)) catch unreachable;
- },
- u21 => {
- if (FeatureFlags.disable_printing_null) {
- if (str == 0) {
- Global.panic("Attempted to print null char", .{});
- }
- }
- p.js.appendChar(@intCast(u8, str)) catch unreachable;
+ comptime_int, u16, u8 => {
+ p.writer.print(@TypeOf(str), str);
},
else => {
- if (FeatureFlags.disable_printing_null) {
- if (str[0] == 0 or (str[0] == '\\' and str[1] == '0')) {
- Global.panic("Attempted to print null char", .{});
- }
- }
- p.js.append(@as(string, str)) catch unreachable;
+ p.writer.print(@TypeOf(str), str);
},
}
}
pub fn unsafePrint(p: *Printer, str: string) void {
- if (FeatureFlags.disable_printing_null) {
- if (str[0] == 0 or (str[0] == '\\' and str[1] == '0')) {
- Global.panic("Attempted to print null char", .{});
- }
- }
- p.js.appendAssumeCapacity(str);
+ p.print(str);
}
pub fn printIndent(p: *Printer) void {
@@ -263,7 +219,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
return;
}
- p.js.growBy(p.options.indent * " ".len) catch unreachable;
+ // p.js.growBy(p.options.indent * " ".len) catch unreachable;
var i: usize = 0;
while (i < p.options.indent) : (i += 1) {
@@ -289,8 +245,8 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
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)) {
+ const n = p.writer.written;
+ if (n > 0 and (js_lexer.isIdentifierContinue(p.writer.prev_char) or n == p.prev_reg_exp_end)) {
p.print(" ");
}
}
@@ -298,10 +254,10 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
pub fn maybePrintSpace(
p: *Printer,
) void {
- const n = p.js.len();
- if (n <= 0) return;
+ const n = p.writer.written;
+ if (n == 0) return;
- switch (p.js.list.items[n - 1]) {
+ switch (p.writer.prev_char) {
' ', '\n' => {},
else => {
p.print(" ");
@@ -513,7 +469,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
// not in zig! CI pays for it instead
// its probably still doing some unnecessary integer conversion somewhere though
var slice = std.fmt.bufPrint(&parts, "{d}", .{float}) catch unreachable;
- p.js.list.appendSlice(p.allocator, slice) catch unreachable;
+ p.print(slice);
}
pub fn printQuotedUTF16(e: *Printer, text: JavascriptString, quote: u8) void {
@@ -526,7 +482,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
var c: u21 = 0;
var width: u3 = 0;
- e.js.growIfNeeded(text.len) catch unreachable;
+ // e(text.len) catch unreachable;
while (i < n) {
c = text[i];
@@ -927,10 +883,10 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
if (has_pure_comment) {
- const was_stmt_start = p.stmt_start == p.js.len();
+ const was_stmt_start = p.stmt_start == p.writer.written;
p.print("/* @__PURE__ */ ");
if (was_stmt_start) {
- p.stmt_start = p.js.lenI();
+ p.stmt_start = p.writer.written;
}
}
// We don't ever want to accidentally generate a direct eval expression here
@@ -1045,7 +1001,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.print("?");
}
if (p.canPrintIdentifier(e.name)) {
- if (isOptionalChain and p.prev_num_end == p.js.len()) {
+ if (isOptionalChain and p.prev_num_end == p.writer.written) {
// "1.toString" is a syntax error, so print "1 .toString" instead
p.print(" ");
}
@@ -1147,7 +1103,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
switch (e.body.stmts[0].data) {
.s_return => {
if (e.body.stmts[0].getReturn().value) |val| {
- p.arrow_expr_start = p.js.lenI();
+ p.arrow_expr_start = p.writer.written;
p.printExpr(val, .comma, ExprFlag.None());
wasPrinted = true;
}
@@ -1165,7 +1121,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
},
.e_function => |e| {
- const n = p.js.lenI();
+ const n = p.writer.written;
var wrap = p.stmt_start == n or p.export_default_start == n;
if (wrap) {
@@ -1193,7 +1149,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
},
.e_class => |e| {
- const n = p.js.lenI();
+ const n = p.writer.written;
var wrap = p.stmt_start == n or p.export_default_start == n;
if (wrap) {
p.print("(");
@@ -1251,7 +1207,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.print("]");
},
.e_object => |e| {
- const n = p.js.lenI();
+ const n = p.writer.written;
const wrap = p.stmt_start == n or p.arrow_expr_start == n;
if (wrap) {
@@ -1346,17 +1302,17 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.print("`");
},
.e_reg_exp => |e| {
- const n = p.js.len();
+ const n = p.writer.written;
// Avoid forming a single-line comment
- if (n > 0 and p.js.list.items[n - 1] == '/') {
+ if (n > 0 and p.writer.prev_char == '/') {
p.print(" ");
}
p.print(e.value);
// Need a space before the next identifier to avoid it turning into flags
- p.prev_reg_exp_end = p.js.lenI();
+ p.prev_reg_exp_end = p.writer.written;
},
.e_big_int => |e| {
p.printSpaceBeforeIdentifier();
@@ -1385,7 +1341,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.printNonNegativeFloat(absValue);
// Remember the end of the latest number
- p.prev_num_end = p.js.lenI();
+ p.prev_num_end = p.writer.written;
} else if (level.gte(.prefix)) {
// Expressions such as "(-1).toString" need to wrap negative numbers.
// Instead of testing for "value < 0" we test for "signbit(value)" and
@@ -1400,12 +1356,12 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.printNonNegativeFloat(absValue);
// Remember the end of the latest number
- p.prev_num_end = p.js.lenI();
+ p.prev_num_end = p.writer.written;
}
},
.e_identifier => |e| {
const name = p.renamer.nameForSymbol(e.ref);
- const wrap = p.js.lenI() == p.for_of_init_start and strings.eqlComptime(name, "let");
+ const wrap = p.writer.written == p.for_of_init_start and strings.eqlComptime(name, "let");
if (wrap) {
p.print("(");
@@ -1518,7 +1474,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.printSpaceBeforeOperator(e.op);
p.print(entry.text);
p.prev_op = e.op;
- p.prev_op_end = p.js.lenI();
+ p.prev_op_end = p.writer.written;
}
if (e.op.isPrefix()) {
@@ -1534,7 +1490,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
var wrap = level.gte(entry.level) or (e.op == Op.Code.bin_in and flags.forbid_in);
// Destructuring assignments must be parenthesized
- const n = p.js.lenI();
+ const n = p.writer.written;
if (n == p.stmt_start or n == p.arrow_expr_start) {
switch (e.left.data) {
.e_object => {
@@ -1626,7 +1582,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.printSpaceBeforeIdentifier();
p.print(entry.text);
p.prev_op = e.op;
- p.prev_op_end = p.js.lenI();
+ p.prev_op_end = p.writer.written;
}
p.printSpace();
@@ -1644,7 +1600,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
pub fn printSpaceBeforeOperator(p: *Printer, next: Op.Code) void {
- if (p.prev_op_end == p.js.lenI()) {
+ if (p.prev_op_end == p.writer.written) {
const prev = p.prev_op;
// "+ + y" => "+ +y"
// "+ ++ y" => "+ ++y"
@@ -1656,7 +1612,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
if (((prev == Op.Code.bin_add or prev == Op.Code.un_pos) and (next == Op.Code.bin_add or next == Op.Code.un_pos or next == Op.Code.un_pre_inc)) or
((prev == Op.Code.bin_sub or prev == Op.Code.un_neg) and (next == Op.Code.bin_sub or next == Op.Code.un_neg or next == Op.Code.un_pre_dec)) or
(prev == Op.Code.un_post_dec and next == Op.Code.bin_gt) or
- (prev == Op.Code.un_not and next == Op.Code.un_pre_dec and p.js.len() > 1 and p.js.list.items[p.js.list.items.len - 2] == '<'))
+ (prev == Op.Code.un_not and next == Op.Code.un_pre_dec and p.writer.written > 1 and p.writer.prev_prev_char == '<'))
{
p.print(" ");
}
@@ -2212,7 +2168,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
switch (s.value) {
.expr => |expr| {
// Functions and classes must be wrapped to avoid confusion with their statement forms
- p.export_default_start = p.js.lenI();
+ p.export_default_start = p.writer.written;
p.printExpr(expr, .comma, ExprFlag.None());
p.printSemicolonAfterStatement();
return;
@@ -2443,7 +2399,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
p.printSpace();
p.print("(");
- p.for_of_init_start = p.js.lenI();
+ p.for_of_init_start = p.writer.written;
p.printForLoopInit(s.init);
p.printSpace();
p.printSpaceBeforeIdentifier();
@@ -2622,11 +2578,11 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
if (record.wrap_with_to_module) {
if (p.options.runtime_imports.__require) |require_ref| {
p.print("import * as ");
- const module_name_start = p.js.list.items.len;
- const module_name_segment = (fs.PathName.init(record.path.pretty).nonUniqueNameString(p.allocator) catch unreachable)[1..];
+ var module_name_buf: [256]u8 = undefined;
+ var fixed_buf_allocator = std.heap.FixedBufferAllocator.init(&module_name_buf);
+ const module_name_segment = (fs.PathName.init(record.path.pretty).nonUniqueNameString(&fixed_buf_allocator.allocator) catch unreachable)[1..];
p.print(module_name_segment);
p.print("_module");
- const module_name_end = p.js.list.items[module_name_start..].len + module_name_start;
p.print(" from \"");
p.print(record.path.text);
p.print("\";\n");
@@ -2792,7 +2748,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
},
.s_expr => |s| {
p.printIndent();
- p.stmt_start = p.js.lenI();
+ p.stmt_start = p.writer.written;
p.printExpr(s.value, .lowest, ExprFlag.ExprResultIsUnused());
p.printSemicolonAfterStatement();
},
@@ -2948,11 +2904,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
pub fn printIdentifier(p: *Printer, identifier: string) void {
- if (ascii_only) {
- quoteIdentifier(&p.js, identifier) catch unreachable;
- } else {
- p.print(identifier);
- }
+ p.print(identifier);
}
pub fn printIdentifierUTF16(p: *Printer, name: JavascriptString) !void {
@@ -3005,16 +2957,19 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
}
- pub fn init(allocator: *std.mem.Allocator, tree: *const Ast, source: *const logger.Source, symbols: Symbol.Map, opts: Options, linker: ?*Linker) !Printer {
- // Heuristic: most lines of JavaScript are short.
- var js = try MutableString.init(allocator, 0);
+ pub fn init(
+ writer: Writer,
+ tree: *const Ast,
+ source: *const logger.Source,
+ symbols: Symbol.Map,
+ opts: Options,
+ linker: ?*Linker,
+ ) !Printer {
return Printer{
- .allocator = allocator,
.import_records = tree.import_records,
.options = opts,
.symbols = symbols,
- .js = js,
- .writer = js.writer(),
+ .writer = writer,
.linker = linker,
.renamer = rename.Renamer.init(symbols, source),
};
@@ -3022,75 +2977,186 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
};
}
-// TODO:
-pub fn quoteIdentifier(js: *MutableString, identifier: string) !void {
- return try js.append(identifier);
- // assert(identifier.len > 0);
- // var utf8iter = std.unicode.Utf8Iterator{ .bytes = identifier, .i = 0 };
- // try js.growIfNeeded(identifier.len);
-
- // var init = utf8iter.nextCodepoint() orelse unreachable;
- // var ascii_start: usize = if (init >= first_ascii and init <= last_ascii) 0 else std.math.maxInt(usize);
-
- // while (utf8iter.nextCodepoint()) |code_point| {
- // switch (code_point) {
- // first_ascii...last_ascii => {},
- // else => {
- // ascii_start = utf8iter.i;
- // },
- // }
- // }
+pub fn NewWriter(
+ comptime ContextType: type,
+ writeByte: fn (ctx: *ContextType, char: u8) anyerror!usize,
+ writeAll: fn (ctx: *ContextType, buf: anytype) anyerror!usize,
+) type {
+ return struct {
+ const Self = @This();
+ ctx: ContextType,
+ written: i32 = -1,
+ // Used by the printer
+ prev_char: u8 = 0,
+ prev_prev_char: u8 = 0,
+ err: ?anyerror = null,
+ orig_err: ?anyerror = null,
+
+ pub fn init(ctx: ContextType) Self {
+ return .{
+ .ctx = ctx,
+ };
+ }
+
+ pub fn getError(writer: *const Self) anyerror!void {
+ if (writer.orig_err) |orig_err| {
+ return orig_err;
+ }
+
+ if (writer.err) |err| {
+ return err;
+ }
+ }
+
+ pub inline fn print(writer: *Self, comptime ValueType: type, str: ValueType) void {
+ if (FeatureFlags.disable_printing_null) {
+ if (str == 0) {
+ Global.panic("Attempted to print null char", .{});
+ }
+ }
+
+ switch (ValueType) {
+ comptime_int, u16, u8 => {
+ const written = writeByte(&writer.ctx, @intCast(u8, str)) catch |err| brk: {
+ writer.orig_err = err;
+ break :brk 0;
+ };
+
+ writer.written += @intCast(i32, written);
+
+ writer.prev_prev_char = writer.prev_char;
+ writer.prev_char = str;
+
+ writer.err = if (written == 0) error.WriteFailed else writer.err;
+ },
+ else => {
+ const written = writeAll(&writer.ctx, str) catch |err| brk: {
+ writer.orig_err = err;
+ break :brk 0;
+ };
+
+ writer.written += @intCast(i32, written);
+
+ writer.prev_prev_char = if (written > 1) str[written - 1] else if (written == 1) writer.prev_char else writer.prev_prev_char;
+ writer.prev_char = if (written > 1) str[written - 1] else writer.prev_char;
+
+ if (written < str.len) {
+ writer.err = if (written == 0) error.WriteFailed else error.PartialWrite;
+ }
+ },
+ }
+ }
+
+ const hasFlush = std.meta.trait.hasFn("flush");
+ pub fn flush(writer: *Self) !void {
+ if (hasFlush(ContextType)) {
+ try writer.ctx.flush();
+ }
+ }
+ const hasDone = std.meta.trait.hasFn("done");
+ pub fn done(writer: *Self) !void {
+ if (hasDone(ContextType)) {
+ try writer.ctx.done();
+ }
+ }
+ };
}
-const UnicodePrinter = NewPrinter(false);
-const AsciiPrinter = NewPrinter(true);
+pub const DirectWriter = struct {
+ handle: FileDescriptorType,
+
+ pub fn write(writer: *DirectWriter, buf: []const u8) !usize {
+ return try std.os.write(writer.handle, buf);
+ }
+
+ pub fn writeAll(writer: *DirectWriter, buf: []const u8) !void {
+ _ = try std.os.write(writer.handle, buf);
+ }
+ pub const Error = std.os.WriteError;
+};
+
+// Unbuffered 653ms
+// Buffered 65k 47ms
+// Buffered 16k 43ms
+// Buffered 4k 55ms
+const FileWriterInternal = struct {
+ file: std.fs.File,
+ threadlocal var buffer: MutableString = undefined;
+ threadlocal var has_loaded_buffer: bool = false;
+
+ pub fn init(file: std.fs.File) FileWriterInternal {
+ // if (isMac) {
+ // _ = std.os.fcntl(file.handle, std.os.F_NOCACHE, 1) catch 0;
+ // }
+
+ if (!has_loaded_buffer) {
+ buffer = MutableString.init(alloc.dynamic, 0) catch unreachable;
+ has_loaded_buffer = true;
+ }
+
+ buffer.reset();
+
+ return FileWriterInternal{
+ .file = file,
+ };
+ }
+ pub fn writeByte(ctx: *FileWriterInternal, byte: u8) anyerror!usize {
+ try buffer.appendChar(byte);
+ return 1;
+ }
+ pub fn writeAll(ctx: *FileWriterInternal, bytes: anytype) anyerror!usize {
+ try buffer.append(bytes);
+ return bytes.len;
+ }
+
+ pub fn done(
+ ctx: *FileWriterInternal,
+ ) anyerror!void {
+ _ = try ctx.file.writeAll(buffer.toOwnedSliceLeaky());
+ buffer.reset();
+ }
+
+ pub fn flush(
+ ctx: *FileWriterInternal,
+ ) anyerror!void {}
+};
+
+pub const FileWriter = NewWriter(FileWriterInternal, FileWriterInternal.writeByte, FileWriterInternal.writeAll);
+pub fn NewFileWriter(file: std.fs.File) FileWriter {
+ var internal = FileWriterInternal.init(file);
+ return FileWriter.init(internal);
+}
pub fn printAst(
- allocator: *std.mem.Allocator,
+ comptime Writer: type,
+ _writer: Writer,
tree: Ast,
symbols: js_ast.Symbol.Map,
source: *const logger.Source,
ascii_only: bool,
opts: Options,
linker: ?*Linker,
-) !PrintResult {
- if (ascii_only) {
- var printer = try AsciiPrinter.init(
- allocator,
- &tree,
- source,
- symbols,
-
- opts,
- linker,
- );
-
- for (tree.parts) |part| {
- for (part.stmts) |stmt| {
- try printer.printStmt(stmt);
+) !usize {
+ const PrinterType = NewPrinter(false, Writer);
+ var writer = _writer;
+ var printer = try PrinterType.init(
+ writer,
+ &tree,
+ source,
+ symbols,
+ opts,
+ linker,
+ );
+ for (tree.parts) |part| {
+ for (part.stmts) |stmt| {
+ try printer.printStmt(stmt);
+ if (printer.writer.getError()) {} else |err| {
+ return err;
}
}
+ }
- return PrintResult{
- .js = printer.js.toOwnedSliceLeaky(),
- };
- } else {
- var printer = try UnicodePrinter.init(
- allocator,
- &tree,
- source,
- symbols,
- opts,
- linker,
- );
- for (tree.parts) |part| {
- for (part.stmts) |stmt| {
- try printer.printStmt(stmt);
- }
- }
+ try printer.writer.done();
- return PrintResult{
- .js = printer.js.toOwnedSliceLeaky(),
- };
- }
+ return @intCast(usize, std.math.max(printer.writer.written, 0));
}