Changeset: Migrate from OpIter to deserializeOps()

This commit is contained in:
Richard Hansen 2021-10-25 05:48:58 -04:00
parent 0eca0251f2
commit 89fe40e080
16 changed files with 147 additions and 179 deletions

View file

@ -187,7 +187,7 @@ exports.newLen = (cs) => exports.unpack(cs).newLen;
* @yields {Op}
* @returns {Generator<Op>}
*/
const deserializeOps = function* (ops) {
exports.deserializeOps = function* (ops) {
// TODO: Migrate to String.prototype.matchAll() once there is enough browser support.
const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
let match;
@ -206,13 +206,15 @@ const deserializeOps = function* (ops) {
* Iterator over a changeset's operations.
*
* Note: This class does NOT implement the ECMAScript iterable or iterator protocols.
*
* @deprecated Use `deserializeOps` instead.
*/
class OpIter {
/**
* @param {string} ops - String encoding the change operations to iterate over.
*/
constructor(ops) {
this._gen = deserializeOps(ops);
this._gen = exports.deserializeOps(ops);
this._next = this._gen.next();
}
@ -246,10 +248,15 @@ class OpIter {
/**
* Creates an iterator which decodes string changeset operations.
*
* @deprecated Use `deserializeOps` instead.
* @param {string} opsStr - String encoding of the change operations to perform.
* @returns {OpIter} Operator iterator object.
*/
exports.opIterator = (opsStr) => new OpIter(opsStr);
exports.opIterator = (opsStr) => {
padutils.warnWithStack(
'Changeset.opIterator() is deprecated; use Changeset.deserializeOps() instead');
return new OpIter(opsStr);
};
/**
* Cleans an Op object.
@ -374,9 +381,7 @@ exports.checkRep = (cs) => {
let oldPos = 0;
let calcNewLen = 0;
let numInserted = 0;
const iter = new OpIter(ops);
while (iter.hasNext()) {
const o = iter.next();
for (const o of exports.deserializeOps(ops)) {
switch (o.opcode) {
case '=':
oldPos += o.chars;
@ -1027,15 +1032,18 @@ class TextLinesMutator {
* @returns {string} the integrated changeset
*/
const applyZip = (in1, in2, func) => {
const iter1 = new OpIter(in1);
const iter2 = new OpIter(in2);
const ops1 = exports.deserializeOps(in1);
const ops2 = exports.deserializeOps(in2);
let next1 = ops1.next();
let next2 = ops2.next();
const assem = exports.smartOpAssembler();
const op1 = new Op();
const op2 = new Op();
while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1);
if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2);
const opOut = func(op1, op2);
while (!next1.done || !next2.done) {
if (!next1.done && !next1.value.opcode) next1 = ops1.next();
if (!next2.done && !next2.value.opcode) next2 = ops2.next();
if (next1.value == null) next1.value = new Op();
if (next2.value == null) next2.value = new Op();
if (!next1.value.opcode && !next2.value.opcode) break;
const opOut = func(next1.value, next2.value);
if (opOut && opOut.opcode) assem.append(opOut);
}
assem.endDocument();
@ -1097,12 +1105,10 @@ exports.pack = (oldLen, newLen, opsStr, bank) => {
exports.applyToText = (cs, str) => {
const unpacked = exports.unpack(cs);
assert(str.length === unpacked.oldLen, `mismatched apply: ${str.length} / ${unpacked.oldLen}`);
const csIter = new OpIter(unpacked.ops);
const bankIter = exports.stringIterator(unpacked.charBank);
const strIter = exports.stringIterator(str);
const assem = exports.stringAssembler();
while (csIter.hasNext()) {
const op = csIter.next();
for (const op of exports.deserializeOps(unpacked.ops)) {
switch (op.opcode) {
case '+':
// op is + and op.lines 0: no newlines must be in op.chars
@ -1142,11 +1148,9 @@ exports.applyToText = (cs, str) => {
*/
exports.mutateTextLines = (cs, lines) => {
const unpacked = exports.unpack(cs);
const csIter = new OpIter(unpacked.ops);
const bankIter = exports.stringIterator(unpacked.charBank);
const mut = new TextLinesMutator(lines);
while (csIter.hasNext()) {
const op = csIter.next();
for (const op of exports.deserializeOps(unpacked.ops)) {
switch (op.opcode) {
case '+':
mut.insert(bankIter.take(op.chars), op.lines);
@ -1273,24 +1277,30 @@ exports.applyToAttribution = (cs, astr, pool) => {
exports.mutateAttributionLines = (cs, lines, pool) => {
const unpacked = exports.unpack(cs);
const csIter = new OpIter(unpacked.ops);
const csOps = exports.deserializeOps(unpacked.ops);
let csOpsNext = csOps.next();
const csBank = unpacked.charBank;
let csBankIndex = 0;
// treat the attribution lines as text lines, mutating a line at a time
const mut = new TextLinesMutator(lines);
/** @type {?OpIter} */
let lineIter = null;
/** @type {?Generator<Op>} */
let lineOps = null;
let lineOpsNext = null;
const isNextMutOp = () => (lineIter && lineIter.hasNext()) || mut.hasMore();
const lineOpsHasNext = () => lineOpsNext && !lineOpsNext.done;
const isNextMutOp = () => lineOpsHasNext() || mut.hasMore();
const nextMutOp = () => {
if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) {
if (!lineOpsHasNext() && mut.hasMore()) {
const line = mut.removeLines(1);
lineIter = new OpIter(line);
lineOps = exports.deserializeOps(line);
lineOpsNext = lineOps.next();
}
if (!lineIter || !lineIter.hasNext()) return new Op();
return lineIter.next();
if (!lineOpsHasNext()) return new Op();
const op = lineOpsNext.value;
lineOpsNext = lineOps.next();
return op;
};
let lineAssem = null;
@ -1308,12 +1318,15 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
let csOp = new Op();
let attOp = new Op();
while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) {
if (!csOp.opcode && csIter.hasNext()) csOp = csIter.next();
if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
while (csOp.opcode || !csOpsNext.done || attOp.opcode || isNextMutOp()) {
if (!csOp.opcode && !csOpsNext.done) {
csOp = csOpsNext.value;
csOpsNext = csOps.next();
}
if (!csOp.opcode && !attOp.opcode && !lineAssem && !lineOpsHasNext()) {
break; // done
} else if (csOp.opcode === '=' && csOp.lines > 0 && (!csOp.attribs) &&
(!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
} else if (csOp.opcode === '=' && csOp.lines > 0 && !csOp.attribs && !attOp.opcode &&
!lineAssem && !lineOpsHasNext()) {
// skip multiple lines; this is what makes small changes not order of the document size
mut.skipLines(csOp.lines);
csOp.opcode = '';
@ -1350,16 +1363,12 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
exports.joinAttributionLines = (theAlines) => {
const assem = exports.mergingOpAssembler();
for (const aline of theAlines) {
const iter = new OpIter(aline);
while (iter.hasNext()) {
assem.append(iter.next());
}
for (const op of exports.deserializeOps(aline)) assem.append(op);
}
return assem.toString();
};
exports.splitAttributionLines = (attrOps, text) => {
const iter = new OpIter(attrOps);
const assem = exports.mergingOpAssembler();
const lines = [];
let pos = 0;
@ -1373,8 +1382,7 @@ exports.splitAttributionLines = (attrOps, text) => {
pos += op.chars;
};
while (iter.hasNext()) {
const op = iter.next();
for (const op of exports.deserializeOps(attrOps)) {
let numChars = op.chars;
let numLines = op.lines;
while (numLines > 1) {
@ -1517,11 +1525,9 @@ const toSplices = (cs) => {
const splices = [];
let oldPos = 0;
const iter = new OpIter(unpacked.ops);
const charIter = exports.stringIterator(unpacked.charBank);
let inSplice = false;
while (iter.hasNext()) {
const op = iter.next();
for (const op of exports.deserializeOps(unpacked.ops)) {
if (op.opcode === '=') {
oldPos += op.chars;
inSplice = false;
@ -1764,11 +1770,10 @@ exports.copyAText = (atext1, atext2) => {
*/
exports.opsFromAText = function* (atext) {
// intentionally skips last newline char of atext
const iter = new OpIter(atext.attribs);
let lastOp = null;
while (iter.hasNext()) {
for (const op of exports.deserializeOps(atext.attribs)) {
if (lastOp != null) yield lastOp;
lastOp = iter.next();
lastOp = op;
}
if (lastOp == null) return;
// exclude final newline
@ -1986,15 +1991,19 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
* Like "substring" but on a single-line attribution string.
*/
exports.subattribution = (astr, start, optEnd) => {
const iter = new OpIter(astr);
const attOps = exports.deserializeOps(astr);
let attOpsNext = attOps.next();
const assem = exports.smartOpAssembler();
let attOp = new Op();
const csOp = new Op();
const doCsOp = () => {
if (!csOp.chars) return;
while (csOp.opcode && (attOp.opcode || iter.hasNext())) {
if (!attOp.opcode) attOp = iter.next();
while (csOp.opcode && (attOp.opcode || !attOpsNext.done)) {
if (!attOp.opcode) {
attOp = attOpsNext.value;
attOpsNext = attOps.next();
}
if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars &&
attOp.lines > 0 && csOp.lines <= 0) {
csOp.lines++;
@ -2013,7 +2022,10 @@ exports.subattribution = (astr, start, optEnd) => {
if (attOp.opcode) {
assem.append(attOp);
}
while (iter.hasNext()) assem.append(iter.next());
while (!attOpsNext.done) {
assem.append(attOpsNext.value);
attOpsNext = attOps.next();
}
} else {
csOp.opcode = '=';
csOp.chars = optEnd - start;
@ -2050,22 +2062,23 @@ exports.inverse = (cs, lines, alines, pool) => {
let curLine = 0;
let curChar = 0;
let curLineOpIter = null;
let curLineOpIterLine;
let curLineOps = null;
let curLineOpsNext = null;
let curLineOpsLine;
let curLineNextOp = new Op('+');
const unpacked = exports.unpack(cs);
const csIter = new OpIter(unpacked.ops);
const builder = exports.builder(unpacked.newLen);
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
if ((!curLineOpIter) || (curLineOpIterLine !== curLine)) {
// create curLineOpIter and advance it to curChar
curLineOpIter = new OpIter(alinesGet(curLine));
curLineOpIterLine = curLine;
if (!curLineOps || curLineOpsLine !== curLine) {
curLineOps = exports.deserializeOps(alinesGet(curLine));
curLineOpsNext = curLineOps.next();
curLineOpsLine = curLine;
let indexIntoLine = 0;
while (curLineOpIter.hasNext()) {
curLineNextOp = curLineOpIter.next();
while (!curLineOpsNext.done) {
curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next();
if (indexIntoLine + curLineNextOp.chars >= curChar) {
curLineNextOp.chars -= (curChar - indexIntoLine);
break;
@ -2075,15 +2088,21 @@ exports.inverse = (cs, lines, alines, pool) => {
}
while (numChars > 0) {
if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) {
if (!curLineNextOp.chars && curLineOpsNext.done) {
curLine++;
curChar = 0;
curLineOpIterLine = curLine;
curLineOpsLine = curLine;
curLineNextOp.chars = 0;
curLineOpIter = new OpIter(alinesGet(curLine));
curLineOps = exports.deserializeOps(alinesGet(curLine));
curLineOpsNext = curLineOps.next();
}
if (!curLineNextOp.chars) {
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Op();
if (curLineOpsNext.done) {
curLineNextOp = new Op();
} else {
curLineNextOp = curLineOpsNext.value;
curLineOpsNext = curLineOps.next();
}
}
const charsToUse = Math.min(numChars, curLineNextOp.chars);
func(charsToUse, curLineNextOp.attribs, charsToUse === curLineNextOp.chars &&
@ -2093,7 +2112,7 @@ exports.inverse = (cs, lines, alines, pool) => {
curChar += charsToUse;
}
if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) {
if (!curLineNextOp.chars && curLineOpsNext.done) {
curLine++;
curChar = 0;
}
@ -2103,7 +2122,7 @@ exports.inverse = (cs, lines, alines, pool) => {
if (L) {
curLine += L;
curChar = 0;
} else if (curLineOpIter && curLineOpIterLine === curLine) {
} else if (curLineOps && curLineOpsLine === curLine) {
consumeAttribRuns(N, () => {});
} else {
curChar += N;
@ -2138,8 +2157,7 @@ exports.inverse = (cs, lines, alines, pool) => {
};
};
while (csIter.hasNext()) {
const csOp = csIter.next();
for (const csOp of exports.deserializeOps(unpacked.ops)) {
if (csOp.opcode === '=') {
if (csOp.attribs) {
const attribs = AttributeMap.fromString(csOp.attribs, pool);