@bablr/agast-vm 0.6.0 → 0.7.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 CHANGED
@@ -1,34 +1,15 @@
1
- import { reifyExpressionShallow, reifyExpression } from '@bablr/agast-vm-helpers';
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';
10
- import { OpenNodeTag, CloseNodeTag } from '@bablr/agast-helpers/symbols';
11
- import { facades, actuals } from './facades.js';
12
-
13
- const { isArray } = Array;
14
-
15
- function* allTagsFor(range, nextTags) {
16
- if (!range) return;
17
- const { 0: start, 1: end } = range;
1
+ import { reifyExpression } from '@bablr/agast-vm-helpers';
2
+ import { sourceTextFor as sourceTextForTree } from '@bablr/agast-helpers/tree';
18
3
 
19
- const pastEnd = nextTags.get(end);
20
-
21
- for (let tag = start; tag && tag !== pastEnd; tag = nextTags.get(tag)) {
22
- yield tag;
23
- }
24
- }
4
+ import { allTagPathsFor } from './path.js';
5
+ import { facades, actuals } from './facades.js';
25
6
 
26
7
  export const ContextFacade = class AgastContextFacade {
27
- getPreviousTag(token) {
8
+ getPreviousTagPath(token) {
28
9
  return actuals.get(this).prevTags.get(token);
29
10
  }
30
11
 
31
- getNextTag(token) {
12
+ getNextTagPath(token) {
32
13
  return actuals.get(this).nextTags.get(token);
33
14
  }
34
15
 
@@ -36,10 +17,6 @@ export const ContextFacade = class AgastContextFacade {
36
17
  return actuals.get(this).allTagsFor(range);
37
18
  }
38
19
 
39
- getCooked(range) {
40
- return actuals.get(this).getCooked(range);
41
- }
42
-
43
20
  reifyExpression(range) {
44
21
  return actuals.get(this).reifyExpression(range);
45
22
  }
@@ -52,8 +29,8 @@ export const ContextFacade = class AgastContextFacade {
52
29
  return actuals.get(this).buildRange(tags);
53
30
  }
54
31
 
55
- unbox(value) {
56
- return actuals.get(this).unbox(value);
32
+ nodeForTag(tag) {
33
+ return actuals.get(this).nodeForTag(tag);
57
34
  }
58
35
  };
59
36
 
@@ -63,41 +40,22 @@ export const Context = class AgastContext {
63
40
  }
64
41
 
65
42
  constructor() {
66
- this.prevTags = new WeakMap();
67
- this.nextTags = new WeakMap();
68
- this.unboxedValues = new WeakMap();
43
+ this.tagNodes = new WeakMap();
69
44
  this.facade = new ContextFacade();
70
45
 
71
46
  facades.set(this, this.facade);
72
47
  }
73
48
 
74
- isEmpty(range) {
75
- const { path, parent } = this;
76
-
77
- if (range[0]?.type === OpenNodeTag && path !== parent.path) {
78
- const nextTag = this.nextTags.get(range[0]);
79
- if (!nextTag || nextTag.type === CloseNodeTag) {
80
- return null;
81
- }
82
- } else {
83
- return range[0] === range[1];
84
- }
85
- }
86
-
87
49
  allTagsFor(range) {
88
- return allTagsFor(range, this.nextTags);
50
+ return allTagPathsFor(range);
89
51
  }
90
52
 
91
53
  allTagsReverseFor(range) {
92
- return allTagsFor([...range].reverse(), this.prevTags);
54
+ throw new Error('not implemented');
93
55
  }
94
56
 
95
- getPreviousTag(token) {
96
- return this.prevTags.get(token);
97
- }
98
-
99
- getNextTag(token) {
100
- return this.nextTags.get(token);
57
+ nodeForTag(tag) {
58
+ return this.tagNodes.get(tag);
101
59
  }
102
60
 
103
61
  sourceTextFor(node) {
@@ -124,22 +82,7 @@ export const Context = class AgastContext {
124
82
  return start ? [start, end] : null;
125
83
  }
126
84
 
127
- unbox(value) {
128
- const { unboxedValues } = this;
129
- if (!unboxedValues.has(value)) {
130
- unboxedValues.set(value, reifyExpressionShallow(value));
131
- }
132
-
133
- return unboxedValues.get(value);
134
- }
135
-
136
85
  reifyExpression(value) {
137
86
  return reifyExpression(value);
138
87
  }
139
-
140
- getCooked(nodeOrRange) {
141
- return isArray(nodeOrRange)
142
- ? getCookedFromStream(this.allTagsFor(nodeOrRange))
143
- : getCookedFromTree(nodeOrRange);
144
- }
145
88
  };
package/lib/evaluate.js CHANGED
@@ -5,21 +5,14 @@ import {
5
5
  buildShiftTag,
6
6
  buildReferenceTag,
7
7
  buildLiteralTag,
8
- buildWriteEffect,
9
8
  buildDoctypeTag,
10
- buildNodeOpenTag,
11
- buildNodeCloseTag,
9
+ buildOpenNodeTag,
10
+ buildCloseNodeTag,
12
11
  } from '@bablr/agast-vm-helpers/internal-builders';
13
- import { getEmbeddedExpression } from '@bablr/agast-vm-helpers/deembed';
12
+ import { getEmbeddedTag } from '@bablr/agast-vm-helpers/deembed';
14
13
  import { StreamIterable, getStreamIterator } from '@bablr/agast-helpers/stream';
15
14
  import { printExpression } from '@bablr/agast-helpers/print';
16
- import {
17
- getRange,
18
- getOpenTag,
19
- buildArrayTag,
20
- buildFragmentCloseTag,
21
- buildFragmentOpenTag,
22
- } from '@bablr/agast-helpers/tree';
15
+ import { buildArrayInitializerTag } from '@bablr/agast-helpers/tree';
23
16
  import {
24
17
  DoctypeTag,
25
18
  OpenNodeTag,
@@ -28,22 +21,22 @@ import {
28
21
  ShiftTag,
29
22
  GapTag,
30
23
  NullTag,
31
- ArrayTag,
24
+ ArrayInitializerTag,
32
25
  LiteralTag,
33
- CloseFragmentTag,
34
- OpenFragmentTag,
35
26
  } from '@bablr/agast-helpers/symbols';
36
- import * as btree from '@bablr/agast-helpers/btree';
37
27
  import { State } from './state.js';
28
+ import { Context } from './context.js';
38
29
  import { facades } from './facades.js';
39
30
 
40
- export const evaluate = (ctx, strategy, options) =>
41
- new StreamIterable(__evaluate(ctx, strategy, options));
42
-
43
- const __evaluate = function* agast(ctx, strategy, options = {}) {
31
+ export const agast = (strategy, options = {}) => {
32
+ const ctx = Context.create();
44
33
  let s = State.from(ctx, options.expressions);
45
34
 
46
- const co = new Coroutine(getStreamIterator(strategy(facades.get(ctx), facades.get(s))));
35
+ return new StreamIterable(__agast(ctx, s, strategy(facades.get(ctx), facades.get(s))));
36
+ };
37
+
38
+ function* __agast(ctx, s, instructions) {
39
+ const co = new Coroutine(getStreamIterator(instructions));
47
40
 
48
41
  co.advance();
49
42
 
@@ -61,35 +54,10 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
61
54
  const { verb, arguments: args = [] } = instr;
62
55
 
63
56
  switch (verb) {
64
- case 'branch': {
65
- s = s.branch();
66
-
67
- returnValue = facades.get(s);
68
- break;
69
- }
70
-
71
- case 'accept': {
72
- s = s.accept();
73
-
74
- if (s.depth === 0) {
75
- yield* s.emit();
76
- }
77
-
78
- returnValue = facades.get(s);
79
- break;
80
- }
81
-
82
- case 'reject': {
83
- s = s.reject();
84
-
85
- returnValue = facades.get(s);
86
- break;
87
- }
88
-
89
57
  case 'advance': {
90
- const { 0: embeddedTag, 1: options } = args;
58
+ const { 0: embeddedTags } = args;
91
59
 
92
- const tag = embeddedTag.value;
60
+ const tag = getEmbeddedTag(embeddedTags);
93
61
 
94
62
  if (
95
63
  s.held &&
@@ -102,7 +70,7 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
102
70
  case DoctypeTag: {
103
71
  const { attributes } = tag.value;
104
72
 
105
- if (s.path) {
73
+ if (s.path.depth !== 0) {
106
74
  throw new Error();
107
75
  }
108
76
 
@@ -124,56 +92,47 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
124
92
  }
125
93
 
126
94
  case ReferenceTag: {
127
- const { name, isArray, hasGap } = tag.value;
95
+ const { name, isArray, flags } = tag.value;
128
96
 
129
- if (s.result.type === ReferenceTag) {
130
- throw new Error('A reference must have a non-reference value');
131
- }
132
-
133
- if (s.node?.flags.token) {
97
+ if (s.node?.flags.token && name !== '@') {
134
98
  throw new Error('A token node cannot contain a reference');
135
99
  }
136
100
 
137
- returnValue = s.advance(buildReferenceTag(name, isArray, hasGap));
101
+ returnValue = s.advance(buildReferenceTag(name, isArray, flags));
138
102
  break;
139
103
  }
140
104
 
141
105
  case GapTag: {
142
- const reference = s.result;
143
-
144
- if (reference?.type !== ReferenceTag) throw new Error();
145
-
146
106
  returnValue = s.advance(buildGapTag());
147
107
  break;
148
108
  }
149
109
 
150
110
  case NullTag: {
151
- const reference = s.result;
152
-
153
- if (reference?.type !== ReferenceTag) throw new Error();
154
-
155
111
  returnValue = s.advance(buildNullTag());
156
112
  break;
157
113
  }
158
114
 
159
- case ArrayTag: {
160
- const reference = s.result;
115
+ case ArrayInitializerTag: {
116
+ const { reference } = s;
161
117
 
162
118
  if (reference?.type !== ReferenceTag) throw new Error();
163
119
  if (!reference.value.isArray) throw new Error();
164
120
 
165
- returnValue = s.advance(buildArrayTag());
121
+ returnValue = s.advance(buildArrayInitializerTag());
166
122
  break;
167
123
  }
168
124
 
169
125
  case ShiftTag: {
170
- const finishedNode = s.nodeForTag(s.result);
126
+ const { index } = tag.value;
127
+ if (s.resultPath.tag.type !== GapTag) throw new Error();
128
+
129
+ const refPath = s.resultPath.previousSibling;
171
130
 
172
- if (!getOpenTag(finishedNode).value.flags.expression) {
131
+ if (!refPath.tag.value.flags.expression) {
173
132
  throw new Error();
174
133
  }
175
134
 
176
- returnValue = s.advance(buildShiftTag());
135
+ returnValue = s.advance(buildShiftTag(index));
177
136
  break;
178
137
  }
179
138
 
@@ -184,29 +143,12 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
184
143
  throw new Error('Expected an absolute-language tag');
185
144
  }
186
145
 
187
- returnValue = s.advance(
188
- buildNodeOpenTag(flags, language, type, attributes),
189
- getEmbeddedExpression(options),
190
- );
191
- break;
192
- }
193
-
194
- case OpenFragmentTag: {
195
- const { flags } = tag.value;
196
-
197
- returnValue = s.advance(buildFragmentOpenTag(flags), getEmbeddedExpression(options));
146
+ returnValue = s.advance(buildOpenNodeTag(flags, language, type, attributes));
198
147
  break;
199
148
  }
200
149
 
201
150
  case CloseNodeTag: {
202
- const { type, language } = tag.value;
203
-
204
- returnValue = s.advance(buildNodeCloseTag(type, language));
205
- break;
206
- }
207
-
208
- case CloseFragmentTag: {
209
- returnValue = s.advance(buildFragmentCloseTag());
151
+ returnValue = s.advance(buildCloseNodeTag());
210
152
  break;
211
153
  }
212
154
 
@@ -214,90 +156,11 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
214
156
  throw new Error();
215
157
  }
216
158
 
217
- yield* s.emit();
159
+ yield returnValue;
218
160
 
219
161
  break;
220
162
  }
221
163
 
222
- case 'bindAttribute': {
223
- const { 0: key, 1: value } = args;
224
-
225
- const { unboundAttributes } = s;
226
-
227
- if (!unboundAttributes || !unboundAttributes.has(key)) {
228
- throw new Error('No unbound attribute to bind');
229
- }
230
-
231
- if (!s.node.type) {
232
- throw new Error('Cannot bind attribute to fragment');
233
- }
234
-
235
- if (key === 'span') throw new Error('too late');
236
-
237
- // if (stateIsDifferent) {
238
- // // we can't allow effects to cross state branches
239
- // throw new Error();
240
- // }
241
-
242
- unboundAttributes.delete(key);
243
-
244
- const openTag = getOpenTag(s.node);
245
-
246
- if (value != null) {
247
- const { flags, language, type } = openTag.value;
248
- const attributes = { ...openTag.value.attributes, [key]: value };
249
- const newOpenTag = buildNodeOpenTag(flags, language, type, attributes);
250
-
251
- let openNext = ctx.nextTags.get(openTag);
252
- let startPrev = ctx.prevTags.get(openTag);
253
-
254
- ctx.prevTags.set(newOpenTag, startPrev);
255
- ctx.nextTags.set(startPrev, newOpenTag);
256
-
257
- if (s.node !== s.tagNodes.get(openTag)) throw new Error();
258
- if (s.path !== s.tagPaths.get(openTag)) throw new Error();
259
-
260
- s.node.attributes = attributes;
261
-
262
- s.tagNodes.set(newOpenTag, s.node);
263
- s.tagPaths.set(newOpenTag, s.path);
264
-
265
- if (openNext) {
266
- ctx.nextTags.set(newOpenTag, openNext);
267
- ctx.prevTags.set(openNext, newOpenTag);
268
- } else {
269
- // could this tag be stored anywhere else?
270
- s.result = newOpenTag;
271
- }
272
-
273
- s.node.children = btree.replaceAt(0, s.node.children, newOpenTag);
274
- }
275
-
276
- if (!unboundAttributes.size) {
277
- yield* s.emit();
278
- }
279
-
280
- returnValue = getRange(s.node);
281
- break;
282
- }
283
-
284
- case 'getState': {
285
- returnValue = facades.get(s);
286
- break;
287
- }
288
-
289
- case 'getContext': {
290
- returnValue = facades.get(ctx);
291
- break;
292
- }
293
-
294
- case 'write': {
295
- if (options.emitEffects) {
296
- yield buildWriteEffect(args[0], args[1].value);
297
- }
298
- break;
299
- }
300
-
301
164
  default: {
302
165
  throw new Error(`Unexpected call of {type: ${printExpression(verb)}}`);
303
166
  }
@@ -310,9 +173,9 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
310
173
  throw new Error('Did not unwind state stack');
311
174
  }
312
175
 
313
- if (s.path?.depth > 0) {
176
+ if (s.path) {
314
177
  throw new Error('Did not unwind path stack');
315
178
  }
316
179
 
317
- return s.nodeForTag(s.result);
318
- };
180
+ return s.resultPath.path.node;
181
+ }
package/lib/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { evaluate } from './evaluate.js';
1
+ export { agast } from './evaluate.js';
2
2
 
3
3
  export { Context } from './context.js';
package/lib/path.js CHANGED
@@ -1,76 +1,3 @@
1
- import { WeakStackFrame } from '@bablr/weak-stack';
2
- import { DoctypeTag, ReferenceTag } from '@bablr/agast-helpers/symbols';
3
- import { skipToDepth, buildSkips } from './utils/skip.js';
4
- import { facades, actuals } from './facades.js';
1
+ export { Path } from '@bablr/agast-helpers/path';
5
2
 
6
- export const PathFacade = class AgastPathFacade {
7
- constructor(path) {
8
- facades.set(path, this);
9
- }
10
-
11
- get reference() {
12
- return actuals.get(this).reference;
13
- }
14
-
15
- get parent() {
16
- return facades.get(actuals.get(this).parent);
17
- }
18
-
19
- get depth() {
20
- return actuals.get(this).depth;
21
- }
22
-
23
- at(depth) {
24
- return facades.get(actuals.get(this).at(depth));
25
- }
26
-
27
- *parents(includeSelf = false) {
28
- if (includeSelf) yield this;
29
- let parent = this;
30
- while ((parent = parent.parent)) {
31
- yield parent;
32
- }
33
- }
34
- };
35
-
36
- export const Path = class AgastPath extends WeakStackFrame {
37
- static from(context, tag, childrenIndex) {
38
- return Path.create(context, tag, childrenIndex);
39
- }
40
-
41
- constructor(context, reference, childrenIndex = -1) {
42
- super();
43
-
44
- if (reference && reference.type !== ReferenceTag && reference.type !== DoctypeTag) {
45
- throw new Error('Invalid reference for path');
46
- }
47
-
48
- this.context = context;
49
- this.reference = reference;
50
- this.childrenIndex = childrenIndex;
51
-
52
- buildSkips(this);
53
-
54
- new PathFacade(this);
55
- }
56
-
57
- get name() {
58
- return this.reference?.value.name;
59
- }
60
-
61
- get isArray() {
62
- return this.reference?.value.isArray || false;
63
- }
64
-
65
- at(depth) {
66
- return skipToDepth(depth, this);
67
- }
68
-
69
- *parents(includeSelf = false) {
70
- if (includeSelf) yield this;
71
- let parent = this;
72
- while ((parent = parent.parent)) {
73
- yield parent;
74
- }
75
- }
76
- };
3
+ export { allTagPathsFor, ownTagPathsFor } from '@bablr/agast-helpers/path';
package/lib/state.js CHANGED
@@ -1,23 +1,18 @@
1
- import emptyStack from '@iter-tools/imm-stack';
1
+ import isArray from 'iter-tools-es/methods/is-array';
2
2
  import { WeakStackFrame } from '@bablr/weak-stack';
3
3
  import {
4
4
  Resolver,
5
5
  add,
6
- createNode,
7
- getOpenTag,
8
- getCloseTag,
9
- branchNode,
10
- acceptNode,
11
6
  finalizeNode,
12
7
  getRoot,
13
- printType,
14
8
  buildStubNode,
9
+ getRange,
10
+ buildNullTag,
11
+ treeFromStream,
12
+ createNode,
15
13
  } from '@bablr/agast-helpers/tree';
16
14
  import * as btree from '@bablr/agast-helpers/btree';
17
- import {
18
- buildBeginningOfStreamToken,
19
- buildEmbeddedNode,
20
- } from '@bablr/agast-vm-helpers/internal-builders';
15
+ import { TagPath, updatePath } from '@bablr/agast-helpers/path';
21
16
  import {
22
17
  DoctypeTag,
23
18
  OpenNodeTag,
@@ -26,25 +21,12 @@ import {
26
21
  ShiftTag,
27
22
  GapTag,
28
23
  NullTag,
29
- ArrayTag,
24
+ ArrayInitializerTag,
30
25
  LiteralTag,
31
- OpenFragmentTag,
32
- CloseFragmentTag,
33
26
  } from '@bablr/agast-helpers/symbols';
34
27
  import { facades, actuals } from './facades.js';
35
28
  import { Path } from './path.js';
36
- import { isArray } from 'iter-tools-es';
37
-
38
- const { hasOwn } = Object;
39
-
40
- const createNodeWithState = (openTag, options = {}) => {
41
- const { unboundAttributes } = options;
42
- const node = createNode(openTag);
43
- nodeStates.set(node, {
44
- unboundAttributes: new Set(unboundAttributes || []),
45
- });
46
- return node;
47
- };
29
+ import { Coroutine } from '@bablr/coroutine';
48
30
 
49
31
  export const StateFacade = class AgastStateFacade {
50
32
  constructor(state) {
@@ -55,8 +37,16 @@ export const StateFacade = class AgastStateFacade {
55
37
  return State.from(actuals.get(context));
56
38
  }
57
39
 
58
- get result() {
59
- return actuals.get(this).result;
40
+ get resultPath() {
41
+ return actuals.get(this).resultPath;
42
+ }
43
+
44
+ get reference() {
45
+ return actuals.get(this).reference;
46
+ }
47
+
48
+ get referencePath() {
49
+ return actuals.get(this).referencePath;
60
50
  }
61
51
 
62
52
  get context() {
@@ -64,7 +54,7 @@ export const StateFacade = class AgastStateFacade {
64
54
  }
65
55
 
66
56
  get path() {
67
- return facades.get(actuals.get(this).path);
57
+ return actuals.get(this).path;
68
58
  }
69
59
 
70
60
  get node() {
@@ -87,16 +77,8 @@ export const StateFacade = class AgastStateFacade {
87
77
  return this.context;
88
78
  }
89
79
 
90
- nodeForPath(path) {
91
- return actuals.get(this).nodeForPath(actuals.get(path));
92
- }
93
-
94
- pathForTag(tag) {
95
- return facades.get(actuals.get(this).pathForTag(tag));
96
- }
97
-
98
- nodeForTag(tag) {
99
- return actuals.get(this).nodeForTag(tag);
80
+ get parent() {
81
+ return facades.get(actuals.get(this).parent);
100
82
  }
101
83
  };
102
84
 
@@ -104,345 +86,230 @@ export const nodeStates = new WeakMap();
104
86
 
105
87
  export const State = class AgastState extends WeakStackFrame {
106
88
  constructor(
89
+ parent,
107
90
  context,
108
- expressions = emptyStack,
109
- path = null,
110
- node = null,
111
- result = buildBeginningOfStreamToken(),
112
- emitted = null,
91
+ expressions = [],
92
+ path = Path.from(createNode()),
113
93
  held = null,
94
+ resultPath = null,
114
95
  resolver = new Resolver(),
115
- internalContext = {
116
- pathNodes: new WeakMap(),
117
- tagPaths: new WeakMap(),
118
- tagNodes: new WeakMap(),
119
- },
120
96
  ) {
121
- super();
97
+ super(parent);
122
98
 
123
- if (!context) throw new Error('invalid args to tagState');
99
+ if (!context || !path || !expressions) throw new Error('invalid args to tagState');
124
100
 
125
101
  this.context = context;
126
- this.expressions = expressions;
102
+ this.expressions = new Coroutine(expressions[Symbol.iterator]());
127
103
  this.path = path;
128
- this.node = node;
129
- this.result = result;
130
- this.emitted = emitted;
131
104
  this.held = held;
105
+ this.resultPath = resultPath;
132
106
  this.resolver = resolver;
133
- this.internalContext = internalContext;
107
+
108
+ if (!this.node) throw new Error();
134
109
 
135
110
  new StateFacade(this);
136
111
  }
137
112
 
138
113
  static from(context, expressions = []) {
139
- return State.create(context, emptyStack.push(...[...expressions].reverse()));
140
- }
141
-
142
- get pathNodes() {
143
- return this.internalContext.pathNodes;
144
- }
145
-
146
- get tagPaths() {
147
- return this.internalContext.tagPaths;
148
- }
149
-
150
- get tagNodes() {
151
- return this.internalContext.tagNodes;
152
- }
153
-
154
- get unboundAttributes() {
155
- return nodeStates.get(this.node).unboundAttributes;
114
+ return State.create(context, expressions);
156
115
  }
157
116
 
158
117
  get holding() {
159
118
  return !!this.held;
160
119
  }
161
120
 
162
- nodeForPath(path) {
163
- return this.pathNodes.get(path);
164
- }
165
-
166
- pathForTag(ref) {
167
- return this.tagPaths.get(ref);
168
- }
169
-
170
- nodeForTag(tag) {
171
- return this.tagNodes.get(tag);
172
- }
173
-
174
- advance(tag, options = {}) {
121
+ advance(tag) {
175
122
  const ctx = this.context;
176
- const { prevTags, nextTags } = ctx;
177
123
 
178
- if (tag) {
179
- if (prevTags.has(tag)) {
180
- throw new Error('Double emit');
181
- }
124
+ if (!tag) throw new Error();
182
125
 
183
- if (
184
- this.result?.type === ReferenceTag &&
185
- ![OpenNodeTag, GapTag, NullTag, ArrayTag].includes(tag.type)
186
- ) {
187
- throw new Error(`${printType(tag.type)} is not a valid reference target`);
188
- }
189
-
190
- prevTags.set(tag, this.result);
191
- nextTags.set(this.result, tag);
126
+ let targetPath = this.path;
192
127
 
193
- this.resolver.advance(tag);
128
+ switch (tag.type) {
129
+ case DoctypeTag: {
130
+ const { path, node } = this;
194
131
 
195
- switch (tag.type) {
196
- case DoctypeTag: {
197
- this.path = Path.from(ctx, tag);
132
+ node.children = btree.push(node.children, tag);
133
+ node.attributes = tag.value.attributes;
198
134
 
199
- this.tagPaths.set(tag, this.path);
200
- break;
201
- }
135
+ this.resolver.advance(tag);
136
+ updatePath(this.path, tag);
202
137
 
203
- case OpenNodeTag: {
204
- const openTag = tag;
205
- const { flags } = tag.value;
206
- this.node = createNodeWithState(tag, options);
207
-
208
- const reference = this.result;
138
+ targetPath = path;
139
+ break;
140
+ }
209
141
 
210
- this.node.children = btree.push(this.node.children, tag);
142
+ case ReferenceTag: {
143
+ this.resolver.advance(tag);
211
144
 
212
- if (!flags.trivia && !flags.escape) {
213
- if (
214
- reference.type !== ReferenceTag &&
215
- reference.type !== ShiftTag &&
216
- reference.type !== OpenNodeTag &&
217
- !reference.value.type
218
- ) {
219
- throw new Error('Invalid location for OpenNodeTag');
220
- }
221
- } else {
222
- this.path = this.path.push(ctx, null, btree.getSum(this.node.children));
223
- }
145
+ this.node.children = btree.push(this.node.children, tag);
146
+ updatePath(this.path, tag);
224
147
 
225
- this.pathNodes.set(this.node, this.path);
226
- this.pathNodes.set(this.path, this.node);
148
+ const { hasGap } = tag.value;
227
149
 
228
- this.tagNodes.set(openTag, this.node);
229
- this.tagPaths.set(openTag, this.path);
230
- break;
150
+ if (hasGap && !this.node.flags.hasGap) {
151
+ throw new Error('gap reference in gapless node');
231
152
  }
232
153
 
233
- case OpenFragmentTag: {
234
- const openTag = tag;
235
- this.node = createNodeWithState(tag, options);
154
+ break;
155
+ }
236
156
 
237
- const reference = this.result;
157
+ case OpenNodeTag: {
158
+ const parentNode = this.node;
238
159
 
239
- this.node.attributes = this.result.value.attributes;
240
- this.node.children = btree.push(this.node.children, reference);
160
+ targetPath = this.path;
241
161
 
162
+ if (!this.path.depth) {
163
+ this.node.flags = tag.value.flags;
242
164
  this.node.children = btree.push(this.node.children, tag);
243
-
244
- this.pathNodes.set(this.node, this.path);
245
- this.pathNodes.set(this.path, this.node);
246
-
247
- this.tagNodes.set(openTag, this.node);
248
- this.tagPaths.set(openTag, this.path);
249
- break;
165
+ this.node.type = tag.value.type;
166
+ this.node.language = tag.value.language;
167
+ this.node.attributes = tag.value.attributes || {};
168
+ } else {
169
+ let node = createNode(tag);
170
+ this.path = this.path.push(node, btree.getSum(parentNode.children) - 2);
171
+ add(parentNode, this.reference, node);
250
172
  }
251
173
 
252
- case CloseNodeTag: {
253
- const openTag = getOpenTag(this.node);
254
- const { flags, type: openType } = openTag.value;
255
- const closeTag = tag;
256
- const { type } = closeTag.value;
257
-
258
- this.node.children = btree.push(this.node.children, tag);
259
-
260
- if (this.node.unboundAttributes?.size)
261
- throw new Error('Grammar failed to bind all attributes');
262
-
263
- if (!type) throw new Error(`CloseNodeTag must have type`);
264
-
265
- if (type !== openType)
266
- throw new Error(
267
- `Grammar close {type: ${printType(type)}} did not match open {type: ${printType(
268
- openType,
269
- )}}`,
270
- );
271
-
272
- if (!flags.escape && !flags.trivia) {
273
- add(this.parentNode, this.path.reference, this.node);
274
- } else if (this.parentNode) {
275
- this.parentNode.children = btree.push(
276
- this.parentNode.children,
277
- buildEmbeddedNode(this.node),
278
- );
279
- }
174
+ this.resolver.advance(tag);
175
+ updatePath(this.path, tag);
280
176
 
281
- this.tagNodes.set(closeTag, this.node);
282
- this.tagPaths.set(closeTag, this.path);
283
-
284
- finalizeNode(this.node);
177
+ break;
178
+ }
285
179
 
286
- this.node = this.parentNode;
287
- this.path = this.path.parent;
288
- break;
289
- }
180
+ case CloseNodeTag: {
181
+ this.resolver.advance(tag);
182
+ this.node.children = btree.push(this.node.children, tag);
183
+ updatePath(this.path, tag);
290
184
 
291
- case CloseFragmentTag: {
292
- const closeTag = tag;
185
+ if (this.node.unboundAttributes?.size)
186
+ throw new Error('Grammar failed to bind all attributes');
293
187
 
294
- this.node.children = btree.push(this.node.children, tag);
188
+ finalizeNode(this.node);
295
189
 
296
- this.tagNodes.set(closeTag, this.node);
297
- this.tagPaths.set(closeTag, this.path);
190
+ this.path = this.path.parent;
191
+ break;
192
+ }
298
193
 
299
- finalizeNode(this.node);
194
+ case GapTag: {
195
+ let target;
196
+ let lastTagPath = TagPath.from(this.path, -1);
197
+ let refPath = lastTagPath;
300
198
 
301
- this.node = this.parentNode;
302
- this.path = this.path.parent;
303
- break;
199
+ while (refPath && refPath.tag.type !== ReferenceTag) {
200
+ refPath = refPath.previousSibling;
304
201
  }
305
202
 
306
- case ReferenceTag: {
307
- this.node.children = btree.push(this.node.children, tag);
308
-
309
- const { isArray, name, hasGap } = tag.value;
310
-
311
- if (hasGap && !this.node.flags.hasGap) {
312
- throw new Error('gap reference in gapless node');
313
- }
203
+ this.resolver.advance(tag);
314
204
 
315
- if (isArray && !hasOwn(this.node.properties, name)) {
316
- this.node.properties[name] = [];
317
- } else {
318
- this.path = this.path.push(ctx, tag, btree.getSum(this.node.children));
319
- }
320
-
321
- this.tagPaths.set(tag, this.path);
322
- break;
323
- }
205
+ let wasHeld = this.held;
324
206
 
325
- case GapTag: {
326
- this.tagPaths.set(tag, this.path);
207
+ if (this.held && lastTagPath.tag.type !== ShiftTag) {
208
+ target = this.held.node;
327
209
 
328
- let target;
329
- let ref = btree.getAt(-1, this.node.children);
210
+ this.held = null;
211
+ } else if (refPath) {
212
+ this.expressions.advance();
330
213
 
331
- if (ref.type !== ReferenceTag) throw new Error();
214
+ if (!this.expressions.done) {
215
+ const expression = this.expressions.value;
332
216
 
333
- if (this.held) {
334
- target = this.held.node;
217
+ if (isArray(expression)) {
218
+ throw new Error('Invalid array interpolation');
219
+ }
335
220
 
336
- this.held = null;
221
+ if (expression == null) {
222
+ target = treeFromStream(buildNullTag());
223
+ } else {
224
+ target = getRoot(expression);
225
+ }
337
226
  } else {
338
227
  if (!this.node.flags.hasGap) throw new Error('Node must allow gaps');
339
228
 
340
- if (this.expressions.size) {
341
- const expression = this.expressions.value;
342
-
343
- if (isArray(expression)) {
344
- throw new Error('Invalid array interpolation');
345
- } else {
346
- target = expression != null ? getRoot(expression) : buildStubNode(tag);
229
+ target = buildStubNode(tag);
230
+ }
347
231
 
348
- this.expressions = this.expressions.pop();
349
- }
232
+ this.held = null;
233
+ } else {
234
+ this.node.language = null;
235
+ this.node.type = null;
236
+ target = this.node;
237
+ }
350
238
 
351
- // const range = ctx.buildRange(streamFromTree(target));
239
+ const range = getRange(target);
352
240
 
353
- // this node is only interpolated into the tree, not the stream
354
- // the stream still contains a gap token, even if expressions were specified
355
- // get rid of the gap token in the stream!
356
- } else {
357
- target = buildStubNode(tag);
358
- }
359
- }
241
+ this.resultPath = range ? range[1] : this.resultPath;
360
242
 
361
- this.tagNodes.set(tag, target);
243
+ if (refPath) {
244
+ add(this.path.node, refPath.tag, target, wasHeld);
245
+ }
362
246
 
363
- this.pathNodes.set(this.pathForTag(ref), target);
364
- add(this.node, ref, target);
247
+ updatePath(this.path, tag);
248
+ break;
249
+ }
365
250
 
366
- this.path = this.path.parent;
367
- break;
368
- }
251
+ case NullTag: {
252
+ const { node: parentNode, reference } = this;
253
+ const { name } = reference.value;
369
254
 
370
- case NullTag: {
371
- this.tagPaths.set(tag, this.path);
255
+ this.resolver.advance(tag);
256
+ updatePath(this.path, tag);
372
257
 
373
- const { properties } = this.node;
374
- const { isArray, name } = this.result.value;
258
+ if (!name) throw new Error();
375
259
 
376
- const newNode = buildStubNode(tag);
260
+ let stubNode = buildStubNode(tag);
377
261
 
378
- if (!hasOwn(properties, name)) {
379
- properties[name] = newNode;
380
- }
262
+ add(parentNode, reference, stubNode);
381
263
 
382
- this.pathNodes.set(this.path, newNode);
264
+ targetPath = this.path.push(stubNode, btree.getSum(this.node.children) - 2);
265
+ break;
266
+ }
383
267
 
384
- this.path = this.path.parent;
385
- break;
386
- }
268
+ case ArrayInitializerTag: {
269
+ const { node, reference } = this;
270
+ this.resolver.advance(tag);
387
271
 
388
- case ShiftTag: {
389
- const finishedNode = this.nodeForTag(this.result);
390
- const ref = ctx.getPreviousTag(getOpenTag(finishedNode));
391
- const finishedPath = this.pathForTag(ref);
392
- const { properties } = this.node;
272
+ add(node, reference, []);
273
+ updatePath(this.path, tag);
274
+ break;
275
+ }
393
276
 
394
- this.pathNodes.set(finishedPath, null);
277
+ case ShiftTag: {
278
+ this.resolver.advance(tag);
279
+ updatePath(this.path, tag);
395
280
 
396
- this.held = { node: finishedNode, path: finishedPath };
281
+ const finishedPath = this.resultPath.innerPath;
282
+ const finishedNode = finishedPath.node;
283
+ const ref = this.resultPath.previousSibling.tag;
397
284
 
398
- let node = properties[ref.value.name];
285
+ this.held = { node: finishedNode, path: finishedPath };
399
286
 
400
- if (ref.value.isArray) {
401
- node = btree.getAt(-1, node);
402
- properties[ref.value.name] = btree.pop(properties[ref.value.name]);
403
- } else {
404
- properties[ref.value.name] = null;
405
- }
287
+ if (!ref.value.name) throw new Error();
406
288
 
407
- this.path = finishedPath;
408
- break;
409
- }
289
+ if (!ref.value.flags.expression) throw new Error();
410
290
 
411
- case LiteralTag:
412
- case ArrayTag:
413
- this.node.children = btree.push(this.node.children, tag);
414
- break;
291
+ this.node.children = btree.push(this.node.children, tag);
415
292
 
416
- default:
417
- throw new Error();
293
+ // this.path = finishedPath;
294
+ targetPath = this.path;
295
+ break;
418
296
  }
297
+
298
+ case LiteralTag:
299
+ this.resolver.advance(tag);
300
+ updatePath(this.path, tag);
301
+ this.node.children = btree.push(this.node.children, tag);
302
+ break;
303
+
304
+ default:
305
+ throw new Error();
419
306
  }
420
307
 
421
- this.result = tag;
308
+ this.resultPath = TagPath.from(targetPath, -1);
422
309
 
423
310
  return tag;
424
311
  }
425
312
 
426
- *emit() {
427
- const { nextTags } = this.context;
428
- if (!this.depth) {
429
- let emittable = this.emitted ? nextTags.get(this.emitted) : this.result;
430
-
431
- while (
432
- emittable &&
433
- !(
434
- emittable.type === OpenNodeTag &&
435
- emittable.value.type &&
436
- nodeStates.get(this.nodeForTag(emittable)).unboundAttributes?.size
437
- )
438
- ) {
439
- yield emittable;
440
- this.emitted = emittable;
441
- emittable = nextTags.get(this.emitted);
442
- }
443
- }
444
- }
445
-
446
313
  get ctx() {
447
314
  return this.context;
448
315
  }
@@ -456,80 +323,18 @@ export const State = class AgastState extends WeakStackFrame {
456
323
  }
457
324
 
458
325
  get parentNode() {
459
- return this.pathNodes.has(this.path) ? this.pathNodes.get(this.path.parent) : this.node;
326
+ return this.path.parent.node;
460
327
  }
461
328
 
462
- branch() {
463
- const { context, expressions, path, node, result, emitted, held, resolver, internalContext } =
464
- this;
465
- const { pathNodes } = internalContext;
466
-
467
- const newNode = node && branchNode(node);
468
-
469
- const nodeState = nodeStates.get(node);
470
-
471
- pathNodes.set(path, newNode);
472
- pathNodes.set(newNode, path);
473
-
474
- nodeStates.set(newNode, { ...nodeState });
475
-
476
- const nodeOpen = getOpenTag(node);
477
- const nodeClose = getCloseTag(node);
478
- if (nodeOpen) this.tagNodes.set(nodeOpen, newNode);
479
- if (nodeClose) this.tagNodes.set(nodeClose, newNode);
480
-
481
- return this.push(
482
- context,
483
- expressions,
484
- path,
485
- newNode,
486
- result,
487
- emitted,
488
- held,
489
- resolver.branch(),
490
- internalContext,
491
- );
329
+ get reference() {
330
+ return this.resolver.reference;
492
331
  }
493
332
 
494
- accept() {
495
- const { parent } = this;
496
-
497
- if (!parent) {
498
- return null;
499
- }
500
-
501
- if (this.node && parent.node) {
502
- acceptNode(parent.node, this.node);
503
- const nodeState = nodeStates.get(this.node);
504
- Object.assign(nodeStates.get(parent.node), nodeState);
505
- } else {
506
- parent.node = this.node;
507
- }
508
-
509
- // emitted isn't used here and probably doesn't need to be part of state
510
-
511
- parent.expressions = this.expressions;
512
- parent.result = this.result;
513
- parent.held = this.held;
514
- parent.path = this.path;
515
- parent.node = this.node;
516
- parent.resolver = this.resolver;
517
-
518
- return parent;
333
+ get referencePath() {
334
+ return this.reference && this.path.referencePath;
519
335
  }
520
336
 
521
- reject() {
522
- const { parent, context, pathNodes, tagNodes } = this;
523
-
524
- if (!parent) throw new Error('rejected root state');
525
-
526
- context.nextTags.delete(parent.result);
527
-
528
- pathNodes.set(parent.path, parent.node);
529
-
530
- if (getOpenTag(parent.node)) tagNodes.set(getOpenTag(parent.node), parent.node);
531
- if (getCloseTag(parent.node)) tagNodes.set(getCloseTag(parent.node), parent.node);
532
-
533
- return parent;
337
+ get node() {
338
+ return this.path?.node;
534
339
  }
535
340
  };
@@ -0,0 +1,13 @@
1
+ export function first(iter) {
2
+ for (const value of iter) {
3
+ return value;
4
+ }
5
+ }
6
+
7
+ export function* drop(n, iterable) {
8
+ let i = -1;
9
+ for (const value of iterable) {
10
+ ++i;
11
+ if (i >= n) yield value;
12
+ }
13
+ }
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.6.0",
4
+ "version": "0.7.0",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
7
  "files": [
@@ -12,8 +12,8 @@
12
12
  },
13
13
  "sideEffects": false,
14
14
  "dependencies": {
15
- "@bablr/agast-helpers": "^0.5.0",
16
- "@bablr/agast-vm-helpers": "^0.5.0",
15
+ "@bablr/agast-helpers": "0.6.0",
16
+ "@bablr/agast-vm-helpers": "0.6.0",
17
17
  "@bablr/coroutine": "0.1.0",
18
18
  "@bablr/weak-stack": "0.1.0",
19
19
  "@iter-tools/imm-stack": "1.1.0"