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