diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 48b9606..12497b8 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -108,12 +108,16 @@ def expression(inp: ourlang.Expression) -> str: if isinstance(inp.function, ourlang.StructConstructor): return f'{inp.function.struct_type3.name}({args})' - # TODO: Broken after new type system - # if isinstance(inp.function, ourlang.TupleConstructor): - # return f'({args}, )' - return f'{inp.function.name}({args})' + if isinstance(inp, ourlang.TupleInstantiation): + args = ', '.join( + expression(arg) + for arg in inp.elements + ) + + return f'({args}, )' + if isinstance(inp, ourlang.Subscript): varref = expression(inp.varref) index = expression(inp.index) diff --git a/phasm/compiler.py b/phasm/compiler.py index 219b680..cf50853 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -80,6 +80,10 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType: # Static Arrays are passed as pointer, which are i32 return wasm.WasmTypeInt32() + if inp.base == type3types.tuple: + # Tuples are passed as pointer, which are i32 + return wasm.WasmTypeInt32() + raise NotImplementedError(type3, inp) # Operators that work for i32, i64, f32, f64 @@ -150,6 +154,45 @@ F64_OPERATOR_MAP = { '/': 'div' # Division by zero is a trap and the program will panic } +def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> None: + assert isinstance(inp.type3, type3types.AppliedType3) + assert inp.type3.base is type3types.tuple + assert len(inp.elements) == len(inp.type3.args) + + comment_elements = '' + for element in inp.elements: + assert isinstance(element.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + comment_elements += f'{element.type3.name}, ' + + tmp_var = wgn.temp_var_i32('tuple_adr') + wgn.add_statement('nop', comment=f'tmp_var := ({comment_elements})') + + # Allocated the required amounts of bytes in memory + wgn.i32.const(_calculate_alloc_size(inp.type3)) + wgn.call(stdlib_alloc.__alloc__) + wgn.local.set(tmp_var) + + # Store each element individually + offset = 0 + for element, exp_type3 in zip(inp.elements, inp.type3.args): + if isinstance(exp_type3, type3types.PlaceholderForType): + assert exp_type3.resolve_as is not None + exp_type3 = exp_type3.resolve_as + + assert element.type3 == exp_type3 + + assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name] + + wgn.local.get(tmp_var) + expression(wgn, element) + wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset)) + + offset += _calculate_alloc_size(exp_type3) + + # Return the allocated address + wgn.local.get(tmp_var) + def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: """ Compile: Any expression @@ -311,6 +354,10 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: wgn.add_statement('call', '${}'.format(inp.function.name)) return + if isinstance(inp, ourlang.TupleInstantiation): + tuple_instantiation(wgn, inp) + return + if isinstance(inp, ourlang.Subscript): assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR @@ -336,11 +383,8 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: wgn.i32.mul() wgn.i32.add() - mtyp = LOAD_STORE_TYPE_MAP.get(el_type.name) - if mtyp is None: - # In the future might extend this by having structs or tuples - # as members of struct or tuples - raise NotImplementedError(expression, inp, el_type) + assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + mtyp = LOAD_STORE_TYPE_MAP[el_type.name] wgn.add_statement(f'{mtyp}.load') return @@ -360,11 +404,8 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: expression(wgn, inp.varref) - mtyp = LOAD_STORE_TYPE_MAP.get(el_type.name) - if mtyp is None: - # In the future might extend this by having structs or tuples - # as members of struct or tuples - raise NotImplementedError(expression, inp, el_type) + assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + mtyp = LOAD_STORE_TYPE_MAP[el_type.name] wgn.add_statement(f'{mtyp}.load', f'offset={offset}') return @@ -372,11 +413,8 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: raise NotImplementedError(expression, inp, inp.varref.type3) if isinstance(inp, ourlang.AccessStructMember): - mtyp = LOAD_STORE_TYPE_MAP.get(inp.struct_type3.members[inp.member].name) - if mtyp is None: - # In the future might extend this by having structs or tuples - # as members of struct or tuples - raise NotImplementedError(expression, inp, inp.struct_type3) + assert isinstance(inp.struct_type3.members[inp.member], type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + mtyp = LOAD_STORE_TYPE_MAP[inp.struct_type3.members[inp.member].name] expression(wgn, inp.varref) wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset( @@ -538,9 +576,7 @@ def function(inp: ourlang.Function) -> wasm.Function: wgn = WasmGenerator() - if False: # TODO: isinstance(inp, ourlang.TupleConstructor): - pass # _generate_tuple_constructor(wgn, inp) - elif isinstance(inp, ourlang.StructConstructor): + if isinstance(inp, ourlang.StructConstructor): _generate_struct_constructor(wgn, inp) else: for stat in inp.statements: @@ -706,30 +742,6 @@ def module(inp: ourlang.Module) -> wasm.Module: return result -# TODO: Broken after new type system -# def _generate_tuple_constructor(wgn: WasmGenerator, inp: ourlang.TupleConstructor) -> None: -# tmp_var = wgn.temp_var_i32('tuple_adr') -# -# # Allocated the required amounts of bytes in memory -# wgn.i32.const(inp.tuple.alloc_size()) -# wgn.call(stdlib_alloc.__alloc__) -# wgn.local.set(tmp_var) -# -# # Store each member individually -# for member in inp.tuple.members: -# mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__) -# if mtyp is None: -# # In the future might extend this by having structs or tuples -# # as members of struct or tuples -# raise NotImplementedError(expression, inp, member) -# -# wgn.local.get(tmp_var) -# wgn.add_statement('local.get', f'$arg{member.idx}') -# wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset)) -# -# # Return the allocated address -# wgn.local.get(tmp_var) - def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: tmp_var = wgn.temp_var_i32('struct_adr') @@ -771,6 +783,17 @@ def _calculate_alloc_size(typ: Union[type3types.StructType3, type3types.Type3]) for x in typ.members.values() ) + if isinstance(typ, type3types.AppliedType3): + if typ.base is type3types.tuple: + size = 0 + for arg in typ.args: + if isinstance(arg, type3types.PlaceholderForType): + assert not arg.resolve_as is None + arg = arg.resolve_as + size += _calculate_alloc_size(arg) + + return size + raise NotImplementedError(_calculate_alloc_size, typ) def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int: diff --git a/phasm/ourlang.py b/phasm/ourlang.py index acfa4bb..af3d581 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -142,14 +142,14 @@ class TupleInstantiation(Expression): """ Instantiation a tuple """ - __slots__ = ('args', ) + __slots__ = ('elements', ) - args: List[Expression] + elements: List[Expression] - def __init__(self, args: List[Expression]) -> None: + def __init__(self, elements: List[Expression]) -> None: super().__init__() - self.args = args + self.elements = elements class Subscript(Expression): """ diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index 40bc2f1..2becba2 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -60,14 +60,10 @@ class ConstraintBase: def __init__(self, comment: Optional[str] = None) -> None: self.comment = comment - def check(self, smap: SubstitutionMap) -> CheckResult: + def check(self) -> CheckResult: """ Checks if the constraint hold - smap will contain a mapping from placeholders to types, for - placeholders discovered from either other constraints or from - earlier calls to check in this constraint. - This function can return an error, if the constraint does not hold, which indicates an error in the typing of the input program. @@ -103,7 +99,7 @@ class SameTypeConstraint(ConstraintBase): assert len(type_list) > 1 self.type_list = [*type_list] - def check(self, smap: SubstitutionMap) -> CheckResult: + def check(self) -> CheckResult: known_types: List[types.Type3] = [] placeholders = [] do_applied_placeholder_check: bool = False @@ -118,8 +114,8 @@ class SameTypeConstraint(ConstraintBase): continue if isinstance(typ, types.PlaceholderForType): - if typ in smap: - known_types.append(smap[typ]) + if typ.resolve_as is not None: + known_types.append(typ.resolve_as) else: placeholders.append(typ) continue @@ -159,6 +155,9 @@ class SameTypeConstraint(ConstraintBase): if not placeholders: return None + for typ in placeholders: + typ.resolve_as = first_type + return { typ: first_type for typ in placeholders @@ -193,14 +192,14 @@ class CastableConstraint(ConstraintBase): self.from_type3 = from_type3 self.to_type3 = to_type3 - def check(self, smap: SubstitutionMap) -> CheckResult: + def check(self) -> CheckResult: ftyp = self.from_type3 - if isinstance(ftyp, types.PlaceholderForType) and ftyp in smap: - ftyp = smap[ftyp] + if isinstance(ftyp, types.PlaceholderForType) and ftyp.resolve_as is not None: + ftyp = ftyp.resolve_as ttyp = self.to_type3 - if isinstance(ttyp, types.PlaceholderForType) and ttyp in smap: - ttyp = smap[ttyp] + if isinstance(ttyp, types.PlaceholderForType) and ttyp.resolve_as is not None: + ttyp = ttyp.resolve_as if isinstance(ftyp, types.PlaceholderForType) or isinstance(ttyp, types.PlaceholderForType): return RequireTypeSubstitutes() @@ -248,10 +247,10 @@ class MustImplementTypeClassConstraint(ConstraintBase): self.type_class3 = type_class3 self.type3 = type3 - def check(self, smap: SubstitutionMap) -> CheckResult: + def check(self) -> CheckResult: typ = self.type3 - if isinstance(typ, types.PlaceholderForType) and typ in smap: - typ = smap[typ] + if isinstance(typ, types.PlaceholderForType) and typ.resolve_as is not None: + typ = typ.resolve_as if isinstance(typ, types.PlaceholderForType): return RequireTypeSubstitutes() @@ -293,7 +292,7 @@ class LiteralFitsConstraint(ConstraintBase): self.type3 = type3 self.literal = literal - def check(self, smap: SubstitutionMap) -> CheckResult: + def check(self) -> CheckResult: int_table: Dict[str, Tuple[int, bool]] = { 'u8': (1, False), 'u32': (4, False), @@ -309,10 +308,10 @@ class LiteralFitsConstraint(ConstraintBase): } if isinstance(self.type3, types.PlaceholderForType): - if self.type3 not in smap: + if self.type3.resolve_as is None: return RequireTypeSubstitutes() - self.type3 = smap[self.type3] + self.type3 = self.type3.resolve_as if self.type3.name in int_table: bts, sgn = int_table[self.type3.name] @@ -440,12 +439,12 @@ class CanBeSubscriptedConstraint(ConstraintBase): self.index = index self.index_type3 = index.type3 - def check(self, smap: SubstitutionMap) -> CheckResult: + def check(self) -> CheckResult: if isinstance(self.type3, types.PlaceholderForType): - if self.type3 not in smap: + if self.type3.resolve_as is None: return RequireTypeSubstitutes() - self.type3 = smap[self.type3] + self.type3 = self.type3.resolve_as if isinstance(self.type3, types.AppliedType3): if self.type3.base == types.static_array: diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index 1c25ddc..ad620e9 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -104,7 +104,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas if isinstance(inp, ourlang.TupleInstantiation): r_type = [] - for arg in inp.args: + for arg in inp.elements: yield from expression(ctx, arg) r_type.append(arg.type3) diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index 4310955..987c94e 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -1,7 +1,7 @@ """ Entry point to the type3 system """ -from typing import Dict, List +from typing import Any, Dict, List, Set from .. import codestyle from .. import ourlang @@ -35,7 +35,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None: new_constraint_list = [] for constraint in constraint_list: - check_result = constraint.check(placeholder_substitutes) + check_result = constraint.check() if check_result is None: if verbose: print_constraint(placeholder_id_map, constraint) diff --git a/phasm/type3/types.py b/phasm/type3/types.py index b5ae5ea..a4f6597 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -4,7 +4,7 @@ Contains the final types for use in Phasm These are actual, instantiated types; not the abstract types that the constraint generator works with. """ -from typing import Any, Dict, Iterable, List, Protocol, Union +from typing import Any, Dict, Iterable, List, Optional, Protocol, Union TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method' @@ -73,12 +73,14 @@ class PlaceholderForType: """ A placeholder type, for when we don't know the final type yet """ - __slots__ = ('update_on_substitution', ) + __slots__ = ('update_on_substitution', 'resolve_as', ) update_on_substitution: List[ExpressionProtocol] + resolve_as: Optional[Type3] def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None: self.update_on_substitution = [*update_on_substitution] + self.resolve_as = None def __repr__(self) -> str: uos = ', '.join(repr(x) for x in self.update_on_substitution)