Changeset: Turn `mergingOpAssembler()` into a real class

rhansen-changeset
Richard Hansen 2021-10-05 12:58:02 -04:00
parent 9ea424c8f9
commit cf82261d2b
1 changed files with 68 additions and 81 deletions

View File

@ -319,13 +319,68 @@ class OpAssembler {
/**
* Efficiently merges consecutive operations that are mergeable, ignores no-ops, and drops final
* pure "keeps". It does not re-order operations.
*
* @typedef {object} MergingOpAssembler
* @property {Function} append -
* @property {Function} clear -
* @property {Function} endDocument -
* @property {Function} toString -
*/
class MergingOpAssembler {
constructor() {
this._assem = new OpAssembler();
this._bufOp = new Op();
// If we get, for example, insertions [xxx\n,yyy], those don't merge, but if we get
// [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n]. This variable stores the length of yyy and
// any other newline-less ops immediately after it.
this._bufOpAdditionalCharsAfterNewline = 0;
}
clear() {
this._assem.clear();
clearOp(this._bufOp);
}
_flush(isEndDocument) {
if (!this._bufOp.opcode) return;
if (isEndDocument && this._bufOp.opcode === '=' && !this._bufOp.attribs) {
// final merged keep, leave it implicit
} else {
this._assem.append(this._bufOp);
if (this._bufOpAdditionalCharsAfterNewline) {
this._bufOp.chars = this._bufOpAdditionalCharsAfterNewline;
this._bufOp.lines = 0;
this._assem.append(this._bufOp);
this._bufOpAdditionalCharsAfterNewline = 0;
}
}
this._bufOp.opcode = '';
}
append(op) {
if (op.chars <= 0) return;
if (this._bufOp.opcode === op.opcode && this._bufOp.attribs === op.attribs) {
if (op.lines > 0) {
// this._bufOp and additional chars are all mergeable into a multi-line op
this._bufOp.chars += this._bufOpAdditionalCharsAfterNewline + op.chars;
this._bufOp.lines += op.lines;
this._bufOpAdditionalCharsAfterNewline = 0;
} else if (this._bufOp.lines === 0) {
// both this._bufOp and op are in-line
this._bufOp.chars += op.chars;
} else {
// append in-line text to multi-line this._bufOp
this._bufOpAdditionalCharsAfterNewline += op.chars;
}
} else {
this._flush();
copyOp(op, this._bufOp);
}
}
endDocument() {
this._flush(true);
}
toString() {
this._flush();
return this._assem.toString();
}
}
/**
* Generates operations from the given text and attributes.
@ -438,9 +493,9 @@ exports.checkRep = (cs) => {
* @returns {SmartOpAssembler}
*/
exports.smartOpAssembler = () => {
const minusAssem = exports.mergingOpAssembler();
const plusAssem = exports.mergingOpAssembler();
const keepAssem = exports.mergingOpAssembler();
const minusAssem = new MergingOpAssembler();
const plusAssem = new MergingOpAssembler();
const keepAssem = new MergingOpAssembler();
const assem = exports.stringAssembler();
let lastOpcode = '';
let lengthChange = 0;
@ -531,73 +586,7 @@ exports.smartOpAssembler = () => {
/**
* @returns {MergingOpAssembler}
*/
exports.mergingOpAssembler = () => {
const assem = exports.opAssembler();
const bufOp = new Op();
// If we get, for example, insertions [xxx\n,yyy], those don't merge,
// but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
// This variable stores the length of yyy and any other newline-less
// ops immediately after it.
let bufOpAdditionalCharsAfterNewline = 0;
const flush = (isEndDocument) => {
if (!bufOp.opcode) return;
if (isEndDocument && bufOp.opcode === '=' && !bufOp.attribs) {
// final merged keep, leave it implicit
} else {
assem.append(bufOp);
if (bufOpAdditionalCharsAfterNewline) {
bufOp.chars = bufOpAdditionalCharsAfterNewline;
bufOp.lines = 0;
assem.append(bufOp);
bufOpAdditionalCharsAfterNewline = 0;
}
}
bufOp.opcode = '';
};
const append = (op) => {
if (op.chars <= 0) return;
if (bufOp.opcode === op.opcode && bufOp.attribs === op.attribs) {
if (op.lines > 0) {
// bufOp and additional chars are all mergeable into a multi-line op
bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
bufOp.lines += op.lines;
bufOpAdditionalCharsAfterNewline = 0;
} else if (bufOp.lines === 0) {
// both bufOp and op are in-line
bufOp.chars += op.chars;
} else {
// append in-line text to multi-line bufOp
bufOpAdditionalCharsAfterNewline += op.chars;
}
} else {
flush();
copyOp(op, bufOp);
}
};
const endDocument = () => {
flush(true);
};
const toString = () => {
flush();
return assem.toString();
};
const clear = () => {
assem.clear();
clearOp(bufOp);
};
return {
append,
toString,
clear,
endDocument,
};
};
exports.mergingOpAssembler = () => new MergingOpAssembler();
/**
* @returns {OpAssembler}
@ -1301,9 +1290,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
let lineAssem = null;
const outputMutOp = (op) => {
if (!lineAssem) {
lineAssem = exports.mergingOpAssembler();
}
if (!lineAssem) lineAssem = new MergingOpAssembler();
lineAssem.append(op);
if (op.lines <= 0) return;
assert(op.lines === 1, `Can't have op.lines of ${op.lines} in attribution lines`);
@ -1357,7 +1344,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
* @returns {string} joined Attribution lines
*/
exports.joinAttributionLines = (theAlines) => {
const assem = exports.mergingOpAssembler();
const assem = new MergingOpAssembler();
for (const aline of theAlines) {
for (const op of exports.deserializeOps(aline)) assem.append(op);
}
@ -1365,7 +1352,7 @@ exports.joinAttributionLines = (theAlines) => {
};
exports.splitAttributionLines = (attrOps, text) => {
const assem = exports.mergingOpAssembler();
const assem = new MergingOpAssembler();
const lines = [];
let pos = 0;