diff --git a/.gitignore b/.gitignore index c504d93..ea79b7d 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,4 @@ prof/** # WASM Spec submodule ./spec/** +./tests/spec/fixtures/** diff --git a/setup.py b/setup.py index 02e8aa0..d9e2c94 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ "numpy>=1.16.0,<2", "toolz>0.9.0,<1;implementation_name=='pypy'", "cytoolz>=0.9.0,<1.0.0;implementation_name=='cpython'", + "parsimonious>=0.8.1,<0.9", ], setup_requires=['setuptools-markdown'], python_requires='>=3.5, <4', diff --git a/tests/core/parsers/test_blocktype_parser.py b/tests/core/parsers/test_blocktype_parser.py index 4c10933..49c420b 100644 --- a/tests/core/parsers/test_blocktype_parser.py +++ b/tests/core/parsers/test_blocktype_parser.py @@ -8,7 +8,7 @@ from wasm.exceptions import ( ParseError, ) -from wasm.parsers.blocks import ( +from wasm.binary.blocks import ( parse_blocktype, ) diff --git a/tests/core/parsers/test_floating_point_parsers.py b/tests/core/parsers/test_floating_point_parsers.py index 9176328..d027ab6 100644 --- a/tests/core/parsers/test_floating_point_parsers.py +++ b/tests/core/parsers/test_floating_point_parsers.py @@ -8,7 +8,7 @@ ) import pytest -from wasm.parsers.floats import ( +from wasm.binary.floats import ( parse_f32, parse_f64, ) diff --git a/tests/core/parsers/test_integer_parsers.py b/tests/core/parsers/test_integer_parsers.py index 1c56225..936ca88 100644 --- a/tests/core/parsers/test_integer_parsers.py +++ b/tests/core/parsers/test_integer_parsers.py @@ -5,7 +5,7 @@ from wasm.exceptions import ( MalformedModule, ) -from wasm.parsers.integers import ( +from wasm.binary.integers import ( parse_i32, parse_i64, parse_s32, diff --git a/tests/core/parsers/test_leb128_parsers.py b/tests/core/parsers/test_leb128_parsers.py index f5cd9fd..00676c0 100644 --- a/tests/core/parsers/test_leb128_parsers.py +++ b/tests/core/parsers/test_leb128_parsers.py @@ -2,7 +2,7 @@ import pytest -from wasm.parsers.leb128 import ( +from wasm.binary.leb128 import ( parse_signed_leb128, parse_unsigned_leb128, ) diff --git a/tests/core/parsers/test_numeric_parsers.py b/tests/core/parsers/test_numeric_parsers.py index 754c63c..b505b87 100644 --- a/tests/core/parsers/test_numeric_parsers.py +++ b/tests/core/parsers/test_numeric_parsers.py @@ -8,7 +8,7 @@ from wasm.opcodes import ( BinaryOpcode, ) -from wasm.parsers.numeric import ( +from wasm.binary.numeric import ( parse_numeric_constant_instruction, ) diff --git a/tests/core/sexpressions/test_memory_type_parsing.py b/tests/core/sexpressions/test_memory_type_parsing.py new file mode 100644 index 0000000..afe9e7e --- /dev/null +++ b/tests/core/sexpressions/test_memory_type_parsing.py @@ -0,0 +1,15 @@ +import pytest + +from wasm.text import parse +from wasm.datatypes import MemoryType + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + #('(memory 1)', MemoryType(1)), + ), +) +def test_sexpression_memory_type_parsing(sexpr, expected): + actual = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_block_instruction_parsing.py b/tests/core/sexpressions/test_sexpression_block_instruction_parsing.py new file mode 100644 index 0000000..e04e1e2 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_block_instruction_parsing.py @@ -0,0 +1,45 @@ +import pytest + +import numpy + +from wasm.text import parse +from wasm.datatypes import ValType +from wasm.opcodes import BinaryOpcode +from wasm.instructions.control import ( + Block, + End, + Nop, + Return, +) +from wasm.instructions.numeric import ( + UnOp, + I32Const, +) +from wasm.text.ir import NamedBlock + + +i32 = ValType.i32 + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(block)", Block((), End.as_tail())), + ("(block $blk)", NamedBlock('$blk', Block((), End.as_tail()))), + ("(block (nop))", Block((), (Nop(),))), + ( + "(block (result i32) (i32.ctz (return (i32.const 1))))", + Block( + (i32,), + ( + I32Const(numpy.uint32(1)), + UnOp.from_opcode(BinaryOpcode.I32_CTZ), + Return(), + ) + ), + ), + ), +) +def test_sexpression_block_instructions_parsing(sexpr, expected): + actual = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_br_ops_parsing.py b/tests/core/sexpressions/test_sexpression_br_ops_parsing.py new file mode 100644 index 0000000..2ce38cc --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_br_ops_parsing.py @@ -0,0 +1,43 @@ +import pytest + +from wasm.text import parse +from wasm.datatypes import ( + LabelIdx, +) +from wasm.instructions.control import ( + Br, + BrIf, + BrTable, +) +from wasm.text.ir import ( + UnresolvedBr, + UnresolvedBrIf, + UnresolvedBrTable, + UnresolvedLabelIdx, +) + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(br 0)", Br(LabelIdx(0))), + ("(br $i)", UnresolvedBr(UnresolvedLabelIdx('$i'))), + ("(br_if 0)", BrIf(LabelIdx(0))), + ("(br_if $i)", UnresolvedBrIf(UnresolvedLabelIdx('$i'))), + ("(br_table 1 2 3)", BrTable((LabelIdx(1), LabelIdx(2)), LabelIdx(3))), + ( + "(br_table 1 2 $default)", + UnresolvedBrTable((LabelIdx(1), LabelIdx(2)), UnresolvedLabelIdx('$default')), + ), + ( + "(br_table 1 2 $three 4)", + UnresolvedBrTable( + (LabelIdx(1), LabelIdx(2), UnresolvedLabelIdx('$three')), + LabelIdx(4), + ), + ), + ), +) +def test_sexpression_br_instructions_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_call_op_parsing.py b/tests/core/sexpressions/test_sexpression_call_op_parsing.py new file mode 100644 index 0000000..50b53ea --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_call_op_parsing.py @@ -0,0 +1,58 @@ +import pytest + +from wasm.text import parse +from wasm.instructions.control import ( + Call, +) +from wasm.datatypes import ( + ValType, + FunctionIdx, +) +from wasm.text.ir import ( + Param, + UnresolvedFunctionType, + UnresolvedCallIndirect, + UnresolvedTypeIdx, + UnresolvedFunctionIdx, + UnresolvedCall, +) + +i32 = ValType.i32 +i64 = ValType.i64 +f32 = ValType.f32 +f64 = ValType.f64 + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(call_indirect (result))", UnresolvedCallIndirect(UnresolvedFunctionType((), ()))), + ( + "(call_indirect (param i64))", + UnresolvedCallIndirect(UnresolvedFunctionType((Param(i64),), ())), + ), + ( + "(call_indirect (param i64) (result i32))", + UnresolvedCallIndirect(UnresolvedFunctionType((Param(i64),), (i32,))), + ), + ( + "(call_indirect (type $check))", + UnresolvedCallIndirect(UnresolvedTypeIdx('$check')), + ), + ), +) +def test_sexpression_call_indirect_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(call $func-name)", UnresolvedCall(UnresolvedFunctionIdx('$func-name'))), + ("(call 1)", Call(FunctionIdx(1))), + ), +) +def test_sexpression_call_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_func_type_parsing.py b/tests/core/sexpressions/test_sexpression_func_type_parsing.py new file mode 100644 index 0000000..177801c --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_func_type_parsing.py @@ -0,0 +1,43 @@ +import pytest + +from wasm.text import parse +from wasm.datatypes import ( + ValType, +) +from wasm.text.ir import ( + Param, + UnresolvedFunctionType, +) + +i32 = ValType.i32 +i64 = ValType.i64 +f32 = ValType.f32 +f64 = ValType.f64 + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(func (param))", UnresolvedFunctionType((), ())), + ("(func (param) (param))", UnresolvedFunctionType((), ())), + ("(func (param i32))", UnresolvedFunctionType((Param(i32),), ())), + ("(func (param $x i32))", UnresolvedFunctionType((Param(i32, '$x'),), ())), + ( + "(func (param i32 f64 i64))", + UnresolvedFunctionType((Param(i32), Param(f64), Param(i64)), ()), + ), + ("(func (param i32) (param f64))", UnresolvedFunctionType((Param(i32), Param(f64)), ())), + ( + "(func (param i32 f32) (param $x i64) (param) (param i32 f64))", + UnresolvedFunctionType( + (Param(i32), Param(f32), Param(i64, '$x'), Param(i32), Param(f64)), + (), + ), + ), + + # ("(func (result i32) (unreachable)) # TODO: this is not a raw functype but rather a `function` definition. + ), +) +def test_sexpression_parametric_instruction_parsing(sexpr, expected): + actual = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_locals_parsing.py b/tests/core/sexpressions/test_sexpression_locals_parsing.py new file mode 100644 index 0000000..ae5d114 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_locals_parsing.py @@ -0,0 +1,33 @@ +import pytest + +from wasm.text import parse +from wasm.text.ir import Local +from wasm.datatypes import ValType + + +i32 = ValType.i32 +i64 = ValType.i64 +f32 = ValType.f32 +f64 = ValType.f64 + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + # unnamed + ('(local i32)', (Local(i32),)), + ('(local i64)', (Local(i64),)), + ('(local f32)', (Local(f32),)), + ('(local f64)', (Local(f64),)), + ('(local i32 i64)', (Local(i32), Local(i64))), + ('(local f32 f64)', (Local(f32), Local(f64))), + ('(local f32 f64 i32 i64)', (Local(f32), Local(f64), Local(i32), Local(i64))), + # named + ('(local $i i32)', (Local(i32, '$i'),)), + # multi + ('(local f32 f64)\n(local $i i32)', (Local(f32), Local(f64), Local(i32, '$i'))), + ), +) +def test_sexpression_locals_parsing(sexpr, expected): + actual = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_memory_op_parsing.py b/tests/core/sexpressions/test_sexpression_memory_op_parsing.py new file mode 100644 index 0000000..6591de6 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_memory_op_parsing.py @@ -0,0 +1,55 @@ +import pytest + +from wasm.text import parse +from wasm.instructions.memory import ( + MemoryArg, + MemoryOp, + MemorySize, + MemoryGrow, +) +from wasm.opcodes import BinaryOpcode + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(i32.load)", MemoryOp.from_opcode(BinaryOpcode.I32_LOAD, MemoryArg(0, 4))), + ("(i64.load)", MemoryOp.from_opcode(BinaryOpcode.I64_LOAD, MemoryArg(0, 8))), + ("(f32.load)", MemoryOp.from_opcode(BinaryOpcode.F32_LOAD, MemoryArg(0, 4))), + ("(f64.load)", MemoryOp.from_opcode(BinaryOpcode.F64_LOAD, MemoryArg(0, 8))), + ("(i32.load8_s)", MemoryOp.from_opcode(BinaryOpcode.I32_LOAD8_S, MemoryArg(0, 1))), + ("(i32.load8_u)", MemoryOp.from_opcode(BinaryOpcode.I32_LOAD8_U, MemoryArg(0, 1))), + ("(i32.load16_s)", MemoryOp.from_opcode(BinaryOpcode.I32_LOAD16_S, MemoryArg(0, 2))), + ("(i32.load16_u)", MemoryOp.from_opcode(BinaryOpcode.I32_LOAD16_U, MemoryArg(0, 2))), + ("(i64.load8_s)", MemoryOp.from_opcode(BinaryOpcode.I64_LOAD8_S, MemoryArg(0, 1))), + ("(i64.load8_u)", MemoryOp.from_opcode(BinaryOpcode.I64_LOAD8_U, MemoryArg(0, 1))), + ("(i64.load16_s)", MemoryOp.from_opcode(BinaryOpcode.I64_LOAD16_S, MemoryArg(0, 2))), + ("(i64.load16_u)", MemoryOp.from_opcode(BinaryOpcode.I64_LOAD16_U, MemoryArg(0, 2))), + ("(i64.load32_s)", MemoryOp.from_opcode(BinaryOpcode.I64_LOAD32_S, MemoryArg(0, 4))), + ("(i64.load32_u)", MemoryOp.from_opcode(BinaryOpcode.I64_LOAD32_U, MemoryArg(0, 4))), + ("(i32.store)", MemoryOp.from_opcode(BinaryOpcode.I32_STORE, MemoryArg(0, 4))), + ("(i64.store)", MemoryOp.from_opcode(BinaryOpcode.I64_STORE, MemoryArg(0, 8))), + ("(f32.store)", MemoryOp.from_opcode(BinaryOpcode.F32_STORE, MemoryArg(0, 4))), + ("(f64.store)", MemoryOp.from_opcode(BinaryOpcode.F64_STORE, MemoryArg(0, 8))), + ("(i32.store8)", MemoryOp.from_opcode(BinaryOpcode.I32_STORE8, MemoryArg(0, 1))), + ("(i32.store16)", MemoryOp.from_opcode(BinaryOpcode.I32_STORE16, MemoryArg(0, 2))), + ("(i64.store8)", MemoryOp.from_opcode(BinaryOpcode.I64_STORE8, MemoryArg(0, 1))), + ("(i64.store16)", MemoryOp.from_opcode(BinaryOpcode.I64_STORE16, MemoryArg(0, 2))), + ("(i64.store32)", MemoryOp.from_opcode(BinaryOpcode.I64_STORE32, MemoryArg(0, 4))), + ), +) +def test_sexpression_memory_load_and_store_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(memory.size)", MemorySize()), + ("(memory.grow)", MemoryGrow()), + ), +) +def test_sexpression_memory_size_and_grow_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_nop_op_parsing.py b/tests/core/sexpressions/test_sexpression_nop_op_parsing.py new file mode 100644 index 0000000..e443703 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_nop_op_parsing.py @@ -0,0 +1,17 @@ +import pytest + +from wasm.text import parse +from wasm.instructions.control import ( + Nop +) + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(nop)", Nop()), + ), +) +def test_sexpression_nop_instructions_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_numeric_op_parsing.py b/tests/core/sexpressions/test_sexpression_numeric_op_parsing.py new file mode 100644 index 0000000..2659488 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_numeric_op_parsing.py @@ -0,0 +1,143 @@ +import pytest + +from wasm.text import parse +from wasm.datatypes import ValType +from wasm.instructions.numeric import ( + Reinterpret, + Demote, + Promote, + Convert, + TestOp as _TestOp, # pytest tries to collect it otherwise + BinOp, + I32Const, + I64Const, + F32Const, + F64Const, + RelOp, + Wrap, + UnOp, + Extend, + Truncate, +) +from wasm.opcodes import BinaryOpcode + + +i32 = ValType.i32 +i64 = ValType.i64 +f32 = ValType.f32 +f64 = ValType.f64 + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + # simple + ('(i32.const 1234)', I32Const(1234)), + ('(i64.const 1234)', I64Const(1234)), + ('(f32.const 1234)', F32Const(1234)), + ('(f64.const 1234)', F64Const(1234)), + ), +) +def test_sexpression_numeric_constant_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected + + +UNOPS = tuple(op for op in BinaryOpcode if op.is_unop) +RELOPS = tuple(op for op in BinaryOpcode if op.is_relop) +BINOPS = tuple(op for op in BinaryOpcode if op.is_binop) +TESTOPS = tuple(op for op in BinaryOpcode if op.is_testop) +OP_PAIRS = ( + (UNOPS, UnOp), + (RELOPS, RelOp), + (BINOPS, BinOp), + (TESTOPS, _TestOp), +) + + +@pytest.mark.parametrize( + 'sexpr,expected', + tuple( + (f'({op.text})', instruction.from_opcode(op)) + for ops, instruction in OP_PAIRS + for op in ops + ), +) +def test_sexpression_unop_binop_relop_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ('(i32.wrap_i64)', Wrap()), + ('(i64.extend_i32_s)', Extend.from_opcode(BinaryOpcode.I64_EXTEND_S_I32)), + ('(i64.extend_i32_u)', Extend.from_opcode(BinaryOpcode.I64_EXTEND_U_I32)), + ), +) +def test_sexpression_wrap_and_extend_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ('(i32.trunc_f32_s)', Truncate.from_opcode(BinaryOpcode.I32_TRUNC_S_F32)), + ('(i32.trunc_f32_u)', Truncate.from_opcode(BinaryOpcode.I32_TRUNC_U_F64)), + ('(i32.trunc_f64_s)', Truncate.from_opcode(BinaryOpcode.I32_TRUNC_S_F32)), + ('(i32.trunc_f64_u)', Truncate.from_opcode(BinaryOpcode.I32_TRUNC_U_F64)), + ('(i64.trunc_f32_s)', Truncate.from_opcode(BinaryOpcode.I64_TRUNC_S_F32)), + ('(i64.trunc_f32_u)', Truncate.from_opcode(BinaryOpcode.I64_TRUNC_U_F64)), + ('(i64.trunc_f64_s)', Truncate.from_opcode(BinaryOpcode.I64_TRUNC_S_F32)), + ('(i64.trunc_f64_u)', Truncate.from_opcode(BinaryOpcode.I64_TRUNC_U_F64)), + ) +) +def test_sexpression_trunc_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ('(f32.convert_i32_s)', Convert.from_opcode(BinaryOpcode.F32_CONVERT_S_I32)), + ('(f32.convert_i32_u)', Convert.from_opcode(BinaryOpcode.F32_CONVERT_U_I32)), + ('(f32.convert_i64_s)', Convert.from_opcode(BinaryOpcode.F32_CONVERT_S_I64)), + ('(f32.convert_i64_u)', Convert.from_opcode(BinaryOpcode.F32_CONVERT_U_I64)), + ('(f64.convert_i32_s)', Convert.from_opcode(BinaryOpcode.F64_CONVERT_S_I32)), + ('(f64.convert_i32_u)', Convert.from_opcode(BinaryOpcode.F64_CONVERT_U_I32)), + ('(f64.convert_i64_s)', Convert.from_opcode(BinaryOpcode.F64_CONVERT_S_I64)), + ('(f64.convert_i64_u)', Convert.from_opcode(BinaryOpcode.F64_CONVERT_U_I64)), + ) +) +def test_sexpression_convert_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ('(f32.demote_f64)', Demote()), + ('(f64.promote_f32)', Promote()), + ), +) +def test_sexpression_demote_and_promote_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ('(i32.reinterpret_f32)', Reinterpret.from_opcode(BinaryOpcode.I32_REINTERPRET_F32)), + ('(i64.reinterpret_f64)', Reinterpret.from_opcode(BinaryOpcode.I64_REINTERPRET_F64)), + ('(f32.reinterpret_i32)', Reinterpret.from_opcode(BinaryOpcode.F32_REINTERPRET_I32)), + ('(f64.reinterpret_i64)', Reinterpret.from_opcode(BinaryOpcode.F64_REINTERPRET_I64)), + ) +) +def test_sexpression_reinterpret_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_param_parsing.py b/tests/core/sexpressions/test_sexpression_param_parsing.py new file mode 100644 index 0000000..20f8533 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_param_parsing.py @@ -0,0 +1,28 @@ +import pytest + +from wasm.text import parse +from wasm.datatypes import ValType +from wasm.text.ir import Param + + +i32 = ValType.i32 +i64 = ValType.i64 +f32 = ValType.f32 +f64 = ValType.f64 + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + # simple + ('(param)', ()), + ('(param i32)', (Param(i32),)), + # multiple + ('(param i32 i64)', (Param(i32), Param(i64),)), + # named + ('(param $x i32)', (Param(i32, '$x'),)), + ), +) +def test_sexpression_param_parsing(sexpr, expected): + actual = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_parametric_op_parsing.py b/tests/core/sexpressions/test_sexpression_parametric_op_parsing.py new file mode 100644 index 0000000..01f5149 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_parametric_op_parsing.py @@ -0,0 +1,19 @@ +import pytest + +from wasm.text import parse +from wasm.instructions.parametric import ( + Drop, + Select, +) + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(drop)", Drop()), + ("(select)", Select()), + ), +) +def test_sexpression_parametric_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_results_parsing.py b/tests/core/sexpressions/test_sexpression_results_parsing.py new file mode 100644 index 0000000..204e4cb --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_results_parsing.py @@ -0,0 +1,26 @@ +import pytest + +from wasm.text import parse +from wasm.datatypes import ValType + + +i32 = ValType.i32 +i64 = ValType.i64 +f32 = ValType.f32 +f64 = ValType.f64 + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + # simple + ('(result)', ()), + ('(result i32)', (i32,)), + ('(result i32 i64)', (i32, i64)), + # many + ('(result i32) (result i64)', (i32, i64)), + ), +) +def test_sexpression_results_parsing(sexpr, expected): + actual = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_return_op_parsing.py b/tests/core/sexpressions/test_sexpression_return_op_parsing.py new file mode 100644 index 0000000..b44c24b --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_return_op_parsing.py @@ -0,0 +1,19 @@ +import pytest + +import numpy + +from wasm.text import parse +from wasm.instructions.control import Return +from wasm.instructions.numeric import I32Const + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(return)", Return()), + ("(return (i32.const 1))", (I32Const(numpy.uint32(1)), Return())), + ), +) +def test_sexpression_return_instruction_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_unreachable_op_parsing.py b/tests/core/sexpressions/test_sexpression_unreachable_op_parsing.py new file mode 100644 index 0000000..3f58385 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_unreachable_op_parsing.py @@ -0,0 +1,17 @@ +import pytest + +from wasm.text import parse +from wasm.instructions.control import ( + Unreachable, +) + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(unreachable)", Unreachable()), + ), +) +def test_sexpression_unreachable_instructions_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/core/sexpressions/test_sexpression_variable_op_parsing.py b/tests/core/sexpressions/test_sexpression_variable_op_parsing.py new file mode 100644 index 0000000..8b73a52 --- /dev/null +++ b/tests/core/sexpressions/test_sexpression_variable_op_parsing.py @@ -0,0 +1,33 @@ +import pytest + +from wasm.text import parse +from wasm.text.ir import UnresolvedVariableOp +from wasm.datatypes import ( + LocalIdx, + GlobalIdx, +) +from wasm.instructions.variable import ( + LocalOp, + GlobalOp, +) +from wasm.opcodes import BinaryOpcode + + +@pytest.mark.parametrize( + 'sexpr,expected', + ( + ("(local.get $i)", UnresolvedVariableOp(BinaryOpcode.GET_LOCAL, '$i')), + ("(local.get 1)", LocalOp.from_opcode(BinaryOpcode.GET_LOCAL, LocalIdx(1))), + ("(local.set $i)", UnresolvedVariableOp(BinaryOpcode.SET_LOCAL, '$i')), + ("(local.set 1)", LocalOp.from_opcode(BinaryOpcode.SET_LOCAL, LocalIdx(1))), + ("(local.tee $i)", UnresolvedVariableOp(BinaryOpcode.TEE_LOCAL, '$i')), + ("(local.tee 1)", LocalOp.from_opcode(BinaryOpcode.TEE_LOCAL, LocalIdx(1))), + ("(global.get $i)", UnresolvedVariableOp(BinaryOpcode.GET_GLOBAL, '$i')), + ("(global.get 1)", GlobalOp.from_opcode(BinaryOpcode.GET_GLOBAL, GlobalIdx(1))), + ("(global.set $i)", UnresolvedVariableOp(BinaryOpcode.SET_GLOBAL, '$i')), + ("(global.set 1)", GlobalOp.from_opcode(BinaryOpcode.SET_GLOBAL, GlobalIdx(1))), + ), +) +def test_sexpression_local_variable_parsing(sexpr, expected): + actual, = parse(sexpr) + assert actual == expected diff --git a/tests/spec/test_sexpr_spec.py b/tests/spec/test_sexpr_spec.py new file mode 100644 index 0000000..d5e0834 --- /dev/null +++ b/tests/spec/test_sexpr_spec.py @@ -0,0 +1,36 @@ +from pathlib import Path +from typing import ( + Tuple, +) + +from wasm.text.grammar import ( + parse, +) + + +BASE_DIR = Path(__file__).parent.parent.parent.parent +FIXTURES_DIR = BASE_DIR / "spec" / "test" / "core" + + +def find_wast_fixture_files(fixtures_base_dir: Path) -> Tuple[Path, ...]: + all_fixture_paths = tuple(fixtures_base_dir.glob('**/*.wast')) + return all_fixture_paths + + +def idfn(fixture_path: Path) -> str: + return str(fixture_path.resolve()) + + +def pytest_generate_tests(metafunc): + all_fixture_paths = find_wast_fixture_files(FIXTURES_DIR) + + if len(all_fixture_paths) == 0: + raise Exception("Invariant: found zero fixtures") + + metafunc.parametrize('fixture_path', all_fixture_paths, ids=idfn) + + +def test_base_sexpression_parser(fixture_path): + raw_sexp = fixture_path.read_text() + sexp = parse(raw_sexp) + assert sexp diff --git a/tox.ini b/tox.ini index f16ab1e..a5135db 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ envlist= combine_as_imports=True force_sort_within_sections=True include_trailing_comma=True -known_third_party=hypothesis,pytest,numpy +known_third_party=hypothesis,pytest,numpy,parsimonious known_first_party=wasm line_length=21 multi_line_output=3 diff --git a/wasm/parsers/__init__.py b/wasm/binary/__init__.py similarity index 100% rename from wasm/parsers/__init__.py rename to wasm/binary/__init__.py diff --git a/wasm/parsers/blocks.py b/wasm/binary/blocks.py similarity index 100% rename from wasm/parsers/blocks.py rename to wasm/binary/blocks.py diff --git a/wasm/parsers/byte.py b/wasm/binary/byte.py similarity index 100% rename from wasm/parsers/byte.py rename to wasm/binary/byte.py diff --git a/wasm/parsers/code.py b/wasm/binary/code.py similarity index 100% rename from wasm/parsers/code.py rename to wasm/binary/code.py diff --git a/wasm/parsers/control.py b/wasm/binary/control.py similarity index 100% rename from wasm/parsers/control.py rename to wasm/binary/control.py diff --git a/wasm/parsers/data_segment.py b/wasm/binary/data_segment.py similarity index 100% rename from wasm/parsers/data_segment.py rename to wasm/binary/data_segment.py diff --git a/wasm/parsers/element_segment.py b/wasm/binary/element_segment.py similarity index 100% rename from wasm/parsers/element_segment.py rename to wasm/binary/element_segment.py diff --git a/wasm/parsers/exports.py b/wasm/binary/exports.py similarity index 100% rename from wasm/parsers/exports.py rename to wasm/binary/exports.py diff --git a/wasm/parsers/expressions.py b/wasm/binary/expressions.py similarity index 100% rename from wasm/parsers/expressions.py rename to wasm/binary/expressions.py diff --git a/wasm/parsers/floats.py b/wasm/binary/floats.py similarity index 100% rename from wasm/parsers/floats.py rename to wasm/binary/floats.py diff --git a/wasm/parsers/functions.py b/wasm/binary/functions.py similarity index 100% rename from wasm/parsers/functions.py rename to wasm/binary/functions.py diff --git a/wasm/parsers/globals.py b/wasm/binary/globals.py similarity index 100% rename from wasm/parsers/globals.py rename to wasm/binary/globals.py diff --git a/wasm/parsers/imports.py b/wasm/binary/imports.py similarity index 100% rename from wasm/parsers/imports.py rename to wasm/binary/imports.py diff --git a/wasm/parsers/indices.py b/wasm/binary/indices.py similarity index 100% rename from wasm/parsers/indices.py rename to wasm/binary/indices.py diff --git a/wasm/parsers/instructions.py b/wasm/binary/instructions.py similarity index 100% rename from wasm/parsers/instructions.py rename to wasm/binary/instructions.py diff --git a/wasm/parsers/integers.py b/wasm/binary/integers.py similarity index 100% rename from wasm/parsers/integers.py rename to wasm/binary/integers.py diff --git a/wasm/parsers/leb128.py b/wasm/binary/leb128.py similarity index 100% rename from wasm/parsers/leb128.py rename to wasm/binary/leb128.py diff --git a/wasm/parsers/limits.py b/wasm/binary/limits.py similarity index 100% rename from wasm/parsers/limits.py rename to wasm/binary/limits.py diff --git a/wasm/parsers/magic.py b/wasm/binary/magic.py similarity index 100% rename from wasm/parsers/magic.py rename to wasm/binary/magic.py diff --git a/wasm/parsers/memory.py b/wasm/binary/memory.py similarity index 100% rename from wasm/parsers/memory.py rename to wasm/binary/memory.py diff --git a/wasm/parsers/module.py b/wasm/binary/module.py similarity index 100% rename from wasm/parsers/module.py rename to wasm/binary/module.py diff --git a/wasm/parsers/mutability.py b/wasm/binary/mutability.py similarity index 100% rename from wasm/parsers/mutability.py rename to wasm/binary/mutability.py diff --git a/wasm/parsers/null.py b/wasm/binary/null.py similarity index 100% rename from wasm/parsers/null.py rename to wasm/binary/null.py diff --git a/wasm/parsers/numeric.py b/wasm/binary/numeric.py similarity index 90% rename from wasm/parsers/numeric.py rename to wasm/binary/numeric.py index 68d0ab3..da5d7eb 100644 --- a/wasm/parsers/numeric.py +++ b/wasm/binary/numeric.py @@ -59,13 +59,13 @@ def parse_numeric_constant_instruction(opcode: BinaryOpcode, Parse a single CONST Numeric instruction. """ if opcode is BinaryOpcode.I32_CONST: - return I32Const.from_opcode(opcode, parse_i32(stream)) + return I32Const(parse_i32(stream)) elif opcode is BinaryOpcode.I64_CONST: - return I64Const.from_opcode(opcode, parse_i64(stream)) + return I64Const(parse_i64(stream)) elif opcode is BinaryOpcode.F32_CONST: - return F32Const.from_opcode(opcode, parse_f32(stream)) + return F32Const(parse_f32(stream)) elif opcode is BinaryOpcode.F64_CONST: - return F64Const.from_opcode(opcode, parse_f64(stream)) + return F64Const(parse_f64(stream)) else: raise Exception("Invariant") diff --git a/wasm/parsers/parametric.py b/wasm/binary/parametric.py similarity index 100% rename from wasm/parsers/parametric.py rename to wasm/binary/parametric.py diff --git a/wasm/parsers/sections.py b/wasm/binary/sections.py similarity index 100% rename from wasm/parsers/sections.py rename to wasm/binary/sections.py diff --git a/wasm/parsers/size.py b/wasm/binary/size.py similarity index 100% rename from wasm/parsers/size.py rename to wasm/binary/size.py diff --git a/wasm/parsers/tables.py b/wasm/binary/tables.py similarity index 100% rename from wasm/parsers/tables.py rename to wasm/binary/tables.py diff --git a/wasm/parsers/text.py b/wasm/binary/text.py similarity index 100% rename from wasm/parsers/text.py rename to wasm/binary/text.py diff --git a/wasm/parsers/valtype.py b/wasm/binary/valtype.py similarity index 100% rename from wasm/parsers/valtype.py rename to wasm/binary/valtype.py diff --git a/wasm/parsers/variable.py b/wasm/binary/variable.py similarity index 100% rename from wasm/parsers/variable.py rename to wasm/binary/variable.py diff --git a/wasm/parsers/vector.py b/wasm/binary/vector.py similarity index 100% rename from wasm/parsers/vector.py rename to wasm/binary/vector.py diff --git a/wasm/parsers/version.py b/wasm/binary/version.py similarity index 100% rename from wasm/parsers/version.py rename to wasm/binary/version.py diff --git a/wasm/datatypes/exports.py b/wasm/datatypes/exports.py index 4403db3..f40c56d 100644 --- a/wasm/datatypes/exports.py +++ b/wasm/datatypes/exports.py @@ -81,3 +81,11 @@ def is_function(self): @property def function_address(self) -> FunctionAddress: return cast(FunctionAddress, self.value) + + @property + def is_memory(self): + return isinstance(self.value, MemoryAddress) + + @property + def memory_address(self) -> MemoryAddress: + return cast(MemoryAddress, self.value) diff --git a/wasm/datatypes/memory.py b/wasm/datatypes/memory.py index 47e4e3c..45f71f6 100644 --- a/wasm/datatypes/memory.py +++ b/wasm/datatypes/memory.py @@ -22,7 +22,7 @@ class MemoryType(NamedTuple): https://webassembly.github.io/spec/core/bikeshed/index.html#memory-types%E2%91%A0 """ min: numpy.uint32 - max: Optional[numpy.uint32] + max: Optional[numpy.uint32] = None class Memory(NamedTuple): diff --git a/wasm/execution/runtime.py b/wasm/execution/runtime.py index 42b5cc1..1a61f6c 100644 --- a/wasm/execution/runtime.py +++ b/wasm/execution/runtime.py @@ -37,7 +37,7 @@ Unlinkable, ValidationError, ) -from wasm.parsers import ( +from wasm.binary import ( parse_module, ) from wasm.typing import ( diff --git a/wasm/instructions/control.py b/wasm/instructions/control.py index 848f717..d1b0efa 100644 --- a/wasm/instructions/control.py +++ b/wasm/instructions/control.py @@ -1,5 +1,6 @@ from typing import ( TYPE_CHECKING, + Any, NamedTuple, Sequence, Tuple, @@ -101,6 +102,19 @@ def __str__(self) -> str: f"default={self.default_idx}]" ) + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, BrTable): + return False + elif self.default_idx != self.default_idx: + return False + elif self.label_indices != other.label_indices: + return False + else: + return True + @register class BrIf(Interned): diff --git a/wasm/instructions/memory.py b/wasm/instructions/memory.py index 4112573..a83f256 100644 --- a/wasm/instructions/memory.py +++ b/wasm/instructions/memory.py @@ -58,6 +58,9 @@ def __init__(self, def __str__(self) -> str: return f"{self.opcode.text}[align={self.memarg.align},offset={self.memarg.offset}]" + def __repr__(self) -> str: + return f"" + @property def memory_bit_size(self) -> BitSize: if self.declared_bit_size is None: diff --git a/wasm/instructions/numeric.py b/wasm/instructions/numeric.py index c2150ef..88f8695 100644 --- a/wasm/instructions/numeric.py +++ b/wasm/instructions/numeric.py @@ -1,6 +1,5 @@ import enum from typing import ( - NamedTuple, Optional, ) @@ -25,76 +24,53 @@ # # Numeric # -@register -class I32Const(NamedTuple): - opcode: BinaryOpcode - valtype: ValType - value: numpy.uint32 - +class BaseNumericConstant(SimpleOp): def __str__(self) -> str: return f"{self.opcode.text}[{self.value}]" - @classmethod - def from_opcode(cls, - opcode: BinaryOpcode, - value: numpy.uint32) -> 'I32Const': - if opcode is not BinaryOpcode.I32_CONST: - raise TypeError(f"Invalid opcode: {opcode}") - return cls(opcode, ValType.i32, value) + +@register +class I32Const(BaseNumericConstant): + opcode = BinaryOpcode.I32_CONST + valtype = ValType.i32 + + value: numpy.uint32 + + def __init__(self, value: numpy.uint32) -> None: + self.value = value @register -class I64Const(NamedTuple): - opcode: BinaryOpcode - valtype: ValType - value: numpy.uint64 +class I64Const(BaseNumericConstant): + opcode = BinaryOpcode.I64_CONST + valtype = ValType.i64 - def __str__(self) -> str: - return f"{self.opcode.text}[{self.value}]" + value: numpy.uint64 - @classmethod - def from_opcode(cls, - opcode: BinaryOpcode, - value: numpy.uint64) -> 'I64Const': - if opcode is not BinaryOpcode.I64_CONST: - raise TypeError(f"Invalid opcode: {opcode}") - return cls(opcode, ValType.i64, value) + def __init__(self, value: numpy.uint64) -> None: + self.value = value @register -class F32Const(NamedTuple): - opcode: BinaryOpcode - valtype: ValType - value: numpy.float32 +class F32Const(BaseNumericConstant): + opcode = BinaryOpcode.F32_CONST + valtype = ValType.f32 - def __str__(self) -> str: - return f"{self.opcode.text}[{self.value}]" + value: numpy.float32 - @classmethod - def from_opcode(cls, - opcode: BinaryOpcode, - value: numpy.float32) -> 'F32Const': - if opcode is not BinaryOpcode.F32_CONST: - raise TypeError(f"Invalid opcode: {opcode}") - return cls(opcode, ValType.f32, value) + def __init__(self, value: numpy.float32) -> None: + self.value = value @register -class F64Const(NamedTuple): - opcode: BinaryOpcode - valtype: ValType - value: numpy.float64 +class F64Const(BaseNumericConstant): + opcode = BinaryOpcode.F64_CONST + valtype = ValType.f64 - def __str__(self) -> str: - return f"{self.opcode.text}[{self.value}]" + value: numpy.float64 - @classmethod - def from_opcode(cls, - opcode: BinaryOpcode, - value: numpy.float64) -> 'F64Const': - if opcode is not BinaryOpcode.F64_CONST: - raise TypeError(f"Invalid opcode: {opcode}") - return cls(opcode, ValType.f64, value) + def __init__(self, value: numpy.float64) -> None: + self.value = value class Comparison(enum.Enum): @@ -444,14 +420,13 @@ def from_opcode(cls, opcode: BinaryOpcode) -> 'Truncate': @register class Extend(Interned): + valtype = ValType.i64 + from_valtype = ValType.i32 + def __init__(self, opcode: BinaryOpcode, - valtype: ValType, - from_valtype: ValType, signed: bool) -> None: self.opcode = opcode - self.valtype = valtype - self.from_valtype = from_valtype self.signed = signed def __str__(self) -> str: @@ -460,9 +435,9 @@ def __str__(self) -> str: @classmethod def from_opcode(cls, opcode: BinaryOpcode) -> 'Extend': if opcode is BinaryOpcode.I64_EXTEND_S_I32: - return cls(opcode, ValType.i64, ValType.i32, True) + return cls(opcode, True) elif opcode is BinaryOpcode.I64_EXTEND_U_I32: - return cls(opcode, ValType.i64, ValType.i32, False) + return cls(opcode, False) else: raise Exception(f"Invariant: got unknown opcode {opcode}") diff --git a/wasm/opcodes/__init__.py b/wasm/opcodes/__init__.py index 63d7d81..1f83508 100644 --- a/wasm/opcodes/__init__.py +++ b/wasm/opcodes/__init__.py @@ -1,3 +1,7 @@ from .binary import ( # noqa: F401 BinaryOpcode, ) +from .text import ( # noqa: F401 + TEXT_TO_OPCODE, + OPCODE_TO_TEXT, +) diff --git a/wasm/opcodes/text.py b/wasm/opcodes/text.py index 2632da5..4cd59b4 100644 --- a/wasm/opcodes/text.py +++ b/wasm/opcodes/text.py @@ -180,3 +180,9 @@ BinaryOpcode.F32_REINTERPRET_I32: "f32.reinterpret/i32", BinaryOpcode.F64_REINTERPRET_I64: "f64.reinterpret/i64", } + + +TEXT_TO_OPCODE: Dict[str, BinaryOpcode] = { + value: key + for key, value in OPCODE_TO_TEXT.items() +} diff --git a/wasm/text/__init__.py b/wasm/text/__init__.py new file mode 100644 index 0000000..d3eccab --- /dev/null +++ b/wasm/text/__init__.py @@ -0,0 +1,7 @@ +from .visitor import ( + NodeVisitor, +) + + +visitor = NodeVisitor() +parse = visitor.parse diff --git a/wasm/text/grammar.py b/wasm/text/grammar.py new file mode 100644 index 0000000..6fa1349 --- /dev/null +++ b/wasm/text/grammar.py @@ -0,0 +1,409 @@ +import parsimonious + + +UNTESTED = """ +script = open (_ cmd)* close + +cmd = + module / + (open "register" string name?) / + action / + assertion / + meta + +assertion = open ( + ("assert_return" _ action exprs) / + ("assert_return_canonical_nan" _ action) / + ("assert_return_arithmetic_nan" _ action) / + ("assert_trap" _ action _ string) / + ("assert_malformed" _ module _ string) / + ("assert_invalid" _ module _ string) / + ("assert_unlinkable" _ module _ string) / + ("assert_trap" _ module _ string) + ) close + +action = open ( + ("invoke" _ name? _ string exprs) / + ("get" _ name? _ string) + ) close + +meta = open ( + ("script" _ name? _ script) / + ("input" _ name? _ string) / + ("output" _ name? _ string?) + ) close + + +module = open ( + ("module" _ name? _ secs) / + ( _ secs) / + ("module" _ secs) / + ("module" _ name? _ "binary" strings) / + ("module" _ name? _ "quote" strings) + ) close + +secs = typedef* _ func* _ import* _ export* _ table? _ memory? _ global* _ elem* _ data* _ start? + +start = open "start" var close + +typedef = open "type" name? open "func" params result close close + +import = open "import" _ string _ string _ imkind close +export = open "export" _ string _ exkind close + +imkind = open ( + ("func" _ name? _ func_type) / + ("global" _ name? _ global_type) / + ("table" _ name? _ table_type) / + ("memory" _ name? _ memory_type) + ) close +exkind = open ( + ("func" _ var) / + ("global" _ var) / + ("table" _ var) / + ("memory" _ var) + ) close + +data = open ( + ("data" _ var? _ open offset instrs close strings) / + ("data" _ var? _ expr strings) + ) close +memory = open ( + ("memory" _ name? memory_type) / + ("memory" _ name? exports) / + ("memory" _ name? _ open "import" _ string _ string close _ memory_type) / + ("memory" _ name? exports_opt _ open "data" strings close) + ) close +elem = open ( + ("elem" _ var? _ open "offset" instrs close vars) / + ("elem" _ var? _ expr vars) + ) close +table = open ( + ("table" _ name? _ table_type) / + ("table" _ name? exports) / + ("table" _ name? open "import" string _ string close table_type) / + ("table" _ name? exports_opt _ elem_type _ open "elem" vars close) + ) close +global = open ( + ("global" _ name? _ global_type instrs) / + ("global" _ name? exports) / + ("global" _ name? open "import" _ string _ string close global_type) + ) close +func = open ( + ("func" _ name? _ func_type locals instrs) / + ("func" _ name? exports) / + ("func" _ name? open "import" _ string _ string close func_type) + ) close + +exports_opt = (_ "export" _ string)* +exports = (_ "export" _ string)+ + +exprs = (_ expr)+ +expr = open ( + op / + (op exprs) / + ("block" _ name? _ block_type instrs) / + ("loop" _ name? _ block_type instrs) / + ("if" _ name? _ block_type open "then" instrs close (open "else" instrs close)?) / + ("if" _ name? _ block_type exprs open "then" instrs close (open "else" instrs close)?) + ) close + +instrs = (_ instr)* +instr = open ( + expr / + op / + ("block" _ name? _ block_type instrs _ "end" _ name?) / + ("loop" _ name? _ block_type instrs _ "end" _ name?) / + ("if" _ name? _ block_type instrs _ "end" _ name?) / + ("if" _ name? _ block_type instrs _ "else" _ name? instrs "end" _ name? ) + ) close + + +block_type = ("result" valtypes)* +func_type = ("type" _ var)? params results +global_type = valtype / ("mut" valtype) +table_type = nat _ nat? _ elem_type +memory_type = nat _ nat? + +elem_type = "funcref" + +""" + +cache = """ + +instr = + expr / + ("loop" _ name? _ block_type instrs _ "end" _ name?) / + ("if" _ name? _ block_type instrs _ "end" _ name?) / + ("if" _ name? _ block_type instrs _ "else" _ name? instrs "end" _ name? ) + +exprs = (_ expr)+ +expr = open ( + (op exprs) / + ("if" _ name? _ block_type open "then" instrs close (open "else" instrs close)?) / + ("if" _ name? _ block_type exprs open "then" instrs close (open "else" instrs close)?) + ) close + + +named_block_instr = "block" _ name)? results? instrs _ "end" _ name?) / +""" + +GRAMMAR = parsimonious.Grammar(r""" +component = results / params / locals / func_type / op / instrs + +instrs = instr instrs_tail* +instrs_tail = (_ instr) +instr = open any_instr close +any_instr = + folded_instr / + op / + block_instr / + loop_instr + +loop_instr = "loop" (_ name)? loop_tail? +loop_tail = (_ result)? (_ instrs)? + +block_instr = "block" (_ name)? block_tail? +block_tail = (_ result)? (_ instrs)? + +folded_instr = op _ instrs + +op = numeric_op / memory_op / variable_op / parametric_op / control_op + +control_op = + unreachable_op / + nop_op / + br_if_op / + br_table_op / + br_op / + return_op / + call_op / + call_indirect_op + +unreachable_op = "unreachable" +nop_op = "nop" + +return_op = "return" + +br_if_op = "br_if" _ var +br_table_op = "br_table" _ vars +br_op = "br" _ var + +call_op = "call" _ var +call_indirect_op = "call_indirect" _ typeuse + +func_type = open "func" _ typeuse close + +typeuse = typeuse_direct / typeuse_params_and_results / params / results +typeuse_direct = open "type" _ var close +typeuse_params_and_results = params _ results + +parametric_op = "drop" / "select" + +variable_op = (local_variable_op / global_variable_op) _ var + +global_variable_op = "global.get" / "global.set" +local_variable_op ="local.get" / "local.set" / "local.tee" + +memory_op = + memory_access_op / + memory_size_op / + memory_grow_op + +memory_size_op = "memory.size" +memory_grow_op = "memory.grow" + +memory_access_op = memory_load_op / memory_store_op + +memory_store_op = memory_store_float_op / memory_store_integer_op +memory_store_float_op = float_types ".store" memory_arg +memory_store_integer_op = integer_types ".store" (("8" / "16" / "32") memory_arg)? + +memory_load_op = memory_load_float_op / memory_load_integer_op +memory_load_float_op = float_types ".load" memory_arg +memory_load_integer_op = integer_types ".load" (("8" / "16" / "32") "_" sign memory_arg)? + +memory_arg = (_ offset)? (_ align)? + +align = "align=" ("1" / "2" / "4" / "8" / "16" / "32") +offset = "offset=" nat + +numeric_op = + constop / + testop / + unop / + relop / + binop / + wrapop / + extendop / + truncop / + convertop / + demoteop / + promoteop / + reinterpretop + + +reinterpretop = + (i32 ".reinterpret_" f32) / + (i64 ".reinterpret_" f64) / + (f32 ".reinterpret_" i32) / + (f64 ".reinterpret_" i64) +promoteop = f64 ".promote_" f32 +demoteop = f32 ".demote_" f64 +convertop = float_types ".convert_" integer_types "_" sign +truncop = integer_types ".trunc_" float_types "_" sign +extendop = i64 ".extend_" i32 "_" sign +wrapop = i32 ".wrap_" i64 +unop = integer_unop / float_unop +relop = integer_relop / float_relop +binop = integer_binop / float_binop +testop = integer_types ".eqz" +constop = valtype ".const" _ value + +float_unop = float_types "." float_unop_names +float_binop = float_types "." float_binop_names +float_relop = float_types "." float_relop_names + +integer_unop = integer_types "." integer_unop_names +integer_binop = integer_types "." integer_binop_names +integer_relop = integer_types "." integer_relop_names + +integer_unop_names = + "clz" / + "ctz" / + "popcnt" +integer_binop_names = + "add" / + "sub" / + "mul" / + ("div_" sign) / + ("rem_" sign) / + "and" / + "or" / + "xor" / + "shl" / + "shr_s" / + "shr_u" / + "rotl" / + "rotr" +integer_relop_names = + "eq" / + "ne" / + ("lt_" sign) / + ("gt_" sign) / + ("le_" sign) / + ("ge_" sign) +float_unop_names = + "abs" / + "neg" / + "ceil" / + "floor" / + "trunc" / + "nearest" / + "sqrt" +float_binop_names = + "add" / + "sub" / + "mul" / + "div" / + "min" / + "max" / + "copysign" +float_relop_names = + "eq" / + "ne" / + "lt" / + "gt" / + "le" / + "ge" + +sign = "s" / "u" + +params = param params_tail* +params_tail = (_ param) +param = open any_param close +any_param = bare_param / named_param / empty_param + +named_param = "param" _ name _ valtype +bare_param = "param" _ valtypes +empty_param = "param" + +results = result results_tail* +results_tail = _ result +result = open any_result close +any_result = declared_result / empty_result +declared_result = "result" _ valtypes +empty_result = "result" + +locals = local locals_tail* +locals_tail = (_ local) +local = open any_local close +any_local = bare_local / named_local + +named_local = "local" _ name _ valtype +bare_local = "local" _ valtypes + +valtypes = valtype valtypes_tail* +valtypes_tail = _ valtype +valtype = integer_types / float_types + +float_types = f32 / f64 +integer_types = i32 / i64 + +i32 = "i32" +i64 = "i64" +f32 = "f32" +f64 = "f64" + +vars = var vars_tail* +vars_tail = _ var +var = nat / name +value = int / float + +string = double_quote ( + char / newline / tab / escaped_backslash / + escaped_single_quote / escaped_double_quote / + escaped_hex_char / escaped_unicode_char + )* double_quote +name = "$" ( + letter / digit / + "_" / "." / + "+" / "-" / "*" / "/" / + backslash / "^" / "~" / "=" / "<" / ">" / + "!" / "?" / "@" / "#" / "$" / "%" / "&" / + "|" / ":" / "'" / "`" + )+ + +char = letter / digit +letter = ~"[a-zA-Z]" + +newline = "\n" +tab = "\t" +backslash = "\\" + +escaped_hex_char = backslash hexdigit hexdigit +escaped_unicode_char = "\\u" hexdigit+ + +escaped_backslash = "\\\\" + +single_quote = "'" +double_quote = "\"" + +escaped_single_quote = "\\\'" +escaped_double_quote = "\\\"" + +float = (num "." num? (("e" / "E") num)?) / ("0x" hexnum "." hexnum? (("p" / "P") num)?) +int = nat / ("+" nat) / ("-" nat) +nat = num / ("0x" hexnum) + +hexnum = hexdigit+ +hexdigit = ~"[0-9a-fA-F]" + +open = "(" _? +close = _? ")" +num = digit+ +digit = ~"[0-9]" +_ = whitespace +whitespace = whitespace_chars+ +whitespace_chars = " " / "\n" / "/t" +""") diff --git a/wasm/text/ir.py b/wasm/text/ir.py new file mode 100644 index 0000000..480837a --- /dev/null +++ b/wasm/text/ir.py @@ -0,0 +1,75 @@ +from typing import ( + NamedTuple, + Optional, + Tuple, + Union, +) + +from wasm.datatypes import ( + ValType, + LabelIdx, +) +from wasm.instructions.control import ( + Block, +) +from wasm.opcodes import ( + BinaryOpcode, +) + + +class Local(NamedTuple): + valtype: ValType + name: Optional[str] = None + + +class Param(NamedTuple): + valtype: ValType + name: Optional[str] = None + + +class UnresolvedVariableOp(NamedTuple): + opcode: BinaryOpcode + name: str + + +class UnresolvedFunctionType(NamedTuple): + params: Tuple[Param, ...] + results: Tuple[ValType, ...] + + +class UnresolvedTypeIdx(NamedTuple): + name: str + + +class UnresolvedLabelIdx(NamedTuple): + name: str + + +class UnresolvedCallIndirect(NamedTuple): + type_idx: Union[UnresolvedFunctionType, UnresolvedTypeIdx] + + +class UnresolvedFunctionIdx(NamedTuple): + name: str + + +class UnresolvedCall(NamedTuple): + func_idx: UnresolvedFunctionIdx + + +class UnresolvedBr(NamedTuple): + label_idx: UnresolvedLabelIdx + + +class UnresolvedBrIf(NamedTuple): + label_idx: UnresolvedLabelIdx + + +class UnresolvedBrTable(NamedTuple): + label_indices: Tuple[Union[UnresolvedLabelIdx, LabelIdx], ...] + default_idx: Union[UnresolvedLabelIdx, LabelIdx] + + +class NamedBlock(NamedTuple): + name: str + block: Block diff --git a/wasm/text/visitor.py b/wasm/text/visitor.py new file mode 100644 index 0000000..efebc40 --- /dev/null +++ b/wasm/text/visitor.py @@ -0,0 +1,708 @@ +import functools + +import parsimonious +from parsimonious import ( + expressions, +) + +from wasm._utils.decorators import ( + to_tuple, +) +from wasm._utils.toolz import ( + cons, + concatv, +) +from wasm.datatypes import ( + TypeIdx, + LabelIdx, + ValType, + FunctionIdx, +) +from wasm.exceptions import ( + ParseError, +) +from wasm.instructions.control import ( + Call, + Return, + Br, + BrIf, + BrTable, + Nop, + Unreachable, + Block, + End, +) +from wasm.instructions.variable import ( + LocalOp, + GlobalOp, +) +from wasm.instructions.parametric import ( + Drop, + Select, +) +from wasm.instructions.numeric import ( + Reinterpret, + Demote, + Promote, + Convert, + I32Const, + UnOp, + BinOp, + I64Const, + F32Const, + F64Const, + RelOp, + TestOp, + Wrap, + Extend, + Truncate, +) +from wasm.instructions.memory import ( + MemoryArg, + MemoryOp, + MemorySize, + MemoryGrow, +) +from wasm.opcodes import ( + BinaryOpcode, + TEXT_TO_OPCODE, +) + +from .grammar import GRAMMAR +from .ir import ( + NamedBlock, + Local, + Param, + UnresolvedCall, + UnresolvedVariableOp, + UnresolvedCallIndirect, + UnresolvedTypeIdx, + UnresolvedFunctionIdx, + UnresolvedFunctionType, + UnresolvedBr, + UnresolvedBrIf, + UnresolvedBrTable, + UnresolvedLabelIdx, +) + + +def is_empty(*values): + """ + empty is defined as: + + - falsy + - a tuple of empty values + """ + return all(map(_is_empty, values)) + + +def _is_empty(value): + if not value: + return True + elif isinstance(value, tuple): + return all(_is_empty(item) for item in value) + else: + return False + + +TRUNC_LOOKUP = { + 'i32.trunc_f32_s': BinaryOpcode.I32_TRUNC_S_F32, + 'i32.trunc_f32_u': BinaryOpcode.I32_TRUNC_U_F64, + 'i32.trunc_f64_s': BinaryOpcode.I32_TRUNC_S_F32, + 'i32.trunc_f64_u': BinaryOpcode.I32_TRUNC_U_F64, + 'i64.trunc_f32_s': BinaryOpcode.I64_TRUNC_S_F32, + 'i64.trunc_f32_u': BinaryOpcode.I64_TRUNC_U_F64, + 'i64.trunc_f64_s': BinaryOpcode.I64_TRUNC_S_F32, + 'i64.trunc_f64_u': BinaryOpcode.I64_TRUNC_U_F64, +} +CONVERT_LOOKUP = { + 'f32.convert_i32_s': BinaryOpcode.F32_CONVERT_S_I32, + 'f32.convert_i32_u': BinaryOpcode.F32_CONVERT_U_I32, + 'f32.convert_i64_s': BinaryOpcode.F32_CONVERT_S_I64, + 'f32.convert_i64_u': BinaryOpcode.F32_CONVERT_U_I64, + 'f64.convert_i32_s': BinaryOpcode.F64_CONVERT_S_I32, + 'f64.convert_i32_u': BinaryOpcode.F64_CONVERT_U_I32, + 'f64.convert_i64_s': BinaryOpcode.F64_CONVERT_S_I64, + 'f64.convert_i64_u': BinaryOpcode.F64_CONVERT_U_I64, +} +REINTERPRET_LOOKUP = { + 'i32.reinterpret_f32': BinaryOpcode.I32_REINTERPRET_F32, + 'i64.reinterpret_f64': BinaryOpcode.I64_REINTERPRET_F64, + 'f32.reinterpret_i32': BinaryOpcode.F32_REINTERPRET_I32, + 'f64.reinterpret_i64': BinaryOpcode.F64_REINTERPRET_I64, +} +MEMORY_ARG_DEFAULTS = { + BinaryOpcode.I32_LOAD: MemoryArg(0, 4), + BinaryOpcode.I64_LOAD: MemoryArg(0, 8), + BinaryOpcode.F32_LOAD: MemoryArg(0, 4), + BinaryOpcode.F64_LOAD: MemoryArg(0, 8), + BinaryOpcode.I32_LOAD8_S: MemoryArg(0, 1), + BinaryOpcode.I32_LOAD8_U: MemoryArg(0, 1), + BinaryOpcode.I32_LOAD16_S: MemoryArg(0, 2), + BinaryOpcode.I32_LOAD16_U: MemoryArg(0, 2), + BinaryOpcode.I64_LOAD8_S: MemoryArg(0, 1), + BinaryOpcode.I64_LOAD8_U: MemoryArg(0, 1), + BinaryOpcode.I64_LOAD16_S: MemoryArg(0, 2), + BinaryOpcode.I64_LOAD16_U: MemoryArg(0, 2), + BinaryOpcode.I64_LOAD32_S: MemoryArg(0, 4), + BinaryOpcode.I64_LOAD32_U: MemoryArg(0, 4), + BinaryOpcode.I32_STORE: MemoryArg(0, 4), + BinaryOpcode.I64_STORE: MemoryArg(0, 8), + BinaryOpcode.F32_STORE: MemoryArg(0, 4), + BinaryOpcode.F64_STORE: MemoryArg(0, 8), + BinaryOpcode.I32_STORE8: MemoryArg(0, 1), + BinaryOpcode.I32_STORE16: MemoryArg(0, 2), + BinaryOpcode.I64_STORE8: MemoryArg(0, 1), + BinaryOpcode.I64_STORE16: MemoryArg(0, 2), + BinaryOpcode.I64_STORE32: MemoryArg(0, 4), +} + + +@to_tuple +def process_vars(resolved_cls, unresolved_cls, vars): + for var in vars: + if isinstance(var, str): + yield unresolved_cls(var) + elif isinstance(var, int): + yield resolved_cls(var) + else: + raise Exception("INVALID") + + +class NodeVisitor(parsimonious.NodeVisitor): + """ + Parsimonious node visitor which performs both parsing of type strings and + post-processing of parse trees. Parsing operations are cached. + + Naming conventions: + + - `lparen`: left parenthesis ')' + - `rparen`: right parenthesis ')' + - `ws`: whitespace + - `txt`: the discardable text literal, aka "module" in (module ...) + """ + grammar = GRAMMAR + + def visit_component(self, node, visited_children): + return visited_children[0] + + @staticmethod + def _process_tail(node, visited_children): + ws, tail = visited_children + assert is_empty(ws) + return tail + + @staticmethod + def _join_multi_head_with_tail(node, visited_children): + head, tail = visited_children + return tuple(concatv(head, *tail)) + + @staticmethod + def _join_single_head_with_tail(node, visited_children): + head, tail = visited_children + return tuple(cons(head, tail)) + + @staticmethod + def _unwrap_parens(node, visited_children): + lparen, thing, rparen = visited_children + assert is_empty(lparen, rparen) + return thing + + # + # Instructions + # + def visit_instrs(self, node, visited_children): + head, tail = visited_children + joined = self._join_multi_head_with_tail(node, visited_children) + print('HEAD:', head) + print('TAIL:', tail) + print('JOINED:', joined) + return joined + return self._join_multi_head_with_tail(node, visited_children) + + def visit_instrs_tail(self, node, visited_children): + assert False + + def visit_instr(self, node, visited_children): + return self._unwrap_parens(node, visited_children) + + def visit_loop_instr(self, node, visited_children): + assert False + + def visit_block_instr(self, node, visited_children): + txt, raw_name, (block_type, instructions) = visited_children + assert is_empty(txt) + + block = Block(block_type, instructions) + + if raw_name is None: + return block + else: + ws, name = raw_name + assert is_empty(ws) + return NamedBlock(name, block) + + def visit_op(self, node, visited_children): + op, = visited_children + return (op,) + + def visit_block_tail(self, node, visited_children): + raw_block_type, raw_instructions = visited_children + + if raw_block_type is None: + block_type = tuple() + else: + ws, block_type = raw_block_type + assert is_empty(ws) + + if raw_instructions is None: + instructions = End.as_tail() + else: + ws, instructions = raw_instructions + assert is_empty(ws) + + print('INSTRUCTIONS:', instructions) + + return block_type, instructions + + def visit_folded_instr(self, node, visited_children): + head, ws, tail = visited_children + return tail + head + + # + # Control ops + # + def visit_nop_op(self, node, visited_children): + assert is_empty(visited_children) + return Nop() + + def visit_unreachable_op(self, node, visited_children): + assert is_empty(visited_children) + return Unreachable() + + def visit_br_table_op(self, node, visited_children): + txt, ws, all_label_indices = visited_children + assert is_empty(txt, ws) + is_resolved = ( + all(isinstance(label_idx, int) for label_idx in all_label_indices) + ) + *label_indices, default_idx = process_vars(LabelIdx, UnresolvedLabelIdx, all_label_indices) + if is_resolved: + return BrTable( + label_indices=tuple(label_indices), + default_idx=default_idx, + ) + else: + return UnresolvedBrTable( + label_indices=tuple(label_indices), + default_idx=default_idx, + ) + + def visit_br_if_op(self, node, visited_children): + txt, ws, label_idx = visited_children + assert is_empty(txt, ws) + if isinstance(label_idx, int): + return BrIf(LabelIdx(label_idx)) + elif isinstance(label_idx, str): + return UnresolvedBrIf(UnresolvedLabelIdx(label_idx)) + else: + raise Exception("INVALID") + + def visit_br_op(self, node, visited_children): + txt, ws, label_idx = visited_children + assert is_empty(txt, ws) + if isinstance(label_idx, int): + return Br(LabelIdx(label_idx)) + elif isinstance(label_idx, str): + return UnresolvedBr(UnresolvedLabelIdx(label_idx)) + else: + raise Exception("INVALID") + + def visit_return_op(self, node, visited_children): + return Return() + + def visit_call_op(self, node, visited_children): + txt, ws, function_idx = visited_children + + if isinstance(function_idx, str): + return UnresolvedCall(UnresolvedFunctionIdx(function_idx)) + elif isinstance(function_idx, int): + return Call(FunctionIdx(function_idx)) + else: + raise Exception("INVALID") + + def visit_call_indirect_op(self, node, visited_children): + txt, ws, typeuse = visited_children + assert is_empty(txt, ws) + + if len(typeuse) == 2: + params, results = typeuse + func_type = UnresolvedFunctionType(params, results) + return UnresolvedCallIndirect(func_type) + elif isinstance(typeuse, UnresolvedTypeIdx): + return UnresolvedCallIndirect(typeuse) + else: + raise Exception("INVALID") + + # + # Function Type + # + def visit_func_type(self, node, visited_children): + lparen, txt, ws, func_type, rparen = visited_children + assert is_empty(lparen, txt, ws, rparen) + return func_type + + def visit_typeuse(self, node, visited_children): + typeuse, = visited_children + if isinstance(typeuse, TypeIdx): + return typeuse + elif isinstance(typeuse, UnresolvedFunctionType): + return typeuse + elif isinstance(typeuse, UnresolvedTypeIdx): + return typeuse + elif is_empty(*visited_children): + return UnresolvedFunctionType((), ()) + elif isinstance(typeuse, tuple) and all(isinstance(item, Param) for item in typeuse): + return UnresolvedFunctionType(typeuse, ()) + elif isinstance(typeuse, tuple) and all(isinstance(item, ValType) for item in typeuse): + return UnresolvedFunctionType((), typeuse) + else: + raise Exception("INVALID") + + def visit_typeuse_direct(self, node, visited_children): + lparen, text, ws, var, rparen = visited_children + assert is_empty(lparen, text, ws, rparen) + if isinstance(var, str): + return UnresolvedTypeIdx(var) + elif isinstance(var, int): + return TypeIdx(var) + else: + raise Exception("INVALID") + + def visit_typeuse_params_and_results(self, node, visited_children): + params, ws, results = visited_children + assert is_empty(ws) + + if params is None: + params = tuple() + + if results is None: + results = tuple() + + return UnresolvedFunctionType(params, results) + + # + # Parametric ops + # + def visit_parametric_op(self, node, visited_children): + if node.text == "drop": + return Drop() + elif node.text == "select": + return Select() + else: + raise Exception("INVALID") + + # + # Variable ops + # + def visit_variable_op(self, node, visited_children): + (instruction_class, opcode), ws, var = visited_children + if isinstance(var, str): + return UnresolvedVariableOp(opcode, var) + else: + return instruction_class.from_opcode(opcode, var) + + def visit_local_variable_op(self, node, visited_children): + _, _, action = node.text.partition('.') + if action == 'get': + return LocalOp, BinaryOpcode.GET_LOCAL + elif action == 'set': + return LocalOp, BinaryOpcode.SET_LOCAL + elif action == 'tee': + return LocalOp, BinaryOpcode.TEE_LOCAL + else: + raise Exception("INVALID") + + def visit_global_variable_op(self, node, visited_children): + _, _, action = node.text.partition('.') + if action == 'get': + return GlobalOp, BinaryOpcode.GET_GLOBAL + elif action == 'set': + return GlobalOp, BinaryOpcode.SET_GLOBAL + else: + raise Exception("INVALID") + + # + # Memory ops + # + def visit_memory_size_op(self, node, visited_children): + return MemorySize() + + def visit_memory_grow_op(self, node, visited_children): + return MemoryGrow() + + def visit_memory_access_op(self, node, visited_children): + memarg, = visited_children + + opcode = TEXT_TO_OPCODE[node.text] + if memarg is None: + memarg = MEMORY_ARG_DEFAULTS[opcode] + else: + assert isinstance(memarg, MemoryArg) + + return MemoryOp.from_opcode(opcode, memarg) + + def visit_memory_store_float_op(self, node, visited_children): + valtype, txt, tail = visited_children + assert is_empty(valtype, txt) + if tail is None: + return None + raise Exception("UNHANDLED") + + def visit_memory_store_integer_op(self, node, visited_children): + valtype, txt, tail = visited_children + assert is_empty(valtype, txt) + if tail is None: + return None + else: + size, memarg = tail + assert is_empty(size) + return memarg + + def visit_memory_load_float_op(self, node, visited_children): + # TODO: identical to `visit_memory_store_float_op` + valtype, txt, tail = visited_children + assert is_empty(valtype, txt) + if tail is None: + return None + raise Exception("UNHANDLED") + + def visit_memory_load_integer_op(self, node, visited_children): + valtype, txt, tail = visited_children + assert is_empty(valtype, txt) + if tail is None: + return None + else: + size, ws, _, memarg = tail + assert is_empty(size, ws) + return memarg + + def visit_memory_arg(self, node, visited_children): + offset, align = visited_children + if offset is None and align is None: + return None + elif offset is None or align is None: + raise Exception("INVALID") + else: + return MemoryArg(offset, align) + + def visit_offset(self, node, visited_children): + assert False + + def visit_align(self, node, visited_children): + assert False + + # + # Numeric ops + # + def visit_constop(self, node, visited_children): + valtype, txt, ws, value = visited_children + assert is_empty(txt, ws) + if valtype is ValType.i32: + return I32Const(value) + elif valtype is ValType.i64: + return I64Const(value) + elif valtype is ValType.f32: + return F32Const(value) + elif valtype is ValType.f64: + return F64Const(value) + else: + raise Exception("INVALID") + + def visit_extendop(self, node, visited_children): + i64, txt, i32, _, sign = visited_children + assert is_empty(i64, txt, i32, _) + + if sign is True: + return Extend.from_opcode(BinaryOpcode.I64_EXTEND_S_I32) + elif sign is False: + return Extend.from_opcode(BinaryOpcode.I64_EXTEND_U_I32) + else: + raise Exception("INVALID") + + def visit_wrapop(self, node, visited_children): + return Wrap() + + def visit_testop(self, node, visited_children): + opcode = TEXT_TO_OPCODE[node.text] + return TestOp.from_opcode(opcode) + + def visit_unop(self, node, visited_children): + opcode = TEXT_TO_OPCODE[node.text] + return UnOp.from_opcode(opcode) + + def visit_relop(self, node, visited_children): + opcode = TEXT_TO_OPCODE[node.text] + return RelOp.from_opcode(opcode) + + def visit_binop(self, node, visited_children): + opcode = TEXT_TO_OPCODE[node.text] + return BinOp.from_opcode(opcode) + + def visit_truncop(self, node, visited_children): + opcode = TRUNC_LOOKUP[node.text] + return Truncate.from_opcode(opcode) + + def visit_convertop(self, node, visited_children): + opcode = CONVERT_LOOKUP[node.text] + return Convert.from_opcode(opcode) + + def visit_demoteop(self, node, visited_children): + return Demote() + + def visit_promoteop(self, node, visited_children): + return Promote() + + def visit_reinterpretop(self, node, visited_children): + opcode = REINTERPRET_LOOKUP[node.text] + return Reinterpret.from_opcode(opcode) + + # + # Params + # + def visit_params(self, node, visited_children): + return self._join_multi_head_with_tail(node, visited_children) + + def visit_params_tail(self, node, visited_children): + return self._process_tail(node, visited_children) + + def visit_param(self, node, visited_children): + return self._unwrap_parens(node, visited_children) + + def visit_named_param(self, node, visited_children): + ws0, txt, name, ws1, valtype = visited_children + assert is_empty(ws0, txt, ws1) + return (Param(valtype, name),) + + def visit_bare_param(self, node, visited_children): + txt, ws, valtypes = visited_children + assert is_empty(txt, ws) + return tuple(Param(valtype) for valtype in valtypes) + + # + # Results + # + def visit_results(self, node, visited_children): + return self._join_multi_head_with_tail(node, visited_children) + + def visit_results_tail(self, node, visited_children): + return self._process_tail(node, visited_children) + + def visit_result(self, node, visited_children): + return self._unwrap_parens(node, visited_children) + + def visit_declared_result(self, node, visited_children): + txt, ws, valtypes = visited_children + assert is_empty(txt, ws) + return valtypes + + def visit_empty_result(self, node, visited_children): + return tuple() + + # + # Locals + # + def visit_locals(self, node, visited_children): + return self._join_multi_head_with_tail(node, visited_children) + + def visit_locals_tail(self, node, visited_children): + return self._process_tail(node, visited_children) + + def visit_local(self, node, visited_children): + return self._unwrap_parens(node, visited_children) + + def visit_named_local(self, node, visited_children): + ws0, txt, name, ws1, valtype = visited_children + assert is_empty(ws0, txt, ws1) + return (Local(valtype, name),) + + def visit_bare_local(self, node, visited_children): + txt, ws, valtypes = visited_children + assert is_empty(txt, ws) + return tuple(Local(valtype) for valtype in valtypes) + + # + # WASM Values + # + def visit_valtypes(self, node, visited_children): + head, tail = visited_children + return tuple(concatv((head,), tail)) + + def visit_valtypes_tail(self, node, visited_children): + return self._process_tail(node, visited_children) + + def visit_valtype(self, node, visited_children): + return ValType.from_str(node.text) + + def visit_vars_tail(self, node, visited_children): + ws, var = visited_children + assert is_empty(ws) + return var + + def visit_vars(self, node, visited_children): + head, tail = visited_children + return tuple(cons(head, tail)) + + # + # Simple Values + # + def visit_sign(self, node, visited_children): + if node.text == 's': + return True + elif node.text == 'u': + return False + else: + raise Exception("INVALID") + + def visit_num(self, node, visited_children): + return int(node.text) + + def visit_name(self, node, visited_children): + return node.text + + # + # Structure Values + # + def visit_open(self, node, visited_children): + return None + + def visit_close(self, node, visited_children): + return None + + def visit_whitespace(self, node, visited_children): + return None + + def generic_visit(self, node, visited_children): + if isinstance(node.expr, expressions.OneOf): + # Unwrap value chosen from alternatives + return visited_children[0] + + if isinstance(node.expr, expressions.Optional): + # Unwrap optional value or return `None` + if len(visited_children) != 0: + return visited_children[0] + + return None + + return tuple(visited_children) + + @functools.lru_cache(maxsize=None) + def parse(self, type_str): + if not isinstance(type_str, str): + raise TypeError('Can only parse string values: got {}'.format(type(type_str))) + + try: + return super().parse(type_str) + except parsimonious.ParseError as e: + arst = e # noqa: F841 + raise ParseError(e.text, e.pos, e.expr)