MVP #1

Merged
jbwdevries merged 73 commits from idea_crc32 into master 2022-08-21 12:59:21 +00:00
6 changed files with 164 additions and 6 deletions
Showing only changes of commit e972b37149 - Show all commits

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -1,4 +1,5 @@
mypy==0.812
pylint==2.7.4
pytest==6.2.2
pytest-integration==0.2.2
pywasm==1.0.7

View File

@ -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:

View File

@ -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