From 6a1f4fc010de9442ec09d1285f80f13bf462c61c Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sat, 12 Jul 2025 11:31:05 +0200 Subject: [PATCH] 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. --- Makefile | 2 +- TODO.md | 6 + phasm/__main__.py | 4 +- phasm/build/base.py | 486 +++++++----- phasm/build/builtins.py | 26 - phasm/build/default.py | 45 +- phasm/build/typeclasses/bits.py | 105 +-- phasm/build/typeclasses/convertable.py | 69 +- phasm/build/typeclasses/eq.py | 69 +- phasm/build/typeclasses/extendable.py | 85 ++- phasm/build/typeclasses/floating.py | 34 +- phasm/build/typeclasses/foldable.py | 208 +++--- phasm/build/typeclasses/fractional.py | 67 +- phasm/build/typeclasses/integral.py | 49 +- phasm/build/typeclasses/intnum.py | 47 +- phasm/build/typeclasses/natnum.py | 101 ++- phasm/build/typeclasses/ord.py | 168 +++-- phasm/build/typeclasses/promotable.py | 41 +- phasm/build/typeclasses/reinterpretable.py | 45 +- phasm/build/typeclasses/sized.py | 55 +- phasm/build/typeclasses/subscriptable.py | 73 +- phasm/build/typeclassregistry.py | 86 +++ phasm/build/typerouter.py | 150 ++++ phasm/codestyle.py | 53 +- phasm/compiler.py | 411 +++++----- phasm/ourlang.py | 236 +++--- phasm/parser.py | 152 ++-- phasm/type3/constraints.py | 704 ------------------ phasm/type3/constraintsgenerator.py | 334 --------- phasm/type3/entry.py | 179 ----- phasm/type3/functions.py | 194 ----- phasm/type3/placeholders.py | 66 -- phasm/type3/routers.py | 142 ---- phasm/type3/typeclasses.py | 104 --- phasm/type3/types.py | 290 -------- phasm/{type3 => type5}/__init__.py | 0 phasm/type5/constrainedexpr.py | 64 ++ phasm/type5/constraints.py | 455 +++++++++++ phasm/type5/fromast.py | 280 +++++++ phasm/type5/kindexpr.py | 57 ++ phasm/type5/record.py | 43 ++ phasm/type5/solver.py | 131 ++++ phasm/type5/typeexpr.py | 201 +++++ phasm/type5/typerouter.py | 43 ++ phasm/type5/unify.py | 128 ++++ phasm/typeclass/__init__.py | 43 ++ requirements.txt | 4 +- tests/integration/helpers.py | 331 +------- tests/integration/memory.py | 551 ++++++++++++++ tests/integration/runners.py | 4 +- tests/integration/test_lang/generator.md | 93 +-- tests/integration/test_lang/generator.py | 8 +- .../test_lang/test_function_calls.py | 15 + tests/integration/test_lang/test_if.py | 29 + tests/integration/test_lang/test_imports.py | 4 +- tests/integration/test_lang/test_literals.py | 6 +- .../test_lang/test_second_order_functions.py | 76 +- .../test_lang/test_static_array.py | 6 +- tests/integration/test_lang/test_struct.py | 84 ++- .../test_lang/test_subscriptable.py | 14 +- tests/integration/test_lang/test_tuple.py | 10 +- .../test_typeclasses/test_convertable.py | 6 +- tests/integration/test_typeclasses/test_eq.py | 10 +- .../test_typeclasses/test_extendable.py | 4 +- .../test_typeclasses/test_floating.py | 16 + .../test_typeclasses/test_foldable.py | 23 +- .../test_typeclasses/test_fractional.py | 22 + .../test_typeclasses/test_integral.py | 20 + .../test_typeclasses/test_natnum.py | 4 +- .../test_typeclasses/test_promotable.py | 6 +- .../test_typeclasses/test_reinterpretable.py | 4 +- .../test_typeclasses/test_sized.py | 14 + 72 files changed, 4080 insertions(+), 3615 deletions(-) delete mode 100644 phasm/build/builtins.py create mode 100644 phasm/build/typeclassregistry.py create mode 100644 phasm/build/typerouter.py delete mode 100644 phasm/type3/constraints.py delete mode 100644 phasm/type3/constraintsgenerator.py delete mode 100644 phasm/type3/entry.py delete mode 100644 phasm/type3/functions.py delete mode 100644 phasm/type3/placeholders.py delete mode 100644 phasm/type3/routers.py delete mode 100644 phasm/type3/typeclasses.py delete mode 100644 phasm/type3/types.py rename phasm/{type3 => type5}/__init__.py (100%) create mode 100644 phasm/type5/constrainedexpr.py create mode 100644 phasm/type5/constraints.py create mode 100644 phasm/type5/fromast.py create mode 100644 phasm/type5/kindexpr.py create mode 100644 phasm/type5/record.py create mode 100644 phasm/type5/solver.py create mode 100644 phasm/type5/typeexpr.py create mode 100644 phasm/type5/typerouter.py create mode 100644 phasm/type5/unify.py create mode 100644 phasm/typeclass/__init__.py create mode 100644 tests/integration/memory.py diff --git a/Makefile b/Makefile index 748a0f4..40e394b 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/TODO.md b/TODO.md index 0757ab3..ec58fb0 100644 --- a/TODO.md +++ b/TODO.md @@ -22,3 +22,9 @@ - Try to implement the min and max functions using select - Read https://bytecodealliance.org/articles/multi-value-all-the-wasm + +- Implement type class 'inheritance' +- Remove FunctionInstance, replace with a substitutions dict + - See phft2 in fromast.py:expression_function_call +- Move unify into the typeconstraints (or other way around) - it's done on two levels now (partly in solver) +- Rework type classes - already started on a separate dir for those, but quite a few things are still in other places. diff --git a/phasm/__main__.py b/phasm/__main__.py index a4e6a31..4c7e2b1 100644 --- a/phasm/__main__.py +++ b/phasm/__main__.py @@ -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() diff --git a/phasm/build/base.py b/phasm/build/base.py index 579c147..316af75 100644 --- a/phasm/build/base.py +++ b/phasm/build/base.py @@ -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 NamedTuple, Protocol, 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 .typeclassregistry import TypeClassRegistry +from .typerouter import TypeAllocSize, TypeName TypeInfo = NamedTuple('TypeInfo', [ # Name of the type @@ -52,16 +36,24 @@ TypeInfo = NamedTuple('TypeInfo', [ class MissingImplementationWarning(Warning): pass +class InternalImplementedMethodProtocol[G](Protocol): + def __call__(self, g: G, tv_map: dict[str, type5typeexpr.TypeExpr]) -> None: + pass + + 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 +61,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 +157,289 @@ 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, TypeClassRegistry[bool]] """ 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, TypeClassRegistry[InternalImplementedMethodProtocol[G]]]] """ Methods that are available without explicit import. """ - operators: dict[str, Type3ClassMethod] + operators: dict[str, tuple[type5typeexpr.TypeExpr | type5constrainedexpr.ConstrainedExpr, TypeClassRegistry[InternalImplementedMethodProtocol[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] = TypeClassRegistry() - 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, TypeClassRegistry(), ) + + 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, TypeClassRegistry(), ) 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, InternalImplementedMethodProtocol[G]] = {}, + operators: dict[str, InternalImplementedMethodProtocol[G]] = {}, + ) -> None: + self.type_class_instances[cls.name].add(args, True) - 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_typ, mtd_rtr = self.methods[mtd_nam] - # 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) + if isinstance(mtd_typ, type5constrainedexpr.ConstrainedExpr): + mtd_typ = mtd_typ.expr - 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 + for var, rep_expr in zip(cls.variables, args, strict=True): + mtd_typ = type5typeexpr.replace_variable(mtd_typ, var, rep_expr) - try: - generator = methods[method_name] - except KeyError: - warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ))) + mtd_rtr.add((mtd_typ, ), mtd_imp) + + for opr_nam, opr_imp in operators.items(): + mtd_typ, opr_rtr = self.operators[opr_nam] + + if isinstance(mtd_typ, type5constrainedexpr.ConstrainedExpr): + mtd_typ = mtd_typ.expr + + for var, rep_expr in zip(cls.variables, args, strict=True): + mtd_typ = type5typeexpr.replace_variable(mtd_typ, var, rep_expr) + + opr_rtr.add((mtd_typ, ), opr_imp) + + def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr: + if not args: + raise TypeError("Functions must at least have a return type") + + 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, + ) diff --git a/phasm/build/builtins.py b/phasm/build/builtins.py deleted file mode 100644 index 5dd65d5..0000000 --- a/phasm/build/builtins.py +++ /dev/null @@ -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)) - diff --git a/phasm/build/default.py b/phasm/build/default.py index 2cbcd8f..952f9ab 100644 --- a/phasm/build/default.py +++ b/phasm/build/default.py @@ -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, @@ -39,26 +32,13 @@ from .typeclasses import ( 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 = [ diff --git a/phasm/build/typeclasses/bits.py b/phasm/build/typeclasses/bits.py index b1883ed..f758fc8 100644 --- a/phasm/build/typeclasses/bits.py +++ b/phasm/build/typeclasses/bits.py @@ -1,150 +1,171 @@ """ 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( + variables={a}, + expr=build.type5_make_function([a, u32, a]), + constraints=(has_bits_a, ), + ) + + fn_a_a_a = ConstrainedExpr( + variables={a}, + 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() diff --git a/phasm/build/typeclasses/convertable.py b/phasm/build/typeclasses/convertable.py index a5f4662..d5488df 100644 --- a/phasm/build/typeclasses/convertable.py +++ b/phasm/build/typeclasses/convertable.py @@ -3,87 +3,106 @@ 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( + variables={a, b}, + expr=build.type5_make_function([a, b]), + constraints=(has_convertable_a_b, ), + ) + + fn_b_a = ConstrainedExpr( + variables={a, b}, + 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() diff --git a/phasm/build/typeclasses/eq.py b/phasm/build/typeclasses/eq.py index 7acc6b4..b4b6105 100644 --- a/phasm/build/typeclasses/eq.py +++ b/phasm/build/typeclasses/eq.py @@ -1,103 +1,116 @@ """ 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( + variables={a}, + 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() diff --git a/phasm/build/typeclasses/extendable.py b/phasm/build/typeclasses/extendable.py index 5d414f3..2ba209a 100644 --- a/phasm/build/typeclasses/extendable.py +++ b/phasm/build/typeclasses/extendable.py @@ -2,145 +2,164 @@ 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( + variables={a, b}, + expr=build.type5_make_function([a, b]), + constraints=(has_extendable_a_b, ), + ) + + fn_b_a = ConstrainedExpr( + variables={a, b}, + 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() diff --git a/phasm/build/typeclasses/floating.py b/phasm/build/typeclasses/floating.py index dc838e8..b3854fd 100644 --- a/phasm/build/typeclasses/floating.py +++ b/phasm/build/typeclasses/floating.py @@ -1,31 +1,45 @@ """ 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( + variables={a}, + 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') diff --git a/phasm/build/typeclasses/foldable.py b/phasm/build/typeclasses/foldable.py index 608315a..afea971 100644 --- a/phasm/build/typeclasses/foldable.py +++ b/phasm/build/typeclasses/foldable.py @@ -1,34 +1,62 @@ """ The Foldable type class is defined for when a value iterated over. """ +from __future__ import annotations + from typing import Any -from ...type3.functions import ( - Constraint_TypeClassInstanceExists, - TypeConstructorVariable, - make_typevar, +from ...type5.constrainedexpr import ConstrainedExpr +from ...type5.kindexpr import Nat, Star +from ...type5.typeexpr import ( + TypeApplication, + TypeExpr, + TypeLevelNat, + TypeVariable, + replace_variable, ) -from ...type3.routers import TypeVariableLookup -from ...type3.typeclasses import Type3Class -from ...type3.types import IntType3, Type3 +from ...typeclass import TypeClass, TypeClassConstraint from ...wasmgenerator import Generator as WasmGenerator -from ..base import BuildBase +from ..base import BuildBase, InternalImplementedMethodProtocol def load(build: BuildBase[Any]) -> None: - a = make_typevar('a') - b = make_typevar('b') - t = TypeConstructorVariable('t') + a = TypeVariable(kind=Star(), name='a') + b = TypeVariable(kind=Star(), name='b') + t = TypeVariable(kind=Star() >> Star(), name='t') + t_a = TypeApplication(constructor=t, argument=a) NatNum = build.type_classes['NatNum'] - Foldable = Type3Class('Foldable', (t, ), methods={ - 'sum': [t(a), a], - 'foldl': [[b, a, b], b, t(a), b], - 'foldr': [[a, b, b], b, t(a), b], - }, operators={}, additional_context={ - 'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))], - }) + Foldable = TypeClass('Foldable', (t, ), methods={}, operators={}) + + has_foldable_t = TypeClassConstraint(Foldable, [t]) + has_natnum_a = TypeClassConstraint(NatNum, [a]) + + fn_sum = ConstrainedExpr( + variables={t, a}, + expr=build.type5_make_function([t_a, a]), + constraints=(has_foldable_t, has_natnum_a, ), + ) + + fn_b_a_b = build.type5_make_function([b, a, b]) + fn_foldl = ConstrainedExpr( + variables={t, a, b}, + expr=build.type5_make_function([fn_b_a_b, b, t_a, b]), + constraints=(has_foldable_t, ), + ) + + fn_a_b_b = build.type5_make_function([a, b, b]) + fn_foldr = ConstrainedExpr( + variables={t, a, b}, + expr=build.type5_make_function([fn_a_b_b, b, t_a, b]), + constraints=(has_foldable_t, ), + ) + + Foldable.methods = { + 'sum': fn_sum, + 'foldl': fn_foldl, + 'foldr': fn_foldr, + } build.register_type_class(Foldable) @@ -36,27 +64,25 @@ class FoldableCodeGenerator: def __init__(self, build: BuildBase[WasmGenerator]) -> None: self.build = build - def wasm_dynamic_array_sum(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl + def get_natnum(self, sa_type: TypeExpr) -> tuple[dict[str, TypeExpr], InternalImplementedMethodProtocol[WasmGenerator]]: + natnum_type, natnum_router = self.build.operators['+'] + assert isinstance(natnum_type, ConstrainedExpr) + assert len(natnum_type.variables) == 1 + natnum_a = next(iter(natnum_type.variables)) + natnum_type = replace_variable(natnum_type.expr, natnum_a, sa_type) + impl_lookup = natnum_router.get((natnum_type, )) + assert impl_lookup is not None + return impl_lookup - tvn_map = { - x.name: y - for x, y in tv_map.items() - } + def wasm_dynamic_array_sum(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_type = tv_map['a'] - sa_type = tvn_map['a'] - - assert isinstance(sa_type, Type3) - - ptr_type_info = self.build.type_info_map['ptr'] + ptr_type_info = self.build.type_info_constructed sa_type_info = self.build.type_info_map.get(sa_type.name) if sa_type_info is None: sa_type_info = ptr_type_info - NatNum = self.build.type_classes['NatNum'] - natnum_router = self.build.type_class_instance_methods[NatNum.operators['+']] - - tv_a = make_typevar('a') + natnum_kwargs, natnum_impl = self.get_natnum(sa_type) # Definitions sum_adr = g.temp_var_t(ptr_type_info.wasm_type, 'sum_adr') @@ -104,7 +130,7 @@ class FoldableCodeGenerator: g.nop(comment='Add array value') g.local.get(sum_adr) g.add_statement(sa_type_info.wasm_load_func) - natnum_router(g, {tv_a: sa_type}) + natnum_impl(g, natnum_kwargs) # adr = adr + sa_type_info.alloc_size # Stack: [sum] -> [sum] @@ -123,32 +149,21 @@ class FoldableCodeGenerator: # End result: [sum] - def wasm_static_array_sum(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl + def wasm_static_array_sum(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_type = tv_map['a'] + sa_len = tv_map['n'] - tvn_map = { - x.name: y - for x, y in tv_map.items() - } - - sa_type = tvn_map['a'] - sa_len = tvn_map['a*'] - - assert isinstance(sa_type, Type3) - assert isinstance(sa_len, IntType3) + assert isinstance(sa_len, TypeLevelNat) if sa_len.value < 1: raise NotImplementedError('Default value in case sum is empty') - ptr_type_info = self.build.type_info_map['ptr'] + ptr_type_info = self.build.type_info_constructed sa_type_info = self.build.type_info_map.get(sa_type.name) if sa_type_info is None: sa_type_info = ptr_type_info - NatNum = self.build.type_classes['NatNum'] - natnum_router = self.build.type_class_instance_methods[NatNum.operators['+']] - - tv_a = make_typevar('a') + natnum_kwargs, natnum_impl = self.get_natnum(sa_type) # Definitions sum_adr = g.temp_var_t(ptr_type_info.wasm_type, 'sum_adr') @@ -191,7 +206,7 @@ class FoldableCodeGenerator: g.nop(comment='Add array value') g.local.get(sum_adr) g.add_statement(sa_type_info.wasm_load_func) - natnum_router(g, {tv_a: sa_type}) + natnum_impl(g, natnum_kwargs) # adr = adr + sa_type_info.alloc_size # Stack: [sum] -> [sum] @@ -211,21 +226,11 @@ class FoldableCodeGenerator: g.nop(comment=f'Completed sum for {sa_type.name}[{sa_len.value}]') # End result: [sum] - def wasm_dynamic_array_foldl(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl + def wasm_dynamic_array_foldl(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_type = tv_map['a'] + res_type = tv_map['b'] - tvn_map = { - x.name: y - for x, y in tv_map.items() - } - - sa_type = tvn_map['a'] - res_type = tvn_map['b'] - - assert isinstance(sa_type, Type3) - assert isinstance(res_type, Type3) - - ptr_type_info = self.build.type_info_map['ptr'] + ptr_type_info = self.build.type_info_constructed u32_type_info = self.build.type_info_map['u32'] sa_type_info = self.build.type_info_map.get(sa_type.name) @@ -321,23 +326,14 @@ class FoldableCodeGenerator: # Stack: [b] - def wasm_static_array_foldl(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl + def wasm_static_array_foldl(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_type = tv_map['a'] + sa_len = tv_map['n'] + res_type = tv_map['b'] - tvn_map = { - x.name: y - for x, y in tv_map.items() - } + assert isinstance(sa_len, TypeLevelNat) - sa_type = tvn_map['a'] - sa_len = tvn_map['a*'] - res_type = tvn_map['b'] - - assert isinstance(sa_type, Type3) - assert isinstance(sa_len, IntType3) - assert isinstance(res_type, Type3) - - ptr_type_info = self.build.type_info_map['ptr'] + ptr_type_info = self.build.type_info_constructed sa_type_info = self.build.type_info_map.get(sa_type.name) if sa_type_info is None: @@ -352,7 +348,7 @@ class FoldableCodeGenerator: fold_init = g.temp_var_t(res_type_info.wasm_type, 'fold_init') fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func') - with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldl a={sa_type.name} a*={sa_len.value} b={res_type.name}'): + with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldl a={sa_type.name} n={sa_len.value} b={res_type.name}'): # Stack: [fn*, b, sa*] -> [fn*, b] g.local.set(fold_adr) # Stack: [fn*, b] -> [fn*] @@ -412,21 +408,11 @@ class FoldableCodeGenerator: # Stack: [b] - def wasm_dynamic_array_foldr(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl + def wasm_dynamic_array_foldr(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_type = tv_map['a'] + res_type = tv_map['b'] - tvn_map = { - x.name: y - for x, y in tv_map.items() - } - - sa_type = tvn_map['a'] - res_type = tvn_map['b'] - - assert isinstance(sa_type, Type3) - assert isinstance(res_type, Type3) - - ptr_type_info = self.build.type_info_map['ptr'] + ptr_type_info = self.build.type_info_constructed u32_type_info = self.build.type_info_map['u32'] sa_type_info = self.build.type_info_map.get(sa_type.name) @@ -540,23 +526,14 @@ class FoldableCodeGenerator: # Stack: [b] - def wasm_static_array_foldr(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl + def wasm_static_array_foldr(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_type = tv_map['a'] + sa_len = tv_map['n'] + res_type = tv_map['b'] - tvn_map = { - x.name: y - for x, y in tv_map.items() - } + assert isinstance(sa_len, TypeLevelNat) - sa_type = tvn_map['a'] - sa_len = tvn_map['a*'] - res_type = tvn_map['b'] - - assert isinstance(sa_type, Type3) - assert isinstance(sa_len, IntType3) - assert isinstance(res_type, Type3) - - ptr_type_info = self.build.type_info_map['ptr'] + ptr_type_info = self.build.type_info_constructed sa_type_info = self.build.type_info_map.get(sa_type.name) if sa_type_info is None: @@ -571,7 +548,7 @@ class FoldableCodeGenerator: fold_tmp = g.temp_var_t(res_type_info.wasm_type, 'fold_tmp') fold_func = g.temp_var_t(ptr_type_info.wasm_type, 'fold_func') - with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldr a={sa_type.name} a*={sa_len.value} b={res_type.name}'): + with g.block(params=['i32', res_type_info.wasm_type, 'i32'], result=res_type_info.wasm_type, comment=f'foldr a={sa_type.name} n={sa_len.value} b={res_type.name}'): # Stack: [fn*, b, sa*] -> [fn*, b] ; fold_adr=fn*, fold_tmp=b, fold_func=fn* g.local.set(fold_adr) # Stack: [fn*, b] -> [fn*] @@ -649,14 +626,17 @@ class FoldableCodeGenerator: def wasm(build: BuildBase[WasmGenerator]) -> None: Foldable = build.type_classes['Foldable'] + n = TypeVariable(kind=Nat(), name='n') + gen = FoldableCodeGenerator(build) - build.instance_type_class(Foldable, build.dynamic_array, methods={ + build.instance_type_class(Foldable, build.dynamic_array_type5_constructor, methods={ 'sum': gen.wasm_dynamic_array_sum, 'foldl': gen.wasm_dynamic_array_foldl, 'foldr': gen.wasm_dynamic_array_foldr, }) - build.instance_type_class(Foldable, build.static_array, methods={ + foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=n) + build.instance_type_class(Foldable, foo, methods={ 'sum': gen.wasm_static_array_sum, 'foldl': gen.wasm_static_array_foldl, 'foldr': gen.wasm_static_array_foldr, diff --git a/phasm/build/typeclasses/fractional.py b/phasm/build/typeclasses/fractional.py index 6ee6ce5..301adac 100644 --- a/phasm/build/typeclasses/fractional.py +++ b/phasm/build/typeclasses/fractional.py @@ -1,67 +1,88 @@ """ 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') - NatNum = build.type_classes['NatNum'] + a = TypeVariable(kind=Star(), name='a') - 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( + variables={a}, + expr=build.type5_make_function([a, a]), + constraints=(has_fractional_a, ), + ) + + fn_a_a_a = ConstrainedExpr( + variables={a}, + 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() diff --git a/phasm/build/typeclasses/integral.py b/phasm/build/typeclasses/integral.py index de98317..e42e568 100644 --- a/phasm/build/typeclasses/integral.py +++ b/phasm/build/typeclasses/integral.py @@ -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') - NatNum = build.type_classes['NatNum'] + a = TypeVariable(kind=Star(), name='a') - 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( + variables={a}, + 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') diff --git a/phasm/build/typeclasses/intnum.py b/phasm/build/typeclasses/intnum.py index 70b7364..262f7e8 100644 --- a/phasm/build/typeclasses/intnum.py +++ b/phasm/build/typeclasses/intnum.py @@ -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') - NatNum = build.type_classes['NatNum'] + a = TypeVariable(kind=Star(), name='a') - 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( + variables={a}, + 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() diff --git a/phasm/build/typeclasses/natnum.py b/phasm/build/typeclasses/natnum.py index 0d3a652..51a44cd 100644 --- a/phasm/build/typeclasses/natnum.py +++ b/phasm/build/typeclasses/natnum.py @@ -3,159 +3,178 @@ 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( + variables={a}, + expr=build.type5_make_function([a, a, a]), + constraints=(has_natnum_a, ), + ) + + fn_a_u32_a = ConstrainedExpr( + variables={a}, + 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() diff --git a/phasm/build/typeclasses/ord.py b/phasm/build/typeclasses/ord.py index 5eea40c..bfcbbd1 100644 --- a/phasm/build/typeclasses/ord.py +++ b/phasm/build/typeclasses/ord.py @@ -1,270 +1,290 @@ """ 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( + variables={a}, + expr=build.type5_make_function([a, a, a]), + constraints=(has_ord_a, ), + ) + + fn_a_a_bool = ConstrainedExpr( + variables={a}, + 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() diff --git a/phasm/build/typeclasses/promotable.py b/phasm/build/typeclasses/promotable.py index a5e2f45..397bbb1 100644 --- a/phasm/build/typeclasses/promotable.py +++ b/phasm/build/typeclasses/promotable.py @@ -2,31 +2,50 @@ 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( + variables={a, b}, + expr=build.type5_make_function([a, b]), + constraints=(has_Promotable_a_b, ), + ) + + fn_b_a = ConstrainedExpr( + variables={a, b}, + 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() diff --git a/phasm/build/typeclasses/reinterpretable.py b/phasm/build/typeclasses/reinterpretable.py index 23f5066..fcb858a 100644 --- a/phasm/build/typeclasses/reinterpretable.py +++ b/phasm/build/typeclasses/reinterpretable.py @@ -2,54 +2,67 @@ 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( + variables={a, b}, + 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() diff --git a/phasm/build/typeclasses/sized.py b/phasm/build/typeclasses/sized.py index 8571aa0..9d39d6a 100644 --- a/phasm/build/typeclasses/sized.py +++ b/phasm/build/typeclasses/sized.py @@ -3,51 +3,60 @@ 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 Nat, Star +from ...type5.typeexpr import TypeApplication, TypeExpr, 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'] - 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( + variables={t, a}, + expr=build.type5_make_function([t_a, u32]), + constraints=(has_sized_t, ), + ) + + Sized.methods = { + 'len': fn_t_a_u32, + } 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: dict[str, TypeExpr]) -> None: + print('tv_map', tv_map) del tv_map # The length is stored in the first 4 bytes g.i32.load() -def wasm_static_array_len(g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl - - tvn_map = { - x.name: y - for x, y in tv_map.items() - } - - sa_len = tvn_map['a*'] - assert isinstance(sa_len, IntType3) +def wasm_static_array_len(g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_len = tv_map['n'] + assert isinstance(sa_len, TypeLevelNat) g.i32.const(sa_len.value) def wasm(build: BuildBase[WasmGenerator]) -> None: Sized = build.type_classes['Sized'] - build.instance_type_class(Sized, build.dynamic_array, methods={ + n = TypeVariable(kind=Nat(), name='n') + + 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={ + foo = TypeApplication(constructor=build.static_array_type5_constructor, argument=n) + build.instance_type_class(Sized, foo, methods={ 'len': wasm_static_array_len, }) diff --git a/phasm/build/typeclasses/subscriptable.py b/phasm/build/typeclasses/subscriptable.py index 1caaa55..d97dc0e 100644 --- a/phasm/build/typeclasses/subscriptable.py +++ b/phasm/build/typeclasses/subscriptable.py @@ -1,24 +1,37 @@ """ 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, TypeExpr, 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( + variables={t, a}, + 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,24 +39,14 @@ class SubscriptableCodeGenerator: def __init__(self, build: BuildBase[WasmGenerator]) -> None: self.build = build - def wasm_dynamic_array_getitem(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl + def wasm_dynamic_array_getitem(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_type = tv_map['a'] - tvn_map = { - x.name: y - for x, y in tv_map.items() - } - - 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'] sa_type_info = self.build.type_info_map.get(sa_type.name) if sa_type_info is None: - sa_type_info = ptr_type_info + sa_type_info = self.build.type_info_constructed getitem_adr = g.temp_var_t(u32_type_info.wasm_type, 'getitem_adr') getitem_idx = g.temp_var_t(u32_type_info.wasm_type, 'getitem_idx') @@ -85,26 +88,17 @@ class SubscriptableCodeGenerator: g.add_statement(sa_type_info.wasm_load_func) # Stack: [el] - def wasm_static_array_getitem(self, g: WasmGenerator, tvl: TypeVariableLookup) -> None: - tv_map, tc_map = tvl + def wasm_static_array_getitem(self, g: WasmGenerator, tv_map: dict[str, TypeExpr]) -> None: + sa_type = tv_map['a'] + sa_len = tv_map['n'] - tvn_map = { - x.name: y - for x, y in tv_map.items() - } + assert isinstance(sa_len, TypeLevelNat) - 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'] sa_type_info = self.build.type_info_map.get(sa_type.name) if sa_type_info is None: - sa_type_info = ptr_type_info + sa_type_info = self.build.type_info_constructed # OPTIMIZE: If index is a constant, we can use offset instead of multiply # and we don't need to do the out of bounds check @@ -137,11 +131,14 @@ class SubscriptableCodeGenerator: def wasm(build: BuildBase[WasmGenerator]) -> None: Subscriptable = build.type_classes['Subscriptable'] + n = TypeVariable(kind=Nat(), name='n') + 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=n) + build.instance_type_class(Subscriptable, foo, operators={ '[]': gen.wasm_static_array_getitem, }) diff --git a/phasm/build/typeclassregistry.py b/phasm/build/typeclassregistry.py new file mode 100644 index 0000000..109104a --- /dev/null +++ b/phasm/build/typeclassregistry.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from typing import Iterable + +from ..type5.typeexpr import ( + AtomicType, + TypeApplication, + TypeConstructor, + TypeExpr, + TypeVariable, + is_concrete, +) + + +class TypeClassRegistry[T]: + __slots__ = ('data', ) + + data: list[tuple[tuple[TypeExpr, ...], T]] + + def __init__(self) -> None: + self.data = [] + + def add(self, arg_list: Iterable[TypeExpr], val: T) -> None: + self.data.append((tuple(arg_list), val)) + + def __contains__(self, arg_list: tuple[TypeExpr, ...]) -> bool: + for candidate, _ in self.data: + subsitutes = _matches(arg_list, candidate) + if subsitutes is not None: + return True + + return False + + def get(self, arg_list: tuple[TypeExpr, ...]) -> tuple[dict[str, TypeExpr], T] | None: + assert all(is_concrete(x) for x in arg_list) + for candidate, val in self.data: + subsitutes = _matches(arg_list, candidate) + if subsitutes is not None: + return (subsitutes, val) + + return None + +def _matches(lft_tpl: tuple[TypeExpr, ...], rgt_tpl: tuple[TypeExpr, ...]) -> dict[str, TypeExpr] | None: + subsitutes: dict[str, TypeExpr] = {} + + for lft, rgt in zip(lft_tpl, rgt_tpl, strict=True): + new_substitutes = _matches_one(lft, rgt) + if new_substitutes is None: + return None + + subsitutes.update(new_substitutes) + + return subsitutes + +def _matches_one(lft: TypeExpr, rgt: TypeExpr) -> dict[str, TypeExpr] | None: + if lft.kind != rgt.kind: + return None + + if isinstance(rgt, TypeVariable): + assert not isinstance(lft, TypeVariable) + return _matches_one(rgt, lft) + + if isinstance(lft, TypeVariable): + return {lft.name: rgt} + + if isinstance(lft, (AtomicType, TypeConstructor, )): + if lft == rgt: + return {} + + return None + + if isinstance(lft, TypeApplication): + if not isinstance(rgt, TypeApplication): + return None + + con_result = _matches_one(lft.constructor, rgt.constructor) + if con_result is None: + return None + + arg_result = _matches_one(lft.argument, rgt.argument) + if arg_result is None: + return None + + return con_result | arg_result + + raise NotImplementedError(lft, rgt) diff --git a/phasm/build/typerouter.py b/phasm/build/typerouter.py new file mode 100644 index 0000000..44cfbf5 --- /dev/null +++ b/phasm/build/typerouter.py @@ -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)) diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 1209c70..965b345 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -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 diff --git a/phasm/compiler.py b/phasm/compiler.py index 439828a..3aafd1d 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -2,26 +2,16 @@ 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 .wasm import ( WasmTypeFloat32, WasmTypeFloat64, @@ -30,7 +20,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 +29,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 +122,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 +130,64 @@ 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] + + impl_lookup = method_router.get((inp.function_instance.type5, )) + assert impl_lookup is not None, (inp.function_instance.function.name, inp.function_instance.type5, ) + kwargs, impl = impl_lookup + impl(wgn, kwargs) + 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 +197,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 +220,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 +233,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 +249,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 +270,46 @@ 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 + assert _is_concrete(inp.index.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['[]']) - inp_as_fc.type3 = inp.type3 + inp_as_fc = ourlang.FunctionCall( + ourlang.FunctionInstance( + ourlang.BuiltinFunction('[]', mod.build.type_classes['Subscriptable'].operators['[]']), + inp.sourceref, + ), + inp.sourceref, + ) inp_as_fc.arguments = [inp.varref, inp.index] + inp_as_fc.function_instance.type5 = mod.build.type5_make_function([ + inp.varref.type5, + inp.index.type5, + inp.type5, + ]) + inp_as_fc.type5 = inp.type5 - expression(wgn, mod, inp_as_fc) + expression_function_call(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), TYPE5_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 +321,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 +363,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 +391,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 +408,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 +442,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 +452,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 +471,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 +543,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 diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 5bd5597..698f5b0 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -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 - value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']] + struct_type5: type5record.Record + value: list[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[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,60 @@ 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', '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 + 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.posonlyargs = [] + self.type5 = None + 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 +376,32 @@ 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) 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 +435,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 = {} diff --git a/phasm/parser.py b/phasm/parser.py index a182822..151747f 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -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,24 +159,24 @@ 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.posonlyargs.append(FunctionParam( - arg.arg, - arg_type, - )) + function.arg_names.append(arg.arg) _not_implemented(not node.args.vararg, 'FunctionDef.args.vararg') _not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs') @@ -215,9 +218,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 +232,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 +250,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 +270,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 +283,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 +296,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, ) @@ -310,10 +318,15 @@ class OurVisitor[G]: def visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> None: function = module.functions[node.name] + assert function.type5 is not None + + func_arg_types = module.build.type5_is_function(function.type5) + assert func_arg_types is not None + func_arg_types.pop() our_locals: OurLocals = { - x.name: x - for x in function.posonlyargs + a_nam: FunctionParam(a_nam, a_typ) + for a_nam, a_typ in zip(function.arg_names, func_arg_types, strict=True) } for stmt in node.body: @@ -328,7 +341,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 +363,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 +400,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 +429,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 +459,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 +481,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 +494,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 +506,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 +526,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 +543,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 +554,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 +571,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 +599,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 +643,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 +655,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 +664,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 +687,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) diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py deleted file mode 100644 index b8c71c4..0000000 --- a/phasm/type3/constraints.py +++ /dev/null @@ -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)})' diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py deleted file mode 100644 index a666885..0000000 --- a/phasm/type3/constraintsgenerator.py +++ /dev/null @@ -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) diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py deleted file mode 100644 index 9b7f96a..0000000 --- a/phasm/type3/entry.py +++ /dev/null @@ -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 ^ === ') diff --git a/phasm/type3/functions.py b/phasm/type3/functions.py deleted file mode 100644 index d10e2b1..0000000 --- a/phasm/type3/functions.py +++ /dev/null @@ -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})' diff --git a/phasm/type3/placeholders.py b/phasm/type3/placeholders.py deleted file mode 100644 index 58f8467..0000000 --- a/phasm/type3/placeholders.py +++ /dev/null @@ -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] diff --git a/phasm/type3/routers.py b/phasm/type3/routers.py deleted file mode 100644 index 26fa6cc..0000000 --- a/phasm/type3/routers.py +++ /dev/null @@ -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) diff --git a/phasm/type3/typeclasses.py b/phasm/type3/typeclasses.py deleted file mode 100644 index 02c6ad0..0000000 --- a/phasm/type3/typeclasses.py +++ /dev/null @@ -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) diff --git a/phasm/type3/types.py b/phasm/type3/types.py deleted file mode 100644 index 9406c1b..0000000 --- a/phasm/type3/types.py +++ /dev/null @@ -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 diff --git a/phasm/type3/__init__.py b/phasm/type5/__init__.py similarity index 100% rename from phasm/type3/__init__.py rename to phasm/type5/__init__.py diff --git a/phasm/type5/constrainedexpr.py b/phasm/type5/constrainedexpr.py new file mode 100644 index 0000000..5199930 --- /dev/null +++ b/phasm/type5/constrainedexpr.py @@ -0,0 +1,64 @@ +""" +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]) -> Self: + raise NotImplementedError + +@dataclass +class ConstrainedExpr: + """ + Also known as a polytype. + + We only have rank 1 types, so this is always on the outside. + """ + variables: set[TypeVariable] + expr: TypeExpr + constraints: tuple[TypeConstraint, ...] + + def __hash__(self) -> int: + return hash(( + tuple(sorted(self.variables)), + self.expr, + self.constraints, + )) + +# 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 + known_map = { + v: make_variable(v.kind, v.name) + for v in constrainedexpr.variables + } + + expr = instantiate(constrainedexpr.expr, known_map) + constraints = tuple( + x.instantiate(known_map) + for x in constrainedexpr.constraints + ) + return ConstrainedExpr(constrainedexpr.variables, expr, constraints) diff --git a/phasm/type5/constraints.py b/phasm/type5/constraints.py new file mode 100644 index 0000000..657b049 --- /dev/null +++ b/phasm/type5/constraints.py @@ -0,0 +1,455 @@ +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 ( + TypeApplication, + 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 + + if not type_info.signed and self.literal < 0: + return fail('May not be negative') + + 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) + + 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 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), + ]) + + if not isinstance(self.container_type5, TypeApplication): + return fail('Missing type class instance') + + return new_constraints([ + TypeClassInstanceExistsConstraint( + self.ctx, + self.sourceref, + 'Subscriptable', + (self.container_type5.constructor, ), + ), + UnifyTypesConstraint(self.ctx, self.sourceref, self.container_type5.argument, self.ret_type5), + UnifyTypesConstraint(self.ctx, self.sourceref, self.ctx.build.u32_type5, self.index_type5), + ]) + + 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}' diff --git a/phasm/type5/fromast.py b/phasm/type5/fromast.py new file mode 100644 index 0000000..f5c4ae0 --- /dev/null +++ b/phasm/type5/fromast.py @@ -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, {}) + + # 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 diff --git a/phasm/type5/kindexpr.py b/phasm/type5/kindexpr.py new file mode 100644 index 0000000..0eefe72 --- /dev/null +++ b/phasm/type5/kindexpr.py @@ -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 diff --git a/phasm/type5/record.py b/phasm/type5/record.py new file mode 100644 index 0000000..6109fa6 --- /dev/null +++ b/phasm/type5/record.py @@ -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 diff --git a/phasm/type5/solver.py b/phasm/type5/solver.py new file mode 100644 index 0000000..e4a4c1b --- /dev/null +++ b/phasm/type5/solver.py @@ -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 = 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 diff --git a/phasm/type5/typeexpr.py b/phasm/type5/typeexpr.py new file mode 100644 index 0000000..30f9fe6 --- /dev/null +++ b/phasm/type5/typeexpr.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +from dataclasses import dataclass + +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)) + + def __lt__(self, other: object) -> bool: + if not isinstance(other, TypeVariable): + raise TypeError + + return self.name < other.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], + ) -> 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): + return known_map[expr] + + if isinstance(expr, TypeConstructor): + return expr + + if isinstance(expr, TypeApplication): + new_constructor = instantiate(expr.constructor, known_map) + + assert isinstance(new_constructor, TypeConstructor | TypeApplication | TypeVariable) # type hint + + return TypeApplication( + constructor=new_constructor, + argument=instantiate(expr.argument, known_map), + ) + + raise NotImplementedError(expr) diff --git a/phasm/type5/typerouter.py b/phasm/type5/typerouter.py new file mode 100644 index 0000000..1609b36 --- /dev/null +++ b/phasm/type5/typerouter.py @@ -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) diff --git a/phasm/type5/unify.py b/phasm/type5/unify.py new file mode 100644 index 0000000..a8530e4 --- /dev/null +++ b/phasm/type5/unify.py @@ -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') diff --git a/phasm/typeclass/__init__.py b/phasm/typeclass/__init__.py new file mode 100644 index 0000000..e2cbdfd --- /dev/null +++ b/phasm/typeclass/__init__.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import dataclasses +from typing import Iterable + +from ..type5.constrainedexpr import ConstrainedExpr, TypeConstraint +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]) -> 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) # type: ignore + for var in self.variables + ] + ) diff --git a/requirements.txt b/requirements.txt index c52ffeb..2fd9083 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index c6d6438..a2c90a1 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -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(' 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(' 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(' 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) diff --git a/tests/integration/memory.py b/tests/integration/memory.py new file mode 100644 index 0000000..3f0f772 --- /dev/null +++ b/tests/integration/memory.py @@ -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, int, )), 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(' 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(' 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 diff --git a/tests/integration/runners.py b/tests/integration/runners.py index 20681de..b2733eb 100644 --- a/tests/integration/runners.py +++ b/tests/integration/runners.py @@ -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: """ diff --git a/tests/integration/test_lang/generator.md b/tests/integration/test_lang/generator.md index fb0244b..d70f653 100644 --- a/tests/integration/test_lang/generator.md +++ b/tests/integration/test_lang/generator.md @@ -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') ``` diff --git a/tests/integration/test_lang/generator.py b/tests/integration/test_lang/generator.py index 045f8c3..6c8c55e 100644 --- a/tests/integration/test_lang/generator.py +++ b/tests/integration/test_lang/generator.py @@ -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() diff --git a/tests/integration/test_lang/test_function_calls.py b/tests/integration/test_lang/test_function_calls.py index 475b658..004e23b 100644 --- a/tests/integration/test_lang/test_function_calls.py +++ b/tests/integration/test_lang/test_function_calls.py @@ -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 = """ diff --git a/tests/integration/test_lang/test_if.py b/tests/integration/test_lang/test_if.py index ff37761..6536a4d 100644 --- a/tests/integration/test_lang/test_if.py +++ b/tests/integration/test_lang/test_if.py @@ -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 diff --git a/tests/integration/test_lang/test_imports.py b/tests/integration/test_lang/test_imports.py index 9c0b93d..ccdfde4 100644 --- a/tests/integration/test_lang/test_imports.py +++ b/tests/integration/test_lang/test_imports.py @@ -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, diff --git a/tests/integration/test_lang/test_literals.py b/tests/integration/test_lang/test_literals.py index 5593341..c0b8539 100644 --- a/tests/integration/test_lang/test_literals.py +++ b/tests/integration/test_lang/test_literals.py @@ -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() diff --git a/tests/integration/test_lang/test_second_order_functions.py b/tests/integration/test_lang/test_second_order_functions.py index 395d804..a598ce4 100644 --- a/tests/integration/test_lang/test_second_order_functions.py +++ b/tests/integration/test_lang/test_second_order_functions.py @@ -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() diff --git a/tests/integration/test_lang/test_static_array.py b/tests/integration/test_lang/test_static_array.py index 373cbec..26d4ae4 100644 --- a/tests/integration/test_lang/test_static_array.py +++ b/tests/integration/test_lang/test_static_array.py @@ -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 diff --git a/tests/integration/test_lang/test_struct.py b/tests/integration/test_lang/test_struct.py index a0396ab..7dbab3d 100644 --- a/tests/integration/test_lang/test_struct.py +++ b/tests/integration/test_lang/test_struct.py @@ -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 diff --git a/tests/integration/test_lang/test_subscriptable.py b/tests/integration/test_lang/test_subscriptable.py index d59ecc2..c838447 100644 --- a/tests/integration/test_lang/test_subscriptable.py +++ b/tests/integration/test_lang/test_subscriptable.py @@ -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='May not be negative'): 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() diff --git a/tests/integration/test_lang/test_tuple.py b/tests/integration/test_lang/test_tuple.py index a7244c7..1eaa148 100644 --- a/tests/integration/test_lang/test_tuple.py +++ b/tests/integration/test_lang/test_tuple.py @@ -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 diff --git a/tests/integration/test_typeclasses/test_convertable.py b/tests/integration/test_typeclasses/test_convertable.py index d0f9156..d41d8b3 100644 --- a/tests/integration/test_typeclasses/test_convertable.py +++ b/tests/integration/test_typeclasses/test_convertable.py @@ -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 diff --git a/tests/integration/test_typeclasses/test_eq.py b/tests/integration/test_typeclasses/test_eq.py index 2709798..d9695e4 100644 --- a/tests/integration/test_typeclasses/test_eq.py +++ b/tests/integration/test_typeclasses/test_eq.py @@ -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 diff --git a/tests/integration/test_typeclasses/test_extendable.py b/tests/integration/test_typeclasses/test_extendable.py index f4f5bcf..03a4ef2 100644 --- a/tests/integration/test_typeclasses/test_extendable.py +++ b/tests/integration/test_typeclasses/test_extendable.py @@ -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 diff --git a/tests/integration/test_typeclasses/test_floating.py b/tests/integration/test_typeclasses/test_floating.py index a88feab..7dc6196 100644 --- a/tests/integration/test_typeclasses/test_floating.py +++ b/tests/integration/test_typeclasses/test_floating.py @@ -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_): diff --git a/tests/integration/test_typeclasses/test_foldable.py b/tests/integration/test_typeclasses/test_foldable.py index ff003b9..d516ae5 100644 --- a/tests/integration/test_typeclasses/test_foldable.py +++ b/tests/integration/test_typeclasses/test_foldable.py @@ -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() diff --git a/tests/integration/test_typeclasses/test_fractional.py b/tests/integration/test_typeclasses/test_fractional.py index 694e840..5049d7b 100644 --- a/tests/integration/test_typeclasses/test_fractional.py +++ b/tests/integration/test_typeclasses/test_fractional.py @@ -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) diff --git a/tests/integration/test_typeclasses/test_integral.py b/tests/integration/test_typeclasses/test_integral.py index c51d892..97199f2 100644 --- a/tests/integration/test_typeclasses/test_integral.py +++ b/tests/integration/test_typeclasses/test_integral.py @@ -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_): diff --git a/tests/integration/test_typeclasses/test_natnum.py b/tests/integration/test_typeclasses/test_natnum.py index 0f7e149..3060c12 100644 --- a/tests/integration/test_typeclasses/test_natnum.py +++ b/tests/integration/test_typeclasses/test_natnum.py @@ -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 diff --git a/tests/integration/test_typeclasses/test_promotable.py b/tests/integration/test_typeclasses/test_promotable.py index 26f3123..b6ffcf7 100644 --- a/tests/integration/test_typeclasses/test_promotable.py +++ b/tests/integration/test_typeclasses/test_promotable.py @@ -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 diff --git a/tests/integration/test_typeclasses/test_reinterpretable.py b/tests/integration/test_typeclasses/test_reinterpretable.py index 627f99b..fbabe3f 100644 --- a/tests/integration/test_typeclasses/test_reinterpretable.py +++ b/tests/integration/test_typeclasses/test_reinterpretable.py @@ -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 diff --git a/tests/integration/test_typeclasses/test_sized.py b/tests/integration/test_typeclasses/test_sized.py index 1b8b6eb..2f2a682 100644 --- a/tests/integration/test_typeclasses/test_sized.py +++ b/tests/integration/test_typeclasses/test_sized.py @@ -1,11 +1,25 @@ import pytest +from phasm.type5.solver import Type5SolverException + from ..helpers import Suite +@pytest.mark.integration_test +def test_sized_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), + ('u16[...]', (1, 2, 8), 3), ('u8[4]', (1, 2, 3, 4), 4), ]) def test_len(type_, in_put, exp_result): -- 2.49.0