Skip to content

Commit a4aec00

Browse files
committed
Changeset: Turn opIterator() into a real class
1 parent 86959f7 commit a4aec00

File tree

1 file changed

+63
-51
lines changed

1 file changed

+63
-51
lines changed

src/static/js/Changeset.js

Lines changed: 63 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -184,50 +184,62 @@ exports.newLen = (cs) => exports.unpack(cs).newLen;
184184
* Iterator over a changeset's operations.
185185
*
186186
* Note: This class does NOT implement the ECMAScript iterable or iterator protocols.
187-
*
188-
* @typedef {object} OpIter
189-
* @property {Function} hasNext -
190-
* @property {Function} next -
191187
*/
188+
class OpIter {
189+
/**
190+
* @param {string} ops - String encoding the change operations to iterate over.
191+
*/
192+
constructor(ops) {
193+
this._ops = ops;
194+
this._regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
195+
this._nextMatch = this._nextRegexMatch();
196+
}
197+
198+
_nextRegexMatch() {
199+
const match = this._regex.exec(this._ops);
200+
if (!match) return null;
201+
if (match[5] === '$') return null; // Start of the insert operation character bank.
202+
if (match[5] != null) error(`invalid operation: ${this._ops.slice(this._regex.lastIndex - 1)}`);
203+
return match;
204+
}
205+
206+
/**
207+
* @returns {boolean} Whether there are any remaining operations.
208+
*/
209+
hasNext() {
210+
return this._nextMatch && !!this._nextMatch[0];
211+
}
212+
213+
/**
214+
* Returns the next operation object and advances the iterator.
215+
*
216+
* Note: This does NOT implement the ECMAScript iterator protocol.
217+
*
218+
* @param {Op} [opOut] - Deprecated. Operation object to recycle for the return value.
219+
* @returns {Op} The next operation, or an operation with a falsy `opcode` property if there are
220+
* no more operations.
221+
*/
222+
next(opOut = new Op()) {
223+
if (this.hasNext()) {
224+
opOut.attribs = this._nextMatch[1];
225+
opOut.lines = exports.parseNum(this._nextMatch[2] || '0');
226+
opOut.opcode = this._nextMatch[3];
227+
opOut.chars = exports.parseNum(this._nextMatch[4]);
228+
this._nextMatch = this._nextRegexMatch();
229+
} else {
230+
clearOp(opOut);
231+
}
232+
return opOut;
233+
}
234+
}
192235

193236
/**
194237
* Creates an iterator which decodes string changeset operations.
195238
*
196239
* @param {string} opsStr - String encoding of the change operations to perform.
197240
* @returns {OpIter} Operator iterator object.
198241
*/
199-
exports.opIterator = (opsStr) => {
200-
const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
201-
202-
const nextRegexMatch = () => {
203-
const result = regex.exec(opsStr);
204-
if (!result) return null;
205-
if (result[5] === '$') return null; // Start of the insert operation character bank.
206-
if (result[5] != null) error(`invalid operation: ${opsStr.slice(regex.lastIndex - 1)}`);
207-
return result;
208-
};
209-
let regexResult = nextRegexMatch();
210-
211-
const hasNext = () => regexResult && !!regexResult[0];
212-
213-
const next = (op = new Op()) => {
214-
if (hasNext()) {
215-
op.attribs = regexResult[1];
216-
op.lines = exports.parseNum(regexResult[2] || '0');
217-
op.opcode = regexResult[3];
218-
op.chars = exports.parseNum(regexResult[4]);
219-
regexResult = nextRegexMatch();
220-
} else {
221-
clearOp(op);
222-
}
223-
return op;
224-
};
225-
226-
return {
227-
next,
228-
hasNext,
229-
};
230-
};
242+
exports.opIterator = (opsStr) => new OpIter(opsStr);
231243

232244
/**
233245
* Cleans an Op object.
@@ -352,7 +364,7 @@ exports.checkRep = (cs) => {
352364
let oldPos = 0;
353365
let calcNewLen = 0;
354366
let numInserted = 0;
355-
const iter = exports.opIterator(ops);
367+
const iter = new OpIter(ops);
356368
while (iter.hasNext()) {
357369
const o = iter.next();
358370
switch (o.opcode) {
@@ -1005,8 +1017,8 @@ class TextLinesMutator {
10051017
* @returns {string} the integrated changeset
10061018
*/
10071019
const applyZip = (in1, in2, func) => {
1008-
const iter1 = exports.opIterator(in1);
1009-
const iter2 = exports.opIterator(in2);
1020+
const iter1 = new OpIter(in1);
1021+
const iter2 = new OpIter(in2);
10101022
const assem = exports.smartOpAssembler();
10111023
const op1 = new Op();
10121024
const op2 = new Op();
@@ -1075,7 +1087,7 @@ exports.pack = (oldLen, newLen, opsStr, bank) => {
10751087
exports.applyToText = (cs, str) => {
10761088
const unpacked = exports.unpack(cs);
10771089
assert(str.length === unpacked.oldLen, `mismatched apply: ${str.length} / ${unpacked.oldLen}`);
1078-
const csIter = exports.opIterator(unpacked.ops);
1090+
const csIter = new OpIter(unpacked.ops);
10791091
const bankIter = exports.stringIterator(unpacked.charBank);
10801092
const strIter = exports.stringIterator(str);
10811093
const assem = exports.stringAssembler();
@@ -1120,7 +1132,7 @@ exports.applyToText = (cs, str) => {
11201132
*/
11211133
exports.mutateTextLines = (cs, lines) => {
11221134
const unpacked = exports.unpack(cs);
1123-
const csIter = exports.opIterator(unpacked.ops);
1135+
const csIter = new OpIter(unpacked.ops);
11241136
const bankIter = exports.stringIterator(unpacked.charBank);
11251137
const mut = new TextLinesMutator(lines);
11261138
while (csIter.hasNext()) {
@@ -1251,7 +1263,7 @@ exports.applyToAttribution = (cs, astr, pool) => {
12511263

12521264
exports.mutateAttributionLines = (cs, lines, pool) => {
12531265
const unpacked = exports.unpack(cs);
1254-
const csIter = exports.opIterator(unpacked.ops);
1266+
const csIter = new OpIter(unpacked.ops);
12551267
const csBank = unpacked.charBank;
12561268
let csBankIndex = 0;
12571269
// treat the attribution lines as text lines, mutating a line at a time
@@ -1265,7 +1277,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
12651277
const nextMutOp = () => {
12661278
if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) {
12671279
const line = mut.removeLines(1);
1268-
lineIter = exports.opIterator(line);
1280+
lineIter = new OpIter(line);
12691281
}
12701282
if (!lineIter || !lineIter.hasNext()) return new Op();
12711283
return lineIter.next();
@@ -1328,7 +1340,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
13281340
exports.joinAttributionLines = (theAlines) => {
13291341
const assem = exports.mergingOpAssembler();
13301342
for (const aline of theAlines) {
1331-
const iter = exports.opIterator(aline);
1343+
const iter = new OpIter(aline);
13321344
while (iter.hasNext()) {
13331345
assem.append(iter.next());
13341346
}
@@ -1337,7 +1349,7 @@ exports.joinAttributionLines = (theAlines) => {
13371349
};
13381350

13391351
exports.splitAttributionLines = (attrOps, text) => {
1340-
const iter = exports.opIterator(attrOps);
1352+
const iter = new OpIter(attrOps);
13411353
const assem = exports.mergingOpAssembler();
13421354
const lines = [];
13431355
let pos = 0;
@@ -1495,7 +1507,7 @@ const toSplices = (cs) => {
14951507
const splices = [];
14961508

14971509
let oldPos = 0;
1498-
const iter = exports.opIterator(unpacked.ops);
1510+
const iter = new OpIter(unpacked.ops);
14991511
const charIter = exports.stringIterator(unpacked.charBank);
15001512
let inSplice = false;
15011513
while (iter.hasNext()) {
@@ -1742,7 +1754,7 @@ exports.copyAText = (atext1, atext2) => {
17421754
*/
17431755
exports.opsFromAText = function* (atext) {
17441756
// intentionally skips last newline char of atext
1745-
const iter = exports.opIterator(atext.attribs);
1757+
const iter = new OpIter(atext.attribs);
17461758
let lastOp = null;
17471759
while (iter.hasNext()) {
17481760
if (lastOp != null) yield lastOp;
@@ -1964,7 +1976,7 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
19641976
* Like "substring" but on a single-line attribution string.
19651977
*/
19661978
exports.subattribution = (astr, start, optEnd) => {
1967-
const iter = exports.opIterator(astr);
1979+
const iter = new OpIter(astr);
19681980
const assem = exports.smartOpAssembler();
19691981
let attOp = new Op();
19701982
const csOp = new Op();
@@ -2033,13 +2045,13 @@ exports.inverse = (cs, lines, alines, pool) => {
20332045
let curLineNextOp = new Op('+');
20342046

20352047
const unpacked = exports.unpack(cs);
2036-
const csIter = exports.opIterator(unpacked.ops);
2048+
const csIter = new OpIter(unpacked.ops);
20372049
const builder = exports.builder(unpacked.newLen);
20382050

20392051
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
20402052
if ((!curLineOpIter) || (curLineOpIterLine !== curLine)) {
20412053
// create curLineOpIter and advance it to curChar
2042-
curLineOpIter = exports.opIterator(alinesGet(curLine));
2054+
curLineOpIter = new OpIter(alinesGet(curLine));
20432055
curLineOpIterLine = curLine;
20442056
let indexIntoLine = 0;
20452057
while (curLineOpIter.hasNext()) {
@@ -2058,7 +2070,7 @@ exports.inverse = (cs, lines, alines, pool) => {
20582070
curChar = 0;
20592071
curLineOpIterLine = curLine;
20602072
curLineNextOp.chars = 0;
2061-
curLineOpIter = exports.opIterator(alinesGet(curLine));
2073+
curLineOpIter = new OpIter(alinesGet(curLine));
20622074
}
20632075
if (!curLineNextOp.chars) {
20642076
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Op();

0 commit comments

Comments
 (0)