Changeset: Use a generator to implement `OpIter`

pull/5299/head
Richard Hansen 2021-10-20 20:34:09 -04:00
parent a4aec006dc
commit 0eca0251f2
1 changed files with 27 additions and 17 deletions

View File

@ -180,6 +180,28 @@ exports.oldLen = (cs) => exports.unpack(cs).oldLen;
*/ */
exports.newLen = (cs) => exports.unpack(cs).newLen; exports.newLen = (cs) => exports.unpack(cs).newLen;
/**
* Parses a string of serialized changeset operations.
*
* @param {string} ops - Serialized changeset operations.
* @yields {Op}
* @returns {Generator<Op>}
*/
const 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;
while ((match = regex.exec(ops)) != null) {
if (match[5] === '$') return; // Start of the insert operation character bank.
if (match[5] != null) error(`invalid operation: ${ops.slice(regex.lastIndex - 1)}`);
const op = new Op(match[3]);
op.lines = exports.parseNum(match[2] || '0');
op.chars = exports.parseNum(match[4]);
op.attribs = match[1];
yield op;
}
};
/** /**
* Iterator over a changeset's operations. * Iterator over a changeset's operations.
* *
@ -190,24 +212,15 @@ class OpIter {
* @param {string} ops - String encoding the change operations to iterate over. * @param {string} ops - String encoding the change operations to iterate over.
*/ */
constructor(ops) { constructor(ops) {
this._ops = ops; this._gen = deserializeOps(ops);
this._regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g; this._next = this._gen.next();
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. * @returns {boolean} Whether there are any remaining operations.
*/ */
hasNext() { hasNext() {
return this._nextMatch && !!this._nextMatch[0]; return !this._next.done;
} }
/** /**
@ -221,11 +234,8 @@ class OpIter {
*/ */
next(opOut = new Op()) { next(opOut = new Op()) {
if (this.hasNext()) { if (this.hasNext()) {
opOut.attribs = this._nextMatch[1]; copyOp(this._next.value, opOut);
opOut.lines = exports.parseNum(this._nextMatch[2] || '0'); this._next = this._gen.next();
opOut.opcode = this._nextMatch[3];
opOut.chars = exports.parseNum(this._nextMatch[4]);
this._nextMatch = this._nextRegexMatch();
} else { } else {
clearOp(opOut); clearOp(opOut);
} }