Compare commits

...

4 Commits

Author SHA1 Message Date
Johan B.W. de Vries
1bcd7cba30 Implements sum for Foldable types
Foldable take a TypeConstructor. The first argument must be a
NatNum.
2025-05-10 19:51:24 +02:00
Johan B.W. de Vries
6c627bca01 Reworks function lookup
Before this commit, finding the implementation for a type
class method was done with a simple lookup table.

This commit adds a router based on function signature.
This also paves the way for adding type constructor
arguments in function signatures.

And it removes quite a few references to the prelude out
of the compiler.

Also adds a bunch of helper methods to render signatures
as strings.
2025-05-10 19:42:17 +02:00
Johan B.W. de Vries
78c98b1e61 Fix: Type error
That's what I get for cleaning up without running tests.

This removes a intermediate hack to detect missing routes.
2025-05-10 16:51:38 +02:00
Johan B.W. de Vries
f8d107f4fa Replaces did_construct with a proper router
By annotating types with the constructor application
that was used to create them.

Later on we can use the router to replace compiler's
INSTANCES or for user defined types.
2025-05-10 16:49:10 +02:00
21 changed files with 1419 additions and 933 deletions

View File

@ -12,9 +12,11 @@
- Also, check the codes for FIXME and TODO - Also, check the codes for FIXME and TODO
- Allocation is done using pointers for members, is this desired? - Allocation is done using pointers for members, is this desired?
- See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental - See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental
- Implement q32? q64? Two i32/i64 divided?
- Does Subscript do what we want? It's a language feature rather a normal typed thing. How would you implement your own Subscript-able type? - Does Subscript do what we want? It's a language feature rather a normal typed thing. How would you implement your own Subscript-able type?
- Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types. - Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.
- Have a set of rules or guidelines for the constraint comments, they're messy. - Have a set of rules or guidelines for the constraint comments, they're messy.
- Why is expression_subscript_bytes using a helper method but expression_subscript_static_array is not?
- Parser is putting stuff in ModuleDataBlock - Parser is putting stuff in ModuleDataBlock
- Surely the compiler should build data blocks - Surely the compiler should build data blocks

View File

@ -6,7 +6,7 @@ It's intented to be a "any color, as long as it's black" kind of renderer
from typing import Generator from typing import Generator
from . import ourlang, prelude from . import ourlang, prelude
from .type3.types import Type3 from .type3.types import Type3, TypeApplication_Struct
def phasm_render(inp: ourlang.Module) -> str: def phasm_render(inp: ourlang.Module) -> str:
@ -30,11 +30,10 @@ def struct_definition(inp: ourlang.StructDefinition) -> str:
""" """
Render: TypeStruct's definition Render: TypeStruct's definition
""" """
st_args = prelude.struct.did_construct(inp.struct_type3) assert isinstance(inp.struct_type3.application, TypeApplication_Struct)
assert st_args is not None
result = f'class {inp.struct_type3.name}:\n' result = f'class {inp.struct_type3.name}:\n'
for mem, typ in st_args.items(): for mem, typ in inp.struct_type3.application.arguments:
result += f' {mem}: {type3(typ)}\n' result += f' {mem}: {type3(typ)}\n'
return result return result

View File

@ -2,7 +2,7 @@
This module contains the code to convert parsed Ourlang into WebAssembly code This module contains the code to convert parsed Ourlang into WebAssembly code
""" """
import struct import struct
from typing import Dict, List, Optional from typing import Dict, List, Optional, Union
from . import codestyle, ourlang, prelude, wasm from . import codestyle, ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset from .runtime import calculate_alloc_size, calculate_member_offset
@ -11,6 +11,7 @@ from .stdlib import types as stdlib_types
from .type3 import functions as type3functions from .type3 import functions as type3functions
from .type3 import typeclasses as type3classes from .type3 import typeclasses as type3classes
from .type3 import types as type3types from .type3 import types as type3types
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
from .wasmgenerator import Generator as WasmGenerator from .wasmgenerator import Generator as WasmGenerator
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled' TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled'
@ -29,239 +30,6 @@ LOAD_STORE_TYPE_MAP = {
'bytes': 'i32', # Bytes are passed around as pointers 'bytes': 'i32', # Bytes are passed around as pointers
} }
# For now this is nice & clean, but this will get messy quick
# Especially once we get functions with polymorphying applied types
INSTANCES = {
prelude.Eq.operators['==']: {
'a=u8': stdlib_types.u8_eq_equals,
'a=u32': stdlib_types.u32_eq_equals,
'a=u64': stdlib_types.u64_eq_equals,
'a=i8': stdlib_types.i8_eq_equals,
'a=i32': stdlib_types.i32_eq_equals,
'a=i64': stdlib_types.i64_eq_equals,
'a=f32': stdlib_types.f32_eq_equals,
'a=f64': stdlib_types.f64_eq_equals,
},
prelude.Eq.operators['!=']: {
'a=u8': stdlib_types.u8_eq_not_equals,
'a=u32': stdlib_types.u32_eq_not_equals,
'a=u64': stdlib_types.u64_eq_not_equals,
'a=i8': stdlib_types.i8_eq_not_equals,
'a=i32': stdlib_types.i32_eq_not_equals,
'a=i64': stdlib_types.i64_eq_not_equals,
'a=f32': stdlib_types.f32_eq_not_equals,
'a=f64': stdlib_types.f64_eq_not_equals,
},
prelude.Ord.methods['min']: {
'a=u8': stdlib_types.u8_ord_min,
'a=u32': stdlib_types.u32_ord_min,
'a=u64': stdlib_types.u64_ord_min,
'a=i8': stdlib_types.i8_ord_min,
'a=i32': stdlib_types.i32_ord_min,
'a=i64': stdlib_types.i64_ord_min,
'a=f32': stdlib_types.f32_ord_min,
'a=f64': stdlib_types.f64_ord_min,
},
prelude.Ord.methods['max']: {
'a=u8': stdlib_types.u8_ord_max,
'a=u32': stdlib_types.u32_ord_max,
'a=u64': stdlib_types.u64_ord_max,
'a=i8': stdlib_types.i8_ord_max,
'a=i32': stdlib_types.i32_ord_max,
'a=i64': stdlib_types.i64_ord_max,
'a=f32': stdlib_types.f32_ord_max,
'a=f64': stdlib_types.f64_ord_max,
},
prelude.Ord.operators['<']: {
'a=u8': stdlib_types.u8_ord_less_than,
'a=u32': stdlib_types.u32_ord_less_than,
'a=u64': stdlib_types.u64_ord_less_than,
'a=i8': stdlib_types.i8_ord_less_than,
'a=i32': stdlib_types.i32_ord_less_than,
'a=i64': stdlib_types.i64_ord_less_than,
'a=f32': stdlib_types.f32_ord_less_than,
'a=f64': stdlib_types.f64_ord_less_than,
},
prelude.Ord.operators['<=']: {
'a=u8': stdlib_types.u8_ord_less_than_or_equal,
'a=u32': stdlib_types.u32_ord_less_than_or_equal,
'a=u64': stdlib_types.u64_ord_less_than_or_equal,
'a=i8': stdlib_types.i8_ord_less_than_or_equal,
'a=i32': stdlib_types.i32_ord_less_than_or_equal,
'a=i64': stdlib_types.i64_ord_less_than_or_equal,
'a=f32': stdlib_types.f32_ord_less_than_or_equal,
'a=f64': stdlib_types.f64_ord_less_than_or_equal,
},
prelude.Ord.operators['>']: {
'a=u8': stdlib_types.u8_ord_greater_than,
'a=u32': stdlib_types.u32_ord_greater_than,
'a=u64': stdlib_types.u64_ord_greater_than,
'a=i8': stdlib_types.i8_ord_greater_than,
'a=i32': stdlib_types.i32_ord_greater_than,
'a=i64': stdlib_types.i64_ord_greater_than,
'a=f32': stdlib_types.f32_ord_greater_than,
'a=f64': stdlib_types.f64_ord_greater_than,
},
prelude.Ord.operators['>=']: {
'a=u8': stdlib_types.u8_ord_greater_than_or_equal,
'a=u32': stdlib_types.u32_ord_greater_than_or_equal,
'a=u64': stdlib_types.u64_ord_greater_than_or_equal,
'a=i8': stdlib_types.i8_ord_greater_than_or_equal,
'a=i32': stdlib_types.i32_ord_greater_than_or_equal,
'a=i64': stdlib_types.i64_ord_greater_than_or_equal,
'a=f32': stdlib_types.f32_ord_greater_than_or_equal,
'a=f64': stdlib_types.f64_ord_greater_than_or_equal,
},
prelude.Bits.methods['shl']: {
'a=u8': stdlib_types.u8_bits_logical_shift_left,
'a=u32': stdlib_types.u32_bits_logical_shift_left,
'a=u64': stdlib_types.u64_bits_logical_shift_left,
},
prelude.Bits.methods['shr']: {
'a=u8': stdlib_types.u8_bits_logical_shift_right,
'a=u32': stdlib_types.u32_bits_logical_shift_right,
'a=u64': stdlib_types.u64_bits_logical_shift_right,
},
prelude.Bits.methods['rotl']: {
'a=u8': stdlib_types.u8_bits_rotate_left,
'a=u32': stdlib_types.u32_bits_rotate_left,
'a=u64': stdlib_types.u64_bits_rotate_left,
},
prelude.Bits.methods['rotr']: {
'a=u8': stdlib_types.u8_bits_rotate_right,
'a=u32': stdlib_types.u32_bits_rotate_right,
'a=u64': stdlib_types.u64_bits_rotate_right,
},
prelude.Bits.operators['&']: {
'a=u8': stdlib_types.u8_bits_bitwise_and,
'a=u32': stdlib_types.u32_bits_bitwise_and,
'a=u64': stdlib_types.u64_bits_bitwise_and,
},
prelude.Bits.operators['|']: {
'a=u8': stdlib_types.u8_bits_bitwise_or,
'a=u32': stdlib_types.u32_bits_bitwise_or,
'a=u64': stdlib_types.u64_bits_bitwise_or,
},
prelude.Bits.operators['^']: {
'a=u8': stdlib_types.u8_bits_bitwise_xor,
'a=u32': stdlib_types.u32_bits_bitwise_xor,
'a=u64': stdlib_types.u64_bits_bitwise_xor,
},
prelude.Floating.methods['sqrt']: {
'a=f32': stdlib_types.f32_floating_sqrt,
'a=f64': stdlib_types.f64_floating_sqrt,
},
prelude.Fractional.methods['ceil']: {
'a=f32': stdlib_types.f32_fractional_ceil,
'a=f64': stdlib_types.f64_fractional_ceil,
},
prelude.Fractional.methods['floor']: {
'a=f32': stdlib_types.f32_fractional_floor,
'a=f64': stdlib_types.f64_fractional_floor,
},
prelude.Fractional.methods['trunc']: {
'a=f32': stdlib_types.f32_fractional_trunc,
'a=f64': stdlib_types.f64_fractional_trunc,
},
prelude.Fractional.methods['nearest']: {
'a=f32': stdlib_types.f32_fractional_nearest,
'a=f64': stdlib_types.f64_fractional_nearest,
},
prelude.Fractional.operators['/']: {
'a=f32': stdlib_types.f32_fractional_div,
'a=f64': stdlib_types.f64_fractional_div,
},
prelude.Integral.operators['//']: {
'a=u32': stdlib_types.u32_integral_div,
'a=u64': stdlib_types.u64_integral_div,
'a=i32': stdlib_types.i32_integral_div,
'a=i64': stdlib_types.i64_integral_div,
},
prelude.Integral.operators['%']: {
'a=u32': stdlib_types.u32_integral_rem,
'a=u64': stdlib_types.u64_integral_rem,
'a=i32': stdlib_types.i32_integral_rem,
'a=i64': stdlib_types.i64_integral_rem,
},
prelude.IntNum.methods['abs']: {
'a=i32': stdlib_types.i32_intnum_abs,
'a=i64': stdlib_types.i64_intnum_abs,
'a=f32': stdlib_types.f32_intnum_abs,
'a=f64': stdlib_types.f64_intnum_abs,
},
prelude.IntNum.methods['neg']: {
'a=i32': stdlib_types.i32_intnum_neg,
'a=i64': stdlib_types.i64_intnum_neg,
'a=f32': stdlib_types.f32_intnum_neg,
'a=f64': stdlib_types.f64_intnum_neg,
},
prelude.NatNum.operators['+']: {
'a=u32': stdlib_types.u32_natnum_add,
'a=u64': stdlib_types.u64_natnum_add,
'a=i32': stdlib_types.i32_natnum_add,
'a=i64': stdlib_types.i64_natnum_add,
'a=f32': stdlib_types.f32_natnum_add,
'a=f64': stdlib_types.f64_natnum_add,
},
prelude.NatNum.operators['-']: {
'a=u32': stdlib_types.u32_natnum_sub,
'a=u64': stdlib_types.u64_natnum_sub,
'a=i32': stdlib_types.i32_natnum_sub,
'a=i64': stdlib_types.i64_natnum_sub,
'a=f32': stdlib_types.f32_natnum_sub,
'a=f64': stdlib_types.f64_natnum_sub,
},
prelude.NatNum.operators['*']: {
'a=u32': stdlib_types.u32_natnum_mul,
'a=u64': stdlib_types.u64_natnum_mul,
'a=i32': stdlib_types.i32_natnum_mul,
'a=i64': stdlib_types.i64_natnum_mul,
'a=f32': stdlib_types.f32_natnum_mul,
'a=f64': stdlib_types.f64_natnum_mul,
},
prelude.NatNum.operators['<<']: {
'a=u32': stdlib_types.u32_natnum_arithmic_shift_left,
'a=u64': stdlib_types.u64_natnum_arithmic_shift_left,
'a=i32': stdlib_types.i32_natnum_arithmic_shift_left,
'a=i64': stdlib_types.i64_natnum_arithmic_shift_left,
'a=f32': stdlib_types.f32_natnum_arithmic_shift_left,
'a=f64': stdlib_types.f64_natnum_arithmic_shift_left,
},
prelude.NatNum.operators['>>']: {
'a=u32': stdlib_types.u32_natnum_arithmic_shift_right,
'a=u64': stdlib_types.u64_natnum_arithmic_shift_right,
'a=i32': stdlib_types.i32_natnum_arithmic_shift_right,
'a=i64': stdlib_types.i64_natnum_arithmic_shift_right,
'a=f32': stdlib_types.f32_natnum_arithmic_shift_right,
'a=f64': stdlib_types.f64_natnum_arithmic_shift_right,
},
prelude.Sized_.methods['len']: {
'a=bytes': stdlib_types.bytes_sized_len,
},
prelude.Extendable.methods['extend']: {
'a=u8,b=u32': stdlib_types.u8_u32_extend,
'a=u8,b=u64': stdlib_types.u8_u64_extend,
'a=u32,b=u64': stdlib_types.u32_u64_extend,
'a=i8,b=i32': stdlib_types.i8_i32_extend,
'a=i8,b=i64': stdlib_types.i8_i64_extend,
'a=i32,b=i64': stdlib_types.i32_i64_extend,
},
prelude.Extendable.methods['wrap']: {
'a=u8,b=u32': stdlib_types.u8_u32_wrap,
'a=u8,b=u64': stdlib_types.u8_u64_wrap,
'a=u32,b=u64': stdlib_types.u32_u64_wrap,
'a=i8,b=i32': stdlib_types.i8_i32_wrap,
'a=i8,b=i64': stdlib_types.i8_i64_wrap,
'a=i32,b=i64': stdlib_types.i32_i64_wrap,
},
prelude.Promotable.methods['promote']: {
'a=f32,b=f64': stdlib_types.f32_f64_promote,
},
prelude.Promotable.methods['demote']: {
'a=f32,b=f64': stdlib_types.f32_f64_demote,
},
}
def phasm_compile(inp: ourlang.Module) -> wasm.Module: def phasm_compile(inp: ourlang.Module) -> wasm.Module:
""" """
Public method for compiling a parsed Phasm module into Public method for compiling a parsed Phasm module into
@ -330,19 +98,24 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
""" """
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
args: list[type3types.Type3] = [] args: tuple[type3types.Type3, ...]
sa_args = prelude.static_array.did_construct(inp.type3) if isinstance(inp.type3.application, type3types.TypeApplication_TypeStar):
if sa_args is not None: # Possibly paranoid assert. If we have a future variadic type,
sa_type, sa_len = sa_args # does it also do this tuple instantation like this?
args = [sa_type for _ in range(sa_len.value)] assert isinstance(inp.type3.application.constructor, type3types.TypeConstructor_Tuple)
if not args: args = inp.type3.application.arguments
tp_args = prelude.tuple_.did_construct(inp.type3) elif isinstance(inp.type3.application, type3types.TypeApplication_TypeInt):
if tp_args is None: # Possibly paranoid assert. If we have a future type of kind * -> Int -> *,
raise NotImplementedError # does it also do this tuple instantation like this?
assert isinstance(inp.type3.application.constructor, type3types.TypeConstructor_StaticArray)
args = list(tp_args) sa_type, sa_len = inp.type3.application.arguments
args = tuple(sa_type for _ in range(sa_len.value))
else:
raise NotImplementedError('tuple_instantiation', inp.type3)
comment_elements = '' comment_elements = ''
for element in inp.elements: for element in inp.elements:
@ -378,6 +151,78 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
# Return the allocated address # Return the allocated address
wgn.local.get(tmp_var) wgn.local.get(tmp_var)
def expression_subscript_bytes(
attrs: tuple[WasmGenerator, ourlang.Subscript],
) -> None:
wgn, inp = attrs
expression(wgn, inp.varref)
expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__)
def expression_subscript_static_array(
attrs: tuple[WasmGenerator, ourlang.Subscript],
args: tuple[type3types.Type3, type3types.IntType3],
) -> None:
wgn, inp = attrs
el_type, el_len = args
# OPTIMIZE: If index is a constant, we can use offset instead of multiply
# and we don't need to do the out of bounds check
expression(wgn, inp.varref)
tmp_var = wgn.temp_var_i32('index')
expression(wgn, inp.index)
wgn.local.tee(tmp_var)
# Out of bounds check based on el_len.value
wgn.i32.const(el_len.value)
wgn.i32.ge_u()
with wgn.if_():
wgn.unreachable(comment='Out of bounds')
wgn.local.get(tmp_var)
wgn.i32.const(calculate_alloc_size(el_type))
wgn.i32.mul()
wgn.i32.add()
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load')
def expression_subscript_tuple(
attrs: tuple[WasmGenerator, ourlang.Subscript],
args: tuple[type3types.Type3, ...],
) -> None:
wgn, inp = attrs
assert isinstance(inp.index, ourlang.ConstantPrimitive)
assert isinstance(inp.index.value, int)
offset = 0
for el_type in args[0:inp.index.value]:
assert el_type is not None, TYPE3_ASSERTION_ERROR
offset += calculate_alloc_size(el_type)
el_type = args[inp.index.value]
assert el_type is not None, TYPE3_ASSERTION_ERROR
expression(wgn, inp.varref)
if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
mtyp = 'i32'
else:
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
SUBSCRIPT_ROUTER = TypeApplicationRouter[tuple[WasmGenerator, ourlang.Subscript], None]()
SUBSCRIPT_ROUTER.add_n(prelude.bytes_, expression_subscript_bytes)
SUBSCRIPT_ROUTER.add(prelude.static_array, expression_subscript_static_array)
SUBSCRIPT_ROUTER.add(prelude.tuple_, expression_subscript_tuple)
def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
""" """
Compile: Any expression Compile: Any expression
@ -447,9 +292,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
expression(wgn, inp.left) expression(wgn, inp.left)
expression(wgn, inp.right) expression(wgn, inp.right)
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR type_var_map: Dict[Union[type3functions.TypeVariable, type3functions.TypeConstructorVariable], type3types.Type3] = {}
type_var_map: Dict[type3functions.TypeVariable, type3types.Type3] = {}
for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True): for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True):
if not isinstance(type_var, type3functions.TypeVariable): if not isinstance(type_var, type3functions.TypeVariable):
@ -459,18 +302,10 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
type_var_map[type_var] = arg_expr.type3 type_var_map[type_var] = arg_expr.type3
instance_key = ','.join( router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator]
f'{k.letter}={v.name}' router(wgn, type_var_map)
for k, v in type_var_map.items()
)
instance = INSTANCES.get(inp.operator, {}).get(instance_key, None)
if instance is not None:
instance(wgn)
return return
raise NotImplementedError(inp.operator, instance_key)
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
for arg in inp.arguments: for arg in inp.arguments:
expression(wgn, arg) expression(wgn, arg)
@ -480,25 +315,24 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
type_var_map = {} type_var_map = {}
for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True): for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True):
if not isinstance(type_var, type3functions.TypeVariable): if isinstance(type_var, type3types.Type3):
# Fixed type, not part of the lookup requirements # Fixed type, not part of the lookup requirements
continue continue
if isinstance(type_var, (type3functions.TypeVariable, type3functions.TypeConstructorVariable, )):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
type_var_map[type_var] = arg_expr.type3 type_var_map[type_var] = arg_expr.type3
continue
instance_key = ','.join( raise NotImplementedError
f'{k.letter}={v.name}'
for k, v in sorted(type_var_map.items(), key=lambda x: x[0].letter)
)
instance = INSTANCES.get(inp.function, {}).get(instance_key, None) router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function]
if instance is not None: try:
instance(wgn) router(wgn, type_var_map)
except NoRouteForTypeException:
raise NotImplementedError(str(inp.function), type_var_map)
return return
raise NotImplementedError(inp.function, instance_key)
wgn.add_statement('call', '${}'.format(inp.function.name)) wgn.add_statement('call', '${}'.format(inp.function.name))
return return
@ -509,81 +343,22 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
if isinstance(inp, ourlang.Subscript): if isinstance(inp, ourlang.Subscript):
assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR
if inp.varref.type3 is prelude.bytes_: # Type checker guarantees we don't get routing errors
expression(wgn, inp.varref) SUBSCRIPT_ROUTER((wgn, inp, ), inp.varref.type3)
expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__)
return return
assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR
sa_args = prelude.static_array.did_construct(inp.varref.type3)
if sa_args is not None:
el_type, el_len = sa_args
# OPTIMIZE: If index is a constant, we can use offset instead of multiply
# and we don't need to do the out of bounds check
expression(wgn, inp.varref)
tmp_var = wgn.temp_var_i32('index')
expression(wgn, inp.index)
wgn.local.tee(tmp_var)
# Out of bounds check based on el_len.value
wgn.i32.const(el_len.value)
wgn.i32.ge_u()
with wgn.if_():
wgn.unreachable(comment='Out of bounds')
wgn.local.get(tmp_var)
wgn.i32.const(calculate_alloc_size(el_type))
wgn.i32.mul()
wgn.i32.add()
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load')
return
tp_args = prelude.tuple_.did_construct(inp.varref.type3)
if tp_args is not None:
assert isinstance(inp.index, ourlang.ConstantPrimitive)
assert isinstance(inp.index.value, int)
offset = 0
for el_type in tp_args[0:inp.index.value]:
assert el_type is not None, TYPE3_ASSERTION_ERROR
offset += calculate_alloc_size(el_type)
el_type = tp_args[inp.index.value]
assert el_type is not None, TYPE3_ASSERTION_ERROR
expression(wgn, inp.varref)
if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
mtyp = 'i32'
else:
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
return
raise NotImplementedError(expression, inp, inp.varref.type3)
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.AccessStructMember):
assert inp.struct_type3 is not None, TYPE3_ASSERTION_ERROR assert inp.struct_type3 is not None, TYPE3_ASSERTION_ERROR
st_args = prelude.struct.did_construct(inp.struct_type3) assert isinstance(inp.struct_type3.application, type3types.TypeApplication_Struct)
assert st_args is not None
member_type = st_args[inp.member] member_type = dict(inp.struct_type3.application.arguments)[inp.member]
mtyp = LOAD_STORE_TYPE_MAP[member_type.name] mtyp = LOAD_STORE_TYPE_MAP[member_type.name]
expression(wgn, inp.varref) expression(wgn, inp.varref)
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset( wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset(
inp.struct_type3.name, st_args, inp.member inp.struct_type3.name, inp.struct_type3.application.arguments, inp.member
))) )))
return return
@ -949,6 +724,7 @@ def module(inp: ourlang.Module) -> wasm.Module:
stdlib_types.__u32_pow2__, stdlib_types.__u32_pow2__,
stdlib_types.__u8_rotl__, stdlib_types.__u8_rotl__,
stdlib_types.__u8_rotr__, stdlib_types.__u8_rotr__,
stdlib_types.__sa_i32_sum__,
] + [ ] + [
function(x) function(x)
for x in inp.functions.values() for x in inp.functions.values()
@ -958,8 +734,9 @@ def module(inp: ourlang.Module) -> wasm.Module:
return result return result
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
st_args = prelude.struct.did_construct(inp.struct_type3) assert isinstance(inp.struct_type3.application, type3types.TypeApplication_Struct)
assert st_args is not None
st_args = inp.struct_type3.application.arguments
tmp_var = wgn.temp_var_i32('struct_adr') tmp_var = wgn.temp_var_i32('struct_adr')
@ -969,7 +746,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
wgn.local.set(tmp_var) wgn.local.set(tmp_var)
# Store each member individually # Store each member individually
for memname, mtyp3 in st_args.items(): for memname, mtyp3 in st_args:
mtyp: Optional[str] mtyp: Optional[str]
if (prelude.InternalPassAsPointer, (mtyp3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if (prelude.InternalPassAsPointer, (mtyp3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
mtyp = 'i32' mtyp = 'i32'

View File

@ -7,7 +7,7 @@ from typing import Dict, Iterable, List, Optional, Union
from . import prelude from . import prelude
from .type3.functions import FunctionSignature, TypeVariableContext from .type3.functions import FunctionSignature, TypeVariableContext
from .type3.typeclasses import Type3ClassMethod from .type3.typeclasses import Type3ClassMethod
from .type3.types import Type3 from .type3.types import Type3, TypeApplication_Struct
class Expression: class Expression:
@ -341,9 +341,9 @@ class StructConstructor(Function):
def __init__(self, struct_type3: Type3) -> None: def __init__(self, struct_type3: Type3) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', -1) super().__init__(f'@{struct_type3.name}@__init___@', -1)
st_args = prelude.struct.did_construct(struct_type3) assert isinstance(struct_type3.application, TypeApplication_Struct)
assert st_args is not None
for mem, typ in st_args.items(): for mem, typ in struct_type3.application.arguments:
self.posonlyargs.append(FunctionParam(mem, typ, )) self.posonlyargs.append(FunctionParam(mem, typ, ))
self.signature.args.append(typ) self.signature.args.append(typ)

View File

@ -246,7 +246,7 @@ class OurVisitor:
members[stmt.target.id] = self.visit_type(module, stmt.annotation) members[stmt.target.id] = self.visit_type(module, stmt.annotation)
return StructDefinition(prelude.struct(node.name, members), node.lineno) return StructDefinition(prelude.struct(node.name, tuple(members.items())), node.lineno)
def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name): if not isinstance(node.target, ast.Name):

View File

@ -1,60 +1,115 @@
""" """
The prelude are all the builtin types, type classes and methods The prelude are all the builtin types, type classes and methods
""" """
from typing import Callable
from warnings import warn
from phasm.stdlib import types as stdtypes
from phasm.wasmgenerator import Generator
from ..type3.functions import TypeVariable from ..type3.functions import TypeVariable
from ..type3.typeclasses import Type3Class from ..type3.routers import FunctionSignatureRouter
from ..type3.typeclasses import Type3Class, Type3ClassMethod
from ..type3.types import ( from ..type3.types import (
IntType3,
Type3, Type3,
TypeApplication_Nullary,
TypeConstructor_StaticArray, TypeConstructor_StaticArray,
TypeConstructor_Struct, TypeConstructor_Struct,
TypeConstructor_Tuple, TypeConstructor_Tuple,
) )
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3, ...]]] = set() PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Union[Type3, TypeConstructor_Base[Any], TypeConstructor_Struct], ...]]] = set()
PRELUDE_TYPE_CLASS_INSTANCE_METHODS: dict[Type3ClassMethod, FunctionSignatureRouter[Generator, None]] = {}
def instance_type_class(cls: Type3Class, *typ: Type3) -> None: class MissingImplementationException(Exception):
pass
class MissingImplementationWarning(Warning):
pass
def instance_type_class(
cls: Type3Class,
*typ: Type3,
methods: dict[str, Callable[[Generator], None]] = {},
operators: dict[str, Callable[[Generator], None]] = {},
) -> None:
global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING
global PRELUDE_TYPE_CLASS_INSTANCE_METHODS
assert len(cls.args) == len(typ)
type_var_map = {}
for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
type_var_map[arg_tv] = arg_tp
# TODO: Check for required existing instantiations # TODO: Check for required existing instantiations
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), )) PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), ))
none = Type3('none') for method_name, method in cls.methods.items():
router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(method)
if router is None:
router = FunctionSignatureRouter[Generator, None](method.signature)
PRELUDE_TYPE_CLASS_INSTANCE_METHODS[method] = router
try:
generator = methods[method_name]
except KeyError:
warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ)))
continue
router.add(type_var_map, generator)
for operator_name, operator in cls.operators.items():
router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(operator)
if router is None:
router = FunctionSignatureRouter[Generator, None](operator.signature)
PRELUDE_TYPE_CLASS_INSTANCE_METHODS[operator] = router
try:
generator = operators[operator_name]
except KeyError:
warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ)))
continue
router.add(type_var_map, generator)
none = Type3('none', TypeApplication_Nullary(None, None))
""" """
The none type, for when functions simply don't return anything. e.g., IO(). The none type, for when functions simply don't return anything. e.g., IO().
""" """
bool_ = Type3('bool') bool_ = Type3('bool', TypeApplication_Nullary(None, None))
""" """
The bool type, either True or False The bool type, either True or False
Suffixes with an underscores, as it's a Python builtin Suffixes with an underscores, as it's a Python builtin
""" """
u8 = Type3('u8') u8 = Type3('u8', TypeApplication_Nullary(None, None))
""" """
The unsigned 8-bit integer type. The unsigned 8-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^8. Operations on variables employ modular arithmetic, with modulus 2^8.
""" """
u32 = Type3('u32') u32 = Type3('u32', TypeApplication_Nullary(None, None))
""" """
The unsigned 32-bit integer type. The unsigned 32-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^32. Operations on variables employ modular arithmetic, with modulus 2^32.
""" """
u64 = Type3('u64') u64 = Type3('u64', TypeApplication_Nullary(None, None))
""" """
The unsigned 64-bit integer type. The unsigned 64-bit integer type.
Operations on variables employ modular arithmetic, with modulus 2^64. Operations on variables employ modular arithmetic, with modulus 2^64.
""" """
i8 = Type3('i8') i8 = Type3('i8', TypeApplication_Nullary(None, None))
""" """
The signed 8-bit integer type. The signed 8-bit integer type.
@ -62,7 +117,7 @@ Operations on variables employ modular arithmetic, with modulus 2^8, but
with the middel point being 0. with the middel point being 0.
""" """
i32 = Type3('i32') i32 = Type3('i32', TypeApplication_Nullary(None, None))
""" """
The unsigned 32-bit integer type. The unsigned 32-bit integer type.
@ -70,7 +125,7 @@ Operations on variables employ modular arithmetic, with modulus 2^32, but
with the middel point being 0. with the middel point being 0.
""" """
i64 = Type3('i64') i64 = Type3('i64', TypeApplication_Nullary(None, None))
""" """
The unsigned 64-bit integer type. The unsigned 64-bit integer type.
@ -78,22 +133,22 @@ Operations on variables employ modular arithmetic, with modulus 2^64, but
with the middel point being 0. with the middel point being 0.
""" """
f32 = Type3('f32') f32 = Type3('f32', TypeApplication_Nullary(None, None))
""" """
A 32-bits IEEE 754 float, of 32 bits width. A 32-bits IEEE 754 float, of 32 bits width.
""" """
f64 = Type3('f64') 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') bytes_ = Type3('bytes', TypeApplication_Nullary(None, None))
""" """
This is a runtime-determined length piece of memory that can be indexed at runtime. This is a runtime-determined length piece of memory that can be indexed at runtime.
""" """
def sa_on_create(typ: Type3) -> None: def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ) instance_type_class(InternalPassAsPointer, typ)
static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create) static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create)
@ -106,7 +161,7 @@ It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated. of the same type repeated.
""" """
def tp_on_create(typ: Type3) -> None: def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ) instance_type_class(InternalPassAsPointer, typ)
tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create) tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create)
@ -117,7 +172,7 @@ It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different. determined length, and each argument can be different.
""" """
def st_on_create(typ: Type3) -> None: def st_on_create(args: tuple[tuple[str, Type3], ...], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ) instance_type_class(InternalPassAsPointer, typ)
struct = TypeConstructor_Struct('struct', on_create=st_on_create) struct = TypeConstructor_Struct('struct', on_create=st_on_create)
@ -143,8 +198,9 @@ PRELUDE_TYPES: dict[str, Type3] = {
a = TypeVariable('a') a = TypeVariable('a')
b = TypeVariable('b') b = TypeVariable('b')
t = TypeConstructorVariable('t')
InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={}) InternalPassAsPointer = Type3Class('InternalPassAsPointer', (a, ), methods={}, operators={})
""" """
Internal type class to keep track which types we pass arounds as a pointer. Internal type class to keep track which types we pass arounds as a pointer.
""" """
@ -154,22 +210,46 @@ instance_type_class(InternalPassAsPointer, bytes_)
# instance_type_class(InternalPassAsPointer, tuple_) # instance_type_class(InternalPassAsPointer, tuple_)
# instance_type_class(InternalPassAsPointer, struct) # instance_type_class(InternalPassAsPointer, struct)
Eq = Type3Class('Eq', [a], methods={}, operators={ Eq = Type3Class('Eq', (a, ), methods={}, operators={
'==': [a, a, bool_], '==': [a, a, bool_],
'!=': [a, a, bool_], '!=': [a, a, bool_],
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization? # FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
}) })
instance_type_class(Eq, u8) instance_type_class(Eq, u8, operators={
instance_type_class(Eq, u32) '==': stdtypes.u8_eq_equals,
instance_type_class(Eq, u64) '!=': stdtypes.u8_eq_not_equals,
instance_type_class(Eq, i8) })
instance_type_class(Eq, i32) instance_type_class(Eq, u32, operators={
instance_type_class(Eq, i64) '==': stdtypes.u32_eq_equals,
instance_type_class(Eq, f32) '!=': stdtypes.u32_eq_not_equals,
instance_type_class(Eq, f64) })
instance_type_class(Eq, u64, operators={
'==': stdtypes.u64_eq_equals,
'!=': stdtypes.u64_eq_not_equals,
})
instance_type_class(Eq, i8, operators={
'==': stdtypes.i8_eq_equals,
'!=': stdtypes.i8_eq_not_equals,
})
instance_type_class(Eq, i32, operators={
'==': stdtypes.i32_eq_equals,
'!=': stdtypes.i32_eq_not_equals,
})
instance_type_class(Eq, i64, operators={
'==': stdtypes.i64_eq_equals,
'!=': stdtypes.i64_eq_not_equals,
})
instance_type_class(Eq, f32, operators={
'==': stdtypes.f32_eq_equals,
'!=': stdtypes.f32_eq_not_equals,
})
instance_type_class(Eq, f64, operators={
'==': stdtypes.f64_eq_equals,
'!=': stdtypes.f64_eq_not_equals,
})
Ord = Type3Class('Ord', [a], methods={ Ord = Type3Class('Ord', (a, ), methods={
'min': [a, a, a], 'min': [a, a, a],
'max': [a, a, a], 'max': [a, a, a],
}, operators={ }, operators={
@ -179,16 +259,80 @@ Ord = Type3Class('Ord', [a], methods={
'>=': [a, a, bool_], '>=': [a, a, bool_],
}, inherited_classes=[Eq]) }, inherited_classes=[Eq])
instance_type_class(Ord, u8) instance_type_class(Ord, u8, methods={
instance_type_class(Ord, u32) 'min': stdtypes.u8_ord_min,
instance_type_class(Ord, u64) 'max': stdtypes.u8_ord_max,
instance_type_class(Ord, i8) }, operators={
instance_type_class(Ord, i32) '<': stdtypes.u8_ord_less_than,
instance_type_class(Ord, i64) '<=': stdtypes.u8_ord_less_than_or_equal,
instance_type_class(Ord, f32) '>': stdtypes.u8_ord_greater_than,
instance_type_class(Ord, f64) '>=': stdtypes.u8_ord_greater_than_or_equal,
})
instance_type_class(Ord, u32, methods={
'min': stdtypes.u32_ord_min,
'max': stdtypes.u32_ord_max,
}, operators={
'<': stdtypes.u32_ord_less_than,
'<=': stdtypes.u32_ord_less_than_or_equal,
'>': stdtypes.u32_ord_greater_than,
'>=': stdtypes.u32_ord_greater_than_or_equal,
})
instance_type_class(Ord, u64, methods={
'min': stdtypes.u64_ord_min,
'max': stdtypes.u64_ord_max,
}, operators={
'<': stdtypes.u64_ord_less_than,
'<=': stdtypes.u64_ord_less_than_or_equal,
'>': stdtypes.u64_ord_greater_than,
'>=': stdtypes.u64_ord_greater_than_or_equal,
})
instance_type_class(Ord, i8, methods={
'min': stdtypes.i8_ord_min,
'max': stdtypes.i8_ord_max,
}, operators={
'<': stdtypes.i8_ord_less_than,
'<=': stdtypes.i8_ord_less_than_or_equal,
'>': stdtypes.i8_ord_greater_than,
'>=': stdtypes.i8_ord_greater_than_or_equal,
})
instance_type_class(Ord, i32, methods={
'min': stdtypes.i32_ord_min,
'max': stdtypes.i32_ord_max,
}, operators={
'<': stdtypes.i32_ord_less_than,
'<=': stdtypes.i32_ord_less_than_or_equal,
'>': stdtypes.i32_ord_greater_than,
'>=': stdtypes.i32_ord_greater_than_or_equal,
})
instance_type_class(Ord, i64, methods={
'min': stdtypes.i64_ord_min,
'max': stdtypes.i64_ord_max,
}, operators={
'<': stdtypes.i64_ord_less_than,
'<=': stdtypes.i64_ord_less_than_or_equal,
'>': stdtypes.i64_ord_greater_than,
'>=': stdtypes.i64_ord_greater_than_or_equal,
})
instance_type_class(Ord, f32, methods={
'min': stdtypes.f32_ord_min,
'max': stdtypes.f32_ord_max,
}, operators={
'<': stdtypes.f32_ord_less_than,
'<=': stdtypes.f32_ord_less_than_or_equal,
'>': stdtypes.f32_ord_greater_than,
'>=': stdtypes.f32_ord_greater_than_or_equal,
})
instance_type_class(Ord, f64, methods={
'min': stdtypes.f64_ord_min,
'max': stdtypes.f64_ord_max,
}, operators={
'<': stdtypes.f64_ord_less_than,
'<=': stdtypes.f64_ord_less_than_or_equal,
'>': stdtypes.f64_ord_greater_than,
'>=': stdtypes.f64_ord_greater_than_or_equal,
})
Bits = Type3Class('Bits', [a], methods={ Bits = Type3Class('Bits', (a, ), methods={
'shl': [a, u32, a], # Logical shift left 'shl': [a, u32, a], # Logical shift left
'shr': [a, u32, a], # Logical shift right 'shr': [a, u32, a], # Logical shift right
'rotl': [a, u32, a], # Rotate bits left 'rotl': [a, u32, a], # Rotate bits left
@ -200,11 +344,38 @@ Bits = Type3Class('Bits', [a], methods={
'^': [a, a, a], # Bit-wise xor '^': [a, a, a], # Bit-wise xor
}) })
instance_type_class(Bits, u8) instance_type_class(Bits, u8, methods={
instance_type_class(Bits, u32) 'shl': stdtypes.u8_bits_logical_shift_left,
instance_type_class(Bits, u64) 'shr': stdtypes.u8_bits_logical_shift_right,
'rotl': stdtypes.u8_bits_rotate_left,
'rotr': stdtypes.u8_bits_rotate_right,
}, operators={
'&': stdtypes.u8_bits_bitwise_and,
'|': stdtypes.u8_bits_bitwise_or,
'^': stdtypes.u8_bits_bitwise_xor,
})
instance_type_class(Bits, u32, methods={
'shl': stdtypes.u32_bits_logical_shift_left,
'shr': stdtypes.u32_bits_logical_shift_right,
'rotl': stdtypes.u32_bits_rotate_left,
'rotr': stdtypes.u32_bits_rotate_right,
}, operators={
'&': stdtypes.u32_bits_bitwise_and,
'|': stdtypes.u32_bits_bitwise_or,
'^': stdtypes.u32_bits_bitwise_xor,
})
instance_type_class(Bits, u64, methods={
'shl': stdtypes.u64_bits_logical_shift_left,
'shr': stdtypes.u64_bits_logical_shift_right,
'rotl': stdtypes.u64_bits_rotate_left,
'rotr': stdtypes.u64_bits_rotate_right,
}, operators={
'&': stdtypes.u64_bits_bitwise_and,
'|': stdtypes.u64_bits_bitwise_or,
'^': stdtypes.u64_bits_bitwise_xor,
})
NatNum = Type3Class('NatNum', [a], methods={}, operators={ NatNum = Type3Class('NatNum', (a, ), methods={}, operators={
'+': [a, a, a], '+': [a, a, a],
'-': [a, a, a], '-': [a, a, a],
'*': [a, a, a], '*': [a, a, a],
@ -212,35 +383,95 @@ NatNum = Type3Class('NatNum', [a], methods={}, operators={
'>>': [a, u32, a], # Arithmic shift right '>>': [a, u32, a], # Arithmic shift right
}) })
instance_type_class(NatNum, u32) instance_type_class(NatNum, u32, operators={
instance_type_class(NatNum, u64) '+': stdtypes.u32_natnum_add,
instance_type_class(NatNum, i32) '-': stdtypes.u32_natnum_sub,
instance_type_class(NatNum, i64) '*': stdtypes.u32_natnum_mul,
instance_type_class(NatNum, f32) '<<': stdtypes.u32_natnum_arithmic_shift_left,
instance_type_class(NatNum, f64) '>>': stdtypes.u32_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, u64, operators={
'+': stdtypes.u64_natnum_add,
'-': stdtypes.u64_natnum_sub,
'*': stdtypes.u64_natnum_mul,
'<<': stdtypes.u64_natnum_arithmic_shift_left,
'>>': stdtypes.u64_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, i32, operators={
'+': stdtypes.i32_natnum_add,
'-': stdtypes.i32_natnum_sub,
'*': stdtypes.i32_natnum_mul,
'<<': stdtypes.i32_natnum_arithmic_shift_left,
'>>': stdtypes.i32_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, i64, operators={
'+': stdtypes.i64_natnum_add,
'-': stdtypes.i64_natnum_sub,
'*': stdtypes.i64_natnum_mul,
'<<': stdtypes.i64_natnum_arithmic_shift_left,
'>>': stdtypes.i64_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, f32, operators={
'+': stdtypes.f32_natnum_add,
'-': stdtypes.f32_natnum_sub,
'*': stdtypes.f32_natnum_mul,
'<<': stdtypes.f32_natnum_arithmic_shift_left,
'>>': stdtypes.f32_natnum_arithmic_shift_right,
})
instance_type_class(NatNum, f64, operators={
'+': stdtypes.f64_natnum_add,
'-': stdtypes.f64_natnum_sub,
'*': stdtypes.f64_natnum_mul,
'<<': stdtypes.f64_natnum_arithmic_shift_left,
'>>': stdtypes.f64_natnum_arithmic_shift_right,
})
IntNum = Type3Class('IntNum', [a], methods={ IntNum = Type3Class('IntNum', (a, ), methods={
'abs': [a, a], 'abs': [a, a],
'neg': [a, a], 'neg': [a, a],
}, operators={}, inherited_classes=[NatNum]) }, operators={}, inherited_classes=[NatNum])
instance_type_class(IntNum, i32) instance_type_class(IntNum, i32, methods={
instance_type_class(IntNum, i64) 'abs': stdtypes.i32_intnum_abs,
instance_type_class(IntNum, f32) 'neg': stdtypes.i32_intnum_neg,
instance_type_class(IntNum, f64) })
instance_type_class(IntNum, i64, methods={
'abs': stdtypes.i64_intnum_abs,
'neg': stdtypes.i64_intnum_neg,
})
instance_type_class(IntNum, f32, methods={
'abs': stdtypes.f32_intnum_abs,
'neg': stdtypes.f32_intnum_neg,
})
instance_type_class(IntNum, f64, methods={
'abs': stdtypes.f64_intnum_abs,
'neg': stdtypes.f64_intnum_neg,
})
Integral = Type3Class('Eq', [a], methods={ Integral = Type3Class('Eq', (a, ), methods={
}, operators={ }, operators={
'//': [a, a, a], '//': [a, a, a],
'%': [a, a, a], '%': [a, a, a],
}, inherited_classes=[NatNum]) }, inherited_classes=[NatNum])
instance_type_class(Integral, u32) instance_type_class(Integral, u32, operators={
instance_type_class(Integral, u64) '//': stdtypes.u32_integral_div,
instance_type_class(Integral, i32) '%': stdtypes.u32_integral_rem,
instance_type_class(Integral, i64) })
instance_type_class(Integral, u64, operators={
'//': stdtypes.u64_integral_div,
'%': stdtypes.u64_integral_rem,
})
instance_type_class(Integral, i32, operators={
'//': stdtypes.i32_integral_div,
'%': stdtypes.i32_integral_rem,
})
instance_type_class(Integral, i64, operators={
'//': stdtypes.i64_integral_div,
'%': stdtypes.i64_integral_rem,
})
Fractional = Type3Class('Fractional', [a], methods={ Fractional = Type3Class('Fractional', (a, ), methods={
'ceil': [a, a], 'ceil': [a, a],
'floor': [a, a], 'floor': [a, a],
'trunc': [a, a], 'trunc': [a, a],
@ -249,42 +480,91 @@ Fractional = Type3Class('Fractional', [a], methods={
'/': [a, a, a], '/': [a, a, a],
}, inherited_classes=[NatNum]) }, inherited_classes=[NatNum])
instance_type_class(Fractional, f32) instance_type_class(Fractional, f32, methods={
instance_type_class(Fractional, f64) 'ceil': stdtypes.f32_fractional_ceil,
'floor': stdtypes.f32_fractional_floor,
'trunc': stdtypes.f32_fractional_trunc,
'nearest': stdtypes.f32_fractional_nearest,
}, operators={
'/': stdtypes.f32_fractional_div,
})
instance_type_class(Fractional, f64, methods={
'ceil': stdtypes.f64_fractional_ceil,
'floor': stdtypes.f64_fractional_floor,
'trunc': stdtypes.f64_fractional_trunc,
'nearest': stdtypes.f64_fractional_nearest,
}, operators={
'/': stdtypes.f64_fractional_div,
})
Floating = Type3Class('Floating', [a], methods={ Floating = Type3Class('Floating', (a, ), methods={
'sqrt': [a, a], 'sqrt': [a, a],
}, operators={}, inherited_classes=[Fractional]) }, operators={}, inherited_classes=[Fractional])
# FIXME: Do we want to expose copysign? # FIXME: Do we want to expose copysign?
instance_type_class(Floating, f32) instance_type_class(Floating, f32, methods={
instance_type_class(Floating, f64) 'sqrt': stdtypes.f32_floating_sqrt,
})
instance_type_class(Floating, f64, methods={
'sqrt': stdtypes.f64_floating_sqrt,
})
Sized_ = Type3Class('Sized', [a], methods={ Sized_ = Type3Class('Sized', (a, ), methods={
'len': [a, u32], 'len': [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_) instance_type_class(Sized_, bytes_, methods={
'len': stdtypes.bytes_sized_len,
})
Extendable = Type3Class('Extendable', [a, b], methods={ Extendable = Type3Class('Extendable', (a, b, ), methods={
'extend': [a, b], 'extend': [a, b],
'wrap': [b, a], 'wrap': [b, a],
}, operators={}) }, operators={})
instance_type_class(Extendable, u8, u32) instance_type_class(Extendable, u8, u32, methods={
instance_type_class(Extendable, u8, u64) 'extend': stdtypes.u8_u32_extend,
instance_type_class(Extendable, u32, u64) 'wrap': stdtypes.u8_u32_wrap,
instance_type_class(Extendable, i8, i32) })
instance_type_class(Extendable, i8, i64) instance_type_class(Extendable, u8, u64, methods={
instance_type_class(Extendable, i32, i64) 'extend': stdtypes.u8_u64_extend,
'wrap': stdtypes.u8_u64_wrap,
})
instance_type_class(Extendable, u32, u64, methods={
'extend': stdtypes.u32_u64_extend,
'wrap': stdtypes.u32_u64_wrap,
})
instance_type_class(Extendable, i8, i32, methods={
'extend': stdtypes.i8_i32_extend,
'wrap': stdtypes.i8_i32_wrap,
})
instance_type_class(Extendable, i8, i64, methods={
'extend': stdtypes.i8_i64_extend,
'wrap': stdtypes.i8_i64_wrap,
})
instance_type_class(Extendable, i32, i64, methods={
'extend': stdtypes.i32_i64_extend,
'wrap': stdtypes.i32_i64_wrap,
})
Promotable = Type3Class('Promotable', [a, b], methods={ Promotable = Type3Class('Promotable', (a, b, ), methods={
'promote': [a, b], 'promote': [a, b],
'demote': [b, a], 'demote': [b, a],
}, operators={}) }, operators={})
instance_type_class(Promotable, f32, f64) instance_type_class(Promotable, f32, f64, methods={
'promote': stdtypes.f32_f64_promote,
'demote': stdtypes.f32_f64_demote,
})
Foldable = Type3Class('Foldable', (t, ), methods={
'sum': [t(a), a],
}, operators={}, additional_context={
'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))],
})
instance_type_class(Foldable, static_array)
PRELUDE_TYPE_CLASSES = { PRELUDE_TYPE_CLASSES = {
'Eq': Eq, 'Eq': Eq,
@ -321,4 +601,5 @@ PRELUDE_METHODS = {
**Sized_.methods, **Sized_.methods,
**Extendable.methods, **Extendable.methods,
**Promotable.methods, **Promotable.methods,
**Foldable.methods,
} }

View File

@ -1,8 +1,40 @@
from . import prelude from . import prelude
from .type3 import types as type3types from .type3.routers import NoRouteForTypeException, TypeApplicationRouter
from .type3.types import IntType3, Type3
def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int:
if is_member:
return 4
sa_type, sa_len = args
return sa_len.value * calculate_alloc_size(sa_type, is_member=True)
def calculate_alloc_size_tuple(is_member: bool, args: tuple[Type3, ...]) -> int:
if is_member:
return 4
return sum(
calculate_alloc_size(x, is_member=True)
for x in args
)
def calculate_alloc_size_struct(is_member: bool, args: tuple[tuple[str, Type3], ...]) -> int:
if is_member:
return 4
return sum(
calculate_alloc_size(x, is_member=True)
for _, x in args
)
ALLOC_SIZE_ROUTER = TypeApplicationRouter[bool, int]()
ALLOC_SIZE_ROUTER.add(prelude.static_array, calculate_alloc_size_static_array)
ALLOC_SIZE_ROUTER.add(prelude.struct, calculate_alloc_size_struct)
ALLOC_SIZE_ROUTER.add(prelude.tuple_, calculate_alloc_size_tuple)
def calculate_alloc_size(typ: Type3, is_member: bool = False) -> int:
if typ in (prelude.u8, prelude.i8, ): if typ in (prelude.u8, prelude.i8, ):
return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32 return 4 # FIXME: We allocate 4 bytes for every u8 since you load them into an i32
@ -12,51 +44,20 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int:
if typ in (prelude.u64, prelude.i64, prelude.f64, ): if typ in (prelude.u64, prelude.i64, prelude.f64, ):
return 8 return 8
if typ == prelude.bytes_: try:
return ALLOC_SIZE_ROUTER(is_member, typ)
except NoRouteForTypeException:
if is_member: if is_member:
# By default, 'boxed' or 'constructed' types are
# stored as pointers when a member of a struct or tuple
return 4 return 4
raise NotImplementedError # When does this happen? raise NotImplementedError(typ)
st_args = prelude.struct.did_construct(typ) def calculate_member_offset(st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int:
if st_args is not None:
if is_member:
# Structs referred to by other structs or tuples are pointers
return 4
return sum(
calculate_alloc_size(x, is_member=True)
for x in st_args.values()
)
sa_args = prelude.static_array.did_construct(typ)
if sa_args is not None:
if is_member:
# tuples referred to by other structs or tuples are pointers
return 4
sa_type, sa_len = sa_args
return sa_len.value * calculate_alloc_size(sa_type, is_member=True)
tp_args = prelude.tuple_.did_construct(typ)
if tp_args is not None:
if is_member:
# tuples referred to by other structs or tuples are pointers
return 4
size = 0
for arg in tp_args:
size += calculate_alloc_size(arg, is_member=True)
return size
raise NotImplementedError(calculate_alloc_size, typ)
def calculate_member_offset(st_name: str, st_args: dict[str, type3types.Type3], needle: str) -> int:
result = 0 result = 0
for memnam, memtyp in st_args.items(): for memnam, memtyp in st_args:
if needle == memnam: if needle == memnam:
return result return result

View File

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

View File

@ -3,11 +3,12 @@ This module contains possible constraints generated based on the AST
These need to be resolved before the program can be compiled. These need to be resolved before the program can be compiled.
""" """
from typing import Dict, Iterable, List, Optional, Tuple, Union from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from .. import ourlang, prelude from .. import ourlang, prelude
from . import placeholders, typeclasses, types from . import placeholders, typeclasses, types
from .placeholders import PlaceholderForType from .placeholders import PlaceholderForType
from .routers import NoRouteForTypeException, TypeApplicationRouter
class Error: class Error:
@ -49,7 +50,7 @@ class Context:
__slots__ = ('type_class_instances_existing', ) __slots__ = ('type_class_instances_existing', )
# Constraint_TypeClassInstanceExists # Constraint_TypeClassInstanceExists
type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[types.Type3, ...]]] type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[Union[types.Type3, types.TypeConstructor_Base[Any], types.TypeConstructor_Struct], ...]]]
def __init__(self) -> None: def __init__(self) -> None:
self.type_class_instances_existing = set() self.type_class_instances_existing = set()
@ -158,24 +159,18 @@ class SameTypeConstraint(ConstraintBase):
return f'SameTypeConstraint({args}, comment={repr(self.comment)})' return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
class TupleMatchConstraint(ConstraintBase): class TupleMatchConstraint(ConstraintBase):
__slots__ = ('exp_type', 'args', )
exp_type: placeholders.Type3OrPlaceholder
args: list[placeholders.Type3OrPlaceholder]
def __init__(self, exp_type: placeholders.Type3OrPlaceholder, args: Iterable[placeholders.Type3OrPlaceholder], comment: str): def __init__(self, exp_type: placeholders.Type3OrPlaceholder, args: Iterable[placeholders.Type3OrPlaceholder], comment: str):
super().__init__(comment=comment) super().__init__(comment=comment)
self.exp_type = exp_type self.exp_type = exp_type
self.args = list(args) self.args = list(args)
def check(self) -> CheckResult: def _generate_static_array(self, sa_args: tuple[types.Type3, types.IntType3]) -> CheckResult:
exp_type = self.exp_type
if isinstance(exp_type, placeholders.PlaceholderForType):
if exp_type.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = exp_type.resolve_as
assert isinstance(exp_type, types.Type3)
sa_args = prelude.static_array.did_construct(exp_type)
if sa_args is not None:
sa_type, sa_len = sa_args sa_type, sa_len = sa_args
if sa_len.value != len(self.args): if sa_len.value != len(self.args):
@ -186,8 +181,7 @@ class TupleMatchConstraint(ConstraintBase):
for arg in self.args for arg in self.args
] ]
tp_args = prelude.tuple_.did_construct(exp_type) def _generate_tuple(self, tp_args: tuple[types.Type3, ...]) -> CheckResult:
if tp_args is not None:
if len(tp_args) != len(self.args): if len(tp_args) != len(self.args):
return Error('Mismatch between applied types argument count', comment=self.comment) return Error('Mismatch between applied types argument count', comment=self.comment)
@ -196,6 +190,21 @@ class TupleMatchConstraint(ConstraintBase):
for arg, oth_arg in zip(self.args, tp_args, strict=True) for arg, oth_arg in zip(self.args, tp_args, strict=True)
] ]
GENERATE_ROUTER = TypeApplicationRouter['TupleMatchConstraint', CheckResult]()
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
def check(self) -> CheckResult:
exp_type = self.exp_type
if isinstance(exp_type, placeholders.PlaceholderForType):
if exp_type.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = exp_type.resolve_as
try:
return self.__class__.GENERATE_ROUTER(self, exp_type)
except NoRouteForTypeException:
raise NotImplementedError(exp_type) raise NotImplementedError(exp_type)
class MustImplementTypeClassConstraint(ConstraintBase): class MustImplementTypeClassConstraint(ConstraintBase):
@ -220,7 +229,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
self.types = types self.types = types
def check(self) -> CheckResult: def check(self) -> CheckResult:
typ_list = [] typ_list: list[types.Type3 | types.TypeConstructor_Base[Any] | types.TypeConstructor_Struct] = []
for typ in self.types: for typ in self.types:
if isinstance(typ, placeholders.PlaceholderForType) and typ.resolve_as is not None: if isinstance(typ, placeholders.PlaceholderForType) and typ.resolve_as is not None:
typ = typ.resolve_as typ = typ.resolve_as
@ -228,7 +237,15 @@ class MustImplementTypeClassConstraint(ConstraintBase):
if isinstance(typ, placeholders.PlaceholderForType): if isinstance(typ, placeholders.PlaceholderForType):
return RequireTypeSubstitutes() return RequireTypeSubstitutes()
if isinstance(typ.application, (types.TypeApplication_Nullary, types.TypeApplication_Struct, )):
typ_list.append(typ) typ_list.append(typ)
continue
if isinstance(typ.application, (types.TypeApplication_TypeInt, types.TypeApplication_TypeStar)):
typ_list.append(typ.application.constructor)
continue
raise NotImplementedError(typ, typ.application)
assert len(typ_list) == len(self.types) assert len(typ_list) == len(self.types)
@ -281,6 +298,85 @@ class LiteralFitsConstraint(ConstraintBase):
self.type3 = type3 self.type3 = type3
self.literal = literal self.literal = literal
def _generate_static_array(self, sa_args: tuple[types.Type3, types.IntType3]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
sa_type, sa_len = sa_args
if sa_len.value != len(self.literal.value):
return Error('Member count mismatch', comment=self.comment)
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(sa_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(sa_type, PlaceholderForType([y]))
for y in self.literal.value
)
return res
def _generate_struct(self, st_args: tuple[tuple[str, types.Type3], ...]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantStruct):
return Error('Must be struct')
if len(st_args) != len(self.literal.value):
return Error('Struct element count mismatch')
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(x, y)
for (_, x), y in zip(st_args, self.literal.value, strict=True)
)
# 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(x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_name}.{x_n}')
for (x_n, x_t, ), y in zip(st_args, self.literal.value, strict=True)
)
return res
def _generate_tuple(self, tp_args: tuple[types.Type3, ...]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
if len(tp_args) != len(self.literal.value):
return Error('Tuple element count mismatch', comment=self.comment)
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(x, y)
for x, y in zip(tp_args, self.literal.value, strict=True)
)
# 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(x, PlaceholderForType([y]))
for x, y in zip(tp_args, self.literal.value, strict=True)
)
return res
GENERATE_ROUTER = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]()
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.struct, _generate_struct)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
def check(self) -> CheckResult: def check(self) -> CheckResult:
int_table: Dict[str, Tuple[int, bool]] = { int_table: Dict[str, Tuple[int, bool]] = {
'u8': (1, False), 'u8': (1, False),
@ -331,92 +427,12 @@ class LiteralFitsConstraint(ConstraintBase):
return Error('Must be bytes', comment=self.comment) # FIXME: Add line information return Error('Must be bytes', comment=self.comment) # FIXME: Add line information
res: NewConstraintList exp_type = self.type3
assert isinstance(self.type3, types.Type3) try:
return self.__class__.GENERATE_ROUTER(self, exp_type)
tp_args = prelude.tuple_.did_construct(self.type3) except NoRouteForTypeException:
if tp_args is not None: raise NotImplementedError(exp_type)
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
if len(tp_args) != len(self.literal.value):
return Error('Tuple element count mismatch', comment=self.comment)
res = []
res.extend(
LiteralFitsConstraint(x, y)
for x, y in zip(tp_args, self.literal.value, strict=True)
)
# 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(x, PlaceholderForType([y]))
for x, y in zip(tp_args, self.literal.value, strict=True)
)
return res
sa_args = prelude.static_array.did_construct(self.type3)
if sa_args is not None:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
sa_type, sa_len = sa_args
if sa_len.value != len(self.literal.value):
return Error('Member count mismatch', comment=self.comment)
res = []
res.extend(
LiteralFitsConstraint(sa_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(sa_type, PlaceholderForType([y]))
for y in self.literal.value
)
return res
st_args = prelude.struct.did_construct(self.type3)
if st_args is not None:
if not isinstance(self.literal, ourlang.ConstantStruct):
return Error('Must be struct')
if self.literal.struct_name != self.type3.name:
return Error('Struct mismatch')
if len(st_args) != len(self.literal.value):
return Error('Struct element count mismatch')
res = []
res.extend(
LiteralFitsConstraint(x, y)
for x, y in zip(st_args.values(), self.literal.value, strict=True)
)
# 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(x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_name}.{x_n}')
for (x_n, x_t, ), y in zip(st_args.items(), self.literal.value, strict=True)
)
return res
raise NotImplementedError(self.type3, self.literal)
def human_readable(self) -> HumanReadableRet: def human_readable(self) -> HumanReadableRet:
return ( return (
@ -456,38 +472,31 @@ class CanBeSubscriptedConstraint(ConstraintBase):
self.index = index self.index = index
self.index_phft = index_phft self.index_phft = index_phft
def check(self) -> CheckResult: def _generate_bytes(self) -> CheckResult:
exp_type = self.type3 return [
if isinstance(exp_type, placeholders.PlaceholderForType): SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: bytes -> u32 -> u8'),
if exp_type.resolve_as is None: SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
return RequireTypeSubstitutes()
exp_type = exp_type.resolve_as
assert isinstance(exp_type, types.Type3)
sa_args = prelude.static_array.did_construct(exp_type)
if sa_args is not None:
sa_type, sa_len = sa_args
result: List[ConstraintBase] = [
SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
] ]
def _generate_static_array(self, sa_args: tuple[types.Type3, types.IntType3]) -> CheckResult:
sa_type, sa_len = sa_args
if isinstance(self.index, ourlang.ConstantPrimitive): if isinstance(self.index, ourlang.ConstantPrimitive):
assert isinstance(self.index.value, int) assert isinstance(self.index.value, int)
if self.index.value < 0 or sa_len.value <= self.index.value: if self.index.value < 0 or sa_len.value <= self.index.value:
return Error('Tuple index out of range') return Error('Tuple index out of range')
return result return [
SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
]
def _generate_tuple(self, tp_args: tuple[types.Type3, ...]) -> CheckResult:
# We special case tuples to allow for ease of use to the programmer # We special case tuples to allow for ease of use to the programmer
# e.g. rather than having to do `fst a` and `snd a` and only have to-sized tuples # e.g. rather than having to do `fst a` and `snd a` and only have to-sized tuples
# we use a[0] and a[1] and allow for a[2] and on. # we use a[0] and a[1] and allow for a[2] and on.
tp_args = prelude.tuple_.did_construct(exp_type)
if tp_args is not None:
if not isinstance(self.index, ourlang.ConstantPrimitive): if not isinstance(self.index, ourlang.ConstantPrimitive):
return Error('Must index with literal') return Error('Must index with literal')
@ -502,12 +511,22 @@ class CanBeSubscriptedConstraint(ConstraintBase):
SameTypeConstraint(tp_args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'), SameTypeConstraint(tp_args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'),
] ]
if exp_type is prelude.bytes_: GENERATE_ROUTER = TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]()
return [ GENERATE_ROUTER.add_n(prelude.bytes_, _generate_bytes)
SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: bytes -> u32 -> u8'), GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
]
def check(self) -> CheckResult:
exp_type = self.type3
if isinstance(exp_type, placeholders.PlaceholderForType):
if exp_type.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = exp_type.resolve_as
try:
return self.__class__.GENERATE_ROUTER(self, exp_type)
except NoRouteForTypeException:
return Error(f'{exp_type.name} cannot be subscripted') return Error(f'{exp_type.name} cannot be subscripted')
def human_readable(self) -> HumanReadableRet: def human_readable(self) -> HumanReadableRet:

View File

@ -39,21 +39,48 @@ def constant(ctx: Context, inp: ourlang.Constant, phft: placeholders.Placeholder
raise NotImplementedError(constant, inp) raise NotImplementedError(constant, inp)
def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.PlaceholderForType) -> ConstraintGenerator: def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, ourlang.Constant): return _expression_function_call(
yield from constant(ctx, inp, phft) ctx,
return f'({inp.operator.name})',
inp.operator.signature,
[inp.left, inp.right],
inp,
phft,
)
if isinstance(inp, ourlang.VariableReference): def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: PlaceholderForType) -> ConstraintGenerator:
yield SameTypeConstraint(inp.variable.type3, phft, return _expression_function_call(
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})') ctx,
return inp.function.name,
inp.function.signature,
inp.arguments,
inp,
phft,
)
if isinstance(inp, ourlang.BinaryOp) or isinstance(inp, ourlang.FunctionCall): def _expression_function_call(
signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature ctx: Context,
arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments func_name: str,
signature: functions.FunctionSignature,
arguments: list[ourlang.Expression],
return_expr: ourlang.Expression,
return_phft: PlaceholderForType,
) -> ConstraintGenerator:
"""
Generates all type-level constraints for a function call.
func_name = f'({inp.operator.name})' if isinstance(inp, ourlang.BinaryOp) else inp.function.name A Binary operator functions pretty much the same as a function call
with two arguments - it's only a syntactic difference.
"""
arg_placeholders = {
arg_expr: PlaceholderForType([arg_expr])
for arg_expr in arguments
}
arg_placeholders[return_expr] = return_phft
for call_arg in arguments:
yield from expression(ctx, call_arg, arg_placeholders[call_arg])
type_var_map = { type_var_map = {
x: placeholders.PlaceholderForType([]) x: placeholders.PlaceholderForType([])
@ -61,11 +88,7 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.Placeho
if isinstance(x, functions.TypeVariable) if isinstance(x, functions.TypeVariable)
} }
arg_placeholders = { print('type_var_map', type_var_map)
arg_expr: PlaceholderForType([arg_expr])
for arg_expr in arguments
}
arg_placeholders[inp] = phft
for arg_expr in arguments: for arg_expr in arguments:
yield from expression(ctx, arg_expr, arg_placeholders[arg_expr]) yield from expression(ctx, arg_expr, arg_placeholders[arg_expr])
@ -81,7 +104,7 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.Placeho
raise NotImplementedError(constraint) raise NotImplementedError(constraint)
for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [inp], strict=True)): for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [return_expr], strict=True)):
if arg_no == len(arguments): if arg_no == len(arguments):
comment = f'The type of a function call to {func_name} is the same as the type that the function returns' comment = f'The type of a function call to {func_name} is the same as the type that the function returns'
else: else:
@ -98,6 +121,24 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.Placeho
raise NotImplementedError(sig_part) raise NotImplementedError(sig_part)
return return
def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, ourlang.Constant):
yield from constant(ctx, inp, phft)
return
if isinstance(inp, ourlang.VariableReference):
yield SameTypeConstraint(inp.variable.type3, phft,
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
return
if isinstance(inp, ourlang.BinaryOp):
yield from expression_binary_op(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionCall):
yield from expression_function_call(ctx, inp, phft)
return
if isinstance(inp, ourlang.TupleInstantiation): if isinstance(inp, ourlang.TupleInstantiation):
r_type = [] r_type = []
for arg in inp.elements: for arg in inp.elements:
@ -124,12 +165,12 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.Placeho
return return
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.AccessStructMember):
assert isinstance(inp.struct_type3, type3types.Type3) # When does this happen? assert isinstance(inp.struct_type3.application, type3types.TypeApplication_Struct) # FIXME: See test_struct.py::test_struct_not_accessible
st_args = prelude.struct.did_construct(inp.struct_type3)
assert st_args is not None # FIXME: See test_struct.py::test_struct_not_accessible mem_typ = dict(inp.struct_type3.application.arguments)[inp.member]
yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO
yield SameTypeConstraint(st_args[inp.member], phft, yield SameTypeConstraint(mem_typ, phft,
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

View File

@ -19,9 +19,18 @@ class TypeVariable:
letter: str letter: str
def __init__(self, letter: str) -> None: def __init__(self, letter: str) -> None:
assert len(letter) == 1, f'{letter} is not a valid type variable'
self.letter = letter self.letter = letter
def deconstruct(self) -> 'TypeConstructorVariable | None':
letter_list = self.letter.split(' ')
if len(letter_list) == 1:
return None
if len(letter_list) == 2:
return TypeConstructorVariable(letter_list[0])
raise NotImplementedError(letter_list)
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(self.letter) return hash(self.letter)
@ -34,6 +43,38 @@ class TypeVariable:
def __repr__(self) -> str: def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})' return f'TypeVariable({repr(self.letter)})'
class TypeConstructorVariable:
"""
Types constructor variable are used in function definition.
They are a lot like TypeVariable, except that they represent a
type constructor rather than a type directly.
For now, we only have type constructor variables for kind
* -> *.
"""
__slots__ = ('letter', )
letter: str
def __init__(self, letter: str) -> None:
self.letter = letter
def __hash__(self) -> int:
return hash((self.letter, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeConstructorVariable):
raise NotImplementedError
return (self.letter == other.letter)
def __call__(self, tvar: TypeVariable) -> 'TypeVariable':
return TypeVariable(self.letter + ' ' + tvar.letter)
def __repr__(self) -> str:
return f'TypeConstructorVariable({self.letter!r})'
class ConstraintBase: class ConstraintBase:
__slots__ = () __slots__ = ()
@ -51,21 +92,31 @@ class Constraint_TypeClassInstanceExists(ConstraintBase):
# you can only add a constraint by supplying types for all variables # you can only add a constraint by supplying types for all variables
assert len(self.type_class3.args) == len(self.types) assert len(self.type_class3.args) == len(self.types)
class TypeVariableContext: def __str__(self) -> str:
__slots__ = ('variables', 'constraints', ) return self.type_class3.name + ' ' + ' '.join(x.letter for x in self.types)
def __repr__(self) -> str:
return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})'
class TypeVariableContext:
__slots__ = ('constraints', )
variables: set[TypeVariable]
constraints: list[ConstraintBase] constraints: list[ConstraintBase]
def __init__(self) -> None: def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
self.variables = set() self.constraints = list(constraints)
self.constraints = []
def __copy__(self) -> 'TypeVariableContext': def __copy__(self) -> 'TypeVariableContext':
result = TypeVariableContext() return TypeVariableContext(self.constraints)
result.variables.update(self.variables)
result.constraints.extend(self.constraints) def __str__(self) -> str:
return result if not self.constraints:
return ''
return '(' + ', '.join(str(x) for x in self.constraints) + ') => '
def __repr__(self) -> str:
return f'TypeVariableContext({self.constraints!r})'
class FunctionSignature: class FunctionSignature:
__slots__ = ('context', 'args', ) __slots__ = ('context', 'args', )
@ -76,3 +127,9 @@ class FunctionSignature:
def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable]]) -> None: def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable]]) -> None:
self.context = context.__copy__() self.context = context.__copy__()
self.args = list(args) self.args = list(args)
def __str__(self) -> str:
return str(self.context) + ' -> '.join(x.letter if isinstance(x, TypeVariable) else x.name for x in self.args)
def __repr__(self) -> str:
return f'FunctionSignature({self.context!r}, {self.args!r})'

88
phasm/type3/routers.py Normal file
View File

@ -0,0 +1,88 @@
from typing import Any, Callable, TypeVar
from .functions import FunctionSignature, TypeVariable
from .types import Type3, TypeConstructor_Base
T = TypeVar('T')
class NoRouteForTypeException(Exception):
pass
class TypeApplicationRouter[S, R]:
"""
Helper class to find a method based on a constructed type
"""
__slots__ = ('by_constructor', 'by_type', )
by_constructor: dict[Any, Callable[[S, Any], R]]
"""
Contains all the added routing functions for constructed types
"""
by_type: dict[Type3, Callable[[S], R]]
"""
Contains all the added routing functions for constructed types
"""
def __init__(self) -> None:
self.by_constructor = {}
self.by_type = {}
def add_n(self, typ: Type3, helper: Callable[[S], R]) -> None:
"""
Lets you route to types that were not constructed
Also known types of kind *
"""
self.by_type[typ] = helper
def add(self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None:
self.by_constructor[constructor] = helper
def __call__(self, arg0: S, typ: Type3) -> R:
t_helper = self.by_type.get(typ)
if t_helper is not None:
return t_helper(arg0)
c_helper = self.by_constructor.get(typ.application.constructor)
if c_helper is not None:
return c_helper(arg0, typ.application.arguments)
raise NoRouteForTypeException(arg0, typ)
class FunctionSignatureRouter[S, R]:
"""
Helper class to find a method based on a function signature
"""
__slots__ = ('signature', 'data', )
signature: FunctionSignature
data: dict[tuple[Type3, ...], Callable[[S], R]]
def __init__(self, signature: FunctionSignature) -> None:
self.signature = signature
self.data = {}
def add(self, tv_map: dict[TypeVariable, Type3], helper: Callable[[S], R]) -> None:
key = tuple(
tv_map[x]
for x in self.signature.args
if isinstance(x, TypeVariable)
)
# assert len(key) == len(tv_map), (key, tv_map)
self.data[key] = helper
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
key = tuple(
tv_map[x]
for x in self.signature.args
if isinstance(x, TypeVariable)
)
t_helper = self.data.get(key)
if t_helper is not None:
return t_helper(arg0)
raise NoRouteForTypeException(arg0, tv_map)

View File

@ -2,7 +2,9 @@ from typing import Dict, Iterable, List, Mapping, Optional, Union
from .functions import ( from .functions import (
Constraint_TypeClassInstanceExists, Constraint_TypeClassInstanceExists,
ConstraintBase,
FunctionSignature, FunctionSignature,
TypeConstructorVariable,
TypeVariable, TypeVariable,
TypeVariableContext, TypeVariableContext,
) )
@ -19,6 +21,9 @@ class Type3ClassMethod:
self.name = name self.name = name
self.signature = signature self.signature = signature
def __str__(self) -> str:
return f'{self.name} :: {self.signature}'
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})' return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})'
@ -26,7 +31,7 @@ class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', ) __slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
name: str name: str
args: List[TypeVariable] args: tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable]
methods: Dict[str, Type3ClassMethod] methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod]
inherited_classes: List['Type3Class'] inherited_classes: List['Type3Class']
@ -34,26 +39,60 @@ class Type3Class:
def __init__( def __init__(
self, self,
name: str, name: str,
args: Iterable[TypeVariable], args: tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable],
methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]], methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]], operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
inherited_classes: Optional[List['Type3Class']] = None, inherited_classes: Optional[List['Type3Class']] = None,
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
) -> None: ) -> None:
self.name = name self.name = name
self.args = list(args) self.args = args
context = TypeVariableContext()
context.constraints.append(Constraint_TypeClassInstanceExists(self, args))
self.methods = { self.methods = {
k: Type3ClassMethod(k, FunctionSignature(context, v)) k: Type3ClassMethod(k, _create_signature(v, self))
for k, v in methods.items() for k, v in methods.items()
} }
self.operators = { self.operators = {
k: Type3ClassMethod(k, FunctionSignature(context, v)) k: Type3ClassMethod(k, _create_signature(v, self))
for k, v in operators.items() for k, v in operators.items()
} }
self.inherited_classes = inherited_classes or [] self.inherited_classes = inherited_classes or []
if additional_context:
for func_name, constraint_list in additional_context.items():
func = self.methods.get(func_name) or self.operators.get(func_name)
assert func is not None # type hint
func.signature.context.constraints.extend(constraint_list)
def __repr__(self) -> str: def __repr__(self) -> str:
return self.name return self.name
def _create_signature(
method_arg_list: Iterable[Type3 | TypeVariable],
type_class3: Type3Class,
) -> FunctionSignature:
context = TypeVariableContext()
if not isinstance(type_class3.args[0], TypeConstructorVariable):
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, type_class3.args))
signature_args: list[Type3 | TypeVariable] = []
for method_arg in method_arg_list:
if isinstance(method_arg, Type3):
signature_args.append(method_arg)
continue
if isinstance(method_arg, TypeVariable):
type_constructor = method_arg.deconstruct()
if type_constructor is None:
signature_args.append(method_arg)
continue
if (type_constructor, ) == type_class3.args:
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, [method_arg]))
signature_args.append(method_arg)
continue
raise NotImplementedError(method_arg)
return FunctionSignature(context, signature_args)

View File

@ -4,15 +4,34 @@ Contains the final types for use in Phasm, as well as construtors.
from typing import ( from typing import (
Any, Any,
Callable, Callable,
Generic, Hashable,
Self,
Tuple, Tuple,
TypeVar, TypeVar,
) )
S = TypeVar('S')
T = TypeVar('T')
class KindArgument: class KindArgument:
pass pass
class TypeApplication_Base[T: Hashable, S: Hashable]:
"""
Records the constructor and arguments used to create this type.
Nullary types, or types of kind *, have both arguments set to None.
"""
constructor: T
arguments: S
def __init__(self, constructor: T, arguments: S) -> None:
self.constructor = constructor
self.arguments = arguments
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
class Type3(KindArgument): class Type3(KindArgument):
""" """
Base class for the type3 types Base class for the type3 types
@ -20,18 +39,25 @@ class Type3(KindArgument):
(Having a separate name makes it easier to distinguish from (Having a separate name makes it easier to distinguish from
Python's Type) Python's Type)
""" """
__slots__ = ('name', ) __slots__ = ('name', 'application', )
name: str name: str
""" """
The name of the string, as parsed and outputted by codestyle. The name of the string, as parsed and outputted by codestyle.
""" """
def __init__(self, name: str) -> None: application: TypeApplication_Base[Any, Any]
"""
How the type was constructed; i.e. which constructor was used and which
type level arguments were applied to the constructor.
"""
def __init__(self, name: str, application: TypeApplication_Base[Any, Any]) -> None:
self.name = name self.name = name
self.application = application
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Type3({repr(self.name)})' return f'Type3({self.name!r}, {self.application!r})'
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
@ -57,6 +83,9 @@ class Type3(KindArgument):
def __bool__(self) -> bool: def __bool__(self) -> bool:
raise NotImplementedError raise NotImplementedError
class TypeApplication_Nullary(TypeApplication_Base[None, None]):
pass
class IntType3(KindArgument): class IntType3(KindArgument):
""" """
Sometimes you can have an int on the type level, e.g. when using static arrays Sometimes you can have an int on the type level, e.g. when using static arrays
@ -93,20 +122,18 @@ class IntType3(KindArgument):
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(self.value) return hash(self.value)
T = TypeVar('T') class TypeConstructor_Base[T]:
class TypeConstructor(Generic[T]):
""" """
Base class for type construtors Base class for type construtors
""" """
__slots__ = ('name', 'on_create', '_cache', '_reverse_cache') __slots__ = ('name', 'on_create', '_cache', )
name: str name: str
""" """
The name of the type constructor The name of the type constructor
""" """
on_create: Callable[[Type3], None] on_create: Callable[[T, Type3], None]
""" """
Who to let know if a type is created Who to let know if a type is created
""" """
@ -117,31 +144,26 @@ class TypeConstructor(Generic[T]):
it should produce the exact same result. it should produce the exact same result.
""" """
_reverse_cache: dict[Type3, T] def __init__(self, name: str, on_create: Callable[[T, Type3], None]) -> None:
"""
Sometimes we need to know the key that created a type.
"""
def __init__(self, name: str, on_create: Callable[[Type3], None]) -> None:
self.name = name self.name = name
self.on_create = on_create self.on_create = on_create
self._cache = {} self._cache = {}
self._reverse_cache = {}
def make_name(self, key: T) -> str: def make_name(self, key: T) -> str:
""" """
Renders the type's name based on the given arguments Renders the type's name based on the given arguments
""" """
raise NotImplementedError raise NotImplementedError('make_name', self)
def did_construct(self, typ: Type3) -> T | None: def make_application(self, key: T) -> TypeApplication_Base[Self, T]:
""" """
Was the given type constructed by this constructor? Records how the type was constructed into type.
If so, which arguments where used? The type checker and compiler will need to know what
arguments where made to construct the type.
""" """
return self._reverse_cache.get(typ) raise NotImplementedError('make_application', self)
def construct(self, key: T) -> Type3: def construct(self, key: T) -> Type3:
""" """
@ -150,22 +172,12 @@ class TypeConstructor(Generic[T]):
""" """
result = self._cache.get(key, None) result = self._cache.get(key, None)
if result is None: if result is None:
self._cache[key] = result = Type3(self.make_name(key)) self._cache[key] = result = Type3(self.make_name(key), self.make_application(key))
self._reverse_cache[result] = key self.on_create(key, result)
self.on_create(result)
return result return result
class TypeConstructor_Type(TypeConstructor[Type3]): class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]):
"""
Base class type constructors of kind: * -> *
"""
__slots__ = ()
def __call__(self, arg: Type3) -> Type3:
raise NotImplementedError
class TypeConstructor_TypeInt(TypeConstructor[Tuple[Type3, IntType3]]):
""" """
Base class type constructors of kind: * -> Int -> * Base class type constructors of kind: * -> Int -> *
@ -173,22 +185,34 @@ class TypeConstructor_TypeInt(TypeConstructor[Tuple[Type3, IntType3]]):
""" """
__slots__ = () __slots__ = ()
def make_application(self, key: Tuple[Type3, IntType3]) -> 'TypeApplication_TypeInt':
return TypeApplication_TypeInt(self, key)
def make_name(self, key: Tuple[Type3, IntType3]) -> str: def make_name(self, key: Tuple[Type3, IntType3]) -> str:
return f'{self.name} {key[0].name} {key[1].value}' return f'{self.name} {key[0].name} {key[1].value}'
def __call__(self, arg0: Type3, arg1: IntType3) -> Type3: def __call__(self, arg0: Type3, arg1: IntType3) -> Type3:
return self.construct((arg0, arg1)) return self.construct((arg0, arg1))
class TypeConstructor_TypeStar(TypeConstructor[Tuple[Type3, ...]]): class TypeApplication_TypeInt(TypeApplication_Base[TypeConstructor_TypeInt, Tuple[Type3, IntType3]]):
pass
class TypeConstructor_TypeStar(TypeConstructor_Base[Tuple[Type3, ...]]):
""" """
Base class type constructors of variadic kind Base class type constructors of variadic kind
Notably, tuple. Notably, tuple.
""" """
def make_application(self, key: Tuple[Type3, ...]) -> 'TypeApplication_TypeStar':
return TypeApplication_TypeStar(self, key)
def __call__(self, *args: Type3) -> Type3: def __call__(self, *args: Type3) -> Type3:
key: Tuple[Type3, ...] = tuple(args) key: Tuple[Type3, ...] = tuple(args)
return self.construct(key) return self.construct(key)
class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]):
pass
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}]'
@ -197,52 +221,30 @@ class TypeConstructor_Tuple(TypeConstructor_TypeStar):
def make_name(self, key: Tuple[Type3, ...]) -> str: def make_name(self, key: Tuple[Type3, ...]) -> str:
return '(' + ', '.join(x.name for x in key) + ', )' return '(' + ', '.join(x.name for x in key) + ', )'
class TypeConstructor_Struct: class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]):
""" """
Base class for type construtors Constructs struct types
""" """
__slots__ = ('name', 'on_create', '_cache', '_reverse_cache') def make_application(self, key: tuple[tuple[str, Type3], ...]) -> 'TypeApplication_Struct':
return TypeApplication_Struct(self, key)
name: str def make_name(self, key: tuple[tuple[str, Type3], ...]) -> str:
""" return f'{self.name}(' + ', '.join(
The name of the type constructor f'{n}: {t.name}'
""" for n, t in key
) + ')'
on_create: Callable[[Type3], None] def construct(self, key: T) -> Type3:
""" """
Who to let know if a type is created Constructs the type by applying the given arguments to this
constructor.
""" """
raise Exception('This does not work with the caching system')
_cache: dict[str, Type3] def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3:
""" result = Type3(name, self.make_application(args))
When constructing a type with the same arguments, self.on_create(args, result)
it should produce the exact same result.
"""
_reverse_cache: dict[Type3, dict[str, Type3]]
"""
After construction you may need to look up the arguments
used for making the type
"""
def __init__(self, name: str, on_create: Callable[[Type3], None]) -> None:
self.name = name
self.on_create = on_create
self._cache = {}
self._reverse_cache = {}
def did_construct(self, typ: Type3) -> dict[str, Type3] | None:
"""
Was the given type constructed by this constructor?
If so, which arguments where used?
"""
return self._reverse_cache.get(typ)
def __call__(self, name: str, args: dict[str, Type3]) -> Type3:
result = Type3(name)
self._reverse_cache[result] = args
self.on_create(result)
return result return result
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):
pass

View File

@ -4,8 +4,14 @@ from typing import Any, Generator, Iterable, List, TextIO, Union
from phasm import compiler, prelude from phasm import compiler, prelude
from phasm.codestyle import phasm_render from phasm.codestyle import phasm_render
from phasm.runtime import calculate_alloc_size from phasm.runtime import (
calculate_alloc_size,
calculate_alloc_size_static_array,
calculate_alloc_size_struct,
calculate_alloc_size_tuple,
)
from phasm.type3 import types as type3types from phasm.type3 import types as type3types
from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter
from . import runners from . import runners
@ -79,29 +85,10 @@ class Suite:
wasm_args.append(arg) wasm_args.append(arg)
continue continue
if arg_typ is prelude.bytes_: try:
adr = _allocate_memory_stored_value(runner, arg_typ, arg) adr = ALLOCATE_MEMORY_STORED_ROUTER((runner, arg), arg_typ)
wasm_args.append(adr) wasm_args.append(adr)
continue except NoRouteForTypeException:
sa_args = prelude.static_array.did_construct(arg_typ)
if sa_args is not None:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
tp_args = prelude.tuple_.did_construct(arg_typ)
if tp_args is not None:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
st_args = prelude.struct.did_construct(arg_typ)
if st_args is not None:
adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr)
continue
raise NotImplementedError(arg_typ, arg) raise NotImplementedError(arg_typ, arg)
write_header(sys.stderr, 'Memory (pre run)') write_header(sys.stderr, 'Memory (pre run)')
@ -141,39 +128,18 @@ def _write_memory_stored_value(
val_typ: type3types.Type3, val_typ: type3types.Type3,
val: Any, val: Any,
) -> int: ) -> int:
if val_typ is prelude.bytes_: try:
adr2 = _allocate_memory_stored_value(runner, val_typ, val) adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4 return 4
except NoRouteForTypeException:
st_args = prelude.struct.did_construct(val_typ)
if st_args is not None:
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
sa_args = prelude.static_array.did_construct(val_typ)
if sa_args is not None:
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
tp_args = prelude.tuple_.did_construct(val_typ)
if tp_args is not None:
adr2 = _allocate_memory_stored_value(runner, val_typ, val)
runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2))
return 4
to_write = WRITE_LOOKUP_MAP[val_typ.name](val) to_write = WRITE_LOOKUP_MAP[val_typ.name](val)
runner.interpreter_write_memory(adr, to_write) runner.interpreter_write_memory(adr, to_write)
return len(to_write) return len(to_write)
def _allocate_memory_stored_value( def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> int:
runner: runners.RunnerBase, runner, val = attrs
val_typ: type3types.Type3,
val: Any
) -> int:
if val_typ is prelude.bytes_:
assert isinstance(val, bytes) assert isinstance(val, bytes)
adr = runner.call('stdlib.types.__alloc_bytes__', len(val)) adr = runner.call('stdlib.types.__alloc_bytes__', len(val))
@ -183,13 +149,14 @@ def _allocate_memory_stored_value(
runner.interpreter_write_memory(adr + 4, val) runner.interpreter_write_memory(adr + 4, val)
return adr return adr
sa_args = prelude.static_array.did_construct(val_typ) def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int:
if sa_args is not None: runner, val = attrs
assert isinstance(val, tuple) assert isinstance(val, tuple)
sa_type, sa_len = sa_args sa_type, sa_len = sa_args
alloc_size = calculate_alloc_size(val_typ) alloc_size = calculate_alloc_size_static_array(False, sa_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size) adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
@ -202,13 +169,32 @@ def _allocate_memory_stored_value(
offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val) offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val)
return adr return adr
val_el_typ: type3types.Type3 def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, type3types.Type3], ...]) -> int:
runner, val = attrs
assert isinstance(val, dict)
alloc_size = calculate_alloc_size_struct(False, st_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
offset = adr
for val_el_name, val_el_typ in st_args:
assert val_el_name in val, f'Missing key value {val_el_name}'
val_el_val = val.pop(val_el_name)
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
assert not val, f'Additional values: {list(val)!r}'
return adr
def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args: tuple[type3types.Type3, ...]) -> int:
runner, val = attrs
tp_args = prelude.tuple_.did_construct(val_typ)
if tp_args is not None:
assert isinstance(val, tuple) assert isinstance(val, tuple)
alloc_size = calculate_alloc_size(val_typ) alloc_size = calculate_alloc_size_tuple(False, tp_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size) adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
@ -220,24 +206,11 @@ def _allocate_memory_stored_value(
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr return adr
st_args = prelude.struct.did_construct(val_typ) ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]()
if st_args is not None: ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes)
assert isinstance(val, dict) ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.static_array, _allocate_memory_stored_static_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct)
alloc_size = calculate_alloc_size(val_typ) ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int)
sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n')
assert list(val.keys()) == list(st_args)
offset = adr
for val_el_name, val_el_typ in st_args.items():
val_el_val = val[val_el_name]
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr
raise NotImplementedError(val_typ, val)
def _load_memory_stored_returned_value( def _load_memory_stored_returned_value(
runner: runners.RunnerBase, runner: runners.RunnerBase,
@ -285,28 +258,9 @@ def _load_memory_stored_returned_value(
assert isinstance(wasm_value, float), wasm_value assert isinstance(wasm_value, float), wasm_value
return wasm_value return wasm_value
if ret_type3 is prelude.bytes_:
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
return _load_bytes_from_address(runner, ret_type3, wasm_value) return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
sa_args = prelude.static_array.did_construct(ret_type3)
if sa_args is not None:
assert isinstance(wasm_value, int), wasm_value
return _load_static_array_from_address(runner, sa_args[0], sa_args[1], wasm_value)
tp_args = prelude.tuple_.did_construct(ret_type3)
if tp_args is not None:
assert isinstance(wasm_value, int), wasm_value
return _load_tuple_from_address(runner, tp_args, wasm_value)
st_args = prelude.struct.did_construct(ret_type3)
if st_args is not None:
return _load_struct_from_address(runner, st_args, wasm_value)
raise NotImplementedError(ret_type3, wasm_value)
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any: def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any:
if typ is prelude.u8: if typ is prelude.u8:
@ -343,39 +297,19 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
assert len(inp) == 8 assert len(inp) == 8
return struct.unpack('<d', inp)[0] return struct.unpack('<d', inp)[0]
if typ is prelude.bytes_:
# Note: For bytes, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0]
return _load_bytes_from_address(runner, typ, adr)
if (prelude.InternalPassAsPointer, (typ, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if (prelude.InternalPassAsPointer, (typ, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
# Note: For applied types, inp should contain a 4 byte pointer # Note: For applied types, inp should contain a 4 byte pointer
assert len(inp) == 4 assert len(inp) == 4
adr = struct.unpack('<I', inp)[0] adr = struct.unpack('<I', inp)[0]
sa_args = prelude.static_array.did_construct(typ) return LOAD_FROM_ADDRESS_ROUTER((runner, adr), typ)
if sa_args is not None:
sa_type, sa_len = sa_args
return _load_static_array_from_address(runner, sa_type, sa_len, adr)
tp_args = prelude.tuple_.did_construct(typ)
if tp_args is not None:
return _load_tuple_from_address(runner, tp_args, adr)
st_args = prelude.struct.did_construct(typ)
if st_args is not None:
# Note: For structs, inp should contain a 4 byte pointer
assert len(inp) == 4
adr = struct.unpack('<I', inp)[0]
return _load_struct_from_address(runner, st_args, adr)
raise NotImplementedError(typ, inp) raise NotImplementedError(typ, inp)
def _load_bytes_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> bytes: def _load_bytes_from_address(attrs: tuple[runners.RunnerBase, int]) -> bytes:
sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n') runner, adr = attrs
sys.stderr.write(f'Reading 0x{adr:08x} bytes\n')
read_bytes = runner.interpreter_read_memory(adr, 4) read_bytes = runner.interpreter_read_memory(adr, 4)
bytes_len, = struct.unpack('<I', read_bytes) bytes_len, = struct.unpack('<I', read_bytes)
@ -388,7 +322,10 @@ 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_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.Type3, len_typ: type3types.IntType3, adr: int) -> Any: 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
sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n') sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n')
sa_len = len_typ.value sa_len = len_typ.value
@ -403,37 +340,42 @@ def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3ty
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
) )
def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.Type3, ...], adr: int) -> Any: def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tuple[tuple[str, type3types.Type3], ...]) -> dict[str, Any]:
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(typ_args)}\n') runner, adr = attrs
arg_sizes = [
calculate_alloc_size(x, is_member=True)
for x in typ_args
]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple(
_unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
)
def _load_struct_from_address(runner: runners.RunnerBase, st_args: dict[str, type3types.Type3], adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n') sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
name_list = list(st_args)
typ_list = list(st_args.values())
assert len(typ_list) == len(st_args)
arg_sizes = [ arg_sizes = [
calculate_alloc_size(x, is_member=True) calculate_alloc_size(x, is_member=True)
for x in typ_list for _, x in st_args
] ]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes)) read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return { return {
arg_name: _unpack(runner, arg_typ, arg_bytes) arg_name: _unpack(runner, arg_typ, arg_bytes)
for arg_name, arg_typ, arg_bytes in zip(name_list, typ_list, _split_read_bytes(read_bytes, arg_sizes), strict=True) for (arg_name, arg_typ, ), arg_bytes in zip(st_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
} }
def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tuple[type3types.Type3, ...]) -> Any:
runner, adr = attrs
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n')
arg_sizes = [
calculate_alloc_size(x, is_member=True)
for x in tp_args
]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple(
_unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(tp_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
)
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.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

@ -0,0 +1,30 @@
import pytest
from ..helpers import Suite
@pytest.mark.integration_test
def test_bytes_export_constant():
code_py = """
CONSTANT: bytes = b'Hello'
@exported
def testEntry() -> bytes:
return CONSTANT
"""
result = Suite(code_py).run_code()
assert b"Hello" == result.returned_value
@pytest.mark.integration_test
def test_bytes_export_instantiation():
code_py = """
@exported
def testEntry() -> bytes:
return b'Hello'
"""
result = Suite(code_py).run_code()
assert b"Hello" == result.returned_value

View File

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

View File

@ -30,3 +30,29 @@ def testEntry() -> i32:
with pytest.raises(Type3Exception, match='Member count mismatch'): with pytest.raises(Type3Exception, match='Member count mismatch'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test
def test_static_array_export_constant():
code_py = """
CONSTANT: u8[3] = (1, 2, 3, )
@exported
def testEntry() -> u8[3]:
return CONSTANT
"""
result = Suite(code_py).run_code()
assert (1, 2, 3) == result.returned_value
@pytest.mark.integration_test
def test_static_array_export_instantiation():
code_py = """
@exported
def testEntry() -> u8[3]:
return (1, 2, 3, )
"""
result = Suite(code_py).run_code()
assert (1, 2, 3) == result.returned_value

View File

@ -112,3 +112,35 @@ def testEntry(x: u8) -> u8:
with pytest.raises(Type3Exception, match='u8 is not struct'): with pytest.raises(Type3Exception, match='u8 is not struct'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test
def test_struct_export_constant():
code_py = """
class CheckedValue:
value: i32
CONSTANT: CheckedValue = CheckedValue(32)
@exported
def testEntry() -> CheckedValue:
return CONSTANT
"""
result = Suite(code_py).run_code()
assert {"value": 32} == result.returned_value
@pytest.mark.integration_test
def test_struct_export_instantiation():
code_py = """
class CheckedValue:
value: i32
@exported
def testEntry() -> CheckedValue:
return CheckedValue(32)
"""
result = Suite(code_py).run_code()
assert {"value": 32} == result.returned_value

View File

@ -23,6 +23,23 @@ def testEntry(f: {type_}) -> u8:
assert exp_result == result.returned_value assert exp_result == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put, exp_result', [
('(u8, u8, u8, )', (45, 46, 47), 47, ),
('u8[5]', (45, 46, 47, 48, 49), 47, ),
('bytes', b'This is a test', 105)
])
def test_subscript_2(type_, in_put, exp_result):
code_py = f"""
@exported
def testEntry(f: {type_}) -> u8:
return f[2]
"""
result = Suite(code_py).run_code(in_put)
assert exp_result == result.returned_value
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_, in_put, exp_result', [ @pytest.mark.parametrize('type_, in_put, exp_result', [
('(u8, u8, )', (45, 46), 45, ), ('(u8, u8, )', (45, 46), 45, ),

View File

@ -70,3 +70,29 @@ CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, )
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'): with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test
def test_tuple_export_constant():
code_py = """
CONSTANT: (u32, u8, u8, ) = (4000, 20, 20, )
@exported
def testEntry() -> (u32, u8, u8, ):
return CONSTANT
"""
result = Suite(code_py).run_code()
assert (4000, 20, 20, ) == result.returned_value
@pytest.mark.integration_test
def test_tuple_export_instantiation():
code_py = """
@exported
def testEntry() -> (u32, u8, u8, ):
return (4000, 20, 20, )
"""
result = Suite(code_py).run_code()
assert (4000, 20, 20, ) == result.returned_value