phasm/phasm/type3/types.py
2023-01-07 16:24:50 +01:00

323 lines
7.4 KiB
Python

"""
Contains the final types for use in Phasm
These are actual, instantiated types; not the abstract types that the
constraint generator works with.
"""
from typing import Any, Dict, Iterable, List, Optional, Protocol, Union
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
class ExpressionProtocol(Protocol):
"""
A protocol for classes that should be updated on substitution
"""
type3: 'Type3OrPlaceholder'
"""
The type to update
"""
class Type3:
"""
Base class for the type3 types
"""
__slots__ = ('name', )
name: str
"""
The name of the string, as parsed and outputted by codestyle.
"""
def __init__(self, name: str) -> None:
self.name = name
def __repr__(self) -> str:
return f'Type3("{self.name}")'
def __str__(self) -> str:
return self.name
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}')
return str(self)
def __eq__(self, other: Any) -> bool:
if isinstance(other, PlaceholderForType):
return False
if not isinstance(other, Type3):
raise NotImplementedError
return self is other
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
raise NotImplementedError
def __bool__(self) -> bool:
raise NotImplementedError
class PrimitiveType3(Type3):
"""
Intermediate class to tell primitive types from others
"""
__slots__ = ()
class IntType3(Type3):
"""
Sometimes you can have an int as type, e.g. when using static arrays
"""
__slots__ = ('value', )
value: int
def __init__(self, value: int) -> None:
super().__init__(str(value))
assert 0 <= value
self.value = value
def __eq__(self, other: Any) -> bool:
if isinstance(other, IntType3):
return self.value == other.value
if isinstance(other, Type3):
return False
raise NotImplementedError
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]
class AppliedType3(Type3):
"""
A Type3 that has been applied to another type
"""
__slots__ = ('base', 'args', )
base: PrimitiveType3
"""
The base type
"""
args: List[Type3OrPlaceholder]
"""
The applied types (or placeholders there for)
"""
def __init__(self, base: PrimitiveType3, args: Iterable[Type3OrPlaceholder]) -> None:
args = [*args]
assert args, 'Must at least one argument'
super().__init__(
base.name
+ ' ('
+ ') ('.join(str(x) for x in args) # FIXME: Do we need to redo the name on substitution?
+ ')'
)
self.base = base
self.args = args
@property
def has_placeholders(self) -> bool:
return any(
isinstance(x, PlaceholderForType)
for x in self.args
)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Type3):
raise NotImplementedError
if not isinstance(other, AppliedType3):
return False
return (
self.base == other.base
and len(self.args) == len(other.args)
and all(
s == x
for s, x in zip(self.args, other.args)
)
)
def __repr__(self) -> str:
return f'AppliedType3({repr(self.base)}, {repr(self.args)})'
class StructType3(Type3):
"""
A Type3 struct with named members
"""
__slots__ = ('name', 'members', )
name: str
"""
The structs fully qualified name
"""
members: Dict[str, Type3]
"""
The struct's field definitions
"""
def __init__(self, name: str, members: Dict[str, Type3]) -> None:
super().__init__(name)
self.name = name
self.members = dict(members)
def __repr__(self) -> str:
return f'StructType3(repr({self.name}), repr({self.members}))'
none = PrimitiveType3('none')
"""
The none type, for when functions simply don't return anything. e.g., IO().
"""
bool_ = PrimitiveType3('bool')
"""
The bool type, either True or False
"""
u8 = PrimitiveType3('u8')
"""
The unsigned 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8.
"""
u32 = PrimitiveType3('u32')
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32.
"""
u64 = PrimitiveType3('u64')
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64.
"""
i8 = PrimitiveType3('i8')
"""
The signed 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8, but
with the middel point being 0.
"""
i32 = PrimitiveType3('i32')
"""
The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32, but
with the middel point being 0.
"""
i64 = PrimitiveType3('i64')
"""
The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64, but
with the middel point being 0.
"""
f32 = PrimitiveType3('f32')
"""
A 32-bits IEEE 754 float, of 32 bits width.
"""
f64 = PrimitiveType3('f64')
"""
A 32-bits IEEE 754 float, of 64 bits width.
"""
bytes = PrimitiveType3('bytes')
"""
This is a runtime-determined length piece of memory that can be indexed at runtime.
"""
static_array = PrimitiveType3('static_array')
"""
This is a fixed length piece of memory that can be indexed at runtime.
It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated.
"""
tuple = PrimitiveType3('tuple') # pylint: disable=W0622
"""
This is a fixed length piece of memory.
It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
"""
LOOKUP_TABLE: Dict[str, Type3] = {
'none': none,
'bool': bool_,
'u8': u8,
'u32': u32,
'u64': u64,
'i8': i8,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes,
}