diff --git a/phasm/build/__init__.py b/phasm/build/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/phasm/build/base.py b/phasm/build/base.py new file mode 100644 index 0000000..89760b1 --- /dev/null +++ b/phasm/build/base.py @@ -0,0 +1,304 @@ +""" +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 +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_', + + 'type_info_map', + 'type_info_constructed', + + 'types', + '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(). + """ + + 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_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.none_ = builtins.none + + self.type_info_map = { + 'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False), + } + self.type_info_constructed = self.type_info_map['ptr'] + + self.types = { + 'None': self.none_, + } + 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 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) + + # TODO: Check for required existing instantiations + + # 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}') diff --git a/phasm/build/builtins.py b/phasm/build/builtins.py new file mode 100644 index 0000000..9da3895 --- /dev/null +++ b/phasm/build/builtins.py @@ -0,0 +1,25 @@ +""" +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') + +none = Type3('none', TypeApplication_Nullary(None, None)) + diff --git a/phasm/build/default.py b/phasm/build/default.py new file mode 100644 index 0000000..426ab4f --- /dev/null +++ b/phasm/build/default.py @@ -0,0 +1,111 @@ +""" +The default class for build environments. + +Contains the compiler builtins as well as some sane defaults. + +# Added types + +f32: A 32-bits IEEE 754 float, of 32 bits width. +""" +from ..stdlib import types as stdlib_types +from ..type3.functions import ( + TypeVariable, + TypeVariableApplication_Nullary, +) +from ..type3.typeclasses import Type3Class +from ..type3.types import ( + Type3, + TypeApplication_Nullary, +) +from ..wasm import ( + WasmTypeFloat32, + WasmTypeFloat64, + WasmTypeInt32, + WasmTypeInt64, +) +from ..wasmgenerator import Generator +from .base import BuildBase, TypeInfo + + +class BuildDefault(BuildBase[Generator]): + def __init__(self) -> None: + super().__init__() + + u8 = Type3('u8', TypeApplication_Nullary(None, None)) + u16 = Type3('u16', TypeApplication_Nullary(None, None)) + u32 = Type3('u32', TypeApplication_Nullary(None, None)) + u64 = Type3('u64', TypeApplication_Nullary(None, None)) + i8 = Type3('i8', TypeApplication_Nullary(None, None)) + i16 = Type3('i16', TypeApplication_Nullary(None, None)) + i32 = Type3('i32', TypeApplication_Nullary(None, None)) + i64 = Type3('i64', TypeApplication_Nullary(None, None)) + f32 = Type3('f32', TypeApplication_Nullary(None, None)) + f64 = Type3('f64', TypeApplication_Nullary(None, None)) + + bytes_ = self.dynamic_array(u8) + + self.type_info_map.update({ + 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8_u', 1, False), + 'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16_u', 2, False), + 'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False), + 'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False), + 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8_s', 1, True), + 'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16_s', 2, True), + 'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4, True), + 'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8, True), + 'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4, None), + 'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8, None), + }) + + 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_, + }) + + a = TypeVariable('a', TypeVariableApplication_Nullary(None, None)) + + NatNum = Type3Class('NatNum', (a, ), methods={}, operators={ + '+': [a, a, a], + '-': [a, a, a], + '*': [a, a, a], + '<<': [a, u32, a], # Arithmic shift left + '>>': [a, u32, a], # Arithmic shift right + }) + + self.instance_type_class(NatNum, i32, operators={ + '+': stdlib_types.i32_natnum_add, + '-': stdlib_types.i32_natnum_sub, + '*': stdlib_types.i32_natnum_mul, + '<<': stdlib_types.i32_natnum_arithmic_shift_left, + '>>': stdlib_types.i32_natnum_arithmic_shift_right, + }) + self.instance_type_class(NatNum, f32, operators={ + '+': stdlib_types.f32_natnum_add, + '-': stdlib_types.f32_natnum_sub, + '*': stdlib_types.f32_natnum_mul, + '<<': stdlib_types.f32_natnum_arithmic_shift_left, + '>>': stdlib_types.f32_natnum_arithmic_shift_right, + }) + self.instance_type_class(NatNum, f64, operators={ + '+': stdlib_types.f64_natnum_add, + '-': stdlib_types.f64_natnum_sub, + '*': stdlib_types.f64_natnum_mul, + '<<': stdlib_types.f64_natnum_arithmic_shift_left, + '>>': stdlib_types.f64_natnum_arithmic_shift_right, + }) + + self.methods.update({ + **NatNum.methods, + }) + self.operators.update({ + **NatNum.operators, + }) diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 940ecfa..1209c70 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -3,13 +3,13 @@ This module generates source code based on the parsed AST It's intented to be a "any color, as long as it's black" kind of renderer """ -from typing import Generator +from typing import Any, Generator -from . import ourlang, prelude +from . import ourlang from .type3.types import Type3, TypeApplication_Struct -def phasm_render(inp: ourlang.Module) -> str: +def phasm_render(inp: ourlang.Module[Any]) -> str: """ Public method for rendering a Phasm module into Phasm code """ @@ -21,9 +21,6 @@ def type3(inp: Type3) -> str: """ Render: type's name """ - if inp is prelude.none: - return 'None' - return inp.name def struct_definition(inp: ourlang.StructDefinition) -> str: @@ -161,7 +158,7 @@ def function(inp: ourlang.Function) -> str: return result -def module(inp: ourlang.Module) -> str: +def module(inp: ourlang.Module[Any]) -> str: """ Render: Module """ diff --git a/phasm/compiler.py b/phasm/compiler.py index e2df411..aca52e6 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -4,8 +4,9 @@ This module contains the code to convert parsed Ourlang into WebAssembly code import struct from typing import List -from . import ourlang, prelude, wasm -from .runtime import calculate_alloc_size, calculate_member_offset +from . import ourlang, wasm +from .build import builtins +from .build.base import TypeInfo from .stdlib import alloc as stdlib_alloc from .stdlib import types as stdlib_types from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP @@ -24,11 +25,17 @@ from .type3.types import ( TypeConstructor_StaticArray, TypeConstructor_Tuple, ) +from .wasm import ( + WasmTypeFloat32, + WasmTypeFloat64, + WasmTypeInt32, + WasmTypeInt64, +) from .wasmgenerator import Generator as WasmGenerator TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled' -def phasm_compile(inp: ourlang.Module) -> wasm.Module: +def phasm_compile(inp: ourlang.Module[WasmGenerator]) -> wasm.Module: """ Public method for compiling a parsed Phasm module into a WebAssembly module @@ -45,7 +52,7 @@ def type3(inp: Type3) -> wasm.WasmType: typ_info = TYPE_INFO_MAP.get(inp.name, TYPE_INFO_CONSTRUCTED) return typ_info.wasm_type() -def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.TupleInstantiation) -> None: +def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.TupleInstantiation) -> None: """ Compile: Instantiation (allocation) of a tuple """ @@ -65,7 +72,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu args = tuple(sa_type for _ in inp.elements) # Can't use calculate_alloc_size directly since that doesn't # know the dynamic array's length - alloc_size = 4 + calculate_alloc_size(sa_type, is_member=True) * len(inp.elements) + alloc_size = 4 + mod.build.calculate_alloc_size(sa_type, is_member=True) * len(inp.elements) alloc_size_header = len(inp.elements) elif isinstance(inp.type3.application, TypeApplication_TypeStar): # Possibly paranoid assert. If we have a future variadic type, @@ -73,7 +80,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple) args = inp.type3.application.arguments - alloc_size = calculate_alloc_size(inp.type3, is_member=False) + alloc_size = mod.build.calculate_alloc_size(inp.type3, is_member=False) elif isinstance(inp.type3.application, TypeApplication_TypeInt): # Possibly paranoid assert. If we have a future type of kind * -> Int -> *, # does it also do this tuple instantation like this? @@ -82,7 +89,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu sa_type, sa_len = inp.type3.application.arguments args = tuple(sa_type for _ in range(sa_len.value)) - alloc_size = calculate_alloc_size(inp.type3, is_member=False) + alloc_size = mod.build.calculate_alloc_size(inp.type3, is_member=False) else: raise NotImplementedError('tuple_instantiation', inp.type3) @@ -117,13 +124,13 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu wgn.add_statement(exp_type_info.wasm_store_func, 'offset=' + str(offset)) wgn.add_statement('nop', comment='POST') - offset += calculate_alloc_size(exp_type3, is_member=True) + offset += mod.build.calculate_alloc_size(exp_type3, is_member=True) # Return the allocated address wgn.local.get(tmp_var) def expression_subscript_bytes( - attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], + attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], ) -> None: wgn, mod, inp = attrs @@ -132,7 +139,7 @@ def expression_subscript_bytes( wgn.call(stdlib_types.__subscript_bytes__) def expression_subscript_static_array( - attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], + attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], args: tuple[Type3, IntType3], ) -> None: wgn, mod, inp = attrs @@ -164,7 +171,7 @@ def expression_subscript_static_array( wgn.add_statement(el_type_info.wasm_load_func) def expression_subscript_tuple( - attrs: tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], + attrs: tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], args: tuple[Type3, ...], ) -> None: wgn, mod, inp = attrs @@ -186,12 +193,12 @@ def expression_subscript_tuple( el_type_info = TYPE_INFO_MAP.get(el_type.name, TYPE_INFO_CONSTRUCTED) wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}') -SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module, ourlang.Subscript], None]() -SUBSCRIPT_ROUTER.add_n(prelude.bytes_, expression_subscript_bytes) -SUBSCRIPT_ROUTER.add(prelude.static_array, expression_subscript_static_array) -SUBSCRIPT_ROUTER.add(prelude.tuple_, expression_subscript_tuple) +SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Module[WasmGenerator], ourlang.Subscript], None]() +# SUBSCRIPT_ROUTER.add(builtins.dynamic_array, expression_subscript_dynamic_array) +SUBSCRIPT_ROUTER.add(builtins.static_array, expression_subscript_static_array) +SUBSCRIPT_ROUTER.add(builtins.tuple_, expression_subscript_tuple) -def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) -> None: +def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Expression) -> None: """ Compile: Any expression """ @@ -202,34 +209,23 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) if isinstance(inp, ourlang.ConstantPrimitive): assert inp.type3 is not None, TYPE3_ASSERTION_ERROR - if inp.type3 in (prelude.i8, prelude.u8, ): - # No native u8 type - treat as i32, with caution + type_info = mod.build.type_info_map[inp.type3.name] + if type_info.wasm_type is WasmTypeInt32: assert isinstance(inp.value, int) wgn.i32.const(inp.value) return - if inp.type3 in (prelude.i16, prelude.u16, ): - # No native u16 type - treat as i32, with caution - assert isinstance(inp.value, int) - wgn.i32.const(inp.value) - return - - if inp.type3 in (prelude.i32, prelude.u32, ): - assert isinstance(inp.value, int) - wgn.i32.const(inp.value) - return - - if inp.type3 in (prelude.i64, prelude.u64, ): + if type_info.wasm_type is WasmTypeInt64: assert isinstance(inp.value, int) wgn.i64.const(inp.value) return - if inp.type3 == prelude.f32: + if type_info.wasm_type is WasmTypeFloat32: assert isinstance(inp.value, float) wgn.f32.const(inp.value) return - if inp.type3 == prelude.f64: + if type_info.wasm_type is WasmTypeFloat64: assert isinstance(inp.value, float) wgn.f64.const(inp.value) return @@ -285,7 +281,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) raise NotImplementedError(type_var, arg_expr.type3) - router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator] + router = mod.build.type_class_instance_methods[inp.operator] router(wgn, type_var_map) return @@ -314,7 +310,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) raise NotImplementedError(type_var, arg_expr.type3) - router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function] + router = mod.build.type_class_instance_methods[inp.function] try: router(wgn, type_var_map) except NoRouteForTypeException: @@ -367,14 +363,14 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression) member_type_info = TYPE_INFO_MAP.get(member_type.name, TYPE_INFO_CONSTRUCTED) expression(wgn, mod, inp.varref) - wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(calculate_member_offset( + wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(mod.build.calculate_member_offset( inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member ))) return raise NotImplementedError(expression, inp) -def statement_return(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.StatementReturn) -> None: +def statement_return(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.StatementReturn) -> None: """ Compile: Return statement """ @@ -391,7 +387,7 @@ def statement_return(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Funct expression(wgn, mod, inp.value) wgn.return_() -def statement_if(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.StatementIf) -> None: +def statement_if(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.StatementIf) -> None: """ Compile: If statement """ @@ -406,7 +402,7 @@ def statement_if(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, # for stat in inp.else_statements: # statement(wgn, stat) -def statement(wgn: WasmGenerator, mod: ourlang.Module, fun: ourlang.Function, inp: ourlang.Statement) -> None: +def statement(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourlang.Function, inp: ourlang.Statement) -> None: """ Compile: any statement """ @@ -446,7 +442,7 @@ def import_(inp: ourlang.Function) -> wasm.Import: type3(inp.returns_type3) ) -def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function: +def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Function: """ Compile: function """ @@ -455,7 +451,7 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function: wgn = WasmGenerator() if isinstance(inp, ourlang.StructConstructor): - _generate_struct_constructor(wgn, inp) + _generate_struct_constructor(wgn, mod, inp) else: for stat in inp.statements: statement(wgn, mod, inp, stat) @@ -475,71 +471,30 @@ def function(mod: ourlang.Module, inp: ourlang.Function) -> wasm.Function: wgn.statements ) -def module_data_u8(inp: int) -> bytes: - """ - Compile: module data, u8 value - """ - return struct.pack(' bytes: + letter_map = { + (WasmTypeInt32, 1, False): 'B', + (WasmTypeInt32, 1, True): 'b', + (WasmTypeInt32, 2, False): 'H', + (WasmTypeInt32, 2, True): 'h', + (WasmTypeInt32, 4, False): 'I', + (WasmTypeInt32, 4, True): 'i', + (WasmTypeInt64, 8, False): 'Q', + (WasmTypeInt64, 8, True): 'q', + (WasmTypeFloat32, 4, None): 'f', + (WasmTypeFloat32, 8, None): 'd', + } -def module_data_u16(inp: int) -> bytes: - """ - Compile: module data, u16 value - """ - return struct.pack(' bytes: - """ - Compile: module data, u32 value - """ - return struct.pack(' bytes: - """ - Compile: module data, u64 value - """ - return struct.pack(' bytes: - """ - Compile: module data, i8 value - """ - return struct.pack(' bytes: - """ - Compile: module data, i16 value - """ - return struct.pack(' bytes: - """ - Compile: module data, i32 value - """ - return struct.pack(' bytes: - """ - Compile: module data, i64 value - """ - return struct.pack(' bytes: - """ - Compile: module data, f32 value - """ - return struct.pack(' bytes: - """ - Compile: module data, f64 value - """ - return struct.pack(' bytes: +def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) -> bytes: """ Compile: module data """ unalloc_ptr = stdlib_alloc.UNALLOC_PTR + u32_type_info = mod.build.type_info_map['u32'] + ptr_type_info = mod.build.type_info_map['ptr'] allocated_data = b'' @@ -551,112 +506,55 @@ def module_data(inp: ourlang.ModuleData) -> bytes: for constant in block.data: assert constant.type3 is not None, TYPE3_ASSERTION_ERROR - if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block: + if isinstance(constant, ourlang.ConstantBytes): + data_list.append(module_data_primitive(u32_type_info, len(constant.value))) + data_list.append(constant.value) + continue + + if isinstance(constant, ourlang.ConstantMemoryStored): + if block is constant.data_block: + raise NotImplementedError(block, constant) + # It's stored in a different block # We only need to store its address # This happens for example when a tuple refers # to a bytes constant assert constant.data_block.address is not None, 'Referred memory not yet stored' - data_list.append(module_data_u32(constant.data_block.address)) + data_list.append(module_data_primitive(ptr_type_info, constant.data_block.address)) continue - if constant.type3 == prelude.u8: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_u8(constant.value)) - continue + type_info = mod.build.type_info_map[constant.type3.name] - if constant.type3 == prelude.u16: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_u16(constant.value)) - continue - - if constant.type3 == prelude.u32: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_u32(constant.value)) - continue - - if constant.type3 == prelude.u64: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_u64(constant.value)) - continue - - if constant.type3 == prelude.i8: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_i8(constant.value)) - continue - - if constant.type3 == prelude.i16: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_i16(constant.value)) - continue - - if constant.type3 == prelude.i32: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_i32(constant.value)) - continue - - if constant.type3 == prelude.i64: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, int) - data_list.append(module_data_i64(constant.value)) - continue - - if constant.type3 == prelude.f32: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, float) - data_list.append(module_data_f32(constant.value)) - continue - - if constant.type3 == prelude.f64: - assert isinstance(constant, ourlang.ConstantPrimitive) - assert isinstance(constant.value, float) - data_list.append(module_data_f64(constant.value)) - continue - - if constant.type3 == prelude.bytes_: - assert isinstance(constant, ourlang.ConstantBytes) - assert isinstance(constant.value, bytes) - data_list.append(module_data_u32(len(constant.value))) - data_list.append(constant.value) - continue - - raise NotImplementedError(constant, constant.type3) + return module_data_primitive(type_info, constant.value) block_data = b''.join(data_list) - allocated_data += module_data_u32(len(block_data)) + block_data + allocated_data += module_data_primitive(u32_type_info, len(block_data)) + block_data unalloc_ptr += 4 + len(block_data) return ( # Store that we've initialized the memory - module_data_u32(stdlib_alloc.IDENTIFIER) + module_data_primitive(u32_type_info, stdlib_alloc.IDENTIFIER) # Store the first reserved i32 - + module_data_u32(0) + + module_data_primitive(u32_type_info, 0) # Store the pointer towards the first free block # In this case, 0 since we haven't freed any blocks yet - + module_data_u32(0) + + module_data_primitive(u32_type_info, 0) # Store the pointer towards the first unallocated block # In this case the end of the stdlib.alloc header at the start - + module_data_u32(unalloc_ptr) + + module_data_primitive(u32_type_info, unalloc_ptr) # Store the actual data + allocated_data ) -def module(inp: ourlang.Module) -> wasm.Module: +def module(inp: ourlang.Module[WasmGenerator]) -> wasm.Module: """ Compile: module """ result = wasm.Module() - result.memory.data = module_data(inp.data) + result.memory.data = module_data(inp, inp.data) result.imports = [ import_(x) @@ -698,7 +596,7 @@ def module(inp: ourlang.Module) -> wasm.Module: return result -def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: +def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.StructConstructor) -> None: assert isinstance(inp.struct_type3.application, TypeApplication_Struct) st_args = inp.struct_type3.application.arguments @@ -706,7 +604,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc tmp_var = wgn.temp_var_i32('struct_adr') # Allocated the required amounts of bytes in memory - wgn.i32.const(calculate_alloc_size(inp.struct_type3)) + wgn.i32.const(mod.build.calculate_alloc_size(inp.struct_type3)) wgn.call(stdlib_alloc.__alloc__) wgn.local.set(tmp_var) @@ -716,7 +614,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc wgn.local.get(tmp_var) wgn.add_statement('local.get', f'${memname}') - wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(calculate_member_offset( + wgn.add_statement(mtyp3_info.wasm_store_func, 'offset=' + str(mod.build.calculate_member_offset( inp.struct_type3.name, st_args, memname ))) diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 2bd3cac..9e60e2a 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -3,7 +3,7 @@ Contains the syntax tree for ourlang """ from typing import Dict, Iterable, List, Optional, Union -from . import prelude +from .build.base import BuildBase from .type3.functions import FunctionSignature, TypeVariableContext from .type3.typeclasses import Type3ClassMethod from .type3.types import Type3, TypeApplication_Struct @@ -288,14 +288,14 @@ class Function: returns_type3: Type3 posonlyargs: List[FunctionParam] - def __init__(self, name: str, lineno: int) -> None: + def __init__(self, name: str, lineno: int, returns_type3: Type3) -> None: self.name = name self.lineno = lineno self.exported = False self.imported = None self.statements = [] self.signature = FunctionSignature(TypeVariableContext(), []) - self.returns_type3 = prelude.none # FIXME: This could be a placeholder + self.returns_type3 = returns_type3 self.posonlyargs = [] class StructDefinition: @@ -323,7 +323,7 @@ class StructConstructor(Function): struct_type3: Type3 def __init__(self, struct_type3: Type3) -> None: - super().__init__(f'@{struct_type3.name}@__init___@', -1) + super().__init__(f'@{struct_type3.name}@__init___@', -1, struct_type3) assert isinstance(struct_type3.application, TypeApplication_Struct) @@ -331,7 +331,6 @@ class StructConstructor(Function): self.posonlyargs.append(FunctionParam(mem, typ, )) self.signature.args.append(typ) - self.returns_type3 = struct_type3 self.signature.args.append(struct_type3) self.struct_type3 = struct_type3 @@ -377,25 +376,30 @@ class ModuleData: def __init__(self) -> None: self.blocks = [] -class Module: +class Module[G]: """ A module is a file and consists of functions """ - __slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions', 'operators', 'functions_table', ) + __slots__ = ('build', 'data', 'types', 'struct_definitions', 'constant_defs', 'functions', 'methods', 'operators', 'functions_table', ) + build: BuildBase[G] data: ModuleData types: dict[str, Type3] struct_definitions: Dict[str, StructDefinition] constant_defs: Dict[str, ModuleConstantDef] functions: Dict[str, Function] + methods: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod] functions_table: dict[Function, int] - def __init__(self) -> None: + def __init__(self, build: BuildBase[G]) -> None: + self.build = build + self.data = ModuleData() self.types = {} self.struct_definitions = {} self.constant_defs = {} self.functions = {} + self.methods = {} self.operators = {} self.functions_table = {} diff --git a/phasm/parser.py b/phasm/parser.py index e72946d..02a942f 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -4,7 +4,8 @@ Parses the source code from the plain text into a syntax tree import ast from typing import Any, Dict, NoReturn, Union -from . import prelude +from .build.base import BuildBase +from .build.default import BuildDefault from .exceptions import StaticError from .ourlang import ( AccessStructMember, @@ -31,12 +32,12 @@ from .ourlang import ( TupleInstantiation, VariableReference, ) -from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES from .type3.typeclasses import Type3ClassMethod from .type3.types import IntType3, Type3 +from .wasmgenerator import Generator -def phasm_parse(source: str) -> Module: +def phasm_parse(source: str) -> Module[Generator]: """ Public method for parsing Phasm code into a Phasm Module """ @@ -44,7 +45,8 @@ def phasm_parse(source: str) -> Module: res = OptimizerTransformer().visit(res) - our_visitor = OurVisitor() + build = BuildDefault() + our_visitor = OurVisitor(build) return our_visitor.visit_Module(res) OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict? @@ -75,7 +77,7 @@ class OptimizerTransformer(ast.NodeTransformer): return node.operand return node -class OurVisitor: +class OurVisitor[G]: """ Class to visit a Python syntax tree and create an ourlang syntax tree @@ -90,14 +92,14 @@ class OurVisitor: # pylint: disable=C0103,C0116,C0301,R0201,R0912 - def __init__(self) -> None: - pass + def __init__(self, build: BuildBase[G]) -> None: + self.build = build - def visit_Module(self, node: ast.Module) -> Module: - module = Module() + def visit_Module(self, node: ast.Module) -> Module[G]: + module = Module(self.build) - module.operators.update(PRELUDE_OPERATORS) - module.types.update(PRELUDE_TYPES) + module.operators.update(self.build.operators) + module.types.update(self.build.types) _not_implemented(not node.type_ignores, 'Module.type_ignores') @@ -140,7 +142,7 @@ class OurVisitor: return module - def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]: + def pre_visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]: if isinstance(node, ast.FunctionDef): return self.pre_visit_Module_FunctionDef(module, node) @@ -152,8 +154,8 @@ class OurVisitor: raise NotImplementedError(f'{node} on Module') - def pre_visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> Function: - function = Function(node.name, node.lineno) + def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function: + function = Function(node.name, node.lineno, self.build.none_) _not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs') @@ -220,7 +222,7 @@ class OurVisitor: return function - def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition: + def pre_visit_Module_ClassDef(self, module: Module[G], node: ast.ClassDef) -> StructDefinition: _not_implemented(not node.bases, 'ClassDef.bases') _not_implemented(not node.keywords, 'ClassDef.keywords') @@ -246,9 +248,9 @@ class OurVisitor: members[stmt.target.id] = self.visit_type(module, stmt.annotation) - return StructDefinition(prelude.struct(node.name, tuple(members.items())), node.lineno) + return StructDefinition(module.build.struct(node.name, tuple(members.items())), node.lineno) - def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: + def pre_visit_Module_AnnAssign(self, module: Module[G], node: ast.AnnAssign) -> ModuleConstantDef: if not isinstance(node.target, ast.Name): _raise_static_error(node.target, 'Must be name') if not isinstance(node.target.ctx, ast.Store): @@ -292,7 +294,7 @@ class OurVisitor: raise NotImplementedError(f'{node} on Module AnnAssign') - def visit_Module_stmt(self, module: Module, node: ast.stmt) -> None: + def visit_Module_stmt(self, module: Module[G], node: ast.stmt) -> None: if isinstance(node, ast.FunctionDef): self.visit_Module_FunctionDef(module, node) return @@ -305,7 +307,7 @@ class OurVisitor: raise NotImplementedError(f'{node} on Module') - def visit_Module_FunctionDef(self, module: Module, node: ast.FunctionDef) -> None: + def visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> None: function = module.functions[node.name] our_locals: OurLocals = { @@ -318,7 +320,7 @@ class OurVisitor: self.visit_Module_FunctionDef_stmt(module, function, our_locals, stmt) ) - def visit_Module_FunctionDef_stmt(self, module: Module, function: Function, our_locals: OurLocals, node: ast.stmt) -> Statement: + def visit_Module_FunctionDef_stmt(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.stmt) -> Statement: if isinstance(node, ast.Return): if node.value is None: # TODO: Implement methods without return values @@ -350,7 +352,7 @@ class OurVisitor: raise NotImplementedError(f'{node} as stmt in FunctionDef') - def visit_Module_FunctionDef_expr(self, module: Module, 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): operator: Union[str, Type3ClassMethod] @@ -466,7 +468,7 @@ class OurVisitor: raise NotImplementedError(f'{node} as expr in FunctionDef') - def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]: + def visit_Module_FunctionDef_Call(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]: if node.keywords: _raise_static_error(node, 'Keyword calling not supported') # Yet? @@ -477,8 +479,8 @@ class OurVisitor: func: Union[Function, FunctionParam, Type3ClassMethod] - if node.func.id in PRELUDE_METHODS: - func = PRELUDE_METHODS[node.func.id] + if node.func.id in module.methods: + func = module.methods[node.func.id] elif node.func.id in our_locals: func = our_locals[node.func.id] else: @@ -494,7 +496,7 @@ class OurVisitor: ) return result - def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression: + def visit_Module_FunctionDef_Attribute(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression: if not isinstance(node.value, ast.Name): _raise_static_error(node, 'Must reference a name') @@ -511,7 +513,7 @@ class OurVisitor: node.attr, ) - def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression: + def visit_Module_FunctionDef_Subscript(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression: if not isinstance(node.value, ast.Name): _raise_static_error(node, 'Must reference a name') @@ -537,7 +539,7 @@ class OurVisitor: return Subscript(varref, slice_expr) - def visit_Module_Constant(self, module: Module, node: Union[ast.Constant, ast.Tuple, ast.Call]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct]: + def visit_Module_Constant(self, module: Module[G], node: Union[ast.Constant, ast.Tuple, ast.Call]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct]: if isinstance(node, ast.Tuple): tuple_data = [ self.visit_Module_Constant(module, arg_node) @@ -597,10 +599,10 @@ class OurVisitor: raise NotImplementedError(f'{node.value} as constant') - def visit_type(self, module: Module, node: ast.expr) -> Type3: + def visit_type(self, module: Module[G], node: ast.expr) -> Type3: if isinstance(node, ast.Constant): if node.value is None: - return prelude.none + return module.types['None'] _raise_static_error(node, f'Unrecognized type {node.value}') @@ -625,7 +627,7 @@ class OurVisitor: _raise_static_error(node, 'Must subscript using a list of types') # Function type - return prelude.function(*[ + return module.build.function(*[ self.visit_type(module, e) for e in func_arg_types ]) @@ -637,7 +639,7 @@ class OurVisitor: _raise_static_error(node, 'Must subscript using a constant index') if node.slice.value is Ellipsis: - return prelude.dynamic_array( + return module.build.dynamic_array( self.visit_type(module, node.value), ) @@ -646,7 +648,7 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - return prelude.static_array( + return module.build.static_array( self.visit_type(module, node.value), IntType3(node.slice.value), ) @@ -655,7 +657,7 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - return prelude.tuple_( + return module.build.tuple_( *(self.visit_type(module, elt) for elt in node.elts) ) diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index f651ff9..e69de29 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -1,772 +0,0 @@ -""" -The prelude are all the builtin types, type classes and methods -""" -from typing import Any, Callable -from warnings import warn - -from phasm.stdlib import types as stdtypes -from phasm.wasmgenerator import Generator - -from ..type3.functions import ( - Constraint_TypeClassInstanceExists, - TypeConstructorVariable, - TypeVariable, - TypeVariableApplication_Nullary, -) -from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup -from ..type3.typeclasses import Type3Class, Type3ClassMethod -from ..type3.types import ( - Type3, - TypeApplication_Nullary, - TypeConstructor_Base, - TypeConstructor_DynamicArray, - TypeConstructor_Function, - TypeConstructor_StaticArray, - TypeConstructor_Struct, - TypeConstructor_Tuple, -) - -PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] = set() - -PRELUDE_TYPE_CLASS_INSTANCE_METHODS: dict[Type3ClassMethod, TypeClassArgsRouter[Generator, None]] = {} - -class MissingImplementationException(Exception): - pass - -class MissingImplementationWarning(Warning): - pass - -def instance_type_class( - cls: Type3Class, - *typ: Type3 | TypeConstructor_Base[Any], - methods: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {}, - operators: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {}, -) -> None: - global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING - global PRELUDE_TYPE_CLASS_INSTANCE_METHODS - - assert len(cls.args) == len(typ) - - 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) - - # TODO: Check for required existing instantiations - - PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), )) - - for method_name, method in cls.methods.items(): - router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(method) - if router is None: - router = TypeClassArgsRouter[Generator, None](cls.args) - PRELUDE_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 = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(operator) - if router is None: - router = TypeClassArgsRouter[Generator, None](cls.args) - PRELUDE_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) - -none = Type3('none', TypeApplication_Nullary(None, None)) -""" -The none type, for when functions simply don't return anything. e.g., IO(). -""" - -bool_ = Type3('bool', TypeApplication_Nullary(None, None)) -""" -The bool type, either True or False - -Suffixes with an underscores, as it's a Python builtin -""" - -u8 = Type3('u8', TypeApplication_Nullary(None, None)) -""" -The unsigned 8-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^8. -""" - -u16 = Type3('u16', TypeApplication_Nullary(None, None)) -""" -The unsigned 16-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^16. -""" - -u32 = Type3('u32', TypeApplication_Nullary(None, None)) -""" -The unsigned 32-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^32. -""" - -u64 = Type3('u64', TypeApplication_Nullary(None, None)) -""" -The unsigned 64-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^64. -""" - -i8 = Type3('i8', TypeApplication_Nullary(None, None)) -""" -The signed 8-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^8, but -with the middel point being 0. -""" - -i16 = Type3('i16', TypeApplication_Nullary(None, None)) -""" -The signed 16-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^16, but -with the middel point being 0. -""" - -i32 = Type3('i32', TypeApplication_Nullary(None, None)) -""" -The unsigned 32-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^32, but -with the middel point being 0. -""" - -i64 = Type3('i64', TypeApplication_Nullary(None, None)) -""" -The unsigned 64-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^64, but -with the middel point being 0. -""" - -f32 = Type3('f32', TypeApplication_Nullary(None, None)) -""" -A 32-bits IEEE 754 float, of 32 bits width. -""" - -f64 = Type3('f64', TypeApplication_Nullary(None, None)) -""" -A 32-bits IEEE 754 float, of 64 bits width. -""" - -dynamic_array = TypeConstructor_DynamicArray('dynamic_array') -""" -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. -""" - -static_array = TypeConstructor_StaticArray('static_array') -""" -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. -""" - -tuple_ = TypeConstructor_Tuple('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. -""" - -function = TypeConstructor_Function('function') -""" -This is a function. - -It should be applied with one or more arguments. The last argument is the 'return' type. -""" - -struct = TypeConstructor_Struct('struct') -""" -This is like a tuple, but each argument is named, so that developers -can get and set fields by name. -""" - -a = TypeVariable('a', TypeVariableApplication_Nullary(None, None)) -b = TypeVariable('b', TypeVariableApplication_Nullary(None, None)) - -t = TypeConstructorVariable('t') - -Eq = Type3Class('Eq', (a, ), methods={}, operators={ - '==': [a, a, bool_], - '!=': [a, a, bool_], - # FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization? -}) - -instance_type_class(Eq, u8, operators={ - '==': stdtypes.u8_eq_equals, - '!=': stdtypes.u8_eq_not_equals, -}) -instance_type_class(Eq, u16, operators={ - '==': stdtypes.u16_eq_equals, - '!=': stdtypes.u16_eq_not_equals, -}) -instance_type_class(Eq, u32, operators={ - '==': stdtypes.u32_eq_equals, - '!=': stdtypes.u32_eq_not_equals, -}) -instance_type_class(Eq, u64, operators={ - '==': stdtypes.u64_eq_equals, - '!=': stdtypes.u64_eq_not_equals, -}) -instance_type_class(Eq, i8, operators={ - '==': stdtypes.i8_eq_equals, - '!=': stdtypes.i8_eq_not_equals, -}) -instance_type_class(Eq, i16, operators={ - '==': stdtypes.i16_eq_equals, - '!=': stdtypes.i16_eq_not_equals, -}) -instance_type_class(Eq, i32, operators={ - '==': stdtypes.i32_eq_equals, - '!=': stdtypes.i32_eq_not_equals, -}) -instance_type_class(Eq, i64, operators={ - '==': stdtypes.i64_eq_equals, - '!=': stdtypes.i64_eq_not_equals, -}) -instance_type_class(Eq, f32, operators={ - '==': stdtypes.f32_eq_equals, - '!=': stdtypes.f32_eq_not_equals, -}) -instance_type_class(Eq, f64, operators={ - '==': stdtypes.f64_eq_equals, - '!=': stdtypes.f64_eq_not_equals, -}) - -Ord = Type3Class('Ord', (a, ), methods={ - 'min': [a, a, a], - 'max': [a, a, a], -}, operators={ - '<': [a, a, bool_], - '<=': [a, a, bool_], - '>': [a, a, bool_], - '>=': [a, a, bool_], -}, inherited_classes=[Eq]) - -instance_type_class(Ord, u8, methods={ - 'min': stdtypes.u8_ord_min, - 'max': stdtypes.u8_ord_max, -}, operators={ - '<': stdtypes.u8_ord_less_than, - '<=': stdtypes.u8_ord_less_than_or_equal, - '>': stdtypes.u8_ord_greater_than, - '>=': stdtypes.u8_ord_greater_than_or_equal, -}) -instance_type_class(Ord, u16, methods={ - 'min': stdtypes.u16_ord_min, - 'max': stdtypes.u16_ord_max, -}, operators={ - '<': stdtypes.u16_ord_less_than, - '<=': stdtypes.u16_ord_less_than_or_equal, - '>': stdtypes.u16_ord_greater_than, - '>=': stdtypes.u16_ord_greater_than_or_equal, -}) -instance_type_class(Ord, u32, methods={ - 'min': stdtypes.u32_ord_min, - 'max': stdtypes.u32_ord_max, -}, operators={ - '<': stdtypes.u32_ord_less_than, - '<=': stdtypes.u32_ord_less_than_or_equal, - '>': stdtypes.u32_ord_greater_than, - '>=': stdtypes.u32_ord_greater_than_or_equal, -}) -instance_type_class(Ord, u64, methods={ - 'min': stdtypes.u64_ord_min, - 'max': stdtypes.u64_ord_max, -}, operators={ - '<': stdtypes.u64_ord_less_than, - '<=': stdtypes.u64_ord_less_than_or_equal, - '>': stdtypes.u64_ord_greater_than, - '>=': stdtypes.u64_ord_greater_than_or_equal, -}) -instance_type_class(Ord, i8, methods={ - 'min': stdtypes.i8_ord_min, - 'max': stdtypes.i8_ord_max, -}, operators={ - '<': stdtypes.i8_ord_less_than, - '<=': stdtypes.i8_ord_less_than_or_equal, - '>': stdtypes.i8_ord_greater_than, - '>=': stdtypes.i8_ord_greater_than_or_equal, -}) -instance_type_class(Ord, i16, methods={ - 'min': stdtypes.i16_ord_min, - 'max': stdtypes.i16_ord_max, -}, operators={ - '<': stdtypes.i16_ord_less_than, - '<=': stdtypes.i16_ord_less_than_or_equal, - '>': stdtypes.i16_ord_greater_than, - '>=': stdtypes.i16_ord_greater_than_or_equal, -}) -instance_type_class(Ord, i32, methods={ - 'min': stdtypes.i32_ord_min, - 'max': stdtypes.i32_ord_max, -}, operators={ - '<': stdtypes.i32_ord_less_than, - '<=': stdtypes.i32_ord_less_than_or_equal, - '>': stdtypes.i32_ord_greater_than, - '>=': stdtypes.i32_ord_greater_than_or_equal, -}) -instance_type_class(Ord, i64, methods={ - 'min': stdtypes.i64_ord_min, - 'max': stdtypes.i64_ord_max, -}, operators={ - '<': stdtypes.i64_ord_less_than, - '<=': stdtypes.i64_ord_less_than_or_equal, - '>': stdtypes.i64_ord_greater_than, - '>=': stdtypes.i64_ord_greater_than_or_equal, -}) -instance_type_class(Ord, f32, methods={ - 'min': stdtypes.f32_ord_min, - 'max': stdtypes.f32_ord_max, -}, operators={ - '<': stdtypes.f32_ord_less_than, - '<=': stdtypes.f32_ord_less_than_or_equal, - '>': stdtypes.f32_ord_greater_than, - '>=': stdtypes.f32_ord_greater_than_or_equal, -}) -instance_type_class(Ord, f64, methods={ - 'min': stdtypes.f64_ord_min, - 'max': stdtypes.f64_ord_max, -}, operators={ - '<': stdtypes.f64_ord_less_than, - '<=': stdtypes.f64_ord_less_than_or_equal, - '>': stdtypes.f64_ord_greater_than, - '>=': stdtypes.f64_ord_greater_than_or_equal, -}) - -Bits = Type3Class('Bits', (a, ), methods={ - 'shl': [a, u32, a], # Logical shift left - 'shr': [a, u32, a], # Logical shift right - 'rotl': [a, u32, a], # Rotate bits left - 'rotr': [a, u32, a], # Rotate bits right - # FIXME: Do we want to expose clz, ctz, popcnt? -}, operators={ - '&': [a, a, a], # Bit-wise and - '|': [a, a, a], # Bit-wise or - '^': [a, a, a], # Bit-wise xor -}) - -instance_type_class(Bits, u8, methods={ - 'shl': stdtypes.u8_bits_logical_shift_left, - 'shr': stdtypes.u8_bits_logical_shift_right, - 'rotl': stdtypes.u8_bits_rotate_left, - 'rotr': stdtypes.u8_bits_rotate_right, -}, operators={ - '&': stdtypes.u8_bits_bitwise_and, - '|': stdtypes.u8_bits_bitwise_or, - '^': stdtypes.u8_bits_bitwise_xor, -}) -instance_type_class(Bits, u16, methods={ - 'shl': stdtypes.u16_bits_logical_shift_left, - 'shr': stdtypes.u16_bits_logical_shift_right, - 'rotl': stdtypes.u16_bits_rotate_left, - 'rotr': stdtypes.u16_bits_rotate_right, -}, operators={ - '&': stdtypes.u16_bits_bitwise_and, - '|': stdtypes.u16_bits_bitwise_or, - '^': stdtypes.u16_bits_bitwise_xor, -}) -instance_type_class(Bits, u32, methods={ - 'shl': stdtypes.u32_bits_logical_shift_left, - 'shr': stdtypes.u32_bits_logical_shift_right, - 'rotl': stdtypes.u32_bits_rotate_left, - 'rotr': stdtypes.u32_bits_rotate_right, -}, operators={ - '&': stdtypes.u32_bits_bitwise_and, - '|': stdtypes.u32_bits_bitwise_or, - '^': stdtypes.u32_bits_bitwise_xor, -}) -instance_type_class(Bits, u64, methods={ - 'shl': stdtypes.u64_bits_logical_shift_left, - 'shr': stdtypes.u64_bits_logical_shift_right, - 'rotl': stdtypes.u64_bits_rotate_left, - 'rotr': stdtypes.u64_bits_rotate_right, -}, operators={ - '&': stdtypes.u64_bits_bitwise_and, - '|': stdtypes.u64_bits_bitwise_or, - '^': stdtypes.u64_bits_bitwise_xor, -}) - -NatNum = Type3Class('NatNum', (a, ), methods={}, operators={ - '+': [a, a, a], - '-': [a, a, a], - '*': [a, a, a], - '<<': [a, u32, a], # Arithmic shift left - '>>': [a, u32, a], # Arithmic shift right -}) - -instance_type_class(NatNum, u32, operators={ - '+': stdtypes.u32_natnum_add, - '-': stdtypes.u32_natnum_sub, - '*': stdtypes.u32_natnum_mul, - '<<': stdtypes.u32_natnum_arithmic_shift_left, - '>>': stdtypes.u32_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, u64, operators={ - '+': stdtypes.u64_natnum_add, - '-': stdtypes.u64_natnum_sub, - '*': stdtypes.u64_natnum_mul, - '<<': stdtypes.u64_natnum_arithmic_shift_left, - '>>': stdtypes.u64_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, i32, operators={ - '+': stdtypes.i32_natnum_add, - '-': stdtypes.i32_natnum_sub, - '*': stdtypes.i32_natnum_mul, - '<<': stdtypes.i32_natnum_arithmic_shift_left, - '>>': stdtypes.i32_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, i64, operators={ - '+': stdtypes.i64_natnum_add, - '-': stdtypes.i64_natnum_sub, - '*': stdtypes.i64_natnum_mul, - '<<': stdtypes.i64_natnum_arithmic_shift_left, - '>>': stdtypes.i64_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, f32, operators={ - '+': stdtypes.f32_natnum_add, - '-': stdtypes.f32_natnum_sub, - '*': stdtypes.f32_natnum_mul, - '<<': stdtypes.f32_natnum_arithmic_shift_left, - '>>': stdtypes.f32_natnum_arithmic_shift_right, -}) -instance_type_class(NatNum, f64, operators={ - '+': stdtypes.f64_natnum_add, - '-': stdtypes.f64_natnum_sub, - '*': stdtypes.f64_natnum_mul, - '<<': stdtypes.f64_natnum_arithmic_shift_left, - '>>': stdtypes.f64_natnum_arithmic_shift_right, -}) - -IntNum = Type3Class('IntNum', (a, ), methods={ - 'abs': [a, a], - 'neg': [a, a], -}, operators={}, inherited_classes=[NatNum]) - -instance_type_class(IntNum, i32, methods={ - 'abs': stdtypes.i32_intnum_abs, - 'neg': stdtypes.i32_intnum_neg, -}) -instance_type_class(IntNum, i64, methods={ - 'abs': stdtypes.i64_intnum_abs, - 'neg': stdtypes.i64_intnum_neg, -}) -instance_type_class(IntNum, f32, methods={ - 'abs': stdtypes.f32_intnum_abs, - 'neg': stdtypes.f32_intnum_neg, -}) -instance_type_class(IntNum, f64, methods={ - 'abs': stdtypes.f64_intnum_abs, - 'neg': stdtypes.f64_intnum_neg, -}) - -Integral = Type3Class('Integral', (a, ), methods={ -}, operators={ - '//': [a, a, a], - '%': [a, a, a], -}, inherited_classes=[NatNum]) - -instance_type_class(Integral, u32, operators={ - '//': stdtypes.u32_integral_div, - '%': stdtypes.u32_integral_rem, -}) -instance_type_class(Integral, u64, operators={ - '//': stdtypes.u64_integral_div, - '%': stdtypes.u64_integral_rem, -}) -instance_type_class(Integral, i32, operators={ - '//': stdtypes.i32_integral_div, - '%': stdtypes.i32_integral_rem, -}) -instance_type_class(Integral, i64, operators={ - '//': stdtypes.i64_integral_div, - '%': stdtypes.i64_integral_rem, -}) - -Fractional = Type3Class('Fractional', (a, ), methods={ - 'ceil': [a, a], - 'floor': [a, a], - 'trunc': [a, a], - 'nearest': [a, a], -}, operators={ - '/': [a, a, a], -}, inherited_classes=[NatNum]) - -instance_type_class(Fractional, f32, methods={ - 'ceil': stdtypes.f32_fractional_ceil, - 'floor': stdtypes.f32_fractional_floor, - 'trunc': stdtypes.f32_fractional_trunc, - 'nearest': stdtypes.f32_fractional_nearest, -}, operators={ - '/': stdtypes.f32_fractional_div, -}) -instance_type_class(Fractional, f64, methods={ - 'ceil': stdtypes.f64_fractional_ceil, - 'floor': stdtypes.f64_fractional_floor, - 'trunc': stdtypes.f64_fractional_trunc, - 'nearest': stdtypes.f64_fractional_nearest, -}, operators={ - '/': stdtypes.f64_fractional_div, -}) - -Floating = Type3Class('Floating', (a, ), methods={ - 'sqrt': [a, a], -}, operators={}, inherited_classes=[Fractional]) - -# FIXME: Do we want to expose copysign? - -instance_type_class(Floating, f32, methods={ - 'sqrt': stdtypes.f32_floating_sqrt, -}) -instance_type_class(Floating, f64, methods={ - 'sqrt': stdtypes.f64_floating_sqrt, -}) - -Sized_ = Type3Class('Sized', (t, ), methods={ - 'len': [t(a), u32], -}, operators={}) # FIXME: Once we get type class families, add [] here - -instance_type_class(Sized_, dynamic_array, methods={ - 'len': stdtypes.dynamic_array_sized_len, -}) -instance_type_class(Sized_, static_array, methods={ - 'len': stdtypes.static_array_sized_len, -}) - -Extendable = Type3Class('Extendable', (a, b, ), methods={ - 'extend': [a, b], - 'wrap': [b, a], -}, operators={}) - -instance_type_class(Extendable, u8, u16, methods={ - 'extend': stdtypes.u8_u16_extend, - 'wrap': stdtypes.u8_u16_wrap, -}) -instance_type_class(Extendable, u8, u32, methods={ - 'extend': stdtypes.u8_u32_extend, - 'wrap': stdtypes.u8_u32_wrap, -}) -instance_type_class(Extendable, u8, u64, methods={ - 'extend': stdtypes.u8_u64_extend, - 'wrap': stdtypes.u8_u64_wrap, -}) -instance_type_class(Extendable, u16, u32, methods={ - 'extend': stdtypes.u16_u32_extend, - 'wrap': stdtypes.u16_u32_wrap, -}) -instance_type_class(Extendable, u16, u64, methods={ - 'extend': stdtypes.u16_u64_extend, - 'wrap': stdtypes.u16_u64_wrap, -}) -instance_type_class(Extendable, u32, u64, methods={ - 'extend': stdtypes.u32_u64_extend, - 'wrap': stdtypes.u32_u64_wrap, -}) -instance_type_class(Extendable, i8, i16, methods={ - 'extend': stdtypes.i8_i16_extend, - 'wrap': stdtypes.i8_i16_wrap, -}) -instance_type_class(Extendable, i8, i32, methods={ - 'extend': stdtypes.i8_i32_extend, - 'wrap': stdtypes.i8_i32_wrap, -}) -instance_type_class(Extendable, i8, i64, methods={ - 'extend': stdtypes.i8_i64_extend, - 'wrap': stdtypes.i8_i64_wrap, -}) -instance_type_class(Extendable, i16, i32, methods={ - 'extend': stdtypes.i16_i32_extend, - 'wrap': stdtypes.i16_i32_wrap, -}) -instance_type_class(Extendable, i16, i64, methods={ - 'extend': stdtypes.i16_i64_extend, - 'wrap': stdtypes.i16_i64_wrap, -}) -instance_type_class(Extendable, i32, i64, methods={ - 'extend': stdtypes.i32_i64_extend, - 'wrap': stdtypes.i32_i64_wrap, -}) - -Promotable = Type3Class('Promotable', (a, b, ), methods={ - 'promote': [a, b], - 'demote': [b, a], -}, operators={}) - -instance_type_class(Promotable, f32, f64, methods={ - 'promote': stdtypes.f32_f64_promote, - 'demote': stdtypes.f32_f64_demote, -}) - -Reinterpretable = Type3Class('Reinterpretable', (a, b, ), methods={ - 'reinterpret': [a, b] -}, operators={}) - -instance_type_class(Reinterpretable, u32, f32, methods={ - 'reinterpret': stdtypes.u32_f32_reinterpret, -}) -instance_type_class(Reinterpretable, u64, f64, methods={ - 'reinterpret': stdtypes.u64_f64_reinterpret, -}) -instance_type_class(Reinterpretable, i32, f32, methods={ - 'reinterpret': stdtypes.i32_f32_reinterpret, -}) -instance_type_class(Reinterpretable, i64, f64, methods={ - 'reinterpret': stdtypes.i64_f64_reinterpret, -}) -instance_type_class(Reinterpretable, f32, u32, methods={ - 'reinterpret': stdtypes.f32_u32_reinterpret, -}) -instance_type_class(Reinterpretable, f64, u64, methods={ - 'reinterpret': stdtypes.f64_u64_reinterpret, -}) -instance_type_class(Reinterpretable, f32, i32, methods={ - 'reinterpret': stdtypes.f32_i32_reinterpret, -}) -instance_type_class(Reinterpretable, f64, i64, methods={ - 'reinterpret': stdtypes.f64_i64_reinterpret, -}) - -Convertable = Type3Class('Convertable', (a, b, ), methods={ - 'convert': [a, b], - 'truncate': [b, a], # To prevent name clas with Fractional -}, operators={}) - -instance_type_class(Convertable, u32, f32, methods={ - 'convert': stdtypes.u32_f32_convert, - 'truncate': stdtypes.u32_f32_truncate, -}) -instance_type_class(Convertable, u32, f64, methods={ - 'convert': stdtypes.u32_f64_convert, - 'truncate': stdtypes.u32_f64_truncate, -}) -instance_type_class(Convertable, u64, f32, methods={ - 'convert': stdtypes.u64_f32_convert, - 'truncate': stdtypes.u64_f32_truncate, -}) -instance_type_class(Convertable, u64, f64, methods={ - 'convert': stdtypes.u64_f64_convert, - 'truncate': stdtypes.u64_f64_truncate, -}) -instance_type_class(Convertable, i32, f32, methods={ - 'convert': stdtypes.i32_f32_convert, - 'truncate': stdtypes.i32_f32_truncate, -}) -instance_type_class(Convertable, i32, f64, methods={ - 'convert': stdtypes.i32_f64_convert, - 'truncate': stdtypes.i32_f64_truncate, -}) -instance_type_class(Convertable, i64, f32, methods={ - 'convert': stdtypes.i64_f32_convert, - 'truncate': stdtypes.i64_f32_truncate, -}) -instance_type_class(Convertable, i64, f64, methods={ - 'convert': stdtypes.i64_f64_convert, - 'truncate': stdtypes.i64_f64_truncate, -}) - - -Foldable = Type3Class('Foldable', (t, ), methods={ - 'sum': [t(a), a], - 'foldl': [[b, a, b], b, t(a), b], - 'foldr': [[a, b, b], b, t(a), b], -}, operators={}, additional_context={ - 'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))], -}) - -instance_type_class(Foldable, dynamic_array, methods={ - 'sum': stdtypes.dynamic_array_sum, - 'foldl': stdtypes.dynamic_array_foldl, - 'foldr': stdtypes.dynamic_array_foldr, -}) -instance_type_class(Foldable, static_array, methods={ - 'sum': stdtypes.static_array_sum, - 'foldl': stdtypes.static_array_foldl, - 'foldr': stdtypes.static_array_foldr, -}) - -bytes_ = dynamic_array(u8) - -PRELUDE_TYPES: dict[str, Type3] = { - 'none': none, - 'bool': bool_, - 'u8': u8, - 'u16': u16, - 'u32': u32, - 'u64': u64, - 'i8': i8, - 'i16': i16, - 'i32': i32, - 'i64': i64, - 'f32': f32, - 'f64': f64, - 'bytes': bytes_, -} - -PRELUDE_TYPE_CLASSES = { - 'Eq': Eq, - 'Ord': Ord, - 'Bits': Bits, - 'NatNum': NatNum, - 'IntNum': IntNum, - 'Integral': Integral, - 'Fractional': Fractional, - 'Floating': Floating, - 'Extendable': Extendable, - 'Promotable': Promotable, -} - -PRELUDE_OPERATORS = { - **Bits.operators, - **Eq.operators, - **Ord.operators, - **Fractional.operators, - **Integral.operators, - **IntNum.operators, - **NatNum.operators, -} - -PRELUDE_METHODS = { - **Bits.methods, - **Eq.methods, - **Ord.methods, - **Floating.methods, - **Fractional.methods, - **Integral.methods, - **IntNum.methods, - **NatNum.methods, - **Sized_.methods, - **Extendable.methods, - **Promotable.methods, - **Reinterpretable.methods, - **Convertable.methods, - **Foldable.methods, -} diff --git a/phasm/runtime.py b/phasm/runtime.py deleted file mode 100644 index d456971..0000000 --- a/phasm/runtime.py +++ /dev/null @@ -1,62 +0,0 @@ -from . import prelude -from .stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP -from .type3.routers import NoRouteForTypeException, TypeApplicationRouter -from .type3.types import IntType3, Type3 - - -def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int: - if is_member: - return TYPE_INFO_CONSTRUCTED.alloc_size - - sa_type, sa_len = args - - return sa_len.value * calculate_alloc_size(sa_type, is_member=True) - -def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int: - if is_member: - return TYPE_INFO_CONSTRUCTED.alloc_size - - return sum( - calculate_alloc_size(x, is_member=True) - for x in args - ) - -def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int: - if is_member: - return TYPE_INFO_CONSTRUCTED.alloc_size - - return sum( - calculate_alloc_size(x, is_member=True) - for _, x in args - ) - -ALLOC_SIZE_ROUTER = TypeApplicationRouter[bool, int]() -ALLOC_SIZE_ROUTER.add(prelude.static_array, calculate_alloc_size_static_array) -ALLOC_SIZE_ROUTER.add(prelude.struct, calculate_alloc_size_struct) -ALLOC_SIZE_ROUTER.add(prelude.tuple_, calculate_alloc_size_tuple) - -def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int: - typ_info = TYPE_INFO_MAP.get(typ.name) - if typ_info is not None: - return typ_info.alloc_size - - try: - return ALLOC_SIZE_ROUTER(is_member, typ) - except NoRouteForTypeException: - if is_member: - # By default, 'boxed' or 'constructed' types are - # stored as pointers when a member of a struct or tuple - return TYPE_INFO_CONSTRUCTED.alloc_size - - raise NotImplementedError(typ) - -def calculate_member_offset(st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int: - result = 0 - - for memnam, memtyp in st_args: - if needle == memnam: - return result - - result += calculate_alloc_size(memtyp, is_member=True) - - raise Exception(f'{needle} not in {st_name}') diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index 38c5c98..5603c5c 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -1,13 +1,13 @@ """ stdlib: Standard types that are not wasm primitives """ -from typing import Mapping, NamedTuple, Type +from typing import Mapping +from phasm.build.base import TypeInfo from phasm.stdlib import alloc from phasm.type3.routers import TypeVariableLookup from phasm.type3.types import IntType3, Type3 from phasm.wasm import ( - WasmType, WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, @@ -18,38 +18,22 @@ from phasm.wasmgenerator import Generator, func_wrapper from phasm.wasmgenerator import VarType_i32 as i32 from phasm.wasmgenerator import VarType_i64 as i64 -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), -]) - TYPE_INFO_MAP: Mapping[str, TypeInfo] = { - 'none': TypeInfo('none', WasmTypeNone, 'nop', 'nop', 0), - 'bool': TypeInfo('bool', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1), + 'none': TypeInfo('none', WasmTypeNone, 'nop', 'nop', 0, None), + 'bool': TypeInfo('bool', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, None), - 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1), - 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1), - 'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2), - 'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2), + 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False), + 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True), + 'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2, False), + 'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True), - 'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4), - 'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8), - 'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4), - 'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8), - 'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4), - 'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8), - 'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4), + 'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False), + 'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False), + 'i32': TypeInfo('i32', WasmTypeInt32, 'i32.load', 'i32.store', 4, True), + 'i64': TypeInfo('i64', WasmTypeInt64, 'i64.load', 'i64.store', 8, True), + 'f32': TypeInfo('f32', WasmTypeFloat32, 'f32.load', 'f32.store', 4, None), + 'f64': TypeInfo('f64', WasmTypeFloat64, 'f64.load', 'f64.store', 8, None), + 'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, None), } # By default, constructed types are passed as pointers @@ -57,7 +41,7 @@ TYPE_INFO_MAP: Mapping[str, TypeInfo] = { # USE runtime.calculate_alloc_size FOR ACCURATE RESULTS # Functions count as constructed types - even though they are # not memory pointers but table addresses instead. -TYPE_INFO_CONSTRUCTED = TypeInfo('t a', WasmTypeInt32, 'i32.load', 'i32.store', 4) +TYPE_INFO_CONSTRUCTED = TYPE_INFO_MAP['ptr'] @func_wrapper(exported=True) def __alloc_bytes__(g: Generator, length: i32) -> i32: diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index cd1175c..440eb3b 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -5,7 +5,14 @@ These need to be resolved before the program can be compiled. """ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union -from .. import ourlang, prelude +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 @@ -59,26 +66,31 @@ class Context: Context for constraints """ - __slots__ = ('type_class_instances_existing', ) + __slots__ = ('build', ) - # Constraint_TypeClassInstanceExists - type_class_instances_existing: set[tuple[Type3Class, tuple[Union[Type3, TypeConstructor_Base[Any], TypeConstructor_Struct], ...]]] + build: BuildBase[Any] - def __init__(self) -> None: - self.type_class_instances_existing = set() + def __init__(self, build: BuildBase[Any]) -> None: + self.build = build class ConstraintBase: """ Base class for constraints """ - __slots__ = ('comment', ) + __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, comment: Optional[str] = None) -> None: + def __init__(self, context: Context, comment: Optional[str] = None) -> None: + self.context = context self.comment = comment def check(self) -> CheckResult: @@ -114,8 +126,8 @@ class SameTypeConstraint(ConstraintBase): type_list: List[Type3OrPlaceholder] - def __init__(self, *type_list: Type3OrPlaceholder, comment: Optional[str] = None) -> None: - super().__init__(comment=comment) + 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] @@ -176,8 +188,8 @@ class SameTypeArgumentConstraint(ConstraintBase): tc_var: PlaceholderForType arg_var: PlaceholderForType - def __init__(self, tc_var: PlaceholderForType, arg_var: PlaceholderForType, *, comment: str) -> None: - super().__init__(comment=comment) + 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 @@ -202,6 +214,7 @@ class SameTypeArgumentConstraint(ConstraintBase): if isinstance(tc_typ.application, TypeApplication_Type): return [SameTypeConstraint( + self.context, tc_typ.application.arguments[0], self.arg_var, comment=self.comment, @@ -211,6 +224,7 @@ class SameTypeArgumentConstraint(ConstraintBase): # 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, @@ -234,8 +248,8 @@ class SameFunctionArgumentConstraint(ConstraintBase): func_arg: FunctionArgument type_var_map: dict[TypeVariable, PlaceholderForType] - def __init__(self, type3: PlaceholderForType, func_arg: FunctionArgument, type_var_map: dict[TypeVariable, PlaceholderForType], *, comment: str) -> None: - super().__init__(comment=comment) + 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 @@ -270,8 +284,9 @@ class SameFunctionArgumentConstraint(ConstraintBase): return [ SameTypeConstraint( + self.context, typ, - prelude.function(*exp_type_arg_list), + self.context.build.function(*exp_type_arg_list), comment=self.comment, ) ] @@ -286,22 +301,27 @@ class SameFunctionArgumentConstraint(ConstraintBase): ) class TupleMatchConstraint(ConstraintBase): - __slots__ = ('exp_type', 'args', ) + __slots__ = ('exp_type', 'args', 'generate_router', ) exp_type: Type3OrPlaceholder args: list[Type3OrPlaceholder] + generate_router: TypeApplicationRouter['TupleMatchConstraint', CheckResult] - def __init__(self, exp_type: Type3OrPlaceholder, args: Iterable[Type3OrPlaceholder], comment: str): - super().__init__(comment=comment) + 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(arg, sa_type) + SameTypeConstraint(self.context, arg, sa_type) for arg in self.args ] @@ -312,7 +332,7 @@ class TupleMatchConstraint(ConstraintBase): return Error('Mismatch between applied types argument count', comment=self.comment) return [ - SameTypeConstraint(arg, sa_type) + SameTypeConstraint(self.context, arg, sa_type) for arg in self.args ] @@ -321,15 +341,10 @@ class TupleMatchConstraint(ConstraintBase): return Error('Mismatch between applied types argument count', comment=self.comment) return [ - SameTypeConstraint(arg, oth_arg) + SameTypeConstraint(self.context, arg, oth_arg) for arg, oth_arg in zip(self.args, tp_args, strict=True) ] - GENERATE_ROUTER = TypeApplicationRouter['TupleMatchConstraint', CheckResult]() - GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array) - GENERATE_ROUTER.add(prelude.static_array, _generate_static_array) - GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) - def check(self) -> CheckResult: exp_type = self.exp_type if isinstance(exp_type, PlaceholderForType): @@ -339,7 +354,7 @@ class TupleMatchConstraint(ConstraintBase): exp_type = exp_type.resolve_as try: - return self.__class__.GENERATE_ROUTER(self, exp_type) + return self.generate_router(self, exp_type) except NoRouteForTypeException: raise NotImplementedError(exp_type) @@ -347,16 +362,14 @@ class MustImplementTypeClassConstraint(ConstraintBase): """ A type must implement a given type class """ - __slots__ = ('context', 'type_class3', 'types', ) + __slots__ = ('type_class3', 'types', ) - context: Context 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__(comment=comment) + super().__init__(context=context, comment=comment) - self.context = context self.type_class3 = type_class3 self.types = typ_list @@ -382,7 +395,7 @@ class MustImplementTypeClassConstraint(ConstraintBase): assert len(typ_list) == len(self.types) key = (self.type_class3, tuple(typ_list), ) - if key in self.context.type_class_instances_existing: + 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 @@ -410,32 +423,46 @@ class LiteralFitsConstraint(ConstraintBase): """ A literal value fits a given type """ - __slots__ = ('type3', 'literal', ) + __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__(comment=comment) + 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) - da_type, = da_args - res: list[ConstraintBase] = [] res.extend( - LiteralFitsConstraint(da_type, y) + LiteralFitsConstraint(self.context, da_type, y) for y in self.literal.value ) @@ -443,7 +470,7 @@ class LiteralFitsConstraint(ConstraintBase): # gets updated when we figure out the type of the # expression the literal is used in res.extend( - SameTypeConstraint(da_type, PlaceholderForType([y])) + SameTypeConstraint(self.context, da_type, PlaceholderForType([y])) for y in self.literal.value ) @@ -461,7 +488,7 @@ class LiteralFitsConstraint(ConstraintBase): res: list[ConstraintBase] = [] res.extend( - LiteralFitsConstraint(sa_type, y) + LiteralFitsConstraint(self.context, sa_type, y) for y in self.literal.value ) @@ -469,7 +496,7 @@ class LiteralFitsConstraint(ConstraintBase): # gets updated when we figure out the type of the # expression the literal is used in res.extend( - SameTypeConstraint(sa_type, PlaceholderForType([y])) + SameTypeConstraint(self.context, sa_type, PlaceholderForType([y])) for y in self.literal.value ) @@ -485,7 +512,7 @@ class LiteralFitsConstraint(ConstraintBase): res: list[ConstraintBase] = [] res.extend( - LiteralFitsConstraint(x, y) + LiteralFitsConstraint(self.context, x, y) for (_, x), y in zip(st_args, self.literal.value, strict=True) ) @@ -493,11 +520,12 @@ class LiteralFitsConstraint(ConstraintBase): # gets updated when we figure out the type of the # expression the literal is used in res.extend( - SameTypeConstraint(x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_type3.name}.{x_n}') + 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', @@ -515,7 +543,7 @@ class LiteralFitsConstraint(ConstraintBase): res: list[ConstraintBase] = [] res.extend( - LiteralFitsConstraint(x, y) + LiteralFitsConstraint(self.context, x, y) for x, y in zip(tp_args, self.literal.value, strict=True) ) @@ -523,57 +551,35 @@ class LiteralFitsConstraint(ConstraintBase): # gets updated when we figure out the type of the # expression the literal is used in res.extend( - SameTypeConstraint(x, PlaceholderForType([y])) + SameTypeConstraint(self.context, x, PlaceholderForType([y])) for x, y in zip(tp_args, self.literal.value, strict=True) ) return res - GENERATE_ROUTER = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]() - GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array) - GENERATE_ROUTER.add(prelude.static_array, _generate_static_array) - GENERATE_ROUTER.add(prelude.struct, _generate_struct) - GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) - def check(self) -> CheckResult: - int_table: Dict[str, Tuple[int, bool]] = { - 'u8': (1, False), - 'u16': (2, False), - 'u32': (4, False), - 'u64': (8, False), - 'i8': (1, True), - 'i16': (2, True), - 'i32': (4, True), - 'i64': (8, True), - } - - float_table: Dict[str, None] = { - 'f32': None, - 'f64': None, - } - if isinstance(self.type3, PlaceholderForType): if self.type3.resolve_as is None: return RequireTypeSubstitutes() self.type3 = self.type3.resolve_as - if self.type3.name in int_table: - bts, sgn = int_table[self.type3.name] + 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(bts, 'big', signed=sgn) + self.literal.value.to_bytes(type_info.alloc_size, 'big', signed=type_info.signed) except OverflowError: - return Error(f'Must fit in {bts} byte(s)', comment=self.comment) # FIXME: Add line information + 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 self.type3.name in float_table: - _ = float_table[self.type3.name] - + 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 @@ -581,16 +587,10 @@ class LiteralFitsConstraint(ConstraintBase): return Error('Must be real', comment=self.comment) # FIXME: Add line information - if self.type3 is prelude.bytes_: - if isinstance(self.literal.value, bytes): - return None - - return Error('Must be bytes', comment=self.comment) # FIXME: Add line information - exp_type = self.type3 try: - return self.__class__.GENERATE_ROUTER(self, exp_type) + return self.generate_router(self, exp_type) except NoRouteForTypeException: raise NotImplementedError(exp_type) @@ -610,32 +610,40 @@ 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', ) + __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__(comment=comment) + super().__init__(context=context, comment=comment) self.ret_type3 = ret_type3 self.type3 = type3 self.index_type3 = index_type3 self.index_const = index_const + + self.generate_router = TypeApplicationRouter() + self.generate_router.add_n(context.build.types['bytes'], self.__class__._generate_bytes) + 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_bytes(self) -> CheckResult: return [ - SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'), - SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), + SameTypeConstraint(self.context, self.context.build.types['u32'], self.index_type3, comment='([]) :: bytes -> u32 -> u8'), + SameTypeConstraint(self.context, self.context.build.types['u8'], self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), ] def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult: @@ -645,8 +653,8 @@ class CanBeSubscriptedConstraint(ConstraintBase): return Error('Tuple index out of range') return [ - SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), - SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), + SameTypeConstraint(self.context, self.context.build.types['u32'], self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), + SameTypeConstraint(self.context, sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), ] def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult: @@ -661,15 +669,10 @@ class CanBeSubscriptedConstraint(ConstraintBase): return Error('Tuple index out of range') return [ - SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), - SameTypeConstraint(tp_args[self.index_const], self.ret_type3, comment=f'Tuple subscript index {self.index_const}'), + 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}'), ] - GENERATE_ROUTER = TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]() - GENERATE_ROUTER.add_n(prelude.bytes_, _generate_bytes) - GENERATE_ROUTER.add(prelude.static_array, _generate_static_array) - GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) - def check(self) -> CheckResult: if self.type3.resolve_as is None: return RequireTypeSubstitutes() @@ -677,7 +680,7 @@ class CanBeSubscriptedConstraint(ConstraintBase): exp_type = self.type3.resolve_as try: - return self.__class__.GENERATE_ROUTER(self, exp_type) + return self.generate_router(self, exp_type) except NoRouteForTypeException: return Error(f'{exp_type.name} cannot be subscripted') diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index f28b88f..a666885 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -3,9 +3,9 @@ This module generates the typing constraints for Phasm. The constraints solver can then try to resolve all constraints. """ -from typing import Generator, List +from typing import Any, Generator, List -from .. import ourlang, prelude +from .. import ourlang from .constraints import ( CanBeSubscriptedConstraint, ConstraintBase, @@ -30,16 +30,15 @@ from .types import Type3, TypeApplication_Struct, TypeConstructor_Function ConstraintGenerator = Generator[ConstraintBase, None, None] -def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]: - ctx = Context() - ctx.type_class_instances_existing.update(prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING) +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( - phft, inp, + ctx, phft, inp, comment='The given literal must fit the expected type' ) return @@ -77,7 +76,8 @@ def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: Plac def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: PlaceholderForType) -> ConstraintGenerator: yield SameTypeConstraint( - prelude.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3), + 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})', ) @@ -151,6 +151,7 @@ def _expression_function_call( type_var_map.setdefault(func_arg, PlaceholderForType([])) yield SameFunctionArgumentConstraint( + ctx, func_var_map[sig_arg], sig_arg, type_var_map, @@ -182,6 +183,7 @@ def _expression_function_call( 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}', @@ -195,15 +197,15 @@ def _expression_function_call( 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(type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment) + yield SameTypeConstraint(ctx, type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment) continue if isinstance(sig_part, Type3): - yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment) + yield SameTypeConstraint(ctx, sig_part, arg_placeholders[arg_expr], comment=comment) continue if isinstance(sig_part, FunctionArgument): - yield SameTypeConstraint(func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment) + yield SameTypeConstraint(ctx, func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment) continue raise NotImplementedError(sig_part) @@ -215,7 +217,7 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType) return if isinstance(inp, ourlang.VariableReference): - yield SameTypeConstraint(inp.variable.type3, phft, + yield SameTypeConstraint(ctx, inp.variable.type3, phft, comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})') return @@ -239,6 +241,7 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType) r_type.append(arg_phft) yield TupleMatchConstraint( + ctx, phft, r_type, comment='The type of a tuple is a combination of its members' @@ -254,9 +257,9 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType) yield from expression(ctx, inp.index, index_phft) if isinstance(inp.index, ourlang.ConstantPrimitive) and isinstance(inp.index.value, int): - yield CanBeSubscriptedConstraint(phft, varref_phft, index_phft, inp.index.value) + yield CanBeSubscriptedConstraint(ctx, phft, varref_phft, index_phft, inp.index.value) else: - yield CanBeSubscriptedConstraint(phft, varref_phft, index_phft, None) + yield CanBeSubscriptedConstraint(ctx, phft, varref_phft, index_phft, None) return if isinstance(inp, ourlang.AccessStructMember): @@ -265,7 +268,7 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType) mem_typ = dict(inp.struct_type3.application.arguments)[inp.member] yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO - yield SameTypeConstraint(mem_typ, phft, + 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 @@ -276,7 +279,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement yield from expression(ctx, inp.value, phft) - yield SameTypeConstraint(fun.returns_type3, 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: @@ -284,7 +287,7 @@ def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) yield from expression(ctx, inp.test, test_phft) - yield SameTypeConstraint(test_phft, prelude.bool_, + yield SameTypeConstraint(ctx, test_phft, ctx.build.types['bool'], comment='Must pass a boolean expression to if') for stmt in inp.statements: @@ -317,10 +320,10 @@ def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Constra phft = PlaceholderForType([inp.constant]) yield from constant(ctx, inp.constant, phft) - yield SameTypeConstraint(inp.type3, 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) -> ConstraintGenerator: +def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator: for cdef in inp.constant_defs.values(): yield from module_constant_def(ctx, cdef) diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index 6819ecb..9b7f96a 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -1,14 +1,14 @@ """ Entry point to the type3 system """ -from typing import Dict, List +from typing import Any, Dict, List from .. import codestyle, ourlang from .constraints import ( ConstraintBase, Error, + HumanReadableRet, RequireTypeSubstitutes, - SameTypeConstraint, SubstitutionMap, ) from .constraintsgenerator import phasm_type3_generate_constraints @@ -25,7 +25,28 @@ class Type3Exception(BaseException): Thrown when the Type3 system detects constraints that do not hold """ -def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: +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 @@ -116,7 +137,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: for expr in plh.update_on_substitution: expr.type3 = typ -def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None: +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(): @@ -151,7 +172,7 @@ def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[in 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, SameTypeConstraint(psk, psv, comment='Deduced type')) + print_constraint(placeholder_id_map, DeducedType(psk, psv)) for constraint in constraint_list: print_constraint(placeholder_id_map, constraint) diff --git a/phasm/type3/types.py b/phasm/type3/types.py index e6bc4f2..9406c1b 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -79,7 +79,7 @@ class Type3(KindArgument): def __eq__(self, other: Any) -> bool: if not isinstance(other, Type3): - raise NotImplementedError + raise NotImplementedError(other) return self is other diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 192ee21..3f40d89 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -3,17 +3,17 @@ import struct import sys from typing import Any, Callable, Generator, Iterable, List, TextIO, Union -from phasm import compiler, prelude +from phasm import compiler +from phasm.build import builtins from phasm.codestyle import phasm_render -from phasm.runtime import ( - calculate_alloc_size, - calculate_alloc_size_static_array, - calculate_alloc_size_struct, - calculate_alloc_size_tuple, -) -from phasm.stdlib.types import TYPE_INFO_CONSTRUCTED, TYPE_INFO_MAP from phasm.type3 import types as type3types from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter +from phasm.wasm import ( + WasmTypeFloat32, + WasmTypeFloat64, + WasmTypeInt32, + WasmTypeInt64, +) from . import runners @@ -125,17 +125,14 @@ class Suite: runner.interpreter_dump_memory(sys.stderr) for arg, arg_typ in zip(args, func_args, strict=True): - if arg_typ in (prelude.u8, prelude.u16, prelude.u32, prelude.u64, ): + arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name) + + if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeInt32 or arg_typ_info.wasm_type is WasmTypeInt64): assert isinstance(arg, int) wasm_args.append(arg) continue - if arg_typ in (prelude.i8, prelude.i16, prelude.i32, prelude.i64, ): - assert isinstance(arg, int) - wasm_args.append(arg) - continue - - if arg_typ in (prelude.f32, prelude.f64, ): + if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeFloat32 or arg_typ_info.wasm_type is WasmTypeFloat64): assert isinstance(arg, float) wasm_args.append(arg) continue @@ -168,19 +165,6 @@ class Suite: def write_header(textio: TextIO, msg: str) -> None: textio.write(f'{DASHES} {msg.ljust(16)} {DASHES}\n') -WRITE_LOOKUP_MAP = { - 'u8': compiler.module_data_u8, - 'u16': compiler.module_data_u16, - 'u32': compiler.module_data_u32, - 'u64': compiler.module_data_u64, - 'i8': compiler.module_data_i8, - 'i16': compiler.module_data_i16, - 'i32': compiler.module_data_i32, - 'i64': compiler.module_data_i64, - 'f32': compiler.module_data_f32, - 'f64': compiler.module_data_f64, -} - def _write_memory_stored_value( runner: runners.RunnerBase, adr: int, @@ -189,10 +173,14 @@ def _write_memory_stored_value( ) -> int: try: adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ) - runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) - return TYPE_INFO_CONSTRUCTED.alloc_size + + ptr_type_info = runner.phasm_ast.build.type_info_map['ptr'] + to_write = compiler.module_data_primitive(ptr_type_info, adr2) + runner.interpreter_write_memory(adr, to_write) + return runner.phasm_ast.build.type_info_constructed.alloc_size except NoRouteForTypeException: - to_write = WRITE_LOOKUP_MAP[val_typ.name](val) + val_typ_info = runner.phasm_ast.build.type_info_map[val_typ.name] + to_write = compiler.module_data_primitive(val_typ_info, val) runner.interpreter_write_memory(adr, to_write) return len(to_write) @@ -213,16 +201,19 @@ def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_type, = da_args + if da_type.name == 'u8': + return _allocate_memory_stored_bytes(attrs) + if not isinstance(val, tuple): raise InvalidArgumentException(f'Expected tuple; got {val!r} instead') - alloc_size = 4 + len(val) * calculate_alloc_size(da_type, True) + alloc_size = 4 + len(val) * runner.phasm_ast.build.calculate_alloc_size(da_type, True) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) # Type int sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') offset = adr - offset += _write_memory_stored_value(runner, offset, prelude.u32, len(val)) + offset += _write_memory_stored_value(runner, offset, runner.phasm_ast.build.types['u32'], len(val)) for val_el_val in val: offset += _write_memory_stored_value(runner, offset, da_type, val_el_val) return adr @@ -237,7 +228,7 @@ def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], if sa_len.value != len(val): raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead') - alloc_size = calculate_alloc_size_static_array(False, sa_args) + alloc_size = runner.phasm_ast.build.calculate_alloc_size_static_array(sa_args) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) # Type int sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') @@ -252,7 +243,7 @@ def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_arg assert isinstance(val, dict) - alloc_size = calculate_alloc_size_struct(False, st_args) + alloc_size = runner.phasm_ast.build.calculate_alloc_size_struct(st_args) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') @@ -272,7 +263,7 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args assert isinstance(val, tuple) - alloc_size = calculate_alloc_size_tuple(False, tp_args) + alloc_size = runner.phasm_ast.build.calculate_alloc_size_tuple(tp_args) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) assert isinstance(adr, int) sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') @@ -285,11 +276,11 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args return adr ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]() -ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes) -ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.dynamic_array, _allocate_memory_stored_dynamic_array) -ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.static_array, _allocate_memory_stored_static_array) -ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct) -ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple) +# ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes) +ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.dynamic_array, _allocate_memory_stored_dynamic_array) +ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.static_array, _allocate_memory_stored_static_array) +ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.struct, _allocate_memory_stored_struct) +ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.tuple_, _allocate_memory_stored_tuple) def _load_memory_stored_returned_value( runner: runners.RunnerBase, @@ -298,96 +289,105 @@ def _load_memory_stored_returned_value( ) -> Any: ret_type3 = runner.phasm_ast.functions[func_name].returns_type3 - if ret_type3 is prelude.none: - return None + if ret_type3.name in runner.phasm_ast.build.type_info_map: + type_info = runner.phasm_ast.build.type_info_map[ret_type3.name] - if ret_type3 is prelude.bool_: - assert isinstance(wasm_value, int), wasm_value - return 0 != wasm_value + # if ret_type3 is prelude.none: + # return None - if ret_type3 in (prelude.i8, prelude.i16, prelude.i32, prelude.i64): - assert isinstance(wasm_value, int), wasm_value + # if ret_type3 is prelude.bool_: + # assert isinstance(wasm_value, int), wasm_value + # return 0 != wasm_value - if ret_type3 is prelude.i8: - # Values are actually i32 - # Have to reinterpret to load proper value - data = struct.pack(' Any: - typ_info = TYPE_INFO_MAP.get(typ.name, TYPE_INFO_CONSTRUCTED) + typ_info = runner.phasm_ast.build.type_info_map.get(typ.name) - assert len(inp) == typ_info.alloc_size + if typ_info is None: + assert len(inp) == 4 - if typ is prelude.u8: - return struct.unpack(' bytes: @@ -410,13 +410,16 @@ def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_a runner, adr = attrs da_type, = da_args + if da_type.name == 'u8': + return _load_bytes_from_address(attrs) + sys.stderr.write(f'Reading 0x{adr:08x} {da_type:s}[...]\n') read_bytes = runner.interpreter_read_memory(adr, 4) array_len, = struct.unpack(' bytes: result = Suite(code_py).run_code() - assert b"Hello" == result.returned_value + assert b"Hello" == result.returned_value[0:5] + assert 5 == len(result.returned_value) @pytest.mark.integration_test def test_bytes_export_instantiation():