diff --git a/TODO.md b/TODO.md index 91963ee..e5cc852 100644 --- a/TODO.md +++ b/TODO.md @@ -12,6 +12,7 @@ - Also, check the codes for FIXME and TODO - Allocation is done using pointers for members, is this desired? - See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental + - Implement q32? q64? Two i32/i64 divided? - 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. diff --git a/phasm/compiler.py b/phasm/compiler.py index 43e4edb..d4534f5 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -2,7 +2,7 @@ This module contains the code to convert parsed Ourlang into WebAssembly code """ import struct -from typing import Dict, List, Optional +from typing import List, Optional from . import codestyle, ourlang, prelude, wasm from .runtime import calculate_alloc_size, calculate_member_offset @@ -292,15 +292,20 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: expression(wgn, inp.left) expression(wgn, inp.right) - type_var_map: Dict[type3functions.TypeVariable, type3types.Type3] = {} + type_var_map: dict[type3functions.TypeVariable, type3types.Type3] = {} for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True): - if not isinstance(type_var, type3functions.TypeVariable): + assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR + + if isinstance(type_var, type3types.Type3): # Fixed type, not part of the lookup requirements continue - assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR - type_var_map[type_var] = arg_expr.type3 + if isinstance(type_var, type3functions.TypeVariable): + type_var_map[type_var] = arg_expr.type3 + continue + + raise NotImplementedError(type_var, arg_expr.type3) router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator] router(wgn, type_var_map) @@ -315,12 +320,17 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: type_var_map = {} for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True): - if not isinstance(type_var, type3functions.TypeVariable): + assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR + + if isinstance(type_var, type3types.Type3): # Fixed type, not part of the lookup requirements continue - assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR - type_var_map[type_var] = arg_expr.type3 + if isinstance(type_var, type3functions.TypeVariable): + type_var_map[type_var] = arg_expr.type3 + continue + + raise NotImplementedError(type_var, arg_expr.type3) router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function] try: diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py index d91128e..e269590 100644 --- a/phasm/prelude/__init__.py +++ b/phasm/prelude/__init__.py @@ -1,27 +1,33 @@ """ The prelude are all the builtin types, type classes and methods """ -from typing import Callable +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 TypeVariable -from ..type3.routers import FunctionSignatureRouter +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 ( IntType3, Type3, TypeApplication_Nullary, + TypeConstructor_Base, TypeConstructor_StaticArray, TypeConstructor_Struct, TypeConstructor_Tuple, ) -PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3, ...]]] = set() +PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] = set() -PRELUDE_TYPE_CLASS_INSTANCE_METHODS: dict[Type3ClassMethod, FunctionSignatureRouter[Generator, None]] = {} +PRELUDE_TYPE_CLASS_INSTANCE_METHODS: dict[Type3ClassMethod, TypeClassArgsRouter[Generator, None]] = {} class MissingImplementationException(Exception): pass @@ -31,18 +37,26 @@ class MissingImplementationWarning(Warning): def instance_type_class( cls: Type3Class, - *typ: Type3, - methods: dict[str, Callable[[Generator], None]] = {}, - operators: dict[str, Callable[[Generator], None]] = {}, + *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) - type_var_map = {} + tv_map: dict[TypeVariable, Type3] = {} + tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {} for arg_tv, arg_tp in zip(cls.args, typ, strict=True): - type_var_map[arg_tv] = arg_tp + 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 @@ -51,7 +65,7 @@ def instance_type_class( for method_name, method in cls.methods.items(): router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(method) if router is None: - router = FunctionSignatureRouter[Generator, None](method.signature) + router = TypeClassArgsRouter[Generator, None](cls.args) PRELUDE_TYPE_CLASS_INSTANCE_METHODS[method] = router try: @@ -60,12 +74,12 @@ def instance_type_class( warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ))) continue - router.add(type_var_map, generator) + 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 = FunctionSignatureRouter[Generator, None](operator.signature) + router = TypeClassArgsRouter[Generator, None](cls.args) PRELUDE_TYPE_CLASS_INSTANCE_METHODS[operator] = router try: @@ -74,7 +88,7 @@ def instance_type_class( warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ))) continue - router.add(type_var_map, generator) + router.add(tv_map, tc_map, generator) none = Type3('none', TypeApplication_Nullary(None, None)) """ @@ -195,11 +209,12 @@ PRELUDE_TYPES: dict[str, Type3] = { 'bytes': bytes_, } -a = TypeVariable('a') -b = TypeVariable('b') +a = TypeVariable('a', TypeVariableApplication_Nullary(None, None)) +b = TypeVariable('b', TypeVariableApplication_Nullary(None, None)) +t = TypeConstructorVariable('t') -InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={}) +InternalPassAsPointer = Type3Class('InternalPassAsPointer', (a, ), methods={}, operators={}) """ Internal type class to keep track which types we pass arounds as a pointer. """ @@ -209,7 +224,7 @@ instance_type_class(InternalPassAsPointer, bytes_) # instance_type_class(InternalPassAsPointer, tuple_) # instance_type_class(InternalPassAsPointer, struct) -Eq = Type3Class('Eq', [a], methods={}, operators={ +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? @@ -248,7 +263,7 @@ instance_type_class(Eq, f64, operators={ '!=': stdtypes.f64_eq_not_equals, }) -Ord = Type3Class('Ord', [a], methods={ +Ord = Type3Class('Ord', (a, ), methods={ 'min': [a, a, a], 'max': [a, a, a], }, operators={ @@ -331,7 +346,7 @@ instance_type_class(Ord, f64, methods={ '>=': stdtypes.f64_ord_greater_than_or_equal, }) -Bits = Type3Class('Bits', [a], methods={ +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 @@ -374,7 +389,7 @@ instance_type_class(Bits, u64, methods={ '^': stdtypes.u64_bits_bitwise_xor, }) -NatNum = Type3Class('NatNum', [a], methods={}, operators={ +NatNum = Type3Class('NatNum', (a, ), methods={}, operators={ '+': [a, a, a], '-': [a, a, a], '*': [a, a, a], @@ -425,7 +440,7 @@ instance_type_class(NatNum, f64, operators={ '>>': stdtypes.f64_natnum_arithmic_shift_right, }) -IntNum = Type3Class('IntNum', [a], methods={ +IntNum = Type3Class('IntNum', (a, ), methods={ 'abs': [a, a], 'neg': [a, a], }, operators={}, inherited_classes=[NatNum]) @@ -447,7 +462,7 @@ instance_type_class(IntNum, f64, methods={ 'neg': stdtypes.f64_intnum_neg, }) -Integral = Type3Class('Eq', [a], methods={ +Integral = Type3Class('Eq', (a, ), methods={ }, operators={ '//': [a, a, a], '%': [a, a, a], @@ -470,7 +485,7 @@ instance_type_class(Integral, i64, operators={ '%': stdtypes.i64_integral_rem, }) -Fractional = Type3Class('Fractional', [a], methods={ +Fractional = Type3Class('Fractional', (a, ), methods={ 'ceil': [a, a], 'floor': [a, a], 'trunc': [a, a], @@ -496,7 +511,7 @@ instance_type_class(Fractional, f64, methods={ '/': stdtypes.f64_fractional_div, }) -Floating = Type3Class('Floating', [a], methods={ +Floating = Type3Class('Floating', (a, ), methods={ 'sqrt': [a, a], }, operators={}, inherited_classes=[Fractional]) @@ -509,7 +524,7 @@ instance_type_class(Floating, f64, methods={ 'sqrt': stdtypes.f64_floating_sqrt, }) -Sized_ = Type3Class('Sized', [a], methods={ +Sized_ = Type3Class('Sized', (a, ), methods={ 'len': [a, u32], }, operators={}) # FIXME: Once we get type class families, add [] here @@ -517,7 +532,7 @@ instance_type_class(Sized_, bytes_, methods={ 'len': stdtypes.bytes_sized_len, }) -Extendable = Type3Class('Extendable', [a, b], methods={ +Extendable = Type3Class('Extendable', (a, b, ), methods={ 'extend': [a, b], 'wrap': [b, a], }, operators={}) @@ -547,7 +562,7 @@ instance_type_class(Extendable, i32, i64, methods={ 'wrap': stdtypes.i32_i64_wrap, }) -Promotable = Type3Class('Promotable', [a, b], methods={ +Promotable = Type3Class('Promotable', (a, b, ), methods={ 'promote': [a, b], 'demote': [b, a], }, operators={}) @@ -557,6 +572,16 @@ instance_type_class(Promotable, f32, f64, methods={ 'demote': stdtypes.f32_f64_demote, }) +Foldable = Type3Class('Foldable', (t, ), methods={ + 'sum': [t(a), a], +}, operators={}, additional_context={ + 'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))], +}) + +instance_type_class(Foldable, static_array, methods={ + 'sum': stdtypes.static_array_sum, +}) + PRELUDE_TYPE_CLASSES = { 'Eq': Eq, 'Ord': Ord, @@ -592,4 +617,5 @@ PRELUDE_METHODS = { **Sized_.methods, **Extendable.methods, **Promotable.methods, + **Foldable.methods, } diff --git a/phasm/stdlib/types.py b/phasm/stdlib/types.py index 5c7e46a..425dfa0 100644 --- a/phasm/stdlib/types.py +++ b/phasm/stdlib/types.py @@ -2,6 +2,8 @@ stdlib: Standard types that are not wasm primitives """ from phasm.stdlib import alloc +from phasm.type3.routers import TypeVariableLookup +from phasm.type3.types import IntType3, Type3 from phasm.wasmgenerator import Generator, func_wrapper from phasm.wasmgenerator import VarType_i32 as i32 from phasm.wasmgenerator import VarType_i64 as i64 @@ -387,444 +389,579 @@ def __u8_rotr__(g: Generator, x: i32, r: i32) -> i32: ## ### ## class Eq -def u8_eq_equals(g: Generator) -> None: +def u8_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.eq() -def u32_eq_equals(g: Generator) -> None: +def u32_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.eq() -def u64_eq_equals(g: Generator) -> None: +def u64_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.eq() -def i8_eq_equals(g: Generator) -> None: +def i8_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.eq() -def i32_eq_equals(g: Generator) -> None: +def i32_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.eq() -def i64_eq_equals(g: Generator) -> None: +def i64_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.eq() -def f32_eq_equals(g: Generator) -> None: +def f32_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.eq() -def f64_eq_equals(g: Generator) -> None: +def f64_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.eq() -def u8_eq_not_equals(g: Generator) -> None: +def u8_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.ne() -def u32_eq_not_equals(g: Generator) -> None: +def u32_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.ne() -def u64_eq_not_equals(g: Generator) -> None: +def u64_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.ne() -def i8_eq_not_equals(g: Generator) -> None: +def i8_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.ne() -def i32_eq_not_equals(g: Generator) -> None: +def i32_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.ne() -def i64_eq_not_equals(g: Generator) -> None: +def i64_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.ne() -def f32_eq_not_equals(g: Generator) -> None: +def f32_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.ne() -def f64_eq_not_equals(g: Generator) -> None: +def f64_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.ne() ## ### ## class Ord -def u8_ord_min(g: Generator) -> None: +def u8_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u32_ord_min__') -def u32_ord_min(g: Generator) -> None: +def u32_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u32_ord_min__') -def u64_ord_min(g: Generator) -> None: +def u64_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u64_ord_min__') -def i8_ord_min(g: Generator) -> None: +def i8_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__i32_ord_min__') -def i32_ord_min(g: Generator) -> None: +def i32_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__i32_ord_min__') -def i64_ord_min(g: Generator) -> None: +def i64_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__i64_ord_min__') -def f32_ord_min(g: Generator) -> None: +def f32_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.min() -def f64_ord_min(g: Generator) -> None: +def f64_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.min() -def u8_ord_max(g: Generator) -> None: +def u8_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u32_ord_max__') -def u32_ord_max(g: Generator) -> None: +def u32_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u32_ord_max__') -def u64_ord_max(g: Generator) -> None: +def u64_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u64_ord_max__') -def i8_ord_max(g: Generator) -> None: +def i8_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__i32_ord_max__') -def i32_ord_max(g: Generator) -> None: +def i32_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__i32_ord_max__') -def i64_ord_max(g: Generator) -> None: +def i64_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__i64_ord_max__') -def f32_ord_max(g: Generator) -> None: +def f32_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.max() -def f64_ord_max(g: Generator) -> None: +def f64_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.max() -def u8_ord_less_than(g: Generator) -> None: +def u8_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.lt_u() -def u32_ord_less_than(g: Generator) -> None: +def u32_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.lt_u() -def u64_ord_less_than(g: Generator) -> None: +def u64_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.lt_u() -def i8_ord_less_than(g: Generator) -> None: +def i8_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.lt_s() -def i32_ord_less_than(g: Generator) -> None: +def i32_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.lt_s() -def i64_ord_less_than(g: Generator) -> None: +def i64_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.lt_s() -def f32_ord_less_than(g: Generator) -> None: +def f32_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.lt() -def f64_ord_less_than(g: Generator) -> None: +def f64_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.lt() -def u8_ord_less_than_or_equal(g: Generator) -> None: +def u8_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.le_u() -def u32_ord_less_than_or_equal(g: Generator) -> None: +def u32_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.le_u() -def u64_ord_less_than_or_equal(g: Generator) -> None: +def u64_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.le_u() -def i8_ord_less_than_or_equal(g: Generator) -> None: +def i8_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.le_s() -def i32_ord_less_than_or_equal(g: Generator) -> None: +def i32_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.le_s() -def i64_ord_less_than_or_equal(g: Generator) -> None: +def i64_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.le_s() -def f32_ord_less_than_or_equal(g: Generator) -> None: +def f32_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.le() -def f64_ord_less_than_or_equal(g: Generator) -> None: +def f64_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.le() -def u8_ord_greater_than(g: Generator) -> None: +def u8_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.gt_u() -def u32_ord_greater_than(g: Generator) -> None: +def u32_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.gt_u() -def u64_ord_greater_than(g: Generator) -> None: +def u64_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.gt_u() -def i8_ord_greater_than(g: Generator) -> None: +def i8_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.gt_s() -def i32_ord_greater_than(g: Generator) -> None: +def i32_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.gt_s() -def i64_ord_greater_than(g: Generator) -> None: +def i64_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.gt_s() -def f32_ord_greater_than(g: Generator) -> None: +def f32_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.gt() -def f64_ord_greater_than(g: Generator) -> None: +def f64_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.gt() -def u8_ord_greater_than_or_equal(g: Generator) -> None: +def u8_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.ge_u() -def u32_ord_greater_than_or_equal(g: Generator) -> None: +def u32_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.ge_u() -def u64_ord_greater_than_or_equal(g: Generator) -> None: +def u64_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.ge_u() -def i8_ord_greater_than_or_equal(g: Generator) -> None: +def i8_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.ge_s() -def i32_ord_greater_than_or_equal(g: Generator) -> None: +def i32_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.ge_s() -def i64_ord_greater_than_or_equal(g: Generator) -> None: +def i64_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.ge_s() -def f32_ord_greater_than_or_equal(g: Generator) -> None: +def f32_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.ge() -def f64_ord_greater_than_or_equal(g: Generator) -> None: +def f64_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.ge() ## ### ## class Bits -def u8_bits_logical_shift_left(g: Generator) -> None: +def u8_bits_logical_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.shl() g.i32.const(255) g.i32.and_() -def u32_bits_logical_shift_left(g: Generator) -> None: +def u32_bits_logical_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.shl() -def u64_bits_logical_shift_left(g: Generator) -> None: +def u64_bits_logical_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() g.i64.shl() -def u8_bits_logical_shift_right(g: Generator) -> None: +def u8_bits_logical_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.shr_u() -def u32_bits_logical_shift_right(g: Generator) -> None: +def u32_bits_logical_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.shr_u() -def u64_bits_logical_shift_right(g: Generator) -> None: +def u64_bits_logical_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() g.i64.shr_u() -def u8_bits_rotate_left(g: Generator) -> None: +def u8_bits_rotate_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u8_rotl__') -def u32_bits_rotate_left(g: Generator) -> None: +def u32_bits_rotate_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.rotl() -def u64_bits_rotate_left(g: Generator) -> None: +def u64_bits_rotate_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() g.i64.rotl() -def u8_bits_rotate_right(g: Generator) -> None: +def u8_bits_rotate_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u8_rotr__') -def u32_bits_rotate_right(g: Generator) -> None: +def u32_bits_rotate_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.rotr() -def u64_bits_rotate_right(g: Generator) -> None: +def u64_bits_rotate_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() g.i64.rotr() -def u8_bits_bitwise_and(g: Generator) -> None: +def u8_bits_bitwise_and(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.and_() -def u32_bits_bitwise_and(g: Generator) -> None: +def u32_bits_bitwise_and(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.and_() -def u64_bits_bitwise_and(g: Generator) -> None: +def u64_bits_bitwise_and(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.and_() -def u8_bits_bitwise_or(g: Generator) -> None: +def u8_bits_bitwise_or(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.or_() -def u32_bits_bitwise_or(g: Generator) -> None: +def u32_bits_bitwise_or(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.or_() -def u64_bits_bitwise_or(g: Generator) -> None: +def u64_bits_bitwise_or(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.or_() -def u8_bits_bitwise_xor(g: Generator) -> None: +def u8_bits_bitwise_xor(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.xor() -def u32_bits_bitwise_xor(g: Generator) -> None: +def u32_bits_bitwise_xor(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.xor() -def u64_bits_bitwise_xor(g: Generator) -> None: +def u64_bits_bitwise_xor(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.xor() ## ### ## class Fractional -def f32_fractional_ceil(g: Generator) -> None: +def f32_fractional_ceil(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.ceil() -def f64_fractional_ceil(g: Generator) -> None: +def f64_fractional_ceil(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.ceil() -def f32_fractional_floor(g: Generator) -> None: +def f32_fractional_floor(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.floor() -def f64_fractional_floor(g: Generator) -> None: +def f64_fractional_floor(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.floor() -def f32_fractional_trunc(g: Generator) -> None: +def f32_fractional_trunc(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.trunc() -def f64_fractional_trunc(g: Generator) -> None: +def f64_fractional_trunc(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.trunc() -def f32_fractional_nearest(g: Generator) -> None: +def f32_fractional_nearest(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.nearest() -def f64_fractional_nearest(g: Generator) -> None: +def f64_fractional_nearest(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.nearest() -def f32_fractional_div(g: Generator) -> None: +def f32_fractional_div(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.div() -def f64_fractional_div(g: Generator) -> None: +def f64_fractional_div(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.div() ## ### ## class Floating -def f32_floating_sqrt(g: Generator) -> None: +def f32_floating_sqrt(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('f32.sqrt') -def f64_floating_sqrt(g: Generator) -> None: +def f64_floating_sqrt(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('f64.sqrt') ## ### ## class Integral -def u32_integral_div(g: Generator) -> None: +def u32_integral_div(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.div_u') -def u64_integral_div(g: Generator) -> None: +def u64_integral_div(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.div_u') -def i32_integral_div(g: Generator) -> None: +def i32_integral_div(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.div_s') -def i64_integral_div(g: Generator) -> None: +def i64_integral_div(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.div_s') -def u32_integral_rem(g: Generator) -> None: +def u32_integral_rem(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.rem_u') -def u64_integral_rem(g: Generator) -> None: +def u64_integral_rem(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.rem_u') -def i32_integral_rem(g: Generator) -> None: +def i32_integral_rem(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.rem_s') -def i64_integral_rem(g: Generator) -> None: +def i64_integral_rem(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.rem_s') ## ### ## class NatNum -def u32_natnum_add(g: Generator) -> None: +def u32_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.add') -def u64_natnum_add(g: Generator) -> None: +def u64_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.add') -def i32_natnum_add(g: Generator) -> None: +def i32_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.add') -def i64_natnum_add(g: Generator) -> None: +def i64_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.add') -def f32_natnum_add(g: Generator) -> None: +def f32_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('f32.add') -def f64_natnum_add(g: Generator) -> None: +def f64_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('f64.add') -def u32_natnum_sub(g: Generator) -> None: +def u32_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.sub') -def u64_natnum_sub(g: Generator) -> None: +def u64_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.sub') -def i32_natnum_sub(g: Generator) -> None: +def i32_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.sub') -def i64_natnum_sub(g: Generator) -> None: +def i64_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.sub') -def f32_natnum_sub(g: Generator) -> None: +def f32_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('f32.sub') -def f64_natnum_sub(g: Generator) -> None: +def f64_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('f64.sub') -def u32_natnum_mul(g: Generator) -> None: +def u32_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.mul') -def u64_natnum_mul(g: Generator) -> None: +def u64_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.mul') -def i32_natnum_mul(g: Generator) -> None: +def i32_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i32.mul') -def i64_natnum_mul(g: Generator) -> None: +def i64_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('i64.mul') -def f32_natnum_mul(g: Generator) -> None: +def f32_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('f32.mul') -def f64_natnum_mul(g: Generator) -> None: +def f64_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('f64.mul') -def u32_natnum_arithmic_shift_left(g: Generator) -> None: +def u32_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.shl() -def u64_natnum_arithmic_shift_left(g: Generator) -> None: +def u64_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() g.i64.shl() -def i32_natnum_arithmic_shift_left(g: Generator) -> None: +def i32_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.shl() -def i64_natnum_arithmic_shift_left(g: Generator) -> None: +def i64_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() g.i64.shl() -def f32_natnum_arithmic_shift_left(g: Generator) -> None: +def f32_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u32_pow2__') g.f32.convert_i32_u() g.f32.mul() -def f64_natnum_arithmic_shift_left(g: Generator) -> None: +def f64_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u32_pow2__') g.f64.convert_i32_u() g.f64.mul() -def u32_natnum_arithmic_shift_right(g: Generator) -> None: +def u32_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.shr_u() -def u64_natnum_arithmic_shift_right(g: Generator) -> None: +def u64_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() g.i64.shr_u() -def i32_natnum_arithmic_shift_right(g: Generator) -> None: +def i32_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.shr_s() -def i64_natnum_arithmic_shift_right(g: Generator) -> None: +def i64_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() g.i64.shr_s() -def f32_natnum_arithmic_shift_right(g: Generator) -> None: +def f32_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u32_pow2__') g.f32.convert_i32_u() g.f32.div() -def f64_natnum_arithmic_shift_right(g: Generator) -> None: +def f64_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__u32_pow2__') g.f64.convert_i32_u() g.f64.div() @@ -832,91 +969,219 @@ def f64_natnum_arithmic_shift_right(g: Generator) -> None: ## ### ## class IntNum -def i32_intnum_abs(g: Generator) -> None: +def i32_intnum_abs(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__i32_intnum_abs__') -def i64_intnum_abs(g: Generator) -> None: +def i64_intnum_abs(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.add_statement('call $stdlib.types.__i64_intnum_abs__') -def f32_intnum_abs(g: Generator) -> None: +def f32_intnum_abs(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.abs() -def f64_intnum_abs(g: Generator) -> None: +def f64_intnum_abs(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.abs() -def i32_intnum_neg(g: Generator) -> None: +def i32_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.const(-1) g.i32.mul() -def i64_intnum_neg(g: Generator) -> None: +def i64_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.const(-1) g.i64.mul() -def f32_intnum_neg(g: Generator) -> None: +def f32_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.neg() -def f64_intnum_neg(g: Generator) -> None: +def f64_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.neg() ## ### ## Class Sized -def bytes_sized_len(g: Generator) -> None: +def bytes_sized_len(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map # The length is stored in the first 4 bytes g.i32.load() ## ### ## Extendable -def u8_u32_extend(g: Generator) -> None: +def u8_u32_extend(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map # No-op # u8 is already stored as u32 pass -def u8_u64_extend(g: Generator) -> None: +def u8_u64_extend(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() -def u32_u64_extend(g: Generator) -> None: +def u32_u64_extend(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_u() -def i8_i32_extend(g: Generator) -> None: +def i8_i32_extend(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map # No-op # i8 is already stored as i32 pass -def i8_i64_extend(g: Generator) -> None: +def i8_i64_extend(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_s() -def i32_i64_extend(g: Generator) -> None: +def i32_i64_extend(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i64.extend_i32_s() -def u8_u32_wrap(g: Generator) -> None: +def u8_u32_wrap(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.const(0xFF) g.i32.and_() -def u8_u64_wrap(g: Generator) -> None: +def u8_u64_wrap(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.wrap_i64() g.i32.const(0xFF) g.i32.and_() -def u32_u64_wrap(g: Generator) -> None: +def u32_u64_wrap(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.wrap_i64() -def i8_i32_wrap(g: Generator) -> None: +def i8_i32_wrap(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.const(0xFF) g.i32.and_() -def i8_i64_wrap(g: Generator) -> None: +def i8_i64_wrap(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.wrap_i64() -def i32_i64_wrap(g: Generator) -> None: +def i32_i64_wrap(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.i32.wrap_i64() ## ### ## Promotable -def f32_f64_promote(g: Generator) -> None: +def f32_f64_promote(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f64.promote_f32() -def f32_f64_demote(g: Generator) -> None: +def f32_f64_demote(g: Generator, tv_map: TypeVariableLookup) -> None: + del tv_map g.f32.demote_f64() + +def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None: + assert len(tv_map) == 1 + sa_type, sa_len = next(iter(tv_map.values())) + assert isinstance(sa_type, Type3) + assert isinstance(sa_len, IntType3) + + if sa_len.value < 1: + raise NotImplementedError('Default value in case sum is empty') + + # FIXME: We should probably use LOAD_STORE_TYPE_MAP for this? + mtyp_map = { + 'u32': 'i32', + 'u64': 'i64', + 'i32': 'i32', + 'i64': 'i64', + 'f32': 'f32', + 'f64': 'f64', + } + + # FIXME: We should probably use calc_alloc_size for this? + type_var_size_map = { + 'u32': 4, + 'u64': 8, + 'i32': 4, + 'i64': 8, + 'f32': 4, + 'f64': 8, + } + + type_var_add_generator = { + 'u32': u32_natnum_add, + 'u64': u64_natnum_add, + 'i32': i32_natnum_add, + 'i64': i64_natnum_add, + 'f32': f32_natnum_add, + 'f64': f64_natnum_add, + } + + # By default, constructed types are passed as pointers + # FIXME: We don't know what add function to call + sa_type_mtyp = mtyp_map.get(sa_type.name, 'i32') + sa_type_alloc_size = type_var_size_map.get(sa_type.name, 4) + sa_type_add_gen = type_var_add_generator[sa_type.name] + + # Definitions + sum_adr = g.temp_var(i32('sum_adr')) + sum_stop = g.temp_var(i32('sum_stop')) + + # Stack before: [adr] + # Stack after: [sum] + + # adr = {address of what's currently on stack} + # Stack: [adr] -> [] + g.nop(comment=f'Start sum for {sa_type.name}[{sa_len.value}]') + g.local.set(sum_adr) + + # stop = adr + ar_len * sa_type_alloc_size + # Stack: [] + g.nop(comment='Calculate address at which to stop looping') + g.local.get(sum_adr) + g.i32.const(sa_len.value * sa_type_alloc_size) + g.i32.add() + g.local.set(sum_stop) + + # sum = *adr + # Stack: [] -> [sum] + g.nop(comment='Get the first array value as starting point') + g.local.get(sum_adr) + g.add_statement(f'{sa_type_mtyp}.load') + + # Since we did the first one, increase adr + # adr = adr + sa_type_alloc_size + # Stack: [sum] -> [sum] + g.local.get(sum_adr) + g.i32.const(sa_type_alloc_size) + g.i32.add() + g.local.set(sum_adr) + + if sa_len.value > 1: + with g.loop(params=[sa_type_mtyp], result=sa_type_mtyp): + # sum = sum + *adr + # Stack: [sum] -> [sum + *adr] + g.nop(comment='Add array value') + g.local.get(sum_adr) + g.add_statement(f'{sa_type_mtyp}.load') + sa_type_add_gen(g, {}) + + # adr = adr + sa_type_alloc_size + # Stack: [sum] -> [sum] + g.nop(comment='Calculate address of the next value') + g.local.get(sum_adr) + g.i32.const(sa_type_alloc_size) + g.i32.add() + g.local.tee(sum_adr) + + # loop if adr < stop + g.nop(comment='Check if address exceeds array bounds') + g.local.get(sum_stop) + g.i32.lt_u() + g.br_if(0) + # else: sum x[1] === x => so we don't need to loop + + g.nop(comment=f'Completed sum for {sa_type.name}[{sa_len.value}]') + # End result: [sum] diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index efa2181..94a17de 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -3,7 +3,7 @@ This module contains possible constraints generated based on the AST These need to be resolved before the program can be compiled. """ -from typing import Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Dict, Iterable, List, Optional, Tuple, Union from .. import ourlang, prelude from . import placeholders, typeclasses, types @@ -50,7 +50,7 @@ class Context: __slots__ = ('type_class_instances_existing', ) # Constraint_TypeClassInstanceExists - type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[types.Type3, ...]]] + type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[Union[types.Type3, types.TypeConstructor_Base[Any], types.TypeConstructor_Struct], ...]]] def __init__(self) -> None: self.type_class_instances_existing = set() @@ -158,6 +158,46 @@ class SameTypeConstraint(ConstraintBase): return f'SameTypeConstraint({args}, comment={repr(self.comment)})' +class SameTypeArgumentConstraint(ConstraintBase): + __slots__ = ('tc_var', 'arg_var', ) + + tc_var: PlaceholderForType + arg_var: PlaceholderForType + + def __init__(self, tc_var: PlaceholderForType, arg_var: PlaceholderForType, *, comment: str) -> None: + super().__init__(comment=comment) + + self.tc_var = tc_var + self.arg_var = arg_var + + def check(self) -> CheckResult: + if self.tc_var.resolve_as is None or self.arg_var.resolve_as is None: + return RequireTypeSubstitutes() + + tc_typ = self.tc_var.resolve_as + arg_typ = self.arg_var.resolve_as + + if isinstance(tc_typ.application, types.TypeApplication_Nullary): + return Error(f'{tc_typ:s} must be a constructed type instead') + + if isinstance(tc_typ.application, types.TypeApplication_TypeStar): + # Sure, it's a constructed type. But it's like a struct, + # though without the way to implement type classes + # Presumably, doing a naked `foo :: t a -> a` + # doesn't work since you don't have any info on t + # So we can let the MustImplementTypeClassConstraint handle it. + return None + + # FIXME: This feels sketchy. Shouldn't the type variable + # have the exact same number as arguments? + if isinstance(tc_typ.application, types.TypeApplication_TypeInt): + if tc_typ.application.arguments[0] == arg_typ: + return None + + return Error(f'{tc_typ.application.arguments[0]:s} must be {arg_typ:s} instead') + + raise NotImplementedError(tc_typ, arg_typ) + class TupleMatchConstraint(ConstraintBase): __slots__ = ('exp_type', 'args', ) @@ -229,7 +269,7 @@ class MustImplementTypeClassConstraint(ConstraintBase): self.types = types def check(self) -> CheckResult: - typ_list = [] + typ_list: list[types.Type3 | types.TypeConstructor_Base[Any] | types.TypeConstructor_Struct] = [] for typ in self.types: if isinstance(typ, placeholders.PlaceholderForType) and typ.resolve_as is not None: typ = typ.resolve_as @@ -237,7 +277,15 @@ class MustImplementTypeClassConstraint(ConstraintBase): if isinstance(typ, placeholders.PlaceholderForType): return RequireTypeSubstitutes() - typ_list.append(typ) + if isinstance(typ.application, (types.TypeApplication_Nullary, types.TypeApplication_Struct, )): + typ_list.append(typ) + continue + + if isinstance(typ.application, (types.TypeApplication_TypeInt, types.TypeApplication_TypeStar)): + typ_list.append(typ.application.constructor) + continue + + raise NotImplementedError(typ, typ.application) assert len(typ_list) == len(self.types) diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index c10ce86..2b98966 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -16,6 +16,7 @@ from .constraints import ( Context, LiteralFitsConstraint, MustImplementTypeClassConstraint, + SameTypeArgumentConstraint, SameTypeConstraint, TupleMatchConstraint, ) @@ -39,6 +40,116 @@ def constant(ctx: Context, inp: ourlang.Constant, phft: placeholders.Placeholder raise NotImplementedError(constant, inp) +def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderForType) -> ConstraintGenerator: + return _expression_function_call( + ctx, + f'({inp.operator.name})', + inp.operator.signature, + [inp.left, inp.right], + inp, + phft, + ) + +def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: PlaceholderForType) -> ConstraintGenerator: + return _expression_function_call( + ctx, + inp.function.name, + inp.function.signature, + inp.arguments, + inp, + phft, + ) + +def _expression_function_call( + ctx: Context, + func_name: str, + signature: functions.FunctionSignature, + arguments: list[ourlang.Expression], + return_expr: ourlang.Expression, + return_phft: PlaceholderForType, + ) -> ConstraintGenerator: + """ + Generates all type-level constraints for a function call. + + A Binary operator functions pretty much the same as a function call + with two arguments - it's only a syntactic difference. + """ + # First create placeholders for all arguments, and generate their constraints + arg_placeholders = { + arg_expr: PlaceholderForType([arg_expr]) + for arg_expr in arguments + } + arg_placeholders[return_expr] = return_phft + + for call_arg in arguments: + yield from expression(ctx, call_arg, arg_placeholders[call_arg]) + + # Then generate placeholders the function signature + # and apply constraints that the function requires + # Skip any fully reference types + # Making this a map ensures that if a function signature has + # the same type on multiple arguments, we only get one + # placeholder here. These don't need to update anything once + # subsituted - that's done by arg_placeholders. + type_var_map = { + x: placeholders.PlaceholderForType([]) + for x in signature.args + if isinstance(x, functions.TypeVariable) + } + + for constraint in signature.context.constraints: + if isinstance(constraint, functions.Constraint_TypeClassInstanceExists): + yield MustImplementTypeClassConstraint( + ctx, + constraint.type_class3, + [type_var_map[x] for x in constraint.types], + ) + continue + + raise NotImplementedError(constraint) + + # If some of the function arguments are type constructors, + # we need to deal with those separately. + # That is, given `foo :: t a -> a` we need to ensure + # that both a's are the same. + for sig_arg in signature.args: + if isinstance(sig_arg, type3types.Type3): + # Not a type variable at all + continue + + if sig_arg.application.constructor is None: + # Not a type variable for a type constructor + continue + + if not isinstance(sig_arg.application, functions.TypeVariableApplication_Unary): + raise NotImplementedError(sig_arg.application) + + assert sig_arg.application.arguments in type_var_map # When does this happen? + + yield SameTypeArgumentConstraint( + type_var_map[sig_arg], + type_var_map[sig_arg.application.arguments], + comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}', + ) + + # Lastly, tie the signature and expression together + for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [return_expr], strict=True)): + if arg_no == len(arguments): + comment = f'The type of a function call to {func_name} is the same as the type that the function returns' + else: + 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, functions.TypeVariable): + yield SameTypeConstraint(type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment) + continue + + if isinstance(sig_part, type3types.Type3): + yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment) + continue + + raise NotImplementedError(sig_part) + return + def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.PlaceholderForType) -> ConstraintGenerator: if isinstance(inp, ourlang.Constant): yield from constant(ctx, inp, phft) @@ -49,53 +160,12 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.Placeho comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})') return - if isinstance(inp, ourlang.BinaryOp) or isinstance(inp, ourlang.FunctionCall): - signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature - arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments + if isinstance(inp, ourlang.BinaryOp): + yield from expression_binary_op(ctx, inp, phft) + return - func_name = f'({inp.operator.name})' if isinstance(inp, ourlang.BinaryOp) else inp.function.name - - type_var_map = { - x: placeholders.PlaceholderForType([]) - for x in signature.args - if isinstance(x, functions.TypeVariable) - } - - arg_placeholders = { - arg_expr: PlaceholderForType([arg_expr]) - for arg_expr in arguments - } - arg_placeholders[inp] = phft - - for arg_expr in arguments: - yield from expression(ctx, arg_expr, arg_placeholders[arg_expr]) - - for constraint in signature.context.constraints: - if isinstance(constraint, functions.Constraint_TypeClassInstanceExists): - yield MustImplementTypeClassConstraint( - ctx, - constraint.type_class3, - [type_var_map[x] for x in constraint.types], - ) - continue - - raise NotImplementedError(constraint) - - for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [inp], strict=True)): - if arg_no == len(arguments): - comment = f'The type of a function call to {func_name} is the same as the type that the function returns' - else: - 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, functions.TypeVariable): - yield SameTypeConstraint(type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment) - continue - - if isinstance(sig_part, type3types.Type3): - yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment) - continue - - raise NotImplementedError(sig_part) + if isinstance(inp, ourlang.FunctionCall): + yield from expression_function_call(ctx, inp, phft) return if isinstance(inp, ourlang.TupleInstantiation): diff --git a/phasm/type3/functions.py b/phasm/type3/functions.py index 4d924a5..1b91948 100644 --- a/phasm/type3/functions.py +++ b/phasm/type3/functions.py @@ -1,10 +1,36 @@ -from typing import TYPE_CHECKING, Any, Iterable, List, Union +from typing import TYPE_CHECKING, Any, Hashable, Iterable, List, Union if TYPE_CHECKING: from .typeclasses import Type3Class from .types import Type3 +class TypeVariableApplication_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 __hash__(self) -> int: + return hash((self.constructor, self.arguments, )) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, TypeVariableApplication_Base): + raise NotImplementedError + + return (self.constructor == other.constructor # type: ignore[no-any-return] + and self.arguments == other.arguments) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})' + class TypeVariable: """ Types variable are used in function definition. @@ -14,25 +40,77 @@ class TypeVariable: during type checking. These type variables are used solely in the function's definition """ - __slots__ = ('letter', ) + __slots__ = ('name', 'application', ) - letter: str + name: str + application: TypeVariableApplication_Base[Any, Any] - def __init__(self, letter: str) -> None: - assert len(letter) == 1, f'{letter} is not a valid type variable' - self.letter = letter + def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None: + self.name = name + self.application = application def __hash__(self) -> int: - return hash(self.letter) + return hash((self.name, self.application, )) def __eq__(self, other: Any) -> bool: if not isinstance(other, TypeVariable): raise NotImplementedError - return self.letter == other.letter + return (self.name == other.name + and self.application == other.application) def __repr__(self) -> str: - return f'TypeVariable({repr(self.letter)})' + return f'TypeVariable({repr(self.name)})' + +class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]): + """ + For the type for this function argument it's not relevant if it was constructed. + """ + +class TypeConstructorVariable: + """ + Types constructor variable are used in function definition. + + They are a lot like TypeVariable, except that they represent a + type constructor rather than a type directly. + + For now, we only have type constructor variables for kind + * -> *. + """ + __slots__ = ('name', ) + + name: str + + def __init__(self, name: str) -> None: + self.name = name + + def __hash__(self) -> int: + return hash((self.name, )) + + def __eq__(self, other: Any) -> bool: + if other is None: + return False + + if not isinstance(other, TypeConstructorVariable): + raise NotImplementedError + + return (self.name == other.name) + + def __call__(self, tvar: TypeVariable) -> 'TypeVariable': + return TypeVariable( + self.name + ' ' + tvar.name, + TypeVariableApplication_Unary(self, tvar) + ) + + def __repr__(self) -> str: + return f'TypeConstructorVariable({self.name!r})' + +class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]): + """ + The type for this function argument should be constructed from a type constructor. + + And we need to know what construtor that was, since that's the one we support. + """ class ConstraintBase: __slots__ = () @@ -52,7 +130,7 @@ class Constraint_TypeClassInstanceExists(ConstraintBase): assert len(self.type_class3.args) == len(self.types) def __str__(self) -> str: - return self.type_class3.name + ' ' + ' '.join(x.letter for x in self.types) + return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types) def __repr__(self) -> str: return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})' @@ -62,13 +140,11 @@ class TypeVariableContext: constraints: list[ConstraintBase] - def __init__(self) -> None: - self.constraints = [] + def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None: + self.constraints = list(constraints) def __copy__(self) -> 'TypeVariableContext': - result = TypeVariableContext() - result.constraints.extend(self.constraints) - return result + return TypeVariableContext(self.constraints) def __str__(self) -> str: if not self.constraints: @@ -90,7 +166,7 @@ class FunctionSignature: self.args = list(args) def __str__(self) -> str: - return str(self.context) + ' -> '.join(x.letter if isinstance(x, TypeVariable) else x.name for x in self.args) + return str(self.context) + ' -> '.join(x.name for x in self.args) def __repr__(self) -> str: return f'FunctionSignature({self.context!r}, {self.args!r})' diff --git a/phasm/type3/routers.py b/phasm/type3/routers.py index d575bb8..c592dac 100644 --- a/phasm/type3/routers.py +++ b/phasm/type3/routers.py @@ -1,9 +1,13 @@ -from typing import Any, Callable, TypeVar +from typing import Any, Callable -from .functions import FunctionSignature, TypeVariable -from .types import Type3, TypeConstructor_Base +from .functions import ( + TypeConstructorVariable, + TypeVariable, + TypeVariableApplication_Unary, +) +from .typeclasses import Type3ClassArgs +from .types import KindArgument, Type3, TypeApplication_TypeInt, TypeConstructor_Base -T = TypeVar('T') class NoRouteForTypeException(Exception): pass @@ -36,7 +40,7 @@ class TypeApplicationRouter[S, R]: """ self.by_type[typ] = helper - def add(self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None: + def add[T](self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None: self.by_constructor[constructor] = helper def __call__(self, arg0: S, typ: Type3) -> R: @@ -50,39 +54,66 @@ class TypeApplicationRouter[S, R]: raise NoRouteForTypeException(arg0, typ) -class FunctionSignatureRouter[S, R]: +TypeVariableLookup = dict[TypeVariable, tuple[KindArgument, ...]] + +class TypeClassArgsRouter[S, R]: """ - Helper class to find a method based on a function signature + Helper class to find a method based on a type class argument list """ - __slots__ = ('signature', 'data', ) + __slots__ = ('args', 'data', ) - signature: FunctionSignature + args: Type3ClassArgs - data: dict[tuple[Type3, ...], Callable[[S], R]] + data: dict[tuple[Type3 | TypeConstructor_Base[Any], ...], Callable[[S, TypeVariableLookup], R]] - def __init__(self, signature: FunctionSignature) -> None: - self.signature = signature + def __init__(self, args: Type3ClassArgs) -> None: + self.args = args self.data = {} - def add(self, tv_map: dict[TypeVariable, Type3], helper: Callable[[S], R]) -> None: - key = tuple( - tv_map[x] - for x in self.signature.args - if isinstance(x, TypeVariable) - ) - # assert len(key) == len(tv_map), (key, tv_map) + def add( + self, + tv_map: dict[TypeVariable, Type3], + tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]], + helper: Callable[[S, TypeVariableLookup], R], + ) -> None: - self.data[key] = helper + key: list[Type3 | TypeConstructor_Base[Any]] = [] + + for tc_arg in self.args: + if isinstance(tc_arg, TypeVariable): + key.append(tv_map[tc_arg]) + else: + key.append(tc_map[tc_arg]) + + self.data[tuple(key)] = helper def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R: - key = tuple( - tv_map[x] - for x in self.signature.args - if isinstance(x, TypeVariable) - ) + key: list[Type3 | TypeConstructor_Base[Any]] = [] + arguments: TypeVariableLookup = {} - t_helper = self.data.get(key) + for tc_arg in self.args: + if isinstance(tc_arg, TypeVariable): + key.append(tv_map[tc_arg]) + continue + + for tvar, typ in tv_map.items(): + tvar_constructor = tvar.application.constructor + if tvar_constructor != tc_arg: + continue + + key.append(typ.application.constructor) + + if isinstance(tvar.application, TypeVariableApplication_Unary): + # FIXME: This feels sketchy. Shouldn't the type variable + # have the exact same number as arguments? + if isinstance(typ.application, TypeApplication_TypeInt): + arguments[tvar.application.arguments] = typ.application.arguments + continue + + raise NotImplementedError(tvar.application, typ.application) + + t_helper = self.data.get(tuple(key)) if t_helper is not None: - return t_helper(arg0) + return t_helper(arg0, arguments) raise NoRouteForTypeException(arg0, tv_map) diff --git a/phasm/type3/typeclasses.py b/phasm/type3/typeclasses.py index 4b09f66..83d87be 100644 --- a/phasm/type3/typeclasses.py +++ b/phasm/type3/typeclasses.py @@ -2,7 +2,9 @@ from typing import Dict, Iterable, List, Mapping, Optional, Union from .functions import ( Constraint_TypeClassInstanceExists, + ConstraintBase, FunctionSignature, + TypeConstructorVariable, TypeVariable, TypeVariableContext, ) @@ -25,11 +27,13 @@ class Type3ClassMethod: def __repr__(self) -> str: return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})' +Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable] + class Type3Class: __slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', ) name: str - args: List[TypeVariable] + args: Type3ClassArgs methods: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod] inherited_classes: List['Type3Class'] @@ -37,26 +41,60 @@ class Type3Class: def __init__( self, name: str, - args: Iterable[TypeVariable], + args: Type3ClassArgs, methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]], operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]], inherited_classes: Optional[List['Type3Class']] = None, + additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None, ) -> None: self.name = name - self.args = list(args) - - context = TypeVariableContext() - context.constraints.append(Constraint_TypeClassInstanceExists(self, args)) + self.args = args self.methods = { - k: Type3ClassMethod(k, FunctionSignature(context, v)) + k: Type3ClassMethod(k, _create_signature(v, self)) for k, v in methods.items() } self.operators = { - k: Type3ClassMethod(k, FunctionSignature(context, v)) + k: Type3ClassMethod(k, _create_signature(v, self)) for k, v in operators.items() } self.inherited_classes = inherited_classes or [] + if additional_context: + for func_name, constraint_list in additional_context.items(): + func = self.methods.get(func_name) or self.operators.get(func_name) + assert func is not None # type hint + + func.signature.context.constraints.extend(constraint_list) + def __repr__(self) -> str: return self.name + +def _create_signature( + method_arg_list: Iterable[Type3 | TypeVariable], + type_class3: Type3Class, + ) -> FunctionSignature: + context = TypeVariableContext() + if not isinstance(type_class3.args[0], TypeConstructorVariable): + context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, type_class3.args)) + + signature_args: list[Type3 | TypeVariable] = [] + for method_arg in method_arg_list: + if isinstance(method_arg, Type3): + signature_args.append(method_arg) + continue + + if isinstance(method_arg, TypeVariable): + type_constructor = method_arg.application.constructor + if type_constructor is None: + signature_args.append(method_arg) + continue + + if (type_constructor, ) == type_class3.args: + context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, [method_arg])) + signature_args.append(method_arg) + continue + + raise NotImplementedError(method_arg) + + return FunctionSignature(context, signature_args) diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 5a971cf..7a1b835 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -29,6 +29,16 @@ class TypeApplication_Base[T: Hashable, S: Hashable]: self.constructor = constructor self.arguments = arguments + def __hash__(self) -> int: + return hash((self.constructor, self.arguments, )) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, TypeApplication_Base): + raise NotImplementedError + + return (self.constructor == other.constructor # type: ignore[no-any-return] + and self.arguments == other.arguments) + def __repr__(self) -> str: return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})' @@ -84,7 +94,9 @@ class Type3(KindArgument): raise NotImplementedError class TypeApplication_Nullary(TypeApplication_Base[None, None]): - pass + """ + There was no constructor used to create this type - it's a 'simple' type like u32 + """ class IntType3(KindArgument): """ @@ -104,6 +116,9 @@ class IntType3(KindArgument): def __init__(self, value: int) -> None: self.value = value + def __repr__(self) -> str: + return f'IntType3({self.value!r})' + def __format__(self, format_spec: str) -> str: if format_spec != 's': raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}') @@ -177,6 +192,9 @@ class TypeConstructor_Base[T]: return result + def __repr__(self) -> str: + return f'{self.__class__.__name__}({self.name!r}, ...)' + class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]): """ Base class type constructors of kind: * -> Int -> * diff --git a/phasm/wasmgenerator.py b/phasm/wasmgenerator.py index 9268219..9bee38e 100644 --- a/phasm/wasmgenerator.py +++ b/phasm/wasmgenerator.py @@ -2,7 +2,7 @@ Helper functions to generate WASM code by writing Python functions """ import functools -from typing import Any, Callable, Dict, List, Optional, Type +from typing import Any, Callable, Dict, Iterable, List, Optional, Type from . import wasm @@ -24,6 +24,12 @@ class VarType_i32(VarType_Base): class VarType_i64(VarType_Base): wasm_type = wasm.WasmTypeInt64 +class VarType_f32(VarType_Base): + wasm_type = wasm.WasmTypeFloat32 + +class VarType_f64(VarType_Base): + wasm_type = wasm.WasmTypeFloat64 + class Generator_i32i64: def __init__(self, prefix: str, generator: 'Generator') -> None: self.prefix = prefix @@ -164,12 +170,23 @@ class Generator_Local: self.generator.add_statement('local.tee', variable.name_ref, comment=comment) class GeneratorBlock: - def __init__(self, generator: 'Generator', name: str) -> None: + def __init__(self, generator: 'Generator', name: str, params: Iterable[str] = (), result: str | None = None) -> None: self.generator = generator self.name = name + self.params = params + self.result = result def __enter__(self) -> None: - self.generator.add_statement(self.name) + stmt = self.name + if self.params: + stmt = f'{stmt} ' + ' '.join( + f'(param {typ})' + for typ in self.params + ) + if self.result: + stmt = f'{stmt} (result {self.result})' + + self.generator.add_statement(stmt) def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: if not exc_type: @@ -210,19 +227,18 @@ class Generator: def add_statement(self, name: str, *args: str, comment: Optional[str] = None) -> None: self.statements.append(wasm.Statement(name, *args, comment=comment)) - def temp_var_i32(self, infix: str) -> VarType_i32: + def temp_var[T: VarType_Base](self, var: T) -> T: idx = 0 - while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals: + while (varname := f'__{var.name}_tmp_var_{idx}__') in self.locals: idx += 1 - return VarType_i32(varname) + return var.__class__(varname) + + def temp_var_i32(self, infix: str) -> VarType_i32: + return self.temp_var(VarType_i32(infix)) def temp_var_u8(self, infix: str) -> VarType_u8: - idx = 0 - while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals: - idx += 1 - - return VarType_u8(varname) + return self.temp_var(VarType_u8(infix)) def func_wrapper(exported: bool = True) -> Callable[[Any], wasm.Function]: """ diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index a335509..74cfb19 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -17,6 +17,9 @@ from . import runners DASHES = '-' * 16 +class InvalidArgumentException(Exception): + pass + class SuiteResult: def __init__(self) -> None: self.returned_value = None @@ -152,18 +155,18 @@ def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> in def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int: runner, val = attrs - assert isinstance(val, tuple) - sa_type, sa_len = sa_args + if not isinstance(val, tuple): + raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead') + 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) adr = runner.call('stdlib.alloc.__alloc__', alloc_size) - assert isinstance(adr, int) + assert isinstance(adr, int) # Type int sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') - tuple_len = sa_len.value - assert tuple_len == len(val) - offset = adr for val_el_val in val: offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val) diff --git a/tests/integration/test_lang/test_foldable.py b/tests/integration/test_lang/test_foldable.py new file mode 100644 index 0000000..c03d6e7 --- /dev/null +++ b/tests/integration/test_lang/test_foldable.py @@ -0,0 +1,71 @@ +import pytest + +from phasm.type3.entry import Type3Exception + +from ..helpers import Suite +from .test_natnum import FLOAT_TYPES, INT_TYPES + + +@pytest.mark.integration_test +@pytest.mark.parametrize('length', [1, 5, 13]) +@pytest.mark.parametrize('a_type', INT_TYPES + FLOAT_TYPES) +def test_foldable_sum(length, a_type): + code_py = f""" +@exported +def testEntry(x: {a_type}[{length}]) -> {a_type}: + return sum(x) +""" + + in_put = tuple(range(length)) + + result = Suite(code_py).run_code(in_put) + + assert sum(in_put) == result.returned_value + +@pytest.mark.integration_test +def test_foldable_sum_not_natnum(): + code_py = """ +class Foo: + bar: i32 + +@exported +def testEntry(x: Foo[4]) -> Foo: + return sum(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'): + Suite(code_py).run_code() + + +@pytest.mark.integration_test +def test_foldable_invalid_return_type(): + code_py = """ +@exported +def testEntry(x: i32[5]) -> f64: + return sum(x) +""" + + with pytest.raises(Type3Exception, match='i32 must be f64 instead'): + Suite(code_py).run_code((4, 5, 6, 7, 8, )) + +@pytest.mark.integration_test +def test_foldable_not_constructed(): + code_py = """ +@exported +def testEntry(x: i32) -> i32: + return sum(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable i32.*i32 must be a constructed type instead'): + Suite(code_py).run_code() + +@pytest.mark.integration_test +def test_foldable_not_foldable(): + code_py = """ +@exported +def testEntry(x: (i32, u32, )) -> i32: + return sum(x) +""" + + with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable tuple'): + Suite(code_py).run_code()