@bablr/agast-vm 0.8.0 → 0.9.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 +14 -49
- package/lib/evaluate.js +116 -147
- package/lib/index.js +2 -1
- package/lib/node.js +176 -0
- package/lib/path.js +160 -1
- package/lib/state.js +256 -146
- package/package.json +3 -6
- package/lib/context.js +0 -88
- package/lib/utils/array.js +0 -7
- package/lib/utils/iterable.js +0 -13
- package/lib/utils/skip.js +0 -39
package/README.md
CHANGED
|
@@ -1,58 +1,23 @@
|
|
|
1
1
|
# @bablr/agast-vm
|
|
2
2
|
|
|
3
|
-
The agAST VM
|
|
3
|
+
The agAST VM's purpose is to define what is valid agAST. For complete documentation, see the [agast-vm API reference](https://docs.bablr.org/reference/agast-vm).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Usage
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```js
|
|
8
|
+
import { agast } from '@bablr/agast-vm';
|
|
9
|
+
import * as b from '@bablr/agast-helpers/builders';
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
let vm = agast();
|
|
12
|
+
let step;
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
let openTag = b.buildOpenNodeTag(b.tokenFlags, 'Token');
|
|
15
|
+
let closeTag = b.buildLiteralTag('OK');
|
|
16
|
+
let closeTag = b.buildCloseNodeTag();
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
step = vm.next(openTag);
|
|
19
|
+
step = vm.next(literalTag);
|
|
20
|
+
step = vm.next(closeTag);
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
type Token = OpenNodeTag | CloseNodeTag | Literal | Reference | Gap;
|
|
19
|
-
|
|
20
|
-
type OpenNodeTag {
|
|
21
|
-
type: 'OpenNodeTag',
|
|
22
|
-
value: {
|
|
23
|
-
flags: {
|
|
24
|
-
token: boolean,
|
|
25
|
-
trivia: boolean,
|
|
26
|
-
escape: boolean
|
|
27
|
-
},
|
|
28
|
-
language: string | null,
|
|
29
|
-
type: string | null, // null type indicates a fragment
|
|
30
|
-
attributes: { [key: string]: boolean | number | string }
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type CloseNodeTag {
|
|
35
|
-
type: 'CloseNodeTag',
|
|
36
|
-
value: {
|
|
37
|
-
language: string,
|
|
38
|
-
type: string,
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
type Literal {
|
|
43
|
-
value: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
type Reference {
|
|
47
|
-
type: 'Reference',
|
|
48
|
-
value: {
|
|
49
|
-
name: string,
|
|
50
|
-
isArray: boolean
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
type Gap {
|
|
55
|
-
type: 'Gap',
|
|
56
|
-
value: null,
|
|
57
|
-
}
|
|
22
|
+
let node = step.value;
|
|
58
23
|
```
|
package/lib/evaluate.js
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
import { Coroutine } from '@bablr/coroutine';
|
|
2
|
-
import {
|
|
3
|
-
buildNullTag,
|
|
4
|
-
buildGapTag,
|
|
5
|
-
buildShiftTag,
|
|
6
|
-
buildReferenceTag,
|
|
7
|
-
buildLiteralTag,
|
|
8
|
-
buildDoctypeTag,
|
|
9
|
-
buildOpenNodeTag,
|
|
10
|
-
buildCloseNodeTag,
|
|
11
|
-
buildInitializerTag,
|
|
12
|
-
} from '@bablr/agast-vm-helpers/internal-builders';
|
|
13
|
-
import { getEmbeddedTag } from '@bablr/agast-vm-helpers/deembed';
|
|
14
|
-
import { StreamIterable, getStreamIterator } from '@bablr/agast-helpers/stream';
|
|
15
|
-
import { printExpression } from '@bablr/agast-helpers/print';
|
|
16
1
|
import {
|
|
17
2
|
DoctypeTag,
|
|
18
3
|
OpenNodeTag,
|
|
@@ -23,186 +8,170 @@ import {
|
|
|
23
8
|
NullTag,
|
|
24
9
|
InitializerTag,
|
|
25
10
|
LiteralTag,
|
|
11
|
+
AttributeDefinition,
|
|
12
|
+
BindingTag,
|
|
13
|
+
Property,
|
|
26
14
|
} from '@bablr/agast-helpers/symbols';
|
|
27
15
|
import { State } from './state.js';
|
|
28
|
-
import { Context } from './context.js';
|
|
29
16
|
import { facades } from './facades.js';
|
|
30
|
-
import {
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
export const agast = (strategy, options = {}) => {
|
|
34
|
-
const ctx = Context.create();
|
|
35
|
-
let s = State.from(ctx, options.expressions);
|
|
17
|
+
import { getFirstNode, TagPath } from '@bablr/agast-helpers/path';
|
|
18
|
+
import * as sumtree from '@bablr/agast-helpers/children';
|
|
19
|
+
import { NodeFacade } from './node.js';
|
|
36
20
|
|
|
37
|
-
|
|
38
|
-
};
|
|
21
|
+
let { isArray } = Array;
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
23
|
+
export const agast = (options = {}) => {
|
|
24
|
+
if (options.held?.properties) throw new Error();
|
|
42
25
|
|
|
43
|
-
|
|
26
|
+
let state = State.from(options.held);
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
if (co.current instanceof Promise) {
|
|
47
|
-
co.current = yield co.current;
|
|
48
|
-
}
|
|
28
|
+
let vm = __agast(state, options);
|
|
49
29
|
|
|
50
|
-
|
|
30
|
+
vm.next();
|
|
51
31
|
|
|
52
|
-
|
|
32
|
+
Object.freeze(options);
|
|
53
33
|
|
|
54
|
-
|
|
34
|
+
return Object.freeze({ options, state: facades.get(state), vm });
|
|
35
|
+
};
|
|
55
36
|
|
|
56
|
-
|
|
37
|
+
function* __agast(s) {
|
|
38
|
+
let tag = yield;
|
|
39
|
+
for (;;) {
|
|
40
|
+
let returnValue = null;
|
|
57
41
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
42
|
+
if (s.resultPath && !s.path) {
|
|
43
|
+
throw new Error('Cannot advance after completion');
|
|
44
|
+
}
|
|
61
45
|
|
|
62
|
-
|
|
46
|
+
if (
|
|
47
|
+
s.held &&
|
|
48
|
+
![OpenNodeTag, ReferenceTag, InitializerTag, Property, BindingTag].includes(tag.type) &&
|
|
49
|
+
!(
|
|
50
|
+
(tag.type === Property &&
|
|
51
|
+
(sumtree.getSize(tag.value.node.children) <= 1 ||
|
|
52
|
+
getFirstNode(tag.value.node) === s.held.node)) ||
|
|
53
|
+
tag.value.node === s.held.node
|
|
54
|
+
)
|
|
55
|
+
) {
|
|
56
|
+
throw new Error('Cannot advance while holding');
|
|
57
|
+
}
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
throw new Error('Cannot advance while holding');
|
|
59
|
+
switch (tag.type) {
|
|
60
|
+
case DoctypeTag: {
|
|
61
|
+
if (s.path) {
|
|
62
|
+
throw new Error();
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
s.advance(tag);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
74
68
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
case LiteralTag: {
|
|
70
|
+
if (!s.node.flags.token) {
|
|
71
|
+
throw new Error('literals must occur inside tokens');
|
|
72
|
+
}
|
|
78
73
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
if (s.held) {
|
|
75
|
+
throw new Error('Cannot consume input while hold register is full');
|
|
76
|
+
}
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
78
|
+
s.advance(tag);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
87
81
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
82
|
+
case ReferenceTag: {
|
|
83
|
+
const { type } = tag.value;
|
|
91
84
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
if (s.node?.flags.token && type !== '@') {
|
|
86
|
+
throw new Error('A token node cannot contain a reference');
|
|
87
|
+
}
|
|
95
88
|
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
s.advance(tag);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
98
92
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
93
|
+
case BindingTag: {
|
|
94
|
+
const { languagePath } = tag.value;
|
|
102
95
|
|
|
103
|
-
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
96
|
+
if (languagePath && !isArray(languagePath)) throw new Error();
|
|
106
97
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
98
|
+
s.advance(tag);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
111
101
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
102
|
+
case NullTag:
|
|
103
|
+
case GapTag: {
|
|
104
|
+
s.advance(tag);
|
|
105
|
+
returnValue = new NodeFacade(s.resultPath.node);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
116
108
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const { reference } = s;
|
|
109
|
+
case AttributeDefinition: {
|
|
110
|
+
if (s.atReference) throw new Error();
|
|
120
111
|
|
|
121
|
-
|
|
112
|
+
s.advance(tag);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
122
115
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
116
|
+
case InitializerTag: {
|
|
117
|
+
if (!s.atReference) throw new Error();
|
|
126
118
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
s.advance(tag);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
130
122
|
|
|
131
|
-
|
|
123
|
+
case ShiftTag: {
|
|
124
|
+
const { index } = tag.value;
|
|
125
|
+
if (s.resultPath.tag.type !== Property) throw new Error();
|
|
132
126
|
|
|
133
|
-
|
|
134
|
-
if (prevRef.tag.value.index + 1 !== index) throw new Error('bad shift index');
|
|
135
|
-
}
|
|
127
|
+
let prevRef = s.resultPath.previousSibling.previousSibling;
|
|
136
128
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
);
|
|
129
|
+
if (prevRef.tag.type === ShiftTag) {
|
|
130
|
+
if (prevRef.tag.value.index + 1 !== index) throw new Error('bad shift index');
|
|
131
|
+
}
|
|
141
132
|
|
|
142
|
-
|
|
133
|
+
let refPath = TagPath.from(
|
|
134
|
+
s.resultPath.path,
|
|
135
|
+
s.resultPath.childrenIndex - (index - 1) * 3 - 2,
|
|
136
|
+
);
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
throw new Error();
|
|
146
|
-
}
|
|
138
|
+
if (refPath.tag.type !== ReferenceTag) throw new Error();
|
|
147
139
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
140
|
+
if (!refPath.tag.value.flags.expression) {
|
|
141
|
+
throw new Error();
|
|
142
|
+
}
|
|
151
143
|
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
s.advance(tag);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
154
147
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
148
|
+
case OpenNodeTag: {
|
|
149
|
+
s.advance(tag);
|
|
158
150
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
151
|
+
returnValue = new NodeFacade(s.node);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
162
154
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (!Array.isArray(value)) {
|
|
166
|
-
if (value.node === undefined) {
|
|
167
|
-
add(s.node, value.reference, buildNullNode());
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
for (const { 0: key, 1: value } of Object.entries(s.node.attributes)) {
|
|
173
|
-
if (value === undefined) {
|
|
174
|
-
defineAttribute(s.node, key, null);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
155
|
+
case CloseNodeTag: {
|
|
156
|
+
returnValue = new NodeFacade(s.node);
|
|
177
157
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
158
|
+
s.advance(tag);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
181
161
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
162
|
+
case Property: {
|
|
163
|
+
let { node } = tag.value;
|
|
185
164
|
|
|
186
|
-
|
|
165
|
+
s.advance(tag);
|
|
187
166
|
|
|
167
|
+
returnValue = new NodeFacade(node);
|
|
188
168
|
break;
|
|
189
169
|
}
|
|
190
170
|
|
|
191
|
-
default:
|
|
192
|
-
throw new Error(
|
|
193
|
-
}
|
|
171
|
+
default:
|
|
172
|
+
throw new Error();
|
|
194
173
|
}
|
|
195
174
|
|
|
196
|
-
|
|
175
|
+
tag = yield returnValue;
|
|
197
176
|
}
|
|
198
|
-
|
|
199
|
-
if (s.depth > 0) {
|
|
200
|
-
throw new Error('Did not unwind state stack');
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (s.path) {
|
|
204
|
-
throw new Error('Did not unwind path stack');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return s.resultPath.path.node;
|
|
208
177
|
}
|
package/lib/index.js
CHANGED
package/lib/node.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildPathSegment,
|
|
3
|
+
get,
|
|
4
|
+
getChildPropertyIndex,
|
|
5
|
+
getCloseTag,
|
|
6
|
+
getProperties,
|
|
7
|
+
getPropertyChildrenIndex,
|
|
8
|
+
} from '@bablr/agast-helpers/path';
|
|
9
|
+
import * as sumtree from '@bablr/agast-helpers/children';
|
|
10
|
+
import * as btree from '@bablr/agast-helpers/btree';
|
|
11
|
+
import { DoctypeTag, GapTag, NullTag, OpenNodeTag } from '@bablr/agast-helpers/symbols';
|
|
12
|
+
import { nodeFlags } from '@bablr/agast-helpers/builders';
|
|
13
|
+
import { nodeStates } from './state.js';
|
|
14
|
+
|
|
15
|
+
let { freeze, hasOwn } = Object;
|
|
16
|
+
|
|
17
|
+
export const nodes = new WeakMap();
|
|
18
|
+
|
|
19
|
+
export class NodeChildrenFacade {
|
|
20
|
+
constructor(node) {
|
|
21
|
+
nodes.set(this, node);
|
|
22
|
+
let tree = node.children;
|
|
23
|
+
|
|
24
|
+
if (!Object.isFrozen(tree)) throw new Error(); // just a sanity check right now
|
|
25
|
+
|
|
26
|
+
this.size = sumtree.getSize(tree);
|
|
27
|
+
|
|
28
|
+
freeze(this);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
at(idx) {
|
|
32
|
+
return sumtree.getAt(idx, nodes.get(this).children);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[Symbol.iterator]() {
|
|
36
|
+
return sumtree.traverse(nodes.get(this).children);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class ListFacade {
|
|
41
|
+
constructor(tree) {
|
|
42
|
+
nodes.set(this, tree);
|
|
43
|
+
|
|
44
|
+
this.size = btree.getSize(tree);
|
|
45
|
+
|
|
46
|
+
freeze(this);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
at(idx) {
|
|
50
|
+
return btree.getAt(idx, nodes.get(this));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[Symbol.iterator]() {
|
|
54
|
+
return btree.traverse(nodes.get(this));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class NodePropertiesFacade {
|
|
59
|
+
constructor(node) {
|
|
60
|
+
nodes.set(this, node);
|
|
61
|
+
|
|
62
|
+
freeze(this);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
at(name, index = -1, shiftIndex = 0) {
|
|
66
|
+
let { properties } = nodes.get(this);
|
|
67
|
+
|
|
68
|
+
if (hasOwn(properties, name)) {
|
|
69
|
+
let prop = properties[name];
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(prop.node) && index === -1) {
|
|
72
|
+
return new ListFacade(prop.node);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (Array.isArray(prop.node)) {
|
|
76
|
+
prop = btree.getAt(index ?? -1, prop.node);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!prop) return index === -1 ? prop : null;
|
|
80
|
+
if (prop.reference.flags.expression) {
|
|
81
|
+
return NodeFacade.from(btree.getAt(shiftIndex + 1, prop.node)?.node);
|
|
82
|
+
} else {
|
|
83
|
+
return NodeFacade.from(prop.node);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
has(name, index = -1) {
|
|
90
|
+
let { properties } = nodes.get(this);
|
|
91
|
+
if (!hasOwn(properties, name)) return false;
|
|
92
|
+
|
|
93
|
+
if (index >= 0 && !getProperties(name, index, properties)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
referenceAt(name, index = -1) {
|
|
101
|
+
return getProperties(buildPathSegment(name, index), nodes.get(this).properties)?.reference;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
[Symbol.iterator]() {
|
|
105
|
+
return Object.entries(nodes.get(this))[Symbol.iterator]();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class NodeFacade {
|
|
110
|
+
static from(node) {
|
|
111
|
+
return node && new NodeFacade(node);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
constructor(node) {
|
|
115
|
+
let sigilTag = sumtree.getAt(0, node.children);
|
|
116
|
+
|
|
117
|
+
if (sigilTag.type === DoctypeTag) {
|
|
118
|
+
sigilTag = sumtree.getAt(1, node.children);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.sigilTag = sigilTag;
|
|
122
|
+
|
|
123
|
+
if ([NullTag, GapTag].includes(sigilTag.type)) {
|
|
124
|
+
this.flags = nodeFlags;
|
|
125
|
+
this.type = null;
|
|
126
|
+
} else if (sigilTag.type === OpenNodeTag) {
|
|
127
|
+
let { flags, type } = sigilTag.value;
|
|
128
|
+
|
|
129
|
+
this.flags = flags;
|
|
130
|
+
this.type = type;
|
|
131
|
+
} else {
|
|
132
|
+
throw new Error();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
nodes.set(this, node);
|
|
136
|
+
|
|
137
|
+
freeze(this);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get children() {
|
|
141
|
+
return new NodeChildrenFacade(nodes.get(this));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get properties() {
|
|
145
|
+
return new NodePropertiesFacade(nodes.get(this));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get attributes() {
|
|
149
|
+
return nodes.get(this).attributes;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get undefinedAttributes() {
|
|
153
|
+
return nodeStates.get(nodes.get(this)).undefinedAttributes;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get node() {
|
|
157
|
+
let node = nodes.get(this);
|
|
158
|
+
|
|
159
|
+
return getCloseTag(node) || [NullTag, GapTag].includes(sumtree.getAt(0, node.children).type)
|
|
160
|
+
? node
|
|
161
|
+
: undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get(path, shiftIndex = null) {
|
|
165
|
+
if (shiftIndex !== null) throw new Error('bad get');
|
|
166
|
+
return NodeFacade.from(get(path, nodes.get(this)));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getChildPropertyIndex(childrenIndex) {
|
|
170
|
+
return getChildPropertyIndex(nodes.get(this), childrenIndex);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getPropertyChildrenIndex(type, name) {
|
|
174
|
+
return getPropertyChildrenIndex(nodes.get(this), type, name);
|
|
175
|
+
}
|
|
176
|
+
}
|