watr 3.2.1 → 3.3.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/package.json +1 -1
- package/readme.md +9 -12
- package/src/compile.js +200 -94
- package/src/const.js +7 -3
- package/src/parse.js +10 -6
- package/src/util.js +46 -0
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -77,25 +77,22 @@ print(src, {
|
|
|
77
77
|
## Status
|
|
78
78
|
|
|
79
79
|
* [x] core
|
|
80
|
-
* [x] [mutable globals](https://github.com/WebAssembly/mutable-global), [extended const](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md), [
|
|
81
|
-
* [x] [multi-value](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md), [bulk memory ops](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md), [multiple memories](https://github.com/WebAssembly/multi-memory/blob/
|
|
80
|
+
* [x] [mutable globals](https://github.com/WebAssembly/mutable-global), [extended const](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md), [sign extension](https://github.com/WebAssembly/sign-extension-ops), [nontrapping float to int](https://github.com/WebAssembly/nontrapping-float-to-int-conversions)
|
|
81
|
+
* [x] [multi-value](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md), [bulk memory ops](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md), [multiple memories](https://github.com/WebAssembly/multi-memory/blob/main/proposals/multi-memory/Overview.md)
|
|
82
82
|
* [x] [simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md), [relaxed simd](https://github.com/WebAssembly/relaxed-simd), [fixed-width simd](https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md)
|
|
83
83
|
* [x] [tail_call](https://github.com/WebAssembly/tail-call)
|
|
84
|
-
* [x] [ref types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), [func refs](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md)
|
|
85
|
-
* [x] [
|
|
86
|
-
* [ ] [exceptions](https://github.com/WebAssembly/exception-handling)
|
|
87
|
-
* [ ] [memory64](https://github.com/WebAssembly/memory64)
|
|
88
|
-
* [ ] [annotations](https://github.com/WebAssembly/annotations), [code_metadata](https://github.com/WebAssembly/tool-conventions/blob/main/CodeMetadata.md)
|
|
89
|
-
* [ ] [js strings](https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md)
|
|
84
|
+
* [x] [ref types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), [func refs](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md), [gc](https://github.com/WebAssembly/gc)
|
|
85
|
+
* [x] [annotations](https://github.com/WebAssembly/annotations), [code_metadata](https://github.com/WebAssembly/tool-conventions/blob/main/CodeMetadata.md)
|
|
86
|
+
* [ ] [exceptions](https://github.com/WebAssembly/exception-handling), [memory64](https://github.com/WebAssembly/memory64), [js strings](https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md), wide arithmetic, threads, custom page size, wasm 3
|
|
90
87
|
|
|
91
88
|
## Alternatives
|
|
92
89
|
|
|
93
90
|
| Size (gzipped) | Performance
|
|
94
91
|
---|---|---
|
|
95
|
-
watr |
|
|
96
|
-
[spec/wast.js](https://github.com/WebAssembly/spec/tree/main/interpreter#javascript-library) | 216 kb |
|
|
97
|
-
[wabt](https://github.com/WebAssembly/wabt) | 282 kb | 2
|
|
98
|
-
[wat-compiler](https://github.com/stagas/wat-compiler) | 7.7 kb |
|
|
92
|
+
watr | 7.5 kb | 6.0 op/s
|
|
93
|
+
[spec/wast.js](https://github.com/WebAssembly/spec/tree/main/interpreter#javascript-library) | 216 kb | 2.2 op/s
|
|
94
|
+
[wabt](https://github.com/WebAssembly/wabt) | 282 kb | 1.2 op/s
|
|
95
|
+
[wat-compiler](https://github.com/stagas/wat-compiler) | 7.7 kb | 0.7 op/s
|
|
99
96
|
|
|
100
97
|
<!--
|
|
101
98
|
## Projects using watr
|
package/src/compile.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as encode from './encode.js'
|
|
2
2
|
import { uleb, i32, i64 } from './encode.js'
|
|
3
|
-
import { SECTION, TYPE, KIND, INSTR, HEAPTYPE, DEFTYPE, RECTYPE, REFTYPE } from './const.js'
|
|
3
|
+
import { SECTION, TYPE, KIND, INSTR, HEAPTYPE, DEFTYPE, RECTYPE, REFTYPE, ESCAPE } from './const.js'
|
|
4
4
|
import parse from './parse.js'
|
|
5
|
-
import { clone, err } from './util.js'
|
|
5
|
+
import { clone, err, str } from './util.js'
|
|
6
6
|
|
|
7
7
|
// build instructions index
|
|
8
8
|
INSTR.forEach((op, i) => INSTR[op] = i >= 0x133 ? [0xfd, i - 0x133] : i >= 0x11b ? [0xfc, i - 0x11b] : i >= 0xfb ? [0xfb, i - 0xfb] : [i]);
|
|
9
9
|
|
|
10
|
+
// recursively strip all annotation nodes from AST, except @custom and @metadata.code.*
|
|
11
|
+
const unannot = (node) => Array.isArray(node) ? (node[0]?.[0] === '@' && node[0] !== '@custom' && !node[0]?.startsWith?.('@metadata.code.') ? null : node.map(unannot).filter(n => n != null)) : node
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
|
|
@@ -20,6 +22,9 @@ export default function watr(nodes) {
|
|
|
20
22
|
if (typeof nodes === 'string') nodes = parse(nodes);
|
|
21
23
|
else nodes = clone(nodes)
|
|
22
24
|
|
|
25
|
+
// strip annotations (text-format only), except @custom which becomes binary custom sections
|
|
26
|
+
nodes = unannot(nodes) || []
|
|
27
|
+
|
|
23
28
|
// module abbr https://webassembly.github.io/spec/core/text/modules.html#id10
|
|
24
29
|
if (nodes[0] === 'module') nodes.shift(), nodes[0]?.[0] === '$' && nodes.shift()
|
|
25
30
|
// single node, not module
|
|
@@ -28,12 +33,12 @@ export default function watr(nodes) {
|
|
|
28
33
|
// binary abbr "\00" "\0x61" ...
|
|
29
34
|
if (nodes[0] === 'binary') {
|
|
30
35
|
nodes.shift()
|
|
31
|
-
return Uint8Array.from(str(nodes
|
|
36
|
+
return Uint8Array.from(str(...nodes))
|
|
32
37
|
}
|
|
33
38
|
// quote "a" "b"
|
|
34
39
|
else if (nodes[0] === 'quote') {
|
|
35
40
|
nodes.shift()
|
|
36
|
-
return watr(nodes.map(
|
|
41
|
+
return watr(nodes.map(s => s.slice(1, -1)).join(''))
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
// scopes are aliased by key as well, eg. section.func.$name = section[SECTION.func] = idx
|
|
@@ -48,7 +53,7 @@ export default function watr(nodes) {
|
|
|
48
53
|
// convert rec type into regular type (first subtype) with stashed subtypes length
|
|
49
54
|
// add rest of subtypes as regular type nodes with subtype flag
|
|
50
55
|
for (let i = 0; i < node.length; i++) {
|
|
51
|
-
let [
|
|
56
|
+
let [, ...subnode] = node[i]
|
|
52
57
|
alias(subnode, ctx.type);
|
|
53
58
|
(subnode = typedef(subnode, ctx)).push(i ? true : [ctx.type.length, node.length])
|
|
54
59
|
ctx.type.push(subnode)
|
|
@@ -62,64 +67,75 @@ export default function watr(nodes) {
|
|
|
62
67
|
alias(node, ctx.type);
|
|
63
68
|
ctx.type.push(typedef(node, ctx));
|
|
64
69
|
}
|
|
70
|
+
// (@custom "name" placement? data)
|
|
71
|
+
else if (kind === '@custom') {
|
|
72
|
+
ctx.custom.push(node) // node is just the arguments, not including @custom
|
|
73
|
+
}
|
|
65
74
|
// other sections may have id
|
|
66
75
|
else if (kind === 'start' || kind === 'export') ctx[kind].push(node)
|
|
67
76
|
|
|
68
77
|
else return true
|
|
69
78
|
})
|
|
70
79
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
// prepare/normalize nodes
|
|
81
|
+
.forEach(([kind, ...node]) => {
|
|
82
|
+
let imported // if node needs to be imported
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
// import abbr
|
|
85
|
+
// (import m n (table|memory|global|func id? type)) -> (table|memory|global|func id? (import m n) type)
|
|
86
|
+
if (kind === 'import') [kind, ...node] = (imported = node).pop()
|
|
78
87
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
// index, alias
|
|
89
|
+
let items = ctx[kind];
|
|
90
|
+
let name = alias(node, items);
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
// export abbr
|
|
93
|
+
// (table|memory|global|func id? (export n)* ...) -> (table|memory|global|func id ...) (export n (table|memory|global|func id))
|
|
94
|
+
while (node[0]?.[0] === 'export') ctx.export.push([node.shift()[1], [kind, items?.length]])
|
|
86
95
|
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
// for import nodes - redirect output to import
|
|
97
|
+
if (node[0]?.[0] === 'import') [, ...imported] = node.shift()
|
|
89
98
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
// table abbr
|
|
100
|
+
if (kind === 'table') {
|
|
101
|
+
// (table id? reftype (elem ...{n})) -> (table id? n n reftype) (elem (table id) (i32.const 0) reftype ...)
|
|
102
|
+
if (node[1]?.[0] === 'elem') {
|
|
103
|
+
let [reftype, [, ...els]] = node
|
|
104
|
+
node = [els.length, els.length, reftype]
|
|
105
|
+
ctx.elem.push([['table', name || items.length], ['i32.const', '0'], reftype, ...els])
|
|
106
|
+
}
|
|
97
107
|
}
|
|
98
|
-
}
|
|
99
108
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
// data abbr
|
|
110
|
+
// (memory id? (data str)) -> (memory id? n n) (data (memory id) (i32.const 0) str)
|
|
111
|
+
else if (kind === 'memory' && node[0]?.[0] === 'data') {
|
|
112
|
+
let [, ...data] = node.shift(), m = '' + Math.ceil(data.map(s => s.slice(1, -1)).join('').length / 65536) // FIXME: figure out actual data size
|
|
113
|
+
ctx.data.push([['memory', items.length], ['i32.const', 0], ...data])
|
|
114
|
+
node = [m, m]
|
|
115
|
+
}
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
// dupe to code section, save implicit type
|
|
118
|
+
else if (kind === 'func') {
|
|
119
|
+
let [idx, param, result] = typeuse(node, ctx);
|
|
120
|
+
idx ??= regtype(param, result, ctx)
|
|
112
121
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
// we save idx because type can be defined after
|
|
123
|
+
!imported && ctx.code.push([[idx, param, result], ...plain(node, ctx)]) // pass param since they may have names
|
|
124
|
+
node.unshift(['type', idx])
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// tag has a type similar to func
|
|
128
|
+
else if (kind === 'tag') {
|
|
129
|
+
let [idx, param] = typeuse(node, ctx);
|
|
130
|
+
idx ??= regtype(param, [], ctx)
|
|
131
|
+
node.unshift(['type', idx])
|
|
132
|
+
}
|
|
117
133
|
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
// import writes to import section amd adds placeholder for (kind) section
|
|
135
|
+
if (imported) ctx.import.push([...imported, [kind, ...node]]), node = null
|
|
120
136
|
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
items?.push(node)
|
|
138
|
+
})
|
|
123
139
|
|
|
124
140
|
// convert nodes to bytes
|
|
125
141
|
const bin = (kind, count = true) => {
|
|
@@ -128,11 +144,14 @@ export default function watr(nodes) {
|
|
|
128
144
|
.map(item => build[kind](item, ctx))
|
|
129
145
|
.filter(Boolean) // filter out unrenderable things (subtype or data.length)
|
|
130
146
|
|
|
147
|
+
// Custom sections - each is output as separate section with own header
|
|
148
|
+
if (kind === SECTION.custom) return items.flatMap(content => [kind, ...vec(content)])
|
|
149
|
+
|
|
131
150
|
return !items.length ? [] : [kind, ...vec(count ? vec(items) : items)]
|
|
132
151
|
}
|
|
133
152
|
|
|
134
153
|
// build final binary
|
|
135
|
-
|
|
154
|
+
const out = [
|
|
136
155
|
0x00, 0x61, 0x73, 0x6d, // magic
|
|
137
156
|
0x01, 0x00, 0x00, 0x00, // version
|
|
138
157
|
...bin(SECTION.custom),
|
|
@@ -141,20 +160,35 @@ export default function watr(nodes) {
|
|
|
141
160
|
...bin(SECTION.func),
|
|
142
161
|
...bin(SECTION.table),
|
|
143
162
|
...bin(SECTION.memory),
|
|
163
|
+
...bin(SECTION.tag),
|
|
144
164
|
...bin(SECTION.global),
|
|
145
165
|
...bin(SECTION.export),
|
|
146
166
|
...bin(SECTION.start, false),
|
|
147
167
|
...bin(SECTION.elem),
|
|
148
168
|
...bin(SECTION.datacount, false),
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
// Build code section first (populates ctx.meta)
|
|
172
|
+
const codeSection = bin(SECTION.code)
|
|
173
|
+
|
|
174
|
+
// Build code metadata custom sections: metadata.code.<type>
|
|
175
|
+
for (const type in ctx.meta) {
|
|
176
|
+
const name = vec(str(`"metadata.code.${type}"`))
|
|
177
|
+
const content = vec(ctx.meta[type].map(([funcIdx, instances]) =>
|
|
178
|
+
[...uleb(funcIdx), ...vec(instances.map(([pos, data]) => [...uleb(pos), ...vec(str(data))]))]
|
|
179
|
+
))
|
|
180
|
+
out.push(0, ...vec([...name, ...content]))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
out.push(...codeSection, ...bin(SECTION.data))
|
|
184
|
+
|
|
185
|
+
return Uint8Array.from(out)
|
|
152
186
|
}
|
|
153
187
|
|
|
154
188
|
// consume name eg. $t ...
|
|
155
189
|
const alias = (node, list) => {
|
|
156
190
|
let name = (node[0]?.[0] === '$' || node[0]?.[0] == null) && node.shift();
|
|
157
|
-
if (name) name in list ? err(`Duplicate ${list.name} ${name}`) : list[name] = list.length; // save alias
|
|
191
|
+
if (name && list) name in list ? err(`Duplicate ${list.name} ${name}`) : list[name] = list.length; // save alias
|
|
158
192
|
return name
|
|
159
193
|
}
|
|
160
194
|
|
|
@@ -179,7 +213,7 @@ const typedef = ([dfn], ctx) => {
|
|
|
179
213
|
}
|
|
180
214
|
|
|
181
215
|
// register (implicit) type
|
|
182
|
-
const regtype = (param, result, ctx, idx='$' + param + '>' + result) => (
|
|
216
|
+
const regtype = (param, result, ctx, idx = '$' + param + '>' + result) => (
|
|
183
217
|
(ctx.type[idx] ??= ctx.type.push(['func', [param, result]]) - 1),
|
|
184
218
|
idx
|
|
185
219
|
)
|
|
@@ -194,7 +228,7 @@ const typeuse = (nodes, ctx, names) => {
|
|
|
194
228
|
[, idx] = nodes.shift();
|
|
195
229
|
[param, result] = paramres(nodes, names);
|
|
196
230
|
|
|
197
|
-
const [,srcParamRes] = ctx.type[id(idx, ctx.type)] ?? err(`Unknown type ${idx}`)
|
|
231
|
+
const [, srcParamRes] = ctx.type[id(idx, ctx.type)] ?? err(`Unknown type ${idx}`)
|
|
198
232
|
|
|
199
233
|
// check type consistency (excludes forward refs)
|
|
200
234
|
if ((param.length || result.length) && srcParamRes.join('>') !== param + '>' + result) err(`Type ${idx} mismatch`)
|
|
@@ -259,10 +293,20 @@ const blocktype = (nodes, ctx) => {
|
|
|
259
293
|
// https://webassembly.github.io/spec/core/text/instructions.html#folded-instructions
|
|
260
294
|
const plain = (nodes, ctx) => {
|
|
261
295
|
let out = [], stack = [], label
|
|
296
|
+
// helper: check if node is immediate (not array operand)
|
|
297
|
+
const isImm = n => typeof n === 'string' || typeof n === 'number'
|
|
262
298
|
|
|
263
299
|
while (nodes.length) {
|
|
264
300
|
let node = nodes.shift()
|
|
265
301
|
|
|
302
|
+
// code metadata annotations - pass through as marker with metadata type and data
|
|
303
|
+
// (@metadata.code.<type> data:str)
|
|
304
|
+
if (Array.isArray(node) && node[0]?.startsWith?.('@metadata.code.')) {
|
|
305
|
+
let type = node[0].slice(15) // remove '@metadata.code.' prefix
|
|
306
|
+
out.push(['@metadata', type, node[1]])
|
|
307
|
+
continue
|
|
308
|
+
}
|
|
309
|
+
|
|
266
310
|
// lookup is slower than sequence of known ifs
|
|
267
311
|
if (typeof node === 'string') {
|
|
268
312
|
out.push(node)
|
|
@@ -278,7 +322,7 @@ const plain = (nodes, ctx) => {
|
|
|
278
322
|
// else $label
|
|
279
323
|
// end $label - make sure it matches block label
|
|
280
324
|
else if (node === 'else' || node === 'end') {
|
|
281
|
-
if (nodes[0]?.[0] === '$') (node === 'end' ? stack.pop() : label) !== (label = nodes.shift()) && err(`Mismatched label ${label}`)
|
|
325
|
+
if (nodes[0]?.[0] === '$') (node === 'end' ? stack.pop() : label) !== (label = nodes.shift()) && err(`Mismatched ${node} label ${label}`)
|
|
282
326
|
}
|
|
283
327
|
|
|
284
328
|
// select (result i32 i32 i32)?
|
|
@@ -297,6 +341,18 @@ const plain = (nodes, ctx) => {
|
|
|
297
341
|
// mark datacount section as required
|
|
298
342
|
else if (node === 'memory.init' || node === 'data.drop' || node === 'array.new_data' || node === 'array.init_data') {
|
|
299
343
|
ctx.datacount[0] = true
|
|
344
|
+
// memory.init memidx? dataidx
|
|
345
|
+
if (node === 'memory.init') out.push(isImm(nodes[1]) ? nodes.shift() : 0, isImm(nodes[0]) ? nodes.shift() : 0)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// memory.* memidx? - multi-memory proposal
|
|
349
|
+
else if (node === 'memory.size' || node === 'memory.grow' || node === 'memory.fill') {
|
|
350
|
+
out.push(isImm(nodes[0]) ? nodes.shift() : 0)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// memory.copy dstmem? srcmem?
|
|
354
|
+
else if (node === 'memory.copy') {
|
|
355
|
+
out.push(isImm(nodes[0]) ? nodes.shift() : 0, isImm(nodes[0]) ? nodes.shift() : 0)
|
|
300
356
|
}
|
|
301
357
|
|
|
302
358
|
// table.init tableidx? elemidx -> table.init tableidx elemidx
|
|
@@ -319,6 +375,9 @@ const plain = (nodes, ctx) => {
|
|
|
319
375
|
|
|
320
376
|
// (if ...) -> if ... end
|
|
321
377
|
else if (node[0] === 'if') {
|
|
378
|
+
// Pop pending metadata (branch_hint) if present
|
|
379
|
+
let meta = out[out.length - 1]?.[0] === '@metadata' && out.pop()
|
|
380
|
+
|
|
322
381
|
let then = [], els = [], immed = [node.shift()]
|
|
323
382
|
// (if label? blocktype? cond*? (then instr*) (else instr*)?) -> cond*? if label? blocktype? instr* else instr*? end
|
|
324
383
|
// https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
|
|
@@ -338,7 +397,8 @@ const plain = (nodes, ctx) => {
|
|
|
338
397
|
|
|
339
398
|
if (typeof node[0] === 'string') err('Unfolded condition')
|
|
340
399
|
|
|
341
|
-
|
|
400
|
+
// conditions, metadata (if any), if, then, else, end
|
|
401
|
+
out.push(...plain(node, ctx), ...(meta ? [meta] : []), ...immed, ...then, ...els, 'end')
|
|
342
402
|
}
|
|
343
403
|
else out.push(plain(node, ctx))
|
|
344
404
|
}
|
|
@@ -349,7 +409,21 @@ const plain = (nodes, ctx) => {
|
|
|
349
409
|
|
|
350
410
|
|
|
351
411
|
// build section binary [by section codes] (non consuming)
|
|
352
|
-
const build = [
|
|
412
|
+
const build = [
|
|
413
|
+
// (@custom "name" placement? data)
|
|
414
|
+
// placement is optional: (before|after section) or (before first)|(after last)
|
|
415
|
+
// For now we ignore placement and just output the custom section
|
|
416
|
+
([name, ...rest], ctx) => {
|
|
417
|
+
// Check if second arg is placement directive
|
|
418
|
+
let data = rest
|
|
419
|
+
if (rest[0]?.[0] === 'before' || rest[0]?.[0] === 'after') {
|
|
420
|
+
// Skip placement for now - would need more complex section ordering
|
|
421
|
+
data = rest.slice(1)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Custom section format: name (vec string) + raw content bytes
|
|
425
|
+
return [...vec(str(name)), ...str(...data)]
|
|
426
|
+
},
|
|
353
427
|
// type kinds
|
|
354
428
|
// (func params result)
|
|
355
429
|
// (array i8)
|
|
@@ -392,6 +466,10 @@ const build = [,
|
|
|
392
466
|
let [[, typeidx]] = dfn
|
|
393
467
|
details = uleb(id(typeidx, ctx.type))
|
|
394
468
|
}
|
|
469
|
+
else if (kind === 'tag') {
|
|
470
|
+
let [[, typeidx]] = dfn
|
|
471
|
+
details = [0x00, ...uleb(id(typeidx, ctx.type))]
|
|
472
|
+
}
|
|
395
473
|
else if (kind === 'memory') {
|
|
396
474
|
details = limits(dfn)
|
|
397
475
|
}
|
|
@@ -403,7 +481,7 @@ const build = [,
|
|
|
403
481
|
}
|
|
404
482
|
else err(`Unknown kind ${kind}`)
|
|
405
483
|
|
|
406
|
-
return ([...vec(str(mod
|
|
484
|
+
return ([...vec(str(mod)), ...vec(str(field)), KIND[kind], ...details])
|
|
407
485
|
},
|
|
408
486
|
|
|
409
487
|
// (func $name? ...params result ...body)
|
|
@@ -422,7 +500,7 @@ const build = [,
|
|
|
422
500
|
([t, init], ctx) => [...fieldtype(t, ctx), ...expr(init, ctx)],
|
|
423
501
|
|
|
424
502
|
// (export "name" (func|table|mem $name|idx))
|
|
425
|
-
([nm, [kind, l]], ctx) => ([...vec(str(nm
|
|
503
|
+
([nm, [kind, l]], ctx) => ([...vec(str(nm)), KIND[kind], ...uleb(id(l, ctx[kind]))]),
|
|
426
504
|
|
|
427
505
|
// (start $main)
|
|
428
506
|
([l], ctx) => uleb(id(l, ctx.func)),
|
|
@@ -523,6 +601,10 @@ const build = [,
|
|
|
523
601
|
ctx.local.name = 'local'
|
|
524
602
|
ctx.block.name = 'block'
|
|
525
603
|
|
|
604
|
+
// Track current code index for code metadata
|
|
605
|
+
if (ctx._codeIdx === undefined) ctx._codeIdx = 0
|
|
606
|
+
let codeIdx = ctx._codeIdx++
|
|
607
|
+
|
|
526
608
|
// collect locals
|
|
527
609
|
while (body[0]?.[0] === 'local') {
|
|
528
610
|
let [, ...types] = body.shift()
|
|
@@ -534,10 +616,21 @@ const build = [,
|
|
|
534
616
|
ctx.local.push(...types)
|
|
535
617
|
}
|
|
536
618
|
|
|
619
|
+
ctx._meta = null
|
|
537
620
|
const bytes = []
|
|
538
621
|
while (body.length) bytes.push(...instr(body, ctx))
|
|
539
622
|
bytes.push(0x0b)
|
|
540
623
|
|
|
624
|
+
// Extract metadata placeholders (arrays), group by type
|
|
625
|
+
const metaByType = {}, cleanBytes = []
|
|
626
|
+
for (const b of bytes)
|
|
627
|
+
if (Array.isArray(b)) for (const [type, data] of b) (metaByType[type] ??= []).push([cleanBytes.length, data])
|
|
628
|
+
else cleanBytes.push(b)
|
|
629
|
+
|
|
630
|
+
// Store metadata for this function, grouped by type
|
|
631
|
+
const funcIdx = ctx.import.filter(imp => imp[2][0] === 'func').length + codeIdx
|
|
632
|
+
for (const type in metaByType) ((ctx.meta ??= {})[type] ??= []).push([funcIdx, metaByType[type]])
|
|
633
|
+
|
|
541
634
|
// squash locals into (n:u32 t:valtype)*, n is number and t is type
|
|
542
635
|
// we skip locals provided by params
|
|
543
636
|
let loctypes = ctx.local.slice(param.length).reduce((a, type) => (type == a[a.length - 1]?.[1] ? a[a.length - 1][0]++ : a.push([1, type]), a), [])
|
|
@@ -546,7 +639,7 @@ const build = [,
|
|
|
546
639
|
ctx.local = ctx.block = null
|
|
547
640
|
|
|
548
641
|
// https://webassembly.github.io/spec/core/binary/modules.html#code-section
|
|
549
|
-
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...
|
|
642
|
+
return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...cleanBytes])
|
|
550
643
|
},
|
|
551
644
|
|
|
552
645
|
// (data (i32.const 0) "\aa" "\bb"?)
|
|
@@ -562,10 +655,10 @@ const build = [,
|
|
|
562
655
|
}
|
|
563
656
|
|
|
564
657
|
// (offset (i32.const 0)) or (i32.const 0)
|
|
565
|
-
if (typeof inits[0] !== 'string') {
|
|
658
|
+
if (typeof inits[0] !== 'string' && inits[0]) {
|
|
566
659
|
offset = inits.shift()
|
|
567
|
-
if (offset[0] === 'offset') [, offset] = offset
|
|
568
|
-
offset ?? err('Bad offset', offset)
|
|
660
|
+
if (offset?.[0] === 'offset') [, offset] = offset
|
|
661
|
+
else offset ?? err('Bad offset', offset)
|
|
569
662
|
}
|
|
570
663
|
|
|
571
664
|
return ([
|
|
@@ -577,7 +670,7 @@ const build = [,
|
|
|
577
670
|
// passive: 1
|
|
578
671
|
[1]
|
|
579
672
|
),
|
|
580
|
-
...vec(str(inits
|
|
673
|
+
...vec(str(...inits))
|
|
581
674
|
])
|
|
582
675
|
},
|
|
583
676
|
|
|
@@ -585,6 +678,9 @@ const build = [,
|
|
|
585
678
|
(nodes, ctx) => uleb(ctx.data.length)
|
|
586
679
|
]
|
|
587
680
|
|
|
681
|
+
// (tag $id? (param i32)*) - tags for exception handling
|
|
682
|
+
build[SECTION.tag] = ([[, typeidx]], ctx) => [0x00, ...uleb(id(typeidx, ctx.type))]
|
|
683
|
+
|
|
588
684
|
// build reftype, either direct absheaptype or wrapped heaptype https://webassembly.github.io/gc/core/binary/types.html#reference-types
|
|
589
685
|
const reftype = (t, ctx) => (
|
|
590
686
|
t[0] === 'ref' ?
|
|
@@ -599,17 +695,26 @@ const reftype = (t, ctx) => (
|
|
|
599
695
|
const fieldtype = (t, ctx, mut = t[0] === 'mut' ? 1 : 0) => [...reftype(mut ? t[1] : t, ctx), mut];
|
|
600
696
|
|
|
601
697
|
|
|
602
|
-
|
|
603
698
|
// consume one instruction from nodes sequence
|
|
604
699
|
const instr = (nodes, ctx) => {
|
|
605
700
|
if (!nodes?.length) return []
|
|
606
701
|
|
|
607
702
|
let out = [], op = nodes.shift(), immed, code
|
|
703
|
+
const isImm = n => typeof n === 'string' || typeof n === 'number'
|
|
704
|
+
|
|
705
|
+
// Handle code metadata marker - store for next instruction
|
|
706
|
+
// ['@metadata', type, data]
|
|
707
|
+
if (op?.[0] === '@metadata') {
|
|
708
|
+
;(ctx._meta ??= []).push(op.slice(1))
|
|
709
|
+
return nodes.length ? instr(nodes, ctx) : []
|
|
710
|
+
}
|
|
608
711
|
|
|
609
712
|
// consume group
|
|
610
713
|
if (Array.isArray(op)) {
|
|
611
714
|
immed = instr(op, ctx)
|
|
612
715
|
while (op.length) out.push(...instr(op, ctx))
|
|
716
|
+
// Insert metadata placeholder before instruction
|
|
717
|
+
if (ctx._meta) out.push(ctx._meta), ctx._meta = null
|
|
613
718
|
out.push(...immed)
|
|
614
719
|
return out
|
|
615
720
|
}
|
|
@@ -632,7 +737,9 @@ const instr = (nodes, ctx) => {
|
|
|
632
737
|
// array.new_fixed $t n
|
|
633
738
|
else if (code === 8) immed.push(...uleb(nodes.shift()))
|
|
634
739
|
// array.new_data|init_data $t $d
|
|
635
|
-
else if (code === 9 || code === 18)
|
|
740
|
+
else if (code === 9 || code === 18) {
|
|
741
|
+
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.data)))
|
|
742
|
+
}
|
|
636
743
|
// array.new_elem|init_elem $t $e
|
|
637
744
|
else if (code === 10 || code === 19) immed.push(...uleb(id(nodes.shift(), ctx.elem)))
|
|
638
745
|
// array.copy $t $t
|
|
@@ -641,7 +748,7 @@ const instr = (nodes, ctx) => {
|
|
|
641
748
|
// ref.test|cast (ref null? $t|heaptype)
|
|
642
749
|
else if (code >= 20 && code <= 23) {
|
|
643
750
|
let ht = reftype(nodes.shift(), ctx)
|
|
644
|
-
if (ht[0] !== REFTYPE.ref) immed.push(code = immed.pop()+1) // ref.test|cast (ref null $t) is next op
|
|
751
|
+
if (ht[0] !== REFTYPE.ref) immed.push(code = immed.pop() + 1) // ref.test|cast (ref null $t) is next op
|
|
645
752
|
if (ht.length > 1) ht.shift() // pop ref
|
|
646
753
|
immed.push(...ht)
|
|
647
754
|
}
|
|
@@ -651,7 +758,7 @@ const instr = (nodes, ctx) => {
|
|
|
651
758
|
ht1 = reftype(nodes.shift(), ctx),
|
|
652
759
|
ht2 = reftype(nodes.shift(), ctx),
|
|
653
760
|
castflags = ((ht2[0] !== REFTYPE.ref) << 1) | (ht1[0] !== REFTYPE.ref)
|
|
654
|
-
|
|
761
|
+
immed.push(castflags, ...uleb(i), ht1.pop(), ht2.pop()) // we take only abstype or
|
|
655
762
|
}
|
|
656
763
|
}
|
|
657
764
|
|
|
@@ -661,14 +768,23 @@ const instr = (nodes, ctx) => {
|
|
|
661
768
|
else if (code == 0xfc) {
|
|
662
769
|
[, code] = immed
|
|
663
770
|
|
|
664
|
-
// memory.init
|
|
665
|
-
if (code === 0x08
|
|
771
|
+
// memory.init memidx dataidx (binary: dataidx memidx)
|
|
772
|
+
if (code === 0x08) {
|
|
773
|
+
let m = isImm(nodes[0]) ? nodes.shift() : 0, d = isImm(nodes[0]) ? nodes.shift() : 0
|
|
774
|
+
immed.push(...uleb(id(d, ctx.data)), ...uleb(id(m, ctx.memory)))
|
|
775
|
+
}
|
|
776
|
+
// data.drop idx
|
|
777
|
+
else if (code === 0x09) {
|
|
666
778
|
immed.push(...uleb(id(nodes.shift(), ctx.data)))
|
|
667
779
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
780
|
+
// memory.copy dstmem srcmem
|
|
781
|
+
else if (code === 0x0a) {
|
|
782
|
+
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)), ...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
783
|
+
}
|
|
784
|
+
// memory.fill memidx
|
|
785
|
+
else if (code === 0x0b) {
|
|
786
|
+
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
787
|
+
}
|
|
672
788
|
|
|
673
789
|
// elem.drop elemidx
|
|
674
790
|
if (code === 0x0d) {
|
|
@@ -848,10 +964,10 @@ const instr = (nodes, ctx) => {
|
|
|
848
964
|
immed.push(...encode[op.split('.')[0]](nodes.shift()))
|
|
849
965
|
}
|
|
850
966
|
|
|
851
|
-
// memory.grow|size
|
|
967
|
+
// memory.grow|size memidx
|
|
852
968
|
// https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions
|
|
853
969
|
else if (code == 0x3f || code == 0x40) {
|
|
854
|
-
immed.push(0)
|
|
970
|
+
immed.push(...uleb(id(isImm(nodes[0]) ? nodes.shift() : 0, ctx.memory)))
|
|
855
971
|
}
|
|
856
972
|
|
|
857
973
|
// table.get|set $id
|
|
@@ -859,6 +975,9 @@ const instr = (nodes, ctx) => {
|
|
|
859
975
|
immed.push(...uleb(id(nodes.shift(), ctx.table)))
|
|
860
976
|
}
|
|
861
977
|
|
|
978
|
+
// Insert metadata placeholder before instruction in flat form
|
|
979
|
+
if (ctx._meta) out.push(ctx._meta), ctx._meta = null
|
|
980
|
+
|
|
862
981
|
out.push(...immed)
|
|
863
982
|
|
|
864
983
|
return out
|
|
@@ -880,10 +999,12 @@ const blockid = (nm, block, i) => (
|
|
|
880
999
|
// consume align/offset params
|
|
881
1000
|
const memarg = (args) => {
|
|
882
1001
|
let align, offset, k, v
|
|
883
|
-
while (args[0]?.includes('='))
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1002
|
+
while (args[0]?.includes('=')) {
|
|
1003
|
+
[k, v] = args.shift().split('='), v = v.replaceAll('_', '')
|
|
1004
|
+
k === 'offset' ? offset = +v : k === 'align' ? align = +v : err(`Unknown param ${k}=${v}`)
|
|
1005
|
+
}
|
|
1006
|
+
if ((offset < 0 || offset > 0xffffffff)) err(`Bad offset ${offset}`)
|
|
1007
|
+
if ((align <= 0 || align > 0xffffffff)) err(`Bad align ${align}`)
|
|
887
1008
|
if (align) ((align = Math.log2(align)) % 1) && err(`Bad align ${align}`)
|
|
888
1009
|
return [align, offset]
|
|
889
1010
|
}
|
|
@@ -912,20 +1033,5 @@ const limits = (node) => (
|
|
|
912
1033
|
// we put extra condition for index ints for tests complacency
|
|
913
1034
|
const parseUint = (v, max = 0xFFFFFFFF) => (typeof v === 'string' && v[0] !== '+' ? (typeof max === 'bigint' ? i64 : i32).parse(v) : typeof v === 'number' ? v : err(`Bad int ${v}`)) > max ? err(`Value out of range ${v}`) : v
|
|
914
1035
|
|
|
915
|
-
|
|
916
|
-
// escape codes
|
|
917
|
-
const escape = { n: 10, r: 13, t: 9, v: 1, '"': 34, "'": 39, '\\': 92 }
|
|
918
|
-
|
|
919
|
-
// build string binary
|
|
920
|
-
const str = str => {
|
|
921
|
-
let res = [], i = 0, c, BSLASH = 92
|
|
922
|
-
// https://webassembly.github.io/spec/core/text/values.html#strings
|
|
923
|
-
for (; i < str.length;) {
|
|
924
|
-
c = str.charCodeAt(i++)
|
|
925
|
-
res.push(c === BSLASH ? escape[str[i++]] || parseInt(str.slice(i - 1, ++i), 16) : c)
|
|
926
|
-
}
|
|
927
|
-
return res
|
|
928
|
-
}
|
|
929
|
-
|
|
930
1036
|
// serialize binary array
|
|
931
1037
|
const vec = a => [...uleb(a.length), ...a.flat()]
|
package/src/const.js
CHANGED
|
@@ -49,17 +49,19 @@ export const INSTR = [
|
|
|
49
49
|
// relaxed SIMD instructions
|
|
50
50
|
'i8x16.relaxed_swizzle', 'i32x4.relaxed_trunc_f32x4_s', 'i32x4.relaxed_trunc_f32x4_u', 'i32x4.relaxed_trunc_f64x2_s_zero', 'i32x4.relaxed_trunc_f64x2_u_zero', 'f32x4.relaxed_madd', 'f32x4.relaxed_nmadd', 'f64x2.relaxed_madd', 'f64x2.relaxed_nmadd', 'i8x16.relaxed_laneselect', 'i16x8.relaxed_laneselect', 'i32x4.relaxed_laneselect', 'i64x2.relaxed_laneselect', 'f32x4.relaxed_min', 'f32x4.relaxed_max', 'f64x2.relaxed_min', 'f64x2.relaxed_max', 'i16x8.relaxed_q15mulr_s', 'i16x8.relaxed_dot_i8x16_i7x16_s', 'i32x4.relaxed_dot_i8x16_i7x16_add_s'
|
|
51
51
|
],
|
|
52
|
-
SECTION = { custom: 0, type: 1, import: 2, func: 3, table: 4, memory: 5, global: 6, export: 7, start: 8, elem: 9, datacount: 12, code: 10, data: 11 },
|
|
52
|
+
SECTION = { custom: 0, type: 1, import: 2, func: 3, table: 4, memory: 5, global: 6, tag: 13, export: 7, start: 8, elem: 9, datacount: 12, code: 10, data: 11 },
|
|
53
53
|
RECTYPE = { sub: 0x50, subfinal: 0x4F, rec: 0x4E },
|
|
54
54
|
DEFTYPE = { func: 0x60, struct: 0x5F, array: 0x5E, ...RECTYPE },
|
|
55
|
-
HEAPTYPE = { nofunc: 0x73, noextern: 0x72, none: 0x71, func: 0x70, extern: 0x6F, any: 0x6E, eq: 0x6D, i31: 0x6C, struct: 0x6B, array: 0x6A },
|
|
55
|
+
HEAPTYPE = { nofunc: 0x73, noextern: 0x72, noexn: 0x74, none: 0x71, func: 0x70, extern: 0x6F, exn: 0x75, any: 0x6E, eq: 0x6D, i31: 0x6C, struct: 0x6B, array: 0x6A },
|
|
56
56
|
REFTYPE = {
|
|
57
57
|
// absheaptype abbrs
|
|
58
58
|
nullfuncref: HEAPTYPE.nofunc,
|
|
59
59
|
nullexternref: HEAPTYPE.noextern,
|
|
60
|
+
nullexnref: HEAPTYPE.noexn,
|
|
60
61
|
nullref: HEAPTYPE.none,
|
|
61
62
|
funcref: HEAPTYPE.func,
|
|
62
63
|
externref: HEAPTYPE.extern,
|
|
64
|
+
exnref: HEAPTYPE.exn,
|
|
63
65
|
anyref: HEAPTYPE.any,
|
|
64
66
|
eqref: HEAPTYPE.eq,
|
|
65
67
|
i31ref: HEAPTYPE.i31,
|
|
@@ -70,4 +72,6 @@ export const INSTR = [
|
|
|
70
72
|
ref: 0x64 /* -0x1c */, refnull: 0x63 /* -0x1d */
|
|
71
73
|
},
|
|
72
74
|
TYPE = { i8: 0x78, i16: 0x77, i32: 0x7f, i64: 0x7e, f32: 0x7d, f64: 0x7c, void: 0x40, v128: 0x7B, ...HEAPTYPE, ...REFTYPE },
|
|
73
|
-
KIND = { func: 0, table: 1, memory: 2, global: 3 }
|
|
75
|
+
KIND = { func: 0, table: 1, memory: 2, global: 3, tag: 4 },
|
|
76
|
+
// WAT escape codes: https://webassembly.github.io/spec/core/text/values.html#strings
|
|
77
|
+
ESCAPE = { n: 10, r: 13, t: 9, v: 11, '"': 34, "'": 39, '\\': 92 }
|
package/src/parse.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { unescape } from "./util.js"
|
|
2
|
+
|
|
1
3
|
const OPAREN = 40, CPAREN = 41, OBRACK = 91, CBRACK = 93, SPACE = 32, DQUOTE = 34, PERIOD = 46,
|
|
2
|
-
_0 = 48, _9 = 57, SEMIC = 59, NEWLINE = 32, PLUS = 43, MINUS = 45, COLON = 58,
|
|
4
|
+
_0 = 48, _9 = 57, SEMIC = 59, NEWLINE = 32, PLUS = 43, MINUS = 45, COLON = 58, BACKSLASH = 92, AT = 64
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Parses a wasm text string and constructs a nested array structure (AST).
|
|
@@ -17,22 +20,23 @@ export default (str, o={ comments: false }) => {
|
|
|
17
20
|
)
|
|
18
21
|
|
|
19
22
|
const parseLevel = () => {
|
|
20
|
-
for (let c, root, q; i < str.length;) {
|
|
23
|
+
for (let c, root, q, id; i < str.length;) {
|
|
21
24
|
|
|
22
25
|
c = str.charCodeAt(i)
|
|
23
26
|
if (q) {
|
|
24
27
|
buf += str[i++]
|
|
25
|
-
if (
|
|
26
|
-
else if (c === DQUOTE) commit(), q = 0
|
|
28
|
+
if (c === BACKSLASH) buf += str[i++]
|
|
29
|
+
else if (c === DQUOTE) id && (buf = '$' + unescape(buf)), commit(), q = id = 0
|
|
27
30
|
}
|
|
28
31
|
else if (c === DQUOTE) {
|
|
29
|
-
commit(),
|
|
32
|
+
q = c, id = buf == '$', !id && commit(), buf = '"', i++
|
|
30
33
|
}
|
|
31
34
|
else if (c === OPAREN) {
|
|
32
35
|
if (str.charCodeAt(i + 1) === SEMIC) comment = str.slice(i, i = str.indexOf(';)', i) + 2), o.comments && level.push(comment) // (; ... ;)
|
|
36
|
+
else if (str.charCodeAt(i + 1) === AT) commit(), i += 2, buf = '@', (root = level).push(level = []), parseLevel(), level = root // (@annotid ...)
|
|
33
37
|
else commit(), i++, (root = level).push(level = []), parseLevel(), level = root
|
|
34
38
|
}
|
|
35
|
-
else if (c === SEMIC) comment = str.slice(i, i = str.indexOf('\n', i) + 1 || str.length), o.comments && level.push(comment) //
|
|
39
|
+
else if (c === SEMIC && str.charCodeAt(i + 1) === SEMIC) comment = str.slice(i, i = str.indexOf('\n', i) + 1 || str.length), o.comments && level.push(comment) // ;; ...
|
|
36
40
|
else if (c <= SPACE) commit(), i++
|
|
37
41
|
else if (c === CPAREN) return commit(), i++
|
|
38
42
|
else buf += str[i++]
|
package/src/util.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ESCAPE } from './const.js'
|
|
1
2
|
|
|
2
3
|
export const err = text => { throw Error(text) }
|
|
3
4
|
|
|
@@ -6,3 +7,48 @@ export const clone = items => items.map(item => Array.isArray(item) ? clone(item
|
|
|
6
7
|
export const sepRE = /^_|_$|[^\da-f]_|_[^\da-f]/i
|
|
7
8
|
|
|
8
9
|
export const intRE = /^[+-]?(?:0x[\da-f]+|\d+)$/i
|
|
10
|
+
|
|
11
|
+
// build string binary - convert WAT string to byte array
|
|
12
|
+
const enc = new TextEncoder()
|
|
13
|
+
export const str = (...parts) => {
|
|
14
|
+
let s = parts.map(s => s[0] === '"' ? s.slice(1, -1) : s).join(''), res = []
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < s.length; i++) {
|
|
17
|
+
let c = s.charCodeAt(i)
|
|
18
|
+
if (c === 92) { // backslash
|
|
19
|
+
let n = s[i + 1]
|
|
20
|
+
// \u{...} unicode - decode and UTF-8 encode
|
|
21
|
+
if (n === 'u' && s[i + 2] === '{') {
|
|
22
|
+
let hex = s.slice(i + 3, i = s.indexOf('}', i + 3))
|
|
23
|
+
res.push(...enc.encode(String.fromCodePoint(parseInt(hex, 16))))
|
|
24
|
+
// i now points to '}', loop i++ will move past it
|
|
25
|
+
}
|
|
26
|
+
// Named escape
|
|
27
|
+
else if (ESCAPE[n]) {
|
|
28
|
+
res.push(ESCAPE[n])
|
|
29
|
+
i++ // skip the named char, loop i++ will move past backslash
|
|
30
|
+
}
|
|
31
|
+
// \xx hex byte (raw byte, not UTF-8 decoded)
|
|
32
|
+
else {
|
|
33
|
+
res.push(parseInt(s.slice(i + 1, i + 3), 16))
|
|
34
|
+
i += 2 // skip two hex digits, loop i++ will complete the skip
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Multi-byte char - UTF-8 encode
|
|
38
|
+
else if (c > 255) {
|
|
39
|
+
res.push(...enc.encode(s[i]))
|
|
40
|
+
}
|
|
41
|
+
// Raw byte
|
|
42
|
+
else res.push(c)
|
|
43
|
+
}
|
|
44
|
+
return res
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Unescapes a WAT string literal by parsing escapes to bytes, then UTF-8 decoding.
|
|
49
|
+
* Reuses str() for escape parsing to eliminate duplication.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} s - String with quotes and escapes, e.g. '"hello\\nworld"'
|
|
52
|
+
* @returns {string} Unescaped string without quotes, e.g. 'hello\nworld'
|
|
53
|
+
*/
|
|
54
|
+
export const unescape = s => new TextDecoder().decode(new Uint8Array(str(s)))
|