Compare commits

...

31 Commits

Author SHA1 Message Date
Johan B.W. de Vries
a838035e1a Cleanup
- Removed unused _str types
- Tested with untyped function arguments, seems to work a bit
2022-11-27 12:33:17 +01:00
Johan B.W. de Vries
30a4cee5af Fixes
- Tuple wasn't an applied type yet
- phasm_type3 would re-order type IDs between prints
- AppliedType3 wouldn't store the args for iterators
-
2022-11-26 14:46:31 +01:00
Johan B.W. de Vries
3bac625714 Introduced type classes, added debugging info for constraint 2022-11-25 15:04:15 +01:00
Johan B.W. de Vries
9f21d0fd1d More work on type3 2022-11-24 15:43:23 +01:00
Johan B.W. de Vries
b5a28daebf Ripping out Type2 (type_var) system 2022-11-24 14:49:17 +01:00
Johan B.W. de Vries
8cc47ae63e Cleanup to module_constant_def, function calls 2022-11-17 19:30:37 +01:00
Johan B.W. de Vries
13f3f33740 PlaceHolder all the things 2022-11-17 15:23:56 +01:00
Johan B.W. de Vries
a0645d94dd More ideas for type3 2022-11-17 14:51:14 +01:00
Johan B.W. de Vries
79ff11f622 Started on attempt3 of the type system 2022-11-17 13:31:10 +01:00
Johan B.W. de Vries
55a45ff17c Trying out some things regarding StaticArray 2022-11-16 13:50:03 +01:00
Johan B.W. de Vries
42c9ff6ca7 idea: Actual type class 2022-10-23 13:52:48 +02:00
Johan B.W. de Vries
312f7949bd Add mathematic edge case tests 2022-09-22 11:58:18 +02:00
Johan B.W. de Vries
bce3ed7ba1 Implements division
Division by zero is a trap on most runtimes, following
the Let it Crash philosophy
2022-09-22 11:50:38 +02:00
Johan B.W. de Vries
977c449c3f Removed redundant file 2022-09-19 14:55:05 +02:00
Johan B.W. de Vries
2a6da91eb9 Simplified locations, adds typing tests 2022-09-19 14:53:22 +02:00
Johan B.W. de Vries
4d3c0c6c3c StaticArray with constant index works again
Also, fix issue with f64 being parsed as f32
2022-09-19 14:43:15 +02:00
Johan B.W. de Vries
5da45e78c2 More work on StaticArray
Also naming fix, buildin => builtin.

Removes the use of ConstantStaticArray, as this was context dependent
2022-09-19 13:50:20 +02:00
Johan B.W. de Vries
4f7608a601 Fix: ModuleConstantDef type annotation was ignored 2022-09-19 12:29:48 +02:00
Johan B.W. de Vries
0097ce782d First work on restoring StaticArray
Removed the separate ModuleConstantRef since you can tell by the variable
property of VariableReference. We'll also add local variables there later
on.
2022-09-19 12:15:03 +02:00
Johan B.W. de Vries
299551db1b All primitive tests work again 2022-09-19 11:49:10 +02:00
Johan B.W. de Vries
906b15c93c Large cleanup to the tests
They are now better organized and easier to extend, I hope.
2022-09-19 11:16:34 +02:00
Johan B.W. de Vries
07c0688d1b Ripping out old type system. Will have to reimplement
bytes, static array, tuple and struct.
2022-09-17 20:50:06 +02:00
Johan B.W. de Vries
564f00a419 Work on ripping out old type system 2022-09-17 20:13:16 +02:00
Johan B.W. de Vries
58f74d3e1d Restored function calling 2022-09-17 19:37:49 +02:00
Johan B.W. de Vries
4b46483895 Worked on floats 2022-09-17 19:21:56 +02:00
Johan B.W. de Vries
b2816164f9 Improved unification 2022-09-17 17:14:17 +02:00
Johan B.W. de Vries
6f3d9a5bcc First attempt at ripping out old system
This breaks test_addition[u32], which is a good thing to chase next.
2022-09-16 17:39:46 +02:00
Johan B.W. de Vries
2d0daf4b90 Fixes 2022-09-16 17:04:13 +02:00
Johan B.W. de Vries
7669f3cbca More framework stuff 2022-09-16 17:01:23 +02:00
Johan B.W. de Vries
48e16c38b9 FunctionParam is a class, more framework stuff 2022-09-16 16:43:40 +02:00
Johan B.W. de Vries
7acb2bd8e6 Framework sketch 2022-09-16 15:54:24 +02:00
36 changed files with 2538 additions and 1934 deletions

View File

@ -1,7 +1,13 @@
# TODO
- Implement a trace() builtin for debugging
- Implement a proper type matching / checking system
- Implement subscript as an operator
- Re-implement Subscript contraints - Doing the LiteralFitsConstraint with a tuple doesn't put the types on the tuple elements
- Implement structs again, with the `.foo` notation working
- Rename constant to literal
- Implement a trace() builtin for debugging
- Check if we can use DataView in the Javascript examples, e.g. with setUint32
- Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this.
- Implement a FizzBuzz example

View File

@ -3,10 +3,10 @@ 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
from typing import Generator, Optional
from . import ourlang
from . import typing
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
def phasm_render(inp: ourlang.Module) -> str:
"""
@ -15,64 +15,34 @@ def phasm_render(inp: ourlang.Module) -> str:
return module(inp)
Statements = Generator[str, None, None]
#
# def type_var(inp: Optional[typing.TypeVar]) -> str:
# """
# Render: type's name
# """
# assert inp is not None, typing.ASSERTION_ERROR
#
# mtyp = typing.simplify(inp)
# if mtyp is None:
# raise NotImplementedError(f'Rendering type {inp}')
#
# return mtyp
def type_(inp: typing.TypeBase) -> str:
def type3(inp: Type3OrPlaceholder) -> str:
"""
Render: Type (name)
Render: type's name
"""
if isinstance(inp, typing.TypeNone):
return 'None'
assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR
if isinstance(inp, typing.TypeBool):
return 'bool'
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:
def struct_definition(inp: ourlang.StructDefinition) -> str:
"""
Render: TypeStruct's definition
"""
result = f'class {inp.name}:\n'
for mem in inp.members:
result += f' {mem.name}: {type_(mem.type)}\n'
result = f'class {inp.struct_type3.name}:\n'
for mem, typ in inp.struct_type3.members.items():
result += f' {mem}: {type3(typ)}\n'
return result
@ -80,40 +50,38 @@ def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
"""
Render: Module Constant's definition
"""
return f'{inp.name}: {type_(inp.type)} = {expression(inp.constant)}\n'
return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n'
def expression(inp: ourlang.Expression) -> str:
"""
Render: A Phasm expression
"""
if isinstance(inp, (
ourlang.ConstantUInt8, ourlang.ConstantUInt32, ourlang.ConstantUInt64,
ourlang.ConstantInt32, ourlang.ConstantInt64,
)):
return str(inp.value)
if isinstance(inp, (ourlang.ConstantFloat32, ourlang.ConstantFloat64, )):
# These might not round trip if the original constant
if isinstance(inp, ourlang.ConstantPrimitive):
# Floats might not round trip if the original constant
# could not fit in the given float type
return str(inp.value)
if isinstance(inp, (ourlang.ConstantTuple, ourlang.ConstantStaticArray, )):
if isinstance(inp, ourlang.ConstantTuple):
return '(' + ', '.join(
expression(x)
for x in inp.value
) + ', )'
if isinstance(inp, ourlang.VariableReference):
return str(inp.name)
return str(inp.variable.name)
if isinstance(inp, ourlang.UnaryOp):
if (
inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS
or inp.operator in ourlang.WEBASSEMBLY_BUILDIN_BYTES_OPS):
inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS
or inp.operator in ourlang.WEBASSEMBLY_BUILTIN_BYTES_OPS):
return f'{inp.operator}({expression(inp.right)})'
if inp.operator == 'cast':
return f'{type_(inp.type)}({expression(inp.right)})'
mtyp = type3(inp.type3)
if mtyp is None:
raise NotImplementedError(f'Casting to type {inp.type_var}')
return f'{mtyp}({expression(inp.right)})'
return f'{inp.operator}{expression(inp.right)}'
@ -127,32 +95,27 @@ def expression(inp: ourlang.Expression) -> str:
)
if isinstance(inp.function, ourlang.StructConstructor):
return f'{inp.function.struct.name}({args})'
return f'{inp.function.struct_type3.name}({args})'
if isinstance(inp.function, ourlang.TupleConstructor):
return f'({args}, )'
# TODO: Broken after new type system
# if isinstance(inp.function, ourlang.TupleConstructor):
# return f'({args}, )'
return f'{inp.function.name}({args})'
if isinstance(inp, ourlang.AccessBytesIndex):
return f'{expression(inp.varref)}[{expression(inp.index)}]'
if isinstance(inp, ourlang.Subscript):
varref = expression(inp.varref)
index = expression(inp.index)
return f'{varref}[{index}]'
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}]'
return f'{expression(inp.varref)}.{inp.member}'
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:
@ -193,11 +156,11 @@ def function(inp: ourlang.Function) -> str:
result += '@imported\n'
args = ', '.join(
f'{x}: {type_(y)}'
for x, y in inp.posonlyargs
f'{p.name}: {type3(p.type3)}'
for p in inp.posonlyargs
)
result += f'def {inp.name}({args}) -> {type_(inp.returns)}:\n'
result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n'
if inp.imported:
result += ' pass\n'
@ -215,7 +178,7 @@ def module(inp: ourlang.Module) -> str:
"""
result = ''
for struct in inp.structs.values():
for struct in inp.struct_definitions.values():
if result:
result += '\n'
result += struct_definition(struct)
@ -227,7 +190,7 @@ def module(inp: ourlang.Module) -> str:
for func in inp.functions.values():
if func.lineno < 0:
# Buildin (-2) or auto generated (-1)
# Builtin (-2) or auto generated (-1)
continue
if result:

View File

@ -1,11 +1,13 @@
"""
This module contains the code to convert parsed Ourlang into WebAssembly code
"""
from typing import List, Union
import struct
from . import codestyle
from . import ourlang
from . import typing
from .type3 import types as type3types
from . import wasm
from .stdlib import alloc as stdlib_alloc
@ -13,17 +15,14 @@ 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',
'u8': 'i32', # Have to use an u32, since there is no native u8 type
'i32': 'i32',
'i64': 'i64',
'u32': 'i32',
'u64': 'i64',
'f32': 'f32',
'f64': 'f64',
}
"""
When generating code, we sometimes need to load or store simple values
"""
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
"""
@ -32,42 +31,44 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module:
"""
return module(inp)
def type_(inp: typing.TypeBase) -> wasm.WasmType:
def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
"""
Compile: type
"""
if isinstance(inp, typing.TypeNone):
return wasm.WasmTypeNone()
if isinstance(inp, typing.TypeUInt8):
Types are used for example in WebAssembly function parameters
and return types.
"""
assert isinstance(inp, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp is type3types.u8:
# WebAssembly has only support for 32 and 64 bits
# So we need to store more memory per byte
return wasm.WasmTypeInt32()
if isinstance(inp, typing.TypeUInt32):
if inp is type3types.u32:
return wasm.WasmTypeInt32()
if isinstance(inp, typing.TypeUInt64):
if inp is type3types.u64:
return wasm.WasmTypeInt64()
if isinstance(inp, typing.TypeInt32):
if inp is type3types.i32:
return wasm.WasmTypeInt32()
if isinstance(inp, typing.TypeInt64):
if inp is type3types.i64:
return wasm.WasmTypeInt64()
if isinstance(inp, typing.TypeFloat32):
if inp is type3types.f32:
return wasm.WasmTypeFloat32()
if isinstance(inp, typing.TypeFloat64):
if inp is type3types.f64:
return wasm.WasmTypeFloat64()
if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeStaticArray, typing.TypeBytes)):
if isinstance(inp, type3types.StructType3):
# Structs and tuples are passed as pointer
# And pointers are i32
return wasm.WasmTypeInt32()
raise NotImplementedError(type_, inp)
raise NotImplementedError(type3, inp)
# Operators that work for i32, i64, f32, f64
OPERATOR_MAP = {
@ -81,8 +82,6 @@ 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',
@ -99,6 +98,7 @@ U32_OPERATOR_MAP = {
'^': 'xor',
'|': 'or',
'&': 'and',
'/': 'div_u' # Division by zero is a trap and the program will panic
}
U64_OPERATOR_MAP = {
@ -111,6 +111,7 @@ U64_OPERATOR_MAP = {
'^': 'xor',
'|': 'or',
'&': 'and',
'/': 'div_u' # Division by zero is a trap and the program will panic
}
I32_OPERATOR_MAP = {
@ -118,6 +119,7 @@ I32_OPERATOR_MAP = {
'>': 'gt_s',
'<=': 'le_s',
'>=': 'ge_s',
'/': 'div_s' # Division by zero is a trap and the program will panic
}
I64_OPERATOR_MAP = {
@ -125,115 +127,165 @@ I64_OPERATOR_MAP = {
'>': 'gt_s',
'<=': 'le_s',
'>=': 'ge_s',
'/': 'div_s' # Division by zero is a trap and the program will panic
}
F32_OPERATOR_MAP = {
'/': 'div' # Division by zero is a trap and the program will panic
}
F64_OPERATOR_MAP = {
'/': 'div' # Division by zero is a trap and the program will panic
}
def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
"""
Compile: Any expression
"""
if isinstance(inp, ourlang.ConstantUInt8):
if isinstance(inp, ourlang.ConstantPrimitive):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp.type3 is type3types.u8:
# No native u8 type - treat as i32, with caution
assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
return
if isinstance(inp, ourlang.ConstantUInt32):
if inp.type3 is type3types.i32 or inp.type3 is type3types.u32:
assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
return
if isinstance(inp, ourlang.ConstantUInt64):
if inp.type3 is type3types.i64 or inp.type3 is type3types.u64:
assert isinstance(inp.value, int)
wgn.i64.const(inp.value)
return
if isinstance(inp, ourlang.ConstantInt32):
wgn.i32.const(inp.value)
return
if isinstance(inp, ourlang.ConstantInt64):
wgn.i64.const(inp.value)
return
if isinstance(inp, ourlang.ConstantFloat32):
if inp.type3 is type3types.f32:
assert isinstance(inp.value, float)
wgn.f32.const(inp.value)
return
if isinstance(inp, ourlang.ConstantFloat64):
if inp.type3 is type3types.f64:
assert isinstance(inp.value, float)
wgn.f64.const(inp.value)
return
raise NotImplementedError(f'Constants with type {inp.type3}')
if isinstance(inp, ourlang.VariableReference):
wgn.add_statement('local.get', '${}'.format(inp.name))
if isinstance(inp.variable, ourlang.FunctionParam):
wgn.add_statement('local.get', '${}'.format(inp.variable.name))
return
if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
# 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'
expression(wgn, inp.variable.constant)
return
raise NotImplementedError(expression, inp.variable)
if isinstance(inp, ourlang.BinaryOp):
expression(wgn, inp.left)
expression(wgn, inp.right)
if isinstance(inp.type, typing.TypeUInt8):
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
# FIXME: Re-implement build-in operators
if inp.type3 is type3types.u8:
if operator := U8_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'i32.{operator}')
return
if isinstance(inp.type, typing.TypeUInt32):
if inp.type3 is type3types.u32:
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 isinstance(inp.type, typing.TypeUInt64):
if inp.type3 is type3types.u64:
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 isinstance(inp.type, typing.TypeInt32):
if inp.type3 is type3types.i32:
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 isinstance(inp.type, typing.TypeInt64):
if inp.type3 is type3types.i64:
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 isinstance(inp.type, typing.TypeFloat32):
if inp.type3 is type3types.f32:
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}')
return
if isinstance(inp.type, typing.TypeFloat64):
if operator := F32_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f32.{operator}')
return
if inp.type3 is type3types.f64:
if operator := OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f64.{operator}')
return
if operator := F64_OPERATOR_MAP.get(inp.operator, None):
wgn.add_statement(f'f64.{operator}')
return
raise NotImplementedError(expression, inp.type, inp.operator)
raise NotImplementedError(expression, inp.type3, inp.operator)
if isinstance(inp, ourlang.UnaryOp):
expression(wgn, inp.right)
if isinstance(inp.type, typing.TypeFloat32):
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
if inp.type3 is type3types.f32:
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
wgn.add_statement(f'f32.{inp.operator}')
return
if isinstance(inp.type, typing.TypeFloat64):
if inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS:
if inp.type3 is type3types.f64:
if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS:
wgn.add_statement(f'f64.{inp.operator}')
return
if isinstance(inp.type, typing.TypeInt32):
if inp.operator == 'len':
if isinstance(inp.right.type, typing.TypeBytes):
wgn.i32.load()
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 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, inp.operator)
raise NotImplementedError(expression, inp.type3, inp.operator)
if isinstance(inp, ourlang.FunctionCall):
for arg in inp.arguments:
@ -242,98 +294,117 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.add_statement('call', '${}'.format(inp.function.name))
return
if isinstance(inp, ourlang.AccessBytesIndex):
if not isinstance(inp.type, typing.TypeUInt8):
raise NotImplementedError(inp, inp.type)
if isinstance(inp, ourlang.Subscript):
# assert inp.varref.type3 is not None, typing.ASSERTION_ERROR
#
# assert inp.varref.type_var is not None, typing.ASSERTION_ERROR
# tc_type = inp.varref.type_var.get_type()
# if tc_type is None:
# raise NotImplementedError(expression, inp, inp.varref.type_var)
expression(wgn, inp.varref)
expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__)
return
# 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)
#
# expression(wgn, inp.varref)
# wgn.add_statement(f'{mtyp}.load', 'offset=' + str(bitwidth // 8 * inp.index.value))
# return
raise NotImplementedError(expression, inp, inp.varref.type3)
# 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.AccessStructMember):
mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__)
mtyp = LOAD_STORE_TYPE_MAP.get(inp.struct_type3.members[inp.member].name)
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)
raise NotImplementedError(expression, inp, inp.struct_type3)
expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset))
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset(
inp.struct_type3, inp.member
)))
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)
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.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
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
"""
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)
assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
raise NotImplementedError('TODO: Broken after new type system')
if inp.iter.type.__class__.__name__ != 'TypeBytes':
raise NotImplementedError(expression, inp, inp.iter.type)
@ -450,7 +521,7 @@ def function_argument(inp: ourlang.FunctionParam) -> wasm.Param:
"""
Compile: function argument
"""
return (inp[0], type_(inp[1]), )
return (inp.name, type3(inp.type3), )
def import_(inp: ourlang.Function) -> wasm.Import:
"""
@ -466,7 +537,7 @@ def import_(inp: ourlang.Function) -> wasm.Import:
function_argument(x)
for x in inp.posonlyargs
],
type_(inp.returns)
type3(inp.returns_type3)
)
def function(inp: ourlang.Function) -> wasm.Function:
@ -477,8 +548,8 @@ def function(inp: ourlang.Function) -> wasm.Function:
wgn = WasmGenerator()
if isinstance(inp, ourlang.TupleConstructor):
_generate_tuple_constructor(wgn, inp)
if False: # TODO: isinstance(inp, ourlang.TupleConstructor):
pass # _generate_tuple_constructor(wgn, inp)
elif isinstance(inp, ourlang.StructConstructor):
_generate_struct_constructor(wgn, inp)
else:
@ -496,7 +567,7 @@ def function(inp: ourlang.Function) -> wasm.Function:
(k, v.wasm_type(), )
for k, v in wgn.locals.items()
],
type_(inp.returns),
type3(inp.returns_type3),
wgn.statements
)
@ -555,38 +626,47 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
for block in inp.blocks:
block.address = unalloc_ptr + 4 # 4 bytes for allocator header
data_list = []
data_list: List[bytes] = []
for constant in block.data:
if isinstance(constant, ourlang.ConstantUInt8):
assert constant.type3 is not None
if constant.type3 is type3types.u8:
assert isinstance(constant.value, int)
data_list.append(module_data_u8(constant.value))
continue
if isinstance(constant, ourlang.ConstantUInt32):
if constant.type3 is type3types.u32:
assert isinstance(constant.value, int)
data_list.append(module_data_u32(constant.value))
continue
if isinstance(constant, ourlang.ConstantUInt64):
if constant.type3 is type3types.u64:
assert isinstance(constant.value, int)
data_list.append(module_data_u64(constant.value))
continue
if isinstance(constant, ourlang.ConstantInt32):
if constant.type3 is type3types.i32:
assert isinstance(constant.value, int)
data_list.append(module_data_i32(constant.value))
continue
if isinstance(constant, ourlang.ConstantInt64):
if constant.type3 is type3types.i64:
assert isinstance(constant.value, int)
data_list.append(module_data_i64(constant.value))
continue
if isinstance(constant, ourlang.ConstantFloat32):
if constant.type3 is type3types.f32:
assert isinstance(constant.value, float)
data_list.append(module_data_f32(constant.value))
continue
if isinstance(constant, ourlang.ConstantFloat64):
if constant.type3 is type3types.f64:
assert isinstance(constant.value, float)
data_list.append(module_data_f64(constant.value))
continue
raise NotImplementedError(constant)
raise NotImplementedError(constant, constant.type3, constant.value)
block_data = b''.join(data_list)
@ -636,48 +716,57 @@ def module(inp: ourlang.Module) -> wasm.Module:
return result
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)
# 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.i32.const(_calculate_alloc_size(inp.struct_type3))
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__)
for memname, mtyp3 in inp.struct_type3.members.items():
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)
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)
raise NotImplementedError(expression, inp, mtyp3)
wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'${member.name}')
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
wgn.add_statement('local.get', f'${memname}')
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(_calculate_member_offset(
inp.struct_type3, memname
)))
# Return the allocated address
wgn.local.get(tmp_var)
def _calculate_alloc_size(type3: Union[type3types.StructType3, type3types.Type3]) -> int:
return 0 # FIXME: Stub
def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
return 0 # FIXME: Stub

View File

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

View File

@ -1,128 +1,51 @@
"""
Contains the syntax tree for ourlang
"""
from typing import Dict, List, Tuple, Optional, Union
from typing import Dict, List, Optional, Union
import enum
from typing_extensions import Final
WEBASSEMBLY_BUILDIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
WEBASSEMBLY_BUILDIN_BYTES_OPS: Final = ('len', )
WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
from .typing import (
TypeBase,
TypeNone,
TypeBool,
TypeUInt8, TypeUInt32, TypeUInt64,
TypeInt32, TypeInt64,
TypeFloat32, TypeFloat64,
TypeBytes,
TypeTuple, TypeTupleMember,
TypeStaticArray, TypeStaticArrayMember,
TypeStruct, TypeStructMember,
)
from .type3 import types as type3types
from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType, StructType3
class Expression:
"""
An expression within a statement
"""
__slots__ = ('type', )
__slots__ = ('type3', )
type: TypeBase
type3: Type3OrPlaceholder
def __init__(self, type_: TypeBase) -> None:
self.type = type_
def __init__(self) -> None:
self.type3 = PlaceholderForType([self])
class Constant(Expression):
"""
An constant value expression within a statement
# FIXME: Rename to literal
"""
__slots__ = ()
class ConstantUInt8(Constant):
class ConstantPrimitive(Constant):
"""
An UInt8 constant value expression within a statement
An primitive constant value expression within a statement
"""
__slots__ = ('value', )
value: int
value: Union[int, float]
def __init__(self, type_: TypeUInt8, value: int) -> None:
super().__init__(type_)
def __init__(self, value: Union[int, float]) -> None:
super().__init__()
self.value = value
class ConstantUInt32(Constant):
"""
An UInt32 constant value expression within a statement
"""
__slots__ = ('value', )
value: int
def __init__(self, type_: TypeUInt32, value: int) -> None:
super().__init__(type_)
self.value = value
class ConstantUInt64(Constant):
"""
An UInt64 constant value expression within a statement
"""
__slots__ = ('value', )
value: int
def __init__(self, type_: TypeUInt64, value: int) -> None:
super().__init__(type_)
self.value = value
class ConstantInt32(Constant):
"""
An Int32 constant value expression within a statement
"""
__slots__ = ('value', )
value: int
def __init__(self, type_: TypeInt32, value: int) -> None:
super().__init__(type_)
self.value = value
class ConstantInt64(Constant):
"""
An Int64 constant value expression within a statement
"""
__slots__ = ('value', )
value: int
def __init__(self, type_: TypeInt64, value: int) -> None:
super().__init__(type_)
self.value = value
class ConstantFloat32(Constant):
"""
An Float32 constant value expression within a statement
"""
__slots__ = ('value', )
value: float
def __init__(self, type_: TypeFloat32, value: float) -> None:
super().__init__(type_)
self.value = value
class ConstantFloat64(Constant):
"""
An Float64 constant value expression within a statement
"""
__slots__ = ('value', )
value: float
def __init__(self, type_: TypeFloat64, value: float) -> None:
super().__init__(type_)
self.value = value
def __repr__(self) -> str:
return f'ConstantPrimitive({repr(self.value)})'
class ConstantTuple(Constant):
"""
@ -130,35 +53,26 @@ class ConstantTuple(Constant):
"""
__slots__ = ('value', )
value: List[Constant]
value: List[ConstantPrimitive]
def __init__(self, type_: TypeTuple, value: List[Constant]) -> None:
super().__init__(type_)
def __init__(self, value: List[ConstantPrimitive]) -> None: # FIXME: Tuple of tuples?
super().__init__()
self.value = value
class ConstantStaticArray(Constant):
"""
A StaticArray constant value expression within a statement
"""
__slots__ = ('value', )
value: List[Constant]
def __init__(self, type_: TypeStaticArray, value: List[Constant]) -> None:
super().__init__(type_)
self.value = value
def __repr__(self) -> str:
return f'ConstantTuple({repr(self.value)})'
class VariableReference(Expression):
"""
An variable reference expression within a statement
"""
__slots__ = ('name', )
__slots__ = ('variable', )
name: str
variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local
def __init__(self, type_: TypeBase, name: str) -> None:
super().__init__(type_)
self.name = name
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam']) -> None:
super().__init__()
self.variable = variable
class UnaryOp(Expression):
"""
@ -169,8 +83,8 @@ class UnaryOp(Expression):
operator: str
right: Expression
def __init__(self, type_: TypeBase, operator: str, right: Expression) -> None:
super().__init__(type_)
def __init__(self, operator: str, right: Expression) -> None:
super().__init__()
self.operator = operator
self.right = right
@ -185,8 +99,8 @@ class BinaryOp(Expression):
left: Expression
right: Expression
def __init__(self, type_: TypeBase, operator: str, left: Expression, right: Expression) -> None:
super().__init__(type_)
def __init__(self, operator: str, left: Expression, right: Expression) -> None:
super().__init__()
self.operator = operator
self.left = left
@ -202,22 +116,23 @@ class FunctionCall(Expression):
arguments: List[Expression]
def __init__(self, function: 'Function') -> None:
super().__init__(function.returns)
super().__init__()
self.function = function
self.arguments = []
class AccessBytesIndex(Expression):
class Subscript(Expression):
"""
Access a bytes index for reading
A subscript, for example to refer to a static array or tuple
by index
"""
__slots__ = ('varref', 'index', )
varref: VariableReference
index: Expression
def __init__(self, type_: TypeBase, varref: VariableReference, index: Expression) -> None:
super().__init__(type_)
def __init__(self, varref: VariableReference, index: Expression) -> None:
super().__init__()
self.varref = varref
self.index = index
@ -226,47 +141,17 @@ class AccessStructMember(Expression):
"""
Access a struct member for reading of writing
"""
__slots__ = ('varref', 'member', )
__slots__ = ('varref', 'struct_type3', 'member', )
varref: VariableReference
member: TypeStructMember
struct_type3: StructType3
member: str
def __init__(self, varref: VariableReference, member: TypeStructMember) -> None:
super().__init__(member.type)
def __init__(self, varref: VariableReference, struct_type3: StructType3, member: str) -> None:
super().__init__()
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.struct_type3 = struct_type3
self.member = member
class Fold(Expression):
@ -287,31 +172,18 @@ class Fold(Expression):
def __init__(
self,
type_: TypeBase,
dir_: Direction,
func: 'Function',
base: Expression,
iter_: Expression,
) -> None:
super().__init__(type_)
super().__init__()
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
@ -348,20 +220,31 @@ class StatementIf(Statement):
self.statements = []
self.else_statements = []
FunctionParam = Tuple[str, TypeBase]
class FunctionParam:
"""
A parameter for a Function
"""
__slots__ = ('name', 'type3', )
name: str
type3: Type3OrPlaceholder
def __init__(self, name: str, type3: Optional[Type3]) -> None:
self.name = name
self.type3 = PlaceholderForType([self]) if type3 is None else type3
class Function:
"""
A function processes input and produces output
"""
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns', 'posonlyargs', )
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'posonlyargs', )
name: str
lineno: int
exported: bool
imported: bool
statements: List[Statement]
returns: TypeBase
returns_type3: Type3
posonlyargs: List[FunctionParam]
def __init__(self, name: str, lineno: int) -> None:
@ -370,9 +253,22 @@ class Function:
self.exported = False
self.imported = False
self.statements = []
self.returns = TypeNone()
self.returns_type3 = type3types.none # FIXME: This could be a placeholder
self.posonlyargs = []
class StructDefinition:
"""
The definition for a struct
"""
__slots__ = ('struct_type3', 'lineno', )
struct_type3: StructType3
lineno: int
def __init__(self, struct_type3: StructType3, lineno: int) -> None:
self.struct_type3 = struct_type3
self.lineno = lineno
class StructConstructor(Function):
"""
The constructor method for a struct
@ -380,56 +276,57 @@ class StructConstructor(Function):
A function will generated to instantiate a struct. The arguments
will be the defaults
"""
__slots__ = ('struct', )
__slots__ = ('struct_type3', )
struct: TypeStruct
struct_type3: StructType3
def __init__(self, struct: TypeStruct) -> None:
super().__init__(f'@{struct.name}@__init___@', -1)
def __init__(self, struct_type3: StructType3) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', -1)
self.returns = struct
self.returns_type3 = struct_type3
for mem in struct.members:
self.posonlyargs.append((mem.name, mem.type, ))
for mem, typ in struct_type3.members.items():
self.posonlyargs.append(FunctionParam(mem, typ, ))
self.struct = struct
self.struct_type3 = struct_type3
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((f'arg{mem.idx}', mem.type, ))
self.tuple = tuple_
# TODO: Broken after new type system
# 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', 'constant', 'data_block', )
__slots__ = ('name', 'lineno', 'type3', 'constant', 'data_block', )
name: str
lineno: int
type: TypeBase
type3: Type3
constant: Constant
data_block: Optional['ModuleDataBlock']
def __init__(self, name: str, lineno: int, type_: TypeBase, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
def __init__(self, name: str, lineno: int, type3: Type3, constant: Constant, data_block: Optional['ModuleDataBlock']) -> None:
self.name = name
self.lineno = lineno
self.type = type_
self.type3 = type3
self.constant = constant
self.data_block = data_block
@ -439,10 +336,10 @@ class ModuleDataBlock:
"""
__slots__ = ('data', 'address', )
data: List[Constant]
data: List[ConstantPrimitive]
address: Optional[int]
def __init__(self, data: List[Constant]) -> None:
def __init__(self, data: List[ConstantPrimitive]) -> None:
self.data = data
self.address = None
@ -461,27 +358,15 @@ class Module:
"""
A module is a file and consists of functions
"""
__slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',)
__slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions',)
data: ModuleData
types: Dict[str, TypeBase]
structs: Dict[str, TypeStruct]
struct_definitions: Dict[str, StructDefinition]
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.struct_definitions = {}
self.constant_defs = {}
self.functions = {}

View File

@ -5,49 +5,30 @@ from typing import Any, Dict, NoReturn, Union
import ast
from .typing import (
TypeBase,
TypeUInt8,
TypeUInt32,
TypeUInt64,
TypeInt32,
TypeInt64,
TypeFloat32,
TypeFloat64,
TypeBytes,
TypeStruct,
TypeStructMember,
TypeTuple,
TypeTupleMember,
TypeStaticArray,
TypeStaticArrayMember,
)
from .type3 import types as type3types
from . import codestyle
from .exceptions import StaticError
from .ourlang import (
WEBASSEMBLY_BUILDIN_FLOAT_OPS,
WEBASSEMBLY_BUILTIN_FLOAT_OPS,
Module, ModuleDataBlock,
Function,
Expression,
AccessBytesIndex, AccessStructMember, AccessTupleMember, AccessStaticArrayMember,
BinaryOp,
Constant,
ConstantFloat32, ConstantFloat64, ConstantInt32, ConstantInt64,
ConstantUInt8, ConstantUInt32, ConstantUInt64,
ConstantTuple, ConstantStaticArray,
ConstantPrimitive, ConstantTuple,
FunctionCall,
StructConstructor, TupleConstructor,
FunctionCall, AccessStructMember, Subscript,
StructDefinition, StructConstructor,
# TupleConstructor,
UnaryOp, VariableReference,
Fold, ModuleConstantReference,
Fold,
Statement,
StatementIf, StatementPass, StatementReturn,
FunctionParam,
ModuleConstantDef,
)
@ -60,7 +41,7 @@ def phasm_parse(source: str) -> Module:
our_visitor = OurVisitor()
return our_visitor.visit_Module(res)
OurLocals = Dict[str, TypeBase]
OurLocals = Dict[str, Union[FunctionParam]] # Also local variable and module constants?
class OurVisitor:
"""
@ -95,14 +76,14 @@ class OurVisitor:
module.constant_defs[res.name] = res
if isinstance(res, TypeStruct):
if res.name in module.structs:
if isinstance(res, StructDefinition):
if res.struct_type3.name in module.struct_definitions:
raise StaticError(
f'{res.name} already defined on line {module.structs[res.name].lineno}'
f'{res.struct_type3.name} already defined on line {module.struct_definitions[res.struct_type3.name].lineno}'
)
module.structs[res.name] = res
constructor = StructConstructor(res)
module.struct_definitions[res.struct_type3.name] = res
constructor = StructConstructor(res.struct_type3)
module.functions[constructor.name] = constructor
if isinstance(res, Function):
@ -120,7 +101,7 @@ class OurVisitor:
return module
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, TypeStruct, ModuleConstantDef]:
def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]:
if isinstance(node, ast.FunctionDef):
return self.pre_visit_Module_FunctionDef(module, node)
@ -138,12 +119,9 @@ class OurVisitor:
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
for arg in node.args.args:
if not arg.annotation:
_raise_static_error(node, 'Type is required')
function.posonlyargs.append((
function.posonlyargs.append(FunctionParam(
arg.arg,
self.visit_type(module, arg.annotation),
self.visit_type(module, arg.annotation) if arg.annotation else None,
))
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
@ -166,21 +144,23 @@ class OurVisitor:
else:
function.imported = True
if node.returns:
function.returns = self.visit_type(module, node.returns)
if node.returns is not None: # Note: `-> None` would be a ast.Constant
function.returns_type3 = self.visit_type(module, node.returns)
else:
# Mostly works already, needs to fix Function.returns_type3 and have it updated
raise NotImplementedError('Function without an explicit return type')
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
return function
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> TypeStruct:
struct = TypeStruct(node.name, node.lineno)
def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition:
_not_implemented(not node.bases, 'ClassDef.bases')
_not_implemented(not node.keywords, 'ClassDef.keywords')
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
offset = 0
members: Dict[str, type3types.Type3] = {}
for stmt in node.body:
if not isinstance(stmt, ast.AnnAssign):
@ -195,12 +175,12 @@ class OurVisitor:
if stmt.simple != 1:
raise NotImplementedError('Class with non-simple arguments')
member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset)
if stmt.target.id in members:
_raise_static_error(stmt, 'Struct members must have unique names')
struct.members.append(member)
offset += member.type.alloc_size()
members[stmt.target.id] = self.visit_type(module, stmt.annotation)
return struct
return StructDefinition(type3types.StructType3(node.name, members), node.lineno)
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name):
@ -208,34 +188,23 @@ 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(exp_type, TypeInt32):
if not isinstance(node.value, ast.Constant):
_raise_static_error(node, 'Must be constant')
constant = ModuleConstantDef(
if isinstance(node.value, ast.Constant):
type3 = self.visit_type(module, node.annotation)
return ModuleConstantDef(
node.target.id,
node.lineno,
exp_type,
self.visit_Module_Constant(module, exp_type, node.value),
type3,
self.visit_Module_Constant(module, node.value),
None,
)
return constant
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')
if isinstance(node.value, ast.Tuple):
tuple_data = [
self.visit_Module_Constant(module, mem.type, arg_node)
for arg_node, mem in zip(node.value.elts, exp_type.members)
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(tuple_data):
if len(node.value.elts) != len(tuple_data):
_raise_static_error(node, 'Tuple arguments must be constants')
# Allocate the data
@ -246,40 +215,69 @@ class OurVisitor:
return ModuleConstantDef(
node.target.id,
node.lineno,
exp_type,
ConstantTuple(exp_type, tuple_data),
self.visit_type(module, node.annotation),
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')
raise NotImplementedError('TODO: Broken after new typing system')
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, exp_type.member_type, 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')
# 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')
def visit_Module_stmt(self, module: Module, node: ast.stmt) -> None:
if isinstance(node, ast.FunctionDef):
@ -297,7 +295,10 @@ class OurVisitor:
def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None:
function = module.functions[node.name]
our_locals = dict(function.posonlyargs)
our_locals: OurLocals = {
x.name: x
for x in function.posonlyargs
}
for stmt in node.body:
function.statements.append(
@ -311,12 +312,12 @@ class OurVisitor:
_raise_static_error(node, 'Return must have an argument')
return StatementReturn(
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.value)
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value)
)
if isinstance(node, ast.If):
result = StatementIf(
self.visit_Module_FunctionDef_expr(module, function, our_locals, function.returns, node.test)
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.test)
)
for stmt in node.body:
@ -336,7 +337,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as stmt in FunctionDef')
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.expr) -> Expression:
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp):
if isinstance(node.op, ast.Add):
operator = '+'
@ -344,6 +345,8 @@ class OurVisitor:
operator = '-'
elif isinstance(node.op, ast.Mult):
operator = '*'
elif isinstance(node.op, ast.Div):
operator = '/'
elif isinstance(node.op, ast.LShift):
operator = '<<'
elif isinstance(node.op, ast.RShift):
@ -361,10 +364,9 @@ class OurVisitor:
# e.g. you can do `"hello" * 3` with the code below (yet)
return BinaryOp(
exp_type,
operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.right),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
)
if isinstance(node, ast.UnaryOp):
@ -376,9 +378,8 @@ class OurVisitor:
raise NotImplementedError(f'Operator {node.op}')
return UnaryOp(
exp_type,
operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.operand),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.operand),
)
if isinstance(node, ast.Compare):
@ -398,28 +399,27 @@ class OurVisitor:
# e.g. you can do `"hello" * 3` with the code below (yet)
return BinaryOp(
exp_type,
operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, exp_type, node.comparators[0]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
)
if isinstance(node, ast.Call):
return self.visit_Module_FunctionDef_Call(module, function, our_locals, exp_type, node)
return self.visit_Module_FunctionDef_Call(module, function, our_locals, node)
if isinstance(node, ast.Constant):
return self.visit_Module_Constant(
module, exp_type, node,
module, node,
)
if isinstance(node, ast.Attribute):
return self.visit_Module_FunctionDef_Attribute(
module, function, our_locals, exp_type, node,
module, function, our_locals, node,
)
if isinstance(node, ast.Subscript):
return self.visit_Module_FunctionDef_Subscript(
module, function, our_locals, exp_type, node,
module, function, our_locals, node,
)
if isinstance(node, ast.Name):
@ -427,45 +427,41 @@ class OurVisitor:
_raise_static_error(node, 'Must be load context')
if node.id in our_locals:
act_type = our_locals[node.id]
if exp_type != act_type:
_raise_static_error(node, f'Expected {codestyle.type_(exp_type)}, {node.id} is actually {codestyle.type_(act_type)}')
return VariableReference(act_type, node.id)
param = our_locals[node.id]
return VariableReference(param)
if node.id in module.constant_defs:
cdef = module.constant_defs[node.id]
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)
return VariableReference(cdef)
_raise_static_error(node, f'Undefined variable {node.id}')
if isinstance(node, ast.Tuple):
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
raise NotImplementedError('TODO: Broken after new type system')
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 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')
raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -474,48 +470,38 @@ 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:
struct = module.structs[node.func.id]
struct_constructor = StructConstructor(struct)
if node.func.id in module.struct_definitions:
struct_definition = module.struct_definitions[node.func.id]
struct_constructor = StructConstructor(struct_definition.struct_type3)
# FIXME: Defer struct de-allocation
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)}')
elif node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS:
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, exp_type, node.args[0]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, 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, module.types['u8'], node.args[0]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, 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, module.types['bytes'], node.args[0]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
)
elif node.func.id == 'foldl':
# TODO: This should a much more generic function!
@ -538,21 +524,13 @@ 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')
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][1].__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][1])}')
if module.types['u8'].__class__ != func.posonlyargs[1][1].__class__:
_raise_static_error(node, 'Only folding over bytes (u8) is supported at this time')
raise NotImplementedError('TODO: Broken after new type system')
return Fold(
exp_type,
Fold.Direction.LEFT,
func,
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]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[1]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[2]),
)
else:
if node.func.id not in module.functions:
@ -560,20 +538,17 @@ 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_type, arg_expr)
for arg_expr, (_, arg_type) in zip(node.args, func.posonlyargs)
self.visit_Module_FunctionDef_expr(module, function, our_locals, 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, exp_type: TypeBase, node: ast.Attribute) -> Expression:
def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression:
del module
del function
@ -586,23 +561,23 @@ class OurVisitor:
if not node.value.id in our_locals:
_raise_static_error(node, f'Undefined variable {node.value.id}')
node_typ = our_locals[node.value.id]
if not isinstance(node_typ, TypeStruct):
param = our_locals[node.value.id]
node_typ = param.type3
if not isinstance(node_typ, type3types.StructType3):
_raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}')
member = node_typ.get_member(node.attr)
member = node_typ.members.get(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, node.value.id),
member,
VariableReference(param),
node_typ,
node.attr,
)
def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, exp_type: TypeBase, node: ast.Subscript) -> Expression:
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')
@ -612,154 +587,93 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
varref: Union[ModuleConstantReference, VariableReference]
varref: VariableReference
if node.value.id in our_locals:
node_typ = our_locals[node.value.id]
varref = VariableReference(node_typ, node.value.id)
param = our_locals[node.value.id]
varref = VariableReference(param)
elif node.value.id in module.constant_defs:
constant_def = module.constant_defs[node.value.id]
node_typ = constant_def.type
varref = ModuleConstantReference(node_typ, constant_def)
varref = VariableReference(constant_def)
else:
_raise_static_error(node, f'Undefined variable {node.value.id}')
slice_expr = self.visit_Module_FunctionDef_expr(
module, function, our_locals, module.types['u32'], node.slice.value,
module, function, our_locals, node.slice.value,
)
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)}')
return Subscript(varref, slice_expr)
if isinstance(varref, ModuleConstantReference):
raise NotImplementedError(f'{node} from module constant')
# 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}')
return AccessBytesIndex(
t_u8,
varref,
slice_expr,
)
if isinstance(node_typ, TypeTuple):
if not isinstance(slice_expr, ConstantUInt32):
_raise_static_error(node, 'Must subscript using a constant index')
idx = slice_expr.value
if len(node_typ.members) <= idx:
_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, ConstantInt32):
return AccessStaticArrayMember(
varref,
node_typ,
slice_expr,
)
idx = slice_expr.value
if len(node_typ.members) <= idx:
_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, exp_type: TypeBase, node: ast.Constant) -> Constant:
def visit_Module_Constant(self, module: Module, node: ast.Constant) -> ConstantPrimitive:
del module
_not_implemented(node.kind is None, 'Constant.kind')
if isinstance(exp_type, TypeUInt8):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if isinstance(node.value, (int, float, )):
return ConstantPrimitive(node.value)
if node.value < 0 or node.value > 255:
_raise_static_error(node, f'Integer value out of range; expected 0..255, actual {node.value}')
raise NotImplementedError(f'{node.value} as constant')
return ConstantUInt8(exp_type, node.value)
if isinstance(exp_type, TypeUInt32):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if node.value < 0 or node.value > 4294967295:
_raise_static_error(node, 'Integer value out of range')
return ConstantUInt32(exp_type, node.value)
if isinstance(exp_type, TypeUInt64):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if node.value < 0 or node.value > 18446744073709551615:
_raise_static_error(node, 'Integer value out of range')
return ConstantUInt64(exp_type, node.value)
if isinstance(exp_type, TypeInt32):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if node.value < -2147483648 or node.value > 2147483647:
_raise_static_error(node, 'Integer value out of range')
return ConstantInt32(exp_type, node.value)
if isinstance(exp_type, TypeInt64):
if not isinstance(node.value, int):
_raise_static_error(node, 'Expected integer value')
if node.value < -9223372036854775808 or node.value > 9223372036854775807:
_raise_static_error(node, 'Integer value out of range')
return ConstantInt64(exp_type, node.value)
if isinstance(exp_type, TypeFloat32):
if not isinstance(node.value, (float, int, )):
_raise_static_error(node, 'Expected float value')
# FIXME: Range check
return ConstantFloat32(exp_type, node.value)
if isinstance(exp_type, TypeFloat64):
if not isinstance(node.value, (float, int, )):
_raise_static_error(node, 'Expected float value')
# FIXME: Range check
return ConstantFloat64(exp_type, node.value)
raise NotImplementedError(f'{node} as const for type {exp_type}')
def visit_type(self, module: Module, node: ast.expr) -> TypeBase:
def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3:
if isinstance(node, ast.Constant):
if node.value is None:
return module.types['None']
return type3types.none
_raise_static_error(node, f'Unrecognized type {node.value}')
@ -767,11 +681,11 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if node.id in module.types:
return module.types[node.id]
if node.id in type3types.LOOKUP_TABLE:
return type3types.LOOKUP_TABLE[node.id]
if node.id in module.structs:
return module.structs[node.id]
if node.id in module.struct_definitions:
return module.struct_definitions[node.id].struct_type3
_raise_static_error(node, f'Unrecognized type {node.id}')
@ -787,50 +701,22 @@ class OurVisitor:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if node.value.id in module.types:
member_type = module.types[node.value.id]
else:
if node.value.id not in type3types.LOOKUP_TABLE: # FIXME: Tuple of tuples?
_raise_static_error(node, f'Unrecognized type {node.value.id}')
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]
return type3types.AppliedType3(
type3types.static_array,
[self.visit_type(module, node.value)],
)
if isinstance(node, ast.Tuple):
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]
return type3types.AppliedType3(
type3types.tuple,
(self.visit_type(module, elt) for elt in node.elts)
)
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
del alloc_size # TODO: Actual implement using a previously freed block
g.unreachable()
return i32('return') # To satisfy mypy

0
phasm/type3/__init__.py Normal file
View File

326
phasm/type3/constraints.py Normal file
View File

@ -0,0 +1,326 @@
"""
This module contains possible constraints generated based on the AST
These need to be resolved before the program can be compiled.
"""
from typing import Dict, Tuple, Union
from .. import ourlang
from . import types
class Error:
def __init__(self, msg: str) -> None:
self.msg = msg
def __repr__(self) -> str:
return f'Error({repr(self.msg)})'
class RequireTypeSubstitutes:
pass
CheckResult = Union[None, Error, RequireTypeSubstitutes]
SubstitutionMap = Dict[types.PlaceholderForType, types.Type3]
HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]]
class Context:
"""
Context for constraints
"""
__slots__ = ()
class ConstraintBase:
"""
Base class for constraints
"""
__slots__ = ()
def check(self) -> CheckResult:
"""
Checks if the constraint hold, returning an error if it doesn't
"""
raise NotImplementedError
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
"""
Returns any new placeholders that can be substituted for actual types
"""
return {}
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
"""
Called with type substitutes, so you can update any placeholders
you may have with the known types. Note that this does not guarantee
that all types are known, you may still have some placeholders left.
"""
raise NotImplementedError(self, self.substitute_placeholders)
def human_readable(self) -> HumanReadableRet:
"""
Returns a more human readable form of this constraint
"""
return repr(self), {}
class SameTypeConstraint(ConstraintBase):
"""
Verifies that an expression has an expected type
"""
__slots__ = ('expected', 'actual', 'message', )
expected: types.Type3OrPlaceholder
actual: types.Type3OrPlaceholder
message: str
def __init__(self, expected: types.Type3OrPlaceholder, actual: types.Type3OrPlaceholder, message: str) -> None:
self.expected = expected
self.actual = actual
self.message = message
def check(self) -> CheckResult:
if isinstance(self.expected, types.PlaceholderForType) or isinstance(self.actual, types.PlaceholderForType):
return RequireTypeSubstitutes()
if self.expected is self.actual:
return None
return Error(f'{self.expected:s} must be {self.actual:s} instead')
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
result: SubstitutionMap = {}
if isinstance(self.expected, types.Type3) and isinstance(self.actual, types.PlaceholderForType):
result = {
self.actual: self.expected
}
self.actual.get_substituted(self.expected)
self.actual = self.expected
if isinstance(self.actual, types.Type3) and isinstance(self.expected, types.PlaceholderForType):
result = {
self.expected: self.actual
}
self.expected.get_substituted(self.actual)
self.expected = self.actual
return result
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
if isinstance(self.expected, types.PlaceholderForType) and self.expected in smap: # FIXME: Check recursive?
self.expected.get_substituted(smap[self.expected])
self.expected = smap[self.expected]
if isinstance(self.actual, types.PlaceholderForType) and self.actual in smap: # FIXME: Check recursive?
self.actual.get_substituted(smap[self.actual])
self.actual = smap[self.actual]
def human_readable(self) -> HumanReadableRet:
return (
'{expected} == {actual}',
{
'expected': self.expected,
'actual': self.actual,
'comment': self.message,
},
)
def __repr__(self) -> str:
return f'SameTypeConstraint({repr(self.expected)}, {repr(self.actual)}, {repr(self.message)})'
class MustImplementTypeClassConstraint(ConstraintBase):
"""
A type must implement a given type class
"""
__slots__ = ('type_class3', 'type3', )
type_class3: str
type3: types.Type3OrPlaceholder
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder) -> None:
self.type_class3 = type_class3
self.type3 = type3
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
self.type3.get_substituted(smap[self.type3])
self.type3 = smap[self.type3]
def check(self) -> CheckResult:
if isinstance(self.type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
if 'BitWiseOr' == self.type_class3 and (self.type3 is types.u8 or self.type3 is types.u32 or self.type3 is types.u64):
return None
return Error(f'{self.type3.name} does not implement the {self.type_class3} type class')
def human_readable(self) -> HumanReadableRet:
return (
'{type3} derives {type_class3}',
{
'type_class3': self.type_class3,
'type3': self.type3,
},
)
def __repr__(self) -> str:
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)})'
class LiteralFitsConstraint(ConstraintBase):
"""
A literal value fits a given type
"""
__slots__ = ('type3', 'literal', )
type3: types.Type3OrPlaceholder
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]
def __init__(self, type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> None:
self.type3 = type3
self.literal = literal
def check(self) -> CheckResult:
int_table: Dict[str, Tuple[int, bool]] = {
'u8': (1, False),
'u32': (4, False),
'u64': (8, False),
'i8': (1, True),
'i32': (4, True),
'i64': (8, True),
}
float_table: Dict[str, None] = {
'f32': None,
'f64': None,
}
def _check(type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> CheckResult:
if isinstance(type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
val = literal.value
if type3.name in int_table:
bts, sgn = int_table[type3.name]
if isinstance(val, int):
try:
val.to_bytes(bts, 'big', signed=sgn)
except OverflowError:
return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information
return None
return Error('Must be integer') # FIXME: Add line information
if type3.name in float_table:
_ = float_table[type3.name]
if isinstance(val, float):
# FIXME: Bit check
return None
return Error('Must be real') # FIXME: Add line information
if isinstance(type3, types.AppliedType3) and type3.base is types.tuple:
if not isinstance(literal, ourlang.ConstantTuple):
return Error('Must be tuple')
assert isinstance(val, list) # type hint
if len(type3.args) != len(val):
return Error('Tuple element count mismatch')
for elt_typ, elt_lit in zip(type3.args, val):
res = _check(elt_typ, elt_lit)
if res is not None:
return res
return None
raise NotImplementedError
return _check(self.type3, self.literal)
def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
self.type3.get_substituted(smap[self.type3])
self.type3 = smap[self.type3]
def human_readable(self) -> HumanReadableRet:
return (
'{literal} : {type3}',
{
'literal': self.literal,
'type3': self.type3,
},
)
def __repr__(self) -> str:
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)})'
class CanBeSubscriptedConstraint(ConstraintBase):
"""
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
"""
__slots__ = ('type3', 'index', 'index_type3', )
type3: types.Type3OrPlaceholder
index: ourlang.Expression
index_type3: types.Type3OrPlaceholder
def __init__(self, type3: types.Type3OrPlaceholder, index: ourlang.Expression) -> None:
self.type3 = type3
self.index = index
self.index_type3 = index.type3
def check(self) -> CheckResult:
if isinstance(self.type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
if isinstance(self.index_type3, types.PlaceholderForType):
return RequireTypeSubstitutes()
if not isinstance(self.type3, types.AppliedType3):
return Error(f'Cannot subscript {self.type3:s}')
if self.type3.base is types.tuple:
return None
raise NotImplementedError(self.type3)
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
if isinstance(self.type3, types.AppliedType3) and self.type3.base is types.tuple and isinstance(self.index_type3, types.PlaceholderForType):
return {
self.index_type3: types.u32,
}
return {}
def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
self.type3.get_substituted(smap[self.type3])
self.type3 = smap[self.type3]
if isinstance(self.index_type3, types.PlaceholderForType) and self.index_type3 in smap: # FIXME: Check recursive?
self.index_type3.get_substituted(smap[self.index_type3])
self.index_type3 = smap[self.index_type3]
def human_readable(self) -> HumanReadableRet:
return (
'{type3}[{index}]',
{
'type3': self.type3,
'index': self.index,
},
)
def __repr__(self) -> str:
return f'CanBeSubscriptedConstraint({repr(self.type3)}, {repr(self.index)})'

View File

@ -0,0 +1,97 @@
"""
This module generates the typing constraints for Phasm.
The constraints solver can then try to resolve all constraints.
"""
from typing import Generator, List
from .. import ourlang
from .constraints import (
Context,
ConstraintBase,
CanBeSubscriptedConstraint,
LiteralFitsConstraint, MustImplementTypeClassConstraint, SameTypeConstraint,
)
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
ctx = Context()
return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant) -> Generator[ConstraintBase, None, None]:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantTuple, )):
yield LiteralFitsConstraint(inp.type3, inp)
return
raise NotImplementedError(constant, inp)
def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBase, None, None]:
if isinstance(inp, ourlang.Constant):
yield from constant(ctx, inp)
return
if isinstance(inp, ourlang.VariableReference):
yield SameTypeConstraint(inp.variable.type3, inp.type3, f'The type of a variable reference is the same as the type of variable {inp.variable.name}')
return
if isinstance(inp, ourlang.BinaryOp):
if '|' == inp.operator:
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
yield MustImplementTypeClassConstraint('BitWiseOr', inp.left.type3)
yield SameTypeConstraint(inp.right.type3, inp.left.type3, '(|) :: a -> a -> a')
yield SameTypeConstraint(inp.type3, inp.right.type3, '(|) :: a -> a -> a')
return
raise NotImplementedError(expression, inp)
if isinstance(inp, ourlang.FunctionCall):
yield SameTypeConstraint(inp.function.returns_type3, inp.type3, f'The type of a function call to {inp.function.name} is the same as the type that the function returns')
assert len(inp.arguments) == len(inp.function.posonlyargs) # FIXME: Make this a Constraint
for fun_arg, call_arg in zip(inp.function.posonlyargs, inp.arguments):
yield from expression(ctx, call_arg)
yield SameTypeConstraint(fun_arg.type3, call_arg.type3,
f'The type of the value passed to argument {fun_arg.name} of function {inp.function.name} should match the type of that argument')
return
if isinstance(inp, ourlang.Subscript):
yield from expression(ctx, inp.varref)
yield from expression(ctx, inp.index)
yield CanBeSubscriptedConstraint(inp.varref.type3, inp.index)
return
if isinstance(inp, ourlang.AccessStructMember):
yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3,
f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
return
raise NotImplementedError(expression, inp)
def function(ctx: Context, inp: ourlang.Function) -> Generator[ConstraintBase, None, None]:
if isinstance(inp, ourlang.StructConstructor):
return
if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn):
raise NotImplementedError('Functions with not just a return statement')
yield from expression(ctx, inp.statements[0].value)
yield SameTypeConstraint(inp.returns_type3, inp.statements[0].value.type3, f'The type of the value returned from function {inp.name} should match its return type')
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Generator[ConstraintBase, None, None]:
yield from constant(ctx, inp.constant)
yield SameTypeConstraint(inp.type3, inp.constant.type3, f'The type of the value for module constant definition {inp.name} should match the type of that constant')
def module(ctx: Context, inp: ourlang.Module) -> Generator[ConstraintBase, None, None]:
for cdef in inp.constant_defs.values():
yield from module_constant_def(ctx, cdef)
for func in inp.functions.values():
yield from function(ctx, func)

103
phasm/type3/entry.py Normal file
View File

@ -0,0 +1,103 @@
"""
Entry point to the type3 system
"""
from typing import Dict, List
from .. import codestyle
from .. import ourlang
from .constraints import ConstraintBase, Error, RequireTypeSubstitutes, SameTypeConstraint, SubstitutionMap
from .constraintsgenerator import phasm_type3_generate_constraints
from .types import PlaceholderForType, Type3
MAX_RESTACK_COUNT = 100
class Type3Exception(BaseException):
"""
Thrown when the Type3 system detects constraints that do not hold
"""
def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
constraint_list = phasm_type3_generate_constraints(inp)
assert constraint_list
placeholder_substitutes: Dict[PlaceholderForType, Type3] = {}
placeholder_id_map: Dict[int, str] = {}
restack_counter = 0
error_list: List[Error] = []
while constraint_list:
if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
constraint = constraint_list.pop(0)
constraint.substitute_placeholders(placeholder_substitutes)
placeholder_substitutes.update(constraint.get_new_placeholder_substitutes())
check_result = constraint.check()
if check_result is None:
if verbose:
print('Constraint checks out')
continue
if isinstance(check_result, Error):
error_list.append(check_result)
if verbose:
print('Got an error')
continue
if isinstance(check_result, RequireTypeSubstitutes):
# FIXME: How to detect infinite loop? Is that necessary?
restack_counter += 1
if restack_counter > MAX_RESTACK_COUNT:
raise Exception('This looks like an infinite loop', constraint_list)
constraint_list.append(constraint)
if verbose:
print('Back on the todo list')
continue
raise NotImplementedError(constraint, check_result)
if error_list:
raise Type3Exception(error_list)
# TODO: Implement type substitution on the AST
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:
txt, fmt = constraint.human_readable()
act_fmt: Dict[str, str] = {}
for fmt_key, fmt_val in fmt.items():
if isinstance(fmt_val, ourlang.Expression):
fmt_val = codestyle.expression(fmt_val)
if isinstance(fmt_val, Type3):
fmt_val = fmt_val.name
if isinstance(fmt_val, PlaceholderForType):
placeholder_id = id(fmt_val)
if placeholder_id not in placeholder_id_map:
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
fmt_val = placeholder_id_map[placeholder_id]
if not isinstance(fmt_val, str):
fmt_val = repr(fmt_val)
act_fmt[fmt_key] = fmt_val
if 'comment' in act_fmt:
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + act_fmt['comment'])
else:
print('- ' + txt.format(**act_fmt))
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
print('=== v type3 constraint_list v === ')
for psk, psv in placeholder_substitutes.items():
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, 'Deduced type'))
for constraint in constraint_list:
print_constraint(placeholder_id_map, constraint)
print('=== ^ type3 constraint_list ^ === ')

259
phasm/type3/types.py Normal file
View File

@ -0,0 +1,259 @@
"""
Contains the final types for use in Phasm
These are actual, instantiated types; not the abstract types that the
constraint generator works with.
"""
from typing import Any, Dict, Iterable, List, Protocol, Union
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
class ExpressionProtocol(Protocol):
"""
A protocol for classes that should be updated on substitution
"""
type3: 'Type3OrPlaceholder'
"""
The type to update
"""
class Type3:
"""
Base class for the type3 types
"""
__slots__ = ('name', )
name: str
"""
The name of the string, as parsed and outputted by codestyle.
"""
def __init__(self, name: str) -> None:
self.name = name
def __repr__(self) -> str:
return f'Type3("{self.name}")'
def __str__(self) -> str:
return self.name
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}')
return str(self)
def __eq__(self, other: Any) -> bool:
raise NotImplementedError
def __ne__(self, other: Any) -> bool:
raise NotImplementedError
def __hash__(self) -> int:
raise NotImplementedError
def __bool__(self) -> bool:
raise NotImplementedError
class PlaceholderForType:
"""
A placeholder type, for when we don't know the final type yet
"""
__slots__ = ('update_on_substitution', )
update_on_substitution: List[ExpressionProtocol]
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
self.update_on_substitution = [*update_on_substitution]
def get_substituted(self, result_type: Type3) -> None:
"""
Informs this Placeholder that it's getting substituted
This will also clear the update_on_substitution list
"""
for uos in self.update_on_substitution:
uos.type3 = result_type
self.update_on_substitution = []
@property
def name(self) -> str:
return f'T{id(self)}'
def __repr__(self) -> str:
uos = ', '.join(repr(x) for x in self.update_on_substitution)
return f'PlaceholderForType({id(self)}, [{uos}])'
def __str__(self) -> str:
return f'PhFT_{id(self)}'
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError('unsupported format string passed to Type3.__format__')
return str(self)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, PlaceholderForType):
raise NotImplementedError
return self is other
def __ne__(self, other: Any) -> bool:
raise NotImplementedError
def __hash__(self) -> int:
return 0 # Valid but performs badly
def __bool__(self) -> bool:
raise NotImplementedError
Type3OrPlaceholder = Union[Type3, PlaceholderForType]
class AppliedType3(Type3):
"""
A Type3 that has been applied to another type
"""
__slots__ = ('base', 'args', )
base: Type3
"""
The base type
"""
args: List[Type3OrPlaceholder]
"""
The applied types (or placeholders there for)
"""
def __init__(self, base: Type3, args: Iterable[Type3OrPlaceholder]) -> None:
args = [*args]
super().__init__(
base.name
+ ' ('
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
+ ')'
)
self.base = base
self.args = args
def __repr__(self) -> str:
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
class StructType3(Type3):
"""
A Type3 struct with named members
"""
__slots__ = ('name', 'members', )
name: str
"""
The structs fully qualified name
"""
members: Dict[str, Type3]
"""
The struct's field definitions
"""
def __init__(self, name: str, members: Dict[str, Type3]) -> None:
super().__init__(name)
self.name = name
self.members = dict(members)
def __repr__(self) -> str:
return f'StructType3(repr({self.name}), repr({self.members}))'
none = Type3('none')
"""
The none type, for when functions simply don't return anything. e.g., IO().
"""
u8 = Type3('u8')
"""
The unsigned 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8.
"""
u32 = Type3('u32')
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32.
"""
u64 = Type3('u64')
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64.
"""
i8 = Type3('i8')
"""
The signed 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8, but
with the middel point being 0.
"""
i32 = Type3('i32')
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32, but
with the middel point being 0.
"""
i64 = Type3('i64')
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64, but
with the middel point being 0.
"""
f32 = Type3('f32')
"""
A 32-bits IEEE 754 float, of 32 bits width.
"""
f64 = Type3('f64')
"""
A 32-bits IEEE 754 float, of 64 bits width.
"""
static_array = Type3('static_array')
"""
This is a fixed length piece of memory that can be indexed at runtime.
It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated.
"""
tuple = Type3('tuple') # pylint: disable=W0622
"""
This is a fixed length piece of memory.
It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
"""
LOOKUP_TABLE: Dict[str, Type3] = {
'none': none,
'u8': u8,
'u32': u32,
'u64': u64,
'i8': i8,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
}

View File

@ -1,202 +0,0 @@
"""
The phasm type system
"""
from typing import Optional, List
class TypeBase:
"""
TypeBase base class
"""
__slots__ = ()
def alloc_size(self) -> int:
"""
When allocating this type in memory, how many bytes do we need to reserve?
"""
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
"""
__slots__ = ()
class TypeTupleMember:
"""
Represents a tuple member
"""
def __init__(self, idx: int, type_: TypeBase, offset: int) -> None:
self.idx = idx
self.type = type_
self.offset = offset
class TypeTuple(TypeBase):
"""
The tuple type
"""
__slots__ = ('members', )
members: List[TypeTupleMember]
def __init__(self) -> None:
self.members = []
def render_internal_name(self) -> str:
"""
Generates an internal name for this tuple
"""
mems = '@'.join('?' for x in self.members) # FIXME: Should not be a questionmark
assert ' ' not in mems, 'Not implement yet: subtuples'
return f'tuple@{mems}'
def alloc_size(self) -> int:
return sum(
x.type.alloc_size()
for x in self.members
)
class TypeStaticArrayMember:
"""
Represents a static array member
"""
def __init__(self, idx: int, offset: int) -> None:
self.idx = idx
self.offset = offset
class TypeStaticArray(TypeBase):
"""
The static array type
"""
__slots__ = ('member_type', 'members', )
member_type: TypeBase
members: List[TypeStaticArrayMember]
def __init__(self, member_type: TypeBase) -> None:
self.member_type = member_type
self.members = []
def alloc_size(self) -> int:
return self.member_type.alloc_size() * len(self.members)
class TypeStructMember:
"""
Represents a struct member
"""
def __init__(self, name: str, type_: TypeBase, offset: int) -> None:
self.name = name
self.type = type_
self.offset = offset
class TypeStruct(TypeBase):
"""
A struct has named properties
"""
__slots__ = ('name', 'lineno', 'members', )
name: str
lineno: int
members: List[TypeStructMember]
def __init__(self, name: str, lineno: int) -> None:
self.name = name
self.lineno = lineno
self.members = []
def get_member(self, name: str) -> Optional[TypeStructMember]:
"""
Returns a member by name
"""
for mem in self.members:
if mem.name == name:
return mem
return None
def alloc_size(self) -> int:
return sum(
x.type.alloc_size()
for x in self.members
)

View File

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

View File

@ -1,5 +1,5 @@
[MASTER]
disable=C0122,R0903,R0911,R0912,R0913,R0915,R1710,W0223
disable=C0103,C0122,R0902,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,
disable=C0116,R0201

View File

@ -0,0 +1,16 @@
"""
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_NUMERIC_TYPES = COMPLETE_INT_TYPES + COMPLETE_FLOAT_TYPES

View File

@ -13,6 +13,7 @@ import wasmtime
from phasm.compiler import phasm_compile
from phasm.parser import phasm_parse
from phasm.type3.entry import phasm_type3
from phasm import ourlang
from phasm import wasm
@ -40,6 +41,7 @@ class RunnerBase:
Parses the Phasm code into an AST
"""
self.phasm_ast = phasm_parse(self.phasm_code)
phasm_type3(self.phasm_ast, verbose=True)
def compile_ast(self) -> None:
"""

View File

@ -1,87 +0,0 @@
import pytest
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', ])
def test_tuple_1(type_):
code_py = f"""
CONSTANT: ({type_}, ) = (65, )
@exported
def testEntry() -> {type_}:
return helper(CONSTANT)
def helper(vector: ({type_}, )) -> {type_}:
return vector[0]
"""
result = Suite(code_py).run_code()
assert 65 == result.returned_value
@pytest.mark.integration_test
def test_tuple_6():
code_py = """
CONSTANT: (u8, u8, u32, u32, u64, u64, ) = (11, 22, 3333, 4444, 555555, 666666, )
@exported
def testEntry() -> u32:
return helper(CONSTANT)
def helper(vector: (u8, u8, u32, u32, u64, u64, )) -> u32:
return vector[2]
"""
result = Suite(code_py).run_code()
assert 3333 == result.returned_value
@pytest.mark.integration_test
@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(CONSTANT)
def helper(vector: {type_}[1]) -> {type_}:
return vector[0]
"""
result = Suite(code_py).run_code()
assert 65 == result.returned_value
@pytest.mark.integration_test
def test_static_array_6():
code_py = """
CONSTANT: u32[6] = (11, 22, 3333, 4444, 555555, 666666, )
@exported
def testEntry() -> u32:
return helper(CONSTANT)
def helper(vector: u32[6]) -> u32:
return vector[2]
"""
result = Suite(code_py).run_code()
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.integration_test
@pytest.mark.slow_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

@ -1,70 +0,0 @@
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

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

@ -0,0 +1,53 @@
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

@ -0,0 +1,71 @@
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

@ -0,0 +1,27 @@
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

@ -0,0 +1,470 @@
import pytest
from phasm.exceptions import TypingError
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, COMPLETE_NUMERIC_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_literal_does_not_fit():
code_py = """
@exported
def testEntry() -> u8:
return 1000
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
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
@pytest.mark.skip('Awaiting result of Type3 experiment')
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_uint(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
def test_bitwise_or_inv_type():
code_py = """
@exported
def testEntry() -> f64:
return 10.0 | 3.0
"""
with pytest.raises(Type3Exception, match='f64 does not implement the BitWiseOr type class'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_bitwise_or_type_mismatch():
code_py = """
CONSTANT1: u32 = 3
CONSTANT2: u64 = 3
@exported
def testEntry() -> u64:
return CONSTANT1 | CONSTANT2
"""
with pytest.raises(Type3Exception, match='u64 must be u32 instead'):
Suite(code_py).run_code()
@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)
@pytest.mark.integration_test
@pytest.mark.skip('TODO: Runtimes return a signed value, which is difficult to test')
@pytest.mark.parametrize('type_', ('u32', 'u64')) # FIXME: u8
def test_subtraction_underflow(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 - 11
"""
result = Suite(code_py).run_code()
assert 0 < result.returned_value
# TODO: Multiplication
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
def test_division_int(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 / 3
"""
result = Suite(code_py).run_code()
assert 3 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
def test_division_float(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10.0 / 8.0
"""
result = Suite(code_py).run_code()
assert 1.25 == result.returned_value
assert TYPE_MAP[type_] == type(result.returned_value)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_NUMERIC_TYPES)
def test_division_zero_let_it_crash(type_):
code_py = f"""
@exported
def testEntry() -> {type_}:
return 10 / 0
"""
with pytest.raises(Exception):
Suite(code_py).run_code()
@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) -> i32:
return left
@exported
def testEntry() -> i32:
return helper(13)
"""
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_return_type():
code_py = """
def helper() -> i64:
return 19
@exported
def testEntry() -> i32:
return helper()
"""
with pytest.raises(Type3Exception, match=r'i32.*i64'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_call_invalid_arg_type():
code_py = """
def helper(left: u8) -> u8:
return left
@exported
def testEntry() -> u8:
return helper(500)
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code()

View File

@ -0,0 +1,173 @@
import pytest
from phasm.exceptions import TypingError
from ..constants import (
ALL_FLOAT_TYPES, ALL_INT_TYPES, COMPLETE_INT_TYPES, COMPLETE_NUMERIC_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[1]
"""
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_NUMERIC_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,103 @@
import pytest
from phasm.exceptions import StaticError
from phasm.type3.entry import Type3Exception
from phasm.parser import phasm_parse
from ..constants import (
ALL_INT_TYPES
)
from ..helpers import Suite
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ALL_INT_TYPES)
def test_module_constant(type_):
code_py = f"""
class CheckedValue:
value: {type_}
CONSTANT: CheckedValue = CheckedValue(24)
@exported
def testEntry() -> {type_}:
return CONSTANT.value
"""
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.parametrize('type_', ALL_INT_TYPES)
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_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_struct_member(type_):
code_py = f"""
class Struct:
param: {type_}
def testEntry(arg: Struct) -> (i32, i32, ):
return arg.param
"""
with pytest.raises(Type3Exception, match=r'\(i32, i32, \) must be ' + type_ + ' instead'):
Suite(code_py).run_code()

View File

@ -0,0 +1,140 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..constants import COMPLETE_NUMERIC_TYPES, TYPE_MAP
from ..helpers import Suite
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['u8', 'u32', 'u64', ])
def test_module_constant_1(type_):
code_py = f"""
CONSTANT: ({type_}, ) = (65, )
@exported
def testEntry() -> {type_}:
return CONSTANT[0]
"""
result = Suite(code_py).run_code()
assert 65 == result.returned_value
@pytest.mark.integration_test
def test_module_constant_6():
code_py = """
CONSTANT: (u8, u8, u32, u32, u64, u64, ) = (11, 22, 3333, 4444, 555555, 666666, )
@exported
def testEntry() -> u32:
return CONSTANT[2]
"""
result = Suite(code_py).run_code()
assert 3333 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', COMPLETE_NUMERIC_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.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_assign_to_tuple_with_tuple():
code_py = """
CONSTANT: (u32, ) = 0
"""
with pytest.raises(Type3Exception, match='Must be tuple'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_constant_too_few_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, )
"""
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_constant_too_many_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
"""
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_constant_type_mismatch():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
"""
with pytest.raises(Type3Exception, match='Must fit in 1 byte(s)'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_must_use_literal_for_indexing():
code_py = """
CONSTANT: u32 = 0
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[CONSTANT]
"""
with pytest.raises(Type3Exception, match='Tuples must be indexed with literals'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_must_use_integer_for_indexing():
code_py = """
@exported
def testEntry(x: (u8, u32, u64)) -> u64:
return x[0.0]
"""
with pytest.raises(Type3Exception, match='Must be integer'):
Suite(code_py).run_code()

View File

@ -1,31 +0,0 @@
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

@ -1,571 +0,0 @@
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_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,109 +0,0 @@
import pytest
from phasm.parser import phasm_parse
from phasm.exceptions import StaticError
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_function_argument(type_):
code_py = f"""
def helper(a: {type_}) -> (i32, i32, ):
return a
"""
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'])
def test_type_mismatch_struct_member(type_):
code_py = f"""
class Struct:
param: {type_}
def testEntry(arg: Struct) -> (i32, i32, ):
return arg.param
"""
with pytest.raises(StaticError, match=f'Static error on line 6: Expected \\(i32, i32, \\), arg.param is actually {type_}'):
phasm_parse(code_py)
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_tuple_member(type_):
code_py = f"""
def testEntry(arg: ({type_}, )) -> (i32, i32, ):
return arg[0]
"""
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 = """
CONSTANT: (u32, u8, u8, ) = (24, 57, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_too_many_values():
code_py = """
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
"""
with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'):
phasm_parse(code_py)
@pytest.mark.integration_test
def test_tuple_constant_type_mismatch():
code_py = """
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)