json-joy
Version:
Collection of libraries for building collaborative editing apps.
258 lines (257 loc) • 9.94 kB
JavaScript
import { OpAdd, OpAnd, OpContains, OpCopy, OpDefined, OpEnds, OpExtend, OpFlip, OpIn, OpInc, OpLess, OpMatches, OpMerge, OpMore, OpMove, OpNot, OpOr, OpRemove, OpReplace, OpSplit, OpStarts, OpStrDel, OpStrIns, OpTest, OpTestString, OpTestStringLen, OpTestType, OpType, OpUndefined, } from '../../op';
import { MsgPackDecoderFast } from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackDecoderFast';
import { OPCODE } from '../../constants';
import { createMatcherDefault } from '../../util';
export class Decoder extends MsgPackDecoderFast {
options;
constructor(options) {
super();
this.options = options;
}
decode(uint8) {
this.reader.reset(uint8);
return this.decodePatch();
}
decodePatch() {
const len = this.decodeArrayHeader();
const ops = [];
for (let i = 0; i < len; i++)
ops.push(this.decodeOp(undefined));
return ops;
}
decodeOp(parent) {
const length = this.decodeArrayHeader();
const opcode = this.reader.u8();
switch (opcode) {
case OPCODE.add: {
const path = this.decodeArray();
const value = this.val();
return new OpAdd(path, value);
}
case OPCODE.and: {
const path = this.decodePath(parent);
const length = this.decodeArrayHeader();
const ops = [];
const op = new OpAnd(path, ops);
for (let i = 0; i < length; i++)
ops.push(this.decodeOp(op));
return op;
}
case OPCODE.contains: {
const path = this.decodePath(parent);
const value = this.decodeString();
const ignoreCase = length > 3;
return new OpContains(path, value, ignoreCase);
}
case OPCODE.copy: {
const path = this.decodeArray();
const from = this.decodeArray();
return new OpCopy(path, from);
}
case OPCODE.defined: {
const path = this.decodePath(parent);
return new OpDefined(path);
}
case OPCODE.ends: {
const path = this.decodePath(parent);
const value = this.decodeString();
const ignoreCase = length > 3;
return new OpEnds(path, value, ignoreCase);
}
case OPCODE.extend: {
const path = this.decodeArray();
const props = this.decodeObject();
const deleteNull = length > 3;
return new OpExtend(path, props, deleteNull);
}
case OPCODE.flip: {
const path = this.decodeArray();
return new OpFlip(path);
}
case OPCODE.in: {
const path = this.decodePath(parent);
const value = this.decodeArray();
return new OpIn(path, value);
}
case OPCODE.inc: {
const path = this.decodePath(parent);
const inc = this.val();
return new OpInc(path, inc);
}
case OPCODE.less: {
const path = this.decodePath(parent);
const value = this.val();
return new OpLess(path, value);
}
case OPCODE.matches: {
const path = this.decodePath(parent);
const value = this.decodeString();
const ignoreCase = length > 3;
return new OpMatches(path, value, ignoreCase, this.options.createMatcher || createMatcherDefault);
}
case OPCODE.merge: {
const hasProps = length > 3;
const path = this.decodeArray();
const pos = this.val();
const props = hasProps ? this.decodeObject() : null;
return new OpMerge(path, pos, props);
}
case OPCODE.more: {
const path = this.decodePath(parent);
const value = this.val();
return new OpMore(path, value);
}
case OPCODE.move: {
const path = this.decodeArray();
const from = this.decodeArray();
return new OpMove(path, from);
}
case OPCODE.not: {
const path = this.decodePath(parent);
const length = this.decodeArrayHeader();
const ops = [];
const op = new OpNot(path, ops);
for (let i = 0; i < length; i++)
ops.push(this.decodeOp(op));
return op;
}
case OPCODE.or: {
const path = this.decodePath(parent);
const length = this.decodeArrayHeader();
const ops = [];
const op = new OpOr(path, ops);
for (let i = 0; i < length; i++)
ops.push(this.decodeOp(op));
return op;
}
case OPCODE.remove: {
const path = this.decodeArray();
const hasOldValue = length > 2;
const oldValue = hasOldValue ? this.val() : undefined;
return new OpRemove(path, oldValue);
}
case OPCODE.replace: {
const path = this.decodeArray();
const value = this.val();
const hasOldValue = length > 3;
const oldValue = hasOldValue ? this.val() : undefined;
return new OpReplace(path, value, oldValue);
}
case OPCODE.split: {
const path = this.decodeArray();
const pos = this.val();
const hasProps = length > 3;
const props = hasProps ? this.decodeObject() : null;
return new OpSplit(path, pos, props);
}
case OPCODE.starts: {
const ignoreCase = length > 3;
const path = this.decodePath(parent);
const value = this.decodeString();
return new OpStarts(path, value, ignoreCase);
}
case OPCODE.str_del: {
const hasStr = length < 5;
const path = this.decodeArray();
const pos = this.val();
if (hasStr) {
const str = this.decodeString();
return new OpStrDel(path, pos, str, undefined);
}
else {
this.reader.u8();
const len = this.val();
return new OpStrDel(path, pos, undefined, len);
}
}
case OPCODE.str_ins: {
const path = this.decodeArray();
const pos = this.val();
const str = this.decodeString();
return new OpStrIns(path, pos, str);
}
case OPCODE.test: {
const not = length > 3;
const path = this.decodePath(parent);
const value = this.val();
return new OpTest(path, value, not);
}
case OPCODE.test_string: {
const not = length > 4;
const path = this.decodePath(parent);
const pos = this.val();
const str = this.decodeString();
return new OpTestString(path, pos, str, not);
}
case OPCODE.test_string_len: {
const not = length > 3;
const path = this.decodePath(parent);
const len = this.val();
return new OpTestStringLen(path, len, not);
}
case OPCODE.test_type: {
const path = this.decodePath(parent);
const type = this.decodeArray();
return new OpTestType(path, type);
}
case OPCODE.type: {
const path = this.decodePath(parent);
const value = this.decodeString();
return new OpType(path, value);
}
case OPCODE.undefined: {
const path = this.decodePath(parent);
return new OpUndefined(path);
}
}
throw new Error('OP_UNKNOWN');
}
decodePath(parent) {
const path = this.decodeArray();
if (!parent)
return path;
return [...parent.path, ...path];
}
decodeObject() {
const reader = this.reader;
const byte = reader.u8();
if (byte <= 0xbf)
return this.obj(byte & 0b1111);
else if (byte === 0xde)
return this.obj(reader.u16());
/* 0xdf */ else
return this.obj(reader.u32());
}
decodeArray() {
const reader = this.reader;
const byte = reader.u8();
if (byte < 0b10011111)
return this.arr(byte & 0b1111);
else if (byte === 0xdc)
return this.arr(reader.u16());
else
return this.arr(reader.u32());
}
decodeArrayHeader() {
const reader = this.reader;
const byte = reader.u8();
if (byte < 0b10011111)
return byte & 0b1111;
else if (byte === 0xdc)
return reader.u16();
else
return reader.u32();
}
decodeString() {
const reader = this.reader;
const byte = reader.u8();
if (byte <= 0xbf)
return reader.utf8(byte & 0b11111);
else if (byte === 0xd9)
return reader.utf8(reader.u8());
else if (byte === 0xda)
return reader.utf8(reader.u16());
/* 0xDB */ else
return reader.utf8(reader.u32());
}
}