@bablr/bablr-vm 0.13.2 → 0.14.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/README.md CHANGED
@@ -1,3 +1,33 @@
1
- # @bablr/vm
1
+ # @bablr/bablr-vm
2
2
 
3
- The BABLR VM!
3
+ [![come chat on Discord](https://img.shields.io/discord/1151914613089251388)](https://discord.gg/NfMNyYN6cX)
4
+
5
+ Welcome to the home of the BABLR VM! The VM is at the core of the BABLR language definition ecosystem: its purpose is to provide a powerful, well-specified, universally-available execution environment for parsers.
6
+
7
+ By doing this BABLR makes it possible to create tools which integrate more deeply: it can create consensus around basic definitions that must be shared between well-integrated tools.
8
+
9
+ ## Usage
10
+
11
+ Unless you are a power user, you should be using either [the BABLR API](https://github.com/bablr-lang/bablr) or [the BABLR CLI](https://github.com/bablr-lang/bablr-cli). If you are a power user (or otherwise have unique reuquirements) looking at how those packages are implemented will be a good place to start.
12
+
13
+ ## Features
14
+
15
+ Parsers defined on the BABLR VM
16
+
17
+ - Execute in a streaming, LR fashion
18
+ - Hold output as necessary for expression building
19
+ - Do just-in-time tokenization
20
+ - Support backtracking to resolve ambiguity
21
+ - Support arbitrary-size textual lookahead
22
+ - Offer high-level tools for defining trivia and precedence
23
+ - Produce results as a CSTML stream or an agAST tree
24
+ - Are backwards compatible with new VMs
25
+ - Are freely extensible
26
+ - Allow incremental reparsing (for responsivene editing)
27
+ - Can be easily debugged
28
+ - Get a comment attachment engine for free
29
+ - Support parsing templates (programs with holes in them)
30
+
31
+ Parsers defined on the BABLR VM do not have error recovery, and this is on purpose. In general error recovery is not science, it's guesswork necessary to keep the semantic model of the program from continually blinking into and out of existence as the user types syntax, passing through invalid syntactic states.
32
+
33
+ BABLR instead aims to make the most obvious solution the most pleasant: help users edit syntax without passing through invalid states
package/lib/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export { evaluate } from './evaluate.js';
1
+ export { createBablrStrategy } from './strategy.js';
2
2
 
3
3
  export { Source } from './source.js';
4
- export { Context } from './context.js';
package/lib/source.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { WeakStackFrame } from '@bablr/weak-stack';
2
- import { maybeWait, getStreamIterator } from '@bablr/agast-helpers/stream';
2
+ import { maybeWait, getStreamIterator, emptyStreamIterator } from '@bablr/agast-helpers/stream';
3
3
  import { facades, actuals } from './facades.js';
4
4
 
5
5
  // Queue item instances are shared between all forks.
@@ -197,7 +197,7 @@ export const Source = class BABLRSource extends WeakStackFrame {
197
197
  }
198
198
 
199
199
  get value() {
200
- return this.fork.value;
200
+ return this.holding ? null : this.fork.value;
201
201
  }
202
202
 
203
203
  get done() {
@@ -247,7 +247,7 @@ export const Source = class BABLRSource extends WeakStackFrame {
247
247
  }
248
248
 
249
249
  [Symbol.for('@@streamIterator')]() {
250
- return this.fork.clone()[Symbol.for('@@streamIterator')]();
250
+ return this.holding ? emptyStreamIterator : this.fork.clone()[Symbol.for('@@streamIterator')]();
251
251
  }
252
252
 
253
253
  formatIndex() {
package/lib/spans.js CHANGED
@@ -5,12 +5,12 @@ export function updateSpans(ctx, s, node, phase) {
5
5
  const { span: innerSpan, balanced, balancedSpan, balancer, openSpan } = attributes || {};
6
6
 
7
7
  if (!flags.intrinsic && (balancer || balanced)) {
8
- throw new Error('not implemented');
8
+ throw new Error('balanced tokens must be instrinsic');
9
9
  }
10
10
 
11
11
  if (flags.intrinsic) {
12
12
  if (s.path && balanced) {
13
- s.spans = s.spans.push({
13
+ s.spans = s.spans.push({
14
14
  type: 'Lexical',
15
15
  name: balancedSpan || s.span.name,
16
16
  path: s.path,
@@ -47,7 +47,7 @@ export function updateSpans(ctx, s, node, phase) {
47
47
  }
48
48
 
49
49
  if (balanced) {
50
- s.balanced = s.balanced.push(ctx.agast.nodeForTag(s.result));
50
+ s.balanced = s.balanced.push(ctx.nodeForTag(s.result));
51
51
  }
52
52
 
53
53
  if (innerSpan) {
package/lib/state.js CHANGED
@@ -9,8 +9,8 @@ export const StateFacade = class BABLRStateFacade {
9
9
  facades.set(state, this);
10
10
  }
11
11
 
12
- static from(context, source) {
13
- return State.from(actuals.get(context), actuals.get(source));
12
+ static from(source) {
13
+ return State.from(actuals.get(source));
14
14
  }
15
15
 
16
16
  get span() {
@@ -25,10 +25,6 @@ export const StateFacade = class BABLRStateFacade {
25
25
  return actuals.get(this).holding;
26
26
  }
27
27
 
28
- get context() {
29
- return facades.get(actuals.get(this).context);
30
- }
31
-
32
28
  get path() {
33
29
  return actuals.get(this).path;
34
30
  }
@@ -45,34 +41,29 @@ export const StateFacade = class BABLRStateFacade {
45
41
  return actuals.get(this).depth;
46
42
  }
47
43
 
48
- get ctx() {
49
- return this.context;
44
+ get status() {
45
+ return actuals.get(this).status;
50
46
  }
51
47
  };
52
48
 
53
49
  export const State = class BABLRState extends WeakStackFrame {
54
- constructor(
55
- context,
56
- source,
57
- agast,
58
- balanced = emptyStack,
59
- spans = emptyStack.push({ name: 'Bare' }),
60
- ) {
50
+ constructor(source, agast, balanced = emptyStack, spans = emptyStack.push({ name: 'Bare' })) {
61
51
  super();
62
52
 
63
- if (!context || !source || !agast) throw new Error('invalid args to State');
53
+ if (!source || !agast) throw new Error('invalid args to State');
64
54
 
65
- this.context = context;
66
55
  this.source = source;
67
56
  this.agast = agast;
68
57
  this.balanced = balanced;
69
58
  this.spans = spans;
70
59
 
60
+ this.status = 'active';
61
+
71
62
  new StateFacade(this);
72
63
  }
73
64
 
74
- static from(context, source, agast) {
75
- return State.create(context, source, agast);
65
+ static from(source, agast) {
66
+ return State.create(source, agast);
76
67
  }
77
68
 
78
69
  get guardedSource() {
@@ -82,10 +73,6 @@ export const State = class BABLRState extends WeakStackFrame {
82
73
  return guard ? guardWithPattern(guard, source) : source;
83
74
  }
84
75
 
85
- get ctx() {
86
- return this.context;
87
- }
88
-
89
76
  get span() {
90
77
  return this.spans.value;
91
78
  }
@@ -118,11 +105,12 @@ export const State = class BABLRState extends WeakStackFrame {
118
105
  let { span, spans, source } = this;
119
106
  let { guard } = span;
120
107
 
121
- if (pattern?.flags?.intrinsic) {
122
- if (pattern.type === 'OpenNodeTag') {
123
- // TODO differntiate better between self-closing tags and matchers
124
- pattern = pattern.value;
125
- }
108
+ if (pattern?.intrinsicValue) {
109
+ // if (pattern.type === 'OpenNodeTag') {
110
+
111
+ // // TODO differntiate better between self-closing tags and matchers
112
+ // pattern = pattern.value;
113
+ // }
126
114
 
127
115
  ({ guard } = span);
128
116
 
@@ -1,33 +1,24 @@
1
1
  import { Coroutine } from '@bablr/coroutine';
2
2
  import { buildCall, buildNull, buildReference, reifyExpression } from '@bablr/agast-vm-helpers';
3
3
  import { StreamGenerator } from '@bablr/agast-helpers/stream';
4
- import { resolveLanguage } from '@bablr/helpers/grammar';
5
4
  import { buildTokens } from './utils/token.js';
6
5
  import { formatType } from './utils/format.js';
7
6
  import { facades } from './facades.js';
8
7
  import { State } from './state.js';
9
8
  import { updateSpans } from './spans.js';
10
9
 
11
- const nodeTopType = Symbol.for('@bablr/node');
12
-
13
- export const evaluate = (ctx, rootLanguage, rootSource, strategy) => {
14
- return (agastCtx, agastState) => {
15
- if (ctx.agast !== agastCtx) throw new Error();
16
-
17
- if (rootLanguage !== ctx.languages.get(rootLanguage.canonicalURL)) {
18
- throw new Error();
19
- }
20
-
21
- return new StreamGenerator(__evaluate(ctx, rootLanguage, rootSource, agastState, strategy));
10
+ export const createBablrStrategy = (rootSource, strategy) => {
11
+ return (ctx, agastState) => {
12
+ return new StreamGenerator(__strategy(ctx, rootSource, agastState, strategy));
22
13
  };
23
14
  };
24
15
 
25
16
  const resolvedLanguages = new WeakMap();
26
17
 
27
- const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastState, strategy) {
28
- let s = State.from(ctx, rootSource, agastState);
18
+ const __strategy = function* bablrStrategy(ctx, rootSource, agastState, strategy) {
19
+ let s = State.from(rootSource, agastState);
29
20
 
30
- let co = new Coroutine(strategy(facades.get(s), facades.get(ctx)));
21
+ let co = new Coroutine(strategy(facades.get(s), ctx));
31
22
 
32
23
  co.advance();
33
24
 
@@ -69,60 +60,31 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
69
60
  }
70
61
 
71
62
  case 'OpenNodeTag': {
72
- const { flags, type, language: tagLanguage, intrinsicValue } = terminal.value;
73
-
74
- const reference = s.result;
75
-
76
- if (
77
- s.path.depth > 1 &&
78
- !flags.trivia &&
79
- !flags.escape &&
80
- reference?.type !== 'Reference' &&
81
- reference?.type !== 'OpenFragmentTag'
82
- ) {
83
- throw new Error('Invalid location for OpenNodeTag');
84
- }
85
-
86
- const oldNode = s.node;
63
+ const { type, intrinsicValue } = terminal.value;
87
64
 
88
65
  const openTag = yield sourceInstr;
89
66
 
90
- const resolvedLanguage =
91
- oldNode.depth > 1 ? resolvedLanguages.get(oldNode) : rootLanguage;
92
- const nextResolvedLanguage = resolveLanguage(resolvedLanguage, tagLanguage);
93
-
94
- if (!nextResolvedLanguage) {
95
- throw new Error(`Resolve failed { language: ${tagLanguage} }`);
96
- }
97
-
98
- const grammar = ctx.grammars.get(nextResolvedLanguage);
99
- const isNode = grammar.covers.get(nodeTopType).has(type);
100
-
101
- let intrinsicResult = intrinsicValue && s.guardedMatch(terminal.value);
67
+ if (type) {
68
+ let intrinsicResult = intrinsicValue && s.guardedMatch(terminal.value);
102
69
 
103
- if (intrinsicResult instanceof Promise) {
104
- intrinsicResult = yield intrinsicResult;
105
- }
70
+ if (intrinsicResult instanceof Promise) {
71
+ intrinsicResult = yield intrinsicResult;
72
+ }
106
73
 
107
- updateSpans(ctx, s, intrinsicValue ? ctx.agast.nodeForTag(openTag) : s.node, 'open');
74
+ updateSpans(ctx, s, intrinsicValue ? ctx.nodeForTag(openTag) : s.node, 'open');
108
75
 
109
- if (intrinsicValue) {
110
- if (!intrinsicResult) {
111
- throw new Error('advance failed to match an intrinsic node');
112
- }
76
+ if (intrinsicValue) {
77
+ if (!intrinsicResult) {
78
+ throw new Error('advance failed to match an intrinsic node');
79
+ }
113
80
 
114
- const sourceStep = s.source.advance(intrinsicResult.length);
81
+ const sourceStep = s.source.advance(intrinsicResult.length);
115
82
 
116
- if (sourceStep instanceof Promise) {
117
- yield sourceStep;
118
- }
83
+ if (sourceStep instanceof Promise) {
84
+ yield sourceStep;
85
+ }
119
86
 
120
- updateSpans(ctx, s, ctx.agast.nodeForTag(openTag), 'close');
121
- } else {
122
- if (isNode) {
123
- resolvedLanguages.set(s.node, nextResolvedLanguage);
124
- } else {
125
- resolvedLanguages.set(s.node, resolvedLanguage);
87
+ updateSpans(ctx, s, ctx.nodeForTag(openTag), 'close');
126
88
  }
127
89
  }
128
90
 
@@ -130,15 +92,6 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
130
92
  break;
131
93
  }
132
94
 
133
- case 'OpenFragmentTag': {
134
- const openTag = yield sourceInstr;
135
-
136
- resolvedLanguages.set(s.node, rootLanguage);
137
-
138
- returnValue = openTag;
139
- break;
140
- }
141
-
142
95
  case 'CloseNodeTag': {
143
96
  const { node } = s;
144
97
 
@@ -146,16 +99,7 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
146
99
 
147
100
  if (s.path) {
148
101
  updateSpans(ctx, s, node, 'close');
149
- }
150
-
151
- returnValue = endTag;
152
- break;
153
- }
154
-
155
- case 'CloseFragmentTag': {
156
- returnValue = yield sourceInstr;
157
-
158
- if (!s.path) {
102
+ } else {
159
103
  if (!s.source.done) {
160
104
  throw new Error('Parser failed to consume input');
161
105
  }
@@ -164,6 +108,8 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
164
108
  throw new Error('Parser did not match all balanced nodes');
165
109
  }
166
110
  }
111
+
112
+ returnValue = endTag;
167
113
  break;
168
114
  }
169
115
 
@@ -192,10 +138,14 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
192
138
 
193
139
  case 'Gap': {
194
140
  if (s.source.value == null && !s.source.done) {
195
- const sourceStep = s.source.advance(1);
141
+ if (s.source.holding) {
142
+ s.source.unshift();
143
+ } else {
144
+ const sourceStep = s.source.advance(1);
196
145
 
197
- if (sourceStep instanceof Promise) {
198
- yield sourceStep;
146
+ if (sourceStep instanceof Promise) {
147
+ yield sourceStep;
148
+ }
199
149
  }
200
150
 
201
151
  returnValue = yield sourceInstr;
@@ -205,6 +155,13 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
205
155
  break;
206
156
  }
207
157
 
158
+ case 'Shift': {
159
+ s.source.shift();
160
+
161
+ returnValue = yield sourceInstr;
162
+ break;
163
+ }
164
+
208
165
  default: {
209
166
  returnValue = yield sourceInstr;
210
167
  break;
@@ -223,33 +180,17 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
223
180
  result = yield result;
224
181
  }
225
182
 
226
- returnValue = result && ctx.agast.buildRange(buildTokens(result));
227
- break;
228
- }
229
-
230
- case 'shift': {
231
- s.source.shift();
232
-
233
- yield sourceInstr;
234
-
235
- break;
236
- }
237
-
238
- case 'unshift': {
239
- s.source.unshift();
240
-
241
- yield sourceInstr;
242
-
183
+ returnValue = result && ctx.buildRange(buildTokens(result));
243
184
  break;
244
185
  }
245
186
 
246
187
  case 'branch': {
247
188
  const baseState = s;
248
- let { context, source, agast, balanced, spans, node } = baseState;
189
+ let { source, agast, balanced, spans, node } = baseState;
249
190
 
250
191
  agast = yield sourceInstr;
251
192
 
252
- s = s.push(context, source.branch(), agast, balanced, spans);
193
+ s = s.push(source.branch(), agast, balanced, spans);
253
194
 
254
195
  resolvedLanguages.set(s.node, resolvedLanguages.get(node));
255
196
 
@@ -260,6 +201,8 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
260
201
  case 'accept': {
261
202
  const accepted = s;
262
203
 
204
+ s.status = 'accepted';
205
+
263
206
  const agastState = yield sourceInstr;
264
207
 
265
208
  s = s.parent;
@@ -281,27 +224,27 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
281
224
  case 'reject': {
282
225
  const rejectedState = s;
283
226
 
227
+ s.status = 'rejected';
228
+
284
229
  yield sourceInstr;
285
230
 
286
231
  s = s.parent;
287
232
 
288
- if (rejectedState.path.depth > s.path.depth) {
233
+ if (s.path.depth && rejectedState.path.depth > s.path.depth) {
234
+ const didShift = rejectedState.node.at(s.node.depth) === s.node;
289
235
  const lowPath = rejectedState.path.at(
290
- Math.min(s.path.depth + 1, rejectedState.path.depth),
236
+ Math.min(s.path.depth + (didShift ? 0 : 1), rejectedState.path.depth),
291
237
  );
238
+ const lowNode = s.node;
292
239
 
293
- if (s.path.depth) {
294
- const { name, isArray } = lowPath.reference?.value || {};
240
+ const { name, isArray } = lowPath.reference?.value || {};
295
241
 
296
- if (s.node.depth === s.path.depth && !s.node.resolver.counters.has(name)) {
297
- if (!lowPath.openTag?.value.flags.trivia && !lowPath.openTag?.value.flags.escape) {
298
- yield buildCall('advance', buildReference(name, isArray));
299
- }
300
-
301
- if (s.result.type === 'Reference') {
302
- yield buildCall('advance', buildNull());
303
- }
242
+ if (lowPath.depth === lowNode.depth + 1 && !lowNode.resolver.counters.has(name)) {
243
+ if (!lowNode.openTag?.value.flags.trivia && !lowNode.openTag?.value.flags.escape) {
244
+ yield buildCall('advance', buildReference(name, isArray));
304
245
  }
246
+
247
+ yield buildCall('advance', buildNull());
305
248
  }
306
249
  }
307
250
 
@@ -313,6 +256,7 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
313
256
  break;
314
257
  }
315
258
 
259
+ case 'write':
316
260
  case 'bindAttribute': {
317
261
  returnValue = yield sourceInstr;
318
262
  break;
@@ -323,11 +267,6 @@ const __evaluate = function* bablrStrategy(ctx, rootLanguage, rootSource, agastS
323
267
  break;
324
268
  }
325
269
 
326
- case 'getContext': {
327
- returnValue = facades.get(ctx);
328
- break;
329
- }
330
-
331
270
  default: {
332
271
  throw new Error(`Unexpected call of {type: ${formatType(verb)}}`);
333
272
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bablr/bablr-vm",
3
3
  "description": "A VM for parsing using BABLR languages",
4
- "version": "0.13.2",
4
+ "version": "0.14.0",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
7
  "files": [
@@ -12,11 +12,11 @@
12
12
  },
13
13
  "sideEffects": false,
14
14
  "dependencies": {
15
- "@bablr/agast-helpers": "0.1.1",
16
- "@bablr/agast-vm-helpers": "0.1.1",
15
+ "@bablr/agast-helpers": "0.1.6",
16
+ "@bablr/agast-vm-helpers": "0.1.5",
17
17
  "@bablr/coroutine": "0.1.0",
18
- "@bablr/helpers": "0.15.1",
19
- "@bablr/regex-vm": "0.5.0",
18
+ "@bablr/helpers": "0.16.0",
19
+ "@bablr/regex-vm": "0.5.1",
20
20
  "@bablr/weak-stack": "0.1.0",
21
21
  "@iter-tools/imm-stack": "1.1.0",
22
22
  "iter-tools-es": "^7.5.3"
package/lib/context.js DELETED
@@ -1,76 +0,0 @@
1
- import { facades, actuals } from './facades.js';
2
-
3
- export const ContextFacade = class BABLRContextFacade {
4
- get languages() {
5
- return actuals.get(this).languages;
6
- }
7
-
8
- get grammars() {
9
- return actuals.get(this).grammars;
10
- }
11
-
12
- get productionEnhancer() {
13
- return actuals.get(this).productionEnhancer;
14
- }
15
-
16
- get agast() {
17
- return actuals.get(this).agast;
18
- }
19
-
20
- getPreviousTerminal(token) {
21
- return actuals.get(this).agast.getPreviousTerminal(token);
22
- }
23
-
24
- ownTerminalsFor(range) {
25
- return actuals.get(this).agast.ownTerminalsFor(range);
26
- }
27
-
28
- allTerminalsFor(range) {
29
- return actuals.get(this).agast.allTerminalsFor(range);
30
- }
31
-
32
- getCooked(range) {
33
- return actuals.get(this).agast.getCooked(range);
34
- }
35
-
36
- reifyExpression(value) {
37
- return actuals.get(this).agast.reifyExpression(value);
38
- }
39
-
40
- getProperty(node, name) {
41
- return actuals.get(this).agast.getProperty(node, name);
42
- }
43
-
44
- sourceTextFor(range) {
45
- return actuals.get(this).agast.sourceTextFor(range);
46
- }
47
-
48
- nodeForTag(tag) {
49
- return actuals.get(this).agast.nodeForTag(tag);
50
- }
51
-
52
- unbox(value) {
53
- return actuals.get(this).agast.unbox(value);
54
- }
55
- };
56
-
57
- export const Context = class BABLRContext {
58
- static from(agastContext, languages, productionEnhancer) {
59
- return new Context(agastContext, languages, productionEnhancer);
60
- }
61
-
62
- constructor(agastContext, languages, productionEnhancer) {
63
- this.agast = agastContext;
64
- this.languages = languages;
65
- this.productionEnhancer = productionEnhancer;
66
-
67
- this.grammars = new WeakMap();
68
- this.facade = new ContextFacade();
69
-
70
- for (const { 1: language } of this.languages) {
71
- this.grammars.set(language, new language.grammar());
72
- }
73
-
74
- facades.set(this, this.facade);
75
- }
76
- };