@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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/all/all.d.ts +18 -0
  4. package/dist/baseclient.js +9567 -0
  5. package/dist/baseclient.js.map +1 -0
  6. package/dist/context/all.d.ts +1 -0
  7. package/dist/context/context.d.ts +13 -0
  8. package/dist/filterexpr/all.d.ts +1 -0
  9. package/dist/filterexpr/filterexpr.d.ts +64 -0
  10. package/dist/fsm/all.d.ts +1 -0
  11. package/dist/fsm/fsm.d.ts +118 -0
  12. package/dist/logabstract/all.d.ts +1 -0
  13. package/dist/logabstract/log.d.ts +26 -0
  14. package/dist/logclient/all.d.ts +1 -0
  15. package/dist/logclient/log.d.ts +6 -0
  16. package/dist/ot-editutil/all.d.ts +2 -0
  17. package/dist/ot-editutil/oteditutil.d.ts +14 -0
  18. package/dist/ot-editutil/otmaputil.d.ts +21 -0
  19. package/dist/ot-js/all.d.ts +9 -0
  20. package/dist/ot-js/otarray.d.ts +111 -0
  21. package/dist/ot-js/otclientengine.d.ts +38 -0
  22. package/dist/ot-js/otcomposite.d.ts +37 -0
  23. package/dist/ot-js/otcounter.d.ts +17 -0
  24. package/dist/ot-js/otengine.d.ts +22 -0
  25. package/dist/ot-js/otmap.d.ts +19 -0
  26. package/dist/ot-js/otserverengine.d.ts +38 -0
  27. package/dist/ot-js/otsession.d.ts +111 -0
  28. package/dist/ot-js/ottypes.d.ts +29 -0
  29. package/dist/poly/all.d.ts +15 -0
  30. package/dist/poly/blend.d.ts +1 -0
  31. package/dist/poly/boundbox.d.ts +16 -0
  32. package/dist/poly/cartesian.d.ts +5 -0
  33. package/dist/poly/graham-scan.d.ts +8 -0
  34. package/dist/poly/hash.d.ts +1 -0
  35. package/dist/poly/matrix.d.ts +24 -0
  36. package/dist/poly/minbound.d.ts +1 -0
  37. package/dist/poly/poly.d.ts +52 -0
  38. package/dist/poly/polybin.d.ts +5 -0
  39. package/dist/poly/polylabel.d.ts +7 -0
  40. package/dist/poly/polypack.d.ts +30 -0
  41. package/dist/poly/polyround.d.ts +1 -0
  42. package/dist/poly/polysimplify.d.ts +1 -0
  43. package/dist/poly/quad.d.ts +48 -0
  44. package/dist/poly/selfintersect.d.ts +1 -0
  45. package/dist/poly/shamos.d.ts +1 -0
  46. package/dist/poly/simplify.d.ts +2 -0
  47. package/dist/poly/topo.d.ts +46 -0
  48. package/dist/poly/union.d.ts +48 -0
  49. package/dist/util/all.d.ts +5 -0
  50. package/dist/util/bintrie.d.ts +93 -0
  51. package/dist/util/countedhash.d.ts +19 -0
  52. package/dist/util/gradient.d.ts +15 -0
  53. package/dist/util/indexedarray.d.ts +15 -0
  54. package/dist/util/util.d.ts +68 -0
  55. package/docs/context.md +2 -0
  56. package/docs/fsm.md +243 -0
  57. package/docs/logabstract.md +2 -0
  58. package/docs/logclient.md +2 -0
  59. package/docs/ot-editutil.md +2 -0
  60. package/docs/ot-js.md +95 -0
  61. package/docs/poly.md +103 -0
  62. package/docs/util.md +2 -0
  63. package/lib/all/all.ts +19 -0
  64. package/lib/context/all.ts +1 -0
  65. package/lib/context/context.ts +82 -0
  66. package/lib/filterexpr/all.ts +1 -0
  67. package/lib/filterexpr/filterexpr.ts +625 -0
  68. package/lib/fsm/all.ts +1 -0
  69. package/lib/fsm/fsm.ts +549 -0
  70. package/lib/logabstract/all.ts +1 -0
  71. package/lib/logabstract/log.ts +55 -0
  72. package/lib/logclient/all.ts +1 -0
  73. package/lib/logclient/log.ts +105 -0
  74. package/lib/ot-editutil/all.ts +2 -0
  75. package/lib/ot-editutil/oteditutil.ts +180 -0
  76. package/lib/ot-editutil/otmaputil.ts +209 -0
  77. package/lib/ot-js/all.ts +9 -0
  78. package/lib/ot-js/otarray.ts +1168 -0
  79. package/lib/ot-js/otclientengine.ts +327 -0
  80. package/lib/ot-js/otcomposite.ts +247 -0
  81. package/lib/ot-js/otcounter.ts +145 -0
  82. package/lib/ot-js/otengine.ts +71 -0
  83. package/lib/ot-js/otmap.ts +144 -0
  84. package/lib/ot-js/otserverengine.ts +329 -0
  85. package/lib/ot-js/otsession.ts +199 -0
  86. package/lib/ot-js/ottypes.ts +98 -0
  87. package/lib/poly/all.ts +15 -0
  88. package/lib/poly/blend.ts +27 -0
  89. package/lib/poly/boundbox.ts +102 -0
  90. package/lib/poly/cartesian.ts +130 -0
  91. package/lib/poly/graham-scan.ts +401 -0
  92. package/lib/poly/hash.ts +15 -0
  93. package/lib/poly/matrix.ts +309 -0
  94. package/lib/poly/minbound.ts +211 -0
  95. package/lib/poly/poly.ts +767 -0
  96. package/lib/poly/polybin.ts +218 -0
  97. package/lib/poly/polylabel.ts +204 -0
  98. package/lib/poly/polypack.ts +458 -0
  99. package/lib/poly/polyround.ts +30 -0
  100. package/lib/poly/polysimplify.ts +24 -0
  101. package/lib/poly/quad.ts +272 -0
  102. package/lib/poly/selfintersect.ts +87 -0
  103. package/lib/poly/shamos.ts +297 -0
  104. package/lib/poly/simplify.ts +119 -0
  105. package/lib/poly/topo.ts +525 -0
  106. package/lib/poly/union.ts +371 -0
  107. package/lib/util/all.ts +5 -0
  108. package/lib/util/bintrie.ts +603 -0
  109. package/lib/util/countedhash.ts +83 -0
  110. package/lib/util/gradient.ts +108 -0
  111. package/lib/util/indexedarray.ts +80 -0
  112. package/lib/util/util.ts +695 -0
  113. 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
+ }