diff --git a/phasm/codestyle.py b/phasm/codestyle.py index cb3cdc8..867252f 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -5,9 +5,8 @@ It's intented to be a "any color, as long as it's black" kind of renderer """ from typing import Generator -from . import ourlang -from .type3 import types as type3types -from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder +from . import ourlang, prelude +from .type3 import placeholders as type3placeholders def phasm_render(inp: ourlang.Module) -> str: @@ -18,13 +17,14 @@ def phasm_render(inp: ourlang.Module) -> str: Statements = Generator[str, None, None] -def type3(inp: Type3OrPlaceholder) -> str: +def type3(inp: type3placeholders.Type3OrPlaceholder) -> str: """ Render: type's name """ - assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR + if isinstance(inp, type3placeholders.PlaceholderForType): + raise NotImplementedError - if inp is type3types.none: + if inp is prelude.none: return 'None' return inp.name @@ -33,8 +33,11 @@ def struct_definition(inp: ourlang.StructDefinition) -> str: """ Render: TypeStruct's definition """ + st_args = prelude.struct.did_construct(inp.struct_type3) + assert st_args is not None + result = f'class {inp.struct_type3.name}:\n' - for mem, typ in inp.struct_type3.members.items(): + for mem, typ in st_args.items(): result += f' {mem}: {type3(typ)}\n' return result diff --git a/phasm/compiler.py b/phasm/compiler.py index 3ad79df..4300f8a 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -4,10 +4,11 @@ This module contains the code to convert parsed Ourlang into WebAssembly code import struct from typing import Dict, List, Optional -from . import codestyle, ourlang, wasm +from . import codestyle, ourlang, prelude, wasm from .runtime import calculate_alloc_size, calculate_member_offset from .stdlib import alloc as stdlib_alloc from .stdlib import types as stdlib_types +from .type3 import placeholders as type3placeholders from .type3 import typeclasses as type3classes from .type3 import types as type3types from .wasmgenerator import Generator as WasmGenerator @@ -29,7 +30,7 @@ LOAD_STORE_TYPE_MAP = { # For now this is nice & clean, but this will get messy quick # Especially once we get functions with polymorphying applied types INSTANCES = { - type3classes.Eq.operators['==']: { + prelude.Eq.operators['==']: { 'a=u8': stdlib_types.u8_eq_equals, 'a=u32': stdlib_types.u32_eq_equals, 'a=u64': stdlib_types.u64_eq_equals, @@ -39,7 +40,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_eq_equals, 'a=f64': stdlib_types.f64_eq_equals, }, - type3classes.Eq.operators['!=']: { + 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, @@ -49,7 +50,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_eq_not_equals, 'a=f64': stdlib_types.f64_eq_not_equals, }, - type3classes.Ord.methods['min']: { + 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, @@ -59,7 +60,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_ord_min, 'a=f64': stdlib_types.f64_ord_min, }, - type3classes.Ord.methods['max']: { + 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, @@ -69,7 +70,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_ord_max, 'a=f64': stdlib_types.f64_ord_max, }, - type3classes.Ord.operators['<']: { + 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, @@ -79,7 +80,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_ord_less_than, 'a=f64': stdlib_types.f64_ord_less_than, }, - type3classes.Ord.operators['<=']: { + 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, @@ -89,7 +90,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_ord_less_than_or_equal, 'a=f64': stdlib_types.f64_ord_less_than_or_equal, }, - type3classes.Ord.operators['>']: { + 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, @@ -99,7 +100,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_ord_greater_than, 'a=f64': stdlib_types.f64_ord_greater_than, }, - type3classes.Ord.operators['>=']: { + 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, @@ -109,90 +110,90 @@ INSTANCES = { 'a=f32': stdlib_types.f32_ord_greater_than_or_equal, 'a=f64': stdlib_types.f64_ord_greater_than_or_equal, }, - type3classes.Bits.methods['shl']: { + 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, }, - type3classes.Bits.methods['shr']: { + 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, }, - type3classes.Bits.methods['rotl']: { + 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, }, - type3classes.Bits.methods['rotr']: { + 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, }, - type3classes.Bits.operators['&']: { + 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, }, - type3classes.Bits.operators['|']: { + 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, }, - type3classes.Bits.operators['^']: { + 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, }, - type3classes.Floating.methods['sqrt']: { + prelude.Floating.methods['sqrt']: { 'a=f32': stdlib_types.f32_floating_sqrt, 'a=f64': stdlib_types.f64_floating_sqrt, }, - type3classes.Fractional.methods['ceil']: { + prelude.Fractional.methods['ceil']: { 'a=f32': stdlib_types.f32_fractional_ceil, 'a=f64': stdlib_types.f64_fractional_ceil, }, - type3classes.Fractional.methods['floor']: { + prelude.Fractional.methods['floor']: { 'a=f32': stdlib_types.f32_fractional_floor, 'a=f64': stdlib_types.f64_fractional_floor, }, - type3classes.Fractional.methods['trunc']: { + prelude.Fractional.methods['trunc']: { 'a=f32': stdlib_types.f32_fractional_trunc, 'a=f64': stdlib_types.f64_fractional_trunc, }, - type3classes.Fractional.methods['nearest']: { + prelude.Fractional.methods['nearest']: { 'a=f32': stdlib_types.f32_fractional_nearest, 'a=f64': stdlib_types.f64_fractional_nearest, }, - type3classes.Fractional.operators['/']: { + prelude.Fractional.operators['/']: { 'a=f32': stdlib_types.f32_fractional_div, 'a=f64': stdlib_types.f64_fractional_div, }, - type3classes.Integral.operators['//']: { + 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, }, - type3classes.Integral.operators['%']: { + 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, }, - type3classes.IntNum.methods['abs']: { + 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, }, - type3classes.IntNum.methods['neg']: { + 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, }, - type3classes.NatNum.operators['+']: { + prelude.NatNum.operators['+']: { 'a=u32': stdlib_types.u32_natnum_add, 'a=u64': stdlib_types.u64_natnum_add, 'a=i32': stdlib_types.i32_natnum_add, @@ -200,7 +201,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_natnum_add, 'a=f64': stdlib_types.f64_natnum_add, }, - type3classes.NatNum.operators['-']: { + prelude.NatNum.operators['-']: { 'a=u32': stdlib_types.u32_natnum_sub, 'a=u64': stdlib_types.u64_natnum_sub, 'a=i32': stdlib_types.i32_natnum_sub, @@ -208,7 +209,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_natnum_sub, 'a=f64': stdlib_types.f64_natnum_sub, }, - type3classes.NatNum.operators['*']: { + prelude.NatNum.operators['*']: { 'a=u32': stdlib_types.u32_natnum_mul, 'a=u64': stdlib_types.u64_natnum_mul, 'a=i32': stdlib_types.i32_natnum_mul, @@ -216,7 +217,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_natnum_mul, 'a=f64': stdlib_types.f64_natnum_mul, }, - type3classes.NatNum.operators['<<']: { + 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, @@ -224,7 +225,7 @@ INSTANCES = { 'a=f32': stdlib_types.f32_natnum_arithmic_shift_left, 'a=f64': stdlib_types.f64_natnum_arithmic_shift_left, }, - type3classes.NatNum.operators['>>']: { + 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, @@ -241,61 +242,57 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module: """ return module(inp) -def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType: +def type3(inp: type3placeholders.Type3OrPlaceholder) -> wasm.WasmType: """ Compile: type Types are used for example in WebAssembly function parameters and return types. """ - assert isinstance(inp, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(inp, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR - if inp == type3types.none: + if inp == prelude.none: return wasm.WasmTypeNone() - if inp == type3types.bool_: + if inp == prelude.bool_: # WebAssembly stores booleans as i32 # See e.g. f32.eq, which is [f32 f32] -> [i32] return wasm.WasmTypeInt32() - if inp == type3types.u8: + if inp == prelude.u8: # WebAssembly has only support for 32 and 64 bits # So we need to store more memory per byte return wasm.WasmTypeInt32() - if inp == type3types.u32: + if inp == prelude.u32: return wasm.WasmTypeInt32() - if inp == type3types.u64: + if inp == prelude.u64: return wasm.WasmTypeInt64() - if inp == type3types.i8: + if inp == prelude.i8: # WebAssembly has only support for 32 and 64 bits # So we need to store more memory per byte return wasm.WasmTypeInt32() - if inp == type3types.i32: + if inp == prelude.i32: return wasm.WasmTypeInt32() - if inp == type3types.i64: + if inp == prelude.i64: return wasm.WasmTypeInt64() - if inp == type3types.f32: + if inp == prelude.f32: return wasm.WasmTypeFloat32() - if inp == type3types.f64: + if inp == prelude.f64: return wasm.WasmTypeFloat64() - if inp == type3types.bytes_: + if inp == prelude.bytes_: # bytes are passed as pointer # And pointers are i32 return wasm.WasmTypeInt32() - if isinstance(inp, type3types.StructType3): - # Structs are passed as pointer, which are i32 - return wasm.WasmTypeInt32() - - if type3classes.InternalPassAsPointer in inp.classes: + if prelude.InternalPassAsPointer in inp.classes: return wasm.WasmTypeInt32() raise NotImplementedError(type3, inp) @@ -304,17 +301,17 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> """ Compile: Instantiation (allocation) of a tuple """ - assert isinstance(inp.type3, type3types.PrimitiveType3) + assert isinstance(inp.type3, type3types.Type3) - args: list[type3types.PrimitiveType3] = [] + args: list[type3types.Type3] = [] - sa_args = type3types.static_array.did_construct(inp.type3) + sa_args = prelude.static_array.did_construct(inp.type3) if sa_args is not None: sa_type, sa_len = sa_args args = [sa_type for _ in range(sa_len.value)] if not args: - tp_args = type3types.tuple_.did_construct(inp.type3) + tp_args = prelude.tuple_.did_construct(inp.type3) if tp_args is None: raise NotImplementedError @@ -322,7 +319,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> comment_elements = '' for element in inp.elements: - assert isinstance(element.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(element.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR comment_elements += f'{element.type3.name}, ' tmp_var = wgn.temp_var_i32('tuple_adr') @@ -336,19 +333,17 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> # Store each element individually offset = 0 for element, exp_type3 in zip(inp.elements, args): - if isinstance(exp_type3, type3types.PlaceholderForType): + if isinstance(exp_type3, type3placeholders.PlaceholderForType): assert exp_type3.resolve_as is not None - assert isinstance(exp_type3.resolve_as, type3types.PrimitiveType3) + assert isinstance(exp_type3.resolve_as, type3types.Type3) exp_type3 = exp_type3.resolve_as assert element.type3 == exp_type3 - if type3classes.InternalPassAsPointer in exp_type3.classes: - mtyp = 'i32' - elif isinstance(exp_type3, type3types.StructType3): + if prelude.InternalPassAsPointer in exp_type3.classes: mtyp = 'i32' else: - assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + assert isinstance(exp_type3, type3types.Type3), NotImplementedError('Tuple of applied types / structs') mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name] wgn.add_statement('nop', comment='PRE') @@ -371,30 +366,30 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: raise Exception if isinstance(inp, ourlang.ConstantPrimitive): - assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR - if inp.type3 in (type3types.i8, type3types.u8, ): + if inp.type3 in (prelude.i8, prelude.u8, ): # No native u8 type - treat as i32, with caution assert isinstance(inp.value, int) wgn.i32.const(inp.value) return - if inp.type3 in (type3types.i32, type3types.u32, ): + if inp.type3 in (prelude.i32, prelude.u32, ): assert isinstance(inp.value, int) wgn.i32.const(inp.value) return - if inp.type3 in (type3types.i64, type3types.u64, ): + if inp.type3 in (prelude.i64, prelude.u64, ): assert isinstance(inp.value, int) wgn.i64.const(inp.value) return - if inp.type3 == type3types.f32: + if inp.type3 == prelude.f32: assert isinstance(inp.value, float) wgn.f32.const(inp.value) return - if inp.type3 == type3types.f64: + if inp.type3 == prelude.f64: assert isinstance(inp.value, float) wgn.f64.const(inp.value) return @@ -412,34 +407,18 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: return if isinstance(inp.variable, ourlang.ModuleConstantDef): - assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR - if type3classes.InternalPassAsPointer in inp.type3.classes: + if prelude.InternalPassAsPointer in inp.type3.classes: # FIXME: Artifact from older version - assert isinstance(inp.variable.constant, ourlang.ConstantTuple) + assert isinstance(inp.variable.constant, (ourlang.ConstantTuple, ourlang.ConstantStruct, )) address = inp.variable.constant.data_block.address assert address is not None, 'Value not allocated' wgn.i32.const(address) return - if inp.type3 is type3types.bytes_: - assert isinstance(inp.variable.constant, ourlang.ConstantBytes) - - address = inp.variable.constant.data_block.address - assert address is not None, 'Value not allocated' - wgn.i32.const(address) - return - - if isinstance(inp.type3, type3types.StructType3): - assert isinstance(inp.variable.constant, ourlang.ConstantStruct) - - address = inp.variable.constant.data_block.address - assert address is not None, 'Value not allocated' - wgn.i32.const(address) - return - - if isinstance(inp.type3, type3types.PrimitiveType3): + if isinstance(inp.type3, type3types.Type3): expression(wgn, inp.variable.constant) return @@ -451,7 +430,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: expression(wgn, inp.left) expression(wgn, inp.right) - assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR type_var_map: Dict[type3classes.TypeVariable, type3types.Type3] = {} @@ -460,7 +439,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: # Fixed type, not part of the lookup requirements continue - assert isinstance(arg_expr.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR type_var_map[type_var] = arg_expr.type3 instance_key = ','.join( @@ -478,16 +457,16 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: if isinstance(inp, ourlang.UnaryOp): expression(wgn, inp.right) - assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR - if inp.type3 == type3types.u32: + if inp.type3 == prelude.u32: if inp.operator == 'len': - if inp.right.type3 == type3types.bytes_: + if inp.right.type3 == prelude.bytes_: wgn.i32.load() return if inp.operator == 'cast': - if inp.type3 == type3types.u32 and inp.right.type3 == type3types.u8: + 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 @@ -506,7 +485,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: # Fixed type, not part of the lookup requirements continue - assert isinstance(arg_expr.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR type_var_map[type_var] = arg_expr.type3 instance_key = ','.join( @@ -529,17 +508,17 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: return if isinstance(inp, ourlang.Subscript): - assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(inp.varref.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR - if inp.varref.type3 is type3types.bytes_: + if inp.varref.type3 is prelude.bytes_: expression(wgn, inp.varref) expression(wgn, inp.index) wgn.call(stdlib_types.__subscript_bytes__) return - assert isinstance(inp.varref.type3, type3types.PrimitiveType3) + assert isinstance(inp.varref.type3, type3types.Type3) - sa_args = type3types.static_array.did_construct(inp.varref.type3) + sa_args = prelude.static_array.did_construct(inp.varref.type3) if sa_args is not None: el_type, el_len = sa_args @@ -563,33 +542,31 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: wgn.i32.mul() wgn.i32.add() - assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + 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 = type3types.tuple_.did_construct(inp.varref.type3) + 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), type3types.TYPE3_ASSERTION_ERROR + 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), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(el_type, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR expression(wgn, inp.varref) - if isinstance(el_type, type3types.StructType3): - mtyp = 'i32' - elif type3classes.InternalPassAsPointer in el_type.classes: + if prelude.InternalPassAsPointer in el_type.classes: mtyp = 'i32' else: - assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') + 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}') @@ -598,12 +575,19 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: raise NotImplementedError(expression, inp, inp.varref.type3) if isinstance(inp, ourlang.AccessStructMember): - assert isinstance(inp.struct_type3.members[inp.member], type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs') - mtyp = LOAD_STORE_TYPE_MAP[inp.struct_type3.members[inp.member].name] + assert isinstance(inp.struct_type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR + + st_args = prelude.struct.did_construct(inp.struct_type3) + assert st_args is not None + + 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] expression(wgn, inp.varref) wgn.add_statement(f'{mtyp}.load', 'offset=' + str(calculate_member_offset( - inp.struct_type3, inp.member + inp.struct_type3.name, st_args, inp.member ))) return @@ -617,9 +601,9 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None: """ Compile: Fold expression """ - assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR - if inp.iter.type3 is not type3types.bytes_: + if inp.iter.type3 is not prelude.bytes_: raise NotImplementedError(expression_fold, inp, inp.iter.type3) wgn.add_statement('nop', comment='acu :: u8') @@ -848,7 +832,7 @@ def module_data(inp: ourlang.ModuleData) -> bytes: data_list: List[bytes] = [] for constant in block.data: - assert isinstance(constant.type3, type3types.Type3), (id(constant), type3types.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: # It's stored in a different block @@ -859,55 +843,55 @@ def module_data(inp: ourlang.ModuleData) -> bytes: data_list.append(module_data_u32(constant.data_block.address)) continue - if constant.type3 == type3types.u8: + if constant.type3 == prelude.u8: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_u8(constant.value)) continue - if constant.type3 == type3types.u32: + if constant.type3 == prelude.u32: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_u32(constant.value)) continue - if constant.type3 == type3types.u64: + if constant.type3 == prelude.u64: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_u64(constant.value)) continue - if constant.type3 == type3types.i8: + if constant.type3 == prelude.i8: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_i8(constant.value)) continue - if constant.type3 == type3types.i32: + if constant.type3 == prelude.i32: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_i32(constant.value)) continue - if constant.type3 == type3types.i64: + if constant.type3 == prelude.i64: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_i64(constant.value)) continue - if constant.type3 == type3types.f32: + if constant.type3 == prelude.f32: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, float) data_list.append(module_data_f32(constant.value)) continue - if constant.type3 == type3types.f64: + if constant.type3 == prelude.f64: assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, float) data_list.append(module_data_f64(constant.value)) continue - if constant.type3 == type3types.bytes_: + if constant.type3 == prelude.bytes_: assert isinstance(constant, ourlang.ConstantBytes) assert isinstance(constant.value, bytes) data_list.append(module_data_u32(len(constant.value))) @@ -978,6 +962,9 @@ def module(inp: ourlang.Module) -> wasm.Module: return result def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: + st_args = prelude.struct.did_construct(inp.struct_type3) + assert st_args is not None + tmp_var = wgn.temp_var_i32('struct_adr') # Allocated the required amounts of bytes in memory @@ -986,11 +973,9 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc wgn.local.set(tmp_var) # Store each member individually - for memname, mtyp3 in inp.struct_type3.members.items(): + for memname, mtyp3 in st_args.items(): mtyp: Optional[str] - if type3classes.InternalPassAsPointer in mtyp3.classes: - mtyp = 'i32' - elif isinstance(mtyp3, type3types.StructType3): + if prelude.InternalPassAsPointer in mtyp3.classes: mtyp = 'i32' else: mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name) @@ -1001,7 +986,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc wgn.local.get(tmp_var) wgn.add_statement('local.get', f'${memname}') wgn.add_statement(f'{mtyp}.store', 'offset=' + str(calculate_member_offset( - inp.struct_type3, memname + inp.struct_type3.name, st_args, memname ))) # Return the allocated address diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 2c0a4d9..ad34243 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -6,9 +6,10 @@ from typing import Dict, Iterable, List, Optional, Union from typing_extensions import Final +from . import prelude from .type3 import typeclasses as type3typeclasses -from .type3 import types as type3types -from .type3.types import PlaceholderForType, StructType3, Type3, Type3OrPlaceholder +from .type3.placeholders import PlaceholderForType, Type3OrPlaceholder +from .type3.types import Type3 WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', ) @@ -214,10 +215,10 @@ class AccessStructMember(Expression): __slots__ = ('varref', 'struct_type3', 'member', ) varref: VariableReference - struct_type3: StructType3 + struct_type3: Type3OrPlaceholder member: str - def __init__(self, varref: VariableReference, struct_type3: StructType3, member: str) -> None: + def __init__(self, varref: VariableReference, struct_type3: Type3OrPlaceholder, member: str) -> None: super().__init__() self.varref = varref @@ -326,7 +327,7 @@ class Function: self.exported = False self.imported = None self.statements = [] - self.returns_type3 = type3types.none # FIXME: This could be a placeholder + self.returns_type3 = prelude.none # FIXME: This could be a placeholder self.posonlyargs = [] class StructDefinition: @@ -335,10 +336,10 @@ class StructDefinition: """ __slots__ = ('struct_type3', 'lineno', ) - struct_type3: StructType3 + struct_type3: Type3 lineno: int - def __init__(self, struct_type3: StructType3, lineno: int) -> None: + def __init__(self, struct_type3: Type3, lineno: int) -> None: self.struct_type3 = struct_type3 self.lineno = lineno @@ -351,14 +352,16 @@ class StructConstructor(Function): """ __slots__ = ('struct_type3', ) - struct_type3: StructType3 + struct_type3: Type3 - def __init__(self, struct_type3: StructType3) -> None: + def __init__(self, struct_type3: Type3) -> None: super().__init__(f'@{struct_type3.name}@__init___@', -1) self.returns_type3 = struct_type3 - for mem, typ in struct_type3.members.items(): + st_args = prelude.struct.did_construct(struct_type3) + assert st_args is not None + for mem, typ in st_args.items(): self.posonlyargs.append(FunctionParam(mem, typ, )) self.struct_type3 = struct_type3 diff --git a/phasm/parser.py b/phasm/parser.py index 227d84a..03dead5 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -4,6 +4,7 @@ Parses the source code from the plain text into a syntax tree import ast from typing import Any, Dict, NoReturn, Union +from . import prelude from .exceptions import StaticError from .ourlang import ( AccessStructMember, @@ -31,29 +32,10 @@ from .ourlang import ( UnaryOp, VariableReference, ) +from .prelude import PRELUDE_METHODS, PRELUDE_OPERATORS, PRELUDE_TYPES from .type3 import typeclasses as type3typeclasses from .type3 import types as type3types -PRELUDE_OPERATORS = { - **type3typeclasses.Bits.operators, - **type3typeclasses.Eq.operators, - **type3typeclasses.Ord.operators, - **type3typeclasses.Fractional.operators, - **type3typeclasses.Integral.operators, - **type3typeclasses.IntNum.operators, - **type3typeclasses.NatNum.operators, -} - -PRELUDE_METHODS = { - **type3typeclasses.Bits.methods, - **type3typeclasses.Eq.methods, - **type3typeclasses.Ord.methods, - **type3typeclasses.Floating.methods, - **type3typeclasses.Fractional.methods, - **type3typeclasses.Integral.methods, - **type3typeclasses.IntNum.methods, - **type3typeclasses.NatNum.methods, -} def phasm_parse(source: str) -> Module: """ @@ -252,7 +234,7 @@ class OurVisitor: members[stmt.target.id] = self.visit_type(module, stmt.annotation) - return StructDefinition(type3types.StructType3(node.name, members), node.lineno) + return StructDefinition(prelude.struct(node.name, members, set()), node.lineno) def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: if not isinstance(node.target, ast.Name): @@ -509,7 +491,7 @@ class OurVisitor: 'cast', self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[0]), ) - unary_op.type3 = type3types.u32 + unary_op.type3 = prelude.u32 return unary_op elif node.func.id == 'len': if 1 != len(node.args): @@ -570,9 +552,6 @@ class OurVisitor: if not isinstance(varref, VariableReference): _raise_static_error(node.value, 'Must refer to variable') - if not isinstance(varref.variable.type3, type3types.StructType3): - _raise_static_error(node.value, 'Must refer to struct') - return AccessStructMember( varref, varref.variable.type3, @@ -664,10 +643,10 @@ class OurVisitor: raise NotImplementedError(f'{node.value} as constant') - def visit_type(self, module: Module, node: ast.expr) -> type3types.PrimitiveType3: + def visit_type(self, module: Module, node: ast.expr) -> type3types.Type3: if isinstance(node, ast.Constant): if node.value is None: - return type3types.none + return prelude.none _raise_static_error(node, f'Unrecognized type {node.value}') @@ -675,8 +654,8 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - if node.id in type3types.LOOKUP_TABLE: - return type3types.LOOKUP_TABLE[node.id] + if node.id in PRELUDE_TYPES: + return PRELUDE_TYPES[node.id] if node.id in module.struct_definitions: return module.struct_definitions[node.id].struct_type3 @@ -693,7 +672,7 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - return type3types.static_array( + return prelude.static_array( self.visit_type(module, node.value), type3types.IntType3(node.slice.value), ) @@ -702,7 +681,7 @@ class OurVisitor: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - return type3types.tuple_( + return prelude.tuple_( *(self.visit_type(module, elt) for elt in node.elts) ) diff --git a/phasm/prelude/__init__.py b/phasm/prelude/__init__.py new file mode 100644 index 0000000..87e1aa1 --- /dev/null +++ b/phasm/prelude/__init__.py @@ -0,0 +1,274 @@ +""" +The prelude are all the builtin types, type classes and methods +""" + +from ..type3.typeclasses import Type3Class, TypeVariable, instance_type_class +from ..type3.types import ( + Type3, + TypeConstructor_StaticArray, + TypeConstructor_Struct, + TypeConstructor_Tuple, +) + +none = Type3('none', []) +""" +The none type, for when functions simply don't return anything. e.g., IO(). +""" + +bool_ = Type3('bool', []) +""" +The bool type, either True or False + +Suffixes with an underscores, as it's a Python builtin +""" + +u8 = Type3('u8', []) +""" +The unsigned 8-bit integer type. + +Operations on variables employ modular arithmetic, with modulus 2^8. +""" + +u32 = Type3('u32', []) +""" +The unsigned 32-bit integer type. + +Operations on variables employ modular arithmetic, with modulus 2^32. +""" + +u64 = Type3('u64', []) +""" +The unsigned 64-bit integer type. + +Operations on variables employ modular arithmetic, with modulus 2^64. +""" + +i8 = Type3('i8', []) +""" +The signed 8-bit integer type. + +Operations on variables employ modular arithmetic, with modulus 2^8, but +with the middel point being 0. +""" + +i32 = Type3('i32', []) +""" +The unsigned 32-bit integer type. + +Operations on variables employ modular arithmetic, with modulus 2^32, but +with the middel point being 0. +""" + +i64 = Type3('i64', []) +""" +The unsigned 64-bit integer type. + +Operations on variables employ modular arithmetic, with modulus 2^64, but +with the middel point being 0. +""" + +f32 = Type3('f32', []) +""" +A 32-bits IEEE 754 float, of 32 bits width. +""" + +f64 = Type3('f64', []) +""" +A 32-bits IEEE 754 float, of 64 bits width. +""" + +bytes_ = Type3('bytes', []) +""" +This is a runtime-determined length piece of memory that can be indexed at runtime. +""" + +static_array = TypeConstructor_StaticArray('static_array', [], []) +""" +A type constructor. + +Any static array is a fixed length piece of memory that can be indexed at runtime. + +It should be applied with one argument. It has a runtime-dynamic length +of the same type repeated. +""" + +tuple_ = TypeConstructor_Tuple('tuple', [], []) +""" +This is a fixed length piece of memory. + +It should be applied with zero or more arguments. It has a compile time +determined length, and each argument can be different. +""" + +struct = TypeConstructor_Struct('struct', [], []) +""" +This is like a tuple, but each argument is named, so that developers +can get and set fields by name. +""" + +PRELUDE_TYPES: dict[str, Type3] = { + 'none': none, + 'bool': bool_, + 'u8': u8, + 'u32': u32, + 'u64': u64, + 'i8': i8, + 'i32': i32, + 'i64': i64, + 'f32': f32, + 'f64': f64, + 'bytes': bytes_, +} + +a = TypeVariable('a') + + +InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={}) +""" +Internal type class to keep track which types we pass arounds as a pointer. +""" + +instance_type_class(InternalPassAsPointer, bytes_) +instance_type_class(InternalPassAsPointer, static_array) +instance_type_class(InternalPassAsPointer, tuple_) +instance_type_class(InternalPassAsPointer, struct) + +Eq = Type3Class('Eq', [a], methods={}, operators={ + '==': [a, a, bool_], + '!=': [a, a, bool_], + # FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization? +}) + +instance_type_class(Eq, u8) +instance_type_class(Eq, u32) +instance_type_class(Eq, u64) +instance_type_class(Eq, i8) +instance_type_class(Eq, i32) +instance_type_class(Eq, i64) +instance_type_class(Eq, f32) +instance_type_class(Eq, f64) +instance_type_class(Eq, static_array) + +Ord = Type3Class('Ord', [a], methods={ + 'min': [a, a, a], + 'max': [a, a, a], +}, operators={ + '<': [a, a, bool_], + '<=': [a, a, bool_], + '>': [a, a, bool_], + '>=': [a, a, bool_], +}, inherited_classes=[Eq]) + +instance_type_class(Ord, u8) +instance_type_class(Ord, u32) +instance_type_class(Ord, u64) +instance_type_class(Ord, i8) +instance_type_class(Ord, i32) +instance_type_class(Ord, i64) +instance_type_class(Ord, f32) +instance_type_class(Ord, f64) + +Bits = Type3Class('Bits', [a], methods={ + 'shl': [a, u32, a], # Logical shift left + 'shr': [a, u32, a], # Logical shift right + 'rotl': [a, u32, a], # Rotate bits left + 'rotr': [a, u32, a], # Rotate bits right + # FIXME: Do we want to expose clz, ctz, popcnt? +}, operators={ + '&': [a, a, a], # Bit-wise and + '|': [a, a, a], # Bit-wise or + '^': [a, a, a], # Bit-wise xor +}) + +instance_type_class(Bits, u8) +instance_type_class(Bits, u32) +instance_type_class(Bits, u64) + +NatNum = Type3Class('NatNum', [a], methods={}, operators={ + '+': [a, a, a], + '-': [a, a, a], + '*': [a, a, a], + '<<': [a, u32, a], # Arithmic shift left + '>>': [a, u32, a], # Arithmic shift right +}) + +instance_type_class(NatNum, u32) +instance_type_class(NatNum, u64) +instance_type_class(NatNum, i32) +instance_type_class(NatNum, i64) +instance_type_class(NatNum, f32) +instance_type_class(NatNum, f64) + +IntNum = Type3Class('IntNum', [a], methods={ + 'abs': [a, a], + 'neg': [a, a], +}, operators={}, inherited_classes=[NatNum]) + +instance_type_class(IntNum, i32) +instance_type_class(IntNum, i64) +instance_type_class(IntNum, f32) +instance_type_class(IntNum, f64) + +Integral = Type3Class('Eq', [a], methods={ +}, operators={ + '//': [a, a, a], + '%': [a, a, a], +}, inherited_classes=[NatNum]) + +instance_type_class(Integral, u32) +instance_type_class(Integral, u64) +instance_type_class(Integral, i32) +instance_type_class(Integral, i64) + +Fractional = Type3Class('Fractional', [a], methods={ + 'ceil': [a, a], + 'floor': [a, a], + 'trunc': [a, a], + 'nearest': [a, a], +}, operators={ + '/': [a, a, a], +}, inherited_classes=[NatNum]) + +instance_type_class(Fractional, f32) +instance_type_class(Fractional, f64) + +Floating = Type3Class('Floating', [a], methods={ + 'sqrt': [a, a], +}, operators={}, inherited_classes=[Fractional]) + +# FIXME: Do we want to expose copysign? + +instance_type_class(Floating, f32) +instance_type_class(Floating, f64) + +PRELUDE_TYPE_CLASSES = { + 'Eq': Eq, + 'Ord': Ord, + 'Bits': Bits, + 'NatNum': NatNum, + 'IntNum': IntNum, + 'Integral': Integral, + 'Fractional': Fractional, + 'Floating': Floating, +} + +PRELUDE_OPERATORS = { + **Bits.operators, + **Eq.operators, + **Ord.operators, + **Fractional.operators, + **Integral.operators, + **IntNum.operators, + **NatNum.operators, +} + +PRELUDE_METHODS = { + **Bits.methods, + **Eq.methods, + **Ord.methods, + **Floating.methods, + **Fractional.methods, + **Integral.methods, + **IntNum.methods, + **NatNum.methods, +} diff --git a/phasm/runtime.py b/phasm/runtime.py index 45019da..27988f9 100644 --- a/phasm/runtime.py +++ b/phasm/runtime.py @@ -1,35 +1,35 @@ +from . import prelude from .type3 import types as type3types def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: - if typ in (type3types.u8, type3types.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 - if typ in (type3types.u32, type3types.i32, type3types.f32, ): + if typ in (prelude.u32, prelude.i32, prelude.f32, ): return 4 - if typ in (type3types.u64, type3types.i64, type3types.f64, ): + if typ in (prelude.u64, prelude.i64, prelude.f64, ): return 8 - if typ == type3types.bytes_: + if typ == prelude.bytes_: if is_member: return 4 raise NotImplementedError # When does this happen? - if isinstance(typ, type3types.StructType3): + 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 typ.members.values() + for x in st_args.values() ) - assert isinstance(typ, type3types.PrimitiveType3) - - sa_args = type3types.static_array.did_construct(typ) + 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 @@ -39,7 +39,7 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: return sa_len.value * calculate_alloc_size(sa_type, is_member=True) - tp_args = type3types.tuple_.did_construct(typ) + 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 @@ -47,25 +47,19 @@ def calculate_alloc_size(typ: type3types.Type3, is_member: bool = False) -> int: size = 0 for arg in tp_args: - assert not isinstance(arg, type3types.IntType3) - - if isinstance(arg, type3types.PlaceholderForType): - assert isinstance(arg.resolve_as, type3types.PrimitiveType3) - arg = arg.resolve_as - size += calculate_alloc_size(arg, is_member=True) return size raise NotImplementedError(calculate_alloc_size, typ) -def calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int: +def calculate_member_offset(st_name: str, st_args: dict[str, type3types.Type3], needle: str) -> int: result = 0 - for mem, memtyp in struct_type3.members.items(): - if member == mem: + for memnam, memtyp in st_args.items(): + if needle == memnam: return result result += calculate_alloc_size(memtyp, is_member=True) - raise Exception(f'{member} not in {struct_type3}') + raise Exception(f'{needle} not in {st_name}') diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index c630def..d2b791f 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -5,8 +5,8 @@ These need to be resolved before the program can be compiled. """ from typing import Dict, List, Optional, Tuple, Union -from .. import ourlang -from . import typeclasses, types +from .. import ourlang, prelude +from . import placeholders, typeclasses, types class Error: @@ -32,13 +32,13 @@ class RequireTypeSubstitutes: typing of the program, so this constraint can be updated. """ -SubstitutionMap = Dict[types.PlaceholderForType, types.Type3] +SubstitutionMap = Dict[placeholders.PlaceholderForType, types.Type3] NewConstraintList = List['ConstraintBase'] CheckResult = Union[None, SubstitutionMap, Error, NewConstraintList, RequireTypeSubstitutes] -HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]] +HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, placeholders.PlaceholderForType]]] class Context: """ @@ -92,9 +92,9 @@ class SameTypeConstraint(ConstraintBase): """ __slots__ = ('type_list', ) - type_list: List[types.Type3OrPlaceholder] + type_list: List[placeholders.Type3OrPlaceholder] - def __init__(self, *type_list: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None: + def __init__(self, *type_list: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None: super().__init__(comment=comment) assert len(type_list) > 1 @@ -102,21 +102,17 @@ class SameTypeConstraint(ConstraintBase): def check(self) -> CheckResult: known_types: List[types.Type3] = [] - placeholders = [] + phft_list = [] for typ in self.type_list: - if isinstance(typ, types.IntType3): + if isinstance(typ, types.Type3): known_types.append(typ) continue - if isinstance(typ, (types.PrimitiveType3, types.StructType3, )): - known_types.append(typ) - continue - - if isinstance(typ, types.PlaceholderForType): + if isinstance(typ, placeholders.PlaceholderForType): if typ.resolve_as is not None: known_types.append(typ.resolve_as) else: - placeholders.append(typ) + phft_list.append(typ) continue raise NotImplementedError(typ) @@ -124,28 +120,20 @@ class SameTypeConstraint(ConstraintBase): if not known_types: return RequireTypeSubstitutes() - new_constraint_list: List[ConstraintBase] = [] - first_type = known_types[0] - for typ in known_types[1:]: - if typ != first_type: - return Error(f'{typ:s} must be {first_type:s} instead', comment=self.comment) - - if new_constraint_list: - # If this happens, make CheckResult a class that can have both - assert not placeholders, 'Cannot (yet) return both new placeholders and new constraints' - - return new_constraint_list + for ktyp in known_types[1:]: + if ktyp != first_type: + return Error(f'{ktyp:s} must be {first_type:s} instead', comment=self.comment) if not placeholders: return None - for typ in placeholders: - typ.resolve_as = first_type + for phft in phft_list: + phft.resolve_as = first_type return { typ: first_type - for typ in placeholders + for typ in phft_list } def human_readable(self) -> HumanReadableRet: @@ -163,7 +151,7 @@ class SameTypeConstraint(ConstraintBase): return f'SameTypeConstraint({args}, comment={repr(self.comment)})' class TupleMatchConstraint(ConstraintBase): - def __init__(self, exp_type: types.Type3OrPlaceholder, args: List[types.Type3OrPlaceholder], comment: str): + def __init__(self, exp_type: placeholders.Type3OrPlaceholder, args: List[placeholders.Type3OrPlaceholder], comment: str): super().__init__(comment=comment) self.exp_type = exp_type @@ -171,15 +159,15 @@ class TupleMatchConstraint(ConstraintBase): def check(self) -> CheckResult: exp_type = self.exp_type - if isinstance(exp_type, types.PlaceholderForType): + 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.PrimitiveType3) + assert isinstance(exp_type, types.Type3) - sa_args = types.static_array.did_construct(exp_type) + sa_args = prelude.static_array.did_construct(exp_type) if sa_args is not None: sa_type, sa_len = sa_args @@ -191,7 +179,7 @@ class TupleMatchConstraint(ConstraintBase): for arg in self.args ] - tp_args = types.tuple_.did_construct(exp_type) + tp_args = prelude.tuple_.did_construct(exp_type) if tp_args is not None: if len(tp_args) != len(self.args): return Error('Mismatch between applied types argument count', comment=self.comment) @@ -209,10 +197,10 @@ class CastableConstraint(ConstraintBase): """ __slots__ = ('from_type3', 'to_type3', ) - from_type3: types.Type3OrPlaceholder - to_type3: types.Type3OrPlaceholder + from_type3: placeholders.Type3OrPlaceholder + to_type3: placeholders.Type3OrPlaceholder - def __init__(self, from_type3: types.Type3OrPlaceholder, to_type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None: + 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 @@ -220,17 +208,17 @@ class CastableConstraint(ConstraintBase): def check(self) -> CheckResult: ftyp = self.from_type3 - if isinstance(ftyp, types.PlaceholderForType) and ftyp.resolve_as is not None: + if isinstance(ftyp, placeholders.PlaceholderForType) and ftyp.resolve_as is not None: ftyp = ftyp.resolve_as ttyp = self.to_type3 - if isinstance(ttyp, types.PlaceholderForType) and ttyp.resolve_as is not None: + if isinstance(ttyp, placeholders.PlaceholderForType) and ttyp.resolve_as is not None: ttyp = ttyp.resolve_as - if isinstance(ftyp, types.PlaceholderForType) or isinstance(ttyp, types.PlaceholderForType): + if isinstance(ftyp, placeholders.PlaceholderForType) or isinstance(ttyp, placeholders.PlaceholderForType): return RequireTypeSubstitutes() - if ftyp is types.u8 and ttyp is types.u32: + if ftyp is prelude.u8 and ttyp is prelude.u32: return None return Error(f'Cannot cast {ftyp.name} to {ttyp.name}') @@ -254,13 +242,13 @@ class MustImplementTypeClassConstraint(ConstraintBase): __slots__ = ('type_class3', 'type3', ) type_class3: Union[str, typeclasses.Type3Class] - type3: types.Type3OrPlaceholder + type3: placeholders.Type3OrPlaceholder DATA = { 'bytes': {'Foldable', 'Sized'}, } - def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None: + def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None: super().__init__(comment=comment) self.type_class3 = type_class3 @@ -268,10 +256,10 @@ class MustImplementTypeClassConstraint(ConstraintBase): def check(self) -> CheckResult: typ = self.type3 - if isinstance(typ, types.PlaceholderForType) and typ.resolve_as is not None: + if isinstance(typ, placeholders.PlaceholderForType) and typ.resolve_as is not None: typ = typ.resolve_as - if isinstance(typ, types.PlaceholderForType): + if isinstance(typ, placeholders.PlaceholderForType): return RequireTypeSubstitutes() if isinstance(self.type_class3, typeclasses.Type3Class): @@ -301,12 +289,12 @@ class LiteralFitsConstraint(ConstraintBase): """ __slots__ = ('type3', 'literal', ) - type3: types.Type3OrPlaceholder + type3: placeholders.Type3OrPlaceholder literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct] def __init__( self, - type3: types.Type3OrPlaceholder, + type3: placeholders.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct], comment: Optional[str] = None, ) -> None: @@ -330,7 +318,7 @@ class LiteralFitsConstraint(ConstraintBase): 'f64': None, } - if isinstance(self.type3, types.PlaceholderForType): + if isinstance(self.type3, placeholders.PlaceholderForType): if self.type3.resolve_as is None: return RequireTypeSubstitutes() @@ -359,7 +347,7 @@ class LiteralFitsConstraint(ConstraintBase): return Error('Must be real', comment=self.comment) # FIXME: Add line information - if self.type3 is types.bytes_: + if self.type3 is prelude.bytes_: if isinstance(self.literal.value, bytes): return None @@ -367,9 +355,9 @@ class LiteralFitsConstraint(ConstraintBase): res: NewConstraintList - assert isinstance(self.type3, types.PrimitiveType3) + assert isinstance(self.type3, types.Type3) - tp_args = types.tuple_.did_construct(self.type3) + tp_args = prelude.tuple_.did_construct(self.type3) if tp_args is not None: if not isinstance(self.literal, ourlang.ConstantTuple): return Error('Must be tuple', comment=self.comment) @@ -390,7 +378,7 @@ class LiteralFitsConstraint(ConstraintBase): return res - sa_args = types.static_array.did_construct(self.type3) + 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) @@ -413,7 +401,8 @@ class LiteralFitsConstraint(ConstraintBase): return res - if isinstance(self.type3, types.StructType3): + 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') @@ -421,18 +410,18 @@ class LiteralFitsConstraint(ConstraintBase): return Error('Struct mismatch') - if len(self.type3.members) != len(self.literal.value): + 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(self.type3.members.values(), self.literal.value) + for x, y in zip(st_args.values(), self.literal.value) ) res.extend( SameTypeConstraint(x_t, y.type3, comment=f'{self.literal.struct_name}.{x_n}') - for (x_n, x_t, ), y in zip(self.type3.members.items(), self.literal.value) + for (x_n, x_t, ), y in zip(st_args.items(), self.literal.value) ) return res @@ -457,12 +446,12 @@ class CanBeSubscriptedConstraint(ConstraintBase): """ __slots__ = ('ret_type3', 'type3', 'index', 'index_type3', ) - ret_type3: types.Type3OrPlaceholder - type3: types.Type3OrPlaceholder + ret_type3: placeholders.Type3OrPlaceholder + type3: placeholders.Type3OrPlaceholder index: ourlang.Expression - index_type3: types.Type3OrPlaceholder + index_type3: placeholders.Type3OrPlaceholder - def __init__(self, ret_type3: types.Type3OrPlaceholder, type3: types.Type3OrPlaceholder, index: ourlang.Expression, comment: Optional[str] = None) -> None: + def __init__(self, ret_type3: placeholders.Type3OrPlaceholder, type3: placeholders.Type3OrPlaceholder, index: ourlang.Expression, comment: Optional[str] = None) -> None: super().__init__(comment=comment) self.ret_type3 = ret_type3 @@ -472,20 +461,20 @@ class CanBeSubscriptedConstraint(ConstraintBase): def check(self) -> CheckResult: exp_type = self.type3 - if isinstance(exp_type, types.PlaceholderForType): + 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.PrimitiveType3) + assert isinstance(exp_type, types.Type3) - sa_args = types.static_array.did_construct(exp_type) + 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(types.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), + 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'), ] @@ -500,7 +489,7 @@ class CanBeSubscriptedConstraint(ConstraintBase): # 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 = types.tuple_.did_construct(exp_type) + 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') @@ -512,14 +501,14 @@ class CanBeSubscriptedConstraint(ConstraintBase): return Error('Tuple index out of range') return [ - SameTypeConstraint(types.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'), + 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 types.bytes_: + if exp_type is prelude.bytes_: return [ - SameTypeConstraint(types.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'), - SameTypeConstraint(types.u8, self.ret_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'), ] return Error(f'{exp_type.name} cannot be subscripted') diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index c1a1f39..7fc5250 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -5,7 +5,8 @@ The constraints solver can then try to resolve all constraints. """ from typing import Generator, List -from .. import ourlang +from .. import ourlang, prelude +from . import placeholders as placeholders from . import typeclasses as type3typeclasses from . import types as type3types from .constraints import ( @@ -50,7 +51,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: if 'len' == inp.operator: yield from expression(ctx, inp.right) yield MustImplementTypeClassConstraint('Sized', inp.right.type3) - yield SameTypeConstraint(type3types.u32, inp.type3, comment='len :: Sized a => a -> u32') + yield SameTypeConstraint(prelude.u32, inp.type3, comment='len :: Sized a => a -> u32') return if 'cast' == inp.operator: @@ -62,7 +63,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: if isinstance(inp, ourlang.BinaryOp): type_var_map = { - x: type3types.PlaceholderForType([]) + x: placeholders.PlaceholderForType([]) for x in inp.operator.signature if isinstance(x, type3typeclasses.TypeVariable) } @@ -83,12 +84,11 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3) continue - if isinstance(sig_part, type3typeclasses.TypeReference): - # On key error: We probably have to a lot of work to do refactoring - # the type lookups - exp_type = type3types.LOOKUP_TABLE[sig_part.name] - yield SameTypeConstraint(exp_type, arg_expr.type3) + if isinstance(sig_part, type3types.Type3): + yield SameTypeConstraint(sig_part, arg_expr.type3) continue + + raise NotImplementedError(sig_part) return if isinstance(inp, ourlang.FunctionCall): @@ -96,7 +96,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: # FIXME: Duplicate code with BinaryOp type_var_map = { - x: type3types.PlaceholderForType([]) + x: placeholders.PlaceholderForType([]) for x in inp.function.signature if isinstance(x, type3typeclasses.TypeVariable) } @@ -117,12 +117,11 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3) continue - if isinstance(sig_part, type3typeclasses.TypeReference): - # On key error: We probably have to a lot of work to do refactoring - # the type lookups - exp_type = type3types.LOOKUP_TABLE[sig_part.name] - yield SameTypeConstraint(exp_type, arg_expr.type3) + if isinstance(sig_part, type3types.Type3): + yield SameTypeConstraint(sig_part, arg_expr.type3) continue + + raise NotImplementedError(sig_part) return yield SameTypeConstraint(inp.function.returns_type3, inp.type3, @@ -159,8 +158,12 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: return if isinstance(inp, ourlang.AccessStructMember): + 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? + yield from expression(ctx, inp.varref) - yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3, + yield SameTypeConstraint(st_args[inp.member], inp.type3, 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 @@ -185,7 +188,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator: yield from expression(ctx, inp.test) - yield SameTypeConstraint(inp.test.type3, type3types.bool_, + yield SameTypeConstraint(inp.test.type3, prelude.bool_, comment='Must pass a boolean expression to if') for stmt in inp.statements: diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index 0e0fda8..2bf05c7 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -12,14 +12,11 @@ from .constraints import ( SubstitutionMap, ) from .constraintsgenerator import phasm_type3_generate_constraints -from .types import ( - IntType3, +from .placeholders import ( PlaceholderForType, - PrimitiveType3, - StructType3, - Type3, Type3OrPlaceholder, ) +from .types import Type3 MAX_RESTACK_COUNT = 100 @@ -143,7 +140,7 @@ def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintB print('- ' + txt.format(**act_fmt)) def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[int, str]) -> str: - if isinstance(inp, (PrimitiveType3, StructType3, IntType3, )): + if isinstance(inp, Type3): return inp.name if isinstance(inp, PlaceholderForType): diff --git a/phasm/type3/placeholders.py b/phasm/type3/placeholders.py new file mode 100644 index 0000000..6821eac --- /dev/null +++ b/phasm/type3/placeholders.py @@ -0,0 +1,67 @@ +""" +Contains the placeholder for types for use in Phasm. + +These are temporary while the compiler is calculating all the types and validating them. +""" +from typing import Any, Iterable, List, Optional, Protocol, Union + +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): + """ + A protocol for classes that should be updated on substitution + """ + + type3: 'Type3OrPlaceholder' + """ + The type to update + """ + +class PlaceholderForType: + """ + A placeholder type, for when we don't know the final type yet + """ + __slots__ = ('update_on_substitution', 'resolve_as', ) + + update_on_substitution: List[ExpressionProtocol] + resolve_as: Optional[Type3] + + def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None: + self.update_on_substitution = [*update_on_substitution] + self.resolve_as = None + + def __repr__(self) -> str: + uos = ', '.join(repr(x) for x in self.update_on_substitution) + + return f'PlaceholderForType({id(self)}, [{uos}])' + + def __str__(self) -> str: + return f'PhFT_{id(self)}' + + def __format__(self, format_spec: str) -> str: + if format_spec != 's': + raise TypeError('unsupported format string passed to Type3.__format__') + + return str(self) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, Type3): + return False + + if not isinstance(other, PlaceholderForType): + raise NotImplementedError + + return self is other + + def __ne__(self, other: Any) -> bool: + return not self.__eq__(other) + + def __hash__(self) -> int: + return 0 # Valid but performs badly + + def __bool__(self) -> bool: + raise NotImplementedError + +Type3OrPlaceholder = Union[Type3, PlaceholderForType] diff --git a/phasm/type3/typeclasses.py b/phasm/type3/typeclasses.py index 6841c73..01f3d02 100644 --- a/phasm/type3/typeclasses.py +++ b/phasm/type3/typeclasses.py @@ -1,5 +1,7 @@ from typing import Any, Dict, Iterable, List, Mapping, Optional, Union +from .types import Type3, TypeConstructor, TypeConstructor_Struct + class TypeVariable: __slots__ = ('letter', ) @@ -48,15 +50,12 @@ class Type3ClassMethod: type3_class: 'Type3Class' name: str - signature: List[Union[TypeReference, TypeVariable]] + signature: List[Union[Type3, TypeVariable]] - def __init__(self, type3_class: 'Type3Class', name: str, signature: str) -> None: + def __init__(self, type3_class: 'Type3Class', name: str, signature: Iterable[Union[Type3, TypeVariable]]) -> None: self.type3_class = type3_class self.name = name - self.signature = [ - TypeVariable(x) if len(x) == 1 else TypeReference(x) - for x in signature.split(' -> ') - ] + self.signature = list(signature) def __repr__(self) -> str: return f'Type3ClassMethod({repr(self.type3_class)}, {repr(self.name)}, {repr(self.signature)})' @@ -73,13 +72,13 @@ class Type3Class: def __init__( self, name: str, - args: Iterable[str], - methods: Mapping[str, str], - operators: Mapping[str, str], + args: Iterable[TypeVariable], + methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]], + operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]], inherited_classes: Optional[List['Type3Class']] = None, ) -> None: self.name = name - self.args = [TypeVariable(x) for x in args] + self.args = list(args) self.methods = { k: Type3ClassMethod(self, k, v) for k, v in methods.items() @@ -93,66 +92,8 @@ class Type3Class: def __repr__(self) -> str: return self.name -InternalPassAsPointer = Type3Class('InternalPassAsPointer', ['a'], methods={}, operators={}) - -Eq = Type3Class('Eq', ['a'], methods={}, operators={ - '==': 'a -> a -> bool', - '!=': 'a -> a -> bool', - # FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization? -}) - -Ord = Type3Class('Ord', ['a'], methods={ - 'min': 'a -> a -> a', - 'max': 'a -> a -> a', -}, operators={ - '<': 'a -> a -> bool', - '<=': 'a -> a -> bool', - '>': 'a -> a -> bool', - '>=': 'a -> a -> bool', -}, inherited_classes=[Eq]) - -Bits = Type3Class('Bits', ['a'], methods={ - 'shl': 'a -> u32 -> a', # Logical shift left - 'shr': 'a -> u32 -> a', # Logical shift right - 'rotl': 'a -> u32 -> a', # Rotate bits left - 'rotr': 'a -> u32 -> a', # Rotate bits right - # FIXME: Do we want to expose clz, ctz, popcnt? -}, operators={ - '&': 'a -> a -> a', # Bit-wise and - '|': 'a -> a -> a', # Bit-wise or - '^': 'a -> a -> a', # Bit-wise xor -}) - -NatNum = Type3Class('NatNum', ['a'], methods={}, operators={ - '+': 'a -> a -> a', - '-': 'a -> a -> a', - '*': 'a -> a -> a', - '<<': 'a -> u32 -> a', # Arithmic shift left - '>>': 'a -> u32 -> a', # Arithmic shift right -}) - -IntNum = Type3Class('IntNum', ['a'], methods={ - 'abs': 'a -> a', - 'neg': 'a -> a', -}, operators={}, inherited_classes=[NatNum]) - -Integral = Type3Class('Eq', ['a'], methods={ -}, operators={ - '//': 'a -> a -> a', - '%': 'a -> a -> a', -}, inherited_classes=[NatNum]) - -Fractional = Type3Class('Fractional', ['a'], methods={ - 'ceil': 'a -> a', - 'floor': 'a -> a', - 'trunc': 'a -> a', - 'nearest': 'a -> a', -}, operators={ - '/': 'a -> a -> a', -}, inherited_classes=[NatNum]) - -Floating = Type3Class('Floating', ['a'], methods={ - 'sqrt': 'a -> a', -}, operators={}, inherited_classes=[Fractional]) - -# FIXME: Do we want to expose copysign? +def instance_type_class(cls: Type3Class, typ: Type3 | TypeConstructor[Any] | TypeConstructor_Struct) -> None: + if isinstance(typ, Type3): + typ.classes.add(cls) + else: + typ.type_classes.add(cls) diff --git a/phasm/type3/types.py b/phasm/type3/types.py index e14778c..47ed034 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -1,51 +1,29 @@ """ -Contains the final types for use in Phasm - -These are actual, instantiated types; not the abstract types that the -constraint generator works with. +Contains the final types for use in Phasm, as well as construtors. """ from typing import ( + TYPE_CHECKING, Any, - Dict, Generic, Iterable, - List, - Optional, - Protocol, Set, Tuple, TypeVar, - Union, ) -from .typeclasses import ( - Bits, - Eq, - Floating, - Fractional, - Integral, - InternalPassAsPointer, - IntNum, - NatNum, - Ord, - Type3Class, -) +if TYPE_CHECKING: + from .typeclasses import Type3Class -TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method' -class ExpressionProtocol(Protocol): - """ - A protocol for classes that should be updated on substitution - """ +class KindArgument: + pass - type3: 'Type3OrPlaceholder' - """ - The type to update - """ - -class Type3: +class Type3(KindArgument): """ Base class for the type3 types + + (Having a separate name makes it easier to distinguish from + Python's Type) """ __slots__ = ('name', 'classes', ) @@ -54,12 +32,12 @@ class Type3: The name of the string, as parsed and outputted by codestyle. """ - classes: Set[Type3Class] + classes: Set['Type3Class'] """ The type classes that this type implements """ - def __init__(self, name: str, classes: Iterable[Type3Class]) -> None: + def __init__(self, name: str, classes: Iterable['Type3Class']) -> None: self.name = name self.classes = set(classes) @@ -84,9 +62,6 @@ class Type3: return str(self) def __eq__(self, other: Any) -> bool: - if isinstance(other, PlaceholderForType): - return False - if not isinstance(other, Type3): raise NotImplementedError @@ -101,19 +76,15 @@ class Type3: def __bool__(self) -> bool: raise NotImplementedError -class PrimitiveType3(Type3): +class IntType3(KindArgument): """ - Intermediate class to tell primitive types from others - """ - - __slots__ = () - -class IntType3(Type3): - """ - Sometimes you can have an int as type, e.g. when using static arrays + Sometimes you can have an int on the type level, e.g. when using static arrays This is not the same as an int on the language level. - [1.0, 1.2] :: Float[2] :: * -> Int -> * + [1.0, 1.2] :: f32[2] :: * -> Int -> * + + That is to say, you can create a static array of size two with each element + a f32 using f32[2]. """ __slots__ = ('value', ) @@ -121,15 +92,13 @@ class IntType3(Type3): value: int def __init__(self, value: int) -> None: - super().__init__(str(value), []) - self.value = value def __eq__(self, other: Any) -> bool: if isinstance(other, IntType3): return self.value == other.value - if isinstance(other, Type3): + if isinstance(other, KindArgument): return False raise NotImplementedError @@ -137,53 +106,6 @@ class IntType3(Type3): def __hash__(self) -> int: return hash(self.value) -class PlaceholderForType: - """ - A placeholder type, for when we don't know the final type yet - """ - __slots__ = ('update_on_substitution', 'resolve_as', ) - - update_on_substitution: List[ExpressionProtocol] - resolve_as: Optional[Type3] - - def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None: - self.update_on_substitution = [*update_on_substitution] - self.resolve_as = None - - def __repr__(self) -> str: - uos = ', '.join(repr(x) for x in self.update_on_substitution) - - return f'PlaceholderForType({id(self)}, [{uos}])' - - def __str__(self) -> str: - return f'PhFT_{id(self)}' - - def __format__(self, format_spec: str) -> str: - if format_spec != 's': - raise TypeError('unsupported format string passed to Type3.__format__') - - return str(self) - - def __eq__(self, other: Any) -> bool: - if isinstance(other, Type3): - return False - - if not isinstance(other, PlaceholderForType): - raise NotImplementedError - - return self is other - - def __ne__(self, other: Any) -> bool: - return not self.__eq__(other) - - def __hash__(self) -> int: - return 0 # Valid but performs badly - - def __bool__(self) -> bool: - raise NotImplementedError - -Type3OrPlaceholder = Union[Type3, PlaceholderForType] - T = TypeVar('T') class TypeConstructor(Generic[T]): @@ -197,28 +119,28 @@ class TypeConstructor(Generic[T]): The name of the type constructor """ - classes: Set[Type3Class] + classes: Set['Type3Class'] """ The type classes that this constructor implements """ - type_classes: Set[Type3Class] + type_classes: Set['Type3Class'] """ The type classes that the constructed types implement """ - _cache: dict[T, PrimitiveType3] + _cache: dict[T, Type3] """ When constructing a type with the same arguments, it should produce the exact same result. """ - _reverse_cache: dict[PrimitiveType3, T] + _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: + 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) @@ -227,186 +149,123 @@ class TypeConstructor(Generic[T]): self._reverse_cache = {} def make_name(self, key: T) -> str: + """ + Renders the type's name based on the given arguments + """ raise NotImplementedError - def did_construct(self, typ: PrimitiveType3) -> T | None: + def did_construct(self, typ: Type3) -> T | None: + """ + Was the given type constructed by this constructor? + + If so, which arguments where used? + """ return self._reverse_cache.get(typ) - def construct(self, key: T) -> PrimitiveType3: + def construct(self, key: T) -> Type3: + """ + Constructs the type by applying the given arguments to this + constructor. + """ result = self._cache.get(key, None) if result is None: - self._cache[key] = result = PrimitiveType3(self.make_name(key), self.type_classes) + self._cache[key] = result = Type3(self.make_name(key), self.type_classes) self._reverse_cache[result] = key return result -class TypeConstructor_Type(TypeConstructor[PrimitiveType3]): +class TypeConstructor_Type(TypeConstructor[Type3]): """ Base class type constructors of kind: * -> * """ __slots__ = () - def __call__(self, arg: PrimitiveType3) -> PrimitiveType3: + def __call__(self, arg: Type3) -> Type3: raise NotImplementedError -class TypeConstructor_TypeInt(TypeConstructor[Tuple[PrimitiveType3, IntType3]]): +class TypeConstructor_TypeInt(TypeConstructor[Tuple[Type3, IntType3]]): """ Base class type constructors of kind: * -> Int -> * + + Notably, static array. """ __slots__ = () - def make_name(self, key: Tuple[PrimitiveType3, IntType3]) -> str: + def make_name(self, key: Tuple[Type3, IntType3]) -> str: return f'{self.name} {key[0].name} {key[1].value}' - def __call__(self, arg0: PrimitiveType3, arg1: IntType3) -> PrimitiveType3: + def __call__(self, arg0: Type3, arg1: IntType3) -> Type3: return self.construct((arg0, arg1)) -class TypeConstructor_TypeStar(TypeConstructor[Tuple[PrimitiveType3, ...]]): +class TypeConstructor_TypeStar(TypeConstructor[Tuple[Type3, ...]]): """ Base class type constructors of variadic kind + + Notably, tuple. """ - def __call__(self, *args: PrimitiveType3) -> PrimitiveType3: - key: Tuple[PrimitiveType3, ...] = tuple(args) + def __call__(self, *args: Type3) -> Type3: + key: Tuple[Type3, ...] = tuple(args) return self.construct(key) class TypeConstructor_StaticArray(TypeConstructor_TypeInt): - def make_name(self, key: Tuple[PrimitiveType3, IntType3]) -> str: + def make_name(self, key: Tuple[Type3, IntType3]) -> str: return f'{key[0].name}[{key[1].value}]' class TypeConstructor_Tuple(TypeConstructor_TypeStar): - def make_name(self, key: Tuple[PrimitiveType3, ...]) -> str: + def make_name(self, key: Tuple[Type3, ...]) -> str: return '(' + ', '.join(x.name for x in key) + ', )' -class StructType3(PrimitiveType3): +class TypeConstructor_Struct: """ - A Type3 struct with named members + Base class for type construtors """ - __slots__ = ('name', 'members', ) + __slots__ = ('name', 'classes', 'type_classes', '_cache', '_reverse_cache') name: str """ - The structs fully qualified name + The name of the type constructor """ - members: Dict[str, Type3] + classes: Set['Type3Class'] """ - The struct's field definitions + The type classes that this constructor implements """ - def __init__(self, name: str, members: Dict[str, Type3]) -> None: - super().__init__(name, []) + 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.members = dict(members) + self.classes = set(classes) + self.type_classes = set(type_classes) - def __repr__(self) -> str: - return f'StructType3(repr({self.name}), repr({self.members}))' + self._cache = {} + self._reverse_cache = {} -none = PrimitiveType3('none', []) -""" -The none type, for when functions simply don't return anything. e.g., IO(). -""" + def did_construct(self, typ: Type3) -> dict[str, Type3] | None: + """ + Was the given type constructed by this constructor? -bool_ = PrimitiveType3('bool', []) -""" -The bool type, either True or False + If so, which arguments where used? + """ + return self._reverse_cache.get(typ) -Suffixes with an underscores, as it's a Python builtin -""" + 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 -u8 = PrimitiveType3('u8', [Bits, Eq, Ord]) -""" -The unsigned 8-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^8. -""" - -u32 = PrimitiveType3('u32', [Bits, Eq, Integral, NatNum, Ord]) -""" -The unsigned 32-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^32. -""" - -u64 = PrimitiveType3('u64', [Bits, Eq, Integral, NatNum, Ord]) -""" -The unsigned 64-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^64. -""" - -i8 = PrimitiveType3('i8', [Eq, Ord]) -""" -The signed 8-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^8, but -with the middel point being 0. -""" - -i32 = PrimitiveType3('i32', [Eq, Integral, IntNum, NatNum, Ord]) -""" -The unsigned 32-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^32, but -with the middel point being 0. -""" - -i64 = PrimitiveType3('i64', [Eq, Integral, IntNum, NatNum, Ord]) -""" -The unsigned 64-bit integer type. - -Operations on variables employ modular arithmetic, with modulus 2^64, but -with the middel point being 0. -""" - -f32 = PrimitiveType3('f32', [Eq, Floating, Fractional, IntNum, NatNum, Ord]) -""" -A 32-bits IEEE 754 float, of 32 bits width. -""" - -f64 = PrimitiveType3('f64', [Eq, Floating, Fractional, IntNum, NatNum, Ord]) -""" -A 32-bits IEEE 754 float, of 64 bits width. -""" - -bytes_ = PrimitiveType3('bytes', []) -""" -This is a runtime-determined length piece of memory that can be indexed at runtime. -""" - -static_array = TypeConstructor_StaticArray('static_array', [], [ - Eq, - InternalPassAsPointer, -]) -""" -A type constructor. - -Any static array is a fixed length piece of memory that can be indexed at runtime. - -It should be applied with one argument. It has a runtime-dynamic length -of the same type repeated. -""" - -tuple_ = TypeConstructor_Tuple('tuple', [], [ - InternalPassAsPointer, -]) -""" -This is a fixed length piece of memory. - -It should be applied with zero or more arguments. It has a compile time -determined length, and each argument can be different. -""" - -LOOKUP_TABLE: Dict[str, PrimitiveType3] = { - 'none': none, - 'bool': bool_, - 'u8': u8, - 'u32': u32, - 'u64': u64, - 'i8': i8, - 'i32': i32, - 'i64': i64, - 'f32': f32, - 'f64': f64, - 'bytes': bytes_, -} + return result diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index d413b87..f682d0b 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -2,10 +2,10 @@ import struct import sys from typing import Any, Generator, Iterable, List, TextIO, Union -from phasm import compiler +from phasm import compiler, prelude from phasm.codestyle import phasm_render from phasm.runtime import calculate_alloc_size -from phasm.type3 import typeclasses as type3classes +from phasm.type3 import placeholders as type3placeholders from phasm.type3 import types as type3types from . import runners @@ -65,43 +65,44 @@ class Suite: runner.interpreter_dump_memory(sys.stderr) for arg, arg_typ in zip(args, func_args): - assert not isinstance(arg_typ, type3types.PlaceholderForType), \ + assert not isinstance(arg_typ, type3placeholders.PlaceholderForType), \ 'Cannot call polymorphic function from outside' - if arg_typ in (type3types.u8, type3types.u32, type3types.u64, ): + if arg_typ in (prelude.u8, prelude.u32, prelude.u64, ): assert isinstance(arg, int) wasm_args.append(arg) continue - if arg_typ in (type3types.i8, type3types.i32, type3types.i64, ): + if arg_typ in (prelude.i8, prelude.i32, prelude.i64, ): assert isinstance(arg, int) wasm_args.append(arg) continue - if arg_typ in (type3types.f32, type3types.f64, ): + if arg_typ in (prelude.f32, prelude.f64, ): assert isinstance(arg, float) wasm_args.append(arg) continue - if arg_typ is type3types.bytes_: + if arg_typ is prelude.bytes_: adr = _allocate_memory_stored_value(runner, arg_typ, arg) wasm_args.append(adr) continue - assert isinstance(arg_typ, type3types.PrimitiveType3) - sa_args = type3types.static_array.did_construct(arg_typ) + 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 = type3types.tuple_.did_construct(arg_typ) + 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 - if isinstance(arg_typ, type3types.StructType3): + 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 @@ -145,41 +146,39 @@ def _write_memory_stored_value( val_typ: type3types.Type3, val: Any, ) -> int: - if val_typ is type3types.bytes_: + if val_typ is prelude.bytes_: adr2 = _allocate_memory_stored_value(runner, val_typ, val) runner.interpreter_write_memory(adr, compiler.module_data_u32(adr2)) return 4 - if isinstance(val_typ, type3types.StructType3): + 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 - if isinstance(val_typ, type3types.PrimitiveType3): - sa_args = type3types.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 + 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 = type3types.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 + 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) - runner.interpreter_write_memory(adr, to_write) - return len(to_write) - - raise NotImplementedError(val_typ, val) + to_write = WRITE_LOOKUP_MAP[val_typ.name](val) + runner.interpreter_write_memory(adr, to_write) + return len(to_write) def _allocate_memory_stored_value( runner: runners.RunnerBase, val_typ: type3types.Type3, val: Any ) -> int: - if val_typ is type3types.bytes_: + if val_typ is prelude.bytes_: assert isinstance(val, bytes) adr = runner.call('stdlib.types.__alloc_bytes__', len(val)) @@ -189,8 +188,7 @@ def _allocate_memory_stored_value( runner.interpreter_write_memory(adr + 4, val) return adr - assert isinstance(val_typ, type3types.PrimitiveType3) - sa_args = type3types.static_array.did_construct(val_typ) + sa_args = prelude.static_array.did_construct(val_typ) if sa_args is not None: assert isinstance(val, tuple) @@ -211,7 +209,7 @@ def _allocate_memory_stored_value( val_el_typ: type3types.Type3 - tp_args = type3types.tuple_.did_construct(val_typ) + tp_args = prelude.tuple_.did_construct(val_typ) if tp_args is not None: assert isinstance(val, tuple) @@ -224,12 +222,13 @@ def _allocate_memory_stored_value( offset = adr for val_el_val, val_el_typ in zip(val, tp_args): - assert not isinstance(val_el_typ, type3types.PlaceholderForType) + assert not isinstance(val_el_typ, type3placeholders.PlaceholderForType) offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) return adr - if isinstance(val_typ, type3types.StructType3): + st_args = prelude.struct.did_construct(val_typ) + if st_args is not None: assert isinstance(val, dict) alloc_size = calculate_alloc_size(val_typ) @@ -237,11 +236,11 @@ def _allocate_memory_stored_value( assert isinstance(adr, int) sys.stderr.write(f'Allocation 0x{adr:08x} {repr(val)}\n') - assert list(val.keys()) == list(val_typ.members.keys()) + assert list(val.keys()) == list(st_args) offset = adr - for val_el_name, val_el_typ in val_typ.members.items(): - assert not isinstance(val_el_typ, type3types.PlaceholderForType) + 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) @@ -256,18 +255,18 @@ def _load_memory_stored_returned_value( ) -> Any: ret_type3 = runner.phasm_ast.functions[func_name].returns_type3 - if ret_type3 is type3types.none: + if ret_type3 is prelude.none: return None - if ret_type3 is type3types.bool_: + if ret_type3 is prelude.bool_: assert isinstance(wasm_value, int), wasm_value return 0 != wasm_value - if ret_type3 in (type3types.i8, type3types.i32, type3types.i64): + if ret_type3 in (prelude.i8, prelude.i32, prelude.i64): assert isinstance(wasm_value, int), wasm_value return wasm_value - if ret_type3 in (type3types.u8, type3types.u32, type3types.u64): + if ret_type3 in (prelude.u8, prelude.u32, prelude.u64): assert isinstance(wasm_value, int), wasm_value if wasm_value < 0: @@ -284,98 +283,100 @@ def _load_memory_stored_returned_value( return wasm_value - if ret_type3 in (type3types.f32, type3types.f64, ): + if ret_type3 in (prelude.f32, prelude.f64, ): assert isinstance(wasm_value, float), wasm_value return wasm_value - if ret_type3 is type3types.bytes_: + if ret_type3 is prelude.bytes_: assert isinstance(wasm_value, int), wasm_value return _load_bytes_from_address(runner, ret_type3, wasm_value) - assert isinstance(ret_type3, type3types.PrimitiveType3) # Type hint + assert isinstance(ret_type3, type3types.Type3) # Type hint - sa_args = type3types.static_array.did_construct(ret_type3) + sa_args = prelude.static_array.did_construct(ret_type3) if sa_args is not None: assert isinstance(wasm_value, int), wasm_value return _load_static_array_from_address(runner, sa_args[0], sa_args[1], wasm_value) - tp_args = type3types.tuple_.did_construct(ret_type3) + 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) - if isinstance(ret_type3, type3types.StructType3): - return _load_struct_from_address(runner, ret_type3, 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: - if typ is type3types.u8: + if typ is prelude.u8: # See compiler.py, LOAD_STORE_TYPE_MAP and module_data_u8 assert len(inp) == 4 return struct.unpack(' Generator yield all_bytes[offset:offset + size] offset += size -def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.PrimitiveType3, len_typ: type3types.IntType3, adr: int) -> Any: +def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.Type3, len_typ: type3types.IntType3, adr: int) -> Any: sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n') - assert not isinstance(sub_typ, type3types.PlaceholderForType) + assert not isinstance(sub_typ, type3placeholders.PlaceholderForType) assert isinstance(len_typ, type3types.IntType3) sa_len = len_typ.value @@ -411,7 +412,7 @@ def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3ty for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) ) -def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.PrimitiveType3, ...], adr: int) -> Any: +def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3types.Type3, ...], adr: int) -> Any: sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(typ_args)}\n') arg_sizes = [ @@ -426,19 +427,13 @@ def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3ty for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes)) ) -def _load_struct_from_address(runner: runners.RunnerBase, typ: type3types.Type3, adr: int) -> Any: - sys.stderr.write(f'Reading 0x{adr:08x} {typ:s}\n') +def _load_struct_from_address(runner: runners.RunnerBase, st_args: dict[str, type3types.Type3], adr: int) -> Any: + sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n') - assert isinstance(typ, type3types.StructType3) + name_list = list(st_args) - name_list = list(typ.members) - - typ_list = [ - x - for x in typ.members.values() - if not isinstance(x, type3types.PlaceholderForType) - ] - assert len(typ_list) == len(typ.members) + typ_list = list(st_args.values()) + assert len(typ_list) == len(st_args) arg_sizes = [ calculate_alloc_size(x, is_member=True)