Compare commits

..

No commits in common. "977c449c3fd9600c6fab291ec14aba44ae10358e" and "6f3d9a5bcc6ada961b87334f492d65cc63da5508" have entirely different histories.

32 changed files with 1733 additions and 1981 deletions

View File

@ -3,7 +3,7 @@ This module generates source code based on the parsed AST
It's intented to be a "any color, as long as it's black" kind of renderer
"""
from typing import Generator, Optional
from typing import Generator
from . import ourlang
from . import typing
@ -16,26 +16,63 @@ def phasm_render(inp: ourlang.Module) -> str:
Statements = Generator[str, None, None]
def type_var(inp: Optional[typing.TypeVar]) -> str:
def type_(inp: typing.TypeBase) -> str:
"""
Render: type's name
Render: Type (name)
"""
assert inp is not None, typing.ASSERTION_ERROR
if isinstance(inp, typing.TypeNone):
return 'None'
mtyp = typing.simplify(inp)
if mtyp is None:
raise NotImplementedError(f'Rendering type {inp}')
if isinstance(inp, typing.TypeBool):
return 'bool'
return mtyp
if isinstance(inp, typing.TypeUInt8):
return 'u8'
if isinstance(inp, typing.TypeUInt32):
return 'u32'
if isinstance(inp, typing.TypeUInt64):
return 'u64'
if isinstance(inp, typing.TypeInt32):
return 'i32'
if isinstance(inp, typing.TypeInt64):
return 'i64'
if isinstance(inp, typing.TypeFloat32):
return 'f32'
if isinstance(inp, typing.TypeFloat64):
return 'f64'
if isinstance(inp, typing.TypeBytes):
return 'bytes'
if isinstance(inp, typing.TypeTuple):
mems = ', '.join(
type_(x.type)
for x in inp.members
)
return f'({mems}, )'
if isinstance(inp, typing.TypeStaticArray):
return f'{type_(inp.member_type)}[{len(inp.members)}]'
if isinstance(inp, typing.TypeStruct):
return inp.name
raise NotImplementedError(type_, inp)
def struct_definition(inp: typing.TypeStruct) -> str:
"""
Render: TypeStruct's definition
"""
result = f'class {inp.name}:\n'
for mem in inp.members: # TODO: Broken after new type system
raise NotImplementedError('Structs broken after new type system')
# result += f' {mem.name}: {type_(mem.type)}\n'
for mem in inp.members:
result += f' {mem.name}: {type_(mem.type)}\n'
return result
@ -43,7 +80,7 @@ def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
"""
Render: Module Constant's definition
"""
return f'{inp.name}: {type_var(inp.type_var)} = {expression(inp.constant)}\n'
return f'{inp.name}: {type_(inp.type)} = {expression(inp.constant)}\n'
def expression(inp: ourlang.Expression) -> str:
"""
@ -54,7 +91,7 @@ def expression(inp: ourlang.Expression) -> str:
# could not fit in the given float type
return str(inp.value)
if isinstance(inp, ourlang.ConstantTuple):
if isinstance(inp, (ourlang.ConstantTuple, ourlang.ConstantStaticArray, )):
return '(' + ', '.join(
expression(x)
for x in inp.value
@ -65,16 +102,12 @@ def expression(inp: ourlang.Expression) -> str:
if isinstance(inp, ourlang.UnaryOp):
if (
inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS
or inp.operator in ourlang.WEBASSEMBLY_BUILTIN_BYTES_OPS):
inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS
or inp.operator in ourlang.WEBASSEMBLY_BUILDIN_BYTES_OPS):
return f'{inp.operator}({expression(inp.right)})'
if inp.operator == 'cast':
mtyp = type_var(inp.type_var)
if mtyp is None:
raise NotImplementedError(f'Casting to type {inp.type_var}')
return f'{mtyp}({expression(inp.right)})'
return f'{type_(inp.type)}({expression(inp.right)})'
return f'{inp.operator}{expression(inp.right)}'
@ -87,29 +120,33 @@ def expression(inp: ourlang.Expression) -> str:
for arg in inp.arguments
)
# TODO: Broken after new type system
# if isinstance(inp.function, ourlang.StructConstructor):
# return f'{inp.function.struct.name}({args})'
#
# if isinstance(inp.function, ourlang.TupleConstructor):
# return f'({args}, )'
if isinstance(inp.function, ourlang.StructConstructor):
return f'{inp.function.struct.name}({args})'
if isinstance(inp.function, ourlang.TupleConstructor):
return f'({args}, )'
return f'{inp.function.name}({args})'
if isinstance(inp, ourlang.Subscript):
varref = expression(inp.varref)
index = expression(inp.index)
if isinstance(inp, ourlang.AccessBytesIndex):
return f'{expression(inp.varref)}[{expression(inp.index)}]'
return f'{varref}[{index}]'
if isinstance(inp, ourlang.AccessStructMember):
return f'{expression(inp.varref)}.{inp.member.name}'
# TODO: Broken after new type system
# if isinstance(inp, ourlang.AccessStructMember):
# return f'{expression(inp.varref)}.{inp.member.name}'
if isinstance(inp, (ourlang.AccessTupleMember, ourlang.AccessStaticArrayMember, )):
if isinstance(inp.member, ourlang.Expression):
return f'{expression(inp.varref)}[{expression(inp.member)}]'
return f'{expression(inp.varref)}[{inp.member.idx}]'
if isinstance(inp, ourlang.Fold):
fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr'
return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})'
if isinstance(inp, ourlang.ModuleConstantReference):
return inp.definition.name
raise NotImplementedError(expression, inp)
def statement(inp: ourlang.Statement) -> Statements:
@ -150,11 +187,11 @@ def function(inp: ourlang.Function) -> str:
result += '@imported\n'
args = ', '.join(
f'{p.name}: {type_var(p.type_var)}'
f'{p.name}: {type_(p.type)}'
for p in inp.posonlyargs
)
result += f'def {inp.name}({args}) -> {type_var(inp.returns_type_var)}:\n'
result += f'def {inp.name}({args}) -> {type_(inp.returns)}:\n'
if inp.imported:
result += ' pass\n'
@ -184,7 +221,7 @@ def module(inp: ourlang.Module) -> str:
for func in inp.functions.values():
if func.lineno < 0:
# Builtin (-2) or auto generated (-1)
# Buildin (-2) or auto generated (-1)
continue
if result:

View File

@ -1,8 +1,6 @@
"""
This module contains the code to convert parsed Ourlang into WebAssembly code
"""
from typing import List, Optional
import struct
from . import codestyle
@ -14,6 +12,19 @@ from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types
from .wasmgenerator import Generator as WasmGenerator
LOAD_STORE_TYPE_MAP = {
typing.TypeUInt8: 'i32',
typing.TypeUInt32: 'i32',
typing.TypeUInt64: 'i64',
typing.TypeInt32: 'i32',
typing.TypeInt64: 'i64',
typing.TypeFloat32: 'f32',
typing.TypeFloat64: 'f64',
}
"""
When generating code, we sometimes need to load or store simple values
"""
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
"""
Public method for compiling a parsed Phasm module into
@ -21,57 +32,42 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module:
"""
return module(inp)
def type_var(inp: Optional[typing.TypeVar]) -> wasm.WasmType:
def type_(inp: typing.TypeBase) -> wasm.WasmType:
"""
Compile: type
Types are used for example in WebAssembly function parameters
and return types.
"""
assert inp is not None, typing.ASSERTION_ERROR
if isinstance(inp, typing.TypeNone):
return wasm.WasmTypeNone()
mtyp = typing.simplify(inp)
if mtyp == 'u8':
if isinstance(inp, typing.TypeUInt8):
# WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte
return wasm.WasmTypeInt32()
if mtyp == 'u32':
if isinstance(inp, typing.TypeUInt32):
return wasm.WasmTypeInt32()
if mtyp == 'u64':
if isinstance(inp, typing.TypeUInt64):
return wasm.WasmTypeInt64()
if mtyp == 'i32':
if isinstance(inp, typing.TypeInt32):
return wasm.WasmTypeInt32()
if mtyp == 'i64':
if isinstance(inp, typing.TypeInt64):
return wasm.WasmTypeInt64()
if mtyp == 'f32':
if isinstance(inp, typing.TypeFloat32):
return wasm.WasmTypeFloat32()
if mtyp == 'f64':
if isinstance(inp, typing.TypeFloat64):
return wasm.WasmTypeFloat64()
assert inp is not None, typing.ASSERTION_ERROR
tc_prim = inp.get_constraint(typing.TypeConstraintPrimitive)
if tc_prim is None:
raise NotImplementedError(type_var, inp)
if tc_prim.primitive is typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
# StaticArray, Tuples and Structs are passed as pointer
if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeStaticArray, typing.TypeBytes)):
# Structs and tuples are passed as pointer
# And pointers are i32
return wasm.WasmTypeInt32()
# TODO: Broken after new type system
# if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeStaticArray, typing.TypeBytes)):
# # Structs and tuples are passed as pointer
# # And pointers are i32
# return wasm.WasmTypeInt32()
raise NotImplementedError(inp, mtyp)
raise NotImplementedError(type_, inp)
# Operators that work for i32, i64, f32, f64
OPERATOR_MAP = {
@ -85,6 +81,8 @@ U8_OPERATOR_MAP = {
# Under the hood, this is an i32
# Implementing Right Shift XOR, OR, AND is fine since the 3 remaining
# bytes stay zero after this operation
# Since it's unsigned an unsigned value, Logical or Arithmetic shift right
# are the same operation
'>>': 'shr_u',
'^': 'xor',
'|': 'or',
@ -134,158 +132,100 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
Compile: Any expression
"""
if isinstance(inp, ourlang.ConstantPrimitive):
assert inp.type_var is not None, typing.ASSERTION_ERROR
stp = typing.simplify(inp.type_var)
if stp is None:
raise NotImplementedError(f'Constants with type {inp.type_var}')
if stp == 'u8':
# No native u8 type - treat as i32, with caution
assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
return
if stp in ('i32', 'u32'):
assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
return
if stp in ('i64', 'u64'):
assert isinstance(inp.value, int)
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):
if isinstance(inp.variable, ourlang.FunctionParam):
wgn.add_statement('local.get', '${}'.format(inp.variable.name))
return
if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert inp.variable.type_var is not None, typing.ASSERTION_ERROR
tc_prim = inp.variable.type_var.get_constraint(typing.TypeConstraintPrimitive)
if tc_prim is None:
raise NotImplementedError(expression, inp, inp.variable.type_var)
# TODO: Broken after new type system
# if isinstance(inp.type, typing.TypeTuple):
# assert isinstance(inp.definition.constant, ourlang.ConstantTuple)
# assert inp.definition.data_block is not None, 'Combined values are memory stored'
# assert inp.definition.data_block.address is not None, 'Value not allocated'
# wgn.i32.const(inp.definition.data_block.address)
# return
#
if tc_prim.primitive == typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
assert inp.variable.data_block is not None, 'Combined values are memory stored'
assert inp.variable.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.variable.data_block.address)
return
assert inp.variable.data_block is None, 'Primitives are not memory stored'
assert inp.variable.type_var is not None, typing.ASSERTION_ERROR
mtyp = typing.simplify(inp.variable.type_var)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.type_var)
expression(wgn, inp.variable.constant)
return
raise NotImplementedError(expression, inp.variable)
if isinstance(inp, ourlang.BinaryOp):
expression(wgn, inp.left)
expression(wgn, inp.right)
assert inp.type_var is not None, typing.ASSERTION_ERROR
mtyp = typing.simplify(inp.type_var)
if mtyp == 'u8':
if isinstance(inp.type, typing.TypeUInt8):
if operator := U8_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}')
return
if mtyp == 'u32':
if isinstance(inp.type, typing.TypeUInt32):
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}')
return
if operator := U32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}')
return
if mtyp == 'u64':
if isinstance(inp.type, typing.TypeUInt64):
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}')
return
if operator := U64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}')
return
if mtyp == 'i32':
if isinstance(inp.type, typing.TypeInt32):
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}')
return
if operator := I32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}')
return
if mtyp == 'i64':
if isinstance(inp.type, typing.TypeInt64):
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}')
return
if operator := I64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i64.{operator}')
return
if mtyp == 'f32':
if isinstance(inp.type, typing.TypeFloat32):
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}')
return
if mtyp == 'f64':
if isinstance(inp.type, typing.TypeFloat64):
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f64.{operator}')
return
raise NotImplementedError(expression, inp.type_var, inp.operator)
raise NotImplementedError(expression, inp.type, inp.operator)
if isinstance(inp, ourlang.UnaryOp):
expression(wgn, inp.right)
assert inp.type_var is not None, typing.ASSERTION_ERROR
mtyp = typing.simplify(inp.type_var)
if mtyp == 'f32':
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
if isinstance(inp.type, typing.TypeFloat32):
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
wgn.add_statement(f'f32.{inp.operator}')
return
if mtyp == 'f64':
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
if isinstance(inp.type, typing.TypeFloat64):
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
wgn.add_statement(f'f64.{inp.operator}')
return
# TODO: Broken after new type system
# if isinstance(inp.type, typing.TypeInt32):
# if inp.operator == 'len':
# if isinstance(inp.right.type, typing.TypeBytes):
# wgn.i32.load()
# return
if isinstance(inp.type, typing.TypeInt32):
if inp.operator == 'len':
if isinstance(inp.right.type, typing.TypeBytes):
wgn.i32.load()
return
# if inp.operator == 'cast':
# if isinstance(inp.type, typing.TypeUInt32) and isinstance(inp.right.type, typing.TypeUInt8):
# # Nothing to do, you can use an u8 value as a u32 no problem
# return
if inp.operator == 'cast':
if isinstance(inp.type, typing.TypeUInt32) and isinstance(inp.right.type, typing.TypeUInt8):
# Nothing to do, you can use an u8 value as a u32 no problem
return
raise NotImplementedError(expression, inp.type_var, inp.operator)
raise NotImplementedError(expression, inp.type, inp.operator)
if isinstance(inp, ourlang.FunctionCall):
for arg in inp.arguments:
@ -294,119 +234,99 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.add_statement('call', '${}'.format(inp.function.name))
return
if isinstance(inp, ourlang.Subscript):
assert inp.varref.type_var is not None, typing.ASSERTION_ERROR
tc_prim = inp.varref.type_var.get_constraint(typing.TypeConstraintPrimitive)
if tc_prim is None:
raise NotImplementedError(expression, inp, inp.varref.type_var)
if tc_prim.primitive == typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
if not isinstance(inp.index, ourlang.ConstantPrimitive):
raise NotImplementedError(expression, inp, inp.index)
if not isinstance(inp.index.value, int):
raise NotImplementedError(expression, inp, inp.index.value)
assert inp.type_var is not None, typing.ASSERTION_ERROR
mtyp = typing.simplify(inp.type_var)
if mtyp is None:
raise NotImplementedError(expression, inp, inp.varref.type_var, mtyp)
if mtyp == 'u8':
# u8 operations are done using i32, since WASM does not have u8 operations
mtyp = 'i32'
elif mtyp == 'u32':
# u32 operations are done using i32, using _u operations
mtyp = 'i32'
elif mtyp == 'u64':
# u64 operations are done using i64, using _u operations
mtyp = 'i64'
tc_subs = inp.varref.type_var.get_constraint(typing.TypeConstraintSubscript)
if tc_subs is None:
raise NotImplementedError(expression, inp, inp.varref.type_var)
assert 0 < len(tc_subs.members)
tc_bits = tc_subs.members[0].get_constraint(typing.TypeConstraintBitWidth)
if tc_bits is None or len(tc_bits.oneof) > 1:
raise NotImplementedError(expression, inp, inp.varref.type_var)
bitwidth = next(iter(tc_bits.oneof))
if bitwidth % 8 != 0:
raise NotImplementedError(expression, inp, inp.varref.type_var)
if isinstance(inp, ourlang.AccessBytesIndex):
if not isinstance(inp.type, typing.TypeUInt8):
raise NotImplementedError(inp, inp.type)
expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(bitwidth // 8 * inp.index.value))
expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__)
return
raise NotImplementedError(expression, inp, inp.varref.type_var)
if isinstance(inp, ourlang.AccessStructMember):
mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.member)
expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
return
# TODO: Broken after new type system
# if isinstance(inp, ourlang.AccessBytesIndex):
# if not isinstance(inp.type, typing.TypeUInt8):
# raise NotImplementedError(inp, inp.type)
#
# expression(wgn, inp.varref)
# expression(wgn, inp.index)
# wgn.call(stdlib_types.__subscript_bytes__)
# return
if isinstance(inp, ourlang.AccessTupleMember):
mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.member)
# if isinstance(inp, ourlang.AccessStructMember):
# mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
# if mtyp is None:
# # In the future might extend this by having structs or tuples
# # as members of struct or tuples
# raise NotImplementedError(expression, inp, inp.member)
#
# expression(wgn, inp.varref)
# wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
# return
#
# if isinstance(inp, ourlang.AccessTupleMember):
# mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
# if mtyp is None:
# # In the future might extend this by having structs or tuples
# # as members of struct or tuples
# raise NotImplementedError(expression, inp, inp.member)
#
# expression(wgn, inp.varref)
# wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
# return
#
# if isinstance(inp, ourlang.AccessStaticArrayMember):
# mtyp = LOAD_STORE_TYPE_MAP.get(inp.static_array.member_type.__class__)
# if mtyp is None:
# # In the future might extend this by having structs or tuples
# # as members of static arrays
# raise NotImplementedError(expression, inp, inp.member)
#
# expression(wgn, inp.varref)
# expression(wgn, inp.member)
# wgn.i32.const(inp.static_array.member_type.alloc_size())
# wgn.i32.mul()
# wgn.i32.add()
# wgn.add_statement(f'{mtyp}.load')
# return
expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
return
if isinstance(inp, ourlang.AccessStaticArrayMember):
mtyp = LOAD_STORE_TYPE_MAP.get(inp.static_array.member_type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of static arrays
raise NotImplementedError(expression, inp, inp.member)
if isinstance(inp.member, typing.TypeStaticArrayMember):
expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
return
expression(wgn, inp.varref)
expression(wgn, inp.member)
wgn.i32.const(inp.static_array.member_type.alloc_size())
wgn.i32.mul()
wgn.i32.add()
wgn.add_statement(f'{mtyp}.load')
return
if isinstance(inp, ourlang.Fold):
expression_fold(wgn, inp)
return
if isinstance(inp, ourlang.ModuleConstantReference):
if isinstance(inp.type, typing.TypeTuple):
assert isinstance(inp.definition.constant, ourlang.ConstantTuple)
assert inp.definition.data_block is not None, 'Combined values are memory stored'
assert inp.definition.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.definition.data_block.address)
return
if isinstance(inp.type, typing.TypeStaticArray):
assert isinstance(inp.definition.constant, ourlang.ConstantStaticArray)
assert inp.definition.data_block is not None, 'Combined values are memory stored'
assert inp.definition.data_block.address is not None, 'Value not allocated'
wgn.i32.const(inp.definition.data_block.address)
return
assert inp.definition.data_block is None, 'Primitives are not memory stored'
mtyp = LOAD_STORE_TYPE_MAP.get(inp.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.type)
expression(wgn, inp.definition.constant)
return
raise NotImplementedError(expression, inp)
def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
"""
Compile: Fold expression
"""
assert inp.base.type_var is not None, typing.ASSERTION_ERROR
mtyp = typing.simplify(inp.base.type_var)
mtyp = LOAD_STORE_TYPE_MAP.get(inp.base.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, inp.base)
raise NotImplementedError('TODO: Broken after new type system')
if inp.iter.type.__class__.__name__ != 'TypeBytes':
raise NotImplementedError(expression, inp, inp.iter.type)
@ -522,7 +442,7 @@ def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
"""
Compile: function argument
"""
return (inp.name, type_var(inp.type_var), )
return (inp.name, type_(inp.type), )
def import_(inp: ourlang.Function) -> wasm.Import:
"""
@ -538,7 +458,7 @@ def import_(inp: ourlang.Function) -> wasm.Import:
function_argument(x)
for x in inp.posonlyargs
],
type_var(inp.returns_type_var)
type_(inp.returns)
)
def function(inp: ourlang.Function) -> wasm.Function:
@ -549,10 +469,10 @@ def function(inp: ourlang.Function) -> wasm.Function:
wgn = WasmGenerator()
if False: # TODO: isinstance(inp, ourlang.TupleConstructor):
pass # _generate_tuple_constructor(wgn, inp)
elif False: # TODO: isinstance(inp, ourlang.StructConstructor):
pass # _generate_struct_constructor(wgn, inp)
if isinstance(inp, ourlang.TupleConstructor):
_generate_tuple_constructor(wgn, inp)
elif isinstance(inp, ourlang.StructConstructor):
_generate_struct_constructor(wgn, inp)
else:
for stat in inp.statements:
statement(wgn, stat)
@ -568,7 +488,7 @@ def function(inp: ourlang.Function) -> wasm.Function:
(k, v.wasm_type(), )
for k, v in wgn.locals.items()
],
type_var(inp.returns_type_var),
type_(inp.returns),
wgn.statements
)
@ -627,48 +547,38 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
for block in inp.blocks:
block.address = unalloc_ptr + 4 # 4 bytes for allocator header
data_list: List[bytes] = []
data_list = []
for constant in block.data:
assert constant.type_var is not None
mtyp = typing.simplify(constant.type_var)
if mtyp == 'u8':
assert isinstance(constant.value, int)
if isinstance(constant, ourlang.ConstantUInt8):
data_list.append(module_data_u8(constant.value))
continue
if mtyp == 'u32':
assert isinstance(constant.value, int)
if isinstance(constant, ourlang.ConstantUInt32):
data_list.append(module_data_u32(constant.value))
continue
if mtyp == 'u64':
assert isinstance(constant.value, int)
if isinstance(constant, ourlang.ConstantUInt64):
data_list.append(module_data_u64(constant.value))
continue
if mtyp == 'i32':
assert isinstance(constant.value, int)
if isinstance(constant, ourlang.ConstantInt32):
data_list.append(module_data_i32(constant.value))
continue
if mtyp == 'i64':
assert isinstance(constant.value, int)
if isinstance(constant, ourlang.ConstantInt64):
data_list.append(module_data_i64(constant.value))
continue
if mtyp == 'f32':
assert isinstance(constant.value, float)
if isinstance(constant, ourlang.ConstantFloat32):
data_list.append(module_data_f32(constant.value))
continue
if mtyp == 'f64':
assert isinstance(constant.value, float)
if isinstance(constant, ourlang.ConstantFloat64):
data_list.append(module_data_f64(constant.value))
continue
raise NotImplementedError(constant, constant.type_var, mtyp)
raise NotImplementedError(constant)
block_data = b''.join(data_list)
@ -718,49 +628,48 @@ def module(inp: ourlang.Module) -> wasm.Module:
return result
# TODO: Broken after new type system
# def _generate_tuple_constructor(wgn: WasmGenerator, inp: ourlang.TupleConstructor) -> None:
# tmp_var = wgn.temp_var_i32('tuple_adr')
#
# # Allocated the required amounts of bytes in memory
# wgn.i32.const(inp.tuple.alloc_size())
# wgn.call(stdlib_alloc.__alloc__)
# wgn.local.set(tmp_var)
#
# # Store each member individually
# for member in inp.tuple.members:
# mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
# if mtyp is None:
# # In the future might extend this by having structs or tuples
# # as members of struct or tuples
# raise NotImplementedError(expression, inp, member)
#
# wgn.local.get(tmp_var)
# wgn.add_statement('local.get', f'$arg{member.idx}')
# wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
#
# # Return the allocated address
# wgn.local.get(tmp_var)
#
# def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
# tmp_var = wgn.temp_var_i32('struct_adr')
#
# # Allocated the required amounts of bytes in memory
# wgn.i32.const(inp.struct.alloc_size())
# wgn.call(stdlib_alloc.__alloc__)
# wgn.local.set(tmp_var)
#
# # Store each member individually
# for member in inp.struct.members:
# mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
# if mtyp is None:
# # In the future might extend this by having structs or tuples
# # as members of struct or tuples
# raise NotImplementedError(expression, inp, member)
#
# wgn.local.get(tmp_var)
# wgn.add_statement('local.get', f'${member.name}')
# wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
#
# # Return the allocated address
# wgn.local.get(tmp_var)
def _generate_tuple_constructor(wgn: WasmGenerator, inp: ourlang.TupleConstructor) -> None:
tmp_var = wgn.temp_var_i32('tuple_adr')
# Allocated the required amounts of bytes in memory
wgn.i32.const(inp.tuple.alloc_size())
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
# Store each member individually
for member in inp.tuple.members:
mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, member)
wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'$arg{member.idx}')
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
# Return the allocated address
wgn.local.get(tmp_var)
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
tmp_var = wgn.temp_var_i32('struct_adr')
# Allocated the required amounts of bytes in memory
wgn.i32.const(inp.struct.alloc_size())
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
# Store each member individually
for member in inp.struct.members:
mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
if mtyp is None:
# In the future might extend this by having structs or tuples
# as members of struct or tuples
raise NotImplementedError(expression, inp, member)
wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'${member.name}')
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
# Return the allocated address
wgn.local.get(tmp_var)

View File

@ -8,6 +8,4 @@ class StaticError(Exception):
"""
class TypingError(Exception):
"""
An error found during the typing phase
"""
pass

View File

@ -1,17 +1,26 @@
"""
Contains the syntax tree for ourlang
"""
from typing import Dict, List, Optional, Union
from typing import Dict, List, Tuple, Optional, Union
import enum
from typing_extensions import Final
WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
WEBASSEMBLY_BUILDIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
WEBASSEMBLY_BUILDIN_BYTES_OPS: Final = ('len', )
from .typing import (
TypeStruct,
TypeBase,
TypeNone,
TypeBool,
TypeUInt8, TypeUInt32, TypeUInt64,
TypeInt32, TypeInt64,
TypeFloat32, TypeFloat64,
TypeBytes,
TypeTuple, TypeTupleMember,
TypeStaticArray, TypeStaticArrayMember,
TypeStruct, TypeStructMember,
TypeVar,
)
@ -20,11 +29,13 @@ class Expression:
"""
An expression within a statement
"""
__slots__ = ('type_var', )
__slots__ = ('type', 'type_var', )
type: TypeBase
type_var: Optional[TypeVar]
def __init__(self) -> None:
def __init__(self, type_: TypeBase) -> None:
self.type = type_
self.type_var = None
class Constant(Expression):
@ -42,7 +53,6 @@ class ConstantPrimitive(Constant):
value: Union[int, float]
def __init__(self, value: Union[int, float]) -> None:
super().__init__()
self.value = value
class ConstantTuple(Constant):
@ -53,8 +63,20 @@ class ConstantTuple(Constant):
value: List[ConstantPrimitive]
def __init__(self, value: List[ConstantPrimitive]) -> None: # FIXME: Tuple of tuples?
super().__init__()
def __init__(self, type_: TypeTuple, value: List[ConstantPrimitive]) -> None: # FIXME: Tuple of tuples?
super().__init__(type_)
self.value = value
class ConstantStaticArray(Constant):
"""
A StaticArray constant value expression within a statement
"""
__slots__ = ('value', )
value: List[ConstantPrimitive]
def __init__(self, type_: TypeStaticArray, value: List[ConstantPrimitive]) -> None: # FIXME: Arrays of arrays?
super().__init__(type_)
self.value = value
class VariableReference(Expression):
@ -63,10 +85,10 @@ class VariableReference(Expression):
"""
__slots__ = ('variable', )
variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local
variable: 'FunctionParam' # also possibly local
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam']) -> None:
super().__init__()
def __init__(self, type_: TypeBase, variable: 'FunctionParam') -> None:
super().__init__(type_)
self.variable = variable
class UnaryOp(Expression):
@ -78,8 +100,8 @@ class UnaryOp(Expression):
operator: str
right: Expression
def __init__(self, operator: str, right: Expression) -> None:
super().__init__()
def __init__(self, type_: TypeBase, operator: str, right: Expression) -> None:
super().__init__(type_)
self.operator = operator
self.right = right
@ -94,8 +116,8 @@ class BinaryOp(Expression):
left: Expression
right: Expression
def __init__(self, operator: str, left: Expression, right: Expression) -> None:
super().__init__()
def __init__(self, type_: TypeBase, operator: str, left: Expression, right: Expression) -> None:
super().__init__(type_)
self.operator = operator
self.left = left
@ -111,27 +133,73 @@ class FunctionCall(Expression):
arguments: List[Expression]
def __init__(self, function: 'Function') -> None:
super().__init__()
super().__init__(function.returns)
self.function = function
self.arguments = []
class Subscript(Expression):
class AccessBytesIndex(Expression):
"""
A subscript, for example to refer to a static array or tuple
by index
Access a bytes index for reading
"""
__slots__ = ('varref', 'index', )
varref: VariableReference
index: Expression
def __init__(self, varref: VariableReference, index: Expression) -> None:
super().__init__()
def __init__(self, type_: TypeBase, varref: VariableReference, index: Expression) -> None:
super().__init__(type_)
self.varref = varref
self.index = index
class AccessStructMember(Expression):
"""
Access a struct member for reading of writing
"""
__slots__ = ('varref', 'member', )
varref: VariableReference
member: TypeStructMember
def __init__(self, varref: VariableReference, member: TypeStructMember) -> None:
super().__init__(member.type)
self.varref = varref
self.member = member
class AccessTupleMember(Expression):
"""
Access a tuple member for reading of writing
"""
__slots__ = ('varref', 'member', )
varref: VariableReference
member: TypeTupleMember
def __init__(self, varref: VariableReference, member: TypeTupleMember, ) -> None:
super().__init__(member.type)
self.varref = varref
self.member = member
class AccessStaticArrayMember(Expression):
"""
Access a tuple member for reading of writing
"""
__slots__ = ('varref', 'static_array', 'member', )
varref: Union['ModuleConstantReference', VariableReference]
static_array: TypeStaticArray
member: Union[Expression, TypeStaticArrayMember]
def __init__(self, varref: Union['ModuleConstantReference', VariableReference], static_array: TypeStaticArray, member: Union[TypeStaticArrayMember, Expression], ) -> None:
super().__init__(static_array.member_type)
self.varref = varref
self.static_array = static_array
self.member = member
class Fold(Expression):
"""
A (left or right) fold
@ -150,18 +218,31 @@ class Fold(Expression):
def __init__(
self,
type_: TypeBase,
dir_: Direction,
func: 'Function',
base: Expression,
iter_: Expression,
) -> None:
super().__init__()
super().__init__(type_)
self.dir = dir_
self.func = func
self.base = base
self.iter = iter_
class ModuleConstantReference(Expression):
"""
An reference to a module constant expression within a statement
"""
__slots__ = ('definition', )
definition: 'ModuleConstantDef'
def __init__(self, type_: TypeBase, definition: 'ModuleConstantDef') -> None:
super().__init__(type_)
self.definition = definition
class Statement:
"""
A statement within a function
@ -199,32 +280,29 @@ class StatementIf(Statement):
self.else_statements = []
class FunctionParam:
"""
A parameter for a Function
"""
__slots__ = ('name', 'type_str', 'type_var', )
__slots__ = ('name', 'type', 'type_var', )
name: str
type_str: str
type: TypeBase
type_var: Optional[TypeVar]
def __init__(self, name: str, type_str: str) -> None:
def __init__(self, name: str, type_: TypeBase) -> None:
self.name = name
self.type_str = type_str
self.type = type_
self.type_var = None
class Function:
"""
A function processes input and produces output
"""
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_str', 'returns_type_var', 'posonlyargs', )
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns', 'returns_type_var', 'posonlyargs', )
name: str
lineno: int
exported: bool
imported: bool
statements: List[Statement]
returns_str: str
returns: TypeBase
returns_type_var: Optional[TypeVar]
posonlyargs: List[FunctionParam]
@ -234,70 +312,67 @@ class Function:
self.exported = False
self.imported = False
self.statements = []
self.returns_str = 'None'
self.returns = TypeNone()
self.returns_type_var = None
self.posonlyargs = []
# TODO: Broken after new type system
# class StructConstructor(Function):
# """
# The constructor method for a struct
#
# A function will generated to instantiate a struct. The arguments
# will be the defaults
# """
# __slots__ = ('struct', )
#
# struct: TypeStruct
#
# def __init__(self, struct: TypeStruct) -> None:
# super().__init__(f'@{struct.name}@__init___@', -1)
#
# self.returns = struct
#
# for mem in struct.members:
# self.posonlyargs.append(FunctionParam(mem.name, mem.type, ))
#
# self.struct = struct
#
# class TupleConstructor(Function):
# """
# The constructor method for a tuple
# """
# __slots__ = ('tuple', )
#
# tuple: TypeTuple
#
# def __init__(self, tuple_: TypeTuple) -> None:
# name = tuple_.render_internal_name()
#
# super().__init__(f'@{name}@__init___@', -1)
#
# self.returns = tuple_
#
# for mem in tuple_.members:
# self.posonlyargs.append(FunctionParam(f'arg{mem.idx}', mem.type, ))
#
# self.tuple = tuple_
class StructConstructor(Function):
"""
The constructor method for a struct
A function will generated to instantiate a struct. The arguments
will be the defaults
"""
__slots__ = ('struct', )
struct: TypeStruct
def __init__(self, struct: TypeStruct) -> None:
super().__init__(f'@{struct.name}@__init___@', -1)
self.returns = struct
for mem in struct.members:
self.posonlyargs.append(FunctionParam(mem.name, mem.type, ))
self.struct = struct
class TupleConstructor(Function):
"""
The constructor method for a tuple
"""
__slots__ = ('tuple', )
tuple: TypeTuple
def __init__(self, tuple_: TypeTuple) -> None:
name = tuple_.render_internal_name()
super().__init__(f'@{name}@__init___@', -1)
self.returns = tuple_
for mem in tuple_.members:
self.posonlyargs.append(FunctionParam(f'arg{mem.idx}', mem.type, ))
self.tuple = tuple_
class ModuleConstantDef:
"""
A constant definition within a module
"""
__slots__ = ('name', 'lineno', 'type_str', 'type_var', 'constant', 'data_block', )
__slots__ = ('name', 'lineno', 'type', 'constant', 'data_block', )
name: str
lineno: int
type_str: str
type_var: Optional[TypeVar]
type: TypeBase
constant: Constant
data_block: Optional['ModuleDataBlock']
def __init__(self, name: str, lineno: int, type_str: str, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
def __init__(self, name: str, lineno: int, type_: TypeBase, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
self.name = name
self.lineno = lineno
self.type_str = type_str
self.type_var = None
self.type = type_
self.constant = constant
self.data_block = data_block
@ -332,11 +407,23 @@ class Module:
__slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',)
data: ModuleData
types: Dict[str, TypeBase]
structs: Dict[str, TypeStruct]
constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function]
def __init__(self) -> None:
self.types = {
'None': TypeNone(),
'u8': TypeUInt8(),
'u32': TypeUInt32(),
'u64': TypeUInt64(),
'i32': TypeInt32(),
'i64': TypeInt64(),
'f32': TypeFloat32(),
'f64': TypeFloat64(),
'bytes': TypeBytes(),
}
self.data = ModuleData()
self.structs = {}
self.constant_defs = {}

View File

@ -6,28 +6,42 @@ from typing import Any, Dict, NoReturn, Union
import ast
from .typing import (
BUILTIN_TYPES,
TypeBase,
TypeUInt8,
TypeUInt32,
TypeUInt64,
TypeInt32,
TypeInt64,
TypeFloat32,
TypeFloat64,
TypeBytes,
TypeStruct,
TypeStructMember,
TypeTuple,
TypeTupleMember,
TypeStaticArray,
TypeStaticArrayMember,
)
from . import codestyle
from .exceptions import StaticError
from .ourlang import (
WEBASSEMBLY_BUILTIN_FLOAT_OPS,
WEBASSEMBLY_BUILDIN_FLOAT_OPS,
Module, ModuleDataBlock,
Function,
Expression,
AccessBytesIndex, AccessStructMember, AccessTupleMember, AccessStaticArrayMember,
BinaryOp,
ConstantPrimitive, ConstantTuple,
Constant,
ConstantPrimitive, ConstantTuple, ConstantStaticArray,
FunctionCall, Subscript,
# StructConstructor, TupleConstructor,
FunctionCall,
StructConstructor, TupleConstructor,
UnaryOp, VariableReference,
Fold,
Fold, ModuleConstantReference,
Statement,
StatementIf, StatementPass, StatementReturn,
@ -80,16 +94,15 @@ class OurVisitor:
module.constant_defs[res.name] = res
# TODO: Broken after type system
# if isinstance(res, TypeStruct):
# if res.name in module.structs:
# raise StaticError(
# f'{res.name} already defined on line {module.structs[res.name].lineno}'
# )
#
# module.structs[res.name] = res
# constructor = StructConstructor(res)
# module.functions[constructor.name] = constructor
if isinstance(res, TypeStruct):
if res.name in module.structs:
raise StaticError(
f'{res.name} already defined on line {module.structs[res.name].lineno}'
)
module.structs[res.name] = res
constructor = StructConstructor(res)
module.functions[constructor.name] = constructor
if isinstance(res, Function):
if res.name in module.functions:
@ -153,7 +166,7 @@ class OurVisitor:
function.imported = True
if node.returns:
function.returns_str = self.visit_type(module, node.returns)
function.returns = self.visit_type(module, node.returns)
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
@ -181,7 +194,6 @@ class OurVisitor:
if stmt.simple != 1:
raise NotImplementedError('Class with non-simple arguments')
raise NotImplementedError('TODO: Broken after new type system')
member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
struct.members.append(member)
@ -195,22 +207,30 @@ class OurVisitor:
if not isinstance(node.target.ctx, ast.Store):
_raise_static_error(node, 'Must be load context')
exp_type = self.visit_type(module, node.annotation)
if isinstance(node.value, ast.Constant):
return ModuleConstantDef(
node.target.id,
node.lineno,
self.visit_type(module, node.annotation),
exp_type,
self.visit_Module_Constant(module, node.value),
None,
)
if isinstance(node.value, ast.Tuple):
if isinstance(exp_type, TypeTuple):
if not isinstance(node.value, ast.Tuple):
_raise_static_error(node, 'Must be tuple')
if len(exp_type.members) != len(node.value.elts):
_raise_static_error(node, 'Invalid number of tuple values')
tuple_data = [
self.visit_Module_Constant(module, arg_node)
for arg_node in node.value.elts
for arg_node, mem in zip(node.value.elts, exp_type.members)
if isinstance(arg_node, ast.Constant)
]
if len(node.value.elts) != len(tuple_data):
if len(exp_type.members) != len(tuple_data):
_raise_static_error(node, 'Tuple arguments must be constants')
# Allocate the data
@ -221,69 +241,40 @@ class OurVisitor:
return ModuleConstantDef(
node.target.id,
node.lineno,
self.visit_type(module, node.annotation),
ConstantTuple(tuple_data),
exp_type,
ConstantTuple(exp_type, tuple_data),
data_block,
)
raise NotImplementedError('TODO: Broken after new typing system')
if isinstance(exp_type, TypeStaticArray):
if not isinstance(node.value, ast.Tuple):
_raise_static_error(node, 'Must be static array')
# if isinstance(exp_type, TypeTuple):
# if not isinstance(node.value, ast.Tuple):
# _raise_static_error(node, 'Must be tuple')
#
# if len(exp_type.members) != len(node.value.elts):
# _raise_static_error(node, 'Invalid number of tuple values')
#
# tuple_data = [
# self.visit_Module_Constant(module, arg_node)
# for arg_node, mem in zip(node.value.elts, exp_type.members)
# if isinstance(arg_node, ast.Constant)
# ]
# if len(exp_type.members) != len(tuple_data):
# _raise_static_error(node, 'Tuple arguments must be constants')
#
# # Allocate the data
# data_block = ModuleDataBlock(tuple_data)
# module.data.blocks.append(data_block)
#
# # Then return the constant as a pointer
# return ModuleConstantDef(
# node.target.id,
# node.lineno,
# exp_type,
# ConstantTuple(tuple_data),
# data_block,
# )
#
# if isinstance(exp_type, TypeStaticArray):
# if not isinstance(node.value, ast.Tuple):
# _raise_static_error(node, 'Must be static array')
#
# if len(exp_type.members) != len(node.value.elts):
# _raise_static_error(node, 'Invalid number of static array values')
#
# static_array_data = [
# self.visit_Module_Constant(module, arg_node)
# for arg_node in node.value.elts
# if isinstance(arg_node, ast.Constant)
# ]
# if len(exp_type.members) != len(static_array_data):
# _raise_static_error(node, 'Static array arguments must be constants')
#
# # Allocate the data
# data_block = ModuleDataBlock(static_array_data)
# module.data.blocks.append(data_block)
#
# # Then return the constant as a pointer
# return ModuleConstantDef(
# node.target.id,
# node.lineno,
# ConstantStaticArray(static_array_data),
# data_block,
# )
#
# raise NotImplementedError(f'{node} on Module AnnAssign')
if len(exp_type.members) != len(node.value.elts):
_raise_static_error(node, 'Invalid number of static array values')
static_array_data = [
self.visit_Module_Constant(module, arg_node)
for arg_node in node.value.elts
if isinstance(arg_node, ast.Constant)
]
if len(exp_type.members) != len(static_array_data):
_raise_static_error(node, 'Static array arguments must be constants')
# Allocate the data
data_block = ModuleDataBlock(static_array_data)
module.data.blocks.append(data_block)
# Then return the constant as a pointer
return ModuleConstantDef(
node.target.id,
node.lineno,
exp_type,
ConstantStaticArray(exp_type, static_array_data),
data_block,
)
raise NotImplementedError(f'{node} on Module AnnAssign')
def visit_Module_stmt(self, module: Module, node: ast.stmt) -> None:
if isinstance(node, ast.FunctionDef):
@ -318,12 +309,12 @@ class OurVisitor:
_raise_static_error(node, 'Return must have an argument')
return StatementReturn(
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value)
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.value)
)
if isinstance(node, ast.If):
result = StatementIf(
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.test)
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.test)
)
for stmt in node.body:
@ -343,7 +334,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as stmt in FunctionDef')
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp):
if isinstance(node.op, ast.Add):
operator = '+'
@ -368,9 +359,10 @@ 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, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
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),
)
if isinstance(node, ast.UnaryOp):
@ -382,8 +374,9 @@ class OurVisitor:
raise NotImplementedError(f'Operator {node.op}')
return UnaryOp(
exp_type,
operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.operand),
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.operand),
)
if isinstance(node, ast.Compare):
@ -403,13 +396,14 @@ 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, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
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]),
)
if isinstance(node, ast.Call):
return self.visit_Module_FunctionDef_Call(module, function, our_locals, node)
return self.visit_Module_FunctionDef_Call(module, function, our_locals, exp_type, node)
if isinstance(node, ast.Constant):
return self.visit_Module_Constant(
@ -418,12 +412,12 @@ class OurVisitor:
if isinstance(node, ast.Attribute):
return self.visit_Module_FunctionDef_Attribute(
module, function, our_locals, node,
module, function, our_locals, exp_type, node,
)
if isinstance(node, ast.Subscript):
return self.visit_Module_FunctionDef_Subscript(
module, function, our_locals, node,
module, function, our_locals, exp_type, node,
)
if isinstance(node, ast.Name):
@ -432,40 +426,44 @@ class OurVisitor:
if node.id in our_locals:
param = our_locals[node.id]
return VariableReference(param)
if exp_type != param.type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.id} is actually {codestyle.type_(param.type)}')
return VariableReference(param.type, param)
if node.id in module.constant_defs:
cdef = module.constant_defs[node.id]
return VariableReference(cdef)
if exp_type != cdef.type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.id} is actually {codestyle.type_(cdef.type)}')
return ModuleConstantReference(exp_type, cdef)
_raise_static_error(node, f'Undefined variable {node.id}')
if isinstance(node, ast.Tuple):
raise NotImplementedError('TODO: Broken after new type system')
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
# if not isinstance(node.ctx, ast.Load):
# _raise_static_error(node, 'Must be load context')
#
# if isinstance(exp_type, TypeTuple):
# if len(exp_type.members) != len(node.elts):
# _raise_static_error(node, f'Expression is expecting a tuple of size {len(exp_type.members)}, but {len(node.elts)} are given')
#
# tuple_constructor = TupleConstructor(exp_type)
#
# func = module.functions[tuple_constructor.name]
#
# result = FunctionCall(func)
# result.arguments = [
# self.visit_Module_FunctionDef_expr(module, function, our_locals, mem.type, arg_node)
# for arg_node, mem in zip(node.elts, exp_type.members)
# ]
# return result
#
# _raise_static_error(node, f'Expression is expecting a {codestyle.type_(exp_type)}, not a tuple')
if isinstance(exp_type, TypeTuple):
if len(exp_type.members) != len(node.elts):
_raise_static_error(node, f'Expression is expecting a tuple of size {len(exp_type.members)}, but {len(node.elts)} are given')
tuple_constructor = TupleConstructor(exp_type)
func = module.functions[tuple_constructor.name]
result = FunctionCall(func)
result.arguments = [
self.visit_Module_FunctionDef_expr(module, function, our_locals, mem.type, arg_node)
for arg_node, mem in zip(node.elts, exp_type.members)
]
return result
_raise_static_error(node, f'Expression is expecting a {codestyle.type_(exp_type)}, not a tuple')
raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -474,37 +472,48 @@ class OurVisitor:
if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
# if node.func.id in module.structs:
# raise NotImplementedError('TODO: Broken after new type system')
# struct = module.structs[node.func.id]
# struct_constructor = StructConstructor(struct)
#
# func = module.functions[struct_constructor.name]
if node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS:
if node.func.id in module.structs:
struct = module.structs[node.func.id]
struct_constructor = StructConstructor(struct)
func = module.functions[struct_constructor.name]
elif node.func.id in WEBASSEMBLY_BUILDIN_FLOAT_OPS:
if not isinstance(exp_type, (TypeFloat32, TypeFloat64, )):
_raise_static_error(node, f'Cannot make {node.func.id} result in {codestyle.type_(exp_type)}')
if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
return UnaryOp(
exp_type,
'sqrt',
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.args[0]),
)
elif node.func.id == 'u32':
if not isinstance(exp_type, TypeUInt32):
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
# FIXME: This is a stub, proper casting is todo
return UnaryOp(
exp_type,
'cast',
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['u8'], node.args[0]),
)
elif node.func.id == 'len':
if not isinstance(exp_type, TypeInt32):
_raise_static_error(node, f'Cannot make {node.func.id} result in {exp_type}')
if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
return UnaryOp(
exp_type,
'len',
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['bytes'], node.args[0]),
)
elif node.func.id == 'foldl':
# TODO: This should a much more generic function!
@ -527,13 +536,21 @@ class OurVisitor:
if 2 != len(func.posonlyargs):
_raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given')
raise NotImplementedError('TODO: Broken after new type system')
if exp_type.__class__ != func.returns.__class__:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {func.name} actually returns {codestyle.type_(func.returns)}')
if func.returns.__class__ != func.posonlyargs[0].type.__class__:
_raise_static_error(node, f'Expected a foldable function, {func.name} returns a {codestyle.type_(func.returns)} but expects a {codestyle.type_(func.posonlyargs[0].type)}')
if module.types['u8'].__class__ != func.posonlyargs[1].type.__class__:
_raise_static_error(node, 'Only folding over bytes (u8) is supported at this time')
return Fold(
exp_type,
Fold.Direction.LEFT,
func,
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[1]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[2]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, func.returns, node.args[1]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, module.types['bytes'], node.args[2]),
)
else:
if node.func.id not in module.functions:
@ -541,46 +558,51 @@ class OurVisitor:
func = module.functions[node.func.id]
# if func.returns != exp_type:
# _raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {func.name} actually returns {codestyle.type_(func.returns)}')
if len(func.posonlyargs) != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires {len(func.posonlyargs)} arguments but {len(node.args)} are given')
result = FunctionCall(func)
result.arguments.extend(
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr)
self.visit_Module_FunctionDef_expr(module, function, our_locals, param.type, arg_expr)
for arg_expr, param in zip(node.args, func.posonlyargs)
)
return result
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
raise NotImplementedError('Broken after new type system')
# del module
# del function
#
# if not isinstance(node.value, ast.Name):
# _raise_static_error(node, 'Must reference a name')
#
# if not isinstance(node.ctx, ast.Load):
# _raise_static_error(node, 'Must be load context')
#
# if not node.value.id in our_locals:
# _raise_static_error(node, f'Undefined variable {node.value.id}')
#
# param = our_locals[node.value.id]
#
# node_typ = param.type
# if not isinstance(node_typ, TypeStruct):
# _raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
#
# member = node_typ.get_member(node.attr)
# if member is None:
# _raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
#
# return AccessStructMember(
# VariableReference(param),
# member,
# )
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Attribute) -> Expression:
del module
del function
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name')
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if not node.value.id in our_locals:
_raise_static_error(node, f'Undefined variable {node.value.id}')
param = our_locals[node.value.id]
node_typ = param.type
if not isinstance(node_typ, TypeStruct):
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
member = node_typ.get_member(node.attr)
if member is None:
_raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}')
if exp_type != member.type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}.{member.name} is actually {codestyle.type_(member.type)}')
return AccessStructMember(
VariableReference(node_typ, param),
member,
)
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Subscript) -> Expression:
if not isinstance(node.value, ast.Name):
_raise_static_error(node, 'Must reference a name')
@ -590,78 +612,88 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
varref: VariableReference
varref: Union[ModuleConstantReference, VariableReference]
if node.value.id in our_locals:
param = our_locals[node.value.id]
varref = VariableReference(param)
node_typ = param.type
varref = VariableReference(param.type, param)
elif node.value.id in module.constant_defs:
constant_def = module.constant_defs[node.value.id]
varref = VariableReference(constant_def)
node_typ = constant_def.type
varref = ModuleConstantReference(node_typ, constant_def)
else:
_raise_static_error(node, f'Undefined variable {node.value.id}')
slice_expr = self.visit_Module_FunctionDef_expr(
module, function, our_locals, node.slice.value,
module, function, our_locals, module.types['u32'], node.slice.value,
)
return Subscript(varref, slice_expr)
if isinstance(node_typ, TypeBytes):
t_u8 = module.types['u8']
if exp_type != t_u8:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{codestyle.expression(slice_expr)}] is actually {codestyle.type_(t_u8)}')
# if isinstance(node_typ, TypeBytes):
# if isinstance(varref, ModuleConstantReference):
# raise NotImplementedError(f'{node} from module constant')
#
# return AccessBytesIndex(
# varref,
# slice_expr,
# )
#
# if isinstance(node_typ, TypeTuple):
# if not isinstance(slice_expr, ConstantPrimitive):
# _raise_static_error(node, 'Must subscript using a constant index')
#
# idx = slice_expr.value
#
# if not isinstance(idx, int):
# _raise_static_error(node, 'Must subscript using a constant integer index')
#
# if not (0 <= idx < len(node_typ.members)):
# _raise_static_error(node, f'Index {idx} out of bounds for tuple {node.value.id}')
#
# tuple_member = node_typ.members[idx]
#
# if isinstance(varref, ModuleConstantReference):
# raise NotImplementedError(f'{node} from module constant')
#
# return AccessTupleMember(
# varref,
# tuple_member,
# )
#
# if isinstance(node_typ, TypeStaticArray):
# if not isinstance(slice_expr, ConstantPrimitive):
# return AccessStaticArrayMember(
# varref,
# node_typ,
# slice_expr,
# )
#
# idx = slice_expr.value
#
# if not isinstance(idx, int):
# _raise_static_error(node, 'Must subscript using an integer index')
#
# if not (0 <= idx < len(node_typ.members)):
# _raise_static_error(node, f'Index {idx} out of bounds for static array {node.value.id}')
#
# static_array_member = node_typ.members[idx]
#
# return AccessStaticArrayMember(
# varref,
# node_typ,
# static_array_member,
# )
#
# _raise_static_error(node, f'Cannot take index of {node_typ} {node.value.id}')
if isinstance(varref, ModuleConstantReference):
raise NotImplementedError(f'{node} from module constant')
return AccessBytesIndex(
t_u8,
varref,
slice_expr,
)
if isinstance(node_typ, TypeTuple):
if not isinstance(slice_expr, ConstantPrimitive):
_raise_static_error(node, 'Must subscript using a constant index')
idx = slice_expr.value
if not isinstance(idx, int):
_raise_static_error(node, 'Must subscript using a constant integer index')
if not (0 <= idx < len(node_typ.members)):
_raise_static_error(node, f'Index {idx} out of bounds for tuple {node.value.id}')
tuple_member = node_typ.members[idx]
if exp_type != tuple_member.type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{idx}] is actually {codestyle.type_(tuple_member.type)}')
if isinstance(varref, ModuleConstantReference):
raise NotImplementedError(f'{node} from module constant')
return AccessTupleMember(
varref,
tuple_member,
)
if isinstance(node_typ, TypeStaticArray):
if exp_type != node_typ.member_type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.value.id}[{idx}] is actually {codestyle.type_(node_typ.member_type)}')
if not isinstance(slice_expr, ConstantPrimitive):
return AccessStaticArrayMember(
varref,
node_typ,
slice_expr,
)
idx = slice_expr.value
if not isinstance(idx, int):
_raise_static_error(node, 'Must subscript using an integer index')
if not (0 <= idx < len(node_typ.members)):
_raise_static_error(node, f'Index {idx} out of bounds for static array {node.value.id}')
static_array_member = node_typ.members[idx]
return AccessStaticArrayMember(
varref,
node_typ,
static_array_member,
)
_raise_static_error(node, f'Cannot take index of {node_typ} {node.value.id}')
def visit_Module_Constant(self, module: Module, node: ast.Constant) -> ConstantPrimitive:
del module
@ -673,10 +705,10 @@ class OurVisitor:
raise NotImplementedError(f'{node.value} as constant')
def visit_type(self, module: Module, node: ast.expr) -> str:
def visit_type(self, module: Module, node: ast.expr) -> TypeBase:
if isinstance(node, ast.Constant):
if node.value is None:
return 'None'
return module.types['None']
_raise_static_error(node, f'Unrecognized type {node.value}')
@ -684,10 +716,8 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if node.id in BUILTIN_TYPES:
return node.id
raise NotImplementedError('TODO: Broken after type system')
if node.id in module.types:
return module.types[node.id]
if node.id in module.structs:
return module.structs[node.id]
@ -706,35 +736,50 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if node.value.id not in BUILTIN_TYPES: # FIXME: Tuple of tuples?
if node.value.id in module.types:
member_type = module.types[node.value.id]
else:
_raise_static_error(node, f'Unrecognized type {node.value.id}')
return f'{node.value.id}[{node.slice.value.value}]'
type_static_array = TypeStaticArray(member_type)
offset = 0
for idx in range(node.slice.value.value):
static_array_member = TypeStaticArrayMember(idx, offset)
type_static_array.members.append(static_array_member)
offset += member_type.alloc_size()
key = f'{node.value.id}[{node.slice.value.value}]'
if key not in module.types:
module.types[key] = type_static_array
return module.types[key]
if isinstance(node, ast.Tuple):
raise NotImplementedError('TODO: Broken after new type system')
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
# if not isinstance(node.ctx, ast.Load):
# _raise_static_error(node, 'Must be load context')
#
# type_tuple = TypeTuple()
#
# offset = 0
#
# for idx, elt in enumerate(node.elts):
# tuple_member = TypeTupleMember(idx, self.visit_type(module, elt), offset)
#
# type_tuple.members.append(tuple_member)
# offset += tuple_member.type.alloc_size()
#
# key = type_tuple.render_internal_name()
#
# if key not in module.types:
# module.types[key] = type_tuple
# constructor = TupleConstructor(type_tuple)
# module.functions[constructor.name] = constructor
#
# return module.types[key]
type_tuple = TypeTuple()
offset = 0
for idx, elt in enumerate(node.elts):
tuple_member = TypeTupleMember(idx, self.visit_type(module, elt), offset)
type_tuple.members.append(tuple_member)
offset += tuple_member.type.alloc_size()
key = type_tuple.render_internal_name()
if key not in module.types:
module.types[key] = type_tuple
constructor = TupleConstructor(type_tuple)
module.functions[constructor.name] = constructor
return module.types[key]
raise NotImplementedError(f'{node} as type')

View File

@ -26,7 +26,7 @@ def __find_free_block__(g: Generator, alloc_size: i32) -> i32:
g.i32.const(0)
g.return_()
del alloc_size # TODO: Actual implement using a previously freed block
del alloc_size # TODO
g.unreachable()
return i32('return') # To satisfy mypy

View File

@ -3,22 +3,18 @@ Type checks and enriches the given ast
"""
from . import ourlang
from .exceptions import TypingError
from .typing import (
Context,
TypeConstraintBitWidth, TypeConstraintPrimitive, TypeConstraintSigned, TypeConstraintSubscript,
TypeVar,
from_str,
)
from .typing import Context, TypeConstraintBitWidth, TypeConstraintPrimitive, TypeConstraintSigned, TypeVar
def phasm_type(inp: ourlang.Module) -> None:
module(inp)
def constant(ctx: Context, inp: ourlang.Constant) -> TypeVar:
def constant(ctx: 'Context', inp: ourlang.Constant) -> 'TypeVar':
if isinstance(inp, ourlang.ConstantPrimitive):
result = ctx.new_var()
if isinstance(inp.value, int):
if not isinstance(inp.value, int):
raise NotImplementedError('Float constants in new type system')
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
# Need at least this many bits to store this constant value
@ -33,160 +29,87 @@ def constant(ctx: Context, inp: ourlang.Constant) -> TypeVar:
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)
if isinstance(inp, ourlang.ConstantTuple):
result = ctx.new_var()
result.add_constraint(TypeConstraintSubscript(members=(
constant(ctx, x)
for x in inp.value
)))
result.add_location(str(inp.value))
inp.type_var = result
return result
raise NotImplementedError(constant, inp)
def expression(ctx: Context, inp: ourlang.Expression) -> 'TypeVar':
def expression(ctx: 'Context', inp: ourlang.Expression) -> 'TypeVar':
if isinstance(inp, ourlang.Constant):
return constant(ctx, inp)
if isinstance(inp, ourlang.VariableReference):
assert inp.variable.type_var is not None
inp.type_var = inp.variable.type_var
assert inp.variable.type_var is not None, inp
return inp.variable.type_var
if isinstance(inp, ourlang.UnaryOp):
# TODO: Simplified version
if inp.operator not in ('sqrt', ):
raise NotImplementedError(expression, inp, inp.operator)
right = expression(ctx, inp.right)
inp.type_var = right
return right
if isinstance(inp, ourlang.BinaryOp):
if inp.operator in ('+', '-', '*', '|', '&', '^'):
left = expression(ctx, inp.left)
right = expression(ctx, inp.right)
ctx.unify(left, right)
inp.type_var = left
return left
if inp.operator in ('<<', '>>', ):
inp.type_var = ctx.new_var()
inp.type_var.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
inp.type_var.add_constraint(TypeConstraintBitWidth(oneof=(32, 64, )))
inp.type_var.add_constraint(TypeConstraintSigned(False))
left = expression(ctx, inp.left)
right = expression(ctx, inp.right)
ctx.unify(left, right)
ctx.unify(inp.type_var, left)
return left
if inp.operator not in ('+', '-', '|', '&', '^'):
raise NotImplementedError(expression, inp, inp.operator)
left = expression(ctx, inp.left)
right = expression(ctx, inp.right)
ctx.unify(left, right)
return left
if isinstance(inp, ourlang.FunctionCall):
assert inp.function.returns_type_var is not None
for param, expr in zip(inp.function.posonlyargs, inp.arguments):
assert param.type_var is not None
arg = expression(ctx, expr)
ctx.unify(param.type_var, arg)
if inp.function.posonlyargs:
raise NotImplementedError
return inp.function.returns_type_var
if isinstance(inp, ourlang.Subscript):
if not isinstance(inp.index, ourlang.ConstantPrimitive):
raise NotImplementedError(expression, inp, inp.index)
if not isinstance(inp.index.value, int):
raise NotImplementedError(expression, inp, inp.index.value)
expression(ctx, inp.varref)
assert inp.varref.type_var is not None
# TODO: I'd much rather resolve this using the narrow functions
tc_subs = inp.varref.type_var.get_constraint(TypeConstraintSubscript)
if tc_subs is None:
raise TypingError(f'Type cannot be subscripted: {inp.varref.type_var}') from None
try:
# TODO: I'd much rather resolve this using the narrow functions
member = tc_subs.members[inp.index.value]
except IndexError:
raise TypingError(f'Type cannot be subscripted with index {inp.index.value}: {inp.varref.type_var}') from None
inp.type_var = member
return member
raise NotImplementedError(expression, inp)
def function(ctx: Context, inp: ourlang.Function) -> None:
def function(ctx: 'Context', inp: ourlang.Function) -> None:
if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn):
raise NotImplementedError('Functions with not just a return statement')
typ = expression(ctx, inp.statements[0].value)
assert inp.returns_type_var is not None
ctx.unify(inp.returns_type_var, typ)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> None:
constant(ctx, inp.constant)
if inp.type_str is None:
inp.type_var = ctx.new_var()
else:
inp.type_var = from_str(ctx, inp.type_str)
assert inp.constant.type_var is not None
ctx.unify(inp.type_var, inp.constant.type_var)
return
def module(inp: ourlang.Module) -> None:
ctx = Context()
for func in inp.functions.values():
func.returns_type_var = from_str(ctx, func.returns_str, f'{func.name}.(returns)')
func.returns_type_var = _convert_old_type(ctx, func.returns, f'{func.name}.(returns)')
for param in func.posonlyargs:
param.type_var = from_str(ctx, param.type_str, f'{func.name}.{param.name}')
for cdef in inp.constant_defs.values():
module_constant_def(ctx, cdef)
param.type_var = _convert_old_type(ctx, param.type, f'{func.name}.{param.name}')
for func in inp.functions.values():
function(ctx, func)
from . import typing
def _convert_old_type(ctx: Context, inp: typing.TypeBase, location: str) -> TypeVar:
result = ctx.new_var()
if isinstance(inp, typing.TypeUInt8):
result.add_constraint(TypeConstraintBitWidth(minb=8, maxb=8))
result.add_constraint(TypeConstraintSigned(False))
result.add_location(location)
return result
if isinstance(inp, typing.TypeUInt32):
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
result.add_constraint(TypeConstraintSigned(False))
result.add_location(location)
return result
if isinstance(inp, typing.TypeUInt64):
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
result.add_constraint(TypeConstraintSigned(False))
result.add_location(location)
return result
if isinstance(inp, typing.TypeInt32):
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
result.add_constraint(TypeConstraintSigned(True))
result.add_location(location)
return result
if isinstance(inp, typing.TypeInt64):
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
result.add_constraint(TypeConstraintSigned(True))
result.add_location(location)
return result
raise NotImplementedError(_convert_old_type, inp)

View File

@ -1,11 +1,9 @@
"""
The phasm type system
"""
from typing import Callable, Dict, Iterable, Optional, List, Set, Type
from typing import TypeVar as MyPyTypeVar
from typing import Dict, Optional, List, Type
import enum
import re
from .exceptions import TypingError
@ -21,6 +19,88 @@ class TypeBase:
"""
raise NotImplementedError(self, 'alloc_size')
class TypeNone(TypeBase):
"""
The None (or Void) type
"""
__slots__ = ()
class TypeBool(TypeBase):
"""
The boolean type
"""
__slots__ = ()
class TypeUInt8(TypeBase):
"""
The Integer type, unsigned and 8 bits wide
Note that under the hood we need to use i32 to represent
these values in expressions. So we need to add some operations
to make sure the math checks out.
So while this does save bytes in memory, it may not actually
speed up or improve your code.
"""
__slots__ = ()
def alloc_size(self) -> int:
return 4 # Int32 under the hood
class TypeUInt32(TypeBase):
"""
The Integer type, unsigned and 32 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 4
class TypeUInt64(TypeBase):
"""
The Integer type, unsigned and 64 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 8
class TypeInt32(TypeBase):
"""
The Integer type, signed and 32 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 4
class TypeInt64(TypeBase):
"""
The Integer type, signed and 64 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 8
class TypeFloat32(TypeBase):
"""
The Float type, 32 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 4
class TypeFloat64(TypeBase):
"""
The Float type, 64 bits wide
"""
__slots__ = ()
def alloc_size(self) -> int:
return 8
class TypeBytes(TypeBase):
"""
The bytes type
@ -127,56 +207,32 @@ class TypeStruct(TypeBase):
## NEW STUFF BELOW
# This error can also mean that the typer somewhere forgot to write a type
# back to the AST. If so, we need to fix the typer.
ASSERTION_ERROR = 'You must call phasm_type after calling phasm_parse before you can call any other method'
class TypingNarrowProtoError(TypingError):
"""
A proto error when trying to narrow two types
This gets turned into a TypingNarrowError by the unify method
"""
# FIXME: Use consistent naming for unify / narrow / entangle
pass
class TypingNarrowError(TypingError):
"""
An error when trying to unify two Type Variables
"""
def __init__(self, l: 'TypeVar', r: 'TypeVar', msg: str) -> None:
super().__init__(
f'Cannot narrow types {l} and {r}: {msg}'
)
class TypeConstraintBase:
"""
Base class for classes implementing a contraint on a type
"""
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintBase':
def narrow(self, other: 'TypeConstraintBase') -> 'TypeConstraintBase':
raise NotImplementedError('narrow', self, other)
class TypeConstraintPrimitive(TypeConstraintBase):
"""
This contraint on a type defines its primitive shape
"""
__slots__ = ('primitive', )
class Primitive(enum.Enum):
"""
The primitive ID
"""
INT = 0
FLOAT = 1
STATIC_ARRAY = 10
primitive: Primitive
def __init__(self, primitive: Primitive) -> None:
self.primitive = primitive
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintPrimitive':
def narrow(self, other: 'TypeConstraintBase') -> 'TypeConstraintPrimitive':
if not isinstance(other, TypeConstraintPrimitive):
raise Exception('Invalid comparison')
@ -189,10 +245,6 @@ class TypeConstraintPrimitive(TypeConstraintBase):
return f'Primitive={self.primitive.name}'
class TypeConstraintSigned(TypeConstraintBase):
"""
Contraint on whether a signed value can be used or not, or whether
a value can be used in a signed expression
"""
__slots__ = ('signed', )
signed: Optional[bool]
@ -200,7 +252,7 @@ class TypeConstraintSigned(TypeConstraintBase):
def __init__(self, signed: Optional[bool]) -> None:
self.signed = signed
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintSigned':
def narrow(self, other: 'TypeConstraintBase') -> 'TypeConstraintSigned':
if not isinstance(other, TypeConstraintSigned):
raise Exception('Invalid comparison')
@ -218,393 +270,110 @@ class TypeConstraintSigned(TypeConstraintBase):
return f'Signed={self.signed}'
class TypeConstraintBitWidth(TypeConstraintBase):
"""
Contraint on how many bits an expression has or can possibly have
"""
__slots__ = ('oneof', )
__slots__ = ('minb', 'maxb', )
oneof: Set[int]
minb: int
maxb: int
def __init__(self, *, oneof: Optional[Iterable[int]] = None, minb: Optional[int] = None, maxb: Optional[int] = None) -> None:
# For now, support up to 64 bits values
self.oneof = set(oneof) if oneof is not None else set(range(1, 65))
def __init__(self, *, minb: int = 1, maxb: int = 64) -> None:
assert minb is not None or maxb is not None
assert maxb <= 64 # For now, support up to 64 bits values
if minb is not None:
self.oneof = {
x
for x in self.oneof
if minb <= x
}
self.minb = minb
self.maxb = maxb
if maxb is not None:
self.oneof = {
x
for x in self.oneof
if x <= maxb
}
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintBitWidth':
def narrow(self, other: 'TypeConstraintBase') -> 'TypeConstraintBitWidth':
if not isinstance(other, TypeConstraintBitWidth):
raise Exception('Invalid comparison')
new_oneof = self.oneof & other.oneof
if self.minb > other.maxb:
raise TypingNarrowProtoError('Min bitwidth exceeds other max bitwidth')
if not new_oneof:
raise TypingNarrowProtoError('Memory width cannot be resolved')
if other.minb > self.maxb:
raise TypingNarrowProtoError('Other min bitwidth exceeds max bitwidth')
return TypeConstraintBitWidth(oneof=new_oneof)
return TypeConstraintBitWidth(
minb=max(self.minb, other.minb),
maxb=min(self.maxb, other.maxb),
)
def __repr__(self) -> str:
result = 'BitWidth='
items = list(sorted(self.oneof))
if not items:
return result
while items:
itm = items.pop(0)
result += str(itm)
cnt = 0
while cnt < len(items) and items[cnt] == itm + cnt + 1:
cnt += 1
if cnt == 1:
result += ',' + str(items[0])
elif cnt > 1:
result += '..' + str(items[cnt - 1])
items = items[cnt:]
if items:
result += ','
return result
class TypeConstraintSubscript(TypeConstraintBase):
"""
Contraint on allowing a type to be subscripted
"""
__slots__ = ('members', )
members: List['TypeVar']
def __init__(self, *, members: Iterable['TypeVar']) -> None:
self.members = list(members)
def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintSubscript':
if not isinstance(other, TypeConstraintSubscript):
raise Exception('Invalid comparison')
if len(self.members) != len(other.members):
raise TypingNarrowProtoError('Member count does not match')
newmembers = []
for smb, omb in zip(self.members, other.members):
nmb = ctx.new_var()
ctx.unify(nmb, smb)
ctx.unify(nmb, omb)
newmembers.append(nmb)
return TypeConstraintSubscript(members=newmembers)
def __repr__(self) -> str:
return 'Subscript=(' + ','.join(map(repr, self.members)) + ')'
TTypeConstraintClass = MyPyTypeVar('TTypeConstraintClass', bound=TypeConstraintBase)
return f'BitWidth={self.minb}..{self.maxb}'
class TypeVar:
"""
A type variable
"""
# FIXME: Explain the type system
__slots__ = ('ctx', 'ctx_id', )
ctx: 'Context'
ctx_id: int
def __init__(self, ctx: 'Context', ctx_id: int) -> None:
self.ctx = ctx
self.ctx_id = ctx_id
def __init__(self, ctx: 'Context') -> None:
self.context = ctx
self.constraints: Dict[Type[TypeConstraintBase], TypeConstraintBase] = {}
self.locations: List[str] = []
def add_constraint(self, newconst: TypeConstraintBase) -> None:
csts = self.ctx.var_constraints[self.ctx_id]
if newconst.__class__ in csts:
csts[newconst.__class__] = csts[newconst.__class__].narrow(self.ctx, newconst)
if newconst.__class__ in self.constraints:
self.constraints[newconst.__class__] = self.constraints[newconst.__class__].narrow(newconst)
else:
csts[newconst.__class__] = newconst
def get_constraint(self, const_type: Type[TTypeConstraintClass]) -> Optional[TTypeConstraintClass]:
csts = self.ctx.var_constraints[self.ctx_id]
res = csts.get(const_type, None)
assert res is None or isinstance(res, const_type) # type hint
return res
self.constraints[newconst.__class__] = newconst
def add_location(self, ref: str) -> None:
self.ctx.var_locations[self.ctx_id].add(ref)
self.locations.append(ref)
def __repr__(self) -> str:
return (
'TypeVar<'
+ '; '.join(map(repr, self.ctx.var_constraints[self.ctx_id].values()))
+ '; '.join(map(repr, self.constraints.values()))
+ '; locations: '
+ ', '.join(sorted(self.ctx.var_locations[self.ctx_id]))
+ ', '.join(self.locations)
+ '>'
)
class Context:
"""
The context for a collection of type variables
"""
def __init__(self) -> None:
# Variables are unified (or entangled, if you will)
# that means that each TypeVar within a context has an ID,
# and all TypeVars with the same ID are the same TypeVar,
# even if they are a different instance
self.next_ctx_id = 1
self.vars_by_id: Dict[int, List[TypeVar]] = {}
# Store the TypeVar properties as a lookup
# so we can update these when unifying
self.var_constraints: Dict[int, Dict[Type[TypeConstraintBase], TypeConstraintBase]] = {}
self.var_locations: Dict[int, Set[str]] = {}
def new_var(self) -> TypeVar:
ctx_id = self.next_ctx_id
self.next_ctx_id += 1
return TypeVar(self)
result = TypeVar(self, ctx_id)
self.vars_by_id[ctx_id] = [result]
self.var_constraints[ctx_id] = {}
self.var_locations[ctx_id] = set()
return result
def unify(self, l: Optional[TypeVar], r: Optional[TypeVar]) -> None:
# FIXME: Write method doc, find out why pylint doesn't error
assert l is not None, ASSERTION_ERROR
assert r is not None, ASSERTION_ERROR
assert l.ctx_id != r.ctx_id # Dunno if this'll happen, if so, just return
# Backup some values that we'll overwrite
l_ctx_id = l.ctx_id
r_ctx_id = r.ctx_id
l_r_var_list = self.vars_by_id[l_ctx_id] + self.vars_by_id[r_ctx_id]
# Create a new TypeVar, with the combined contraints
# and locations of the old ones
n = self.new_var()
def unify(self, l: 'TypeVar', r: 'TypeVar') -> None:
newtypevar = self.new_var()
try:
for const in self.var_constraints[l_ctx_id].values():
n.add_constraint(const)
for const in self.var_constraints[r_ctx_id].values():
n.add_constraint(const)
except TypingNarrowProtoError as exc:
raise TypingNarrowError(l, r, str(exc)) from None
for const in l.constraints.values():
newtypevar.add_constraint(const)
for const in r.constraints.values():
newtypevar.add_constraint(const)
except TypingNarrowProtoError as ex:
raise TypingNarrowError(l, r, str(ex)) from None
self.var_locations[n.ctx_id] = self.var_locations[l_ctx_id] | self.var_locations[r_ctx_id]
newtypevar.locations.extend(l.locations)
newtypevar.locations.extend(r.locations)
# ##
# And unify (or entangle) the old ones
# Make pointer locations to the constraints and locations
# so they get linked together throughout the unification
# First update the IDs, so they all point to the new list
for type_var in l_r_var_list:
type_var.ctx_id = n.ctx_id
l.constraints = newtypevar.constraints
l.locations = newtypevar.locations
# Update our registry of TypeVars by ID, so we can find them
# on the next unify
self.vars_by_id[n.ctx_id].extend(l_r_var_list)
r.constraints = newtypevar.constraints
r.locations = newtypevar.locations
# Then delete the old values for the now gone variables
# Do this last, so exceptions thrown in the code above
# still have a valid context
del self.var_constraints[l_ctx_id]
del self.var_constraints[r_ctx_id]
del self.var_locations[l_ctx_id]
del self.var_locations[r_ctx_id]
return
def simplify(inp: TypeVar) -> Optional[str]:
"""
Simplifies a TypeVar into a string that wasm can work with
and users can recognize
Should round trip with from_str
"""
tc_prim = inp.get_constraint(TypeConstraintPrimitive)
tc_bits = inp.get_constraint(TypeConstraintBitWidth)
tc_sign = inp.get_constraint(TypeConstraintSigned)
tc_prim = inp.constraints.get(TypeConstraintPrimitive)
tc_bits = inp.constraints.get(TypeConstraintBitWidth)
tc_sign = inp.constraints.get(TypeConstraintSigned)
if tc_prim is None:
return None
assert isinstance(tc_prim, TypeConstraintPrimitive) # type hint
primitive = tc_prim.primitive
if primitive is TypeConstraintPrimitive.Primitive.INT:
if tc_bits is None or tc_sign is None:
return None
if tc_sign.signed is None or len(tc_bits.oneof) != 1:
return None
assert isinstance(tc_bits, TypeConstraintBitWidth) # type hint
assert isinstance(tc_sign, TypeConstraintSigned) # type hint
bitwidth = next(iter(tc_bits.oneof))
if bitwidth not in (8, 32, 64):
if tc_sign.signed is None or tc_bits.minb != tc_bits.maxb or tc_bits.minb not in (8, 32, 64):
return None
base = 'i' if tc_sign.signed else 'u'
return f'{base}{bitwidth}'
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
if len(tc_bits.oneof) != 1:
return None
bitwidth = next(iter(tc_bits.oneof))
if bitwidth not in (32, 64):
return None
return f'f{bitwidth}'
if primitive is TypeConstraintPrimitive.Primitive.STATIC_ARRAY:
tc_subs = inp.get_constraint(TypeConstraintSubscript)
assert tc_subs is not None
assert tc_subs.members
sab = simplify(tc_subs.members[0])
if sab is None:
return None
return f'{sab}[{len(tc_subs.members)}]'
return f'{base}{tc_bits.minb}'
return None
def make_u8(ctx: Context) -> TypeVar:
"""
Makes a u8 TypeVar
"""
result = ctx.new_var()
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
result.add_constraint(TypeConstraintBitWidth(minb=8, maxb=8))
result.add_constraint(TypeConstraintSigned(False))
result.add_location('u8')
return result
def make_u32(ctx: Context) -> TypeVar:
"""
Makes a u32 TypeVar
"""
result = ctx.new_var()
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
result.add_constraint(TypeConstraintSigned(False))
result.add_location('u32')
return result
def make_u64(ctx: Context) -> TypeVar:
"""
Makes a u64 TypeVar
"""
result = ctx.new_var()
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
result.add_constraint(TypeConstraintSigned(False))
result.add_location('u64')
return result
def make_i32(ctx: Context) -> TypeVar:
"""
Makes a i32 TypeVar
"""
result = ctx.new_var()
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
result.add_constraint(TypeConstraintSigned(True))
result.add_location('i32')
return result
def make_i64(ctx: Context) -> TypeVar:
"""
Makes a i64 TypeVar
"""
result = ctx.new_var()
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.INT))
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
result.add_constraint(TypeConstraintSigned(True))
result.add_location('i64')
return result
def make_f32(ctx: Context) -> TypeVar:
"""
Makes a f32 TypeVar
"""
result = ctx.new_var()
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.FLOAT))
result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32))
result.add_location('f32')
return result
def make_f64(ctx: Context) -> TypeVar:
"""
Makes a f64 TypeVar
"""
result = ctx.new_var()
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.FLOAT))
result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64))
result.add_location('f64')
return result
BUILTIN_TYPES: Dict[str, Callable[[Context], TypeVar]] = {
'u8': make_u8,
'u32': make_u32,
'u64': make_u64,
'i32': make_i32,
'i64': make_i64,
'f32': make_f32,
'f64': make_f64,
}
TYPE_MATCH_STATIC_ARRAY = re.compile(r'^([uif][0-9]+)\[([0-9]+)\]')
def from_str(ctx: Context, inp: str, location: Optional[str] = None) -> TypeVar:
"""
Creates a new TypeVar from the string
Should round trip with simplify
The location is a reference to where you found the string
in the source code.
This could be conidered part of parsing. Though that would give trouble
with the context creation.
"""
if inp in BUILTIN_TYPES:
result = BUILTIN_TYPES[inp](ctx)
if location is not None:
result.add_location(location)
return result
match = TYPE_MATCH_STATIC_ARRAY.fullmatch(inp)
if match:
result = ctx.new_var()
result.add_constraint(TypeConstraintPrimitive(TypeConstraintPrimitive.Primitive.STATIC_ARRAY))
result.add_constraint(TypeConstraintSubscript(members=(
# Make copies so they don't get entangled
# with each other.
from_str(ctx, match[1], match[1])
for _ in range(int(match[2]))
)))
result.add_location(inp)
if location is not None:
result.add_location(location)
return result
raise NotImplementedError(from_str, inp)

View File

@ -1,7 +1,7 @@
"""
Helper functions to quickly generate WASM code
"""
from typing import List, Optional
from typing import Any, Dict, List, Optional, Type
import functools

View File

@ -1,5 +1,5 @@
[MASTER]
disable=C0103,C0122,R0902,R0903,R0911,R0912,R0913,R0915,R1710,W0223
disable=C0122,R0903,R0911,R0912,R0913,R0915,R1710,W0223
max-line-length=180
@ -7,4 +7,4 @@ max-line-length=180
good-names=g
[tests]
disable=C0116,R0201
disable=C0116,

View File

@ -1,16 +0,0 @@
"""
Constants for use in the tests
"""
ALL_INT_TYPES = ['u8', 'u32', 'u64', 'i32', 'i64']
COMPLETE_INT_TYPES = ['u32', 'u64', 'i32', 'i64']
ALL_FLOAT_TYPES = ['f32', 'f64']
COMPLETE_FLOAT_TYPES = ALL_FLOAT_TYPES
TYPE_MAP = {
**{x: int for x in ALL_INT_TYPES},
**{x: float for x in ALL_FLOAT_TYPES},
}
COMPLETE_PRIMITIVE_TYPES = COMPLETE_INT_TYPES + COMPLETE_FLOAT_TYPES

View File

@ -5,6 +5,7 @@ from typing import Any, Callable, Dict, Iterable, Optional, TextIO
import ctypes
import io
import warnings
import pywasm.binary
import wasm3
@ -41,7 +42,10 @@ class RunnerBase:
Parses the Phasm code into an AST
"""
self.phasm_ast = phasm_parse(self.phasm_code)
try:
phasm_type(self.phasm_ast)
except NotImplementedError as exc:
warnings.warn(f'phasm_type throws an NotImplementedError on this test: {exc}')
def compile_ast(self) -> None:
"""

View File

@ -2,8 +2,8 @@ import sys
import pytest
from ..helpers import Suite, write_header
from ..runners import RunnerPywasm
from .helpers import Suite, write_header
from .runners import RunnerPywasm
def setup_interpreter(phash_code: str) -> RunnerPywasm:
runner = RunnerPywasm(phash_code)

View File

@ -1,20 +0,0 @@
import pytest
from phasm import typing as sut
class TestTypeConstraintBitWidth:
@pytest.mark.parametrize('oneof,exp', [
(set(), '', ),
({1}, '1', ),
({1,2}, '1,2', ),
({1,2,3}, '1..3', ),
({1,2,3,4}, '1..4', ),
({1,3}, '1,3', ),
({1,4}, '1,4', ),
({1,2,3,4,6,7,8,9}, '1..4,6..9', ),
])
def test_repr(self, oneof, exp):
mut_self = sut.TypeConstraintBitWidth(oneof=oneof)
assert ('BitWidth=' + exp) == repr(mut_self)

View File

@ -1,7 +1,20 @@
import pytest
from ..constants import COMPLETE_PRIMITIVE_TYPES, TYPE_MAP
from ..helpers import Suite
from .helpers import Suite
@pytest.mark.integration_test
def test_i32():
code_py = """
CONSTANT: i32 = 13
@exported
def testEntry() -> i32:
return CONSTANT * 5
"""
result = Suite(code_py).run_code()
assert 65 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
@ -39,46 +52,36 @@ def helper(vector: (u8, u8, u32, u32, u64, u64, )) -> u32:
assert 3333 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_PRIMITIVE_TYPES)
def test_tuple_simple_constructor(type_):
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
def test_static_array_1(type_):
code_py = f"""
CONSTANT: {type_}[1] = (65, )
@exported
def testEntry() -> {type_}:
return helper((24, 57, 80, ))
return helper(CONSTANT)
def helper(vector: ({type_}, {type_}, {type_}, )) -> {type_}:
return vector[0] + vector[1] + vector[2]
def helper(vector: {type_}[1]) -> {type_}:
return vector[0]
"""
result = Suite(code_py).run_code()
assert 161 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
assert 65 == result.returned_value
@pytest.mark.integration_test
def test_tuple_float():
def test_static_array_6():
code_py = """
@exported
def testEntry() -> f32:
return helper((1.0, 2.0, 3.0, ))
CONSTANT: u32[6] = (11, 22, 3333, 4444, 555555, 666666, )
def helper(v: (f32, f32, f32, )) -> f32:
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
@exported
def testEntry() -> u32:
return helper(CONSTANT)
def helper(vector: u32[6]) -> u32:
return vector[2]
"""
result = Suite(code_py).run_code()
assert 3.74 < result.returned_value < 3.75
@pytest.mark.integration_test
@pytest.mark.skip('SIMD support is but a dream')
def test_tuple_i32x4():
code_py = """
@exported
def testEntry() -> i32x4:
return (51, 153, 204, 0, )
"""
result = Suite(code_py).run_code()
assert (1, 2, 3, 0) == result.returned_value
assert 3333 == result.returned_value

View File

@ -3,9 +3,9 @@ import struct
import pytest
from ..helpers import Suite
from .helpers import Suite
@pytest.mark.slow_integration_test
@pytest.mark.integration_test
def test_crc32():
# FIXME: Stub
# crc = 0xFFFFFFFF

View File

@ -1,6 +1,6 @@
import pytest
from ..helpers import Suite
from .helpers import Suite
@pytest.mark.slow_integration_test
def test_fib():

View File

@ -0,0 +1,70 @@
import io
import pytest
from pywasm import binary
from pywasm import Runtime
from wasmer import wat2wasm
def run(code_wat):
code_wasm = wat2wasm(code_wat)
module = binary.Module.from_reader(io.BytesIO(code_wasm))
runtime = Runtime(module, {}, {})
out_put = runtime.exec('testEntry', [])
return (runtime, out_put)
@pytest.mark.parametrize('size,offset,exp_out_put', [
('32', 0, 0x3020100),
('32', 1, 0x4030201),
('64', 0, 0x706050403020100),
('64', 2, 0x908070605040302),
])
def test_i32_64_load(size, offset, exp_out_put):
code_wat = f"""
(module
(memory 1)
(data (memory 0) (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\10")
(func (export "testEntry") (result i{size})
i32.const {offset}
i{size}.load
return ))
"""
(_, out_put) = run(code_wat)
assert exp_out_put == out_put
def test_load_then_store():
code_wat = """
(module
(memory 1)
(data (memory 0) (i32.const 0) "\\04\\00\\00\\00")
(func (export "testEntry") (result i32) (local $my_memory_value i32)
;; Load i32 from address 0
i32.const 0
i32.load
;; Add 8 to the loaded value
i32.const 8
i32.add
local.set $my_memory_value
;; Store back to the memory
i32.const 0
local.get $my_memory_value
i32.store
;; Return something
i32.const 9
return ))
"""
(runtime, out_put) = run(code_wat)
assert 9 == out_put
assert (b'\x0c'+ b'\00' * 23) == runtime.store.mems[0].data[:24]

View File

@ -1,53 +0,0 @@
import pytest
from ..helpers import Suite
@pytest.mark.integration_test
def test_bytes_address():
code_py = """
@exported
def testEntry(f: bytes) -> bytes:
return f
"""
result = Suite(code_py).run_code(b'This is a test')
# THIS DEPENDS ON THE ALLOCATOR
# A different allocator will return a different value
assert 20 == result.returned_value
@pytest.mark.integration_test
def test_bytes_length():
code_py = """
@exported
def testEntry(f: bytes) -> i32:
return len(f)
"""
result = Suite(code_py).run_code(b'This is another test')
assert 20 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[8]
"""
result = Suite(code_py).run_code(b'This is another test')
assert 0x61 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index_out_of_bounds():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[50]
"""
result = Suite(code_py).run_code(b'Short', b'Long' * 100)
assert 0 == result.returned_value

View File

@ -1,71 +0,0 @@
import pytest
from ..helpers import Suite
@pytest.mark.integration_test
@pytest.mark.parametrize('inp', [9, 10, 11, 12])
def test_if_simple(inp):
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a > 10:
return 15
return 3
"""
exp_result = 15 if inp > 10 else 3
suite = Suite(code_py)
result = suite.run_code(inp)
assert exp_result == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Such a return is not how things should be')
def test_if_complex():
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a > 10:
return 10
elif a > 0:
return a
else:
return 0
return -1 # Required due to function type
"""
suite = Suite(code_py)
assert 10 == suite.run_code(20).returned_value
assert 10 == suite.run_code(10).returned_value
assert 8 == suite.run_code(8).returned_value
assert 0 == suite.run_code(0).returned_value
assert 0 == suite.run_code(-1).returned_value
@pytest.mark.integration_test
def test_if_nested():
code_py = """
@exported
def testEntry(a: i32, b: i32) -> i32:
if a > 11:
if b > 11:
return 3
return 2
if b > 11:
return 1
return 0
"""
suite = Suite(code_py)
assert 3 == suite.run_code(20, 20).returned_value
assert 2 == suite.run_code(20, 10).returned_value
assert 1 == suite.run_code(10, 20).returned_value
assert 0 == suite.run_code(10, 10).returned_value

View File

@ -1,27 +0,0 @@
import pytest
from ..helpers import Suite
@pytest.mark.integration_test
def test_imported():
code_py = """
@imported
def helper(mul: i32) -> i32:
pass
@exported
def testEntry() -> i32:
return helper(2)
"""
def helper(mul: int) -> int:
return 4238 * mul
result = Suite(code_py).run_code(
runtime='wasmer',
imports={
'helper': helper,
}
)
assert 8476 == result.returned_value

View File

@ -1,376 +0,0 @@
import pytest
from phasm.exceptions import TypingError
from ..helpers import Suite
from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, TYPE_MAP
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
def test_expr_constant_int(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 13
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
def test_expr_constant_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
def test_expr_constant_entanglement():
code_py = """
@exported
def testEntry() -> u8:
return 1000
"""
with pytest.raises(TypingError, match='u8.*1000'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
def test_module_constant_int(type_):
code_py = f"""
CONSTANT: {type_} = 13
@exported
def testEntry() -> {type_}:
return CONSTANT
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
def test_module_constant_float(type_):
code_py = f"""
CONSTANT: {type_} = 32.125
@exported
def testEntry() -> {type_}:
return CONSTANT
"""
result = Suite(code_py).run_code()
assert 32.125 == result.returned_value
@pytest.mark.integration_test
def test_module_constant_entanglement():
code_py = """
CONSTANT: u8 = 1000
@exported
def testEntry() -> u32:
return 14
"""
with pytest.raises(TypingError, match='u8.*1000'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u32', 'u64']) # FIXME: Support u8, requires an extra AND operation
def test_logical_left_shift(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 << 3
"""
result = Suite(code_py).run_code()
assert 80 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u32', 'u64'])
def test_logical_right_shift_left_bit_zero(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 >> 3
"""
result = Suite(code_py).run_code()
assert 1 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_logical_right_shift_left_bit_one():
code_py = """
@exported
def testEntry() -> u32:
return 4294967295 >> 16
"""
result = Suite(code_py).run_code()
assert 0xFFFF == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_bitwise_or(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 | 3
"""
result = Suite(code_py).run_code()
assert 11 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_bitwise_xor(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 ^ 3
"""
result = Suite(code_py).run_code()
assert 9 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_bitwise_and(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 & 3
"""
result = Suite(code_py).run_code()
assert 2 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
def test_addition_int(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 + 3
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@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_', COMPLETE_INT_TYPES)
def test_subtraction_int(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 - 3
"""
result = Suite(code_py).run_code()
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_float(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)
# TODO: Multiplication
# TODO: Division
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['f32', 'f64'])
def test_builtins_sqrt(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return sqrt(25.0)
"""
result = Suite(code_py).run_code()
assert 5 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
def test_function_argument(type_):
code_py = f"""
@exported
def testEntry(a: {type_}) -> {type_}:
return a
"""
result = Suite(code_py).run_code(125)
assert 125 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.skip('TODO')
def test_explicit_positive_number():
code_py = """
@exported
def testEntry() -> i32:
return +523
"""
result = Suite(code_py).run_code()
assert 523 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('TODO')
def test_explicit_negative_number():
code_py = """
@exported
def testEntry() -> i32:
return -19
"""
result = Suite(code_py).run_code()
assert -19 == result.returned_value
@pytest.mark.integration_test
def test_call_no_args():
code_py = """
def helper() -> i32:
return 19
@exported
def testEntry() -> i32:
return helper()
"""
result = Suite(code_py).run_code()
assert 19 == result.returned_value
@pytest.mark.integration_test
def test_call_pre_defined():
code_py = """
def helper(left: i32, right: i32) -> i32:
return left + right
@exported
def testEntry() -> i32:
return helper(10, 3)
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
@pytest.mark.integration_test
def test_call_post_defined():
code_py = """
@exported
def testEntry() -> i32:
return helper(10, 3)
def helper(left: i32, right: i32) -> i32:
return left - right
"""
result = Suite(code_py).run_code()
assert 7 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
def test_call_with_expression_int(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return helper(10 + 20, 3 + 5)
def helper(left: {type_}, right: {type_}) -> {type_}:
return left - right
"""
result = Suite(code_py).run_code()
assert 22 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
def test_call_with_expression_float(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return helper(10.078125 + 90.046875, 63.0 + 5.0)
def helper(left: {type_}, right: {type_}) -> {type_}:
return left - right
"""
result = Suite(code_py).run_code()
assert 32.125 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_call_invalid_type():
code_py = """
def helper() -> i64:
return 19
@exported
def testEntry() -> i32:
return helper()
"""
with pytest.raises(TypingError, match=r'i32.*i64'):
Suite(code_py).run_code()

View File

@ -1,173 +0,0 @@
import pytest
from phasm.exceptions import StaticError, TypingError
from ..constants import (
ALL_FLOAT_TYPES, ALL_INT_TYPES, COMPLETE_INT_TYPES, COMPLETE_PRIMITIVE_TYPES, TYPE_MAP
)
from ..helpers import Suite
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
def test_module_constant(type_):
code_py = f"""
CONSTANT: {type_}[3] = (24, 57, 80, )
@exported
def testEntry() -> {type_}:
return CONSTANT[0]
"""
result = Suite(code_py).run_code()
assert 24 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.skip('To decide: What to do on out of index?')
@pytest.mark.parametrize('type_', COMPLETE_PRIMITIVE_TYPES)
def test_static_array_indexed(type_):
code_py = f"""
CONSTANT: {type_}[3] = (24, 57, 80, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT, 0, 1, 2)
def helper(array: {type_}[3], i0: u32, i1: u32, i2: u32) -> {type_}:
return array[i0] + array[i1] + array[i2]
"""
result = Suite(code_py).run_code()
assert 161 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
def test_function_call_int(type_):
code_py = f"""
CONSTANT: {type_}[3] = (24, 57, 80, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT)
def helper(array: {type_}[3]) -> {type_}:
return array[0] + array[1] + array[2]
"""
result = Suite(code_py).run_code()
assert 161 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
def test_function_call_float(type_):
code_py = f"""
CONSTANT: {type_}[3] = (24.0, 57.5, 80.75, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT)
def helper(array: {type_}[3]) -> {type_}:
return array[0] + array[1] + array[2]
"""
result = Suite(code_py).run_code()
assert 162.25 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_module_constant_type_mismatch_bitwidth():
code_py = """
CONSTANT: u8[3] = (24, 57, 280, )
"""
with pytest.raises(TypingError, match='u8.*280'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_return_as_int():
code_py = """
CONSTANT: u8[3] = (24, 57, 80, )
def testEntry() -> u32:
return CONSTANT
"""
with pytest.raises(TypingError, match=r'u32.*u8\[3\]'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_module_constant_type_mismatch_not_subscriptable():
code_py = """
CONSTANT: u8 = 24
@exported
def testEntry() -> u8:
return CONSTANT[0]
"""
with pytest.raises(TypingError, match='Type cannot be subscripted:'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_module_constant_type_mismatch_index_out_of_range():
code_py = """
CONSTANT: u8[3] = (24, 57, 80, )
@exported
def testEntry() -> u8:
return CONSTANT[3]
"""
with pytest.raises(TypingError, match='Type cannot be subscripted with index 3:'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_static_array_constant_too_few_values():
code_py = """
CONSTANT: u8[3] = (24, 57, )
"""
with pytest.raises(TypingError, match='Member count does not match'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_static_array_constant_too_many_values():
code_py = """
CONSTANT: u8[3] = (24, 57, 1, 1, )
"""
with pytest.raises(TypingError, match='Member count does not match'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_static_array_constant_type_mismatch():
code_py = """
CONSTANT: u8[3] = (24, 4000, 1, )
"""
with pytest.raises(TypingError, match='u8.*4000'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.skip('To decide: What to do on out of index?')
def test_static_array_index_out_of_bounds():
code_py = """
CONSTANT0: u32[3] = (24, 57, 80, )
CONSTANT1: u32[16] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, )
@exported
def testEntry() -> u32:
return CONSTANT0[16]
"""
result = Suite(code_py).run_code()
assert 0 == result.returned_value

View File

@ -0,0 +1,31 @@
import pytest
from .helpers import Suite
@pytest.mark.integration_test
def test_bytes_index_out_of_bounds():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[50]
"""
result = Suite(code_py).run_code(b'Short', b'Long' * 100)
assert 0 == result.returned_value
@pytest.mark.integration_test
def test_static_array_index_out_of_bounds():
code_py = """
CONSTANT0: u32[3] = (24, 57, 80, )
CONSTANT1: u32[16] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, )
@exported
def testEntry() -> u32:
return CONSTANT0[16]
"""
result = Suite(code_py).run_code()
assert 0 == result.returned_value

View File

@ -0,0 +1,586 @@
import pytest
from .helpers import Suite
TYPE_MAP = {
'u8': int,
'u32': int,
'u64': int,
'i32': int,
'i64': int,
'f32': float,
'f64': float,
}
COMPLETE_SIMPLE_TYPES = [
'u32', 'u64',
'i32', 'i64',
'f32', 'f64',
]
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
def test_return(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 13
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
def test_addition(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 + 3
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
def test_subtraction(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 - 3
"""
result = Suite(code_py).run_code()
assert 7 == 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_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 << 3
"""
result = Suite(code_py).run_code()
assert 80 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_logical_right_shift(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 >> 3
"""
result = Suite(code_py).run_code()
assert 1 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_bitwise_or(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 | 3
"""
result = Suite(code_py).run_code()
assert 11 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_bitwise_xor(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 ^ 3
"""
result = Suite(code_py).run_code()
assert 9 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64'])
def test_bitwise_and(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 & 3
"""
result = Suite(code_py).run_code()
assert 2 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['f32', 'f64'])
def test_buildins_sqrt(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return sqrt(25)
"""
result = Suite(code_py).run_code()
assert 5 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
def test_arg(type_):
code_py = f"""
@exported
def testEntry(a: {type_}) -> {type_}:
return a
"""
result = Suite(code_py).run_code(125)
assert 125 == result.returned_value
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
def testEntry(a: i32) -> i64:
return a
"""
result = Suite(code_py).run_code(125)
assert 125 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_i32_plus_i64():
code_py = """
@exported
def testEntry(a: i32, b: i64) -> i64:
return a + b
"""
result = Suite(code_py).run_code(125, 100)
assert 225 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_f32_to_f64():
code_py = """
@exported
def testEntry(a: f32) -> f64:
return a
"""
result = Suite(code_py).run_code(125.5)
assert 125.5 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Do we want it to work like this?')
def test_f32_plus_f64():
code_py = """
@exported
def testEntry(a: f32, b: f64) -> f64:
return a + b
"""
result = Suite(code_py).run_code(125.5, 100.25)
assert 225.75 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('TODO')
def test_uadd():
code_py = """
@exported
def testEntry() -> i32:
return +523
"""
result = Suite(code_py).run_code()
assert 523 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('TODO')
def test_usub():
code_py = """
@exported
def testEntry() -> i32:
return -19
"""
result = Suite(code_py).run_code()
assert -19 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('inp', [9, 10, 11, 12])
def test_if_simple(inp):
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a > 10:
return 15
return 3
"""
exp_result = 15 if inp > 10 else 3
suite = Suite(code_py)
result = suite.run_code(inp)
assert exp_result == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('Such a return is not how things should be')
def test_if_complex():
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a > 10:
return 10
elif a > 0:
return a
else:
return 0
return -1 # Required due to function type
"""
suite = Suite(code_py)
assert 10 == suite.run_code(20).returned_value
assert 10 == suite.run_code(10).returned_value
assert 8 == suite.run_code(8).returned_value
assert 0 == suite.run_code(0).returned_value
assert 0 == suite.run_code(-1).returned_value
@pytest.mark.integration_test
def test_if_nested():
code_py = """
@exported
def testEntry(a: i32, b: i32) -> i32:
if a > 11:
if b > 11:
return 3
return 2
if b > 11:
return 1
return 0
"""
suite = Suite(code_py)
assert 3 == suite.run_code(20, 20).returned_value
assert 2 == suite.run_code(20, 10).returned_value
assert 1 == suite.run_code(10, 20).returned_value
assert 0 == suite.run_code(10, 10).returned_value
@pytest.mark.integration_test
def test_call_no_args():
code_py = """
def helper() -> i32:
return 19
@exported
def testEntry() -> i32:
return helper()
"""
result = Suite(code_py).run_code()
assert 19 == result.returned_value
@pytest.mark.integration_test
def test_call_pre_defined():
code_py = """
def helper(left: i32, right: i32) -> i32:
return left + right
@exported
def testEntry() -> i32:
return helper(10, 3)
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
@pytest.mark.integration_test
def test_call_post_defined():
code_py = """
@exported
def testEntry() -> i32:
return helper(10, 3)
def helper(left: i32, right: i32) -> i32:
return left - right
"""
result = Suite(code_py).run_code()
assert 7 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
def test_call_with_expression(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return helper(10 + 20, 3 + 5)
def helper(left: {type_}, right: {type_}) -> {type_}:
return left - right
"""
result = Suite(code_py).run_code()
assert 22 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.skip('Not yet implemented')
def test_assign():
code_py = """
@exported
def testEntry() -> i32:
a: i32 = 8947
return a
"""
result = Suite(code_py).run_code()
assert 8947 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_MAP.keys())
def test_struct_0(type_):
code_py = f"""
class CheckedValue:
value: {type_}
@exported
def testEntry() -> {type_}:
return helper(CheckedValue(23))
def helper(cv: CheckedValue) -> {type_}:
return cv.value
"""
result = Suite(code_py).run_code()
assert 23 == result.returned_value
@pytest.mark.integration_test
def test_struct_1():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2))
def helper(shape: Rectangle) -> i32:
return shape.height + shape.width + shape.border
"""
result = Suite(code_py).run_code()
assert 252 == result.returned_value
@pytest.mark.integration_test
def test_struct_2():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2), Rectangle(200, 90, 3))
def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
return shape1.height + shape1.width + shape1.border + shape2.height + shape2.width + shape2.border
"""
result = Suite(code_py).run_code()
assert 545 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
def test_tuple_simple_constructor(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return helper((24, 57, 80, ))
def helper(vector: ({type_}, {type_}, {type_}, )) -> {type_}:
return vector[0] + vector[1] + vector[2]
"""
result = Suite(code_py).run_code()
assert 161 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_tuple_float():
code_py = """
@exported
def testEntry() -> f32:
return helper((1.0, 2.0, 3.0, ))
def helper(v: (f32, f32, f32, )) -> f32:
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
"""
result = Suite(code_py).run_code()
assert 3.74 < result.returned_value < 3.75
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
def test_static_array_module_constant(type_):
code_py = f"""
CONSTANT: {type_}[3] = (24, 57, 80, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT)
def helper(array: {type_}[3]) -> {type_}:
return array[0] + array[1] + array[2]
"""
result = Suite(code_py).run_code()
assert 161 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_SIMPLE_TYPES)
def test_static_array_indexed(type_):
code_py = f"""
CONSTANT: {type_}[3] = (24, 57, 80, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT, 0, 1, 2)
def helper(array: {type_}[3], i0: u32, i1: u32, i2: u32) -> {type_}:
return array[i0] + array[i1] + array[i2]
"""
result = Suite(code_py).run_code()
assert 161 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
def test_bytes_address():
code_py = """
@exported
def testEntry(f: bytes) -> bytes:
return f
"""
result = Suite(code_py).run_code(b'This is a test')
# THIS DEPENDS ON THE ALLOCATOR
# A different allocator will return a different value
assert 20 == result.returned_value
@pytest.mark.integration_test
def test_bytes_length():
code_py = """
@exported
def testEntry(f: bytes) -> i32:
return len(f)
"""
result = Suite(code_py).run_code(b'This is another test')
assert 20 == result.returned_value
@pytest.mark.integration_test
def test_bytes_index():
code_py = """
@exported
def testEntry(f: bytes) -> u8:
return f[8]
"""
result = Suite(code_py).run_code(b'This is another test')
assert 0x61 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.skip('SIMD support is but a dream')
def test_tuple_i32x4():
code_py = """
@exported
def testEntry() -> i32x4:
return (51, 153, 204, 0, )
"""
result = Suite(code_py).run_code()
assert (1, 2, 3, 0) == result.returned_value
@pytest.mark.integration_test
def test_imported():
code_py = """
@imported
def helper(mul: i32) -> i32:
pass
@exported
def testEntry() -> i32:
return helper(2)
"""
def helper(mul: int) -> int:
return 4238 * mul
result = Suite(code_py).run_code(
runtime='wasmer',
imports={
'helper': helper,
}
)
assert 8476 == result.returned_value

View File

@ -1,65 +1,18 @@
import pytest
from ..helpers import Suite
from phasm.parser import phasm_parse
from phasm.exceptions import StaticError
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ('i32', 'f64', ))
def test_struct_0(type_):
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_function_argument(type_):
code_py = f"""
class CheckedValue:
value: {type_}
@exported
def testEntry() -> {type_}:
return helper(CheckedValue(23))
def helper(cv: CheckedValue) -> {type_}:
return cv.value
def helper(a: {type_}) -> (i32, i32, ):
return a
"""
result = Suite(code_py).run_code()
assert 23 == result.returned_value
@pytest.mark.integration_test
def test_struct_1():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2))
def helper(shape: Rectangle) -> i32:
return shape.height + shape.width + shape.border
"""
result = Suite(code_py).run_code()
assert 252 == result.returned_value
@pytest.mark.integration_test
def test_struct_2():
code_py = """
class Rectangle:
height: i32
width: i32
border: i32
@exported
def testEntry() -> i32:
return helper(Rectangle(100, 150, 2), Rectangle(200, 90, 3))
def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
return shape1.height + shape1.width + shape1.border + shape2.height + shape2.width + shape2.border
"""
result = Suite(code_py).run_code()
assert 545 == result.returned_value
with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), a is actually {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
@ -86,6 +39,21 @@ def testEntry(arg: ({type_}, )) -> (i32, i32, ):
with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), arg\\[0\\] is actually {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_function_result(type_):
code_py = f"""
def helper() -> {type_}:
return 1
@exported
def testEntry() -> (i32, i32, ):
return helper()
"""
with pytest.raises(StaticError, match=f'Static error on line 7: Expected \\(i32, i32, \\), helper actually returns {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_too_few_values():
code_py = """
@ -112,3 +80,30 @@ CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_static_array_constant_too_few_values():
code_py = """
CONSTANT: u8[3] = (24, 57, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of static array values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_static_array_constant_too_many_values():
code_py = """
CONSTANT: u8[3] = (24, 57, 1, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of static array values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_static_array_constant_type_mismatch():
code_py = """
CONSTANT: u8[3] = (24, 4000, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'):
phasm_parse(code_py)

View File

@ -2,8 +2,8 @@ import sys
import pytest
from ..helpers import write_header
from ..runners import RunnerPywasm3 as Runner
from .helpers import write_header
from .runners import RunnerPywasm3 as Runner
def setup_interpreter(phash_code: str) -> Runner:
runner = Runner(phash_code)

View File

@ -0,0 +1,31 @@
import pytest
from phasm.parser import phasm_parse
from phasm.typer import phasm_type
from phasm.exceptions import TypingError
@pytest.mark.integration_test
def test_constant_too_wide():
code_py = """
def func_const() -> u8:
return 0xFFF
"""
ast = phasm_parse(code_py)
with pytest.raises(TypingError, match='Other min bitwidth exceeds max bitwidth'):
phasm_type(ast)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', [32, 64])
def test_signed_mismatch(type_):
code_py = f"""
def func_const() -> u{type_}:
return 0
def func_call() -> i{type_}:
return func_const()
"""
ast = phasm_parse(code_py)
with pytest.raises(TypingError, match='Signed does not match'):
phasm_type(ast)