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/src/util.js CHANGED
@@ -1,49 +1,81 @@
1
- import { ESCAPE } from './const.js'
2
-
3
- export const err = text => { throw Error(text) }
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
- // 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
+ 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
- // 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)
70
+ code != null ? (commit(), bytes.push(code)) : buf += c
43
71
  }
44
- return res
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 => new TextDecoder().decode(new Uint8Array(str(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,3 @@
1
+ declare function _default(str: string): any[];
2
+ export default _default;
3
+ //# sourceMappingURL=parse.d.ts.map
@@ -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"}
@@ -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 compile from './src/compile.js'
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
- export default compile
12
- export { compile, parse, print }
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 }