@bablr/agast-vm 0.2.0 → 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/context.js +22 -52
- package/lib/evaluate.js +58 -187
- package/lib/node.js +36 -0
- package/lib/path.js +9 -131
- package/lib/state.js +337 -20
- package/package.json +5 -9
package/lib/context.js
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import { reifyExpressionShallow, reifyExpression } from '@bablr/agast-vm-helpers';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getCooked as getCookedFromTree,
|
|
4
|
+
sourceTextFor as sourceTextForTree,
|
|
5
|
+
} from '@bablr/agast-helpers/tree';
|
|
6
|
+
import {
|
|
7
|
+
getCooked as getCookedFromStream,
|
|
8
|
+
sourceTextFor as sourceTextForStream,
|
|
9
|
+
} from '@bablr/agast-helpers/stream';
|
|
3
10
|
import { facades, actuals } from './facades.js';
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
if (!range) return;
|
|
7
|
-
|
|
8
|
-
const { 0: start, 1: end } = range;
|
|
9
|
-
|
|
10
|
-
const pastEnd = nextTerminals.get(end);
|
|
11
|
-
|
|
12
|
-
for (let term = start; term && term !== pastEnd; term = nextTerminals.get(term)) {
|
|
13
|
-
if (!(term === range[0] || term === range[1]) && term.type === 'OpenNodeTag') {
|
|
14
|
-
term = nextTerminals.get(tagNodes.get(term).endTag);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
yield term;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
12
|
+
const { isArray } = Array;
|
|
20
13
|
|
|
21
14
|
function* allTerminalsFor(range, nextTerminals) {
|
|
22
15
|
if (!range) return;
|
|
@@ -38,10 +31,6 @@ export const ContextFacade = class AgastContextFacade {
|
|
|
38
31
|
return actuals.get(this).nextTerminals.get(token);
|
|
39
32
|
}
|
|
40
33
|
|
|
41
|
-
ownTerminalsFor(range) {
|
|
42
|
-
return actuals.get(this).ownTerminalsFor(range);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
34
|
allTerminalsFor(range) {
|
|
46
35
|
return actuals.get(this).allTerminalsFor(range);
|
|
47
36
|
}
|
|
@@ -54,10 +43,6 @@ export const ContextFacade = class AgastContextFacade {
|
|
|
54
43
|
return actuals.get(this).reifyExpression(range);
|
|
55
44
|
}
|
|
56
45
|
|
|
57
|
-
getProperty(node, name) {
|
|
58
|
-
return actuals.get(this).getProperty(node, name);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
46
|
sourceTextFor(range) {
|
|
62
47
|
return actuals.get(this).sourceTextFor(range);
|
|
63
48
|
}
|
|
@@ -66,10 +51,6 @@ export const ContextFacade = class AgastContextFacade {
|
|
|
66
51
|
return actuals.get(this).buildRange(terminals);
|
|
67
52
|
}
|
|
68
53
|
|
|
69
|
-
nodeForTag(tag) {
|
|
70
|
-
return actuals.get(this).nodeForTag(tag);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
54
|
unbox(value) {
|
|
74
55
|
return actuals.get(this).unbox(value);
|
|
75
56
|
}
|
|
@@ -83,23 +64,12 @@ export const Context = class AgastContext {
|
|
|
83
64
|
constructor() {
|
|
84
65
|
this.prevTerminals = new WeakMap();
|
|
85
66
|
this.nextTerminals = new WeakMap();
|
|
86
|
-
this.tagNodes = new WeakMap();
|
|
87
67
|
this.unboxedValues = new WeakMap();
|
|
88
68
|
this.facade = new ContextFacade();
|
|
89
69
|
|
|
90
70
|
facades.set(this, this.facade);
|
|
91
71
|
}
|
|
92
72
|
|
|
93
|
-
getProperty(result, name) {
|
|
94
|
-
let startTag = result[0] || result;
|
|
95
|
-
|
|
96
|
-
if (startTag.type === 'Reference') {
|
|
97
|
-
startTag = this.nextTerminals.get(startTag);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return this.tagNodes.get(startTag).properties.get(name);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
73
|
isEmpty(range) {
|
|
104
74
|
const { path, parent } = this;
|
|
105
75
|
|
|
@@ -118,23 +88,21 @@ export const Context = class AgastContext {
|
|
|
118
88
|
}
|
|
119
89
|
|
|
120
90
|
allTerminalsReverseFor(range) {
|
|
121
|
-
return allTerminalsFor(range, this.prevTerminals);
|
|
91
|
+
return allTerminalsFor([...range].reverse(), this.prevTerminals);
|
|
122
92
|
}
|
|
123
93
|
|
|
124
|
-
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
ownTerminalsReverseFor(range) {
|
|
129
|
-
return ownTerminalsFor(range, this.prevTerminals, this.tagNodes);
|
|
94
|
+
getPreviousTerminal(token) {
|
|
95
|
+
return this.prevTerminals.get(token);
|
|
130
96
|
}
|
|
131
97
|
|
|
132
|
-
|
|
133
|
-
return this.
|
|
98
|
+
getNextTerminal(token) {
|
|
99
|
+
return this.nextTerminals.get(token);
|
|
134
100
|
}
|
|
135
101
|
|
|
136
|
-
sourceTextFor(
|
|
137
|
-
return
|
|
102
|
+
sourceTextFor(nodeOrRange) {
|
|
103
|
+
return isArray(nodeOrRange)
|
|
104
|
+
? sourceTextForStream(this.allTerminalsFor(nodeOrRange))
|
|
105
|
+
: sourceTextForTree(nodeOrRange);
|
|
138
106
|
}
|
|
139
107
|
|
|
140
108
|
buildRange(terminals) {
|
|
@@ -170,7 +138,9 @@ export const Context = class AgastContext {
|
|
|
170
138
|
return reifyExpression(value);
|
|
171
139
|
}
|
|
172
140
|
|
|
173
|
-
getCooked(
|
|
174
|
-
return
|
|
141
|
+
getCooked(nodeOrRange) {
|
|
142
|
+
return isArray(nodeOrRange)
|
|
143
|
+
? getCookedFromStream(this.allTerminalsFor(nodeOrRange))
|
|
144
|
+
: getCookedFromTree(nodeOrRange);
|
|
175
145
|
}
|
|
176
146
|
};
|
package/lib/evaluate.js
CHANGED
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
buildDoctypeTag,
|
|
10
10
|
buildNodeOpenTag,
|
|
11
11
|
buildNodeCloseTag,
|
|
12
|
-
} from '@bablr/agast-helpers/builders';
|
|
12
|
+
} from '@bablr/agast-vm-helpers/internal-builders';
|
|
13
|
+
import { deembedExpression } from '@bablr/agast-vm-helpers/deembed';
|
|
13
14
|
import { StreamIterable, getStreamIterator } from '@bablr/agast-helpers/stream';
|
|
14
15
|
import { printExpression } from '@bablr/agast-helpers/print';
|
|
15
|
-
import {
|
|
16
|
-
import { Path, Node } from './path.js';
|
|
16
|
+
import { getRange, getOpenTag } from '@bablr/agast-helpers/tree';
|
|
17
17
|
import { State } from './state.js';
|
|
18
18
|
import { facades } from './facades.js';
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ export const evaluate = (ctx, strategy, options) =>
|
|
|
21
21
|
new StreamIterable(__evaluate(ctx, strategy, options));
|
|
22
22
|
|
|
23
23
|
const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
24
|
-
let s = State.from(ctx);
|
|
24
|
+
let s = State.from(ctx, options.expressions);
|
|
25
25
|
|
|
26
26
|
const co = new Coroutine(getStreamIterator(strategy(facades.get(ctx), facades.get(s))));
|
|
27
27
|
|
|
@@ -34,8 +34,8 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
34
34
|
|
|
35
35
|
if (co.done) break;
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
|
|
37
|
+
const instr = co.value;
|
|
38
|
+
|
|
39
39
|
let returnValue = undefined;
|
|
40
40
|
|
|
41
41
|
const { verb, arguments: args = [] } = instr;
|
|
@@ -67,7 +67,9 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
case 'advance': {
|
|
70
|
-
const { 0:
|
|
70
|
+
const { 0: embeddedTerminal, 1: options } = args;
|
|
71
|
+
|
|
72
|
+
const terminal = embeddedTerminal.value;
|
|
71
73
|
|
|
72
74
|
if (
|
|
73
75
|
s.held &&
|
|
@@ -83,24 +85,16 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
83
85
|
switch (terminal?.type || 'Null') {
|
|
84
86
|
case 'DoctypeTag': {
|
|
85
87
|
const { attributes } = terminal.value;
|
|
86
|
-
const doctypeTag = buildDoctypeTag(attributes);
|
|
87
|
-
const rootPath = Path.from(ctx);
|
|
88
88
|
|
|
89
89
|
if (s.path) {
|
|
90
90
|
throw new Error();
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
yield* s.emit(doctypeTag);
|
|
96
|
-
|
|
97
|
-
returnValue = doctypeTag;
|
|
93
|
+
returnValue = s.advance(buildDoctypeTag(attributes));
|
|
98
94
|
break;
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
case 'Literal': {
|
|
102
|
-
const literal = buildLiteral(terminal.value);
|
|
103
|
-
|
|
104
98
|
if (!s.node.flags.token) {
|
|
105
99
|
throw new Error('literals must occur inside tokens');
|
|
106
100
|
}
|
|
@@ -109,36 +103,22 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
109
103
|
throw new Error('Cannot consume input while hold register is full');
|
|
110
104
|
}
|
|
111
105
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
returnValue = literal;
|
|
106
|
+
returnValue = s.advance(buildLiteral(terminal.value));
|
|
115
107
|
break;
|
|
116
108
|
}
|
|
117
109
|
|
|
118
110
|
case 'Reference': {
|
|
119
111
|
const { name, isArray } = terminal.value;
|
|
120
112
|
|
|
121
|
-
const tag = buildReference(name, isArray);
|
|
122
|
-
|
|
123
113
|
if (s.result.type === 'Reference') {
|
|
124
114
|
throw new Error('A reference must have a non-reference value');
|
|
125
115
|
}
|
|
126
116
|
|
|
127
|
-
if (
|
|
128
|
-
throw new Error();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (s.node.flags.token) {
|
|
132
|
-
throw new Error();
|
|
117
|
+
if (s.node?.flags.token) {
|
|
118
|
+
throw new Error('A token node cannot contain a reference');
|
|
133
119
|
}
|
|
134
120
|
|
|
135
|
-
s.
|
|
136
|
-
|
|
137
|
-
s.path = s.path.push(ctx, tag);
|
|
138
|
-
|
|
139
|
-
yield* s.emit(tag);
|
|
140
|
-
|
|
141
|
-
returnValue = tag;
|
|
121
|
+
returnValue = s.advance(buildReference(name, isArray));
|
|
142
122
|
break;
|
|
143
123
|
}
|
|
144
124
|
|
|
@@ -147,36 +127,7 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
147
127
|
|
|
148
128
|
if (reference?.type !== 'Reference') throw new Error();
|
|
149
129
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const gapTag = buildGap();
|
|
153
|
-
|
|
154
|
-
s.held = null;
|
|
155
|
-
|
|
156
|
-
yield* s.emit(gapTag);
|
|
157
|
-
|
|
158
|
-
returnValue = gapTag;
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
case 'Shift': {
|
|
163
|
-
const { tagNodes, prevTerminals } = ctx;
|
|
164
|
-
const tag = buildShift();
|
|
165
|
-
|
|
166
|
-
const finishedNode = tagNodes.get(s.result);
|
|
167
|
-
const finishedPath = finishedNode.path;
|
|
168
|
-
|
|
169
|
-
s.held = { node: finishedNode, path: finishedPath };
|
|
170
|
-
|
|
171
|
-
if (!finishedNode.openTag.value.flags.expression) {
|
|
172
|
-
throw new Error();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
s.path = finishedPath;
|
|
176
|
-
|
|
177
|
-
yield* s.emit(tag);
|
|
178
|
-
|
|
179
|
-
returnValue = tag;
|
|
130
|
+
returnValue = s.advance(buildGap());
|
|
180
131
|
break;
|
|
181
132
|
}
|
|
182
133
|
|
|
@@ -185,127 +136,39 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
185
136
|
|
|
186
137
|
if (reference?.type !== 'Reference') throw new Error();
|
|
187
138
|
|
|
188
|
-
|
|
139
|
+
returnValue = s.advance(buildNull());
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
189
142
|
|
|
190
|
-
|
|
143
|
+
case 'Shift': {
|
|
144
|
+
const finishedNode = s.nodeForTag(s.result);
|
|
191
145
|
|
|
192
|
-
|
|
146
|
+
if (!getOpenTag(finishedNode).value.flags.expression) {
|
|
147
|
+
throw new Error();
|
|
148
|
+
}
|
|
193
149
|
|
|
194
|
-
returnValue =
|
|
150
|
+
returnValue = s.advance(buildShift());
|
|
195
151
|
break;
|
|
196
152
|
}
|
|
197
153
|
|
|
198
154
|
case 'OpenNodeTag': {
|
|
199
|
-
const { flags, language, type,
|
|
200
|
-
const { unboundAttributes } = options || {};
|
|
201
|
-
const reference = s.result;
|
|
202
|
-
const openTag = buildNodeOpenTag(flags, language, type, intrinsicValue, attributes);
|
|
203
|
-
|
|
204
|
-
if (!type) {
|
|
205
|
-
s.node = Node.from(s.path, openTag);
|
|
206
|
-
ctx.tagNodes.set(openTag, s.node);
|
|
207
|
-
} else {
|
|
208
|
-
if (!flags.trivia && !flags.escape && s.node.type) {
|
|
209
|
-
if (
|
|
210
|
-
reference.type !== 'Reference' &&
|
|
211
|
-
reference.type !== 'Shift' &&
|
|
212
|
-
reference.type !== 'OpenFragmentTag'
|
|
213
|
-
) {
|
|
214
|
-
throw new Error('Invalid location for OpenNodeTag');
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const { flags: openFlags } = openTag.value;
|
|
219
|
-
|
|
220
|
-
if (!(openFlags.trivia || openFlags.escape) && !s.path.depth) {
|
|
221
|
-
const tag = buildReference('root', false);
|
|
222
|
-
s.path = s.path.push(ctx, tag);
|
|
223
|
-
s.node.resolver.consume(tag);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const newNode = s.node.push(s.path, openTag);
|
|
227
|
-
|
|
228
|
-
newNode.unboundAttributes = new Set(unboundAttributes);
|
|
229
|
-
|
|
230
|
-
ctx.tagNodes.set(openTag, newNode);
|
|
231
|
-
|
|
232
|
-
if (!intrinsicValue) {
|
|
233
|
-
s.node = newNode;
|
|
234
|
-
} else {
|
|
235
|
-
s.path = s.path.parent;
|
|
236
|
-
|
|
237
|
-
if (s.path.depth > 1) {
|
|
238
|
-
const { properties } = s.node;
|
|
239
|
-
const { name: refName, isArray } = reference.value;
|
|
240
|
-
|
|
241
|
-
if (!isArray) {
|
|
242
|
-
properties.set(refName, [openTag, openTag]);
|
|
243
|
-
} else {
|
|
244
|
-
if (!properties.has(refName)) {
|
|
245
|
-
properties.set(refName, []);
|
|
246
|
-
}
|
|
247
|
-
properties.get(refName).push([openTag, openTag]);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
155
|
+
const { flags, language, type, attributes } = terminal.value;
|
|
252
156
|
|
|
253
|
-
|
|
157
|
+
if (language && !language.startsWith('https://')) {
|
|
158
|
+
throw new Error('Expected an absolute-language tag');
|
|
159
|
+
}
|
|
254
160
|
|
|
255
|
-
returnValue =
|
|
161
|
+
returnValue = s.advance(
|
|
162
|
+
buildNodeOpenTag(flags, language, type, attributes),
|
|
163
|
+
deembedExpression(options),
|
|
164
|
+
);
|
|
256
165
|
break;
|
|
257
166
|
}
|
|
258
167
|
|
|
259
168
|
case 'CloseNodeTag': {
|
|
260
169
|
const { type, language } = terminal.value;
|
|
261
|
-
const { openTag } = s.node;
|
|
262
|
-
const { flags, type: openType } = openTag.value;
|
|
263
|
-
|
|
264
|
-
const closeTag = buildNodeCloseTag(type, language);
|
|
265
|
-
|
|
266
|
-
if (openType) {
|
|
267
|
-
if (s.node.unboundAttributes?.size)
|
|
268
|
-
throw new Error('Grammar failed to bind all attributes');
|
|
269
|
-
|
|
270
|
-
if (!type) throw new Error(`CloseNodeTag must have type`);
|
|
271
|
-
|
|
272
|
-
if (s.path.depth > 1 && type !== openType)
|
|
273
|
-
throw new Error(
|
|
274
|
-
`Grammar close {type: ${type}} did not match open {type: ${openType}}`,
|
|
275
|
-
);
|
|
276
170
|
|
|
277
|
-
|
|
278
|
-
const { name: refName, isArray } = s.path.reference.value;
|
|
279
|
-
|
|
280
|
-
// is this right?
|
|
281
|
-
if (s.path.depth > 2) {
|
|
282
|
-
const { properties } = s.node.parent;
|
|
283
|
-
|
|
284
|
-
if (!isArray) {
|
|
285
|
-
properties.set(refName, [openTag, closeTag]);
|
|
286
|
-
} else {
|
|
287
|
-
if (!properties.has(refName)) {
|
|
288
|
-
properties.set(refName, []);
|
|
289
|
-
}
|
|
290
|
-
properties.get(refName).push([openTag, closeTag]);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
ctx.tagNodes.set(closeTag, s.node);
|
|
296
|
-
|
|
297
|
-
s.node.closeTag = closeTag;
|
|
298
|
-
|
|
299
|
-
s.node = s.node.parent;
|
|
300
|
-
|
|
301
|
-
if (!(flags.trivia || flags.escape)) {
|
|
302
|
-
s.path = s.path.parent;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
yield* s.emit(closeTag, flags.expression);
|
|
307
|
-
|
|
308
|
-
returnValue = closeTag;
|
|
171
|
+
returnValue = s.advance(buildNodeCloseTag(type, language));
|
|
309
172
|
break;
|
|
310
173
|
}
|
|
311
174
|
|
|
@@ -313,19 +176,21 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
313
176
|
throw new Error();
|
|
314
177
|
}
|
|
315
178
|
|
|
179
|
+
yield* s.emit();
|
|
180
|
+
|
|
316
181
|
break;
|
|
317
182
|
}
|
|
318
183
|
|
|
319
184
|
case 'bindAttribute': {
|
|
320
185
|
const { 0: key, 1: value } = args;
|
|
321
186
|
|
|
322
|
-
const { unboundAttributes } = s
|
|
187
|
+
const { unboundAttributes } = s;
|
|
323
188
|
|
|
324
189
|
if (!unboundAttributes || !unboundAttributes.has(key)) {
|
|
325
190
|
throw new Error('No unbound attribute to bind');
|
|
326
191
|
}
|
|
327
192
|
|
|
328
|
-
if (!s.node.
|
|
193
|
+
if (!s.node.type) {
|
|
329
194
|
throw new Error('Cannot bind attribute to fragment');
|
|
330
195
|
}
|
|
331
196
|
|
|
@@ -342,37 +207,43 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
342
207
|
|
|
343
208
|
unboundAttributes.delete(key);
|
|
344
209
|
|
|
345
|
-
const
|
|
210
|
+
const openTag = s.node.children[0];
|
|
346
211
|
|
|
347
212
|
if (value != null) {
|
|
348
|
-
const { flags, language, type
|
|
213
|
+
const { flags, language, type } = openTag.value;
|
|
349
214
|
const attributes = { ...openTag.value.attributes, [key]: value };
|
|
350
|
-
const
|
|
215
|
+
const newOpenTag = buildNodeOpenTag(flags, language, type, attributes);
|
|
351
216
|
|
|
352
|
-
let
|
|
217
|
+
let openNext = ctx.nextTerminals.get(openTag);
|
|
353
218
|
let startPrev = ctx.prevTerminals.get(openTag);
|
|
354
219
|
|
|
355
|
-
ctx.prevTerminals.set(
|
|
356
|
-
ctx.nextTerminals.set(startPrev,
|
|
220
|
+
ctx.prevTerminals.set(newOpenTag, startPrev);
|
|
221
|
+
ctx.nextTerminals.set(startPrev, newOpenTag);
|
|
222
|
+
|
|
223
|
+
if (s.node !== s.tagNodes.get(openTag)) throw new Error();
|
|
224
|
+
if (s.path !== s.tagPaths.get(openTag)) throw new Error();
|
|
357
225
|
|
|
358
|
-
|
|
226
|
+
s.node.attributes = attributes;
|
|
359
227
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
228
|
+
s.tagNodes.set(newOpenTag, s.node);
|
|
229
|
+
s.tagPaths.set(newOpenTag, s.path);
|
|
230
|
+
|
|
231
|
+
if (openNext) {
|
|
232
|
+
ctx.nextTerminals.set(newOpenTag, openNext);
|
|
233
|
+
ctx.prevTerminals.set(openNext, newOpenTag);
|
|
363
234
|
} else {
|
|
364
235
|
// could this terminal be stored anywhere else?
|
|
365
|
-
s.result =
|
|
236
|
+
s.result = newOpenTag;
|
|
366
237
|
}
|
|
367
238
|
|
|
368
|
-
s.node.
|
|
239
|
+
s.node.children[0] = newOpenTag;
|
|
369
240
|
}
|
|
370
241
|
|
|
371
242
|
if (!unboundAttributes.size) {
|
|
372
243
|
yield* s.emit();
|
|
373
244
|
}
|
|
374
245
|
|
|
375
|
-
returnValue = s.node
|
|
246
|
+
returnValue = getRange(s.node);
|
|
376
247
|
break;
|
|
377
248
|
}
|
|
378
249
|
|
|
@@ -401,8 +272,6 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
401
272
|
co.advance(returnValue);
|
|
402
273
|
}
|
|
403
274
|
|
|
404
|
-
s.path = s.path.parent;
|
|
405
|
-
|
|
406
275
|
if (s.depth > 0) {
|
|
407
276
|
throw new Error('Did not unwind state stack');
|
|
408
277
|
}
|
|
@@ -410,4 +279,6 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
|
|
|
410
279
|
if (s.path?.depth > 0) {
|
|
411
280
|
throw new Error('Did not unwind path stack');
|
|
412
281
|
}
|
|
282
|
+
|
|
283
|
+
return s.nodeForTag(s.result);
|
|
413
284
|
};
|
package/lib/node.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getRange, getOpenTag, getCloseTag } from '@bablr/agast-helpers/tree';
|
|
2
|
+
import { facades, actuals } from './facades.js';
|
|
3
|
+
|
|
4
|
+
export const NodeFacade = class AgastNodeFacade {
|
|
5
|
+
constructor(path) {
|
|
6
|
+
facades.set(path, this);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get language() {
|
|
10
|
+
return actuals.get(this).language;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get type() {
|
|
14
|
+
return actuals.get(this).type;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get range() {
|
|
18
|
+
return getRange(actuals.get(this));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get openTag() {
|
|
22
|
+
return getOpenTag(actuals.get(this));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get closeTag() {
|
|
26
|
+
return getCloseTag(actuals.get(this));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get flags() {
|
|
30
|
+
return actuals.get(this).flags;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get attributes() {
|
|
34
|
+
return actuals.get(this).attributes;
|
|
35
|
+
}
|
|
36
|
+
};
|
package/lib/path.js
CHANGED
|
@@ -1,58 +1,7 @@
|
|
|
1
1
|
import { WeakStackFrame } from '@bablr/weak-stack';
|
|
2
|
-
import { Resolver } from '@bablr/agast-helpers/tree';
|
|
3
2
|
import { skipToDepth, buildSkips } from './utils/skip.js';
|
|
4
3
|
import { facades, actuals } from './facades.js';
|
|
5
4
|
|
|
6
|
-
export const NodeFacade = class AgastNodeFacade {
|
|
7
|
-
constructor(path) {
|
|
8
|
-
facades.set(path, this);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
get language() {
|
|
12
|
-
return actuals.get(this).language;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
get type() {
|
|
16
|
-
return actuals.get(this).type;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
get path() {
|
|
20
|
-
return actuals.get(this).path;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
get parent() {
|
|
24
|
-
return facades.get(actuals.get(this).parent);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
get range() {
|
|
28
|
-
return actuals.get(this).range;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
get openTag() {
|
|
32
|
-
return actuals.get(this).openTag;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get closeTag() {
|
|
36
|
-
return actuals.get(this).closeTag;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get depth() {
|
|
40
|
-
return actuals.get(this).depth;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
get flags() {
|
|
44
|
-
return actuals.get(this).flags;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
get attributes() {
|
|
48
|
-
return actuals.get(this).attributes;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
at(depth) {
|
|
52
|
-
return facades.get(actuals.get(this).at(depth));
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
5
|
export const PathFacade = class AgastPathFacade {
|
|
57
6
|
constructor(path) {
|
|
58
7
|
facades.set(path, this);
|
|
@@ -83,85 +32,6 @@ export const PathFacade = class AgastPathFacade {
|
|
|
83
32
|
}
|
|
84
33
|
};
|
|
85
34
|
|
|
86
|
-
export const Node = class AgastNode extends WeakStackFrame {
|
|
87
|
-
static from(path, openTag) {
|
|
88
|
-
return AgastNode.create(path, openTag);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
constructor(
|
|
92
|
-
path,
|
|
93
|
-
openTag,
|
|
94
|
-
closeTag = null,
|
|
95
|
-
properties = new Map(),
|
|
96
|
-
resolver = new Resolver(),
|
|
97
|
-
unboundAttributes = null,
|
|
98
|
-
) {
|
|
99
|
-
super();
|
|
100
|
-
|
|
101
|
-
this.path = path;
|
|
102
|
-
this.openTag = openTag;
|
|
103
|
-
this.closeTag = closeTag;
|
|
104
|
-
this.properties = properties;
|
|
105
|
-
this.resolver = resolver;
|
|
106
|
-
this.unboundAttributes = unboundAttributes;
|
|
107
|
-
|
|
108
|
-
buildSkips(this);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
get language() {
|
|
112
|
-
return this.openTag.value?.language;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
get type() {
|
|
116
|
-
return this.openTag.value?.type || null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
get flags() {
|
|
120
|
-
return this.openTag.value?.flags || {};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
get attributes() {
|
|
124
|
-
return this.openTag.value?.attributes || {};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
at(depth) {
|
|
128
|
-
return skipToDepth(depth, this);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
*parents(includeSelf = false) {
|
|
132
|
-
if (includeSelf) yield this;
|
|
133
|
-
let parent = this;
|
|
134
|
-
while ((parent = parent.parent)) {
|
|
135
|
-
yield parent;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
branch() {
|
|
140
|
-
const { path, openTag, closeTag, properties, resolver, unboundAttributes } = this;
|
|
141
|
-
|
|
142
|
-
return this.replace(
|
|
143
|
-
path,
|
|
144
|
-
openTag,
|
|
145
|
-
closeTag,
|
|
146
|
-
new Map(properties), // there is probably a better way
|
|
147
|
-
resolver.branch(),
|
|
148
|
-
new Set(unboundAttributes),
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
accept(node) {
|
|
153
|
-
this.path = node.path;
|
|
154
|
-
this.openTag = node.openTag;
|
|
155
|
-
this.closeTag = node.closeTag;
|
|
156
|
-
this.properties = node.properties;
|
|
157
|
-
this.unboundAttributes = node.unboundAttributes;
|
|
158
|
-
|
|
159
|
-
this.resolver.accept(node.resolver);
|
|
160
|
-
|
|
161
|
-
return this;
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
|
|
165
35
|
export const Path = class AgastPath extends WeakStackFrame {
|
|
166
36
|
static from(context, tag) {
|
|
167
37
|
return Path.create(context, tag);
|
|
@@ -170,7 +40,7 @@ export const Path = class AgastPath extends WeakStackFrame {
|
|
|
170
40
|
constructor(context, reference) {
|
|
171
41
|
super();
|
|
172
42
|
|
|
173
|
-
if (reference && reference.type !== 'Reference') {
|
|
43
|
+
if (reference && reference.type !== 'Reference' && reference.type !== 'DoctypeTag') {
|
|
174
44
|
throw new Error('Invalid reference for path');
|
|
175
45
|
}
|
|
176
46
|
|
|
@@ -182,6 +52,14 @@ export const Path = class AgastPath extends WeakStackFrame {
|
|
|
182
52
|
new PathFacade(this);
|
|
183
53
|
}
|
|
184
54
|
|
|
55
|
+
get name() {
|
|
56
|
+
return this.reference?.value.name || '[anonymous]';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get isArray() {
|
|
60
|
+
return this.reference?.value.isArray || false;
|
|
61
|
+
}
|
|
62
|
+
|
|
185
63
|
at(depth) {
|
|
186
64
|
return skipToDepth(depth, this);
|
|
187
65
|
}
|
package/lib/state.js
CHANGED
|
@@ -1,7 +1,58 @@
|
|
|
1
|
+
import emptyStack from '@iter-tools/imm-stack';
|
|
1
2
|
import { WeakStackFrame } from '@bablr/weak-stack';
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Resolver,
|
|
5
|
+
add,
|
|
6
|
+
createNode,
|
|
7
|
+
getOpenTag,
|
|
8
|
+
getCloseTag,
|
|
9
|
+
branchNode,
|
|
10
|
+
acceptNode,
|
|
11
|
+
finalizeNode,
|
|
12
|
+
getRoot,
|
|
13
|
+
} from '@bablr/agast-helpers/tree';
|
|
14
|
+
import {
|
|
15
|
+
buildBeginningOfStreamToken,
|
|
16
|
+
buildEmbeddedNode,
|
|
17
|
+
nodeFlags,
|
|
18
|
+
} from '@bablr/agast-vm-helpers/internal-builders';
|
|
19
|
+
import * as sym from '@bablr/agast-helpers/symbols';
|
|
3
20
|
import { facades, actuals } from './facades.js';
|
|
4
|
-
import {
|
|
21
|
+
import { Path } from './path.js';
|
|
22
|
+
|
|
23
|
+
const { hasOwn } = Object;
|
|
24
|
+
|
|
25
|
+
const arrayLast = (arr) => arr[arr.length - 1];
|
|
26
|
+
|
|
27
|
+
const createNodeWithState = (startTag, options = {}) => {
|
|
28
|
+
const { unboundAttributes } = options;
|
|
29
|
+
const node = createNode(startTag);
|
|
30
|
+
nodeStates.set(node, {
|
|
31
|
+
resolver: new Resolver(node),
|
|
32
|
+
unboundAttributes: new Set(unboundAttributes || []),
|
|
33
|
+
});
|
|
34
|
+
return node;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const symbolTypeFor = (type) => {
|
|
38
|
+
// prettier-ignore
|
|
39
|
+
switch (type) {
|
|
40
|
+
case 'Null': return sym.null;
|
|
41
|
+
case 'Gap': return sym.gap;
|
|
42
|
+
default: throw new Error();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const buildStubNode = (tag) => {
|
|
47
|
+
return {
|
|
48
|
+
flags: nodeFlags,
|
|
49
|
+
language: null,
|
|
50
|
+
type: symbolTypeFor(tag.type),
|
|
51
|
+
children: [tag],
|
|
52
|
+
properties: {},
|
|
53
|
+
attributes: {},
|
|
54
|
+
};
|
|
55
|
+
};
|
|
5
56
|
|
|
6
57
|
export const StateFacade = class AgastStateFacade {
|
|
7
58
|
constructor(state) {
|
|
@@ -28,6 +79,10 @@ export const StateFacade = class AgastStateFacade {
|
|
|
28
79
|
return actuals.get(this).node;
|
|
29
80
|
}
|
|
30
81
|
|
|
82
|
+
get parentNode() {
|
|
83
|
+
return actuals.get(this).parentNode;
|
|
84
|
+
}
|
|
85
|
+
|
|
31
86
|
get holding() {
|
|
32
87
|
return actuals.get(this).holding;
|
|
33
88
|
}
|
|
@@ -39,41 +94,96 @@ export const StateFacade = class AgastStateFacade {
|
|
|
39
94
|
get ctx() {
|
|
40
95
|
return this.context;
|
|
41
96
|
}
|
|
97
|
+
|
|
98
|
+
nodeForPath(path) {
|
|
99
|
+
return actuals.get(this).nodeForPath(path);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pathForTag(tag) {
|
|
103
|
+
return actuals.get(this).pathForTag(tag);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
nodeForTag(tag) {
|
|
107
|
+
return actuals.get(this).nodeForTag(tag);
|
|
108
|
+
}
|
|
42
109
|
};
|
|
43
110
|
|
|
111
|
+
export const nodeStates = new WeakMap();
|
|
112
|
+
|
|
44
113
|
export const State = class AgastState extends WeakStackFrame {
|
|
45
114
|
constructor(
|
|
46
115
|
context,
|
|
116
|
+
expressions = emptyStack,
|
|
47
117
|
path = null,
|
|
48
118
|
node = null,
|
|
49
119
|
result = buildBeginningOfStreamToken(),
|
|
50
120
|
emitted = null,
|
|
51
121
|
held = null,
|
|
122
|
+
internalContext = {
|
|
123
|
+
pathNodes: new WeakMap(),
|
|
124
|
+
tagPaths: new WeakMap(),
|
|
125
|
+
tagNodes: new WeakMap(),
|
|
126
|
+
},
|
|
52
127
|
) {
|
|
53
128
|
super();
|
|
54
129
|
|
|
55
130
|
if (!context) throw new Error('invalid args to tagState');
|
|
56
131
|
|
|
57
132
|
this.context = context;
|
|
133
|
+
this.expressions = expressions;
|
|
58
134
|
this.path = path;
|
|
59
135
|
this.node = node;
|
|
60
136
|
this.result = result;
|
|
61
137
|
this.emitted = emitted;
|
|
62
138
|
this.held = held;
|
|
139
|
+
this.internalContext = internalContext;
|
|
63
140
|
|
|
64
141
|
new StateFacade(this);
|
|
65
142
|
}
|
|
66
143
|
|
|
67
|
-
static from(context) {
|
|
68
|
-
return State.create(context,
|
|
144
|
+
static from(context, expressions = []) {
|
|
145
|
+
return State.create(context, emptyStack.push(...[...expressions].reverse()));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get pathNodes() {
|
|
149
|
+
return this.internalContext.pathNodes;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get tagPaths() {
|
|
153
|
+
return this.internalContext.tagPaths;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get tagNodes() {
|
|
157
|
+
return this.internalContext.tagNodes;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get unboundAttributes() {
|
|
161
|
+
return nodeStates.get(this.node).unboundAttributes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get resolver() {
|
|
165
|
+
return nodeStates.get(this.node).resolver;
|
|
69
166
|
}
|
|
70
167
|
|
|
71
168
|
get holding() {
|
|
72
169
|
return !!this.held;
|
|
73
170
|
}
|
|
74
171
|
|
|
75
|
-
|
|
76
|
-
|
|
172
|
+
nodeForPath(path) {
|
|
173
|
+
return this.pathNodes.get(path);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
pathForTag(ref) {
|
|
177
|
+
return this.tagPaths.get(ref);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
nodeForTag(tag) {
|
|
181
|
+
return this.tagNodes.get(tag);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
advance(terminal, options = {}) {
|
|
185
|
+
const ctx = this.context;
|
|
186
|
+
const { prevTerminals, nextTerminals } = ctx;
|
|
77
187
|
|
|
78
188
|
if (terminal) {
|
|
79
189
|
if (prevTerminals.has(terminal)) {
|
|
@@ -90,24 +200,203 @@ export const State = class AgastState extends WeakStackFrame {
|
|
|
90
200
|
prevTerminals.set(terminal, this.result);
|
|
91
201
|
nextTerminals.set(this.result, terminal);
|
|
92
202
|
|
|
93
|
-
|
|
203
|
+
switch (terminal.type) {
|
|
204
|
+
case 'DoctypeTag': {
|
|
205
|
+
this.path = Path.from(ctx, terminal);
|
|
206
|
+
|
|
207
|
+
this.tagPaths.set(terminal, this.path);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
case 'OpenNodeTag': {
|
|
212
|
+
const openTag = terminal;
|
|
213
|
+
const { type, flags } = terminal.value;
|
|
214
|
+
this.node = createNodeWithState(terminal, options);
|
|
215
|
+
|
|
216
|
+
const reference = this.result;
|
|
217
|
+
|
|
218
|
+
this.node.children.push(terminal);
|
|
219
|
+
|
|
220
|
+
if (!type) {
|
|
221
|
+
this.node.attributes = this.result.value.attributes;
|
|
222
|
+
} else {
|
|
223
|
+
if (!flags.trivia && !flags.escape) {
|
|
224
|
+
if (
|
|
225
|
+
reference.type !== 'Reference' &&
|
|
226
|
+
reference.type !== 'Shift' &&
|
|
227
|
+
reference.type !== 'OpenNodeTag' &&
|
|
228
|
+
!reference.value.type
|
|
229
|
+
) {
|
|
230
|
+
throw new Error('Invalid location for OpenNodeTag');
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
this.path = this.path.push(ctx, null);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.pathNodes.set(this.node, this.path);
|
|
238
|
+
this.pathNodes.set(this.path, this.node);
|
|
239
|
+
|
|
240
|
+
this.tagNodes.set(openTag, this.node);
|
|
241
|
+
this.tagPaths.set(openTag, this.path);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
case 'CloseNodeTag': {
|
|
246
|
+
const openTag = this.node.children[0];
|
|
247
|
+
const { flags, type: openType } = openTag.value;
|
|
248
|
+
const closeTag = terminal;
|
|
249
|
+
const { type } = closeTag.value;
|
|
250
|
+
|
|
251
|
+
this.node.children.push(terminal);
|
|
252
|
+
|
|
253
|
+
if (openType) {
|
|
254
|
+
if (this.node.unboundAttributes?.size)
|
|
255
|
+
throw new Error('Grammar failed to bind all attributes');
|
|
256
|
+
|
|
257
|
+
if (!type) throw new Error(`CloseNodeTag must have type`);
|
|
258
|
+
|
|
259
|
+
if (type !== openType)
|
|
260
|
+
throw new Error(
|
|
261
|
+
`Grammar close {type: ${type}} did not match open {type: ${openType}}`,
|
|
262
|
+
);
|
|
94
263
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
264
|
+
if (!flags.escape && !flags.trivia) {
|
|
265
|
+
const { name: refName, isArray } = this.path.reference.value;
|
|
266
|
+
|
|
267
|
+
const { properties } = this.parentNode;
|
|
268
|
+
|
|
269
|
+
if (!isArray) {
|
|
270
|
+
properties[refName] = this.node;
|
|
271
|
+
} else {
|
|
272
|
+
if (!hasOwn(properties, refName)) {
|
|
273
|
+
properties[refName] = [];
|
|
274
|
+
}
|
|
275
|
+
properties[refName].push(this.node);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
this.parentNode.children.push(buildEmbeddedNode(this.node));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this.tagNodes.set(closeTag, this.node);
|
|
283
|
+
this.tagPaths.set(closeTag, this.path);
|
|
284
|
+
|
|
285
|
+
finalizeNode(this.node);
|
|
286
|
+
|
|
287
|
+
this.node = this.parentNode;
|
|
288
|
+
this.path = this.path.parent;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
case 'Reference': {
|
|
293
|
+
if (this.path.depth) {
|
|
294
|
+
nodeStates.get(this.node).resolver.consume(terminal);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.node.children.push(terminal);
|
|
298
|
+
|
|
299
|
+
this.path = this.path.push(ctx, terminal);
|
|
300
|
+
|
|
301
|
+
this.tagPaths.set(terminal, this.path);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
case 'Gap': {
|
|
306
|
+
this.tagPaths.set(terminal, this.path);
|
|
307
|
+
|
|
308
|
+
let target;
|
|
309
|
+
let ref = arrayLast(this.node.children);
|
|
310
|
+
|
|
311
|
+
if (ref.type !== 'Reference') throw new Error();
|
|
312
|
+
|
|
313
|
+
if (this.held) {
|
|
314
|
+
target = this.held.node;
|
|
315
|
+
|
|
316
|
+
this.held = null;
|
|
317
|
+
} else if (this.expressions.size) {
|
|
318
|
+
const expression = this.expressions.value;
|
|
319
|
+
target = getRoot(expression);
|
|
320
|
+
this.expressions = this.expressions.pop();
|
|
321
|
+
} else {
|
|
322
|
+
target = buildStubNode(terminal);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.pathNodes.set(this.pathForTag(ref), target);
|
|
326
|
+
add(this.node, ref, target);
|
|
327
|
+
|
|
328
|
+
this.path = this.path.parent;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
case 'Null': {
|
|
333
|
+
this.tagPaths.set(terminal, this.path);
|
|
334
|
+
|
|
335
|
+
const { properties } = this.node;
|
|
336
|
+
const { isArray, name } = this.result.value;
|
|
337
|
+
|
|
338
|
+
const newNode = buildStubNode(terminal);
|
|
339
|
+
|
|
340
|
+
if (!hasOwn(properties, name)) {
|
|
341
|
+
// TODO is this behavior right
|
|
342
|
+
properties[name] = isArray ? [] : newNode;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this.pathNodes.set(this.path, newNode);
|
|
346
|
+
|
|
347
|
+
this.path = this.path.parent;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
case 'Shift': {
|
|
352
|
+
const finishedNode = this.nodeForTag(this.result);
|
|
353
|
+
const ref = ctx.getPreviousTerminal(getOpenTag(finishedNode));
|
|
354
|
+
const finishedPath = this.pathForTag(ref);
|
|
355
|
+
const { properties } = this.node;
|
|
356
|
+
|
|
357
|
+
this.pathNodes.set(finishedPath, null);
|
|
358
|
+
|
|
359
|
+
this.held = { node: finishedNode, path: finishedPath };
|
|
360
|
+
|
|
361
|
+
let node = properties[ref.value.name];
|
|
362
|
+
|
|
363
|
+
if (ref.value.isArray) {
|
|
364
|
+
node = arrayLast(node);
|
|
365
|
+
properties[ref.value.name].pop();
|
|
366
|
+
} else {
|
|
367
|
+
properties[ref.value.name] = null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
this.path = finishedPath;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
case 'Literal': {
|
|
375
|
+
this.node.children.push(terminal);
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
default:
|
|
380
|
+
throw new Error();
|
|
99
381
|
}
|
|
100
382
|
}
|
|
101
383
|
|
|
102
|
-
|
|
103
|
-
|
|
384
|
+
this.result = terminal;
|
|
385
|
+
|
|
386
|
+
return terminal;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
*emit() {
|
|
390
|
+
const { nextTerminals } = this.context;
|
|
391
|
+
if (!this.depth) {
|
|
392
|
+
let emittable = this.emitted ? nextTerminals.get(this.emitted) : this.result;
|
|
104
393
|
|
|
105
394
|
while (
|
|
106
395
|
emittable &&
|
|
107
396
|
!(
|
|
108
397
|
emittable.type === 'OpenNodeTag' &&
|
|
109
398
|
emittable.value.type &&
|
|
110
|
-
|
|
399
|
+
nodeStates.get(this.nodeForTag(emittable)).unboundAttributes?.size
|
|
111
400
|
)
|
|
112
401
|
) {
|
|
113
402
|
yield emittable;
|
|
@@ -115,8 +404,6 @@ export const State = class AgastState extends WeakStackFrame {
|
|
|
115
404
|
emittable = nextTerminals.get(this.emitted);
|
|
116
405
|
}
|
|
117
406
|
}
|
|
118
|
-
|
|
119
|
-
return terminal;
|
|
120
407
|
}
|
|
121
408
|
|
|
122
409
|
get ctx() {
|
|
@@ -131,10 +418,28 @@ export const State = class AgastState extends WeakStackFrame {
|
|
|
131
418
|
return !!this.parent;
|
|
132
419
|
}
|
|
133
420
|
|
|
421
|
+
get parentNode() {
|
|
422
|
+
return this.pathNodes.get(this.path.parent);
|
|
423
|
+
}
|
|
424
|
+
|
|
134
425
|
branch() {
|
|
135
|
-
const { context, path, node, result, emitted, held } = this;
|
|
426
|
+
const { context, expressions, path, node, result, emitted, held, internalContext } = this;
|
|
427
|
+
const { pathNodes } = internalContext;
|
|
428
|
+
|
|
429
|
+
const newNode = node && branchNode(node);
|
|
430
|
+
|
|
431
|
+
const nodeState = nodeStates.get(node);
|
|
432
|
+
|
|
433
|
+
pathNodes.set(path, newNode);
|
|
136
434
|
|
|
137
|
-
|
|
435
|
+
nodeStates.set(newNode, { ...nodeState, resolver: nodeState.resolver.branch() });
|
|
436
|
+
|
|
437
|
+
const nodeOpen = getOpenTag(node);
|
|
438
|
+
const nodeClose = getCloseTag(node);
|
|
439
|
+
if (nodeOpen) this.tagNodes.set(nodeOpen, newNode);
|
|
440
|
+
if (nodeClose) this.tagNodes.set(nodeClose, newNode);
|
|
441
|
+
|
|
442
|
+
return this.push(context, expressions, path, newNode, result, emitted, held, internalContext);
|
|
138
443
|
}
|
|
139
444
|
|
|
140
445
|
accept() {
|
|
@@ -144,10 +449,17 @@ export const State = class AgastState extends WeakStackFrame {
|
|
|
144
449
|
return null;
|
|
145
450
|
}
|
|
146
451
|
|
|
147
|
-
|
|
452
|
+
if (this.node && parent.node) {
|
|
453
|
+
acceptNode(parent.node, this.node);
|
|
454
|
+
const nodeState = nodeStates.get(this.node);
|
|
455
|
+
Object.assign(nodeStates.get(parent.node), nodeState);
|
|
456
|
+
} else {
|
|
457
|
+
parent.node = this.node;
|
|
458
|
+
}
|
|
148
459
|
|
|
149
460
|
// emitted isn't used here and probably doesn't need to be part of state
|
|
150
461
|
|
|
462
|
+
parent.expressions = this.expressions;
|
|
151
463
|
parent.result = this.result;
|
|
152
464
|
parent.held = this.held;
|
|
153
465
|
parent.path = this.path;
|
|
@@ -156,12 +468,17 @@ export const State = class AgastState extends WeakStackFrame {
|
|
|
156
468
|
}
|
|
157
469
|
|
|
158
470
|
reject() {
|
|
159
|
-
const { parent, context } = this;
|
|
471
|
+
const { parent, context, pathNodes, tagNodes } = this;
|
|
160
472
|
|
|
161
473
|
if (!parent) throw new Error('rejected root state');
|
|
162
474
|
|
|
163
475
|
context.nextTerminals.delete(parent.result);
|
|
164
476
|
|
|
477
|
+
pathNodes.set(parent.path, parent.node);
|
|
478
|
+
|
|
479
|
+
if (getOpenTag(parent.node)) tagNodes.set(getOpenTag(parent.node), parent.node);
|
|
480
|
+
if (getCloseTag(parent.node)) tagNodes.set(getCloseTag(parent.node), parent.node);
|
|
481
|
+
|
|
165
482
|
return parent;
|
|
166
483
|
}
|
|
167
484
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bablr/agast-vm",
|
|
3
3
|
"description": "A VM providing DOM-like guarantees about agAST trees",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"author": "Conrad Buck<conartist6@gmail.com>",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -11,24 +11,20 @@
|
|
|
11
11
|
".": "./lib/index.js"
|
|
12
12
|
},
|
|
13
13
|
"sideEffects": false,
|
|
14
|
-
"scripts": {
|
|
15
|
-
"test": "node ./test/runner.js"
|
|
16
|
-
},
|
|
17
14
|
"dependencies": {
|
|
18
|
-
"@bablr/agast-helpers": "0.
|
|
19
|
-
"@bablr/agast-vm-helpers": "0.
|
|
15
|
+
"@bablr/agast-helpers": "0.3.0",
|
|
16
|
+
"@bablr/agast-vm-helpers": "0.3.0",
|
|
20
17
|
"@bablr/coroutine": "0.1.0",
|
|
21
|
-
"@bablr/weak-stack": "0.1.0"
|
|
18
|
+
"@bablr/weak-stack": "0.1.0",
|
|
19
|
+
"@iter-tools/imm-stack": "1.1.0"
|
|
22
20
|
},
|
|
23
21
|
"devDependencies": {
|
|
24
22
|
"@bablr/agast-vm-strategy-passthrough": "github:bablr-lang/agast-vm-strategy-passthrough#2bd3a0c7311037af92c5b81941c79161499f6c9e",
|
|
25
23
|
"@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#49f5952efed27f94ee9b94340eb1563c440bf64e",
|
|
26
|
-
"@bablr/strategy_enhancer-debug-log": "0.1.1",
|
|
27
24
|
"enhanced-resolve": "^5.12.0",
|
|
28
25
|
"eslint": "^8.32.0",
|
|
29
26
|
"eslint-import-resolver-enhanced-resolve": "^1.0.5",
|
|
30
27
|
"eslint-plugin-import": "^2.27.5",
|
|
31
|
-
"expect": "29.7.0",
|
|
32
28
|
"iter-tools-es": "^7.3.1",
|
|
33
29
|
"prettier": "^2.6.2"
|
|
34
30
|
},
|