@bablr/agast-helpers 0.3.1 → 0.4.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/btree.js +1 -0
- package/lib/builders.js +100 -81
- package/lib/path.js +1 -1
- package/lib/print.js +75 -51
- package/lib/shorthand.js +12 -8
- package/lib/stream.js +141 -102
- package/lib/symbols.js +16 -0
- package/lib/template.js +18 -11
- package/lib/tree.js +320 -178
- package/package.json +3 -1
package/lib/tree.js
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
import emptyStack from '@iter-tools/imm-stack';
|
|
2
1
|
import { Coroutine } from '@bablr/coroutine';
|
|
3
|
-
import
|
|
2
|
+
import emptyStack from '@iter-tools/imm-stack';
|
|
3
|
+
import { nodeFlags, buildDoctypeTag, buildEmbeddedNode } from './builders.js';
|
|
4
4
|
import {
|
|
5
5
|
printPrettyCSTML as printPrettyCSTMLFromStream,
|
|
6
6
|
printCSTML as printCSTMLFromStream,
|
|
7
7
|
getStreamIterator,
|
|
8
8
|
StreamIterable,
|
|
9
9
|
} from './stream.js';
|
|
10
|
+
import {
|
|
11
|
+
DoctypeTag,
|
|
12
|
+
OpenNodeTag,
|
|
13
|
+
CloseNodeTag,
|
|
14
|
+
ReferenceTag,
|
|
15
|
+
ShiftTag,
|
|
16
|
+
GapTag,
|
|
17
|
+
NullTag,
|
|
18
|
+
ArrayTag,
|
|
19
|
+
LiteralTag,
|
|
20
|
+
EmbeddedNode,
|
|
21
|
+
} from './symbols.js';
|
|
22
|
+
import * as btree from './btree.js';
|
|
10
23
|
import * as sym from './symbols.js';
|
|
11
24
|
export * from './builders.js';
|
|
12
25
|
export * from './print.js';
|
|
@@ -17,11 +30,6 @@ const isString = (str) => typeof str === 'string';
|
|
|
17
30
|
|
|
18
31
|
const { isArray } = Array;
|
|
19
32
|
|
|
20
|
-
const buildFrame = (node) => {
|
|
21
|
-
if (!node) throw new Error();
|
|
22
|
-
return { node, childrenIdx: -1, resolver: new Resolver(node) };
|
|
23
|
-
};
|
|
24
|
-
|
|
25
33
|
const { hasOwn, freeze } = Object;
|
|
26
34
|
|
|
27
35
|
export const get = (node, path) => {
|
|
@@ -33,7 +41,7 @@ export const get = (node, path) => {
|
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
if (index != null) {
|
|
36
|
-
return
|
|
44
|
+
return btree.getAt(parseInt(index, 10), properties[name]);
|
|
37
45
|
} else {
|
|
38
46
|
return properties[name];
|
|
39
47
|
}
|
|
@@ -46,18 +54,18 @@ export const add = (node, ref, value) => {
|
|
|
46
54
|
if (!hasOwn(node.properties, name)) {
|
|
47
55
|
node.properties[name] = [];
|
|
48
56
|
}
|
|
49
|
-
const array = node.properties[name];
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
node.properties[name] = btree.push(node.properties[name], value);
|
|
52
59
|
} else {
|
|
53
60
|
node.properties[name] = value;
|
|
54
61
|
}
|
|
55
62
|
};
|
|
56
63
|
|
|
57
64
|
function* __treeFromStream(tokens) {
|
|
58
|
-
let
|
|
59
|
-
let
|
|
65
|
+
let path = null;
|
|
66
|
+
let rootPath = null;
|
|
60
67
|
let held = null;
|
|
68
|
+
let doctype = null;
|
|
61
69
|
const co = new Coroutine(getStreamIterator(tokens));
|
|
62
70
|
|
|
63
71
|
for (;;) {
|
|
@@ -69,65 +77,47 @@ function* __treeFromStream(tokens) {
|
|
|
69
77
|
|
|
70
78
|
if (co.done) break;
|
|
71
79
|
|
|
72
|
-
const
|
|
80
|
+
const tag = co.value;
|
|
73
81
|
|
|
74
|
-
if (
|
|
82
|
+
if (tag.type === 'Effect') {
|
|
75
83
|
continue;
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
rootNode = freeze({
|
|
82
|
-
flags: nodeFlags,
|
|
83
|
-
language: attributes['bablr-language'],
|
|
84
|
-
type: null,
|
|
85
|
-
children: [],
|
|
86
|
-
properties: {},
|
|
87
|
-
attributes: freeze(attributes),
|
|
88
|
-
});
|
|
89
|
-
nodes = nodes.push(rootNode);
|
|
86
|
+
if (tag.type === DoctypeTag) {
|
|
87
|
+
doctype = tag;
|
|
90
88
|
continue;
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
if (
|
|
94
|
-
throw new Error('
|
|
91
|
+
if (held && tag.type !== 'StartNodeTag' && tag.type !== GapTag) {
|
|
92
|
+
throw new Error('cannot eat this type of tag while holding');
|
|
95
93
|
}
|
|
96
94
|
|
|
97
|
-
switch (
|
|
98
|
-
case
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (ref.type !== 'Reference') throw new Error();
|
|
103
|
-
|
|
104
|
-
add(node, ref, null);
|
|
105
|
-
|
|
95
|
+
switch (tag.type) {
|
|
96
|
+
case LiteralTag:
|
|
97
|
+
case ReferenceTag:
|
|
98
|
+
case CloseNodeTag: {
|
|
106
99
|
break;
|
|
107
100
|
}
|
|
108
101
|
|
|
109
|
-
case
|
|
110
|
-
case
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
102
|
+
case NullTag:
|
|
103
|
+
case GapTag: {
|
|
104
|
+
const parentNode = path.parent.node;
|
|
105
|
+
const ref = arrayLast(parentNode.children);
|
|
106
|
+
const isGap = tag.type === GapTag;
|
|
114
107
|
|
|
115
|
-
|
|
116
|
-
if (held) {
|
|
117
|
-
const node = nodes.value;
|
|
118
|
-
const ref = arrayLast(node.children);
|
|
108
|
+
if (ref.type !== ReferenceTag) throw new Error();
|
|
119
109
|
|
|
120
|
-
|
|
110
|
+
const node = (isGap && held) || createNode(nodeFlags, null, sym.null);
|
|
121
111
|
|
|
122
|
-
|
|
112
|
+
held = isGap ? null : held;
|
|
113
|
+
path = { parent: path, node, depth: (path.depth || -1) + 1 };
|
|
123
114
|
|
|
124
|
-
|
|
125
|
-
}
|
|
115
|
+
add(parentNode, ref, node);
|
|
126
116
|
break;
|
|
127
117
|
}
|
|
128
118
|
|
|
129
|
-
case
|
|
130
|
-
const { children, properties } =
|
|
119
|
+
case ShiftTag: {
|
|
120
|
+
const { children, properties } = path.node;
|
|
131
121
|
|
|
132
122
|
const ref = arrayLast(children);
|
|
133
123
|
let node = properties[ref.value.name];
|
|
@@ -143,68 +133,67 @@ function* __treeFromStream(tokens) {
|
|
|
143
133
|
break;
|
|
144
134
|
}
|
|
145
135
|
|
|
146
|
-
case
|
|
147
|
-
const { flags,
|
|
148
|
-
const node = nodes.value;
|
|
136
|
+
case OpenNodeTag: {
|
|
137
|
+
const { flags, type } = tag.value;
|
|
149
138
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
139
|
+
const language = type ? tag.value.language : doctype.value.attributes['bablr-language'];
|
|
140
|
+
const attributes = type ? tag.value.attributes : doctype.value.attributes;
|
|
153
141
|
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
type,
|
|
158
|
-
children: [],
|
|
159
|
-
properties: {},
|
|
160
|
-
attributes: freeze(attributes),
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
if (node && !(flags.escape || flags.trivia)) {
|
|
164
|
-
if (!node.children.length) {
|
|
165
|
-
throw new Error('Nodes must follow references');
|
|
166
|
-
}
|
|
142
|
+
const node = createNode(flags, language, type, [], {}, attributes);
|
|
143
|
+
|
|
144
|
+
const parentPath = path;
|
|
167
145
|
|
|
168
|
-
|
|
146
|
+
path = { parent: path, node, depth: (path.depth || -1) + 1 };
|
|
169
147
|
|
|
170
|
-
|
|
148
|
+
if (parentPath) {
|
|
149
|
+
const { node: parentNode } = path;
|
|
150
|
+
if (!(flags.escape || flags.trivia)) {
|
|
151
|
+
if (!parentNode.children.length) {
|
|
152
|
+
throw new Error('Nodes must follow references');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const ref = arrayLast(parentNode.children);
|
|
156
|
+
|
|
157
|
+
add(parentNode, ref, node);
|
|
158
|
+
} else {
|
|
159
|
+
parentNode.children.push(buildEmbeddedNode(node));
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
rootPath = path;
|
|
171
163
|
}
|
|
172
164
|
|
|
173
|
-
nodes = nodes.push(newNode);
|
|
174
165
|
break;
|
|
175
166
|
}
|
|
176
167
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
168
|
+
default: {
|
|
169
|
+
throw new Error();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
180
172
|
|
|
181
|
-
|
|
182
|
-
const parentChildren = nodes.prev.value.children;
|
|
173
|
+
path.node.children.push(tag);
|
|
183
174
|
|
|
184
|
-
|
|
185
|
-
|
|
175
|
+
switch (tag.type) {
|
|
176
|
+
case NullTag:
|
|
177
|
+
case GapTag:
|
|
178
|
+
case CloseNodeTag: {
|
|
179
|
+
const completedNode = path.node;
|
|
186
180
|
|
|
187
|
-
|
|
188
|
-
freeze(completedNode.children);
|
|
181
|
+
finalizeNode(completedNode);
|
|
189
182
|
|
|
190
|
-
if (!completedNode.type &&
|
|
183
|
+
if (!completedNode.type && path.depth !== 1) {
|
|
191
184
|
throw new Error('imbalanced tag stack');
|
|
192
185
|
}
|
|
193
186
|
|
|
194
|
-
|
|
187
|
+
path = path.parent;
|
|
195
188
|
break;
|
|
196
189
|
}
|
|
197
|
-
|
|
198
|
-
default: {
|
|
199
|
-
throw new Error();
|
|
200
|
-
}
|
|
201
190
|
}
|
|
202
191
|
}
|
|
203
192
|
|
|
204
|
-
return
|
|
193
|
+
return rootPath.node;
|
|
205
194
|
}
|
|
206
195
|
|
|
207
|
-
export const treeFromStream = (
|
|
196
|
+
export const treeFromStream = (tags) => new StreamIterable(__treeFromStream(tags));
|
|
208
197
|
|
|
209
198
|
export const treeFromStreamSync = (tokens) => {
|
|
210
199
|
return evaluateReturnSync(treeFromStream(tokens));
|
|
@@ -235,67 +224,73 @@ export const evaluateReturnAsync = async (generator) => {
|
|
|
235
224
|
export const streamFromTree = (rootNode) => __streamFromTree(rootNode);
|
|
236
225
|
|
|
237
226
|
function* __streamFromTree(rootNode) {
|
|
238
|
-
if (!rootNode || rootNode.type ===
|
|
227
|
+
if (!rootNode || rootNode.type === GapTag) {
|
|
239
228
|
return rootNode;
|
|
240
229
|
}
|
|
241
230
|
|
|
242
231
|
yield buildDoctypeTag(rootNode.attributes);
|
|
243
232
|
|
|
244
|
-
let stack = emptyStack.push(
|
|
233
|
+
let stack = emptyStack.push(rootNode);
|
|
234
|
+
const resolver = new Resolver();
|
|
245
235
|
|
|
246
236
|
stack: while (stack.size) {
|
|
247
|
-
const
|
|
248
|
-
const { node, resolver } = frame;
|
|
237
|
+
const node = stack.value;
|
|
249
238
|
const { children } = node;
|
|
250
239
|
|
|
251
|
-
while (
|
|
252
|
-
const
|
|
240
|
+
while (true) {
|
|
241
|
+
const tag = btree.getAt(resolver.idx, children);
|
|
253
242
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
243
|
+
if (isArray(tag)) {
|
|
244
|
+
throw new Error();
|
|
245
|
+
}
|
|
257
246
|
|
|
258
|
-
|
|
247
|
+
switch (tag.type) {
|
|
248
|
+
case EmbeddedNode: {
|
|
249
|
+
stack = stack.push(tag.value);
|
|
250
|
+
|
|
251
|
+
resolver.advance(tag);
|
|
252
|
+
|
|
253
|
+
continue stack;
|
|
259
254
|
}
|
|
260
255
|
|
|
261
|
-
case
|
|
262
|
-
const
|
|
256
|
+
case ReferenceTag: {
|
|
257
|
+
const resolvedPath = resolver.resolve(tag);
|
|
258
|
+
const resolved = get(stack.value, resolvedPath);
|
|
259
|
+
const { isArray: refIsArray } = tag.value;
|
|
263
260
|
|
|
264
|
-
|
|
261
|
+
if (!resolved) throw new Error();
|
|
265
262
|
|
|
266
|
-
|
|
267
|
-
// TODO evaluate if this is still smart
|
|
268
|
-
yield buildNull();
|
|
269
|
-
} else {
|
|
270
|
-
if (!resolved) throw new Error();
|
|
263
|
+
yield tag;
|
|
271
264
|
|
|
272
|
-
|
|
273
|
-
}
|
|
265
|
+
resolver.advance(tag);
|
|
274
266
|
|
|
275
|
-
|
|
267
|
+
if (!refIsArray || !isArray(resolved)) {
|
|
268
|
+
if (isArray(resolved)) throw new Error();
|
|
269
|
+
stack = stack.push(resolved);
|
|
270
|
+
}
|
|
271
|
+
continue stack;
|
|
276
272
|
}
|
|
277
273
|
|
|
278
|
-
case
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
274
|
+
case GapTag:
|
|
275
|
+
case NullTag:
|
|
276
|
+
case CloseNodeTag: {
|
|
277
|
+
stack = stack.pop();
|
|
278
|
+
resolver.advance(tag);
|
|
279
|
+
yield tag;
|
|
280
|
+
continue stack;
|
|
282
281
|
}
|
|
283
282
|
|
|
284
283
|
default:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (terminal.type === 'EmbeddedNode' || terminal.type === 'Reference') {
|
|
289
|
-
continue stack;
|
|
284
|
+
resolver.advance(tag);
|
|
285
|
+
yield tag;
|
|
286
|
+
break;
|
|
290
287
|
}
|
|
291
288
|
}
|
|
292
|
-
|
|
293
|
-
stack = stack.pop();
|
|
294
289
|
}
|
|
295
290
|
}
|
|
296
291
|
|
|
297
292
|
export const getCooked = (cookable) => {
|
|
298
|
-
if (!cookable || cookable.type ===
|
|
293
|
+
if (!cookable || cookable.type === GapTag) {
|
|
299
294
|
return '';
|
|
300
295
|
}
|
|
301
296
|
|
|
@@ -303,14 +298,17 @@ export const getCooked = (cookable) => {
|
|
|
303
298
|
|
|
304
299
|
let cooked = '';
|
|
305
300
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
301
|
+
const openTag = getOpenTag(cookable);
|
|
302
|
+
const closeTag = getCloseTag(cookable);
|
|
303
|
+
|
|
304
|
+
for (const tag of btree.traverse(children)) {
|
|
305
|
+
switch (tag.type) {
|
|
306
|
+
case ReferenceTag: {
|
|
309
307
|
throw new Error('cookable nodes must not contain other nodes');
|
|
310
308
|
}
|
|
311
309
|
|
|
312
|
-
case
|
|
313
|
-
const { flags, attributes } =
|
|
310
|
+
case EmbeddedNode: {
|
|
311
|
+
const { flags, attributes } = tag.value;
|
|
314
312
|
|
|
315
313
|
if (!(flags.trivia || (flags.escape && attributes.cooked))) {
|
|
316
314
|
throw new Error('cookable nodes must not contain other nodes');
|
|
@@ -328,8 +326,16 @@ export const getCooked = (cookable) => {
|
|
|
328
326
|
break;
|
|
329
327
|
}
|
|
330
328
|
|
|
331
|
-
case
|
|
332
|
-
cooked +=
|
|
329
|
+
case LiteralTag: {
|
|
330
|
+
cooked += tag.value;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
case OpenNodeTag: {
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
case CloseNodeTag: {
|
|
333
339
|
break;
|
|
334
340
|
}
|
|
335
341
|
|
|
@@ -350,26 +356,35 @@ export const printPrettyCSTML = (rootNode, options = {}) => {
|
|
|
350
356
|
return printPrettyCSTMLFromStream(streamFromTree(rootNode), options);
|
|
351
357
|
};
|
|
352
358
|
|
|
353
|
-
|
|
354
|
-
const resolver = new Resolver(node);
|
|
359
|
+
const __printSource = (rootNode, resolver = new Resolver()) => {
|
|
355
360
|
let printed = '';
|
|
356
361
|
|
|
357
|
-
if (!
|
|
362
|
+
if (!rootNode) return '';
|
|
363
|
+
|
|
364
|
+
let node = rootNode;
|
|
358
365
|
|
|
359
366
|
if (node instanceof Promise) {
|
|
360
367
|
printed += '$Promise';
|
|
361
368
|
} else {
|
|
362
|
-
for (const child of node.children) {
|
|
363
|
-
if (child.type ===
|
|
369
|
+
for (const child of btree.traverse(node.children)) {
|
|
370
|
+
if (child.type === LiteralTag) {
|
|
364
371
|
printed += child.value;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
+
resolver.advance(child);
|
|
373
|
+
} else if (child.type === EmbeddedNode) {
|
|
374
|
+
resolver.advance(child);
|
|
375
|
+
printed += __printSource(child.value, resolver);
|
|
376
|
+
} else if (child.type === ReferenceTag) {
|
|
377
|
+
const resolvedPath = resolver.resolve(child);
|
|
378
|
+
const resolvedNode = get(node, resolvedPath);
|
|
379
|
+
|
|
380
|
+
resolver.advance(child);
|
|
381
|
+
if (resolvedNode) {
|
|
382
|
+
if (!isArray(resolvedNode)) {
|
|
383
|
+
printed += __printSource(resolvedNode, resolver);
|
|
384
|
+
}
|
|
372
385
|
}
|
|
386
|
+
} else {
|
|
387
|
+
resolver.advance(child);
|
|
373
388
|
}
|
|
374
389
|
}
|
|
375
390
|
}
|
|
@@ -377,24 +392,26 @@ export const printSource = (node) => {
|
|
|
377
392
|
return printed;
|
|
378
393
|
};
|
|
379
394
|
|
|
395
|
+
export const printSource = (rootNode) => __printSource(rootNode);
|
|
396
|
+
|
|
380
397
|
export const sourceTextFor = printSource;
|
|
381
398
|
|
|
382
399
|
export const getOpenTag = (node) => {
|
|
383
|
-
const tag = node.children
|
|
384
|
-
if (tag && tag.type !==
|
|
400
|
+
const tag = btree.getAt(0, node.children);
|
|
401
|
+
if (tag && tag.type !== OpenNodeTag) throw new Error();
|
|
385
402
|
return tag;
|
|
386
403
|
};
|
|
387
404
|
|
|
388
405
|
export const getCloseTag = (node) => {
|
|
389
406
|
const { children } = node;
|
|
390
|
-
const tag =
|
|
391
|
-
if (tag.type !==
|
|
407
|
+
const tag = btree.getAt(-1, children);
|
|
408
|
+
if (tag.type !== CloseNodeTag) return null;
|
|
392
409
|
return tag;
|
|
393
410
|
};
|
|
394
411
|
|
|
395
412
|
export const getRange = (node) => {
|
|
396
413
|
const { children } = node;
|
|
397
|
-
return children.length ? [children[
|
|
414
|
+
return children.length ? [children[1], children[children.length - 1]] : null;
|
|
398
415
|
};
|
|
399
416
|
|
|
400
417
|
export const createNode = (openTag) => {
|
|
@@ -403,8 +420,14 @@ export const createNode = (openTag) => {
|
|
|
403
420
|
};
|
|
404
421
|
|
|
405
422
|
export const finalizeNode = (node) => {
|
|
423
|
+
for (const propertyValue of Object.values(node.properties)) {
|
|
424
|
+
if (isArray(propertyValue)) {
|
|
425
|
+
btree.freeze(propertyValue);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
406
429
|
freeze(node);
|
|
407
|
-
freeze(node.children);
|
|
430
|
+
btree.freeze(node.children);
|
|
408
431
|
freeze(node.properties);
|
|
409
432
|
freeze(node.attributes);
|
|
410
433
|
return node;
|
|
@@ -453,33 +476,145 @@ export const acceptNode = (node, accepted) => {
|
|
|
453
476
|
export const getRoot = (fragmentNode) => {
|
|
454
477
|
if (!fragmentNode) return null;
|
|
455
478
|
|
|
456
|
-
for (const
|
|
457
|
-
if (
|
|
458
|
-
|
|
459
|
-
return fragmentNode.properties[terminal.value.name];
|
|
479
|
+
for (const tag of btree.traverse(fragmentNode.children)) {
|
|
480
|
+
if (tag.type === ReferenceTag) {
|
|
481
|
+
return fragmentNode.properties[tag.value.name];
|
|
460
482
|
}
|
|
461
483
|
}
|
|
462
484
|
};
|
|
463
485
|
|
|
464
486
|
export class Resolver {
|
|
465
|
-
constructor(
|
|
466
|
-
|
|
467
|
-
|
|
487
|
+
constructor(
|
|
488
|
+
states = emptyStack.push({ properties: new Map(), idx: 0 }),
|
|
489
|
+
reference = null,
|
|
490
|
+
popped = false,
|
|
491
|
+
held = null,
|
|
492
|
+
) {
|
|
493
|
+
this.states = states;
|
|
494
|
+
this.reference = reference;
|
|
495
|
+
this.popped = popped;
|
|
496
|
+
this.held = held;
|
|
468
497
|
}
|
|
469
498
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
499
|
+
get idx() {
|
|
500
|
+
return this.states.value.idx;
|
|
501
|
+
}
|
|
473
502
|
|
|
474
|
-
|
|
475
|
-
|
|
503
|
+
get properties() {
|
|
504
|
+
return this.states.value.properties;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
advance(tag) {
|
|
508
|
+
const { states } = this;
|
|
509
|
+
|
|
510
|
+
++states.value.idx;
|
|
511
|
+
|
|
512
|
+
this.popped = false;
|
|
513
|
+
|
|
514
|
+
switch (tag.type) {
|
|
515
|
+
case ReferenceTag: {
|
|
516
|
+
const { name, isArray } = tag.value;
|
|
517
|
+
const { properties } = states.value;
|
|
518
|
+
|
|
519
|
+
if (this.reference) throw new Error();
|
|
520
|
+
|
|
521
|
+
this.reference = tag;
|
|
522
|
+
|
|
523
|
+
let state = properties.get(name);
|
|
524
|
+
|
|
525
|
+
if (isArray) {
|
|
526
|
+
if (state && !state.isArray) throw new Error();
|
|
527
|
+
|
|
528
|
+
const { count = -1 } = state || {};
|
|
529
|
+
|
|
530
|
+
state = { count: count + 1, isArray };
|
|
531
|
+
} else if (state) {
|
|
532
|
+
throw new Error(`attempted to consume property {name: ${name}} twice`);
|
|
533
|
+
} else {
|
|
534
|
+
state = { count: 1, isArray: false };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
properties.set(name, state);
|
|
538
|
+
|
|
539
|
+
if (!isArray || state.count > 0) {
|
|
540
|
+
this.states = states.push({ properties: new Map(), idx: 0 });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
case EmbeddedNode: {
|
|
547
|
+
if (this.reference) throw new Error();
|
|
548
|
+
|
|
549
|
+
this.reference = tag;
|
|
550
|
+
|
|
551
|
+
this.states = states.push({ properties: new Map(), idx: 0 });
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
case OpenNodeTag: {
|
|
556
|
+
const { flags } = tag.value;
|
|
557
|
+
const isRootNode = states.size === 1;
|
|
558
|
+
|
|
559
|
+
if (!isRootNode && !this.reference && !(flags.trivia || flags.escape)) {
|
|
560
|
+
throw new Error();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (this.reference?.type !== EmbeddedNode && (flags.trivia || flags.escape)) {
|
|
564
|
+
this.states = states.push({ properties: new Map(), idx: 0 });
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
this.reference = null;
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
476
570
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (counters.has(name))
|
|
480
|
-
throw new Error(`attempted to consume property {name: ${name}} twice`);
|
|
571
|
+
case ArrayTag: {
|
|
572
|
+
if (!this.reference) throw new Error();
|
|
481
573
|
|
|
482
|
-
|
|
574
|
+
const { name } = this.reference.value;
|
|
575
|
+
const { properties } = states.value;
|
|
576
|
+
const state = properties.get(name);
|
|
577
|
+
|
|
578
|
+
if (!state || !state.isArray || state.count !== 0) throw new Error();
|
|
579
|
+
|
|
580
|
+
properties.set(name, { count: 0, isArray: true });
|
|
581
|
+
|
|
582
|
+
this.reference = null;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
case ShiftTag: {
|
|
587
|
+
this.held = this.states.value;
|
|
588
|
+
this.states = this.states.push({ properties: new Map(), idx: 0 });
|
|
589
|
+
this.reference = tag;
|
|
590
|
+
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
case NullTag: {
|
|
595
|
+
this.states = states.pop();
|
|
596
|
+
this.popped = true;
|
|
597
|
+
this.reference = null;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
case GapTag: {
|
|
602
|
+
this.states = states.pop();
|
|
603
|
+
|
|
604
|
+
if (this.held) {
|
|
605
|
+
// this.states = this.states.push(this.held);
|
|
606
|
+
this.held = null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
this.popped = true;
|
|
610
|
+
this.reference = null;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
case CloseNodeTag: {
|
|
615
|
+
this.states = states.pop();
|
|
616
|
+
this.popped = true;
|
|
617
|
+
}
|
|
483
618
|
}
|
|
484
619
|
|
|
485
620
|
return this;
|
|
@@ -487,30 +622,37 @@ export class Resolver {
|
|
|
487
622
|
|
|
488
623
|
resolve(reference) {
|
|
489
624
|
let { name, isArray } = reference.value;
|
|
490
|
-
const {
|
|
625
|
+
const { states } = this;
|
|
626
|
+
const state = states.value.properties.get(name);
|
|
491
627
|
let path = name;
|
|
492
628
|
|
|
493
629
|
if (isArray) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
630
|
+
if (state) {
|
|
631
|
+
const count = state?.count || 0;
|
|
632
|
+
path += '.' + count;
|
|
633
|
+
}
|
|
497
634
|
}
|
|
498
635
|
|
|
499
636
|
return path;
|
|
500
637
|
}
|
|
501
638
|
|
|
502
|
-
get(reference) {
|
|
503
|
-
if (!this.node) throw new Error('Cannot get from a resolver with no node');
|
|
504
|
-
|
|
505
|
-
return get(this.node, this.resolve(reference));
|
|
506
|
-
}
|
|
507
|
-
|
|
508
639
|
branch() {
|
|
509
|
-
|
|
640
|
+
const { states, reference, popped, held } = this;
|
|
641
|
+
const { properties, idx } = states.value;
|
|
642
|
+
|
|
643
|
+
return new Resolver(
|
|
644
|
+
states.replace({ properties: new Map(properties), idx }),
|
|
645
|
+
reference,
|
|
646
|
+
popped,
|
|
647
|
+
held,
|
|
648
|
+
);
|
|
510
649
|
}
|
|
511
650
|
|
|
512
651
|
accept(resolver) {
|
|
513
|
-
this.
|
|
652
|
+
this.states = resolver.states;
|
|
653
|
+
this.reference = resolver.reference;
|
|
654
|
+
this.popped = resolver.popped;
|
|
655
|
+
this.held = resolver.held;
|
|
514
656
|
|
|
515
657
|
return this;
|
|
516
658
|
}
|