Compare commits

...

2 Commits

Author SHA1 Message Date
Johan B.W. de Vries
e8e7acc102 Removes the special casing for foldl
Had to implement both functions as arguments and type
place holders (variables) for type constructors.

Had to implement functions as a type as well.

Still have to figure out how to pass functions around.
2025-05-18 15:46:39 +02:00
Johan B.W. de Vries
83186cce78 Reworks bytes into dynamic array
bytes continues to be the preferred name for u8[...].
Also, putting bytes values into the VM and taking them
out still uses Python bytes values.

This also lets used use the len function on them, for
whatever that's worth.
2025-05-18 15:37:13 +02:00
20 changed files with 694 additions and 290 deletions

View File

@ -30,3 +30,5 @@
- Functions don't seem to be a thing on typing level yet?
- Related to the FIXME in phasm_type3?
- Type constuctor should also be able to constuct placeholders - somehow.
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm

View File

@ -105,10 +105,6 @@ def expression(inp: ourlang.Expression) -> str:
if isinstance(inp, ourlang.AccessStructMember):
return f'{expression(inp.varref)}.{inp.member}'
if isinstance(inp, ourlang.Fold):
fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr'
return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})'
raise NotImplementedError(expression, inp)
def statement(inp: ourlang.Statement) -> Statements:

View File

@ -4,19 +4,21 @@ This module contains the code to convert parsed Ourlang into WebAssembly code
import struct
from typing import List, Optional
from . import codestyle, ourlang, prelude, wasm
from . import ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset
from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types
from .type3.functions import TypeVariable
from .type3.functions import FunctionArgument, TypeVariable
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
from .type3.typeclasses import Type3ClassMethod
from .type3.types import (
IntType3,
Type3,
TypeApplication_Struct,
TypeApplication_Type,
TypeApplication_TypeInt,
TypeApplication_TypeStar,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Tuple,
@ -109,12 +111,25 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
args: tuple[Type3, ...]
if isinstance(inp.type3.application, TypeApplication_TypeStar):
alloc_size_header = None
if isinstance(inp.type3.application, TypeApplication_Type):
# Possibly paranoid assert. If we have a future variadic type,
# does it also do this tuple instantation like this?
assert isinstance(inp.type3.application.constructor, TypeConstructor_DynamicArray)
sa_type, = inp.type3.application.arguments
args = tuple(sa_type for _ in inp.elements)
alloc_size = 4 + calculate_alloc_size(sa_type, is_member=False) * len(inp.elements)
alloc_size_header = len(inp.elements)
elif isinstance(inp.type3.application, TypeApplication_TypeStar):
# Possibly paranoid assert. If we have a future variadic type,
# does it also do this tuple instantation like this?
assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple)
args = inp.type3.application.arguments
alloc_size = calculate_alloc_size(inp.type3, is_member=False)
elif isinstance(inp.type3.application, TypeApplication_TypeInt):
# Possibly paranoid assert. If we have a future type of kind * -> Int -> *,
# does it also do this tuple instantation like this?
@ -123,6 +138,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
sa_type, sa_len = inp.type3.application.arguments
args = tuple(sa_type for _ in range(sa_len.value))
alloc_size = calculate_alloc_size(inp.type3, is_member=False)
else:
raise NotImplementedError('tuple_instantiation', inp.type3)
@ -135,12 +151,17 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})')
# Allocated the required amounts of bytes in memory
wgn.i32.const(calculate_alloc_size(inp.type3, is_member=False))
wgn.i32.const(alloc_size)
wgn.call(stdlib_alloc.__alloc__)
wgn.local.set(tmp_var)
if alloc_size_header is not None:
wgn.local.get(tmp_var)
wgn.i32.const(alloc_size_header)
wgn.i32.store()
# Store each element individually
offset = 0
offset = 0 if alloc_size_header is None else 4
for element, exp_type3 in zip(inp.elements, args, strict=True):
assert element.type3 == exp_type3
@ -314,6 +335,10 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
type_var_map[type_var] = arg_expr.type3
continue
if isinstance(type_var, FunctionArgument):
# Fixed type, not part of the lookup requirements
continue
raise NotImplementedError(type_var, arg_expr.type3)
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator]
@ -339,6 +364,10 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
type_var_map[type_var] = arg_expr.type3
continue
if isinstance(type_var, FunctionArgument):
# Fixed type, not part of the lookup requirements
continue
raise NotImplementedError(type_var, arg_expr.type3)
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function]
@ -401,90 +430,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
)))
return
if isinstance(inp, ourlang.Fold):
expression_fold(wgn, mod, inp)
return
raise NotImplementedError(expression, inp)
def expression_fold(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Fold) -> None:
"""
Compile: Fold expression
"""
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
if inp.iter.type3 is not prelude.bytes_:
raise NotImplementedError(expression_fold, inp, inp.iter.type3)
wgn.add_statement('nop', comment='acu :: u8')
acu_var = wgn.temp_var_u8(f'fold_{codestyle.type3(inp.type3)}_acu')
wgn.add_statement('nop', comment='adr :: bytes*')
adr_var = wgn.temp_var_i32('fold_i32_adr')
wgn.add_statement('nop', comment='len :: i32')
len_var = wgn.temp_var_i32('fold_i32_len')
wgn.add_statement('nop', comment='acu = base')
expression(wgn, mod, inp.base)
wgn.local.set(acu_var)
wgn.add_statement('nop', comment='adr = adr(iter)')
expression(wgn, mod, inp.iter)
wgn.local.set(adr_var)
wgn.add_statement('nop', comment='len = len(iter)')
wgn.local.get(adr_var)
wgn.i32.load()
wgn.local.set(len_var)
wgn.add_statement('nop', comment='i = 0')
idx_var = wgn.temp_var_i32(f'fold_{codestyle.type3(inp.type3)}_idx')
wgn.i32.const(0)
wgn.local.set(idx_var)
wgn.add_statement('nop', comment='if i < len')
wgn.local.get(idx_var)
wgn.local.get(len_var)
wgn.i32.lt_u()
with wgn.if_():
# From here on, adr_var is the address of byte we're referencing
# This is akin to calling stdlib_types.__subscript_bytes__
# But since we already know we are inside of bounds,
# can just bypass it and load the memory directly.
wgn.local.get(adr_var)
wgn.i32.const(3) # Bytes header -1, since we do a +1 every loop
wgn.i32.add()
wgn.local.set(adr_var)
wgn.add_statement('nop', comment='while True')
with wgn.loop():
wgn.add_statement('nop', comment='acu = func(acu, iter[i])')
wgn.local.get(acu_var)
# Get the next byte, write back the address
wgn.local.get(adr_var)
wgn.i32.const(1)
wgn.i32.add()
wgn.local.tee(adr_var)
wgn.i32.load8_u()
wgn.add_statement('call', f'${inp.func.name}')
wgn.local.set(acu_var)
wgn.add_statement('nop', comment='i = i + 1')
wgn.local.get(idx_var)
wgn.i32.const(1)
wgn.i32.add()
wgn.local.set(idx_var)
wgn.add_statement('nop', comment='if i >= len: break')
wgn.local.get(idx_var)
wgn.local.get(len_var)
wgn.i32.lt_u()
wgn.br_if(0)
# return acu
wgn.local.get(acu_var)
def statement_return(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.StatementReturn) -> None:
"""
Compile: Return statement

View File

@ -1,7 +1,6 @@
"""
Contains the syntax tree for ourlang
"""
import enum
from typing import Dict, Iterable, List, Optional, Union
from . import prelude
@ -219,36 +218,6 @@ class AccessStructMember(Expression):
self.struct_type3 = struct_type3
self.member = member
class Fold(Expression):
"""
A (left or right) fold
"""
class Direction(enum.Enum):
"""
Which direction to fold in
"""
LEFT = 0
RIGHT = 1
dir: Direction
func: 'Function'
base: Expression
iter: Expression
def __init__(
self,
dir_: Direction,
func: 'Function',
base: Expression,
iter_: Expression,
) -> None:
super().__init__()
self.dir = dir_
self.func = func
self.base = base
self.iter = iter_
class Statement:
"""
A statement within a function
@ -301,6 +270,9 @@ class FunctionParam:
self.name = name
self.type3 = type3
def __repr__(self) -> str:
return f'FunctionParam({self.name!r}, {self.type3!r})'
class Function:
"""
A function processes input and produces output

View File

@ -14,7 +14,6 @@ from .ourlang import (
ConstantStruct,
ConstantTuple,
Expression,
Fold,
Function,
FunctionCall,
FunctionParam,
@ -467,7 +466,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[Fold, FunctionCall]:
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]:
if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -480,28 +479,6 @@ class OurVisitor:
if node.func.id in PRELUDE_METHODS:
func = PRELUDE_METHODS[node.func.id]
elif node.func.id == 'foldl':
if 3 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given')
# TODO: This is not generic, you cannot return a function
subnode = node.args[0]
if not isinstance(subnode, ast.Name):
raise NotImplementedError(f'Calling methods that are not a name {subnode}')
if not isinstance(subnode.ctx, ast.Load):
_raise_static_error(subnode, 'Must be load context')
if subnode.id not in module.functions:
_raise_static_error(subnode, 'Reference to undefined function')
func = module.functions[subnode.id]
if 2 != len(func.posonlyargs):
_raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given')
return Fold(
Fold.Direction.LEFT,
func,
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[1]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[2]),
)
elif node.func.id in our_locals:
func = our_locals[node.func.id]
else:
@ -655,8 +632,15 @@ class OurVisitor:
if isinstance(node.slice, ast.Slice):
_raise_static_error(node, 'Must subscript using an index')
if not isinstance(node.slice, ast.Constant):
_raise_static_error(node, 'Must subscript using a constant index')
if node.slice.value is Ellipsis:
return prelude.dynamic_array(
self.visit_type(module, node.value),
)
if not isinstance(node.slice.value, int):
_raise_static_error(node, 'Must subscript using a constant integer index')
if not isinstance(node.ctx, ast.Load):

View File

@ -20,6 +20,7 @@ from ..type3.types import (
Type3,
TypeApplication_Nullary,
TypeConstructor_Base,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Struct,
@ -158,9 +159,15 @@ f64 = Type3('f64', TypeApplication_Nullary(None, None))
A 32-bits IEEE 754 float, of 64 bits width.
"""
bytes_ = Type3('bytes', TypeApplication_Nullary(None, None))
def da_on_create(args: tuple[Type3], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ)
dynamic_array = TypeConstructor_DynamicArray('dynamic_array', on_create=da_on_create)
"""
This is a runtime-determined length piece of memory that can be indexed at runtime.
This is a dynamic length piece of memory.
It should be applied with two arguments. It has a runtime
determined length, and each argument is the same.
"""
def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None:
@ -168,12 +175,10 @@ def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None:
static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create)
"""
A type constructor.
This is a fixed length piece of memory.
Any static array is a fixed length piece of memory that can be indexed at runtime.
It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated.
It should be applied with two arguments. It has a compile time
determined length, and each argument is the same.
"""
def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None:
@ -208,20 +213,6 @@ This is like a tuple, but each argument is named, so that developers
can get and set fields by name.
"""
PRELUDE_TYPES: dict[str, Type3] = {
'none': none,
'bool': bool_,
'u8': u8,
'u32': u32,
'u64': u64,
'i8': i8,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes_,
}
a = TypeVariable('a', TypeVariableApplication_Nullary(None, None))
b = TypeVariable('b', TypeVariableApplication_Nullary(None, None))
@ -232,7 +223,7 @@ InternalPassAsPointer = Type3Class('InternalPassAsPointer', (a, ), methods={}, o
Internal type class to keep track which types we pass arounds as a pointer.
"""
instance_type_class(InternalPassAsPointer, bytes_)
# instance_type_class(InternalPassAsPointer, bytes_)
# instance_type_class(InternalPassAsPointer, static_array)
# instance_type_class(InternalPassAsPointer, tuple_)
# instance_type_class(InternalPassAsPointer, struct)
@ -537,12 +528,15 @@ instance_type_class(Floating, f64, methods={
'sqrt': stdtypes.f64_floating_sqrt,
})
Sized_ = Type3Class('Sized', (a, ), methods={
'len': [a, u32],
Sized_ = Type3Class('Sized', (t, ), methods={
'len': [t(a), u32],
}, operators={}) # FIXME: Once we get type class families, add [] here
instance_type_class(Sized_, bytes_, methods={
'len': stdtypes.bytes_sized_len,
instance_type_class(Sized_, dynamic_array, methods={
'len': stdtypes.dynamic_array_sized_len,
})
instance_type_class(Sized_, static_array, methods={
'len': stdtypes.static_array_sized_len,
})
Extendable = Type3Class('Extendable', (a, b, ), methods={
@ -587,14 +581,39 @@ instance_type_class(Promotable, f32, f64, methods={
Foldable = Type3Class('Foldable', (t, ), methods={
'sum': [t(a), a],
'foldl': [[b, a, b], b, t(a), b],
'foldr': [[a, b, b], b, t(a), b],
}, operators={}, additional_context={
'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))],
})
instance_type_class(Foldable, dynamic_array, methods={
'sum': stdtypes.dynamic_array_sum,
'foldl': stdtypes.dynamic_array_foldl,
'foldr': stdtypes.dynamic_array_foldr,
})
instance_type_class(Foldable, static_array, methods={
'sum': stdtypes.static_array_sum,
'foldl': stdtypes.static_array_foldl,
'foldr': stdtypes.static_array_foldr,
})
bytes_ = dynamic_array(u8)
PRELUDE_TYPES: dict[str, Type3] = {
'none': none,
'bool': bool_,
'u8': u8,
'u32': u32,
'u64': u64,
'i8': i8,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes_,
}
PRELUDE_TYPE_CLASSES = {
'Eq': Eq,
'Ord': Ord,

View File

@ -4,7 +4,9 @@ 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 Generator, VarType_Base, func_wrapper
from phasm.wasmgenerator import VarType_f32 as f32
from phasm.wasmgenerator import VarType_f64 as f64
from phasm.wasmgenerator import VarType_i32 as i32
from phasm.wasmgenerator import VarType_i64 as i64
@ -1006,11 +1008,23 @@ def f64_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None:
## ###
## Class Sized
def bytes_sized_len(g: Generator, tv_map: TypeVariableLookup) -> None:
def dynamic_array_sized_len(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
# The length is stored in the first 4 bytes
g.i32.load()
def static_array_sized_len(g: Generator, tvl: TypeVariableLookup) -> None:
tv_map, tc_map = tvl
tvn_map = {
x.name: y
for x, y in tv_map.items()
}
sa_len = tvn_map['a*']
assert isinstance(sa_len, IntType3)
g.i32.const(sa_len.value)
## ###
## Extendable
@ -1081,9 +1095,23 @@ 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()))
## ###
## Foldable
def dynamic_array_sum(g: Generator, tvl: TypeVariableLookup) -> None:
raise NotImplementedError
def static_array_sum(g: Generator, tvl: TypeVariableLookup) -> None:
tv_map, tc_map = tvl
tvn_map = {
x.name: y
for x, y in tv_map.items()
}
sa_type = tvn_map['a']
sa_len = tvn_map['a*']
assert isinstance(sa_type, Type3)
assert isinstance(sa_len, IntType3)
@ -1166,7 +1194,7 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None:
g.nop(comment='Add array value')
g.local.get(sum_adr)
g.add_statement(f'{sa_type_mtyp}.load')
sa_type_add_gen(g, {})
sa_type_add_gen(g, ({}, {}, ))
# adr = adr + sa_type_alloc_size
# Stack: [sum] -> [sum]
@ -1185,3 +1213,253 @@ def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None:
g.nop(comment=f'Completed sum for {sa_type.name}[{sa_len.value}]')
# End result: [sum]
def dynamic_array_foldl(g: Generator, tvl: TypeVariableLookup) -> None:
raise NotImplementedError
def static_array_foldl(g: Generator, tvl: TypeVariableLookup) -> None:
tv_map, tc_map = tvl
tvn_map = {
x.name: y
for x, y in tv_map.items()
}
sa_type = tvn_map['a']
sa_len = tvn_map['a*']
res_type = tvn_map['b']
assert isinstance(sa_type, Type3)
assert isinstance(sa_len, IntType3)
assert isinstance(res_type, Type3)
# 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',
}
mtyp_f_map: dict[str, type[VarType_Base]] = {
'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,
}
# 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)
res_type_mtyp = mtyp_map.get(res_type.name, 'i32')
res_type_mtyp_f = mtyp_f_map[res_type_mtyp]
# Definitions
fold_adr = g.temp_var(i32('fold_adr'))
fold_stop = g.temp_var(i32('fold_stop'))
fold_init = g.temp_var(res_type_mtyp_f('fold_init'))
fold_func = g.temp_var(i32('fold_func'))
with g.block(params=['i32', res_type_mtyp, 'i32'], result=res_type_mtyp, comment=f'foldl a={sa_type.name} a*={sa_len.value} b={res_type.name}'):
# Stack: [fn*, b, sa*] -> [fn*, b]
g.local.set(fold_adr)
# Stack: [fn*, b] -> [fn*]
g.local.set(fold_init)
# Stack: [fn*] -> []
g.local.set(fold_func)
if sa_len.value < 1:
g.local.get(fold_init)
return
# Stack: [] -> [b]
g.nop(comment='Apply the first function call')
g.local.get(fold_init)
g.local.get(fold_adr)
g.add_statement(f'{sa_type_mtyp}.load')
g.local.get(fold_func)
g.add_statement(f'call_indirect (param {res_type_mtyp} {sa_type_mtyp}) (result {res_type_mtyp})')
if sa_len.value > 1:
# Stack: [] ; fold_stop=fold_adr + (sa_len.value * sa_type_alloc_size)
g.nop(comment='Calculate address at which to stop looping')
g.local.get(fold_adr)
g.i32.const(sa_len.value * sa_type_alloc_size)
g.i32.add()
g.local.set(fold_stop)
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_alloc_size)
g.i32.add()
g.local.set(fold_adr)
with g.loop(params=[res_type_mtyp], result=res_type_mtyp):
# Stack: [b] -> [b]
g.nop(comment='Apply function call')
g.local.get(fold_adr)
g.add_statement(f'{sa_type_mtyp}.load')
g.local.get(fold_func)
g.add_statement(f'call_indirect (param {res_type_mtyp} {sa_type_mtyp}) (result {res_type_mtyp})')
# Stack: [b] -> [b] ; fold_adr = fold_adr + sa_type_alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_alloc_size)
g.i32.add()
g.local.tee(fold_adr)
# loop if adr > stop
# Stack: [b] -> [b]
g.nop(comment='Check if address exceeds array bounds')
g.local.get(fold_stop)
g.i32.lt_u()
g.br_if(0)
# else: just one value, don't need to loop
# Stack: [b]
def dynamic_array_foldr(g: Generator, tvl: TypeVariableLookup) -> None:
raise NotImplementedError
def static_array_foldr(g: Generator, tvl: TypeVariableLookup) -> None:
tv_map, tc_map = tvl
tvn_map = {
x.name: y
for x, y in tv_map.items()
}
sa_type = tvn_map['a']
sa_len = tvn_map['a*']
res_type = tvn_map['b']
assert isinstance(sa_type, Type3)
assert isinstance(sa_len, IntType3)
assert isinstance(res_type, Type3)
# 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',
}
mtyp_f_map: dict[str, type[VarType_Base]] = {
'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,
}
# By default, constructed types are passed as pointers
sa_type_mtyp = mtyp_map.get(sa_type.name, 'i32')
sa_type_alloc_size = type_var_size_map.get(sa_type.name, 4)
res_type_mtyp = mtyp_map.get(res_type.name, 'i32')
res_type_mtyp_f = mtyp_f_map[res_type_mtyp]
# Definitions
fold_adr = g.temp_var(i32('fold_adr'))
fold_stop = g.temp_var(i32('fold_stop'))
fold_tmp = g.temp_var(res_type_mtyp_f('fold_tmp'))
fold_func = g.temp_var(i32('fold_func'))
with g.block(params=['i32', res_type_mtyp, 'i32'], result=res_type_mtyp, comment=f'foldr a={sa_type.name} a*={sa_len.value} b={res_type.name}'):
# Stack: [fn*, b, sa*] -> [fn*, b] ; fold_adr=fn*, fold_tmp=b, fold_func=fn*
g.local.set(fold_adr)
# Stack: [fn*, b] -> [fn*]
g.local.set(fold_tmp)
# Stack: [fn*] -> []
g.local.set(fold_func)
if sa_len.value < 1:
g.local.get(fold_tmp)
return
# Stack: [] -> [] ; fold_stop=fold_adr
g.nop(comment='Calculate address at which to stop looping')
g.local.get(fold_adr)
g.local.set(fold_stop)
# Stack: [] -> [] ; fold_adr=fold_adr + (sa_len.value - 1) * sa_type_alloc_size
g.nop(comment='Calculate address at which to stop looping')
g.local.get(fold_adr)
g.i32.const((sa_len.value - 1) * sa_type_alloc_size)
g.i32.add()
g.local.set(fold_adr)
# Stack: [] -> [b]
g.nop(comment='Get the init value and first array value as starting point')
g.local.get(fold_adr)
g.add_statement(f'{sa_type_mtyp}.load')
g.local.get(fold_tmp)
g.local.get(fold_func)
g.add_statement(f'call_indirect (param {sa_type_mtyp} {res_type_mtyp}) (result {res_type_mtyp})')
if sa_len.value > 1:
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_alloc_size)
g.i32.sub()
g.local.set(fold_adr)
with g.loop(params=[res_type_mtyp], result=res_type_mtyp):
g.nop(comment='Apply function call')
# Stack [b] since we don't have proper stack switching opcodes
# Stack: [b] -> []
g.local.set(fold_tmp)
# Stack: [] -> [a]
g.local.get(fold_adr)
g.add_statement(f'{sa_type_mtyp}.load')
# Stack [a] -> [a, b]
g.local.get(fold_tmp)
# Stack [a, b] -> [b]
g.local.get(fold_func)
g.add_statement(f'call_indirect (param {sa_type_mtyp} {res_type_mtyp}) (result {res_type_mtyp})')
# Stack: [b] -> [b] ; fold_adr = fold_adr - sa_type_alloc_size
g.nop(comment='Calculate address of the next value')
g.local.get(fold_adr)
g.i32.const(sa_type_alloc_size)
g.i32.sub()
g.local.tee(fold_adr)
# loop if adr >= stop
# Stack: [b] -> [b]
g.nop(comment='Check if address exceeds array bounds')
g.local.get(fold_stop)
g.i32.ge_u()
g.br_if(0)
# else: just one value, don't need to loop
# Stack: [b]

View File

@ -15,6 +15,7 @@ from .types import (
Type3,
TypeApplication_Nullary,
TypeApplication_Struct,
TypeApplication_Type,
TypeApplication_TypeInt,
TypeApplication_TypeStar,
TypeConstructor_Base,
@ -182,7 +183,7 @@ class SameTypeArgumentConstraint(ConstraintBase):
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:
if self.tc_var.resolve_as is None:
return RequireTypeSubstitutes()
tc_typ = self.tc_var.resolve_as
@ -199,12 +200,16 @@ class SameTypeArgumentConstraint(ConstraintBase):
# So we can let the MustImplementTypeClassConstraint handle it.
return None
if isinstance(tc_typ.application, TypeApplication_Type):
return [SameTypeConstraint(
tc_typ.application.arguments[0],
self.arg_var,
comment=self.comment,
)]
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(tc_typ.application, TypeApplication_TypeInt):
if tc_typ.application.arguments[0] == arg_typ:
return None
return [SameTypeConstraint(
tc_typ.application.arguments[0],
self.arg_var,
@ -292,6 +297,14 @@ class TupleMatchConstraint(ConstraintBase):
self.exp_type = exp_type
self.args = list(args)
def _generate_dynamic_array(self, sa_args: tuple[Type3]) -> CheckResult:
sa_type, = sa_args
return [
SameTypeConstraint(arg, sa_type)
for arg in self.args
]
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
sa_type, sa_len = sa_args
@ -313,6 +326,7 @@ class TupleMatchConstraint(ConstraintBase):
]
GENERATE_ROUTER = TypeApplicationRouter['TupleMatchConstraint', CheckResult]()
GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array)
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
@ -336,14 +350,10 @@ class MustImplementTypeClassConstraint(ConstraintBase):
__slots__ = ('context', 'type_class3', 'types', )
context: Context
type_class3: Union[str, Type3Class]
type_class3: Type3Class
types: list[Type3OrPlaceholder]
DATA = {
'bytes': {'Foldable'},
}
def __init__(self, context: Context, type_class3: Union[str, Type3Class], typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None:
def __init__(self, context: Context, type_class3: Type3Class, typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
self.context = context
@ -363,7 +373,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
typ_list.append(typ)
continue
if isinstance(typ.application, (TypeApplication_TypeInt, TypeApplication_TypeStar)):
if isinstance(typ.application, (TypeApplication_Type, TypeApplication_TypeInt, TypeApplication_TypeStar)):
typ_list.append(typ.application.constructor)
continue
@ -371,13 +381,9 @@ class MustImplementTypeClassConstraint(ConstraintBase):
assert len(typ_list) == len(self.types)
if isinstance(self.type_class3, Type3Class):
key = (self.type_class3, tuple(typ_list), )
if key in self.context.type_class_instances_existing:
return None
else:
if self.type_class3 in self.__class__.DATA.get(typ_list[0].name, set()):
return None
typ_cls_name = self.type_class3 if isinstance(self.type_class3, str) else self.type_class3.name
typ_name_list = ' '.join(x.name for x in typ_list)
@ -420,6 +426,29 @@ class LiteralFitsConstraint(ConstraintBase):
self.type3 = type3
self.literal = literal
def _generate_dynamic_array(self, da_args: tuple[Type3]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
da_type, = da_args
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(da_type, y)
for y in self.literal.value
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(da_type, PlaceholderForType([y]))
for y in self.literal.value
)
return res
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
@ -501,6 +530,7 @@ class LiteralFitsConstraint(ConstraintBase):
return res
GENERATE_ROUTER = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]()
GENERATE_ROUTER.add(prelude.dynamic_array, _generate_dynamic_array)
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.struct, _generate_struct)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)

View File

@ -176,7 +176,10 @@ def _expression_function_call(
if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
raise NotImplementedError(sig_arg.application)
assert sig_arg.application.arguments in type_var_map # When does this happen?
if sig_arg.application.arguments not in type_var_map:
# e.g., len :: t a -> u32
# i.e. "a" does not matter at all
continue
yield SameTypeArgumentConstraint(
type_var_map[sig_arg],
@ -199,6 +202,10 @@ def _expression_function_call(
yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment)
continue
if isinstance(sig_part, FunctionArgument):
yield SameTypeConstraint(func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
continue
raise NotImplementedError(sig_part)
return
@ -262,19 +269,6 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType)
comment=f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
return
if isinstance(inp, ourlang.Fold):
base_phft = PlaceholderForType([inp.base])
iter_phft = PlaceholderForType([inp.iter])
yield from expression(ctx, inp.base, base_phft)
yield from expression(ctx, inp.iter, iter_phft)
yield SameTypeConstraint(inp.func.posonlyargs[0].type3, inp.func.returns_type3, base_phft, phft,
comment='foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b')
yield MustImplementTypeClassConstraint(ctx, 'Foldable', [iter_phft])
return
raise NotImplementedError(expression, inp)
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:

View File

@ -3,10 +3,17 @@ from typing import Any, Callable
from .functions import (
TypeConstructorVariable,
TypeVariable,
TypeVariableApplication_Nullary,
TypeVariableApplication_Unary,
)
from .typeclasses import Type3ClassArgs
from .types import KindArgument, Type3, TypeApplication_TypeInt, TypeConstructor_Base
from .types import (
KindArgument,
Type3,
TypeApplication_Type,
TypeApplication_TypeInt,
TypeConstructor_Base,
)
class NoRouteForTypeException(Exception):
@ -54,7 +61,10 @@ class TypeApplicationRouter[S, R]:
raise NoRouteForTypeException(arg0, typ)
TypeVariableLookup = dict[TypeVariable, tuple[KindArgument, ...]]
TypeVariableLookup = tuple[
dict[TypeVariable, KindArgument],
dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
]
class TypeClassArgsRouter[S, R]:
"""
@ -89,11 +99,12 @@ class TypeClassArgsRouter[S, R]:
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
key: list[Type3 | TypeConstructor_Base[Any]] = []
arguments: TypeVariableLookup = {}
arguments: TypeVariableLookup = (dict(tv_map), {}, )
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
arguments[0][tc_arg] = tv_map[tc_arg]
continue
for tvar, typ in tv_map.items():
@ -102,16 +113,30 @@ class TypeClassArgsRouter[S, R]:
continue
key.append(typ.application.constructor)
arguments[1][tc_arg] = typ.application.constructor
if isinstance(tvar.application, TypeVariableApplication_Unary):
if isinstance(typ.application, TypeApplication_Type):
da_type, = typ.application.arguments
sa_type_tv = tvar.application.arguments
arguments[0][sa_type_tv] = da_type
continue
# 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
sa_type, sa_len = typ.application.arguments
sa_type_tv = tvar.application.arguments
sa_len_tv = TypeVariable(sa_type_tv.name + '*', TypeVariableApplication_Nullary(None, None))
arguments[0][sa_type_tv] = sa_type
arguments[0][sa_len_tv] = sa_len
continue
raise NotImplementedError(tvar.application, typ.application)
continue
t_helper = self.data.get(tuple(key))
if t_helper is not None:
return t_helper(arg0, arguments)

View File

@ -1,4 +1,4 @@
from typing import Dict, Iterable, List, Mapping, Optional, Union
from typing import Dict, Iterable, List, Mapping, Optional
from .functions import (
Constraint_TypeClassInstanceExists,
@ -42,8 +42,8 @@ class Type3Class:
self,
name: str,
args: Type3ClassArgs,
methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
methods: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
operators: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
inherited_classes: Optional[List['Type3Class']] = None,
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
) -> None:
@ -71,19 +71,23 @@ class Type3Class:
return self.name
def _create_signature(
method_arg_list: Iterable[Type3 | TypeVariable],
method_arg_list: Iterable[Type3 | TypeVariable | list[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] = []
signature_args: list[Type3 | TypeVariable | 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, list):
signature_args.append(method_arg)
continue
if isinstance(method_arg, TypeVariable):
type_constructor = method_arg.application.constructor
if type_constructor is None:

View File

@ -195,6 +195,26 @@ class TypeConstructor_Base[T]:
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.name!r}, ...)'
class TypeConstructor_Type(TypeConstructor_Base[Tuple[Type3]]):
"""
Base class type constructors of kind: * -> *
Notably, static array.
"""
__slots__ = ()
def make_application(self, key: Tuple[Type3]) -> 'TypeApplication_Type':
return TypeApplication_Type(self, key)
def make_name(self, key: Tuple[Type3]) -> str:
return f'{self.name} {key[0].name} '
def __call__(self, arg0: Type3) -> Type3:
return self.construct((arg0, ))
class TypeApplication_Type(TypeApplication_Base[TypeConstructor_Type, Tuple[Type3]]):
pass
class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]):
"""
Base class type constructors of kind: * -> Int -> *
@ -231,6 +251,13 @@ class TypeConstructor_TypeStar(TypeConstructor_Base[Tuple[Type3, ...]]):
class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]):
pass
class TypeConstructor_DynamicArray(TypeConstructor_Type):
def make_name(self, key: Tuple[Type3]) -> str:
if 'u8' == key[0].name:
return 'bytes'
return f'{key[0].name}[...]'
class TypeConstructor_StaticArray(TypeConstructor_TypeInt):
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
return f'{key[0].name}[{key[1].value}]'

View File

@ -170,11 +170,12 @@ class Generator_Local:
self.generator.add_statement('local.tee', variable.name_ref, comment=comment)
class GeneratorBlock:
def __init__(self, generator: 'Generator', name: str, params: Iterable[str] = (), result: str | None = None) -> None:
def __init__(self, generator: 'Generator', name: str, params: Iterable[str] = (), result: str | None = None, comment: str | None = None) -> None:
self.generator = generator
self.name = name
self.params = params
self.result = result
self.comment = comment
def __enter__(self) -> None:
stmt = self.name
@ -186,7 +187,7 @@ class GeneratorBlock:
if self.result:
stmt = f'{stmt} (result {self.result})'
self.generator.add_statement(stmt)
self.generator.add_statement(stmt, comment=self.comment)
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
if not exc_type:
@ -208,7 +209,7 @@ class Generator:
# 2.4.5 Control Instructions
self.nop = functools.partial(self.add_statement, 'nop')
self.unreachable = functools.partial(self.add_statement, 'unreachable')
# block
self.block = functools.partial(GeneratorBlock, self, 'block')
self.loop = functools.partial(GeneratorBlock, self, 'loop')
self.if_ = functools.partial(GeneratorBlock, self, 'if')
# br

View File

@ -152,6 +152,25 @@ def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> in
runner.interpreter_write_memory(adr + 4, val)
return adr
def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_args: tuple[type3types.Type3]) -> int:
runner, val = attrs
da_type, = da_args
if not isinstance(val, tuple):
raise InvalidArgumentException(f'Expected tuple; got {val!r} instead')
alloc_size = 4 + len(val) * calculate_alloc_size(da_type, True)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type int
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
offset = adr
offset += _write_memory_stored_value(runner, offset, prelude.u32, len(val))
for val_el_val in val:
offset += _write_memory_stored_value(runner, offset, da_type, val_el_val)
return adr
def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int:
runner, val = attrs
@ -211,6 +230,7 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args
ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]()
ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.dynamic_array, _allocate_memory_stored_dynamic_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.static_array, _allocate_memory_stored_static_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple)
@ -325,6 +345,26 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator
yield all_bytes[offset:offset + size]
offset += size
def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_args: tuple[type3types.Type3]) -> Any:
runner, adr = attrs
da_type, = da_args
sys.stderr.write(f'Reading 0x{adr:08x} {da_type:s}[...]\n')
read_bytes = runner.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
adr += 4
arg_size_1 = calculate_alloc_size(da_type, is_member=True)
arg_sizes = [arg_size_1 for _ in range(array_len)] # _split_read_bytes requires one arg per value
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple(
_unpack(runner, da_type, arg_bytes)
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
)
def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> Any:
runner, adr = attrs
sub_typ, len_typ = sa_args
@ -379,6 +419,7 @@ def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tup
LOAD_FROM_ADDRESS_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]()
LOAD_FROM_ADDRESS_ROUTER.add_n(prelude.bytes_, _load_bytes_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.dynamic_array, _load_dynamic_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.static_array, _load_static_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.struct, _load_struct_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.tuple_, _load_tuple_from_address)

View File

@ -120,6 +120,8 @@ class RunnerWasmtime(RunnerBase):
if vartype is int:
params.append(wasmtime.ValType.i32())
elif vartype is float:
params.append(wasmtime.ValType.f32())
else:
raise NotImplementedError
@ -128,6 +130,8 @@ class RunnerWasmtime(RunnerBase):
pass # No return value
elif func.__annotations__['return'] is int:
results.append(wasmtime.ValType.i32())
elif func.__annotations__['return'] is float:
results.append(wasmtime.ValType.f32())
else:
raise NotImplementedError('Return type', func.__annotations__['return'])

View File

@ -60,7 +60,7 @@ CONSTANT: (u32, ) = $VAL0
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error(
'Tuple element count mismatch',
'The given literal must fit the expected type',
@ -113,7 +113,7 @@ def testEntry() -> i32:
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error(
'Mismatch between applied types argument count',
'The type of a tuple is a combination of its members',
@ -175,7 +175,7 @@ def testEntry() -> i32:
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'):
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value returned from function constant should match its return type',
@ -226,7 +226,7 @@ def select(x: $TYPE) -> (u32, ):
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'):
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value returned from function select should match its return type',
@ -273,7 +273,7 @@ def testEntry() -> i32:
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_'):
expect_type_error(
'Mismatch between applied types argument count',
# FIXME: Shouldn't this be the same as for the else statement?
@ -330,7 +330,7 @@ def testEntry() -> i32:
```
```py
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'):
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('dynamic_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value passed to argument 0 of function helper should match the type of that argument',

View File

@ -0,0 +1,5 @@
{
"TYPE_NAME": "dynamic_array_u64",
"TYPE": "u64[...]",
"VAL0": "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, )"
}

View File

@ -1,61 +0,0 @@
import pytest
from ..helpers import Suite
@pytest.mark.integration_test
def test_foldl_1():
code_py = """
def u8_or(l: u8, r: u8) -> u8:
return l | r
@exported
def testEntry(b: bytes) -> u8:
return foldl(u8_or, 128, b)
"""
suite = Suite(code_py)
result = suite.run_code(b'')
assert 128 == result.returned_value
result = suite.run_code(b'\x80')
assert 128 == result.returned_value
result = suite.run_code(b'\x80\x40')
assert 192 == result.returned_value
result = suite.run_code(b'\x80\x40\x20\x10')
assert 240 == result.returned_value
result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01')
assert 255 == result.returned_value
@pytest.mark.integration_test
def test_foldl_2():
code_py = """
def xor(l: u8, r: u8) -> u8:
return l ^ r
@exported
def testEntry(a: bytes, b: bytes) -> u8:
return foldl(xor, 0, a) ^ foldl(xor, 0, b)
"""
suite = Suite(code_py)
result = suite.run_code(b'\x55\x0F', b'\x33\x80')
assert 233 == result.returned_value
@pytest.mark.integration_test
def test_foldl_3():
code_py = """
def xor(l: u32, r: u8) -> u32:
return l ^ extend(r)
@exported
def testEntry(a: bytes) -> u32:
return foldl(xor, 0, a)
"""
suite = Suite(code_py)
result = suite.run_code(b'\x55\x0F\x33\x80')
assert 233 == result.returned_value

View File

@ -36,6 +36,142 @@ def testEntry(x: Foo[4]) -> Foo:
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('length', [1, 5, 13])
@pytest.mark.parametrize('direction', ['foldl', 'foldr'])
def test_foldable_foldl_foldr_size(direction, length):
code_py = f"""
def u64_add(l: u64, r: u64) -> u64:
return l + r
@exported
def testEntry(b: u64[{length}]) -> u64:
return {direction}(u64_add, 100, b)
"""
suite = Suite(code_py)
in_put = tuple(range(1, length + 1))
result = suite.run_code(in_put)
assert (100 + sum(in_put)) == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('direction', ['foldl', 'foldr'])
def test_foldable_foldl_foldr_compounded_type(direction):
code_py = f"""
def combine_foldl(b: u64, a: (u32, u32, )) -> u64:
return extend(a[0] * a[1]) + b
def combine_foldr(a: (u32, u32, ), b: u64) -> u64:
return extend(a[0] * a[1]) + b
@exported
def testEntry(b: (u32, u32, )[3]) -> u64:
return {direction}(combine_{direction}, 10000, b)
"""
suite = Suite(code_py)
result = suite.run_code(((2, 5), (25, 4), (125, 8)))
assert 11110 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('in_put, direction, exp_result', [
([], 'foldl', 0, ),
([], 'foldr', 0, ),
([1], 'foldl', -1, ),
([1], 'foldr', 1, ),
([1,2], 'foldl', -3, ),
([1,2], 'foldr', -1, ),
([1,2,3], 'foldl', -6, ),
([1,2,3], 'foldr', 2, ),
([1,2,3,4], 'foldl', -10, ),
([1,2,3,4], 'foldr', -2, ),
([1,2,3,4,5], 'foldl', -15, ),
([1,2,3,4,5], 'foldr', 3, ),
([1,2,3,4,5,6], 'foldl', -21, ),
([1,2,3,4,5,6], 'foldr', -3, ),
([1,2,3,4,5,6,7], 'foldl', -28, ),
([1,2,3,4,5,6,7], 'foldr', 4, ),
([1,2,3,4,5,6,7,8], 'foldl', -36, ),
([1,2,3,4,5,6,7,8], 'foldr', -4, ),
])
def test_foldable_foldl_foldr_result(direction, in_put, exp_result):
# See https://stackoverflow.com/a/13280185
code_py = f"""
def i32_sub(l: i32, r: i32) -> i32:
return l - r
@exported
def testEntry(b: i32[{len(in_put)}]) -> i32:
return {direction}(i32_sub, 0, b)
"""
suite = Suite(code_py)
result = suite.run_code(tuple(in_put))
assert exp_result == result.returned_value
@pytest.mark.integration_test
def test_foldable_foldl_bytes():
code_py = """
def u8_or(l: u8, r: u8) -> u8:
return l | r
@exported
def testEntry(b: bytes) -> u8:
return foldl(u8_or, 128, b)
"""
suite = Suite(code_py)
result = suite.run_code(b'')
assert 128 == result.returned_value
result = suite.run_code(b'\x80')
assert 128 == result.returned_value
result = suite.run_code(b'\x80\x40')
assert 192 == result.returned_value
result = suite.run_code(b'\x80\x40\x20\x10')
assert 240 == result.returned_value
result = suite.run_code(b'\x80\x40\x20\x10\x08\x04\x02\x01')
assert 255 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('in_typ', ['i8', 'i8[3]'])
def test_foldable_argument_must_be_a_function(in_typ):
code_py = f"""
@exported
def testEntry(x: {in_typ}, y: i32, z: i64[3]) -> i32:
return foldl(x, y, z)
"""
r_in_typ = in_typ.replace('[', '\\[').replace(']', '\\]')
with pytest.raises(Type3Exception, match=f'{r_in_typ} must be a function instead'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_foldable_argument_must_be_right_function():
code_py = """
def foo(l: i32, r: i64) -> i64:
return extend(l) + r
@exported
def testEntry(i: i64, l: i64[3]) -> i64:
return foldr(foo, i, l)
"""
with pytest.raises(Type3Exception, match=r'Callable\[i64, i64, i64\] must be Callable\[i32, i64, i64\] instead'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_foldable_invalid_return_type():

View File

@ -6,7 +6,7 @@ from ..helpers import Suite
@pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put, exp_result', [
('bytes', b'Hello, world!', 13),
# ('u8[4]', (1, 2, 3, 4), 4), # FIXME: Implement this
('u8[4]', (1, 2, 3, 4), 4),
])
def test_len(type_, in_put, exp_result):
code_py = f"""