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.
|
* Iterator over a changeset's operations.
|
||||||
*
|
*
|
||||||
* Note: This class does NOT implement the ECMAScript iterable or iterator protocols.
|
* 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.
|
* 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.
|
* @param {string} opsStr - String encoding of the change operations to perform.
|
||||||
* @returns {OpIter} Operator iterator object.
|
* @returns {OpIter} Operator iterator object.
|
||||||
*/
|
*/
|
||||||
exports.opIterator = (opsStr) => {
|
exports.opIterator = (opsStr) => new OpIter(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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans an Op object.
|
* Cleans an Op object.
|
||||||
|
@ -352,7 +364,7 @@ exports.checkRep = (cs) => {
|
||||||
let oldPos = 0;
|
let oldPos = 0;
|
||||||
let calcNewLen = 0;
|
let calcNewLen = 0;
|
||||||
let numInserted = 0;
|
let numInserted = 0;
|
||||||
const iter = exports.opIterator(ops);
|
const iter = new OpIter(ops);
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
const o = iter.next();
|
const o = iter.next();
|
||||||
switch (o.opcode) {
|
switch (o.opcode) {
|
||||||
|
@ -1005,8 +1017,8 @@ class TextLinesMutator {
|
||||||
* @returns {string} the integrated changeset
|
* @returns {string} the integrated changeset
|
||||||
*/
|
*/
|
||||||
const applyZip = (in1, in2, func) => {
|
const applyZip = (in1, in2, func) => {
|
||||||
const iter1 = exports.opIterator(in1);
|
const iter1 = new OpIter(in1);
|
||||||
const iter2 = exports.opIterator(in2);
|
const iter2 = new OpIter(in2);
|
||||||
const assem = exports.smartOpAssembler();
|
const assem = exports.smartOpAssembler();
|
||||||
const op1 = new Op();
|
const op1 = new Op();
|
||||||
const op2 = new Op();
|
const op2 = new Op();
|
||||||
|
@ -1075,7 +1087,7 @@ exports.pack = (oldLen, newLen, opsStr, bank) => {
|
||||||
exports.applyToText = (cs, str) => {
|
exports.applyToText = (cs, str) => {
|
||||||
const unpacked = exports.unpack(cs);
|
const unpacked = exports.unpack(cs);
|
||||||
assert(str.length === unpacked.oldLen, `mismatched apply: ${str.length} / ${unpacked.oldLen}`);
|
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 bankIter = exports.stringIterator(unpacked.charBank);
|
||||||
const strIter = exports.stringIterator(str);
|
const strIter = exports.stringIterator(str);
|
||||||
const assem = exports.stringAssembler();
|
const assem = exports.stringAssembler();
|
||||||
|
@ -1120,7 +1132,7 @@ exports.applyToText = (cs, str) => {
|
||||||
*/
|
*/
|
||||||
exports.mutateTextLines = (cs, lines) => {
|
exports.mutateTextLines = (cs, lines) => {
|
||||||
const unpacked = exports.unpack(cs);
|
const unpacked = exports.unpack(cs);
|
||||||
const csIter = exports.opIterator(unpacked.ops);
|
const csIter = new OpIter(unpacked.ops);
|
||||||
const bankIter = exports.stringIterator(unpacked.charBank);
|
const bankIter = exports.stringIterator(unpacked.charBank);
|
||||||
const mut = new TextLinesMutator(lines);
|
const mut = new TextLinesMutator(lines);
|
||||||
while (csIter.hasNext()) {
|
while (csIter.hasNext()) {
|
||||||
|
@ -1251,7 +1263,7 @@ exports.applyToAttribution = (cs, astr, pool) => {
|
||||||
|
|
||||||
exports.mutateAttributionLines = (cs, lines, pool) => {
|
exports.mutateAttributionLines = (cs, lines, pool) => {
|
||||||
const unpacked = exports.unpack(cs);
|
const unpacked = exports.unpack(cs);
|
||||||
const csIter = exports.opIterator(unpacked.ops);
|
const csIter = new OpIter(unpacked.ops);
|
||||||
const csBank = unpacked.charBank;
|
const csBank = unpacked.charBank;
|
||||||
let csBankIndex = 0;
|
let csBankIndex = 0;
|
||||||
// treat the attribution lines as text lines, mutating a line at a time
|
// treat the attribution lines as text lines, mutating a line at a time
|
||||||
|
@ -1265,7 +1277,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
|
||||||
const nextMutOp = () => {
|
const nextMutOp = () => {
|
||||||
if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) {
|
if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) {
|
||||||
const line = mut.removeLines(1);
|
const line = mut.removeLines(1);
|
||||||
lineIter = exports.opIterator(line);
|
lineIter = new OpIter(line);
|
||||||
}
|
}
|
||||||
if (!lineIter || !lineIter.hasNext()) return new Op();
|
if (!lineIter || !lineIter.hasNext()) return new Op();
|
||||||
return lineIter.next();
|
return lineIter.next();
|
||||||
|
@ -1328,7 +1340,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
|
||||||
exports.joinAttributionLines = (theAlines) => {
|
exports.joinAttributionLines = (theAlines) => {
|
||||||
const assem = exports.mergingOpAssembler();
|
const assem = exports.mergingOpAssembler();
|
||||||
for (const aline of theAlines) {
|
for (const aline of theAlines) {
|
||||||
const iter = exports.opIterator(aline);
|
const iter = new OpIter(aline);
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
assem.append(iter.next());
|
assem.append(iter.next());
|
||||||
}
|
}
|
||||||
|
@ -1337,7 +1349,7 @@ exports.joinAttributionLines = (theAlines) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.splitAttributionLines = (attrOps, text) => {
|
exports.splitAttributionLines = (attrOps, text) => {
|
||||||
const iter = exports.opIterator(attrOps);
|
const iter = new OpIter(attrOps);
|
||||||
const assem = exports.mergingOpAssembler();
|
const assem = exports.mergingOpAssembler();
|
||||||
const lines = [];
|
const lines = [];
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
|
@ -1495,7 +1507,7 @@ const toSplices = (cs) => {
|
||||||
const splices = [];
|
const splices = [];
|
||||||
|
|
||||||
let oldPos = 0;
|
let oldPos = 0;
|
||||||
const iter = exports.opIterator(unpacked.ops);
|
const iter = new OpIter(unpacked.ops);
|
||||||
const charIter = exports.stringIterator(unpacked.charBank);
|
const charIter = exports.stringIterator(unpacked.charBank);
|
||||||
let inSplice = false;
|
let inSplice = false;
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
|
@ -1742,7 +1754,7 @@ exports.copyAText = (atext1, atext2) => {
|
||||||
*/
|
*/
|
||||||
exports.opsFromAText = function* (atext) {
|
exports.opsFromAText = function* (atext) {
|
||||||
// intentionally skips last newline char of atext
|
// intentionally skips last newline char of atext
|
||||||
const iter = exports.opIterator(atext.attribs);
|
const iter = new OpIter(atext.attribs);
|
||||||
let lastOp = null;
|
let lastOp = null;
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
if (lastOp != null) yield lastOp;
|
if (lastOp != null) yield lastOp;
|
||||||
|
@ -1964,7 +1976,7 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
|
||||||
* Like "substring" but on a single-line attribution string.
|
* Like "substring" but on a single-line attribution string.
|
||||||
*/
|
*/
|
||||||
exports.subattribution = (astr, start, optEnd) => {
|
exports.subattribution = (astr, start, optEnd) => {
|
||||||
const iter = exports.opIterator(astr);
|
const iter = new OpIter(astr);
|
||||||
const assem = exports.smartOpAssembler();
|
const assem = exports.smartOpAssembler();
|
||||||
let attOp = new Op();
|
let attOp = new Op();
|
||||||
const csOp = new Op();
|
const csOp = new Op();
|
||||||
|
@ -2033,13 +2045,13 @@ exports.inverse = (cs, lines, alines, pool) => {
|
||||||
let curLineNextOp = new Op('+');
|
let curLineNextOp = new Op('+');
|
||||||
|
|
||||||
const unpacked = exports.unpack(cs);
|
const unpacked = exports.unpack(cs);
|
||||||
const csIter = exports.opIterator(unpacked.ops);
|
const csIter = new OpIter(unpacked.ops);
|
||||||
const builder = exports.builder(unpacked.newLen);
|
const builder = exports.builder(unpacked.newLen);
|
||||||
|
|
||||||
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
|
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
|
||||||
if ((!curLineOpIter) || (curLineOpIterLine !== curLine)) {
|
if ((!curLineOpIter) || (curLineOpIterLine !== curLine)) {
|
||||||
// create curLineOpIter and advance it to curChar
|
// create curLineOpIter and advance it to curChar
|
||||||
curLineOpIter = exports.opIterator(alinesGet(curLine));
|
curLineOpIter = new OpIter(alinesGet(curLine));
|
||||||
curLineOpIterLine = curLine;
|
curLineOpIterLine = curLine;
|
||||||
let indexIntoLine = 0;
|
let indexIntoLine = 0;
|
||||||
while (curLineOpIter.hasNext()) {
|
while (curLineOpIter.hasNext()) {
|
||||||
|
@ -2058,7 +2070,7 @@ exports.inverse = (cs, lines, alines, pool) => {
|
||||||
curChar = 0;
|
curChar = 0;
|
||||||
curLineOpIterLine = curLine;
|
curLineOpIterLine = curLine;
|
||||||
curLineNextOp.chars = 0;
|
curLineNextOp.chars = 0;
|
||||||
curLineOpIter = exports.opIterator(alinesGet(curLine));
|
curLineOpIter = new OpIter(alinesGet(curLine));
|
||||||
}
|
}
|
||||||
if (!curLineNextOp.chars) {
|
if (!curLineNextOp.chars) {
|
||||||
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Op();
|
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Op();
|
||||||
|
|
Loading…
Reference in New Issue