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
|
||||
"""
|
||||
__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
|
||||
|
||||
@ -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?
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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 = """
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user