Replaces type3 with type5

type5 is much more first principles based, so we get a lot
of weird quirks removed:

- FromLiteral no longer needs to understand AST
- Type unifications works more like Haskell
- Function types are just ordinary types, saving a lot of
  manual busywork

and more.
This commit is contained in:
Johan B.W. de Vries 2025-07-12 11:31:05 +02:00
parent 1a3bc19dce
commit 38e43944c7
72 changed files with 3906 additions and 3463 deletions

View File

@ -23,7 +23,7 @@ lint: venv/.done
venv/bin/ruff check phasm tests
typecheck: venv/.done
venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/runners.py
venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/memory.py tests/integration/runners.py
venv/.done: requirements.txt
python3.12 -m venv venv

22
TODO.md
View File

@ -22,3 +22,25 @@
- Try to implement the min and max functions using select
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
- GRose :: (* -> *) -> * -> *
- skolem => variable that cannot be unified
Limitations (for now):
- no type level nats
- only support first order kinds
Do not support yet:
```
data Record f = Record {
field: f Int
}
Record :: (* -> *) -> *
```
(Nested arrows)
- only support rank 1 types
```
mapRecord :: forall f g. (forall a. f a -> f b) -> Record f -> Record g
```
(Nested forall)

View File

@ -7,7 +7,7 @@ import sys
from .compiler import phasm_compile
from .optimise.removeunusedfuncs import removeunusedfuncs
from .parser import phasm_parse
from .type3.entry import phasm_type3
from .type5.solver import phasm_type5
def main(source: str, sink: str) -> int:
@ -19,7 +19,7 @@ def main(source: str, sink: str) -> int:
code_py = fil.read()
our_module = phasm_parse(code_py)
phasm_type3(our_module, verbose=False)
phasm_type5(our_module, verbose=False)
wasm_module = phasm_compile(our_module)
removeunusedfuncs(wasm_module)
code_wat = wasm_module.to_wat()

View File

@ -3,32 +3,16 @@ The base class for build environments.
Contains nothing but the explicit compiler builtins.
"""
from typing import Any, Callable, NamedTuple, Type
from warnings import warn
from typing import Any, Callable, NamedTuple, Sequence, Type
from ..type3.functions import (
TypeConstructorVariable,
TypeVariable,
)
from ..type3.routers import (
NoRouteForTypeException,
TypeApplicationRouter,
TypeClassArgsRouter,
TypeVariableLookup,
)
from ..type3.typeclasses import Type3Class, Type3ClassMethod
from ..type3.types import (
IntType3,
Type3,
TypeConstructor_Base,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Struct,
TypeConstructor_Tuple,
)
from ..type5 import constrainedexpr as type5constrainedexpr
from ..type5 import kindexpr as type5kindexpr
from ..type5 import record as type5record
from ..type5 import typeexpr as type5typeexpr
from ..typeclass import TypeClass
from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone
from . import builtins
from .typerouter import TypeAllocSize, TypeName
from .typevariablerouter import TypeVariableRouter
TypeInfo = NamedTuple('TypeInfo', [
# Name of the type
@ -54,14 +38,17 @@ class MissingImplementationWarning(Warning):
class BuildBase[G]:
__slots__ = (
'dynamic_array',
'function',
'static_array',
'struct',
'tuple_',
'dynamic_array_type5_constructor',
'function_type5_constructor',
'static_array_type5_constructor',
'tuple_type5_constructor_map',
'none_',
'bool_',
'none_type5',
'unit_type5',
'bool_type5',
'u8_type5',
'u32_type5',
'bytes_type5',
'type_info_map',
'type_info_constructed',
@ -69,58 +56,86 @@ class BuildBase[G]:
'types',
'type_classes',
'type_class_instances',
'type_class_instance_methods',
'methods',
'operators',
'alloc_size_router',
'type5_name',
'type5_alloc_size_root',
'type5_alloc_size_member',
)
dynamic_array: TypeConstructor_DynamicArray
dynamic_array_type5_constructor: type5typeexpr.TypeConstructor
"""
This is a dynamic length piece of memory.
Constructor for arrays of runtime deterined length.
It should be applied with two arguments. It has a runtime
determined length, and each argument is the same.
See type5_make_dynamic_array and type5_is_dynamic_array.
"""
function: TypeConstructor_Function
function_type5_constructor: type5typeexpr.TypeConstructor
"""
This is a function.
Constructor for functions.
It should be applied with one or more arguments. The last argument is the 'return' type.
See type5_make_function and type5_is_function.
"""
static_array: TypeConstructor_StaticArray
static_array_type5_constructor: type5typeexpr.TypeConstructor
"""
This is a fixed length piece of memory.
Constructor for arrays of compiled time determined length.
It should be applied with two arguments. It has a compile time
determined length, and each argument is the same.
See type5_make_static_array and type5_is_static_array.
"""
struct: TypeConstructor_Struct
tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor]
"""
This is like a tuple, but each argument is named, so that developers
can get and set fields by name.
Map for constructors for tuples of each length.
See type5_make_tuple and type5_is_tuple.
"""
tuple_: TypeConstructor_Tuple
none_type5: type5typeexpr.AtomicType
"""
This is a fixed length piece of memory.
The none type.
It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
TODO: Not sure this should be a buildin (rather than a Maybe type).
"""
none_: Type3
unit_type5: type5typeexpr.AtomicType
"""
The none type, for when functions simply don't return anything. e.g., IO().
The unit type has exactly one value and can always be constructed.
Use for functions that don't take any arguments or do not produce any result.
This only make sense for IO functions.
TODO: Is this not what Python calls None?
"""
bool_: Type3
bool_type5: type5typeexpr.AtomicType
"""
The bool type, either True or False
The bool type, either True or False.
Builtin since functions require a boolean value in their test.
"""
u8_type5: type5typeexpr.AtomicType
"""
The u8 type, an integer value between 0 and 255.
Builtin since we can have bytes literals - which are the same as u8[...].
"""
u32_type5: type5typeexpr.AtomicType
"""
The u32 type, an integer value between 0 and 4 294 967 295.
Builtin since we can use this for indexing arrays and since
we use this for the length prefix on dynamic arrays.
"""
bytes_type5: type5typeexpr.TypeApplication
"""
The bytes type, a dynamic array with u8 elements.
Builtin since we can have bytes literals.
"""
type_info_map: dict[str, TypeInfo]
@ -137,203 +152,275 @@ class BuildBase[G]:
not memory pointers but table addresses instead.
"""
types: dict[str, Type3]
types: dict[str, type5typeexpr.TypeExpr]
"""
Types that are available without explicit import.
"""
type_classes: dict[str, Type3Class]
type_classes: dict[str, TypeClass]
"""
Type classes that are available without explicit import.
"""
type_class_instances: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]]
type_class_instances: dict[str, set[tuple[type5typeexpr.TypeExpr, ...]]]
"""
Type class instances that are available without explicit import.
"""
type_class_instance_methods: dict[Type3ClassMethod, TypeClassArgsRouter[G, None]]
"""
Methods (and operators) for type class instances that are available without explicit import.
"""
methods: dict[str, Type3ClassMethod]
methods: dict[str, tuple[type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr, TypeVariableRouter[G]]]
"""
Methods that are available without explicit import.
"""
operators: dict[str, Type3ClassMethod]
operators: dict[str, tuple[type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr, TypeVariableRouter[G]]]
"""
Operators that are available without explicit import.
"""
alloc_size_router: TypeApplicationRouter['BuildBase[G]', int]
type5_name: TypeName
"""
Helper value for calculate_alloc_size.
Helper router to turn types into their human readable names.
"""
type5_alloc_size_root: TypeAllocSize
"""
Helper router to turn types into their allocation sizes.
This calculates the value when allocated directly.
"""
type5_alloc_size_member: TypeAllocSize
"""
Helper router to turn types into their allocation sizes.
This calculates the value when allocated as a member, e.g. in a tuple or struct.
"""
def __init__(self) -> None:
self.dynamic_array = builtins.dynamic_array
self.function = builtins.function
self.static_array = builtins.static_array
self.struct = builtins.struct
self.tuple_ = builtins.tuple_
S = type5kindexpr.Star()
N = type5kindexpr.Nat()
self.bool_ = builtins.bool_
self.none_ = builtins.none_
self.dynamic_array_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> S, name="dynamic_array")
self.function_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> (S >> S), name="function")
self.static_array_type5_constructor = type5typeexpr.TypeConstructor(kind=N >> (S >> S), name='static_array')
self.tuple_type5_constructor_map = {}
self.none_type5 = type5typeexpr.AtomicType('None')
self.unit_type5 = type5typeexpr.AtomicType('()')
self.bool_type5 = type5typeexpr.AtomicType('bool')
self.u8_type5 = type5typeexpr.AtomicType('u8')
self.u32_type5 = type5typeexpr.AtomicType('u32')
self.bytes_type5 = self.type5_make_dynamic_array(self.u8_type5)
self.type_info_map = {
'None': TypeInfo('ptr', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
'None': TypeInfo('None', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
'()': TypeInfo('()', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
'bool': TypeInfo('bool', WasmTypeInt32, 'unreachable', 'unreachable', 0, None),
'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
}
self.type_info_constructed = self.type_info_map['ptr']
self.type_info_constructed = TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False)
self.types = {
'None': self.none_,
'bool': self.bool_,
'None': self.none_type5,
'()': self.unit_type5,
'bool': self.bool_type5,
'u8': self.u8_type5,
'u32': self.u32_type5,
'bytes': self.bytes_type5,
}
self.type_classes = {}
self.type_class_instances = set()
self.type_class_instance_methods = {}
self.type_class_instances = {}
self.methods = {}
self.operators = {}
self.alloc_size_router = TypeApplicationRouter['BuildBase[G]', int]()
self.alloc_size_router.add(self.static_array, self.__class__.calculate_alloc_size_static_array)
self.alloc_size_router.add(self.struct, self.__class__.calculate_alloc_size_struct)
self.alloc_size_router.add(self.tuple_, self.__class__.calculate_alloc_size_tuple)
self.type5_name = TypeName(self)
self.type5_alloc_size_root = TypeAllocSize(self, is_member=False)
self.type5_alloc_size_member = TypeAllocSize(self, is_member=True)
def register_type_class(self, cls: Type3Class) -> None:
"""
Register that the given type class exists
"""
old_len_methods = len(self.methods)
old_len_operators = len(self.operators)
def register_type_class(self, cls: TypeClass) -> None:
assert cls.name not in self.type_classes, 'Duplicate typeclass name'
self.type_classes[cls.name] = cls
self.methods.update(cls.methods)
self.operators.update(cls.operators)
self.type_class_instances[cls.name] = set()
assert len(self.methods) == old_len_methods + len(cls.methods), 'Duplicated method detected'
assert len(self.operators) == old_len_operators + len(cls.operators), 'Duplicated operator detected'
for mtd_nam, mtd_typ in cls.methods.items():
assert mtd_nam not in self.methods, 'Duplicate typeclass method name'
self.methods[mtd_nam] = (mtd_typ, TypeVariableRouter(cls.variables), )
for opr_nam, opr_typ in cls.operators.items():
assert opr_nam not in self.operators, 'Duplicate typeclass operator name'
self.operators[opr_nam] = (opr_typ, TypeVariableRouter(cls.variables), )
def instance_type_class(
self,
cls: Type3Class,
*typ: Type3 | TypeConstructor_Base[Any],
methods: dict[str, Callable[[G, TypeVariableLookup], None]] = {},
operators: dict[str, Callable[[G, TypeVariableLookup], None]] = {},
) -> None:
"""
Registered the given type class and its implementation
"""
assert len(cls.args) == len(typ)
self,
cls: TypeClass,
*args: type5typeexpr.TypeExpr,
methods: dict[str, Callable[[G, Any], None]] = {},
operators: dict[str, Callable[[G, Any], None]] = {},
) -> None:
self.type_class_instances[cls.name].add(tuple(args))
for incls in cls.inherited_classes:
if (incls, tuple(typ), ) not in self.type_class_instances:
warn(MissingImplementationWarning(
incls.name + ' ' + ' '.join(x.name for x in typ) + ' - required for ' + cls.name
))
assert len(cls.variables) == len(args)
# First just register the type
self.type_class_instances.add((cls, tuple(typ), ))
for mtd_nam, mtd_imp in methods.items():
_, mtd_rtr = self.methods[mtd_nam]
mtd_rtr.register(args, mtd_imp)
# Then make the implementation findable
# We route based on the type class arguments.
tv_map: dict[TypeVariable, Type3] = {}
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {}
for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
if isinstance(arg_tv, TypeVariable):
assert isinstance(arg_tp, Type3)
tv_map[arg_tv] = arg_tp
elif isinstance(arg_tv, TypeConstructorVariable):
assert isinstance(arg_tp, TypeConstructor_Base)
tc_map[arg_tv] = arg_tp
else:
raise NotImplementedError(arg_tv, arg_tp)
for opr_nam, opr_imp in operators.items():
_, opr_rtr = self.operators[opr_nam]
opr_rtr.register(args, opr_imp)
for method_name, method in cls.methods.items():
router = self.type_class_instance_methods.get(method)
if router is None:
router = TypeClassArgsRouter[G, None](cls.args)
self.type_class_instance_methods[method] = router
def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr:
if not args:
raise TypeError("Functions must at least have a return type")
try:
generator = methods[method_name]
except KeyError:
warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ)))
if len(args) == 1:
# Functions always take an argument
# To distinguish between a function without arguments and a value
# of the type, we have a unit type
# This type has one value so it can always be called
args = [self.unit_type5, *args]
res_type5 = None
for arg_type5 in reversed(args):
if res_type5 is None:
res_type5 = arg_type5
continue
router.add(tv_map, tc_map, generator)
res_type5 = type5typeexpr.TypeApplication(
constructor=type5typeexpr.TypeApplication(
constructor=self.function_type5_constructor,
argument=arg_type5,
),
argument=res_type5,
)
for operator_name, operator in cls.operators.items():
router = self.type_class_instance_methods.get(operator)
if router is None:
router = TypeClassArgsRouter[G, None](cls.args)
self.type_class_instance_methods[operator] = router
assert res_type5 is not None # type hint
try:
generator = operators[operator_name]
except KeyError:
warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ)))
return res_type5
def type5_is_function(self, typeexpr: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr) -> list[type5typeexpr.TypeExpr] | None:
if isinstance(typeexpr, type5constrainedexpr.ConstrainedExpr):
typeexpr = typeexpr.expr
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
return None
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
return None
if typeexpr.constructor.constructor != self.function_type5_constructor:
return None
arg0 = typeexpr.constructor.argument
if arg0 is self.unit_type5:
my_args = []
else:
my_args = [arg0]
arg1 = typeexpr.argument
more_args = self.type5_is_function(arg1)
if more_args is None:
return my_args + [arg1]
return my_args + more_args
def type5_make_tuple(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeApplication:
if not args:
raise TypeError("Tuples must at least one field")
arlen = len(args)
constructor = self.tuple_type5_constructor_map.get(arlen)
if constructor is None:
star = type5kindexpr.Star()
kind: type5kindexpr.Arrow = star >> star
for _ in range(len(args) - 1):
kind = star >> kind
constructor = type5typeexpr.TypeConstructor(kind=kind, name=f'tuple_{arlen}')
self.tuple_type5_constructor_map[arlen] = constructor
result: type5typeexpr.TypeApplication | None = None
for arg in args:
if result is None:
result = type5typeexpr.TypeApplication(
constructor=constructor,
argument=arg
)
continue
router.add(tv_map, tc_map, generator)
result = type5typeexpr.TypeApplication(
constructor=result,
argument=arg
)
assert result is not None # type hint
return result
def calculate_alloc_size_static_array(self, args: tuple[Type3, IntType3]) -> int:
"""
Helper method for calculate_alloc_size - static_array
"""
sa_type, sa_len = args
def type5_is_tuple(self, typeexpr: type5typeexpr.TypeExpr) -> list[type5typeexpr.TypeExpr] | None:
arg_list = []
return sa_len.value * self.calculate_alloc_size(sa_type, is_member=True)
while isinstance(typeexpr, type5typeexpr.TypeApplication):
arg_list.append(typeexpr.argument)
typeexpr = typeexpr.constructor
def calculate_alloc_size_tuple(self, args: tuple[Type3, ...]) -> int:
"""
Helper method for calculate_alloc_size - tuple
"""
return sum(
self.calculate_alloc_size(x, is_member=True)
for x in args
if not isinstance(typeexpr, type5typeexpr.TypeConstructor):
return None
if typeexpr not in self.tuple_type5_constructor_map.values():
return None
return list(reversed(arg_list))
def type5_make_struct(self, name: str, fields: tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...]) -> type5record.Record:
return type5record.Record(name, fields)
def type5_is_struct(self, arg: type5typeexpr.TypeExpr) -> tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...] | None:
if not isinstance(arg, type5record.Record):
return None
return arg.fields
def type5_make_dynamic_array(self, arg: type5typeexpr.TypeExpr) -> type5typeexpr.TypeApplication:
return type5typeexpr.TypeApplication(
constructor=self.dynamic_array_type5_constructor,
argument=arg,
)
def calculate_alloc_size_struct(self, args: tuple[tuple[str, Type3], ...]) -> int:
def type5_is_dynamic_array(self, typeexpr: type5typeexpr.TypeExpr) -> type5typeexpr.TypeExpr | None:
"""
Helper method for calculate_alloc_size - struct
Check if the given type expr is a concrete dynamic array type.
The element argument type is returned if so. Else, None is returned.
"""
return sum(
self.calculate_alloc_size(x, is_member=True)
for _, x in args
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
return None
if typeexpr.constructor != self.dynamic_array_type5_constructor:
return None
return typeexpr.argument
def type5_make_static_array(self, len: int, arg: type5typeexpr.TypeExpr) -> type5typeexpr.TypeApplication:
return type5typeexpr.TypeApplication(
constructor=type5typeexpr.TypeApplication(
constructor=self.static_array_type5_constructor,
argument=type5typeexpr.TypeLevelNat(len),
),
argument=arg,
)
def calculate_alloc_size(self, typ: Type3, is_member: bool = False) -> int:
"""
Calculates how much bytes you need to allocate when reserving memory for the given type.
"""
typ_info = self.type_info_map.get(typ.name)
if typ_info is not None:
return typ_info.alloc_size
def type5_is_static_array(self, typeexpr: type5typeexpr.TypeExpr) -> tuple[int, type5typeexpr.TypeExpr] | None:
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
return None
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
return None
if typeexpr.constructor.constructor != self.static_array_type5_constructor:
return None
if is_member:
return self.type_info_constructed.alloc_size
assert isinstance(typeexpr.constructor.argument, type5typeexpr.TypeLevelNat) # type hint
try:
return self.alloc_size_router(self, typ)
except NoRouteForTypeException:
raise NotImplementedError(typ)
def calculate_member_offset(self, st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int:
"""
Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name.
"""
result = 0
for memnam, memtyp in st_args:
if needle == memnam:
return result
result += self.calculate_alloc_size(memtyp, is_member=True)
raise Exception(f'{needle} not in {st_name}')
return (
typeexpr.constructor.argument.value,
typeexpr.argument,
)

View File

@ -1,26 +0,0 @@
"""
Type (constructors) that are used integral to the compiler.
These cannot be changed as there is compiler logic depending on them.
For mode documentation, see base.py.
"""
from ..type3.types import (
Type3,
TypeApplication_Nullary,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Struct,
TypeConstructor_Tuple,
)
dynamic_array = TypeConstructor_DynamicArray('dynamic_array')
function = TypeConstructor_Function('function')
static_array = TypeConstructor_StaticArray('static_array')
struct = TypeConstructor_Struct('struct')
tuple_ = TypeConstructor_Tuple('tuple')
bool_ = Type3('bool', TypeApplication_Nullary(None, None))
none_ = Type3('None', TypeApplication_Nullary(None, None))

View File

@ -2,15 +2,8 @@
The default class for build environments.
Contains the compiler builtins as well as some sane defaults.
# Added types
f32: A 32-bits IEEE 754 float, of 32 bits width.
"""
from ..type3.types import (
Type3,
TypeApplication_Nullary,
)
from ..type5 import typeexpr as type5typeexpr
from ..wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
@ -25,7 +18,7 @@ from .typeclasses import (
eq,
extendable,
floating,
foldable,
# foldable,
fractional,
integral,
intnum,
@ -34,31 +27,18 @@ from .typeclasses import (
promotable,
reinterpretable,
sized,
subscriptable,
# subscriptable,
)
class BuildDefault(BuildBase[Generator]):
__slots__ = ()
def __init__(self) -> None:
super().__init__()
u8 = Type3('u8', TypeApplication_Nullary(None, None))
u16 = Type3('u16', TypeApplication_Nullary(None, None))
u32 = Type3('u32', TypeApplication_Nullary(None, None))
u64 = Type3('u64', TypeApplication_Nullary(None, None))
i8 = Type3('i8', TypeApplication_Nullary(None, None))
i16 = Type3('i16', TypeApplication_Nullary(None, None))
i32 = Type3('i32', TypeApplication_Nullary(None, None))
i64 = Type3('i64', TypeApplication_Nullary(None, None))
f32 = Type3('f32', TypeApplication_Nullary(None, None))
f64 = Type3('f64', TypeApplication_Nullary(None, None))
bytes_ = self.dynamic_array(u8)
self.type_info_map.update({
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False),
'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2, False),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False),
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True),
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True),
@ -69,17 +49,14 @@ class BuildDefault(BuildBase[Generator]):
})
self.types.update({
'u8': u8,
'u16': u16,
'u32': u32,
'u64': u64,
'i8': i8,
'i16': i16,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes_,
'u16': type5typeexpr.AtomicType('u16'),
'u64': type5typeexpr.AtomicType('u64'),
'i8': type5typeexpr.AtomicType('i8'),
'i16': type5typeexpr.AtomicType('i16'),
'i32': type5typeexpr.AtomicType('i32'),
'i64': type5typeexpr.AtomicType('i64'),
'f32': type5typeexpr.AtomicType('f32'),
'f64': type5typeexpr.AtomicType('f64'),
})
tc_list = [
@ -89,7 +66,7 @@ class BuildDefault(BuildBase[Generator]):
convertable, reinterpretable,
natnum, intnum, fractional, floating,
integral,
foldable, subscriptable,
# foldable, subscriptable,
sized,
]

View File

@ -1,150 +1,169 @@
"""
The Bits type class is defined for types that can be bit manipulated.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
a = TypeVariable(kind=Star(), name='a')
u32 = build.types['u32']
Bits = Type3Class('Bits', (a, ), methods={
'shl': [a, u32, a], # Logical shift left
'shr': [a, u32, a], # Logical shift right
'rotl': [a, u32, a], # Rotate bits left
'rotr': [a, u32, a], # Rotate bits right
Bits = TypeClass('Bits', (a, ), methods={}, operators={})
has_bits_a = TypeClassConstraint(Bits, [a])
fn_a_u32_a = ConstrainedExpr(
expr=build.type5_make_function([a, u32, a]),
constraints=(has_bits_a, ),
)
fn_a_a_a = ConstrainedExpr(
expr=build.type5_make_function([a, a, a]),
constraints=(has_bits_a, ),
)
Bits.methods = {
'shl': fn_a_u32_a, # Logical shift left
'shr': fn_a_u32_a, # Logical shift right
'rotl': fn_a_u32_a, # Rotate bits left
'rotr': fn_a_u32_a, # Rotate bits right
# FIXME: Do we want to expose clz, ctz, popcnt?
}, operators={
'&': [a, a, a], # Bit-wise and
'|': [a, a, a], # Bit-wise or
'^': [a, a, a], # Bit-wise xor
})
}
Bits.operators = {
'&': fn_a_a_a, # Bit-wise and
'|': fn_a_a_a, # Bit-wise or
'^': fn_a_a_a, # Bit-wise xor
}
build.register_type_class(Bits)
def wasm_u8_logical_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
g.i32.const(0xFF)
g.i32.and_()
def wasm_u16_logical_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_u32_logical_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
def wasm_u64_logical_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_logical_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shl()
def wasm_u8_logical_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_u()
def wasm_u16_logical_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_u()
def wasm_u32_logical_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_u()
def wasm_u64_logical_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_logical_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shr_u()
def wasm_u8_rotate_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u8_rotl__')
def wasm_u16_rotate_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u16_rotl__')
def wasm_u32_rotate_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.rotl()
def wasm_u64_rotate_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_rotate_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.rotl()
def wasm_u8_rotate_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u8_rotr__')
def wasm_u16_rotate_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u16_rotr__')
def wasm_u32_rotate_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.rotr()
def wasm_u64_rotate_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_rotate_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.rotr()
def wasm_u8_bitwise_and(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.and_()
def wasm_u16_bitwise_and(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.and_()
def wasm_u32_bitwise_and(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.and_()
def wasm_u64_bitwise_and(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_bitwise_and(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.and_()
def wasm_u8_bitwise_or(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.or_()
def wasm_u16_bitwise_or(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.or_()
def wasm_u32_bitwise_or(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.or_()
def wasm_u64_bitwise_or(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_bitwise_or(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.or_()
def wasm_u8_bitwise_xor(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.xor()
def wasm_u16_bitwise_xor(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.xor()
def wasm_u32_bitwise_xor(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.xor()
def wasm_u64_bitwise_xor(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_bitwise_xor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.xor()

View File

@ -3,87 +3,104 @@ The Convertable type class is defined for when a value from one type can be
converted to another type - but there's no real guarantee about precision or
value loss.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
b = make_typevar('b')
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
Convertable = Type3Class('Convertable', (a, b, ), methods={
'convert': [a, b],
'truncate': [b, a], # To prevent name clas with Fractional
}, operators={})
Convertable = TypeClass('Convertable', (a, b, ), methods={}, operators={})
has_convertable_a_b = TypeClassConstraint(Convertable, [a, b])
fn_a_b = ConstrainedExpr(
expr=build.type5_make_function([a, b]),
constraints=(has_convertable_a_b, ),
)
fn_b_a = ConstrainedExpr(
expr=build.type5_make_function([b, a]),
constraints=(has_convertable_a_b, ),
)
Convertable.methods = {
'convert': fn_a_b,
'truncate': fn_b_a, # To prevent name clas with Fractional
}
build.register_type_class(Convertable)
def wasm_u32_f32_convert(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.convert_i32_u()
def wasm_u32_f64_convert(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.convert_i32_u()
def wasm_u64_f32_convert(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.convert_i64_u()
def wasm_u64_f64_convert(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.convert_i64_u()
def wasm_i32_f32_convert(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.convert_i32_s()
def wasm_i32_f64_convert(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.convert_i32_s()
def wasm_i64_f32_convert(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_f32_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.convert_i64_s()
def wasm_i64_f64_convert(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_f64_convert(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.convert_i64_s()
def wasm_u32_f32_truncate(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.trunc_f32_u()
def wasm_u32_f64_truncate(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.trunc_f64_u()
def wasm_u64_f32_truncate(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.trunc_f32_u()
def wasm_u64_f64_truncate(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.trunc_f64_u()
def wasm_i32_f32_truncate(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.trunc_f32_s()
def wasm_i32_f64_truncate(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.trunc_f64_s()
def wasm_i64_f32_truncate(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_f32_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.trunc_f32_s()
def wasm_i64_f64_truncate(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_f64_truncate(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.trunc_f64_s()

View File

@ -1,103 +1,115 @@
"""
The Eq type class is defined for types that can be compered based on equality.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
a = TypeVariable(kind=Star(), name='a')
Eq = Type3Class('Eq', (a, ), methods={}, operators={
'==': [a, a, build.bool_],
'!=': [a, a, build.bool_],
Eq = TypeClass('Eq', (a, ), methods={}, operators={})
has_eq_a = TypeClassConstraint(Eq, [a])
fn_a_a_bool = ConstrainedExpr(
expr=build.type5_make_function([a, a, build.bool_type5]),
constraints=(has_eq_a, ),
)
Eq.operators = {
'==': fn_a_a_bool,
'!=': fn_a_a_bool,
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
})
}
build.register_type_class(Eq)
def wasm_u8_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_u16_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_u32_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_u64_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.eq()
def wasm_i8_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_i16_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_i32_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.eq()
def wasm_i64_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.eq()
def wasm_f32_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.eq()
def wasm_f64_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.eq()
def wasm_u8_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_u16_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_u32_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_u64_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.ne()
def wasm_i8_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_i16_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_i32_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ne()
def wasm_i64_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.ne()
def wasm_f32_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.ne()
def wasm_f64_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_not_equals(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.ne()

View File

@ -2,145 +2,162 @@
The Extendable type class is defined for types that can safely be extended to a type
that can hold strictly more values. Going back will result in some values being lost.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
b = make_typevar('b')
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
Extendable = Type3Class('Extendable', (a, b, ), methods={
'extend': [a, b],
'wrap': [b, a],
}, operators={})
Extendable = TypeClass('Extendable', (a, b, ), methods={}, operators={})
has_extendable_a_b = TypeClassConstraint(Extendable, [a, b])
fn_a_b = ConstrainedExpr(
expr=build.type5_make_function([a, b]),
constraints=(has_extendable_a_b, ),
)
fn_b_a = ConstrainedExpr(
expr=build.type5_make_function([b, a]),
constraints=(has_extendable_a_b, ),
)
Extendable.methods = {
'extend': fn_a_b,
'wrap': fn_b_a,
}
build.register_type_class(Extendable)
def wasm_u8_u16_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_u16_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# u8 and u16 are both stored as u32
pass
def wasm_u8_u32_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_u32_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# u8 is already stored as u32
pass
def wasm_u8_u64_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
def wasm_u16_u32_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_u32_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# u16 is already stored as u32
pass
def wasm_u16_u64_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
def wasm_u32_u64_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_u64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
def wasm_i8_i16_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_i16_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# i8 is already stored as i32
pass
def wasm_i8_i32_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_i32_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# i8 is already stored as i32
pass
def wasm_i8_i64_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_s()
def wasm_i16_i32_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_i32_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# No-op
# i16 is already stored as i32
pass
def wasm_i16_i64_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_s()
def wasm_i32_i64_extend(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_i64_extend(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_s()
def wasm_u8_u16_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_u16_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def wasm_u8_u32_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_u32_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def wasm_u8_u64_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFF)
g.i32.and_()
def wasm_u16_u32_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_u32_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_u16_u64_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_u32_u64_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_u64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
def wasm_i8_i16_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_i16_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def wasm_i8_i32_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_i32_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def wasm_i8_i64_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFF)
g.i32.and_()
def wasm_i16_i32_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_i32_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_i16_i64_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFFFF)
g.i32.and_()
def wasm_i32_i64_wrap(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_i64_wrap(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.wrap_i64()

View File

@ -1,31 +1,44 @@
"""
The Floating type class is defined for Real numbers.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
Fractional = build.type_classes['Fractional']
a = TypeVariable(kind=Star(), name='a')
Floating = Type3Class('Floating', (a, ), methods={
'sqrt': [a, a],
}, operators={}, inherited_classes=[Fractional])
Floating = TypeClass('Floating', (a, ), methods={}, operators={})
has_floating_a = TypeClassConstraint(Floating, [a])
fn_a_a = ConstrainedExpr(
expr=build.type5_make_function([a, a]),
constraints=(has_floating_a, ),
)
Floating.methods = {
'sqrt': fn_a_a
}
# FIXME: inherited_classes=[Fractional]
# FIXME: Do we want to expose copysign?
build.register_type_class(Floating)
def wasm_f32_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_sqrt(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f32.sqrt')
def wasm_f64_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_sqrt(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f64.sqrt')

View File

@ -1,67 +1,87 @@
"""
The Fractional type class is defined for numeric types that can be (precisely) divided.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
a = TypeVariable(kind=Star(), name='a')
NatNum = build.type_classes['NatNum']
Fractional = Type3Class('Fractional', (a, ), methods={
'ceil': [a, a],
'floor': [a, a],
'trunc': [a, a],
'nearest': [a, a],
}, operators={
'/': [a, a, a],
}, inherited_classes=[NatNum])
Fractional = TypeClass('Fractional', (a, ), methods={}, operators={})
has_fractional_a = TypeClassConstraint(Fractional, [a])
fn_a_a = ConstrainedExpr(
expr=build.type5_make_function([a, a]),
constraints=(has_fractional_a, ),
)
fn_a_a_a = ConstrainedExpr(
expr=build.type5_make_function([a, a, a]),
constraints=(has_fractional_a, ),
)
Fractional.methods = {
'ceil': fn_a_a,
'floor': fn_a_a,
'trunc': fn_a_a,
'nearest': fn_a_a,
}
Fractional.operators = {
'/': fn_a_a_a,
}
# FIXME: inherited_classes=[NatNum])
build.register_type_class(Fractional)
def wasm_f32_ceil(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_ceil(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.ceil()
def wasm_f64_ceil(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_ceil(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.ceil()
def wasm_f32_floor(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_floor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.floor()
def wasm_f64_floor(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_floor(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.floor()
def wasm_f32_trunc(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_trunc(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.trunc()
def wasm_f64_trunc(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_trunc(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.trunc()
def wasm_f32_nearest(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_nearest(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.nearest()
def wasm_f64_nearest(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_nearest(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.nearest()
def wasm_f32_div(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.div()
def wasm_f64_div(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.div()

View File

@ -1,56 +1,69 @@
"""
The Integral type class is defined for types that can only be approximately divided.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
a = TypeVariable(kind=Star(), name='a')
NatNum = build.type_classes['NatNum']
Integral = Type3Class('Integral', (a, ), methods={
}, operators={
'//': [a, a, a],
'%': [a, a, a],
}, inherited_classes=[NatNum])
Integral = TypeClass('Integral', (a, ), methods={}, operators={})
has_integral_a = TypeClassConstraint(Integral, [a])
fn_a_a_a = ConstrainedExpr(
expr=build.type5_make_function([a, a, a]),
constraints=(has_integral_a, ),
)
Integral.operators = {
'//': fn_a_a_a,
'%': fn_a_a_a,
}
# FIXME: inherited_classes=[NatNum]
build.register_type_class(Integral)
def wasm_u32_div(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.div_u')
def wasm_u64_div(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.div_u')
def wasm_i32_div(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.div_s')
def wasm_i64_div(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_div(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.div_s')
def wasm_u32_rem(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_rem(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.rem_u')
def wasm_u64_rem(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_rem(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.rem_u')
def wasm_i32_rem(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_rem(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.rem_s')
def wasm_i64_rem(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_rem(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.rem_s')

View File

@ -1,57 +1,70 @@
"""
The IntNum type class is defined for Integer Number types.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
a = TypeVariable(kind=Star(), name='a')
NatNum = build.type_classes['NatNum']
IntNum = Type3Class('IntNum', (a, ), methods={
'abs': [a, a],
'neg': [a, a],
}, operators={}, inherited_classes=[NatNum])
IntNum = TypeClass('IntNum', (a, ), methods={}, operators={})
has_intnum_a = TypeClassConstraint(IntNum, [a])
fn_a_a = ConstrainedExpr(
expr=build.type5_make_function([a, a]),
constraints=(has_intnum_a, ),
)
IntNum.methods = {
'abs': fn_a_a,
'neg': fn_a_a,
}
# FIXME: inherited_classes=[NatNum])
build.register_type_class(IntNum)
def wasm_i32_abs(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_abs(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_abs__')
def wasm_i64_abs(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_abs(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i64_abs__')
def wasm_f32_abs(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_abs(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.abs()
def wasm_f64_abs(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_abs(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.abs()
def wasm_i32_neg(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_neg(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.const(-1)
g.i32.mul()
def wasm_i64_neg(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_neg(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.const(-1)
g.i64.mul()
def wasm_f32_neg(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_neg(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.neg()
def wasm_f64_neg(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_neg(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.neg()

View File

@ -3,159 +3,176 @@ The NatNum type class is defined for Natural Number types.
These cannot be negative so functions like abs and neg make no sense.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
a = TypeVariable(kind=Star(), name='a')
u32 = build.types['u32']
NatNum = Type3Class('NatNum', (a, ), methods={}, operators={
'+': [a, a, a],
'-': [a, a, a],
'*': [a, a, a],
'<<': [a, u32, a], # Arithmic shift left
'>>': [a, u32, a], # Arithmic shift right
})
NatNum = TypeClass('NatNum', (a, ), methods={}, operators={})
has_natnum_a = TypeClassConstraint(NatNum, [a])
fn_a_a_a = ConstrainedExpr(
expr=build.type5_make_function([a, a, a]),
constraints=(has_natnum_a, ),
)
fn_a_u32_a = ConstrainedExpr(
expr=build.type5_make_function([a, u32, a]),
constraints=(has_natnum_a, ),
)
NatNum.operators = {
'+': fn_a_a_a,
'-': fn_a_a_a,
'*': fn_a_a_a,
'<<': fn_a_u32_a, # Arithmic shift left
'>>': fn_a_u32_a, # Arithmic shift right
}
build.register_type_class(NatNum)
## ###
## class NatNum
def wasm_u32_add(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.add')
def wasm_u64_add(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.add')
def wasm_i32_add(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.add')
def wasm_i64_add(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.add')
def wasm_f32_add(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f32.add')
def wasm_f64_add(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_add(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f64.add')
def wasm_u32_sub(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.sub')
def wasm_u64_sub(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.sub')
def wasm_i32_sub(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.sub')
def wasm_i64_sub(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.sub')
def wasm_f32_sub(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f32.sub')
def wasm_f64_sub(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_sub(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f64.sub')
def wasm_u32_mul(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.mul')
def wasm_u64_mul(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.mul')
def wasm_i32_mul(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i32.mul')
def wasm_i64_mul(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('i64.mul')
def wasm_f32_mul(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f32.mul')
def wasm_f64_mul(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_mul(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.add_statement('f64.mul')
def wasm_u32_arithmic_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
def wasm_u64_arithmic_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shl()
def wasm_i32_arithmic_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shl()
def wasm_i64_arithmic_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shl()
def wasm_f32_arithmic_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_pow2__')
g.f32.convert_i32_u()
g.f32.mul()
def wasm_f64_arithmic_shift_left(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_arithmic_shift_left(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_pow2__')
g.f64.convert_i32_u()
g.f64.mul()
def wasm_u32_arithmic_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_u()
def wasm_u64_arithmic_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shr_u()
def wasm_i32_arithmic_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.shr_s()
def wasm_i64_arithmic_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.extend_i32_u()
g.i64.shr_s()
def wasm_f32_arithmic_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_pow2__')
g.f32.convert_i32_u()
g.f32.div()
def wasm_f64_arithmic_shift_right(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_arithmic_shift_right(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_pow2__')
g.f64.convert_i32_u()

View File

@ -1,270 +1,288 @@
"""
The Ord type class is defined for totally ordered datatypes.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
a = TypeVariable(kind=Star(), name='a')
Eq = build.type_classes['Eq']
Ord = TypeClass('Ord', (a, ), methods={}, operators={})
Ord = Type3Class('Ord', (a, ), methods={
'min': [a, a, a],
'max': [a, a, a],
}, operators={
'<': [a, a, build.bool_],
'<=': [a, a, build.bool_],
'>': [a, a, build.bool_],
'>=': [a, a, build.bool_],
}, inherited_classes=[Eq])
has_ord_a = TypeClassConstraint(Ord, [a])
fn_a_a_a = ConstrainedExpr(
expr=build.type5_make_function([a, a, a]),
constraints=(has_ord_a, ),
)
fn_a_a_bool = ConstrainedExpr(
expr=build.type5_make_function([a, a, build.bool_type5]),
constraints=(has_ord_a, ),
)
Ord.methods = {
'min': fn_a_a_a,
'max': fn_a_a_a,
}
Ord.operators = {
'<': fn_a_a_bool,
'<=': fn_a_a_bool,
'>': fn_a_a_bool,
'>=': fn_a_a_bool,
}
#FIXME: }, inherited_classes=[Eq])
build.register_type_class(Ord)
def wasm_u8_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_min__')
def wasm_u16_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_min__')
def wasm_u32_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_min__')
def wasm_u64_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u64_min__')
def wasm_i8_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_min__')
def wasm_i16_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_min__')
def wasm_i32_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_min__')
def wasm_i64_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i64_min__')
def wasm_f32_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.min()
def wasm_f64_min(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_min(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.min()
def wasm_u8_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_max__')
def wasm_u16_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_max__')
def wasm_u32_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u32_max__')
def wasm_u64_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__u64_max__')
def wasm_i8_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_max__')
def wasm_i16_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_max__')
def wasm_i32_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i32_max__')
def wasm_i64_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.call('stdlib.types.__i64_max__')
def wasm_f32_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.max()
def wasm_f64_max(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_max(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.max()
def wasm_u8_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_u()
def wasm_u16_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_u()
def wasm_u32_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_u()
def wasm_u64_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.lt_u()
def wasm_i8_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_s()
def wasm_i16_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_s()
def wasm_i32_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.lt_s()
def wasm_i64_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.lt_s()
def wasm_f32_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.lt()
def wasm_f64_less_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_less_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.lt()
def wasm_u8_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_u()
def wasm_u16_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_u()
def wasm_u32_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_u()
def wasm_u64_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.le_u()
def wasm_i8_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_s()
def wasm_i16_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_s()
def wasm_i32_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.le_s()
def wasm_i64_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.le_s()
def wasm_f32_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.le()
def wasm_f64_less_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_less_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.le()
def wasm_u8_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_u()
def wasm_u16_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_u()
def wasm_u32_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_u()
def wasm_u64_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.gt_u()
def wasm_i8_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_s()
def wasm_i16_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_s()
def wasm_i32_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.gt_s()
def wasm_i64_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.gt_s()
def wasm_f32_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.gt()
def wasm_f64_greater_than(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_greater_than(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.gt()
def wasm_u8_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u8_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_u()
def wasm_u16_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u16_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_u()
def wasm_u32_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_u()
def wasm_u64_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.ge_u()
def wasm_i8_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i8_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_s()
def wasm_i16_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i16_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_s()
def wasm_i32_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.ge_s()
def wasm_i64_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.ge_s()
def wasm_f32_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.ge()
def wasm_f64_greater_than_or_equal(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_greater_than_or_equal(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.ge()

View File

@ -2,31 +2,48 @@
The Promotable type class is defined for types that can safely be promoted to a type
that can hold strictly more values. Going back will result in some precision being lost.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
b = make_typevar('b')
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
Promotable = Type3Class('Promotable', (a, b, ), methods={
'promote': [a, b],
'demote': [b, a],
}, operators={})
Promotable = TypeClass('Promotable', (a, b, ), methods={}, operators={})
has_Promotable_a_b = TypeClassConstraint(Promotable, [a, b])
fn_a_b = ConstrainedExpr(
expr=build.type5_make_function([a, b]),
constraints=(has_Promotable_a_b, ),
)
fn_b_a = ConstrainedExpr(
expr=build.type5_make_function([b, a]),
constraints=(has_Promotable_a_b, ),
)
Promotable.methods = {
'promote': fn_a_b,
'demote': fn_b_a,
}
build.register_type_class(Promotable)
def wasm_f32_f64_promote(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_f64_promote(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.promote_f32()
def wasm_f32_f64_demote(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_f64_demote(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.demote_f64()

View File

@ -2,54 +2,66 @@
The Reinterpretable type class is defined for when the data for a type can be reinterpreted
to hold a value for another type.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
b = make_typevar('b')
a = TypeVariable(kind=Star(), name='a')
b = TypeVariable(kind=Star(), name='b')
Reinterpretable = Type3Class('Reinterpretable', (a, b, ), methods={
'reinterpret': [a, b]
}, operators={})
Reinterpretable = TypeClass('Reinterpretable', (a, b, ), methods={}, operators={})
has_reinterpretable_a_b = TypeClassConstraint(Reinterpretable, [a, b])
fn_a_b = ConstrainedExpr(
expr=build.type5_make_function([a, b]),
constraints=(has_reinterpretable_a_b, ),
)
Reinterpretable.methods = {
'reinterpret': fn_a_b,
}
build.register_type_class(Reinterpretable)
def wasm_i32_f32_reinterpret(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i32_f32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.reinterpret_i32()
def wasm_u32_f32_reinterpret(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u32_f32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f32.reinterpret_i32()
def wasm_i64_f64_reinterpret(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_i64_f64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.reinterpret_i64()
def wasm_u64_f64_reinterpret(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_u64_f64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.f64.reinterpret_i64()
def wasm_f32_i32_reinterpret(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_i32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.reinterpret_f32()
def wasm_f32_u32_reinterpret(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f32_u32_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i32.reinterpret_f32()
def wasm_f64_i64_reinterpret(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_i64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.reinterpret_f64()
def wasm_f64_u64_reinterpret(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_f64_u64_reinterpret(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
g.i64.reinterpret_f64()

View File

@ -3,29 +3,42 @@ The Sized type class is defined for when a value can be considered to have a len
The length is always in number of items, and never in number of bytes (unless an item is a byte).
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import TypeConstructorVariable, make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type3.types import IntType3
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeApplication, TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
t = TypeConstructorVariable('t')
a = TypeVariable(kind=Star(), name='a')
t = TypeVariable(kind=Star() >> Star(), name='t')
t_a = TypeApplication(constructor=t, argument=a)
u32 = build.types['u32']
Sized = Type3Class('Sized', (t, ), methods={
'len': [t(a), u32],
}, operators={}) # FIXME: Once we get type class families, add [] here
Sized = TypeClass('Sized', (t, ), methods={}, operators={})
has_sized_t = TypeClassConstraint(Sized, [t])
fn_t_a_u32 = ConstrainedExpr(
expr=build.type5_make_function([t_a, u32]),
constraints=(has_sized_t, ),
)
Sized.methods = {
'len': fn_t_a_u32,
}
# FIXME: Once we get type class families (?), add [] here
build.register_type_class(Sized)
def wasm_dynamic_array_len(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm_dynamic_array_len(g: WasmGenerator, tv_map: Any) -> None:
del tv_map
# The length is stored in the first 4 bytes
g.i32.load()
@ -45,9 +58,10 @@ def wasm_static_array_len(g: WasmGenerator, tvl: TypeVariableLookup) -> None:
def wasm(build: BuildBase[WasmGenerator]) -> None:
Sized = build.type_classes['Sized']
build.instance_type_class(Sized, build.dynamic_array, methods={
build.instance_type_class(Sized, build.dynamic_array_type5_constructor, methods={
'len': wasm_dynamic_array_len,
})
build.instance_type_class(Sized, build.static_array, methods={
'len': wasm_static_array_len,
})
# FIXME: This should be `forall n: a[n]`
# build.instance_type_class(Sized, build.static_array_type5_constructor, methods={
# 'len': wasm_static_array_len,
# })

View File

@ -1,24 +1,36 @@
"""
The Eq type class is defined for types that can be compered based on equality.
"""
from __future__ import annotations
from typing import Any
from ...type3.functions import TypeConstructorVariable, make_typevar
from ...type3.routers import TypeVariableLookup
from ...type3.typeclasses import Type3Class
from ...type3.types import IntType3, Type3
from ...type5.constrainedexpr import ConstrainedExpr
from ...type5.kindexpr import Nat, Star
from ...type5.typeexpr import TypeApplication, TypeLevelNat, TypeVariable
from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase
def load(build: BuildBase[Any]) -> None:
a = make_typevar('a')
t = TypeConstructorVariable('t')
a = TypeVariable(kind=Star(), name='a')
t = TypeVariable(kind=Star() >> Star(), name='t')
t_a = TypeApplication(constructor=t, argument=a)
u32 = build.types['u32']
Subscriptable = Type3Class('Subscriptable', (t, ), methods={}, operators={
'[]': [t(a), u32, a],
})
Subscriptable = TypeClass('Subscriptable', (t, ), methods={}, operators={})
has_subscriptable_t = TypeClassConstraint(Subscriptable, [t])
fn_t_a_u32_a = ConstrainedExpr(
expr=build.type5_make_function([t_a, u32, a]),
constraints=(has_subscriptable_t, ),
)
Subscriptable.operators = {
'[]': fn_t_a_u32_a,
}
build.register_type_class(Subscriptable)
@ -26,7 +38,7 @@ class SubscriptableCodeGenerator:
def __init__(self, build: BuildBase[WasmGenerator]) -> None:
self.build = build
def wasm_dynamic_array_getitem(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None:
def wasm_dynamic_array_getitem(self, g: WasmGenerator, tvl: Any) -> None:
tv_map, tc_map = tvl
tvn_map = {
@ -36,8 +48,6 @@ class SubscriptableCodeGenerator:
sa_type = tvn_map['a']
assert isinstance(sa_type, Type3)
ptr_type_info = self.build.type_info_map['ptr']
u32_type_info = self.build.type_info_map['u32']
@ -85,7 +95,7 @@ class SubscriptableCodeGenerator:
g.add_statement(sa_type_info.wasm_load_func)
# Stack: [el]
def wasm_static_array_getitem(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None:
def wasm_static_array_getitem(self, g: WasmGenerator, tvl: Any) -> None:
tv_map, tc_map = tvl
tvn_map = {
@ -96,9 +106,6 @@ class SubscriptableCodeGenerator:
sa_type = tvn_map['a']
sa_len = tvn_map['a*']
assert isinstance(sa_type, Type3)
assert isinstance(sa_len, IntType3)
ptr_type_info = self.build.type_info_map['ptr']
u32_type_info = self.build.type_info_map['u32']
@ -139,9 +146,10 @@ def wasm(build: BuildBase[WasmGenerator]) -> None:
gen = SubscriptableCodeGenerator(build)
build.instance_type_class(Subscriptable, build.dynamic_array, operators={
build.instance_type_class(Subscriptable, build.dynamic_array_type5_constructor, operators={
'[]': gen.wasm_dynamic_array_getitem,
})
build.instance_type_class(Subscriptable, build.static_array, operators={
foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=TypeLevelNat(2))
build.instance_type_class(Subscriptable, foo, operators={
'[]': gen.wasm_static_array_getitem,
})

150
phasm/build/typerouter.py Normal file
View File

@ -0,0 +1,150 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..type5.record import Record
from ..type5.typeexpr import (
AtomicType,
TypeApplication,
TypeConstructor,
TypeExpr,
TypeVariable,
)
from ..type5.typerouter import TypeRouter
if TYPE_CHECKING:
from .base import BuildBase
class BuildTypeRouter[T](TypeRouter[T]):
"""
Extends the general type router with phasm builtin types.
Like functions, tuples, static and dynamic arrays.
"""
__slots__ = ('build', )
def __init__(self, build: BuildBase[Any]) -> None:
self.build = build
def when_application(self, typ: TypeApplication) -> T:
da_arg = self.build.type5_is_dynamic_array(typ)
if da_arg is not None:
return self.when_dynamic_array(da_arg)
fn_args = self.build.type5_is_function(typ)
if fn_args is not None:
return self.when_function(fn_args)
sa_args = self.build.type5_is_static_array(typ)
if sa_args is not None:
sa_len, sa_typ = sa_args
return self.when_static_array(sa_len, sa_typ)
tp_args = self.build.type5_is_tuple(typ)
if tp_args is not None:
return self.when_tuple(tp_args)
return self.when_application_other(typ)
def when_record(self, typ: Record) -> T:
return self.when_struct(typ)
def when_application_other(self, typ: TypeApplication) -> T:
raise NotImplementedError
def when_dynamic_array(self, da_arg: TypeExpr) -> T:
raise NotImplementedError
def when_function(self, fn_args: list[TypeExpr]) -> T:
raise NotImplementedError
def when_struct(self, typ: Record) -> T:
raise NotImplementedError
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> T:
raise NotImplementedError
def when_tuple(self, tp_args: list[TypeExpr]) -> T:
raise NotImplementedError
class TypeName(BuildTypeRouter[str]):
"""
Router to generate a type's name.
Also serves an example implementation.
"""
__slots__ = ()
def when_application_other(self, typ: TypeApplication) -> str:
return typ.name
def when_atomic(self, typ: AtomicType) -> str:
return typ.name
def when_constructor(self, typ: TypeConstructor) -> str:
return typ.name
def when_dynamic_array(self, da_arg: TypeExpr) -> str:
if da_arg == self.build.u8_type5:
return 'bytes'
return self(da_arg) + '[...]'
def when_function(self, fn_args: list[TypeExpr]) -> str:
return 'Callable[' + ', '.join(map(self, fn_args)) + ']'
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> str:
return f'{self(sa_typ)}[{sa_len}]'
def when_struct(self, typ: Record) -> str:
return typ.name
def when_tuple(self, tp_args: list[TypeExpr]) -> str:
return '(' + ', '.join(map(self, tp_args)) + ', )'
def when_variable(self, typ: TypeVariable) -> str:
return typ.name
class TypeAllocSize(BuildTypeRouter[int]):
"""
Router to generate a type's allocation size.
"""
__slots__ = ('is_member', )
is_member: bool
def __init__(self, build: BuildBase[Any], is_member: bool) -> None:
super().__init__(build)
self.is_member = is_member
def when_atomic(self, typ: AtomicType) -> int:
typ_info = self.build.type_info_map.get(typ.name)
if typ_info is None:
raise NotImplementedError(typ)
return typ_info.alloc_size
def when_dynamic_array(self, da_arg: TypeExpr) -> int:
if self.is_member:
return self.build.type_info_constructed.alloc_size
raise RuntimeError("Cannot know size of dynamic array at type level")
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> int:
if self.is_member:
return self.build.type_info_constructed.alloc_size
raise NotImplementedError
def when_struct(self, typ: Record) -> int:
if self.is_member:
return self.build.type_info_constructed.alloc_size
return sum(map(self.build.type5_alloc_size_member, (x[1] for x in typ.fields)))
def when_tuple(self, tp_args: list[TypeExpr]) -> int:
if self.is_member:
return self.build.type_info_constructed.alloc_size
return sum(map(self.build.type5_alloc_size_member, tp_args))

View File

@ -0,0 +1,46 @@
from typing import Any, Callable, Iterable, Mapping
from ..type5 import typeexpr as type5typeexpr
class TypeVariableRouter[G]:
__slots__ = ('data', 'variables',)
data: dict[
tuple[type5typeexpr.TypeExpr, ...],
Callable[[G, dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]], None],
]
variables: tuple[type5typeexpr.TypeVariable, ...]
def __init__(self, variables: Iterable[type5typeexpr.TypeVariable]) -> None:
self.data = {}
self.variables = tuple(variables)
assert len(self.variables) == len(set(self.variables))
def register(
self,
types_it: Iterable[type5typeexpr.TypeExpr],
implementation: Callable[[G, Any], None],
) -> None:
types = tuple(types_it)
assert all(
v.kind == t.kind and type5typeexpr.is_concrete(t)
for v, t in zip(self.variables, types, strict=True)
), (self.variables, types_it, )
self.data[types] = implementation
def __call__(
self,
lookup: Mapping[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr],
) -> Callable[[G, Any], None]:
types = tuple(lookup[v] for v in self.variables)
assert all(
v.kind == t.kind and type5typeexpr.is_concrete(t)
for v, t in zip(self.variables, types, strict=True)
), (self.variables, lookup, )
return self.data[types]

View File

@ -6,7 +6,7 @@ It's intented to be a "any color, as long as it's black" kind of renderer
from typing import Any, Generator
from . import ourlang
from .type3.types import Type3, TypeApplication_Struct
from .type5 import typeexpr as type5typeexpr
def phasm_render(inp: ourlang.Module[Any]) -> str:
@ -17,29 +17,27 @@ def phasm_render(inp: ourlang.Module[Any]) -> str:
Statements = Generator[str, None, None]
def type3(inp: Type3) -> str:
def type5(mod: ourlang.Module[Any], inp: type5typeexpr.TypeExpr) -> str:
"""
Render: type's name
"""
return inp.name
return mod.build.type5_name(inp)
def struct_definition(inp: ourlang.StructDefinition) -> str:
def struct_definition(mod: ourlang.Module[Any], inp: ourlang.StructDefinition) -> str:
"""
Render: TypeStruct's definition
"""
assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
result = f'class {inp.struct_type3.name}:\n'
for mem, typ in inp.struct_type3.application.arguments:
result += f' {mem}: {type3(typ)}\n'
result = f'class {inp.struct_type5.name}:\n'
for mem, typ in inp.struct_type5.fields:
result += f' {mem}: {type5(mod, typ)}\n'
return result
def constant_definition(inp: ourlang.ModuleConstantDef) -> str:
def constant_definition(mod: ourlang.Module[Any], inp: ourlang.ModuleConstantDef) -> str:
"""
Render: Module Constant's definition
"""
return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n'
return f'{inp.name}: {type5(mod, inp.type5)} = {expression(inp.constant)}\n'
def expression(inp: ourlang.Expression) -> str:
"""
@ -60,7 +58,7 @@ def expression(inp: ourlang.Expression) -> str:
) + ', )'
if isinstance(inp, ourlang.ConstantStruct):
return inp.struct_type3.name + '(' + ', '.join(
return inp.struct_type5.name + '(' + ', '.join(
expression(x)
for x in inp.value
) + ')'
@ -69,7 +67,7 @@ def expression(inp: ourlang.Expression) -> str:
return str(inp.variable.name)
if isinstance(inp, ourlang.BinaryOp):
return f'{expression(inp.left)} {inp.operator.name} {expression(inp.right)}'
return f'{expression(inp.left)} {inp.operator.function.name} {expression(inp.right)}'
if isinstance(inp, ourlang.FunctionCall):
args = ', '.join(
@ -77,10 +75,10 @@ def expression(inp: ourlang.Expression) -> str:
for arg in inp.arguments
)
if isinstance(inp.function, ourlang.StructConstructor):
return f'{inp.function.struct_type3.name}({args})'
if isinstance(inp.function_instance.function, ourlang.StructConstructor):
return f'{inp.function_instance.function.struct_type5.name}({args})'
return f'{inp.function.name}({args})'
return f'{inp.function_instance.function.name}({args})'
if isinstance(inp, ourlang.FunctionReference):
return str(inp.function.name)
@ -128,7 +126,7 @@ def statement(inp: ourlang.Statement) -> Statements:
raise NotImplementedError(statement, inp)
def function(inp: ourlang.Function) -> str:
def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str:
"""
Render: Function body
@ -141,12 +139,17 @@ def function(inp: ourlang.Function) -> str:
if inp.imported:
result += '@imported\n'
assert inp.type5 is not None
fn_args = mod.build.type5_is_function(inp.type5)
assert fn_args is not None
ret_type5 = fn_args.pop()
args = ', '.join(
f'{p.name}: {type3(p.type3)}'
for p in inp.posonlyargs
f'{arg_name}: {type5(mod, arg_type)}'
for arg_name, arg_type in zip(inp.arg_names, fn_args, strict=True)
)
result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n'
result += f'def {inp.name}({args}) -> {type5(mod, ret_type5)}:\n'
if inp.imported:
result += ' pass\n'
@ -167,20 +170,20 @@ def module(inp: ourlang.Module[Any]) -> str:
for struct in inp.struct_definitions.values():
if result:
result += '\n'
result += struct_definition(struct)
result += struct_definition(inp, struct)
for cdef in inp.constant_defs.values():
if result:
result += '\n'
result += constant_definition(cdef)
result += constant_definition(inp, cdef)
for func in inp.functions.values():
if func.lineno < 0:
# Builtin (-2) or auto generated (-1)
if isinstance(func, ourlang.StructConstructor):
# Auto generated
continue
if result:
result += '\n'
result += function(func)
result += function(inp, func)
return result

View File

@ -2,26 +2,17 @@
This module contains the code to convert parsed Ourlang into WebAssembly code
"""
import struct
from typing import List
from dataclasses import dataclass
from typing import Any, List, TypeGuard
from . import ourlang, wasm
from .build.base import TypeInfo
from .build.base import BuildBase, TypeInfo
from .build.typerouter import BuildTypeRouter
from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types
from .type3.functions import FunctionArgument, TypeVariable
from .type3.routers import NoRouteForTypeException
from .type3.typeclasses import Type3ClassMethod
from .type3.types import (
Type3,
TypeApplication_Struct,
TypeApplication_Type,
TypeApplication_TypeInt,
TypeApplication_TypeStar,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Tuple,
)
from .type5.constrainedexpr import ConstrainedExpr
from .type5.typeexpr import AtomicType, TypeApplication, TypeExpr, is_concrete
from .type5.unify import ActionList, ReplaceVariable, unify
from .wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
@ -31,6 +22,7 @@ from .wasm import (
from .wasmgenerator import Generator as WasmGenerator
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled'
TYPE5_ASSERTION_ERROR = 'You must call phasm_type5 after calling phasm_parse before your program can be compiled'
def phasm_compile(inp: ourlang.Module[WasmGenerator]) -> wasm.Module:
"""
@ -39,86 +31,92 @@ def phasm_compile(inp: ourlang.Module[WasmGenerator]) -> wasm.Module:
"""
return module(inp)
def type3(mod: ourlang.Module[WasmGenerator], inp: Type3) -> wasm.WasmType:
def type5(mod: ourlang.Module[WasmGenerator], inp: TypeExpr) -> wasm.WasmType:
"""
Compile: type
Types are used for example in WebAssembly function parameters
and return types.
"""
typ_info = mod.build.type_info_map.get(inp.name, )
typ_info = mod.build.type_info_map.get(inp.name)
if typ_info is None:
typ_info = mod.build.type_info_map['ptr']
typ_info = mod.build.type_info_constructed
return typ_info.wasm_type()
@dataclass
class TupleInstantiationResult:
args: list[TypeExpr]
alloc_size: int
header_value: int | None
class TupleInstantiationRouter(BuildTypeRouter[TupleInstantiationResult]):
__slots__ = ('el_count', )
el_count: int
def __init__(self, build: BuildBase[Any], el_count: int) -> None:
super().__init__(build)
self.el_count = el_count
def when_dynamic_array(self, da_arg: TypeExpr) -> TupleInstantiationResult:
return TupleInstantiationResult(
args=[da_arg for _ in range(self.el_count)],
alloc_size=5 + self.el_count * self.build.type5_alloc_size_member(da_arg),
header_value=self.el_count,
)
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> TupleInstantiationResult:
return TupleInstantiationResult(
args=[sa_typ for _ in range(sa_len)],
alloc_size=5 + sa_len * self.build.type5_alloc_size_member(sa_typ),
header_value=None,
)
def when_tuple(self, tp_args: list[TypeExpr]) -> TupleInstantiationResult:
return TupleInstantiationResult(
args=tp_args,
alloc_size=sum(
self.build.type5_alloc_size_member(x)
for x in tp_args
),
header_value=None,
)
def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.TupleInstantiation) -> None:
"""
Compile: Instantiation (allocation) of a tuple
"""
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
args: tuple[Type3, ...]
alloc_size_header = None
if isinstance(inp.type3.application, TypeApplication_Type):
# Possibly paranoid assert. If we have a future variadic type,
# does it also do this tuple instantation like this?
assert isinstance(inp.type3.application.constructor, TypeConstructor_DynamicArray)
sa_type, = inp.type3.application.arguments
args = tuple(sa_type for _ in inp.elements)
# Can't use calculate_alloc_size directly since that doesn't
# know the dynamic array's length
alloc_size = 4 + mod.build.calculate_alloc_size(sa_type, is_member=True) * len(inp.elements)
alloc_size_header = len(inp.elements)
elif isinstance(inp.type3.application, TypeApplication_TypeStar):
# Possibly paranoid assert. If we have a future variadic type,
# does it also do this tuple instantation like this?
assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple)
args = inp.type3.application.arguments
alloc_size = mod.build.calculate_alloc_size(inp.type3, is_member=False)
elif isinstance(inp.type3.application, TypeApplication_TypeInt):
# Possibly paranoid assert. If we have a future type of kind * -> Int -> *,
# does it also do this tuple instantation like this?
assert isinstance(inp.type3.application.constructor, TypeConstructor_StaticArray)
sa_type, sa_len = inp.type3.application.arguments
args = tuple(sa_type for _ in range(sa_len.value))
alloc_size = mod.build.calculate_alloc_size(inp.type3, is_member=False)
else:
raise NotImplementedError('tuple_instantiation', inp.type3)
result = TupleInstantiationRouter(mod.build, len(inp.elements))(inp.type5)
comment_elements = ''
for element in inp.elements:
assert element.type3 is not None, TYPE3_ASSERTION_ERROR
comment_elements += f'{element.type3.name}, '
assert _is_concrete(element.type5), TYPE5_ASSERTION_ERROR
comment_elements += f'{mod.build.type5_name(element.type5)}, '
tmp_var = wgn.temp_var_i32('tuple_adr')
wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})')
# Allocated the required amounts of bytes in memory
wgn.i32.const(alloc_size)
wgn.i32.const(result.alloc_size)
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
if alloc_size_header is not None:
if result.header_value is not None:
wgn.local.get(tmp_var)
wgn.i32.const(alloc_size_header)
wgn.i32.const(result.header_value )
wgn.i32.store()
# Store each element individually
offset = 0 if alloc_size_header is None else 4
for element, exp_type3 in zip(inp.elements, args, strict=True):
assert element.type3 == exp_type3
offset = 0 if result.header_value is None else 4
for element in inp.elements:
assert _is_concrete(element.type5), TYPE5_ASSERTION_ERROR
exp_type_info = mod.build.type_info_map.get(exp_type3.name)
exp_type_info = mod.build.type_info_map.get(element.type5.name)
if exp_type_info is None:
exp_type_info = mod.build.type_info_map['ptr']
exp_type_info = mod.build.type_info_constructed
wgn.add_statement('nop', comment='PRE')
wgn.local.get(tmp_var)
@ -126,7 +124,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator],
wgn.add_statement(exp_type_info.wasm_store_func, 'offset=' + str(offset))
wgn.add_statement('nop', comment='POST')
offset += mod.build.calculate_alloc_size(exp_type3, is_member=True)
offset += mod.build.type5_alloc_size_member(element.type5)
# Return the allocated address
wgn.local.get(tmp_var)
@ -134,29 +132,74 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator],
def expression_subscript_tuple(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Subscript) -> None:
assert isinstance(inp.index, ourlang.ConstantPrimitive)
assert isinstance(inp.index.value, int)
assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR
assert isinstance(inp.varref.type3.application, TypeApplication_TypeStar)
args = inp.varref.type3.application.arguments
assert _is_concrete(inp.varref.type5), TYPE5_ASSERTION_ERROR
args = mod.build.type5_is_tuple(inp.varref.type5)
assert args is not None
offset = 0
for el_type in args[0:inp.index.value]:
assert el_type is not None, TYPE3_ASSERTION_ERROR
el_type_info = mod.build.type_info_map.get(el_type.name)
if el_type_info is None:
el_type_info = mod.build.type_info_map['ptr']
offset += el_type_info.alloc_size
offset = sum(map(
mod.build.type5_alloc_size_member,
args[0:inp.index.value]
))
el_type = args[inp.index.value]
assert el_type is not None, TYPE3_ASSERTION_ERROR
expression(wgn, mod, inp.varref)
el_type_info = mod.build.type_info_map.get(el_type.name)
if el_type_info is None:
el_type_info = mod.build.type_info_map['ptr']
el_type_info = mod.build.type_info_constructed
expression(wgn, mod, inp.varref)
wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}')
def expression_binary_op(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.BinaryOp) -> None:
expression_function_call(wgn, mod, _binary_op_to_function(inp))
def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionCall) -> None:
for arg in inp.arguments:
expression(wgn, mod, arg)
if isinstance(inp.function_instance.function, ourlang.BuiltinFunction):
assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR
try:
method_type, method_router = mod.build.methods[inp.function_instance.function.name]
except KeyError:
method_type, method_router = mod.build.operators[inp.function_instance.function.name]
method_type = method_type.expr if isinstance(method_type, ConstrainedExpr) else method_type
instance_type = inp.function_instance.type5
actions = unify(method_type, instance_type)
assert isinstance(actions, ActionList)
tv_map = {}
for action in actions:
if isinstance(action, ReplaceVariable):
tv_map[action.var] = action.typ
continue
raise NotImplementedError
method_router(tv_map)(wgn, None)
return
if isinstance(inp.function_instance.function, ourlang.FunctionParam):
assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR
fn_args = mod.build.type5_is_function(inp.function_instance.type5)
assert fn_args is not None
params = [
type5(mod, x)
for x in fn_args
]
result = params.pop()
wgn.add_statement('local.get', '${}'.format(inp.function_instance.function.name))
wgn.call_indirect(params=params, result=result)
return
wgn.call(inp.function_instance.function.name)
def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Expression) -> None:
"""
Compile: Any expression
@ -166,9 +209,9 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
raise Exception
if isinstance(inp, ourlang.ConstantPrimitive):
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
type_info = mod.build.type_info_map[inp.type3.name]
type_info = mod.build.type_info_map[inp.type5.name]
if type_info.wasm_type is WasmTypeInt32:
assert isinstance(inp.value, int)
wgn.i32.const(inp.value)
@ -189,7 +232,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
wgn.f64.const(inp.value)
return
raise NotImplementedError(f'Constants with type {inp.type3:s}')
raise NotImplementedError(inp.type5)
if isinstance(inp, ourlang.ConstantBytes):
assert inp.data_block.address is not None, 'Value not allocated'
@ -202,9 +245,9 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
return
if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
if inp.type3.name not in mod.build.type_info_map:
if inp.type5.name not in mod.build.type_info_map:
assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, ))
address = inp.variable.constant.data_block.address
@ -218,79 +261,11 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
raise NotImplementedError(expression, inp.variable)
if isinstance(inp, ourlang.BinaryOp):
expression(wgn, mod, inp.left)
expression(wgn, mod, inp.right)
type_var_map: dict[TypeVariable, Type3] = {}
for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
if isinstance(type_var, Type3):
# Fixed type, not part of the lookup requirements
continue
if isinstance(type_var, TypeVariable):
type_var_map[type_var] = arg_expr.type3
continue
if isinstance(type_var, FunctionArgument):
# Fixed type, not part of the lookup requirements
continue
raise NotImplementedError(type_var, arg_expr.type3)
router = mod.build.type_class_instance_methods[inp.operator]
router(wgn, type_var_map)
expression_binary_op(wgn, mod, inp)
return
if isinstance(inp, ourlang.FunctionCall):
for arg in inp.arguments:
expression(wgn, mod, arg)
if isinstance(inp.function, Type3ClassMethod):
# FIXME: Duplicate code with BinaryOp
type_var_map = {}
for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
if isinstance(type_var, Type3):
# Fixed type, not part of the lookup requirements
continue
if isinstance(type_var, TypeVariable):
type_var_map[type_var] = arg_expr.type3
continue
if isinstance(type_var, FunctionArgument):
# Fixed type, not part of the lookup requirements
continue
raise NotImplementedError(type_var, arg_expr.type3)
router = mod.build.type_class_instance_methods[inp.function]
try:
router(wgn, type_var_map)
except NoRouteForTypeException:
raise NotImplementedError(str(inp.function), type_var_map)
return
if isinstance(inp.function, ourlang.FunctionParam):
assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function)
params = [
type3(mod, x)
for x in inp.function.type3.application.arguments
]
result = params.pop()
wgn.add_statement('local.get', '${}'.format(inp.function.name))
wgn.call_indirect(params=params, result=result)
return
wgn.call(inp.function.name)
expression_function_call(wgn, mod, inp)
return
if isinstance(inp, ourlang.FunctionReference):
@ -307,34 +282,37 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
return
if isinstance(inp, ourlang.Subscript):
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
assert _is_concrete(inp.varref.type5), TYPE5_ASSERTION_ERROR
if inp.varref.type3.application.constructor is mod.build.tuple_:
if mod.build.type5_is_tuple(inp.varref.type5):
expression_subscript_tuple(wgn, mod, inp)
return
inp_as_fc = ourlang.FunctionCall(mod.build.type_classes['Subscriptable'].operators['[]'])
raise NotImplementedError
inp_as_fc = ourlang.FunctionCall(mod.build.type_classes['Subscriptable'].operators['[]'], inp.sourceref)
inp_as_fc.type3 = inp.type3
inp_as_fc.type5 = inp.type5
inp_as_fc.arguments = [inp.varref, inp.index]
expression(wgn, mod, inp_as_fc)
return
if isinstance(inp, ourlang.AccessStructMember):
assert inp.struct_type3 is not None, TYPE3_ASSERTION_ERROR
assert _is_concrete(inp.varref.type5), TYPE3_ASSERTION_ERROR
assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
st_args = mod.build.type5_is_struct(inp.varref.type5)
assert st_args is not None
member_type = dict(inp.struct_type3.application.arguments)[inp.member]
member_type = dict(st_args)[inp.member]
member_type_info = mod.build.type_info_map.get(member_type.name)
if member_type_info is None:
member_type_info = mod.build.type_info_map['ptr']
member_type_info = mod.build.type_info_constructed
offset = _type5_struct_offset(mod.build, st_args, inp.member)
expression(wgn, mod, inp.varref)
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(mod.build.calculate_member_offset(
inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member
)))
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(offset))
return
raise NotImplementedError(expression, inp)
@ -346,11 +324,11 @@ def statement_return(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun
# Support tail calls
# https://github.com/WebAssembly/tail-call
# These help a lot with some functional programming techniques
if isinstance(inp.value, ourlang.FunctionCall) and inp.value.function is fun:
if isinstance(inp.value, ourlang.FunctionCall) and inp.value.function_instance.function is fun:
for arg in inp.value.arguments:
expression(wgn, mod, arg)
wgn.add_statement('return_call', '${}'.format(inp.value.function.name))
wgn.add_statement('return_call', '${}'.format(inp.value.function_instance.function.name))
return
expression(wgn, mod, inp.value)
@ -388,27 +366,26 @@ def statement(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourla
raise NotImplementedError(statement, inp)
def function_argument(mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionParam) -> wasm.Param:
"""
Compile: function argument
"""
return (inp.name, type3(mod, inp.type3), )
def import_(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Import:
"""
Compile: imported function
"""
assert inp.imported
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
fn_args = mod.build.type5_is_function(inp.type5)
assert fn_args is not None
fn_ret = fn_args.pop()
return wasm.Import(
inp.imported,
inp.name,
inp.name,
[
function_argument(mod, x)
for x in inp.posonlyargs
(arg_name, type5(mod, arg_type5), )
for arg_name, arg_type5 in zip(inp.arg_names, fn_args, strict=True)
],
type3(mod, inp.returns_type3)
type5(mod, fn_ret)
)
def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Function:
@ -417,6 +394,11 @@ def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.
"""
assert not inp.imported
assert _is_concrete(inp.type5), TYPE5_ASSERTION_ERROR
fn_args = mod.build.type5_is_function(inp.type5)
assert fn_args is not None
fn_ret = fn_args.pop()
wgn = WasmGenerator()
if isinstance(inp, ourlang.StructConstructor):
@ -429,14 +411,14 @@ def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.
inp.name,
inp.name if inp.exported else None,
[
function_argument(mod, x)
for x in inp.posonlyargs
(arg_name, type5(mod, arg_type5), )
for arg_name, arg_type5 in zip(inp.arg_names, fn_args, strict=True)
],
[
(k, v.wasm_type(), )
for k, v in wgn.locals.items()
],
type3(mod, inp.returns_type3),
type5(mod, fn_ret),
wgn.statements
)
@ -463,7 +445,7 @@ def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) ->
"""
unalloc_ptr = stdlib_alloc.UNALLOC_PTR
u32_type_info = mod.build.type_info_map['u32']
ptr_type_info = mod.build.type_info_map['ptr']
ptr_type_info = mod.build.type_info_constructed
allocated_data = b''
@ -473,7 +455,7 @@ def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) ->
data_list: List[bytes] = []
for constant in block.data:
assert constant.type3 is not None, TYPE3_ASSERTION_ERROR
assert _is_concrete(constant.type5), TYPE5_ASSERTION_ERROR
if isinstance(constant, ourlang.ConstantBytes):
data_list.append(module_data_primitive(u32_type_info, len(constant.value)))
@ -492,7 +474,7 @@ def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) ->
data_list.append(module_data_primitive(ptr_type_info, constant.data_block.address))
continue
type_info = mod.build.type_info_map[constant.type3.name]
type_info = mod.build.type_info_map[constant.type5.name]
data_list.append(module_data_primitive(type_info, constant.value))
block_data = b''.join(data_list)
@ -564,28 +546,66 @@ def module(inp: ourlang.Module[WasmGenerator]) -> wasm.Module:
return result
def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.StructConstructor) -> None:
assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
st_args = inp.struct_type3.application.arguments
st_args = mod.build.type5_is_struct(inp.struct_type5)
assert st_args is not None
tmp_var = wgn.temp_var_i32('struct_adr')
# Allocated the required amounts of bytes in memory
wgn.i32.const(mod.build.calculate_alloc_size(inp.struct_type3))
wgn.i32.const(mod.build.type5_alloc_size_root(inp.struct_type5))
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
# Store each member individually
for memname, mtyp3 in st_args:
mtyp3_info = mod.build.type_info_map.get(mtyp3.name)
if mtyp3_info is None:
mtyp3_info = mod.build.type_info_map['ptr']
offset = 0
for memname, mtyp5 in st_args:
mtyp5_info = mod.build.type_info_map.get(mtyp5.name)
if mtyp5_info is None:
mtyp5_info = mod.build.type_info_constructed
wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'${memname}')
wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(mod.build.calculate_member_offset(
inp.struct_type3.name, st_args, memname
)))
wgn.add_statement(mtyp5_info.wasm_store_func, 'offset=' + str(offset))
offset += mod.build.type5_alloc_size_member(mtyp5)
# Return the allocated address
wgn.local.get(tmp_var)
def _is_concrete(type5: TypeExpr | ConstrainedExpr | None) -> TypeGuard[TypeExpr]:
if type5 is None:
return False
if isinstance(type5, ConstrainedExpr):
type5 = type5.expr
return is_concrete(type5)
def _type5_struct_offset(
build: BuildBase[Any],
fields: tuple[tuple[str, AtomicType | TypeApplication], ...],
needle: str,
) -> int:
"""
Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name.
"""
result = 0
for memnam, memtyp in fields:
if needle == memnam:
return result
result += build.type5_alloc_size_member(memtyp)
raise RuntimeError('Member not found')
def _binary_op_to_function(inp: ourlang.BinaryOp) -> ourlang.FunctionCall:
"""
For compilation purposes, a binary operator is just a function call.
It's only syntactic sugar - e.g. `1 + 2` vs `+(1, 2)`
"""
assert inp.sourceref is not None # TODO: sourceref required
call = ourlang.FunctionCall(inp.operator, inp.sourceref)
call.arguments = [inp.left, inp.right]
return call

View File

@ -1,24 +1,46 @@
"""
Contains the syntax tree for ourlang
"""
from __future__ import annotations
from typing import Dict, Iterable, List, Optional, Union
from .build.base import BuildBase
from .type3.functions import FunctionSignature, TypeVariableContext
from .type3.typeclasses import Type3ClassMethod
from .type3.types import Type3, TypeApplication_Struct
from .type5 import constrainedexpr as type5constrainedexpr
from .type5 import record as type5record
from .type5 import typeexpr as type5typeexpr
class SourceRef:
__slots__ = ('filename', 'lineno', 'colno', )
filename: str | None
lineno: int | None
colno: int | None
def __init__(self, filename: str | None, lineno: int | None = None, colno: int | None = None) -> None:
self.filename = filename
self.lineno = lineno
self.colno = colno
def __repr__(self) -> str:
return f"SourceRef({self.filename!r}, {self.lineno!r}, {self.colno!r})"
def __str__(self) -> str:
return f"{self.filename}:{self.lineno:>4}:{self.colno:<3}"
class Expression:
"""
An expression within a statement
"""
__slots__ = ('type3', )
__slots__ = ('type5', 'sourceref', )
type3: Type3 | None
sourceref: SourceRef
type5: type5typeexpr.TypeExpr | None
def __init__(self) -> None:
self.type3 = None
def __init__(self, *, sourceref: SourceRef) -> None:
self.sourceref = sourceref
self.type5 = None
class Constant(Expression):
"""
@ -34,10 +56,10 @@ class ConstantPrimitive(Constant):
"""
__slots__ = ('value', )
value: Union[int, float]
value: int | float
def __init__(self, value: Union[int, float]) -> None:
super().__init__()
def __init__(self, value: int | float, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.value = value
def __repr__(self) -> str:
@ -53,8 +75,8 @@ class ConstantMemoryStored(Constant):
data_block: 'ModuleDataBlock'
def __init__(self, data_block: 'ModuleDataBlock') -> None:
super().__init__()
def __init__(self, data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.data_block = data_block
class ConstantBytes(ConstantMemoryStored):
@ -65,8 +87,8 @@ class ConstantBytes(ConstantMemoryStored):
value: bytes
def __init__(self, value: bytes, data_block: 'ModuleDataBlock') -> None:
super().__init__(data_block)
def __init__(self, value: bytes, data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
super().__init__(data_block, sourceref=sourceref)
self.value = value
def __repr__(self) -> str:
@ -83,8 +105,8 @@ class ConstantTuple(ConstantMemoryStored):
value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']]
def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']], data_block: 'ModuleDataBlock') -> None:
super().__init__(data_block)
def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']], data_block: 'ModuleDataBlock', sourceref: SourceRef) -> None:
super().__init__(data_block, sourceref=sourceref)
self.value = value
def __repr__(self) -> str:
@ -97,21 +119,27 @@ class ConstantStruct(ConstantMemoryStored):
"""
A Struct constant value expression within a statement
"""
__slots__ = ('struct_type3', 'value', )
__slots__ = ('struct_type5', 'value', )
struct_type3: Type3
struct_type5: type5record.Record
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']]
def __init__(self, struct_type3: Type3, value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']], data_block: 'ModuleDataBlock') -> None:
super().__init__(data_block)
self.struct_type3 = struct_type3
def __init__(
self,
struct_type5: type5record.Record,
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']],
data_block: 'ModuleDataBlock',
sourceref: SourceRef
) -> None:
super().__init__(data_block, sourceref=sourceref)
self.struct_type5 = struct_type5
self.value = value
def __repr__(self) -> str:
# Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data
# which it needs to compile the data into the program
return f'ConstantStruct({self.struct_type3!r}, {self.value!r}, @{self.data_block.address!r})'
return f'ConstantStruct({self.struct_type5!r}, {self.value!r}, @{self.data_block.address!r})'
class VariableReference(Expression):
"""
@ -121,8 +149,8 @@ class VariableReference(Expression):
variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam']) -> None:
super().__init__()
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam'], sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.variable = variable
class BinaryOp(Expression):
@ -131,12 +159,12 @@ class BinaryOp(Expression):
"""
__slots__ = ('operator', 'left', 'right', )
operator: Type3ClassMethod
operator: FunctionInstance
left: Expression
right: Expression
def __init__(self, operator: Type3ClassMethod, left: Expression, right: Expression) -> None:
super().__init__()
def __init__(self, operator: FunctionInstance, left: Expression, right: Expression, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.operator = operator
self.left = left
@ -145,19 +173,36 @@ class BinaryOp(Expression):
def __repr__(self) -> str:
return f'BinaryOp({repr(self.operator)}, {repr(self.left)}, {repr(self.right)})'
class FunctionInstance(Expression):
"""
When calling a polymorphic function with concrete arguments, we can generate
code for that specific instance of the function.
"""
__slots__ = ('function', )
function: Union['Function', 'FunctionParam']
def __init__(self, function: Union['Function', 'FunctionParam'], sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.function = function
class FunctionCall(Expression):
"""
A function call expression within a statement
"""
__slots__ = ('function', 'arguments', )
__slots__ = ('function_instance', 'arguments', )
function: Union['Function', 'FunctionParam', Type3ClassMethod]
function_instance: FunctionInstance
# TODO: FunctionInstance is wrong - we should have
# substitutions: dict[TypeVariable, TypeExpr]
# And it should have the same variables as the polytype (ConstrainedExpr) for function
arguments: List[Expression]
def __init__(self, function: Union['Function', 'FunctionParam', Type3ClassMethod]) -> None:
super().__init__()
def __init__(self, function_instance: FunctionInstance, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.function = function
self.function_instance = function_instance
self.arguments = []
class FunctionReference(Expression):
@ -168,8 +213,8 @@ class FunctionReference(Expression):
function: 'Function'
def __init__(self, function: 'Function') -> None:
super().__init__()
def __init__(self, function: 'Function', sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.function = function
class TupleInstantiation(Expression):
@ -180,8 +225,8 @@ class TupleInstantiation(Expression):
elements: List[Expression]
def __init__(self, elements: List[Expression]) -> None:
super().__init__()
def __init__(self, elements: List[Expression], sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.elements = elements
@ -195,8 +240,8 @@ class Subscript(Expression):
varref: VariableReference
index: Expression
def __init__(self, varref: VariableReference, index: Expression) -> None:
super().__init__()
def __init__(self, varref: VariableReference, index: Expression, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.varref = varref
self.index = index
@ -205,24 +250,27 @@ class AccessStructMember(Expression):
"""
Access a struct member for reading of writing
"""
__slots__ = ('varref', 'struct_type3', 'member', )
__slots__ = ('varref', 'member', )
varref: VariableReference
struct_type3: Type3
member: str
def __init__(self, varref: VariableReference, struct_type3: Type3, member: str) -> None:
super().__init__()
def __init__(self, varref: VariableReference, member: str, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.varref = varref
self.struct_type3 = struct_type3
self.member = member
class Statement:
"""
A statement within a function
"""
__slots__ = ()
__slots__ = ("sourceref", )
sourceref: SourceRef
def __init__(self, *, sourceref: SourceRef) -> None:
self.sourceref = sourceref
class StatementPass(Statement):
"""
@ -230,13 +278,18 @@ class StatementPass(Statement):
"""
__slots__ = ()
def __init__(self, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
class StatementReturn(Statement):
"""
A return statement within a function
"""
__slots__ = ('value', )
def __init__(self, value: Expression) -> None:
def __init__(self, value: Expression, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref)
self.value = value
def __repr__(self) -> str:
@ -261,55 +314,62 @@ class FunctionParam:
"""
A parameter for a Function
"""
__slots__ = ('name', 'type3', )
__slots__ = ('name', 'type5', )
name: str
type3: Type3
type5: type5typeexpr.TypeExpr
def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None:
assert type5typeexpr.is_concrete(type5)
def __init__(self, name: str, type3: Type3) -> None:
self.name = name
self.type3 = type3
self.type5 = type5
def __repr__(self) -> str:
return f'FunctionParam({self.name!r}, {self.type3!r})'
return f'FunctionParam({self.name!r}, {self.type5!r})'
class Function:
"""
A function processes input and produces output
"""
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'returns_type3', 'posonlyargs', )
__slots__ = ('name', 'sourceref', 'exported', 'imported', 'statements', 'type5', 'posonlyargs', 'arg_names', )
name: str
lineno: int
sourceref: SourceRef
exported: bool
imported: Optional[str]
statements: List[Statement]
signature: FunctionSignature
returns_type3: Type3
posonlyargs: List[FunctionParam]
type5: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr | None
posonlyargs: List[FunctionParam] # TODO: Replace me with arg names
arg_names: list[str]
def __init__(self, name: str, lineno: int, returns_type3: Type3) -> None:
def __init__(self, name: str, sourceref: SourceRef) -> None:
self.name = name
self.lineno = lineno
self.sourceref = sourceref
self.exported = False
self.imported = None
self.statements = []
self.signature = FunctionSignature(TypeVariableContext(), [])
self.returns_type3 = returns_type3
self.type5 = None
self.posonlyargs = []
self.arg_names = []
class BuiltinFunction(Function):
def __init__(self, name: str, type5: type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr) -> None:
super().__init__(name, SourceRef("/", 0, 0))
self.type5 = type5
class StructDefinition:
"""
The definition for a struct
"""
__slots__ = ('struct_type3', 'lineno', )
__slots__ = ('struct_type5', 'sourceref', )
struct_type3: Type3
lineno: int
struct_type5: type5record.Record
sourceref: SourceRef
def __init__(self, struct_type3: Type3, lineno: int) -> None:
self.struct_type3 = struct_type3
self.lineno = lineno
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
self.struct_type5 = struct_type5
self.sourceref = sourceref
class StructConstructor(Function):
"""
@ -318,38 +378,33 @@ class StructConstructor(Function):
A function will generated to instantiate a struct. The arguments
will be the defaults
"""
__slots__ = ('struct_type3', )
__slots__ = ('struct_type5', )
struct_type3: Type3
struct_type5: type5record.Record
def __init__(self, struct_type3: Type3) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', -1, struct_type3)
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
super().__init__(f'@{struct_type5.name}@__init___@', sourceref)
self.struct_type5 = struct_type5
assert isinstance(struct_type3.application, TypeApplication_Struct)
for mem, typ in struct_type3.application.arguments:
self.posonlyargs.append(FunctionParam(mem, typ, ))
self.signature.args.append(typ)
self.signature.args.append(struct_type3)
self.struct_type3 = struct_type3
for mem, typ in struct_type5.fields:
self.arg_names.append(mem)
self.posonlyargs.append(FunctionParam(mem, typ))
class ModuleConstantDef:
"""
A constant definition within a module
"""
__slots__ = ('name', 'lineno', 'type3', 'constant', )
__slots__ = ('name', 'sourceref', 'type5', 'constant', )
name: str
lineno: int
type3: Type3
sourceref: SourceRef
type5: type5typeexpr.TypeExpr
constant: Constant
def __init__(self, name: str, lineno: int, type3: Type3, constant: Constant) -> None:
def __init__(self, name: str, sourceref: SourceRef, type5: type5typeexpr.TypeExpr, constant: Constant) -> None:
self.name = name
self.lineno = lineno
self.type3 = type3
self.sourceref = sourceref
self.type5 = type5
self.constant = constant
class ModuleDataBlock:
@ -383,20 +438,22 @@ class Module[G]:
"""
A module is a file and consists of functions
"""
__slots__ = ('build', 'data', 'types', 'struct_definitions', 'constant_defs', 'functions', 'methods', 'operators', 'functions_table', )
__slots__ = ('build', 'filename', 'data', 'types', 'type5s', 'struct_definitions', 'constant_defs', 'functions', 'methods', 'operators', 'functions_table', )
build: BuildBase[G]
filename: str
data: ModuleData
types: dict[str, Type3]
types: dict[str, type5typeexpr.TypeExpr]
struct_definitions: Dict[str, StructDefinition]
constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function]
methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod]
methods: Dict[str, type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr]
operators: Dict[str, type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr]
functions_table: dict[Function, int]
def __init__(self, build: BuildBase[G]) -> None:
def __init__(self, build: BuildBase[G], filename: str) -> None:
self.build = build
self.filename = filename
self.data = ModuleData()
self.types = {}

View File

@ -10,6 +10,7 @@ from .exceptions import StaticError
from .ourlang import (
AccessStructMember,
BinaryOp,
BuiltinFunction,
ConstantBytes,
ConstantPrimitive,
ConstantStruct,
@ -17,11 +18,13 @@ from .ourlang import (
Expression,
Function,
FunctionCall,
FunctionInstance,
FunctionParam,
FunctionReference,
Module,
ModuleConstantDef,
ModuleDataBlock,
SourceRef,
Statement,
StatementIf,
StatementPass,
@ -32,8 +35,7 @@ from .ourlang import (
TupleInstantiation,
VariableReference,
)
from .type3.typeclasses import Type3ClassMethod
from .type3.types import IntType3, Type3
from .type5 import typeexpr as type5typeexpr
from .wasmgenerator import Generator
@ -96,10 +98,10 @@ class OurVisitor[G]:
self.build = build
def visit_Module(self, node: ast.Module) -> Module[G]:
module = Module(self.build)
module = Module(self.build, "-")
module.methods.update(self.build.methods)
module.operators.update(self.build.operators)
module.methods.update({k: v[0] for k, v in self.build.methods.items()})
module.operators.update({k: v[0] for k, v in self.build.operators.items()})
module.types.update(self.build.types)
_not_implemented(not node.type_ignores, 'Module.type_ignores')
@ -112,26 +114,27 @@ class OurVisitor[G]:
if isinstance(res, ModuleConstantDef):
if res.name in module.constant_defs:
raise StaticError(
f'{res.name} already defined on line {module.constant_defs[res.name].lineno}'
f'{res.name} already defined on line {module.constant_defs[res.name].sourceref.lineno}'
)
module.constant_defs[res.name] = res
if isinstance(res, StructDefinition):
if res.struct_type3.name in module.types:
if res.struct_type5.name in module.types:
raise StaticError(
f'{res.struct_type3.name} already defined as type'
f'{res.struct_type5.name} already defined as type'
)
module.types[res.struct_type3.name] = res.struct_type3
module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3)
module.types[res.struct_type5.name] = res.struct_type5
module.functions[res.struct_type5.name] = StructConstructor(res.struct_type5, res.sourceref)
module.functions[res.struct_type5.name].type5 = module.build.type5_make_function([x[1] for x in res.struct_type5.fields] + [res.struct_type5])
# Store that the definition was done in this module for the formatter
module.struct_definitions[res.struct_type3.name] = res
module.struct_definitions[res.struct_type5.name] = res
if isinstance(res, Function):
if res.name in module.functions:
raise StaticError(
f'{res.name} already defined on line {module.functions[res.name].lineno}'
f'{res.name} already defined on line {module.functions[res.name].sourceref.lineno}'
)
module.functions[res.name] = res
@ -156,23 +159,27 @@ class OurVisitor[G]:
raise NotImplementedError(f'{node} on Module')
def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function:
function = Function(node.name, node.lineno, self.build.none_)
function = Function(node.name, srf(module, node))
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
arg_type5_list = []
for arg in node.args.args:
if arg.annotation is None:
_raise_static_error(node, 'Must give a argument type')
arg_type = self.visit_type(module, arg.annotation)
arg_type5 = self.visit_type5(module, arg.annotation)
arg_type5_list.append(arg_type5)
# FIXME: Allow TypeVariable in the function signature
# This would also require FunctionParam to accept a placeholder
function.signature.args.append(arg_type)
function.arg_names.append(arg.arg)
function.posonlyargs.append(FunctionParam(
arg.arg,
arg_type,
arg_type5,
))
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
@ -215,9 +222,9 @@ class OurVisitor[G]:
if node.returns is None: # Note: `-> None` would be a ast.Constant
_raise_static_error(node, 'Must give a return type')
return_type = self.visit_type(module, node.returns)
function.signature.args.append(return_type)
function.returns_type3 = return_type
arg_type5_list.append(self.visit_type5(module, node.returns))
function.type5 = module.build.type5_make_function(arg_type5_list)
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
@ -229,7 +236,7 @@ class OurVisitor[G]:
_not_implemented(not node.keywords, 'ClassDef.keywords')
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
members: Dict[str, Type3] = {}
members: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {}
for stmt in node.body:
if not isinstance(stmt, ast.AnnAssign):
@ -247,9 +254,14 @@ class OurVisitor[G]:
if stmt.target.id in members:
_raise_static_error(stmt, 'Struct members must have unique names')
members[stmt.target.id] = self.visit_type(module, stmt.annotation)
field_type5 = self.visit_type5(module, stmt.annotation)
assert isinstance(field_type5, (type5typeexpr.AtomicType, type5typeexpr.TypeApplication, ))
members[stmt.target.id] = field_type5
return StructDefinition(module.build.struct(node.name, tuple(members.items())), node.lineno)
return StructDefinition(
module.build.type5_make_struct(node.name, tuple(members.items())),
srf(module, node),
)
def pre_visit_Module_AnnAssign(self, module: Module[G], node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name):
@ -262,8 +274,8 @@ class OurVisitor[G]:
return ModuleConstantDef(
node.target.id,
node.lineno,
self.visit_type(module, node.annotation),
srf(module, node),
self.visit_type5(module, node.annotation),
value_data,
)
@ -275,8 +287,8 @@ class OurVisitor[G]:
# Then return the constant as a pointer
return ModuleConstantDef(
node.target.id,
node.lineno,
self.visit_type(module, node.annotation),
srf(module, node),
self.visit_type5(module, node.annotation),
value_data,
)
@ -288,8 +300,8 @@ class OurVisitor[G]:
# Then return the constant as a pointer
return ModuleConstantDef(
node.target.id,
node.lineno,
self.visit_type(module, node.annotation),
srf(module, node),
self.visit_type5(module, node.annotation),
value_data,
)
@ -328,7 +340,8 @@ class OurVisitor[G]:
_raise_static_error(node, 'Return must have an argument')
return StatementReturn(
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value)
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.value),
srf(module, node),
)
if isinstance(node, ast.If):
@ -349,13 +362,13 @@ class OurVisitor[G]:
return result
if isinstance(node, ast.Pass):
return StatementPass()
return StatementPass(srf(module, node))
raise NotImplementedError(f'{node} as stmt in FunctionDef')
def visit_Module_FunctionDef_expr(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp):
operator: Union[str, Type3ClassMethod]
operator: str
if isinstance(node.op, ast.Add):
operator = '+'
@ -386,9 +399,10 @@ class OurVisitor[G]:
raise NotImplementedError(f'Operator {operator}')
return BinaryOp(
module.operators[operator],
FunctionInstance(BuiltinFunction(operator, module.operators[operator]), srf(module, node)),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
srf(module, node),
)
if isinstance(node, ast.Compare):
@ -414,9 +428,10 @@ class OurVisitor[G]:
raise NotImplementedError(f'Operator {operator}')
return BinaryOp(
module.operators[operator],
FunctionInstance(BuiltinFunction(operator, module.operators[operator]), srf(module, node)),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
srf(module, node),
)
if isinstance(node, ast.Call):
@ -443,15 +458,15 @@ class OurVisitor[G]:
if node.id in our_locals:
param = our_locals[node.id]
return VariableReference(param)
return VariableReference(param, srf(module, node))
if node.id in module.constant_defs:
cdef = module.constant_defs[node.id]
return VariableReference(cdef)
return VariableReference(cdef, srf(module, node))
if node.id in module.functions:
fun = module.functions[node.id]
return FunctionReference(fun)
return FunctionReference(fun, srf(module, node))
_raise_static_error(node, f'Undefined variable {node.id}')
@ -465,7 +480,7 @@ class OurVisitor[G]:
if len(arguments) != len(node.elts):
raise NotImplementedError('Non-constant tuple members')
return TupleInstantiation(arguments)
return TupleInstantiation(arguments, srf(module, node))
raise NotImplementedError(f'{node} as expr in FunctionDef')
@ -478,10 +493,10 @@ class OurVisitor[G]:
if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
func: Union[Function, FunctionParam, Type3ClassMethod]
func: Union[Function, FunctionParam]
if node.func.id in module.methods:
func = module.methods[node.func.id]
func = BuiltinFunction(node.func.id, module.methods[node.func.id])
elif node.func.id in our_locals:
func = our_locals[node.func.id]
else:
@ -490,7 +505,7 @@ class OurVisitor[G]:
func = module.functions[node.func.id]
result = FunctionCall(func)
result = FunctionCall(FunctionInstance(func, srf(module, node)), sourceref=srf(module, node))
result.arguments.extend(
self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr)
for arg_expr in node.args
@ -510,8 +525,8 @@ class OurVisitor[G]:
return AccessStructMember(
varref,
varref.variable.type3,
node.attr,
srf(module, node),
)
def visit_Module_FunctionDef_Subscript(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression:
@ -527,10 +542,10 @@ class OurVisitor[G]:
varref: VariableReference
if node.value.id in our_locals:
param = our_locals[node.value.id]
varref = VariableReference(param)
varref = VariableReference(param, srf(module, node))
elif node.value.id in module.constant_defs:
constant_def = module.constant_defs[node.value.id]
varref = VariableReference(constant_def)
varref = VariableReference(constant_def, srf(module, node))
else:
_raise_static_error(node, f'Undefined variable {node.value.id}')
@ -538,7 +553,7 @@ class OurVisitor[G]:
module, function, our_locals, node.slice,
)
return Subscript(varref, slice_expr)
return Subscript(varref, slice_expr, srf(module, node))
def visit_Module_Constant(self, module: Module[G], node: Union[ast.Constant, ast.Tuple, ast.Call]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct]:
if isinstance(node, ast.Tuple):
@ -555,7 +570,7 @@ class OurVisitor[G]:
data_block = ModuleDataBlock(tuple_data)
module.data.blocks.append(data_block)
return ConstantTuple(tuple_data, data_block)
return ConstantTuple(tuple_data, data_block, srf(module, node))
if isinstance(node, ast.Call):
# Struct constant
@ -583,29 +598,29 @@ class OurVisitor[G]:
data_block = ModuleDataBlock(struct_data)
module.data.blocks.append(data_block)
return ConstantStruct(struct_def.struct_type3, struct_data, data_block)
return ConstantStruct(struct_def.struct_type5, struct_data, data_block, srf(module, node))
_not_implemented(node.kind is None, 'Constant.kind')
if isinstance(node.value, (int, float, )):
return ConstantPrimitive(node.value)
return ConstantPrimitive(node.value, srf(module, node))
if isinstance(node.value, bytes):
data_block = ModuleDataBlock([])
module.data.blocks.append(data_block)
result = ConstantBytes(node.value, data_block)
result = ConstantBytes(node.value, data_block, srf(module, node))
data_block.data.append(result)
return result
raise NotImplementedError(f'{node.value} as constant')
def visit_type(self, module: Module[G], node: ast.expr) -> Type3:
def visit_type5(self, module: Module[G], node: ast.expr) -> type5typeexpr.TypeExpr:
if isinstance(node, ast.Constant):
if node.value is None:
return module.types['None']
return module.build.none_type5
_raise_static_error(node, f'Unrecognized type {node.value}')
_raise_static_error(node, f'Unrecognized type {node.value!r}')
if isinstance(node, ast.Name):
if not isinstance(node.ctx, ast.Load):
@ -627,9 +642,8 @@ class OurVisitor[G]:
else:
_raise_static_error(node, 'Must subscript using a list of types')
# Function type
return module.build.function(*[
self.visit_type(module, e)
return module.build.type5_make_function([
self.visit_type5(module, e)
for e in func_arg_types
])
@ -640,8 +654,8 @@ class OurVisitor[G]:
_raise_static_error(node, 'Must subscript using a constant index')
if node.slice.value is Ellipsis:
return module.build.dynamic_array(
self.visit_type(module, node.value),
return module.build.type5_make_dynamic_array(
self.visit_type5(module, node.value),
)
if not isinstance(node.slice.value, int):
@ -649,20 +663,20 @@ class OurVisitor[G]:
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
return module.build.static_array(
self.visit_type(module, node.value),
IntType3(node.slice.value),
return module.build.type5_make_static_array(
node.slice.value,
self.visit_type5(module, node.value),
)
if isinstance(node, ast.Tuple):
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
return module.build.tuple_(
*(self.visit_type(module, elt) for elt in node.elts)
return module.build.type5_make_tuple(
[self.visit_type5(module, elt) for elt in node.elts],
)
raise NotImplementedError(f'{node} as type')
raise NotImplementedError(node)
def _not_implemented(check: Any, msg: str) -> None:
if not check:
@ -672,3 +686,6 @@ def _raise_static_error(node: Union[ast.stmt, ast.expr], msg: str) -> NoReturn:
raise StaticError(
f'Static error on line {node.lineno}: {msg}'
)
def srf(mod: Module[Any], node: ast.stmt | ast.expr) -> SourceRef:
return SourceRef(mod.filename, node.lineno, node.col_offset)

View File

@ -1,704 +0,0 @@
"""
This module contains possible constraints generated based on the AST
These need to be resolved before the program can be compiled.
"""
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from .. import ourlang
from ..build.base import BuildBase
from ..wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
)
from .functions import FunctionArgument, TypeVariable
from .placeholders import PlaceholderForType, Type3OrPlaceholder
from .routers import NoRouteForTypeException, TypeApplicationRouter
from .typeclasses import Type3Class
from .types import (
IntType3,
Type3,
TypeApplication_Nullary,
TypeApplication_Struct,
TypeApplication_Type,
TypeApplication_TypeInt,
TypeApplication_TypeStar,
TypeConstructor_Base,
TypeConstructor_Struct,
)
class Error:
"""
An error returned by the check functions for a contraint
This means the programmer has to make some kind of chance to the
typing of their program before the compiler can do its thing.
"""
def __init__(self, msg: str, *, comment: Optional[str] = None) -> None:
self.msg = msg
self.comment = comment
def __repr__(self) -> str:
return f'Error({repr(self.msg)}, comment={repr(self.comment)})'
class RequireTypeSubstitutes:
"""
Returned by the check function for a contraint if they do not have all
their types substituted yet.
Hopefully, another constraint will give the right information about the
typing of the program, so this constraint can be updated.
"""
SubstitutionMap = Dict[PlaceholderForType, Type3]
NewConstraintList = List['ConstraintBase']
CheckResult = Union[None, SubstitutionMap, Error, NewConstraintList, RequireTypeSubstitutes]
HumanReadableRet = Tuple[str, Dict[str, Union[None, int, str, ourlang.Expression, Type3, PlaceholderForType]]]
class Context:
"""
Context for constraints
"""
__slots__ = ('build', )
build: BuildBase[Any]
def __init__(self, build: BuildBase[Any]) -> None:
self.build = build
class ConstraintBase:
"""
Base class for constraints
"""
__slots__ = ('context', 'comment', )
context: Context
"""
Additional information regarding the type environment
"""
comment: Optional[str]
"""
A comment to help the programmer with debugging the types in their program
"""
def __init__(self, context: Context, comment: Optional[str] = None) -> None:
self.context = context
self.comment = comment
def check(self) -> CheckResult:
"""
Checks if the constraint hold
This function can return an error, if the constraint does not hold,
which indicates an error in the typing of the input program.
This function can return RequireTypeSubstitutes(), if we cannot deduce
all the types yet.
This function can return a SubstitutionMap, if during the evaluation
of the contraint we discovered new types. In this case, the constraint
is expected to hold.
This function can return None, if the constraint holds, but no new
information was deduced from evaluating this constraint.
"""
raise NotImplementedError(self.__class__, self.check)
def human_readable(self) -> HumanReadableRet:
"""
Returns a more human readable form of this constraint
"""
return repr(self), {}
class SameTypeConstraint(ConstraintBase):
"""
Verifies that a number of types all are the same type
"""
__slots__ = ('type_list', )
type_list: List[Type3OrPlaceholder]
def __init__(self, context: Context, *type_list: Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(context=context, comment=comment)
assert len(type_list) > 1
self.type_list = [*type_list]
def check(self) -> CheckResult:
known_types: List[Type3] = []
phft_list = []
for typ in self.type_list:
if isinstance(typ, Type3):
known_types.append(typ)
continue
if isinstance(typ, PlaceholderForType):
if typ.resolve_as is not None:
known_types.append(typ.resolve_as)
else:
phft_list.append(typ)
continue
raise NotImplementedError(typ)
if not known_types:
return RequireTypeSubstitutes()
first_type = known_types[0]
for ktyp in known_types[1:]:
if ktyp != first_type:
return Error(f'{ktyp:s} must be {first_type:s} instead', comment=self.comment)
if not phft_list:
return None
for phft in phft_list:
phft.resolve_as = first_type
return {
typ: first_type
for typ in phft_list
}
def human_readable(self) -> HumanReadableRet:
return (
' == '.join('{t' + str(idx) + '}' for idx in range(len(self.type_list))),
{
't' + str(idx): typ
for idx, typ in enumerate(self.type_list)
},
)
def __repr__(self) -> str:
args = ', '.join(repr(x) for x in self.type_list)
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
class SameTypeArgumentConstraint(ConstraintBase):
__slots__ = ('tc_var', 'arg_var', )
tc_var: PlaceholderForType
arg_var: PlaceholderForType
def __init__(self, context: Context, tc_var: PlaceholderForType, arg_var: PlaceholderForType, *, comment: str) -> None:
super().__init__(context=context, comment=comment)
self.tc_var = tc_var
self.arg_var = arg_var
def check(self) -> CheckResult:
if self.tc_var.resolve_as is None:
return RequireTypeSubstitutes()
tc_typ = self.tc_var.resolve_as
arg_typ = self.arg_var.resolve_as
if isinstance(tc_typ.application, TypeApplication_Nullary):
return Error(f'{tc_typ:s} must be a constructed type instead')
if isinstance(tc_typ.application, TypeApplication_TypeStar):
# Sure, it's a constructed type. But it's like a struct,
# though without the way to implement type classes
# Presumably, doing a naked `foo :: t a -> a`
# doesn't work since you don't have any info on t
# So we can let the MustImplementTypeClassConstraint handle it.
return None
if isinstance(tc_typ.application, TypeApplication_Type):
return [SameTypeConstraint(
self.context,
tc_typ.application.arguments[0],
self.arg_var,
comment=self.comment,
)]
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(tc_typ.application, TypeApplication_TypeInt):
return [SameTypeConstraint(
self.context,
tc_typ.application.arguments[0],
self.arg_var,
comment=self.comment,
)]
raise NotImplementedError(tc_typ, arg_typ)
def human_readable(self) -> HumanReadableRet:
return (
'{tc_var}` == {arg_var}',
{
'tc_var': self.tc_var if self.tc_var.resolve_as is None else self.tc_var,
'arg_var': self.arg_var if self.arg_var.resolve_as is None else self.arg_var,
},
)
class SameFunctionArgumentConstraint(ConstraintBase):
__slots__ = ('type3', 'func_arg', 'type_var_map', )
type3: PlaceholderForType
func_arg: FunctionArgument
type_var_map: dict[TypeVariable, PlaceholderForType]
def __init__(self, context: Context, type3: PlaceholderForType, func_arg: FunctionArgument, type_var_map: dict[TypeVariable, PlaceholderForType], *, comment: str) -> None:
super().__init__(context=context, comment=comment)
self.type3 = type3
self.func_arg = func_arg
self.type_var_map = type_var_map
def check(self) -> CheckResult:
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
typ = self.type3.resolve_as
if isinstance(typ.application, TypeApplication_Nullary):
return Error(f'{typ:s} must be a function instead')
if not isinstance(typ.application, TypeApplication_TypeStar):
return Error(f'{typ:s} must be a function instead')
type_var_map = {
x: y.resolve_as
for x, y in self.type_var_map.items()
if y.resolve_as is not None
}
exp_type_arg_list = [
tv if isinstance(tv, Type3) else type_var_map[tv]
for tv in self.func_arg.args
if isinstance(tv, Type3) or tv in type_var_map
]
if len(exp_type_arg_list) != len(self.func_arg.args):
return RequireTypeSubstitutes()
return [
SameTypeConstraint(
self.context,
typ,
self.context.build.function(*exp_type_arg_list),
comment=self.comment,
)
]
def human_readable(self) -> HumanReadableRet:
return (
'{type3} == {func_arg}',
{
'type3': self.type3,
'func_arg': self.func_arg.name,
},
)
class TupleMatchConstraint(ConstraintBase):
__slots__ = ('exp_type', 'args', 'generate_router', )
exp_type: Type3OrPlaceholder
args: list[Type3OrPlaceholder]
generate_router: TypeApplicationRouter['TupleMatchConstraint', CheckResult]
def __init__(self, context: Context, exp_type: Type3OrPlaceholder, args: Iterable[Type3OrPlaceholder], comment: str):
super().__init__(context=context, comment=comment)
self.exp_type = exp_type
self.args = list(args)
self.generate_router = TypeApplicationRouter()
self.generate_router.add(context.build.dynamic_array, self.__class__._generate_dynamic_array)
self.generate_router.add(context.build.static_array, self.__class__._generate_static_array)
self.generate_router.add(context.build.tuple_, self.__class__._generate_tuple)
def _generate_dynamic_array(self, sa_args: tuple[Type3]) -> CheckResult:
sa_type, = sa_args
return [
SameTypeConstraint(self.context, arg, sa_type)
for arg in self.args
]
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
sa_type, sa_len = sa_args
if sa_len.value != len(self.args):
return Error('Mismatch between applied types argument count', comment=self.comment)
return [
SameTypeConstraint(self.context, arg, sa_type)
for arg in self.args
]
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
if len(tp_args) != len(self.args):
return Error('Mismatch between applied types argument count', comment=self.comment)
return [
SameTypeConstraint(self.context, arg, oth_arg)
for arg, oth_arg in zip(self.args, tp_args, strict=True)
]
def check(self) -> CheckResult:
exp_type = self.exp_type
if isinstance(exp_type, PlaceholderForType):
if exp_type.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = exp_type.resolve_as
try:
return self.generate_router(self, exp_type)
except NoRouteForTypeException:
raise NotImplementedError(exp_type)
class MustImplementTypeClassConstraint(ConstraintBase):
"""
A type must implement a given type class
"""
__slots__ = ('type_class3', 'types', )
type_class3: Type3Class
types: list[Type3OrPlaceholder]
def __init__(self, context: Context, type_class3: Type3Class, typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None:
super().__init__(context=context, comment=comment)
self.type_class3 = type_class3
self.types = typ_list
def check(self) -> CheckResult:
typ_list: list[Type3 | TypeConstructor_Base[Any] | TypeConstructor_Struct] = []
for typ in self.types:
if isinstance(typ, PlaceholderForType) and typ.resolve_as is not None:
typ = typ.resolve_as
if isinstance(typ, PlaceholderForType):
return RequireTypeSubstitutes()
if isinstance(typ.application, (TypeApplication_Nullary, TypeApplication_Struct, )):
typ_list.append(typ)
continue
if isinstance(typ.application, (TypeApplication_Type, TypeApplication_TypeInt, TypeApplication_TypeStar)):
typ_list.append(typ.application.constructor)
continue
raise NotImplementedError(typ, typ.application)
assert len(typ_list) == len(self.types)
key = (self.type_class3, tuple(typ_list), )
if key in self.context.build.type_class_instances:
return None
typ_cls_name = self.type_class3 if isinstance(self.type_class3, str) else self.type_class3.name
typ_name_list = ' '.join(x.name for x in typ_list)
return Error(f'Missing type class instantation: {typ_cls_name} {typ_name_list}')
def human_readable(self) -> HumanReadableRet:
keys = {
f'type{idx}': typ
for idx, typ in enumerate(self.types)
}
return (
'Exists instance {type_class3} ' + ' '.join(f'{{{x}}}' for x in keys),
{
'type_class3': str(self.type_class3),
**keys,
},
)
def __repr__(self) -> str:
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.types)}, comment={repr(self.comment)})'
class LiteralFitsConstraint(ConstraintBase):
"""
A literal value fits a given type
"""
__slots__ = ('type3', 'literal', 'generate_router', )
type3: Type3OrPlaceholder
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct]
generate_router: TypeApplicationRouter['LiteralFitsConstraint', CheckResult]
def __init__(
self,
context: Context,
type3: Type3OrPlaceholder,
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct],
comment: Optional[str] = None,
) -> None:
super().__init__(context=context, comment=comment)
self.type3 = type3
self.literal = literal
self.generate_router = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]()
self.generate_router.add(context.build.dynamic_array, self.__class__._generate_dynamic_array)
self.generate_router.add(context.build.static_array, self.__class__._generate_static_array)
self.generate_router.add(context.build.struct, self.__class__._generate_struct)
self.generate_router.add(context.build.tuple_, self.__class__._generate_tuple)
def _generate_dynamic_array(self, da_args: tuple[Type3]) -> CheckResult:
da_type, = da_args
if da_type.name == 'u8':
if not isinstance(self.literal, ourlang.ConstantBytes):
return Error('Must be bytes', comment=self.comment)
return None
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(self.context, da_type, y)
for y in self.literal.value
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(self.context, da_type, PlaceholderForType([y]))
for y in self.literal.value
)
return res
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
sa_type, sa_len = sa_args
if sa_len.value != len(self.literal.value):
return Error('Member count mismatch', comment=self.comment)
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(self.context, sa_type, y)
for y in self.literal.value
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(self.context, sa_type, PlaceholderForType([y]))
for y in self.literal.value
)
return res
def _generate_struct(self, st_args: tuple[tuple[str, Type3], ...]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantStruct):
return Error('Must be struct')
if len(st_args) != len(self.literal.value):
return Error('Struct element count mismatch')
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(self.context, x, y)
for (_, x), y in zip(st_args, self.literal.value, strict=True)
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(self.context, x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_type3.name}.{x_n}')
for (x_n, x_t, ), y in zip(st_args, self.literal.value, strict=True)
)
res.append(SameTypeConstraint(
self.context,
self.literal.struct_type3,
self.type3,
comment='Struct types must match',
))
return res
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
if len(tp_args) != len(self.literal.value):
return Error('Tuple element count mismatch', comment=self.comment)
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(self.context, x, y)
for x, y in zip(tp_args, self.literal.value, strict=True)
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(self.context, x, PlaceholderForType([y]))
for x, y in zip(tp_args, self.literal.value, strict=True)
)
return res
def check(self) -> CheckResult:
if isinstance(self.type3, PlaceholderForType):
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
self.type3 = self.type3.resolve_as
type_info = self.context.build.type_info_map.get(self.type3.name)
if type_info is not None and (type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64):
assert type_info.signed is not None
if isinstance(self.literal.value, int):
try:
self.literal.value.to_bytes(type_info.alloc_size, 'big', signed=type_info.signed)
except OverflowError:
return Error(f'Must fit in {type_info.alloc_size} byte(s)', comment=self.comment) # FIXME: Add line information
return None
return Error('Must be integer', comment=self.comment) # FIXME: Add line information
if type_info is not None and (type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64):
if isinstance(self.literal.value, float):
# FIXME: Bit check
return None
return Error('Must be real', comment=self.comment) # FIXME: Add line information
exp_type = self.type3
try:
return self.generate_router(self, exp_type)
except NoRouteForTypeException:
raise NotImplementedError(exp_type)
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)}, comment={repr(self.comment)})'
class CanBeSubscriptedConstraint(ConstraintBase):
"""
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
"""
__slots__ = ('ret_type3', 'type3', 'index_type3', 'index_const', 'generate_router', )
ret_type3: PlaceholderForType
type3: PlaceholderForType
index_type3: PlaceholderForType
index_const: int | None
generate_router: TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]
def __init__(
self,
context: Context,
ret_type3: PlaceholderForType,
type3: PlaceholderForType,
index_type3: PlaceholderForType,
index_const: int | None,
comment: Optional[str] = None,
) -> None:
super().__init__(context=context, comment=comment)
self.ret_type3 = ret_type3
self.type3 = type3
self.index_type3 = index_type3
self.index_const = index_const
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
# We special case tuples to allow for ease of use to the programmer
# e.g. rather than having to do `fst a` and `snd a` and only have tuples of size 2
# we use a[0] and a[1] and allow for a[2] and on.
if self.index_const is None:
return Error('Must index with integer literal')
if self.index_const < 0 or len(tp_args) <= self.index_const:
return Error('Tuple index out of range')
return [
SameTypeConstraint(self.context, self.context.build.types['u32'], self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(self.context, tp_args[self.index_const], self.ret_type3, comment=f'Tuple subscript index {self.index_const}'),
]
def check(self) -> CheckResult:
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = self.type3.resolve_as
if exp_type.application.constructor == self.context.build.tuple_:
return self._generate_tuple(exp_type.application.arguments)
result: NewConstraintList = []
result.extend([
MustImplementTypeClassConstraint(
self.context,
self.context.build.type_classes['Subscriptable'],
[exp_type],
),
SameTypeConstraint(
self.context,
self.context.build.types['u32'],
self.index_type3,
),
])
if isinstance(exp_type.application, (TypeApplication_Type, TypeApplication_TypeInt, )):
result.extend([
SameTypeConstraint(
self.context,
exp_type.application.arguments[0],
self.ret_type3,
),
])
# else: The MustImplementTypeClassConstraint will catch this
if exp_type.application.constructor == self.context.build.static_array:
_, sa_len = exp_type.application.arguments
if self.index_const is not None and (self.index_const < 0 or sa_len.value <= self.index_const):
return Error('Tuple index out of range')
return result
def human_readable(self) -> HumanReadableRet:
return (
'{type3}[{index}]',
{
'type3': self.type3,
'index': self.index_type3 if self.index_const is None else self.index_const,
},
)
def __repr__(self) -> str:
return f'CanBeSubscriptedConstraint({self.ret_type3!r}, {self.type3!r}, {self.index_type3!r}, {self.index_const!r}, comment={repr(self.comment)})'

View File

@ -1,334 +0,0 @@
"""
This module generates the typing constraints for Phasm.
The constraints solver can then try to resolve all constraints.
"""
from typing import Any, Generator, List
from .. import ourlang
from .constraints import (
CanBeSubscriptedConstraint,
ConstraintBase,
Context,
LiteralFitsConstraint,
MustImplementTypeClassConstraint,
SameFunctionArgumentConstraint,
SameTypeArgumentConstraint,
SameTypeConstraint,
TupleMatchConstraint,
)
from .functions import (
Constraint_TypeClassInstanceExists,
FunctionArgument,
FunctionSignature,
TypeVariable,
TypeVariableApplication_Unary,
TypeVariableContext,
)
from .placeholders import PlaceholderForType
from .types import Type3, TypeApplication_Struct, TypeConstructor_Function
ConstraintGenerator = Generator[ConstraintBase, None, None]
def phasm_type3_generate_constraints(inp: ourlang.Module[Any]) -> List[ConstraintBase]:
ctx = Context(inp.build)
return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant, phft: PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
yield LiteralFitsConstraint(
ctx, phft, inp,
comment='The given literal must fit the expected type'
)
return
raise NotImplementedError(constant, inp)
def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderForType) -> ConstraintGenerator:
return _expression_function_call(
ctx,
f'({inp.operator.name})',
inp.operator.signature,
[inp.left, inp.right],
inp,
phft,
)
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp.function, ourlang.FunctionParam):
assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function)
signature = FunctionSignature(
TypeVariableContext(),
inp.function.type3.application.arguments,
)
else:
signature = inp.function.signature
return _expression_function_call(
ctx,
inp.function.name,
signature,
inp.arguments,
inp,
phft,
)
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: PlaceholderForType) -> ConstraintGenerator:
yield SameTypeConstraint(
ctx,
ctx.build.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3),
phft,
comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})',
)
def _expression_function_call(
ctx: Context,
func_name: str,
signature: FunctionSignature,
arguments: list[ourlang.Expression],
return_expr: ourlang.Expression,
return_phft: PlaceholderForType,
) -> ConstraintGenerator:
"""
Generates all type-level constraints for a function call.
A Binary operator functions pretty much the same as a function call
with two arguments - it's only a syntactic difference.
"""
# First create placeholders for all arguments, and generate their constraints
arg_placeholders = {
arg_expr: PlaceholderForType([arg_expr])
for arg_expr in arguments
}
arg_placeholders[return_expr] = return_phft
for call_arg in arguments:
yield from expression(ctx, call_arg, arg_placeholders[call_arg])
# Then generate placeholders the function signature
# and apply constraints that the function requires
# Skip any fully reference types
# Making this a map ensures that if a function signature has
# the same type on multiple arguments, we only get one
# placeholder here. These don't need to update anything once
# subsituted - that's done by arg_placeholders.
type_var_map = {
x: PlaceholderForType([])
for x in signature.args
if isinstance(x, TypeVariable)
}
for constraint in signature.context.constraints:
if isinstance(constraint, Constraint_TypeClassInstanceExists):
yield MustImplementTypeClassConstraint(
ctx,
constraint.type_class3,
[type_var_map[x] for x in constraint.types],
)
continue
raise NotImplementedError(constraint)
func_var_map = {
x: PlaceholderForType([])
for x in signature.args
if isinstance(x, FunctionArgument)
}
# If some of the function arguments are functions,
# we need to deal with those separately.
for sig_arg in signature.args:
if not isinstance(sig_arg, FunctionArgument):
continue
# Ensure that for all type variables in the function
# there are also type variables available
for func_arg in sig_arg.args:
if isinstance(func_arg, Type3):
continue
type_var_map.setdefault(func_arg, PlaceholderForType([]))
yield SameFunctionArgumentConstraint(
ctx,
func_var_map[sig_arg],
sig_arg,
type_var_map,
comment=f'Ensure `{sig_arg.name}` matches in {signature}',
)
# If some of the function arguments are type constructors,
# we need to deal with those separately.
# That is, given `foo :: t a -> a` we need to ensure
# that both a's are the same.
for sig_arg in signature.args:
if isinstance(sig_arg, Type3):
# Not a type variable at all
continue
if isinstance(sig_arg, FunctionArgument):
continue
if sig_arg.application.constructor is None:
# Not a type variable for a type constructor
continue
if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
raise NotImplementedError(sig_arg.application)
if sig_arg.application.arguments not in type_var_map:
# e.g., len :: t a -> u32
# i.e. "a" does not matter at all
continue
yield SameTypeArgumentConstraint(
ctx,
type_var_map[sig_arg],
type_var_map[sig_arg.application.arguments],
comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}',
)
# Lastly, tie the signature and expression together
for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [return_expr], strict=True)):
if arg_no == len(arguments):
comment = f'The type of a function call to {func_name} is the same as the type that the function returns'
else:
comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument'
if isinstance(sig_part, TypeVariable):
yield SameTypeConstraint(ctx, type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
continue
if isinstance(sig_part, Type3):
yield SameTypeConstraint(ctx, sig_part, arg_placeholders[arg_expr], comment=comment)
continue
if isinstance(sig_part, FunctionArgument):
yield SameTypeConstraint(ctx, func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
continue
raise NotImplementedError(sig_part)
return
def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, ourlang.Constant):
yield from constant(ctx, inp, phft)
return
if isinstance(inp, ourlang.VariableReference):
yield SameTypeConstraint(ctx, inp.variable.type3, phft,
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
return
if isinstance(inp, ourlang.BinaryOp):
yield from expression_binary_op(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionCall):
yield from expression_function_call(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionReference):
yield from expression_function_reference(ctx, inp, phft)
return
if isinstance(inp, ourlang.TupleInstantiation):
r_type = []
for arg in inp.elements:
arg_phft = PlaceholderForType([arg])
yield from expression(ctx, arg, arg_phft)
r_type.append(arg_phft)
yield TupleMatchConstraint(
ctx,
phft,
r_type,
comment='The type of a tuple is a combination of its members'
)
return
if isinstance(inp, ourlang.Subscript):
varref_phft = PlaceholderForType([inp.varref])
index_phft = PlaceholderForType([inp.index])
yield from expression(ctx, inp.varref, varref_phft)
yield from expression(ctx, inp.index, index_phft)
if isinstance(inp.index, ourlang.ConstantPrimitive) and isinstance(inp.index.value, int):
yield CanBeSubscriptedConstraint(ctx, phft, varref_phft, index_phft, inp.index.value)
else:
yield CanBeSubscriptedConstraint(ctx, phft, varref_phft, index_phft, None)
return
if isinstance(inp, ourlang.AccessStructMember):
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) # FIXME: See test_struct.py::test_struct_not_accessible
mem_typ = dict(inp.struct_type3.application.arguments)[inp.member]
yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO
yield SameTypeConstraint(ctx, mem_typ, phft,
comment=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 statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
phft = PlaceholderForType([inp.value])
yield from expression(ctx, inp.value, phft)
yield SameTypeConstraint(ctx, fun.returns_type3, phft,
comment=f'The type of the value returned from function {fun.name} should match its return type')
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
test_phft = PlaceholderForType([inp.test])
yield from expression(ctx, inp.test, test_phft)
yield SameTypeConstraint(ctx, test_phft, ctx.build.types['bool'],
comment='Must pass a boolean expression to if')
for stmt in inp.statements:
yield from statement(ctx, fun, stmt)
for stmt in inp.else_statements:
yield from statement(ctx, fun, stmt)
def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> ConstraintGenerator:
if isinstance(inp, ourlang.StatementReturn):
yield from statement_return(ctx, fun, inp)
return
if isinstance(inp, ourlang.StatementIf):
yield from statement_if(ctx, fun, inp)
return
raise NotImplementedError(statement, fun, inp)
def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
assert not inp.imported
if isinstance(inp, ourlang.StructConstructor):
return
for stmt in inp.statements:
yield from statement(ctx, inp, stmt)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
phft = PlaceholderForType([inp.constant])
yield from constant(ctx, inp.constant, phft)
yield SameTypeConstraint(ctx, inp.type3, phft,
comment=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[Any]) -> ConstraintGenerator:
for cdef in inp.constant_defs.values():
yield from module_constant_def(ctx, cdef)
for func in inp.functions.values():
if func.imported:
continue
yield from function(ctx, func)

View File

@ -1,179 +0,0 @@
"""
Entry point to the type3 system
"""
from typing import Any, Dict, List
from .. import codestyle, ourlang
from .constraints import (
ConstraintBase,
Error,
HumanReadableRet,
RequireTypeSubstitutes,
SubstitutionMap,
)
from .constraintsgenerator import phasm_type3_generate_constraints
from .placeholders import (
PlaceholderForType,
Type3OrPlaceholder,
)
from .types import Type3
MAX_RESTACK_COUNT = 100
class Type3Exception(BaseException):
"""
Thrown when the Type3 system detects constraints that do not hold
"""
class DeducedType:
__slots__ = ('phft', 'typ', 'comment')
phft: PlaceholderForType
typ: Type3
comment: str
def __init__(self, phft: PlaceholderForType, typ: Type3) -> None:
self.phft = phft
self.typ = typ
self.comment = 'Deduced type'
def human_readable(self) -> HumanReadableRet:
return (
'{phft} == {typ}',
{
'phft': self.phft,
'typ': self.typ,
}
)
def phasm_type3(inp: ourlang.Module[Any], 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] = {}
error_list: List[Error] = []
for _ in range(MAX_RESTACK_COUNT):
if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
old_constraint_ids = {id(x) for x in constraint_list}
old_placeholder_substitutes_len = len(placeholder_substitutes)
back_on_todo_list_count = 0
new_constraint_list = []
for constraint in constraint_list:
check_result = constraint.check()
if check_result is None:
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Constraint checks out')
continue
if isinstance(check_result, dict):
placeholder_substitutes.update(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Constraint checks out, and gave us new information')
continue
if isinstance(check_result, Error):
error_list.append(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Got an error')
continue
if isinstance(check_result, RequireTypeSubstitutes):
new_constraint_list.append(constraint)
back_on_todo_list_count += 1
continue
if isinstance(check_result, list):
new_constraint_list.extend(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print(f'-> Resulted in {len(check_result)} new constraints')
continue
raise NotImplementedError(constraint, check_result)
if verbose and 0 < back_on_todo_list_count:
print(f'{back_on_todo_list_count} constraints skipped for now')
if not new_constraint_list:
constraint_list = new_constraint_list
break
# Infinite loop detection
new_constraint_ids = {id(x) for x in new_constraint_list}
new_placeholder_substitutes_len = len(placeholder_substitutes)
if old_constraint_ids == new_constraint_ids and old_placeholder_substitutes_len == new_placeholder_substitutes_len:
if error_list:
raise Type3Exception(error_list)
raise Exception('Cannot type this program - not enough information')
constraint_list = new_constraint_list
if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
if constraint_list:
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')
if error_list:
raise Type3Exception(error_list)
for plh, typ in placeholder_substitutes.items():
for expr in plh.update_on_substitution:
expr.type3 = typ
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase | DeducedType) -> 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) or isinstance(fmt_val, PlaceholderForType):
fmt_val = get_printable_type_name(fmt_val, placeholder_id_map)
if not isinstance(fmt_val, str):
fmt_val = repr(fmt_val)
act_fmt[fmt_key] = fmt_val
if constraint.comment is not None:
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + constraint.comment)
else:
print('- ' + txt.format(**act_fmt))
def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[int, str]) -> str:
if isinstance(inp, Type3):
return inp.name
if isinstance(inp, PlaceholderForType):
placeholder_id = id(inp)
if placeholder_id not in placeholder_id_map:
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
return placeholder_id_map[placeholder_id]
raise NotImplementedError(inp)
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, DeducedType(psk, psv))
for constraint in constraint_list:
print_constraint(placeholder_id_map, constraint)
print('=== ^ type3 constraint_list ^ === ')

View File

@ -1,194 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Hashable, Iterable, List
if TYPE_CHECKING:
from .typeclasses import Type3Class
from .types import Type3
class TypeVariableApplication_Base[T: Hashable, S: Hashable]:
"""
Records the constructor and arguments used to create this type.
Nullary types, or types of kind *, have both arguments set to None.
"""
constructor: T
arguments: S
def __init__(self, constructor: T, arguments: S) -> None:
self.constructor = constructor
self.arguments = arguments
def __hash__(self) -> int:
return hash((self.constructor, self.arguments, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariableApplication_Base):
raise NotImplementedError
return (self.constructor == other.constructor # type: ignore[no-any-return]
and self.arguments == other.arguments)
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
class TypeVariable:
"""
Types variable are used in function definition.
They are used in places where you don't know the exact type.
They are different from PlaceholderForType, as those are instanced
during type checking. These type variables are used solely in the
function's definition
"""
__slots__ = ('name', 'application', )
name: str
application: TypeVariableApplication_Base[Any, Any]
def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None:
self.name = name
self.application = application
def __hash__(self) -> int:
return hash((self.name, self.application, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariable):
raise NotImplementedError
return (self.name == other.name
and self.application == other.application)
def __repr__(self) -> str:
return f'TypeVariable({repr(self.name)})'
class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]):
"""
For the type for this function argument it's not relevant if it was constructed.
"""
def make_typevar(name: str) -> TypeVariable:
"""
Helper function to make a type variable for a non-constructed type.
"""
return TypeVariable(name, TypeVariableApplication_Nullary(None, None))
class TypeConstructorVariable:
"""
Types constructor variable are used in function definition.
They are a lot like TypeVariable, except that they represent a
type constructor rather than a type directly.
For now, we only have type constructor variables for kind
* -> *.
"""
__slots__ = ('name', )
name: str
def __init__(self, name: str) -> None:
self.name = name
def __hash__(self) -> int:
return hash((self.name, ))
def __eq__(self, other: Any) -> bool:
if other is None:
return False
if not isinstance(other, TypeConstructorVariable):
raise NotImplementedError(other)
return (self.name == other.name)
def __call__(self, tvar: TypeVariable) -> 'TypeVariable':
return TypeVariable(
self.name + ' ' + tvar.name,
TypeVariableApplication_Unary(self, tvar)
)
def __repr__(self) -> str:
return f'TypeConstructorVariable({self.name!r})'
class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]):
"""
The type for this function argument should be constructed from a type constructor.
And we need to know what construtor that was, since that's the one we support.
"""
class ConstraintBase:
__slots__ = ()
class Constraint_TypeClassInstanceExists(ConstraintBase):
__slots__ = ('type_class3', 'types', )
type_class3: 'Type3Class'
types: list[TypeVariable]
def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None:
self.type_class3 = type_class3
self.types = list(types)
# Sanity check. AFAIK, if you have a multi-parameter type class,
# you can only add a constraint by supplying types for all variables
assert len(self.type_class3.args) == len(self.types)
def __str__(self) -> str:
return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types)
def __repr__(self) -> str:
return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})'
class TypeVariableContext:
__slots__ = ('constraints', )
constraints: list[ConstraintBase]
def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
self.constraints = list(constraints)
def __copy__(self) -> 'TypeVariableContext':
return TypeVariableContext(self.constraints)
def __str__(self) -> str:
if not self.constraints:
return ''
return '(' + ', '.join(str(x) for x in self.constraints) + ') => '
def __repr__(self) -> str:
return f'TypeVariableContext({self.constraints!r})'
class FunctionArgument:
__slots__ = ('args', 'name', )
args: list[Type3 | TypeVariable]
name: str
def __init__(self, args: list[Type3 | TypeVariable]) -> None:
self.args = args
self.name = '(' + ' -> '.join(x.name for x in args) + ')'
class FunctionSignature:
__slots__ = ('context', 'args', )
context: TypeVariableContext
args: List[Type3 | TypeVariable | FunctionArgument]
def __init__(self, context: TypeVariableContext, args: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]) -> None:
self.context = context.__copy__()
self.args = list(
FunctionArgument(x) if isinstance(x, list) else x
for x in args
)
def __str__(self) -> str:
return str(self.context) + ' -> '.join(x.name for x in self.args)
def __repr__(self) -> str:
return f'FunctionSignature({self.context!r}, {self.args!r})'

View File

@ -1,66 +0,0 @@
"""
Contains the placeholder for types for use in Phasm.
These are temporary while the compiler is calculating all the types and validating them.
"""
from typing import Any, Iterable, List, Optional, Protocol, Union
from .types import Type3
class ExpressionProtocol(Protocol):
"""
A protocol for classes that should be updated on substitution
"""
type3: Type3 | None
"""
The type to update
"""
class PlaceholderForType:
"""
A placeholder type, for when we don't know the final type yet
"""
__slots__ = ('update_on_substitution', 'resolve_as', )
update_on_substitution: List[ExpressionProtocol]
resolve_as: Optional[Type3]
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
self.update_on_substitution = [*update_on_substitution]
self.resolve_as = None
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 isinstance(other, Type3):
return False
if not isinstance(other, PlaceholderForType):
raise NotImplementedError
return self is other
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
return 0 # Valid but performs badly
def __bool__(self) -> bool:
raise NotImplementedError
Type3OrPlaceholder = Union[Type3, PlaceholderForType]

View File

@ -1,142 +0,0 @@
from typing import Any, Callable
from .functions import (
TypeConstructorVariable,
TypeVariable,
TypeVariableApplication_Nullary,
TypeVariableApplication_Unary,
)
from .typeclasses import Type3ClassArgs
from .types import (
KindArgument,
Type3,
TypeApplication_Type,
TypeApplication_TypeInt,
TypeConstructor_Base,
)
class NoRouteForTypeException(Exception):
pass
class TypeApplicationRouter[S, R]:
"""
Helper class to find a method based on a constructed type
"""
__slots__ = ('by_constructor', 'by_type', )
by_constructor: dict[Any, Callable[[S, Any], R]]
"""
Contains all the added routing functions for constructed types
"""
by_type: dict[Type3, Callable[[S], R]]
"""
Contains all the added routing functions for constructed types
"""
def __init__(self) -> None:
self.by_constructor = {}
self.by_type = {}
def add_n(self, typ: Type3, helper: Callable[[S], R]) -> None:
"""
Lets you route to types that were not constructed
Also known types of kind *
"""
self.by_type[typ] = helper
def add[T](self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None:
self.by_constructor[constructor] = helper
def __call__(self, arg0: S, typ: Type3) -> R:
t_helper = self.by_type.get(typ)
if t_helper is not None:
return t_helper(arg0)
c_helper = self.by_constructor.get(typ.application.constructor)
if c_helper is not None:
return c_helper(arg0, typ.application.arguments)
raise NoRouteForTypeException(arg0, typ)
TypeVariableLookup = tuple[
dict[TypeVariable, KindArgument],
dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
]
class TypeClassArgsRouter[S, R]:
"""
Helper class to find a method based on a type class argument list
"""
__slots__ = ('args', 'data', )
args: Type3ClassArgs
data: dict[tuple[Type3 | TypeConstructor_Base[Any], ...], Callable[[S, TypeVariableLookup], R]]
def __init__(self, args: Type3ClassArgs) -> None:
self.args = args
self.data = {}
def add(
self,
tv_map: dict[TypeVariable, Type3],
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
helper: Callable[[S, TypeVariableLookup], R],
) -> None:
key: list[Type3 | TypeConstructor_Base[Any]] = []
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
else:
key.append(tc_map[tc_arg])
self.data[tuple(key)] = helper
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
key: list[Type3 | TypeConstructor_Base[Any]] = []
arguments: TypeVariableLookup = (dict(tv_map), {}, )
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
arguments[0][tc_arg] = tv_map[tc_arg]
continue
for tvar, typ in tv_map.items():
tvar_constructor = tvar.application.constructor
if tvar_constructor != tc_arg:
continue
key.append(typ.application.constructor)
arguments[1][tc_arg] = typ.application.constructor
if isinstance(tvar.application, TypeVariableApplication_Unary):
if isinstance(typ.application, TypeApplication_Type):
da_type, = typ.application.arguments
sa_type_tv = tvar.application.arguments
arguments[0][sa_type_tv] = da_type
continue
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(typ.application, TypeApplication_TypeInt):
sa_type, sa_len = typ.application.arguments
sa_type_tv = tvar.application.arguments
sa_len_tv = TypeVariable(sa_type_tv.name + '*', TypeVariableApplication_Nullary(None, None))
arguments[0][sa_type_tv] = sa_type
arguments[0][sa_len_tv] = sa_len
continue
raise NotImplementedError(tvar.application, typ.application)
t_helper = self.data.get(tuple(key))
if t_helper is not None:
return t_helper(arg0, arguments)
raise NoRouteForTypeException(arg0, tv_map)

View File

@ -1,104 +0,0 @@
from typing import Dict, Iterable, List, Mapping, Optional
from .functions import (
Constraint_TypeClassInstanceExists,
ConstraintBase,
FunctionSignature,
TypeConstructorVariable,
TypeVariable,
TypeVariableContext,
)
from .types import Type3
class Type3ClassMethod:
__slots__ = ('name', 'signature', )
name: str
signature: FunctionSignature
def __init__(self, name: str, signature: FunctionSignature) -> None:
self.name = name
self.signature = signature
def __str__(self) -> str:
return f'{self.name} :: {self.signature}'
def __repr__(self) -> str:
return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})'
Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable]
class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
name: str
args: Type3ClassArgs
methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod]
inherited_classes: List['Type3Class']
def __init__(
self,
name: str,
args: Type3ClassArgs,
methods: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
operators: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
inherited_classes: Optional[List['Type3Class']] = None,
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
) -> None:
self.name = name
self.args = args
self.methods = {
k: Type3ClassMethod(k, _create_signature(v, self))
for k, v in methods.items()
}
self.operators = {
k: Type3ClassMethod(k, _create_signature(v, self))
for k, v in operators.items()
}
self.inherited_classes = inherited_classes or []
if additional_context:
for func_name, constraint_list in additional_context.items():
func = self.methods.get(func_name) or self.operators.get(func_name)
assert func is not None # type hint
func.signature.context.constraints.extend(constraint_list)
def __repr__(self) -> str:
return self.name
def _create_signature(
method_arg_list: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]],
type_class3: Type3Class,
) -> FunctionSignature:
context = TypeVariableContext()
if not isinstance(type_class3.args[0], TypeConstructorVariable):
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, type_class3.args))
signature_args: list[Type3 | TypeVariable | list[Type3 | TypeVariable]] = []
for method_arg in method_arg_list:
if isinstance(method_arg, Type3):
signature_args.append(method_arg)
continue
if isinstance(method_arg, list):
signature_args.append(method_arg)
continue
if isinstance(method_arg, TypeVariable):
type_constructor = method_arg.application.constructor
if type_constructor is None:
signature_args.append(method_arg)
continue
if (type_constructor, ) == type_class3.args:
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, [method_arg]))
signature_args.append(method_arg)
continue
raise NotImplementedError(method_arg)
return FunctionSignature(context, signature_args)

View File

@ -1,290 +0,0 @@
"""
Contains the final types for use in Phasm, as well as construtors.
"""
from typing import (
Any,
Hashable,
Self,
Tuple,
TypeVar,
)
S = TypeVar('S')
T = TypeVar('T')
class KindArgument:
pass
class TypeApplication_Base[T: Hashable, S: Hashable]:
"""
Records the constructor and arguments used to create this type.
Nullary types, or types of kind *, have both arguments set to None.
"""
constructor: T
arguments: S
def __init__(self, constructor: T, arguments: S) -> None:
self.constructor = constructor
self.arguments = arguments
def __hash__(self) -> int:
return hash((self.constructor, self.arguments, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeApplication_Base):
raise NotImplementedError
return (self.constructor == other.constructor # type: ignore[no-any-return]
and self.arguments == other.arguments)
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
class Type3(KindArgument):
"""
Base class for the type3 types
(Having a separate name makes it easier to distinguish from
Python's Type)
"""
__slots__ = ('name', 'application', )
name: str
"""
The name of the string, as parsed and outputted by codestyle.
"""
application: TypeApplication_Base[Any, Any]
"""
How the type was constructed; i.e. which constructor was used and which
type level arguments were applied to the constructor.
"""
def __init__(self, name: str, application: TypeApplication_Base[Any, Any]) -> None:
self.name = name
self.application = application
def __repr__(self) -> str:
return f'Type3({self.name!r}, {self.application!r})'
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:
if not isinstance(other, Type3):
raise NotImplementedError(other)
return self is other
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
return hash(self.name)
def __bool__(self) -> bool:
raise NotImplementedError
class TypeApplication_Nullary(TypeApplication_Base[None, None]):
"""
There was no constructor used to create this type - it's a 'simple' type like u32
"""
class IntType3(KindArgument):
"""
Sometimes you can have an int on the type level, e.g. when using static arrays
This is not the same as an int on the language level.
[1.0, 1.2] :: f32[2] :: * -> Int -> *
That is to say, you can create a static array of size two with each element
a f32 using f32[2].
"""
__slots__ = ('value', )
value: int
def __init__(self, value: int) -> None:
self.value = value
def __repr__(self) -> str:
return f'IntType3({self.value!r})'
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.value)
def __eq__(self, other: Any) -> bool:
if isinstance(other, IntType3):
return self.value == other.value
if isinstance(other, KindArgument):
return False
raise NotImplementedError
def __hash__(self) -> int:
return hash(self.value)
class TypeConstructor_Base[T]:
"""
Base class for type construtors
"""
__slots__ = ('name', '_cache', )
name: str
"""
The name of the type constructor
"""
_cache: dict[T, Type3]
"""
When constructing a type with the same arguments,
it should produce the exact same result.
"""
def __init__(self, name: str) -> None:
self.name = name
self._cache = {}
def make_name(self, key: T) -> str:
"""
Renders the type's name based on the given arguments
"""
raise NotImplementedError('make_name', self)
def make_application(self, key: T) -> TypeApplication_Base[Self, T]:
"""
Records how the type was constructed into type.
The type checker and compiler will need to know what
arguments where made to construct the type.
"""
raise NotImplementedError('make_application', self)
def construct(self, key: T) -> Type3:
"""
Constructs the type by applying the given arguments to this
constructor.
"""
result = self._cache.get(key, None)
if result is None:
self._cache[key] = result = Type3(self.make_name(key), self.make_application(key))
return result
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.name!r}, ...)'
class TypeConstructor_Type(TypeConstructor_Base[Tuple[Type3]]):
"""
Base class type constructors of kind: * -> *
Notably, static array.
"""
__slots__ = ()
def make_application(self, key: Tuple[Type3]) -> 'TypeApplication_Type':
return TypeApplication_Type(self, key)
def make_name(self, key: Tuple[Type3]) -> str:
return f'{self.name} {key[0].name} '
def __call__(self, arg0: Type3) -> Type3:
return self.construct((arg0, ))
class TypeApplication_Type(TypeApplication_Base[TypeConstructor_Type, Tuple[Type3]]):
pass
class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]):
"""
Base class type constructors of kind: * -> Int -> *
Notably, static array.
"""
__slots__ = ()
def make_application(self, key: Tuple[Type3, IntType3]) -> 'TypeApplication_TypeInt':
return TypeApplication_TypeInt(self, key)
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
return f'{self.name} {key[0].name} {key[1].value}'
def __call__(self, arg0: Type3, arg1: IntType3) -> Type3:
return self.construct((arg0, arg1))
class TypeApplication_TypeInt(TypeApplication_Base[TypeConstructor_TypeInt, Tuple[Type3, IntType3]]):
pass
class TypeConstructor_TypeStar(TypeConstructor_Base[Tuple[Type3, ...]]):
"""
Base class type constructors of variadic kind
Notably, tuple.
"""
def make_application(self, key: Tuple[Type3, ...]) -> 'TypeApplication_TypeStar':
return TypeApplication_TypeStar(self, key)
def __call__(self, *args: Type3) -> Type3:
key: Tuple[Type3, ...] = tuple(args)
return self.construct(key)
class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]):
pass
class TypeConstructor_DynamicArray(TypeConstructor_Type):
def make_name(self, key: Tuple[Type3]) -> str:
if 'u8' == key[0].name:
return 'bytes'
return f'{key[0].name}[...]'
class TypeConstructor_StaticArray(TypeConstructor_TypeInt):
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
return f'{key[0].name}[{key[1].value}]'
class TypeConstructor_Tuple(TypeConstructor_TypeStar):
def make_name(self, key: Tuple[Type3, ...]) -> str:
return '(' + ', '.join(x.name for x in key) + ', )'
class TypeConstructor_Function(TypeConstructor_TypeStar):
def make_name(self, key: Tuple[Type3, ...]) -> str:
return 'Callable[' + ', '.join(x.name for x in key) + ']'
class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]):
"""
Constructs struct types
"""
def make_application(self, key: tuple[tuple[str, Type3], ...]) -> 'TypeApplication_Struct':
return TypeApplication_Struct(self, key)
def make_name(self, key: tuple[tuple[str, Type3], ...]) -> str:
return f'{self.name}(' + ', '.join(
f'{n}: {t.name}'
for n, t in key
) + ')'
def construct(self, key: T) -> Type3:
"""
Constructs the type by applying the given arguments to this
constructor.
"""
raise Exception('This does not work with the caching system')
def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3:
result = Type3(name, self.make_application(args))
return result
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):
pass

View File

@ -0,0 +1,52 @@
"""
Some expression have constraints - those are usually the outmost expressions.
"""
from dataclasses import dataclass
from typing import Callable, Self
from .kindexpr import KindExpr
from .typeexpr import TypeExpr, TypeVariable, instantiate
class TypeConstraint:
"""
Base class for type contraints
"""
__slots__ = ()
def __str__(self) -> str:
raise NotImplementedError
def instantiate(self, known_map: dict[TypeVariable, TypeVariable], make_variable: Callable[[KindExpr, str], TypeVariable]) -> Self:
raise NotImplementedError
@dataclass
class ConstrainedExpr:
"""
Also known as a polytype.
We only have rank 1 types, so this is always on the outside.
"""
expr: TypeExpr
constraints: tuple[TypeConstraint, ...]
# TODO: Move instantiate here? it only makes sense for polytypes
def instantiate_constrained(
constrainedexpr: ConstrainedExpr,
known_map: dict[TypeVariable, TypeVariable],
make_variable: Callable[[KindExpr, str], TypeVariable],
) -> ConstrainedExpr:
"""
Instantiates a type expression and its constraints
"""
# Would be handier if we had the list of variables on ConstrainedExpr
# So we don't have to pass make_variable
# This also helps with FunctionCall.substitutions
expr = instantiate(constrainedexpr.expr, known_map, make_variable)
constraints = tuple(
x.instantiate(known_map, make_variable)
for x in constrainedexpr.constraints
)
return ConstrainedExpr(expr, constraints)

458
phasm/type5/constraints.py Normal file
View File

@ -0,0 +1,458 @@
from __future__ import annotations
import dataclasses
from typing import Any, Callable, Iterable, Protocol, Sequence
from ..build.base import BuildBase
from ..ourlang import SourceRef
from ..wasm import WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, WasmTypeInt64
from .kindexpr import KindExpr, Star
from .record import Record
from .typeexpr import (
TypeExpr,
TypeVariable,
is_concrete,
replace_variable,
)
from .unify import Action, ActionList, Failure, ReplaceVariable, unify
class ExpressionProtocol(Protocol):
"""
A protocol for classes that should be updated on substitution
"""
type5: TypeExpr | None
"""
The type to update
"""
class Context:
__slots__ = ("build", "placeholder_update", )
build: BuildBase[Any]
placeholder_update: dict[TypeVariable, ExpressionProtocol | None]
def __init__(self, build: BuildBase[Any]) -> None:
self.build = build
self.placeholder_update = {}
def make_placeholder(self, arg: ExpressionProtocol | None = None, kind: KindExpr = Star(), prefix: str = 'p') -> TypeVariable:
res = TypeVariable(kind, f"{prefix}_{len(self.placeholder_update)}")
self.placeholder_update[res] = arg
return res
@dataclasses.dataclass
class CheckResult:
_: dataclasses.KW_ONLY
done: bool = True
actions: ActionList = dataclasses.field(default_factory=ActionList)
new_constraints: list[ConstraintBase] = dataclasses.field(default_factory=list)
failures: list[Failure] = dataclasses.field(default_factory=list)
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
if not self.done and not self.actions and not self.new_constraints and not self.failures:
return '(skip for now)'
if self.done and not self.actions and not self.new_constraints and not self.failures:
return '(ok)'
if self.done and self.actions and not self.new_constraints and not self.failures:
return self.actions.to_str(type_namer)
if self.done and not self.actions and self.new_constraints and not self.failures:
return f'(got {len(self.new_constraints)} new constraints)'
if self.done and not self.actions and not self.new_constraints and self.failures:
return 'ERR: ' + '; '.join(x.msg for x in self.failures)
return f'{self.actions.to_str(type_namer)} {self.failures} {self.new_constraints} {self.done}'
def skip_for_now() -> CheckResult:
return CheckResult(done=False)
def new_constraints(lst: Iterable[ConstraintBase]) -> CheckResult:
return CheckResult(new_constraints=list(lst))
def ok() -> CheckResult:
return CheckResult(done=True)
def fail(msg: str) -> CheckResult:
return CheckResult(failures=[Failure(msg)])
class ConstraintBase:
__slots__ = ("ctx", "sourceref", )
ctx: Context
sourceref: SourceRef
def __init__(self, ctx: Context, sourceref: SourceRef) -> None:
self.ctx = ctx
self.sourceref = sourceref
def check(self) -> CheckResult:
raise NotImplementedError(self)
def apply(self, action: Action) -> None:
if isinstance(action, ReplaceVariable):
self.replace_variable(action.var, action.typ)
return
raise NotImplementedError(action)
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
pass
class FromLiteralInteger(ConstraintBase):
__slots__ = ('type5', 'literal', )
type5: TypeExpr
literal: int
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: int) -> None:
super().__init__(ctx, sourceref)
self.type5 = type5
self.literal = literal
def check(self) -> CheckResult:
if not is_concrete(self.type5):
return skip_for_now()
type_info = self.ctx.build.type_info_map.get(self.type5.name)
if type_info is None:
return fail('Cannot convert from literal integer')
if type_info.wasm_type is not WasmTypeInt32 and type_info.wasm_type is not WasmTypeInt64:
return fail('Cannot convert from literal integer')
assert type_info.signed is not None # type hint
try:
self.literal.to_bytes(type_info.alloc_size, 'big', signed=type_info.signed)
except OverflowError:
return fail(f'Must fit in {type_info.alloc_size} byte(s)')
return ok()
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.type5 = replace_variable(self.type5, var, typ)
def __str__(self) -> str:
return f'FromLiteralInteger {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
class FromLiteralFloat(ConstraintBase):
__slots__ = ('type5', 'literal', )
type5: TypeExpr
literal: float
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: float) -> None:
super().__init__(ctx, sourceref)
self.type5 = type5
self.literal = literal
def check(self) -> CheckResult:
if not is_concrete(self.type5):
return skip_for_now()
type_info = self.ctx.build.type_info_map.get(self.type5.name)
if type_info is None:
return fail('Cannot convert from literal float')
if type_info.wasm_type is not WasmTypeFloat32 and type_info.wasm_type is not WasmTypeFloat64:
return fail('Cannot convert from literal float')
# TODO: Precision check
return ok()
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.type5 = replace_variable(self.type5, var, typ)
def __str__(self) -> str:
return f'FromLiteralInteger {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
class FromLiteralBytes(ConstraintBase):
__slots__ = ('type5', 'literal', )
type5: TypeExpr
literal: bytes
def __init__(self, ctx: Context, sourceref: SourceRef, type5: TypeExpr, literal: bytes) -> None:
super().__init__(ctx, sourceref)
self.type5 = type5
self.literal = literal
def check(self) -> CheckResult:
if not is_concrete(self.type5):
return skip_for_now()
da_arg = self.ctx.build.type5_is_dynamic_array(self.type5)
if da_arg is None or da_arg != self.ctx.build.u8_type5:
return fail('Cannot convert from literal bytes')
return ok()
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.type5 = replace_variable(self.type5, var, typ)
def __str__(self) -> str:
return f'FromLiteralBytes {self.ctx.build.type5_name(self.type5)} ~ {self.literal!r}'
class UnifyTypesConstraint(ConstraintBase):
__slots__ = ("lft", "rgt",)
def __init__(self, ctx: Context, sourceref: SourceRef, lft: TypeExpr, rgt: TypeExpr) -> None:
super().__init__(ctx, sourceref)
self.lft = lft
self.rgt = rgt
def check(self) -> CheckResult:
result = unify(self.lft, self.rgt)
if isinstance(result, Failure):
return CheckResult(failures=[result])
return CheckResult(actions=result)
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.lft = replace_variable(self.lft, var, typ)
self.rgt = replace_variable(self.rgt, var, typ)
def __str__(self) -> str:
return f"{self.ctx.build.type5_name(self.lft)} ~ {self.ctx.build.type5_name(self.rgt)}"
class CanBeSubscriptedConstraint(ConstraintBase):
__slots__ = ('ret_type5', 'container_type5', 'index_type5', 'index_const', )
ret_type5: TypeExpr
container_type5: TypeExpr
index_type5: TypeExpr
index_const: int | None
def __init__(
self,
ctx: Context,
sourceref: SourceRef,
ret_type5: TypeExpr,
container_type5: TypeExpr,
index_type5: TypeExpr,
index_const: int | None,
) -> None:
super().__init__(ctx, sourceref)
self.ret_type5 = ret_type5
self.container_type5 = container_type5
self.index_type5 = index_type5
self.index_const = index_const
def check(self) -> CheckResult:
if not is_concrete(self.container_type5):
return CheckResult(done=False)
da_arg = self.ctx.build.type5_is_dynamic_array(self.container_type5)
if da_arg is not None:
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, da_arg, self.ret_type5),
UnifyTypesConstraint(self.ctx, self.sourceref, self.ctx.build.u32_type5, self.index_type5),
])
sa_args = self.ctx.build.type5_is_static_array(self.container_type5)
if sa_args is not None:
sa_len, sa_typ = sa_args
if self.index_const is not None and (self.index_const < 0 or sa_len <= self.index_const):
return fail('Tuple index out of range')
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, sa_typ, self.ret_type5),
UnifyTypesConstraint(self.ctx, self.sourceref, self.ctx.build.u32_type5, self.index_type5),
])
tp_args = self.ctx.build.type5_is_tuple(self.container_type5)
if tp_args is not None:
if self.index_const is None:
return fail('Must index with integer literal')
if self.index_const < 0 or len(tp_args) <= self.index_const:
return fail('Tuple index out of range')
return new_constraints([
UnifyTypesConstraint(self.ctx, self.sourceref, tp_args[self.index_const], self.ret_type5),
UnifyTypesConstraint(self.ctx, self.sourceref, self.ctx.build.u32_type5, self.index_type5),
])
raise NotImplementedError('This should be converted to a TypeClass, also for da and sa, only tuple should be the exception')
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
self.container_type5 = replace_variable(self.container_type5, var, typ)
self.index_type5 = replace_variable(self.index_type5, var, typ)
def __str__(self) -> str:
return f"[] :: t a -> b -> a ~ {self.ctx.build.type5_name(self.container_type5)} -> {self.ctx.build.type5_name(self.index_type5)} -> {self.ctx.build.type5_name(self.ret_type5)}"
class CanAccessStructMemberConstraint(ConstraintBase):
__slots__ = ('ret_type5', 'struct_type5', 'member_name', )
ret_type5: TypeExpr
struct_type5: TypeExpr
member_name: str
def __init__(
self,
ctx: Context,
sourceref: SourceRef,
ret_type5: TypeExpr,
struct_type5: TypeExpr,
member_name: str,
) -> None:
super().__init__(ctx, sourceref)
self.ret_type5 = ret_type5
self.struct_type5 = struct_type5
self.member_name = member_name
def check(self) -> CheckResult:
if not is_concrete(self.struct_type5):
return CheckResult(done=False)
st_args = self.ctx.build.type5_is_struct(self.struct_type5)
if st_args is None:
return fail('Must be a struct')
member_dict = dict(st_args)
if self.member_name not in member_dict:
return fail('Must have a field with this name')
return UnifyTypesConstraint(self.ctx, self.sourceref, self.ret_type5, member_dict[self.member_name]).check()
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
self.struct_type5 = replace_variable(self.struct_type5, var, typ)
def __str__(self) -> str:
st_args = self.ctx.build.type5_is_struct(self.struct_type5)
member_dict = dict(st_args or [])
member_typ = member_dict.get(self.member_name)
if member_typ is None:
expect = 'a -> b'
else:
expect = f'{self.ctx.build.type5_name(self.struct_type5)} -> {self.ctx.build.type5_name(member_typ)}'
return f".{self.member_name} :: {expect} ~ {self.ctx.build.type5_name(self.struct_type5)} -> {self.ctx.build.type5_name(self.ret_type5)}"
class FromTupleConstraint(ConstraintBase):
__slots__ = ('ret_type5', 'member_type5_list', )
ret_type5: TypeExpr
member_type5_list: list[TypeExpr]
def __init__(
self,
ctx: Context,
sourceref: SourceRef,
ret_type5: TypeExpr,
member_type5_list: Sequence[TypeExpr],
) -> None:
super().__init__(ctx, sourceref)
self.ret_type5 = ret_type5
self.member_type5_list = list(member_type5_list)
def check(self) -> CheckResult:
if not is_concrete(self.ret_type5):
return CheckResult(done=False)
da_arg = self.ctx.build.type5_is_dynamic_array(self.ret_type5)
if da_arg is not None:
return CheckResult(new_constraints=[
UnifyTypesConstraint(self.ctx, self.sourceref, da_arg, x)
for x in self.member_type5_list
])
sa_args = self.ctx.build.type5_is_static_array(self.ret_type5)
if sa_args is not None:
sa_len, sa_typ = sa_args
if sa_len != len(self.member_type5_list):
return fail('Tuple element count mismatch')
return CheckResult(new_constraints=[
UnifyTypesConstraint(self.ctx, self.sourceref, sa_typ, x)
for x in self.member_type5_list
])
tp_args = self.ctx.build.type5_is_tuple(self.ret_type5)
if tp_args is not None:
if len(tp_args) != len(self.member_type5_list):
return fail('Tuple element count mismatch')
return CheckResult(new_constraints=[
UnifyTypesConstraint(self.ctx, self.sourceref, act_typ, exp_typ)
for act_typ, exp_typ in zip(tp_args, self.member_type5_list, strict=True)
])
raise NotImplementedError(self.ret_type5)
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.ret_type5 = replace_variable(self.ret_type5, var, typ)
self.member_type5_list = [
replace_variable(x, var, typ)
for x in self.member_type5_list
]
def __str__(self) -> str:
args = ', '.join(self.ctx.build.type5_name(x) for x in self.member_type5_list)
return f'FromTuple {self.ctx.build.type5_name(self.ret_type5)} ~ ({args}, )'
class TypeClassInstanceExistsConstraint(ConstraintBase):
__slots__ = ('typeclass', 'arg_list', )
typeclass: str
arg_list: list[TypeExpr]
def __init__(
self,
ctx: Context,
sourceref: SourceRef,
typeclass: str,
arg_list: Sequence[TypeExpr]
) -> None:
super().__init__(ctx, sourceref)
self.typeclass = typeclass
self.arg_list = list(arg_list)
def check(self) -> CheckResult:
c_arg_list = [
x for x in self.arg_list if is_concrete(x)
]
if len(c_arg_list) != len(self.arg_list):
return skip_for_now()
if any(isinstance(x, Record) for x in c_arg_list):
# TODO: Allow users to implement type classes on their structs
return fail('Missing type class instance')
key = tuple(c_arg_list)
existing_instances = self.ctx.build.type_class_instances[self.typeclass]
if key in existing_instances:
return ok()
return fail('Missing type class instance')
def replace_variable(self, var: TypeVariable, typ: TypeExpr) -> None:
self.arg_list = [
replace_variable(x, var, typ)
for x in self.arg_list
]
def __str__(self) -> str:
args = ' '.join(self.ctx.build.type5_name(x) for x in self.arg_list)
return f'Exists {self.typeclass} {args}'

280
phasm/type5/fromast.py Normal file
View File

@ -0,0 +1,280 @@
from typing import Any, Generator
from .. import ourlang
from ..typeclass import TypeClassConstraint
from .constrainedexpr import ConstrainedExpr, instantiate_constrained
from .constraints import (
CanAccessStructMemberConstraint,
CanBeSubscriptedConstraint,
ConstraintBase,
Context,
FromLiteralBytes,
FromLiteralFloat,
FromLiteralInteger,
FromTupleConstraint,
TypeClassInstanceExistsConstraint,
UnifyTypesConstraint,
)
from .kindexpr import KindExpr
from .typeexpr import TypeApplication, TypeVariable, instantiate
ConstraintGenerator = Generator[ConstraintBase, None, None]
def phasm_type5_generate_constraints(ctx: Context, inp: ourlang.Module[Any]) -> list[ConstraintBase]:
return [*module(ctx, inp)]
def expression_constant_primitive(ctx: Context, inp: ourlang.ConstantPrimitive, phft: TypeVariable) -> ConstraintGenerator:
if isinstance(inp.value, int):
yield FromLiteralInteger(ctx, inp.sourceref, phft, inp.value)
return
if isinstance(inp.value, float):
yield FromLiteralFloat(ctx, inp.sourceref, phft, inp.value)
return
raise NotImplementedError(inp.value)
def expression_constant_bytes(ctx: Context, inp: ourlang.ConstantBytes, phft: TypeVariable) -> ConstraintGenerator:
yield FromLiteralBytes(ctx, inp.sourceref, phft, inp.value)
def expression_constant_tuple(ctx: Context, inp: ourlang.ConstantTuple, phft: TypeVariable) -> ConstraintGenerator:
member_type5_list = [
ctx.make_placeholder(arg)
for arg in inp.value
]
for arg, arg_phft in zip(inp.value, member_type5_list):
yield from expression(ctx, arg, arg_phft)
yield FromTupleConstraint(ctx, inp.sourceref, phft, member_type5_list)
def expression_constant_struct(ctx: Context, inp: ourlang.ConstantStruct, phft: TypeVariable) -> ConstraintGenerator:
member_type5_list = [
ctx.make_placeholder(arg)
for arg in inp.value
]
for arg, arg_phft in zip(inp.value, member_type5_list):
yield from expression(ctx, arg, arg_phft)
lft = ctx.build.type5_make_function([x[1] for x in inp.struct_type5.fields] + [inp.struct_type5])
rgt = ctx.build.type5_make_function(member_type5_list + [phft])
yield UnifyTypesConstraint(ctx, inp.sourceref, lft, rgt)
def expression_constant_memory_stored(ctx: Context, inp: ourlang.ConstantMemoryStored, phft: TypeVariable) -> ConstraintGenerator:
if isinstance(inp, ourlang.ConstantBytes):
yield from expression_constant_bytes(ctx, inp, phft)
return
if isinstance(inp, ourlang.ConstantTuple):
yield from expression_constant_tuple(ctx, inp, phft)
return
if isinstance(inp, ourlang.ConstantStruct):
yield from expression_constant_struct(ctx, inp, phft)
return
raise NotImplementedError(inp)
def expression_constant(ctx: Context, inp: ourlang.Constant, phft: TypeVariable) -> ConstraintGenerator:
if isinstance(inp, ourlang.ConstantPrimitive):
yield from expression_constant_primitive(ctx, inp, phft)
return
if isinstance(inp, ourlang.ConstantMemoryStored):
yield from expression_constant_memory_stored(ctx, inp, phft)
return
raise NotImplementedError(inp)
def expression_variable_reference(ctx: Context, inp: ourlang.VariableReference, phft: TypeVariable) -> ConstraintGenerator:
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.variable.type5, phft)
def expression_binary_operator(ctx: Context, inp: ourlang.BinaryOp, phft: TypeVariable) -> ConstraintGenerator:
yield from expression_function_call(ctx, _binary_op_to_function(inp), phft)
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: TypeVariable) -> ConstraintGenerator:
arg_typ_list = []
for arg in inp.arguments:
arg_tv = ctx.make_placeholder(arg)
yield from expression(ctx, arg, arg_tv)
arg_typ_list.append(arg_tv)
def make_placeholder(x: KindExpr, p: str) -> TypeVariable:
return ctx.make_placeholder(kind=x, prefix=p)
ftp5 = inp.function_instance.function.type5
assert ftp5 is not None
if isinstance(ftp5, ConstrainedExpr):
ftp5 = instantiate_constrained(ftp5, {}, make_placeholder)
for type_constraint in ftp5.constraints:
if isinstance(type_constraint, TypeClassConstraint):
yield TypeClassInstanceExistsConstraint(ctx, inp.sourceref, type_constraint.cls.name, type_constraint.variables)
continue
raise NotImplementedError(type_constraint)
ftp5 = ftp5.expr
else:
ftp5 = instantiate(ftp5, {}, make_placeholder)
# We need an extra placeholder so that the inp.function_instance gets updated
phft2 = ctx.make_placeholder(inp.function_instance)
yield UnifyTypesConstraint(
ctx,
inp.sourceref,
ftp5,
phft2,
)
expr_type = ctx.build.type5_make_function(arg_typ_list + [phft])
yield UnifyTypesConstraint(ctx, inp.sourceref, phft2, expr_type)
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator:
assert inp.function.type5 is not None # Todo: Make not nullable
ftp5 = inp.function.type5
if isinstance(ftp5, ConstrainedExpr):
ftp5 = ftp5.expr
yield UnifyTypesConstraint(ctx, inp.sourceref, ftp5, phft)
def expression_tuple_instantiation(ctx: Context, inp: ourlang.TupleInstantiation, phft: TypeVariable) -> ConstraintGenerator:
arg_typ_list = []
for arg in inp.elements:
arg_tv = ctx.make_placeholder(arg)
yield from expression(ctx, arg, arg_tv)
arg_typ_list.append(arg_tv)
yield FromTupleConstraint(ctx, inp.sourceref, phft, arg_typ_list)
def expression_subscript(ctx: Context, inp: ourlang.Subscript, phft: TypeVariable) -> ConstraintGenerator:
varref_phft = ctx.make_placeholder(inp.varref)
index_phft = ctx.make_placeholder(inp.index)
yield from expression(ctx, inp.varref, varref_phft)
yield from expression(ctx, inp.index, index_phft)
if isinstance(inp.index, ourlang.ConstantPrimitive) and isinstance(inp.index.value, int):
yield CanBeSubscriptedConstraint(ctx, inp.sourceref, phft, varref_phft, index_phft, inp.index.value)
else:
yield CanBeSubscriptedConstraint(ctx, inp.sourceref, phft, varref_phft, index_phft, None)
def expression_access_struct_member(ctx: Context, inp: ourlang.AccessStructMember, phft: TypeVariable) -> ConstraintGenerator:
varref_phft = ctx.make_placeholder(inp.varref)
yield from expression_variable_reference(ctx, inp.varref, varref_phft)
yield CanAccessStructMemberConstraint(ctx, inp.sourceref, phft, varref_phft, inp.member)
def expression(ctx: Context, inp: ourlang.Expression, phft: TypeVariable) -> ConstraintGenerator:
if isinstance(inp, ourlang.Constant):
yield from expression_constant(ctx, inp, phft)
return
if isinstance(inp, ourlang.VariableReference):
yield from expression_variable_reference(ctx, inp, phft)
return
if isinstance(inp, ourlang.BinaryOp):
yield from expression_binary_operator(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionCall):
yield from expression_function_call(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionReference):
yield from expression_function_reference(ctx, inp, phft)
return
if isinstance(inp, ourlang.TupleInstantiation):
yield from expression_tuple_instantiation(ctx, inp, phft)
return
if isinstance(inp, ourlang.Subscript):
yield from expression_subscript(ctx, inp, phft)
return
if isinstance(inp, ourlang.AccessStructMember):
yield from expression_access_struct_member(ctx, inp, phft)
return
raise NotImplementedError(inp)
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
phft = ctx.make_placeholder(inp.value)
if fun.type5 is None:
raise NotImplementedError("Deducing function type - you'll have to annotate it.")
if isinstance(fun.type5, TypeApplication):
args = ctx.build.type5_is_function(fun.type5)
assert args is not None
type5 = args[-1]
else:
type5 = fun.type5.expr if isinstance(fun.type5, ConstrainedExpr) else fun.type5
yield from expression(ctx, inp.value, phft)
yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft)
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
test_phft = ctx.make_placeholder(inp.test)
yield from expression(ctx, inp.test, test_phft)
yield UnifyTypesConstraint(ctx, inp.test.sourceref, test_phft, ctx.build.bool_type5)
for stmt in inp.statements:
yield from statement(ctx, fun, stmt)
for stmt in inp.else_statements:
yield from statement(ctx, fun, stmt)
def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> ConstraintGenerator:
if isinstance(inp, ourlang.StatementReturn):
yield from statement_return(ctx, fun, inp)
return
if isinstance(inp, ourlang.StatementIf):
yield from statement_if(ctx, fun, inp)
return
raise NotImplementedError(inp)
def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
for stmt in inp.statements:
yield from statement(ctx, inp, stmt)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
phft = ctx.make_placeholder(inp.constant)
yield from expression_constant(ctx, inp.constant, phft)
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.type5, phft)
def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator:
for cdef in inp.constant_defs.values():
yield from module_constant_def(ctx, cdef)
for func in inp.functions.values():
if func.imported:
continue
yield from function(ctx, func)
# TODO: Generalize?
def _binary_op_to_function(inp: ourlang.BinaryOp) -> ourlang.FunctionCall:
"""
For typing purposes, a binary operator is just a function call.
It's only syntactic sugar - e.g. `1 + 2` vs `+(1, 2)`
"""
assert inp.sourceref is not None # TODO: sourceref required
call = ourlang.FunctionCall(inp.operator, inp.sourceref)
call.arguments = [inp.left, inp.right]
return call

57
phasm/type5/kindexpr.py Normal file
View File

@ -0,0 +1,57 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TypeAlias
@dataclass
class Star:
def __rshift__(self, other: KindExpr) -> Arrow:
return Arrow(self, other)
def __str__(self) -> str:
return "*"
def __hash__(self) -> int:
return 0 # All Stars are the same
@dataclass
class Nat:
def __rshift__(self, other: KindExpr) -> Arrow:
return Arrow(self, other)
def __str__(self) -> str:
return "Nat"
def __hash__(self) -> int:
return 0 # All Stars are the same
@dataclass
class Arrow:
"""
Represents an arrow kind `K1 -> K2`.
To create K1 -> K2 -> K3, pass an Arrow for result_kind.
For now, we do not support Arrows as arguments (i.e.,
no higher order kinds).
"""
arg_kind: Star | Nat
result_kind: KindExpr
def __str__(self) -> str:
if isinstance(self.arg_kind, Star):
arg_kind = "*"
else:
arg_kind = f"({str(self.arg_kind)})"
if isinstance(self.result_kind, Star):
result_kind = "*"
else:
result_kind = f"({str(self.result_kind)})"
return f"{arg_kind} -> {result_kind}"
def __hash__(self) -> int:
return hash((self.arg_kind, self.result_kind, ))
KindExpr: TypeAlias = Star | Nat | Arrow

43
phasm/type5/record.py Normal file
View File

@ -0,0 +1,43 @@
from dataclasses import dataclass
from .kindexpr import Star
from .typeexpr import AtomicType, TypeApplication, is_concrete
@dataclass
class Record(AtomicType):
"""
Records are a fundamental type. But we need to store some extra info.
"""
fields: tuple[tuple[str, AtomicType | TypeApplication], ...]
def __init__(self, name: str, fields: tuple[tuple[str, AtomicType | TypeApplication], ...]) -> None:
for field_name, field_type in fields:
if field_type.kind != Star():
raise TypeError(f"Record fields must not be constructors ({field_name} :: {field_type})")
if not is_concrete(field_type):
raise TypeError("Record field types must be concrete types ({field_name} :: {field_type})")
super().__init__(name)
self.fields = fields
def __str__(self) -> str:
args = ", ".join(
f"{field_name} :: {field_type}"
for field_name, field_type in self.fields
)
return f"{self.name} {{{args}}} :: {self.kind}"
# @dataclass
# class RecordConstructor(TypeConstructor):
# """
# TODO.
# i.e.:
# ```
# class Foo[T, R]:
# lft: T
# rgt: R
# """
# name: str

8
phasm/type5/router.py Normal file
View File

@ -0,0 +1,8 @@
from typing import Callable
from .typeexpr import TypeExpr
class TypeRouter[S, R]:
def add(self, typ: TypeExpr, mtd: Callable[[S, TypeExpr], R]) -> None:
raise NotImplementedError

131
phasm/type5/solver.py Normal file
View File

@ -0,0 +1,131 @@
from typing import Any
from ..ourlang import Module
from .constraints import ConstraintBase, Context
from .fromast import phasm_type5_generate_constraints
from .typeexpr import TypeExpr, TypeVariable, replace_variable
from .unify import ReplaceVariable
MAX_RESTACK_COUNT = 10 # 100
class Type5SolverException(Exception):
pass
def phasm_type5(inp: Module[Any], verbose: bool = False) -> None:
ctx = Context(inp.build)
constraint_list = phasm_type5_generate_constraints(ctx, inp)
assert constraint_list
placeholder_types: dict[TypeVariable, TypeExpr] = {}
error_list: list[tuple[str, str, str]] = []
if verbose:
print('Constraints')
for _ in range(MAX_RESTACK_COUNT):
if verbose:
for constraint in constraint_list:
print(f"{constraint.sourceref!s} {constraint!s}")
print("Validating")
new_constraint_list: list[ConstraintBase] = []
while constraint_list:
constraint = constraint_list.pop(0)
result = constraint.check()
if verbose:
print(f"{constraint.sourceref!s} {constraint!s}")
print(f"{constraint.sourceref!s} => {result.to_str(inp.build.type5_name)}")
if not result:
# None or empty list
# Means it checks out and we don't need do anything
continue
while result.actions:
action = result.actions.pop(0)
if isinstance(action, ReplaceVariable):
action_var: TypeExpr = action.var
while isinstance(action_var, TypeVariable) and action_var in placeholder_types:
# TODO: Does this still happen?
action_var = placeholder_types[action_var]
action_typ: TypeExpr = action.typ
while isinstance(action_typ, TypeVariable) and action_typ in placeholder_types:
# TODO: Does this still happen?
action_typ = placeholder_types[action_typ]
# print(inp.build.type5_name(action_var), ':=', inp.build.type5_name(action_typ))
if action_var == action_typ:
continue
if not isinstance(action_var, TypeVariable) and isinstance(action_typ, TypeVariable):
action_typ, action_var = action_var, action_typ
if isinstance(action_var, TypeVariable):
# Ensure all existing found types are updated
placeholder_types = {
k: replace_variable(v, action_var, action_typ)
for k, v in placeholder_types.items()
}
placeholder_types[action_var] = action_typ
for oth_const in new_constraint_list + constraint_list:
if oth_const is constraint and result.done:
continue
old_str = str(oth_const)
oth_const.replace_variable(action_var, action_typ)
new_str = str(oth_const)
if verbose and old_str != new_str:
print(f"{oth_const.sourceref!s} => - {old_str!s}")
print(f"{oth_const.sourceref!s} => + {new_str!s}")
continue
error_list.append((str(constraint.sourceref), str(constraint), "Not the same type", ))
if verbose:
print(f"{constraint.sourceref!s} => ERR: Conflict in applying {action.to_str(inp.build.type5_name)}")
continue
# Action of unsupported type
raise NotImplementedError(action)
for failure in result.failures:
error_list.append((str(constraint.sourceref), str(constraint), failure.msg, ))
new_constraint_list.extend(result.new_constraints)
if result.done:
continue
new_constraint_list.append(constraint)
if error_list:
raise Type5SolverException(error_list)
if not new_constraint_list:
break
if verbose:
print()
print('New round')
constraint_list = new_constraint_list
if new_constraint_list:
raise Type5SolverException('Was unable to complete typing this program')
for placeholder, expression in ctx.placeholder_update.items():
if expression is None:
continue
new_type5 = placeholder_types[placeholder]
while isinstance(new_type5, TypeVariable):
new_type5 = placeholder_types[new_type5]
expression.type5 = new_type5

198
phasm/type5/typeexpr.py Normal file
View File

@ -0,0 +1,198 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from .kindexpr import Arrow, KindExpr, Nat, Star
@dataclass
class TypeExpr:
kind: KindExpr
name: str
def __str__(self) -> str:
return f"{self.name} :: {self.kind}"
@dataclass
class AtomicType(TypeExpr):
def __init__(self, name: str) -> None:
super().__init__(Star(), name)
def __hash__(self) -> int:
return hash((self.kind, self.name))
@dataclass
class TypeLevelNat(TypeExpr):
value: int
def __init__(self, nat: int) -> None:
assert 0 <= nat
super().__init__(Nat(), str(nat))
self.value = nat
def __hash__(self) -> int:
return hash((self.kind, self.name, self.value))
@dataclass
class TypeVariable(TypeExpr):
"""
A placeholder in a type expression
"""
def __hash__(self) -> int:
return hash((self.kind, self.name))
@dataclass
class TypeConstructor(TypeExpr):
def __init__(self, kind: Arrow, name: str) -> None:
super().__init__(kind, name)
def __hash__(self) -> int:
return hash((self.kind, self.name))
@dataclass
class TypeApplication(TypeExpr):
constructor: TypeConstructor | TypeApplication | TypeVariable
argument: TypeExpr
def __init__(
self,
constructor: TypeConstructor | TypeApplication | TypeVariable,
argument: TypeExpr,
) -> None:
if isinstance(constructor.kind, Star):
raise TypeError("A constructor cannot be a concrete type")
if isinstance(constructor.kind, Nat):
raise TypeError("A constructor cannot be a number")
if constructor.kind.arg_kind != argument.kind:
raise TypeError("Argument does match construtor's expectations")
super().__init__(
constructor.kind.result_kind,
f"{constructor.name} ({argument.name})",
)
self.constructor = constructor
self.argument = argument
def __hash__(self) -> int:
return hash((self.kind, self.name, self.constructor, self.argument))
def occurs(lft: TypeVariable, rgt: TypeApplication) -> bool:
"""
Checks whether the given variable occurs in the given application.
"""
if lft == rgt.constructor:
return True
if lft == rgt.argument:
return True
if isinstance(rgt.argument, TypeApplication):
return occurs(lft, rgt.argument)
return False
def is_concrete(lft: TypeExpr) -> bool:
"""
A concrete type has no variables in it.
This is also known as a monomorphic type or a closed type.
TODO: I don't know the differen between them yet.
"""
if isinstance(lft, AtomicType):
return True
if isinstance(lft, TypeLevelNat):
return True
if isinstance(lft, TypeVariable):
return False
if isinstance(lft, TypeConstructor):
return True
if isinstance(lft, TypeApplication):
return is_concrete(lft.constructor) and is_concrete(lft.argument)
raise NotImplementedError
def is_polymorphic(lft: TypeExpr) -> bool:
"""
A polymorphic type has one or more variables in it.
"""
return not is_concrete(lft)
def replace_variable(expr: TypeExpr, var: TypeVariable, rep_expr: TypeExpr) -> TypeExpr:
assert var.kind == rep_expr.kind, (var, rep_expr, )
if isinstance(expr, AtomicType):
# Nothing to replace
return expr
if isinstance(expr, TypeLevelNat):
# Nothing to replace
return expr
if isinstance(expr, TypeVariable):
if expr == var:
return rep_expr
return expr
if isinstance(expr, TypeConstructor):
# Nothing to replace
return expr
if isinstance(expr, TypeApplication):
new_constructor = replace_variable(expr.constructor, var, rep_expr)
assert isinstance(new_constructor, TypeConstructor | TypeApplication | TypeVariable) # type hint
return TypeApplication(
constructor=new_constructor,
argument=replace_variable(expr.argument, var, rep_expr),
)
raise NotImplementedError
def instantiate(
expr: TypeExpr,
known_map: dict[TypeVariable, TypeVariable],
make_variable: Callable[[KindExpr, str], TypeVariable],
) -> TypeExpr:
"""
Make a copy of all variables.
This is need when you use a polymorphic function in two places. In the
one place, `t a` may refer to `u32[...]` and in another to `f32[4]`.
These should not be unified since they refer to a different monomorphic
version of the function.
"""
if isinstance(expr, AtomicType):
return expr
if isinstance(expr, TypeLevelNat):
return expr
if isinstance(expr, TypeVariable):
known_map.setdefault(expr, make_variable(expr.kind, expr.name))
return known_map[expr]
if isinstance(expr, TypeConstructor):
return expr
if isinstance(expr, TypeApplication):
new_constructor = instantiate(expr.constructor, known_map, make_variable)
assert isinstance(new_constructor, TypeConstructor | TypeApplication | TypeVariable) # type hint
return TypeApplication(
constructor=new_constructor,
argument=instantiate(expr.argument, known_map, make_variable),
)
raise NotImplementedError(expr)

43
phasm/type5/typerouter.py Normal file
View File

@ -0,0 +1,43 @@
from .record import Record
from .typeexpr import (
AtomicType,
TypeApplication,
TypeConstructor,
TypeExpr,
TypeVariable,
)
class TypeRouter[T]:
def when_atomic(self, typ: AtomicType) -> T:
raise NotImplementedError(typ)
def when_application(self, typ: TypeApplication) -> T:
raise NotImplementedError(typ)
def when_constructor(self, typ: TypeConstructor) -> T:
raise NotImplementedError(typ)
def when_record(self, typ: Record) -> T:
raise NotImplementedError(typ)
def when_variable(self, typ: TypeVariable) -> T:
raise NotImplementedError(typ)
def __call__(self, typ: TypeExpr) -> T:
if isinstance(typ, AtomicType):
if isinstance(typ, Record):
return self.when_record(typ)
return self.when_atomic(typ)
if isinstance(typ, TypeApplication):
return self.when_application(typ)
if isinstance(typ, TypeConstructor):
return self.when_constructor(typ)
if isinstance(typ, TypeVariable):
return self.when_variable(typ)
raise NotImplementedError(typ)

128
phasm/type5/unify.py Normal file
View File

@ -0,0 +1,128 @@
from dataclasses import dataclass
from typing import Callable
from .typeexpr import (
AtomicType,
TypeApplication,
TypeConstructor,
TypeExpr,
TypeVariable,
is_concrete,
occurs,
)
@dataclass
class Failure:
"""
Both types are already different - cannot be unified.
"""
msg: str
@dataclass
class Action:
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
raise NotImplementedError
class ActionList(list[Action]):
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
return '{' + ', '.join((x.to_str(type_namer) for x in self)) + '}'
UnifyResult = Failure | ActionList
@dataclass
class ReplaceVariable(Action):
var: TypeVariable
typ: TypeExpr
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
return f'{self.var.name} := {type_namer(self.typ)}'
def unify(lft: TypeExpr, rgt: TypeExpr) -> UnifyResult:
"""
Be warned: This only matches type variables with other variables or types
- it does not apply substituions nor does it validate if the matching
pairs are correct.
TODO: Remove this. It should be part of UnifyTypesConstraint
and should just generate new constraints for applications.
"""
if lft == rgt:
return ActionList()
if lft.kind != rgt.kind:
return Failure("Kind mismatch")
if isinstance(lft, AtomicType) and isinstance(rgt, AtomicType):
return Failure("Not the same type")
if isinstance(lft, AtomicType) and isinstance(rgt, TypeVariable):
return ActionList([ReplaceVariable(rgt, lft)])
if isinstance(lft, AtomicType) and isinstance(rgt, TypeConstructor):
raise NotImplementedError # Should have been caught by kind check above
if isinstance(lft, AtomicType) and isinstance(rgt, TypeApplication):
if is_concrete(rgt):
return Failure("Not the same type")
return Failure("Type shape mismatch")
if isinstance(lft, TypeVariable) and isinstance(rgt, AtomicType):
return unify(rgt, lft)
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeVariable):
return ActionList([ReplaceVariable(lft, rgt)])
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeConstructor):
return ActionList([ReplaceVariable(lft, rgt)])
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeApplication):
if occurs(lft, rgt):
return Failure("One type occurs in the other")
return ActionList([ReplaceVariable(lft, rgt)])
if isinstance(lft, TypeConstructor) and isinstance(rgt, AtomicType):
return unify(rgt, lft)
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeVariable):
return unify(rgt, lft)
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeConstructor):
return Failure("Not the same type constructor")
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeApplication):
return Failure("Not the same type constructor")
if isinstance(lft, TypeApplication) and isinstance(rgt, AtomicType):
return unify(rgt, lft)
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeVariable):
return unify(rgt, lft)
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeConstructor):
return unify(rgt, lft)
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeApplication):
con_res = unify(lft.constructor, rgt.constructor)
if isinstance(con_res, Failure):
return con_res
arg_res = unify(lft.argument, rgt.argument)
if isinstance(arg_res, Failure):
return arg_res
return ActionList(con_res + arg_res)
return Failure('Not implemented')

View File

@ -0,0 +1,44 @@
from __future__ import annotations
import dataclasses
from typing import Callable, Iterable
from ..type5.constrainedexpr import ConstrainedExpr, TypeConstraint
from ..type5.kindexpr import KindExpr
from ..type5.typeexpr import TypeExpr, TypeVariable, instantiate
@dataclasses.dataclass
class TypeClass:
name: str
variables: tuple[TypeVariable, ...]
methods: dict[str, TypeExpr | ConstrainedExpr] = dataclasses.field(default_factory=dict)
operators: dict[str, TypeExpr | ConstrainedExpr] = dataclasses.field(default_factory=dict)
class TypeClassConstraint(TypeConstraint):
__slots__ = ('cls', 'variables', )
def __init__(self, cls: TypeClass, variables: Iterable[TypeVariable]) -> None:
self.cls = cls
self.variables = tuple(variables)
def __str__(self) -> str:
vrs = ' '.join(x.name for x in self.variables)
return f'{self.cls.name} {vrs}'
def __repr__(self) -> str:
vrs = ', '.join(str(x) for x in self.variables)
return f'TypeClassConstraint({self.cls.name}, [{vrs}])'
def instantiate(self, known_map: dict[TypeVariable, TypeVariable], make_variable: Callable[[KindExpr, str], TypeVariable]) -> TypeClassConstraint:
return TypeClassConstraint(
self.cls,
[
# instantiate returns a TypeVariable if you give it a TypeVariable,
# but I can't seem to convince mypy of that.
instantiate(var, known_map, make_variable) # type: ignore
for var in self.variables
]
)

View File

@ -1,9 +1,9 @@
marko==2.1.3
mypy==1.15.0
mypy==1.17.1
pygments==2.19.1
pytest==8.3.5
pytest-integration==0.2.2
ruff==0.11.4
ruff==0.12.7
wasmtime==31.0.0

View File

@ -1,22 +1,18 @@
import os
import struct
import sys
from typing import Any, Callable, Generator, Iterable, List, TextIO, Union
from __future__ import annotations
import os
import sys
from typing import Any, Callable, List, TextIO, Union
from phasm import compiler
from phasm.build import builtins
from phasm.codestyle import phasm_render
from phasm.type3 import types as type3types
from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter
from phasm.wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
WasmTypeNone,
)
from . import runners
from . import memory, runners
DASHES = '-' * 16
@ -111,11 +107,17 @@ class Suite:
runner.interpreter_setup()
runner.interpreter_load(imports)
allocator_generator = memory.Allocator(runner.phasm_ast.build, runner)
# Check if code formatting works
if do_format_check:
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs]
func = runner.phasm_ast.functions[func_name]
assert func.type5 is not None # Type hint
func_args = runner.phasm_ast.build.type5_is_function(func.type5)
assert func_args is not None
func_ret = func_args.pop()
if len(func_args) != len(args):
raise RuntimeError(f'Invalid number of args for {func_name}')
@ -138,11 +140,9 @@ class Suite:
wasm_args.append(arg)
continue
try:
adr = ALLOCATE_MEMORY_STORED_ROUTER((runner, arg), arg_typ)
wasm_args.append(adr)
except NoRouteForTypeException:
raise NotImplementedError(arg_typ, arg)
allocator = allocator_generator(arg_typ)
adr = allocator(arg)
wasm_args.append(adr)
if verbose:
write_header(sys.stderr, 'Memory (pre run)')
@ -151,11 +151,8 @@ class Suite:
result = SuiteResult()
result.returned_value = runner.call(func_name, *wasm_args)
result.returned_value = _load_memory_stored_returned_value(
runner,
func_name,
result.returned_value,
)
extractor = memory.Extractor(runner.phasm_ast.build, runner)(func_ret)
result.returned_value = extractor(result.returned_value)
if verbose:
write_header(sys.stderr, 'Memory (post run)')
@ -165,295 +162,3 @@ class Suite:
def write_header(textio: TextIO, msg: str) -> None:
textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n')
def _write_memory_stored_value(
runner: runners.RunnerBase,
adr: int,
val_typ: type3types.Type3,
val: Any,
) -> int:
try:
adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ)
ptr_type_info = runner.phasm_ast.build.type_info_map['ptr']
to_write = compiler.module_data_primitive(ptr_type_info, adr2)
runner.interpreter_write_memory(adr, to_write)
return runner.phasm_ast.build.type_info_constructed.alloc_size
except NoRouteForTypeException:
val_typ_info = runner.phasm_ast.build.type_info_map[val_typ.name]
to_write = compiler.module_data_primitive(val_typ_info, val)
runner.interpreter_write_memory(adr, to_write)
return len(to_write)
def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> int:
runner, val = attrs
assert isinstance(val, bytes)
adr = runner.call('stdlib.types.__alloc_bytes__', len(val))
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
runner.interpreter_write_memory(adr + 4, val)
return adr
def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_args: tuple[type3types.Type3]) -> int:
runner, val = attrs
da_type, = da_args
if da_type.name == 'u8':
return _allocate_memory_stored_bytes(attrs)
if not isinstance(val, tuple):
raise InvalidArgumentException(f'Expected tuple; got {val!r} instead')
alloc_size = 4 + len(val) * runner.phasm_ast.build.calculate_alloc_size(da_type, True)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
offset = adr
offset += _write_memory_stored_value(runner, offset, runner.phasm_ast.build.types['u32'], len(val))
for val_el_val in val:
offset += _write_memory_stored_value(runner, offset, da_type, val_el_val)
return adr
def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int:
runner, val = attrs
sa_type, sa_len = sa_args
if not isinstance(val, tuple):
raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead')
if sa_len.value != len(val):
raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead')
alloc_size = runner.phasm_ast.build.calculate_alloc_size_static_array(sa_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
offset = adr
for val_el_val in val:
offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val)
return adr
def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, type3types.Type3], ...]) -> int:
runner, val = attrs
assert isinstance(val, dict)
alloc_size = runner.phasm_ast.build.calculate_alloc_size_struct(st_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
offset = adr
for val_el_name, val_el_typ in st_args:
assert val_el_name in val, f'Missing key value {val_el_name}'
val_el_val = val.pop(val_el_name)
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
assert not val, f'Additional values: {list(val)!r}'
return adr
def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args: tuple[type3types.Type3, ...]) -> int:
runner, val = attrs
assert isinstance(val, tuple)
alloc_size = runner.phasm_ast.build.calculate_alloc_size_tuple(tp_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
assert len(val) == len(tp_args)
offset = adr
for val_el_val, val_el_typ in zip(val, tp_args, strict=True):
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr
ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]()
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.dynamic_array, _allocate_memory_stored_dynamic_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.static_array, _allocate_memory_stored_static_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.struct, _allocate_memory_stored_struct)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.tuple_, _allocate_memory_stored_tuple)
def _load_memory_stored_returned_value(
runner: runners.RunnerBase,
func_name: str,
wasm_value: Any,
) -> Any:
ret_type3 = runner.phasm_ast.functions[func_name].returns_type3
if ret_type3.name in runner.phasm_ast.build.type_info_map:
type_info = runner.phasm_ast.build.type_info_map[ret_type3.name]
if type_info.wasm_type is WasmTypeNone:
return None
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
assert isinstance(wasm_value, int), wasm_value
if type_info.signed is None:
# Must be bool type
return 0 != wasm_value
data = wasm_value.to_bytes(8, 'big', signed=True)
data = data[-type_info.alloc_size:]
wasm_value = int.from_bytes(data, 'big', signed=type_info.signed)
return wasm_value
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
assert isinstance(wasm_value, float), wasm_value
return wasm_value
assert isinstance(wasm_value, int), wasm_value
return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
typ_info = runner.phasm_ast.build.type_info_map.get(typ.name)
if typ_info is None:
assert len(inp) == 4
# Note: For applied types, inp should contain a 4 byte pointer
adr = struct.unpack('<I', inp)[0]
return LOAD_FROM_ADDRESS_ROUTER((runner, adr), typ)
assert len(inp) == typ_info.alloc_size
if typ.name == 'u8':
return struct.unpack('<B', inp)[0]
if typ.name == 'u16':
return struct.unpack('<H', inp)[0]
if typ.name == 'u32':
return struct.unpack('<I', inp)[0]
if typ.name == 'u64':
return struct.unpack('<Q', inp)[0]
if typ.name == 'i8':
return struct.unpack('<b', inp)[0]
if typ.name == 'i16':
return struct.unpack('<h', inp)[0]
if typ.name == 'i32':
return struct.unpack('<i', inp)[0]
if typ.name == 'i64':
return struct.unpack('<q', inp)[0]
if typ.name == 'f32':
return struct.unpack('<f', inp)[0]
if typ.name == 'f64':
return struct.unpack('<d', inp)[0]
raise NotImplementedError(typ, inp)
def _load_bytes_from_address(attrs: tuple[runners.RunnerBase, int]) -> bytes:
runner, adr = attrs
sys.stderr.write(f'Reading 0x{adr:08x} bytes\n')
read_bytes = runner.interpreter_read_memory(adr, 4)
bytes_len, = struct.unpack('<I', read_bytes)
adr += 4
return runner.interpreter_read_memory(adr, bytes_len)
def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator[bytes, None, None]:
offset = 0
for size in split_sizes:
yield all_bytes[offset:offset + size]
offset += size
def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_args: tuple[type3types.Type3]) -> Any:
runner, adr = attrs
da_type, = da_args
if da_type.name == 'u8':
return _load_bytes_from_address(attrs)
sys.stderr.write(f'Reading 0x{adr:08x} {da_type:s}[...]\n')
read_bytes = runner.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
adr += 4
arg_size_1 = runner.phasm_ast.build.calculate_alloc_size(da_type, is_member=True)
arg_sizes = [arg_size_1 for _ in range(array_len)] # _split_read_bytes requires one arg per value
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple(
_unpack(runner, da_type, arg_bytes)
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
)
def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> Any:
runner, adr = attrs
sub_typ, len_typ = sa_args
sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n')
sa_len = len_typ.value
arg_size_1 = runner.phasm_ast.build.calculate_alloc_size(sub_typ, is_member=True)
arg_sizes = [arg_size_1 for _ in range(sa_len)] # _split_read_bytes requires one arg per value
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple(
_unpack(runner, sub_typ, arg_bytes)
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
)
def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tuple[tuple[str, type3types.Type3], ...]) -> dict[str, Any]:
runner, adr = attrs
sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
arg_sizes = [
runner.phasm_ast.build.calculate_alloc_size(x, is_member=True)
for _, x in st_args
]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return {
arg_name: _unpack(runner, arg_typ, arg_bytes)
for (arg_name, arg_typ, ), arg_bytes in zip(st_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
}
def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tuple[type3types.Type3, ...]) -> Any:
runner, adr = attrs
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n')
arg_sizes = [
runner.phasm_ast.build.calculate_alloc_size(x, is_member=True)
for x in tp_args
]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple(
_unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(tp_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
)
LOAD_FROM_ADDRESS_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]()
LOAD_FROM_ADDRESS_ROUTER.add(builtins.dynamic_array, _load_dynamic_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.static_array, _load_static_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.struct, _load_struct_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.tuple_, _load_tuple_from_address)

551
tests/integration/memory.py Normal file
View File

@ -0,0 +1,551 @@
import struct
from typing import Any, Protocol
from phasm.build.base import BuildBase
from phasm.build.typerouter import BuildTypeRouter
from phasm.type5.record import Record
from phasm.type5.typeexpr import AtomicType, TypeExpr
from phasm.wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
WasmTypeNone,
)
class MemoryAccess(Protocol):
def call(self, function: str, *args: Any) -> Any:
"""
Use for calling allocator methods inside the WASM environment.
"""
def interpreter_write_memory(self, offset: int, data: bytes) -> None:
"""
Writes bytes directly to WASM environment memory.
Addresses should be generated using allocators via call.
"""
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
"""
Reads bytes directly from WASM environment memory.
"""
class MemorySlice:
__slots__ = ('memory', 'offset', )
def __init__(self, memory: bytes, offset: int) -> None:
self.memory = memory
self.offset = offset
def __call__(self, size: int) -> bytes:
return self.memory[self.offset:self.offset + size]
def __repr__(self) -> str:
return f'MemorySlice({self.memory!r}, {self.offset!r})'
class AllocatorFunc(Protocol):
alloc_size: int
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
"""
Takes a Python value and allocaties it in the given memory
Based on the phasm type.
When the parent already has allocated memory, the store_at_adr is set.
In that case, write your value to the given address, and return it.
"""
class Allocator(BuildTypeRouter[AllocatorFunc]):
__slots__ = ('access', )
access: MemoryAccess
def __init__(self, build: BuildBase[Any], access: MemoryAccess) -> None:
super().__init__(build)
self.access = access
def when_atomic(self, typ: AtomicType) -> AllocatorFunc:
type_info = self.build.type_info_map[typ.name]
if type_info.wasm_type is WasmTypeNone:
raise NotImplementedError
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
if type_info.signed is None:
raise NotImplementedError
return IntAllocator(self.access, type_info.signed, type_info.alloc_size)
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
return FloatAllocator(self.access, type_info.alloc_size)
raise NotImplementedError(typ)
def when_dynamic_array(self, da_arg: TypeExpr) -> AllocatorFunc:
if da_arg.name == 'u8':
return BytesAllocator(self.access)
return DynamicArrayAllocator(self.access, self(da_arg))
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> AllocatorFunc:
return StaticArrayAllocator(self.access, sa_len, self(sa_typ))
def when_struct(self, typ: Record) -> AllocatorFunc:
return StructAllocator(self.access, [(x_nam, self(x_typ)) for x_nam, x_typ in typ.fields])
def when_tuple(self, tp_args: list[TypeExpr]) -> AllocatorFunc:
return TupleAllocator(self.access, list(map(self, tp_args)))
class ExtractorFunc(Protocol):
alloc_size: int
def __call__(self, wasm_value: Any) -> Any:
"""
Takes a WASM value and returns a Python value
Based on the phasm type
"""
class Extractor(BuildTypeRouter[ExtractorFunc]):
__slots__ = ('access', )
access: MemoryAccess
def __init__(self, build: BuildBase[Any], access: MemoryAccess) -> None:
super().__init__(build)
self.access = access
def when_atomic(self, typ: AtomicType) -> ExtractorFunc:
type_info = self.build.type_info_map[typ.name]
if type_info.wasm_type is WasmTypeNone:
return NoneExtractor()
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
if type_info.signed is None:
return BoolExtractor()
return IntExtractor(type_info.signed, type_info.alloc_size)
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
return FloatExtractor(type_info.alloc_size)
raise NotImplementedError(typ)
def when_dynamic_array(self, da_arg: TypeExpr) -> ExtractorFunc:
if da_arg.name == 'u8':
return BytesExtractor(self.access)
return DynamicArrayExtractor(self.access, self(da_arg))
def when_static_array(self, sa_len: int, sa_typ: TypeExpr) -> ExtractorFunc:
return StaticArrayExtractor(self.access, sa_len, self(sa_typ))
def when_struct(self, typ: Record) -> ExtractorFunc:
return StructExtractor(self.access, [(x_nam, self(x_typ)) for x_nam, x_typ in typ.fields])
def when_tuple(self, tp_args: list[TypeExpr]) -> ExtractorFunc:
return TupleExtractor(self.access, list(map(self, tp_args)))
class NoneExtractor:
__slots__ = ('alloc_size', )
alloc_size: int
def __init__(self) -> None:
# Do not set alloc_size, it should not be called
# this will generate an AttributeError
pass
def __call__(self, wasm_value: Any) -> None:
assert wasm_value is None
class BoolExtractor:
__slots__ = ('alloc_size', )
def __init__(self) -> None:
self.alloc_size = 1
def __call__(self, wasm_value: Any) -> bool:
assert isinstance(wasm_value, int), wasm_value
return wasm_value != 0
class IntAllocator:
__slots__ = ('access', 'alloc_size', 'signed', )
def __init__(self, access: MemoryAccess, signed: bool, alloc_size: int) -> None:
self.access = access
self.signed = signed
self.alloc_size = alloc_size
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
if store_at_adr is None:
raise NotImplementedError
assert isinstance(py_value, int), py_value
data = py_value.to_bytes(self.alloc_size, 'little', signed=self.signed)
self.access.interpreter_write_memory(store_at_adr, data)
return store_at_adr
class IntExtractor:
__slots__ = ('alloc_size', 'signed', )
def __init__(self, signed: bool, alloc_size: int) -> None:
self.signed = signed
self.alloc_size = alloc_size
def __call__(self, wasm_value: Any) -> int:
if isinstance(wasm_value, MemorySlice):
# Memory stored int
data = wasm_value(self.alloc_size)
else:
# Int received from the wasm interface
# Work around the fact that phasm has unsigned integers but wasm does not
# Use little endian since that matches with what WASM uses internally
assert isinstance(wasm_value, int), wasm_value
data = wasm_value.to_bytes(8, 'little', signed=True)
data = data[:self.alloc_size]
return int.from_bytes(data, 'little', signed=self.signed)
class PtrAllocator(IntAllocator):
def __init__(self, access: MemoryAccess) -> None:
super().__init__(access, False, 4)
class PtrExtractor(IntExtractor):
def __init__(self) -> None:
super().__init__(False, 4)
FLOAT_LETTER_MAP = {
4: 'f',
8: 'd'
}
class FloatAllocator:
__slots__ = ('access', 'alloc_size', )
def __init__(self, access: MemoryAccess, alloc_size: int) -> None:
self.access = access
self.alloc_size = alloc_size
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
if store_at_adr is None:
raise NotImplementedError
assert isinstance(py_value, float), py_value
data = struct.pack(f'<{FLOAT_LETTER_MAP[self.alloc_size]}', py_value)
self.access.interpreter_write_memory(store_at_adr, data)
return store_at_adr
class FloatExtractor:
__slots__ = ('alloc_size', )
def __init__(self, alloc_size: int) -> None:
self.alloc_size = alloc_size
def __call__(self, wasm_value: Any) -> float:
if isinstance(wasm_value, MemorySlice):
# Memory stored float
data = wasm_value(self.alloc_size)
wasm_value, = struct.unpack(f'<{FLOAT_LETTER_MAP[self.alloc_size]}', data)
assert isinstance(wasm_value, float), wasm_value
return wasm_value
class DynamicArrayAllocator:
__slots__ = ('access', 'alloc_size', 'sub_allocator', )
access: MemoryAccess
alloc_size: int
sub_allocator: AllocatorFunc
def __init__(self, access: MemoryAccess, sub_allocator: AllocatorFunc) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_allocator = sub_allocator
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
if store_at_adr is not None:
raise NotImplementedError
assert isinstance(py_value, tuple), py_value
py_len = len(py_value)
alloc_size = 4 + py_len * self.sub_allocator.alloc_size
adr = self.access.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
PtrAllocator(self.access)(py_len, adr)
for idx, el_value in enumerate(py_value):
offset = adr + 4 + idx * self.sub_allocator.alloc_size
self.sub_allocator(el_value, offset)
return adr
class DynamicArrayExtractor:
__slots__ = ('access', 'alloc_size', 'sub_extractor', )
access: MemoryAccess
alloc_size: int
sub_extractor: ExtractorFunc
def __init__(self, access: MemoryAccess, sub_extractor: ExtractorFunc) -> None:
self.access = access
self.sub_extractor = sub_extractor
def __call__(self, wasm_value: Any) -> Any:
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
# wasm_value must be a pointer
# The first value at said pointer is the length of the array
read_bytes = self.access.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
read_bytes = self.access.interpreter_read_memory(adr + 4, array_len * self.sub_extractor.alloc_size)
return tuple(
self.sub_extractor(MemorySlice(read_bytes, idx * self.sub_extractor.alloc_size))
for idx in range(array_len)
)
class BytesAllocator:
__slots__ = ('access', 'alloc_size', )
access: MemoryAccess
def __init__(self, access: MemoryAccess) -> None:
self.access = access
self.alloc_size = 4 # ptr
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
assert isinstance(py_value, bytes), py_value
adr = self.access.call('stdlib.types.__alloc_bytes__', len(py_value))
assert isinstance(adr, int)
self.access.interpreter_write_memory(adr + 4, py_value)
if store_at_adr is not None:
PtrAllocator(self.access)(adr, store_at_adr)
return adr
class BytesExtractor:
__slots__ = ('access', 'alloc_size', )
access: MemoryAccess
alloc_size: int
def __init__(self, access: MemoryAccess) -> None:
self.access = access
self.alloc_size = 4 # ptr
def __call__(self, wasm_value: Any) -> bytes:
if isinstance(wasm_value, MemorySlice):
wasm_value = PtrExtractor()(wasm_value)
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
# wasm_value must be a pointer
# The first value at said pointer is the length of the array
read_bytes = self.access.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
adr += 4
return self.access.interpreter_read_memory(adr, array_len)
class StaticArrayAllocator:
__slots__ = ('access', 'alloc_size', 'sa_len', 'sub_allocator', )
access: MemoryAccess
alloc_size: int
sa_len: int
sub_allocator: AllocatorFunc
def __init__(self, access: MemoryAccess, sa_len: int, sub_allocator: AllocatorFunc) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sa_len = sa_len
self.sub_allocator = sub_allocator
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
assert isinstance(py_value, tuple), py_value
assert len(py_value) == self.sa_len
alloc_size = self.sa_len * self.sub_allocator.alloc_size
adr = self.access.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
for idx, el_value in enumerate(py_value):
sub_adr = adr + idx * self.sub_allocator.alloc_size
self.sub_allocator(el_value, sub_adr)
if store_at_adr is not None:
PtrAllocator(self.access)(adr, store_at_adr)
return adr
class StaticArrayExtractor:
__slots__ = ('access', 'alloc_size', 'sa_len', 'sub_extractor', )
access: MemoryAccess
alloc_size: int
sa_len: int
sub_extractor: ExtractorFunc
def __init__(self, access: MemoryAccess, sa_len: int, sub_extractor: ExtractorFunc) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sa_len = sa_len
self.sub_extractor = sub_extractor
def __call__(self, wasm_value: Any) -> Any:
if isinstance(wasm_value, MemorySlice):
wasm_value = PtrExtractor()(wasm_value)
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
read_bytes = self.access.interpreter_read_memory(adr, self.sa_len * self.sub_extractor.alloc_size)
return tuple(
self.sub_extractor(MemorySlice(read_bytes, idx * self.sub_extractor.alloc_size))
for idx in range(self.sa_len)
)
class TupleAllocator:
__slots__ = ('access', 'alloc_size', 'sub_allocator_list', )
access: MemoryAccess
alloc_size: int
sub_allocator_list: list[AllocatorFunc]
def __init__(self, access: MemoryAccess, sub_allocator_list: list[AllocatorFunc]) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_allocator_list = sub_allocator_list
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
assert isinstance(py_value, tuple), py_value
total_alloc_size = sum(x.alloc_size for x in self.sub_allocator_list)
adr = self.access.call('stdlib.alloc.__alloc__', total_alloc_size)
assert isinstance(adr, int) # Type int
sub_adr = adr
for sub_allocator, sub_value in zip(self.sub_allocator_list, py_value, strict=True):
sub_allocator(sub_value, sub_adr)
sub_adr += sub_allocator.alloc_size
if store_at_adr is not None:
PtrAllocator(self.access)(adr, store_at_adr)
return adr
class TupleExtractor:
__slots__ = ('access', 'alloc_size', 'sub_extractor_list', )
access: MemoryAccess
alloc_size: int
sub_extractor_list: list[ExtractorFunc]
def __init__(self, access: MemoryAccess, sub_extractor_list: list[ExtractorFunc]) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_extractor_list = sub_extractor_list
def __call__(self, wasm_value: Any) -> tuple[Any]:
if isinstance(wasm_value, MemorySlice):
wasm_value = PtrExtractor()(wasm_value)
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
total_alloc_size = sum(x.alloc_size for x in self.sub_extractor_list)
read_bytes = self.access.interpreter_read_memory(adr, total_alloc_size)
result = []
offset = 0
for sub_extractor in self.sub_extractor_list:
result.append(sub_extractor(MemorySlice(read_bytes, offset)))
offset += sub_extractor.alloc_size
return tuple(result)
class StructAllocator:
__slots__ = ('access', 'alloc_size', 'sub_allocator_list', )
access: MemoryAccess
alloc_size: int
sub_allocator_list: list[tuple[str, AllocatorFunc]]
def __init__(self, access: MemoryAccess, sub_allocator_list: list[tuple[str, AllocatorFunc]]) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_allocator_list = sub_allocator_list
def __call__(self, py_value: Any, store_at_adr: int | None = None) -> int:
assert isinstance(py_value, dict), py_value
total_alloc_size = sum(x.alloc_size for _, x in self.sub_allocator_list)
adr = self.access.call('stdlib.alloc.__alloc__', total_alloc_size)
assert isinstance(adr, int) # Type int
sub_adr = adr
for field_name, sub_allocator in self.sub_allocator_list:
sub_value = py_value[field_name]
sub_allocator(sub_value, sub_adr)
sub_adr += sub_allocator.alloc_size
if store_at_adr is not None:
PtrAllocator(self.access)(adr, store_at_adr)
return adr
class StructExtractor:
__slots__ = ('access', 'alloc_size', 'sub_extractor_list', )
access: MemoryAccess
alloc_size: int
sub_extractor_list: list[tuple[str, ExtractorFunc]]
def __init__(self, access: MemoryAccess, sub_extractor_list: list[tuple[str, ExtractorFunc]]) -> None:
self.access = access
self.alloc_size = 4 # ptr
self.sub_extractor_list = sub_extractor_list
def __call__(self, wasm_value: Any) -> dict[str, Any]:
if isinstance(wasm_value, MemorySlice):
wasm_value = PtrExtractor()(wasm_value)
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
total_alloc_size = sum(x.alloc_size for _, x in self.sub_extractor_list)
read_bytes = self.access.interpreter_read_memory(adr, total_alloc_size)
result = {}
offset = 0
for field_name, sub_extractor in self.sub_extractor_list:
result[field_name] = sub_extractor(MemorySlice(read_bytes, offset))
offset += sub_extractor.alloc_size
return result

View File

@ -10,7 +10,7 @@ from phasm import ourlang, wasm
from phasm.compiler import phasm_compile
from phasm.optimise.removeunusedfuncs import removeunusedfuncs
from phasm.parser import phasm_parse
from phasm.type3.entry import phasm_type3
from phasm.type5.solver import phasm_type5
from phasm.wasmgenerator import Generator as WasmGenerator
Imports = Optional[Dict[str, Callable[[Any], Any]]]
@ -39,7 +39,7 @@ class RunnerBase:
Parses the Phasm code into an AST
"""
self.phasm_ast = phasm_parse(self.phasm_code)
phasm_type3(self.phasm_ast, verbose=verbose)
phasm_type5(self.phasm_ast, verbose=verbose)
def compile_ast(self) -> None:
"""

View File

@ -61,15 +61,15 @@ CONSTANT: (u32, ) = $VAL0
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error(
'Tuple element count mismatch',
'The given literal must fit the expected type',
)
expect_type_error('Tuple element count mismatch')
elif TYPE_NAME == 'bytes':
expect_type_error('Cannot convert from literal bytes')
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
expect_type_error('Cannot convert from literal float')
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
expect_type_error('Cannot convert from literal integer')
else:
expect_type_error(
'Must be tuple',
'The given literal must fit the expected type',
)
expect_type_error('Not the same type')
```
# function_result_is_literal_ok
@ -114,20 +114,17 @@ def testEntry() -> i32:
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error(
'Mismatch between applied types argument count',
'The type of a tuple is a combination of its members',
)
expect_type_error('Tuple element count mismatch')
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value returned from function constant should match its return type',
)
expect_type_error('Not the same type')
elif TYPE_NAME == 'bytes':
expect_type_error('Cannot convert from literal bytes')
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
expect_type_error('Cannot convert from literal float')
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
expect_type_error('Cannot convert from literal integer')
else:
expect_type_error(
'Must be tuple',
'The given literal must fit the expected type',
)
expect_type_error('Not the same type')
```
# function_result_is_module_constant_ok
@ -175,16 +172,7 @@ def testEntry() -> i32:
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value returned from function constant should match its return type',
)
else:
expect_type_error(
TYPE_NAME + ' must be (u32, ) instead',
'The type of the value returned from function constant should match its return type',
)
expect_type_error('Not the same type')
```
# function_result_is_arg_ok
@ -226,16 +214,7 @@ def select(x: $TYPE) -> (u32, ):
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value returned from function select should match its return type',
)
else:
expect_type_error(
TYPE_NAME + ' must be (u32, ) instead',
'The type of the value returned from function select should match its return type',
)
expect_type_error('Not the same type')
```
# function_arg_literal_ok
@ -274,21 +253,17 @@ def testEntry() -> i32:
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error(
'Mismatch between applied types argument count',
# FIXME: Shouldn't this be the same as for the else statement?
'The type of a tuple is a combination of its members',
)
expect_type_error('Tuple element count mismatch')
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value passed to argument 0 of function helper should match the type of that argument',
)
expect_type_error('Not the same type')
elif TYPE_NAME == 'bytes':
expect_type_error('Cannot convert from literal bytes')
elif TYPE_NAME == 'f32' or TYPE_NAME == 'f64':
expect_type_error('Cannot convert from literal float')
elif TYPE_NAME == 'i32' or TYPE_NAME == 'i64' or TYPE_NAME == 'u32' or TYPE_NAME == 'u64':
expect_type_error('Cannot convert from literal integer')
else:
expect_type_error(
'Must be tuple',
'The given literal must fit the expected type',
)
expect_type_error('Not the same type')
```
# function_arg_module_constant_def_ok
@ -330,14 +305,8 @@ def testEntry() -> i32:
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value passed to argument 0 of function helper should match the type of that argument',
)
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error('Not the same type constructor')
else:
expect_type_error(
TYPE_NAME + ' must be (u32, ) instead',
'The type of the value passed to argument 0 of function helper should match the type of that argument',
)
expect_type_error('Not the same type')
```

View File

@ -41,11 +41,9 @@ def generate_assertion_expect(result, arg, given=None):
result.append('result = Suite(code_py).run_code(' + ', '.join(repr(x) for x in given) + ')')
result.append(f'assert {repr(arg)} == result.returned_value')
def generate_assertion_expect_type_error(result, error_msg, error_comment = None):
result.append('with pytest.raises(Type3Exception) as exc_info:')
def generate_assertion_expect_type_error(result, error_msg):
result.append(f'with pytest.raises(Type5SolverException, match={error_msg!r}):')
result.append(' Suite(code_py).run_code()')
result.append(f'assert {repr(error_msg)} == exc_info.value.args[0][0].msg')
result.append(f'assert {repr(error_comment)} == exc_info.value.args[0][0].comment')
def json_does_not_support_byte_or_tuple_values_fix(inp: Any):
if isinstance(inp, (int, float, )):
@ -98,7 +96,7 @@ def generate_code(markdown, template, settings):
print('"""')
print('import pytest')
print()
print('from phasm.type3.entry import Type3Exception')
print('from phasm.type5.solver import Type5SolverException')
print()
print('from ..helpers import Suite')
print()

View File

@ -3,6 +3,21 @@ import pytest
from ..helpers import Suite
@pytest.mark.integration_test
def test_call_nullary():
code_py = """
def helper() -> i32:
return 3
@exported
def testEntry() -> i32:
return helper()
"""
result = Suite(code_py).run_code()
assert 3 == result.returned_value
@pytest.mark.integration_test
def test_call_pre_defined():
code_py = """

View File

@ -1,5 +1,7 @@
import pytest
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -70,3 +72,30 @@ def testEntry(a: i32, b: i32) -> i32:
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_if_type_invalid():
code_py = """
@exported
def testEntry(a: i32) -> i32:
if a:
return 1
return 0
"""
with pytest.raises(Type5SolverException, match='i32 ~ bool'):
Suite(code_py).run_code(1)
@pytest.mark.integration_test
def test_if_type_ok():
code_py = """
@exported
def testEntry(a: bool) -> i32:
if a:
return 1
return 0
"""
assert 1 == Suite(code_py).run_code(1).returned_value

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -69,7 +69,7 @@ def testEntry(x: u32) -> u8:
def helper(mul: int) -> int:
return 4238 * mul
with pytest.raises(Type3Exception, match=r'u32 must be u8 instead'):
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code(
imports={
'helper': helper,

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -15,7 +15,7 @@ def testEntry() -> u8:
return CONSTANT
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
with pytest.raises(Type5SolverException, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -26,5 +26,5 @@ def testEntry() -> u8:
return 1000
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
with pytest.raises(Type5SolverException, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code()

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -78,11 +78,29 @@ def testEntry() -> i32:
assert 42 == result.returned_value
@pytest.mark.integration_test
def test_sof_wrong_argument_type():
def test_sof_function_with_wrong_argument_type_use():
code_py = """
def double(left: f32) -> f32:
def double(left: i32) -> i32:
return left * 2
def action(applicable: Callable[i32, i32], left: f32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(double, 13.0)
"""
match = r'Callable\[i32, i32\] ~ Callable\[f32, [^]]+\]'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_function_with_wrong_argument_type_pass():
code_py = """
def double(left: f32) -> i32:
return truncate(left) * 2
def action(applicable: Callable[i32, i32], left: i32) -> i32:
return applicable(left)
@ -91,11 +109,12 @@ def testEntry() -> i32:
return action(double, 13)
"""
with pytest.raises(Type3Exception, match=r'Callable\[f32, f32\] must be Callable\[i32, i32\] instead'):
match = r'Callable\[Callable\[i32, i32\], i32, i32\] ~ Callable\[Callable\[f32, i32\], p_[0-9]+, [^]]+\]'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_wrong_return():
def test_sof_function_with_wrong_return_type_use():
code_py = """
def double(left: i32) -> i32:
return left * 2
@ -103,17 +122,34 @@ def double(left: i32) -> i32:
def action(applicable: Callable[i32, i32], left: i32) -> f32:
return applicable(left)
@exported
def testEntry() -> f32:
return action(double, 13)
"""
with pytest.raises(Type5SolverException, match='f32 ~ i32'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_function_with_wrong_return_type_pass():
code_py = """
def double(left: i32) -> f32:
return convert(left) * 2
def action(applicable: Callable[i32, i32], left: i32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(double, 13)
"""
with pytest.raises(Type3Exception, match=r'f32 must be i32 instead'):
match = r'Callable\[Callable\[i32, i32\], i32, i32\] ~ Callable\[Callable\[i32, f32\], p_[0-9]+, [^]]+\]'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.skip('FIXME: Probably have the remainder be the a function type')
def test_sof_wrong_not_enough_args_call():
def test_sof_not_enough_args_use():
code_py = """
def add(left: i32, right: i32) -> i32:
return left + right
@ -126,11 +162,11 @@ def testEntry() -> i32:
return action(add, 13)
"""
with pytest.raises(Type3Exception, match=r'f32 must be i32 instead'):
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_wrong_not_enough_args_refere():
def test_sof_not_enough_args_pass():
code_py = """
def double(left: i32) -> i32:
return left * 2
@ -143,12 +179,12 @@ def testEntry() -> i32:
return action(double, 13, 14)
"""
with pytest.raises(Type3Exception, match=r'Callable\[i32, i32\] must be Callable\[i32, i32, i32\] instead'):
match = r'Callable\[Callable\[i32, i32, i32\], i32, i32, i32\] ~ Callable\[Callable\[i32, i32\], p_[0-9]+, p_[0-9]+, p_[0-9]+\]'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.skip('FIXME: Probably have the remainder be the a function type')
def test_sof_wrong_too_many_args_call():
def test_sof_too_many_args_use():
code_py = """
def thirteen() -> i32:
return 13
@ -161,22 +197,24 @@ def testEntry() -> i32:
return action(thirteen, 13)
"""
with pytest.raises(Type3Exception, match=r'f32 must be i32 instead'):
Suite(code_py).run_code()
match = r'Callable\[i32\] ~ Callable\[i32, p_[0-9]+\]'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code(verbose=True)
@pytest.mark.integration_test
def test_sof_wrong_too_many_args_refere():
def test_sof_too_many_args_pass():
code_py = """
def double(left: i32) -> i32:
return left * 2
def action(applicable: Callable[i32]) -> i32:
def action(applicable: Callable[i32], left: i32, right: i32) -> i32:
return applicable()
@exported
def testEntry() -> i32:
return action(double)
return action(double, 13, 14)
"""
with pytest.raises(Type3Exception, match=r'Callable\[i32, i32\] must be Callable\[i32\] instead'):
match = r'Callable\[Callable\[i32\], i32, i32, i32\] ~ Callable\[Callable\[i32, i32\], p_[0-9]+, p_[0-9]+, p_[0-9]+\]'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code()

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -15,7 +15,7 @@ def testEntry() -> i32:
return 0
"""
with pytest.raises(Type3Exception, match='Member count mismatch'):
with pytest.raises(Type5SolverException, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -28,7 +28,7 @@ def testEntry() -> i32:
return 0
"""
with pytest.raises(Type3Exception, match='Member count mismatch'):
with pytest.raises(Type5SolverException, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -1,7 +1,7 @@
import pytest
from phasm.exceptions import StaticError
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -76,7 +76,7 @@ class CheckedValueRed:
CONST: CheckedValueBlue = CheckedValueRed(1)
"""
with pytest.raises(Type3Exception, match='CheckedValueBlue must be CheckedValueRed instead'):
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -91,7 +91,20 @@ class CheckedValueRed:
CONST: (CheckedValueBlue, u32, ) = (CheckedValueRed(1), 16, )
"""
with pytest.raises(Type3Exception, match='CheckedValueBlue must be CheckedValueRed instead'):
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_type_mismatch_struct_call_arg_count():
code_py = """
class CheckedValue:
value1: i32
value2: i32
CONST: CheckedValue = CheckedValue(1)
"""
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -105,7 +118,7 @@ def testEntry(arg: Struct) -> (i32, i32, ):
return arg.param
"""
with pytest.raises(Type3Exception, match=type_ + r' must be \(i32, i32, \) instead'):
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -132,7 +145,6 @@ class f32:
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.skip(reason='FIXME: See constraintgenerator.py for AccessStructMember')
def test_struct_not_accessible():
code_py = """
@exported
@ -140,7 +152,67 @@ def testEntry(x: u8) -> u8:
return x.y
"""
with pytest.raises(Type3Exception, match='u8 is not struct'):
with pytest.raises(Type5SolverException, match='Must be a struct'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_struct_does_not_have_field():
code_py = """
class CheckedValue:
value: i32
@exported
def testEntry(x: CheckedValue) -> u8:
return x.y
"""
with pytest.raises(Type5SolverException, match='Must have a field with this name'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_struct_literal_does_not_fit():
code_py = """
class CheckedValue:
value: i32
@exported
def testEntry() -> CheckedValue:
return 14
"""
with pytest.raises(Type5SolverException, match='Cannot convert from literal integer'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_struct_wrong_struct():
code_py = """
class CheckedValue:
value: i32
class MessedValue:
value: i32
@exported
def testEntry() -> CheckedValue:
return MessedValue(14)
"""
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_struct_wrong_arg_count():
code_py = """
class CheckedValue:
value1: i32
value2: i32
@exported
def testEntry() -> CheckedValue:
return CheckedValue(14)
"""
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -1,7 +1,7 @@
import pytest
import wasmtime
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -71,7 +71,7 @@ def testEntry(f: {type_}) -> u32:
return f[0]
"""
with pytest.raises(Type3Exception, match='u32 must be u8 instead'):
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code(in_put)
@pytest.mark.integration_test
@ -82,7 +82,7 @@ def testEntry(x: (u8, u32, u64), y: u8) -> u64:
return x[y]
"""
with pytest.raises(Type3Exception, match='Must index with integer literal'):
with pytest.raises(Type5SolverException, match='Must index with integer literal'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -93,7 +93,7 @@ def testEntry(x: (u8, u32, u64)) -> u64:
return x[0.0]
"""
with pytest.raises(Type3Exception, match='Must index with integer literal'):
with pytest.raises(Type5SolverException, match='Must index with integer literal'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -109,7 +109,7 @@ def testEntry(x: {type_}) -> u8:
return x[-1]
"""
with pytest.raises(Type3Exception, match='Tuple index out of range'):
with pytest.raises(Type5SolverException, match='Tuple index out of range'):
Suite(code_py).run_code(in_put)
@pytest.mark.integration_test
@ -120,7 +120,7 @@ def testEntry(x: (u8, u32, u64)) -> u64:
return x[4]
"""
with pytest.raises(Type3Exception, match='Tuple index out of range'):
with pytest.raises(Type5SolverException, match='Tuple index out of range'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -147,5 +147,5 @@ def testEntry(x: u8) -> u8:
return x[0]
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Subscriptable u8'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -41,7 +41,7 @@ def test_assign_to_tuple_with_tuple():
CONSTANT: (u32, ) = 0
"""
with pytest.raises(Type3Exception, match='Must be tuple'):
with pytest.raises(Type5SolverException, match='Cannot convert from literal integer'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -50,7 +50,7 @@ def test_tuple_constant_too_few_values():
CONSTANT: (u32, u8, u8, ) = (24, 57, )
"""
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
with pytest.raises(Type5SolverException, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -59,7 +59,7 @@ def test_tuple_constant_too_many_values():
CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, )
"""
with pytest.raises(Type3Exception, match='Tuple element count mismatch'):
with pytest.raises(Type5SolverException, match='Tuple element count mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -68,7 +68,7 @@ def test_tuple_constant_type_mismatch():
CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
with pytest.raises(Type5SolverException, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -1,7 +1,7 @@
import pytest
import wasmtime
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -20,7 +20,7 @@ def testEntry(x: Foo) -> Baz:
return convert(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Convertable Foo Baz'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -58,7 +58,7 @@ def testEntry(x: Foo) -> Baz:
return truncate(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Convertable Baz Foo'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -25,11 +25,11 @@ class Foo:
val: i32
@exported
def testEntry(x: Foo, y: Foo) -> Foo:
def testEntry(x: Foo, y: Foo) -> bool:
return x == y
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Eq Foo'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -107,11 +107,11 @@ class Foo:
val: i32
@exported
def testEntry(x: Foo, y: Foo) -> Foo:
def testEntry(x: Foo, y: Foo) -> bool:
return x != y
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Eq Foo'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -34,7 +34,7 @@ def testEntry(x: Foo) -> Baz:
return extend(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Extendable Foo Baz'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -1,8 +1,24 @@
import pytest
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@pytest.mark.integration_test
def test_sqrt_not_implemented():
code_py = """
class Foo:
val: i32
@exported
def testEntry(x: Foo) -> Foo:
return sqrt(x)
"""
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['f32', 'f64'])
def test_floating_sqrt(type_):

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
from .test_natnum import FLOAT_TYPES, INT_TYPES
@ -36,7 +36,7 @@ def testEntry(x: Foo[4]) -> Foo:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -129,7 +129,7 @@ def testEntry(b: i32[{typ_arg}]) -> i32:
"""
suite = Suite(code_py)
result = suite.run_code(tuple(in_put), with_traces=True, do_format_check=False)
result = suite.run_code(tuple(in_put))
assert exp_result == result.returned_value
@pytest.mark.integration_test
@ -168,9 +168,12 @@ def testEntry(x: {in_typ}, y: i32, z: i64[3]) -> i32:
return foldl(x, y, z)
"""
r_in_typ = in_typ.replace('[', '\\[').replace(']', '\\]')
match = {
'i8': 'Type shape mismatch',
'i8[3]': 'Kind mismatch',
}
with pytest.raises(Type3Exception, match=f'{r_in_typ} must be a function instead'):
with pytest.raises(Type5SolverException, match=match[in_typ]):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -184,7 +187,7 @@ def testEntry(i: i64, l: i64[3]) -> i64:
return foldr(foo, i, l)
"""
with pytest.raises(Type3Exception, match=r'Callable\[i64, i64, i64\] must be Callable\[i32, i64, i64\] instead'):
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -195,7 +198,7 @@ def testEntry(x: i32[5]) -> f64:
return sum(x)
"""
with pytest.raises(Type3Exception, match='f64 must be i32 instead'):
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code((4, 5, 6, 7, 8, ))
@pytest.mark.integration_test
@ -206,16 +209,16 @@ def testEntry(x: i32) -> i32:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable i32.*i32 must be a constructed type instead'):
with pytest.raises(Type5SolverException, match='Type shape mismatch'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_foldable_not_foldable():
code_py = """
@exported
def testEntry(x: (i32, u32, )) -> i32:
def testEntry(x: (u32, i32, )) -> i32:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable tuple'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()

View File

@ -1,5 +1,7 @@
import pytest
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
TYPE_LIST = ['f32', 'f64']
@ -57,6 +59,26 @@ TEST_LIST = [
('nearest(-5.5)', -6.0, ),
]
@pytest.mark.integration_test
@pytest.mark.parametrize('method', [
'ceil',
'floor',
'trunc',
'nearest',
])
def test_fractional_not_implemented(method):
code_py = f"""
class Foo:
val: i32
@exported
def testEntry(x: Foo) -> Foo:
return {method}(x)
"""
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_LIST)
@pytest.mark.parametrize('test_in,test_out', TEST_LIST)

View File

@ -1,9 +1,29 @@
import pytest
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
TYPE_LIST = ['u32', 'u64', 'i32', 'i64']
@pytest.mark.integration_test
@pytest.mark.parametrize('operator', [
'//',
'%',
])
def test_integral_not_implemented(operator):
code_py = f"""
class Foo:
val: i32
@exported
def testEntry(x: Foo, y: Foo) -> Foo:
return x {operator} y
"""
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_', TYPE_LIST)
def test_integral_div_ok(type_):

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -27,7 +27,7 @@ def testEntry(x: Foo, y: Foo) -> Foo:
return x + y
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -2,7 +2,7 @@ import math
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -21,7 +21,7 @@ def testEntry(x: Foo) -> Baz:
return promote(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Promotable Foo Baz'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@ -54,7 +54,7 @@ def testEntry(x: Foo) -> Baz:
return demote(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Promotable Baz Foo'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -1,6 +1,6 @@
import pytest
from phasm.type3.entry import Type3Exception
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@ -19,7 +19,7 @@ def testEntry(x: Foo) -> Baz:
return reinterpret(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Reinterpretable Foo Baz'):
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test

View File

@ -1,8 +1,21 @@
import pytest
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@pytest.mark.integration_test
def test_fractional_not_implemented():
code_py = """
@exported
def testEntry(x: (i32, )) -> u32:
return len(x)
"""
with pytest.raises(Type5SolverException, match='Missing type class instance'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put, exp_result', [
('bytes', b'Hello, world!', 13),