watr 3.3.0 → 4.0.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/LICENSE +26 -0
- package/bin/watr.js +83 -0
- package/dist/watr.js +1815 -0
- package/dist/watr.min.js +5 -0
- package/package.json +61 -14
- package/readme.md +42 -89
- package/src/compile.js +512 -590
- package/src/const.js +142 -56
- package/src/encode.js +89 -35
- package/src/parse.js +41 -32
- package/src/print.js +73 -12
- package/src/util.js +67 -35
- package/types/src/compile.d.ts +8 -0
- package/types/src/compile.d.ts.map +1 -0
- package/types/src/const.d.ts +87 -0
- package/types/src/const.d.ts.map +1 -0
- package/types/src/encode.d.ts +58 -0
- package/types/src/encode.d.ts.map +1 -0
- package/types/src/parse.d.ts +3 -0
- package/types/src/parse.d.ts.map +1 -0
- package/types/src/print.d.ts +16 -0
- package/types/src/print.d.ts.map +1 -0
- package/types/src/util.d.ts +18 -0
- package/types/src/util.d.ts.map +1 -0
- package/types/watr.d.ts +34 -0
- package/types/watr.d.ts.map +1 -0
- package/watr.js +233 -3
package/src/util.js
CHANGED
|
@@ -1,49 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Throws an error with optional source position.
|
|
3
|
+
* Uses err.src for source and err.i for default position.
|
|
4
|
+
* If pos provided or err.i set, appends "at line:col".
|
|
5
|
+
*
|
|
6
|
+
* @param {string} text - Error message
|
|
7
|
+
* @param {number} [pos] - Byte offset in source (defaults to err.i)
|
|
8
|
+
* @throws {Error}
|
|
9
|
+
*/
|
|
10
|
+
export const err = (text, pos=err.i) => {
|
|
11
|
+
if (pos != null && err.src) {
|
|
12
|
+
let line = 1, col = 1
|
|
13
|
+
for (let i = 0; i < pos && i < err.src.length; i++) {
|
|
14
|
+
if (err.src[i] === '\n') line++, col = 1
|
|
15
|
+
else col++
|
|
16
|
+
}
|
|
17
|
+
text += ` at ${line}:${col}`
|
|
18
|
+
}
|
|
19
|
+
throw Error(text)
|
|
20
|
+
}
|
|
4
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Deep clone an array tree structure.
|
|
24
|
+
* @param {Array} items - Array to clone
|
|
25
|
+
* @returns {Array} Cloned array
|
|
26
|
+
*/
|
|
5
27
|
export const clone = items => items.map(item => Array.isArray(item) ? clone(item) : item)
|
|
6
28
|
|
|
29
|
+
/** Regex to detect invalid underscore placement in numbers */
|
|
7
30
|
export const sepRE = /^_|_$|[^\da-f]_|_[^\da-f]/i
|
|
8
31
|
|
|
32
|
+
/** Regex to match valid integer literals (decimal or hex) */
|
|
9
33
|
export const intRE = /^[+-]?(?:0x[\da-f]+|\d+)$/i
|
|
10
34
|
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
const tenc = new TextEncoder();
|
|
36
|
+
const tdec = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
37
|
+
const escape = { n: 10, r: 13, t: 9, '"': 34, "'": 39, '\\': 92 }
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Convert WAT string literal (with quotes) to byte array.
|
|
42
|
+
* Handles escape sequences: \n, \t, \r, \xx (hex), \u{xxxx} (unicode).
|
|
43
|
+
* Attaches valueOf() returning original string for roundtrip.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} s - String literal including quotes, e.g. '"hello\n"'
|
|
46
|
+
* @returns {number[]} Byte array with valueOf() method
|
|
47
|
+
*/
|
|
48
|
+
export const str = s => {
|
|
49
|
+
let bytes = [], i = 1, code, c, buf = '' // i=1 to skip opening quote
|
|
50
|
+
|
|
51
|
+
const commit = () => (buf && bytes.push(...tenc.encode(buf)), buf = '')
|
|
52
|
+
|
|
53
|
+
while (i < s.length - 1) { // -1 to skip closing quote
|
|
54
|
+
c = s[i++], code = null
|
|
55
|
+
|
|
56
|
+
if (c === '\\') {
|
|
57
|
+
// \u{abcd}
|
|
58
|
+
if (s[i] === 'u') {
|
|
59
|
+
i++, i++ // 'u{'
|
|
60
|
+
c = String.fromCodePoint(parseInt(s.slice(i, i = s.indexOf('}', i)), 16))
|
|
61
|
+
i++ // '}'
|
|
35
62
|
}
|
|
63
|
+
// \n, \t, \r
|
|
64
|
+
else if (escape[s[i]]) code = escape[s[i++]]
|
|
65
|
+
// \00 - raw bytes
|
|
66
|
+
else if (!isNaN(code = parseInt(s[i] + s[i + 1], 16))) i++, i++
|
|
67
|
+
// \*
|
|
68
|
+
else c += s[i]
|
|
36
69
|
}
|
|
37
|
-
|
|
38
|
-
else if (c > 255) {
|
|
39
|
-
res.push(...enc.encode(s[i]))
|
|
40
|
-
}
|
|
41
|
-
// Raw byte
|
|
42
|
-
else res.push(c)
|
|
70
|
+
code != null ? (commit(), bytes.push(code)) : buf += c
|
|
43
71
|
}
|
|
44
|
-
|
|
72
|
+
commit()
|
|
73
|
+
|
|
74
|
+
bytes.valueOf = () => s
|
|
75
|
+
return bytes
|
|
45
76
|
}
|
|
46
77
|
|
|
78
|
+
|
|
47
79
|
/**
|
|
48
80
|
* Unescapes a WAT string literal by parsing escapes to bytes, then UTF-8 decoding.
|
|
49
81
|
* Reuses str() for escape parsing to eliminate duplication.
|
|
@@ -51,4 +83,4 @@ export const str = (...parts) => {
|
|
|
51
83
|
* @param {string} s - String with quotes and escapes, e.g. '"hello\\nworld"'
|
|
52
84
|
* @returns {string} Unescaped string without quotes, e.g. 'hello\nworld'
|
|
53
85
|
*/
|
|
54
|
-
export const unescape = s =>
|
|
86
|
+
export const unescape = s => tdec.decode(new Uint8Array(str(s)))
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
|
|
3
|
+
*
|
|
4
|
+
* @param {string|Array} nodes - The WAT tree or string to be compiled to WASM binary.
|
|
5
|
+
* @returns {Uint8Array} The compiled WASM binary data.
|
|
6
|
+
*/
|
|
7
|
+
export default function compile(nodes: string | any[]): Uint8Array;
|
|
8
|
+
//# sourceMappingURL=compile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.js"],"names":[],"mappings":"AA+BA;;;;;GAKG;AACH,uCAHW,MAAM,QAAM,GACV,UAAU,CAmLtB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const INSTR: (string | string[])[];
|
|
2
|
+
export namespace SECTION {
|
|
3
|
+
export let custom: number;
|
|
4
|
+
export let type: number;
|
|
5
|
+
let _import: number;
|
|
6
|
+
export { _import as import };
|
|
7
|
+
export let func: number;
|
|
8
|
+
export let table: number;
|
|
9
|
+
export let memory: number;
|
|
10
|
+
export let tag: number;
|
|
11
|
+
export let global: number;
|
|
12
|
+
let _export: number;
|
|
13
|
+
export { _export as export };
|
|
14
|
+
export let start: number;
|
|
15
|
+
export let elem: number;
|
|
16
|
+
export let datacount: number;
|
|
17
|
+
export let code: number;
|
|
18
|
+
export let data: number;
|
|
19
|
+
}
|
|
20
|
+
export namespace TYPE {
|
|
21
|
+
export let i8: number;
|
|
22
|
+
export let i16: number;
|
|
23
|
+
export let i32: number;
|
|
24
|
+
export let i64: number;
|
|
25
|
+
export let f32: number;
|
|
26
|
+
export let f64: number;
|
|
27
|
+
let _void: number;
|
|
28
|
+
export { _void as void };
|
|
29
|
+
export let v128: number;
|
|
30
|
+
export let exn: number;
|
|
31
|
+
export let noexn: number;
|
|
32
|
+
export let nofunc: number;
|
|
33
|
+
export let noextern: number;
|
|
34
|
+
export let none: number;
|
|
35
|
+
let func_1: number;
|
|
36
|
+
export { func_1 as func };
|
|
37
|
+
export let extern: number;
|
|
38
|
+
export let any: number;
|
|
39
|
+
export let eq: number;
|
|
40
|
+
export let i31: number;
|
|
41
|
+
export let struct: number;
|
|
42
|
+
export let array: number;
|
|
43
|
+
export let nullfuncref: number;
|
|
44
|
+
export let nullexternref: number;
|
|
45
|
+
export let nullexnref: number;
|
|
46
|
+
export let nullref: number;
|
|
47
|
+
export let funcref: number;
|
|
48
|
+
export let externref: number;
|
|
49
|
+
export let exnref: number;
|
|
50
|
+
export let anyref: number;
|
|
51
|
+
export let eqref: number;
|
|
52
|
+
export let i31ref: number;
|
|
53
|
+
export let structref: number;
|
|
54
|
+
export let arrayref: number;
|
|
55
|
+
export let ref: number;
|
|
56
|
+
export let refnull: number;
|
|
57
|
+
export let sub: number;
|
|
58
|
+
export let subfinal: number;
|
|
59
|
+
export let rec: number;
|
|
60
|
+
}
|
|
61
|
+
export namespace DEFTYPE {
|
|
62
|
+
let func_2: number;
|
|
63
|
+
export { func_2 as func };
|
|
64
|
+
let struct_1: number;
|
|
65
|
+
export { struct_1 as struct };
|
|
66
|
+
let array_1: number;
|
|
67
|
+
export { array_1 as array };
|
|
68
|
+
let sub_1: number;
|
|
69
|
+
export { sub_1 as sub };
|
|
70
|
+
let subfinal_1: number;
|
|
71
|
+
export { subfinal_1 as subfinal };
|
|
72
|
+
let rec_1: number;
|
|
73
|
+
export { rec_1 as rec };
|
|
74
|
+
}
|
|
75
|
+
export namespace KIND {
|
|
76
|
+
let func_3: number;
|
|
77
|
+
export { func_3 as func };
|
|
78
|
+
let table_1: number;
|
|
79
|
+
export { table_1 as table };
|
|
80
|
+
let memory_1: number;
|
|
81
|
+
export { memory_1 as memory };
|
|
82
|
+
let global_1: number;
|
|
83
|
+
export { global_1 as global };
|
|
84
|
+
let tag_1: number;
|
|
85
|
+
export { tag_1 as tag };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=const.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../src/const.js"],"names":[],"mappings":"AAIA,0CAqIC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode as fixed-width 5-byte ULEB128 (canonical form).
|
|
3
|
+
* Used by some tools for predictable binary layout.
|
|
4
|
+
*
|
|
5
|
+
* @param {number} value - 32-bit unsigned value
|
|
6
|
+
* @returns {number[]} 5-byte array
|
|
7
|
+
*/
|
|
8
|
+
export function uleb5(value: number): number[];
|
|
9
|
+
/**
|
|
10
|
+
* Encode signed LEB128 for i32 values.
|
|
11
|
+
*
|
|
12
|
+
* @param {number|string} n - Signed 32-bit value
|
|
13
|
+
* @param {number[]} [buffer=[]] - Output buffer
|
|
14
|
+
* @returns {number[]} Encoded bytes
|
|
15
|
+
*/
|
|
16
|
+
export function i32(n: number | string, buffer?: number[]): number[];
|
|
17
|
+
export namespace i32 {
|
|
18
|
+
function parse(n: any): any;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Encode signed LEB128 for i64 values (BigInt).
|
|
22
|
+
*
|
|
23
|
+
* @param {bigint|string} n - Signed 64-bit value
|
|
24
|
+
* @param {number[]} [buffer=[]] - Output buffer
|
|
25
|
+
* @returns {number[]} Encoded bytes
|
|
26
|
+
*/
|
|
27
|
+
export function i64(n: bigint | string, buffer?: number[]): number[];
|
|
28
|
+
export namespace i64 {
|
|
29
|
+
function parse(n: any): any;
|
|
30
|
+
}
|
|
31
|
+
export function f32(input: any, value: any, idx: any): number[];
|
|
32
|
+
export namespace f32 {
|
|
33
|
+
function parse(input: any): number;
|
|
34
|
+
}
|
|
35
|
+
export function f64(input: any, value: any, idx: any): number[];
|
|
36
|
+
export namespace f64 {
|
|
37
|
+
function parse(input: any, max?: number): number;
|
|
38
|
+
}
|
|
39
|
+
export function uleb(n: number | bigint | string | null, buffer?: number[]): number[];
|
|
40
|
+
/**
|
|
41
|
+
* Encode signed LEB128 for i32 values.
|
|
42
|
+
*
|
|
43
|
+
* @param {number|string} n - Signed 32-bit value
|
|
44
|
+
* @param {number[]} [buffer=[]] - Output buffer
|
|
45
|
+
* @returns {number[]} Encoded bytes
|
|
46
|
+
*/
|
|
47
|
+
export function i8(n: number | string, buffer?: number[]): number[];
|
|
48
|
+
export namespace i8 { }
|
|
49
|
+
/**
|
|
50
|
+
* Encode signed LEB128 for i32 values.
|
|
51
|
+
*
|
|
52
|
+
* @param {number|string} n - Signed 32-bit value
|
|
53
|
+
* @param {number[]} [buffer=[]] - Output buffer
|
|
54
|
+
* @returns {number[]} Encoded bytes
|
|
55
|
+
*/
|
|
56
|
+
export function i16(n: number | string, buffer?: number[]): number[];
|
|
57
|
+
export namespace i16 { }
|
|
58
|
+
//# sourceMappingURL=encode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../src/encode.js"],"names":[],"mappings":"AA6CA;;;;;;GAMG;AACH,6BAHW,MAAM,GACJ,MAAM,EAAE,CAapB;AAED;;;;;;GAMG;AACH,uBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAepB;;IAQD,4BAIC;;AAED;;;;;;GAMG;AACH,uBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAepB;;IACD,4BAMC;;AAKD,gEAkBC;;IAmED,mCAA6D;;AAhE7D,gEAsBC;;IAED,iDAsCC;;AAvMM,wBAJI,MAAM,GAAC,MAAM,GAAC,MAAM,GAAC,IAAI,WACzB,MAAM,EAAE,GACN,MAAM,EAAE,CA8BpB;AAsBD;;;;;;GAMG;AACH,sBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAepB;;AApBD;;;;;;GAMG;AACH,uBAJW,MAAM,GAAC,MAAM,WACb,MAAM,EAAE,GACN,MAAM,EAAE,CAepB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/parse.js"],"names":[],"mappings":"AASe,+BAHJ,MAAM,SAqDhB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a tree or a WAT (WebAssembly Text) string into a readable format.
|
|
3
|
+
*
|
|
4
|
+
* @param {string | Array} tree - The code to print. If a string is provided, it will be parsed into a tree structure first.
|
|
5
|
+
* @param {Object} [options={}] - Optional settings for printing.
|
|
6
|
+
* @param {string} [options.indent=' '] - The string used for one level of indentation. Defaults to two spaces.
|
|
7
|
+
* @param {string} [options.newline='\n'] - The string used for line breaks. Defaults to a newline character.
|
|
8
|
+
* @param {boolean} [options.comments=false] - Whether to include comments in the output. Defaults to false.
|
|
9
|
+
* @returns {string} The formatted WAT string.
|
|
10
|
+
*/
|
|
11
|
+
export default function print(tree: string | any[], options?: {
|
|
12
|
+
indent?: string;
|
|
13
|
+
newline?: string;
|
|
14
|
+
comments?: boolean;
|
|
15
|
+
}): string;
|
|
16
|
+
//# sourceMappingURL=print.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print.d.ts","sourceRoot":"","sources":["../../src/print.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,oCAPW,MAAM,QAAQ,YAEtB;IAAyB,MAAM,GAAvB,MAAM;IACW,OAAO,GAAxB,MAAM;IACY,QAAQ,GAA1B,OAAO;CACf,GAAU,MAAM,CA0GlB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws an error with optional source position.
|
|
3
|
+
* Uses err.src for source and err.i for default position.
|
|
4
|
+
* If pos provided or err.i set, appends "at line:col".
|
|
5
|
+
*
|
|
6
|
+
* @param {string} text - Error message
|
|
7
|
+
* @param {number} [pos] - Byte offset in source (defaults to err.i)
|
|
8
|
+
* @throws {Error}
|
|
9
|
+
*/
|
|
10
|
+
export const err: (text: string, pos?: number) => never;
|
|
11
|
+
export function clone(items: any[]): any[];
|
|
12
|
+
/** Regex to detect invalid underscore placement in numbers */
|
|
13
|
+
export const sepRE: RegExp;
|
|
14
|
+
/** Regex to match valid integer literals (decimal or hex) */
|
|
15
|
+
export const intRE: RegExp;
|
|
16
|
+
export function str(s: string): number[];
|
|
17
|
+
export function unescape(s: string): string;
|
|
18
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util.js"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,mBAAoB,MAJT,MAIa,EAAE,MAHf,MAGwB,WAUlC;AAOM,2CAAkF;AAEzF,8DAA8D;AAC9D,2BAAiD;AAEjD,6DAA6D;AAC7D,2BAAiD;AAe1C,uBAHI,MAAM,GACJ,MAAM,EAAE,CA8BpB;AAUM,4BAHI,MAAM,GACJ,MAAM,CAE6C"}
|
package/types/watr.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default watr;
|
|
2
|
+
/**
|
|
3
|
+
* Compile and instantiate WAT, returning exports.
|
|
4
|
+
*
|
|
5
|
+
* @param {string|TemplateStringsArray} strings - WAT source string or template strings
|
|
6
|
+
* @param {...any} values - Interpolation values (for template literal)
|
|
7
|
+
* @returns {WebAssembly.Exports} Module exports
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Template literal
|
|
11
|
+
* const { add } = watr`(func (export "add") (param i32 i32) (result i32)
|
|
12
|
+
* (i32.add (local.get 0) (local.get 1))
|
|
13
|
+
* )`
|
|
14
|
+
*
|
|
15
|
+
* // Plain string
|
|
16
|
+
* const { add } = watr('(func (export "add") (param i32 i32) (result i32) (i32.add (local.get 0) (local.get 1)))')
|
|
17
|
+
*/
|
|
18
|
+
export function watr(strings: string | TemplateStringsArray, ...values: any[]): WebAssembly.Exports;
|
|
19
|
+
/**
|
|
20
|
+
* Compile WAT to binary. Supports both string and template literal.
|
|
21
|
+
*
|
|
22
|
+
* @param {string|TemplateStringsArray} source - WAT source or template strings
|
|
23
|
+
* @param {...any} values - Interpolation values (for template literal)
|
|
24
|
+
* @returns {Uint8Array} WebAssembly binary
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* compile('(func (export "f") (result i32) (i32.const 42))')
|
|
28
|
+
* compile`(func (export "f") (result f64) (f64.const ${Math.PI}))`
|
|
29
|
+
*/
|
|
30
|
+
export function compile(source: string | TemplateStringsArray, ...values: any[]): Uint8Array;
|
|
31
|
+
import parse from './src/parse.js';
|
|
32
|
+
import print from './src/print.js';
|
|
33
|
+
export { parse, print };
|
|
34
|
+
//# sourceMappingURL=watr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watr.d.ts","sourceRoot":"","sources":["../watr.js"],"names":[],"mappings":";AAyNA;;;;;;;;;;;;;;;GAeG;AACH,8BAbW,MAAM,GAAC,oBAAoB,aACxB,GAAG,EAAA,GACJ,WAAW,CAAC,OAAO,CAgB/B;AAxGD;;;;;;;;;;GAUG;AACH,gCARW,MAAM,GAAC,oBAAoB,aACxB,GAAG,EAAA,GACJ,UAAU,CA4EtB;kBAhNiB,gBAAgB;kBAChB,gBAAgB"}
|
package/watr.js
CHANGED
|
@@ -4,9 +4,239 @@
|
|
|
4
4
|
* @module watr
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import _compile from './src/compile.js'
|
|
8
8
|
import parse from './src/parse.js'
|
|
9
9
|
import print from './src/print.js'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/** Private Use Area character as placeholder for interpolation */
|
|
12
|
+
const PUA = '\uE000'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Infer result type from instruction name.
|
|
16
|
+
* E.g., 'i32.add' → 'i32', 'f64.const' → 'f64'
|
|
17
|
+
*
|
|
18
|
+
* @param {string} op - Instruction name
|
|
19
|
+
* @returns {string|null} Type string or null if unknown
|
|
20
|
+
*/
|
|
21
|
+
const instrType = op => {
|
|
22
|
+
if (!op || typeof op !== 'string') return null
|
|
23
|
+
// i32.add → i32, f64.const → f64, v128.load → v128
|
|
24
|
+
const prefix = op.split('.')[0]
|
|
25
|
+
if (/^[if](32|64)|v128/.test(prefix)) return prefix
|
|
26
|
+
// comparisons return i32: .eq .ne .lt .gt .le .ge .eqz
|
|
27
|
+
if (/\.(eq|ne|[lg][te]|eqz)/.test(op)) return 'i32'
|
|
28
|
+
// memory.size/grow return i32
|
|
29
|
+
if (op === 'memory.size' || op === 'memory.grow') return 'i32'
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Infer type of an expression AST node.
|
|
35
|
+
* Used for auto-import parameter type inference.
|
|
36
|
+
*
|
|
37
|
+
* @param {any} node - AST node (array or primitive)
|
|
38
|
+
* @param {Object} [ctx={}] - Context with locals/funcs type info
|
|
39
|
+
* @returns {string|null} Type string or null if unknown
|
|
40
|
+
*/
|
|
41
|
+
const exprType = (node, ctx = {}) => {
|
|
42
|
+
if (!Array.isArray(node)) {
|
|
43
|
+
// local.get $x - lookup type
|
|
44
|
+
if (typeof node === 'string' && node[0] === '$' && ctx.locals?.[node]) return ctx.locals[node]
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
const [op, ...args] = node
|
|
48
|
+
// (i32.const 42) → i32
|
|
49
|
+
if (instrType(op)) return instrType(op)
|
|
50
|
+
// (local.get $x) → lookup
|
|
51
|
+
if (op === 'local.get' && ctx.locals?.[args[0]]) return ctx.locals[args[0]]
|
|
52
|
+
// (call $fn ...) → lookup function result type
|
|
53
|
+
if (op === 'call' && ctx.funcs?.[args[0]]) return ctx.funcs[args[0]].result?.[0]
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Walk AST and transform nodes depth-first.
|
|
59
|
+
* Handles array splicing when child has `_splice` property.
|
|
60
|
+
*
|
|
61
|
+
* @param {any} node - AST node to walk
|
|
62
|
+
* @param {Function} fn - Transform function (node) => node
|
|
63
|
+
* @returns {any} Transformed node
|
|
64
|
+
*/
|
|
65
|
+
function walk(node, fn) {
|
|
66
|
+
node = fn(node)
|
|
67
|
+
if (Array.isArray(node)) {
|
|
68
|
+
for (let i = 0; i < node.length; i++) {
|
|
69
|
+
let child = walk(node[i], fn)
|
|
70
|
+
if (child?._splice) node.splice(i, 1, ...child), i += child.length - 1
|
|
71
|
+
else node[i] = child
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return node
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Find function references in AST and infer import signatures.
|
|
79
|
+
* Scans for `(call fn args...)` where fn is a JS function,
|
|
80
|
+
* infers param types from arguments, generates import entries.
|
|
81
|
+
*
|
|
82
|
+
* @param {Array} ast - AST to scan
|
|
83
|
+
* @param {Function[]} funcs - Functions to look for
|
|
84
|
+
* @returns {Array<{idx: number, name: string, params: string[], fn: Function}>} Import entries
|
|
85
|
+
*/
|
|
86
|
+
function inferImports(ast, funcs) {
|
|
87
|
+
const imports = []
|
|
88
|
+
const importMap = new Map() // fn → import index
|
|
89
|
+
|
|
90
|
+
walk(ast, node => {
|
|
91
|
+
if (!Array.isArray(node)) return node
|
|
92
|
+
|
|
93
|
+
// Find (call ${fn} args...) where fn is a function
|
|
94
|
+
if (node[0] === 'call' && typeof node[1] === 'function') {
|
|
95
|
+
const fn = node[1]
|
|
96
|
+
|
|
97
|
+
if (!importMap.has(fn)) {
|
|
98
|
+
// Infer param types from arguments
|
|
99
|
+
const params = []
|
|
100
|
+
for (let i = 2; i < node.length; i++) {
|
|
101
|
+
const t = exprType(node[i])
|
|
102
|
+
if (t) params.push(t)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Create import entry
|
|
106
|
+
const idx = imports.length
|
|
107
|
+
const name = fn.name || `$fn${idx}`
|
|
108
|
+
importMap.set(fn, { idx, name: name.startsWith('$') ? name : '$' + name, params, fn })
|
|
109
|
+
imports.push(importMap.get(fn))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Replace function with import reference
|
|
113
|
+
const imp = importMap.get(fn)
|
|
114
|
+
node[1] = imp.name
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return node
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return imports
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Generate WAT import declarations from inferred imports.
|
|
125
|
+
*
|
|
126
|
+
* @param {Array<{name: string, params: string[]}>} imports - Import entries
|
|
127
|
+
* @returns {Array} AST nodes for import declarations
|
|
128
|
+
*/
|
|
129
|
+
function genImports(imports) {
|
|
130
|
+
return imports.map(({ name, params }) =>
|
|
131
|
+
['import', '"env"', `"${name.slice(1)}"`, ['func', name, ...params.map(t => ['param', t])]]
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Compile WAT to binary. Supports both string and template literal.
|
|
137
|
+
*
|
|
138
|
+
* @param {string|TemplateStringsArray} source - WAT source or template strings
|
|
139
|
+
* @param {...any} values - Interpolation values (for template literal)
|
|
140
|
+
* @returns {Uint8Array} WebAssembly binary
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* compile('(func (export "f") (result i32) (i32.const 42))')
|
|
144
|
+
* compile`(func (export "f") (result f64) (f64.const ${Math.PI}))`
|
|
145
|
+
*/
|
|
146
|
+
function compile(source, ...values) {
|
|
147
|
+
// Template literal: source is TemplateStringsArray
|
|
148
|
+
if (Array.isArray(source) && source.raw) {
|
|
149
|
+
// Build source with placeholders
|
|
150
|
+
let src = source[0]
|
|
151
|
+
for (let i = 0; i < values.length; i++) {
|
|
152
|
+
src += PUA + source[i + 1]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Parse to AST
|
|
156
|
+
let ast = parse(src)
|
|
157
|
+
|
|
158
|
+
// Collect functions for auto-import
|
|
159
|
+
const funcsToImport = []
|
|
160
|
+
|
|
161
|
+
// Replace placeholders with actual values
|
|
162
|
+
let idx = 0
|
|
163
|
+
ast = walk(ast, node => {
|
|
164
|
+
if (node === PUA) {
|
|
165
|
+
const value = values[idx++]
|
|
166
|
+
// Function → mark for import inference
|
|
167
|
+
if (typeof value === 'function') {
|
|
168
|
+
funcsToImport.push(value)
|
|
169
|
+
return value // keep function reference for now
|
|
170
|
+
}
|
|
171
|
+
// String containing WAT code → parse and splice
|
|
172
|
+
if (typeof value === 'string' && (value[0] === '(' || /^\s*\(/.test(value))) {
|
|
173
|
+
const parsed = parse(value)
|
|
174
|
+
if (Array.isArray(parsed) && Array.isArray(parsed[0])) {
|
|
175
|
+
parsed._splice = true
|
|
176
|
+
}
|
|
177
|
+
return parsed
|
|
178
|
+
}
|
|
179
|
+
// Uint8Array → convert to plain array for flat() compatibility
|
|
180
|
+
if (value instanceof Uint8Array) return [...value]
|
|
181
|
+
return value
|
|
182
|
+
}
|
|
183
|
+
return node
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// If we have functions to import, infer and generate imports
|
|
187
|
+
let importObjs = null
|
|
188
|
+
if (funcsToImport.length) {
|
|
189
|
+
const imports = inferImports(ast, funcsToImport)
|
|
190
|
+
if (imports.length) {
|
|
191
|
+
// Insert import declarations at start of module
|
|
192
|
+
const importDecls = genImports(imports)
|
|
193
|
+
if (ast[0] === 'module') {
|
|
194
|
+
ast.splice(1, 0, ...importDecls)
|
|
195
|
+
} else if (typeof ast[0] === 'string') {
|
|
196
|
+
// Single top-level node like ['func', ...] - wrap in array with imports
|
|
197
|
+
ast = [...importDecls, ast]
|
|
198
|
+
} else {
|
|
199
|
+
// Multiple top-level nodes like [['func', ...], ['func', ...]]
|
|
200
|
+
ast.unshift(...importDecls)
|
|
201
|
+
}
|
|
202
|
+
// Build imports object for instantiation
|
|
203
|
+
importObjs = { env: {} }
|
|
204
|
+
for (const imp of imports) {
|
|
205
|
+
importObjs.env[imp.name.slice(1)] = imp.fn
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const binary = _compile(ast)
|
|
211
|
+
// Attach imports for watr() to use
|
|
212
|
+
if (importObjs) binary._imports = importObjs
|
|
213
|
+
return binary
|
|
214
|
+
}
|
|
215
|
+
return _compile(source)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Compile and instantiate WAT, returning exports.
|
|
220
|
+
*
|
|
221
|
+
* @param {string|TemplateStringsArray} strings - WAT source string or template strings
|
|
222
|
+
* @param {...any} values - Interpolation values (for template literal)
|
|
223
|
+
* @returns {WebAssembly.Exports} Module exports
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* // Template literal
|
|
227
|
+
* const { add } = watr`(func (export "add") (param i32 i32) (result i32)
|
|
228
|
+
* (i32.add (local.get 0) (local.get 1))
|
|
229
|
+
* )`
|
|
230
|
+
*
|
|
231
|
+
* // Plain string
|
|
232
|
+
* const { add } = watr('(func (export "add") (param i32 i32) (result i32) (i32.add (local.get 0) (local.get 1)))')
|
|
233
|
+
*/
|
|
234
|
+
function watr(strings, ...values) {
|
|
235
|
+
const binary = compile(strings, ...values)
|
|
236
|
+
const module = new WebAssembly.Module(binary)
|
|
237
|
+
const instance = new WebAssembly.Instance(module, binary._imports)
|
|
238
|
+
return instance.exports
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export default watr
|
|
242
|
+
export { watr, compile, parse, print }
|