Implements sum for Foldable types

Foldable take a TypeConstructor. The first argument must be a
NatNum.
This commit is contained in:
Johan B.W. de Vries 2025-05-05 14:09:38 +02:00
parent 6b66935c67
commit 23ca1799b2
10 changed files with 204 additions and 40 deletions

View File

@ -12,9 +12,12 @@
- 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.
- Do we need to store the placeholders on the expressions? They're only temporary while the type checker is running
- Might not even need to store them at all outside the generated constraints?
- Parser is putting stuff in ModuleDataBlock
- Surely the compiler should build data blocks

View File

@ -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 Dict, List, Optional, Union
from . import codestyle, ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset
@ -260,6 +260,10 @@ INSTANCES = {
prelude.Promotable.methods['demote']: {
'a=f32,b=f64': stdlib_types.f32_f64_demote,
},
prelude.Foldable.methods['sum']: {
'a=i32,t=i32[4]': stdlib_types.static_array_i32_4_sum,
'a=i32,t=i32[5]': stdlib_types.static_array_i32_5_sum,
},
}
def phasm_compile(inp: ourlang.Module) -> wasm.Module:
@ -449,7 +453,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
type_var_map: Dict[type3functions.TypeVariable, type3types.Type3] = {}
type_var_map: Dict[Union[type3functions.TypeVariable, type3functions.TypeConstructorVariable], 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):
@ -480,12 +484,16 @@ 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):
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, type3functions.TypeConstructorVariable, )):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
type_var_map[type_var] = arg_expr.type3
continue
raise NotImplementedError
instance_key = ','.join(
f'{k.letter}={v.name}'
@ -949,6 +957,7 @@ def module(inp: ourlang.Module) -> wasm.Module:
stdlib_types.__u32_pow2__,
stdlib_types.__u8_rotl__,
stdlib_types.__u8_rotr__,
stdlib_types.__sa_i32_sum__,
] + [
function(x)
for x in inp.functions.values()

View File

@ -1,20 +1,27 @@
"""
The prelude are all the builtin types, type classes and methods
"""
from typing import Any, Union
from ..type3.functions import TypeVariable
from ..type3.functions import (
Constraint_TypeClassInstanceExists,
TypeConstructorVariable,
TypeVariable,
)
from ..type3.typeclasses import Type3Class
from ..type3.types import (
IntType3,
Type3,
TypeConstructor,
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[Union[Type3, TypeConstructor[Any], TypeConstructor_Struct], ...]]] = set()
def instance_type_class(cls: Type3Class, *typ: Type3) -> None:
def instance_type_class(cls: Type3Class, *typ: Union[Type3, TypeConstructor[Any], TypeConstructor_Struct]) -> None:
global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING
# TODO: Check for required existing instantiations
@ -93,7 +100,7 @@ bytes_ = Type3('bytes')
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 +113,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)
@ -143,6 +150,7 @@ PRELUDE_TYPES: dict[str, Type3] = {
a = TypeVariable('a')
b = TypeVariable('b')
t = TypeConstructorVariable('t', [])
InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={})
"""
@ -286,6 +294,14 @@ Promotable = Type3Class('Promotable', [a, b], methods={
instance_type_class(Promotable, f32, f64)
Foldable = Type3Class('Foldable', [t], methods={
'sum': [t(a), a],
}, operators={}, additional_context={
'sum': [Constraint_TypeClassInstanceExists(NatNum, [a])],
})
instance_type_class(Foldable, static_array)
PRELUDE_TYPE_CLASSES = {
'Eq': Eq,
'Ord': Ord,
@ -321,4 +337,5 @@ PRELUDE_METHODS = {
**Sized_.methods,
**Extendable.methods,
**Promotable.methods,
**Foldable.methods,
}

View File

@ -384,6 +384,50 @@ def __u8_rotr__(g: Generator, x: i32, r: i32) -> i32:
return i32('return') # To satisfy mypy
@func_wrapper()
def __sa_i32_sum__(g: Generator, adr: i32, arlen: i32) -> i32:
i32_size = 4
s = i32('s')
stop = i32('stop')
# stop = adr + ar_len * i32_size
g.local.get(adr)
g.local.get(arlen)
g.i32.const(i32_size)
g.i32.mul()
g.i32.add()
g.local.set(stop)
# sum = 0
g.i32.const(0)
g.local.set(s)
with g.loop():
# sum = sum + *adr
g.local.get(adr)
g.i32.load()
g.local.get(s)
g.i32.add()
g.local.set(s)
# adr = adr + i32_size
g.local.get(adr)
g.i32.const(i32_size)
g.i32.add()
g.local.tee(adr)
# loop if adr < stop
g.local.get(stop)
g.i32.lt_u()
g.br_if(0)
# return sum
g.local.get(s)
g.return_()
return i32('return') # To satisfy mypy
## ###
## class Eq
@ -920,3 +964,11 @@ def f32_f64_promote(g: Generator) -> None:
def f32_f64_demote(g: Generator) -> None:
g.f32.demote_f64()
def static_array_i32_4_sum(g: Generator) -> None:
g.i32.const(4)
g.add_statement('call $stdlib.types.__sa_i32_sum__')
def static_array_i32_5_sum(g: Generator) -> None:
g.i32.const(5)
g.add_statement('call $stdlib.types.__sa_i32_sum__')

View File

@ -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
@ -49,7 +49,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[Any], types.TypeConstructor_Struct], ...]]]
def __init__(self) -> None:
self.type_class_instances_existing = set()

View File

@ -50,16 +50,9 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.Placeho
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
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)
}
arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments
arg_placeholders = {
arg_expr: PlaceholderForType([arg_expr])
@ -67,6 +60,17 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.Placeho
}
arg_placeholders[inp] = phft
for call_arg in arguments:
yield from expression(ctx, call_arg, arg_placeholders[call_arg])
signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature
type_var_map = {
x: placeholders.PlaceholderForType([])
for x in signature.args
if isinstance(x, functions.TypeVariable)
or isinstance(x, functions.TypeConstructorVariable)
}
for arg_expr in arguments:
yield from expression(ctx, arg_expr, arg_placeholders[arg_expr])

View File

@ -34,6 +34,25 @@ class TypeVariable:
def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})'
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.
"""
__slots__ = ('letter', 'args', )
def __init__(self, letter: str, args: Iterable[TypeVariable]) -> None:
self.letter = letter
self.args = list(args)
def __call__(self, tvar: TypeVariable) -> 'TypeConstructorVariable':
return TypeConstructorVariable(self.letter, self.args + [tvar])
def __repr__(self) -> str:
return f'TypeConstructorVariable({self.letter!r}, {self.args!r})'
class ConstraintBase:
__slots__ = ()
@ -41,9 +60,9 @@ class Constraint_TypeClassInstanceExists(ConstraintBase):
__slots__ = ('type_class3', 'types', )
type_class3: 'Type3Class'
types: list[TypeVariable]
types: list[Union[TypeVariable, TypeConstructorVariable]]
def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None:
def __init__(self, type_class3: 'Type3Class', types: Iterable[Union[TypeVariable, TypeConstructorVariable]]) -> None:
self.type_class3 = type_class3
self.types = list(types)
@ -52,27 +71,22 @@ class Constraint_TypeClassInstanceExists(ConstraintBase):
assert len(self.type_class3.args) == len(self.types)
class TypeVariableContext:
__slots__ = ('variables', 'constraints', )
__slots__ = ('constraints', )
variables: set[TypeVariable]
constraints: list[ConstraintBase]
def __init__(self) -> None:
self.variables = set()
self.constraints = []
def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
self.constraints = list(constraints)
def __copy__(self) -> 'TypeVariableContext':
result = TypeVariableContext()
result.variables.update(self.variables)
result.constraints.extend(self.constraints)
return result
return TypeVariableContext(self.constraints)
class FunctionSignature:
__slots__ = ('context', 'args', )
context: TypeVariableContext
args: List[Union['Type3', TypeVariable]]
args: List[Union['Type3', TypeVariable, TypeConstructorVariable]]
def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable]]) -> None:
def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable, TypeConstructorVariable]]) -> None:
self.context = context.__copy__()
self.args = list(args)

View File

@ -2,7 +2,9 @@ from typing import Dict, Iterable, List, Mapping, Optional, Union
from .functions import (
Constraint_TypeClassInstanceExists,
ConstraintBase,
FunctionSignature,
TypeConstructorVariable,
TypeVariable,
TypeVariableContext,
)
@ -26,7 +28,7 @@ class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
name: str
args: List[TypeVariable]
args: List[Union[TypeVariable, TypeConstructorVariable]]
methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod]
inherited_classes: List['Type3Class']
@ -34,10 +36,11 @@ class Type3Class:
def __init__(
self,
name: str,
args: Iterable[TypeVariable],
methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
args: Iterable[Union[TypeVariable, TypeConstructorVariable]],
methods: Mapping[str, Iterable[Union[Type3, TypeVariable, TypeConstructorVariable]]],
operators: Mapping[str, Iterable[Union[Type3, TypeVariable, TypeConstructorVariable]]],
inherited_classes: Optional[List['Type3Class']] = None,
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
) -> None:
self.name = name
self.args = list(args)
@ -55,5 +58,12 @@ class Type3Class:
}
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

View File

@ -106,7 +106,7 @@ class TypeConstructor(Generic[T]):
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
"""
@ -122,7 +122,7 @@ class TypeConstructor(Generic[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
@ -152,7 +152,7 @@ class TypeConstructor(Generic[T]):
if result is None:
self._cache[key] = result = Type3(self.make_name(key))
self._reverse_cache[result] = key
self.on_create(result)
self.on_create(key, result)
return result

View File

@ -0,0 +1,55 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
@pytest.mark.integration_test
def test_foldable_sum():
code_py = """
@exported
def testEntry(x: i32[5]) -> i32:
return sum(x)
"""
result = Suite(code_py).run_code((4, 5, 6, 7, 8, ))
assert 30 == 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='f64 must be i32 instead'):
Suite(code_py).run_code((4, 5, 6, 7, 8, ))
@pytest.mark.integration_test
def test_foldable_not_foldable():
code_py = """
@exported
def testEntry(x: i32) -> i32:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable i32'):
Suite(code_py).run_code()