Cleanup
This commit is contained in:
parent
07ffbbbaab
commit
4e798ce78b
2
Makefile
2
Makefile
@ -23,7 +23,7 @@ lint: venv/.done
|
|||||||
venv/bin/ruff check phasm tests
|
venv/bin/ruff check phasm tests
|
||||||
|
|
||||||
typecheck: venv/.done
|
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
|
venv/.done: requirements.txt
|
||||||
python3.12 -m venv venv
|
python3.12 -m venv venv
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import sys
|
|||||||
from .compiler import phasm_compile
|
from .compiler import phasm_compile
|
||||||
from .optimise.removeunusedfuncs import removeunusedfuncs
|
from .optimise.removeunusedfuncs import removeunusedfuncs
|
||||||
from .parser import phasm_parse
|
from .parser import phasm_parse
|
||||||
from .type3.entry import phasm_type3
|
from .type5.solver import phasm_type5
|
||||||
|
|
||||||
|
|
||||||
def main(source: str, sink: str) -> int:
|
def main(source: str, sink: str) -> int:
|
||||||
@ -19,7 +19,7 @@ def main(source: str, sink: str) -> int:
|
|||||||
code_py = fil.read()
|
code_py = fil.read()
|
||||||
|
|
||||||
our_module = phasm_parse(code_py)
|
our_module = phasm_parse(code_py)
|
||||||
phasm_type3(our_module, verbose=False)
|
phasm_type5(our_module, verbose=False)
|
||||||
wasm_module = phasm_compile(our_module)
|
wasm_module = phasm_compile(our_module)
|
||||||
removeunusedfuncs(wasm_module)
|
removeunusedfuncs(wasm_module)
|
||||||
code_wat = wasm_module.to_wat()
|
code_wat = wasm_module.to_wat()
|
||||||
|
|||||||
@ -6,32 +6,11 @@ Contains nothing but the explicit compiler builtins.
|
|||||||
from typing import Any, Callable, NamedTuple, Sequence, Type
|
from typing import Any, Callable, NamedTuple, Sequence, Type
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from ..type3.functions import (
|
|
||||||
TypeConstructorVariable,
|
|
||||||
TypeVariable,
|
|
||||||
)
|
|
||||||
from ..type3.routers import (
|
|
||||||
NoRouteForTypeException,
|
|
||||||
TypeApplicationRouter,
|
|
||||||
TypeClassArgsRouter,
|
|
||||||
TypeVariableLookup,
|
|
||||||
)
|
|
||||||
from ..typeclass import TypeClass
|
from ..typeclass import TypeClass
|
||||||
from ..type3.types import (
|
|
||||||
IntType3,
|
|
||||||
Type3,
|
|
||||||
TypeConstructor_Base,
|
|
||||||
TypeConstructor_DynamicArray,
|
|
||||||
TypeConstructor_Function,
|
|
||||||
TypeConstructor_StaticArray,
|
|
||||||
TypeConstructor_Struct,
|
|
||||||
TypeConstructor_Tuple,
|
|
||||||
)
|
|
||||||
from ..type5 import kindexpr as type5kindexpr
|
from ..type5 import kindexpr as type5kindexpr
|
||||||
from ..type5 import record as type5record
|
from ..type5 import record as type5record
|
||||||
from ..type5 import typeexpr as type5typeexpr
|
from ..type5 import typeexpr as type5typeexpr
|
||||||
from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone
|
from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone
|
||||||
from . import builtins
|
|
||||||
from .typerouter import TypeAllocSize, TypeName
|
from .typerouter import TypeAllocSize, TypeName
|
||||||
from .typevariablerouter import TypeVariableRouter
|
from .typevariablerouter import TypeVariableRouter
|
||||||
|
|
||||||
@ -59,102 +38,105 @@ class MissingImplementationWarning(Warning):
|
|||||||
|
|
||||||
class BuildBase[G]:
|
class BuildBase[G]:
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'dynamic_array',
|
|
||||||
'dynamic_array_type5_constructor',
|
'dynamic_array_type5_constructor',
|
||||||
'function',
|
|
||||||
'function_type5_constructor',
|
'function_type5_constructor',
|
||||||
'static_array',
|
|
||||||
'static_array_type5_constructor',
|
'static_array_type5_constructor',
|
||||||
'struct',
|
|
||||||
'tuple_',
|
|
||||||
'tuple_type5_constructor_map',
|
'tuple_type5_constructor_map',
|
||||||
|
|
||||||
'none_',
|
|
||||||
'none_type5',
|
'none_type5',
|
||||||
'unit_type5',
|
'unit_type5',
|
||||||
'bool_',
|
|
||||||
'bool_type5',
|
'bool_type5',
|
||||||
'u8_type5',
|
'u8_type5',
|
||||||
'u32_type5',
|
'u32_type5',
|
||||||
|
'bytes_type5',
|
||||||
|
|
||||||
'type_info_map',
|
'type_info_map',
|
||||||
'type_info_constructed',
|
'type_info_constructed',
|
||||||
|
|
||||||
'types',
|
'types',
|
||||||
'type5s',
|
|
||||||
'type_classes',
|
'type_classes',
|
||||||
'type_class_instances',
|
'type_class_instances',
|
||||||
'type_class_instance_methods',
|
|
||||||
'methods',
|
'methods',
|
||||||
'operators',
|
'operators',
|
||||||
|
|
||||||
'type5_name',
|
'type5_name',
|
||||||
'type5_alloc_size_root',
|
'type5_alloc_size_root',
|
||||||
'type5_alloc_size_member',
|
'type5_alloc_size_member',
|
||||||
'alloc_size_router',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
dynamic_array: TypeConstructor_DynamicArray
|
|
||||||
"""
|
|
||||||
This is a dynamic length piece of memory.
|
|
||||||
|
|
||||||
It should be applied with two arguments. It has a runtime
|
|
||||||
determined length, and each argument is the same.
|
|
||||||
"""
|
|
||||||
|
|
||||||
dynamic_array_type5_constructor: type5typeexpr.TypeConstructor
|
dynamic_array_type5_constructor: type5typeexpr.TypeConstructor
|
||||||
|
|
||||||
function: TypeConstructor_Function
|
|
||||||
"""
|
"""
|
||||||
This is a function.
|
Constructor for arrays of runtime deterined length.
|
||||||
|
|
||||||
It should be applied with one or more arguments. The last argument is the 'return' type.
|
See type5_make_dynamic_array and type5_is_dynamic_array.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
function_type5_constructor: type5typeexpr.TypeConstructor
|
function_type5_constructor: type5typeexpr.TypeConstructor
|
||||||
|
|
||||||
static_array: TypeConstructor_StaticArray
|
|
||||||
"""
|
"""
|
||||||
This is a fixed length piece of memory.
|
Constructor for functions.
|
||||||
|
|
||||||
It should be applied with two arguments. It has a compile time
|
See type5_make_function and type5_is_function.
|
||||||
determined length, and each argument is the same.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
static_array_type5_constructor: type5typeexpr.TypeConstructor
|
static_array_type5_constructor: type5typeexpr.TypeConstructor
|
||||||
|
|
||||||
struct: TypeConstructor_Struct
|
|
||||||
"""
|
|
||||||
This is like a tuple, but each argument is named, so that developers
|
|
||||||
can get and set fields by name.
|
|
||||||
"""
|
"""
|
||||||
|
Constructor for arrays of compiled time determined length.
|
||||||
|
|
||||||
tuple_: TypeConstructor_Tuple
|
See type5_make_static_array and type5_is_static_array.
|
||||||
"""
|
|
||||||
This is a fixed length piece of memory.
|
|
||||||
|
|
||||||
It should be applied with zero or more arguments. It has a compile time
|
|
||||||
determined length, and each argument can be different.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor]
|
tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor]
|
||||||
|
"""
|
||||||
|
Map for constructors for tuples of each length.
|
||||||
|
|
||||||
none_: Type3
|
See type5_make_tuple and type5_is_tuple.
|
||||||
"""
|
|
||||||
The none type, for when functions simply don't return anything. e.g., IO().
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
none_type5: type5typeexpr.AtomicType
|
none_type5: type5typeexpr.AtomicType
|
||||||
|
"""
|
||||||
|
The none type.
|
||||||
|
|
||||||
|
TODO: Not sure this should be a buildin (rather than a Maybe type).
|
||||||
|
"""
|
||||||
|
|
||||||
unit_type5: type5typeexpr.AtomicType
|
unit_type5: type5typeexpr.AtomicType
|
||||||
|
|
||||||
bool_: Type3
|
|
||||||
"""
|
"""
|
||||||
The bool type, either True or False
|
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_type5: type5typeexpr.AtomicType
|
bool_type5: type5typeexpr.AtomicType
|
||||||
|
"""
|
||||||
|
The bool type, either True or False.
|
||||||
|
|
||||||
|
Builtin since functions require a boolean value in their test.
|
||||||
|
"""
|
||||||
|
|
||||||
u8_type5: type5typeexpr.AtomicType
|
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
|
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]
|
type_info_map: dict[str, TypeInfo]
|
||||||
"""
|
"""
|
||||||
@ -170,12 +152,7 @@ class BuildBase[G]:
|
|||||||
not memory pointers but table addresses instead.
|
not memory pointers but table addresses instead.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
types: dict[str, Type3]
|
types: dict[str, type5typeexpr.TypeExpr]
|
||||||
"""
|
|
||||||
Types that are available without explicit import.
|
|
||||||
"""
|
|
||||||
|
|
||||||
type5s: dict[str, type5typeexpr.TypeExpr]
|
|
||||||
"""
|
"""
|
||||||
Types that are available without explicit import.
|
Types that are available without explicit import.
|
||||||
"""
|
"""
|
||||||
@ -190,17 +167,12 @@ class BuildBase[G]:
|
|||||||
Type class instances that are available without explicit import.
|
Type class instances that are available without explicit import.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# type_class_instance_methods: dict[tuple[TypeClass, str], TypeClassArgsRouter[G, None]]
|
methods: dict[str, tuple[type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr, TypeVariableRouter[G]]]
|
||||||
"""
|
|
||||||
Methods (and operators) for type class instances that are available without explicit import.
|
|
||||||
"""
|
|
||||||
|
|
||||||
methods: dict[str, tuple[type5typeexpr.TypeExpr, TypeVariableRouter[G]]]
|
|
||||||
"""
|
"""
|
||||||
Methods that are available without explicit import.
|
Methods that are available without explicit import.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# operators: dict[str, Type3ClassMethod]
|
operators: dict[str, tuple[type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr, TypeVariableRouter[G]]]
|
||||||
"""
|
"""
|
||||||
Operators that are available without explicit import.
|
Operators that are available without explicit import.
|
||||||
"""
|
"""
|
||||||
@ -224,163 +196,68 @@ class BuildBase[G]:
|
|||||||
This calculates the value when allocated as a member, e.g. in a tuple or struct.
|
This calculates the value when allocated as a member, e.g. in a tuple or struct.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alloc_size_router: TypeApplicationRouter['BuildBase[G]', int]
|
|
||||||
"""
|
|
||||||
Helper value for calculate_alloc_size.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
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()
|
S = type5kindexpr.Star()
|
||||||
N = type5kindexpr.Nat()
|
N = type5kindexpr.Nat()
|
||||||
|
|
||||||
self.function_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> (S >> S), name="function")
|
|
||||||
self.tuple_type5_constructor_map = {}
|
|
||||||
self.dynamic_array_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> S, name="dynamic_array")
|
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.static_array_type5_constructor = type5typeexpr.TypeConstructor(kind=N >> (S >> S), name='static_array')
|
||||||
|
self.tuple_type5_constructor_map = {}
|
||||||
|
|
||||||
self.bool_ = builtins.bool_
|
|
||||||
self.bool_type5 = type5typeexpr.AtomicType('bool')
|
|
||||||
self.unit_type5 = type5typeexpr.AtomicType('()')
|
|
||||||
self.none_ = builtins.none_
|
|
||||||
self.none_type5 = type5typeexpr.AtomicType('None')
|
self.none_type5 = type5typeexpr.AtomicType('None')
|
||||||
|
self.unit_type5 = type5typeexpr.AtomicType('()')
|
||||||
|
self.bool_type5 = type5typeexpr.AtomicType('bool')
|
||||||
self.u8_type5 = type5typeexpr.AtomicType('u8')
|
self.u8_type5 = type5typeexpr.AtomicType('u8')
|
||||||
self.u32_type5 = type5typeexpr.AtomicType('u32')
|
self.u32_type5 = type5typeexpr.AtomicType('u32')
|
||||||
|
self.bytes_type5 = self.type5_make_dynamic_array(self.u8_type5)
|
||||||
|
|
||||||
self.type_info_map = {
|
self.type_info_map = {
|
||||||
'None': TypeInfo('ptr', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
|
'None': TypeInfo('None', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
|
||||||
|
'()': TypeInfo('()', WasmTypeInt32, 'unreachable', 'unreachable', 0, None),
|
||||||
'bool': TypeInfo('bool', WasmTypeInt32, '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 = {
|
self.types = {
|
||||||
'None': self.none_,
|
'None': self.none_type5,
|
||||||
'bool': self.bool_,
|
'()': self.unit_type5,
|
||||||
}
|
|
||||||
self.type5s = {
|
|
||||||
'bool': self.bool_type5,
|
'bool': self.bool_type5,
|
||||||
'u8': self.u8_type5,
|
'u8': self.u8_type5,
|
||||||
'u32': self.u32_type5,
|
'u32': self.u32_type5,
|
||||||
|
'bytes': self.bytes_type5,
|
||||||
}
|
}
|
||||||
self.type_classes = {}
|
self.type_classes = {}
|
||||||
self.type_class_instances = {}
|
self.type_class_instances = {}
|
||||||
self.type_class_instance_methods = {}
|
|
||||||
self.methods = {}
|
self.methods = {}
|
||||||
self.operators = {}
|
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_name = TypeName(self)
|
||||||
self.type5_alloc_size_root = TypeAllocSize(self, is_member=False)
|
self.type5_alloc_size_root = TypeAllocSize(self, is_member=False)
|
||||||
self.type5_alloc_size_member = TypeAllocSize(self, is_member=True)
|
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)
|
|
||||||
|
|
||||||
# self.type_classes[cls.name] = cls
|
|
||||||
# self.methods.update(cls.methods)
|
|
||||||
# self.operators.update(cls.operators)
|
|
||||||
|
|
||||||
# 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'
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# ))
|
|
||||||
|
|
||||||
# # First just register the type
|
|
||||||
# self.type_class_instances.add((cls, tuple(typ), ))
|
|
||||||
|
|
||||||
# # Then make the implementation findable
|
|
||||||
# # We route based on the type class arguments.
|
|
||||||
# tv_map: dict[TypeVariable, Type3] = {}
|
|
||||||
# tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {}
|
|
||||||
# for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
|
|
||||||
# if isinstance(arg_tv, TypeVariable):
|
|
||||||
# assert isinstance(arg_tp, Type3)
|
|
||||||
# tv_map[arg_tv] = arg_tp
|
|
||||||
# elif isinstance(arg_tv, TypeConstructorVariable):
|
|
||||||
# assert isinstance(arg_tp, TypeConstructor_Base)
|
|
||||||
# tc_map[arg_tv] = arg_tp
|
|
||||||
# else:
|
|
||||||
# raise NotImplementedError(arg_tv, arg_tp)
|
|
||||||
|
|
||||||
# for 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
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# generator = methods[method_name]
|
|
||||||
# except KeyError:
|
|
||||||
# warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ)))
|
|
||||||
# continue
|
|
||||||
|
|
||||||
# router.add(tv_map, tc_map, generator)
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# generator = operators[operator_name]
|
|
||||||
# except KeyError:
|
|
||||||
# warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ)))
|
|
||||||
# continue
|
|
||||||
|
|
||||||
# router.add(tv_map, tc_map, generator)
|
|
||||||
|
|
||||||
def register_type_class(self, cls: TypeClass) -> None:
|
def register_type_class(self, cls: TypeClass) -> None:
|
||||||
assert cls.name not in self.type_classes, 'Duplicate typeclass name'
|
assert cls.name not in self.type_classes, 'Duplicate typeclass name'
|
||||||
|
|
||||||
self.type_classes[cls.name] = cls
|
self.type_classes[cls.name] = cls
|
||||||
self.type_class_instances[cls.name] = set()
|
self.type_class_instances[cls.name] = set()
|
||||||
|
|
||||||
def register_type_class_method(
|
for mtd_nam, mtd_typ in cls.methods.items():
|
||||||
self,
|
assert mtd_nam not in self.methods, 'Duplicate typeclass method name'
|
||||||
cls: TypeClass,
|
self.methods[mtd_nam] = (mtd_typ, TypeVariableRouter(), )
|
||||||
name: str,
|
|
||||||
type: type5typeexpr.TypeExpr,
|
|
||||||
) -> None:
|
|
||||||
assert name not in self.methods, 'Duplicate typeclass method name'
|
|
||||||
|
|
||||||
self.methods[name] = (type, TypeVariableRouter(), )
|
for opr_nam, opr_typ in cls.operators.items():
|
||||||
|
assert opr_nam not in self.operators, 'Duplicate typeclass operator name'
|
||||||
|
self.operators[opr_nam] = (opr_typ, TypeVariableRouter(), )
|
||||||
|
|
||||||
def register_type_class_instance(
|
def instance_type_class(
|
||||||
self,
|
self,
|
||||||
cls: TypeClass,
|
cls: TypeClass,
|
||||||
*args: type5typeexpr.TypeExpr,
|
*args: type5typeexpr.TypeExpr,
|
||||||
methods: dict[str, Callable[[G, Any], None]],
|
methods: dict[str, Callable[[G, Any], None]] = {},
|
||||||
|
operators: dict[str, Callable[[G, Any], None]] = {},
|
||||||
) -> None:
|
) -> None:
|
||||||
self.type_class_instances[cls.name].add(tuple(args))
|
self.type_class_instances[cls.name].add(tuple(args))
|
||||||
|
|
||||||
@ -390,61 +267,9 @@ class BuildBase[G]:
|
|||||||
_, mtd_rtr = self.methods[mtd_nam]
|
_, mtd_rtr = self.methods[mtd_nam]
|
||||||
mtd_rtr.register(cls.variables, args, mtd_imp)
|
mtd_rtr.register(cls.variables, args, mtd_imp)
|
||||||
|
|
||||||
def calculate_alloc_size_static_array(self, args: tuple[Type3, IntType3]) -> int:
|
for opr_nam, opr_imp in operators.items():
|
||||||
"""
|
_, opr_rtr = self.operators[opr_nam]
|
||||||
Helper method for calculate_alloc_size - static_array
|
opr_rtr.register(cls.variables, args, opr_imp)
|
||||||
"""
|
|
||||||
sa_type, sa_len = args
|
|
||||||
|
|
||||||
return sa_len.value * self.calculate_alloc_size(sa_type, is_member=True)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
def calculate_alloc_size_struct(self, args: tuple[tuple[str, Type3], ...]) -> int:
|
|
||||||
"""
|
|
||||||
Helper method for calculate_alloc_size - struct
|
|
||||||
"""
|
|
||||||
return sum(
|
|
||||||
self.calculate_alloc_size(x, is_member=True)
|
|
||||||
for _, x in args
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if is_member:
|
|
||||||
return self.type_info_constructed.alloc_size
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.alloc_size_router(self, typ)
|
|
||||||
except NoRouteForTypeException:
|
|
||||||
raise NotImplementedError(typ)
|
|
||||||
|
|
||||||
def type5_struct_offset(self, fields: tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.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 += self.type5_alloc_size_member(memtyp)
|
|
||||||
|
|
||||||
raise RuntimeError('Member not found')
|
|
||||||
|
|
||||||
def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr:
|
def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr:
|
||||||
if not args:
|
if not args:
|
||||||
@ -476,7 +301,10 @@ class BuildBase[G]:
|
|||||||
|
|
||||||
return res_type5
|
return res_type5
|
||||||
|
|
||||||
def type5_is_function(self, typeexpr: type5typeexpr.TypeExpr) -> list[type5typeexpr.TypeExpr] | None:
|
def type5_is_function(self, typeexpr: type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr) -> list[type5typeexpr.TypeExpr] | None:
|
||||||
|
if isinstance(typeexpr, type5typeexpr.ConstrainedExpr):
|
||||||
|
typeexpr = typeexpr.expr
|
||||||
|
|
||||||
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
|
if not isinstance(typeexpr, type5typeexpr.TypeApplication):
|
||||||
return None
|
return None
|
||||||
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
|
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):
|
||||||
|
|||||||
@ -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))
|
|
||||||
|
|
||||||
@ -7,10 +7,6 @@ Contains the compiler builtins as well as some sane defaults.
|
|||||||
|
|
||||||
f32: A 32-bits IEEE 754 float, of 32 bits width.
|
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 ..type5 import typeexpr as type5typeexpr
|
||||||
from ..wasm import (
|
from ..wasm import (
|
||||||
WasmTypeFloat32,
|
WasmTypeFloat32,
|
||||||
@ -21,21 +17,21 @@ from ..wasm import (
|
|||||||
from ..wasmgenerator import Generator
|
from ..wasmgenerator import Generator
|
||||||
from .base import BuildBase, TypeInfo
|
from .base import BuildBase, TypeInfo
|
||||||
from .typeclasses import (
|
from .typeclasses import (
|
||||||
bits,
|
# bits,
|
||||||
convertable,
|
# convertable,
|
||||||
eq,
|
eq,
|
||||||
extendable,
|
# extendable,
|
||||||
floating,
|
floating,
|
||||||
foldable,
|
# foldable,
|
||||||
fractional,
|
# fractional,
|
||||||
integral,
|
# integral,
|
||||||
intnum,
|
# intnum,
|
||||||
natnum,
|
natnum,
|
||||||
ord,
|
ord,
|
||||||
promotable,
|
# promotable,
|
||||||
reinterpretable,
|
# reinterpretable,
|
||||||
sized,
|
# sized,
|
||||||
subscriptable,
|
# subscriptable,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -45,23 +41,8 @@ class BuildDefault(BuildBase[Generator]):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
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({
|
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),
|
'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),
|
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False),
|
||||||
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True),
|
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True),
|
||||||
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True),
|
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True),
|
||||||
@ -72,20 +53,6 @@ class BuildDefault(BuildBase[Generator]):
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.types.update({
|
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_,
|
|
||||||
})
|
|
||||||
|
|
||||||
self.type5s.update({
|
|
||||||
'u16': type5typeexpr.AtomicType('u16'),
|
'u16': type5typeexpr.AtomicType('u16'),
|
||||||
'u64': type5typeexpr.AtomicType('u64'),
|
'u64': type5typeexpr.AtomicType('u64'),
|
||||||
'i8': type5typeexpr.AtomicType('i8'),
|
'i8': type5typeexpr.AtomicType('i8'),
|
||||||
@ -94,16 +61,15 @@ class BuildDefault(BuildBase[Generator]):
|
|||||||
'i64': type5typeexpr.AtomicType('i64'),
|
'i64': type5typeexpr.AtomicType('i64'),
|
||||||
'f32': type5typeexpr.AtomicType('f32'),
|
'f32': type5typeexpr.AtomicType('f32'),
|
||||||
'f64': type5typeexpr.AtomicType('f64'),
|
'f64': type5typeexpr.AtomicType('f64'),
|
||||||
'bytes': type5typeexpr.TypeApplication(self.dynamic_array_type5_constructor, self.u8_type5),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
tc_list = [
|
tc_list = [
|
||||||
floating,
|
floating,
|
||||||
# bits,
|
# bits,
|
||||||
# eq, ord,
|
eq, ord,
|
||||||
# extendable, promotable,
|
# extendable, promotable,
|
||||||
# convertable, reinterpretable,
|
# convertable, reinterpretable,
|
||||||
# natnum, intnum, fractional, floating,
|
natnum, # intnum, fractional, floating,
|
||||||
# integral,
|
# integral,
|
||||||
# foldable, subscriptable,
|
# foldable, subscriptable,
|
||||||
# sized,
|
# sized,
|
||||||
|
|||||||
@ -1,23 +1,34 @@
|
|||||||
"""
|
"""
|
||||||
The Eq type class is defined for types that can be compered based on equality.
|
The Eq type class is defined for types that can be compered based on equality.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ...type3.functions import make_typevar
|
from ...type5.kindexpr import Star
|
||||||
from ...type3.routers import TypeVariableLookup
|
from ...type5.typeexpr import ConstrainedExpr, TypeVariable
|
||||||
from ...type3.typeclasses import Type3Class
|
from ...typeclass import TypeClass, TypeClassConstraint
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
from ...wasmgenerator import Generator as WasmGenerator
|
||||||
from ..base import BuildBase
|
from ..base import BuildBase
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
def load(build: BuildBase[Any]) -> None:
|
||||||
a = make_typevar('a')
|
a = TypeVariable(kind=Star(), name='a')
|
||||||
|
|
||||||
Eq = Type3Class('Eq', (a, ), methods={}, operators={
|
Eq = TypeClass('Eq', (a, ), methods={}, operators={})
|
||||||
'==': [a, a, build.bool_],
|
|
||||||
'!=': [a, a, build.bool_],
|
has_eq_a = TypeClassConstraint(Eq, [a])
|
||||||
|
|
||||||
|
fn_a_a_bool = ConstrainedExpr(
|
||||||
|
expr=build.type5_make_function([a, a, build.bool_type5]),
|
||||||
|
constraints=(has_eq_a, ),
|
||||||
|
)
|
||||||
|
|
||||||
|
Eq.operators = {
|
||||||
|
'==': fn_a_a_bool,
|
||||||
|
'!=': fn_a_a_bool,
|
||||||
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
|
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
|
||||||
})
|
}
|
||||||
|
|
||||||
build.register_type_class(Eq)
|
build.register_type_class(Eq)
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
The Floating type class is defined for Real numbers.
|
The Floating type class is defined for Real numbers.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ...type5.kindexpr import Star
|
from ...type5.kindexpr import Star
|
||||||
from ...type5.typeexpr import TypeVariable
|
from ...type5.typeexpr import TypeVariable
|
||||||
from ...type3.routers import TypeVariableLookup
|
|
||||||
from ...typeclass import TypeClass
|
from ...typeclass import TypeClass
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
from ...wasmgenerator import Generator as WasmGenerator
|
||||||
from ..base import BuildBase
|
from ..base import BuildBase
|
||||||
@ -14,18 +15,15 @@ from ..base import BuildBase
|
|||||||
def load(build: BuildBase[Any]) -> None:
|
def load(build: BuildBase[Any]) -> None:
|
||||||
a = TypeVariable(kind=Star(), name='a')
|
a = TypeVariable(kind=Star(), name='a')
|
||||||
|
|
||||||
Floating = TypeClass('Floating', [a])
|
fn_a_a = build.type5_make_function([a, a])
|
||||||
|
|
||||||
|
Floating = TypeClass('Floating', [a], methods={
|
||||||
|
'sqrt': fn_a_a
|
||||||
|
})
|
||||||
build.register_type_class(Floating)
|
build.register_type_class(Floating)
|
||||||
|
|
||||||
build.register_type_class_method(Floating, 'sqrt', build.type5_make_function([a, a]))
|
# FIXME: inherited_classes=[Fractional]
|
||||||
|
# FIXME: Do we want to expose copysign?
|
||||||
# a = make_typevar('a')
|
|
||||||
# # Fractional = build.type_classes['Fractional'] # TODO
|
|
||||||
|
|
||||||
# Floating = Type3Class('Floating', (a, ), methods={
|
|
||||||
# 'sqrt': [a, a],
|
|
||||||
# }, operators={}, inherited_classes=[Fractional])
|
|
||||||
# # FIXME: Do we want to expose copysign?
|
|
||||||
|
|
||||||
def wasm_f32_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
|
def wasm_f32_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
|
||||||
del tv_map
|
del tv_map
|
||||||
@ -38,9 +36,9 @@ def wasm_f64_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
|
|||||||
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
def wasm(build: BuildBase[WasmGenerator]) -> None:
|
||||||
Floating = build.type_classes['Floating']
|
Floating = build.type_classes['Floating']
|
||||||
|
|
||||||
build.register_type_class_instance(Floating, build.type5s['f32'], methods={
|
build.instance_type_class(Floating, build.types['f32'], methods={
|
||||||
'sqrt': wasm_f32_sqrt,
|
'sqrt': wasm_f32_sqrt,
|
||||||
})
|
})
|
||||||
build.register_type_class_instance(Floating, build.type5s['f64'], methods={
|
build.instance_type_class(Floating, build.types['f64'], methods={
|
||||||
'sqrt': wasm_f64_sqrt,
|
'sqrt': wasm_f64_sqrt,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,26 +3,42 @@ The NatNum type class is defined for Natural Number types.
|
|||||||
|
|
||||||
These cannot be negative so functions like abs and neg make no sense.
|
These cannot be negative so functions like abs and neg make no sense.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ...type3.functions import make_typevar
|
from ...type5.kindexpr import Star
|
||||||
from ...type3.routers import TypeVariableLookup
|
from ...type5.typeexpr import ConstrainedExpr, TypeVariable
|
||||||
from ...type3.typeclasses import Type3Class
|
from ...typeclass import TypeClass, TypeClassConstraint
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
from ...wasmgenerator import Generator as WasmGenerator
|
||||||
from ..base import BuildBase
|
from ..base import BuildBase
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
def load(build: BuildBase[Any]) -> None:
|
||||||
a = make_typevar('a')
|
a = TypeVariable(kind=Star(), name='a')
|
||||||
u32 = build.types['u32']
|
u32 = build.types['u32']
|
||||||
|
|
||||||
NatNum = Type3Class('NatNum', (a, ), methods={}, operators={
|
NatNum = TypeClass('NatNum', (a, ), methods={}, operators={})
|
||||||
'+': [a, a, a],
|
|
||||||
'-': [a, a, a],
|
has_natnum_a = TypeClassConstraint(NatNum, [a])
|
||||||
'*': [a, a, a],
|
|
||||||
'<<': [a, u32, a], # Arithmic shift left
|
fn_a_a_a = ConstrainedExpr(
|
||||||
'>>': [a, u32, a], # Arithmic shift right
|
expr=build.type5_make_function([a, a, a]),
|
||||||
})
|
constraints=(has_natnum_a, ),
|
||||||
|
)
|
||||||
|
|
||||||
|
fn_a_u32_a = ConstrainedExpr(
|
||||||
|
expr=build.type5_make_function([a, u32, a]),
|
||||||
|
constraints=(has_natnum_a, ),
|
||||||
|
)
|
||||||
|
|
||||||
|
NatNum.operators = {
|
||||||
|
'+': fn_a_a_a,
|
||||||
|
'-': fn_a_a_a,
|
||||||
|
'*': fn_a_a_a,
|
||||||
|
'<<': fn_a_u32_a, # Arithmic shift left
|
||||||
|
'>>': fn_a_u32_a, # Arithmic shift right
|
||||||
|
}
|
||||||
|
|
||||||
build.register_type_class(NatNum)
|
build.register_type_class(NatNum)
|
||||||
|
|
||||||
|
|||||||
@ -1,29 +1,46 @@
|
|||||||
"""
|
"""
|
||||||
The Ord type class is defined for totally ordered datatypes.
|
The Ord type class is defined for totally ordered datatypes.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ...type3.functions import make_typevar
|
from ...type5.kindexpr import Star
|
||||||
from ...type3.routers import TypeVariableLookup
|
from ...type5.typeexpr import ConstrainedExpr, TypeVariable
|
||||||
from ...type3.typeclasses import Type3Class
|
from ...typeclass import TypeClass, TypeClassConstraint
|
||||||
from ...wasmgenerator import Generator as WasmGenerator
|
from ...wasmgenerator import Generator as WasmGenerator
|
||||||
from ..base import BuildBase
|
from ..base import BuildBase
|
||||||
|
|
||||||
|
|
||||||
def load(build: BuildBase[Any]) -> None:
|
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={
|
has_ord_a = TypeClassConstraint(Ord, [a])
|
||||||
'min': [a, a, a],
|
|
||||||
'max': [a, a, a],
|
fn_a_a_a = ConstrainedExpr(
|
||||||
}, operators={
|
expr=build.type5_make_function([a, a, a]),
|
||||||
'<': [a, a, build.bool_],
|
constraints=(has_ord_a, ),
|
||||||
'<=': [a, a, build.bool_],
|
)
|
||||||
'>': [a, a, build.bool_],
|
|
||||||
'>=': [a, a, build.bool_],
|
fn_a_a_bool = ConstrainedExpr(
|
||||||
}, inherited_classes=[Eq])
|
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)
|
build.register_type_class(Ord)
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Callable, Iterable, TypeAlias
|
from typing import Any, Callable, Iterable
|
||||||
|
|
||||||
from ..type5 import typeexpr as type5typeexpr
|
from ..type5 import typeexpr as type5typeexpr
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,7 @@ from .build.base import BuildBase, TypeInfo
|
|||||||
from .build.typerouter import BuildTypeRouter
|
from .build.typerouter import BuildTypeRouter
|
||||||
from .stdlib import alloc as stdlib_alloc
|
from .stdlib import alloc as stdlib_alloc
|
||||||
from .stdlib import types as stdlib_types
|
from .stdlib import types as stdlib_types
|
||||||
from .type3.functions import FunctionArgument, TypeVariable
|
from .type5.typeexpr import AtomicType, ConstrainedExpr, TypeApplication, TypeExpr, is_concrete
|
||||||
from .type3.routers import NoRouteForTypeException
|
|
||||||
from .type3.typeclasses import Type3ClassMethod
|
|
||||||
from .type3.types import (
|
|
||||||
Type3,
|
|
||||||
)
|
|
||||||
from .type5.typeexpr import TypeExpr, is_concrete
|
|
||||||
from .type5.unify import ActionList, ReplaceVariable, unify
|
from .type5.unify import ActionList, ReplaceVariable, unify
|
||||||
from .wasm import (
|
from .wasm import (
|
||||||
WasmTypeFloat32,
|
WasmTypeFloat32,
|
||||||
@ -121,7 +115,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator],
|
|||||||
|
|
||||||
exp_type_info = mod.build.type_info_map.get(element.type5.name)
|
exp_type_info = mod.build.type_info_map.get(element.type5.name)
|
||||||
if exp_type_info is None:
|
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.add_statement('nop', comment='PRE')
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
@ -150,11 +144,14 @@ def expression_subscript_tuple(wgn: WasmGenerator, mod: ourlang.Module[WasmGener
|
|||||||
el_type = args[inp.index.value]
|
el_type = args[inp.index.value]
|
||||||
el_type_info = mod.build.type_info_map.get(el_type.name)
|
el_type_info = mod.build.type_info_map.get(el_type.name)
|
||||||
if el_type_info is None:
|
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)
|
expression(wgn, mod, inp.varref)
|
||||||
wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}')
|
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:
|
def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionCall) -> None:
|
||||||
for arg in inp.arguments:
|
for arg in inp.arguments:
|
||||||
expression(wgn, mod, arg)
|
expression(wgn, mod, arg)
|
||||||
@ -162,7 +159,12 @@ def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerat
|
|||||||
if isinstance(inp.function_instance.function, ourlang.BuiltinFunction):
|
if isinstance(inp.function_instance.function, ourlang.BuiltinFunction):
|
||||||
assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR
|
assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR
|
||||||
|
|
||||||
|
try:
|
||||||
method_type, method_router = mod.build.methods[inp.function_instance.function.name]
|
method_type, method_router = mod.build.methods[inp.function_instance.function.name]
|
||||||
|
except KeyError:
|
||||||
|
method_type, method_router = mod.build.operators[inp.function_instance.function.name]
|
||||||
|
|
||||||
|
method_type = method_type.expr if isinstance(method_type, ConstrainedExpr) else method_type
|
||||||
instance_type = inp.function_instance.type5
|
instance_type = inp.function_instance.type5
|
||||||
|
|
||||||
actions = unify(method_type, instance_type)
|
actions = unify(method_type, instance_type)
|
||||||
@ -305,10 +307,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
|
|||||||
member_type = dict(st_args)[inp.member]
|
member_type = dict(st_args)[inp.member]
|
||||||
member_type_info = mod.build.type_info_map.get(member_type.name)
|
member_type_info = mod.build.type_info_map.get(member_type.name)
|
||||||
if member_type_info is None:
|
if member_type_info is None:
|
||||||
member_type_info = mod.build.type_info_map['ptr']
|
member_type_info = mod.build.type_info_constructed
|
||||||
offset = mod.build.type5_struct_offset(
|
offset = _type5_struct_offset(mod.build, st_args, inp.member)
|
||||||
st_args, inp.member,
|
|
||||||
)
|
|
||||||
|
|
||||||
expression(wgn, mod, inp.varref)
|
expression(wgn, mod, inp.varref)
|
||||||
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(offset))
|
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(offset))
|
||||||
@ -444,7 +444,7 @@ def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) ->
|
|||||||
"""
|
"""
|
||||||
unalloc_ptr = stdlib_alloc.UNALLOC_PTR
|
unalloc_ptr = stdlib_alloc.UNALLOC_PTR
|
||||||
u32_type_info = mod.build.type_info_map['u32']
|
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''
|
allocated_data = b''
|
||||||
|
|
||||||
@ -560,7 +560,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGen
|
|||||||
for memname, mtyp5 in st_args:
|
for memname, mtyp5 in st_args:
|
||||||
mtyp5_info = mod.build.type_info_map.get(mtyp5.name)
|
mtyp5_info = mod.build.type_info_map.get(mtyp5.name)
|
||||||
if mtyp5_info is None:
|
if mtyp5_info is None:
|
||||||
mtyp5_info = mod.build.type_info_map['ptr']
|
mtyp5_info = mod.build.type_info_constructed
|
||||||
|
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
wgn.add_statement('local.get', f'${memname}')
|
wgn.add_statement('local.get', f'${memname}')
|
||||||
@ -571,8 +571,40 @@ def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGen
|
|||||||
# Return the allocated address
|
# Return the allocated address
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
|
|
||||||
def _is_concrete(type5: TypeExpr | None) -> TypeGuard[TypeExpr]:
|
def _is_concrete(type5: TypeExpr | ConstrainedExpr | None) -> TypeGuard[TypeExpr]:
|
||||||
if type5 is None:
|
if type5 is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if isinstance(type5, ConstrainedExpr):
|
||||||
|
type5 = type5.expr
|
||||||
|
|
||||||
return is_concrete(type5)
|
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
|
||||||
|
|||||||
@ -6,8 +6,6 @@ from __future__ import annotations
|
|||||||
from typing import Dict, Iterable, List, Optional, Union
|
from typing import Dict, Iterable, List, Optional, Union
|
||||||
|
|
||||||
from .build.base import BuildBase
|
from .build.base import BuildBase
|
||||||
from .type3.functions import FunctionSignature, TypeVariableContext
|
|
||||||
from .type3.types import Type3, TypeApplication_Struct
|
|
||||||
from .type5 import record as type5record
|
from .type5 import record as type5record
|
||||||
from .type5 import typeexpr as type5typeexpr
|
from .type5 import typeexpr as type5typeexpr
|
||||||
|
|
||||||
@ -34,15 +32,13 @@ class Expression:
|
|||||||
"""
|
"""
|
||||||
An expression within a statement
|
An expression within a statement
|
||||||
"""
|
"""
|
||||||
__slots__ = ('type3', 'type5', 'sourceref', )
|
__slots__ = ('type5', 'sourceref', )
|
||||||
|
|
||||||
sourceref: SourceRef
|
sourceref: SourceRef
|
||||||
type3: Type3 | None
|
|
||||||
type5: type5typeexpr.TypeExpr | None
|
type5: type5typeexpr.TypeExpr | None
|
||||||
|
|
||||||
def __init__(self, *, sourceref: SourceRef) -> None:
|
def __init__(self, *, sourceref: SourceRef) -> None:
|
||||||
self.sourceref = sourceref
|
self.sourceref = sourceref
|
||||||
self.type3 = None
|
|
||||||
self.type5 = None
|
self.type5 = None
|
||||||
|
|
||||||
class Constant(Expression):
|
class Constant(Expression):
|
||||||
@ -122,22 +118,19 @@ class ConstantStruct(ConstantMemoryStored):
|
|||||||
"""
|
"""
|
||||||
A Struct constant value expression within a statement
|
A Struct constant value expression within a statement
|
||||||
"""
|
"""
|
||||||
__slots__ = ('struct_type3', 'struct_type5', 'value', )
|
__slots__ = ('struct_type5', 'value', )
|
||||||
|
|
||||||
struct_type3: Type3
|
|
||||||
struct_type5: type5record.Record
|
struct_type5: type5record.Record
|
||||||
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']]
|
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
struct_type3: Type3,
|
|
||||||
struct_type5: type5record.Record,
|
struct_type5: type5record.Record,
|
||||||
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']],
|
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']],
|
||||||
data_block: 'ModuleDataBlock',
|
data_block: 'ModuleDataBlock',
|
||||||
sourceref: SourceRef
|
sourceref: SourceRef
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(data_block, sourceref=sourceref)
|
super().__init__(data_block, sourceref=sourceref)
|
||||||
self.struct_type3 = struct_type3
|
|
||||||
self.struct_type5 = struct_type5
|
self.struct_type5 = struct_type5
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
@ -145,7 +138,7 @@ class ConstantStruct(ConstantMemoryStored):
|
|||||||
# Do not repr the whole ModuleDataBlock
|
# Do not repr the whole ModuleDataBlock
|
||||||
# As this has a reference back to this constant for its data
|
# As this has a reference back to this constant for its data
|
||||||
# which it needs to compile the data into the program
|
# 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):
|
class VariableReference(Expression):
|
||||||
"""
|
"""
|
||||||
@ -253,17 +246,15 @@ class AccessStructMember(Expression):
|
|||||||
"""
|
"""
|
||||||
Access a struct member for reading of writing
|
Access a struct member for reading of writing
|
||||||
"""
|
"""
|
||||||
__slots__ = ('varref', 'struct_type3', 'member', )
|
__slots__ = ('varref', 'member', )
|
||||||
|
|
||||||
varref: VariableReference
|
varref: VariableReference
|
||||||
struct_type3: Type3 # FIXME: Remove this (already at varref.type5)
|
|
||||||
member: str
|
member: str
|
||||||
|
|
||||||
def __init__(self, varref: VariableReference, struct_type3: Type3, member: str, sourceref: SourceRef) -> None:
|
def __init__(self, varref: VariableReference, member: str, sourceref: SourceRef) -> None:
|
||||||
super().__init__(sourceref=sourceref)
|
super().__init__(sourceref=sourceref)
|
||||||
|
|
||||||
self.varref = varref
|
self.varref = varref
|
||||||
self.struct_type3 = struct_type3
|
|
||||||
self.member = member
|
self.member = member
|
||||||
|
|
||||||
class Statement:
|
class Statement:
|
||||||
@ -319,68 +310,60 @@ class FunctionParam:
|
|||||||
"""
|
"""
|
||||||
A parameter for a Function
|
A parameter for a Function
|
||||||
"""
|
"""
|
||||||
__slots__ = ('name', 'type3', 'type5', )
|
__slots__ = ('name', 'type5', )
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
type3: Type3
|
|
||||||
type5: type5typeexpr.TypeExpr
|
type5: type5typeexpr.TypeExpr
|
||||||
|
|
||||||
def __init__(self, name: str, type3: Type3, type5: type5typeexpr.TypeExpr) -> None:
|
def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None:
|
||||||
assert type5typeexpr.is_concrete(type5)
|
assert type5typeexpr.is_concrete(type5)
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.type3 = type3
|
|
||||||
self.type5 = type5
|
self.type5 = type5
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'FunctionParam({self.name!r}, {self.type3!r})'
|
return f'FunctionParam({self.name!r}, {self.type5!r})'
|
||||||
|
|
||||||
class Function:
|
class Function:
|
||||||
"""
|
"""
|
||||||
A function processes input and produces output
|
A function processes input and produces output
|
||||||
"""
|
"""
|
||||||
__slots__ = ('name', 'sourceref', 'exported', 'imported', 'statements', 'signature', 'returns_type3', 'type5', 'posonlyargs', 'arg_names', )
|
__slots__ = ('name', 'sourceref', 'exported', 'imported', 'statements', 'type5', 'posonlyargs', 'arg_names', )
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
sourceref: SourceRef
|
sourceref: SourceRef
|
||||||
exported: bool
|
exported: bool
|
||||||
imported: Optional[str]
|
imported: Optional[str]
|
||||||
statements: List[Statement]
|
statements: List[Statement]
|
||||||
signature: FunctionSignature # TODO: Remove me
|
type5: type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr | None
|
||||||
returns_type3: Type3 # TODO: Remove me
|
|
||||||
type5: type5typeexpr.TypeExpr | None
|
|
||||||
posonlyargs: List[FunctionParam] # TODO: Replace me with arg names
|
posonlyargs: List[FunctionParam] # TODO: Replace me with arg names
|
||||||
arg_names: list[str]
|
arg_names: list[str]
|
||||||
|
|
||||||
def __init__(self, name: str, sourceref: SourceRef, returns_type3: Type3) -> None:
|
def __init__(self, name: str, sourceref: SourceRef) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.sourceref = sourceref
|
self.sourceref = sourceref
|
||||||
self.exported = False
|
self.exported = False
|
||||||
self.imported = None
|
self.imported = None
|
||||||
self.statements = []
|
self.statements = []
|
||||||
self.signature = FunctionSignature(TypeVariableContext(), [])
|
|
||||||
self.returns_type3 = returns_type3
|
|
||||||
self.type5 = None
|
self.type5 = None
|
||||||
self.posonlyargs = []
|
self.posonlyargs = []
|
||||||
self.arg_names = []
|
self.arg_names = []
|
||||||
|
|
||||||
class BuiltinFunction(Function):
|
class BuiltinFunction(Function):
|
||||||
def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None:
|
def __init__(self, name: str, type5: type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr) -> None:
|
||||||
super().__init__(name, SourceRef("/", 0, 0), None)
|
super().__init__(name, SourceRef("/", 0, 0))
|
||||||
self.type5 = type5
|
self.type5 = type5
|
||||||
|
|
||||||
class StructDefinition:
|
class StructDefinition:
|
||||||
"""
|
"""
|
||||||
The definition for a struct
|
The definition for a struct
|
||||||
"""
|
"""
|
||||||
__slots__ = ('struct_type3', 'struct_type5', 'sourceref', )
|
__slots__ = ('struct_type5', 'sourceref', )
|
||||||
|
|
||||||
struct_type3: Type3
|
|
||||||
struct_type5: type5record.Record
|
struct_type5: type5record.Record
|
||||||
sourceref: SourceRef
|
sourceref: SourceRef
|
||||||
|
|
||||||
def __init__(self, struct_type3: Type3, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
|
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
|
||||||
self.struct_type3 = struct_type3
|
|
||||||
self.struct_type5 = struct_type5
|
self.struct_type5 = struct_type5
|
||||||
self.sourceref = sourceref
|
self.sourceref = sourceref
|
||||||
|
|
||||||
@ -391,44 +374,32 @@ class StructConstructor(Function):
|
|||||||
A function will generated to instantiate a struct. The arguments
|
A function will generated to instantiate a struct. The arguments
|
||||||
will be the defaults
|
will be the defaults
|
||||||
"""
|
"""
|
||||||
__slots__ = ('struct_type3', 'struct_type5', )
|
__slots__ = ('struct_type5', )
|
||||||
|
|
||||||
struct_type3: Type3
|
|
||||||
struct_type5: type5record.Record
|
struct_type5: type5record.Record
|
||||||
|
|
||||||
def __init__(self, struct_type3: Type3, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
|
def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
|
||||||
super().__init__(f'@{struct_type3.name}@__init___@', sourceref, struct_type3)
|
super().__init__(f'@{struct_type5.name}@__init___@', sourceref)
|
||||||
|
|
||||||
assert isinstance(struct_type3.application, TypeApplication_Struct)
|
|
||||||
|
|
||||||
mem_typ5_map = dict(struct_type5.fields)
|
|
||||||
|
|
||||||
for mem, typ in struct_type3.application.arguments:
|
|
||||||
self.arg_names.append(mem)
|
|
||||||
self.posonlyargs.append(FunctionParam(mem, typ, mem_typ5_map[mem]))
|
|
||||||
self.signature.args.append(typ)
|
|
||||||
|
|
||||||
self.signature.args.append(struct_type3)
|
|
||||||
|
|
||||||
self.struct_type3 = struct_type3
|
|
||||||
self.struct_type5 = struct_type5
|
self.struct_type5 = struct_type5
|
||||||
|
|
||||||
|
for mem, typ in struct_type5.fields:
|
||||||
|
self.arg_names.append(mem)
|
||||||
|
self.posonlyargs.append(FunctionParam(mem, typ))
|
||||||
|
|
||||||
class ModuleConstantDef:
|
class ModuleConstantDef:
|
||||||
"""
|
"""
|
||||||
A constant definition within a module
|
A constant definition within a module
|
||||||
"""
|
"""
|
||||||
__slots__ = ('name', 'sourceref', 'type3', 'type5', 'constant', )
|
__slots__ = ('name', 'sourceref', 'type5', 'constant', )
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
sourceref: SourceRef
|
sourceref: SourceRef
|
||||||
type3: Type3
|
|
||||||
type5: type5typeexpr.TypeExpr
|
type5: type5typeexpr.TypeExpr
|
||||||
constant: Constant
|
constant: Constant
|
||||||
|
|
||||||
def __init__(self, name: str, sourceref: SourceRef, type3: Type3, type5: type5typeexpr.TypeExpr, constant: Constant) -> None:
|
def __init__(self, name: str, sourceref: SourceRef, type5: type5typeexpr.TypeExpr, constant: Constant) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.sourceref = sourceref
|
self.sourceref = sourceref
|
||||||
self.type3 = type3
|
|
||||||
self.type5 = type5
|
self.type5 = type5
|
||||||
self.constant = constant
|
self.constant = constant
|
||||||
|
|
||||||
@ -468,13 +439,12 @@ class Module[G]:
|
|||||||
build: BuildBase[G]
|
build: BuildBase[G]
|
||||||
filename: str
|
filename: str
|
||||||
data: ModuleData
|
data: ModuleData
|
||||||
types: dict[str, Type3]
|
types: dict[str, type5typeexpr.TypeExpr]
|
||||||
type5s: dict[str, type5typeexpr.TypeExpr]
|
|
||||||
struct_definitions: Dict[str, StructDefinition]
|
struct_definitions: Dict[str, StructDefinition]
|
||||||
constant_defs: Dict[str, ModuleConstantDef]
|
constant_defs: Dict[str, ModuleConstantDef]
|
||||||
functions: Dict[str, Function]
|
functions: Dict[str, Function]
|
||||||
methods: Dict[str, type5typeexpr.TypeExpr]
|
methods: Dict[str, type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr]
|
||||||
operators: Dict[str, type5typeexpr.TypeExpr]
|
operators: Dict[str, type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr]
|
||||||
functions_table: dict[Function, int]
|
functions_table: dict[Function, int]
|
||||||
|
|
||||||
def __init__(self, build: BuildBase[G], filename: str) -> None:
|
def __init__(self, build: BuildBase[G], filename: str) -> None:
|
||||||
@ -483,7 +453,6 @@ class Module[G]:
|
|||||||
|
|
||||||
self.data = ModuleData()
|
self.data = ModuleData()
|
||||||
self.types = {}
|
self.types = {}
|
||||||
self.type5s = {}
|
|
||||||
self.struct_definitions = {}
|
self.struct_definitions = {}
|
||||||
self.constant_defs = {}
|
self.constant_defs = {}
|
||||||
self.functions = {}
|
self.functions = {}
|
||||||
|
|||||||
119
phasm/parser.py
119
phasm/parser.py
@ -35,8 +35,6 @@ from .ourlang import (
|
|||||||
TupleInstantiation,
|
TupleInstantiation,
|
||||||
VariableReference,
|
VariableReference,
|
||||||
)
|
)
|
||||||
from .type3.typeclasses import Type3ClassMethod
|
|
||||||
from .type3.types import IntType3, Type3
|
|
||||||
from .type5 import typeexpr as type5typeexpr
|
from .type5 import typeexpr as type5typeexpr
|
||||||
from .wasmgenerator import Generator
|
from .wasmgenerator import Generator
|
||||||
|
|
||||||
@ -103,9 +101,8 @@ class OurVisitor[G]:
|
|||||||
module = Module(self.build, "-")
|
module = Module(self.build, "-")
|
||||||
|
|
||||||
module.methods.update({k: v[0] for k, v in self.build.methods.items()})
|
module.methods.update({k: v[0] for k, v in self.build.methods.items()})
|
||||||
module.operators.update(self.build.operators)
|
module.operators.update({k: v[0] for k, v in self.build.operators.items()})
|
||||||
module.types.update(self.build.types)
|
module.types.update(self.build.types)
|
||||||
module.type5s.update(self.build.type5s)
|
|
||||||
|
|
||||||
_not_implemented(not node.type_ignores, 'Module.type_ignores')
|
_not_implemented(not node.type_ignores, 'Module.type_ignores')
|
||||||
|
|
||||||
@ -123,17 +120,16 @@ class OurVisitor[G]:
|
|||||||
module.constant_defs[res.name] = res
|
module.constant_defs[res.name] = res
|
||||||
|
|
||||||
if isinstance(res, StructDefinition):
|
if isinstance(res, StructDefinition):
|
||||||
if res.struct_type3.name in module.types:
|
if res.struct_type5.name in module.types:
|
||||||
raise StaticError(
|
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.types[res.struct_type5.name] = res.struct_type5
|
||||||
module.type5s[res.struct_type5.name] = res.struct_type5
|
module.functions[res.struct_type5.name] = StructConstructor(res.struct_type5, res.sourceref)
|
||||||
module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3, 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])
|
||||||
module.functions[res.struct_type3.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
|
# 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 isinstance(res, Function):
|
||||||
if res.name in module.functions:
|
if res.name in module.functions:
|
||||||
@ -163,7 +159,7 @@ class OurVisitor[G]:
|
|||||||
raise NotImplementedError(f'{node} on Module')
|
raise NotImplementedError(f'{node} on Module')
|
||||||
|
|
||||||
def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function:
|
def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function:
|
||||||
function = Function(node.name, srf(module, node), self.build.none_)
|
function = Function(node.name, srf(module, node))
|
||||||
|
|
||||||
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
|
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
|
||||||
|
|
||||||
@ -173,7 +169,6 @@ class OurVisitor[G]:
|
|||||||
if arg.annotation is None:
|
if arg.annotation is None:
|
||||||
_raise_static_error(node, 'Must give a argument type')
|
_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 = self.visit_type5(module, arg.annotation)
|
||||||
|
|
||||||
arg_type5_list.append(arg_type5)
|
arg_type5_list.append(arg_type5)
|
||||||
@ -182,10 +177,8 @@ class OurVisitor[G]:
|
|||||||
# This would also require FunctionParam to accept a placeholder
|
# This would also require FunctionParam to accept a placeholder
|
||||||
|
|
||||||
function.arg_names.append(arg.arg)
|
function.arg_names.append(arg.arg)
|
||||||
function.signature.args.append(arg_type)
|
|
||||||
function.posonlyargs.append(FunctionParam(
|
function.posonlyargs.append(FunctionParam(
|
||||||
arg.arg,
|
arg.arg,
|
||||||
arg_type,
|
|
||||||
arg_type5,
|
arg_type5,
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -229,12 +222,8 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
if node.returns is None: # Note: `-> None` would be a ast.Constant
|
if node.returns is None: # Note: `-> None` would be a ast.Constant
|
||||||
_raise_static_error(node, 'Must give a return type')
|
_raise_static_error(node, 'Must give a return type')
|
||||||
return_type = self.visit_type(module, node.returns)
|
|
||||||
arg_type5_list.append(self.visit_type5(module, node.returns))
|
arg_type5_list.append(self.visit_type5(module, node.returns))
|
||||||
|
|
||||||
function.signature.args.append(return_type)
|
|
||||||
function.returns_type3 = return_type
|
|
||||||
|
|
||||||
function.type5 = module.build.type5_make_function(arg_type5_list)
|
function.type5 = module.build.type5_make_function(arg_type5_list)
|
||||||
|
|
||||||
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
|
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
|
||||||
@ -247,8 +236,7 @@ class OurVisitor[G]:
|
|||||||
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
_not_implemented(not node.keywords, 'ClassDef.keywords')
|
||||||
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
|
||||||
|
|
||||||
members: Dict[str, Type3] = {}
|
members: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {}
|
||||||
members5: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {}
|
|
||||||
|
|
||||||
for stmt in node.body:
|
for stmt in node.body:
|
||||||
if not isinstance(stmt, ast.AnnAssign):
|
if not isinstance(stmt, ast.AnnAssign):
|
||||||
@ -266,15 +254,12 @@ class OurVisitor[G]:
|
|||||||
if stmt.target.id in members:
|
if stmt.target.id in members:
|
||||||
_raise_static_error(stmt, 'Struct members must have unique names')
|
_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)
|
field_type5 = self.visit_type5(module, stmt.annotation)
|
||||||
assert isinstance(field_type5, (type5typeexpr.AtomicType, type5typeexpr.TypeApplication, ))
|
assert isinstance(field_type5, (type5typeexpr.AtomicType, type5typeexpr.TypeApplication, ))
|
||||||
members5[stmt.target.id] = field_type5
|
members[stmt.target.id] = field_type5
|
||||||
|
|
||||||
return StructDefinition(
|
return StructDefinition(
|
||||||
module.build.struct(node.name, tuple(members.items())),
|
module.build.type5_make_struct(node.name, tuple(members.items())),
|
||||||
module.build.type5_make_struct(node.name, tuple(members5.items())),
|
|
||||||
srf(module, node),
|
srf(module, node),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -290,7 +275,6 @@ class OurVisitor[G]:
|
|||||||
return ModuleConstantDef(
|
return ModuleConstantDef(
|
||||||
node.target.id,
|
node.target.id,
|
||||||
srf(module, node),
|
srf(module, node),
|
||||||
self.visit_type(module, node.annotation),
|
|
||||||
self.visit_type5(module, node.annotation),
|
self.visit_type5(module, node.annotation),
|
||||||
value_data,
|
value_data,
|
||||||
)
|
)
|
||||||
@ -304,7 +288,6 @@ class OurVisitor[G]:
|
|||||||
return ModuleConstantDef(
|
return ModuleConstantDef(
|
||||||
node.target.id,
|
node.target.id,
|
||||||
srf(module, node),
|
srf(module, node),
|
||||||
self.visit_type(module, node.annotation),
|
|
||||||
self.visit_type5(module, node.annotation),
|
self.visit_type5(module, node.annotation),
|
||||||
value_data,
|
value_data,
|
||||||
)
|
)
|
||||||
@ -318,7 +301,6 @@ class OurVisitor[G]:
|
|||||||
return ModuleConstantDef(
|
return ModuleConstantDef(
|
||||||
node.target.id,
|
node.target.id,
|
||||||
srf(module, node),
|
srf(module, node),
|
||||||
self.visit_type(module, node.annotation),
|
|
||||||
self.visit_type5(module, node.annotation),
|
self.visit_type5(module, node.annotation),
|
||||||
value_data,
|
value_data,
|
||||||
)
|
)
|
||||||
@ -386,7 +368,7 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
def visit_Module_FunctionDef_expr(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
|
def visit_Module_FunctionDef_expr(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
|
||||||
if isinstance(node, ast.BinOp):
|
if isinstance(node, ast.BinOp):
|
||||||
operator: Union[str, Type3ClassMethod]
|
operator: str
|
||||||
|
|
||||||
if isinstance(node.op, ast.Add):
|
if isinstance(node.op, ast.Add):
|
||||||
operator = '+'
|
operator = '+'
|
||||||
@ -417,7 +399,7 @@ class OurVisitor[G]:
|
|||||||
raise NotImplementedError(f'Operator {operator}')
|
raise NotImplementedError(f'Operator {operator}')
|
||||||
|
|
||||||
return BinaryOp(
|
return BinaryOp(
|
||||||
FunctionInstance(module.operators[operator], srf(module, node)),
|
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.left),
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
|
||||||
srf(module, node),
|
srf(module, node),
|
||||||
@ -446,7 +428,7 @@ class OurVisitor[G]:
|
|||||||
raise NotImplementedError(f'Operator {operator}')
|
raise NotImplementedError(f'Operator {operator}')
|
||||||
|
|
||||||
return BinaryOp(
|
return BinaryOp(
|
||||||
FunctionInstance(module.operators[operator], srf(module, node)),
|
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.left),
|
||||||
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
|
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
|
||||||
srf(module, node),
|
srf(module, node),
|
||||||
@ -511,7 +493,7 @@ class OurVisitor[G]:
|
|||||||
if not isinstance(node.func.ctx, ast.Load):
|
if not isinstance(node.func.ctx, ast.Load):
|
||||||
_raise_static_error(node, 'Must be load context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
func: Union[Function, FunctionParam, Type3ClassMethod]
|
func: Union[Function, FunctionParam]
|
||||||
|
|
||||||
if node.func.id in module.methods:
|
if node.func.id in module.methods:
|
||||||
func = BuiltinFunction(node.func.id, module.methods[node.func.id])
|
func = BuiltinFunction(node.func.id, module.methods[node.func.id])
|
||||||
@ -543,7 +525,6 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
return AccessStructMember(
|
return AccessStructMember(
|
||||||
varref,
|
varref,
|
||||||
varref.variable.type3,
|
|
||||||
node.attr,
|
node.attr,
|
||||||
srf(module, node),
|
srf(module, node),
|
||||||
)
|
)
|
||||||
@ -617,7 +598,7 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
data_block = ModuleDataBlock(struct_data)
|
data_block = ModuleDataBlock(struct_data)
|
||||||
module.data.blocks.append(data_block)
|
module.data.blocks.append(data_block)
|
||||||
return ConstantStruct(struct_def.struct_type3, struct_def.struct_type5, struct_data, data_block, srf(module, node))
|
return ConstantStruct(struct_def.struct_type5, struct_data, data_block, srf(module, node))
|
||||||
|
|
||||||
_not_implemented(node.kind is None, 'Constant.kind')
|
_not_implemented(node.kind is None, 'Constant.kind')
|
||||||
|
|
||||||
@ -634,70 +615,6 @@ class OurVisitor[G]:
|
|||||||
|
|
||||||
raise NotImplementedError(f'{node.value} as constant')
|
raise NotImplementedError(f'{node.value} as constant')
|
||||||
|
|
||||||
def visit_type(self, module: Module[G], node: ast.expr) -> Type3:
|
|
||||||
if isinstance(node, ast.Constant):
|
|
||||||
if node.value is None:
|
|
||||||
return module.types['None']
|
|
||||||
|
|
||||||
_raise_static_error(node, f'Unrecognized type {node.value!r}')
|
|
||||||
|
|
||||||
if isinstance(node, ast.Name):
|
|
||||||
if not isinstance(node.ctx, ast.Load):
|
|
||||||
_raise_static_error(node, 'Must be load context')
|
|
||||||
|
|
||||||
if node.id in module.types:
|
|
||||||
return module.types[node.id]
|
|
||||||
|
|
||||||
_raise_static_error(node, f'Unrecognized type {node.id}')
|
|
||||||
|
|
||||||
if isinstance(node, ast.Subscript):
|
|
||||||
if isinstance(node.value, ast.Name) and node.value.id == 'Callable':
|
|
||||||
func_arg_types: list[ast.expr]
|
|
||||||
|
|
||||||
if isinstance(node.slice, ast.Name):
|
|
||||||
func_arg_types = [node.slice]
|
|
||||||
elif isinstance(node.slice, ast.Tuple):
|
|
||||||
func_arg_types = node.slice.elts
|
|
||||||
else:
|
|
||||||
_raise_static_error(node, 'Must subscript using a list of types')
|
|
||||||
|
|
||||||
# Function type
|
|
||||||
return module.build.function(*[
|
|
||||||
self.visit_type(module, e)
|
|
||||||
for e in func_arg_types
|
|
||||||
])
|
|
||||||
|
|
||||||
if isinstance(node.slice, ast.Slice):
|
|
||||||
_raise_static_error(node, 'Must subscript using an index')
|
|
||||||
|
|
||||||
if not isinstance(node.slice, ast.Constant):
|
|
||||||
_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),
|
|
||||||
)
|
|
||||||
|
|
||||||
if not isinstance(node.slice.value, int):
|
|
||||||
_raise_static_error(node, 'Must subscript using a constant integer index')
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
raise NotImplementedError(f'{node} as type')
|
|
||||||
|
|
||||||
def visit_type5(self, module: Module[G], node: ast.expr) -> type5typeexpr.TypeExpr:
|
def visit_type5(self, module: Module[G], node: ast.expr) -> type5typeexpr.TypeExpr:
|
||||||
if isinstance(node, ast.Constant):
|
if isinstance(node, ast.Constant):
|
||||||
if node.value is None:
|
if node.value is None:
|
||||||
@ -709,8 +626,8 @@ class OurVisitor[G]:
|
|||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
_raise_static_error(node, 'Must be load context')
|
_raise_static_error(node, 'Must be load context')
|
||||||
|
|
||||||
if node.id in module.type5s:
|
if node.id in module.types:
|
||||||
return module.type5s[node.id]
|
return module.types[node.id]
|
||||||
|
|
||||||
_raise_static_error(node, f'Unrecognized type {node.id}')
|
_raise_static_error(node, f'Unrecognized type {node.id}')
|
||||||
|
|
||||||
|
|||||||
@ -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)})'
|
|
||||||
@ -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)
|
|
||||||
@ -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 ^ === ')
|
|
||||||
@ -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})'
|
|
||||||
@ -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]
|
|
||||||
@ -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)
|
|
||||||
@ -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)
|
|
||||||
@ -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
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
from .kindexpr import Star
|
|
||||||
from .record import Record
|
|
||||||
from .typeexpr import AtomicType, TypeApplication, TypeConstructor, TypeVariable
|
|
||||||
from .unify import unify
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
S = Star()
|
|
||||||
|
|
||||||
a_var = TypeVariable(name="a", kind=S)
|
|
||||||
b_var = TypeVariable(name="b", kind=S)
|
|
||||||
t_var = TypeVariable(name="t", kind=S >> S)
|
|
||||||
r_var = TypeVariable(name="r", kind=S >> S)
|
|
||||||
print(a_var)
|
|
||||||
print(b_var)
|
|
||||||
print(t_var)
|
|
||||||
print(r_var)
|
|
||||||
print()
|
|
||||||
|
|
||||||
u32_type = AtomicType(name="u32")
|
|
||||||
f64_type = AtomicType(name="f64")
|
|
||||||
print(u32_type)
|
|
||||||
print(f64_type)
|
|
||||||
print()
|
|
||||||
|
|
||||||
maybe_constructor = TypeConstructor(name="Maybe", kind=S >> S)
|
|
||||||
maybe_a_type = TypeApplication(constructor=maybe_constructor, argument=a_var)
|
|
||||||
maybe_u32_type = TypeApplication(constructor=maybe_constructor, argument=u32_type)
|
|
||||||
print(maybe_constructor)
|
|
||||||
print(maybe_a_type)
|
|
||||||
print(maybe_u32_type)
|
|
||||||
print()
|
|
||||||
|
|
||||||
either_constructor = TypeConstructor(name="Either", kind=S >> (S >> S))
|
|
||||||
either_a_constructor = TypeApplication(constructor=either_constructor, argument=a_var)
|
|
||||||
either_a_a_type = TypeApplication(constructor=either_a_constructor, argument=a_var)
|
|
||||||
either_u32_constructor = TypeApplication(constructor=either_constructor, argument=u32_type)
|
|
||||||
either_u32_f64_type = TypeApplication(constructor=either_u32_constructor, argument=f64_type)
|
|
||||||
either_u32_a_type = TypeApplication(constructor=either_u32_constructor, argument=a_var)
|
|
||||||
print(either_constructor)
|
|
||||||
print(either_a_constructor)
|
|
||||||
print(either_a_a_type)
|
|
||||||
print(either_u32_constructor)
|
|
||||||
print(either_u32_f64_type)
|
|
||||||
print(either_u32_a_type)
|
|
||||||
print()
|
|
||||||
|
|
||||||
t_a_type = TypeApplication(constructor=t_var, argument=a_var)
|
|
||||||
t_u32_type = TypeApplication(constructor=t_var, argument=u32_type)
|
|
||||||
print(t_a_type)
|
|
||||||
print(t_u32_type)
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
shape_record = Record("Shape", (
|
|
||||||
("width", u32_type),
|
|
||||||
("height", either_u32_f64_type),
|
|
||||||
))
|
|
||||||
print('shape_record', shape_record)
|
|
||||||
print()
|
|
||||||
|
|
||||||
values = [
|
|
||||||
a_var,
|
|
||||||
b_var,
|
|
||||||
u32_type,
|
|
||||||
f64_type,
|
|
||||||
t_var,
|
|
||||||
t_a_type,
|
|
||||||
r_var,
|
|
||||||
maybe_constructor,
|
|
||||||
maybe_u32_type,
|
|
||||||
maybe_a_type,
|
|
||||||
either_u32_constructor,
|
|
||||||
either_u32_a_type,
|
|
||||||
either_u32_f64_type,
|
|
||||||
]
|
|
||||||
|
|
||||||
seen_exprs: set[str] = set()
|
|
||||||
for lft in values:
|
|
||||||
for rgt in values:
|
|
||||||
expr = f"{lft.name} ~ {rgt.name}"
|
|
||||||
if expr in seen_exprs:
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(expr.ljust(40) + " => " + str(unify(lft, rgt)))
|
|
||||||
|
|
||||||
inv_expr = f"{rgt.name} ~ {lft.name}"
|
|
||||||
seen_exprs.add(inv_expr)
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@ -5,12 +5,10 @@ from typing import Any, Callable, Iterable, Protocol, Sequence
|
|||||||
|
|
||||||
from ..build.base import BuildBase
|
from ..build.base import BuildBase
|
||||||
from ..ourlang import SourceRef
|
from ..ourlang import SourceRef
|
||||||
from ..type3 import types as type3types
|
|
||||||
from ..wasm import WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, WasmTypeInt64
|
from ..wasm import WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, WasmTypeInt64
|
||||||
from .kindexpr import KindExpr, Star
|
from .kindexpr import KindExpr, Star
|
||||||
from .record import Record
|
from .record import Record
|
||||||
from .typeexpr import (
|
from .typeexpr import (
|
||||||
AtomicType,
|
|
||||||
TypeExpr,
|
TypeExpr,
|
||||||
TypeVariable,
|
TypeVariable,
|
||||||
is_concrete,
|
is_concrete,
|
||||||
@ -440,6 +438,10 @@ class TypeClassInstanceExistsConstraint(ConstraintBase):
|
|||||||
if len(c_arg_list) != len(self.arg_list):
|
if len(c_arg_list) != len(self.arg_list):
|
||||||
return skip_for_now()
|
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)
|
key = tuple(c_arg_list)
|
||||||
existing_instances = self.ctx.build.type_class_instances[self.typeclass]
|
existing_instances = self.ctx.build.type_class_instances[self.typeclass]
|
||||||
if key in existing_instances:
|
if key in existing_instances:
|
||||||
@ -456,27 +458,3 @@ class TypeClassInstanceExistsConstraint(ConstraintBase):
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
args = ' '.join(self.ctx.build.type5_name(x) for x in self.arg_list)
|
args = ' '.join(self.ctx.build.type5_name(x) for x in self.arg_list)
|
||||||
return f'Exists {self.typeclass} {args}'
|
return f'Exists {self.typeclass} {args}'
|
||||||
|
|
||||||
class RecordFoundException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _type5_to_type3_or_type3_const(build: BuildBase[Any], type5: TypeExpr) -> type3types.Type3 | type3types.TypeConstructor_Base[Any] :
|
|
||||||
if isinstance(type5, Record):
|
|
||||||
raise RecordFoundException
|
|
||||||
|
|
||||||
if isinstance(type5, AtomicType):
|
|
||||||
return build.types[type5.name]
|
|
||||||
|
|
||||||
da_arg5 = build.type5_is_dynamic_array(type5)
|
|
||||||
if da_arg5 is not None:
|
|
||||||
return build.dynamic_array
|
|
||||||
|
|
||||||
sa_arg5 = build.type5_is_static_array(type5)
|
|
||||||
if sa_arg5 is not None:
|
|
||||||
return build.static_array
|
|
||||||
|
|
||||||
tp_arg5 = build.type5_is_tuple(type5)
|
|
||||||
if tp_arg5 is not None:
|
|
||||||
return build.tuple_
|
|
||||||
|
|
||||||
raise NotImplementedError(type5)
|
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
from typing import Any, Generator
|
from typing import Any, Generator
|
||||||
|
|
||||||
from .. import ourlang
|
from .. import ourlang
|
||||||
from ..type3 import functions as type3functions
|
|
||||||
from ..type3 import typeclasses as type3classes
|
|
||||||
from ..type3 import types as type3types
|
|
||||||
from .constraints import (
|
from .constraints import (
|
||||||
CanAccessStructMemberConstraint,
|
CanAccessStructMemberConstraint,
|
||||||
CanBeSubscriptedConstraint,
|
CanBeSubscriptedConstraint,
|
||||||
@ -16,8 +13,8 @@ from .constraints import (
|
|||||||
TypeClassInstanceExistsConstraint,
|
TypeClassInstanceExistsConstraint,
|
||||||
UnifyTypesConstraint,
|
UnifyTypesConstraint,
|
||||||
)
|
)
|
||||||
from .kindexpr import Star
|
from .typeexpr import ConstrainedExpr, TypeApplication, TypeExpr, TypeVariable, instantiate, instantiate_constrained
|
||||||
from .typeexpr import TypeApplication, TypeExpr, TypeVariable, instantiate
|
from ..typeclass import TypeClassConstraint
|
||||||
|
|
||||||
ConstraintGenerator = Generator[ConstraintBase, None, None]
|
ConstraintGenerator = Generator[ConstraintBase, None, None]
|
||||||
|
|
||||||
@ -94,11 +91,7 @@ def expression_variable_reference(ctx: Context, inp: ourlang.VariableReference,
|
|||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.variable.type5, phft)
|
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.variable.type5, phft)
|
||||||
|
|
||||||
def expression_binary_operator(ctx: Context, inp: ourlang.BinaryOp, phft: TypeVariable) -> ConstraintGenerator:
|
def expression_binary_operator(ctx: Context, inp: ourlang.BinaryOp, phft: TypeVariable) -> ConstraintGenerator:
|
||||||
yield from expression_function_call(
|
yield from expression_function_call(ctx, _binary_op_to_function(inp), phft)
|
||||||
ctx,
|
|
||||||
_binary_op_to_function(ctx, inp),
|
|
||||||
phft,
|
|
||||||
)
|
|
||||||
|
|
||||||
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: TypeVariable) -> ConstraintGenerator:
|
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: TypeVariable) -> ConstraintGenerator:
|
||||||
arg_typ_list = []
|
arg_typ_list = []
|
||||||
@ -107,20 +100,44 @@ def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: Type
|
|||||||
yield from expression(ctx, arg, arg_tv)
|
yield from expression(ctx, arg, arg_tv)
|
||||||
arg_typ_list.append(arg_tv)
|
arg_typ_list.append(arg_tv)
|
||||||
|
|
||||||
assert isinstance(inp.function_instance.function.type5, TypeExpr)
|
make_placeholder = lambda x, p: ctx.make_placeholder(kind=x, prefix=p)
|
||||||
inp.function_instance.type5 = ctx.make_placeholder(inp.function_instance)
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, instantiate(inp.function_instance.function.type5, {}, lambda x, p: ctx.make_placeholder(kind=x, prefix=p)), inp.function_instance.type5)
|
ftp5 = inp.function_instance.function.type5
|
||||||
# constraints = []
|
assert ftp5 is not None
|
||||||
|
if isinstance(ftp5, ConstrainedExpr):
|
||||||
|
ftp5 = instantiate_constrained(ftp5, {}, make_placeholder)
|
||||||
|
|
||||||
|
for type_constraint in ftp5.constraints:
|
||||||
|
if isinstance(type_constraint, TypeClassConstraint):
|
||||||
|
yield TypeClassInstanceExistsConstraint(ctx, inp.sourceref, type_constraint.cls.name, type_constraint.variables)
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise NotImplementedError(type_constraint)
|
||||||
|
|
||||||
|
ftp5 = ftp5.expr
|
||||||
|
else:
|
||||||
|
ftp5 = instantiate(ftp5, {}, make_placeholder)
|
||||||
|
|
||||||
|
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])
|
expr_type = ctx.build.type5_make_function(arg_typ_list + [phft])
|
||||||
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.function_instance.type5, expr_type)
|
yield UnifyTypesConstraint(ctx, inp.sourceref, phft2, expr_type)
|
||||||
# yield from constraints
|
|
||||||
|
|
||||||
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator:
|
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator:
|
||||||
assert inp.function.type5 is not None # Todo: Make not nullable
|
assert inp.function.type5 is not None # Todo: Make not nullable
|
||||||
|
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.function.type5, phft)
|
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:
|
def expression_tuple_instantiation(ctx: Context, inp: ourlang.TupleInstantiation, phft: TypeVariable) -> ConstraintGenerator:
|
||||||
arg_typ_list = []
|
arg_typ_list = []
|
||||||
@ -196,7 +213,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement
|
|||||||
assert args is not None
|
assert args is not None
|
||||||
type5 = args[-1]
|
type5 = args[-1]
|
||||||
else:
|
else:
|
||||||
type5 = fun.type5
|
type5 = fun.type5.expr if isinstance(fun.type5, ConstrainedExpr) else fun.type5
|
||||||
|
|
||||||
yield from expression(ctx, inp.value, phft)
|
yield from expression(ctx, inp.value, phft)
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft)
|
yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft)
|
||||||
@ -247,99 +264,13 @@ def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator:
|
|||||||
|
|
||||||
# TODO: Generalize?
|
# TODO: Generalize?
|
||||||
|
|
||||||
def _binary_op_to_function(ctx: Context, inp: ourlang.BinaryOp) -> ourlang.FunctionCall:
|
def _binary_op_to_function(inp: ourlang.BinaryOp) -> ourlang.FunctionCall:
|
||||||
"""
|
"""
|
||||||
Temporary method while migrating from type3 to type5
|
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
|
assert inp.sourceref is not None # TODO: sourceref required
|
||||||
call = ourlang.FunctionCall(inp.operator, inp.sourceref)
|
call = ourlang.FunctionCall(inp.operator, inp.sourceref)
|
||||||
call.arguments = [inp.left, inp.right]
|
call.arguments = [inp.left, inp.right]
|
||||||
return call
|
return call
|
||||||
|
|
||||||
class TypeVarMap:
|
|
||||||
__slots__ = ('ctx', 'cache', )
|
|
||||||
|
|
||||||
ctx: Context
|
|
||||||
cache: dict[type3functions.TypeVariable, TypeVariable | TypeApplication]
|
|
||||||
|
|
||||||
def __init__(self, ctx: Context):
|
|
||||||
self.ctx = ctx
|
|
||||||
self.cache = {}
|
|
||||||
|
|
||||||
def __getitem__(self, var: type3functions.TypeVariable) -> TypeVariable | TypeApplication:
|
|
||||||
exists = self.cache.get(var)
|
|
||||||
if exists is not None:
|
|
||||||
return exists
|
|
||||||
|
|
||||||
if isinstance(var.application, type3functions.TypeVariableApplication_Nullary):
|
|
||||||
res_var = self.ctx.make_placeholder()
|
|
||||||
self.cache[var] = res_var
|
|
||||||
return res_var
|
|
||||||
|
|
||||||
if isinstance(var.application, type3functions.TypeVariableApplication_Unary):
|
|
||||||
# TODO: t a -> t a
|
|
||||||
# solve by caching var.application.constructor in separate map
|
|
||||||
cvar = self.ctx.make_placeholder(kind=Star() >> Star())
|
|
||||||
avar = self.__getitem__(var.application.arguments)
|
|
||||||
res_app = TypeApplication(constructor=cvar, argument=avar)
|
|
||||||
self.cache[var] = res_app
|
|
||||||
return res_app
|
|
||||||
|
|
||||||
raise NotImplementedError(var)
|
|
||||||
|
|
||||||
def _signature_to_type5(
|
|
||||||
ctx: Context,
|
|
||||||
sourceref: ourlang.SourceRef,
|
|
||||||
signature: type3functions.FunctionSignature,
|
|
||||||
) -> tuple[TypeExpr, list[TypeClassInstanceExistsConstraint]]:
|
|
||||||
"""
|
|
||||||
Temporary hack while migrating from type3 to type5
|
|
||||||
"""
|
|
||||||
tv_map = TypeVarMap(ctx)
|
|
||||||
|
|
||||||
args: list[TypeExpr] = []
|
|
||||||
for t3arg in signature.args:
|
|
||||||
if isinstance(t3arg, type3types.Type3):
|
|
||||||
args.append(ctx.build.type5s[t3arg.name])
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(t3arg, type3functions.TypeVariable):
|
|
||||||
args.append(tv_map[t3arg])
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(t3arg, type3functions.FunctionArgument):
|
|
||||||
func_t3arg_list: list[TypeExpr] = []
|
|
||||||
for func_t3arg in t3arg.args:
|
|
||||||
if isinstance(func_t3arg, type3types.Type3):
|
|
||||||
func_t3arg_list.append(ctx.build.type5s[t3arg.name])
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(func_t3arg, type3functions.TypeVariable):
|
|
||||||
func_t3arg_list.append(tv_map[func_t3arg])
|
|
||||||
continue
|
|
||||||
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
args.append(ctx.build.type5_make_function(func_t3arg_list))
|
|
||||||
continue
|
|
||||||
|
|
||||||
raise NotImplementedError(t3arg)
|
|
||||||
|
|
||||||
constraints: list[TypeClassInstanceExistsConstraint] = []
|
|
||||||
|
|
||||||
for const in signature.context.constraints:
|
|
||||||
if isinstance(const, type3functions.Constraint_TypeClassInstanceExists):
|
|
||||||
constraints.append(TypeClassInstanceExistsConstraint(
|
|
||||||
ctx,
|
|
||||||
sourceref,
|
|
||||||
const.type_class3.name,
|
|
||||||
[
|
|
||||||
tv_map[x]
|
|
||||||
for x in const.types
|
|
||||||
],
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
|
|
||||||
raise NotImplementedError(const)
|
|
||||||
|
|
||||||
return (ctx.build.type5_make_function(args), constraints, )
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from typing import Callable, Sequence
|
from typing import Callable, Iterable, Self, Sequence
|
||||||
|
|
||||||
from .kindexpr import Arrow, KindExpr, Nat, Star
|
from .kindexpr import Arrow, KindExpr, Nat, Star
|
||||||
|
|
||||||
@ -38,8 +38,6 @@ class TypeVariable(TypeExpr):
|
|||||||
"""
|
"""
|
||||||
A placeholder in a type expression
|
A placeholder in a type expression
|
||||||
"""
|
"""
|
||||||
constraints: Sequence[Callable[[TypeExpr], bool]] = ()
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash((self.kind, self.name))
|
return hash((self.kind, self.name))
|
||||||
|
|
||||||
@ -53,7 +51,11 @@ class TypeApplication(TypeExpr):
|
|||||||
constructor: TypeConstructor | TypeApplication | TypeVariable
|
constructor: TypeConstructor | TypeApplication | TypeVariable
|
||||||
argument: TypeExpr
|
argument: TypeExpr
|
||||||
|
|
||||||
def __init__(self, constructor: TypeConstructor | TypeApplication | TypeVariable, argument: TypeExpr) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
constructor: TypeConstructor | TypeApplication | TypeVariable,
|
||||||
|
argument: TypeExpr,
|
||||||
|
) -> None:
|
||||||
if isinstance(constructor.kind, Star):
|
if isinstance(constructor.kind, Star):
|
||||||
raise TypeError("A constructor cannot be a concrete type")
|
raise TypeError("A constructor cannot be a concrete type")
|
||||||
|
|
||||||
@ -71,6 +73,23 @@ class TypeApplication(TypeExpr):
|
|||||||
self.constructor = constructor
|
self.constructor = constructor
|
||||||
self.argument = argument
|
self.argument = argument
|
||||||
|
|
||||||
|
class TypeConstraint:
|
||||||
|
"""
|
||||||
|
Base class for type contraints
|
||||||
|
"""
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def instantiate(self, known_map: dict[TypeVariable, TypeVariable], make_variable: Callable[[KindExpr, str], TypeVariable]) -> Self:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ConstrainedExpr:
|
||||||
|
expr: TypeExpr
|
||||||
|
constraints: tuple[TypeConstraint, ...]
|
||||||
|
|
||||||
def occurs(lft: TypeVariable, rgt: TypeApplication) -> bool:
|
def occurs(lft: TypeVariable, rgt: TypeApplication) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks whether the given variable occurs in the given application.
|
Checks whether the given variable occurs in the given application.
|
||||||
@ -152,6 +171,14 @@ def instantiate(
|
|||||||
known_map: dict[TypeVariable, TypeVariable],
|
known_map: dict[TypeVariable, TypeVariable],
|
||||||
make_variable: Callable[[KindExpr, str], TypeVariable],
|
make_variable: Callable[[KindExpr, str], TypeVariable],
|
||||||
) -> TypeExpr:
|
) -> 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):
|
if isinstance(expr, AtomicType):
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
@ -176,3 +203,18 @@ def instantiate(
|
|||||||
)
|
)
|
||||||
|
|
||||||
raise NotImplementedError(expr)
|
raise NotImplementedError(expr)
|
||||||
|
|
||||||
|
def instantiate_constrained(
|
||||||
|
constrainedexpr: ConstrainedExpr,
|
||||||
|
known_map: dict[TypeVariable, TypeVariable],
|
||||||
|
make_variable: Callable[[KindExpr, str], TypeVariable],
|
||||||
|
) -> ConstrainedExpr:
|
||||||
|
"""
|
||||||
|
Instantiates a type expression and its constraints
|
||||||
|
"""
|
||||||
|
expr = instantiate(constrainedexpr.expr, known_map, make_variable)
|
||||||
|
constraints = tuple(
|
||||||
|
x.instantiate(known_map, make_variable)
|
||||||
|
for x in constrainedexpr.constraints
|
||||||
|
)
|
||||||
|
return ConstrainedExpr(expr, constraints)
|
||||||
|
|||||||
@ -1,8 +1,38 @@
|
|||||||
from dataclasses import dataclass
|
import dataclasses
|
||||||
|
from typing import Callable, Self
|
||||||
|
|
||||||
from ..type5.typeexpr import TypeVariable
|
from ..type5.kindexpr import KindExpr
|
||||||
|
from ..type5.typeexpr import TypeConstraint, TypeExpr, TypeVariable, instantiate
|
||||||
|
|
||||||
@dataclass
|
@dataclasses.dataclass
|
||||||
class TypeClass:
|
class TypeClass:
|
||||||
name: str
|
name: str
|
||||||
variables: list[TypeVariable]
|
variables: list[TypeVariable]
|
||||||
|
methods: dict[str, TypeExpr] = dataclasses.field(default_factory=dict)
|
||||||
|
operators: dict[str, TypeExpr] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
|
class TypeClassConstraint(TypeConstraint):
|
||||||
|
__slots__ = ('cls', 'variables', )
|
||||||
|
|
||||||
|
def __init__(self, cls: TypeClass, variables: list[TypeVariable]) -> None:
|
||||||
|
self.cls = cls
|
||||||
|
self.variables = variables
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
vrs = ' '.join(x.name for x in self.variables)
|
||||||
|
|
||||||
|
return f'{self.cls.name} {vrs}'
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
vrs = ', '.join(str(x) for x in self.variables)
|
||||||
|
|
||||||
|
return f'TypeClassConstraint({self.cls.name}, [{vrs}])'
|
||||||
|
|
||||||
|
def instantiate(self, known_map: dict[TypeVariable, TypeVariable], make_variable: Callable[[KindExpr, str], TypeVariable]) -> Self:
|
||||||
|
return TypeClassConstraint(
|
||||||
|
self.cls,
|
||||||
|
[
|
||||||
|
instantiate(var, known_map, make_variable)
|
||||||
|
for var in self.variables
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Callable, Generator, Iterable, List, TextIO, Union
|
from typing import Any, Callable, Generator, Iterable, List, TextIO, Union
|
||||||
|
|
||||||
from phasm import compiler
|
from phasm import compiler
|
||||||
from phasm.build import builtins
|
|
||||||
from phasm.codestyle import phasm_render
|
from phasm.codestyle import phasm_render
|
||||||
from phasm.type3 import types as type3types
|
from phasm.type5.typeexpr import TypeExpr
|
||||||
from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter
|
|
||||||
from phasm.wasm import (
|
from phasm.wasm import (
|
||||||
WasmTypeFloat32,
|
WasmTypeFloat32,
|
||||||
WasmTypeFloat64,
|
WasmTypeFloat64,
|
||||||
@ -17,6 +17,7 @@ from phasm.wasm import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from . import runners
|
from . import runners
|
||||||
|
from . import memory
|
||||||
|
|
||||||
DASHES = '-' * 16
|
DASHES = '-' * 16
|
||||||
|
|
||||||
@ -115,7 +116,10 @@ class Suite:
|
|||||||
if do_format_check:
|
if do_format_check:
|
||||||
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
|
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]
|
||||||
|
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):
|
if len(func_args) != len(args):
|
||||||
raise RuntimeError(f'Invalid number of args for {func_name}')
|
raise RuntimeError(f'Invalid number of args for {func_name}')
|
||||||
|
|
||||||
@ -151,11 +155,8 @@ class Suite:
|
|||||||
result = SuiteResult()
|
result = SuiteResult()
|
||||||
result.returned_value = runner.call(func_name, *wasm_args)
|
result.returned_value = runner.call(func_name, *wasm_args)
|
||||||
|
|
||||||
result.returned_value = _load_memory_stored_returned_value(
|
loader = memory.Loader(runner.phasm_ast.build, runner)(func_ret)
|
||||||
runner,
|
result.returned_value = loader(result.returned_value)
|
||||||
func_name,
|
|
||||||
result.returned_value,
|
|
||||||
)
|
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
write_header(sys.stderr, 'Memory (post run)')
|
write_header(sys.stderr, 'Memory (post run)')
|
||||||
@ -169,7 +170,7 @@ def write_header(textio: TextIO, msg: str) -> None:
|
|||||||
def _write_memory_stored_value(
|
def _write_memory_stored_value(
|
||||||
runner: runners.RunnerBase,
|
runner: runners.RunnerBase,
|
||||||
adr: int,
|
adr: int,
|
||||||
val_typ: type3types.Type3,
|
val_typ: TypeExpr,
|
||||||
val: Any,
|
val: Any,
|
||||||
) -> int:
|
) -> int:
|
||||||
try:
|
try:
|
||||||
@ -197,7 +198,7 @@ def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> in
|
|||||||
runner.interpreter_write_memory(adr + 4, val)
|
runner.interpreter_write_memory(adr + 4, val)
|
||||||
return adr
|
return adr
|
||||||
|
|
||||||
def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_args: tuple[type3types.Type3]) -> int:
|
def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_args: tuple[TypeExpr]) -> int:
|
||||||
runner, val = attrs
|
runner, val = attrs
|
||||||
|
|
||||||
da_type, = da_args
|
da_type, = da_args
|
||||||
@ -219,7 +220,7 @@ def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any],
|
|||||||
offset += _write_memory_stored_value(runner, offset, da_type, val_el_val)
|
offset += _write_memory_stored_value(runner, offset, da_type, val_el_val)
|
||||||
return adr
|
return adr
|
||||||
|
|
||||||
def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int:
|
def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[TypeExpr, type3types.IntType3]) -> int:
|
||||||
runner, val = attrs
|
runner, val = attrs
|
||||||
|
|
||||||
sa_type, sa_len = sa_args
|
sa_type, sa_len = sa_args
|
||||||
@ -239,7 +240,7 @@ def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any],
|
|||||||
offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val)
|
offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val)
|
||||||
return adr
|
return adr
|
||||||
|
|
||||||
def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, type3types.Type3], ...]) -> int:
|
def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, TypeExpr], ...]) -> int:
|
||||||
runner, val = attrs
|
runner, val = attrs
|
||||||
|
|
||||||
assert isinstance(val, dict)
|
assert isinstance(val, dict)
|
||||||
@ -259,7 +260,7 @@ def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_arg
|
|||||||
|
|
||||||
return adr
|
return adr
|
||||||
|
|
||||||
def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args: tuple[type3types.Type3, ...]) -> int:
|
def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args: tuple[TypeExpr, ...]) -> int:
|
||||||
runner, val = attrs
|
runner, val = attrs
|
||||||
|
|
||||||
assert isinstance(val, tuple)
|
assert isinstance(val, tuple)
|
||||||
@ -276,12 +277,6 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args
|
|||||||
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
|
||||||
return adr
|
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(
|
def _load_memory_stored_returned_value(
|
||||||
runner: runners.RunnerBase,
|
runner: runners.RunnerBase,
|
||||||
func_name: str,
|
func_name: str,
|
||||||
@ -316,7 +311,7 @@ def _load_memory_stored_returned_value(
|
|||||||
|
|
||||||
return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
|
return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
|
||||||
|
|
||||||
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
|
def _unpack(runner: runners.RunnerBase, typ: TypeExpr, inp: bytes) -> Any:
|
||||||
typ_info = runner.phasm_ast.build.type_info_map.get(typ.name)
|
typ_info = runner.phasm_ast.build.type_info_map.get(typ.name)
|
||||||
|
|
||||||
if typ_info is None:
|
if typ_info is None:
|
||||||
@ -377,7 +372,7 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator
|
|||||||
yield all_bytes[offset:offset + size]
|
yield all_bytes[offset:offset + size]
|
||||||
offset += size
|
offset += size
|
||||||
|
|
||||||
def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_args: tuple[type3types.Type3]) -> Any:
|
def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_args: tuple[TypeExpr]) -> Any:
|
||||||
runner, adr = attrs
|
runner, adr = attrs
|
||||||
da_type, = da_args
|
da_type, = da_args
|
||||||
|
|
||||||
@ -400,7 +395,7 @@ def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_a
|
|||||||
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
|
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> Any:
|
def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[TypeExpr, type3types.IntType3]) -> Any:
|
||||||
runner, adr = attrs
|
runner, adr = attrs
|
||||||
sub_typ, len_typ = sa_args
|
sub_typ, len_typ = sa_args
|
||||||
|
|
||||||
@ -418,7 +413,7 @@ def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_ar
|
|||||||
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
|
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]:
|
def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tuple[tuple[str, TypeExpr], ...]) -> dict[str, Any]:
|
||||||
runner, adr = attrs
|
runner, adr = attrs
|
||||||
|
|
||||||
sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
|
sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
|
||||||
@ -435,7 +430,7 @@ def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tu
|
|||||||
for (arg_name, arg_typ, ), arg_bytes in zip(st_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
|
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:
|
def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tuple[TypeExpr, ...]) -> Any:
|
||||||
runner, adr = attrs
|
runner, adr = attrs
|
||||||
|
|
||||||
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n')
|
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n')
|
||||||
@ -451,9 +446,3 @@ def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tup
|
|||||||
_unpack(runner, arg_typ, arg_bytes)
|
_unpack(runner, arg_typ, arg_bytes)
|
||||||
for arg_typ, arg_bytes in zip(tp_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
|
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)
|
|
||||||
|
|||||||
145
tests/integration/memory.py
Normal file
145
tests/integration/memory.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
from typing import Any, Callable, Protocol, TypeAlias
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from phasm.build.base import BuildBase
|
||||||
|
from phasm.build.typerouter import BuildTypeRouter
|
||||||
|
from phasm.type5.typeexpr import AtomicType, TypeExpr
|
||||||
|
from phasm.wasm import (
|
||||||
|
WasmTypeFloat32,
|
||||||
|
WasmTypeFloat64,
|
||||||
|
WasmTypeInt32,
|
||||||
|
WasmTypeInt64,
|
||||||
|
WasmTypeNone,
|
||||||
|
)
|
||||||
|
|
||||||
|
class MemoryAccess(Protocol):
|
||||||
|
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class LoaderFunc(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 Loader(BuildTypeRouter[LoaderFunc]):
|
||||||
|
__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) -> LoaderFunc:
|
||||||
|
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:
|
||||||
|
return BoolLoader()
|
||||||
|
|
||||||
|
return IntLoader(type_info.signed, type_info.alloc_size)
|
||||||
|
|
||||||
|
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
|
||||||
|
return FloatLoader(type_info.alloc_size)
|
||||||
|
|
||||||
|
raise NotImplementedError(typ)
|
||||||
|
|
||||||
|
def when_dynamic_array(self, da_arg: TypeExpr) -> LoaderFunc:
|
||||||
|
if da_arg.name == 'u8':
|
||||||
|
return BytesLoader(self.access)
|
||||||
|
|
||||||
|
return DynamicArrayLoader(self.access, self(da_arg))
|
||||||
|
|
||||||
|
class BoolLoader:
|
||||||
|
__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 IntLoader:
|
||||||
|
__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:
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
|
||||||
|
# Work around the fact that phasm has unsigned integers but wasm does not
|
||||||
|
data = wasm_value.to_bytes(8, 'big', signed=True)
|
||||||
|
data = data[-self.alloc_size:]
|
||||||
|
wasm_value = int.from_bytes(data, 'big', signed=self.signed)
|
||||||
|
|
||||||
|
return wasm_value
|
||||||
|
|
||||||
|
class FloatLoader:
|
||||||
|
__slots__ = ('alloc_size', )
|
||||||
|
|
||||||
|
def __init__(self, alloc_size: int) -> None:
|
||||||
|
self.alloc_size = alloc_size
|
||||||
|
|
||||||
|
def __call__(self, wasm_value: Any) -> float:
|
||||||
|
assert isinstance(wasm_value, float), wasm_value
|
||||||
|
return wasm_value
|
||||||
|
|
||||||
|
class DynamicArrayLoader:
|
||||||
|
__slots__ = ('access', 'alloc_size', 'sub_loader', )
|
||||||
|
|
||||||
|
access: MemoryAccess
|
||||||
|
alloc_size: int
|
||||||
|
sub_loader: LoaderFunc
|
||||||
|
|
||||||
|
def __init__(self, access: MemoryAccess, sub_loader: LoaderFunc) -> None:
|
||||||
|
self.access = access
|
||||||
|
self.sub_loader = sub_loader
|
||||||
|
|
||||||
|
def __call__(self, wasm_value: Any) -> Any:
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
adr = wasm_value
|
||||||
|
del wasm_value
|
||||||
|
|
||||||
|
# wasm_value must be a pointer
|
||||||
|
# The first value at said pointer is the length of the array
|
||||||
|
|
||||||
|
read_bytes = self.access.interpreter_read_memory(adr, 4)
|
||||||
|
array_len, = struct.unpack('<I', read_bytes)
|
||||||
|
adr += 4
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class BytesLoader:
|
||||||
|
__slots__ = ('access', 'alloc_size', )
|
||||||
|
|
||||||
|
access: MemoryAccess
|
||||||
|
alloc_size: int
|
||||||
|
|
||||||
|
def __init__(self, access: MemoryAccess) -> None:
|
||||||
|
self.access = access
|
||||||
|
|
||||||
|
def __call__(self, wasm_value: Any) -> bytes:
|
||||||
|
assert isinstance(wasm_value, int), wasm_value
|
||||||
|
adr = wasm_value
|
||||||
|
del wasm_value
|
||||||
|
|
||||||
|
# wasm_value must be a pointer
|
||||||
|
# The first value at said pointer is the length of the array
|
||||||
|
|
||||||
|
read_bytes = self.access.interpreter_read_memory(adr, 4)
|
||||||
|
array_len, = struct.unpack('<I', read_bytes)
|
||||||
|
adr += 4
|
||||||
|
|
||||||
|
return self.access.interpreter_read_memory(adr, array_len)
|
||||||
@ -10,7 +10,6 @@ from phasm import ourlang, wasm
|
|||||||
from phasm.compiler import phasm_compile
|
from phasm.compiler import phasm_compile
|
||||||
from phasm.optimise.removeunusedfuncs import removeunusedfuncs
|
from phasm.optimise.removeunusedfuncs import removeunusedfuncs
|
||||||
from phasm.parser import phasm_parse
|
from phasm.parser import phasm_parse
|
||||||
from phasm.type3.entry import phasm_type3
|
|
||||||
from phasm.type5.solver import phasm_type5
|
from phasm.type5.solver import phasm_type5
|
||||||
from phasm.wasmgenerator import Generator as WasmGenerator
|
from phasm.wasmgenerator import Generator as WasmGenerator
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user