""" The base class for build environments. Contains nothing but the explicit compiler builtins. """ from typing import Any, Callable, NamedTuple, Type from warnings import warn from ..type3.functions import ( TypeConstructorVariable, TypeVariable, ) from ..type3.routers import ( NoRouteForTypeException, TypeApplicationRouter, TypeClassArgsRouter, TypeVariableLookup, ) from ..type3.typeclasses import Type3Class, Type3ClassMethod from ..type3.types import ( IntType3, Type3, TypeConstructor_Base, TypeConstructor_DynamicArray, TypeConstructor_Function, TypeConstructor_StaticArray, TypeConstructor_Struct, TypeConstructor_Tuple, ) from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone from . import builtins TypeInfo = NamedTuple('TypeInfo', [ # Name of the type ('typ', str, ), # What WebAssembly type to use when passing this value around # For example in function arguments ('wasm_type', Type[WasmType]), # What WebAssembly function to use when loading a value from memory ('wasm_load_func', str), # What WebAssembly function to use when storing a value to memory ('wasm_store_func', str), # When storing this value in memory, how many bytes do we use? # Only valid for non-constructed types, see calculate_alloc_size # Should match wasm_load_func / wasm_store_func ('alloc_size', int), # When storing integers, the values can be stored as natural number # (False) or as integer number (True). For other types, this is None. ('signed', bool | None), ]) class MissingImplementationWarning(Warning): pass class BuildBase[G]: __slots__ = ( 'dynamic_array', 'function', 'static_array', 'struct', 'tuple_', 'none_', 'bool_', 'type_info_map', 'type_info_constructed', 'types', 'type_classes', 'type_class_instances', 'type_class_instance_methods', 'methods', 'operators', '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. """ function: TypeConstructor_Function """ This is a function. It should be applied with one or more arguments. The last argument is the 'return' type. """ static_array: TypeConstructor_StaticArray """ This is a fixed length piece of memory. It should be applied with two arguments. It has a compile time determined length, and each argument is the same. """ struct: TypeConstructor_Struct """ This is like a tuple, but each argument is named, so that developers can get and set fields by name. """ tuple_: TypeConstructor_Tuple """ 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. """ none_: Type3 """ The none type, for when functions simply don't return anything. e.g., IO(). """ bool_: Type3 """ The bool type, either True or False """ type_info_map: dict[str, TypeInfo] """ Map from type name to the info of that type """ type_info_constructed: TypeInfo """ By default, constructed types are passed as pointers NOTE: ALLOC SIZE IN THIS STRUCT DOES NOT WORK FOR CONSTRUCTED TYPES USE calculate_alloc_size FOR ACCURATE RESULTS Functions count as constructed types - even though they are not memory pointers but table addresses instead. """ types: dict[str, Type3] """ Types that are available without explicit import. """ type_classes: dict[str, Type3Class] """ Type classes that are available without explicit import. """ type_class_instances: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] """ Type class instances that are available without explicit import. """ type_class_instance_methods: dict[Type3ClassMethod, TypeClassArgsRouter[G, None]] """ Methods (and operators) for type class instances that are available without explicit import. """ methods: dict[str, Type3ClassMethod] """ Methods that are available without explicit import. """ operators: dict[str, Type3ClassMethod] """ Operators that are available without explicit import. """ alloc_size_router: TypeApplicationRouter['BuildBase[G]', int] """ Helper value for calculate_alloc_size. """ 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_ self.bool_ = builtins.bool_ self.none_ = builtins.none_ self.type_info_map = { 'None': TypeInfo('ptr', WasmTypeNone, 'unreachable', 'unreachable', 0, None), 'bool': TypeInfo('bool', WasmTypeInt32, 'unreachable', 'unreachable', 0, None), 'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False), } self.type_info_constructed = self.type_info_map['ptr'] self.types = { 'None': self.none_, 'bool': self.bool_, } self.type_classes = {} self.type_class_instances = set() self.type_class_instance_methods = {} self.methods = {} self.operators = {} self.alloc_size_router = TypeApplicationRouter['BuildBase[G]', int]() self.alloc_size_router.add(self.static_array, self.__class__.calculate_alloc_size_static_array) self.alloc_size_router.add(self.struct, self.__class__.calculate_alloc_size_struct) self.alloc_size_router.add(self.tuple_, self.__class__.calculate_alloc_size_tuple) 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 calculate_alloc_size_static_array(self, args: tuple[Type3, IntType3]) -> int: """ Helper method for calculate_alloc_size - static_array """ 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 calculate_member_offset(self, st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int: """ Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name. """ result = 0 for memnam, memtyp in st_args: if needle == memnam: return result result += self.calculate_alloc_size(memtyp, is_member=True) raise Exception(f'{needle} not in {st_name}')