Started on compilation, typing changes

This commit is contained in:
Johan B.W. de Vries 2022-06-19 15:10:13 +02:00
parent 658e442df2
commit 8c25227f40
5 changed files with 180 additions and 63 deletions

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

View File

@ -585,6 +585,9 @@ class Visitor:
return return
if isinstance(exp_type, wasm.OurTypeFloat32): if isinstance(exp_type, wasm.OurTypeFloat32):
if isinstance(node.value, int):
node.value = float(node.value)
assert isinstance(node.value, float), \ assert isinstance(node.value, float), \
f'Expression expected a float, but received {node.value}' f'Expression expected a float, but received {node.value}'
# TODO: Size check? # TODO: Size check?
@ -593,6 +596,9 @@ class Visitor:
return return
if isinstance(exp_type, wasm.OurTypeFloat64): if isinstance(exp_type, wasm.OurTypeFloat64):
if isinstance(node.value, int):
node.value = float(node.value)
assert isinstance(node.value, float) assert isinstance(node.value, float)
# TODO: Size check? # 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 from typing import Any, Iterable, List, Optional, Tuple, Union
@ -220,7 +221,7 @@ class Function:
for nam, typ in self.params: for nam, typ in self.params:
header += f' (param ${nam} {typ.to_wasm()})' header += f' (param ${nam} {typ.to_wasm()})'
if self.result: if not isinstance(self.result, OurTypeNone):
header += f' (result {self.result.to_wasm()})' header += f' (result {self.result.to_wasm()})'
for nam, typ in self.locals: for nam, typ in self.locals:

View File

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