@bablr/agast-helpers 0.10.10 → 0.11.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/lib/symbols.js CHANGED
@@ -5,6 +5,7 @@ export const ReferenceTag = Symbol.for('ReferenceTag');
5
5
  export const ShiftTag = Symbol.for('ShiftTag');
6
6
  export const GapTag = Symbol.for('GapTag');
7
7
  export const BindingTag = Symbol.for('BindingTag');
8
+ export const StreamTag = Symbol.for('StreamTag');
8
9
  export const NullTag = Symbol.for('NullTag');
9
10
  export const AttributeDefinition = Symbol.for('AttributeDefinition');
10
11
  export const LiteralTag = Symbol.for('LiteralTag');
package/lib/tags.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { buildModule, defaultNodeSize } from '@bablr/btree/enhanceable';
2
2
  import {
3
+ ReferenceTag,
3
4
  AttributeDefinition,
4
5
  CloseNodeTag,
5
6
  GapTag,
@@ -7,22 +8,59 @@ import {
7
8
  OpenNodeTag,
8
9
  Property,
9
10
  ShiftTag,
11
+ TreeNode,
12
+ GapNode,
13
+ NullNode,
14
+ BindingTag,
15
+ Document,
10
16
  } from './symbols.js';
11
- import { isObject } from './object.js';
12
- import { referenceIsSingular } from './path.js';
13
- import { buildReference } from './builders.js';
17
+ import { freezeRecord, isArray, isObject, isString } from './object.js';
18
+ import { buildReference, parseTag, parseTagType } from './builders.js';
19
+ import { printTag } from './print.js';
20
+ import { arrayValues } from './iterable.js';
14
21
 
15
- const { isArray } = Array;
16
- const { freeze } = Object;
22
+ let { reduce } = Array.prototype;
17
23
 
18
24
  export { defaultNodeSize };
19
25
 
20
- const __getAtName = (name, sortedArray, startIdx = 0, endIdx = sortedArray.length - 1) => {
26
+ export const referenceIsSingular = (ref) => {
27
+ return !ref.flags.array && !['#', '@', '.'].includes(ref.type);
28
+ };
29
+
30
+ export const referencesAreEqual = (a, b) => {
31
+ return (
32
+ a === b ||
33
+ (a.type === b.type &&
34
+ a.name === b.name &&
35
+ a.flags.array === b.flags.array &&
36
+ a.flags.intrinsic === b.flags.intrinsic &&
37
+ a.flags.hasGap === b.flags.hasGap &&
38
+ a.flags.expression === b.flags.expression)
39
+ );
40
+ };
41
+
42
+ export const getTags = (node) => {
43
+ switch (node.type) {
44
+ case TreeNode:
45
+ return node.value.tags;
46
+ case NullNode:
47
+ return node.value.tags;
48
+ case GapNode:
49
+ return node.value.tags;
50
+ case Document:
51
+ return getTags(node.value.tree);
52
+ default:
53
+ throw new Error();
54
+ }
55
+ };
56
+
57
+ const __findStats = (name, sortedArray, startIdx, endIdx) => {
21
58
  if (!sortedArray.length || endIdx < startIdx) return null;
22
59
 
23
60
  let idx = startIdx + Math.floor((endIdx - startIdx) / 2 + 0.1);
24
- let entry = sortedArray[idx];
25
- let { 0: key, 1: value } = entry;
61
+ let skipIdx = idx * 3;
62
+ let key = sortedArray[skipIdx];
63
+ let value = sortedArray[skipIdx + 1];
26
64
 
27
65
  let direction = compareNames(name, key);
28
66
 
@@ -32,21 +70,127 @@ const __getAtName = (name, sortedArray, startIdx = 0, endIdx = sortedArray.lengt
32
70
  if (startIdx === endIdx) return null;
33
71
 
34
72
  if (direction > 0) {
35
- return __getAtName(name, sortedArray, idx + 1, endIdx);
73
+ return __findStats(name, sortedArray, idx + 1, endIdx);
74
+ } else {
75
+ return __findStats(name, sortedArray, startIdx, idx - 1);
76
+ }
77
+ }
78
+ };
79
+
80
+ export const findStats = (name, sortedArray, startIdx = 0, endIdx = sortedArray.length / 3 - 1) => {
81
+ return __findStats(name, sortedArray, startIdx, endIdx);
82
+ };
83
+
84
+ export const getIndex = (pathSegment, tags) => {
85
+ let { type, name, index, shiftIndex } = pathSegment;
86
+ let firstPropsIndex = __getPropertyTagsIndex(tags, type, name, 0);
87
+ let prop = firstPropsIndex == null ? null : getAt(firstPropsIndex, tags);
88
+ let ref = prop?.value.reference;
89
+ let sums = getSums(tags);
90
+ let counts = name ? sums.names : sums.types;
91
+
92
+ if (ref?.flags.array) {
93
+ if (index < 0) {
94
+ index = findStats(type, counts) + index;
95
+ } else if (index == null) {
96
+ index = findStats(name, counts) - 1;
97
+
98
+ if (index < 0) return null;
99
+ }
100
+ }
101
+
102
+ let parentIndex = __getPropertyTagsIndex(tags, type, name, index ?? 0);
103
+
104
+ if (parentIndex != null && ref?.flags.expression) {
105
+ let shiftIndex_ = shiftIndex == null ? -1 : shiftIndex;
106
+ if (shiftIndex_ < 0) {
107
+ let shifts = 0;
108
+ // TODO speed this up for deeply nested shifts
109
+ // algorithm: make big jump forward, then look at shift index to see if we overshot completely
110
+ // works because we know the size of the thing and a bigger thing doesn't fit in a smaller one
111
+ while (getAt(parentIndex + shifts, getValues(tags)[1])?.value.shift) {
112
+ shifts++;
113
+ }
114
+
115
+ if (-shiftIndex_ > shifts + 1) return null;
116
+
117
+ return parentIndex + shifts + shiftIndex_ + 1;
36
118
  } else {
37
- return __getAtName(name, sortedArray, startIdx, idx - 1);
119
+ if (parentIndex + shiftIndex_ >= getSize(tags)) {
120
+ return null;
121
+ }
122
+
123
+ return parentIndex + shiftIndex_;
38
124
  }
39
125
  }
126
+
127
+ return parentIndex;
40
128
  };
41
129
 
42
- export const getAtName = (name, sortedArray, startIdx = 0, endIdx = sortedArray.length - 1) => {
43
- return __getAtName(name, sortedArray, startIdx, endIdx);
130
+ const __getPropertyTagsIndex = (tags, type, name, index) => {
131
+ let nameCount = 0;
132
+ let node = getValues(tags)[1];
133
+ let idx = -1;
134
+
135
+ // drill into subtrees, passing over subtrees with too few references of the desired name
136
+ outer: while (node) {
137
+ let sums = getSums(node);
138
+
139
+ if (!sums) throw new Error();
140
+
141
+ let valueNameCount = name != null ? findStats(name, sums.names) : findStats(type, sums.types);
142
+
143
+ if (nameCount + valueNameCount < index) {
144
+ return null;
145
+ }
146
+
147
+ for (const value of arrayValues(getValues(node))) {
148
+ if (!isNode(value)) {
149
+ idx++;
150
+ let tag = value;
151
+ if (isObject(tag) && tag.type === Property) {
152
+ let { tags, reference } = tag.value;
153
+ if (parseTagType(tags[1][0]) === ReferenceTag) {
154
+ if (
155
+ (name != null && reference.name === name) ||
156
+ (type != null && reference.type === type)
157
+ ) {
158
+ nameCount += 1;
159
+ if (nameCount > index) {
160
+ return idx + 1;
161
+ }
162
+ }
163
+ }
164
+ }
165
+ } else {
166
+ let valueSums = getSums(value);
167
+ if (
168
+ nameCount +
169
+ (name != null ? findStats(name, valueSums.names) : findStats(type, valueSums.types)) >
170
+ index
171
+ ) {
172
+ node = value;
173
+ continue outer;
174
+ } else {
175
+ nameCount +=
176
+ (name != null ? findStats(name, valueSums.names) : findStats(type, valueSums.types)) ??
177
+ 0;
178
+ idx += getSize(value);
179
+ }
180
+ }
181
+ }
182
+
183
+ return null;
184
+ }
185
+
186
+ return null;
44
187
  };
45
188
 
46
189
  export const compareNames = (a, b) => (a > b ? 1 : b > a ? -1 : 0);
47
190
 
48
- export const sumValues = (values) => {
49
- let mapStats = values.reduce(
191
+ export const sumValues = (values, depth) => {
192
+ let mapStats = reduce.call(
193
+ values,
50
194
  (acc, val) => {
51
195
  const { names, types } = acc;
52
196
  if (isArray(val)) {
@@ -59,30 +203,35 @@ export const sumValues = (values) => {
59
203
  ];
60
204
 
61
205
  for (let { 0: arr, 1: map } of arrs) {
62
- for (const { 0: key, 1: value, 2: reference } of arr) {
206
+ for (let i = 0; i < arr.length; i++) {
207
+ let key = arr[i];
208
+ let value = arr[++i];
209
+ let reference = arr[++i];
210
+
63
211
  let count = map.get(key)?.[1] ?? 0;
64
212
 
65
- if (!reference.flags.array && referenceIsSingular(reference) && count > 0)
66
- throw new Error();
213
+ if (referenceIsSingular(reference) && count > 0) throw new Error();
67
214
 
68
- map.set(key, freeze([key, count + value, reference]));
215
+ map.set(key, [key, count + value, reference]);
69
216
  }
70
217
  }
71
218
  } else if (val) {
72
- if (val.type === LiteralTag) {
73
- let text = val.value;
219
+ let tag = parseTag(val);
220
+ if (tag.type === LiteralTag) {
221
+ let text = tag.value;
74
222
  let idx = 0;
75
223
  while ((idx = text.indexOf('\n', idx + 1)) >= 0) {
76
224
  acc.lineBreaks++;
77
225
  }
78
- } else if (val.type === GapTag) {
226
+ } else if (tag.type === GapTag) {
79
227
  acc.gaps++;
80
- } else if (val.type === Property) {
81
- let { tags } = val.value;
82
- let refOrShiftTag = tags[0];
228
+ } else if (tag.type === Property) {
229
+ let { tags } = tag.value;
230
+ if (!isArray(tags[1][0]) && !isString(tags[1][0])) throw new Error();
231
+ let refOrShiftTag = isArray(tags[1][0]) ? buildReference() : parseTag(tags[1][0]);
83
232
 
84
- if (tags[2]) {
85
- acc.gaps += tags[2].value.tags[2].gaps;
233
+ if (tags[1][2]) {
234
+ acc.gaps += tags[1][2].value.tags[2].gaps;
86
235
  }
87
236
  if (refOrShiftTag?.type !== ShiftTag) {
88
237
  let ref = refOrShiftTag?.value || buildReference();
@@ -91,17 +240,17 @@ export const sumValues = (values) => {
91
240
  if (name) {
92
241
  let count = names.get(name)?.[1] ?? 0;
93
242
 
94
- if (!ref.flags.array && referenceIsSingular(ref) && count > 0) throw new Error();
243
+ if (referenceIsSingular(ref) && count > 0) throw new Error();
95
244
 
96
- names.set(name, freeze([name, count + 1, ref]));
245
+ names.set(name, [name, count + 1, ref]);
97
246
  }
98
247
 
99
248
  if (type) {
100
249
  let count = types.get(type)?.[1] ?? 0;
101
250
 
102
- if (!ref.flags.array && referenceIsSingular(ref) && count > 0) throw new Error();
251
+ if (referenceIsSingular(ref) && count > 0) throw new Error();
103
252
 
104
- types.set(type, freeze([type, count + 1, ref]));
253
+ types.set(type, [type, count + 1, ref]);
105
254
  }
106
255
  }
107
256
  }
@@ -117,130 +266,146 @@ export const sumValues = (values) => {
117
266
  );
118
267
 
119
268
  let { lineBreaks, gaps } = mapStats;
120
- let stats = {
269
+ let stats = freezeRecord({
121
270
  gaps,
122
271
  lineBreaks,
123
- names: [...mapStats.names.values()].sort((a, b) => compareNames(a[0], b[0])),
124
- types: [...mapStats.types.values()].sort((a, b) => compareNames(a[0], b[0])),
125
- };
126
- freeze(stats);
127
- freeze(stats.names);
128
- freeze(stats.types);
272
+ names: freezeRecord(
273
+ [...mapStats.names.values()].sort((a, b) => compareNames(a[0], b[0])).flatMap((_) => _),
274
+ ),
275
+ types: freezeRecord(
276
+ [...mapStats.types.values()].sort((a, b) => compareNames(a[0], b[0])).flatMap((_) => _),
277
+ ),
278
+ });
129
279
  return stats;
130
280
  };
131
281
 
132
- const module_ = buildModule(defaultNodeSize, sumValues);
282
+ const module_ = buildModule(defaultNodeSize, sumValues, Symbol.for('Tags'));
133
283
 
134
284
  const {
135
285
  from,
136
286
  fromValues,
287
+ create,
137
288
  addAt,
138
289
  isValidNode,
290
+ isNode,
139
291
  assertValidNode,
140
292
  getValues,
141
293
  getSums,
142
- setValues,
294
+ getDepth,
295
+ getType,
143
296
  traverse,
144
297
  getSize,
145
298
  findPath,
146
299
  } = module_;
147
300
 
148
- const getAt = (idx, tags, propertyIndex) => {
149
- if (propertyIndex !== undefined) {
150
- throw new Error('propertyIndex');
301
+ let { slice } = Array.prototype;
302
+
303
+ const getAt = (idx, tags) => {
304
+ if (isArray(idx) && idx.length > 1) {
305
+ return module_.getAt(slice.call(idx, 1), module_.getAt(slice.call(idx, 0, 1), tags));
306
+ } else {
307
+ return module_.getAt(idx, tags);
151
308
  }
152
- return module_.getAt(idx, tags);
153
309
  };
154
310
 
155
- const push = (tags, tag) => {
156
- let firstTag = getAt(0, tags);
311
+ const push = (tag, tags) => {
312
+ let tag_ = parseTag(tag);
313
+ let firstTag = parseTag(getAt(0, tags));
157
314
 
158
315
  if (
159
- !Array.isArray(tag) &&
160
- ![OpenNodeTag, Property, CloseNodeTag, AttributeDefinition, LiteralTag, GapTag].includes(
161
- tag.type,
162
- )
316
+ !isNode(tag_) &&
317
+ ![
318
+ OpenNodeTag,
319
+ Property,
320
+ CloseNodeTag,
321
+ AttributeDefinition,
322
+ BindingTag,
323
+ LiteralTag,
324
+ GapTag,
325
+ ].includes(tag_.type)
163
326
  )
164
327
  throw new Error();
165
328
 
166
- if (isArray(tag)) {
329
+ if (isNode(tag_)) {
167
330
  if (!firstTag || firstTag.type !== OpenNodeTag) {
168
- return module_.concat(tags, tag);
331
+ return module_.concat(tags, tag_);
169
332
  }
170
333
  let children = getValues(tags)[1] ?? module_.fromValues([]);
171
334
 
172
335
  let { 0: open } = getValues(tags);
173
336
 
174
- children = module_.concat(children, tag);
337
+ children = module_.concat(children, tag_);
175
338
 
176
339
  return module_.fromValues([open, children]);
177
340
  } else {
178
- if (tag.type === OpenNodeTag) {
341
+ if (tag_.type === OpenNodeTag) {
179
342
  if (getSize(tags) !== 0) throw new Error();
180
- return module_.push(tags, tag);
343
+ return module_.push(printTag(tag_), tags);
181
344
  } else {
182
345
  if (!firstTag || firstTag.type !== OpenNodeTag) {
183
- return module_.push(tags, tag);
346
+ return module_.push(printTag(tag_), tags);
184
347
  }
185
348
 
186
- let children = getValues(tags)[1] ?? [];
349
+ let children = getValues(tags)[1] ?? module_.fromValues([]);
187
350
 
188
351
  // sometimes this is the top level
189
352
  if (firstTag) {
190
- if (tag.type === CloseNodeTag) {
353
+ if (tag_.type === CloseNodeTag) {
191
354
  let { 0: open, 1: children = module_.fromValues([]) } = getValues(tags);
192
355
 
193
- return module_.fromValues([open, children, tag]);
356
+ return module_.fromValues([open, children, printTag(tag_)]);
194
357
  } else {
195
358
  let { 0: open } = getValues(tags);
196
359
 
197
- children = module_.push(children, tag);
360
+ children = module_.push(printTag(tag_), children);
198
361
 
199
- return module_.fromValues([open, children]);
362
+ return module_.fromValues([open, children], 1);
200
363
  }
201
364
  } else {
202
- return module_.push(tags, tag);
365
+ return module_.push(tag_, tags);
203
366
  }
204
367
  }
205
368
  }
206
369
  };
207
370
 
208
- const replaceAt = (idx, tree, value) => {
371
+ const replaceAt = (idx, tag, tree) => {
209
372
  let idx_ = idx < 0 ? getSize(tree) + idx : idx;
373
+ let tagObj = parseTag(tag);
374
+ let tag_ = printTag(tag);
210
375
 
211
- if (value.type !== getAt(idx_, tree).type) throw new Error();
212
- let firstTag = getAt(0, tree);
376
+ if (tagObj.type !== parseTagType(getAt(idx_, tree))) throw new Error();
377
+ let firstTag = parseTag(getAt(0, tree));
213
378
 
214
379
  if (firstTag && firstTag.type === OpenNodeTag) {
215
- let newTags = [...getValues(tree)];
380
+ let newTags = [...arrayValues(getValues(tree))];
216
381
  if (idx_ === 0) {
217
- newTags[0] = value;
218
- } else if (isObject(value) && value.type === CloseNodeTag) {
382
+ newTags[0] = tag_;
383
+ } else if (tagObj.type === CloseNodeTag) {
219
384
  if (idx_ !== getSize(tree) - 1) throw new Error();
220
- newTags[2] = value;
385
+ newTags[2] = tag_;
221
386
  } else {
222
387
  let children = getValues(tree)[1];
223
388
 
224
- children = module_.replaceAt(idx_ - 1, children, value);
389
+ children = module_.replaceAt(idx_ - 1, tag_, children);
225
390
 
226
391
  newTags[1] = children;
227
392
  }
228
393
  return module_.fromValues(newTags);
229
394
  } else {
230
- return module_.replaceAt(idx_, tree, value);
395
+ return module_.replaceAt(idx_, tag_, tree);
231
396
  }
232
397
  };
233
398
 
234
399
  const removeAt = (idx, tree) => {
235
400
  let idx_ = idx < 0 ? getSize(tree) + idx : idx;
236
401
 
237
- let firstTag = getAt(0, tree);
402
+ let firstTag = parseTag(getAt(0, tree));
238
403
 
239
404
  if (firstTag && firstTag.type === OpenNodeTag) {
240
- let newTags = [...getValues(tree)];
405
+ let newTags = [...arrayValues(getValues(tree))];
241
406
  if (idx_ === 0) {
242
407
  newTags.splice(0, 1);
243
- } else if (idx_ === getSize(tree - 1)) {
408
+ } else if (idx_ === getSize(tree) - 1) {
244
409
  newTags.splice(2, 1);
245
410
  } else {
246
411
  let children = getValues(tree)[1];
@@ -256,27 +421,41 @@ const removeAt = (idx, tree) => {
256
421
  };
257
422
 
258
423
  function* traverseInner(tree) {
259
- for (let tag of module_.traverse(tree)) {
260
- if (tag.type === Property) {
261
- yield* module_.traverse(tag.value.tags);
424
+ for (let tag of traverse(tree)) {
425
+ if (isObject(tag) && tag.type === Property) {
426
+ yield* traverse(tag.value.tags);
262
427
  } else {
263
428
  yield tag;
264
429
  }
265
430
  }
266
431
  }
267
432
 
433
+ const map = (fn, tree) => {
434
+ let { 0: open, 1: children, 2: close } = getValues(tree);
435
+
436
+ let newValues = [fn(open)];
437
+ if (children) newValues.push(module_.map(fn, children));
438
+ if (close) newValues.push(fn(close));
439
+
440
+ return module_.fromValues(newValues);
441
+ };
442
+
268
443
  export {
269
444
  from,
270
445
  fromValues,
446
+ create,
271
447
  push,
272
448
  addAt,
273
449
  isValidNode,
450
+ isNode,
274
451
  assertValidNode,
275
452
  getSize,
276
453
  getValues,
277
454
  getSums,
278
- setValues,
455
+ getDepth,
456
+ getType,
279
457
  traverse,
458
+ map,
280
459
  traverseInner,
281
460
  findPath,
282
461
  getAt,
package/lib/template.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as t from './builders.js';
2
- import { buildNullNode, getTags, isMultiFragment, Path } from './path.js';
2
+ import { isObject } from './object.js';
3
+ import { parseTagType } from './builders.js';
4
+ import { buildNullNode, isMultiFragment, isNode, Path } from './path.js';
3
5
  import { OpenNodeTag, CloseNodeTag, DoctypeTag, Property, GapNode } from './symbols.js';
4
6
  import * as Tags from './tags.js';
5
7
  import { getOpenTag, isNull } from './tree.js';
@@ -12,6 +14,8 @@ export const buildFilledGapFunction = (expressions) => (value) => {
12
14
  };
13
15
 
14
16
  export const interpolate = (expressions, node) => {
17
+ if (!isNode(node)) throw new Error();
18
+
15
19
  let path = Path.from(node);
16
20
 
17
21
  outer: for (let expression of expressions) {
@@ -19,12 +23,12 @@ export const interpolate = (expressions, node) => {
19
23
  do {
20
24
  path = path.atDepth(0);
21
25
  let tag;
22
- while ((tag = Tags.getAt(i, getTags(path.node)))) {
26
+ while ((tag = Tags.getAt(i, Tags.getTags(path.node)))) {
23
27
  if (path.node.type === GapNode) {
24
28
  path = path.replaceWith(expression ?? buildNullNode());
25
29
  continue outer;
26
- } else if (tag.type === Property && tag.value.tags[2]) {
27
- let tags = getTags(tag.value.tags[2]);
30
+ } else if (isObject(tag) && tag.type === Property && tag.value.tags[1][2]) {
31
+ let tags = Tags.getTags(tag.value.tags[1][2]);
28
32
  if (tags[2].gaps) {
29
33
  path = path.push(i);
30
34
  i = 0;
@@ -41,21 +45,19 @@ export const interpolate = (expressions, node) => {
41
45
  return path.atDepth(0).node;
42
46
  };
43
47
 
44
- export const interpolateFragment = (node, ref, expressions) => {
45
- return __interpolateFragment(node, ref, expressions);
48
+ export const interpolateFragment = (node, ref) => {
49
+ return __interpolateFragment(node, ref);
46
50
  };
47
51
 
48
- function* __interpolateFragment(node, ref, expressions) {
52
+ function* __interpolateFragment(node, ref) {
49
53
  if (isNull(node)) return;
50
54
 
51
- const open = getOpenTag(node);
52
-
53
- const gap = buildFilledGapFunction(expressions);
55
+ const open = t.parseTag(getOpenTag(node));
54
56
 
55
57
  if (!open.value.name) {
56
58
  let isMultiFragment_ = isMultiFragment(node);
57
- for (let tag of Tags.traverse(getTags(node))) {
58
- switch (tag.type) {
59
+ for (let tag of Tags.traverse(Tags.getTags(node))) {
60
+ switch (parseTagType(tag)) {
59
61
  case DoctypeTag: {
60
62
  break;
61
63
  }
@@ -71,16 +73,17 @@ function* __interpolateFragment(node, ref, expressions) {
71
73
  let { reference, node, tags } = tag.value;
72
74
  const { type } = reference;
73
75
 
76
+ // don't I elsewhere forbid this?
74
77
  if (type === '_') {
75
78
  // TODO check/combine flags
76
79
  yield ref;
77
80
  } else {
78
- yield tags[0];
81
+ yield tags[1][0];
79
82
  }
80
83
 
81
- yield* tags[1];
84
+ yield* Tags.traverse(tags[1][1]);
82
85
 
83
- yield gap(node);
86
+ yield node;
84
87
 
85
88
  break;
86
89
  }
@@ -93,7 +96,7 @@ function* __interpolateFragment(node, ref, expressions) {
93
96
  }
94
97
  } else if (open.type === OpenNodeTag) {
95
98
  yield freeze(ref);
96
- yield gap(node);
99
+ yield node;
97
100
  } else {
98
101
  throw new Error();
99
102
  }