MVP #1
2
Makefile
2
Makefile
@ -8,7 +8,7 @@ server:
|
|||||||
python3.8 -m http.server
|
python3.8 -m http.server
|
||||||
|
|
||||||
test: venv/.done
|
test: venv/.done
|
||||||
venv/bin/pytest tests
|
venv/bin/pytest tests $(TEST_FLAGS)
|
||||||
|
|
||||||
lint: venv/.done
|
lint: venv/.done
|
||||||
venv/bin/pylint py2wasm
|
venv/bin/pylint py2wasm
|
||||||
|
|||||||
@ -2,3 +2,9 @@ webasm (python)
|
|||||||
===============
|
===============
|
||||||
|
|
||||||
You will need wat2wasm from github.com/WebAssembly/wabt in your path.
|
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
|
||||||
|
|||||||
@ -127,6 +127,9 @@ class Visitor:
|
|||||||
|
|
||||||
return self.visit_Call(module, wlocals, "None", stmt.value)
|
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)
|
raise NotImplementedError(stmt)
|
||||||
|
|
||||||
def visit_Return(
|
def visit_Return(
|
||||||
@ -137,14 +140,44 @@ class Visitor:
|
|||||||
stmt: ast.Return,
|
stmt: ast.Return,
|
||||||
) -> StatementGenerator:
|
) -> StatementGenerator:
|
||||||
"""
|
"""
|
||||||
Visits a statement node
|
Visits a Return node
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert stmt.value is not None
|
assert stmt.value is not None
|
||||||
|
|
||||||
return_type = _parse_annotation(func.returns)
|
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(
|
def visit_expr(
|
||||||
self,
|
self,
|
||||||
@ -159,6 +192,12 @@ class Visitor:
|
|||||||
if isinstance(node, ast.BinOp):
|
if isinstance(node, ast.BinOp):
|
||||||
return self.visit_BinOp(module, wlocals, exp_type, node)
|
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):
|
if isinstance(node, ast.Call):
|
||||||
return self.visit_Call(module, wlocals, exp_type, node)
|
return self.visit_Call(module, wlocals, exp_type, node)
|
||||||
|
|
||||||
@ -170,6 +209,24 @@ class Visitor:
|
|||||||
|
|
||||||
raise NotImplementedError(node)
|
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(
|
def visit_BinOp(
|
||||||
self,
|
self,
|
||||||
module: wasm.Module,
|
module: wasm.Module,
|
||||||
@ -189,6 +246,34 @@ class Visitor:
|
|||||||
|
|
||||||
raise NotImplementedError(node.op)
|
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(
|
def visit_Call(
|
||||||
self,
|
self,
|
||||||
module: wasm.Module,
|
module: wasm.Module,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
mypy==0.812
|
mypy==0.812
|
||||||
pylint==2.7.4
|
pylint==2.7.4
|
||||||
pytest==6.2.2
|
pytest==6.2.2
|
||||||
|
pytest-integration==0.2.2
|
||||||
pywasm==1.0.7
|
pywasm==1.0.7
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from .helpers import Suite
|
from .helpers import Suite
|
||||||
|
|
||||||
|
@pytest.mark.slow_integration_test
|
||||||
def test_fib():
|
def test_fib():
|
||||||
code_py = """
|
code_py = """
|
||||||
def helper(n: i32, a: i32, b: i32) -> i32:
|
def helper(n: i32, a: i32, b: i32) -> i32:
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from .helpers import Suite
|
from .helpers import Suite
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
def test_return():
|
def test_return():
|
||||||
code_py = """
|
code_py = """
|
||||||
@exported
|
@exported
|
||||||
@ -7,11 +10,25 @@ def testEntry() -> i32:
|
|||||||
return 13
|
return 13
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = Suite(code_py, 'test_fib').run_code()
|
result = Suite(code_py, 'test_return').run_code()
|
||||||
|
|
||||||
assert 13 == result.returned_value
|
assert 13 == result.returned_value
|
||||||
assert [] == result.log_int32_list
|
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():
|
def test_addition():
|
||||||
code_py = """
|
code_py = """
|
||||||
@exported
|
@exported
|
||||||
@ -19,11 +36,57 @@ def testEntry() -> i32:
|
|||||||
return 10 + 3
|
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 13 == result.returned_value
|
||||||
assert [] == result.log_int32_list
|
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():
|
def test_call():
|
||||||
code_py = """
|
code_py = """
|
||||||
def helper(left: i32, right: i32) -> i32:
|
def helper(left: i32, right: i32) -> i32:
|
||||||
@ -34,7 +97,7 @@ def testEntry() -> i32:
|
|||||||
return helper(10, 3)
|
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 13 == result.returned_value
|
||||||
assert [] == result.log_int32_list
|
assert [] == result.log_int32_list
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user