diff --git a/TODO.md b/TODO.md index a98e4dd..91963ee 100644 --- a/TODO.md +++ b/TODO.md @@ -15,6 +15,7 @@ - Does Subscript do what we want? It's a language feature rather a normal typed thing. How would you implement your own Subscript-able type? - Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types. - Have a set of rules or guidelines for the constraint comments, they're messy. +- Why is expression_subscript_bytes using a helper method but expression_subscript_static_array is not? - Parser is putting stuff in ModuleDataBlock - Surely the compiler should build data blocks diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 1ab8430..bbb7a05 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -6,7 +6,7 @@ It's intented to be a "any color, as long as it's black" kind of renderer from typing import Generator from . import ourlang, prelude -from .type3.types import Type3 +from .type3.types import Type3, TypeApplication_Struct def phasm_render(inp: ourlang.Module) -> str: @@ -30,11 +30,10 @@ def struct_definition(inp: ourlang.StructDefinition) -> str: """ Render: TypeStruct's definition """ - st_args = prelude.struct.did_construct(inp.struct_type3) - assert st_args is not None + assert isinstance(inp.struct_type3.application, TypeApplication_Struct) result = f'class {inp.struct_type3.name}:\n' - for mem, typ in st_args.items(): + for mem, typ in inp.struct_type3.application.arguments: result += f' {mem}: {type3(typ)}\n' return result diff --git a/phasm/compiler.py b/phasm/compiler.py index 66409b2..328f04d 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -330,19 +330,24 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> """ assert inp.type3 is not None, TYPE3_ASSERTION_ERROR - args: list[type3types.Type3] = [] + args: tuple[type3types.Type3, ...] - sa_args = prelude.static_array.did_construct(inp.type3) - if sa_args is not None: - sa_type, sa_len = sa_args - args = [sa_type for _ in range(sa_len.value)] + if isinstance(inp.type3.application, type3types.TypeApplication_TypeStar): + # Possibly paranoid assert. If we have a future variadic type, + # does it also do this tuple instantation like this? + assert isinstance(inp.type3.application.constructor, type3types.TypeConstructor_Tuple) - if not args: - tp_args = prelude.tuple_.did_construct(inp.type3) - if tp_args is None: - raise NotImplementedError + args = inp.type3.application.arguments + elif isinstance(inp.type3.application, type3types.TypeApplication_TypeInt): + # Possibly paranoid assert. If we have a future type of kind * -> Int -> *, + # does it also do this tuple instantation like this? + assert isinstance(inp.type3.application.constructor, type3types.TypeConstructor_StaticArray) - args = list(tp_args) + sa_type, sa_len = inp.type3.application.arguments + + args = tuple(sa_type for _ in range(sa_len.value)) + else: + raise NotImplementedError('tuple_instantiation', inp.type3) comment_elements = '' for element in inp.elements: @@ -378,6 +383,81 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> # Return the allocated address wgn.local.get(tmp_var) +def expression_subscript_bytes( + attrs: tuple[WasmGenerator, ourlang.Subscript], + ) -> bool: + wgn, inp = attrs + + expression(wgn, inp.varref) + expression(wgn, inp.index) + wgn.call(stdlib_types.__subscript_bytes__) + return True + +def expression_subscript_static_array( + attrs: tuple[WasmGenerator, ourlang.Subscript], + args: tuple[type3types.Type3, type3types.IntType3], + ) -> bool: + wgn, inp = attrs + + el_type, el_len = args + + # OPTIMIZE: If index is a constant, we can use offset instead of multiply + # and we don't need to do the out of bounds check + + expression(wgn, inp.varref) + + tmp_var = wgn.temp_var_i32('index') + expression(wgn, inp.index) + wgn.local.tee(tmp_var) + + # Out of bounds check based on el_len.value + wgn.i32.const(el_len.value) + wgn.i32.ge_u() + with wgn.if_(): + wgn.unreachable(comment='Out of bounds') + + wgn.local.get(tmp_var) + wgn.i32.const(calculate_alloc_size(el_type)) + wgn.i32.mul() + wgn.i32.add() + + mtyp = LOAD_STORE_TYPE_MAP[el_type.name] + + wgn.add_statement(f'{mtyp}.load') + return True + +def expression_subscript_tuple( + attrs: tuple[WasmGenerator, ourlang.Subscript], + args: tuple[type3types.Type3, ...], + ) -> bool: + wgn, inp = attrs + + assert isinstance(inp.index, ourlang.ConstantPrimitive) + assert isinstance(inp.index.value, int) + + offset = 0 + for el_type in args[0:inp.index.value]: + assert el_type is not None, TYPE3_ASSERTION_ERROR + offset += calculate_alloc_size(el_type) + + el_type = args[inp.index.value] + assert el_type is not None, TYPE3_ASSERTION_ERROR + + expression(wgn, inp.varref) + + if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: + mtyp = 'i32' + else: + mtyp = LOAD_STORE_TYPE_MAP[el_type.name] + + wgn.add_statement(f'{mtyp}.load', f'offset={offset}') + return True + +SUBSCRIPT_ROUTER = type3types.TypeApplicationRouter[tuple[WasmGenerator, ourlang.Subscript], bool]() +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) + def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: """ Compile: Any expression @@ -509,81 +589,21 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: if isinstance(inp, ourlang.Subscript): assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR - if inp.varref.type3 is prelude.bytes_: - expression(wgn, inp.varref) - expression(wgn, inp.index) - wgn.call(stdlib_types.__subscript_bytes__) - return - - assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR - - sa_args = prelude.static_array.did_construct(inp.varref.type3) - if sa_args is not None: - el_type, el_len = sa_args - - # OPTIMIZE: If index is a constant, we can use offset instead of multiply - # and we don't need to do the out of bounds check - - expression(wgn, inp.varref) - - tmp_var = wgn.temp_var_i32('index') - expression(wgn, inp.index) - wgn.local.tee(tmp_var) - - # Out of bounds check based on el_len.value - wgn.i32.const(el_len.value) - wgn.i32.ge_u() - with wgn.if_(): - wgn.unreachable(comment='Out of bounds') - - wgn.local.get(tmp_var) - wgn.i32.const(calculate_alloc_size(el_type)) - wgn.i32.mul() - wgn.i32.add() - - mtyp = LOAD_STORE_TYPE_MAP[el_type.name] - - wgn.add_statement(f'{mtyp}.load') - return - - tp_args = prelude.tuple_.did_construct(inp.varref.type3) - if tp_args is not None: - assert isinstance(inp.index, ourlang.ConstantPrimitive) - assert isinstance(inp.index.value, int) - - offset = 0 - for el_type in tp_args[0:inp.index.value]: - assert el_type is not None, TYPE3_ASSERTION_ERROR - offset += calculate_alloc_size(el_type) - - el_type = tp_args[inp.index.value] - assert el_type is not None, TYPE3_ASSERTION_ERROR - - expression(wgn, inp.varref) - - if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: - mtyp = 'i32' - else: - mtyp = LOAD_STORE_TYPE_MAP[el_type.name] - - wgn.add_statement(f'{mtyp}.load', f'offset={offset}') - return - - raise NotImplementedError(expression, inp, inp.varref.type3) + # Type checker guarantees we don't get routing errors + return SUBSCRIPT_ROUTER((wgn, inp, ), inp.varref.type3) if isinstance(inp, ourlang.AccessStructMember): assert inp.struct_type3 is not None, TYPE3_ASSERTION_ERROR - st_args = prelude.struct.did_construct(inp.struct_type3) - assert st_args is not None + assert isinstance(inp.struct_type3.application, type3types.TypeApplication_Struct) - member_type = st_args[inp.member] + member_type = dict(inp.struct_type3.application.arguments)[inp.member] mtyp = LOAD_STORE_TYPE_MAP[member_type.name] expression(wgn, inp.varref) wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset( - inp.struct_type3.name, st_args, inp.member + inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member ))) return @@ -958,8 +978,9 @@ def module(inp: ourlang.Module) -> wasm.Module: return result def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: - st_args = prelude.struct.did_construct(inp.struct_type3) - assert st_args is not None + assert isinstance(inp.struct_type3.application, type3types.TypeApplication_Struct) + + st_args = inp.struct_type3.application.arguments tmp_var = wgn.temp_var_i32('struct_adr') @@ -969,7 +990,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc wgn.local.set(tmp_var) # Store each member individually - for memname, mtyp3 in st_args.items(): + for memname, mtyp3 in st_args: mtyp: Optional[str] if (prelude.InternalPassAsPointer, (mtyp3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: mtyp = 'i32' diff --git a/phasm/ourlang.py b/phasm/ourlang.py index a4b1618..f103555 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -7,7 +7,7 @@ from typing import Dict, Iterable, List, Optional, Union from . import prelude from .type3.functions import FunctionSignature, TypeVariableContext from .type3.typeclasses import Type3ClassMethod -from .type3.types import Type3 +from .type3.types import Type3, TypeApplication_Struct class Expression: @@ -341,9 +341,9 @@ class StructConstructor(Function): def __init__(self, struct_type3: Type3) -> None: super().__init__(f'@{struct_type3.name}@__init___@', -1) - st_args = prelude.struct.did_construct(struct_type3) - assert st_args is not None - for mem, typ in st_args.items(): + assert isinstance(struct_type3.application, TypeApplication_Struct) + + for mem, typ in struct_type3.application.arguments: self.posonlyargs.append(FunctionParam(mem, typ, )) self.signature.args.append(typ) diff --git a/phasm/parser.py b/phasm/parser.py index 5440eba..95c6703 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -246,7 +246,7 @@ class OurVisitor: members[stmt.target.id] = self.visit_type(module, stmt.annotation) - return StructDefinition(prelude.struct(node.name, members), node.lineno) + return StructDefinition(prelude.struct(node.name, tuple(members.items())), node.lineno) def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: if not isinstance(node.target, ast.Name): diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index 86e42b1..df34f77 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -1,11 +1,14 @@ """ The prelude are all the builtin types, type classes and methods """ - -from ..type3.functions import TypeVariable +from ..type3.functions import ( + TypeVariable, +) from ..type3.typeclasses import Type3Class from ..type3.types import ( + IntType3, Type3, + TypeApplication_Nullary, TypeConstructor_StaticArray, TypeConstructor_Struct, TypeConstructor_Tuple, @@ -21,40 +24,40 @@ def instance_type_class(cls: Type3Class, *typ: Type3) -> None: PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), )) -none = Type3('none') +none = Type3('none', TypeApplication_Nullary(None, None)) """ The none type, for when functions simply don't return anything. e.g., IO(). """ -bool_ = Type3('bool') +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') +u8 = Type3('u8', TypeApplication_Nullary(None, None)) """ The unsigned 8-bit integer type. Operations on variables employ modular arithmetic, with modulus 2^8. """ -u32 = Type3('u32') +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') +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') +i8 = Type3('i8', TypeApplication_Nullary(None, None)) """ The signed 8-bit integer type. @@ -62,7 +65,7 @@ Operations on variables employ modular arithmetic, with modulus 2^8, but with the middel point being 0. """ -i32 = Type3('i32') +i32 = Type3('i32', TypeApplication_Nullary(None, None)) """ The unsigned 32-bit integer type. @@ -70,7 +73,7 @@ Operations on variables employ modular arithmetic, with modulus 2^32, but with the middel point being 0. """ -i64 = Type3('i64') +i64 = Type3('i64', TypeApplication_Nullary(None, None)) """ The unsigned 64-bit integer type. @@ -78,22 +81,22 @@ Operations on variables employ modular arithmetic, with modulus 2^64, but with the middel point being 0. """ -f32 = Type3('f32') +f32 = Type3('f32', TypeApplication_Nullary(None, None)) """ A 32-bits IEEE 754 float, of 32 bits width. """ -f64 = Type3('f64') +f64 = Type3('f64', TypeApplication_Nullary(None, None)) """ A 32-bits IEEE 754 float, of 64 bits width. """ -bytes_ = Type3('bytes') +bytes_ = Type3('bytes', TypeApplication_Nullary(None, None)) """ This is a runtime-determined length piece of memory that can be indexed at runtime. """ -def sa_on_create(typ: Type3) -> None: +def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None: instance_type_class(InternalPassAsPointer, typ) static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create) @@ -106,7 +109,7 @@ It should be applied with one argument. It has a runtime-dynamic length of the same type repeated. """ -def tp_on_create(typ: Type3) -> None: +def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None: instance_type_class(InternalPassAsPointer, typ) tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create) @@ -117,7 +120,7 @@ It should be applied with zero or more arguments. It has a compile time determined length, and each argument can be different. """ -def st_on_create(typ: Type3) -> None: +def st_on_create(args: tuple[tuple[str, Type3], ...], typ: Type3) -> None: instance_type_class(InternalPassAsPointer, typ) struct = TypeConstructor_Struct('struct', on_create=st_on_create) diff --git a/phasm/runtime.py b/phasm/runtime.py index 27988f9..c7bacb0 100644 --- a/phasm/runtime.py +++ b/phasm/runtime.py @@ -1,8 +1,39 @@ from . import prelude -from .type3 import types as type3types +from .type3.types import IntType3, NoRouteForTypeException, Type3, TypeApplicationRouter -def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: +def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int: + if is_member: + return 4 + + 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 4 + + 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 4 + + 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: if typ in (prelude.u8, prelude.i8, ): return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32 @@ -12,51 +43,20 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: if typ in (prelude.u64, prelude.i64, prelude.f64, ): return 8 - if typ == prelude.bytes_: + 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 4 - raise NotImplementedError # When does this happen? + raise NotImplementedError(typ) - st_args = prelude.struct.did_construct(typ) - if st_args is not None: - if is_member: - # Structs referred to by other structs or tuples are pointers - return 4 - - return sum( - calculate_alloc_size(x, is_member=True) - for x in st_args.values() - ) - - sa_args = prelude.static_array.did_construct(typ) - if sa_args is not None: - if is_member: - # tuples referred to by other structs or tuples are pointers - return 4 - - sa_type, sa_len = sa_args - - return sa_len.value * calculate_alloc_size(sa_type, is_member=True) - - tp_args = prelude.tuple_.did_construct(typ) - if tp_args is not None: - if is_member: - # tuples referred to by other structs or tuples are pointers - return 4 - - size = 0 - for arg in tp_args: - size += calculate_alloc_size(arg, is_member=True) - - return size - - raise NotImplementedError(calculate_alloc_size, typ) - -def calculate_member_offset(st_name: str, st_args: dict[str, type3types.Type3], needle: str) -> int: +def calculate_member_offset(st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int: result = 0 - for memnam, memtyp in st_args.items(): + for memnam, memtyp in st_args: if needle == memnam: return result diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index 4b5a24c..1eafac2 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -158,12 +158,41 @@ class SameTypeConstraint(ConstraintBase): return f'SameTypeConstraint({args}, comment={repr(self.comment)})' class TupleMatchConstraint(ConstraintBase): + __slots__ = ('exp_type', 'args', ) + + exp_type: placeholders.Type3OrPlaceholder + args: list[placeholders.Type3OrPlaceholder] + def __init__(self, exp_type: placeholders.Type3OrPlaceholder, args: Iterable[placeholders.Type3OrPlaceholder], comment: str): super().__init__(comment=comment) self.exp_type = exp_type self.args = list(args) + def _generate_static_array(self, sa_args: tuple[types.Type3, types.IntType3]) -> CheckResult: + sa_type, sa_len = sa_args + + if sa_len.value != len(self.args): + return Error('Mismatch between applied types argument count', comment=self.comment) + + return [ + SameTypeConstraint(arg, sa_type) + for arg in self.args + ] + + def _generate_tuple(self, tp_args: tuple[types.Type3, ...]) -> CheckResult: + if len(tp_args) != len(self.args): + return Error('Mismatch between applied types argument count', comment=self.comment) + + return [ + SameTypeConstraint(arg, oth_arg) + for arg, oth_arg in zip(self.args, tp_args, strict=True) + ] + + GENERATE_ROUTER = types.TypeApplicationRouter['TupleMatchConstraint', CheckResult]() + 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, placeholders.PlaceholderForType): @@ -172,31 +201,10 @@ class TupleMatchConstraint(ConstraintBase): exp_type = exp_type.resolve_as - assert isinstance(exp_type, types.Type3) - - sa_args = prelude.static_array.did_construct(exp_type) - if sa_args is not None: - sa_type, sa_len = sa_args - - if sa_len.value != len(self.args): - return Error('Mismatch between applied types argument count', comment=self.comment) - - return [ - SameTypeConstraint(arg, sa_type) - for arg in self.args - ] - - tp_args = prelude.tuple_.did_construct(exp_type) - if tp_args is not None: - if len(tp_args) != len(self.args): - return Error('Mismatch between applied types argument count', comment=self.comment) - - return [ - SameTypeConstraint(arg, oth_arg) - for arg, oth_arg in zip(self.args, tp_args, strict=True) - ] - - raise NotImplementedError(exp_type) + try: + return self.__class__.GENERATE_ROUTER(self, exp_type) + except types.NoRouteForTypeException: + raise NotImplementedError(exp_type) class MustImplementTypeClassConstraint(ConstraintBase): """ @@ -281,6 +289,85 @@ class LiteralFitsConstraint(ConstraintBase): self.type3 = type3 self.literal = literal + def _generate_static_array(self, sa_args: tuple[types.Type3, types.IntType3]) -> CheckResult: + if not isinstance(self.literal, ourlang.ConstantTuple): + return Error('Must be tuple', comment=self.comment) + + sa_type, sa_len = sa_args + + if sa_len.value != len(self.literal.value): + return Error('Member count mismatch', comment=self.comment) + + res: list[ConstraintBase] = [] + + res.extend( + LiteralFitsConstraint(sa_type, y) + for y in self.literal.value + ) + + # Generate placeholders so each Literal expression + # gets updated when we figure out the type of the + # expression the literal is used in + res.extend( + SameTypeConstraint(sa_type, PlaceholderForType([y])) + for y in self.literal.value + ) + + return res + + def _generate_struct(self, st_args: tuple[tuple[str, types.Type3], ...]) -> CheckResult: + if not isinstance(self.literal, ourlang.ConstantStruct): + return Error('Must be struct') + + if len(st_args) != len(self.literal.value): + return Error('Struct element count mismatch') + + res: list[ConstraintBase] = [] + + res.extend( + LiteralFitsConstraint(x, y) + for (_, x), y in zip(st_args, self.literal.value, strict=True) + ) + + # Generate placeholders so each Literal expression + # gets updated when we figure out the type of the + # expression the literal is used in + res.extend( + SameTypeConstraint(x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_name}.{x_n}') + for (x_n, x_t, ), y in zip(st_args, self.literal.value, strict=True) + ) + + return res + + def _generate_tuple(self, tp_args: tuple[types.Type3, ...]) -> CheckResult: + if not isinstance(self.literal, ourlang.ConstantTuple): + return Error('Must be tuple', comment=self.comment) + + if len(tp_args) != len(self.literal.value): + return Error('Tuple element count mismatch', comment=self.comment) + + res: list[ConstraintBase] = [] + + res.extend( + LiteralFitsConstraint(x, y) + for x, y in zip(tp_args, self.literal.value, strict=True) + ) + + # Generate placeholders so each Literal expression + # gets updated when we figure out the type of the + # expression the literal is used in + res.extend( + SameTypeConstraint(x, PlaceholderForType([y])) + for x, y in zip(tp_args, self.literal.value, strict=True) + ) + + return res + + GENERATE_ROUTER = types.TypeApplicationRouter['LiteralFitsConstraint', CheckResult]() + 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), @@ -331,92 +418,12 @@ class LiteralFitsConstraint(ConstraintBase): return Error('Must be bytes', comment=self.comment) # FIXME: Add line information - res: NewConstraintList + exp_type = self.type3 - assert isinstance(self.type3, types.Type3) - - tp_args = prelude.tuple_.did_construct(self.type3) - if tp_args is not None: - if not isinstance(self.literal, ourlang.ConstantTuple): - return Error('Must be tuple', comment=self.comment) - - if len(tp_args) != len(self.literal.value): - return Error('Tuple element count mismatch', comment=self.comment) - - res = [] - - res.extend( - LiteralFitsConstraint(x, y) - for x, y in zip(tp_args, self.literal.value, strict=True) - ) - - # Generate placeholders so each Literal expression - # gets updated when we figure out the type of the - # expression the literal is used in - res.extend( - SameTypeConstraint(x, PlaceholderForType([y])) - for x, y in zip(tp_args, self.literal.value, strict=True) - ) - - return res - - sa_args = prelude.static_array.did_construct(self.type3) - if sa_args is not None: - if not isinstance(self.literal, ourlang.ConstantTuple): - return Error('Must be tuple', comment=self.comment) - - sa_type, sa_len = sa_args - - if sa_len.value != len(self.literal.value): - return Error('Member count mismatch', comment=self.comment) - - res = [] - - res.extend( - LiteralFitsConstraint(sa_type, y) - for y in self.literal.value - ) - - # Generate placeholders so each Literal expression - # gets updated when we figure out the type of the - # expression the literal is used in - res.extend( - SameTypeConstraint(sa_type, PlaceholderForType([y])) - for y in self.literal.value - ) - - return res - - st_args = prelude.struct.did_construct(self.type3) - if st_args is not None: - if not isinstance(self.literal, ourlang.ConstantStruct): - return Error('Must be struct') - - if self.literal.struct_name != self.type3.name: - return Error('Struct mismatch') - - - if len(st_args) != len(self.literal.value): - return Error('Struct element count mismatch') - - res = [] - - res.extend( - LiteralFitsConstraint(x, y) - for x, y in zip(st_args.values(), self.literal.value, strict=True) - ) - - # Generate placeholders so each Literal expression - # gets updated when we figure out the type of the - # expression the literal is used in - res.extend( - SameTypeConstraint(x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_name}.{x_n}') - for (x_n, x_t, ), y in zip(st_args.items(), self.literal.value, strict=True) - ) - - return res - - raise NotImplementedError(self.type3, self.literal) + try: + return self.__class__.GENERATE_ROUTER(self, exp_type) + except types.NoRouteForTypeException: + raise NotImplementedError(exp_type) def human_readable(self) -> HumanReadableRet: return ( @@ -456,6 +463,50 @@ class CanBeSubscriptedConstraint(ConstraintBase): self.index = index self.index_phft = index_phft + def _generate_bytes(self) -> CheckResult: + return [ + SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: bytes -> u32 -> u8'), + SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), + ] + + def _generate_static_array(self, sa_args: tuple[types.Type3, types.IntType3]) -> CheckResult: + sa_type, sa_len = sa_args + + if isinstance(self.index, ourlang.ConstantPrimitive): + assert isinstance(self.index.value, int) + + if self.index.value < 0 or sa_len.value <= self.index.value: + return Error('Tuple index out of range') + + return [ + SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: Subscriptable a => a b -> u32 -> b'), + SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), + ] + + def _generate_tuple(self, tp_args: tuple[types.Type3, ...]) -> CheckResult: + # We special case tuples to allow for ease of use to the programmer + # e.g. rather than having to do `fst a` and `snd a` and only have to-sized tuples + # we use a[0] and a[1] and allow for a[2] and on. + + if not isinstance(self.index, ourlang.ConstantPrimitive): + return Error('Must index with literal') + + if not isinstance(self.index.value, int): + return Error('Must index with integer literal') + + if self.index.value < 0 or len(tp_args) <= self.index.value: + return Error('Tuple index out of range') + + return [ + SameTypeConstraint(prelude.u32, self.index_phft, comment=f'Tuple subscript index {self.index.value}'), + SameTypeConstraint(tp_args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'), + ] + + GENERATE_ROUTER = types.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: exp_type = self.type3 if isinstance(exp_type, placeholders.PlaceholderForType): @@ -464,51 +515,10 @@ class CanBeSubscriptedConstraint(ConstraintBase): exp_type = exp_type.resolve_as - assert isinstance(exp_type, types.Type3) - - sa_args = prelude.static_array.did_construct(exp_type) - if sa_args is not None: - sa_type, sa_len = sa_args - - result: List[ConstraintBase] = [ - SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: Subscriptable a => a b -> u32 -> b'), - SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), - ] - - if isinstance(self.index, ourlang.ConstantPrimitive): - assert isinstance(self.index.value, int) - - if self.index.value < 0 or sa_len.value <= self.index.value: - return Error('Tuple index out of range') - - return result - - # We special case tuples to allow for ease of use to the programmer - # e.g. rather than having to do `fst a` and `snd a` and only have to-sized tuples - # we use a[0] and a[1] and allow for a[2] and on. - tp_args = prelude.tuple_.did_construct(exp_type) - if tp_args is not None: - if not isinstance(self.index, ourlang.ConstantPrimitive): - return Error('Must index with literal') - - if not isinstance(self.index.value, int): - return Error('Must index with integer literal') - - if self.index.value < 0 or len(tp_args) <= self.index.value: - return Error('Tuple index out of range') - - return [ - SameTypeConstraint(prelude.u32, self.index_phft, comment=f'Tuple subscript index {self.index.value}'), - SameTypeConstraint(tp_args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'), - ] - - if exp_type is prelude.bytes_: - return [ - SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: bytes -> u32 -> u8'), - SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), - ] - - return Error(f'{exp_type.name} cannot be subscripted') + try: + return self.__class__.GENERATE_ROUTER(self, exp_type) + except types.NoRouteForTypeException: + return Error(f'{exp_type.name} cannot be subscripted') def human_readable(self) -> HumanReadableRet: return ( diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index 8a9c37b..c10ce86 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -124,12 +124,12 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.Placeho return if isinstance(inp, ourlang.AccessStructMember): - assert isinstance(inp.struct_type3, type3types.Type3) # When does this happen? - st_args = prelude.struct.did_construct(inp.struct_type3) - assert st_args is not None # FIXME: See test_struct.py::test_struct_not_accessible + assert isinstance(inp.struct_type3.application, type3types.TypeApplication_Struct) # FIXME: See test_struct.py::test_struct_not_accessible + + mem_typ = dict(inp.struct_type3.application.arguments)[inp.member] yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO - yield SameTypeConstraint(st_args[inp.member], phft, + yield SameTypeConstraint(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 diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 6dad13c..ceb86ab 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -4,15 +4,34 @@ Contains the final types for use in Phasm, as well as construtors. from typing import ( Any, Callable, - Generic, + Hashable, + Self, Tuple, TypeVar, ) +S = TypeVar('S') +T = TypeVar('T') class KindArgument: pass +class TypeApplication_Base[T: Hashable, S: Hashable]: + """ + Records the constructor and arguments used to create this type. + + Nullary types, or types of kind *, have both arguments set to None. + """ + constructor: T + arguments: S + + def __init__(self, constructor: T, arguments: S) -> None: + self.constructor = constructor + self.arguments = arguments + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})' + class Type3(KindArgument): """ Base class for the type3 types @@ -20,18 +39,25 @@ class Type3(KindArgument): (Having a separate name makes it easier to distinguish from Python's Type) """ - __slots__ = ('name', ) + __slots__ = ('name', 'application', ) name: str """ The name of the string, as parsed and outputted by codestyle. """ - def __init__(self, name: str) -> None: + application: TypeApplication_Base[Any, Any] + """ + How the type was constructed; i.e. which constructor was used and which + type level arguments were applied to the constructor. + """ + + def __init__(self, name: str, application: TypeApplication_Base[Any, Any]) -> None: self.name = name + self.application = application def __repr__(self) -> str: - return f'Type3({repr(self.name)})' + return f'Type3({self.name!r}, {self.application!r})' def __str__(self) -> str: return self.name @@ -57,6 +83,9 @@ class Type3(KindArgument): def __bool__(self) -> bool: raise NotImplementedError +class TypeApplication_Nullary(TypeApplication_Base[None, None]): + pass + class IntType3(KindArgument): """ Sometimes you can have an int on the type level, e.g. when using static arrays @@ -93,20 +122,18 @@ class IntType3(KindArgument): def __hash__(self) -> int: return hash(self.value) -T = TypeVar('T') - -class TypeConstructor(Generic[T]): +class TypeConstructor_Base[T]: """ Base class for type construtors """ - __slots__ = ('name', 'on_create', '_cache', '_reverse_cache') + __slots__ = ('name', 'on_create', '_cache', ) name: str """ The name of the type constructor """ - on_create: Callable[[Type3], None] + on_create: Callable[[T, Type3], None] """ Who to let know if a type is created """ @@ -117,31 +144,26 @@ class TypeConstructor(Generic[T]): it should produce the exact same result. """ - _reverse_cache: dict[Type3, T] - """ - Sometimes we need to know the key that created a type. - """ - - def __init__(self, name: str, on_create: Callable[[Type3], None]) -> None: + def __init__(self, name: str, on_create: Callable[[T, Type3], None]) -> None: self.name = name self.on_create = on_create self._cache = {} - self._reverse_cache = {} def make_name(self, key: T) -> str: """ Renders the type's name based on the given arguments """ - raise NotImplementedError + raise NotImplementedError('make_name', self) - def did_construct(self, typ: Type3) -> T | None: + def make_application(self, key: T) -> TypeApplication_Base[Self, T]: """ - Was the given type constructed by this constructor? + Records how the type was constructed into type. - If so, which arguments where used? + The type checker and compiler will need to know what + arguments where made to construct the type. """ - return self._reverse_cache.get(typ) + raise NotImplementedError('make_application', self) def construct(self, key: T) -> Type3: """ @@ -150,22 +172,12 @@ class TypeConstructor(Generic[T]): """ result = self._cache.get(key, None) if result is None: - self._cache[key] = result = Type3(self.make_name(key)) - self._reverse_cache[result] = key - self.on_create(result) + self._cache[key] = result = Type3(self.make_name(key), self.make_application(key)) + self.on_create(key, result) return result -class TypeConstructor_Type(TypeConstructor[Type3]): - """ - Base class type constructors of kind: * -> * - """ - __slots__ = () - - def __call__(self, arg: Type3) -> Type3: - raise NotImplementedError - -class TypeConstructor_TypeInt(TypeConstructor[Tuple[Type3, IntType3]]): +class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]): """ Base class type constructors of kind: * -> Int -> * @@ -173,22 +185,34 @@ class TypeConstructor_TypeInt(TypeConstructor[Tuple[Type3, IntType3]]): """ __slots__ = () + def make_application(self, key: Tuple[Type3, IntType3]) -> 'TypeApplication_TypeInt': + return TypeApplication_TypeInt(self, key) + def make_name(self, key: Tuple[Type3, IntType3]) -> str: return f'{self.name} {key[0].name} {key[1].value}' def __call__(self, arg0: Type3, arg1: IntType3) -> Type3: return self.construct((arg0, arg1)) -class TypeConstructor_TypeStar(TypeConstructor[Tuple[Type3, ...]]): +class TypeApplication_TypeInt(TypeApplication_Base[TypeConstructor_TypeInt, Tuple[Type3, IntType3]]): + pass + +class TypeConstructor_TypeStar(TypeConstructor_Base[Tuple[Type3, ...]]): """ Base class type constructors of variadic kind Notably, tuple. """ + def make_application(self, key: Tuple[Type3, ...]) -> 'TypeApplication_TypeStar': + return TypeApplication_TypeStar(self, key) + def __call__(self, *args: Type3) -> Type3: key: Tuple[Type3, ...] = tuple(args) return self.construct(key) +class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]): + pass + class TypeConstructor_StaticArray(TypeConstructor_TypeInt): def make_name(self, key: Tuple[Type3, IntType3]) -> str: return f'{key[0].name}[{key[1].value}]' @@ -197,52 +221,75 @@ class TypeConstructor_Tuple(TypeConstructor_TypeStar): def make_name(self, key: Tuple[Type3, ...]) -> str: return '(' + ', '.join(x.name for x in key) + ', )' -class TypeConstructor_Struct: +class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]): """ - Base class for type construtors + Constructs struct types """ - __slots__ = ('name', 'on_create', '_cache', '_reverse_cache') + def make_application(self, key: tuple[tuple[str, Type3], ...]) -> 'TypeApplication_Struct': + return TypeApplication_Struct(self, key) - name: str - """ - The name of the type constructor - """ + def make_name(self, key: tuple[tuple[str, Type3], ...]) -> str: + return f'{self.name}(' + ', '.join( + f'{n}: {t.name}' + for n, t in key + ) + ')' - on_create: Callable[[Type3], None] - """ - Who to let know if a type is created - """ - - _cache: dict[str, Type3] - """ - When constructing a type with the same arguments, - it should produce the exact same result. - """ - - _reverse_cache: dict[Type3, dict[str, Type3]] - """ - After construction you may need to look up the arguments - used for making the type - """ - - def __init__(self, name: str, on_create: Callable[[Type3], None]) -> None: - self.name = name - self.on_create = on_create - - self._cache = {} - self._reverse_cache = {} - - def did_construct(self, typ: Type3) -> dict[str, Type3] | None: + def construct(self, key: T) -> Type3: """ - Was the given type constructed by this constructor? - - If so, which arguments where used? + Constructs the type by applying the given arguments to this + constructor. """ - return self._reverse_cache.get(typ) - - def __call__(self, name: str, args: dict[str, Type3]) -> Type3: - result = Type3(name) - self._reverse_cache[result] = args - self.on_create(result) + raise Exception('This does not work with the caching system') + def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3: + result = Type3(name, self.make_application(args)) + self.on_create(args, result) return result + +class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]): + pass + +class NoRouteForTypeException(Exception): + pass + +class TypeApplicationRouter[S, R]: + """ + Helper class to find a method based on a constructed type + """ + __slots__ = ('by_constructor', 'by_type', ) + + by_constructor: dict[Any, Callable[[S, Any], R]] + """ + Contains all the added routing functions for constructed types + """ + + by_type: dict[Type3, Callable[[S], R]] + """ + Contains all the added routing functions for constructed types + """ + + def __init__(self) -> None: + self.by_constructor = {} + self.by_type = {} + + def add_n(self, typ: Type3, helper: Callable[[S], R]) -> None: + """ + Lets you route to types that were not constructed + + Also known types of kind * + """ + self.by_type[typ] = helper + + def add(self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None: + self.by_constructor[constructor] = helper + + def __call__(self, arg0: S, typ: Type3) -> R: + t_helper = self.by_type.get(typ) + if t_helper is not None: + return t_helper(arg0) + + c_helper = self.by_constructor.get(typ.application.constructor) + if c_helper is not None: + return c_helper(arg0, typ.application.arguments) + + raise NoRouteForTypeException(arg0, typ) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 3f6b8f0..c71f6ae 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -4,7 +4,12 @@ from typing import Any, Generator, Iterable, List, TextIO, Union from phasm import compiler, prelude from phasm.codestyle import phasm_render -from phasm.runtime import calculate_alloc_size +from phasm.runtime import ( + calculate_alloc_size, + calculate_alloc_size_static_array, + calculate_alloc_size_struct, + calculate_alloc_size_tuple, +) from phasm.type3 import types as type3types from . import runners @@ -79,30 +84,11 @@ class Suite: wasm_args.append(arg) continue - if arg_typ is prelude.bytes_: - adr = _allocate_memory_stored_value(runner, arg_typ, arg) + try: + adr = ALLOCATE_MEMORY_STORED_ROUTER((runner, arg), arg_typ) wasm_args.append(adr) - continue - - sa_args = prelude.static_array.did_construct(arg_typ) - if sa_args is not None: - adr = _allocate_memory_stored_value(runner, arg_typ, arg) - wasm_args.append(adr) - continue - - tp_args = prelude.tuple_.did_construct(arg_typ) - if tp_args is not None: - adr = _allocate_memory_stored_value(runner, arg_typ, arg) - wasm_args.append(adr) - continue - - st_args = prelude.struct.did_construct(arg_typ) - if st_args is not None: - adr = _allocate_memory_stored_value(runner, arg_typ, arg) - wasm_args.append(adr) - continue - - raise NotImplementedError(arg_typ, arg) + except type3types.NoRouteForTypeException: + raise NotImplementedError(arg_typ, arg) write_header(sys.stderr, 'Memory (pre run)') runner.interpreter_dump_memory(sys.stderr) @@ -141,103 +127,89 @@ def _write_memory_stored_value( val_typ: type3types.Type3, val: Any, ) -> int: - if val_typ is prelude.bytes_: - adr2 = _allocate_memory_stored_value(runner, val_typ, val) + try: + adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) return 4 + except type3types.NoRouteForTypeException: + to_write = WRITE_LOOKUP_MAP[val_typ.name](val) + runner.interpreter_write_memory(adr, to_write) + return len(to_write) - st_args = prelude.struct.did_construct(val_typ) - if st_args is not None: - adr2 = _allocate_memory_stored_value(runner, val_typ, val) - runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) - return 4 +def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> int: + runner, val = attrs - sa_args = prelude.static_array.did_construct(val_typ) - if sa_args is not None: - adr2 = _allocate_memory_stored_value(runner, val_typ, val) - runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) - return 4 + assert isinstance(val, bytes) - tp_args = prelude.tuple_.did_construct(val_typ) - if tp_args is not None: - adr2 = _allocate_memory_stored_value(runner, val_typ, val) - runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) - return 4 + adr = runner.call('stdlib.types.__alloc_bytes__', len(val)) + assert isinstance(adr, int) - to_write = WRITE_LOOKUP_MAP[val_typ.name](val) - runner.interpreter_write_memory(adr, to_write) - return len(to_write) + sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') + runner.interpreter_write_memory(adr + 4, val) + return adr -def _allocate_memory_stored_value( - runner: runners.RunnerBase, - val_typ: type3types.Type3, - val: Any - ) -> int: - if val_typ is prelude.bytes_: - assert isinstance(val, bytes) +def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int: + runner, val = attrs - adr = runner.call('stdlib.types.__alloc_bytes__', len(val)) - assert isinstance(adr, int) + assert isinstance(val, tuple) - sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') - runner.interpreter_write_memory(adr + 4, val) - return adr + sa_type, sa_len = sa_args - sa_args = prelude.static_array.did_construct(val_typ) - if sa_args is not None: - assert isinstance(val, tuple) + alloc_size = calculate_alloc_size_static_array(False, sa_args) + adr = runner.call('stdlib.alloc.__alloc__', alloc_size) + assert isinstance(adr, int) + sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') - sa_type, sa_len = sa_args + tuple_len = sa_len.value + assert tuple_len == len(val) - alloc_size = calculate_alloc_size(val_typ) - adr = runner.call('stdlib.alloc.__alloc__', alloc_size) - assert isinstance(adr, int) - sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') + offset = adr + for val_el_val in val: + offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val) + return adr - tuple_len = sa_len.value - assert tuple_len == len(val) +def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, type3types.Type3], ...]) -> int: + runner, val = attrs - offset = adr - for val_el_val in val: - offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val) - return adr + assert isinstance(val, dict) - val_el_typ: type3types.Type3 + alloc_size = calculate_alloc_size_struct(False, 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') - tp_args = prelude.tuple_.did_construct(val_typ) - if tp_args is not None: - assert isinstance(val, tuple) + offset = adr + for val_el_name, val_el_typ in st_args: + assert val_el_name in val, f'Missing key value {val_el_name}' + val_el_val = val.pop(val_el_name) + offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) - alloc_size = calculate_alloc_size(val_typ) - adr = runner.call('stdlib.alloc.__alloc__', alloc_size) - assert isinstance(adr, int) - sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') + assert not val, f'Additional values: {list(val)!r}' - assert len(val) == len(tp_args) + return adr - offset = adr - for val_el_val, val_el_typ in zip(val, tp_args, strict=True): - offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) - return adr +def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args: tuple[type3types.Type3, ...]) -> int: + runner, val = attrs - st_args = prelude.struct.did_construct(val_typ) - if st_args is not None: - assert isinstance(val, dict) + assert isinstance(val, tuple) - alloc_size = calculate_alloc_size(val_typ) - adr = runner.call('stdlib.alloc.__alloc__', alloc_size) - assert isinstance(adr, int) - sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') + alloc_size = calculate_alloc_size_tuple(False, 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') - assert list(val.keys()) == list(st_args) + assert len(val) == len(tp_args) - offset = adr - for val_el_name, val_el_typ in st_args.items(): - val_el_val = val[val_el_name] - offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) - return adr + offset = adr + for val_el_val, val_el_typ in zip(val, tp_args, strict=True): + offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) + return adr - raise NotImplementedError(val_typ, val) +ALLOCATE_MEMORY_STORED_ROUTER = type3types.TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]() +ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes) +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) def _load_memory_stored_returned_value( runner: runners.RunnerBase, @@ -285,28 +257,9 @@ def _load_memory_stored_returned_value( assert isinstance(wasm_value, float), wasm_value return wasm_value - if ret_type3 is prelude.bytes_: - assert isinstance(wasm_value, int), wasm_value + assert isinstance(wasm_value, int), wasm_value - return _load_bytes_from_address(runner, ret_type3, wasm_value) - - sa_args = prelude.static_array.did_construct(ret_type3) - if sa_args is not None: - assert isinstance(wasm_value, int), wasm_value - - return _load_static_array_from_address(runner, sa_args[0], sa_args[1], wasm_value) - - tp_args = prelude.tuple_.did_construct(ret_type3) - if tp_args is not None: - assert isinstance(wasm_value, int), wasm_value - - return _load_tuple_from_address(runner, tp_args, wasm_value) - - st_args = prelude.struct.did_construct(ret_type3) - if st_args is not None: - return _load_struct_from_address(runner, st_args, wasm_value) - - raise NotImplementedError(ret_type3, wasm_value) + return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3) def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any: if typ is prelude.u8: @@ -343,39 +296,19 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An assert len(inp) == 8 return struct.unpack(' bytes: - sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n') +def _load_bytes_from_address(attrs: tuple[runners.RunnerBase, int]) -> bytes: + runner, adr = attrs + + sys.stderr.write(f'Reading 0x{adr:08x} bytes\n') read_bytes = runner.interpreter_read_memory(adr, 4) bytes_len, = struct.unpack(' Generator yield all_bytes[offset:offset + size] offset += size -def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.Type3, len_typ: type3types.IntType3, adr: int) -> Any: +def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> Any: + runner, adr = attrs + sub_typ, len_typ = sa_args + sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n') sa_len = len_typ.value @@ -403,37 +339,42 @@ def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3ty for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) ) -def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.Type3, ...], adr: int) -> Any: - sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(typ_args)}\n') +def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tuple[tuple[str, type3types.Type3], ...]) -> dict[str, Any]: + runner, adr = attrs - arg_sizes = [ - calculate_alloc_size(x, is_member=True) - for x in typ_args - ] - - read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes)) - - return tuple( - _unpack(runner, arg_typ, arg_bytes) - for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes), strict=True) - ) - -def _load_struct_from_address(runner: runners.RunnerBase, st_args: dict[str, type3types.Type3], adr: int) -> Any: sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n') - name_list = list(st_args) - - typ_list = list(st_args.values()) - assert len(typ_list) == len(st_args) - arg_sizes = [ calculate_alloc_size(x, is_member=True) - for x in typ_list + for _, x in st_args ] read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes)) return { arg_name: _unpack(runner, arg_typ, arg_bytes) - for arg_name, arg_typ, arg_bytes in zip(name_list, typ_list, _split_read_bytes(read_bytes, arg_sizes), strict=True) + for (arg_name, arg_typ, ), arg_bytes in zip(st_args, _split_read_bytes(read_bytes, arg_sizes), strict=True) } + +def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tuple[type3types.Type3, ...]) -> Any: + runner, adr = attrs + + sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n') + + arg_sizes = [ + calculate_alloc_size(x, is_member=True) + for x in tp_args + ] + + read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes)) + + return tuple( + _unpack(runner, arg_typ, arg_bytes) + for arg_typ, arg_bytes in zip(tp_args, _split_read_bytes(read_bytes, arg_sizes), strict=True) + ) + +LOAD_FROM_ADDRESS_ROUTER = type3types.TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]() +LOAD_FROM_ADDRESS_ROUTER.add_n(prelude.bytes_, _load_bytes_from_address) +LOAD_FROM_ADDRESS_ROUTER.add(prelude.static_array, _load_static_array_from_address) +LOAD_FROM_ADDRESS_ROUTER.add(prelude.struct, _load_struct_from_address) +LOAD_FROM_ADDRESS_ROUTER.add(prelude.tuple_, _load_tuple_from_address) diff --git a/tests/integration/test_lang/test_bytes.py b/tests/integration/test_lang/test_bytes.py new file mode 100644 index 0000000..9511ff9 --- /dev/null +++ b/tests/integration/test_lang/test_bytes.py @@ -0,0 +1,30 @@ +import pytest + +from ..helpers import Suite + + +@pytest.mark.integration_test +def test_bytes_export_constant(): + code_py = """ +CONSTANT: bytes = b'Hello' + +@exported +def testEntry() -> bytes: + return CONSTANT +""" + + result = Suite(code_py).run_code() + + assert b"Hello" == result.returned_value + +@pytest.mark.integration_test +def test_bytes_export_instantiation(): + code_py = """ +@exported +def testEntry() -> bytes: + return b'Hello' +""" + + result = Suite(code_py).run_code() + + assert b"Hello" == result.returned_value diff --git a/tests/integration/test_lang/test_static_array.py b/tests/integration/test_lang/test_static_array.py index add37c0..373cbec 100644 --- a/tests/integration/test_lang/test_static_array.py +++ b/tests/integration/test_lang/test_static_array.py @@ -30,3 +30,29 @@ def testEntry() -> i32: with pytest.raises(Type3Exception, match='Member count mismatch'): Suite(code_py).run_code() + +@pytest.mark.integration_test +def test_static_array_export_constant(): + code_py = """ +CONSTANT: u8[3] = (1, 2, 3, ) + +@exported +def testEntry() -> u8[3]: + return CONSTANT +""" + + result = Suite(code_py).run_code() + + assert (1, 2, 3) == result.returned_value + +@pytest.mark.integration_test +def test_static_array_export_instantiation(): + code_py = """ +@exported +def testEntry() -> u8[3]: + return (1, 2, 3, ) +""" + + result = Suite(code_py).run_code() + + assert (1, 2, 3) == result.returned_value diff --git a/tests/integration/test_lang/test_struct.py b/tests/integration/test_lang/test_struct.py index bf9f2b2..6b211e9 100644 --- a/tests/integration/test_lang/test_struct.py +++ b/tests/integration/test_lang/test_struct.py @@ -112,3 +112,35 @@ def testEntry(x: u8) -> u8: with pytest.raises(Type3Exception, match='u8 is not struct'): Suite(code_py).run_code() + +@pytest.mark.integration_test +def test_struct_export_constant(): + code_py = """ +class CheckedValue: + value: i32 + +CONSTANT: CheckedValue = CheckedValue(32) + +@exported +def testEntry() -> CheckedValue: + return CONSTANT +""" + + result = Suite(code_py).run_code() + + assert {"value": 32} == result.returned_value + +@pytest.mark.integration_test +def test_struct_export_instantiation(): + code_py = """ +class CheckedValue: + value: i32 + +@exported +def testEntry() -> CheckedValue: + return CheckedValue(32) +""" + + result = Suite(code_py).run_code() + + assert {"value": 32} == result.returned_value diff --git a/tests/integration/test_lang/test_subscriptable.py b/tests/integration/test_lang/test_subscriptable.py index 61d00e5..1009227 100644 --- a/tests/integration/test_lang/test_subscriptable.py +++ b/tests/integration/test_lang/test_subscriptable.py @@ -23,6 +23,23 @@ def testEntry(f: {type_}) -> u8: assert exp_result == result.returned_value +@pytest.mark.integration_test +@pytest.mark.parametrize('type_, in_put, exp_result', [ + ('(u8, u8, u8, )', (45, 46, 47), 47, ), + ('u8[5]', (45, 46, 47, 48, 49), 47, ), + ('bytes', b'This is a test', 105) +]) +def test_subscript_2(type_, in_put, exp_result): + code_py = f""" +@exported +def testEntry(f: {type_}) -> u8: + return f[2] +""" + + result = Suite(code_py).run_code(in_put) + + assert exp_result == result.returned_value + @pytest.mark.integration_test @pytest.mark.parametrize('type_, in_put, exp_result', [ ('(u8, u8, )', (45, 46), 45, ), diff --git a/tests/integration/test_lang/test_tuple.py b/tests/integration/test_lang/test_tuple.py index 3b4aebc..a7244c7 100644 --- a/tests/integration/test_lang/test_tuple.py +++ b/tests/integration/test_lang/test_tuple.py @@ -70,3 +70,29 @@ CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, ) with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'): Suite(code_py).run_code() + +@pytest.mark.integration_test +def test_tuple_export_constant(): + code_py = """ +CONSTANT: (u32, u8, u8, ) = (4000, 20, 20, ) + +@exported +def testEntry() -> (u32, u8, u8, ): + return CONSTANT +""" + + result = Suite(code_py).run_code() + + assert (4000, 20, 20, ) == result.returned_value + +@pytest.mark.integration_test +def test_tuple_export_instantiation(): + code_py = """ +@exported +def testEntry() -> (u32, u8, u8, ): + return (4000, 20, 20, ) +""" + + result = Suite(code_py).run_code() + + assert (4000, 20, 20, ) == result.returned_value