@bablr/btree 0.4.0 → 0.4.2
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/README.md +13 -12
- package/lib/enhanceable.js +245 -111
- package/lib/index.js +3 -3
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# @bablr/btree
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This library offers a set of functional utilities for working with immutable btrees such as those used in agAST.
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
|
-
|
|
7
|
-
3,
|
|
8
|
-
[['a'], ['b', 'c']],
|
|
9
|
-
]);
|
|
6
|
+
import * as btree from '@bablr/btree';
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
let tree = btree.fromValues([]);
|
|
9
|
+
tree = btree.push('a');
|
|
10
|
+
tree = btree.push('b');
|
|
11
|
+
tree = btree.push('c');
|
|
12
|
+
let tree3 = tree;
|
|
13
|
+
tree = btree.concat(tree, tree);
|
|
14
|
+
tree = btree.concat(tree3, tree);
|
|
15
|
+
|
|
16
|
+
btree.getSize(tree); // 9
|
|
17
|
+
btree.getAt(-2, tree); // 'b'
|
|
18
|
+
[...btree.traverse(tree)]; // ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']
|
|
18
19
|
```
|
package/lib/enhanceable.js
CHANGED
|
@@ -1,23 +1,50 @@
|
|
|
1
|
-
import emptyStack from '@iter-tools/imm-stack';
|
|
2
|
-
|
|
3
1
|
const { isArray } = Array;
|
|
4
2
|
const { freeze } = Object;
|
|
5
3
|
const { isFinite } = Number;
|
|
6
4
|
|
|
7
5
|
export const defaultNodeSize = 8;
|
|
8
6
|
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
export const deepFreeze = (object) => {
|
|
8
|
+
let stack = [object];
|
|
9
|
+
while (stack.length) {
|
|
10
|
+
let item = stack[stack.length - 1];
|
|
11
|
+
stack.pop();
|
|
12
|
+
|
|
13
|
+
for (const value of Object.values(item)) {
|
|
14
|
+
if (value && typeof value === 'object') {
|
|
15
|
+
if (
|
|
16
|
+
!(Array.isArray(value) || [Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
17
|
+
)
|
|
18
|
+
throw new Error();
|
|
19
|
+
stack.push(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Object.freeze(item);
|
|
24
|
+
}
|
|
25
|
+
return object;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const buildModule = (NODE_SIZE = defaultNodeSize, buildStats) => {
|
|
15
29
|
const sumNodes = (nodes) => {
|
|
16
30
|
return nodes.map(getSize).reduce((a, b) => a + b, 0);
|
|
17
31
|
};
|
|
18
32
|
|
|
33
|
+
const getHeight = (tree) => {
|
|
34
|
+
let height = 0;
|
|
35
|
+
let node = tree;
|
|
36
|
+
|
|
37
|
+
while (Array.isArray(node)) {
|
|
38
|
+
height++;
|
|
39
|
+
node = getValues(node)[0];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return height;
|
|
43
|
+
};
|
|
44
|
+
|
|
19
45
|
const treeFrom = (...values) => {
|
|
20
|
-
|
|
46
|
+
validateValues(values);
|
|
47
|
+
if (!buildStats && values.length <= NODE_SIZE) {
|
|
21
48
|
return freeze(values);
|
|
22
49
|
} else {
|
|
23
50
|
let tree = [];
|
|
@@ -28,18 +55,14 @@ export const buildModule = (
|
|
|
28
55
|
}
|
|
29
56
|
};
|
|
30
57
|
|
|
31
|
-
const buildStats = (values) => {
|
|
32
|
-
let sums = values.reduce(statsReducer, getSumsInitial());
|
|
33
|
-
finalizeSums(sums);
|
|
34
|
-
return sums;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
58
|
const treeFromValues = (values) => {
|
|
38
|
-
|
|
59
|
+
validateValues(values);
|
|
60
|
+
|
|
61
|
+
if (!buildStats && values.length <= NODE_SIZE) {
|
|
39
62
|
return isArray(values[0]) ? freeze([sumNodes(values), freeze(values)]) : freeze(values);
|
|
40
63
|
} else {
|
|
41
64
|
if (values.length <= NODE_SIZE) {
|
|
42
|
-
if (
|
|
65
|
+
if (buildStats) {
|
|
43
66
|
return freeze([sumNodes(values), freeze(values), buildStats(values)]);
|
|
44
67
|
} else {
|
|
45
68
|
return freeze([sumNodes(values), freeze(values)]);
|
|
@@ -78,11 +101,11 @@ export const buildModule = (
|
|
|
78
101
|
|
|
79
102
|
let midIndex;
|
|
80
103
|
|
|
81
|
-
if (isLeaf) {
|
|
82
|
-
|
|
83
|
-
} else {
|
|
84
|
-
|
|
85
|
-
}
|
|
104
|
+
// if (isLeaf) {
|
|
105
|
+
midIndex = Math.floor(values.length / 2 + 0.01);
|
|
106
|
+
// } else {
|
|
107
|
+
// midIndex = findBalancePoint(values);
|
|
108
|
+
// }
|
|
86
109
|
|
|
87
110
|
let leftValues = values.slice(0, midIndex);
|
|
88
111
|
let rightValues = values.slice(midIndex);
|
|
@@ -91,32 +114,81 @@ export const buildModule = (
|
|
|
91
114
|
};
|
|
92
115
|
|
|
93
116
|
const setValuesAt = (idx, node, value) => {
|
|
94
|
-
const isLeaf = isLeafNode(node);
|
|
95
117
|
const values = getValues(node);
|
|
96
118
|
|
|
97
119
|
if (!Number.isFinite(idx)) throw new Error();
|
|
98
120
|
|
|
99
|
-
if (!
|
|
121
|
+
if (!value == null) {
|
|
100
122
|
throw new Error();
|
|
101
123
|
}
|
|
102
124
|
|
|
103
|
-
if (isLeaf && isArray(value) && node.length) throw new Error();
|
|
104
|
-
|
|
105
125
|
const newValues = values.slice();
|
|
106
126
|
newValues[idx] = value;
|
|
107
127
|
return treeFromValues(newValues);
|
|
108
128
|
};
|
|
109
129
|
|
|
130
|
+
const concat = (first, second) => {
|
|
131
|
+
if (!isArray(first) || !isArray(second)) throw new Error();
|
|
132
|
+
|
|
133
|
+
let firstHeight = getHeight(first);
|
|
134
|
+
let secondHeight = getHeight(second);
|
|
135
|
+
let firstValues = getValues(first);
|
|
136
|
+
let secondValues = getValues(second);
|
|
137
|
+
|
|
138
|
+
if (!secondValues.length) return first;
|
|
139
|
+
if (!firstValues.length) return second;
|
|
140
|
+
|
|
141
|
+
if (firstHeight === secondHeight) {
|
|
142
|
+
if (firstValues.length + secondValues.length <= NODE_SIZE) {
|
|
143
|
+
return treeFromValues([...firstValues, ...secondValues]);
|
|
144
|
+
} else {
|
|
145
|
+
let { leftValues, rightValues } = splitValues([...firstValues, ...secondValues]);
|
|
146
|
+
return treeFromValues([treeFromValues(leftValues), treeFromValues(rightValues)]);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
let tree = firstHeight <= secondHeight ? second : first;
|
|
150
|
+
let pushout = firstHeight > secondHeight ? second : first;
|
|
151
|
+
|
|
152
|
+
if (!nodeCollapses(pushout)) {
|
|
153
|
+
let targetIndex = firstHeight > secondHeight ? getSize(tree) : 0;
|
|
154
|
+
return addAt(targetIndex, tree, pushout);
|
|
155
|
+
} else {
|
|
156
|
+
let depth = Math.abs(firstHeight - secondHeight) - 1;
|
|
157
|
+
let targetIndex = firstHeight > secondHeight ? getSize(tree) - 1 : 0;
|
|
158
|
+
let targetPath = findPath(targetIndex, tree, depth);
|
|
159
|
+
let lastIndex = targetPath.length - 1;
|
|
160
|
+
let targetNode = getValues(targetPath[lastIndex].node)[targetPath[lastIndex].index];
|
|
161
|
+
let combinedValues =
|
|
162
|
+
firstHeight > secondHeight
|
|
163
|
+
? [...getValues(targetNode), ...getValues(pushout)]
|
|
164
|
+
: [...getValues(pushout), ...getValues(targetNode)];
|
|
165
|
+
if (getValues(pushout).length + getValues(targetNode).length <= NODE_SIZE) {
|
|
166
|
+
return replaceAt(targetIndex, tree, treeFromValues(combinedValues));
|
|
167
|
+
} else {
|
|
168
|
+
let { leftValues, rightValues } = splitValues(combinedValues);
|
|
169
|
+
|
|
170
|
+
if (firstHeight > secondHeight) {
|
|
171
|
+
return replaceAt(targetIndex, addAt(targetIndex + 1, tree, rightValues), leftValues);
|
|
172
|
+
// return addAt(targetIndex + 1, replaceAt(targetIndex, tree, leftValues), rightValues);
|
|
173
|
+
} else {
|
|
174
|
+
// TODO change order of operations like above?
|
|
175
|
+
return addAt(targetIndex, replaceAt(targetIndex, tree, rightValues), leftValues);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
110
182
|
const addAt = (idx, tree, value) => {
|
|
111
183
|
if (idx < 0 || !Number.isFinite(idx)) throw new Error('invalid argument');
|
|
112
184
|
if (!isArray(tree)) throw new Error();
|
|
113
|
-
if (
|
|
185
|
+
if (!value) throw new Error();
|
|
114
186
|
|
|
115
|
-
let path = findPath(idx, tree);
|
|
187
|
+
let path = findPath(idx, tree, getHeight(tree) - getHeight(value) - 1);
|
|
116
188
|
|
|
117
|
-
let
|
|
189
|
+
let pathIdx = path.length - 1;
|
|
190
|
+
let { node, index } = path[pathIdx];
|
|
118
191
|
|
|
119
|
-
// left pushout vs right pushout?
|
|
120
192
|
let pushout = value;
|
|
121
193
|
|
|
122
194
|
let values = getValues(node);
|
|
@@ -128,7 +200,7 @@ export const buildModule = (
|
|
|
128
200
|
let finiteIndex = index === Infinity ? values.length : index;
|
|
129
201
|
|
|
130
202
|
if (!isFinite(finiteIndex)) throw new Error();
|
|
131
|
-
if (values.length +
|
|
203
|
+
if (values.length + (pushout ? 1 : 0) > NODE_SIZE) {
|
|
132
204
|
values.splice(finiteIndex, 0, pushout);
|
|
133
205
|
const { leftValues, rightValues } = splitValues(values);
|
|
134
206
|
|
|
@@ -141,7 +213,7 @@ export const buildModule = (
|
|
|
141
213
|
}
|
|
142
214
|
}
|
|
143
215
|
|
|
144
|
-
if (
|
|
216
|
+
if (pathIdx === 0) {
|
|
145
217
|
if (pushout) {
|
|
146
218
|
return treeFromValues([pushout, node]);
|
|
147
219
|
} else {
|
|
@@ -150,21 +222,21 @@ export const buildModule = (
|
|
|
150
222
|
}
|
|
151
223
|
|
|
152
224
|
const poppedNode = node;
|
|
153
|
-
|
|
154
|
-
({ node, index } = path
|
|
225
|
+
pathIdx--;
|
|
226
|
+
({ node, index } = path[pathIdx]);
|
|
155
227
|
|
|
156
228
|
node = setValuesAt(index, node, poppedNode);
|
|
157
229
|
values = getValues(node);
|
|
158
|
-
path = path.replace({ node, index });
|
|
159
230
|
}
|
|
160
231
|
};
|
|
161
232
|
|
|
162
233
|
const push = (tree, value) => {
|
|
163
|
-
|
|
234
|
+
let size = getSize(tree);
|
|
235
|
+
return size ? addAt(size, tree, value) : treeFromValues([value]);
|
|
164
236
|
};
|
|
165
237
|
|
|
166
238
|
const collapses = (size) => {
|
|
167
|
-
return size
|
|
239
|
+
return size < NODE_SIZE / 2 - 0.01;
|
|
168
240
|
};
|
|
169
241
|
|
|
170
242
|
const nodeCollapses = (node) => {
|
|
@@ -172,25 +244,29 @@ export const buildModule = (
|
|
|
172
244
|
};
|
|
173
245
|
|
|
174
246
|
const nodeCanDonate = (node) => {
|
|
175
|
-
return collapses(getValues(node).length - 1);
|
|
247
|
+
return !collapses(getValues(node).length - 1);
|
|
176
248
|
};
|
|
177
249
|
|
|
178
|
-
const
|
|
179
|
-
|
|
250
|
+
const removeAt = (idx, tree) => {
|
|
251
|
+
if (idx > getSize(tree)) throw new Error('Index exceeds tree bounds');
|
|
180
252
|
|
|
181
|
-
let
|
|
253
|
+
let path = findPath(idx, tree);
|
|
182
254
|
|
|
183
|
-
|
|
184
|
-
let
|
|
185
|
-
|
|
186
|
-
|
|
255
|
+
let pathIdx = path.length - 1;
|
|
256
|
+
let { node, index } = path[pathIdx];
|
|
257
|
+
|
|
258
|
+
const initialValues = [...getValues(node)];
|
|
259
|
+
|
|
260
|
+
initialValues.splice(index, 1);
|
|
261
|
+
|
|
262
|
+
let returnValue = !isFinite(node[0]) ? initialValues : treeFromValues(initialValues);
|
|
187
263
|
|
|
188
264
|
for (;;) {
|
|
189
265
|
let values = getValues(returnValue);
|
|
190
266
|
let adjustSibling = null;
|
|
191
267
|
|
|
192
|
-
if (
|
|
193
|
-
let { node: parentNode, index: parentIndex } = path
|
|
268
|
+
if (pathIdx >= 1 && nodeCollapses(returnValue)) {
|
|
269
|
+
let { node: parentNode, index: parentIndex } = path[pathIdx - 1];
|
|
194
270
|
const prevSibling = getValues(parentNode)[parentIndex - 1];
|
|
195
271
|
const nextSibling = getValues(parentNode)[parentIndex + 1];
|
|
196
272
|
let targetSibling = nodeCanDonate(prevSibling)
|
|
@@ -220,15 +296,12 @@ export const buildModule = (
|
|
|
220
296
|
}
|
|
221
297
|
}
|
|
222
298
|
|
|
223
|
-
if (
|
|
224
|
-
if (isFinite(returnValue[0]) && getSize(returnValue) <= NODE_SIZE) {
|
|
225
|
-
returnValue = returnValue[1].flat();
|
|
226
|
-
}
|
|
299
|
+
if (pathIdx === 0) {
|
|
227
300
|
return returnValue;
|
|
228
301
|
}
|
|
229
302
|
|
|
230
|
-
|
|
231
|
-
({ node, index } = path
|
|
303
|
+
pathIdx--;
|
|
304
|
+
({ node, index } = path[pathIdx]);
|
|
232
305
|
|
|
233
306
|
values = getValues(node).slice();
|
|
234
307
|
|
|
@@ -247,10 +320,30 @@ export const buildModule = (
|
|
|
247
320
|
}
|
|
248
321
|
};
|
|
249
322
|
|
|
323
|
+
const pop = (tree) => {
|
|
324
|
+
return removeAt(-1, tree);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const validateValues = (values) => {
|
|
328
|
+
if (values.length > NODE_SIZE) throw new Error();
|
|
329
|
+
if (!values.length) return;
|
|
330
|
+
|
|
331
|
+
let nodeIsLeaf = !isArray(values[0]);
|
|
332
|
+
for (let value of values) {
|
|
333
|
+
if (!nodeIsLeaf && nodeCollapses(value)) {
|
|
334
|
+
throw new Error('invalid btree: uncollapsed node');
|
|
335
|
+
} else if (!nodeIsLeaf && value == null) {
|
|
336
|
+
throw new Error('invalid btree: nil sibling');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
250
341
|
const isValidNode = (node) => {
|
|
251
342
|
if (!isArray(node)) return false;
|
|
252
343
|
const values = getValues(node);
|
|
253
|
-
|
|
344
|
+
if (!isArray(values) || values.length > NODE_SIZE) return false; // ;
|
|
345
|
+
|
|
346
|
+
return !node[0] || isFinite(node[0]) || ['object', 'string'].includes(typeof node[0]);
|
|
254
347
|
};
|
|
255
348
|
|
|
256
349
|
const assertValidNode = (node) => {
|
|
@@ -264,45 +357,46 @@ export const buildModule = (
|
|
|
264
357
|
|
|
265
358
|
const getSums = (node) => {
|
|
266
359
|
if (!isValidNode(node)) throw new Error();
|
|
267
|
-
|
|
360
|
+
let sums = Number.isFinite(node[0]) ? node[2] : null;
|
|
361
|
+
return sums || (buildStats ? buildStats(getValues(node)) : null);
|
|
268
362
|
};
|
|
269
363
|
|
|
270
364
|
const setValues = (node, values) => {
|
|
271
|
-
if (
|
|
365
|
+
if (isFinite(node[0]) || buildStats) {
|
|
366
|
+
return treeFromValues(values);
|
|
367
|
+
}
|
|
272
368
|
|
|
273
|
-
|
|
274
|
-
};
|
|
369
|
+
validateValues(values);
|
|
275
370
|
|
|
276
|
-
|
|
277
|
-
return !isArray(getValues(node)[0]);
|
|
371
|
+
return freeze(values);
|
|
278
372
|
};
|
|
279
373
|
|
|
280
374
|
function* traverse(tree) {
|
|
281
|
-
let states =
|
|
375
|
+
let states = [{ node: tree, i: 0 }];
|
|
282
376
|
|
|
283
377
|
assertValidNode(tree);
|
|
284
378
|
|
|
285
|
-
stack: while (states.
|
|
286
|
-
const s = states.
|
|
379
|
+
stack: while (states.length) {
|
|
380
|
+
const s = states[states.length - 1];
|
|
287
381
|
const { node } = s;
|
|
288
|
-
const isLeaf = isLeafNode(node);
|
|
289
382
|
|
|
290
383
|
const values = getValues(node);
|
|
291
384
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
} else {
|
|
297
|
-
for (let { i } = s; s.i < values.length; ) {
|
|
298
|
-
const node = values[i];
|
|
385
|
+
for (let { i } = s; s.i < values.length; ) {
|
|
386
|
+
const value = values[i];
|
|
387
|
+
if (isArray(value)) {
|
|
388
|
+
let node = value;
|
|
299
389
|
assertValidNode(node);
|
|
300
|
-
states
|
|
390
|
+
states.push({ node, i: 0 });
|
|
301
391
|
i = ++s.i;
|
|
302
392
|
continue stack;
|
|
393
|
+
} else {
|
|
394
|
+
yield value;
|
|
395
|
+
i = ++s.i;
|
|
303
396
|
}
|
|
304
397
|
}
|
|
305
|
-
|
|
398
|
+
|
|
399
|
+
states.pop();
|
|
306
400
|
}
|
|
307
401
|
}
|
|
308
402
|
|
|
@@ -318,65 +412,103 @@ export const buildModule = (
|
|
|
318
412
|
}
|
|
319
413
|
};
|
|
320
414
|
|
|
321
|
-
const findPath = (idx, tree) => {
|
|
415
|
+
const findPath = (idx, tree, depth = Infinity) => {
|
|
322
416
|
if (idx == null) throw new Error();
|
|
417
|
+
if (tree && !isArray(tree)) throw new Error();
|
|
418
|
+
|
|
419
|
+
let path = [];
|
|
420
|
+
let node = tree;
|
|
421
|
+
|
|
422
|
+
if (isArray(idx)) {
|
|
423
|
+
for (let seg of idx) {
|
|
424
|
+
let index = typeof seg !== 'object' ? seg : seg.index;
|
|
425
|
+
if (typeof index === 'string') throw new Error();
|
|
426
|
+
if (!isArray(node)) return null;
|
|
427
|
+
let index_ = index < 0 ? getSize(node) + index : index;
|
|
428
|
+
path.push({ index: index_, node });
|
|
429
|
+
node = getValues(node)[index_];
|
|
430
|
+
if (node && !isArray(node)) {
|
|
431
|
+
return freeze(path);
|
|
432
|
+
}
|
|
433
|
+
if (!node) return null;
|
|
434
|
+
}
|
|
323
435
|
|
|
324
|
-
|
|
436
|
+
return freeze(path);
|
|
437
|
+
}
|
|
325
438
|
|
|
326
439
|
let treeSum = getSize(tree);
|
|
327
|
-
let currentIdx = idx < 0 ? treeSum : 0;
|
|
440
|
+
let currentIdx = idx < 0 ? treeSum - 1 : 0;
|
|
328
441
|
let direction = idx < 0 ? -1 : 1;
|
|
329
442
|
let targetIdx = idx < 0 ? treeSum + idx : idx;
|
|
330
443
|
|
|
331
|
-
let node = tree;
|
|
332
444
|
stack: while (node) {
|
|
333
445
|
assertValidNode(node);
|
|
334
446
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
let backwards = idx < 0;
|
|
350
|
-
const increment = backwards ? -1 : 1;
|
|
351
|
-
for (
|
|
352
|
-
let i = backwards ? values.length - 1 : 0;
|
|
353
|
-
backwards ? i >= 0 : i < values.length;
|
|
354
|
-
i += increment
|
|
355
|
-
) {
|
|
356
|
-
candidateNode = values[i];
|
|
447
|
+
const values = getValues(node);
|
|
448
|
+
let candidateNode;
|
|
449
|
+
|
|
450
|
+
let backwards = idx < 0;
|
|
451
|
+
const increment = backwards ? -1 : 1;
|
|
452
|
+
for (
|
|
453
|
+
let i = backwards ? values.length - 1 : 0;
|
|
454
|
+
backwards ? i >= 0 : i < values.length;
|
|
455
|
+
i += increment
|
|
456
|
+
) {
|
|
457
|
+
let value = values[i];
|
|
458
|
+
if (isArray(value) && path.length < depth) {
|
|
459
|
+
candidateNode = value;
|
|
460
|
+
|
|
357
461
|
const sum = getSize(candidateNode);
|
|
358
|
-
const
|
|
359
|
-
if (
|
|
360
|
-
|
|
462
|
+
const nextIndex = currentIdx + sum * direction;
|
|
463
|
+
if (
|
|
464
|
+
(backwards ? nextIndex < targetIdx : nextIndex > targetIdx) ||
|
|
465
|
+
(backwards ? nextIndex < 0 : nextIndex >= treeSum)
|
|
466
|
+
) {
|
|
467
|
+
path.push({ index: i, node });
|
|
361
468
|
node = candidateNode;
|
|
362
469
|
continue stack;
|
|
363
470
|
} else {
|
|
364
471
|
currentIdx += sum * direction;
|
|
365
472
|
}
|
|
473
|
+
} else {
|
|
474
|
+
const sum = getSize(value);
|
|
475
|
+
const nextIndex = currentIdx + sum * direction;
|
|
476
|
+
if (!isFinite(targetIdx)) {
|
|
477
|
+
path.push({ index: targetIdx, node });
|
|
478
|
+
|
|
479
|
+
return freeze(path);
|
|
480
|
+
} else if (backwards ? nextIndex < targetIdx : nextIndex > targetIdx) {
|
|
481
|
+
path.push({ index: i, node });
|
|
482
|
+
|
|
483
|
+
return freeze(path);
|
|
484
|
+
} else if (
|
|
485
|
+
backwards
|
|
486
|
+
? nextIndex < targetIdx || nextIndex < 0
|
|
487
|
+
: nextIndex > targetIdx || nextIndex >= treeSum
|
|
488
|
+
) {
|
|
489
|
+
break;
|
|
490
|
+
} else {
|
|
491
|
+
currentIdx += direction * sum;
|
|
492
|
+
}
|
|
366
493
|
}
|
|
367
494
|
}
|
|
495
|
+
|
|
496
|
+
path.push({ index: backwards ? -Infinity : Infinity, node });
|
|
497
|
+
|
|
498
|
+
return freeze(path);
|
|
368
499
|
}
|
|
369
500
|
|
|
370
501
|
return null;
|
|
371
502
|
};
|
|
372
503
|
|
|
373
504
|
const getAt = (idx, tree) => {
|
|
374
|
-
const
|
|
375
|
-
|
|
505
|
+
const path = findPath(idx, tree);
|
|
506
|
+
let seg = path && path[path.length - 1];
|
|
507
|
+
return seg && getValues(seg.node)[seg.index];
|
|
376
508
|
};
|
|
377
509
|
|
|
378
510
|
const replaceAt = (idx, tree, value) => {
|
|
379
|
-
let path = findPath(idx, tree);
|
|
511
|
+
let path = findPath(idx, tree, getHeight(tree) - getHeight(value) - 1);
|
|
380
512
|
|
|
381
513
|
if (getSize(tree) < idx) {
|
|
382
514
|
throw new Error('Cannot add past the end of a list');
|
|
@@ -384,16 +516,17 @@ export const buildModule = (
|
|
|
384
516
|
return addAt(idx, tree, value);
|
|
385
517
|
}
|
|
386
518
|
|
|
387
|
-
let
|
|
519
|
+
let pathIndex = path.length - 1;
|
|
520
|
+
let { node, index } = path[pathIndex];
|
|
388
521
|
|
|
389
522
|
let returnValue = setValuesAt(index, node, value);
|
|
390
523
|
|
|
391
524
|
for (;;) {
|
|
392
|
-
({ node, index } = path
|
|
525
|
+
({ node, index } = path[pathIndex]);
|
|
393
526
|
|
|
394
|
-
if (
|
|
395
|
-
|
|
396
|
-
({ node, index } = path
|
|
527
|
+
if (pathIndex > 0) {
|
|
528
|
+
pathIndex--;
|
|
529
|
+
({ node, index } = path[pathIndex]);
|
|
397
530
|
|
|
398
531
|
returnValue = setValuesAt(index, node, returnValue);
|
|
399
532
|
} else {
|
|
@@ -414,14 +547,15 @@ export const buildModule = (
|
|
|
414
547
|
nodeCollapses,
|
|
415
548
|
nodeCanDonate,
|
|
416
549
|
pop,
|
|
550
|
+
removeAt,
|
|
417
551
|
push,
|
|
418
552
|
addAt,
|
|
553
|
+
concat,
|
|
419
554
|
isValidNode,
|
|
420
555
|
assertValidNode,
|
|
421
556
|
getValues,
|
|
422
557
|
getSums,
|
|
423
558
|
setValues,
|
|
424
|
-
isLeafNode,
|
|
425
559
|
traverse,
|
|
426
560
|
getSize,
|
|
427
561
|
findPath,
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { defaultNodeSize, buildModule } from './enhanceable.js';
|
|
1
|
+
import { defaultNodeSize, deepFreeze, buildModule } from './enhanceable.js';
|
|
2
2
|
|
|
3
|
-
export { defaultNodeSize };
|
|
3
|
+
export { defaultNodeSize, deepFreeze };
|
|
4
4
|
|
|
5
5
|
export const {
|
|
6
6
|
btreeFrom,
|
|
@@ -14,13 +14,13 @@ export const {
|
|
|
14
14
|
nodeCanDonate,
|
|
15
15
|
pop,
|
|
16
16
|
push,
|
|
17
|
+
concat,
|
|
17
18
|
addAt,
|
|
18
19
|
isValidNode,
|
|
19
20
|
assertValidNode,
|
|
20
21
|
getValues,
|
|
21
22
|
getSums,
|
|
22
23
|
setValues,
|
|
23
|
-
isLeafNode,
|
|
24
24
|
traverse,
|
|
25
25
|
getSize,
|
|
26
26
|
findPath,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bablr/btree",
|
|
3
3
|
"description": "Functional utilities for working with btrees such as those used in agAST",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"author": "Conrad Buck<conartist6@gmail.com>",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -15,9 +15,7 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"test": "mocha test/*.test.js"
|
|
17
17
|
},
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"@iter-tools/imm-stack": "1.1.0"
|
|
20
|
-
},
|
|
18
|
+
"dependencies": {},
|
|
21
19
|
"devDependencies": {
|
|
22
20
|
"@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#c97bfa4b3663f8378e9b3e42bb5a41e685406cf9",
|
|
23
21
|
"enhanced-resolve": "^5.12.0",
|
|
@@ -29,7 +27,10 @@
|
|
|
29
27
|
"mocha": "10.7.3",
|
|
30
28
|
"prettier": "^2.6.2"
|
|
31
29
|
},
|
|
32
|
-
"repository":
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/bablr-lang/btree.git"
|
|
33
|
+
},
|
|
33
34
|
"homepage": "https://github.com/bablr-lang/btree",
|
|
34
35
|
"license": "MIT"
|
|
35
36
|
}
|