Changeset: Turn `opIterator()` into a real class
parent
86959f7ebc
commit
a4aec006dc
|
@ -184,11 +184,54 @@ exports.newLen = (cs) => exports.unpack(cs).newLen;
|
|||
* Iterator over a changeset's operations.
|
||||
*
|
||||
* Note: This class does NOT implement the ECMAScript iterable or iterator protocols.
|
||||
*
|
||||
* @typedef {object} OpIter
|
||||
* @property {Function} hasNext -
|
||||
* @property {Function} next -
|
||||
*/
|
||||
class OpIter {
|
||||
/**
|
||||
* @param {string} ops - String encoding the change operations to iterate over.
|
||||
*/
|
||||
constructor(ops) {
|
||||
this._ops = ops;
|
||||
this._regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
|
||||
this._nextMatch = this._nextRegexMatch();
|
||||
}
|
||||
|
||||
_nextRegexMatch() {
|
||||
const match = this._regex.exec(this._ops);
|
||||
if (!match) return null;
|
||||
if (match[5] === '$') return null; // Start of the insert operation character bank.
|
||||
if (match[5] != null) error(`invalid operation: ${this._ops.slice(this._regex.lastIndex - 1)}`);
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} Whether there are any remaining operations.
|
||||
*/
|
||||
hasNext() {
|
||||
return this._nextMatch && !!this._nextMatch[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next operation object and advances the iterator.
|
||||
*
|
||||
* Note: This does NOT implement the ECMAScript iterator protocol.
|
||||
*
|
||||
* @param {Op} [opOut] - Deprecated. Operation object to recycle for the return value.
|
||||
* @returns {Op} The next operation, or an operation with a falsy `opcode` property if there are
|
||||
* no more operations.
|
||||
*/
|
||||
next(opOut = new Op()) {
|
||||
if (this.hasNext()) {
|
||||
opOut.attribs = this._nextMatch[1];
|
||||
opOut.lines = exports.parseNum(this._nextMatch[2] || '0');
|
||||
opOut.opcode = this._nextMatch[3];
|
||||
opOut.chars = exports.parseNum(this._nextMatch[4]);
|
||||
this._nextMatch = this._nextRegexMatch();
|
||||
} else {
|
||||
clearOp(opOut);
|
||||
}
|
||||
return opOut;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an iterator which decodes string changeset operations.
|
||||
|
@ -196,38 +239,7 @@ exports.newLen = (cs) => exports.unpack(cs).newLen;
|
|||
* @param {string} opsStr - String encoding of the change operations to perform.
|
||||
* @returns {OpIter} Operator iterator object.
|
||||
*/
|
||||
exports.opIterator = (opsStr) => {
|
||||
const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
|
||||
|
||||
const nextRegexMatch = () => {
|
||||
const result = regex.exec(opsStr);
|
||||
if (!result) return null;
|
||||
if (result[5] === '$') return null; // Start of the insert operation character bank.
|
||||
if (result[5] != null) error(`invalid operation: ${opsStr.slice(regex.lastIndex - 1)}`);
|
||||
return result;
|
||||
};
|
||||
let regexResult = nextRegexMatch();
|
||||
|
||||
const hasNext = () => regexResult && !!regexResult[0];
|
||||
|
||||
const next = (op = new Op()) => {
|
||||
if (hasNext()) {
|
||||
op.attribs = regexResult[1];
|
||||
op.lines = exports.parseNum(regexResult[2] || '0');
|
||||
op.opcode = regexResult[3];
|
||||
op.chars = exports.parseNum(regexResult[4]);
|
||||
regexResult = nextRegexMatch();
|
||||
} else {
|
||||
clearOp(op);
|
||||
}
|
||||
return op;
|
||||
};
|
||||
|
||||
return {
|
||||
next,
|
||||
hasNext,
|
||||
};
|
||||
};
|
||||
exports.opIterator = (opsStr) => new OpIter(opsStr);
|
||||
|
||||
/**
|
||||
* Cleans an Op object.
|
||||
|
@ -352,7 +364,7 @@ exports.checkRep = (cs) => {
|
|||
let oldPos = 0;
|
||||
let calcNewLen = 0;
|
||||
let numInserted = 0;
|
||||
const iter = exports.opIterator(ops);
|
||||
const iter = new OpIter(ops);
|
||||
while (iter.hasNext()) {
|
||||
const o = iter.next();
|
||||
switch (o.opcode) {
|
||||
|
@ -1005,8 +1017,8 @@ class TextLinesMutator {
|
|||
* @returns {string} the integrated changeset
|
||||
*/
|
||||
const applyZip = (in1, in2, func) => {
|
||||
const iter1 = exports.opIterator(in1);
|
||||
const iter2 = exports.opIterator(in2);
|
||||
const iter1 = new OpIter(in1);
|
||||
const iter2 = new OpIter(in2);
|
||||
const assem = exports.smartOpAssembler();
|
||||
const op1 = new Op();
|
||||
const op2 = new Op();
|
||||
|
@ -1075,7 +1087,7 @@ 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 = exports.opIterator(unpacked.ops);
|
||||
const csIter = new OpIter(unpacked.ops);
|
||||
const bankIter = exports.stringIterator(unpacked.charBank);
|
||||
const strIter = exports.stringIterator(str);
|
||||
const assem = exports.stringAssembler();
|
||||
|
@ -1120,7 +1132,7 @@ exports.applyToText = (cs, str) => {
|
|||
*/
|
||||
exports.mutateTextLines = (cs, lines) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const csIter = exports.opIterator(unpacked.ops);
|
||||
const csIter = new OpIter(unpacked.ops);
|
||||
const bankIter = exports.stringIterator(unpacked.charBank);
|
||||
const mut = new TextLinesMutator(lines);
|
||||
while (csIter.hasNext()) {
|
||||
|
@ -1251,7 +1263,7 @@ exports.applyToAttribution = (cs, astr, pool) => {
|
|||
|
||||
exports.mutateAttributionLines = (cs, lines, pool) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const csIter = exports.opIterator(unpacked.ops);
|
||||
const csIter = new OpIter(unpacked.ops);
|
||||
const csBank = unpacked.charBank;
|
||||
let csBankIndex = 0;
|
||||
// treat the attribution lines as text lines, mutating a line at a time
|
||||
|
@ -1265,7 +1277,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
|
|||
const nextMutOp = () => {
|
||||
if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) {
|
||||
const line = mut.removeLines(1);
|
||||
lineIter = exports.opIterator(line);
|
||||
lineIter = new OpIter(line);
|
||||
}
|
||||
if (!lineIter || !lineIter.hasNext()) return new Op();
|
||||
return lineIter.next();
|
||||
|
@ -1328,7 +1340,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
|
|||
exports.joinAttributionLines = (theAlines) => {
|
||||
const assem = exports.mergingOpAssembler();
|
||||
for (const aline of theAlines) {
|
||||
const iter = exports.opIterator(aline);
|
||||
const iter = new OpIter(aline);
|
||||
while (iter.hasNext()) {
|
||||
assem.append(iter.next());
|
||||
}
|
||||
|
@ -1337,7 +1349,7 @@ exports.joinAttributionLines = (theAlines) => {
|
|||
};
|
||||
|
||||
exports.splitAttributionLines = (attrOps, text) => {
|
||||
const iter = exports.opIterator(attrOps);
|
||||
const iter = new OpIter(attrOps);
|
||||
const assem = exports.mergingOpAssembler();
|
||||
const lines = [];
|
||||
let pos = 0;
|
||||
|
@ -1495,7 +1507,7 @@ const toSplices = (cs) => {
|
|||
const splices = [];
|
||||
|
||||
let oldPos = 0;
|
||||
const iter = exports.opIterator(unpacked.ops);
|
||||
const iter = new OpIter(unpacked.ops);
|
||||
const charIter = exports.stringIterator(unpacked.charBank);
|
||||
let inSplice = false;
|
||||
while (iter.hasNext()) {
|
||||
|
@ -1742,7 +1754,7 @@ exports.copyAText = (atext1, atext2) => {
|
|||
*/
|
||||
exports.opsFromAText = function* (atext) {
|
||||
// intentionally skips last newline char of atext
|
||||
const iter = exports.opIterator(atext.attribs);
|
||||
const iter = new OpIter(atext.attribs);
|
||||
let lastOp = null;
|
||||
while (iter.hasNext()) {
|
||||
if (lastOp != null) yield lastOp;
|
||||
|
@ -1964,7 +1976,7 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
|
|||
* Like "substring" but on a single-line attribution string.
|
||||
*/
|
||||
exports.subattribution = (astr, start, optEnd) => {
|
||||
const iter = exports.opIterator(astr);
|
||||
const iter = new OpIter(astr);
|
||||
const assem = exports.smartOpAssembler();
|
||||
let attOp = new Op();
|
||||
const csOp = new Op();
|
||||
|
@ -2033,13 +2045,13 @@ exports.inverse = (cs, lines, alines, pool) => {
|
|||
let curLineNextOp = new Op('+');
|
||||
|
||||
const unpacked = exports.unpack(cs);
|
||||
const csIter = exports.opIterator(unpacked.ops);
|
||||
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 = exports.opIterator(alinesGet(curLine));
|
||||
curLineOpIter = new OpIter(alinesGet(curLine));
|
||||
curLineOpIterLine = curLine;
|
||||
let indexIntoLine = 0;
|
||||
while (curLineOpIter.hasNext()) {
|
||||
|
@ -2058,7 +2070,7 @@ exports.inverse = (cs, lines, alines, pool) => {
|
|||
curChar = 0;
|
||||
curLineOpIterLine = curLine;
|
||||
curLineNextOp.chars = 0;
|
||||
curLineOpIter = exports.opIterator(alinesGet(curLine));
|
||||
curLineOpIter = new OpIter(alinesGet(curLine));
|
||||
}
|
||||
if (!curLineNextOp.chars) {
|
||||
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Op();
|
||||
|
|
Loading…
Reference in New Issue