@dra2020/baseclient 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/all/all.d.ts +18 -0
- package/dist/baseclient.js +9567 -0
- package/dist/baseclient.js.map +1 -0
- package/dist/context/all.d.ts +1 -0
- package/dist/context/context.d.ts +13 -0
- package/dist/filterexpr/all.d.ts +1 -0
- package/dist/filterexpr/filterexpr.d.ts +64 -0
- package/dist/fsm/all.d.ts +1 -0
- package/dist/fsm/fsm.d.ts +118 -0
- package/dist/logabstract/all.d.ts +1 -0
- package/dist/logabstract/log.d.ts +26 -0
- package/dist/logclient/all.d.ts +1 -0
- package/dist/logclient/log.d.ts +6 -0
- package/dist/ot-editutil/all.d.ts +2 -0
- package/dist/ot-editutil/oteditutil.d.ts +14 -0
- package/dist/ot-editutil/otmaputil.d.ts +21 -0
- package/dist/ot-js/all.d.ts +9 -0
- package/dist/ot-js/otarray.d.ts +111 -0
- package/dist/ot-js/otclientengine.d.ts +38 -0
- package/dist/ot-js/otcomposite.d.ts +37 -0
- package/dist/ot-js/otcounter.d.ts +17 -0
- package/dist/ot-js/otengine.d.ts +22 -0
- package/dist/ot-js/otmap.d.ts +19 -0
- package/dist/ot-js/otserverengine.d.ts +38 -0
- package/dist/ot-js/otsession.d.ts +111 -0
- package/dist/ot-js/ottypes.d.ts +29 -0
- package/dist/poly/all.d.ts +15 -0
- package/dist/poly/blend.d.ts +1 -0
- package/dist/poly/boundbox.d.ts +16 -0
- package/dist/poly/cartesian.d.ts +5 -0
- package/dist/poly/graham-scan.d.ts +8 -0
- package/dist/poly/hash.d.ts +1 -0
- package/dist/poly/matrix.d.ts +24 -0
- package/dist/poly/minbound.d.ts +1 -0
- package/dist/poly/poly.d.ts +52 -0
- package/dist/poly/polybin.d.ts +5 -0
- package/dist/poly/polylabel.d.ts +7 -0
- package/dist/poly/polypack.d.ts +30 -0
- package/dist/poly/polyround.d.ts +1 -0
- package/dist/poly/polysimplify.d.ts +1 -0
- package/dist/poly/quad.d.ts +48 -0
- package/dist/poly/selfintersect.d.ts +1 -0
- package/dist/poly/shamos.d.ts +1 -0
- package/dist/poly/simplify.d.ts +2 -0
- package/dist/poly/topo.d.ts +46 -0
- package/dist/poly/union.d.ts +48 -0
- package/dist/util/all.d.ts +5 -0
- package/dist/util/bintrie.d.ts +93 -0
- package/dist/util/countedhash.d.ts +19 -0
- package/dist/util/gradient.d.ts +15 -0
- package/dist/util/indexedarray.d.ts +15 -0
- package/dist/util/util.d.ts +68 -0
- package/docs/context.md +2 -0
- package/docs/fsm.md +243 -0
- package/docs/logabstract.md +2 -0
- package/docs/logclient.md +2 -0
- package/docs/ot-editutil.md +2 -0
- package/docs/ot-js.md +95 -0
- package/docs/poly.md +103 -0
- package/docs/util.md +2 -0
- package/lib/all/all.ts +19 -0
- package/lib/context/all.ts +1 -0
- package/lib/context/context.ts +82 -0
- package/lib/filterexpr/all.ts +1 -0
- package/lib/filterexpr/filterexpr.ts +625 -0
- package/lib/fsm/all.ts +1 -0
- package/lib/fsm/fsm.ts +549 -0
- package/lib/logabstract/all.ts +1 -0
- package/lib/logabstract/log.ts +55 -0
- package/lib/logclient/all.ts +1 -0
- package/lib/logclient/log.ts +105 -0
- package/lib/ot-editutil/all.ts +2 -0
- package/lib/ot-editutil/oteditutil.ts +180 -0
- package/lib/ot-editutil/otmaputil.ts +209 -0
- package/lib/ot-js/all.ts +9 -0
- package/lib/ot-js/otarray.ts +1168 -0
- package/lib/ot-js/otclientengine.ts +327 -0
- package/lib/ot-js/otcomposite.ts +247 -0
- package/lib/ot-js/otcounter.ts +145 -0
- package/lib/ot-js/otengine.ts +71 -0
- package/lib/ot-js/otmap.ts +144 -0
- package/lib/ot-js/otserverengine.ts +329 -0
- package/lib/ot-js/otsession.ts +199 -0
- package/lib/ot-js/ottypes.ts +98 -0
- package/lib/poly/all.ts +15 -0
- package/lib/poly/blend.ts +27 -0
- package/lib/poly/boundbox.ts +102 -0
- package/lib/poly/cartesian.ts +130 -0
- package/lib/poly/graham-scan.ts +401 -0
- package/lib/poly/hash.ts +15 -0
- package/lib/poly/matrix.ts +309 -0
- package/lib/poly/minbound.ts +211 -0
- package/lib/poly/poly.ts +767 -0
- package/lib/poly/polybin.ts +218 -0
- package/lib/poly/polylabel.ts +204 -0
- package/lib/poly/polypack.ts +458 -0
- package/lib/poly/polyround.ts +30 -0
- package/lib/poly/polysimplify.ts +24 -0
- package/lib/poly/quad.ts +272 -0
- package/lib/poly/selfintersect.ts +87 -0
- package/lib/poly/shamos.ts +297 -0
- package/lib/poly/simplify.ts +119 -0
- package/lib/poly/topo.ts +525 -0
- package/lib/poly/union.ts +371 -0
- package/lib/util/all.ts +5 -0
- package/lib/util/bintrie.ts +603 -0
- package/lib/util/countedhash.ts +83 -0
- package/lib/util/gradient.ts +108 -0
- package/lib/util/indexedarray.ts +80 -0
- package/lib/util/util.ts +695 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1168 @@
|
|
|
1
|
+
import * as OT from "./ottypes";
|
|
2
|
+
|
|
3
|
+
const TestUnitSize: number = 4;
|
|
4
|
+
let TestCounter: number = 0;
|
|
5
|
+
|
|
6
|
+
// Array Ops
|
|
7
|
+
export const OpInsert: number = 1;
|
|
8
|
+
export const OpDelete: number = 2;
|
|
9
|
+
export const OpRetain: number = 3;
|
|
10
|
+
export const OpCursor: number = 4; // 2nd arg is 0/1 for start/end of region, 3rd arg is clientID
|
|
11
|
+
export const OpSet: number = 5;
|
|
12
|
+
export const OpTmpRetain: number = 6;
|
|
13
|
+
|
|
14
|
+
// Op, Len, Data
|
|
15
|
+
export type OTSingleArrayEdit = [number, number, any];
|
|
16
|
+
export type OTEdits = OTSingleArrayEdit[];
|
|
17
|
+
|
|
18
|
+
enum OTalignEdgesType { AlignForCompose, AlignForTransform };
|
|
19
|
+
|
|
20
|
+
// Set of interfaces that operates on the underlying array-like value type.
|
|
21
|
+
// If more efficient, can modify the initial argument and simply return it.
|
|
22
|
+
// Alternatively, can create new instance and return it (string works this way since
|
|
23
|
+
// underlying type is immutable).
|
|
24
|
+
// This is used for the array-like specialization of IOTInterface
|
|
25
|
+
export interface IOTArrayLikeOperations
|
|
26
|
+
{
|
|
27
|
+
underlyingTypeName(): string;
|
|
28
|
+
|
|
29
|
+
// Return empty instance
|
|
30
|
+
empty(): any;
|
|
31
|
+
|
|
32
|
+
// Insert new instance at position specified
|
|
33
|
+
insert(t: any, pos: number, tInsert: any): any; // Can modify input
|
|
34
|
+
|
|
35
|
+
// Delete part of instance
|
|
36
|
+
delete(t: any, pos: number, len: number): any; // Can modify input
|
|
37
|
+
|
|
38
|
+
// Set value of part of instance
|
|
39
|
+
set(t: any, pos: number, tSet: any): any; // Can modify input
|
|
40
|
+
|
|
41
|
+
// append to instance (insert at end)
|
|
42
|
+
append(t: any, tAppend: any): any; // Can modify input
|
|
43
|
+
|
|
44
|
+
// Take substring of instance
|
|
45
|
+
substr(t: any, pos: number, len: number): any; // Can modify input
|
|
46
|
+
|
|
47
|
+
// Take substring of provided base value type and set
|
|
48
|
+
substrOf(t: any, pos: number, len: number, tsub: any): any; // Cannot modify input
|
|
49
|
+
|
|
50
|
+
// Construct an instance N long
|
|
51
|
+
constructN(n: number): any;
|
|
52
|
+
|
|
53
|
+
// Test for equality
|
|
54
|
+
equal(t1: any, t2: any): boolean;
|
|
55
|
+
|
|
56
|
+
// copy an instance
|
|
57
|
+
copy(t: any): any; // Need to do deep copy if T is mutable.
|
|
58
|
+
|
|
59
|
+
// Return length of instance
|
|
60
|
+
length(t: any): number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Operates on a single "OTSingleArrayEdit", parameterized by an object that manipulates the underlying
|
|
64
|
+
// array-like value stored as the third property of the 3-element edit object.
|
|
65
|
+
export class OTSingleArrayEditor
|
|
66
|
+
{
|
|
67
|
+
raw: IOTArrayLikeOperations;
|
|
68
|
+
|
|
69
|
+
constructor(raw: IOTArrayLikeOperations)
|
|
70
|
+
{
|
|
71
|
+
this.raw = raw;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
copy(a: OTSingleArrayEdit): OTSingleArrayEdit // If a[2] is mutable, need to override and do deep copy
|
|
75
|
+
{ return [ a[0], a[1], this.raw.copy(a[2]) ]; }
|
|
76
|
+
|
|
77
|
+
// Static Predicates for MoveAction
|
|
78
|
+
isDelete(a: OTSingleArrayEdit): boolean { return a[0] == OpDelete; }
|
|
79
|
+
isNotDelete(a: OTSingleArrayEdit): boolean { return a[0] != OpDelete; }
|
|
80
|
+
isCursor(a: OTSingleArrayEdit): boolean { return a[0] == OpCursor; }
|
|
81
|
+
isNotCursor(a: OTSingleArrayEdit): boolean { return a[0] != OpCursor; }
|
|
82
|
+
isTmpRetain(a: OTSingleArrayEdit): boolean { return a[0] == OpTmpRetain; }
|
|
83
|
+
isNotTmpRetainOrDelete(a: OTSingleArrayEdit): boolean { return (a[0] != OpTmpRetain && a[0] != OpDelete); }
|
|
84
|
+
isTmpRetainOrDelete(a: OTSingleArrayEdit): boolean { return (a[0] == OpTmpRetain || a[0] == OpDelete); }
|
|
85
|
+
|
|
86
|
+
// Other static predicates
|
|
87
|
+
isIgnore(a: OTSingleArrayEdit): boolean { return a[0] < 0; }
|
|
88
|
+
isNoOp(a: OTSingleArrayEdit): boolean { return a[1] === 0 && a[0] != OpCursor; }
|
|
89
|
+
isEqual(a1: OTSingleArrayEdit, a2: OTSingleArrayEdit) { return a1[0] == a2[0] && a1[1] == a2[1] && this.raw.equal(a1[2], a2[2]); }
|
|
90
|
+
|
|
91
|
+
// Helpers
|
|
92
|
+
appendValue(a: OTSingleArrayEdit, s: any): void
|
|
93
|
+
{ a[2] = this.raw.append(a[2], s); a[1] = a[1] + this.raw.length(s); }
|
|
94
|
+
empty(a: OTSingleArrayEdit): void { a[0] = OpCursor; a[1] = 0; a[2] = this.raw.empty(); }
|
|
95
|
+
setIgnore(a: OTSingleArrayEdit): void { if (a[0] > 0) a[0] = - a[0]; }
|
|
96
|
+
substr(aIn: OTSingleArrayEdit, pos: number, len: number): void
|
|
97
|
+
{
|
|
98
|
+
let sSource: any = aIn[2];
|
|
99
|
+
if (len > 0 && pos+len <= this.raw.length(sSource))
|
|
100
|
+
aIn[2] = this.raw.substr(sSource, pos, len);
|
|
101
|
+
aIn[1] = len;
|
|
102
|
+
}
|
|
103
|
+
substrFromRaw(aIn: OTSingleArrayEdit, pos: number, len: number, s: any): void
|
|
104
|
+
{
|
|
105
|
+
let sSource: any = s;
|
|
106
|
+
if (len > 0 && pos+len <= this.raw.length(sSource))
|
|
107
|
+
aIn[2] = this.raw.substr(sSource, pos, len);
|
|
108
|
+
aIn[1] = len;
|
|
109
|
+
}
|
|
110
|
+
copyWithSubstr(aIn: OTSingleArrayEdit, pos: number, len: number): OTSingleArrayEdit
|
|
111
|
+
{
|
|
112
|
+
let aOut: OTSingleArrayEdit = this.copy(aIn);
|
|
113
|
+
this.substr(aOut, pos, len);
|
|
114
|
+
return aOut;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export class OTStringOperations implements IOTArrayLikeOperations
|
|
119
|
+
{
|
|
120
|
+
underlyingTypeName(): string { return 'string'; }
|
|
121
|
+
empty(): any { return ''; }
|
|
122
|
+
insert(a: any, pos: number, aInsert: any): any
|
|
123
|
+
{
|
|
124
|
+
let s: string = a as string;
|
|
125
|
+
let sInsert: string = aInsert as string;
|
|
126
|
+
return s.substr(0, pos) + sInsert + s.substr(pos);
|
|
127
|
+
}
|
|
128
|
+
delete(a: any, pos: number, len: number): any
|
|
129
|
+
{
|
|
130
|
+
let s: string = a as string;
|
|
131
|
+
return s.substr(0, pos) + s.substr(pos+len);
|
|
132
|
+
}
|
|
133
|
+
set(a: any, pos: number, aSet: any): any
|
|
134
|
+
{
|
|
135
|
+
let s: string = a as string;
|
|
136
|
+
let sSet: string = aSet as string;
|
|
137
|
+
return s.substr(0, pos) + sSet + s.substr(pos+sSet.length);
|
|
138
|
+
}
|
|
139
|
+
append(a: any, aAppend: any): any
|
|
140
|
+
{
|
|
141
|
+
let s: string = a as string;
|
|
142
|
+
let sAppend: string = aAppend as string;
|
|
143
|
+
return s + sAppend;
|
|
144
|
+
}
|
|
145
|
+
substr(a: any, pos: number, len: number): any // Can modify source
|
|
146
|
+
{
|
|
147
|
+
let s: string = a as string;
|
|
148
|
+
return s.substr(pos, len);
|
|
149
|
+
}
|
|
150
|
+
substrOf(a: any, pos: number, len: number, aSub: any): any // Copies from tsub argument
|
|
151
|
+
{
|
|
152
|
+
// a unused if not updated with return value contents
|
|
153
|
+
let sSub: string = aSub as string;
|
|
154
|
+
return sSub.substr(pos, len);
|
|
155
|
+
}
|
|
156
|
+
constructN(n: number): any
|
|
157
|
+
{
|
|
158
|
+
let x = ' ';
|
|
159
|
+
let s = '';
|
|
160
|
+
for (;;)
|
|
161
|
+
{
|
|
162
|
+
if (n & 1)
|
|
163
|
+
s += x;
|
|
164
|
+
n >>= 1;
|
|
165
|
+
if (n)
|
|
166
|
+
x += x;
|
|
167
|
+
else
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
return s;
|
|
171
|
+
}
|
|
172
|
+
equal(a1: any, a2: any): boolean
|
|
173
|
+
{
|
|
174
|
+
let s1: string = a1 as string;
|
|
175
|
+
let s2: string = a2 as string;
|
|
176
|
+
return s1 === s2;
|
|
177
|
+
}
|
|
178
|
+
copy(a: any): any { return a; }
|
|
179
|
+
length(a: any): number { return a.length; }
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export class OTArrayOperations implements IOTArrayLikeOperations
|
|
183
|
+
{
|
|
184
|
+
underlyingTypeName(): string { return 'array'; }
|
|
185
|
+
empty(): any { return []; }
|
|
186
|
+
insert(a: any, pos: number, aInsert: any): any
|
|
187
|
+
{
|
|
188
|
+
let arr: Array<any> = a as Array<any>;
|
|
189
|
+
let arrInsert: Array<any> = aInsert as Array<any>;
|
|
190
|
+
let arrReturn = Array(arr.length + arrInsert.length);
|
|
191
|
+
let i: number, j: number;
|
|
192
|
+
for (i = 0; i < pos; i++)
|
|
193
|
+
arrReturn[i] = arr[i];
|
|
194
|
+
for (j = 0; j < arrInsert.length; j++)
|
|
195
|
+
arrReturn[i+j] = arrInsert[j];
|
|
196
|
+
for (i = pos; i < arr.length; i++)
|
|
197
|
+
arrReturn[i+j] = arr[i];
|
|
198
|
+
return arrReturn;
|
|
199
|
+
}
|
|
200
|
+
delete(a: any, pos: number, len: number): any
|
|
201
|
+
{
|
|
202
|
+
let arr: Array<any> = a as Array<any>;
|
|
203
|
+
arr.splice(pos, len);
|
|
204
|
+
return arr;
|
|
205
|
+
}
|
|
206
|
+
set(a: any, pos: number, aSet: any): any
|
|
207
|
+
{
|
|
208
|
+
let arr: Array<any> = a as Array<any>;
|
|
209
|
+
let arrSet: Array<any> = aSet as Array<any>;
|
|
210
|
+
for (let i: number = 0; i < arrSet.length; i++)
|
|
211
|
+
arr[i+pos] = arrSet[i];
|
|
212
|
+
return arr;
|
|
213
|
+
}
|
|
214
|
+
append(a: any, aAppend: any): any
|
|
215
|
+
{
|
|
216
|
+
let arr: Array<any> = a as Array<any>;
|
|
217
|
+
let arrAppend: Array<any> = aAppend as Array<any>;
|
|
218
|
+
return arr.concat(arrAppend);
|
|
219
|
+
}
|
|
220
|
+
substr(a: any, pos: number, len: number): any // Can modify source
|
|
221
|
+
{
|
|
222
|
+
let arr: Array<any> = a as Array<any>;
|
|
223
|
+
return arr.slice(pos, pos+len);
|
|
224
|
+
}
|
|
225
|
+
substrOf(a: any, pos: number, len: number, aSub: any): any // Copies from tsub argument
|
|
226
|
+
{
|
|
227
|
+
// a unused if not updated with return value contents
|
|
228
|
+
let arrSub: Array<any> = aSub as Array<any>;
|
|
229
|
+
return arrSub.slice(pos, pos+len);
|
|
230
|
+
}
|
|
231
|
+
constructN(n: number): any
|
|
232
|
+
{
|
|
233
|
+
return new Array(n);
|
|
234
|
+
}
|
|
235
|
+
equal(a1: any, a2: any): boolean
|
|
236
|
+
{
|
|
237
|
+
let arr1: Array<any> = a1 as Array<any>;
|
|
238
|
+
let arr2: Array<any> = a2 as Array<any>;
|
|
239
|
+
if (arr1.length != arr2.length)
|
|
240
|
+
return false;
|
|
241
|
+
for (let i: number = 0; i < arr1.length; i++)
|
|
242
|
+
if (arr1[i] !== arr2[i])
|
|
243
|
+
return false;
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
copy(a: any): any
|
|
247
|
+
{
|
|
248
|
+
let arr: Array<any> = a as Array<any>;
|
|
249
|
+
let arrRet = new Array(arr.length);
|
|
250
|
+
for (let i: number = 0; i < arr.length; i++)
|
|
251
|
+
arrRet[i] = arr[i];
|
|
252
|
+
return arrRet;
|
|
253
|
+
}
|
|
254
|
+
length(a: any): number
|
|
255
|
+
{
|
|
256
|
+
return a.length;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export class OTArrayLikeResource extends OT.OTResourceBase
|
|
261
|
+
{
|
|
262
|
+
editor: OTSingleArrayEditor;
|
|
263
|
+
|
|
264
|
+
constructor(ed: OTSingleArrayEditor, rname: string)
|
|
265
|
+
{
|
|
266
|
+
super(rname, ed.raw.underlyingTypeName());
|
|
267
|
+
this.editor = ed;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
copy(): OTArrayLikeResource
|
|
271
|
+
{
|
|
272
|
+
return null; // Needs to be overridden
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
moveEdits(newA: OTEdits, iStart: number, iEnd?: number, pred?: (a: OTSingleArrayEdit) => boolean)
|
|
276
|
+
{
|
|
277
|
+
if (iEnd == undefined)
|
|
278
|
+
iEnd = this.edits.length - 1;
|
|
279
|
+
|
|
280
|
+
for (; iStart <= iEnd; iStart++)
|
|
281
|
+
{
|
|
282
|
+
let a: OTSingleArrayEdit = this.edits[iStart];
|
|
283
|
+
if (!this.editor.isIgnore(a) && (pred == undefined || pred(a)))
|
|
284
|
+
newA.push(a);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
equal(rhs: OTArrayLikeResource): boolean
|
|
289
|
+
{
|
|
290
|
+
if (this.length != rhs.length)
|
|
291
|
+
return false;
|
|
292
|
+
for (let i: number = 0; i < this.length; i++)
|
|
293
|
+
if (! this.editor.isEqual(this.edits[i], rhs.edits[i]))
|
|
294
|
+
return false;
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Function: OTArrayLikeResource::effectivelyEqual
|
|
299
|
+
//
|
|
300
|
+
// Description:
|
|
301
|
+
// A looser definition than operator==. Returns true if two actions would result in the
|
|
302
|
+
// same final string. This ignores no-ops like OpCursor and allows different orderings of
|
|
303
|
+
// inserts and deletes at the same location.
|
|
304
|
+
//
|
|
305
|
+
// Played around with different algorithms, but the simplest is probably just to apply
|
|
306
|
+
// the two actions and see if I get the same final string. Came up with an interesting
|
|
307
|
+
// algorithm of walking through comparing hashes, but that was not robust to operations
|
|
308
|
+
// being split into fragments and interposed with alternate ops (OpCursor or interleaving of Ins/Del)
|
|
309
|
+
// that still leave the string the same. If unhappy with this approach (which scales with size
|
|
310
|
+
// of string to edit rather than complexity of the edit), the other approach would be to canonicalize
|
|
311
|
+
// the edit operations (including removing cursor operations and normalizing order of deletes).
|
|
312
|
+
// (Added that version of the algorithm under #ifdef). Could also dynamically choose approach based
|
|
313
|
+
// on relative size of arrays.
|
|
314
|
+
//
|
|
315
|
+
effectivelyEqual(rhs: OTArrayLikeResource): boolean
|
|
316
|
+
{
|
|
317
|
+
// Exactly equal is always effectively equal
|
|
318
|
+
if (this.equal(rhs))
|
|
319
|
+
return true;
|
|
320
|
+
|
|
321
|
+
if (this.originalLength() != rhs.originalLength())
|
|
322
|
+
return false;
|
|
323
|
+
|
|
324
|
+
// Preferred algorithm
|
|
325
|
+
let s: any = this.editor.raw.constructN(this.originalLength());
|
|
326
|
+
|
|
327
|
+
let sL: any = this.apply(s);
|
|
328
|
+
let sR: any = rhs.apply(s);
|
|
329
|
+
return sL === sR;
|
|
330
|
+
|
|
331
|
+
// Alternate algorithm (see above)
|
|
332
|
+
//let aL: OTArrayLikeResource = this.copy();
|
|
333
|
+
//let aR: OTArrayLikeResource = rhs.copy();
|
|
334
|
+
|
|
335
|
+
//aL.fullyCoalesce();
|
|
336
|
+
//aR.fullyCoalesce();
|
|
337
|
+
//return aL.equal(aR);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
basesConsistent(rhs: OTArrayLikeResource): void
|
|
341
|
+
{
|
|
342
|
+
if (this.originalLength() != rhs.originalLength())
|
|
343
|
+
{
|
|
344
|
+
console.log("Logic Failure: transform: Bases Inconsistent.");
|
|
345
|
+
throw("Logic Failure: transform: Bases Inconsistent.");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
originalLength(): number
|
|
350
|
+
{
|
|
351
|
+
let len: number = 0;
|
|
352
|
+
|
|
353
|
+
for (let i: number = 0; i < this.length; i++)
|
|
354
|
+
{
|
|
355
|
+
let a: OTSingleArrayEdit = this.edits[i];
|
|
356
|
+
if (a[0] == OpRetain || a[0] == OpDelete || a[0] == OpSet)
|
|
357
|
+
len += a[1];
|
|
358
|
+
}
|
|
359
|
+
return len;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
finalLength(): number
|
|
363
|
+
{
|
|
364
|
+
let len: number = 0;
|
|
365
|
+
|
|
366
|
+
for (let i: number = 0; i < this.length; i++)
|
|
367
|
+
{
|
|
368
|
+
let a: OTSingleArrayEdit = this.edits[i];
|
|
369
|
+
if (a[0] == OpRetain || a[0] == OpInsert || a[0] == OpSet)
|
|
370
|
+
len += a[1];
|
|
371
|
+
}
|
|
372
|
+
return len;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
apply(aValue: any): any
|
|
376
|
+
{
|
|
377
|
+
if (aValue == null)
|
|
378
|
+
aValue = this.editor.raw.empty();
|
|
379
|
+
let pos: number = 0;
|
|
380
|
+
for (let i: number = 0; i < this.length; i++)
|
|
381
|
+
{
|
|
382
|
+
let a: OTSingleArrayEdit = this.edits[i];
|
|
383
|
+
|
|
384
|
+
switch (a[0])
|
|
385
|
+
{
|
|
386
|
+
case OpRetain:
|
|
387
|
+
pos += a[1];
|
|
388
|
+
break;
|
|
389
|
+
case OpCursor:
|
|
390
|
+
break;
|
|
391
|
+
case OpDelete:
|
|
392
|
+
aValue = this.editor.raw.delete(aValue, pos, a[1]);
|
|
393
|
+
break;
|
|
394
|
+
case OpInsert:
|
|
395
|
+
aValue = this.editor.raw.insert(aValue, pos, a[2]);
|
|
396
|
+
pos += a[1];
|
|
397
|
+
break;
|
|
398
|
+
case OpSet:
|
|
399
|
+
aValue = this.editor.raw.set(aValue, pos, a[2]);
|
|
400
|
+
pos += a[1];
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return aValue;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
coalesce(bDeleteCursor: boolean = false): void
|
|
408
|
+
{
|
|
409
|
+
if (this.length == 0)
|
|
410
|
+
return;
|
|
411
|
+
|
|
412
|
+
// coalesce adjoining actions and delete no-ops
|
|
413
|
+
let newA: OTEdits = [];
|
|
414
|
+
let aLast: OTSingleArrayEdit;
|
|
415
|
+
for (let i: number = 0; i < this.length; i++)
|
|
416
|
+
{
|
|
417
|
+
let aNext: OTSingleArrayEdit = this.edits[i];
|
|
418
|
+
if (this.editor.isNoOp(aNext) || (bDeleteCursor && aNext[0] == OpCursor))
|
|
419
|
+
continue;
|
|
420
|
+
|
|
421
|
+
if (newA.length > 0 && aNext[0] == aLast[0])
|
|
422
|
+
{
|
|
423
|
+
if (aNext[0] == OpInsert || aNext[0] == OpSet)
|
|
424
|
+
this.editor.appendValue(aLast, aNext[2]);
|
|
425
|
+
else
|
|
426
|
+
aLast[1] += aNext[1];
|
|
427
|
+
}
|
|
428
|
+
else
|
|
429
|
+
{
|
|
430
|
+
newA.push(aNext);
|
|
431
|
+
aLast = aNext;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
this.edits = newA;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Function: fullyCoalesce
|
|
439
|
+
//
|
|
440
|
+
// Description:
|
|
441
|
+
// Heavier duty version of coalesce that fully normalizes so that two actions that result in same
|
|
442
|
+
// final edit are exactly the same. This normalizes order of insert/deletes and deletes OpCursor,
|
|
443
|
+
// and then does coalesce.
|
|
444
|
+
//
|
|
445
|
+
fullyCoalesce(): void
|
|
446
|
+
{
|
|
447
|
+
// TODO
|
|
448
|
+
this.coalesce(true);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Function: Invert
|
|
452
|
+
//
|
|
453
|
+
// Description:
|
|
454
|
+
// Given an action, convert it to its inverse (action + inverse) = identity (retain(n)).
|
|
455
|
+
//
|
|
456
|
+
// Note that in order to compute the inverse, you need the input state (e.g. because in order to invert
|
|
457
|
+
// OpDelete, you need to know the deleted characters.
|
|
458
|
+
//
|
|
459
|
+
invert(sInput: any): void
|
|
460
|
+
{
|
|
461
|
+
let pos: number = 0; // Tracks position in input string.
|
|
462
|
+
|
|
463
|
+
for (let i: number = 0; i < this.length; i++)
|
|
464
|
+
{
|
|
465
|
+
let a: OTSingleArrayEdit = this.edits[i];
|
|
466
|
+
|
|
467
|
+
switch (a[0])
|
|
468
|
+
{
|
|
469
|
+
case OpCursor:
|
|
470
|
+
break;
|
|
471
|
+
case OpRetain:
|
|
472
|
+
pos += a[1];
|
|
473
|
+
break;
|
|
474
|
+
case OpInsert:
|
|
475
|
+
a[2] = '';
|
|
476
|
+
a[0] = OpDelete;
|
|
477
|
+
break;
|
|
478
|
+
case OpDelete:
|
|
479
|
+
a[2] = this.editor.copyWithSubstr(sInput, pos, a[1]);
|
|
480
|
+
a[0] = OpInsert;
|
|
481
|
+
pos += a[1];
|
|
482
|
+
break;
|
|
483
|
+
case OpSet:
|
|
484
|
+
a[2] = this.editor.copyWithSubstr(sInput, pos, a[1]);
|
|
485
|
+
pos += a[1];
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Function: alignEdges
|
|
492
|
+
//
|
|
493
|
+
// Description:
|
|
494
|
+
// Slice up this action sequence so its edges align with the action sequence I am going to
|
|
495
|
+
// process it with. The processing (compose or transform) determines which actions Slice
|
|
496
|
+
// takes into account when moving the parallel counters forward. When processing for
|
|
497
|
+
// compose, deletes in rhs can be ignored. When processing for transform, inserts in both
|
|
498
|
+
// lhs and rhs can be ignored.
|
|
499
|
+
//
|
|
500
|
+
|
|
501
|
+
alignEdges(rhs: OTArrayLikeResource, st: OTalignEdgesType): void
|
|
502
|
+
{
|
|
503
|
+
let posR: number = 0;
|
|
504
|
+
let posL: number = 0;
|
|
505
|
+
let iL: number = 0;
|
|
506
|
+
let newA: OTEdits = [];
|
|
507
|
+
let aAfter: OTSingleArrayEdit = undefined;
|
|
508
|
+
let aL: OTSingleArrayEdit = undefined;
|
|
509
|
+
|
|
510
|
+
for (let iR: number = 0; iR < rhs.length; iR++)
|
|
511
|
+
{
|
|
512
|
+
let aR: OTSingleArrayEdit = rhs.edits[iR];
|
|
513
|
+
|
|
514
|
+
switch (aR[0])
|
|
515
|
+
{
|
|
516
|
+
case OpCursor:
|
|
517
|
+
break;
|
|
518
|
+
case OpInsert:
|
|
519
|
+
break;
|
|
520
|
+
case OpDelete:
|
|
521
|
+
posR += aR[1];
|
|
522
|
+
break;
|
|
523
|
+
case OpSet:
|
|
524
|
+
posR += aR[1];
|
|
525
|
+
break;
|
|
526
|
+
case OpRetain:
|
|
527
|
+
posR += aR[1];
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Advance iL/posL to equal to posR
|
|
532
|
+
while (posL < posR && (aAfter != undefined || iL < this.length))
|
|
533
|
+
{
|
|
534
|
+
if (aAfter == undefined)
|
|
535
|
+
{
|
|
536
|
+
aL = this.edits[iL];
|
|
537
|
+
newA.push(aL);
|
|
538
|
+
iL++;
|
|
539
|
+
}
|
|
540
|
+
else
|
|
541
|
+
{
|
|
542
|
+
aL = aAfter;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
switch (aL[0])
|
|
546
|
+
{
|
|
547
|
+
case OpCursor:
|
|
548
|
+
break;
|
|
549
|
+
case OpInsert:
|
|
550
|
+
if (st == OTalignEdgesType.AlignForCompose) posL += aL[1];
|
|
551
|
+
break;
|
|
552
|
+
case OpDelete:
|
|
553
|
+
if (st == OTalignEdgesType.AlignForTransform) posL += aL[1];
|
|
554
|
+
break;
|
|
555
|
+
case OpSet:
|
|
556
|
+
posL += aL[1];
|
|
557
|
+
break;
|
|
558
|
+
case OpRetain:
|
|
559
|
+
posL += aL[1];
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Split this one if it spans boundary
|
|
564
|
+
if (posL > posR)
|
|
565
|
+
{
|
|
566
|
+
let nRight: number = posL - posR;
|
|
567
|
+
let nLeft: number = aL[1] - nRight;
|
|
568
|
+
|
|
569
|
+
aAfter = this.editor.copyWithSubstr(aL, nLeft, nRight);
|
|
570
|
+
this.editor.substr(aL, 0, nLeft);
|
|
571
|
+
newA.push(aAfter);
|
|
572
|
+
posL = posR;
|
|
573
|
+
}
|
|
574
|
+
else
|
|
575
|
+
aAfter = undefined;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Append any we missed
|
|
580
|
+
this.moveEdits(newA, iL);
|
|
581
|
+
|
|
582
|
+
this.edits = newA;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
getCursorCache(): any
|
|
586
|
+
{
|
|
587
|
+
let cursorCache: any = { };
|
|
588
|
+
|
|
589
|
+
for (let i: number = 0; i < this.length; i++)
|
|
590
|
+
{
|
|
591
|
+
let a: OTSingleArrayEdit = this.edits[i];
|
|
592
|
+
|
|
593
|
+
if (a[0] == OpCursor && a[2] != null)
|
|
594
|
+
cursorCache[a[2]] = '';
|
|
595
|
+
}
|
|
596
|
+
return cursorCache;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Function: compose
|
|
600
|
+
//
|
|
601
|
+
// Description:
|
|
602
|
+
// compose the current action with the action passed in. This alters the current action.
|
|
603
|
+
//
|
|
604
|
+
// Basic structure is to walk through the RHS list of actions, processing each one in turn.
|
|
605
|
+
// That then drives the walk through the left hand side and the necessary edits. I use
|
|
606
|
+
// "posR" and "posL" to work through equivalent positions in the two strings being edited.
|
|
607
|
+
// Deletions in the LHS don't effect posL because they don't show up in the input string to RHS.
|
|
608
|
+
// Similarly, insertions in the RHS don't effect posR since they have no equivalent string location
|
|
609
|
+
// in the LHS. (Note transform follows similar structure but different logic for how posR and posL
|
|
610
|
+
// track each other since in that case they are effectively referencing the same input string.)
|
|
611
|
+
//
|
|
612
|
+
compose(rhs: OTArrayLikeResource): void
|
|
613
|
+
{
|
|
614
|
+
let cursorCache: any = rhs.getCursorCache();
|
|
615
|
+
|
|
616
|
+
if (this.length == 0)
|
|
617
|
+
{
|
|
618
|
+
this.edits = rhs.edits.map(this.editor.copy, this.editor);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
else if (rhs.edits.length == 0)
|
|
622
|
+
return;
|
|
623
|
+
|
|
624
|
+
if (this.finalLength() != rhs.originalLength())
|
|
625
|
+
{
|
|
626
|
+
console.log("Logic Failure: compose: Bases Inconsistent.");
|
|
627
|
+
throw("Logic Failure: compose: Bases Inconsistent.");
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Break overlapping segments before start to simplify logic below.
|
|
631
|
+
this.alignEdges(rhs, OTalignEdgesType.AlignForCompose);
|
|
632
|
+
|
|
633
|
+
// Iterate with parallel position markers in two arrays
|
|
634
|
+
let posR: number = 0;
|
|
635
|
+
let posL: number = 0;
|
|
636
|
+
let iL: number = 0;
|
|
637
|
+
let bDone: boolean;
|
|
638
|
+
let newA: OTSingleArrayEdit[] = [];
|
|
639
|
+
|
|
640
|
+
for (let iR: number = 0; iR < rhs.length; iR++)
|
|
641
|
+
{
|
|
642
|
+
let aR: OTSingleArrayEdit = rhs.edits[iR];
|
|
643
|
+
|
|
644
|
+
switch (aR[0])
|
|
645
|
+
{
|
|
646
|
+
case OpRetain:
|
|
647
|
+
posR += aR[1];
|
|
648
|
+
break;
|
|
649
|
+
|
|
650
|
+
case OpSet:
|
|
651
|
+
case OpDelete:
|
|
652
|
+
case OpInsert:
|
|
653
|
+
case OpCursor:
|
|
654
|
+
// Advance to cursor location
|
|
655
|
+
bDone = false;
|
|
656
|
+
while (!bDone && iL < this.length)
|
|
657
|
+
{
|
|
658
|
+
let aL: OTSingleArrayEdit = this.edits[iL];
|
|
659
|
+
|
|
660
|
+
switch (aL[0])
|
|
661
|
+
{
|
|
662
|
+
case OpCursor:
|
|
663
|
+
// Only copy old cursor locations if they aren't empty and aren't duplicated in this rhs.
|
|
664
|
+
if (aL[2] != '' && cursorCache[aL[2]] === undefined)
|
|
665
|
+
newA.push(aL);
|
|
666
|
+
iL++;
|
|
667
|
+
break;
|
|
668
|
+
case OpSet:
|
|
669
|
+
case OpRetain:
|
|
670
|
+
case OpInsert:
|
|
671
|
+
if (posL == posR)
|
|
672
|
+
bDone = true;
|
|
673
|
+
else
|
|
674
|
+
{
|
|
675
|
+
posL += aL[1];
|
|
676
|
+
newA.push(aL);
|
|
677
|
+
iL++;
|
|
678
|
+
}
|
|
679
|
+
break;
|
|
680
|
+
case OpDelete:
|
|
681
|
+
newA.push(aL);
|
|
682
|
+
iL++; // Move past since deletes are not referenced by RHS
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (aR[0] == OpDelete)
|
|
688
|
+
{
|
|
689
|
+
// Remove sequence of cursor, insert, retains, sets, replaced by delete.
|
|
690
|
+
// Note that insert/delete cancel each other out, so there is a bit of complexity there.
|
|
691
|
+
let nChange: number = aR[1];
|
|
692
|
+
let nRemain: number = aR[1];
|
|
693
|
+
for (; nChange > 0 && iL < this.length; iL++)
|
|
694
|
+
{
|
|
695
|
+
let aL: OTSingleArrayEdit = this.edits[iL];
|
|
696
|
+
|
|
697
|
+
switch (aL[0])
|
|
698
|
+
{
|
|
699
|
+
case OpCursor:
|
|
700
|
+
// Only copy old cursor locations if they aren't empty and aren't duplicated in this rhs.
|
|
701
|
+
if (aL[2] != '' && cursorCache[aL[2]] === undefined)
|
|
702
|
+
newA.push(aL);
|
|
703
|
+
break;
|
|
704
|
+
case OpDelete:
|
|
705
|
+
newA.push(aL);
|
|
706
|
+
break;
|
|
707
|
+
case OpSet:
|
|
708
|
+
case OpRetain:
|
|
709
|
+
case OpInsert:
|
|
710
|
+
nRemain -= aL[0] == OpInsert ? aL[1] : 0;
|
|
711
|
+
nChange -= aL[1];
|
|
712
|
+
// Don't copy into new array
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Now add in the delete
|
|
718
|
+
if (nRemain > 0)
|
|
719
|
+
newA.push([ OpDelete, nRemain, '' ]);
|
|
720
|
+
}
|
|
721
|
+
else if (aR[0] == OpSet)
|
|
722
|
+
{
|
|
723
|
+
// Process sequence of cursor, insert, retains, sets
|
|
724
|
+
let nChange: number = aR[1];
|
|
725
|
+
for (; nChange > 0 && iL < this.length; iL++)
|
|
726
|
+
{
|
|
727
|
+
let aL: OTSingleArrayEdit = this.edits[iL];
|
|
728
|
+
let opL: number = OpInsert;
|
|
729
|
+
|
|
730
|
+
switch (aL[0])
|
|
731
|
+
{
|
|
732
|
+
case OpCursor:
|
|
733
|
+
// Only copy old cursor locations if they aren't empty and aren't duplicated in this rhs.
|
|
734
|
+
if (aL[2] != '' && cursorCache[aL[2]] === undefined)
|
|
735
|
+
newA.push(aL);
|
|
736
|
+
break;
|
|
737
|
+
case OpDelete:
|
|
738
|
+
newA.push(aL);
|
|
739
|
+
break;
|
|
740
|
+
case OpSet:
|
|
741
|
+
case OpRetain:
|
|
742
|
+
opL = OpSet;
|
|
743
|
+
// fallthrough
|
|
744
|
+
case OpInsert:
|
|
745
|
+
// A Set composed with Insert becomes Insert of Set content
|
|
746
|
+
this.editor.substrFromRaw(aL, aR[1]-nChange, aL[1], aR[2]);
|
|
747
|
+
aL[0] = opL;
|
|
748
|
+
nChange -= aL[1];
|
|
749
|
+
newA.push(aL);
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
else // cursor, insert
|
|
755
|
+
{
|
|
756
|
+
// Add in the RHS operation at proper location
|
|
757
|
+
newA.push(this.editor.copy(aR));
|
|
758
|
+
}
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// copy any remaining actions, excluding cursors duplicated in rhs
|
|
764
|
+
this.moveEdits(newA, iL, this.length-1,
|
|
765
|
+
function (e: OTSingleArrayEdit)
|
|
766
|
+
{ return (e[0] != OpCursor) || (e[2] != '' && cursorCache[e[2]] === undefined) } );
|
|
767
|
+
|
|
768
|
+
this.edits = newA;
|
|
769
|
+
|
|
770
|
+
this.coalesce();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
performTransformReorder(bForceRetainBeforeInsert: boolean, newA: OTEdits, iBegin: number, iEnd: number): void
|
|
774
|
+
{
|
|
775
|
+
if (iBegin < 0 || iBegin > iEnd) return;
|
|
776
|
+
if (bForceRetainBeforeInsert)
|
|
777
|
+
{
|
|
778
|
+
this.moveEdits(newA, iBegin, iEnd, this.editor.isTmpRetainOrDelete);
|
|
779
|
+
this.moveEdits(newA, iBegin, iEnd, this.editor.isNotTmpRetainOrDelete); // Is Insert or Cursor
|
|
780
|
+
}
|
|
781
|
+
else
|
|
782
|
+
{
|
|
783
|
+
this.moveEdits(newA, iBegin, iEnd, this.editor.isNotTmpRetainOrDelete); // Is Insert or Cursor
|
|
784
|
+
this.moveEdits(newA, iBegin, iEnd, this.editor.isTmpRetainOrDelete);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Function: normalizeNewRetainsAfterTransform
|
|
789
|
+
//
|
|
790
|
+
// Description:
|
|
791
|
+
// Helper function for transform() that does a post-processing pass to ensure that all
|
|
792
|
+
// Retains are properly ordered with respect to Inserts that occur at the same location
|
|
793
|
+
// (either before or after, depending on whether we are transforming based on server or client side).
|
|
794
|
+
// This ensures that the transform process is not sensitive to precise ordering of Inserts and
|
|
795
|
+
// Retains (since that ordering doesn't actually change the semantics of the edit performed and
|
|
796
|
+
// therefore should not result in a difference in processing here). And yes, it's a subtle issue
|
|
797
|
+
// that may not actually occur in real edits produced by some particular editor but does arise when
|
|
798
|
+
// testing against randomly generated edit streams.
|
|
799
|
+
//
|
|
800
|
+
// A side consequence is also normalizing the ordering of inserts and deletes which also doesn't
|
|
801
|
+
// change the semantics of the edit but ensures we properly detect conflicting insertions.
|
|
802
|
+
//
|
|
803
|
+
// The way to think of this algorithm is that Set's and Retains (pre-existing, not new TmpRetains) form
|
|
804
|
+
// hard boundaries in the ordering. The series of Cursor/TmpRetain/Insert/Deletes between Sets and Retains
|
|
805
|
+
// are re-ordered by this algorithm. TmpRetain's get pushed to the front or the back depending on the bForce
|
|
806
|
+
// flag passed in (which reflects which operation had precedence).
|
|
807
|
+
//
|
|
808
|
+
normalizeNewRetainsAfterTransform(bForceRetainBeforeInsert: boolean): void
|
|
809
|
+
{
|
|
810
|
+
if (this.length == 0)
|
|
811
|
+
return;
|
|
812
|
+
|
|
813
|
+
let i: number = 0;
|
|
814
|
+
let newA: OTSingleArrayEdit[] = [];
|
|
815
|
+
let iLastEdge: number = 0;
|
|
816
|
+
|
|
817
|
+
// Normalize ordering for newly insert retains so they are properly ordered
|
|
818
|
+
// with respect to inserts occurring at the same location.
|
|
819
|
+
for (i = 0; i < this.length; i++)
|
|
820
|
+
{
|
|
821
|
+
let a: OTSingleArrayEdit = this.edits[i];
|
|
822
|
+
if (a[0] == OpSet || a[0] == OpRetain)
|
|
823
|
+
{
|
|
824
|
+
this.performTransformReorder(bForceRetainBeforeInsert, newA, iLastEdge, i-1);
|
|
825
|
+
newA.push(a);
|
|
826
|
+
iLastEdge = i+1;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
this.performTransformReorder(bForceRetainBeforeInsert, newA, iLastEdge, this.length-1);
|
|
830
|
+
|
|
831
|
+
// One last time to switch TmpRetain to Retain
|
|
832
|
+
for (i = 0; i < newA.length; i++)
|
|
833
|
+
if ((newA[i])[0] == OpTmpRetain)
|
|
834
|
+
(newA[i])[0] = OpRetain;
|
|
835
|
+
this.edits = newA;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
transform(prior: OTArrayLikeResource, bPriorIsService: boolean): void
|
|
839
|
+
{
|
|
840
|
+
if (this.length == 0 || prior.length == 0)
|
|
841
|
+
return;
|
|
842
|
+
|
|
843
|
+
// Validate
|
|
844
|
+
this.basesConsistent(prior);
|
|
845
|
+
|
|
846
|
+
// Break overlapping segments before start to simplify logic below.
|
|
847
|
+
this.alignEdges(prior, OTalignEdgesType.AlignForTransform);
|
|
848
|
+
|
|
849
|
+
let posR: number = 0; // These walk in parallel across the consistent base strings (only retains, sets and deletes count)
|
|
850
|
+
let posL: number = 0;
|
|
851
|
+
let iL: number = 0;
|
|
852
|
+
let bDone: boolean;
|
|
853
|
+
let newA: OTEdits = [];
|
|
854
|
+
|
|
855
|
+
for (let iR: number = 0; iR < prior.length; iR++)
|
|
856
|
+
{
|
|
857
|
+
let aR: OTSingleArrayEdit = prior.edits[iR];
|
|
858
|
+
|
|
859
|
+
switch (aR[0])
|
|
860
|
+
{
|
|
861
|
+
case OpCursor:
|
|
862
|
+
// No-op
|
|
863
|
+
break;
|
|
864
|
+
case OpInsert:
|
|
865
|
+
{
|
|
866
|
+
// Converts to a retain.
|
|
867
|
+
// Need to find spot to insert retain. After loop, iL will contain location
|
|
868
|
+
for (; iL < this.length; iL++)
|
|
869
|
+
{
|
|
870
|
+
if (posR == posL)
|
|
871
|
+
break;
|
|
872
|
+
|
|
873
|
+
let aL: OTSingleArrayEdit = this.edits[iL];
|
|
874
|
+
if (this.editor.isIgnore(aL))
|
|
875
|
+
continue;
|
|
876
|
+
if (aL[0] != OpCursor && aL[0] != OpInsert)
|
|
877
|
+
posL += aL[1];
|
|
878
|
+
newA.push(aL);
|
|
879
|
+
}
|
|
880
|
+
let nRetain: number = aR[1];
|
|
881
|
+
newA.push([ OpTmpRetain, nRetain, '' ]);
|
|
882
|
+
posR += nRetain;
|
|
883
|
+
posL += nRetain;
|
|
884
|
+
}
|
|
885
|
+
break;
|
|
886
|
+
case OpSet:
|
|
887
|
+
// Somewhat unintuitively, if prior is *not* service, then it will actually get applied *after*
|
|
888
|
+
// the service instance of OpSet and so should take precedence. Therefore if prior is not service,
|
|
889
|
+
// we need to go through and convert "OpSets" that overlap to be this content. If prior is service,
|
|
890
|
+
// we can just treat them as "retains" since they have no effect on our operations.
|
|
891
|
+
if (bPriorIsService)
|
|
892
|
+
posR += aR[1];
|
|
893
|
+
else
|
|
894
|
+
{
|
|
895
|
+
let nRemaining: number = aR[1];
|
|
896
|
+
while (nRemaining > 0 && iL < this.length)
|
|
897
|
+
{
|
|
898
|
+
let aL: OTSingleArrayEdit = this.edits[iL];
|
|
899
|
+
if (this.editor.isIgnore(aL))
|
|
900
|
+
{
|
|
901
|
+
iL++;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
let valL: number = aL[1];
|
|
905
|
+
|
|
906
|
+
if (aL[0] == OpCursor || aL[0] == OpInsert)
|
|
907
|
+
{
|
|
908
|
+
iL++;
|
|
909
|
+
newA.push(aL);
|
|
910
|
+
}
|
|
911
|
+
else
|
|
912
|
+
{
|
|
913
|
+
if (posR >= posL+valL)
|
|
914
|
+
{
|
|
915
|
+
// Not there yet
|
|
916
|
+
posL += valL;
|
|
917
|
+
iL++;
|
|
918
|
+
newA.push(aL);
|
|
919
|
+
}
|
|
920
|
+
else
|
|
921
|
+
{
|
|
922
|
+
if (aL[0] == OpDelete || aL[0] == OpRetain)
|
|
923
|
+
{
|
|
924
|
+
if (valL <= nRemaining)
|
|
925
|
+
{
|
|
926
|
+
posR += valL;
|
|
927
|
+
posL += valL;
|
|
928
|
+
nRemaining -= valL;
|
|
929
|
+
iL++;
|
|
930
|
+
newA.push(aL);
|
|
931
|
+
}
|
|
932
|
+
else
|
|
933
|
+
{
|
|
934
|
+
// Not subsumed, but means that I didn't encounter an OpSet
|
|
935
|
+
posR += nRemaining;
|
|
936
|
+
nRemaining = 0;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
else // OpSet
|
|
940
|
+
{
|
|
941
|
+
if (aL[1] <= nRemaining)
|
|
942
|
+
{
|
|
943
|
+
posR += valL;
|
|
944
|
+
posL += valL;
|
|
945
|
+
this.editor.substrFromRaw(aL, aR[1] - nRemaining, valL, aR[2]);
|
|
946
|
+
nRemaining -= valL;
|
|
947
|
+
iL++;
|
|
948
|
+
newA.push(aL);
|
|
949
|
+
}
|
|
950
|
+
else
|
|
951
|
+
{
|
|
952
|
+
// don't advance posL or iL because we will re-process the left over
|
|
953
|
+
// part for the next action. Simply edit the data in place.
|
|
954
|
+
// Set [0, nRemaining] of aL.Data to [aR[1]-nRemaining, nRemaining]
|
|
955
|
+
//aL.Data.delete(0, nRemaining);
|
|
956
|
+
//aL.Data.InsertValue(0, aR.Data, aR[1]-nRemaining, nRemaining);
|
|
957
|
+
aL[2] = aR[2].substr(aR[1] - nRemaining) + aL[2].substr(nRemaining);
|
|
958
|
+
posR += nRemaining;
|
|
959
|
+
nRemaining = 0;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
break;
|
|
967
|
+
case OpDelete:
|
|
968
|
+
{
|
|
969
|
+
let nRemaining: number = aR[1];
|
|
970
|
+
let nDelay: number = 0;
|
|
971
|
+
let iDelay: number;
|
|
972
|
+
|
|
973
|
+
// Retains, sets and deletes are subsumed by prior deletes
|
|
974
|
+
for (; nRemaining > 0 && iL < this.length; iL++)
|
|
975
|
+
{
|
|
976
|
+
let aL: OTSingleArrayEdit = this.edits[iL];
|
|
977
|
+
if (this.editor.isIgnore(aL))
|
|
978
|
+
{
|
|
979
|
+
if (nDelay > 0)
|
|
980
|
+
nDelay++;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (aL[0] == OpCursor || aL[0] == OpInsert)
|
|
985
|
+
{
|
|
986
|
+
if (nDelay == 0)
|
|
987
|
+
iDelay = iL;
|
|
988
|
+
nDelay++;
|
|
989
|
+
}
|
|
990
|
+
else
|
|
991
|
+
{
|
|
992
|
+
if (posR >= posL+aL[1])
|
|
993
|
+
{
|
|
994
|
+
// Go ahead and push any delayed actions
|
|
995
|
+
for (let j: number = iDelay; nDelay > 0; nDelay--, j++)
|
|
996
|
+
{
|
|
997
|
+
let aD: OTSingleArrayEdit = this.edits[j];
|
|
998
|
+
if (! this.editor.isIgnore(aD))
|
|
999
|
+
newA.push(aD);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Prior to the deleted content
|
|
1003
|
+
posL += aL[1];
|
|
1004
|
+
newA.push(aL);
|
|
1005
|
+
}
|
|
1006
|
+
else
|
|
1007
|
+
{
|
|
1008
|
+
// Retain/set/delete is fully subsumed.
|
|
1009
|
+
posR += aL[1];
|
|
1010
|
+
posL += aL[1];
|
|
1011
|
+
nRemaining -= aL[1];
|
|
1012
|
+
this.editor.setIgnore(aL);
|
|
1013
|
+
if (nDelay > 0)
|
|
1014
|
+
nDelay++;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// We want to reprocess any trailing insert/cursors so we recognize conflicting inserts even when
|
|
1020
|
+
// deletes intervene.
|
|
1021
|
+
if (nDelay > 0)
|
|
1022
|
+
iL = iDelay;
|
|
1023
|
+
}
|
|
1024
|
+
break;
|
|
1025
|
+
case OpRetain:
|
|
1026
|
+
// Just advance cursor
|
|
1027
|
+
posR += aR[1];
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
this.moveEdits(newA, iL);
|
|
1033
|
+
this.edits = newA;
|
|
1034
|
+
this.normalizeNewRetainsAfterTransform(bPriorIsService);
|
|
1035
|
+
this.coalesce();
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
//
|
|
1039
|
+
// Function: generateRandom
|
|
1040
|
+
//
|
|
1041
|
+
// Description:
|
|
1042
|
+
// Generate action containing a sequence of retain, insert, delete, cursor with the initial
|
|
1043
|
+
// state of the string being nInitial. Make sure I always generate at least one insert or delete.
|
|
1044
|
+
// Always operate in units of 4 (.123).
|
|
1045
|
+
//
|
|
1046
|
+
generateRandom(nInitial: number, clientID: string): void
|
|
1047
|
+
{
|
|
1048
|
+
// Ensure clean start
|
|
1049
|
+
this.empty();
|
|
1050
|
+
|
|
1051
|
+
// Setup randomizer
|
|
1052
|
+
let nOps: number = 0;
|
|
1053
|
+
let nLen: number;
|
|
1054
|
+
let nBound: number;
|
|
1055
|
+
let s: any;
|
|
1056
|
+
|
|
1057
|
+
while (nInitial > 0 || nOps == 0)
|
|
1058
|
+
{
|
|
1059
|
+
let op: number = randomWithinRange(0, 4);
|
|
1060
|
+
|
|
1061
|
+
nBound = nInitial / TestUnitSize;
|
|
1062
|
+
if (nInitial == 0 && (op == OpDelete || op == OpRetain || op == OpSet))
|
|
1063
|
+
continue;
|
|
1064
|
+
switch (op)
|
|
1065
|
+
{
|
|
1066
|
+
case OpInsert:
|
|
1067
|
+
nOps++;
|
|
1068
|
+
nLen = randomWithinRange(1, 5);
|
|
1069
|
+
s = this.editor.raw.empty();
|
|
1070
|
+
for (let i: number = 0; i < nLen; i++)
|
|
1071
|
+
s = this.editor.raw.append(s, counterValue(this.editor.raw, TestCounter++));
|
|
1072
|
+
nLen *= TestUnitSize;
|
|
1073
|
+
this.edits.push([ OpInsert, nLen, s ]);
|
|
1074
|
+
break;
|
|
1075
|
+
case OpDelete:
|
|
1076
|
+
nOps++;
|
|
1077
|
+
nLen = randomWithinRange(1, nBound > 3 ? nBound / 3 : nBound);
|
|
1078
|
+
nLen *= TestUnitSize;
|
|
1079
|
+
nInitial -= nLen;
|
|
1080
|
+
this.edits.push([ OpDelete, nLen, this.editor.raw.empty() ]);
|
|
1081
|
+
break;
|
|
1082
|
+
case OpCursor:
|
|
1083
|
+
this.edits.push([ OpCursor, 0, clientID ]);
|
|
1084
|
+
break;
|
|
1085
|
+
case OpRetain:
|
|
1086
|
+
nLen = randomWithinRange(1, nBound);
|
|
1087
|
+
nLen *= TestUnitSize;
|
|
1088
|
+
nInitial -= nLen;
|
|
1089
|
+
this.edits.push([ OpRetain, nLen, this.editor.raw.empty() ]);
|
|
1090
|
+
break;
|
|
1091
|
+
case OpSet:
|
|
1092
|
+
nLen = 1;
|
|
1093
|
+
s = this.editor.raw.empty();
|
|
1094
|
+
for (let i: number = 0; i < nLen; i++)
|
|
1095
|
+
this.editor.raw.append(s, counterValue(this.editor.raw, TestCounter++));
|
|
1096
|
+
nLen *= TestUnitSize;
|
|
1097
|
+
nInitial -= nLen;
|
|
1098
|
+
this.edits.push([ OpSet, nLen, s ]);
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Most importantly ensures canonical ordering of inserts and deletes.
|
|
1104
|
+
this.coalesce();
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
export class OTStringResource extends OTArrayLikeResource
|
|
1109
|
+
{
|
|
1110
|
+
static _editor: OTSingleArrayEditor = new OTSingleArrayEditor(new OTStringOperations());
|
|
1111
|
+
constructor(rname: string)
|
|
1112
|
+
{
|
|
1113
|
+
super(OTStringResource._editor, rname);
|
|
1114
|
+
}
|
|
1115
|
+
static factory(rname: string): OTStringResource { return new OTStringResource(rname); }
|
|
1116
|
+
copy(): OTStringResource
|
|
1117
|
+
{
|
|
1118
|
+
let copy: OTStringResource = new OTStringResource(this.resourceName);
|
|
1119
|
+
copy.edits = this.edits.map(copy.editor.copy, copy.editor);
|
|
1120
|
+
return copy;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
export class OTArrayResource extends OTArrayLikeResource
|
|
1125
|
+
{
|
|
1126
|
+
static _editor: OTSingleArrayEditor = new OTSingleArrayEditor(new OTArrayOperations());
|
|
1127
|
+
constructor(rname: string)
|
|
1128
|
+
{
|
|
1129
|
+
super(OTArrayResource._editor, rname);
|
|
1130
|
+
}
|
|
1131
|
+
static factory(rname: string): OTArrayResource { return new OTArrayResource(rname); }
|
|
1132
|
+
|
|
1133
|
+
copy(): OTArrayResource
|
|
1134
|
+
{
|
|
1135
|
+
let copy: OTArrayResource = new OTArrayResource(this.resourceName);
|
|
1136
|
+
copy.edits = this.edits.map(copy.editor.copy, copy.editor);
|
|
1137
|
+
return copy;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function randomWithinRange(nMin: number, nMax: number): number
|
|
1142
|
+
{
|
|
1143
|
+
return nMin + Math.floor(Math.random() * (nMax - nMin + 1));
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function counterValue(ops: IOTArrayLikeOperations, c: number): any
|
|
1147
|
+
{
|
|
1148
|
+
switch (ops.underlyingTypeName())
|
|
1149
|
+
{
|
|
1150
|
+
case 'string':
|
|
1151
|
+
{
|
|
1152
|
+
let a: string[] = new Array(TestUnitSize);
|
|
1153
|
+
a[0] = '.';
|
|
1154
|
+
for (let j: number = 1; j < TestUnitSize; j++, c = Math.floor(c / 10))
|
|
1155
|
+
a[TestUnitSize - j] = "" + (c % 10);
|
|
1156
|
+
return a.join('');
|
|
1157
|
+
}
|
|
1158
|
+
case 'array':
|
|
1159
|
+
{
|
|
1160
|
+
let a: number[] = new Array(TestUnitSize);
|
|
1161
|
+
for (let i: number = 0; i < TestUnitSize; i++, c += 0.1)
|
|
1162
|
+
a[i] = c;
|
|
1163
|
+
return a;
|
|
1164
|
+
}
|
|
1165
|
+
default:
|
|
1166
|
+
throw "counterValue: Unexpected underlying array-like type."
|
|
1167
|
+
}
|
|
1168
|
+
}
|