Created
January 19, 2017 20:27
-
-
Save vjeux/d1d241a237e851e809c82f6dfd1221e6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
const MODE_BREAK = 1; | |
const MODE_FLAT = 2; | |
function fits(next, restCommands, width) { | |
let restIdx = restCommands.length; | |
const cmds = [ next ]; | |
while (width >= 0) { | |
if (cmds.length === 0) { | |
if (restIdx === 0) { | |
return true; | |
} else { | |
cmds.push(restCommands[restIdx - 1]); | |
restIdx--; | |
continue; | |
} | |
} | |
const x = cmds.pop(); | |
const ind = x[0]; | |
const mode = x[1]; | |
const doc = x[2]; | |
if (typeof doc === "string") { | |
width -= doc.length; | |
} else { | |
switch (doc.type) { | |
case "concat": | |
for (var i = doc.parts.length - 1; i >= 0; i--) { | |
cmds.push([ ind, mode, doc.parts[i] ]); | |
} | |
break; | |
case "indent": | |
cmds.push([ ind + doc.n, mode, doc.contents ]); | |
break; | |
case "group": | |
cmds.push([ ind, doc.break ? MODE_BREAK : mode, doc.contents ]); | |
break; | |
case "if-break": | |
if (mode === MODE_BREAK) { | |
if (doc.breakContents) { | |
cmds.push([ ind, mode, doc.breakContents ]); | |
} | |
} | |
if (mode === MODE_FLAT) { | |
if (doc.flatContents) { | |
cmds.push([ ind, mode, doc.flatContents ]); | |
} | |
} | |
break; | |
case "line": | |
switch (mode) { | |
// fallthrough | |
case MODE_FLAT: | |
if (!doc.hard) { | |
if (!doc.soft) { | |
width -= 1; | |
} | |
break; | |
} | |
case MODE_BREAK: | |
return true; | |
} | |
break; | |
} | |
} | |
} | |
return false; | |
} | |
function printDocToString(w, doc) { | |
let pos = 0; | |
// cmds is basically a stack. We've turned a recursive call into a | |
// while loop which is much faster. The while loop below adds new | |
// cmds to the array instead of recursively calling `print`. | |
let cmds = [ [ 0, MODE_BREAK, doc ] ]; | |
let out = []; | |
let shouldRemeasure = false; | |
while (cmds.length !== 0) { | |
const x = cmds.pop(); | |
const ind = x[0]; | |
const mode = x[1]; | |
const doc = x[2]; | |
if (typeof doc === "string") { | |
out.push(doc); | |
pos += doc.length; | |
} else { | |
switch (doc.type) { | |
case "concat": | |
for (var i = doc.parts.length - 1; i >= 0; i--) { | |
cmds.push([ ind, mode, doc.parts[i] ]); | |
} | |
break; | |
case "indent": | |
cmds.push([ ind + doc.n, mode, doc.contents ]); | |
break; | |
case "group": | |
switch (mode) { | |
// fallthrough | |
case MODE_FLAT: | |
if (!shouldRemeasure) { | |
cmds.push([ | |
ind, | |
doc.break ? MODE_BREAK : MODE_FLAT, | |
doc.contents | |
]); | |
break; | |
} | |
case MODE_BREAK: | |
shouldRemeasure = false; | |
const next = [ ind, MODE_FLAT, doc.contents ]; | |
let rem = w - pos; | |
if (!doc.break && fits(next, cmds, rem)) { | |
cmds.push(next); | |
} else { | |
// Expanded states are a rare case where a document | |
// can manually provide multiple representations of | |
// itself. It provides an array of documents | |
// going from the least expanded (most flattened) | |
// representation first to the most expanded. If a | |
// group has these, we need to manually go through | |
// these states and find the first one that fits. | |
if (doc.expandedStates) { | |
const mostExpanded = doc.expandedStates[doc.expandedStates.length - | |
1]; | |
if (doc.break) { | |
cmds.push([ ind, MODE_BREAK, mostExpanded ]); | |
break; | |
} else { | |
for (var i = 1; i < doc.expandedStates.length + 1; i++) { | |
if (i >= doc.expandedStates.length) { | |
cmds.push([ ind, MODE_BREAK, mostExpanded ]); | |
break; | |
} else { | |
const state = doc.expandedStates[i]; | |
const cmd = [ ind, MODE_FLAT, state ]; | |
if (fits(cmd, cmds, rem)) { | |
cmds.push(cmd); | |
break; | |
} | |
} | |
} | |
} | |
} else { | |
cmds.push([ ind, MODE_BREAK, doc.contents ]); | |
} | |
} | |
break; | |
} | |
break; | |
case "if-break": | |
if (mode === MODE_BREAK) { | |
if (doc.breakContents) { | |
cmds.push([ ind, mode, doc.breakContents ]); | |
} | |
} | |
if (mode === MODE_FLAT) { | |
if (doc.flatContents) { | |
cmds.push([ ind, mode, doc.flatContents ]); | |
} | |
} | |
break; | |
case "line": | |
switch (mode) { | |
// fallthrough | |
case MODE_FLAT: | |
if (!doc.hard) { | |
if (!doc.soft) { | |
out.push(" "); | |
pos += 1; | |
} | |
break; | |
} else { | |
// This line was forced into the output even if we | |
// were in flattened mode, so we need to tell the next | |
// group that no matter what, it needs to remeasure | |
// because the previous measurement didn't accurately | |
// capture the entire expression (this is necessary | |
// for nested groups) | |
shouldRemeasure = true; | |
} | |
case MODE_BREAK: | |
if (doc.literal) { | |
out.push("\n"); | |
pos = 0; | |
} else { | |
if (out.length > 0) { | |
// Trim whitespace at the end of line | |
out[out.length - 1] = out[out.length - 1].replace( | |
/[^\S\n]*$/, | |
"" | |
); | |
} | |
out.push("\n" + " ".repeat(ind)); | |
pos = ind; | |
} | |
break; | |
} | |
break; | |
default: | |
} | |
} | |
} | |
return out.join(""); | |
} | |
module.exports = { | |
printDocToString, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment