| 
									
										
										
										
											2021-05-07 10:02:58 +04:30
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | import struct | 
					
						
							|  |  |  | import subprocess | 
					
						
							| 
									
										
										
										
											2025-06-09 10:41:54 -04:00
										 |  |  | import sys | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | from dataclasses import dataclass | 
					
						
							|  |  |  | from pathlib import Path | 
					
						
							| 
									
										
										
										
											2025-06-09 10:41:54 -04:00
										 |  |  | from typing import Any | 
					
						
							|  |  |  | from typing import Literal | 
					
						
							|  |  |  | from typing import Union | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ParseException(Exception): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class GenerateException(Exception): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  | class WasmPrimitiveValue: | 
					
						
							|  |  |  |     kind: Literal["i32", "i64", "f32", "f64", "externref", "funcref"] | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     value: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  | @dataclass | 
					
						
							|  |  |  | class WasmVector: | 
					
						
							|  |  |  |     lanes: list[str] | 
					
						
							|  |  |  |     num_bits: int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | WasmValue = Union[WasmPrimitiveValue, WasmVector] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | @dataclass | 
					
						
							|  |  |  | class ModuleCommand: | 
					
						
							|  |  |  |     line: int | 
					
						
							|  |  |  |     file_name: Path | 
					
						
							|  |  |  |     name: str | None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class Invoke: | 
					
						
							|  |  |  |     field: str | 
					
						
							|  |  |  |     args: list[WasmValue] | 
					
						
							|  |  |  |     module: str | None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class Get: | 
					
						
							|  |  |  |     field: str | 
					
						
							|  |  |  |     module: str | None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Action = Union[Invoke, Get] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class Register: | 
					
						
							|  |  |  |     line: int | 
					
						
							|  |  |  |     name: str | None | 
					
						
							|  |  |  |     as_: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class AssertReturn: | 
					
						
							|  |  |  |     line: int | 
					
						
							|  |  |  |     action: Action | 
					
						
							|  |  |  |     expected: WasmValue | None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class AssertTrap: | 
					
						
							|  |  |  |     line: int | 
					
						
							|  |  |  |     messsage: str | 
					
						
							|  |  |  |     action: Action | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class ActionCommand: | 
					
						
							|  |  |  |     line: int | 
					
						
							|  |  |  |     action: Action | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class AssertInvalid: | 
					
						
							|  |  |  |     line: int | 
					
						
							|  |  |  |     filename: str | 
					
						
							|  |  |  |     message: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Command = Union[ | 
					
						
							|  |  |  |     ModuleCommand, | 
					
						
							|  |  |  |     AssertReturn, | 
					
						
							|  |  |  |     AssertTrap, | 
					
						
							|  |  |  |     ActionCommand, | 
					
						
							|  |  |  |     AssertInvalid, | 
					
						
							|  |  |  |     Register, | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  | @dataclass | 
					
						
							|  |  |  | class ArithmeticNan: | 
					
						
							|  |  |  |     num_bits: int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class CanonicalNan: | 
					
						
							|  |  |  |     num_bits: int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  | @dataclass | 
					
						
							|  |  |  | class GeneratedVector: | 
					
						
							|  |  |  |     repr: str | 
					
						
							|  |  |  |     num_bits: int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GeneratedValue = Union[str, ArithmeticNan, CanonicalNan, GeneratedVector] | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | @dataclass | 
					
						
							|  |  |  | class WastDescription: | 
					
						
							|  |  |  |     source_filename: str | 
					
						
							|  |  |  |     commands: list[Command] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dataclass | 
					
						
							|  |  |  | class Context: | 
					
						
							|  |  |  |     current_module_name: str | 
					
						
							|  |  |  |     has_unclosed: bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_value(arg: dict[str, str]) -> WasmValue: | 
					
						
							|  |  |  |     type_ = arg["type"] | 
					
						
							|  |  |  |     match type_: | 
					
						
							|  |  |  |         case "i32" | "i64" | "f32" | "f64" | "externref" | "funcref": | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  |             return WasmPrimitiveValue(type_, arg["value"]) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         case "v128": | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  |             if not isinstance(arg["value"], list): | 
					
						
							|  |  |  |                 raise ParseException("Got unknown type for Wasm value") | 
					
						
							|  |  |  |             num_bits = int(arg["lane_type"][1:]) | 
					
						
							|  |  |  |             return WasmVector(arg["value"], num_bits) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         case _: | 
					
						
							|  |  |  |             raise ParseException(f"Unknown value type: {type_}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_args(raw_args: list[dict[str, str]]) -> list[WasmValue]: | 
					
						
							|  |  |  |     return [parse_value(arg) for arg in raw_args] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_action(action: dict[str, Any]) -> Action: | 
					
						
							|  |  |  |     match action["type"]: | 
					
						
							|  |  |  |         case "invoke": | 
					
						
							| 
									
										
										
										
											2025-05-22 07:30:45 -04:00
										 |  |  |             return Invoke(action["field"], parse_args(action["args"]), action.get("module")) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         case "get": | 
					
						
							|  |  |  |             return Get(action["field"], action.get("module")) | 
					
						
							|  |  |  |         case _: | 
					
						
							|  |  |  |             raise ParseException(f"Action not implemented: {action['type']}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse(raw: dict[str, Any]) -> WastDescription: | 
					
						
							|  |  |  |     commands: list[Command] = [] | 
					
						
							|  |  |  |     for raw_cmd in raw["commands"]: | 
					
						
							|  |  |  |         line = raw_cmd["line"] | 
					
						
							|  |  |  |         cmd: Command | 
					
						
							|  |  |  |         match raw_cmd["type"]: | 
					
						
							|  |  |  |             case "module": | 
					
						
							| 
									
										
										
										
											2025-05-22 07:30:45 -04:00
										 |  |  |                 cmd = ModuleCommand(line, Path(raw_cmd["filename"]), raw_cmd.get("name")) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |             case "action": | 
					
						
							|  |  |  |                 cmd = ActionCommand(line, parse_action(raw_cmd["action"])) | 
					
						
							|  |  |  |             case "register": | 
					
						
							|  |  |  |                 cmd = Register(line, raw_cmd.get("name"), raw_cmd["as"]) | 
					
						
							|  |  |  |             case "assert_return": | 
					
						
							|  |  |  |                 cmd = AssertReturn( | 
					
						
							|  |  |  |                     line, | 
					
						
							|  |  |  |                     parse_action(raw_cmd["action"]), | 
					
						
							| 
									
										
										
										
											2025-05-22 07:30:45 -04:00
										 |  |  |                     parse_value(raw_cmd["expected"][0]) if len(raw_cmd["expected"]) == 1 else None, | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |                 ) | 
					
						
							|  |  |  |             case "assert_trap" | "assert_exhaustion": | 
					
						
							|  |  |  |                 cmd = AssertTrap(line, raw_cmd["text"], parse_action(raw_cmd["action"])) | 
					
						
							|  |  |  |             case "assert_invalid" | "assert_malformed" | "assert_uninstantiable" | "assert_unlinkable": | 
					
						
							|  |  |  |                 if raw_cmd.get("module_type") == "text": | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 cmd = AssertInvalid(line, raw_cmd["filename"], raw_cmd["text"]) | 
					
						
							|  |  |  |             case _: | 
					
						
							|  |  |  |                 raise ParseException(f"Unknown command type: {raw_cmd['type']}") | 
					
						
							|  |  |  |         commands.append(cmd) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return WastDescription(raw["source_filename"], commands) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def escape(s: str) -> str: | 
					
						
							|  |  |  |     return s.replace('"', '\\"') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def make_description(input_path: Path, name: str, out_path: Path) -> WastDescription: | 
					
						
							|  |  |  |     out_json_path = out_path / f"{name}.json" | 
					
						
							|  |  |  |     result = subprocess.run( | 
					
						
							| 
									
										
										
										
											2025-09-22 11:38:44 +02:00
										 |  |  |         ["wast2json", "--enable-all", input_path, f"--output={out_json_path}", "--no-check"], | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     ) | 
					
						
							|  |  |  |     result.check_returncode() | 
					
						
							|  |  |  |     with open(out_json_path, "r") as f: | 
					
						
							|  |  |  |         description = json.load(f) | 
					
						
							|  |  |  |     return parse(description) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  | def gen_vector(vec: WasmVector, *, array=False) -> str: | 
					
						
							|  |  |  |     addition = "n" if vec.num_bits == 64 else "" | 
					
						
							|  |  |  |     vals = ", ".join(v + addition if v.isdigit() else f'"{v}"' for v in vec.lanes) | 
					
						
							|  |  |  |     if not array: | 
					
						
							|  |  |  |         type_ = "BigUint64Array" if vec.num_bits == 64 else f"Uint{vec.num_bits}Array" | 
					
						
							|  |  |  |         return f"new {type_}([{vals}])" | 
					
						
							|  |  |  |     return f"[{vals}]" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  | def gen_value_arg(value: WasmValue) -> str: | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  |     if isinstance(value, WasmVector): | 
					
						
							|  |  |  |         return gen_vector(value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     def unsigned_to_signed(uint: int, bits: int) -> int: | 
					
						
							|  |  |  |         max_value = 2**bits | 
					
						
							|  |  |  |         if uint >= 2 ** (bits - 1): | 
					
						
							|  |  |  |             signed_int = uint - max_value | 
					
						
							| 
									
										
										
										
											2021-05-07 10:02:58 +04:30
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |             signed_int = uint | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return signed_int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def int_to_float_bitcast(uint: int) -> float: | 
					
						
							|  |  |  |         b = struct.pack("I", uint) | 
					
						
							|  |  |  |         f = struct.unpack("f", b)[0] | 
					
						
							|  |  |  |         return f | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def int_to_float64_bitcast(uint: int) -> float: | 
					
						
							|  |  |  |         uint64 = uint & 0xFFFFFFFFFFFFFFFF | 
					
						
							|  |  |  |         b = struct.pack("Q", uint64) | 
					
						
							|  |  |  |         f = struct.unpack("d", b)[0] | 
					
						
							|  |  |  |         return f | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |     def float_to_str(bits: int, *, double=False) -> str: | 
					
						
							| 
									
										
										
										
											2024-07-06 08:01:26 -07:00
										 |  |  |         f = int_to_float64_bitcast(bits) if double else int_to_float_bitcast(bits) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         return str(f) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if value.value.startswith("nan"): | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |         raise GenerateException("Should not get indeterminate nan value as an argument") | 
					
						
							|  |  |  |     if value.value == "inf": | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         return "Infinity" | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |     if value.value == "-inf": | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         return "-Infinity" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     match value.kind: | 
					
						
							|  |  |  |         case "i32": | 
					
						
							|  |  |  |             return str(unsigned_to_signed(int(value.value), 32)) | 
					
						
							|  |  |  |         case "i64": | 
					
						
							|  |  |  |             return str(unsigned_to_signed(int(value.value), 64)) + "n" | 
					
						
							|  |  |  |         case "f32": | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |             return str(int(value.value)) + f" /* {float_to_str(int(value.value))} */" | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         case "f64": | 
					
						
							| 
									
										
										
										
											2025-05-22 07:30:45 -04:00
										 |  |  |             return str(int(value.value)) + f"n /* {float_to_str(int(value.value), double=True)} */" | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         case "externref" | "funcref" | "v128": | 
					
						
							|  |  |  |             return value.value | 
					
						
							|  |  |  |         case _: | 
					
						
							|  |  |  |             raise GenerateException(f"Not implemented: {value.kind}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  | def gen_value_result(value: WasmValue) -> GeneratedValue: | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  |     if isinstance(value, WasmVector): | 
					
						
							|  |  |  |         return GeneratedVector(gen_vector(value, array=True), value.num_bits) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |     if (value.kind == "f32" or value.kind == "f64") and value.value.startswith("nan"): | 
					
						
							|  |  |  |         num_bits = int(value.kind[1:]) | 
					
						
							|  |  |  |         match value.value: | 
					
						
							|  |  |  |             case "nan:canonical": | 
					
						
							|  |  |  |                 return CanonicalNan(num_bits) | 
					
						
							|  |  |  |             case "nan:arithmetic": | 
					
						
							|  |  |  |                 return ArithmeticNan(num_bits) | 
					
						
							|  |  |  |             case _: | 
					
						
							|  |  |  |                 raise GenerateException(f"Unknown indeterminate nan: {value.value}") | 
					
						
							|  |  |  |     return gen_value_arg(value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | def gen_args(args: list[WasmValue]) -> str: | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |     return ",".join(gen_value_arg(arg) for arg in args) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def gen_module_command(command: ModuleCommand, ctx: Context): | 
					
						
							|  |  |  |     if ctx.has_unclosed: | 
					
						
							|  |  |  |         print("});") | 
					
						
							|  |  |  |     print( | 
					
						
							|  |  |  |         f"""describe("{command.file_name.stem}", () => {{
 | 
					
						
							|  |  |  | let _test = test; | 
					
						
							|  |  |  | let content, module; | 
					
						
							|  |  |  | try {{ | 
					
						
							|  |  |  | content = readBinaryWasmFile("Fixtures/SpecTests/{command.file_name}"); | 
					
						
							|  |  |  | module = parseWebAssemblyModule(content, globalImportObject); | 
					
						
							|  |  |  | }} catch (e) {{ | 
					
						
							|  |  |  | _test("parse", () => expect().fail(e)); | 
					
						
							|  |  |  | _test = test.skip; | 
					
						
							|  |  |  | _test.skip = test.skip; | 
					
						
							|  |  |  | }} | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     if command.name is not None: | 
					
						
							|  |  |  |         print(f'namedModules["{command.name}"] = module;') | 
					
						
							|  |  |  |     ctx.current_module_name = command.file_name.stem | 
					
						
							|  |  |  |     ctx.has_unclosed = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def gen_invalid(invalid: AssertInvalid, ctx: Context): | 
					
						
							| 
									
										
										
										
											2024-07-11 09:45:01 -07:00
										 |  |  |     # TODO: Remove this once the multiple memories proposal is standardized. | 
					
						
							|  |  |  |     # We support the multiple memories proposal, so spec-tests that check that | 
					
						
							|  |  |  |     # we don't do not make any sense to include right now. | 
					
						
							|  |  |  |     if invalid.message == "multiple memories": | 
					
						
							|  |  |  |         return | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     if ctx.has_unclosed: | 
					
						
							|  |  |  |         print("});") | 
					
						
							|  |  |  |         ctx.has_unclosed = False | 
					
						
							|  |  |  |     stem = Path(invalid.filename).stem | 
					
						
							|  |  |  |     print( | 
					
						
							|  |  |  |         f"""
 | 
					
						
							|  |  |  | describe("{stem}", () => {{ | 
					
						
							|  |  |  | let _test = test; | 
					
						
							|  |  |  | _test("parse of {stem} (line {invalid.line})", () => {{ | 
					
						
							|  |  |  | content = readBinaryWasmFile("Fixtures/SpecTests/{invalid.filename}"); | 
					
						
							|  |  |  | expect(() => parseWebAssemblyModule(content, globalImportObject)).toThrow(Error, "{invalid.message}"); | 
					
						
							|  |  |  | }}); | 
					
						
							|  |  |  | }});"""
 | 
					
						
							| 
									
										
										
										
											2021-05-07 10:02:58 +04:30
										 |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  | def gen_pretty_expect(expr: str, got: str, expect: str): | 
					
						
							| 
									
										
										
										
											2025-05-22 07:30:45 -04:00
										 |  |  |     print(f"if (!{expr}) {{ expect().fail(`Failed with ${{{got}}}, expected {expect}`); }}") | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | def gen_invoke( | 
					
						
							|  |  |  |     line: int, | 
					
						
							|  |  |  |     invoke: Invoke, | 
					
						
							|  |  |  |     result: WasmValue | None, | 
					
						
							|  |  |  |     ctx: Context, | 
					
						
							|  |  |  |     *, | 
					
						
							|  |  |  |     fail_msg: str | None = None, | 
					
						
							|  |  |  | ): | 
					
						
							| 
									
										
										
										
											2024-07-11 09:31:28 -07:00
										 |  |  |     if not ctx.has_unclosed: | 
					
						
							|  |  |  |         print(f'describe("inline (line {line}))", () => {{\nlet _test = test;\n') | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     module = "module" | 
					
						
							|  |  |  |     if invoke.module is not None: | 
					
						
							|  |  |  |         module = f'namedModules["{invoke.module}"]' | 
					
						
							| 
									
										
										
										
											2025-05-22 07:30:45 -04:00
										 |  |  |     utf8 = str(invoke.field.encode("utf8"))[2:-1].replace("\\'", "'").replace("`", "${'`'}") | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     print( | 
					
						
							| 
									
										
										
										
											2024-07-11 09:31:28 -07:00
										 |  |  |         f"""_test(`execution of {ctx.current_module_name}: {utf8} (line {line})`, () => {{
 | 
					
						
							|  |  |  | let _field = {module}.getExport(decodeURIComponent(escape(`{utf8}`))); | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | expect(_field).not.toBeUndefined();"""
 | 
					
						
							| 
									
										
										
										
											2021-05-07 10:02:58 +04:30
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     if fail_msg is not None: | 
					
						
							|  |  |  |         print(f'expect(() => {module}.invoke(_field)).toThrow(Error, "{fail_msg}");') | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print(f"let _result = {module}.invoke(_field, {gen_args(invoke.args)});") | 
					
						
							|  |  |  |     if result is not None: | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |         gen_result = gen_value_result(result) | 
					
						
							|  |  |  |         match gen_result: | 
					
						
							|  |  |  |             case str(): | 
					
						
							|  |  |  |                 print(f"expect(_result).toBe({gen_result});") | 
					
						
							|  |  |  |             case ArithmeticNan(): | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  |                 gen_pretty_expect( | 
					
						
							|  |  |  |                     f"isArithmeticNaN{gen_result.num_bits}(_result)", | 
					
						
							|  |  |  |                     "_result", | 
					
						
							|  |  |  |                     "nan:arithmetic", | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |                 ) | 
					
						
							|  |  |  |             case CanonicalNan(): | 
					
						
							| 
									
										
										
										
											2024-07-11 19:15:12 -07:00
										 |  |  |                 gen_pretty_expect( | 
					
						
							|  |  |  |                     f"isCanonicalNaN{gen_result.num_bits}(_result)", | 
					
						
							|  |  |  |                     "_result", | 
					
						
							|  |  |  |                     "nan:canonical", | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             case GeneratedVector(): | 
					
						
							|  |  |  |                 if gen_result.num_bits == 64: | 
					
						
							|  |  |  |                     array = "new BigUint64Array(_result)" | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     array = f"new Uint{gen_result.num_bits}Array(_result)" | 
					
						
							|  |  |  |                 gen_pretty_expect( | 
					
						
							|  |  |  |                     f"testSIMDVector({gen_result.repr}, {array})", | 
					
						
							|  |  |  |                     array, | 
					
						
							|  |  |  |                     gen_result.repr, | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     print("});") | 
					
						
							| 
									
										
										
										
											2024-07-11 09:31:28 -07:00
										 |  |  |     if not ctx.has_unclosed: | 
					
						
							|  |  |  |         print("});") | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def gen_get(line: int, get: Get, result: WasmValue | None, ctx: Context): | 
					
						
							|  |  |  |     module = "module" | 
					
						
							|  |  |  |     if get.module is not None: | 
					
						
							|  |  |  |         module = f'namedModules["{get.module}"]' | 
					
						
							|  |  |  |     print( | 
					
						
							|  |  |  |         f"""_test("execution of {ctx.current_module_name}: get-{get.field} (line {line})", () => {{
 | 
					
						
							|  |  |  | let _field = {module}.getExport("{get.field}");"""
 | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     if result is not None: | 
					
						
							| 
									
										
										
										
											2024-07-11 12:33:49 -07:00
										 |  |  |         print(f"expect(_field).toBe({gen_value_result(result)});") | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     print("});") | 
					
						
							| 
									
										
										
										
											2021-05-07 10:02:58 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | def gen_register(register: Register, _: Context): | 
					
						
							|  |  |  |     module = "module" | 
					
						
							|  |  |  |     if register.name is not None: | 
					
						
							| 
									
										
										
										
											2024-06-10 15:42:05 -07:00
										 |  |  |         module = f'namedModules["{register.name}"]' | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     print(f'globalImportObject["{register.as_}"] = {module};') | 
					
						
							| 
									
										
										
										
											2021-06-21 20:10:41 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | def gen_command(command: Command, ctx: Context): | 
					
						
							|  |  |  |     match command: | 
					
						
							|  |  |  |         case ModuleCommand(): | 
					
						
							|  |  |  |             gen_module_command(command, ctx) | 
					
						
							|  |  |  |         case ActionCommand(): | 
					
						
							|  |  |  |             if isinstance(command.action, Invoke): | 
					
						
							|  |  |  |                 gen_invoke(command.line, command.action, None, ctx) | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2025-05-22 07:30:45 -04:00
										 |  |  |                 raise GenerateException(f"Not implemented: top-level {type(command.action)}") | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |         case AssertInvalid(): | 
					
						
							|  |  |  |             gen_invalid(command, ctx) | 
					
						
							|  |  |  |         case Register(): | 
					
						
							|  |  |  |             gen_register(command, ctx) | 
					
						
							|  |  |  |         case AssertReturn(): | 
					
						
							|  |  |  |             match command.action: | 
					
						
							|  |  |  |                 case Invoke(): | 
					
						
							|  |  |  |                     gen_invoke(command.line, command.action, command.expected, ctx) | 
					
						
							|  |  |  |                 case Get(): | 
					
						
							|  |  |  |                     gen_get(command.line, command.action, command.expected, ctx) | 
					
						
							|  |  |  |         case AssertTrap(): | 
					
						
							|  |  |  |             if not isinstance(command.action, Invoke): | 
					
						
							|  |  |  |                 raise GenerateException(f"Not implemented: {type(command.action)}") | 
					
						
							| 
									
										
										
										
											2025-05-22 07:30:45 -04:00
										 |  |  |             gen_invoke(command.line, command.action, None, ctx, fail_msg=command.messsage) | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def generate(description: WastDescription): | 
					
						
							|  |  |  |     print("let globalImportObject = {};\nlet namedModules = {};\n") | 
					
						
							|  |  |  |     ctx = Context("", False) | 
					
						
							|  |  |  |     for command in description.commands: | 
					
						
							|  |  |  |         gen_command(command, ctx) | 
					
						
							|  |  |  |     if ctx.has_unclosed: | 
					
						
							|  |  |  |         print("});") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def clean_up(path: Path): | 
					
						
							|  |  |  |     for file in path.iterdir(): | 
					
						
							|  |  |  |         if file.suffix in ("wat", "json"): | 
					
						
							|  |  |  |             file.unlink() | 
					
						
							| 
									
										
										
										
											2021-08-30 17:31:06 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-07 10:02:58 +04:30
										 |  |  | def main(): | 
					
						
							| 
									
										
										
										
											2024-06-08 06:56:58 -07:00
										 |  |  |     input_path = Path(sys.argv[1]) | 
					
						
							|  |  |  |     name = sys.argv[2] | 
					
						
							|  |  |  |     out_path = Path(sys.argv[3]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     description = make_description(input_path, name, out_path) | 
					
						
							|  |  |  |     generate(description) | 
					
						
							|  |  |  |     clean_up(out_path) | 
					
						
							| 
									
										
										
										
											2021-05-07 10:02:58 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     main() |