@bablr/agast-vm 0.5.0 → 0.6.1

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
@@ -3,10 +3,7 @@ import {
3
3
  getCooked as getCookedFromTree,
4
4
  sourceTextFor as sourceTextForTree,
5
5
  } from '@bablr/agast-helpers/tree';
6
- import {
7
- getCooked as getCookedFromStream,
8
- sourceTextFor as sourceTextForStream,
9
- } from '@bablr/agast-helpers/stream';
6
+ import { getCooked as getCookedFromStream } from '@bablr/agast-helpers/stream';
10
7
  import { OpenNodeTag, CloseNodeTag } from '@bablr/agast-helpers/symbols';
11
8
  import { facades, actuals } from './facades.js';
12
9
 
@@ -52,6 +49,10 @@ export const ContextFacade = class AgastContextFacade {
52
49
  return actuals.get(this).buildRange(tags);
53
50
  }
54
51
 
52
+ nodeForTag(tag) {
53
+ return actuals.get(this).nodeForTag(tag);
54
+ }
55
+
55
56
  unbox(value) {
56
57
  return actuals.get(this).unbox(value);
57
58
  }
@@ -65,6 +66,7 @@ export const Context = class AgastContext {
65
66
  constructor() {
66
67
  this.prevTags = new WeakMap();
67
68
  this.nextTags = new WeakMap();
69
+ this.tagNodes = new WeakMap();
68
70
  this.unboxedValues = new WeakMap();
69
71
  this.facade = new ContextFacade();
70
72
 
@@ -100,10 +102,12 @@ export const Context = class AgastContext {
100
102
  return this.nextTags.get(token);
101
103
  }
102
104
 
103
- sourceTextFor(nodeOrRange) {
104
- return isArray(nodeOrRange)
105
- ? sourceTextForStream(this.allTagsFor(nodeOrRange))
106
- : sourceTextForTree(nodeOrRange);
105
+ nodeForTag(tag) {
106
+ return this.tagNodes.get(tag);
107
+ }
108
+
109
+ sourceTextFor(node) {
110
+ return sourceTextForTree(node);
107
111
  }
108
112
 
109
113
  buildRange(tags) {
package/lib/evaluate.js CHANGED
@@ -13,7 +13,13 @@ 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, buildArrayTag } from '@bablr/agast-helpers/tree';
16
+ import {
17
+ getRange,
18
+ getOpenTag,
19
+ buildArrayTag,
20
+ buildFragmentCloseTag,
21
+ buildFragmentOpenTag,
22
+ } from '@bablr/agast-helpers/tree';
17
23
  import {
18
24
  DoctypeTag,
19
25
  OpenNodeTag,
@@ -24,6 +30,8 @@ import {
24
30
  NullTag,
25
31
  ArrayTag,
26
32
  LiteralTag,
33
+ CloseFragmentTag,
34
+ OpenFragmentTag,
27
35
  } from '@bablr/agast-helpers/symbols';
28
36
  import * as btree from '@bablr/agast-helpers/btree';
29
37
  import { State } from './state.js';
@@ -64,7 +72,7 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
64
72
  s = s.accept();
65
73
 
66
74
  if (s.depth === 0) {
67
- yield* s.emit();
75
+ yield* s.emit(options);
68
76
  }
69
77
 
70
78
  returnValue = facades.get(s);
@@ -79,7 +87,7 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
79
87
  }
80
88
 
81
89
  case 'advance': {
82
- const { 0: embeddedTag, 1: options } = args;
90
+ const { 0: embeddedTag, 1: advanceOptions } = args;
83
91
 
84
92
  const tag = embeddedTag.value;
85
93
 
@@ -116,7 +124,7 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
116
124
  }
117
125
 
118
126
  case ReferenceTag: {
119
- const { name, isArray } = tag.value;
127
+ const { name, isArray, hasGap } = tag.value;
120
128
 
121
129
  if (s.result.type === ReferenceTag) {
122
130
  throw new Error('A reference must have a non-reference value');
@@ -126,7 +134,7 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
126
134
  throw new Error('A token node cannot contain a reference');
127
135
  }
128
136
 
129
- returnValue = s.advance(buildReferenceTag(name, isArray));
137
+ returnValue = s.advance(buildReferenceTag(name, isArray, hasGap));
130
138
  break;
131
139
  }
132
140
 
@@ -178,7 +186,17 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
178
186
 
179
187
  returnValue = s.advance(
180
188
  buildNodeOpenTag(flags, language, type, attributes),
181
- getEmbeddedExpression(options),
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),
182
200
  );
183
201
  break;
184
202
  }
@@ -190,11 +208,16 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
190
208
  break;
191
209
  }
192
210
 
211
+ case CloseFragmentTag: {
212
+ returnValue = s.advance(buildFragmentCloseTag());
213
+ break;
214
+ }
215
+
193
216
  default:
194
217
  throw new Error();
195
218
  }
196
219
 
197
- yield* s.emit();
220
+ yield* s.emit(options);
198
221
 
199
222
  break;
200
223
  }
@@ -214,10 +237,6 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
214
237
 
215
238
  if (key === 'span') throw new Error('too late');
216
239
 
217
- if (key === 'balancedSpan') {
218
- throw new Error('not implemented');
219
- }
220
-
221
240
  // if (stateIsDifferent) {
222
241
  // // we can't allow effects to cross state branches
223
242
  // throw new Error();
@@ -258,7 +277,7 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
258
277
  }
259
278
 
260
279
  if (!unboundAttributes.size) {
261
- yield* s.emit();
280
+ yield* s.emit(options);
262
281
  }
263
282
 
264
283
  returnValue = getRange(s.node);
@@ -277,7 +296,7 @@ const __evaluate = function* agast(ctx, strategy, options = {}) {
277
296
 
278
297
  case 'write': {
279
298
  if (options.emitEffects) {
280
- yield buildWriteEffect(args[0], getEmbeddedExpression(args[1]));
299
+ yield buildWriteEffect(args[0], args[1].value);
281
300
  }
282
301
  break;
283
302
  }
package/lib/path.js CHANGED
@@ -55,7 +55,7 @@ export const Path = class AgastPath extends WeakStackFrame {
55
55
  }
56
56
 
57
57
  get name() {
58
- return this.reference?.value.name || '[anonymous]';
58
+ return this.reference?.value.name;
59
59
  }
60
60
 
61
61
  get isArray() {
package/lib/state.js CHANGED
@@ -11,14 +11,15 @@ import {
11
11
  finalizeNode,
12
12
  getRoot,
13
13
  printType,
14
+ buildStubNode,
14
15
  } from '@bablr/agast-helpers/tree';
15
16
  import * as btree from '@bablr/agast-helpers/btree';
16
17
  import {
17
18
  buildBeginningOfStreamToken,
18
19
  buildEmbeddedNode,
19
- nodeFlags,
20
+ buildEffect,
21
+ buildYieldEffect,
20
22
  } from '@bablr/agast-vm-helpers/internal-builders';
21
- import * as sym from '@bablr/agast-helpers/symbols';
22
23
  import {
23
24
  DoctypeTag,
24
25
  OpenNodeTag,
@@ -29,45 +30,24 @@ import {
29
30
  NullTag,
30
31
  ArrayTag,
31
32
  LiteralTag,
33
+ OpenFragmentTag,
34
+ CloseFragmentTag,
32
35
  } from '@bablr/agast-helpers/symbols';
33
36
  import { facades, actuals } from './facades.js';
34
37
  import { Path } from './path.js';
38
+ import { isArray } from 'iter-tools-es';
35
39
 
36
40
  const { hasOwn } = Object;
37
41
 
38
- const arrayLast = (arr) => arr[arr.length - 1];
39
-
40
- const createNodeWithState = (startTag, options = {}) => {
42
+ const createNodeWithState = (openTag, options = {}) => {
41
43
  const { unboundAttributes } = options;
42
- const node = createNode(startTag);
44
+ const node = createNode(openTag);
43
45
  nodeStates.set(node, {
44
46
  unboundAttributes: new Set(unboundAttributes || []),
45
47
  });
46
48
  return node;
47
49
  };
48
50
 
49
- const symbolTypeFor = (type) => {
50
- // prettier-ignore
51
- switch (type) {
52
- case NullTag: return sym.null;
53
- case GapTag: return sym.gap;
54
- default: throw new Error();
55
- }
56
- };
57
-
58
- const { freeze } = Object;
59
-
60
- const buildStubNode = (tag) => {
61
- return freeze({
62
- flags: nodeFlags,
63
- language: null,
64
- type: symbolTypeFor(tag.type),
65
- children: freeze([tag]),
66
- properties: freeze({}),
67
- attributes: freeze({}),
68
- });
69
- };
70
-
71
51
  export const StateFacade = class AgastStateFacade {
72
52
  constructor(state) {
73
53
  facades.set(state, this);
@@ -110,11 +90,11 @@ export const StateFacade = class AgastStateFacade {
110
90
  }
111
91
 
112
92
  nodeForPath(path) {
113
- return actuals.get(this).nodeForPath(path);
93
+ return actuals.get(this).nodeForPath(actuals.get(path));
114
94
  }
115
95
 
116
96
  pathForTag(tag) {
117
- return actuals.get(this).pathForTag(tag);
97
+ return facades.get(actuals.get(this).pathForTag(tag));
118
98
  }
119
99
 
120
100
  nodeForTag(tag) {
@@ -170,7 +150,7 @@ export const State = class AgastState extends WeakStackFrame {
170
150
  }
171
151
 
172
152
  get tagNodes() {
173
- return this.internalContext.tagNodes;
153
+ return this.context.tagNodes;
174
154
  }
175
155
 
176
156
  get unboundAttributes() {
@@ -206,7 +186,7 @@ export const State = class AgastState extends WeakStackFrame {
206
186
  this.result?.type === ReferenceTag &&
207
187
  ![OpenNodeTag, GapTag, NullTag, ArrayTag].includes(tag.type)
208
188
  ) {
209
- throw new Error(`${tag.type} is not a valid reference target`);
189
+ throw new Error(`${printType(tag.type)} is not a valid reference target`);
210
190
  }
211
191
 
212
192
  prevTags.set(tag, this.result);
@@ -224,28 +204,24 @@ export const State = class AgastState extends WeakStackFrame {
224
204
 
225
205
  case OpenNodeTag: {
226
206
  const openTag = tag;
227
- const { type, flags } = tag.value;
207
+ const { flags } = tag.value;
228
208
  this.node = createNodeWithState(tag, options);
229
209
 
230
210
  const reference = this.result;
231
211
 
232
212
  this.node.children = btree.push(this.node.children, tag);
233
213
 
234
- if (!type) {
235
- this.node.attributes = this.result.value.attributes;
236
- } else {
237
- if (!flags.trivia && !flags.escape) {
238
- if (
239
- reference.type !== ReferenceTag &&
240
- reference.type !== ShiftTag &&
241
- reference.type !== OpenNodeTag &&
242
- !reference.value.type
243
- ) {
244
- throw new Error('Invalid location for OpenNodeTag');
245
- }
246
- } else {
247
- this.path = this.path.push(ctx, null, btree.getSum(this.node.children));
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');
248
222
  }
223
+ } else {
224
+ this.path = this.path.push(ctx, null, btree.getSum(this.node.children));
249
225
  }
250
226
 
251
227
  this.pathNodes.set(this.node, this.path);
@@ -256,6 +232,25 @@ export const State = class AgastState extends WeakStackFrame {
256
232
  break;
257
233
  }
258
234
 
235
+ case OpenFragmentTag: {
236
+ const openTag = tag;
237
+ this.node = createNodeWithState(tag, options);
238
+
239
+ const reference = this.result;
240
+
241
+ this.node.attributes = this.result.value.attributes;
242
+ this.node.children = btree.push(this.node.children, reference);
243
+
244
+ 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;
252
+ }
253
+
259
254
  case CloseNodeTag: {
260
255
  const openTag = getOpenTag(this.node);
261
256
  const { flags, type: openType } = openTag.value;
@@ -264,28 +259,41 @@ export const State = class AgastState extends WeakStackFrame {
264
259
 
265
260
  this.node.children = btree.push(this.node.children, tag);
266
261
 
267
- if (openType) {
268
- if (this.node.unboundAttributes?.size)
269
- throw new Error('Grammar failed to bind all attributes');
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
+ }
270
282
 
271
- if (!type) throw new Error(`CloseNodeTag must have type`);
283
+ this.tagNodes.set(closeTag, this.node);
284
+ this.tagPaths.set(closeTag, this.path);
272
285
 
273
- if (type !== openType)
274
- throw new Error(
275
- `Grammar close {type: ${printType(type)}} did not match open {type: ${printType(
276
- openType,
277
- )}}`,
278
- );
286
+ finalizeNode(this.node);
279
287
 
280
- if (!flags.escape && !flags.trivia) {
281
- add(this.parentNode, this.path.reference, this.node);
282
- } else {
283
- this.parentNode.children = btree.push(
284
- this.parentNode.children,
285
- buildEmbeddedNode(this.node),
286
- );
287
- }
288
- }
288
+ this.node = this.parentNode;
289
+ this.path = this.path.parent;
290
+ break;
291
+ }
292
+
293
+ case CloseFragmentTag: {
294
+ const closeTag = tag;
295
+
296
+ this.node.children = btree.push(this.node.children, tag);
289
297
 
290
298
  this.tagNodes.set(closeTag, this.node);
291
299
  this.tagPaths.set(closeTag, this.path);
@@ -300,7 +308,11 @@ export const State = class AgastState extends WeakStackFrame {
300
308
  case ReferenceTag: {
301
309
  this.node.children = btree.push(this.node.children, tag);
302
310
 
303
- const { isArray, name } = tag.value;
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
+ }
304
316
 
305
317
  if (isArray && !hasOwn(this.node.properties, name)) {
306
318
  this.node.properties[name] = [];
@@ -324,14 +336,32 @@ export const State = class AgastState extends WeakStackFrame {
324
336
  target = this.held.node;
325
337
 
326
338
  this.held = null;
327
- } else if (this.expressions.size) {
328
- const expression = this.expressions.value;
329
- target = expression != null ? getRoot(expression) : buildStubNode(tag);
330
- this.expressions = this.expressions.pop();
331
339
  } else {
332
- target = buildStubNode(tag);
340
+ if (!this.node.flags.hasGap) throw new Error('Node must allow gaps');
341
+
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);
349
+
350
+ this.expressions = this.expressions.pop();
351
+ }
352
+
353
+ // const range = ctx.buildRange(streamFromTree(target));
354
+
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
+ }
333
361
  }
334
362
 
363
+ this.tagNodes.set(tag, target);
364
+
335
365
  this.pathNodes.set(this.pathForTag(ref), target);
336
366
  add(this.node, ref, target);
337
367
 
@@ -380,11 +410,7 @@ export const State = class AgastState extends WeakStackFrame {
380
410
  break;
381
411
  }
382
412
 
383
- case LiteralTag: {
384
- this.node.children = btree.push(this.node.children, tag);
385
- break;
386
- }
387
-
413
+ case LiteralTag:
388
414
  case ArrayTag:
389
415
  this.node.children = btree.push(this.node.children, tag);
390
416
  break;
@@ -399,7 +425,7 @@ export const State = class AgastState extends WeakStackFrame {
399
425
  return tag;
400
426
  }
401
427
 
402
- *emit() {
428
+ *emit(options) {
403
429
  const { nextTags } = this.context;
404
430
  if (!this.depth) {
405
431
  let emittable = this.emitted ? nextTags.get(this.emitted) : this.result;
@@ -412,7 +438,8 @@ export const State = class AgastState extends WeakStackFrame {
412
438
  nodeStates.get(this.nodeForTag(emittable)).unboundAttributes?.size
413
439
  )
414
440
  ) {
415
- yield emittable;
441
+ yield options.emitEffects ? buildYieldEffect(emittable) : emittable;
442
+
416
443
  this.emitted = emittable;
417
444
  emittable = nextTags.get(this.emitted);
418
445
  }
@@ -424,7 +451,7 @@ export const State = class AgastState extends WeakStackFrame {
424
451
  }
425
452
 
426
453
  get isGap() {
427
- return this.tag.type === 'NodeGapTag';
454
+ return this.tag.type === GapTag;
428
455
  }
429
456
 
430
457
  get speculative() {
package/package.json CHANGED
@@ -1,19 +1,17 @@
1
1
  {
2
2
  "name": "@bablr/agast-vm",
3
3
  "description": "A VM providing DOM-like guarantees about agAST trees",
4
- "version": "0.5.0",
4
+ "version": "0.6.1",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
- "files": [
8
- "lib"
9
- ],
7
+ "files": ["lib"],
10
8
  "exports": {
11
9
  ".": "./lib/index.js"
12
10
  },
13
11
  "sideEffects": false,
14
12
  "dependencies": {
15
- "@bablr/agast-helpers": "0.4.0",
16
- "@bablr/agast-vm-helpers": "0.4.0",
13
+ "@bablr/agast-helpers": "^0.5.0",
14
+ "@bablr/agast-vm-helpers": "^0.5.0",
17
15
  "@bablr/coroutine": "0.1.0",
18
16
  "@bablr/weak-stack": "0.1.0",
19
17
  "@iter-tools/imm-stack": "1.1.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
- };