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