@yuku-parser/wasm 0.5.30 → 0.5.32

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/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { decode } from "./decode.js";
2
2
 
3
+ export { WalkContext, _walk, walk } from "./walk.js";
4
+
3
5
  const wasmUrl = new URL("./yuku-parser.wasm", import.meta.url);
4
6
 
5
7
  async function instantiate() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuku-parser/wasm",
3
- "version": "0.5.30",
3
+ "version": "0.5.32",
4
4
  "description": "High-performance JavaScript/TypeScript parser, compiled to WebAssembly",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,8 +14,12 @@
14
14
  "index.js",
15
15
  "index.d.ts",
16
16
  "decode.js",
17
+ "walk.js",
17
18
  "yuku-parser.wasm"
18
19
  ],
20
+ "dependencies": {
21
+ "@yuku/types": "0.1.0"
22
+ },
19
23
  "keywords": [
20
24
  "acorn",
21
25
  "ast",
package/walk.js ADDED
@@ -0,0 +1,198 @@
1
+ import { CHILD_KEYS } from "./decode.js";
2
+
3
+ export class WalkContext {
4
+ _ancestors = [];
5
+ _node = null;
6
+ _key = null;
7
+ _list = null;
8
+ _frame = null;
9
+ _skip = false;
10
+ _stopped = false;
11
+ _removed = false;
12
+ _replacement = null;
13
+ state;
14
+
15
+ get node() {
16
+ return this._node;
17
+ }
18
+ get parent() {
19
+ const a = this._ancestors;
20
+ return a.length === 0 ? null : a[a.length - 1];
21
+ }
22
+ get key() {
23
+ return this._key;
24
+ }
25
+ get index() {
26
+ return this._list === null ? null : this._frame.i;
27
+ }
28
+ ancestors() {
29
+ return [...this._ancestors];
30
+ }
31
+ skip() {
32
+ this._skip = true;
33
+ }
34
+ stop() {
35
+ this._stopped = true;
36
+ }
37
+ replace(node) {
38
+ if (node === null || typeof node !== "object" || typeof node.type !== "string") {
39
+ throw new TypeError("replace: expected an AST node");
40
+ }
41
+ if (this.parent === null) {
42
+ throw new TypeError("replace: cannot replace the walk root");
43
+ }
44
+ // synthetic nodes inherit the original span, for source maps
45
+ if (node.start === 0 && node.end === 0) {
46
+ node.start = this._node.start;
47
+ node.end = this._node.end;
48
+ }
49
+ this._replacement = node;
50
+ }
51
+ remove() {
52
+ if (this.parent === null) {
53
+ throw new TypeError("remove: cannot remove the walk root");
54
+ }
55
+ this._removed = true;
56
+ }
57
+ insertBefore(node) {
58
+ // advance past the insert so the current node keeps its turn and the
59
+ // new sibling is not visited
60
+ this.#insert(node, 0);
61
+ this._frame.i++;
62
+ }
63
+ insertAfter(node) {
64
+ // lands at the next slot, so the walk visits it
65
+ this.#insert(node, 1);
66
+ }
67
+ #insert(node, offset) {
68
+ if (this._list === null) {
69
+ throw new TypeError("insertBefore/insertAfter require a node in an array field");
70
+ }
71
+ this._list.splice(this._frame.i + offset, 0, node);
72
+ }
73
+ }
74
+
75
+ function createDispatch(visitors) {
76
+ let enter = null;
77
+ let leave = null;
78
+ const concrete = new Map();
79
+ for (const [name, value] of Object.entries(visitors)) {
80
+ if (value == null) continue;
81
+ if (name === "enter") enter = value;
82
+ else if (name === "leave") leave = value;
83
+ else if (typeof value === "function") concrete.set(name, { enter: value, leave: null });
84
+ else concrete.set(name, { enter: value.enter ?? null, leave: value.leave ?? null });
85
+ }
86
+ return { enter, leave, typed: (type) => concrete.get(type) };
87
+ }
88
+
89
+ /**
90
+ * Walk an AST depth-first, dispatching to typed visitors and mutating
91
+ * in place. Returns the root.
92
+ */
93
+ export function walk(root, visitors, state) {
94
+ _walk(root, visitors, state, null, new WalkContext());
95
+ return root;
96
+ }
97
+
98
+ // internal entry point. yuku-analyzer layers scope replay via `hooks`
99
+ // (enter returns a token passed to exit, which runs after the node is
100
+ // handled, removal included) and a WalkContext subclass. exits are
101
+ // skipped once stopped.
102
+ export function _walk(root, visitors, state, hooks, ctx) {
103
+ const d = createDispatch(visitors);
104
+ ctx.state = state;
105
+ const ancestors = ctx._ancestors;
106
+
107
+ function position(node, key, list, frame) {
108
+ ctx._node = node;
109
+ ctx._key = key;
110
+ ctx._list = list;
111
+ ctx._frame = frame;
112
+ }
113
+
114
+ // swaps the current node in its parent slot and continues the walk on
115
+ // the replacement
116
+ function applyReplace(parent, key, list, frame) {
117
+ const next = ctx._replacement;
118
+ ctx._replacement = null;
119
+ if (list !== null) list[frame.i] = next;
120
+ else parent[key] = next;
121
+ return next;
122
+ }
123
+
124
+ function applyRemove(parent, key, list, frame) {
125
+ ctx._removed = false;
126
+ if (list !== null) {
127
+ list.splice(frame.i, 1);
128
+ frame.i--;
129
+ } else {
130
+ parent[key] = null;
131
+ }
132
+ }
133
+
134
+ (function visit(node, key, list, frame) {
135
+ let typed = d.typed(node.type);
136
+ const token = hooks === null ? undefined : hooks.enter(node);
137
+ const parent = ctx.parent;
138
+
139
+ position(node, key, list, frame);
140
+ if (d.enter !== null) {
141
+ d.enter(node, ctx);
142
+ if (ctx._stopped) return false;
143
+ }
144
+ if (typed !== undefined && typed.enter !== null) {
145
+ typed.enter(node, ctx);
146
+ if (ctx._stopped) return false;
147
+ }
148
+ if (ctx._removed) {
149
+ applyRemove(parent, key, list, frame);
150
+ if (hooks !== null) hooks.exit(token);
151
+ return true;
152
+ }
153
+ if (ctx._replacement !== null) {
154
+ node = applyReplace(parent, key, list, frame);
155
+ typed = d.typed(node.type);
156
+ }
157
+
158
+ const skipped = ctx._skip;
159
+ ctx._skip = false;
160
+ if (!skipped) {
161
+ const keys = CHILD_KEYS[node.type];
162
+ if (keys !== undefined && keys.length > 0) {
163
+ ancestors.push(node);
164
+ for (let k = 0; k < keys.length; k++) {
165
+ const key2 = keys[k];
166
+ const value = node[key2];
167
+ if (value === null || value === undefined || typeof value !== "object") continue;
168
+ if (Array.isArray(value)) {
169
+ const childFrame = { i: 0 };
170
+ for (; childFrame.i < value.length; childFrame.i++) {
171
+ const item = value[childFrame.i];
172
+ // pattern element holes are null
173
+ if (item !== null && !visit(item, key2, value, childFrame)) return false;
174
+ }
175
+ } else if (!visit(value, key2, null, null)) {
176
+ return false;
177
+ }
178
+ }
179
+ ancestors.pop();
180
+ }
181
+ }
182
+
183
+ position(node, key, list, frame);
184
+ if (typed !== undefined && typed.leave !== null) {
185
+ typed.leave(node, ctx);
186
+ if (ctx._stopped) return false;
187
+ }
188
+ if (d.leave !== null) {
189
+ d.leave(node, ctx);
190
+ if (ctx._stopped) return false;
191
+ }
192
+ if (ctx._removed) applyRemove(parent, key, list, frame);
193
+ else if (ctx._replacement !== null) applyReplace(parent, key, list, frame);
194
+
195
+ if (hooks !== null) hooks.exit(token);
196
+ return true;
197
+ })(root, null, null, null);
198
+ }
package/yuku-parser.wasm CHANGED
Binary file