@bablr/agast-vm 0.1.2 → 0.1.3
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 +16 -26
- package/lib/evaluate.js +16 -26
- package/lib/path.js +4 -11
- package/lib/state.js +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,19 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
The agAST VM provides consistency guarantees when with CSTML documents to parse or transform code. It has no language-specific functionality of any kind. Instead it acts as a streaming traversal engine for CSTML.
|
|
4
4
|
|
|
5
|
-
## Why
|
|
6
|
-
|
|
7
|
-
The goal of this project is to transition editors towards being a lot more like web browsers. You can have many of them, and they can be written in a variety of languages (though many share internals). You can even have a terminal browser like Lynx that does presentation very differently, yet it is still possible (if not trivial) to write a website once that will run on all (er, most) web browsers.
|
|
8
|
-
|
|
9
|
-
If the parallel is not immediately obvious, try thinking about it this way: a webapp is really more or less a set of automated tools for editing a DOM tree. As programmers we have all these amazing web libraries and frameworks that can exist because at the end of the day everything comes down to editing a shared DOM tree. There's a great explanation of those dynamics here: https://glazkov.com/2024/01/02/the-protocol-force/
|
|
10
|
-
|
|
11
|
-
If a code-DOM existed and was shared by all IDEs there would spring up a rich ecosystem of tools for accomplishing many kinds of tree alterations. For example it could become common for library authors to publish codemods that could help any project upgrade past breaking changes in its APIs!
|
|
12
|
-
|
|
13
5
|
## API
|
|
14
6
|
|
|
15
|
-
The VM responds to several instructions, but its primary API is `advance(token)`, where `token` may be a `
|
|
7
|
+
The VM responds to several instructions, but its primary API is `advance(token)`, where `token` may be a `OpenFragmentTag`, `CloseFragmentTag`, `OpenNodeTag`, `CloseNodeTag`, `Literal`, `Reference`, or `Gap`.
|
|
16
8
|
|
|
17
|
-
The VM requires the basic invariants of CSTML to be followed, for example that `Reference` must be followed by either a `
|
|
9
|
+
The VM requires the basic invariants of CSTML to be followed, for example that `Reference` must be followed by either a `OpenNodeTag` or a `Gap`. In fact, `agast-vm` is the reference implementation of these invariants.
|
|
18
10
|
|
|
19
11
|
The VM supports `branch()`, `accept()`, and `reject()` instructions, which allow a series of instructions to have their effects applied or discarded together in a kind of transaction.
|
|
20
12
|
|
|
@@ -23,25 +15,20 @@ Finally the VM supports `bindAttribute(key, value)`. A node's attributes start u
|
|
|
23
15
|
Here are the basic types used by the VM:
|
|
24
16
|
|
|
25
17
|
```ts
|
|
26
|
-
type Token =
|
|
18
|
+
type Token = OpenFragmentTag | CloseFragmentTag | OpenNodeTag | CloseNodeTag | Literal | Reference | Gap;
|
|
27
19
|
|
|
28
|
-
type
|
|
29
|
-
type: '
|
|
30
|
-
value:
|
|
31
|
-
flags: {
|
|
32
|
-
trivia: boolean
|
|
33
|
-
},
|
|
34
|
-
language: string,
|
|
35
|
-
}
|
|
20
|
+
type OpenFragmentTag {
|
|
21
|
+
type: 'OpenFragmentTag',
|
|
22
|
+
value: null
|
|
36
23
|
}
|
|
37
24
|
|
|
38
|
-
type
|
|
39
|
-
type: '
|
|
25
|
+
type CloseFragmentTag {
|
|
26
|
+
type: 'CloseFragmentTag',
|
|
40
27
|
value: null
|
|
41
28
|
}
|
|
42
29
|
|
|
43
|
-
type
|
|
44
|
-
type: '
|
|
30
|
+
type OpenNodeTag {
|
|
31
|
+
type: 'OpenNodeTag',
|
|
45
32
|
value: {
|
|
46
33
|
flags: {
|
|
47
34
|
token: boolean,
|
|
@@ -54,9 +41,12 @@ type StartNodeTag {
|
|
|
54
41
|
}
|
|
55
42
|
}
|
|
56
43
|
|
|
57
|
-
type
|
|
58
|
-
type: '
|
|
59
|
-
value:
|
|
44
|
+
type CloseNodeTag {
|
|
45
|
+
type: 'CloseNodeTag',
|
|
46
|
+
value: {
|
|
47
|
+
language: string,
|
|
48
|
+
type: string,
|
|
49
|
+
}
|
|
60
50
|
}
|
|
61
51
|
|
|
62
52
|
type Literal {
|
package/lib/evaluate.js
CHANGED
|
@@ -19,7 +19,7 @@ import { facades } from './facades.js';
|
|
|
19
19
|
|
|
20
20
|
export const evaluate = (ctx, strategy) => new StreamIterable(__evaluate(ctx, strategy));
|
|
21
21
|
|
|
22
|
-
const __evaluate = function*
|
|
22
|
+
const __evaluate = function* agast(ctx, strategy) {
|
|
23
23
|
let s = State.from(ctx);
|
|
24
24
|
|
|
25
25
|
const co = new Coroutine(getStreamIterator(strategy(facades.get(ctx), facades.get(s))));
|
|
@@ -66,7 +66,7 @@ const __evaluate = function* agastStrategy(ctx, strategy) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
case 'advance': {
|
|
69
|
-
const { arguments: { 0: terminal } = [] } = instr;
|
|
69
|
+
const { arguments: { 0: terminal, 1: options } = [] } = instr;
|
|
70
70
|
|
|
71
71
|
switch (terminal?.type || 'Null') {
|
|
72
72
|
case 'DoctypeTag': {
|
|
@@ -163,7 +163,7 @@ const __evaluate = function* agastStrategy(ctx, strategy) {
|
|
|
163
163
|
case 'OpenFragmentTag': {
|
|
164
164
|
const openTag = buildFragmentOpenTag();
|
|
165
165
|
|
|
166
|
-
s.node = Node.from(
|
|
166
|
+
s.node = Node.from(openTag);
|
|
167
167
|
|
|
168
168
|
yield* s.emit(openTag);
|
|
169
169
|
|
|
@@ -182,16 +182,9 @@ const __evaluate = function* agastStrategy(ctx, strategy) {
|
|
|
182
182
|
|
|
183
183
|
case 'OpenNodeTag': {
|
|
184
184
|
const { flags, language, type, intrinsicValue, attributes } = terminal.value;
|
|
185
|
+
const { unboundAttributes } = options || {};
|
|
185
186
|
const reference = s.result;
|
|
186
|
-
const
|
|
187
|
-
const unboundAttributes = Object.entries(attributes).filter((a) => a[1].type === 'Gap');
|
|
188
|
-
const openTag = buildNodeOpenTag(
|
|
189
|
-
flags,
|
|
190
|
-
language,
|
|
191
|
-
type,
|
|
192
|
-
intrinsicValue,
|
|
193
|
-
Object.fromEntries(boundAttributes),
|
|
194
|
-
);
|
|
187
|
+
const openTag = buildNodeOpenTag(flags, language, type, intrinsicValue, attributes);
|
|
195
188
|
|
|
196
189
|
if (!flags.trivia && !flags.escape && s.path.depth > 1) {
|
|
197
190
|
if (reference.type !== 'Reference' && reference.type !== 'OpenFragmentTag') {
|
|
@@ -199,29 +192,25 @@ const __evaluate = function* agastStrategy(ctx, strategy) {
|
|
|
199
192
|
}
|
|
200
193
|
}
|
|
201
194
|
|
|
202
|
-
|
|
203
|
-
throw new Error('intrinsic nodes must occur inside tokens');
|
|
204
|
-
}
|
|
195
|
+
const { flags: openFlags } = openTag.value;
|
|
205
196
|
|
|
206
|
-
if (!
|
|
207
|
-
const
|
|
197
|
+
if (!(openFlags.trivia || openFlags.escape) && !s.path.depth) {
|
|
198
|
+
const tag = buildReference('root', false);
|
|
199
|
+
s.path = s.path.push(ctx, tag);
|
|
200
|
+
s.node.resolver.consume(tag);
|
|
201
|
+
}
|
|
208
202
|
|
|
209
|
-
|
|
210
|
-
const tag = buildReference('root', false);
|
|
211
|
-
s.path = s.path.push(ctx, tag);
|
|
212
|
-
s.node.resolver.consume(tag);
|
|
213
|
-
}
|
|
214
|
-
} else if (!intrinsicValue) {
|
|
203
|
+
if (flags.expression && !flags.intrinsic) {
|
|
215
204
|
s.expressionDepth++;
|
|
216
205
|
}
|
|
217
206
|
|
|
218
|
-
const newNode = s.node.push(
|
|
207
|
+
const newNode = s.node.push(openTag);
|
|
219
208
|
|
|
220
|
-
newNode.unboundAttributes = new Set(unboundAttributes
|
|
209
|
+
newNode.unboundAttributes = new Set(unboundAttributes);
|
|
221
210
|
|
|
222
211
|
ctx.tagNodes.set(openTag, newNode);
|
|
223
212
|
|
|
224
|
-
if (!
|
|
213
|
+
if (!intrinsicValue) {
|
|
225
214
|
s.node = newNode;
|
|
226
215
|
} else {
|
|
227
216
|
s.path = s.path.parent;
|
|
@@ -267,6 +256,7 @@ const __evaluate = function* agastStrategy(ctx, strategy) {
|
|
|
267
256
|
if (!flags.escape && !flags.trivia) {
|
|
268
257
|
const { name: refName, isArray } = s.path.reference.value;
|
|
269
258
|
|
|
259
|
+
// is this right?
|
|
270
260
|
if (s.path.depth > 2) {
|
|
271
261
|
const { properties } = s.node.parent;
|
|
272
262
|
|
package/lib/path.js
CHANGED
|
@@ -97,12 +97,11 @@ export const PathFacade = class AgastPathFacade {
|
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
export const Node = class AgastNode extends WeakStackFrame {
|
|
100
|
-
static from(
|
|
101
|
-
return AgastNode.create(
|
|
100
|
+
static from(openTag) {
|
|
101
|
+
return AgastNode.create(openTag);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
constructor(
|
|
105
|
-
path,
|
|
106
105
|
openTag,
|
|
107
106
|
closeTag = null,
|
|
108
107
|
properties = new Map(),
|
|
@@ -111,7 +110,6 @@ export const Node = class AgastNode extends WeakStackFrame {
|
|
|
111
110
|
) {
|
|
112
111
|
super();
|
|
113
112
|
|
|
114
|
-
this.path = path;
|
|
115
113
|
this.openTag = openTag;
|
|
116
114
|
this.closeTag = closeTag;
|
|
117
115
|
this.properties = properties;
|
|
@@ -119,10 +117,6 @@ export const Node = class AgastNode extends WeakStackFrame {
|
|
|
119
117
|
this.unboundAttributes = unboundAttributes;
|
|
120
118
|
}
|
|
121
119
|
|
|
122
|
-
get context() {
|
|
123
|
-
return this.path.context;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
120
|
get language() {
|
|
127
121
|
return this.openTag.value?.language;
|
|
128
122
|
}
|
|
@@ -140,10 +134,9 @@ export const Node = class AgastNode extends WeakStackFrame {
|
|
|
140
134
|
}
|
|
141
135
|
|
|
142
136
|
branch() {
|
|
143
|
-
const {
|
|
137
|
+
const { openTag, closeTag, properties, resolver, unboundAttributes } = this;
|
|
144
138
|
|
|
145
|
-
return this.
|
|
146
|
-
path,
|
|
139
|
+
return this.replace(
|
|
147
140
|
openTag,
|
|
148
141
|
closeTag,
|
|
149
142
|
new Map(properties), // there is probably a better way
|
package/lib/state.js
CHANGED
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.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"author": "Conrad Buck<conartist6@gmail.com>",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"test": "node ./test/runner.js"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@bablr/agast-helpers": "0.1.
|
|
19
|
-
"@bablr/agast-vm-helpers": "0.1.
|
|
18
|
+
"@bablr/agast-helpers": "0.1.5",
|
|
19
|
+
"@bablr/agast-vm-helpers": "0.1.3",
|
|
20
20
|
"@bablr/coroutine": "0.1.0",
|
|
21
21
|
"@bablr/weak-stack": "0.1.0"
|
|
22
22
|
},
|