diff --git a/lib/output.js b/lib/output.js index 1ee6b8e52..0dba8d016 100644 --- a/lib/output.js +++ b/lib/output.js @@ -176,6 +176,48 @@ function is_some_comments(comment) { ); } +class Rope { + constructor() { + this.committed = ""; + this.current = ""; + } + + append(str) { + this.current += str; + } + + insertAt(char, index) { + const { committed, current } = this; + if (index < committed.length) { + this.committed = committed.slice(0, index) + char + committed.slice(index); + } else if (index === committed.length) { + this.committed += char; + } else { + index -= committed.length; + this.committed += current.slice(0, index) + char; + this.current = current.slice(index); + } + } + + charAt(index) { + const { committed } = this; + if (index < committed.length) return committed[index]; + return this.current[index - committed.length]; + } + + curLength() { + return this.current.length; + } + + length() { + return this.committed.length + this.current.length; + } + + toString() { + return this.committed + this.current; + } +} + function OutputStream(options) { var readonly = !options; @@ -240,7 +282,7 @@ function OutputStream(options) { var current_col = 0; var current_line = 1; var current_pos = 0; - var OUTPUT = ""; + var OUTPUT = new Rope(); let printed_comments = new Set(); var to_utf8 = options.ascii_only ? function(str, identifier) { @@ -371,19 +413,18 @@ function OutputStream(options) { var ensure_line_len = options.max_line_len ? function() { if (current_col > options.max_line_len) { if (might_add_newline) { - var left = OUTPUT.slice(0, might_add_newline); - var right = OUTPUT.slice(might_add_newline); + OUTPUT.insertAt("\n", might_add_newline); + const curLength = OUTPUT.curLength(); if (mappings) { - var delta = right.length - current_col; + var delta = curLength - current_col; mappings.forEach(function(mapping) { mapping.line++; mapping.col += delta; }); } - OUTPUT = left + "\n" + right; current_line++; current_pos++; - current_col = right.length; + current_col = curLength; } } if (might_add_newline) { @@ -417,13 +458,13 @@ function OutputStream(options) { if (prev === ":" && ch === "}" || (!ch || !";}".includes(ch)) && prev !== ";") { if (options.semicolons || requireSemicolonChars.has(ch)) { - OUTPUT += ";"; + OUTPUT.append(";"); current_col++; current_pos++; } else { ensure_line_len(); if (current_col > 0) { - OUTPUT += "\n"; + OUTPUT.append("\n"); current_pos++; current_line++; current_col = 0; @@ -447,7 +488,7 @@ function OutputStream(options) { || (ch == "/" && ch == prev) || ((ch == "+" || ch == "-") && ch == last) ) { - OUTPUT += " "; + OUTPUT.append(" "); current_col++; current_pos++; } @@ -465,7 +506,7 @@ function OutputStream(options) { if (!might_add_newline) do_add_mapping(); } - OUTPUT += str; + OUTPUT.append(str); has_parens = str[str.length - 1] == "("; current_pos += str.length; var a = str.split(/\r?\n/), n = a.length - 1; @@ -505,15 +546,15 @@ function OutputStream(options) { var newline = options.beautify ? function() { if (newline_insert < 0) return print("\n"); - if (OUTPUT[newline_insert] != "\n") { - OUTPUT = OUTPUT.slice(0, newline_insert) + "\n" + OUTPUT.slice(newline_insert); + if (OUTPUT.charAt(newline_insert) != "\n") { + OUTPUT.insertAt("\n", newline_insert); current_pos++; current_line++; } newline_insert++; } : options.max_line_len ? function() { ensure_line_len(); - might_add_newline = OUTPUT.length; + might_add_newline = OUTPUT.length(); } : noop; var semicolon = options.beautify ? function() { @@ -579,13 +620,14 @@ function OutputStream(options) { if (might_add_newline) { ensure_line_len(); } - return OUTPUT; + return OUTPUT.toString(); } function has_nlb() { - let n = OUTPUT.length - 1; + const output = OUTPUT.toString(); + let n = output.length - 1; while (n >= 0) { - const code = OUTPUT.charCodeAt(n); + const code = output.charCodeAt(n); if (code === CODE_LINE_BREAK) { return true; } @@ -722,7 +764,7 @@ function OutputStream(options) { !/comment[134]/.test(c.type) ))) return; printed_comments.add(comments); - var insert = OUTPUT.length; + var insert = OUTPUT.length(); comments.filter(comment_filter, node).forEach(function(c, i) { if (printed_comments.has(c)) return; printed_comments.add(c); @@ -751,7 +793,7 @@ function OutputStream(options) { need_space = true; } }); - if (OUTPUT.length > insert) newline_insert = insert; + if (OUTPUT.length() > insert) newline_insert = insert; } var stack = []; @@ -781,7 +823,7 @@ function OutputStream(options) { var encoded = encode_string(str, quote); if (escape_directive === true && !encoded.includes("\\")) { // Insert semicolons to break directive prologue - if (!EXPECT_DIRECTIVE.test(OUTPUT)) { + if (!EXPECT_DIRECTIVE.test(OUTPUT.toString())) { force_semicolon(); } force_semicolon();