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