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