Compare commits
2 Commits
3be4599fc1
...
e8e7acc102
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8e7acc102 | ||
|
|
83186cce78 |
2
TODO.md
2
TODO.md
@ -30,3 +30,5 @@
|
|||||||
- Functions don't seem to be a thing on typing level yet?
|
- Functions don't seem to be a thing on typing level yet?
|
||||||
- Related to the FIXME in phasm_type3?
|
- Related to the FIXME in phasm_type3?
|
||||||
- Type constuctor should also be able to constuct placeholders - somehow.
|
- Type constuctor should also be able to constuct placeholders - somehow.
|
||||||
|
|
||||||
|
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
|
||||||
|
|||||||
@ -105,10 +105,6 @@ def expression(inp: ourlang.Expression) -> str:
|
|||||||
if isinstance(inp, ourlang.AccessStructMember):
|
if isinstance(inp, ourlang.AccessStructMember):
|
||||||
return f'{expression(inp.varref)}.{inp.member}'
|
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)
|
raise NotImplementedError(expression, inp)
|
||||||
|
|
||||||
def statement(inp: ourlang.Statement) -> Statements:
|
def statement(inp: ourlang.Statement) -> Statements:
|
||||||
|
|||||||
@ -4,19 +4,21 @@ This module contains the code to convert parsed Ourlang into WebAssembly code
|
|||||||
import struct
|
import struct
|
||||||
from typing import List, Optional
|
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 .runtime import calculate_alloc_size, calculate_member_offset
|
||||||
from .stdlib import alloc as stdlib_alloc
|
from .stdlib import alloc as stdlib_alloc
|
||||||
from .stdlib import types as stdlib_types
|
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.routers import NoRouteForTypeException, TypeApplicationRouter
|
||||||
from .type3.typeclasses import Type3ClassMethod
|
from .type3.typeclasses import Type3ClassMethod
|
||||||
from .type3.types import (
|
from .type3.types import (
|
||||||
IntType3,
|
IntType3,
|
||||||
Type3,
|
Type3,
|
||||||
TypeApplication_Struct,
|
TypeApplication_Struct,
|
||||||
|
TypeApplication_Type,
|
||||||
TypeApplication_TypeInt,
|
TypeApplication_TypeInt,
|
||||||
TypeApplication_TypeStar,
|
TypeApplication_TypeStar,
|
||||||
|
TypeConstructor_DynamicArray,
|
||||||
TypeConstructor_Function,
|
TypeConstructor_Function,
|
||||||
TypeConstructor_StaticArray,
|
TypeConstructor_StaticArray,
|
||||||
TypeConstructor_Tuple,
|
TypeConstructor_Tuple,
|
||||||
@ -109,12 +111,25 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Tu
|
|||||||
|
|
||||||
args: tuple[Type3, ...]
|
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,
|
# Possibly paranoid assert. If we have a future variadic type,
|
||||||
# does it also do this tuple instantation like this?
|
# does it also do this tuple instantation like this?
|
||||||
assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple)
|
assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple)
|
||||||
|
|
||||||
args = inp.type3.application.arguments
|
args = inp.type3.application.arguments
|
||||||
|
alloc_size = calculate_alloc_size(inp.type3, is_member=False)
|
||||||
elif isinstance(inp.type3.application, TypeApplication_TypeInt):
|
elif isinstance(inp.type3.application, TypeApplication_TypeInt):
|
||||||
# Possibly paranoid assert. If we have a future type of kind * -> Int -> *,
|
# Possibly paranoid assert. If we have a future type of kind * -> Int -> *,
|
||||||
# does it also do this tuple instantation like this?
|
# 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
|
sa_type, sa_len = inp.type3.application.arguments
|
||||||
|
|
||||||
args = tuple(sa_type for _ in range(sa_len.value))
|
args = tuple(sa_type for _ in range(sa_len.value))
|
||||||
|
alloc_size = calculate_alloc_size(inp.type3, is_member=False)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('tuple_instantiation', inp.type3)
|
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})')
|
wgn.add_statement('nop', comment=f'{tmp_var.name} := ({comment_elements})')
|
||||||
|
|
||||||
# Allocated the required amounts of bytes in memory
|
# 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.call(stdlib_alloc.__alloc__)
|
||||||
wgn.local.set(tmp_var)
|
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
|
# 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):
|
for element, exp_type3 in zip(inp.elements, args, strict=True):
|
||||||
assert element.type3 == exp_type3
|
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
|
type_var_map[type_var] = arg_expr.type3
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if isinstance(type_var, FunctionArgument):
|
||||||
|
# Fixed type, not part of the lookup requirements
|
||||||
|
continue
|
||||||
|
|
||||||
raise NotImplementedError(type_var, arg_expr.type3)
|
raise NotImplementedError(type_var, arg_expr.type3)
|
||||||
|
|
||||||
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator]
|
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
|
type_var_map[type_var] = arg_expr.type3
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if isinstance(type_var, FunctionArgument):
|
||||||
|
# Fixed type, not part of the lookup requirements
|
||||||
|
continue
|
||||||
|
|
||||||
raise NotImplementedError(type_var, arg_expr.type3)
|
raise NotImplementedError(type_var, arg_expr.type3)
|
||||||
|
|
||||||
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function]
|
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function]
|
||||||
@ -401,90 +430,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.Expression)
|
|||||||
)))
|
)))
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(inp, ourlang.Fold):
|
|
||||||
expression_fold(wgn, mod, inp)
|
|
||||||
return
|
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp)
|
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:
|
def statement_return(wgn: WasmGenerator, mod: ourlang.Module, inp: ourlang.StatementReturn) -> None:
|
||||||
"""
|
"""
|
||||||
Compile: Return statement
|
Compile: Return statement
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
Contains the syntax tree for ourlang
|
Contains the syntax tree for ourlang
|
||||||
"""
|
"""
|
||||||
import enum
|
|
||||||
from typing import Dict, Iterable, List, Optional, Union
|
from typing import Dict, Iterable, List, Optional, Union
|
||||||
|
|
||||||
from . import prelude
|
from . import prelude
|
||||||
@ -219,36 +218,6 @@ class AccessStructMember(Expression):
|
|||||||
self.struct_type3 = struct_type3
|
self.struct_type3 = struct_type3
|
||||||
self.member = member
|
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:
|
class Statement:
|
||||||
"""
|
"""
|
||||||
A statement within a function
|
A statement within a function
|
||||||
@ -301,6 +270,9 @@ class FunctionParam:
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.type3 = type3
|
self.type3 = type3
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'FunctionParam({self.name!r}, {self.type3!r})'
|
||||||
|
|
||||||
class Function:
|
class Function:
|
||||||
"""
|
"""
|
||||||
A function processes input and produces output
|
A function processes input and produces output
|
||||||
|
|||||||
@ -14,7 +14,6 @@ from .ourlang import (
|
|||||||
ConstantStruct,
|
ConstantStruct,
|
||||||
ConstantTuple,
|
ConstantTuple,
|
||||||
Expression,
|
Expression,
|
||||||
Fold,
|
|
||||||
Function,
|
Function,
|
||||||
FunctionCall,
|
FunctionCall,
|
||||||
FunctionParam,
|
FunctionParam,
|
||||||
@ -467,7 +466,7 @@ class OurVisitor:
|
|||||||
|
|
||||||
raise NotImplementedError(f'{node} as expr in FunctionDef')
|
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:
|
if node.keywords:
|
||||||
_raise_static_error(node, 'Keyword calling not supported') # Yet?
|
_raise_static_error(node, 'Keyword calling not supported') # Yet?
|
||||||
|
|
||||||
@ -480,28 +479,6 @@ class OurVisitor:
|
|||||||
|
|
||||||
if node.func.id in PRELUDE_METHODS:
|
if node.func.id in PRELUDE_METHODS:
|
||||||
func = PRELUDE_METHODS[node.func.id]
|
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:
|
elif node.func.id in our_locals:
|
||||||
func = our_locals[node.func.id]
|
func = our_locals[node.func.id]
|
||||||
else:
|
else:
|
||||||
@ -655,8 +632,15 @@ class OurVisitor:
|
|||||||
|
|
||||||
if isinstance(node.slice, ast.Slice):
|
if isinstance(node.slice, ast.Slice):
|
||||||
_raise_static_error(node, 'Must subscript using an index')
|
_raise_static_error(node, 'Must subscript using an index')
|
||||||
|
|
||||||
if not isinstance(node.slice, ast.Constant):
|
if not isinstance(node.slice, ast.Constant):
|
||||||
_raise_static_error(node, 'Must subscript using a constant index')
|
_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):
|
if not isinstance(node.slice.value, int):
|
||||||
_raise_static_error(node, 'Must subscript using a constant integer index')
|
_raise_static_error(node, 'Must subscript using a constant integer index')
|
||||||
if not isinstance(node.ctx, ast.Load):
|
if not isinstance(node.ctx, ast.Load):
|
||||||
|
|||||||
@ -20,6 +20,7 @@ from ..type3.types import (
|
|||||||
Type3,
|
Type3,
|
||||||
TypeApplication_Nullary,
|
TypeApplication_Nullary,
|
||||||
TypeConstructor_Base,
|
TypeConstructor_Base,
|
||||||
|
TypeConstructor_DynamicArray,
|
||||||
TypeConstructor_Function,
|
TypeConstructor_Function,
|
||||||
TypeConstructor_StaticArray,
|
TypeConstructor_StaticArray,
|
||||||
TypeConstructor_Struct,
|
TypeConstructor_Struct,
|
||||||
@ -158,9 +159,15 @@ f64 = Type3('f64', TypeApplication_Nullary(None, None))
|
|||||||
A 32-bits IEEE 754 float, of 64 bits width.
|
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:
|
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)
|
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 two arguments. It has a compile time
|
||||||
|
determined length, and each argument is the same.
|
||||||
It should be applied with one argument. It has a runtime-dynamic length
|
|
||||||
of the same type repeated.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None:
|
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.
|
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))
|
a = TypeVariable('a', TypeVariableApplication_Nullary(None, None))
|
||||||
b = TypeVariable('b', 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.
|
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, static_array)
|
||||||
# instance_type_class(InternalPassAsPointer, tuple_)
|
# instance_type_class(InternalPassAsPointer, tuple_)
|
||||||
# instance_type_class(InternalPassAsPointer, struct)
|
# instance_type_class(InternalPassAsPointer, struct)
|
||||||
@ -537,12 +528,15 @@ instance_type_class(Floating, f64, methods={
|
|||||||
'sqrt': stdtypes.f64_floating_sqrt,
|
'sqrt': stdtypes.f64_floating_sqrt,
|
||||||
})
|
})
|
||||||
|
|
||||||
Sized_ = Type3Class('Sized', (a, ), methods={
|
Sized_ = Type3Class('Sized', (t, ), methods={
|
||||||
'len': [a, u32],
|
'len': [t(a), u32],
|
||||||
}, operators={}) # FIXME: Once we get type class families, add [] here
|
}, operators={}) # FIXME: Once we get type class families, add [] here
|
||||||
|
|
||||||
instance_type_class(Sized_, bytes_, methods={
|
instance_type_class(Sized_, dynamic_array, methods={
|
||||||
'len': stdtypes.bytes_sized_len,
|
'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={
|
Extendable = Type3Class('Extendable', (a, b, ), methods={
|
||||||
@ -587,14 +581,39 @@ instance_type_class(Promotable, f32, f64, methods={
|
|||||||
|
|
||||||
Foldable = Type3Class('Foldable', (t, ), methods={
|
Foldable = Type3Class('Foldable', (t, ), methods={
|
||||||
'sum': [t(a), a],
|
'sum': [t(a), a],
|
||||||
|
'foldl': [[b, a, b], b, t(a), b],
|
||||||
|
'foldr': [[a, b, b], b, t(a), b],
|
||||||
}, operators={}, additional_context={
|
}, operators={}, additional_context={
|
||||||
'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))],
|
'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={
|
instance_type_class(Foldable, static_array, methods={
|
||||||
'sum': stdtypes.static_array_sum,
|
'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 = {
|
PRELUDE_TYPE_CLASSES = {
|
||||||
'Eq': Eq,
|
'Eq': Eq,
|
||||||
'Ord': Ord,
|
'Ord': Ord,
|
||||||
|
|||||||
@ -4,7 +4,9 @@ stdlib: Standard types that are not wasm primitives
|
|||||||
from phasm.stdlib import alloc
|
from phasm.stdlib import alloc
|
||||||
from phasm.type3.routers import TypeVariableLookup
|
from phasm.type3.routers import TypeVariableLookup
|
||||||
from phasm.type3.types import IntType3, Type3
|
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_i32 as i32
|
||||||
from phasm.wasmgenerator import VarType_i64 as i64
|
from phasm.wasmgenerator import VarType_i64 as i64
|
||||||
|
|
||||||
@ -1006,11 +1008,23 @@ def f64_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None:
|
|||||||
## ###
|
## ###
|
||||||
## Class Sized
|
## 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
|
del tv_map
|
||||||
# The length is stored in the first 4 bytes
|
# The length is stored in the first 4 bytes
|
||||||
g.i32.load()
|
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
|
## Extendable
|
||||||
|
|
||||||
@ -1081,9 +1095,23 @@ def f32_f64_demote(g: Generator, tv_map: TypeVariableLookup) -> None:
|
|||||||
del tv_map
|
del tv_map
|
||||||
g.f32.demote_f64()
|
g.f32.demote_f64()
|
||||||
|
|
||||||
def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None:
|
## ###
|
||||||
assert len(tv_map) == 1
|
## Foldable
|
||||||
sa_type, sa_len = next(iter(tv_map.values()))
|
|
||||||
|
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_type, Type3)
|
||||||
assert isinstance(sa_len, IntType3)
|
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.nop(comment='Add array value')
|
||||||
g.local.get(sum_adr)
|
g.local.get(sum_adr)
|
||||||
g.add_statement(f'{sa_type_mtyp}.load')
|
g.add_statement(f'{sa_type_mtyp}.load')
|
||||||
sa_type_add_gen(g, {})
|
sa_type_add_gen(g, ({}, {}, ))
|
||||||
|
|
||||||
# adr = adr + sa_type_alloc_size
|
# adr = adr + sa_type_alloc_size
|
||||||
# Stack: [sum] -> [sum]
|
# 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}]')
|
g.nop(comment=f'Completed sum for {sa_type.name}[{sa_len.value}]')
|
||||||
# End result: [sum]
|
# 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]
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from .types import (
|
|||||||
Type3,
|
Type3,
|
||||||
TypeApplication_Nullary,
|
TypeApplication_Nullary,
|
||||||
TypeApplication_Struct,
|
TypeApplication_Struct,
|
||||||
|
TypeApplication_Type,
|
||||||
TypeApplication_TypeInt,
|
TypeApplication_TypeInt,
|
||||||
TypeApplication_TypeStar,
|
TypeApplication_TypeStar,
|
||||||
TypeConstructor_Base,
|
TypeConstructor_Base,
|
||||||
@ -182,7 +183,7 @@ class SameTypeArgumentConstraint(ConstraintBase):
|
|||||||
self.arg_var = arg_var
|
self.arg_var = arg_var
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
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()
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
tc_typ = self.tc_var.resolve_as
|
tc_typ = self.tc_var.resolve_as
|
||||||
@ -199,12 +200,16 @@ class SameTypeArgumentConstraint(ConstraintBase):
|
|||||||
# So we can let the MustImplementTypeClassConstraint handle it.
|
# So we can let the MustImplementTypeClassConstraint handle it.
|
||||||
return None
|
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
|
# FIXME: This feels sketchy. Shouldn't the type variable
|
||||||
# have the exact same number as arguments?
|
# have the exact same number as arguments?
|
||||||
if isinstance(tc_typ.application, TypeApplication_TypeInt):
|
if isinstance(tc_typ.application, TypeApplication_TypeInt):
|
||||||
if tc_typ.application.arguments[0] == arg_typ:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return [SameTypeConstraint(
|
return [SameTypeConstraint(
|
||||||
tc_typ.application.arguments[0],
|
tc_typ.application.arguments[0],
|
||||||
self.arg_var,
|
self.arg_var,
|
||||||
@ -292,6 +297,14 @@ class TupleMatchConstraint(ConstraintBase):
|
|||||||
self.exp_type = exp_type
|
self.exp_type = exp_type
|
||||||
self.args = list(args)
|
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:
|
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
|
||||||
sa_type, sa_len = sa_args
|
sa_type, sa_len = sa_args
|
||||||
|
|
||||||
@ -313,6 +326,7 @@ class TupleMatchConstraint(ConstraintBase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
GENERATE_ROUTER = TypeApplicationRouter['TupleMatchConstraint', CheckResult]()
|
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.static_array, _generate_static_array)
|
||||||
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
|
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
|
||||||
|
|
||||||
@ -336,14 +350,10 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
__slots__ = ('context', 'type_class3', 'types', )
|
__slots__ = ('context', 'type_class3', 'types', )
|
||||||
|
|
||||||
context: Context
|
context: Context
|
||||||
type_class3: Union[str, Type3Class]
|
type_class3: Type3Class
|
||||||
types: list[Type3OrPlaceholder]
|
types: list[Type3OrPlaceholder]
|
||||||
|
|
||||||
DATA = {
|
def __init__(self, context: Context, type_class3: Type3Class, typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None:
|
||||||
'bytes': {'Foldable'},
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, context: Context, type_class3: Union[str, Type3Class], typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None:
|
|
||||||
super().__init__(comment=comment)
|
super().__init__(comment=comment)
|
||||||
|
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -363,7 +373,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
typ_list.append(typ)
|
typ_list.append(typ)
|
||||||
continue
|
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)
|
typ_list.append(typ.application.constructor)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -371,13 +381,9 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
|
|
||||||
assert len(typ_list) == len(self.types)
|
assert len(typ_list) == len(self.types)
|
||||||
|
|
||||||
if isinstance(self.type_class3, Type3Class):
|
key = (self.type_class3, tuple(typ_list), )
|
||||||
key = (self.type_class3, tuple(typ_list), )
|
if key in self.context.type_class_instances_existing:
|
||||||
if key in self.context.type_class_instances_existing:
|
return None
|
||||||
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_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)
|
typ_name_list = ' '.join(x.name for x in typ_list)
|
||||||
@ -420,6 +426,29 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
self.type3 = type3
|
self.type3 = type3
|
||||||
self.literal = literal
|
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:
|
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
|
||||||
if not isinstance(self.literal, ourlang.ConstantTuple):
|
if not isinstance(self.literal, ourlang.ConstantTuple):
|
||||||
return Error('Must be tuple', comment=self.comment)
|
return Error('Must be tuple', comment=self.comment)
|
||||||
@ -501,6 +530,7 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
GENERATE_ROUTER = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]()
|
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.static_array, _generate_static_array)
|
||||||
GENERATE_ROUTER.add(prelude.struct, _generate_struct)
|
GENERATE_ROUTER.add(prelude.struct, _generate_struct)
|
||||||
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
|
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
|
||||||
|
|||||||
@ -176,7 +176,10 @@ def _expression_function_call(
|
|||||||
if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
|
if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
|
||||||
raise NotImplementedError(sig_arg.application)
|
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(
|
yield SameTypeArgumentConstraint(
|
||||||
type_var_map[sig_arg],
|
type_var_map[sig_arg],
|
||||||
@ -199,6 +202,10 @@ def _expression_function_call(
|
|||||||
yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment)
|
yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if isinstance(sig_part, FunctionArgument):
|
||||||
|
yield SameTypeConstraint(func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
|
||||||
|
continue
|
||||||
|
|
||||||
raise NotImplementedError(sig_part)
|
raise NotImplementedError(sig_part)
|
||||||
return
|
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}')
|
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
|
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)
|
raise NotImplementedError(expression, inp)
|
||||||
|
|
||||||
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
|
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
|
||||||
|
|||||||
@ -3,10 +3,17 @@ from typing import Any, Callable
|
|||||||
from .functions import (
|
from .functions import (
|
||||||
TypeConstructorVariable,
|
TypeConstructorVariable,
|
||||||
TypeVariable,
|
TypeVariable,
|
||||||
|
TypeVariableApplication_Nullary,
|
||||||
TypeVariableApplication_Unary,
|
TypeVariableApplication_Unary,
|
||||||
)
|
)
|
||||||
from .typeclasses import Type3ClassArgs
|
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):
|
class NoRouteForTypeException(Exception):
|
||||||
@ -54,7 +61,10 @@ class TypeApplicationRouter[S, R]:
|
|||||||
|
|
||||||
raise NoRouteForTypeException(arg0, typ)
|
raise NoRouteForTypeException(arg0, typ)
|
||||||
|
|
||||||
TypeVariableLookup = dict[TypeVariable, tuple[KindArgument, ...]]
|
TypeVariableLookup = tuple[
|
||||||
|
dict[TypeVariable, KindArgument],
|
||||||
|
dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
|
||||||
|
]
|
||||||
|
|
||||||
class TypeClassArgsRouter[S, R]:
|
class TypeClassArgsRouter[S, R]:
|
||||||
"""
|
"""
|
||||||
@ -89,11 +99,12 @@ class TypeClassArgsRouter[S, R]:
|
|||||||
|
|
||||||
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
|
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
|
||||||
key: list[Type3 | TypeConstructor_Base[Any]] = []
|
key: list[Type3 | TypeConstructor_Base[Any]] = []
|
||||||
arguments: TypeVariableLookup = {}
|
arguments: TypeVariableLookup = (dict(tv_map), {}, )
|
||||||
|
|
||||||
for tc_arg in self.args:
|
for tc_arg in self.args:
|
||||||
if isinstance(tc_arg, TypeVariable):
|
if isinstance(tc_arg, TypeVariable):
|
||||||
key.append(tv_map[tc_arg])
|
key.append(tv_map[tc_arg])
|
||||||
|
arguments[0][tc_arg] = tv_map[tc_arg]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for tvar, typ in tv_map.items():
|
for tvar, typ in tv_map.items():
|
||||||
@ -102,16 +113,30 @@ class TypeClassArgsRouter[S, R]:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
key.append(typ.application.constructor)
|
key.append(typ.application.constructor)
|
||||||
|
arguments[1][tc_arg] = typ.application.constructor
|
||||||
|
|
||||||
if isinstance(tvar.application, TypeVariableApplication_Unary):
|
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
|
# FIXME: This feels sketchy. Shouldn't the type variable
|
||||||
# have the exact same number as arguments?
|
# have the exact same number as arguments?
|
||||||
if isinstance(typ.application, TypeApplication_TypeInt):
|
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
|
continue
|
||||||
|
|
||||||
raise NotImplementedError(tvar.application, typ.application)
|
raise NotImplementedError(tvar.application, typ.application)
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
t_helper = self.data.get(tuple(key))
|
t_helper = self.data.get(tuple(key))
|
||||||
if t_helper is not None:
|
if t_helper is not None:
|
||||||
return t_helper(arg0, arguments)
|
return t_helper(arg0, arguments)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, Iterable, List, Mapping, Optional, Union
|
from typing import Dict, Iterable, List, Mapping, Optional
|
||||||
|
|
||||||
from .functions import (
|
from .functions import (
|
||||||
Constraint_TypeClassInstanceExists,
|
Constraint_TypeClassInstanceExists,
|
||||||
@ -42,8 +42,8 @@ class Type3Class:
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
args: Type3ClassArgs,
|
args: Type3ClassArgs,
|
||||||
methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
|
methods: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
|
||||||
operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
|
operators: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
|
||||||
inherited_classes: Optional[List['Type3Class']] = None,
|
inherited_classes: Optional[List['Type3Class']] = None,
|
||||||
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
|
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -71,19 +71,23 @@ class Type3Class:
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def _create_signature(
|
def _create_signature(
|
||||||
method_arg_list: Iterable[Type3 | TypeVariable],
|
method_arg_list: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]],
|
||||||
type_class3: Type3Class,
|
type_class3: Type3Class,
|
||||||
) -> FunctionSignature:
|
) -> FunctionSignature:
|
||||||
context = TypeVariableContext()
|
context = TypeVariableContext()
|
||||||
if not isinstance(type_class3.args[0], TypeConstructorVariable):
|
if not isinstance(type_class3.args[0], TypeConstructorVariable):
|
||||||
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, type_class3.args))
|
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:
|
for method_arg in method_arg_list:
|
||||||
if isinstance(method_arg, Type3):
|
if isinstance(method_arg, Type3):
|
||||||
signature_args.append(method_arg)
|
signature_args.append(method_arg)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if isinstance(method_arg, list):
|
||||||
|
signature_args.append(method_arg)
|
||||||
|
continue
|
||||||
|
|
||||||
if isinstance(method_arg, TypeVariable):
|
if isinstance(method_arg, TypeVariable):
|
||||||
type_constructor = method_arg.application.constructor
|
type_constructor = method_arg.application.constructor
|
||||||
if type_constructor is None:
|
if type_constructor is None:
|
||||||
|
|||||||
@ -195,6 +195,26 @@ class TypeConstructor_Base[T]:
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'{self.__class__.__name__}({self.name!r}, ...)'
|
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]]):
|
class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]):
|
||||||
"""
|
"""
|
||||||
Base class type constructors of kind: * -> Int -> *
|
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, ...]]):
|
class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]):
|
||||||
pass
|
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):
|
class TypeConstructor_StaticArray(TypeConstructor_TypeInt):
|
||||||
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
|
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
|
||||||
return f'{key[0].name}[{key[1].value}]'
|
return f'{key[0].name}[{key[1].value}]'
|
||||||
|
|||||||
@ -170,11 +170,12 @@ class Generator_Local:
|
|||||||
self.generator.add_statement('local.tee', variable.name_ref, comment=comment)
|
self.generator.add_statement('local.tee', variable.name_ref, comment=comment)
|
||||||
|
|
||||||
class GeneratorBlock:
|
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.generator = generator
|
||||||
self.name = name
|
self.name = name
|
||||||
self.params = params
|
self.params = params
|
||||||
self.result = result
|
self.result = result
|
||||||
|
self.comment = comment
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
def __enter__(self) -> None:
|
||||||
stmt = self.name
|
stmt = self.name
|
||||||
@ -186,7 +187,7 @@ class GeneratorBlock:
|
|||||||
if self.result:
|
if self.result:
|
||||||
stmt = f'{stmt} (result {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:
|
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
||||||
if not exc_type:
|
if not exc_type:
|
||||||
@ -208,7 +209,7 @@ class Generator:
|
|||||||
# 2.4.5 Control Instructions
|
# 2.4.5 Control Instructions
|
||||||
self.nop = functools.partial(self.add_statement, 'nop')
|
self.nop = functools.partial(self.add_statement, 'nop')
|
||||||
self.unreachable = functools.partial(self.add_statement, 'unreachable')
|
self.unreachable = functools.partial(self.add_statement, 'unreachable')
|
||||||
# block
|
self.block = functools.partial(GeneratorBlock, self, 'block')
|
||||||
self.loop = functools.partial(GeneratorBlock, self, 'loop')
|
self.loop = functools.partial(GeneratorBlock, self, 'loop')
|
||||||
self.if_ = functools.partial(GeneratorBlock, self, 'if')
|
self.if_ = functools.partial(GeneratorBlock, self, 'if')
|
||||||
# br
|
# br
|
||||||
|
|||||||
@ -152,6 +152,25 @@ def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> in
|
|||||||
runner.interpreter_write_memory(adr + 4, val)
|
runner.interpreter_write_memory(adr + 4, val)
|
||||||
return adr
|
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:
|
def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int:
|
||||||
runner, val = attrs
|
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 = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]()
|
||||||
ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes)
|
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.static_array, _allocate_memory_stored_static_array)
|
||||||
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct)
|
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct)
|
||||||
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple)
|
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]
|
yield all_bytes[offset:offset + size]
|
||||||
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:
|
def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> Any:
|
||||||
runner, adr = attrs
|
runner, adr = attrs
|
||||||
sub_typ, len_typ = sa_args
|
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 = TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]()
|
||||||
LOAD_FROM_ADDRESS_ROUTER.add_n(prelude.bytes_, _load_bytes_from_address)
|
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.static_array, _load_static_array_from_address)
|
||||||
LOAD_FROM_ADDRESS_ROUTER.add(prelude.struct, _load_struct_from_address)
|
LOAD_FROM_ADDRESS_ROUTER.add(prelude.struct, _load_struct_from_address)
|
||||||
LOAD_FROM_ADDRESS_ROUTER.add(prelude.tuple_, _load_tuple_from_address)
|
LOAD_FROM_ADDRESS_ROUTER.add(prelude.tuple_, _load_tuple_from_address)
|
||||||
|
|||||||
@ -120,6 +120,8 @@ class RunnerWasmtime(RunnerBase):
|
|||||||
|
|
||||||
if vartype is int:
|
if vartype is int:
|
||||||
params.append(wasmtime.ValType.i32())
|
params.append(wasmtime.ValType.i32())
|
||||||
|
elif vartype is float:
|
||||||
|
params.append(wasmtime.ValType.f32())
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -128,6 +130,8 @@ class RunnerWasmtime(RunnerBase):
|
|||||||
pass # No return value
|
pass # No return value
|
||||||
elif func.__annotations__['return'] is int:
|
elif func.__annotations__['return'] is int:
|
||||||
results.append(wasmtime.ValType.i32())
|
results.append(wasmtime.ValType.i32())
|
||||||
|
elif func.__annotations__['return'] is float:
|
||||||
|
results.append(wasmtime.ValType.f32())
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Return type', func.__annotations__['return'])
|
raise NotImplementedError('Return type', func.__annotations__['return'])
|
||||||
|
|
||||||
|
|||||||
@ -60,7 +60,7 @@ CONSTANT: (u32, ) = $VAL0
|
|||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```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(
|
expect_type_error(
|
||||||
'Tuple element count mismatch',
|
'Tuple element count mismatch',
|
||||||
'The given literal must fit the expected type',
|
'The given literal must fit the expected type',
|
||||||
@ -113,7 +113,7 @@ def testEntry() -> i32:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```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(
|
expect_type_error(
|
||||||
'Mismatch between applied types argument count',
|
'Mismatch between applied types argument count',
|
||||||
'The type of a tuple is a combination of its members',
|
'The type of a tuple is a combination of its members',
|
||||||
@ -175,7 +175,7 @@ def testEntry() -> i32:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```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(
|
expect_type_error(
|
||||||
TYPE + ' must be (u32, ) instead',
|
TYPE + ' must be (u32, ) instead',
|
||||||
'The type of the value returned from function constant should match its return type',
|
'The type of the value returned from function constant should match its return type',
|
||||||
@ -226,7 +226,7 @@ def select(x: $TYPE) -> (u32, ):
|
|||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```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(
|
expect_type_error(
|
||||||
TYPE + ' must be (u32, ) instead',
|
TYPE + ' must be (u32, ) instead',
|
||||||
'The type of the value returned from function select should match its return type',
|
'The type of the value returned from function select should match its return type',
|
||||||
@ -273,7 +273,7 @@ def testEntry() -> i32:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```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(
|
expect_type_error(
|
||||||
'Mismatch between applied types argument count',
|
'Mismatch between applied types argument count',
|
||||||
# FIXME: Shouldn't this be the same as for the else statement?
|
# FIXME: Shouldn't this be the same as for the else statement?
|
||||||
@ -330,7 +330,7 @@ def testEntry() -> i32:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```py
|
```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(
|
expect_type_error(
|
||||||
TYPE + ' must be (u32, ) instead',
|
TYPE + ' must be (u32, ) instead',
|
||||||
'The type of the value passed to argument 0 of function helper should match the type of that argument',
|
'The type of the value passed to argument 0 of function helper should match the type of that argument',
|
||||||
|
|||||||
@ -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, )"
|
||||||
|
}
|
||||||
@ -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
|
|
||||||
@ -36,6 +36,142 @@ def testEntry(x: Foo[4]) -> Foo:
|
|||||||
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
|
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
|
||||||
Suite(code_py).run_code()
|
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
|
@pytest.mark.integration_test
|
||||||
def test_foldable_invalid_return_type():
|
def test_foldable_invalid_return_type():
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from ..helpers import Suite
|
|||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_, in_put, exp_result', [
|
@pytest.mark.parametrize('type_, in_put, exp_result', [
|
||||||
('bytes', b'Hello, world!', 13),
|
('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):
|
def test_len(type_, in_put, exp_result):
|
||||||
code_py = f"""
|
code_py = f"""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user