From e972b37149d56810f7d3f42cb0ab212a69a3438d Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sat, 7 Aug 2021 14:34:50 +0200 Subject: [PATCH] If statements \o/ --- Makefile | 2 +- README.md | 6 +++ py2wasm/python.py | 89 +++++++++++++++++++++++++++++++- requirements.txt | 1 + tests/integration/test_fib.py | 3 ++ tests/integration/test_simple.py | 69 +++++++++++++++++++++++-- 6 files changed, 164 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 3633841..9eb1785 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ server: python3.8 -m http.server test: venv/.done - venv/bin/pytest tests + venv/bin/pytest tests $(TEST_FLAGS) lint: venv/.done venv/bin/pylint py2wasm diff --git a/README.md b/README.md index 4fb6851..93683c4 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,9 @@ webasm (python) =============== You will need wat2wasm from github.com/WebAssembly/wabt in your path. + +Ideas +===== +- https://github.com/wasmerio/wasmer-python +- https://github.com/diekmann/wasm-fizzbuzz +- https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html diff --git a/py2wasm/python.py b/py2wasm/python.py index 9710051..8e2740a 100644 --- a/py2wasm/python.py +++ b/py2wasm/python.py @@ -127,6 +127,9 @@ class Visitor: return self.visit_Call(module, wlocals, "None", stmt.value) + if isinstance(stmt, ast.If): + return self.visit_If(module, func, wlocals, stmt) + raise NotImplementedError(stmt) def visit_Return( @@ -137,14 +140,44 @@ class Visitor: stmt: ast.Return, ) -> StatementGenerator: """ - Visits a statement node + Visits a Return node """ assert stmt.value is not None return_type = _parse_annotation(func.returns) - return self.visit_expr(module, wlocals, return_type, stmt.value) + yield from self.visit_expr(module, wlocals, return_type, stmt.value) + yield wasm.Statement('return') + + def visit_If( + self, + module: wasm.Module, + func: ast.FunctionDef, + wlocals: WLocals, + stmt: ast.If, + ) -> StatementGenerator: + """ + Visits an If node + """ + yield from self.visit_expr( + module, + wlocals, + 'bool', + stmt.test, + ) + + yield wasm.Statement('if') + + for py_stmt in stmt.body: + yield from self.visit_stmt(module, func, wlocals, py_stmt) + + yield wasm.Statement('else') + + for py_stmt in stmt.orelse: + yield from self.visit_stmt(module, func, wlocals, py_stmt) + + yield wasm.Statement('end') def visit_expr( self, @@ -159,6 +192,12 @@ class Visitor: if isinstance(node, ast.BinOp): return self.visit_BinOp(module, wlocals, exp_type, node) + if isinstance(node, ast.UnaryOp): + return self.visit_UnaryOp(module, wlocals, exp_type, node) + + if isinstance(node, ast.Compare): + return self.visit_Compare(module, wlocals, exp_type, node) + if isinstance(node, ast.Call): return self.visit_Call(module, wlocals, exp_type, node) @@ -170,6 +209,24 @@ class Visitor: raise NotImplementedError(node) + def visit_UnaryOp( + self, + module: wasm.Module, + wlocals: WLocals, + exp_type: str, + node: ast.UnaryOp, + ) -> StatementGenerator: + """ + Visits a UnaryOp node as (part of) an expression + """ + del module + del wlocals + + if not isinstance(node.operand, ast.Constant): + raise NotImplementedError + + return self.visit_Constant(exp_type, node.operand) + def visit_BinOp( self, module: wasm.Module, @@ -189,6 +246,34 @@ class Visitor: raise NotImplementedError(node.op) + def visit_Compare( + self, + module: wasm.Module, + wlocals: WLocals, + exp_type: str, + node: ast.Compare, + ) -> StatementGenerator: + """ + Visits a Compare node as (part of) an expression + """ + assert 'bool' == exp_type + + if 1 != len(node.ops) or 1 != len(node.comparators): + raise NotImplementedError + + yield from self.visit_expr(module, wlocals, 'i32', node.left) + yield from self.visit_expr(module, wlocals, 'i32', node.comparators[0]) + + if isinstance(node.ops[0], ast.Lt): + yield wasm.Statement('i32.lt_s') + return + + if isinstance(node.ops[0], ast.Gt): + yield wasm.Statement('i32.gt_s') + return + + raise NotImplementedError(node.ops) + def visit_Call( self, module: wasm.Module, diff --git a/requirements.txt b/requirements.txt index ef8699b..c20edb1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ mypy==0.812 pylint==2.7.4 pytest==6.2.2 +pytest-integration==0.2.2 pywasm==1.0.7 diff --git a/tests/integration/test_fib.py b/tests/integration/test_fib.py index ff00b05..a68cb0e 100644 --- a/tests/integration/test_fib.py +++ b/tests/integration/test_fib.py @@ -1,5 +1,8 @@ +import pytest + from .helpers import Suite +@pytest.mark.slow_integration_test def test_fib(): code_py = """ def helper(n: i32, a: i32, b: i32) -> i32: diff --git a/tests/integration/test_simple.py b/tests/integration/test_simple.py index 0d015b4..fdcab75 100644 --- a/tests/integration/test_simple.py +++ b/tests/integration/test_simple.py @@ -1,5 +1,8 @@ +import pytest + from .helpers import Suite +@pytest.mark.integration_test def test_return(): code_py = """ @exported @@ -7,11 +10,25 @@ def testEntry() -> i32: return 13 """ - result = Suite(code_py, 'test_fib').run_code() + result = Suite(code_py, 'test_return').run_code() assert 13 == result.returned_value assert [] == result.log_int32_list +@pytest.mark.integration_test +def test_arg(): + code_py = """ +@exported +def testEntry(a: i32) -> i32: + return a +""" + + result = Suite(code_py, 'test_return').run_code(125) + + assert 125 == result.returned_value + assert [] == result.log_int32_list + +@pytest.mark.integration_test def test_addition(): code_py = """ @exported @@ -19,11 +36,57 @@ def testEntry() -> i32: return 10 + 3 """ - result = Suite(code_py, 'test_fib').run_code() + result = Suite(code_py, 'test_addition').run_code() assert 13 == result.returned_value assert [] == result.log_int32_list +@pytest.mark.integration_test +def test_if_simple(): + code_py = """ +@exported +def testEntry(a: i32) -> i32: + if a > 10: + return 1 + + return 0 +""" + + suite = Suite(code_py, 'test_return') + + result = suite.run_code(10) + assert 0 == result.returned_value + assert [] == result.log_int32_list + + result = suite.run_code(11) + assert 1 == result.returned_value + +@pytest.mark.integration_test +def test_if_complex(): + code_py = """ +@exported +def testEntry(a: i32) -> i32: + if a > 10: + return 10 + elif a > 0: + return a + else: + return 0 + + return -1 # Required due to function type +""" + + suite = Suite(code_py, 'test_return') + + assert 10 == suite.run_code(20).returned_value + assert 10 == suite.run_code(10).returned_value + + assert 8 == suite.run_code(8).returned_value + + assert 0 == suite.run_code(0).returned_value + assert 0 == suite.run_code(-1).returned_value + +@pytest.mark.integration_test def test_call(): code_py = """ def helper(left: i32, right: i32) -> i32: @@ -34,7 +97,7 @@ def testEntry() -> i32: return helper(10, 3) """ - result = Suite(code_py, 'test_fib').run_code() + result = Suite(code_py, 'test_call').run_code() assert 13 == result.returned_value assert [] == result.log_int32_list