@ohm-js/wasm 0.1.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/.mise.toml +2 -0
- package/AGENT.md +25 -0
- package/LICENSE +21 -0
- package/Makefile +23 -0
- package/README.md +34 -0
- package/TODO.md +28 -0
- package/package.json +32 -0
- package/runtime/ohmRuntime.ts +252 -0
- package/scripts/bundlewasm.ts +49 -0
- package/scripts/modparse.ts +397 -0
- package/src/cli.js +36 -0
- package/src/index.js +1195 -0
- package/test/data/_book-review.liquid +257 -0
- package/test/data/_es5.js +1057 -0
- package/test/data/_es5.wasm +0 -0
- package/test/data/_html5shiv-3.7.3.js +326 -0
- package/test/data/_liquid-html.ohm +605 -0
- package/test/go/README.md +67 -0
- package/test/go/cst.go +164 -0
- package/test/go/go.mod +5 -0
- package/test/go/go.sum +2 -0
- package/test/go/matcher.go +370 -0
- package/test/go/testmain.go +161 -0
- package/test/test-es5.js +104 -0
- package/test/test-liquid-html.js +27 -0
- package/test/test-wasm.js +764 -0
package/.mise.toml
ADDED
package/AGENT.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Guide to @ohm-js/wasm
|
|
2
|
+
|
|
3
|
+
This is the code for the @ohm-js/wasm package. Its purpose is to convert Ohm grammars to WebAssembly, and also for using the result Wasm modules from JavaScript.
|
|
4
|
+
|
|
5
|
+
There is also code for using the Wasm modules from Go.
|
|
6
|
+
|
|
7
|
+
## Repository structure
|
|
8
|
+
|
|
9
|
+
- `src`: Source of the @ohm-js/wasm NPM package.
|
|
10
|
+
- `test`: Tests
|
|
11
|
+
- `runtime`: AssemblyScript code, which is used to write the runtime support library for the JavaScript code.
|
|
12
|
+
- `test/go`: Go code and tests for using the generate grammars from Go.
|
|
13
|
+
|
|
14
|
+
## Build and test commands
|
|
15
|
+
|
|
16
|
+
- `pnpm test` to run the JavaScript tests.
|
|
17
|
+
- `make go-test-es5` to run the Go tests.
|
|
18
|
+
- `make` to rebuild the runtime support library after changing the AssemblyScript code.
|
|
19
|
+
|
|
20
|
+
## Code style guidelines
|
|
21
|
+
|
|
22
|
+
- Strive to write the smallest amount of code that will solve the problem as posed. Do not add things "just in case".
|
|
23
|
+
- Do not include JSDoc in the code that you write.
|
|
24
|
+
- Use comments sparingly. Only leave comments where the meaning is not clear to an informed reader of the code.
|
|
25
|
+
- When the Go and JS code handle similar things, the Go code should follow the patterns of the JavaScript code, but it should also look like idiomatic Go. It's better for the Go code to have a slightly different interface than to be very un-idiomatic.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014-2022 Alessandro Warth and the Ohm project contributors.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Makefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.PHONY: all
|
|
2
|
+
all: out/ohmRuntime.wasm_sections.ts
|
|
3
|
+
|
|
4
|
+
out/ohmRuntime.wasm_sections.ts: scripts/bundlewasm.ts build/ohmRuntime.wasm
|
|
5
|
+
node scripts/bundlewasm.ts build/ohmRuntime.wasm
|
|
6
|
+
|
|
7
|
+
build/ohmRuntime.wasm: runtime/ohmRuntime.ts
|
|
8
|
+
npx asc --runtime stub -o build/ohmRuntime.wasm --memoryBase 67239936 runtime/ohmRuntime.ts
|
|
9
|
+
|
|
10
|
+
.PHONY: print-runtime
|
|
11
|
+
print-runtime:
|
|
12
|
+
npx asc --runtime stub --memoryBase 67239936 runtime/ohmRuntime.ts
|
|
13
|
+
|
|
14
|
+
.PHONY: go-test-es5
|
|
15
|
+
go-test-es5: test/go/testmain
|
|
16
|
+
cd test/go && ./testmain -wasm ../data/_es5.wasm -file ../data/_html5shiv-3.7.3.js
|
|
17
|
+
|
|
18
|
+
.PHONY: go-test-es5-lite
|
|
19
|
+
go-test-es5-lite: test/go/testmain
|
|
20
|
+
cd test/go && ./testmain -wasm ../data/_es5.wasm -input "var x = 3; function foo() {}"
|
|
21
|
+
|
|
22
|
+
test/go/testmain: test/go/testmain.go test/go/matcher.go
|
|
23
|
+
cd test/go && go mod tidy && go build -o testmain
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @ohm-js/wasm
|
|
2
|
+
|
|
3
|
+
Compile Ohm.js grammars to WebAsssembly, so they can be used from other languages.
|
|
4
|
+
|
|
5
|
+
To use the grammar, use the appropriate _miniohm_ package for your language.
|
|
6
|
+
|
|
7
|
+
**NOTE:** This package is experimental; the API is not yet stable.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
This package requires Node 24.
|
|
12
|
+
|
|
13
|
+
## Usage (compiling to Wasm)
|
|
14
|
+
|
|
15
|
+
### Command line
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npx ohm2wasm my-grammar.ohm
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This will write a Wasm grammar blob to ./my-grammar.wasm.
|
|
22
|
+
|
|
23
|
+
### API
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
import * as ohm from 'ohm-js';
|
|
27
|
+
import {Compiler} from '@ohm-js/wasm';
|
|
28
|
+
|
|
29
|
+
// Instantiate your own grammar…
|
|
30
|
+
const g = ohm.grammar('MyGrammar { start = "blah" }');
|
|
31
|
+
|
|
32
|
+
// compile() returns the Wasm grammar blob as a Uint8Array.
|
|
33
|
+
const bytes = new Compiler(g).compile();
|
|
34
|
+
```
|
package/TODO.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## TODOs
|
|
2
|
+
|
|
3
|
+
- [x] Include a map of rule name to ruleId in the module.
|
|
4
|
+
- [ ] Implicit space skipping
|
|
5
|
+
- [ ] Error handling
|
|
6
|
+
- [x] NonterminalNodes should keep track of the rule
|
|
7
|
+
- [ ] When iteration contains a sequence, the children are flattened into the iter node.
|
|
8
|
+
- [x] Basic parameterized rules
|
|
9
|
+
- [ ] Parameterized rules with >3 params
|
|
10
|
+
- [ ] Parameters that aren't terminals
|
|
11
|
+
- [ ] Memoization for parameterized rules
|
|
12
|
+
- [x] Support direct left recursion.
|
|
13
|
+
- [ ] Handle left recursion detection at grammar parse time.
|
|
14
|
+
- [ ] Separate API for _creating_ the Wasm module from the WasmMatcher interface.
|
|
15
|
+
- [ ] Implement a proper CLI.
|
|
16
|
+
|
|
17
|
+
## Limitations
|
|
18
|
+
|
|
19
|
+
- The input is assumed to be no bigger than 64k.
|
|
20
|
+
- For the memo table, we assume that there are no more than 256 rules in the grammar.
|
|
21
|
+
- Parameterized rules only support up to 3 parameters, and no memoization.
|
|
22
|
+
- Parameters must be terminals.
|
|
23
|
+
|
|
24
|
+
## Unanswered questions
|
|
25
|
+
|
|
26
|
+
- How to deal with matchLength in lookahead. In regular Ohm, lookahead _does_ bind things. But that is hard to square with the current CST representation, that stores only the matchLength. Because somehow the things inside a lookahead must consume nothing — but if you have `&("a" "b")`, the only way to make them consume nothing (in the current representation) is to rewrite the matchLength of the two terminal nodes.
|
|
27
|
+
- Could we introduce a pseudo-node for lookahead? It could get transparently unpacked when walking the tree.
|
|
28
|
+
- Memoization of parameterized rules: Alex suggested assigning memoization keys statically to unique applications
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ohm-js/wasm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Compile Ohm.js grammars to WebAsssembly",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ohm2wasm": "src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "Patrick Dubroy <pdubroy@gmail.com>",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@thi.ng/leb128": "^3.1.48",
|
|
15
|
+
"assemblyscript": "^0.27.36",
|
|
16
|
+
"ava": "^6.2.0",
|
|
17
|
+
"liquid-html-parser": "link:@shopify/liquid-html-parser",
|
|
18
|
+
"wabt": "1.0.37-nightly.20250428"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"ohm-js": "^17.1.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@shopify/liquid-html-parser": "^2.8.2",
|
|
25
|
+
"@wasmgroundup/emit": "^1.0.2"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build-and-test": "make && ava test",
|
|
29
|
+
"check-node-version": "node -e \"process.exit(parseInt(process.versions.node.split('.')[0]) < 24 ? 1 : 0)\"",
|
|
30
|
+
"test": "pnpm run check-node-version && pnpm run build-and-test || echo 'Skipping tests: Node 24 required'"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
type Result = i32;
|
|
2
|
+
|
|
3
|
+
declare function fillInputBuffer(offset: i32, maxLen: i32): i32;
|
|
4
|
+
declare function printI32(val: i32): void;
|
|
5
|
+
|
|
6
|
+
// TODO: Find a way to share these.
|
|
7
|
+
@inline const WASM_PAGE_SIZE: usize = 64 * 1024;
|
|
8
|
+
@inline const MEMO_START_OFFSET: usize = 2 * WASM_PAGE_SIZE;
|
|
9
|
+
@inline const MEMO_COL_SIZE_BYTES: usize = 4 * 256;
|
|
10
|
+
@inline const STACK_START_OFFSET: usize = WASM_PAGE_SIZE;
|
|
11
|
+
@inline const MAX_INPUT_LEN_BYTES: usize = 64 * 1024;
|
|
12
|
+
|
|
13
|
+
// Note: the rule evaluation functions use a different representation.
|
|
14
|
+
// They return non-zero for success and zero for failure.
|
|
15
|
+
@inline const EMPTY: Result = 0;
|
|
16
|
+
@inline const FAIL: Result = 0xfffffff0;
|
|
17
|
+
@inline const UNUSED_LR_BOMB: Result = FAIL | 0x1;
|
|
18
|
+
@inline const USED_LR_BOMB: Result = FAIL | 0x3;
|
|
19
|
+
|
|
20
|
+
@inline const CST_NODE_OVERHEAD: usize = 12;
|
|
21
|
+
@inline const NODE_TYPE_ITERATION: i32 = -2;
|
|
22
|
+
|
|
23
|
+
// Shared globals
|
|
24
|
+
let pos: i32 = 0;
|
|
25
|
+
let sp: usize = 0;
|
|
26
|
+
let bindings: Array<i32> = new Array<i32>();
|
|
27
|
+
|
|
28
|
+
@inline function memoTableGet(memoPos: usize, ruleId: i32): Result {
|
|
29
|
+
return load<Result>(memoPos * MEMO_COL_SIZE_BYTES + ruleId * sizeof<Result>(), MEMO_START_OFFSET);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@inline function memoTableSet(memoPos: usize, ruleId: i32, value: Result): void {
|
|
33
|
+
store<Result>(memoPos * MEMO_COL_SIZE_BYTES + ruleId * sizeof<Result>(), value, MEMO_START_OFFSET);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@inline function cstGetCount(ptr: usize): i32 {
|
|
37
|
+
return load<i32>(ptr, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@inline function cstSetCount(ptr: usize, count: i32): void {
|
|
41
|
+
store<i32>(ptr, count, 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@inline function cstGetMatchLength(ptr: usize): i32 {
|
|
45
|
+
return load<i32>(ptr, 4);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@inline function cstSetMatchLength(ptr: usize, len: i32): void {
|
|
49
|
+
store<i32>(ptr, len, 4);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@inline function cstGetType(ptr: usize): i32 {
|
|
53
|
+
return load<i32>(ptr, 8);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@inline function cstSetType(ptr: usize, t: i32): void {
|
|
57
|
+
store<i32>(ptr, t, 8);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@inline function memoizeResult(memoPos: usize, ruleId: i32, result: Result): void {
|
|
61
|
+
memoTableSet(memoPos, ruleId, result);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@inline function isFailure(result: Result): bool {
|
|
65
|
+
return result < 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function useMemoizedResult(ruleId: i32, result: Result): Result {
|
|
69
|
+
if (result === UNUSED_LR_BOMB) {
|
|
70
|
+
memoTableSet(pos, ruleId, USED_LR_BOMB);
|
|
71
|
+
return 0;
|
|
72
|
+
} else if (isFailure(result)) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
pos += cstGetMatchLength(result);
|
|
76
|
+
bindings.push(result);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasMemoizedResult(ruleId: i32): boolean {
|
|
81
|
+
return memoTableGet(pos, ruleId) !== 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function match(startRuleId: i32): Result {
|
|
85
|
+
// (Re-)initialize globals, clear memo table.
|
|
86
|
+
pos = 0;
|
|
87
|
+
sp = STACK_START_OFFSET;
|
|
88
|
+
bindings = new Array<i32>();
|
|
89
|
+
memory.fill(MEMO_START_OFFSET, 0, MEMO_COL_SIZE_BYTES * MAX_INPUT_LEN_BYTES);
|
|
90
|
+
|
|
91
|
+
// Get the input and do the match.
|
|
92
|
+
let inputLen = fillInputBuffer(0, i32(WASM_PAGE_SIZE));
|
|
93
|
+
const succeeded = evalApply0(startRuleId) !== 0;
|
|
94
|
+
if (inputLen === pos) {
|
|
95
|
+
return succeeded;
|
|
96
|
+
}
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@inline function evalRuleBody(ruleId: i32): Result {
|
|
101
|
+
return call_indirect<Result>(ruleId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function evalApplyNoMemo0(ruleId: i32): Result {
|
|
105
|
+
const origPos = pos;
|
|
106
|
+
const origNumBindings = bindings.length;
|
|
107
|
+
if (evalRuleBody(ruleId)) {
|
|
108
|
+
return newNonterminalNode(origPos, pos, ruleId, origNumBindings);
|
|
109
|
+
}
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function evalApply0(ruleId: i32): Result {
|
|
114
|
+
let result = memoTableGet(pos, ruleId);
|
|
115
|
+
if (result !== 0) {
|
|
116
|
+
return useMemoizedResult(ruleId, result);
|
|
117
|
+
}
|
|
118
|
+
const origPos = pos;
|
|
119
|
+
let origNumBindings = bindings.length;
|
|
120
|
+
memoizeResult(origPos, ruleId, UNUSED_LR_BOMB);
|
|
121
|
+
let succeeded: i32 = evalRuleBody(ruleId);
|
|
122
|
+
|
|
123
|
+
// Straight failure — record a clean failure in the memo table.
|
|
124
|
+
if (!succeeded) {
|
|
125
|
+
memoizeResult(origPos, ruleId, FAIL);
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (memoTableGet(origPos, ruleId) === USED_LR_BOMB) {
|
|
130
|
+
return handleLeftRecursion(origPos, ruleId, origNumBindings);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// No left recursion — memoize and return.
|
|
134
|
+
result = newNonterminalNode(origPos, pos, ruleId, origNumBindings);
|
|
135
|
+
memoizeResult(origPos, ruleId, result);
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function handleLeftRecursion(origPos: usize, ruleId: i32, origNumBindings: i32): Result {
|
|
140
|
+
let maxPos: i32;
|
|
141
|
+
let result: Result;
|
|
142
|
+
let succeeded: i32;
|
|
143
|
+
do {
|
|
144
|
+
// The current result is the best one -- record it.
|
|
145
|
+
maxPos = pos;
|
|
146
|
+
result = newNonterminalNode(origPos, pos, ruleId, origNumBindings);
|
|
147
|
+
memoizeResult(origPos, ruleId, result);
|
|
148
|
+
|
|
149
|
+
// Reset and try to improve on the current best.
|
|
150
|
+
pos = origPos;
|
|
151
|
+
bindings.length = origNumBindings;
|
|
152
|
+
succeeded = evalRuleBody(ruleId);
|
|
153
|
+
} while (succeeded && pos > maxPos);
|
|
154
|
+
|
|
155
|
+
pos = maxPos;
|
|
156
|
+
bindings.length = origNumBindings + 1;
|
|
157
|
+
bindings[origNumBindings] = result;
|
|
158
|
+
return succeeded;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function evalApply1(ruleId: i32, arg0: i32): Result {
|
|
162
|
+
// if (hasMemoizedResult(ruleId)) {
|
|
163
|
+
// return useMemoizedResult(ruleId);
|
|
164
|
+
// }
|
|
165
|
+
const origPos = pos;
|
|
166
|
+
const origNumBindings = bindings.length;
|
|
167
|
+
let result: Result = FAIL;
|
|
168
|
+
const succeeded = call_indirect<Result>(ruleId, arg0);
|
|
169
|
+
if (succeeded) {
|
|
170
|
+
const numChildren = bindings.length - origNumBindings;
|
|
171
|
+
result = newNonterminalNode(origPos, pos, ruleId, origNumBindings);
|
|
172
|
+
}
|
|
173
|
+
// memoizeResult(origPos, ruleId, result);
|
|
174
|
+
return succeeded;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function evalApply2(ruleId: i32, arg0: i32, arg1: i32): Result {
|
|
178
|
+
// if (hasMemoizedResult(ruleId)) {
|
|
179
|
+
// return useMemoizedResult(ruleId);
|
|
180
|
+
// }
|
|
181
|
+
const origPos = pos;
|
|
182
|
+
const origNumBindings = bindings.length;
|
|
183
|
+
let result: Result = FAIL;
|
|
184
|
+
const succeeded = call_indirect<Result>(ruleId, arg0, arg1);
|
|
185
|
+
if (succeeded) {
|
|
186
|
+
const numChildren = bindings.length - origNumBindings;
|
|
187
|
+
result = newNonterminalNode(origPos, pos, ruleId, origNumBindings);
|
|
188
|
+
}
|
|
189
|
+
// memoizeResult(origPos, ruleId, result);
|
|
190
|
+
return succeeded;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function evalApply3(ruleId: i32, arg0: i32, arg1: i32, arg2: i32): Result {
|
|
194
|
+
// if (hasMemoizedResult(ruleId)) {
|
|
195
|
+
// return useMemoizedResult(ruleId);
|
|
196
|
+
// }
|
|
197
|
+
const origPos = pos;
|
|
198
|
+
const origNumBindings = bindings.length;
|
|
199
|
+
let result: Result = FAIL;
|
|
200
|
+
const succeeded = call_indirect<Result>(ruleId, arg0, arg1, arg2);
|
|
201
|
+
if (succeeded) {
|
|
202
|
+
const numChildren = bindings.length - origNumBindings;
|
|
203
|
+
result = newNonterminalNode(origPos, pos, ruleId, origNumBindings);
|
|
204
|
+
}
|
|
205
|
+
// memoizeResult(origPos, ruleId, result);
|
|
206
|
+
return succeeded;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function newTerminalNode(startIdx: i32, endIdx: i32): usize {
|
|
210
|
+
const ptr = heap.alloc(CST_NODE_OVERHEAD);
|
|
211
|
+
cstSetCount(ptr, 0);
|
|
212
|
+
cstSetMatchLength(ptr, endIdx - startIdx);
|
|
213
|
+
cstSetType(ptr, -1);
|
|
214
|
+
bindings.push(ptr);
|
|
215
|
+
return ptr;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Create an internal (non-leaf) node (IterationNode or NonterminalNode).
|
|
219
|
+
@inline function newNonLeafNodeWithType(startIdx: i32, endIdx: i32, type: i32, origNumBindings: i32): usize {
|
|
220
|
+
const bindingsLen = bindings.length;
|
|
221
|
+
const numChildren = bindingsLen - origNumBindings;
|
|
222
|
+
const ptr = heap.alloc(CST_NODE_OVERHEAD + numChildren * 4);
|
|
223
|
+
cstSetCount(ptr, numChildren);
|
|
224
|
+
cstSetMatchLength(ptr, endIdx - startIdx);
|
|
225
|
+
cstSetType(ptr, type);
|
|
226
|
+
for (let i = 0; i < numChildren; i++) {
|
|
227
|
+
store<i32>(ptr + CST_NODE_OVERHEAD + i * 4, bindings[bindingsLen - numChildren + i]);
|
|
228
|
+
}
|
|
229
|
+
bindings.length = origNumBindings;
|
|
230
|
+
bindings.push(ptr);
|
|
231
|
+
return ptr;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function newNonterminalNode(startIdx: i32, endIdx: i32, ruleId: i32, origNumBindings: i32): usize {
|
|
235
|
+
return newNonLeafNodeWithType(startIdx, endIdx, ruleId, origNumBindings);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function newIterationNode(startIdx: i32, endIdx: i32, origNumBindings: i32): usize {
|
|
239
|
+
return newNonLeafNodeWithType(startIdx, endIdx, NODE_TYPE_ITERATION, origNumBindings);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function getBindingsLength(): i32 {
|
|
243
|
+
return bindings.length;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function setBindingsLength(len: i32): void {
|
|
247
|
+
return bindings.length = len;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function getCstRoot(): usize {
|
|
251
|
+
return bindings[0];
|
|
252
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import {URL} from 'node:url';
|
|
3
|
+
import * as w from '@wasmgroundup/emit';
|
|
4
|
+
|
|
5
|
+
import {extractSections} from './modparse.ts';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
Extracts the code section from the AssemblyScript release build
|
|
9
|
+
and writes it to a .ts module in the same directory.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const inputPath = process.argv[2];
|
|
13
|
+
const outputPath = inputPath + '_sections.ts';
|
|
14
|
+
|
|
15
|
+
const destImportCount = 3;
|
|
16
|
+
|
|
17
|
+
const buf = fs.readFileSync(inputPath);
|
|
18
|
+
const sections = extractSections(buf, {
|
|
19
|
+
destImportCount
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
let output = `function decodeBase64(str: string) {
|
|
23
|
+
const bytes = atob(str)
|
|
24
|
+
const result: number[] = []
|
|
25
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
26
|
+
result[i] = bytes.charCodeAt(i)
|
|
27
|
+
}
|
|
28
|
+
return result
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const destImportCount = ${destImportCount};
|
|
32
|
+
export const startFuncidx = ${sections.startFuncidx};
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
output += `export const funcidxByName = ${JSON.stringify(sections.funcidxByName)};\n`;
|
|
36
|
+
|
|
37
|
+
for (const [secName, payload] of Object.entries(sections)) {
|
|
38
|
+
if (secName === 'funcidxByName' || secName === 'startFuncidx') {
|
|
39
|
+
continue; // Skip this section as it's already handled above
|
|
40
|
+
}
|
|
41
|
+
const {entryCount, contents} = payload;
|
|
42
|
+
const base64Contents = Buffer.from(contents).toString('base64');
|
|
43
|
+
output += `export const ${secName} = {
|
|
44
|
+
entryCount: ${JSON.stringify(entryCount)},
|
|
45
|
+
contents: decodeBase64(${JSON.stringify(base64Contents)})
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
fs.writeFileSync(outputPath, output, 'utf8');
|