MVP #1

Merged
jbwdevries merged 73 commits from idea_crc32 into master 2022-08-21 12:59:21 +00:00
5 changed files with 180 additions and 63 deletions
Showing only changes of commit 8c25227f40 - Show all commits

115
py2wasm/compiler.py Normal file
View File

@ -0,0 +1,115 @@
"""
This module contains the code to convert parsed Ourlang into WebAssembly code
"""
from typing import Generator, Tuple
from . import ourlang
from . import wasm
Statements = Generator[wasm.Statement, None, None]
def type_(inp: ourlang.OurType) -> wasm.OurType:
if isinstance(inp, ourlang.OurTypeInt32):
return wasm.OurTypeInt32()
if isinstance(inp, ourlang.OurTypeInt64):
return wasm.OurTypeInt64()
if isinstance(inp, ourlang.OurTypeFloat32):
return wasm.OurTypeFloat32()
if isinstance(inp, ourlang.OurTypeFloat64):
return wasm.OurTypeFloat64()
raise NotImplementedError(type_, inp)
OPERATOR_MAP = {
'+': 'add',
'-': 'sub',
}
def expression(inp: ourlang.Expression) -> Statements:
if isinstance(inp, ourlang.ConstantInt32):
yield wasm.Statement('i32.const', str(inp.value))
return
if isinstance(inp, ourlang.ConstantInt64):
yield wasm.Statement('i64.const', str(inp.value))
return
if isinstance(inp, ourlang.ConstantFloat32):
yield wasm.Statement('f32.const', str(inp.value))
return
if isinstance(inp, ourlang.ConstantFloat64):
yield wasm.Statement('f64.const', str(inp.value))
return
if isinstance(inp, ourlang.VariableReference):
yield wasm.Statement('local.get', '${}'.format(inp.name))
return
if isinstance(inp, ourlang.BinaryOp):
yield from expression(inp.left)
yield from expression(inp.right)
if isinstance(inp.type, ourlang.OurTypeInt32):
if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i32.{operator}')
return
if isinstance(inp.type, ourlang.OurTypeInt64):
if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'i64.{operator}')
return
if isinstance(inp.type, ourlang.OurTypeFloat32):
if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'f32.{operator}')
return
if isinstance(inp.type, ourlang.OurTypeFloat64):
if operator := OPERATOR_MAP.get(inp.operator, None):
yield wasm.Statement(f'f64.{operator}')
return
raise NotImplementedError(expression, inp.type, inp.operator)
raise NotImplementedError(expression, inp)
def statement_return(inp: ourlang.StatementReturn) -> Statements:
yield from expression(inp.value)
yield wasm.Statement('return')
def statement(inp: ourlang.Statement) -> Statements:
if isinstance(inp, ourlang.StatementReturn):
yield from statement_return(inp)
return
raise NotImplementedError(statement, inp)
def function_argument(inp: Tuple[str, ourlang.OurType]) -> wasm.Param:
return (inp[0], type_(inp[1]), )
def function(inp: ourlang.Function) -> wasm.Function:
return wasm.Function(
inp.name,
inp.exported,
[
function_argument(x)
for x in inp.posonlyargs
],
[], # TODO
type_(inp.returns),
[
x
for y in inp.statements
for x in statement(y)
]
)
def module(inp: ourlang.Module) -> wasm.Module:
result = wasm.Module()
result.functions = [*map(
function, inp.functions.values(),
)]
return result

View File

@ -92,7 +92,12 @@ class Expression:
"""
An expression within a statement
"""
__slots__ = ()
__slots__ = ('type', )
type: OurType
def __init__(self, type_: OurType) -> None:
self.type = type_
def render(self) -> str:
"""
@ -106,12 +111,7 @@ class Constant(Expression):
"""
An constant value expression within a statement
"""
__slots__ = ('type', )
type: OurType
def __init__(self, type_: OurType) -> None:
self.type = type_
__slots__ = ()
class ConstantInt32(Constant):
"""
@ -177,13 +177,12 @@ class VariableReference(Expression):
"""
An variable reference expression within a statement
"""
__slots__ = ('type', 'name', )
__slots__ = ('name', )
type: OurType
name: str
def __init__(self, type_: OurType, name: str) -> None:
self.type = type_
super().__init__(type_)
self.name = name
def render(self) -> str:
@ -199,7 +198,9 @@ class BinaryOp(Expression):
left: Expression
right: Expression
def __init__(self, operator: str, left: Expression, right: Expression) -> None:
def __init__(self, type_: OurType, operator: str, left: Expression, right: Expression) -> None:
super().__init__(type_)
self.operator = operator
self.left = left
self.right = right
@ -509,11 +510,12 @@ class Module:
# sqrt is guaranteed by wasm, so we should provide it
# ATM it's a 32 bit variant, but that might change.
# TODO: Could be a UnaryOp?
sqrt = Function('sqrt', -2)
sqrt.buildin = True
sqrt.returns = self.types['f32']
sqrt.posonlyargs = [('@', self.types['f32'], )]
self.functions[sqrt.name] = sqrt
# self.functions[sqrt.name] = sqrt
def render(self) -> str:
"""
@ -730,6 +732,7 @@ class OurVisitor:
# e.g. you can do `"hello" * 3` with the code below (yet)
return BinaryOp(
exp_type,
operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.right),
@ -765,6 +768,7 @@ class OurVisitor:
# e.g. you can do `"hello" * 3` with the code below (yet)
return BinaryOp(
exp_type,
operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.comparators[0]),
@ -932,7 +936,7 @@ class OurVisitor:
return ConstantInt64(exp_type, node.value)
if isinstance(exp_type, OurTypeFloat32):
if not isinstance(node.value, float):
if not isinstance(node.value, (float, int, )):
_raise_static_error(node, 'Expected float value')
# FIXME: Range check
@ -940,7 +944,7 @@ class OurVisitor:
return ConstantFloat32(exp_type, node.value)
if isinstance(exp_type, OurTypeFloat64):
if not isinstance(node.value, float):
if not isinstance(node.value, (float, int, )):
_raise_static_error(node, 'Expected float value')
# FIXME: Range check

View File

@ -585,6 +585,9 @@ class Visitor:
return
if isinstance(exp_type, wasm.OurTypeFloat32):
if isinstance(node.value, int):
node.value = float(node.value)
assert isinstance(node.value, float), \
f'Expression expected a float, but received {node.value}'
# TODO: Size check?
@ -593,6 +596,9 @@ class Visitor:
return
if isinstance(exp_type, wasm.OurTypeFloat64):
if isinstance(node.value, int):
node.value = float(node.value)
assert isinstance(node.value, float)
# TODO: Size check?

View File

@ -1,5 +1,6 @@
"""
Python classes for storing the representation of Web Assembly code
Python classes for storing the representation of Web Assembly code,
and being able to conver it to Web Assembly Text Format
"""
from typing import Any, Iterable, List, Optional, Tuple, Union
@ -220,7 +221,7 @@ class Function:
for nam, typ in self.params:
header += f' (param ${nam} {typ.to_wasm()})'
if self.result:
if not isinstance(self.result, OurTypeNone):
header += f' (result {self.result.to_wasm()})'
for nam, typ in self.locals:

View File

@ -2,72 +2,71 @@ import pytest
from .helpers import Suite
TYPE_MAP = {
'i32': int,
'i64': int,
'f32': float,
'f64': float,
}
@pytest.mark.integration_test
def test_return_i32():
code_py = """
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_return(type_):
code_py = f"""
@exported
def testEntry() -> i32:
def testEntry() -> {type_}:
return 13
"""
result = Suite(code_py, 'test_return').run_code()
assert 13 == result.returned_value
assert [] == result.log_int32_list
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_return_i64():
code_py = """
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_addition(type_):
code_py = f"""
@exported
def testEntry() -> i64:
return 13
def testEntry() -> {type_}:
return 10 + 3
"""
result = Suite(code_py, 'test_return').run_code()
result = Suite(code_py, 'test_addition').run_code()
assert 13 == result.returned_value
assert [] == result.log_int32_list
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_return_f32():
code_py = """
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_subtraction(type_):
code_py = f"""
@exported
def testEntry() -> f32:
return 13.5
def testEntry() -> {type_}:
return 10 - 3
"""
result = Suite(code_py, 'test_return').run_code()
result = Suite(code_py, 'test_addition').run_code()
assert 13.5 == result.returned_value
assert [] == result.log_int32_list
assert 7 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_return_f64():
code_py = """
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_arg(type_):
code_py = f"""
@exported
def testEntry() -> f64:
return 13.5
"""
result = Suite(code_py, 'test_return').run_code()
assert 13.5 == result.returned_value
assert [] == result.log_int32_list
@pytest.mark.integration_test
def test_arg():
code_py = """
@exported
def testEntry(a: i32) -> i32:
def testEntry(a: {type_}) -> {type_}:
return a
"""
result = Suite(code_py, 'test_return').run_code(125)
assert 125 == result.returned_value
assert [] == result.log_int32_list
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_i32_to_i64():
code_py = """
@exported
@ -81,6 +80,7 @@ def testEntry(a: i32) -> i64:
assert [] == result.log_int32_list
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_i32_plus_i64():
code_py = """
@exported
@ -94,6 +94,7 @@ def testEntry(a: i32, b: i64) -> i64:
assert [] == result.log_int32_list
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_f32_to_f64():
code_py = """
@exported
@ -107,6 +108,7 @@ def testEntry(a: f32) -> f64:
assert [] == result.log_int32_list
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_f32_plus_f64():
code_py = """
@exported
@ -120,6 +122,7 @@ def testEntry(a: f32, b: f64) -> f64:
assert [] == result.log_int32_list
@pytest.mark.integration_test
@pytest.mark.skip('TODO')
def test_uadd():
code_py = """
@exported
@ -133,6 +136,7 @@ def testEntry() -> i32:
assert [] == result.log_int32_list
@pytest.mark.integration_test
@pytest.mark.skip('TODO')
def test_usub():
code_py = """
@exported
@ -145,19 +149,6 @@ def testEntry() -> i32:
assert -19 == result.returned_value
assert [] == result.log_int32_list
@pytest.mark.integration_test
def test_addition():
code_py = """
@exported
def testEntry() -> i32:
return 10 + 3
"""
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 = """