phasm/phasm/type3/types.py
Johan B.W. de Vries 1f1a3442cd Moves the prelude to runtime
Previously, it was hardcoded at 'compile' time (in as much
Python has that). This would make it more difficult to add
stuff to it. Also, in a lot of places we made assumptions
about prelude instead of checking properly.
2025-05-29 12:10:12 +02:00

291 lines
8.3 KiB
Python

"""
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