Adds a separte typing system #3
@ -154,6 +154,16 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||
wgn.i64.const(inp.value)
|
||||
return
|
||||
|
||||
if stp == 'f32':
|
||||
assert isinstance(inp.value, float)
|
||||
wgn.f32.const(inp.value)
|
||||
return
|
||||
|
||||
if stp == 'f64':
|
||||
assert isinstance(inp.value, float)
|
||||
wgn.f64.const(inp.value)
|
||||
return
|
||||
|
||||
raise NotImplementedError(f'Constants with type {stp}')
|
||||
|
||||
if isinstance(inp, ourlang.VariableReference):
|
||||
|
||||
@ -12,22 +12,48 @@ def constant(ctx: 'Context', inp: ourlang.Constant) -> 'TypeVar':
|
||||
if isinstance(inp, ourlang.ConstantPrimitive):
|
||||
result = ctx.new_var()
|
||||
|
||||
if not isinstance(inp.value, int):
|
||||
raise NotImplementedError('Float constants in new type system')
|
||||
if isinstance(inp.value, int):
|
||||
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
|
||||
|
||||
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
|
||||
# Need at least this many bits to store this constant value
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=len(bin(inp.value)) - 2))
|
||||
# Don't dictate anything about signedness - you can use a signed
|
||||
# constant in an unsigned variable if the bits fit
|
||||
result.add_constraint(TypeConstraintSigned(None))
|
||||
|
||||
# Need at least this many bits to store this constant value
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=len(bin(inp.value)) - 2))
|
||||
# Don't dictate anything about signedness - you can use a signed
|
||||
# constant in an unsigned variable if the bits fit
|
||||
result.add_constraint(TypeConstraintSigned(None))
|
||||
result.add_location(str(inp.value))
|
||||
|
||||
result.add_location(str(inp.value))
|
||||
inp.type_var = result
|
||||
|
||||
inp.type_var = result
|
||||
return result
|
||||
|
||||
return result
|
||||
if isinstance(inp.value, float):
|
||||
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.FLOAT))
|
||||
|
||||
# We don't have fancy logic here to detect if the float constant
|
||||
# fits in the given type. There a number of edge cases to consider,
|
||||
# before implementing this.
|
||||
|
||||
# 1) It may fit anyhow
|
||||
# e.g., if the user has 3.14 as a float constant, neither a
|
||||
# f32 nor a f64 can really fit this value. But does that mean
|
||||
# we should throw an error?
|
||||
|
||||
# If we'd implement it, we'd want to convert it to hex using
|
||||
# inp.value.hex(), which would give us the mantissa and exponent.
|
||||
# We can use those to determine what bit size the value should be in.
|
||||
|
||||
# If that doesn't work out, we'd need another way to calculate the
|
||||
# difference between what was written and what actually gets stored
|
||||
# in memory, and warn if the difference is beyond a treshold.
|
||||
|
||||
result.add_location(str(inp.value))
|
||||
|
||||
inp.type_var = result
|
||||
|
||||
return result
|
||||
|
||||
raise NotImplementedError(constant, inp, inp.value)
|
||||
|
||||
raise NotImplementedError(constant, inp)
|
||||
|
||||
@ -39,6 +65,13 @@ def expression(ctx: 'Context', inp: ourlang.Expression) -> 'TypeVar':
|
||||
assert inp.variable.type_var is not None, inp
|
||||
return inp.variable.type_var
|
||||
|
||||
if isinstance(inp, ourlang.UnaryOp):
|
||||
if inp.operator not in ('sqrt', ):
|
||||
raise NotImplementedError(expression, inp, inp.operator)
|
||||
|
||||
right = expression(ctx, inp.right)
|
||||
return right
|
||||
|
||||
if isinstance(inp, ourlang.BinaryOp):
|
||||
if inp.operator not in ('+', '-', '*', '|', '&', '^'):
|
||||
raise NotImplementedError(expression, inp, inp.operator)
|
||||
@ -51,7 +84,7 @@ def expression(ctx: 'Context', inp: ourlang.Expression) -> 'TypeVar':
|
||||
if isinstance(inp, ourlang.FunctionCall):
|
||||
assert inp.function.returns_type_var is not None
|
||||
if inp.function.posonlyargs:
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError('TODO: Functions with arguments')
|
||||
|
||||
return inp.function.returns_type_var
|
||||
|
||||
@ -123,4 +156,16 @@ def _convert_old_type(ctx: Context, inp: typing.TypeBase, location: str) -> Type
|
||||
result.add_location(location)
|
||||
return result
|
||||
|
||||
if isinstance(inp, typing.TypeFloat32):
|
||||
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.FLOAT))
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
|
||||
result.add_location(location)
|
||||
return result
|
||||
|
||||
if isinstance(inp, typing.TypeFloat64):
|
||||
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.FLOAT))
|
||||
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
|
||||
result.add_location(location)
|
||||
return result
|
||||
|
||||
raise NotImplementedError(_convert_old_type, inp)
|
||||
|
||||
@ -421,4 +421,15 @@ def simplify(inp: TypeVar) -> Optional[str]:
|
||||
base = 'i' if tc_sign.signed else 'u'
|
||||
return f'{base}{tc_bits.minb}'
|
||||
|
||||
if primitive is TypeConstraintPrimitive.Primitive.FLOAT:
|
||||
if tc_bits is None or tc_sign is not None: # Floats should not hava sign contraint
|
||||
return None
|
||||
|
||||
assert isinstance(tc_bits, TypeConstraintBitWidth) # type hint
|
||||
|
||||
if tc_bits.minb != tc_bits.maxb or tc_bits.minb not in (32, 64):
|
||||
return None
|
||||
|
||||
return f'f{tc_bits.minb}'
|
||||
|
||||
return None
|
||||
|
||||
@ -2,14 +2,12 @@ import pytest
|
||||
|
||||
from .helpers import Suite
|
||||
|
||||
ALL_INT_TYPES = ['u8', 'u32', 'u64', 'i32', 'i64']
|
||||
ALL_FLOAT_TYPES = ['f32', 'f64']
|
||||
|
||||
TYPE_MAP = {
|
||||
'u8': int,
|
||||
'u32': int,
|
||||
'u64': int,
|
||||
'i32': int,
|
||||
'i64': int,
|
||||
'f32': float,
|
||||
'f64': float,
|
||||
**{x: int for x in ALL_INT_TYPES},
|
||||
**{x: float for x in ALL_FLOAT_TYPES},
|
||||
}
|
||||
|
||||
COMPLETE_SIMPLE_TYPES = [
|
||||
@ -19,8 +17,8 @@ COMPLETE_SIMPLE_TYPES = [
|
||||
]
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
|
||||
def test_return(type_):
|
||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
||||
def test_return_int(type_):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
@ -33,8 +31,22 @@ def testEntry() -> {type_}:
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
|
||||
def test_addition(type_):
|
||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
||||
def test_return_float(type_):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
return 32.125
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
assert 32.125 == result.returned_value
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
||||
def test_addition_int(type_):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
@ -47,8 +59,22 @@ def testEntry() -> {type_}:
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
|
||||
def test_subtraction(type_):
|
||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
||||
def test_addition_float(type_):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
return 32.0 + 0.125
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
assert 32.125 == result.returned_value
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
|
||||
def test_subtraction_int(type_):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
@ -60,6 +86,20 @@ def testEntry() -> {type_}:
|
||||
assert 7 == result.returned_value
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
||||
def test_subtraction(type_):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
return 100.0 - 67.875
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
assert 32.125 == result.returned_value
|
||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||
|
||||
@pytest.mark.integration_test
|
||||
@pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation
|
||||
def test_logical_left_shift(type_):
|
||||
@ -136,7 +176,7 @@ def test_buildins_sqrt(type_):
|
||||
code_py = f"""
|
||||
@exported
|
||||
def testEntry() -> {type_}:
|
||||
return sqrt(25)
|
||||
return sqrt(25.0)
|
||||
"""
|
||||
|
||||
result = Suite(code_py).run_code()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user