MVP #1
115
py2wasm/compiler.py
Normal file
115
py2wasm/compiler.py
Normal 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
|
||||||
@ -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
|
||||||
|
|||||||
@ -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?
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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 = """
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user