Compare commits

..

1 Commits

Author SHA1 Message Date
Johan B.W. de Vries
c2eaaa7e4a Removes the special casing for foldl
Has to implement both functions as arguments and type
place holders (variables) for type constructors.
2025-04-27 15:34:10 +02:00
31 changed files with 1483 additions and 2530 deletions

26
TODO.md
View File

@ -11,22 +11,28 @@
- Implement a FizzBuzz example - Implement a FizzBuzz example
- 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?
- Functions don't seem to be a thing on typing level yet?
- static_array and tuple should probably not be PrimitiveType3, but instead subclass AppliedType3?
- 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.
- 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 - Find pytest.mark.skip
- Surely the compiler should build data blocks - There's a weird resolve_as reference in calculate_alloc_size
- Either there should be more of them or less
- At first glance, looks like failure in the typing system
- Related to the FIXME in phasm_type3?
- Related: Parser is putting stuff in ModuleDataBlock
- Casting is not implemented except u32 which is special cased
- Make prelude more an actual thing - Make prelude more an actual thing
- Merge in compiler.INSTANCES - Merge in type3types.LOOKUP_TABLE
- Make it less build in - have a environment class of some kind
- Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded - Implemented Bounded: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#t:Bounded
- Try to implement the min and max functions using select - Try to implement the min and max functions using select
- Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed - Filter out methods that aren't used; other the other way around (easier?) only add __ methods when needed
- Move UnaryOp.operator into type class methods
- Move foldr into type class methods - Move foldr into type class methods
- Functions don't seem to be a thing on typing level yet? - PrimitiveType is just the type, nothing primitive about it (change the name). u32 are still basic types or something.
- Related to the FIXME in phasm_type3? - Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.
- Type constuctor should also be able to constuct placeholders - somehow. - Type constuctor should also be able to constuct placeholders - somehow.
- Make struct a type constructor?
- Add InternalPassAsPointer to Struct?

View File

@ -51,7 +51,7 @@ _CRC32_Table: u32[256] = (
) )
def _crc32_f(crc: u32, byt: u8) -> u32: def _crc32_f(crc: u32, byt: u8) -> u32:
return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ extend(byt)] return (crc >> 8) ^ _CRC32_Table[(crc & 0xFF) ^ u32(byt)]
@exported @exported
def crc32(data: bytes) -> u32: def crc32(data: bytes) -> u32:

View File

@ -6,7 +6,8 @@ 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, TypeApplication_Struct from .type3.placeholders import TYPE3_ASSERTION_ERROR, Type3OrPlaceholder
from .type3.types import Type3
def phasm_render(inp: ourlang.Module) -> str: def phasm_render(inp: ourlang.Module) -> str:
@ -17,10 +18,12 @@ def phasm_render(inp: ourlang.Module) -> str:
Statements = Generator[str, None, None] Statements = Generator[str, None, None]
def type3(inp: Type3) -> str: def type3(inp: Type3OrPlaceholder) -> str:
""" """
Render: type's name Render: type's name
""" """
assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR
if inp is prelude.none: if inp is prelude.none:
return 'None' return 'None'
@ -30,10 +33,11 @@ def struct_definition(inp: ourlang.StructDefinition) -> str:
""" """
Render: TypeStruct's definition Render: TypeStruct's definition
""" """
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) st_args = prelude.struct.did_construct(inp.struct_type3)
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 inp.struct_type3.application.arguments: for mem, typ in st_args.items():
result += f' {mem}: {type3(typ)}\n' result += f' {mem}: {type3(typ)}\n'
return result return result
@ -63,7 +67,7 @@ def expression(inp: ourlang.Expression) -> str:
) + ', )' ) + ', )'
if isinstance(inp, ourlang.ConstantStruct): if isinstance(inp, ourlang.ConstantStruct):
return inp.struct_type3.name + '(' + ', '.join( return inp.struct_name + '(' + ', '.join(
expression(x) expression(x)
for x in inp.value for x in inp.value
) + ')' ) + ')'
@ -71,6 +75,16 @@ def expression(inp: ourlang.Expression) -> str:
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
return str(inp.variable.name) return str(inp.variable.name)
if isinstance(inp, ourlang.UnaryOp):
if inp.operator == 'cast':
mtyp = type3(inp.type3)
if mtyp is None:
raise NotImplementedError(f'Casting to type {inp.type_var}')
return f'{mtyp}({expression(inp.right)})'
return f'{inp.operator}{expression(inp.right)}'
if isinstance(inp, ourlang.BinaryOp): if isinstance(inp, ourlang.BinaryOp):
return f'{expression(inp.left)} {inp.operator.name} {expression(inp.right)}' return f'{expression(inp.left)} {inp.operator.name} {expression(inp.right)}'

View File

@ -2,28 +2,18 @@
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 List, Optional from typing import Dict, List, Optional
from . import ourlang, prelude, wasm from . import ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset from .runtime import calculate_alloc_size, calculate_member_offset
from .stdlib import alloc as stdlib_alloc from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types from .stdlib import types as stdlib_types
from .type3.functions import TypeVariable from .type3 import functions as type3functions
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter from .type3 import placeholders as type3placeholders
from .type3.typeclasses import Type3ClassMethod from .type3 import typeclasses as type3classes
from .type3.types import ( from .type3 import types as type3types
IntType3,
Type3,
TypeApplication_Struct,
TypeApplication_TypeInt,
TypeApplication_TypeStar,
TypeConstructor_StaticArray,
TypeConstructor_Tuple,
)
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'
LOAD_STORE_TYPE_MAP = { LOAD_STORE_TYPE_MAP = {
'i8': 'i32', # Have to use an u32, since there is no native i8 type 'i8': 'i32', # Have to use an u32, since there is no native i8 type
'u8': 'i32', # Have to use an u32, since there is no native u8 type 'u8': 'i32', # Have to use an u32, since there is no native u8 type
@ -38,6 +28,217 @@ 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,
},
}
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
@ -45,14 +246,14 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module:
""" """
return module(inp) return module(inp)
def type3(inp: Type3) -> wasm.WasmType: def type3(inp: type3placeholders.Type3OrPlaceholder) -> wasm.WasmType:
""" """
Compile: type Compile: type
Types are used for example in WebAssembly function parameters Types are used for example in WebAssembly function parameters
and return types. and return types.
""" """
assert inp is not None, TYPE3_ASSERTION_ERROR assert isinstance(inp, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
if inp == prelude.none: if inp == prelude.none:
return wasm.WasmTypeNone() return wasm.WasmTypeNone()
@ -95,7 +296,7 @@ def type3(inp: Type3) -> wasm.WasmType:
# And pointers are i32 # And pointers are i32
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
if (prelude.InternalPassAsPointer, (inp, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in inp.classes:
return wasm.WasmTypeInt32() return wasm.WasmTypeInt32()
raise NotImplementedError(type3, inp) raise NotImplementedError(type3, inp)
@ -104,30 +305,25 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
""" """
Compile: Instantiation (allocation) of a tuple Compile: Instantiation (allocation) of a tuple
""" """
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3)
args: tuple[Type3, ...] args: list[type3types.Type3] = []
if isinstance(inp.type3.application, TypeApplication_TypeStar): sa_args = prelude.static_array.did_construct(inp.type3)
# Possibly paranoid assert. If we have a future variadic type, if sa_args is not None:
# does it also do this tuple instantation like this? sa_type, sa_len = sa_args
assert isinstance(inp.type3.application.constructor, TypeConstructor_Tuple) args = [sa_type for _ in range(sa_len.value)]
args = inp.type3.application.arguments if not args:
elif isinstance(inp.type3.application, TypeApplication_TypeInt): tp_args = prelude.tuple_.did_construct(inp.type3)
# Possibly paranoid assert. If we have a future type of kind * -> Int -> *, if tp_args is None:
# does it also do this tuple instantation like this? raise NotImplementedError
assert isinstance(inp.type3.application.constructor, TypeConstructor_StaticArray)
sa_type, sa_len = inp.type3.application.arguments args = list(tp_args)
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:
assert element.type3 is not None, TYPE3_ASSERTION_ERROR assert isinstance(element.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
comment_elements += f'{element.type3.name}, ' comment_elements += f'{element.type3.name}, '
tmp_var = wgn.temp_var_i32('tuple_adr') tmp_var = wgn.temp_var_i32('tuple_adr')
@ -141,11 +337,17 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
# Store each element individually # Store each element individually
offset = 0 offset = 0
for element, exp_type3 in zip(inp.elements, args, strict=True): for element, exp_type3 in zip(inp.elements, args, strict=True):
if isinstance(exp_type3, type3placeholders.PlaceholderForType):
assert exp_type3.resolve_as is not None
assert isinstance(exp_type3.resolve_as, type3types.Type3)
exp_type3 = exp_type3.resolve_as
assert element.type3 == exp_type3 assert element.type3 == exp_type3
if (prelude.InternalPassAsPointer, (exp_type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in exp_type3.classes:
mtyp = 'i32' mtyp = 'i32'
else: else:
assert isinstance(exp_type3, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name] mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name]
wgn.add_statement('nop', comment='PRE') wgn.add_statement('nop', comment='PRE')
@ -159,78 +361,6 @@ 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[Type3, 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[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
@ -240,7 +370,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
raise Exception raise Exception
if isinstance(inp, ourlang.ConstantPrimitive): if isinstance(inp, ourlang.ConstantPrimitive):
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
if inp.type3 in (prelude.i8, prelude.u8, ): if inp.type3 in (prelude.i8, prelude.u8, ):
# No native u8 type - treat as i32, with caution # No native u8 type - treat as i32, with caution
@ -281,9 +411,9 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
return return
if isinstance(inp.variable, ourlang.ModuleConstantDef): if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
if (prelude.InternalPassAsPointer, (inp.type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in inp.type3.classes:
assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, )) assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, ))
address = inp.variable.constant.data_block.address address = inp.variable.constant.data_block.address
@ -291,62 +421,82 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.i32.const(address) wgn.i32.const(address)
return return
if isinstance(inp.type3, type3types.Type3):
expression(wgn, inp.variable.constant) expression(wgn, inp.variable.constant)
return return
raise NotImplementedError(expression, inp)
raise NotImplementedError(expression, inp.variable) raise NotImplementedError(expression, inp.variable)
if isinstance(inp, ourlang.BinaryOp): if isinstance(inp, ourlang.BinaryOp):
expression(wgn, inp.left) expression(wgn, inp.left)
expression(wgn, inp.right) expression(wgn, inp.right)
type_var_map: dict[TypeVariable, Type3] = {} assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
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):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR if not isinstance(type_var, type3functions.TypeVariable):
if isinstance(type_var, Type3):
# Fixed type, not part of the lookup requirements # Fixed type, not part of the lookup requirements
continue continue
if isinstance(type_var, TypeVariable): assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
type_var_map[type_var] = arg_expr.type3 type_var_map[type_var] = arg_expr.type3
continue
raise NotImplementedError(type_var, arg_expr.type3) instance_key = ','.join(
f'{k.letter}={v.name}'
for k, v in type_var_map.items()
)
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.operator] instance = INSTANCES.get(inp.operator, {}).get(instance_key, None)
router(wgn, type_var_map) if instance is not None:
instance(wgn)
return return
raise NotImplementedError(inp.operator, instance_key)
if isinstance(inp, ourlang.UnaryOp):
expression(wgn, inp.right)
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
if inp.operator == 'cast':
if inp.type3 == prelude.u32 and inp.right.type3 == prelude.u8:
# Nothing to do, you can use an u8 value as a u32 no problem
return
raise NotImplementedError(expression, inp.type3, inp.operator)
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)
if isinstance(inp.function, Type3ClassMethod): if isinstance(inp.function, type3classes.Type3ClassMethod):
# FIXME: Duplicate code with BinaryOp # FIXME: Duplicate code with BinaryOp
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):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR if not isinstance(type_var, type3functions.TypeVariable):
if isinstance(type_var, Type3):
# Fixed type, not part of the lookup requirements # Fixed type, not part of the lookup requirements
continue continue
if isinstance(type_var, TypeVariable): assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
type_var_map[type_var] = arg_expr.type3 type_var_map[type_var] = arg_expr.type3
continue
raise NotImplementedError(type_var, arg_expr.type3) instance_key = ','.join(
f'{k.letter}={v.name}'
for k, v in type_var_map.items()
)
router = prelude.PRELUDE_TYPE_CLASS_INSTANCE_METHODS[inp.function] instance = INSTANCES.get(inp.function, {}).get(instance_key, None)
try: if instance is not None:
router(wgn, type_var_map) instance(wgn)
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
@ -355,29 +505,169 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
return return
if isinstance(inp, ourlang.Subscript): if isinstance(inp, ourlang.Subscript):
assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR assert isinstance(inp.varref.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
# Type checker guarantees we don't get routing errors if inp.varref.type3 is prelude.bytes_:
SUBSCRIPT_ROUTER((wgn, inp, ), inp.varref.type3) expression(wgn, inp.varref)
expression(wgn, inp.index)
wgn.call(stdlib_types.__subscript_bytes__)
return return
assert isinstance(inp.varref.type3, type3types.Type3)
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()
assert isinstance(el_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
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 isinstance(el_type, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
offset += calculate_alloc_size(el_type)
el_type = tp_args[inp.index.value]
assert isinstance(el_type, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
expression(wgn, inp.varref)
if prelude.InternalPassAsPointer in el_type.classes:
mtyp = 'i32'
else:
assert isinstance(el_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
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 isinstance(inp.struct_type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) st_args = prelude.struct.did_construct(inp.struct_type3)
assert st_args is not None
member_type = dict(inp.struct_type3.application.arguments)[inp.member] member_type = st_args[inp.member]
assert isinstance(member_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
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, inp.struct_type3.application.arguments, inp.member inp.struct_type3.name, st_args, inp.member
))) )))
return return
raise NotImplementedError(expression, inp) raise NotImplementedError(expression, inp)
# def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
# """
# Compile: Fold expression
# """
# assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
# if inp.iter.type3 is not prelude.bytes_:
# raise NotImplementedError(expression_fold, inp, inp.iter.type3)
# wgn.add_statement('nop', comment='acu :: u8')
# acu_var = wgn.temp_var_u8(f'fold_{codestyle.type3(inp.type3)}_acu')
# wgn.add_statement('nop', comment='adr :: bytes*')
# adr_var = wgn.temp_var_i32('fold_i32_adr')
# wgn.add_statement('nop', comment='len :: i32')
# len_var = wgn.temp_var_i32('fold_i32_len')
# wgn.add_statement('nop', comment='acu = base')
# expression(wgn, inp.base)
# wgn.local.set(acu_var)
# wgn.add_statement('nop', comment='adr = adr(iter)')
# expression(wgn, inp.iter)
# wgn.local.set(adr_var)
# wgn.add_statement('nop', comment='len = len(iter)')
# wgn.local.get(adr_var)
# wgn.i32.load()
# wgn.local.set(len_var)
# wgn.add_statement('nop', comment='i = 0')
# idx_var = wgn.temp_var_i32(f'fold_{codestyle.type3(inp.type3)}_idx')
# wgn.i32.const(0)
# wgn.local.set(idx_var)
# wgn.add_statement('nop', comment='if i < len')
# wgn.local.get(idx_var)
# wgn.local.get(len_var)
# wgn.i32.lt_u()
# with wgn.if_():
# # From here on, adr_var is the address of byte we're referencing
# # This is akin to calling stdlib_types.__subscript_bytes__
# # But since we already know we are inside of bounds,
# # can just bypass it and load the memory directly.
# wgn.local.get(adr_var)
# wgn.i32.const(3) # Bytes header -1, since we do a +1 every loop
# wgn.i32.add()
# wgn.local.set(adr_var)
# wgn.add_statement('nop', comment='while True')
# with wgn.loop():
# wgn.add_statement('nop', comment='acu = func(acu, iter[i])')
# wgn.local.get(acu_var)
# # Get the next byte, write back the address
# wgn.local.get(adr_var)
# wgn.i32.const(1)
# wgn.i32.add()
# wgn.local.tee(adr_var)
# wgn.i32.load8_u()
# wgn.add_statement('call', f'${inp.func.name}')
# wgn.local.set(acu_var)
# wgn.add_statement('nop', comment='i = i + 1')
# wgn.local.get(idx_var)
# wgn.i32.const(1)
# wgn.i32.add()
# wgn.local.set(idx_var)
# wgn.add_statement('nop', comment='if i >= len: break')
# wgn.local.get(idx_var)
# wgn.local.get(len_var)
# wgn.i32.lt_u()
# wgn.br_if(0)
# # return acu
# wgn.local.get(acu_var)
def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None: def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None:
""" """
Compile: Return statement Compile: Return statement
@ -535,7 +825,7 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
data_list: List[bytes] = [] data_list: List[bytes] = []
for constant in block.data: for constant in block.data:
assert constant.type3 is not None, TYPE3_ASSERTION_ERROR assert isinstance(constant.type3, type3types.Type3), (id(constant), type3placeholders.TYPE3_ASSERTION_ERROR)
if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block: if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block:
# It's stored in a different block # It's stored in a different block
@ -665,9 +955,8 @@ 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:
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) st_args = prelude.struct.did_construct(inp.struct_type3)
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')
@ -677,9 +966,9 @@ 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: for memname, mtyp3 in st_args.items():
mtyp: Optional[str] mtyp: Optional[str]
if (prelude.InternalPassAsPointer, (mtyp3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if prelude.InternalPassAsPointer in mtyp3.classes:
mtyp = 'i32' mtyp = 'i32'
else: else:
mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name) mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name)

View File

@ -5,8 +5,9 @@ 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.placeholders import PlaceholderForType, Type3OrPlaceholder
from .type3.typeclasses import Type3ClassMethod from .type3.typeclasses import Type3ClassMethod
from .type3.types import Type3, TypeApplication_Struct from .type3.types import Type3
class Expression: class Expression:
@ -15,10 +16,10 @@ class Expression:
""" """
__slots__ = ('type3', ) __slots__ = ('type3', )
type3: Type3 | None type3: Type3OrPlaceholder
def __init__(self) -> None: def __init__(self) -> None:
self.type3 = None self.type3 = PlaceholderForType([self])
class Constant(Expression): class Constant(Expression):
""" """
@ -97,21 +98,21 @@ class ConstantStruct(ConstantMemoryStored):
""" """
A Struct constant value expression within a statement A Struct constant value expression within a statement
""" """
__slots__ = ('struct_type3', 'value', ) __slots__ = ('struct_name', 'value', )
struct_type3: Type3 struct_name: str
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']] value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']]
def __init__(self, struct_type3: Type3, value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']], data_block: 'ModuleDataBlock') -> None: def __init__(self, struct_name: str, value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']], data_block: 'ModuleDataBlock') -> None:
super().__init__(data_block) super().__init__(data_block)
self.struct_type3 = struct_type3 self.struct_name = struct_name
self.value = value self.value = value
def __repr__(self) -> str: def __repr__(self) -> str:
# Do not repr the whole ModuleDataBlock # Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data # As this has a reference back to this constant for its data
# which it needs to compile the data into the program # which it needs to compile the data into the program
return f'ConstantStruct({self.struct_type3!r}, {self.value!r}, @{self.data_block.address!r})' return f'ConstantStruct({repr(self.struct_name)}, {repr(self.value)}, @{repr(self.data_block.address)})'
class VariableReference(Expression): class VariableReference(Expression):
""" """
@ -125,6 +126,21 @@ class VariableReference(Expression):
super().__init__() super().__init__()
self.variable = variable self.variable = variable
class UnaryOp(Expression):
"""
A unary operator expression within a statement
"""
__slots__ = ('operator', 'right', )
operator: str
right: Expression
def __init__(self, operator: str, right: Expression) -> None:
super().__init__()
self.operator = operator
self.right = right
class BinaryOp(Expression): class BinaryOp(Expression):
""" """
A binary operator expression within a statement A binary operator expression within a statement
@ -196,10 +212,10 @@ class AccessStructMember(Expression):
__slots__ = ('varref', 'struct_type3', 'member', ) __slots__ = ('varref', 'struct_type3', 'member', )
varref: VariableReference varref: VariableReference
struct_type3: Type3 struct_type3: Type3OrPlaceholder
member: str member: str
def __init__(self, varref: VariableReference, struct_type3: Type3, member: str) -> None: def __init__(self, varref: VariableReference, struct_type3: Type3OrPlaceholder, member: str) -> None:
super().__init__() super().__init__()
self.varref = varref self.varref = varref
@ -252,7 +268,7 @@ class FunctionParam:
__slots__ = ('name', 'type3', ) __slots__ = ('name', 'type3', )
name: str name: str
type3: Type3 type3: Type3OrPlaceholder
def __init__(self, name: str, type3: Type3) -> None: def __init__(self, name: str, type3: Type3) -> None:
self.name = name self.name = name
@ -310,9 +326,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)
assert isinstance(struct_type3.application, TypeApplication_Struct) st_args = prelude.struct.did_construct(struct_type3)
assert st_args is not None
for mem, typ in struct_type3.application.arguments: for mem, typ in st_args.items():
self.posonlyargs.append(FunctionParam(mem, typ, )) self.posonlyargs.append(FunctionParam(mem, typ, ))
self.signature.args.append(typ) self.signature.args.append(typ)

View File

@ -28,11 +28,12 @@ from .ourlang import (
StructDefinition, StructDefinition,
Subscript, Subscript,
TupleInstantiation, TupleInstantiation,
UnaryOp,
VariableReference, VariableReference,
) )
from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES
from .type3.typeclasses import Type3ClassMethod from .type3 import typeclasses as type3typeclasses
from .type3.types import IntType3, Type3 from .type3 import types as type3types
def phasm_parse(source: str) -> Module: def phasm_parse(source: str) -> Module:
@ -162,8 +163,8 @@ class OurVisitor:
arg_type = self.visit_type(module, arg.annotation) arg_type = self.visit_type(module, arg.annotation)
# FIXME: Allow TypeVariable in the function signature # if isisntance(arg_type, TypeVariable):
# This would also require FunctionParam to accept a placeholder # arg_type = PlaceHolderForType()
function.signature.args.append(arg_type) function.signature.args.append(arg_type)
function.posonlyargs.append(FunctionParam( function.posonlyargs.append(FunctionParam(
@ -225,7 +226,7 @@ class OurVisitor:
_not_implemented(not node.keywords, 'ClassDef.keywords') _not_implemented(not node.keywords, 'ClassDef.keywords')
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list') _not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
members: Dict[str, Type3] = {} members: Dict[str, type3types.Type3] = {}
for stmt in node.body: for stmt in node.body:
if not isinstance(stmt, ast.AnnAssign): if not isinstance(stmt, ast.AnnAssign):
@ -245,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, tuple(members.items())), node.lineno) return StructDefinition(prelude.struct(node.name, members, set()), 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):
@ -351,7 +352,7 @@ class OurVisitor:
def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression: def visit_Module_FunctionDef_expr(self, module: Module, function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp): if isinstance(node, ast.BinOp):
operator: Union[str, Type3ClassMethod] operator: Union[str, type3typeclasses.Type3ClassMethod]
if isinstance(node.op, ast.Add): if isinstance(node.op, ast.Add):
operator = '+' operator = '+'
@ -387,6 +388,19 @@ class OurVisitor:
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
) )
if isinstance(node, ast.UnaryOp):
if isinstance(node.op, ast.UAdd):
operator = '+'
elif isinstance(node.op, ast.USub):
operator = '-'
else:
raise NotImplementedError(f'Operator {node.op}')
return UnaryOp(
operator,
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.operand),
)
if isinstance(node, ast.Compare): if isinstance(node, ast.Compare):
if 1 < len(node.ops): if 1 < len(node.ops):
raise NotImplementedError('Multiple operators') raise NotImplementedError('Multiple operators')
@ -461,7 +475,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as expr in FunctionDef') raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall]: def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall, UnaryOp]:
if node.keywords: if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet? _raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -470,10 +484,42 @@ class OurVisitor:
if not isinstance(node.func.ctx, ast.Load): if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
func: Union[Function, Type3ClassMethod] func: Union[Function, type3typeclasses.Type3ClassMethod]
if node.func.id in PRELUDE_METHODS: if node.func.id in PRELUDE_METHODS:
func = PRELUDE_METHODS[node.func.id] func = PRELUDE_METHODS[node.func.id]
elif node.func.id == 'u32':
if 1 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given')
unary_op = UnaryOp(
'cast',
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]),
)
unary_op.type3 = prelude.u32
return unary_op
# elif node.func.id == 'foldl':
# if 3 != len(node.args):
# _raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given')
# # TODO: This is not generic, you cannot return a function
# subnode = node.args[0]
# if not isinstance(subnode, ast.Name):
# raise NotImplementedError(f'Calling methods that are not a name {subnode}')
# if not isinstance(subnode.ctx, ast.Load):
# _raise_static_error(subnode, 'Must be load context')
# if subnode.id not in module.functions:
# _raise_static_error(subnode, 'Reference to undefined function')
# func = module.functions[subnode.id]
# if 2 != len(func.posonlyargs):
# _raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given')
# return Fold(
# Fold.Direction.LEFT,
# func,
# self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[1]),
# self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[2]),
# )
else: else:
if node.func.id not in module.functions: if node.func.id not in module.functions:
_raise_static_error(node, 'Call to undefined function') _raise_static_error(node, 'Call to undefined function')
@ -561,8 +607,7 @@ class OurVisitor:
if not isinstance(node.func.ctx, ast.Load): if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node.func, 'Must be load context') _raise_static_error(node.func, 'Must be load context')
struct_def = module.struct_definitions.get(node.func.id) if node.func.id not in module.struct_definitions:
if struct_def is None:
_raise_static_error(node.func, 'Undefined struct') _raise_static_error(node.func, 'Undefined struct')
if node.keywords: if node.keywords:
@ -578,7 +623,7 @@ class OurVisitor:
data_block = ModuleDataBlock(struct_data) data_block = ModuleDataBlock(struct_data)
module.data.blocks.append(data_block) module.data.blocks.append(data_block)
return ConstantStruct(struct_def.struct_type3, struct_data, data_block) return ConstantStruct(node.func.id, struct_data, data_block)
_not_implemented(node.kind is None, 'Constant.kind') _not_implemented(node.kind is None, 'Constant.kind')
@ -595,7 +640,7 @@ class OurVisitor:
raise NotImplementedError(f'{node.value} as constant') raise NotImplementedError(f'{node.value} as constant')
def visit_type(self, module: Module, node: ast.expr) -> Type3: def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3:
if isinstance(node, ast.Constant): if isinstance(node, ast.Constant):
if node.value is None: if node.value is None:
return prelude.none return prelude.none
@ -623,7 +668,7 @@ class OurVisitor:
return prelude.static_array( return prelude.static_array(
self.visit_type(module, node.value), self.visit_type(module, node.value),
IntType3(node.slice.value), type3types.IntType3(node.slice.value),
) )
if isinstance(node, ast.Tuple): if isinstance(node, ast.Tuple):

View File

@ -1,129 +1,54 @@
""" """
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 Any, Callable
from warnings import warn
from phasm.stdlib import types as stdtypes from ..type3.typeclasses import (
from phasm.wasmgenerator import Generator Type3Class,
from ..type3.functions import (
Constraint_TypeClassInstanceExists,
TypeConstructorVariable, TypeConstructorVariable,
TypeVariable, TypeVariable,
TypeVariableApplication_Nullary, instance_type_class,
) )
from ..type3.routers import TypeClassArgsRouter, TypeVariableLookup
from ..type3.typeclasses import Type3Class, Type3ClassMethod
from ..type3.types import ( from ..type3.types import (
IntType3,
Type3, Type3,
TypeApplication_Nullary,
TypeConstructor_Base,
TypeConstructor_StaticArray, TypeConstructor_StaticArray,
TypeConstructor_Struct, TypeConstructor_Struct,
TypeConstructor_Tuple, TypeConstructor_Tuple,
) )
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] = set() none = Type3('none', [])
PRELUDE_TYPE_CLASS_INSTANCE_METHODS: dict[Type3ClassMethod, TypeClassArgsRouter[Generator, None]] = {}
class MissingImplementationException(Exception):
pass
class MissingImplementationWarning(Warning):
pass
def instance_type_class(
cls: Type3Class,
*typ: Type3 | TypeConstructor_Base[Any],
methods: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {},
operators: dict[str, Callable[[Generator, TypeVariableLookup], None]] = {},
) -> None:
global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING
global PRELUDE_TYPE_CLASS_INSTANCE_METHODS
assert len(cls.args) == len(typ)
tv_map: dict[TypeVariable, Type3] = {}
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {}
for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
if isinstance(arg_tv, TypeVariable):
assert isinstance(arg_tp, Type3)
tv_map[arg_tv] = arg_tp
elif isinstance(arg_tv, TypeConstructorVariable):
assert isinstance(arg_tp, TypeConstructor_Base)
tc_map[arg_tv] = arg_tp
else:
raise NotImplementedError(arg_tv, arg_tp)
# TODO: Check for required existing instantiations
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING.add((cls, tuple(typ), ))
for method_name, method in cls.methods.items():
router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(method)
if router is None:
router = TypeClassArgsRouter[Generator, None](cls.args)
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(tv_map, tc_map, generator)
for operator_name, operator in cls.operators.items():
router = PRELUDE_TYPE_CLASS_INSTANCE_METHODS.get(operator)
if router is None:
router = TypeClassArgsRouter[Generator, None](cls.args)
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(tv_map, tc_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', TypeApplication_Nullary(None, None)) bool_ = Type3('bool', [])
""" """
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', TypeApplication_Nullary(None, None)) u8 = Type3('u8', [])
""" """
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', TypeApplication_Nullary(None, None)) u32 = Type3('u32', [])
""" """
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', TypeApplication_Nullary(None, None)) u64 = Type3('u64', [])
""" """
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', TypeApplication_Nullary(None, None)) i8 = Type3('i8', [])
""" """
The signed 8-bit integer type. The signed 8-bit integer type.
@ -131,7 +56,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', TypeApplication_Nullary(None, None)) i32 = Type3('i32', [])
""" """
The unsigned 32-bit integer type. The unsigned 32-bit integer type.
@ -139,7 +64,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', TypeApplication_Nullary(None, None)) i64 = Type3('i64', [])
""" """
The unsigned 64-bit integer type. The unsigned 64-bit integer type.
@ -147,25 +72,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', TypeApplication_Nullary(None, None)) f32 = Type3('f32', [])
""" """
A 32-bits IEEE 754 float, of 32 bits width. A 32-bits IEEE 754 float, of 32 bits width.
""" """
f64 = Type3('f64', TypeApplication_Nullary(None, None)) f64 = Type3('f64', [])
""" """
A 32-bits IEEE 754 float, of 64 bits width. A 32-bits IEEE 754 float, of 64 bits width.
""" """
bytes_ = Type3('bytes', TypeApplication_Nullary(None, None)) bytes_ = Type3('bytes', [])
""" """
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(args: tuple[Type3, IntType3], typ: Type3) -> None: static_array = TypeConstructor_StaticArray('static_array', [], [])
instance_type_class(InternalPassAsPointer, typ)
static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create)
""" """
A type constructor. A type constructor.
@ -175,10 +97,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(args: tuple[Type3, ...], typ: Type3) -> None: tuple_ = TypeConstructor_Tuple('tuple', [], [])
instance_type_class(InternalPassAsPointer, typ)
tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create)
""" """
This is a fixed length piece of memory. This is a fixed length piece of memory.
@ -186,10 +105,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(args: tuple[tuple[str, Type3], ...], typ: Type3) -> None: struct = TypeConstructor_Struct('struct', [], [])
instance_type_class(InternalPassAsPointer, typ)
struct = TypeConstructor_Struct('struct', on_create=st_on_create)
""" """
This is like a tuple, but each argument is named, so that developers This is like a tuple, but each argument is named, so that developers
can get and set fields by name. can get and set fields by name.
@ -209,61 +125,37 @@ PRELUDE_TYPES: dict[str, Type3] = {
'bytes': bytes_, 'bytes': bytes_,
} }
a = TypeVariable('a', TypeVariableApplication_Nullary(None, None)) a = TypeVariable('a')
b = TypeVariable('b', TypeVariableApplication_Nullary(None, None)) b = TypeVariable('b')
t = TypeConstructorVariable('t') 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.
""" """
instance_type_class(InternalPassAsPointer, bytes_) instance_type_class(InternalPassAsPointer, bytes_)
# instance_type_class(InternalPassAsPointer, static_array) instance_type_class(InternalPassAsPointer, static_array)
# instance_type_class(InternalPassAsPointer, tuple_) instance_type_class(InternalPassAsPointer, tuple_)
# instance_type_class(InternalPassAsPointer, struct) instance_type_class(InternalPassAsPointer, struct)
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, operators={ instance_type_class(Eq, u8)
'==': stdtypes.u8_eq_equals, instance_type_class(Eq, u32)
'!=': stdtypes.u8_eq_not_equals, instance_type_class(Eq, u64)
}) instance_type_class(Eq, i8)
instance_type_class(Eq, u32, operators={ instance_type_class(Eq, i32)
'==': stdtypes.u32_eq_equals, instance_type_class(Eq, i64)
'!=': stdtypes.u32_eq_not_equals, instance_type_class(Eq, f32)
}) instance_type_class(Eq, f64)
instance_type_class(Eq, u64, operators={ instance_type_class(Eq, static_array)
'==': 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={
@ -273,80 +165,16 @@ Ord = Type3Class('Ord', (a, ), methods={
'>=': [a, a, bool_], '>=': [a, a, bool_],
}, inherited_classes=[Eq]) }, inherited_classes=[Eq])
instance_type_class(Ord, u8, methods={ instance_type_class(Ord, u8)
'min': stdtypes.u8_ord_min, instance_type_class(Ord, u32)
'max': stdtypes.u8_ord_max, instance_type_class(Ord, u64)
}, operators={ instance_type_class(Ord, i8)
'<': stdtypes.u8_ord_less_than, instance_type_class(Ord, i32)
'<=': stdtypes.u8_ord_less_than_or_equal, instance_type_class(Ord, i64)
'>': stdtypes.u8_ord_greater_than, instance_type_class(Ord, f32)
'>=': stdtypes.u8_ord_greater_than_or_equal, instance_type_class(Ord, f64)
})
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
@ -358,38 +186,11 @@ Bits = Type3Class('Bits', (a, ), methods={
'^': [a, a, a], # Bit-wise xor '^': [a, a, a], # Bit-wise xor
}) })
instance_type_class(Bits, u8, methods={ instance_type_class(Bits, u8)
'shl': stdtypes.u8_bits_logical_shift_left, instance_type_class(Bits, u32)
'shr': stdtypes.u8_bits_logical_shift_right, instance_type_class(Bits, u64)
'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],
@ -397,95 +198,35 @@ NatNum = Type3Class('NatNum', (a, ), methods={}, operators={
'>>': [a, u32, a], # Arithmic shift right '>>': [a, u32, a], # Arithmic shift right
}) })
instance_type_class(NatNum, u32, operators={ instance_type_class(NatNum, u32)
'+': stdtypes.u32_natnum_add, instance_type_class(NatNum, u64)
'-': stdtypes.u32_natnum_sub, instance_type_class(NatNum, i32)
'*': stdtypes.u32_natnum_mul, instance_type_class(NatNum, i64)
'<<': stdtypes.u32_natnum_arithmic_shift_left, instance_type_class(NatNum, f32)
'>>': stdtypes.u32_natnum_arithmic_shift_right, instance_type_class(NatNum, f64)
})
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, methods={ instance_type_class(IntNum, i32)
'abs': stdtypes.i32_intnum_abs, instance_type_class(IntNum, i64)
'neg': stdtypes.i32_intnum_neg, instance_type_class(IntNum, f32)
}) 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, operators={ instance_type_class(Integral, u32)
'//': stdtypes.u32_integral_div, instance_type_class(Integral, u64)
'%': stdtypes.u32_integral_rem, instance_type_class(Integral, i32)
}) 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],
@ -494,94 +235,27 @@ Fractional = Type3Class('Fractional', (a, ), methods={
'/': [a, a, a], '/': [a, a, a],
}, inherited_classes=[NatNum]) }, inherited_classes=[NatNum])
instance_type_class(Fractional, f32, methods={ instance_type_class(Fractional, f32)
'ceil': stdtypes.f32_fractional_ceil, instance_type_class(Fractional, f64)
'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, methods={ instance_type_class(Floating, f32)
'sqrt': stdtypes.f32_floating_sqrt, instance_type_class(Floating, f64)
})
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_, methods={ instance_type_class(Sized_, bytes_)
'len': stdtypes.bytes_sized_len,
})
Extendable = Type3Class('Extendable', (a, b, ), methods={ Foldable = Type3Class('Foldable', [t], methods={
'extend': [a, b],
'wrap': [b, a],
}, operators={})
instance_type_class(Extendable, u8, u32, methods={
'extend': stdtypes.u8_u32_extend,
'wrap': stdtypes.u8_u32_wrap,
})
instance_type_class(Extendable, u8, u64, methods={
'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={
'promote': [a, b],
'demote': [b, a],
}, operators={})
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],
'foldl': [[a, b, b], b, t(a), b], 'foldl': [[a, b, b], b, t(a), b],
}, operators={}, additional_context={ }, operators={})
'sum': [Constraint_TypeClassInstanceExists(NatNum, (a, ))],
})
instance_type_class(Foldable, static_array, methods={
'sum': stdtypes.static_array_sum,
})
PRELUDE_TYPE_CLASSES = { PRELUDE_TYPE_CLASSES = {
'Eq': Eq, 'Eq': Eq,
@ -592,8 +266,6 @@ PRELUDE_TYPE_CLASSES = {
'Integral': Integral, 'Integral': Integral,
'Fractional': Fractional, 'Fractional': Fractional,
'Floating': Floating, 'Floating': Floating,
'Extendable': Extendable,
'Promotable': Promotable,
} }
PRELUDE_OPERATORS = { PRELUDE_OPERATORS = {
@ -616,7 +288,4 @@ PRELUDE_METHODS = {
**IntNum.methods, **IntNum.methods,
**NatNum.methods, **NatNum.methods,
**Sized_.methods, **Sized_.methods,
**Extendable.methods,
**Promotable.methods,
**Foldable.methods,
} }

View File

@ -1,40 +1,8 @@
from . import prelude from . import prelude
from .type3.routers import NoRouteForTypeException, TypeApplicationRouter from .type3 import types as type3types
from .type3.types import IntType3, Type3
def calculate_alloc_size_static_array(is_member: bool, args: tuple[Type3, IntType3]) -> int: def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> 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
@ -44,20 +12,51 @@ def calculate_alloc_size(typ: 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
try: if typ == prelude.bytes_:
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(typ) raise NotImplementedError # When does this happen?
def calculate_member_offset(st_name: str, st_args: tuple[tuple[str, Type3], ...], needle: str) -> int: st_args = prelude.struct.did_construct(typ)
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: for memnam, memtyp in st_args.items():
if needle == memnam: if needle == memnam:
return result return result

View File

@ -2,8 +2,6 @@
stdlib: Standard types that are not wasm primitives stdlib: Standard types that are not wasm primitives
""" """
from phasm.stdlib import alloc from phasm.stdlib import alloc
from phasm.type3.routers import TypeVariableLookup
from phasm.type3.types import IntType3, Type3
from phasm.wasmgenerator import Generator, func_wrapper from phasm.wasmgenerator import Generator, func_wrapper
from phasm.wasmgenerator import VarType_i32 as i32 from phasm.wasmgenerator import VarType_i32 as i32
from phasm.wasmgenerator import VarType_i64 as i64 from phasm.wasmgenerator import VarType_i64 as i64
@ -41,7 +39,8 @@ def __subscript_bytes__(g: Generator, adr: i32, ofs: i32) -> i32:
Returns an index from a bytes value Returns an index from a bytes value
If ofs is more than the length of the bytes, this If ofs is more than the length of the bytes, this
function stop as unreachable. function returns 0, following the 'no undefined behaviour'
philosophy.
adr i32 The pointer for the allocated bytes adr i32 The pointer for the allocated bytes
ofs i32 The offset within the allocated bytes ofs i32 The offset within the allocated bytes
@ -389,579 +388,444 @@ def __u8_rotr__(g: Generator, x: i32, r: i32) -> i32:
## ### ## ###
## class Eq ## class Eq
def u8_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_eq_equals(g: Generator) -> None:
del tv_map
g.i32.eq() g.i32.eq()
def u32_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_eq_equals(g: Generator) -> None:
del tv_map
g.i32.eq() g.i32.eq()
def u64_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_eq_equals(g: Generator) -> None:
del tv_map
g.i64.eq() g.i64.eq()
def i8_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def i8_eq_equals(g: Generator) -> None:
del tv_map
g.i32.eq() g.i32.eq()
def i32_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_eq_equals(g: Generator) -> None:
del tv_map
g.i32.eq() g.i32.eq()
def i64_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_eq_equals(g: Generator) -> None:
del tv_map
g.i64.eq() g.i64.eq()
def f32_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_eq_equals(g: Generator) -> None:
del tv_map
g.f32.eq() g.f32.eq()
def f64_eq_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_eq_equals(g: Generator) -> None:
del tv_map
g.f64.eq() g.f64.eq()
def u8_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_eq_not_equals(g: Generator) -> None:
del tv_map
g.i32.ne() g.i32.ne()
def u32_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_eq_not_equals(g: Generator) -> None:
del tv_map
g.i32.ne() g.i32.ne()
def u64_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_eq_not_equals(g: Generator) -> None:
del tv_map
g.i64.ne() g.i64.ne()
def i8_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def i8_eq_not_equals(g: Generator) -> None:
del tv_map
g.i32.ne() g.i32.ne()
def i32_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_eq_not_equals(g: Generator) -> None:
del tv_map
g.i32.ne() g.i32.ne()
def i64_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_eq_not_equals(g: Generator) -> None:
del tv_map
g.i64.ne() g.i64.ne()
def f32_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_eq_not_equals(g: Generator) -> None:
del tv_map
g.f32.ne() g.f32.ne()
def f64_eq_not_equals(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_eq_not_equals(g: Generator) -> None:
del tv_map
g.f64.ne() g.f64.ne()
## ### ## ###
## class Ord ## class Ord
def u8_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_ord_min(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u32_ord_min__') g.add_statement('call $stdlib.types.__u32_ord_min__')
def u32_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_ord_min(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u32_ord_min__') g.add_statement('call $stdlib.types.__u32_ord_min__')
def u64_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_ord_min(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u64_ord_min__') g.add_statement('call $stdlib.types.__u64_ord_min__')
def i8_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: def i8_ord_min(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__i32_ord_min__') g.add_statement('call $stdlib.types.__i32_ord_min__')
def i32_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_ord_min(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__i32_ord_min__') g.add_statement('call $stdlib.types.__i32_ord_min__')
def i64_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_ord_min(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__i64_ord_min__') g.add_statement('call $stdlib.types.__i64_ord_min__')
def f32_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_ord_min(g: Generator) -> None:
del tv_map
g.f32.min() g.f32.min()
def f64_ord_min(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_ord_min(g: Generator) -> None:
del tv_map
g.f64.min() g.f64.min()
def u8_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_ord_max(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u32_ord_max__') g.add_statement('call $stdlib.types.__u32_ord_max__')
def u32_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_ord_max(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u32_ord_max__') g.add_statement('call $stdlib.types.__u32_ord_max__')
def u64_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_ord_max(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u64_ord_max__') g.add_statement('call $stdlib.types.__u64_ord_max__')
def i8_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: def i8_ord_max(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__i32_ord_max__') g.add_statement('call $stdlib.types.__i32_ord_max__')
def i32_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_ord_max(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__i32_ord_max__') g.add_statement('call $stdlib.types.__i32_ord_max__')
def i64_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_ord_max(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__i64_ord_max__') g.add_statement('call $stdlib.types.__i64_ord_max__')
def f32_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_ord_max(g: Generator) -> None:
del tv_map
g.f32.max() g.f32.max()
def f64_ord_max(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_ord_max(g: Generator) -> None:
del tv_map
g.f64.max() g.f64.max()
def u8_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_ord_less_than(g: Generator) -> None:
del tv_map
g.i32.lt_u() g.i32.lt_u()
def u32_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_ord_less_than(g: Generator) -> None:
del tv_map
g.i32.lt_u() g.i32.lt_u()
def u64_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_ord_less_than(g: Generator) -> None:
del tv_map
g.i64.lt_u() g.i64.lt_u()
def i8_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: def i8_ord_less_than(g: Generator) -> None:
del tv_map
g.i32.lt_s() g.i32.lt_s()
def i32_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_ord_less_than(g: Generator) -> None:
del tv_map
g.i32.lt_s() g.i32.lt_s()
def i64_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_ord_less_than(g: Generator) -> None:
del tv_map
g.i64.lt_s() g.i64.lt_s()
def f32_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_ord_less_than(g: Generator) -> None:
del tv_map
g.f32.lt() g.f32.lt()
def f64_ord_less_than(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_ord_less_than(g: Generator) -> None:
del tv_map
g.f64.lt() g.f64.lt()
def u8_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_ord_less_than_or_equal(g: Generator) -> None:
del tv_map
g.i32.le_u() g.i32.le_u()
def u32_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_ord_less_than_or_equal(g: Generator) -> None:
del tv_map
g.i32.le_u() g.i32.le_u()
def u64_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_ord_less_than_or_equal(g: Generator) -> None:
del tv_map
g.i64.le_u() g.i64.le_u()
def i8_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def i8_ord_less_than_or_equal(g: Generator) -> None:
del tv_map
g.i32.le_s() g.i32.le_s()
def i32_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_ord_less_than_or_equal(g: Generator) -> None:
del tv_map
g.i32.le_s() g.i32.le_s()
def i64_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_ord_less_than_or_equal(g: Generator) -> None:
del tv_map
g.i64.le_s() g.i64.le_s()
def f32_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_ord_less_than_or_equal(g: Generator) -> None:
del tv_map
g.f32.le() g.f32.le()
def f64_ord_less_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_ord_less_than_or_equal(g: Generator) -> None:
del tv_map
g.f64.le() g.f64.le()
def u8_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_ord_greater_than(g: Generator) -> None:
del tv_map
g.i32.gt_u() g.i32.gt_u()
def u32_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_ord_greater_than(g: Generator) -> None:
del tv_map
g.i32.gt_u() g.i32.gt_u()
def u64_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_ord_greater_than(g: Generator) -> None:
del tv_map
g.i64.gt_u() g.i64.gt_u()
def i8_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: def i8_ord_greater_than(g: Generator) -> None:
del tv_map
g.i32.gt_s() g.i32.gt_s()
def i32_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_ord_greater_than(g: Generator) -> None:
del tv_map
g.i32.gt_s() g.i32.gt_s()
def i64_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_ord_greater_than(g: Generator) -> None:
del tv_map
g.i64.gt_s() g.i64.gt_s()
def f32_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_ord_greater_than(g: Generator) -> None:
del tv_map
g.f32.gt() g.f32.gt()
def f64_ord_greater_than(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_ord_greater_than(g: Generator) -> None:
del tv_map
g.f64.gt() g.f64.gt()
def u8_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_ord_greater_than_or_equal(g: Generator) -> None:
del tv_map
g.i32.ge_u() g.i32.ge_u()
def u32_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_ord_greater_than_or_equal(g: Generator) -> None:
del tv_map
g.i32.ge_u() g.i32.ge_u()
def u64_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_ord_greater_than_or_equal(g: Generator) -> None:
del tv_map
g.i64.ge_u() g.i64.ge_u()
def i8_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def i8_ord_greater_than_or_equal(g: Generator) -> None:
del tv_map
g.i32.ge_s() g.i32.ge_s()
def i32_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_ord_greater_than_or_equal(g: Generator) -> None:
del tv_map
g.i32.ge_s() g.i32.ge_s()
def i64_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_ord_greater_than_or_equal(g: Generator) -> None:
del tv_map
g.i64.ge_s() g.i64.ge_s()
def f32_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_ord_greater_than_or_equal(g: Generator) -> None:
del tv_map
g.f32.ge() g.f32.ge()
def f64_ord_greater_than_or_equal(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_ord_greater_than_or_equal(g: Generator) -> None:
del tv_map
g.f64.ge() g.f64.ge()
## ### ## ###
## class Bits ## class Bits
def u8_bits_logical_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_bits_logical_shift_left(g: Generator) -> None:
del tv_map
g.i32.shl() g.i32.shl()
g.i32.const(255) g.i32.const(255)
g.i32.and_() g.i32.and_()
def u32_bits_logical_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_bits_logical_shift_left(g: Generator) -> None:
del tv_map
g.i32.shl() g.i32.shl()
def u64_bits_logical_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_bits_logical_shift_left(g: Generator) -> None:
del tv_map
g.i64.extend_i32_u() g.i64.extend_i32_u()
g.i64.shl() g.i64.shl()
def u8_bits_logical_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_bits_logical_shift_right(g: Generator) -> None:
del tv_map
g.i32.shr_u() g.i32.shr_u()
def u32_bits_logical_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_bits_logical_shift_right(g: Generator) -> None:
del tv_map
g.i32.shr_u() g.i32.shr_u()
def u64_bits_logical_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_bits_logical_shift_right(g: Generator) -> None:
del tv_map
g.i64.extend_i32_u() g.i64.extend_i32_u()
g.i64.shr_u() g.i64.shr_u()
def u8_bits_rotate_left(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_bits_rotate_left(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u8_rotl__') g.add_statement('call $stdlib.types.__u8_rotl__')
def u32_bits_rotate_left(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_bits_rotate_left(g: Generator) -> None:
del tv_map
g.i32.rotl() g.i32.rotl()
def u64_bits_rotate_left(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_bits_rotate_left(g: Generator) -> None:
del tv_map
g.i64.extend_i32_u() g.i64.extend_i32_u()
g.i64.rotl() g.i64.rotl()
def u8_bits_rotate_right(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_bits_rotate_right(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u8_rotr__') g.add_statement('call $stdlib.types.__u8_rotr__')
def u32_bits_rotate_right(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_bits_rotate_right(g: Generator) -> None:
del tv_map
g.i32.rotr() g.i32.rotr()
def u64_bits_rotate_right(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_bits_rotate_right(g: Generator) -> None:
del tv_map
g.i64.extend_i32_u() g.i64.extend_i32_u()
g.i64.rotr() g.i64.rotr()
def u8_bits_bitwise_and(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_bits_bitwise_and(g: Generator) -> None:
del tv_map
g.i32.and_() g.i32.and_()
def u32_bits_bitwise_and(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_bits_bitwise_and(g: Generator) -> None:
del tv_map
g.i32.and_() g.i32.and_()
def u64_bits_bitwise_and(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_bits_bitwise_and(g: Generator) -> None:
del tv_map
g.i64.and_() g.i64.and_()
def u8_bits_bitwise_or(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_bits_bitwise_or(g: Generator) -> None:
del tv_map
g.i32.or_() g.i32.or_()
def u32_bits_bitwise_or(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_bits_bitwise_or(g: Generator) -> None:
del tv_map
g.i32.or_() g.i32.or_()
def u64_bits_bitwise_or(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_bits_bitwise_or(g: Generator) -> None:
del tv_map
g.i64.or_() g.i64.or_()
def u8_bits_bitwise_xor(g: Generator, tv_map: TypeVariableLookup) -> None: def u8_bits_bitwise_xor(g: Generator) -> None:
del tv_map
g.i32.xor() g.i32.xor()
def u32_bits_bitwise_xor(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_bits_bitwise_xor(g: Generator) -> None:
del tv_map
g.i32.xor() g.i32.xor()
def u64_bits_bitwise_xor(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_bits_bitwise_xor(g: Generator) -> None:
del tv_map
g.i64.xor() g.i64.xor()
## ### ## ###
## class Fractional ## class Fractional
def f32_fractional_ceil(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_fractional_ceil(g: Generator) -> None:
del tv_map
g.f32.ceil() g.f32.ceil()
def f64_fractional_ceil(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_fractional_ceil(g: Generator) -> None:
del tv_map
g.f64.ceil() g.f64.ceil()
def f32_fractional_floor(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_fractional_floor(g: Generator) -> None:
del tv_map
g.f32.floor() g.f32.floor()
def f64_fractional_floor(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_fractional_floor(g: Generator) -> None:
del tv_map
g.f64.floor() g.f64.floor()
def f32_fractional_trunc(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_fractional_trunc(g: Generator) -> None:
del tv_map
g.f32.trunc() g.f32.trunc()
def f64_fractional_trunc(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_fractional_trunc(g: Generator) -> None:
del tv_map
g.f64.trunc() g.f64.trunc()
def f32_fractional_nearest(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_fractional_nearest(g: Generator) -> None:
del tv_map
g.f32.nearest() g.f32.nearest()
def f64_fractional_nearest(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_fractional_nearest(g: Generator) -> None:
del tv_map
g.f64.nearest() g.f64.nearest()
def f32_fractional_div(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_fractional_div(g: Generator) -> None:
del tv_map
g.f32.div() g.f32.div()
def f64_fractional_div(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_fractional_div(g: Generator) -> None:
del tv_map
g.f64.div() g.f64.div()
## ### ## ###
## class Floating ## class Floating
def f32_floating_sqrt(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_floating_sqrt(g: Generator) -> None:
del tv_map
g.add_statement('f32.sqrt') g.add_statement('f32.sqrt')
def f64_floating_sqrt(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_floating_sqrt(g: Generator) -> None:
del tv_map
g.add_statement('f64.sqrt') g.add_statement('f64.sqrt')
## ### ## ###
## class Integral ## class Integral
def u32_integral_div(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_integral_div(g: Generator) -> None:
del tv_map
g.add_statement('i32.div_u') g.add_statement('i32.div_u')
def u64_integral_div(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_integral_div(g: Generator) -> None:
del tv_map
g.add_statement('i64.div_u') g.add_statement('i64.div_u')
def i32_integral_div(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_integral_div(g: Generator) -> None:
del tv_map
g.add_statement('i32.div_s') g.add_statement('i32.div_s')
def i64_integral_div(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_integral_div(g: Generator) -> None:
del tv_map
g.add_statement('i64.div_s') g.add_statement('i64.div_s')
def u32_integral_rem(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_integral_rem(g: Generator) -> None:
del tv_map
g.add_statement('i32.rem_u') g.add_statement('i32.rem_u')
def u64_integral_rem(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_integral_rem(g: Generator) -> None:
del tv_map
g.add_statement('i64.rem_u') g.add_statement('i64.rem_u')
def i32_integral_rem(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_integral_rem(g: Generator) -> None:
del tv_map
g.add_statement('i32.rem_s') g.add_statement('i32.rem_s')
def i64_integral_rem(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_integral_rem(g: Generator) -> None:
del tv_map
g.add_statement('i64.rem_s') g.add_statement('i64.rem_s')
## ### ## ###
## class NatNum ## class NatNum
def u32_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_natnum_add(g: Generator) -> None:
del tv_map
g.add_statement('i32.add') g.add_statement('i32.add')
def u64_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_natnum_add(g: Generator) -> None:
del tv_map
g.add_statement('i64.add') g.add_statement('i64.add')
def i32_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_natnum_add(g: Generator) -> None:
del tv_map
g.add_statement('i32.add') g.add_statement('i32.add')
def i64_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_natnum_add(g: Generator) -> None:
del tv_map
g.add_statement('i64.add') g.add_statement('i64.add')
def f32_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_natnum_add(g: Generator) -> None:
del tv_map
g.add_statement('f32.add') g.add_statement('f32.add')
def f64_natnum_add(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_natnum_add(g: Generator) -> None:
del tv_map
g.add_statement('f64.add') g.add_statement('f64.add')
def u32_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_natnum_sub(g: Generator) -> None:
del tv_map
g.add_statement('i32.sub') g.add_statement('i32.sub')
def u64_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_natnum_sub(g: Generator) -> None:
del tv_map
g.add_statement('i64.sub') g.add_statement('i64.sub')
def i32_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_natnum_sub(g: Generator) -> None:
del tv_map
g.add_statement('i32.sub') g.add_statement('i32.sub')
def i64_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_natnum_sub(g: Generator) -> None:
del tv_map
g.add_statement('i64.sub') g.add_statement('i64.sub')
def f32_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_natnum_sub(g: Generator) -> None:
del tv_map
g.add_statement('f32.sub') g.add_statement('f32.sub')
def f64_natnum_sub(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_natnum_sub(g: Generator) -> None:
del tv_map
g.add_statement('f64.sub') g.add_statement('f64.sub')
def u32_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_natnum_mul(g: Generator) -> None:
del tv_map
g.add_statement('i32.mul') g.add_statement('i32.mul')
def u64_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_natnum_mul(g: Generator) -> None:
del tv_map
g.add_statement('i64.mul') g.add_statement('i64.mul')
def i32_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_natnum_mul(g: Generator) -> None:
del tv_map
g.add_statement('i32.mul') g.add_statement('i32.mul')
def i64_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_natnum_mul(g: Generator) -> None:
del tv_map
g.add_statement('i64.mul') g.add_statement('i64.mul')
def f32_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_natnum_mul(g: Generator) -> None:
del tv_map
g.add_statement('f32.mul') g.add_statement('f32.mul')
def f64_natnum_mul(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_natnum_mul(g: Generator) -> None:
del tv_map
g.add_statement('f64.mul') g.add_statement('f64.mul')
def u32_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_natnum_arithmic_shift_left(g: Generator) -> None:
del tv_map
g.i32.shl() g.i32.shl()
def u64_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_natnum_arithmic_shift_left(g: Generator) -> None:
del tv_map
g.i64.extend_i32_u() g.i64.extend_i32_u()
g.i64.shl() g.i64.shl()
def i32_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_natnum_arithmic_shift_left(g: Generator) -> None:
del tv_map
g.i32.shl() g.i32.shl()
def i64_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_natnum_arithmic_shift_left(g: Generator) -> None:
del tv_map
g.i64.extend_i32_u() g.i64.extend_i32_u()
g.i64.shl() g.i64.shl()
def f32_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_natnum_arithmic_shift_left(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u32_pow2__') g.add_statement('call $stdlib.types.__u32_pow2__')
g.f32.convert_i32_u() g.f32.convert_i32_u()
g.f32.mul() g.f32.mul()
def f64_natnum_arithmic_shift_left(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_natnum_arithmic_shift_left(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u32_pow2__') g.add_statement('call $stdlib.types.__u32_pow2__')
g.f64.convert_i32_u() g.f64.convert_i32_u()
g.f64.mul() g.f64.mul()
def u32_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def u32_natnum_arithmic_shift_right(g: Generator) -> None:
del tv_map
g.i32.shr_u() g.i32.shr_u()
def u64_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def u64_natnum_arithmic_shift_right(g: Generator) -> None:
del tv_map
g.i64.extend_i32_u() g.i64.extend_i32_u()
g.i64.shr_u() g.i64.shr_u()
def i32_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_natnum_arithmic_shift_right(g: Generator) -> None:
del tv_map
g.i32.shr_s() g.i32.shr_s()
def i64_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_natnum_arithmic_shift_right(g: Generator) -> None:
del tv_map
g.i64.extend_i32_u() g.i64.extend_i32_u()
g.i64.shr_s() g.i64.shr_s()
def f32_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_natnum_arithmic_shift_right(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u32_pow2__') g.add_statement('call $stdlib.types.__u32_pow2__')
g.f32.convert_i32_u() g.f32.convert_i32_u()
g.f32.div() g.f32.div()
def f64_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_natnum_arithmic_shift_right(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__u32_pow2__') g.add_statement('call $stdlib.types.__u32_pow2__')
g.f64.convert_i32_u() g.f64.convert_i32_u()
g.f64.div() g.f64.div()
@ -969,219 +833,35 @@ def f64_natnum_arithmic_shift_right(g: Generator, tv_map: TypeVariableLookup) ->
## ### ## ###
## class IntNum ## class IntNum
def i32_intnum_abs(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_intnum_abs(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__i32_intnum_abs__') g.add_statement('call $stdlib.types.__i32_intnum_abs__')
def i64_intnum_abs(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_intnum_abs(g: Generator) -> None:
del tv_map
g.add_statement('call $stdlib.types.__i64_intnum_abs__') g.add_statement('call $stdlib.types.__i64_intnum_abs__')
def f32_intnum_abs(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_intnum_abs(g: Generator) -> None:
del tv_map
g.f32.abs() g.f32.abs()
def f64_intnum_abs(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_intnum_abs(g: Generator) -> None:
del tv_map
g.f64.abs() g.f64.abs()
def i32_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: def i32_intnum_neg(g: Generator) -> None:
del tv_map
g.i32.const(-1) g.i32.const(-1)
g.i32.mul() g.i32.mul()
def i64_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: def i64_intnum_neg(g: Generator) -> None:
del tv_map
g.i64.const(-1) g.i64.const(-1)
g.i64.mul() g.i64.mul()
def f32_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: def f32_intnum_neg(g: Generator) -> None:
del tv_map
g.f32.neg() g.f32.neg()
def f64_intnum_neg(g: Generator, tv_map: TypeVariableLookup) -> None: def f64_intnum_neg(g: Generator) -> None:
del tv_map
g.f64.neg() g.f64.neg()
## ### ## ###
## Class Sized ## Class Sized
def bytes_sized_len(g: Generator, tv_map: TypeVariableLookup) -> None: def bytes_sized_len(g: Generator) -> None:
del tv_map
# The length is stored in the first 4 bytes # The length is stored in the first 4 bytes
g.i32.load() g.i32.load()
## ###
## Extendable
def u8_u32_extend(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
# No-op
# u8 is already stored as u32
pass
def u8_u64_extend(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i64.extend_i32_u()
def u32_u64_extend(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i64.extend_i32_u()
def i8_i32_extend(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
# No-op
# i8 is already stored as i32
pass
def i8_i64_extend(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i64.extend_i32_s()
def i32_i64_extend(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i64.extend_i32_s()
def u8_u32_wrap(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def u8_u64_wrap(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i32.wrap_i64()
g.i32.const(0xFF)
g.i32.and_()
def u32_u64_wrap(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i32.wrap_i64()
def i8_i32_wrap(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i32.const(0xFF)
g.i32.and_()
def i8_i64_wrap(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i32.wrap_i64()
def i32_i64_wrap(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.i32.wrap_i64()
## ###
## Promotable
def f32_f64_promote(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.f64.promote_f32()
def f32_f64_demote(g: Generator, tv_map: TypeVariableLookup) -> None:
del tv_map
g.f32.demote_f64()
def static_array_sum(g: Generator, tv_map: TypeVariableLookup) -> None:
assert len(tv_map) == 1
sa_type, sa_len = next(iter(tv_map.values()))
assert isinstance(sa_type, Type3)
assert isinstance(sa_len, IntType3)
if sa_len.value < 1:
raise NotImplementedError('Default value in case sum is empty')
# FIXME: We should probably use LOAD_STORE_TYPE_MAP for this?
mtyp_map = {
'u32': 'i32',
'u64': 'i64',
'i32': 'i32',
'i64': 'i64',
'f32': 'f32',
'f64': 'f64',
}
# FIXME: We should probably use calc_alloc_size for this?
type_var_size_map = {
'u32': 4,
'u64': 8,
'i32': 4,
'i64': 8,
'f32': 4,
'f64': 8,
}
type_var_add_generator = {
'u32': u32_natnum_add,
'u64': u64_natnum_add,
'i32': i32_natnum_add,
'i64': i64_natnum_add,
'f32': f32_natnum_add,
'f64': f64_natnum_add,
}
# By default, constructed types are passed as pointers
# FIXME: We don't know what add function to call
sa_type_mtyp = mtyp_map.get(sa_type.name, 'i32')
sa_type_alloc_size = type_var_size_map.get(sa_type.name, 4)
sa_type_add_gen = type_var_add_generator[sa_type.name]
# Definitions
sum_adr = g.temp_var(i32('sum_adr'))
sum_stop = g.temp_var(i32('sum_stop'))
# Stack before: [adr]
# Stack after: [sum]
# adr = {address of what's currently on stack}
# Stack: [adr] -> []
g.nop(comment=f'Start sum for {sa_type.name}[{sa_len.value}]')
g.local.set(sum_adr)
# stop = adr + ar_len * sa_type_alloc_size
# Stack: []
g.nop(comment='Calculate address at which to stop looping')
g.local.get(sum_adr)
g.i32.const(sa_len.value * sa_type_alloc_size)
g.i32.add()
g.local.set(sum_stop)
# sum = *adr
# Stack: [] -> [sum]
g.nop(comment='Get the first array value as starting point')
g.local.get(sum_adr)
g.add_statement(f'{sa_type_mtyp}.load')
# Since we did the first one, increase adr
# adr = adr + sa_type_alloc_size
# Stack: [sum] -> [sum]
g.local.get(sum_adr)
g.i32.const(sa_type_alloc_size)
g.i32.add()
g.local.set(sum_adr)
if sa_len.value > 1:
with g.loop(params=[sa_type_mtyp], result=sa_type_mtyp):
# sum = sum + *adr
# Stack: [sum] -> [sum + *adr]
g.nop(comment='Add array value')
g.local.get(sum_adr)
g.add_statement(f'{sa_type_mtyp}.load')
sa_type_add_gen(g, {})
# adr = adr + sa_type_alloc_size
# Stack: [sum] -> [sum]
g.nop(comment='Calculate address of the next value')
g.local.get(sum_adr)
g.i32.const(sa_type_alloc_size)
g.i32.add()
g.local.tee(sum_adr)
# loop if adr < stop
g.nop(comment='Check if address exceeds array bounds')
g.local.get(sum_stop)
g.i32.lt_u()
g.br_if(0)
# else: sum x[1] === x => so we don't need to loop
g.nop(comment=f'Completed sum for {sa_type.name}[{sa_len.value}]')
# End result: [sum]

View File

@ -3,22 +3,10 @@ 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 Any, Dict, Iterable, List, Optional, Tuple, Union from typing import Dict, List, Optional, Tuple, Union
from .. import ourlang, prelude from .. import ourlang, prelude
from .placeholders import PlaceholderForType, Type3OrPlaceholder from . import placeholders, typeclasses, types
from .routers import NoRouteForTypeException, TypeApplicationRouter
from .typeclasses import Type3Class
from .types import (
IntType3,
Type3,
TypeApplication_Nullary,
TypeApplication_Struct,
TypeApplication_TypeInt,
TypeApplication_TypeStar,
TypeConstructor_Base,
TypeConstructor_Struct,
)
class Error: class Error:
@ -44,26 +32,20 @@ class RequireTypeSubstitutes:
typing of the program, so this constraint can be updated. typing of the program, so this constraint can be updated.
""" """
SubstitutionMap = Dict[PlaceholderForType, Type3] SubstitutionMap = Dict[placeholders.PlaceholderForType, types.Type3]
NewConstraintList = List['ConstraintBase'] NewConstraintList = List['ConstraintBase']
CheckResult = Union[None, SubstitutionMap, Error, NewConstraintList, RequireTypeSubstitutes] CheckResult = Union[None, SubstitutionMap, Error, NewConstraintList, RequireTypeSubstitutes]
HumanReadableRet = Tuple[str, Dict[str, Union[None, int, str, ourlang.Expression, Type3, PlaceholderForType]]] HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, placeholders.PlaceholderForType]]]
class Context: class Context:
""" """
Context for constraints Context for constraints
""" """
__slots__ = ('type_class_instances_existing', ) __slots__ = ()
# Constraint_TypeClassInstanceExists
type_class_instances_existing: set[tuple[Type3Class, tuple[Union[Type3, TypeConstructor_Base[Any], TypeConstructor_Struct], ...]]]
def __init__(self) -> None:
self.type_class_instances_existing = set()
class ConstraintBase: class ConstraintBase:
""" """
@ -110,23 +92,23 @@ class SameTypeConstraint(ConstraintBase):
""" """
__slots__ = ('type_list', ) __slots__ = ('type_list', )
type_list: List[Type3OrPlaceholder] type_list: List[placeholders.Type3OrPlaceholder]
def __init__(self, *type_list: Type3OrPlaceholder, comment: Optional[str] = None) -> None: def __init__(self, *type_list: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment) super().__init__(comment=comment)
assert len(type_list) > 1 assert len(type_list) > 1
self.type_list = [*type_list] self.type_list = [*type_list]
def check(self) -> CheckResult: def check(self) -> CheckResult:
known_types: List[Type3] = [] known_types: List[types.Type3] = []
phft_list = [] phft_list = []
for typ in self.type_list: for typ in self.type_list:
if isinstance(typ, Type3): if isinstance(typ, types.Type3):
known_types.append(typ) known_types.append(typ)
continue continue
if isinstance(typ, PlaceholderForType): if isinstance(typ, placeholders.PlaceholderForType):
if typ.resolve_as is not None: if typ.resolve_as is not None:
known_types.append(typ.resolve_as) known_types.append(typ.resolve_as)
else: else:
@ -143,7 +125,7 @@ class SameTypeConstraint(ConstraintBase):
if ktyp != first_type: if ktyp != first_type:
return Error(f'{ktyp:s} must be {first_type:s} instead', comment=self.comment) return Error(f'{ktyp:s} must be {first_type:s} instead', comment=self.comment)
if not phft_list: if not placeholders:
return None return None
for phft in phft_list: for phft in phft_list:
@ -168,59 +150,25 @@ class SameTypeConstraint(ConstraintBase):
return f'SameTypeConstraint({args}, comment={repr(self.comment)})' return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
class SameTypeArgumentConstraint(ConstraintBase):
__slots__ = ('tc_var', 'arg_var', )
tc_var: PlaceholderForType
arg_var: PlaceholderForType
def __init__(self, tc_var: PlaceholderForType, arg_var: PlaceholderForType, *, comment: str) -> None:
super().__init__(comment=comment)
self.tc_var = tc_var
self.arg_var = arg_var
def check(self) -> CheckResult:
if self.tc_var.resolve_as is None or self.arg_var.resolve_as is None:
return RequireTypeSubstitutes()
tc_typ = self.tc_var.resolve_as
arg_typ = self.arg_var.resolve_as
if isinstance(tc_typ.application, TypeApplication_Nullary):
return Error(f'{tc_typ:s} must be a constructed type instead')
if isinstance(tc_typ.application, TypeApplication_TypeStar):
# Sure, it's a constructed type. But it's like a struct,
# though without the way to implement type classes
# Presumably, doing a naked `foo :: t a -> a`
# doesn't work since you don't have any info on t
# So we can let the MustImplementTypeClassConstraint handle it.
return None
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(tc_typ.application, TypeApplication_TypeInt):
if tc_typ.application.arguments[0] == arg_typ:
return None
return Error(f'{tc_typ.application.arguments[0]:s} must be {arg_typ:s} instead')
raise NotImplementedError(tc_typ, arg_typ)
class TupleMatchConstraint(ConstraintBase): class TupleMatchConstraint(ConstraintBase):
__slots__ = ('exp_type', 'args', ) def __init__(self, exp_type: placeholders.Type3OrPlaceholder, args: List[placeholders.Type3OrPlaceholder], comment: str):
exp_type: Type3OrPlaceholder
args: list[Type3OrPlaceholder]
def __init__(self, exp_type: Type3OrPlaceholder, args: Iterable[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 _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult: 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
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):
@ -231,7 +179,8 @@ class TupleMatchConstraint(ConstraintBase):
for arg in self.args for arg in self.args
] ]
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult: tp_args = prelude.tuple_.did_construct(exp_type)
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)
@ -240,85 +189,91 @@ 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]() raise NotImplementedError(exp_type)
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple) class CastableConstraint(ConstraintBase):
"""
A type can be cast to another type
"""
__slots__ = ('from_type3', 'to_type3', )
from_type3: placeholders.Type3OrPlaceholder
to_type3: placeholders.Type3OrPlaceholder
def __init__(self, from_type3: placeholders.Type3OrPlaceholder, to_type3: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
self.from_type3 = from_type3
self.to_type3 = to_type3
def check(self) -> CheckResult: def check(self) -> CheckResult:
exp_type = self.exp_type ftyp = self.from_type3
if isinstance(exp_type, PlaceholderForType): if isinstance(ftyp, placeholders.PlaceholderForType) and ftyp.resolve_as is not None:
if exp_type.resolve_as is None: ftyp = ftyp.resolve_as
ttyp = self.to_type3
if isinstance(ttyp, placeholders.PlaceholderForType) and ttyp.resolve_as is not None:
ttyp = ttyp.resolve_as
if isinstance(ftyp, placeholders.PlaceholderForType) or isinstance(ttyp, placeholders.PlaceholderForType):
return RequireTypeSubstitutes() return RequireTypeSubstitutes()
exp_type = exp_type.resolve_as if ftyp is prelude.u8 and ttyp is prelude.u32:
return None
try: return Error(f'Cannot cast {ftyp.name} to {ttyp.name}')
return self.__class__.GENERATE_ROUTER(self, exp_type)
except NoRouteForTypeException: def human_readable(self) -> HumanReadableRet:
raise NotImplementedError(exp_type) return (
'{to_type3}({from_type3})',
{
'to_type3': self.to_type3,
'from_type3': self.from_type3,
},
)
def __repr__(self) -> str:
return f'CastableConstraint({repr(self.from_type3)}, {repr(self.to_type3)}, comment={repr(self.comment)})'
class MustImplementTypeClassConstraint(ConstraintBase): class MustImplementTypeClassConstraint(ConstraintBase):
""" """
A type must implement a given type class A type must implement a given type class
""" """
__slots__ = ('context', 'type_class3', 'types', ) __slots__ = ('type_class3', 'type3', )
context: Context type_class3: typeclasses.Type3Class
type_class3: Type3Class type3: placeholders.Type3OrPlaceholder
types: list[Type3OrPlaceholder]
def __init__(self, context: Context, type_class3: Type3Class, typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None: def __init__(self, type_class3: typeclasses.Type3Class, type3: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment) super().__init__(comment=comment)
self.context = context
self.type_class3 = type_class3 self.type_class3 = type_class3
self.types = typ_list self.type3 = type3
def check(self) -> CheckResult: def check(self) -> CheckResult:
typ_list: list[Type3 | TypeConstructor_Base[Any] | TypeConstructor_Struct] = [] typ = self.type3
for typ in self.types: if isinstance(typ, placeholders.PlaceholderForType) and typ.resolve_as is not None:
if isinstance(typ, PlaceholderForType) and typ.resolve_as is not None:
typ = typ.resolve_as typ = typ.resolve_as
if isinstance(typ, PlaceholderForType): if isinstance(typ, placeholders.PlaceholderForType):
return RequireTypeSubstitutes() return RequireTypeSubstitutes()
if isinstance(typ.application, (TypeApplication_Nullary, TypeApplication_Struct, )): if self.type_class3 in typ.classes:
typ_list.append(typ)
continue
if isinstance(typ.application, (TypeApplication_TypeInt, TypeApplication_TypeStar)):
typ_list.append(typ.application.constructor)
continue
raise NotImplementedError(typ, typ.application)
assert len(typ_list) == len(self.types)
key = (self.type_class3, tuple(typ_list), )
if key in self.context.type_class_instances_existing:
return None return None
typ_cls_name = self.type_class3 if isinstance(self.type_class3, str) else self.type_class3.name return Error(f'{typ.name} does not implement the {self.type_class3} type class')
typ_name_list = ' '.join(x.name for x in typ_list)
return Error(f'Missing type class instantation: {typ_cls_name} {typ_name_list}')
def human_readable(self) -> HumanReadableRet: def human_readable(self) -> HumanReadableRet:
keys = {
f'type{idx}': typ
for idx, typ in enumerate(self.types)
}
return ( return (
'Exists instance {type_class3} ' + ' '.join(f'{{{x}}}' for x in keys), '{type3} derives {type_class3}',
{ {
'type_class3': str(self.type_class3), 'type_class3': str(self.type_class3),
**keys, 'type3': self.type3,
}, },
) )
def __repr__(self) -> str: def __repr__(self) -> str:
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.types)}, comment={repr(self.comment)})' return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)}, comment={repr(self.comment)})'
class LiteralFitsConstraint(ConstraintBase): class LiteralFitsConstraint(ConstraintBase):
""" """
@ -326,12 +281,12 @@ class LiteralFitsConstraint(ConstraintBase):
""" """
__slots__ = ('type3', 'literal', ) __slots__ = ('type3', 'literal', )
type3: Type3OrPlaceholder type3: placeholders.Type3OrPlaceholder
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct] literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct]
def __init__( def __init__(
self, self,
type3: Type3OrPlaceholder, type3: placeholders.Type3OrPlaceholder,
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct], literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct],
comment: Optional[str] = None, comment: Optional[str] = None,
) -> None: ) -> None:
@ -340,91 +295,6 @@ class LiteralFitsConstraint(ConstraintBase):
self.type3 = type3 self.type3 = type3
self.literal = literal self.literal = literal
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
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, 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_type3.name}.{x_n}')
for (x_n, x_t, ), y in zip(st_args, self.literal.value, strict=True)
)
res.append(SameTypeConstraint(
self.literal.struct_type3,
self.type3,
comment='Struct types must match',
))
return res
def _generate_tuple(self, tp_args: tuple[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),
@ -440,7 +310,7 @@ class LiteralFitsConstraint(ConstraintBase):
'f64': None, 'f64': None,
} }
if isinstance(self.type3, PlaceholderForType): if isinstance(self.type3, placeholders.PlaceholderForType):
if self.type3.resolve_as is None: if self.type3.resolve_as is None:
return RequireTypeSubstitutes() return RequireTypeSubstitutes()
@ -475,12 +345,80 @@ 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
exp_type = self.type3 res: NewConstraintList
try: assert isinstance(self.type3, types.Type3)
return self.__class__.GENERATE_ROUTER(self, exp_type)
except NoRouteForTypeException: tp_args = prelude.tuple_.did_construct(self.type3)
raise NotImplementedError(exp_type) if tp_args is not None:
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)
)
res.extend(
SameTypeConstraint(x, y.type3)
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
)
res.extend(
SameTypeConstraint(sa_type, y.type3)
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)
)
res.extend(
SameTypeConstraint(x_t, y.type3, 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 (
@ -498,75 +436,73 @@ class CanBeSubscriptedConstraint(ConstraintBase):
""" """
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array) A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
""" """
__slots__ = ('ret_type3', 'type3', 'index_type3', 'index_const', ) __slots__ = ('ret_type3', 'type3', 'index', 'index_type3', )
ret_type3: PlaceholderForType ret_type3: placeholders.Type3OrPlaceholder
type3: PlaceholderForType type3: placeholders.Type3OrPlaceholder
index_type3: PlaceholderForType index: ourlang.Expression
index_const: int | None index_type3: placeholders.Type3OrPlaceholder
def __init__( def __init__(self, ret_type3: placeholders.Type3OrPlaceholder, type3: placeholders.Type3OrPlaceholder, index: ourlang.Expression, comment: Optional[str] = None) -> None:
self,
ret_type3: PlaceholderForType,
type3: PlaceholderForType,
index_type3: PlaceholderForType,
index_const: int | None,
comment: Optional[str] = None,
) -> None:
super().__init__(comment=comment) super().__init__(comment=comment)
self.ret_type3 = ret_type3 self.ret_type3 = ret_type3
self.type3 = type3 self.type3 = type3
self.index_type3 = index_type3 self.index = index
self.index_const = index_const self.index_type3 = index.type3
def _generate_bytes(self) -> CheckResult: 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
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_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
]
if isinstance(self.index, ourlang.ConstantPrimitive):
assert isinstance(self.index.value, int)
if self.index.value < 0 or sa_len.value <= self.index.value:
return Error('Tuple index out of range')
return result
# 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
# 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):
return Error('Must index with literal')
if not isinstance(self.index.value, int):
return Error('Must index with integer literal')
if self.index.value < 0 or len(tp_args) <= self.index.value:
return Error('Tuple index out of range')
return [
SameTypeConstraint(prelude.u32, self.index_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_:
return [ return [
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'), SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'),
SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
] ]
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
sa_type, sa_len = sa_args
if self.index_const is not None and (self.index_const < 0 or sa_len.value <= self.index_const):
return Error('Tuple index out of range')
return [
SameTypeConstraint(prelude.u32, self.index_type3, 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[Type3, ...]) -> CheckResult:
# 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
# we use a[0] and a[1] and allow for a[2] and on.
if self.index_const is None:
return Error('Must index with integer literal')
if self.index_const < 0 or len(tp_args) <= self.index_const:
return Error('Tuple index out of range')
return [
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(tp_args[self.index_const], self.ret_type3, comment=f'Tuple subscript index {self.index_const}'),
]
GENERATE_ROUTER = TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]()
GENERATE_ROUTER.add_n(prelude.bytes_, _generate_bytes)
GENERATE_ROUTER.add(prelude.static_array, _generate_static_array)
GENERATE_ROUTER.add(prelude.tuple_, _generate_tuple)
def check(self) -> CheckResult:
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = self.type3.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:
@ -574,9 +510,9 @@ class CanBeSubscriptedConstraint(ConstraintBase):
'{type3}[{index}]', '{type3}[{index}]',
{ {
'type3': self.type3, 'type3': self.type3,
'index': self.index_type3 if self.index_const is None else self.index_const, 'index': self.index,
}, },
) )
def __repr__(self) -> str: def __repr__(self) -> str:
return f'CanBeSubscriptedConstraint({self.ret_type3!r}, {self.type3!r}, {self.index_type3!r}, {self.index_const!r}, comment={repr(self.comment)})' return f'CanBeSubscriptedConstraint({repr(self.type3)}, {repr(self.index)}, comment={repr(self.comment)})'

View File

@ -6,180 +6,109 @@ The constraints solver can then try to resolve all constraints.
from typing import Generator, List from typing import Generator, List
from .. import ourlang, prelude from .. import ourlang, prelude
from . import functions as functions
from . import placeholders as placeholders
from . import typeclasses as typeclasses
from . import types as type3types
from .constraints import ( from .constraints import (
CanBeSubscriptedConstraint, CanBeSubscriptedConstraint,
CastableConstraint,
ConstraintBase, ConstraintBase,
Context, Context,
LiteralFitsConstraint, LiteralFitsConstraint,
MustImplementTypeClassConstraint, MustImplementTypeClassConstraint,
SameTypeArgumentConstraint,
SameTypeConstraint, SameTypeConstraint,
TupleMatchConstraint, TupleMatchConstraint,
) )
from .functions import (
Constraint_TypeClassInstanceExists,
FunctionSignature,
TypeVariable,
TypeVariableApplication_Unary,
)
from .placeholders import PlaceholderForType
from .types import Type3, TypeApplication_Struct
ConstraintGenerator = Generator[ConstraintBase, None, None] ConstraintGenerator = Generator[ConstraintBase, None, None]
def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]: def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]:
ctx = Context() ctx = Context()
ctx.type_class_instances_existing.update(prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING)
return [*module(ctx, inp)] return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant, phft: PlaceholderForType) -> ConstraintGenerator: def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)): if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
yield LiteralFitsConstraint( yield LiteralFitsConstraint(
phft, inp, inp.type3, inp,
comment='The given literal must fit the expected type' comment='The given literal must fit the expected type'
) )
return return
raise NotImplementedError(constant, inp) raise NotImplementedError(constant, inp)
def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderForType) -> ConstraintGenerator: def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
return _expression_function_call( if isinstance(inp, ourlang.Constant):
ctx, yield from constant(ctx, inp)
f'({inp.operator.name})', return
inp.operator.signature,
[inp.left, inp.right],
inp,
phft,
)
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: PlaceholderForType) -> ConstraintGenerator: if isinstance(inp, ourlang.VariableReference):
return _expression_function_call( yield SameTypeConstraint(inp.variable.type3, inp.type3,
ctx, comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
inp.function.name, return
inp.function.signature,
inp.arguments,
inp,
phft,
)
def _expression_function_call( if isinstance(inp, ourlang.UnaryOp):
ctx: Context, if 'cast' == inp.operator:
func_name: str, yield from expression(ctx, inp.right)
signature: FunctionSignature, yield CastableConstraint(inp.right.type3, inp.type3)
arguments: list[ourlang.Expression], return
return_expr: ourlang.Expression,
return_phft: PlaceholderForType,
) -> ConstraintGenerator:
"""
Generates all type-level constraints for a function call.
A Binary operator functions pretty much the same as a function call raise NotImplementedError(expression, inp, inp.operator)
with two arguments - it's only a syntactic difference.
""" if isinstance(inp, ourlang.BinaryOp) or isinstance(inp, ourlang.FunctionCall):
# First create placeholders for all arguments, and generate their constraints signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature
arg_placeholders = { arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments
arg_expr: PlaceholderForType([arg_expr])
for arg_expr in arguments func_name = f'({inp.operator.name})' if isinstance(inp, ourlang.BinaryOp) else inp.function.name
type_var_map = {
x: placeholders.PlaceholderForType([])
for x in signature.args
if isinstance(x, functions.TypeVariable)
} }
arg_placeholders[return_expr] = return_phft
for call_arg in arguments: for call_arg in arguments:
yield from expression(ctx, call_arg, arg_placeholders[call_arg]) yield from expression(ctx, call_arg)
# Then generate placeholders the function signature for type_var, constraint_list in signature.context.constraints.items():
# and apply constraints that the function requires assert type_var in type_var_map # When can this happen?
# Skip any fully reference types
# Making this a map ensures that if a function signature has
# the same type on multiple arguments, we only get one
# placeholder here. These don't need to update anything once
# subsituted - that's done by arg_placeholders.
type_var_map = {
x: PlaceholderForType([])
for x in signature.args
if isinstance(x, TypeVariable)
}
for constraint in signature.context.constraints: for constraint in constraint_list:
if isinstance(constraint, Constraint_TypeClassInstanceExists): if isinstance(constraint, functions.TypeVariableConstraint_TypeHasTypeClass):
yield MustImplementTypeClassConstraint( yield MustImplementTypeClassConstraint(
ctx,
constraint.type_class3, constraint.type_class3,
[type_var_map[x] for x in constraint.types], type_var_map[type_var],
) )
continue continue
raise NotImplementedError(constraint) raise NotImplementedError(constraint)
# If some of the function arguments are type constructors, for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [inp], strict=True)):
# we need to deal with those separately.
# That is, given `foo :: t a -> a` we need to ensure
# that both a's are the same.
for sig_arg in signature.args:
if isinstance(sig_arg, Type3):
# Not a type variable at all
continue
if sig_arg.application.constructor is None:
# Not a type variable for a type constructor
continue
if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
raise NotImplementedError(sig_arg.application)
assert sig_arg.application.arguments in type_var_map # When does this happen?
yield SameTypeArgumentConstraint(
type_var_map[sig_arg],
type_var_map[sig_arg.application.arguments],
comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}',
)
# Lastly, tie the signature and expression together
for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [return_expr], strict=True)):
if arg_no == len(arguments): 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:
comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument' comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument'
if isinstance(sig_part, TypeVariable): if isinstance(sig_part, functions.TypeVariable):
yield SameTypeConstraint(type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment) yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3, comment=comment)
continue continue
if isinstance(sig_part, Type3): if isinstance(sig_part, type3types.Type3):
yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment) yield SameTypeConstraint(sig_part, arg_expr.type3, comment=comment)
continue continue
raise NotImplementedError(sig_part) raise NotImplementedError(sig_part)
return return
def expression(ctx: Context, inp: ourlang.Expression, phft: 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:
arg_phft = PlaceholderForType([arg]) yield from expression(ctx, arg)
yield from expression(ctx, arg, arg_phft) r_type.append(arg.type3)
r_type.append(arg_phft)
yield TupleMatchConstraint( yield TupleMatchConstraint(
phft, inp.type3,
r_type, r_type,
comment='The type of a tuple is a combination of its members' comment='The type of a tuple is a combination of its members'
) )
@ -187,44 +116,34 @@ def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType)
return return
if isinstance(inp, ourlang.Subscript): if isinstance(inp, ourlang.Subscript):
varref_phft = PlaceholderForType([inp.varref]) yield from expression(ctx, inp.varref)
index_phft = PlaceholderForType([inp.index]) yield from expression(ctx, inp.index)
yield from expression(ctx, inp.varref, varref_phft) yield CanBeSubscriptedConstraint(inp.type3, inp.varref.type3, inp.index)
yield from expression(ctx, inp.index, index_phft)
if isinstance(inp.index, ourlang.ConstantPrimitive) and isinstance(inp.index.value, int):
yield CanBeSubscriptedConstraint(phft, varref_phft, index_phft, inp.index.value)
else:
yield CanBeSubscriptedConstraint(phft, varref_phft, index_phft, None)
return return
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.AccessStructMember):
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) # FIXME: See test_struct.py::test_struct_not_accessible assert isinstance(inp.struct_type3, type3types.Type3) # When does this happen?
st_args = prelude.struct.did_construct(inp.struct_type3)
assert st_args is not None # When does this happen?
mem_typ = dict(inp.struct_type3.application.arguments)[inp.member] yield from expression(ctx, inp.varref)
yield SameTypeConstraint(st_args[inp.member], inp.type3,
yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO
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
raise NotImplementedError(expression, inp) raise NotImplementedError(expression, inp)
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator: def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
phft = PlaceholderForType([inp.value]) yield from expression(ctx, inp.value)
yield from expression(ctx, inp.value, phft) yield SameTypeConstraint(fun.returns_type3, inp.value.type3,
yield SameTypeConstraint(fun.returns_type3, phft,
comment=f'The type of the value returned from function {fun.name} should match its return type') comment=f'The type of the value returned from function {fun.name} should match its return type')
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator: def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
test_phft = PlaceholderForType([inp.test]) yield from expression(ctx, inp.test)
yield from expression(ctx, inp.test, test_phft) yield SameTypeConstraint(inp.test.type3, prelude.bool_,
yield SameTypeConstraint(test_phft, prelude.bool_,
comment='Must pass a boolean expression to if') comment='Must pass a boolean expression to if')
for stmt in inp.statements: for stmt in inp.statements:
@ -254,10 +173,8 @@ def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
yield from statement(ctx, inp, stmt) yield from statement(ctx, inp, stmt)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator: def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
phft = PlaceholderForType([inp.constant]) yield from constant(ctx, inp.constant)
yield SameTypeConstraint(inp.type3, inp.constant.type3,
yield from constant(ctx, inp.constant, phft)
yield SameTypeConstraint(inp.type3, phft,
comment=f'The type of the value for module constant definition {inp.name} should match the type of that constant') comment=f'The type of the value for module constant definition {inp.name} should match the type of that constant')
def module(ctx: Context, inp: ourlang.Module) -> ConstraintGenerator: def module(ctx: Context, inp: ourlang.Module) -> ConstraintGenerator:

View File

@ -115,6 +115,8 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
# FIXME: This doesn't work with e.g. `:: [a] -> a`, as the placeholder is inside a type # FIXME: This doesn't work with e.g. `:: [a] -> a`, as the placeholder is inside a type
for plh, typ in placeholder_substitutes.items(): for plh, typ in placeholder_substitutes.items():
for expr in plh.update_on_substitution: for expr in plh.update_on_substitution:
assert expr.type3 is plh
expr.type3 = typ expr.type3 = typ
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None: def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:

View File

@ -1,36 +1,10 @@
from typing import TYPE_CHECKING, Any, Hashable, Iterable, List, Union from typing import TYPE_CHECKING, Any, Iterable, List, Union
if TYPE_CHECKING: if TYPE_CHECKING:
from .typeclasses import Type3Class from .typeclasses import Type3Class
from .types import Type3 from .types import Type3
class TypeVariableApplication_Base[T: Hashable, S: Hashable]:
"""
Records the constructor and arguments used to create this type.
Nullary types, or types of kind *, have both arguments set to None.
"""
constructor: T
arguments: S
def __init__(self, constructor: T, arguments: S) -> None:
self.constructor = constructor
self.arguments = arguments
def __hash__(self) -> int:
return hash((self.constructor, self.arguments, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariableApplication_Base):
raise NotImplementedError
return (self.constructor == other.constructor # type: ignore[no-any-return]
and self.arguments == other.arguments)
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
class TypeVariable: class TypeVariable:
""" """
Types variable are used in function definition. Types variable are used in function definition.
@ -40,120 +14,47 @@ class TypeVariable:
during type checking. These type variables are used solely in the during type checking. These type variables are used solely in the
function's definition function's definition
""" """
__slots__ = ('name', 'application', ) __slots__ = ('letter', )
name: str letter: str
application: TypeVariableApplication_Base[Any, Any]
def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None: def __init__(self, letter: str) -> None:
self.name = name assert len(letter) == 1, f'{letter} is not a valid type variable'
self.application = application self.letter = letter
def __hash__(self) -> int: def __hash__(self) -> int:
return hash((self.name, self.application, )) return hash(self.letter)
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariable): if not isinstance(other, TypeVariable):
raise NotImplementedError raise NotImplementedError
return (self.name == other.name return self.letter == other.letter
and self.application == other.application)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'TypeVariable({repr(self.name)})' return f'TypeVariable({repr(self.letter)})'
class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]): class TypeVariableConstraintBase:
"""
For the type for this function argument it's not relevant if it was constructed.
"""
class TypeConstructorVariable:
"""
Types constructor variable are used in function definition.
They are a lot like TypeVariable, except that they represent a
type constructor rather than a type directly.
For now, we only have type constructor variables for kind
* -> *.
"""
__slots__ = ('name', )
name: str
def __init__(self, name: str) -> None:
self.name = name
def __hash__(self) -> int:
return hash((self.name, ))
def __eq__(self, other: Any) -> bool:
if other is None:
return False
if not isinstance(other, TypeConstructorVariable):
raise NotImplementedError
return (self.name == other.name)
def __call__(self, tvar: TypeVariable) -> 'TypeVariable':
return TypeVariable(
self.name + ' ' + tvar.name,
TypeVariableApplication_Unary(self, tvar)
)
def __repr__(self) -> str:
return f'TypeConstructorVariable({self.name!r})'
class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]):
"""
The type for this function argument should be constructed from a type constructor.
And we need to know what construtor that was, since that's the one we support.
"""
class ConstraintBase:
__slots__ = () __slots__ = ()
class Constraint_TypeClassInstanceExists(ConstraintBase): class TypeVariableConstraint_TypeHasTypeClass(TypeVariableConstraintBase):
__slots__ = ('type_class3', 'types', ) __slots__ = ('type_class3', )
type_class3: 'Type3Class' def __init__(self, type_class3: 'Type3Class') -> None:
types: list[TypeVariable]
def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None:
self.type_class3 = type_class3 self.type_class3 = type_class3
self.types = list(types)
# Sanity check. AFAIK, if you have a multi-parameter type class,
# you can only add a constraint by supplying types for all variables
assert len(self.type_class3.args) == len(self.types)
def __str__(self) -> str:
return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types)
def __repr__(self) -> str:
return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})'
class TypeVariableContext: class TypeVariableContext:
__slots__ = ('constraints', ) __slots__ = ('constraints', )
constraints: list[ConstraintBase] constraints: dict[TypeVariable, list[TypeVariableConstraintBase]]
def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None: def __init__(self) -> None:
self.constraints = list(constraints) self.constraints = {}
def __copy__(self) -> 'TypeVariableContext': def __copy__(self) -> 'TypeVariableContext':
return TypeVariableContext(self.constraints) result = TypeVariableContext()
result.constraints.update(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', )
@ -164,9 +65,3 @@ 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.name for x in self.args)
def __repr__(self) -> str:
return f'FunctionSignature({self.context!r}, {self.args!r})'

View File

@ -7,13 +7,14 @@ from typing import Any, Iterable, List, Optional, Protocol, Union
from .types import Type3 from .types import Type3
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
class ExpressionProtocol(Protocol): class ExpressionProtocol(Protocol):
""" """
A protocol for classes that should be updated on substitution A protocol for classes that should be updated on substitution
""" """
type3: Type3 | None type3: 'Type3OrPlaceholder'
""" """
The type to update The type to update
""" """

View File

@ -1,119 +0,0 @@
from typing import Any, Callable
from .functions import (
TypeConstructorVariable,
TypeVariable,
TypeVariableApplication_Unary,
)
from .typeclasses import Type3ClassArgs
from .types import KindArgument, Type3, TypeApplication_TypeInt, TypeConstructor_Base
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[T](self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None:
self.by_constructor[constructor] = helper
def __call__(self, arg0: S, typ: Type3) -> R:
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)
TypeVariableLookup = dict[TypeVariable, tuple[KindArgument, ...]]
class TypeClassArgsRouter[S, R]:
"""
Helper class to find a method based on a type class argument list
"""
__slots__ = ('args', 'data', )
args: Type3ClassArgs
data: dict[tuple[Type3 | TypeConstructor_Base[Any], ...], Callable[[S, TypeVariableLookup], R]]
def __init__(self, args: Type3ClassArgs) -> None:
self.args = args
self.data = {}
def add(
self,
tv_map: dict[TypeVariable, Type3],
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
helper: Callable[[S, TypeVariableLookup], R],
) -> None:
key: list[Type3 | TypeConstructor_Base[Any]] = []
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
else:
key.append(tc_map[tc_arg])
self.data[tuple(key)] = helper
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
key: list[Type3 | TypeConstructor_Base[Any]] = []
arguments: TypeVariableLookup = {}
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
continue
for tvar, typ in tv_map.items():
tvar_constructor = tvar.application.constructor
if tvar_constructor != tc_arg:
continue
key.append(typ.application.constructor)
if isinstance(tvar.application, TypeVariableApplication_Unary):
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(typ.application, TypeApplication_TypeInt):
arguments[tvar.application.arguments] = typ.application.arguments
continue
raise NotImplementedError(tvar.application, typ.application)
t_helper = self.data.get(tuple(key))
if t_helper is not None:
return t_helper(arg0, arguments)
raise NoRouteForTypeException(arg0, tv_map)

View File

@ -1,14 +1,12 @@
from typing import Dict, Iterable, List, Mapping, Optional, Union from typing import Any, Dict, Iterable, List, Mapping, Optional, Union
from .functions import ( from .functions import (
Constraint_TypeClassInstanceExists,
ConstraintBase,
FunctionSignature, FunctionSignature,
TypeConstructorVariable,
TypeVariable, TypeVariable,
TypeVariableConstraint_TypeHasTypeClass,
TypeVariableContext, TypeVariableContext,
) )
from .types import Type3 from .types import Type3, TypeConstructor, TypeConstructor_Struct
class Type3ClassMethod: class Type3ClassMethod:
@ -21,19 +19,14 @@ 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)})'
Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable]
class Type3Class: class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', ) __slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
name: str name: str
args: Type3ClassArgs args: List[TypeVariable]
methods: Dict[str, Type3ClassMethod] methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod]
inherited_classes: List['Type3Class'] inherited_classes: List['Type3Class']
@ -41,60 +34,42 @@ class Type3Class:
def __init__( def __init__(
self, self,
name: str, name: str,
args: Type3ClassArgs, args: Iterable[TypeVariable],
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 = args self.args = list(args)
context = TypeVariableContext()
for arg in args:
context.constraints[arg] = [
TypeVariableConstraint_TypeHasTypeClass(self)
]
# FIXME: Multi parameter class types
# To fix this, realise that an instantiation of a multi paramater type class
# means that the instantiation depends on the combination of type classes
# and so we can't store the type classes on the types anymore
# This also means constraints should store a tuple of types as its key
assert len(context.constraints) <= 1
self.methods = { self.methods = {
k: Type3ClassMethod(k, _create_signature(v, self)) k: Type3ClassMethod(k, FunctionSignature(context, v))
for k, v in methods.items() for k, v in methods.items()
} }
self.operators = { self.operators = {
k: Type3ClassMethod(k, _create_signature(v, self)) k: Type3ClassMethod(k, FunctionSignature(context, v))
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( def instance_type_class(cls: Type3Class, typ: Type3 | TypeConstructor[Any] | TypeConstructor_Struct) -> None:
method_arg_list: Iterable[Type3 | TypeVariable], if isinstance(typ, Type3):
type_class3: Type3Class, typ.classes.add(cls)
) -> FunctionSignature: else:
context = TypeVariableContext() typ.type_classes.add(cls)
if not isinstance(type_class3.args[0], TypeConstructorVariable):
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, type_class3.args))
signature_args: list[Type3 | TypeVariable] = []
for method_arg in method_arg_list:
if isinstance(method_arg, Type3):
signature_args.append(method_arg)
continue
if isinstance(method_arg, TypeVariable):
type_constructor = method_arg.application.constructor
if type_constructor is None:
signature_args.append(method_arg)
continue
if (type_constructor, ) == type_class3.args:
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, [method_arg]))
signature_args.append(method_arg)
continue
raise NotImplementedError(method_arg)
return FunctionSignature(context, signature_args)

View File

@ -2,46 +2,22 @@
Contains the final types for use in Phasm, as well as construtors. Contains the final types for use in Phasm, as well as construtors.
""" """
from typing import ( from typing import (
TYPE_CHECKING,
Any, Any,
Callable, Generic,
Hashable, Iterable,
Self, Set,
Tuple, Tuple,
TypeVar, TypeVar,
) )
S = TypeVar('S') if TYPE_CHECKING:
T = TypeVar('T') from .typeclasses import Type3Class
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 __hash__(self) -> int:
return hash((self.constructor, self.arguments, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeApplication_Base):
raise NotImplementedError
return (self.constructor == other.constructor # type: ignore[no-any-return]
and self.arguments == other.arguments)
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
class Type3(KindArgument): class Type3(KindArgument):
""" """
Base class for the type3 types Base class for the type3 types
@ -49,25 +25,32 @@ 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', 'application', ) __slots__ = ('name', 'classes', )
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.
""" """
application: TypeApplication_Base[Any, Any] classes: Set['Type3Class']
""" """
How the type was constructed; i.e. which constructor was used and which The type classes that this type implements
type level arguments were applied to the constructor.
""" """
def __init__(self, name: str, application: TypeApplication_Base[Any, Any]) -> None: def __init__(self, name: str, classes: Iterable['Type3Class']) -> None:
self.name = name self.name = name
self.application = application self.classes = set(classes)
for cls in self.classes:
for inh_cls in cls.inherited_classes:
if inh_cls not in self.classes:
raise Exception(
f'No instance for ({inh_cls} {self.name})'
f'; required for ({cls} {self.name})'
)
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Type3({self.name!r}, {self.application!r})' return f'Type3({repr(self.name)}, {repr(self.classes)})'
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
@ -93,11 +76,6 @@ class Type3(KindArgument):
def __bool__(self) -> bool: def __bool__(self) -> bool:
raise NotImplementedError raise NotImplementedError
class TypeApplication_Nullary(TypeApplication_Base[None, None]):
"""
There was no constructor used to create this type - it's a 'simple' type like u32
"""
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
@ -116,9 +94,6 @@ class IntType3(KindArgument):
def __init__(self, value: int) -> None: def __init__(self, value: int) -> None:
self.value = value self.value = value
def __repr__(self) -> str:
return f'IntType3({self.value!r})'
def __format__(self, format_spec: str) -> str: def __format__(self, format_spec: str) -> str:
if format_spec != 's': if format_spec != 's':
raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}') raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}')
@ -137,20 +112,27 @@ class IntType3(KindArgument):
def __hash__(self) -> int: def __hash__(self) -> int:
return hash(self.value) return hash(self.value)
class TypeConstructor_Base[T]: T = TypeVar('T')
class TypeConstructor(Generic[T]):
""" """
Base class for type construtors Base class for type construtors
""" """
__slots__ = ('name', 'on_create', '_cache', ) __slots__ = ('name', 'classes', 'type_classes', '_cache', '_reverse_cache')
name: str name: str
""" """
The name of the type constructor The name of the type constructor
""" """
on_create: Callable[[T, Type3], None] classes: Set['Type3Class']
""" """
Who to let know if a type is created The type classes that this constructor implements
"""
type_classes: Set['Type3Class']
"""
The type classes that the constructed types implement
""" """
_cache: dict[T, Type3] _cache: dict[T, Type3]
@ -159,26 +141,32 @@ class TypeConstructor_Base[T]:
it should produce the exact same result. it should produce the exact same result.
""" """
def __init__(self, name: str, on_create: Callable[[T, Type3], None]) -> None: _reverse_cache: dict[Type3, T]
"""
Sometimes we need to know the key that created a type.
"""
def __init__(self, name: str, classes: Iterable['Type3Class'], type_classes: Iterable['Type3Class']) -> None:
self.name = name self.name = name
self.on_create = on_create self.classes = set(classes)
self.type_classes = set(type_classes)
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('make_name', self) raise NotImplementedError
def make_application(self, key: T) -> TypeApplication_Base[Self, T]: def did_construct(self, typ: Type3) -> T | None:
""" """
Records how the type was constructed into type. Was the given type constructed by this constructor?
The type checker and compiler will need to know what If so, which arguments where used?
arguments where made to construct the type.
""" """
raise NotImplementedError('make_application', self) return self._reverse_cache.get(typ)
def construct(self, key: T) -> Type3: def construct(self, key: T) -> Type3:
""" """
@ -187,15 +175,21 @@ class TypeConstructor_Base[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.make_application(key)) self._cache[key] = result = Type3(self.make_name(key), self.type_classes)
self.on_create(key, result) self._reverse_cache[result] = key
return result return result
def __repr__(self) -> str: class TypeConstructor_Type(TypeConstructor[Type3]):
return f'{self.__class__.__name__}({self.name!r}, ...)' """
Base class type constructors of kind: * -> *
"""
__slots__ = ()
class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]): 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 -> *
@ -203,34 +197,22 @@ class TypeConstructor_TypeInt(TypeConstructor_Base[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 TypeApplication_TypeInt(TypeApplication_Base[TypeConstructor_TypeInt, Tuple[Type3, IntType3]]): class TypeConstructor_TypeStar(TypeConstructor[Tuple[Type3, ...]]):
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}]'
@ -239,30 +221,57 @@ 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(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]): class TypeConstructor_Struct:
""" """
Constructs struct types Base class for type construtors
""" """
def make_application(self, key: tuple[tuple[str, Type3], ...]) -> 'TypeApplication_Struct': __slots__ = ('name', 'classes', 'type_classes', '_cache', '_reverse_cache')
return TypeApplication_Struct(self, key)
def make_name(self, key: tuple[tuple[str, Type3], ...]) -> str: name: str
return f'{self.name}(' + ', '.join(
f'{n}: {t.name}'
for n, t in key
) + ')'
def construct(self, key: T) -> Type3:
""" """
Constructs the type by applying the given arguments to this The name of the type constructor
constructor.
""" """
raise Exception('This does not work with the caching system')
def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3: classes: Set['Type3Class']
result = Type3(name, self.make_application(args)) """
self.on_create(args, result) The type classes that this constructor implements
"""
type_classes: Set['Type3Class']
"""
The type classes that the constructed types implement
"""
_cache: dict[str, Type3]
"""
When constructing a type with the same arguments,
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, classes: Iterable['Type3Class'], type_classes: Iterable['Type3Class']) -> None:
self.name = name
self.classes = set(classes)
self.type_classes = set(type_classes)
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], classes: Set['Type3Class']) -> Type3:
result = Type3(name, classes | self.type_classes)
self._reverse_cache[result] = args
return result return result
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):
pass

View File

@ -2,7 +2,7 @@
Helper functions to generate WASM code by writing Python functions Helper functions to generate WASM code by writing Python functions
""" """
import functools import functools
from typing import Any, Callable, Dict, Iterable, List, Optional, Type from typing import Any, Callable, Dict, List, Optional, Type
from . import wasm from . import wasm
@ -24,12 +24,6 @@ class VarType_i32(VarType_Base):
class VarType_i64(VarType_Base): class VarType_i64(VarType_Base):
wasm_type = wasm.WasmTypeInt64 wasm_type = wasm.WasmTypeInt64
class VarType_f32(VarType_Base):
wasm_type = wasm.WasmTypeFloat32
class VarType_f64(VarType_Base):
wasm_type = wasm.WasmTypeFloat64
class Generator_i32i64: class Generator_i32i64:
def __init__(self, prefix: str, generator: 'Generator') -> None: def __init__(self, prefix: str, generator: 'Generator') -> None:
self.prefix = prefix self.prefix = prefix
@ -80,9 +74,6 @@ class Generator_i32(Generator_i32i64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('i32', generator) super().__init__('i32', generator)
# 2.4.1. Numeric Instructions
self.wrap_i64 = functools.partial(self.generator.add_statement, 'i32.wrap_i64')
class Generator_i64(Generator_i32i64): class Generator_i64(Generator_i32i64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('i64', generator) super().__init__('i64', generator)
@ -141,16 +132,10 @@ class Generator_f32(Generator_f32f64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('f32', generator) super().__init__('f32', generator)
# 2.4.1 Numeric Instructions
self.demote_f64 = functools.partial(self.generator.add_statement, 'f32.demote_f64')
class Generator_f64(Generator_f32f64): class Generator_f64(Generator_f32f64):
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
super().__init__('f64', generator) super().__init__('f64', generator)
# 2.4.1 Numeric Instructions
self.promote_f32 = functools.partial(self.generator.add_statement, 'f64.promote_f32')
class Generator_Local: class Generator_Local:
def __init__(self, generator: 'Generator') -> None: def __init__(self, generator: 'Generator') -> None:
self.generator = generator self.generator = generator
@ -170,23 +155,12 @@ class Generator_Local:
self.generator.add_statement('local.tee', variable.name_ref, comment=comment) self.generator.add_statement('local.tee', variable.name_ref, comment=comment)
class GeneratorBlock: class GeneratorBlock:
def __init__(self, generator: 'Generator', name: str, params: Iterable[str] = (), result: str | None = None) -> None: def __init__(self, generator: 'Generator', name: str) -> None:
self.generator = generator self.generator = generator
self.name = name self.name = name
self.params = params
self.result = result
def __enter__(self) -> None: def __enter__(self) -> None:
stmt = self.name self.generator.add_statement(self.name)
if self.params:
stmt = f'{stmt} ' + ' '.join(
f'(param {typ})'
for typ in self.params
)
if self.result:
stmt = f'{stmt} (result {self.result})'
self.generator.add_statement(stmt)
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
if not exc_type: if not exc_type:
@ -227,18 +201,19 @@ class Generator:
def add_statement(self, name: str, *args: str, comment: Optional[str] = None) -> None: def add_statement(self, name: str, *args: str, comment: Optional[str] = None) -> None:
self.statements.append(wasm.Statement(name, *args, comment=comment)) self.statements.append(wasm.Statement(name, *args, comment=comment))
def temp_var[T: VarType_Base](self, var: T) -> T: def temp_var_i32(self, infix: str) -> VarType_i32:
idx = 0 idx = 0
while (varname := f'__{var.name}_tmp_var_{idx}__') in self.locals: while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals:
idx += 1 idx += 1
return var.__class__(varname) return VarType_i32(varname)
def temp_var_i32(self, infix: str) -> VarType_i32:
return self.temp_var(VarType_i32(infix))
def temp_var_u8(self, infix: str) -> VarType_u8: def temp_var_u8(self, infix: str) -> VarType_u8:
return self.temp_var(VarType_u8(infix)) idx = 0
while (varname := f'__{infix}_tmp_var_{idx}__') in self.locals:
idx += 1
return VarType_u8(varname)
def func_wrapper(exported: bool = True) -> Callable[[Any], wasm.Function]: def func_wrapper(exported: bool = True) -> Callable[[Any], wasm.Function]:
""" """

View File

@ -4,22 +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 ( from phasm.runtime import calculate_alloc_size
calculate_alloc_size, from phasm.type3 import placeholders as type3placeholders
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
DASHES = '-' * 16 DASHES = '-' * 16
class InvalidArgumentException(Exception):
pass
class SuiteResult: class SuiteResult:
def __init__(self) -> None: def __init__(self) -> None:
self.returned_value = None self.returned_value = None
@ -73,6 +65,9 @@ class Suite:
runner.interpreter_dump_memory(sys.stderr) runner.interpreter_dump_memory(sys.stderr)
for arg, arg_typ in zip(args, func_args, strict=True): for arg, arg_typ in zip(args, func_args, strict=True):
assert not isinstance(arg_typ, type3placeholders.PlaceholderForType), \
'Cannot call polymorphic function from outside'
if arg_typ in (prelude.u8, prelude.u32, prelude.u64, ): if arg_typ in (prelude.u8, prelude.u32, prelude.u64, ):
assert isinstance(arg, int) assert isinstance(arg, int)
wasm_args.append(arg) wasm_args.append(arg)
@ -88,10 +83,30 @@ class Suite:
wasm_args.append(arg) wasm_args.append(arg)
continue continue
try: if arg_typ is prelude.bytes_:
adr = ALLOCATE_MEMORY_STORED_ROUTER((runner, arg), arg_typ) adr = _allocate_memory_stored_value(runner, arg_typ, arg)
wasm_args.append(adr) wasm_args.append(adr)
except NoRouteForTypeException: continue
assert isinstance(arg_typ, type3types.Type3)
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)')
@ -131,18 +146,39 @@ def _write_memory_stored_value(
val_typ: type3types.Type3, val_typ: type3types.Type3,
val: Any, val: Any,
) -> int: ) -> int:
try: if val_typ is prelude.bytes_:
adr2 = ALLOCATE_MEMORY_STORED_ROUTER((runner, val), val_typ) adr2 = _allocate_memory_stored_value(runner, val_typ, val)
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_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> int: def _allocate_memory_stored_value(
runner, val = attrs runner: runners.RunnerBase,
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))
@ -152,52 +188,32 @@ def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> in
runner.interpreter_write_memory(adr + 4, val) runner.interpreter_write_memory(adr + 4, val)
return adr return adr
def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int: sa_args = prelude.static_array.did_construct(val_typ)
runner, val = attrs if sa_args is not None:
assert isinstance(val, tuple)
sa_type, sa_len = sa_args sa_type, sa_len = sa_args
if not isinstance(val, tuple): alloc_size = calculate_alloc_size(val_typ)
raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead')
if sa_len.value != len(val):
raise InvalidArgumentException(f'Expected tuple of length {sa_len.value}; got {val!r} instead')
alloc_size = calculate_alloc_size_static_array(False, sa_args)
adr = runner.call('stdlib.alloc.__alloc__', alloc_size) adr = runner.call('stdlib.alloc.__alloc__', alloc_size)
assert isinstance(adr, int) # Type 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')
tuple_len = sa_len.value
assert tuple_len == len(val)
offset = adr offset = adr
for val_el_val in val: for val_el_val in val:
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
def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, type3types.Type3], ...]) -> int: val_el_typ: type3types.Type3
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_tuple(False, tp_args) alloc_size = calculate_alloc_size(val_typ)
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')
@ -206,14 +222,31 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args
offset = adr offset = adr
for val_el_val, val_el_typ in zip(val, tp_args, strict=True): for val_el_val, val_el_typ in zip(val, tp_args, strict=True):
assert not isinstance(val_el_typ, type3placeholders.PlaceholderForType)
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
ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]() st_args = prelude.struct.did_construct(val_typ)
ALLOCATE_MEMORY_STORED_ROUTER.add_n(prelude.bytes_, _allocate_memory_stored_bytes) if st_args is not None:
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.static_array, _allocate_memory_stored_static_array) assert isinstance(val, dict)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.struct, _allocate_memory_stored_struct)
ALLOCATE_MEMORY_STORED_ROUTER.add(prelude.tuple_, _allocate_memory_stored_tuple) alloc_size = calculate_alloc_size(val_typ)
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():
assert not isinstance(val_el_typ, type3placeholders.PlaceholderForType)
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,
@ -231,13 +264,6 @@ def _load_memory_stored_returned_value(
if ret_type3 in (prelude.i8, prelude.i32, prelude.i64): if ret_type3 in (prelude.i8, prelude.i32, prelude.i64):
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
if ret_type3 is prelude.i8:
# Values are actually i32
# Have to reinterpret to load proper value
data = struct.pack('<i', wasm_value)
wasm_value, = struct.unpack('<bxxx', data)
return wasm_value return wasm_value
if ret_type3 in (prelude.u8, prelude.u32, prelude.u64): if ret_type3 in (prelude.u8, prelude.u32, prelude.u64):
@ -261,9 +287,30 @@ 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_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3) return _load_bytes_from_address(runner, ret_type3, wasm_value)
assert isinstance(ret_type3, type3types.Type3) # Type hint
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:
@ -300,19 +347,41 @@ 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 (prelude.InternalPassAsPointer, (typ, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: 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 in typ.classes:
# 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]
return LOAD_FROM_ADDRESS_ROUTER((runner, adr), typ) assert isinstance(typ, type3types.Type3)
sa_args = prelude.static_array.did_construct(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(attrs: tuple[runners.RunnerBase, int]) -> bytes: def _load_bytes_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> bytes:
runner, adr = attrs sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n')
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)
@ -325,12 +394,12 @@ 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(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> Any: def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.Type3, len_typ: type3types.IntType3, adr: int) -> 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')
assert not isinstance(sub_typ, type3placeholders.PlaceholderForType)
assert isinstance(len_typ, type3types.IntType3)
sa_len = len_typ.value sa_len = len_typ.value
arg_size_1 = calculate_alloc_size(sub_typ, is_member=True) arg_size_1 = calculate_alloc_size(sub_typ, is_member=True)
@ -343,42 +412,37 @@ def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_ar
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
) )
def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tuple[tuple[str, type3types.Type3], ...]) -> dict[str, Any]: def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.Type3, ...], adr: int) -> Any:
runner, adr = attrs sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(typ_args)}\n')
sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
arg_sizes = [ arg_sizes = [
calculate_alloc_size(x, is_member=True) calculate_alloc_size(x, is_member=True)
for _, x in st_args for x in typ_args
]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return {
arg_name: _unpack(runner, arg_typ, arg_bytes)
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)) read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return tuple( return tuple(
_unpack(runner, arg_typ, arg_bytes) _unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(tp_args, _split_read_bytes(read_bytes, arg_sizes), strict=True) for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
) )
LOAD_FROM_ADDRESS_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]() def _load_struct_from_address(runner: runners.RunnerBase, st_args: dict[str, type3types.Type3], adr: int) -> Any:
LOAD_FROM_ADDRESS_ROUTER.add_n(prelude.bytes_, _load_bytes_from_address) sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
LOAD_FROM_ADDRESS_ROUTER.add(prelude.static_array, _load_static_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.struct, _load_struct_from_address) name_list = list(st_args)
LOAD_FROM_ADDRESS_ROUTER.add(prelude.tuple_, _load_tuple_from_address)
typ_list = list(st_args.values())
assert len(typ_list) == len(st_args)
arg_sizes = [
calculate_alloc_size(x, is_member=True)
for x in typ_list
]
read_bytes = runner.interpreter_read_memory(adr, sum(arg_sizes))
return {
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)
}

View File

@ -49,7 +49,7 @@ def testEntry(a: bytes, b: bytes) -> u8:
def test_foldl_3(): def test_foldl_3():
code_py = """ code_py = """
def xor(l: u32, r: u8) -> u32: def xor(l: u32, r: u8) -> u32:
return l ^ extend(r) return l ^ u32(r)
@exported @exported
def testEntry(a: bytes) -> u32: def testEntry(a: bytes) -> u32:

View File

@ -1,30 +0,0 @@
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

@ -29,7 +29,7 @@ def testEntry(x: Foo, y: Foo) -> Foo:
return x == y return x == y
""" """
with pytest.raises(Type3Exception, match='Missing type class instantation: Eq Foo'): with pytest.raises(Type3Exception, match='Foo does not implement the Eq type class'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test
@ -111,7 +111,7 @@ def testEntry(x: Foo, y: Foo) -> Foo:
return x != y return x != y
""" """
with pytest.raises(Type3Exception, match='Missing type class instantation: Eq Foo'): with pytest.raises(Type3Exception, match='Foo does not implement the Eq type class'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test

View File

@ -1,112 +0,0 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
EXTENTABLE = [
('u8', 'u32', ),
('u8', 'u64', ),
('u32', 'u64', ),
('i8', 'i32', ),
('i8', 'i64', ),
('i32', 'i64', ),
]
@pytest.mark.integration_test
def test_extend_not_implemented():
code_py = """
class Foo:
val: i32
class Baz:
val: i32
@exported
def testEntry(x: Foo) -> Baz:
return extend(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Extendable Foo Baz'):
Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.parametrize('ext_from,ext_to', EXTENTABLE)
def test_extend_ok(ext_from,ext_to):
code_py = f"""
CONSTANT: {ext_from} = 10
@exported
def testEntry() -> {ext_to}:
return extend(CONSTANT)
"""
result = Suite(code_py).run_code()
assert 10 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('ext_from,in_put,ext_to,exp_out', [
('u8', 241, 'u32', 241),
('u32', 4059165169, 'u64', 4059165169),
('u8', 241, 'u64', 241),
('i8', 113, 'i32', 113),
('i32', 1911681521, 'i64', 1911681521),
('i8', 113, 'i64', 113),
('i8', -15, 'i32', -15),
('i32', -15, 'i64', -15),
('i8', -15, 'i64', -15),
])
def test_extend_results(ext_from, ext_to, in_put, exp_out):
code_py = f"""
@exported
def testEntry(x: {ext_from}) -> {ext_to}:
return extend(x)
"""
result = Suite(code_py).run_code(in_put)
assert exp_out == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('ext_from,ext_to', EXTENTABLE)
def test_wrap_ok(ext_from,ext_to):
code_py = f"""
CONSTANT: {ext_to} = 10
@exported
def testEntry() -> {ext_from}:
return wrap(CONSTANT)
"""
result = Suite(code_py).run_code()
assert 10 == result.returned_value
@pytest.mark.integration_test
@pytest.mark.parametrize('ext_to,in_put,ext_from,exp_out', [
('u32', 0xF1F1F1F1, 'u8', 0xF1),
('u64', 0xF1F1F1F1F1F1F1F1, 'u32', 0xF1F1F1F1),
('u64', 0xF1F1F1F1F1F1F1F1, 'u8', 0xF1),
('i32', 0xF1F1F171, 'i8', 113),
('i32', 0xF1F1F1F1, 'i8', -15),
('i64', 0x71F1F1F171F1F1F1, 'i32', 1911681521),
('i64', 0x71F1F1F1F1F1F1F1, 'i32', -235802127),
('i64', 0xF1F1F1F1F1F1F171, 'i8', 113),
('i64', 0xF1F1F1F1F1F1F1F1, 'i8', -15),
])
def test_wrap_results(ext_from, ext_to, in_put, exp_out):
code_py = f"""
@exported
def testEntry(x: {ext_to}) -> {ext_from}:
return wrap(x)
"""
result = Suite(code_py).run_code(in_put)
assert exp_out == result.returned_value

View File

@ -1,71 +0,0 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
from .test_natnum import FLOAT_TYPES, INT_TYPES
@pytest.mark.integration_test
@pytest.mark.parametrize('length', [1, 5, 13])
@pytest.mark.parametrize('a_type', INT_TYPES + FLOAT_TYPES)
def test_foldable_sum(length, a_type):
code_py = f"""
@exported
def testEntry(x: {a_type}[{length}]) -> {a_type}:
return sum(x)
"""
in_put = tuple(range(length))
result = Suite(code_py).run_code(in_put)
assert sum(in_put) == result.returned_value
@pytest.mark.integration_test
def test_foldable_sum_not_natnum():
code_py = """
class Foo:
bar: i32
@exported
def testEntry(x: Foo[4]) -> Foo:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_foldable_invalid_return_type():
code_py = """
@exported
def testEntry(x: i32[5]) -> f64:
return sum(x)
"""
with pytest.raises(Type3Exception, match='i32 must be f64 instead'):
Suite(code_py).run_code((4, 5, 6, 7, 8, ))
@pytest.mark.integration_test
def test_foldable_not_constructed():
code_py = """
@exported
def testEntry(x: i32) -> i32:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable i32.*i32 must be a constructed type instead'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_foldable_not_foldable():
code_py = """
@exported
def testEntry(x: (i32, u32, )) -> i32:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable tuple'):
Suite(code_py).run_code()

View File

@ -6,20 +6,7 @@ from ..helpers import Suite
@pytest.mark.integration_test @pytest.mark.integration_test
def test_expr_constant_literal_does_not_fit_module_constant(): def test_expr_constant_literal_does_not_fit():
code_py = """
CONSTANT: u8 = 1000
@exported
def testEntry() -> u8:
return CONSTANT
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_expr_constant_literal_does_not_fit_return():
code_py = """ code_py = """
@exported @exported
def testEntry() -> u8: def testEntry() -> u8:

View File

@ -27,7 +27,7 @@ def testEntry(x: Foo, y: Foo) -> Foo:
return x + y return x + y
""" """
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'): with pytest.raises(Type3Exception, match='Foo does not implement the NatNum type class'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test

View File

@ -1,51 +0,0 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
@pytest.mark.integration_test
def test_promote_not_implemented():
code_py = """
class Foo:
val: i32
class Baz:
val: i32
@exported
def testEntry(x: Foo) -> Baz:
return promote(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Promotable Foo Baz'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_promote_ok():
code_py = """
CONSTANT: f32 = 10.5
@exported
def testEntry() -> f64:
return promote(CONSTANT)
"""
result = Suite(code_py).run_code()
assert 10.5 == result.returned_value
@pytest.mark.integration_test
def test_demote_ok():
code_py = """
CONSTANT: f64 = 10.5
@exported
def testEntry() -> f32:
return demote(CONSTANT)
"""
result = Suite(code_py).run_code()
assert 10.5 == result.returned_value

View File

@ -30,29 +30,3 @@ 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

@ -64,36 +64,6 @@ def helper(shape1: Rectangle, shape2: Rectangle) -> i32:
assert 545 == result.returned_value assert 545 == result.returned_value
@pytest.mark.integration_test
def test_type_mismatch_struct_call_root():
code_py = """
class CheckedValueBlue:
value: i32
class CheckedValueRed:
value: i32
CONST: CheckedValueBlue = CheckedValueRed(1)
"""
with pytest.raises(Type3Exception, match='CheckedValueBlue must be CheckedValueRed instead'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_type_mismatch_struct_call_nested():
code_py = """
class CheckedValueBlue:
value: i32
class CheckedValueRed:
value: i32
CONST: (CheckedValueBlue, u32, ) = (CheckedValueRed(1), 16, )
"""
with pytest.raises(Type3Exception, match='CheckedValueBlue must be CheckedValueRed instead'):
Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test
@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) @pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64'])
def test_type_mismatch_struct_member(type_): def test_type_mismatch_struct_member(type_):
@ -130,47 +100,3 @@ class f32:
with pytest.raises(StaticError, match='f32 already defined as type'): with pytest.raises(StaticError, match='f32 already defined as type'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test
@pytest.mark.skip(reason='FIXME: See constraintgenerator.py for AccessStructMember')
def test_struct_not_accessible():
code_py = """
@exported
def testEntry(x: u8) -> u8:
return x.y
"""
with pytest.raises(Type3Exception, match='u8 is not struct'):
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,23 +23,6 @@ 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, ),
@ -64,7 +47,7 @@ def testEntry(x: (u8, u32, u64), y: u8) -> u64:
return x[y] return x[y]
""" """
with pytest.raises(Type3Exception, match='Must index with integer literal'): with pytest.raises(Type3Exception, match='Must index with literal'):
Suite(code_py).run_code() Suite(code_py).run_code()
@pytest.mark.integration_test @pytest.mark.integration_test

View File

@ -70,29 +70,3 @@ 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