From 9f21d0fd1d5855df6497d23bbac21982e14bf14f Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Thu, 24 Nov 2022 15:43:23 +0100 Subject: [PATCH] More work on type3 --- TODO.md | 7 +- phasm/codestyle.py | 38 +++-- phasm/compiler.py | 108 ++++++++------ phasm/ourlang.py | 80 ++++++---- phasm/parser.py | 166 ++++++++++----------- phasm/type3/constraintsgenerator.py | 9 +- phasm/type3/types.py | 25 ++++ tests/integration/test_lang/test_struct.py | 68 ++++----- tests/integration/test_lang/test_tuple.py | 27 ++++ 9 files changed, 308 insertions(+), 220 deletions(-) diff --git a/TODO.md b/TODO.md index 0da4619..d2a31cc 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,12 @@ # TODO -- Implement a trace() builtin for debugging - Implement a proper type matching / checking system + - Implement subscript as an operator + - Implement another operator + - Figure out how to do type classes + - Implement structs again, with the `.foo` notation working + +- Implement a trace() builtin for debugging - Check if we can use DataView in the Javascript examples, e.g. with setUint32 - Storing u8 in memory still claims 32 bits (since that's what you need in local variables). However, using load8_u / loadu_s we can optimize this. - Implement a FizzBuzz example diff --git a/phasm/codestyle.py b/phasm/codestyle.py index bf58a00..8df7e89 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -36,16 +36,15 @@ def type3(inp: Type3OrPlaceholder) -> str: return inp.name -# def struct_definition(inp: typing.TypeStruct) -> str: -# """ -# Render: TypeStruct's definition -# """ -# result = f'class {inp.name}:\n' -# for mem in inp.members: # TODO: Broken after new type system -# raise NotImplementedError('Structs broken after new type system') -# # result += f' {mem.name}: {type_(mem.type)}\n' -# -# return result +def struct_definition(inp: ourlang.StructDefinition) -> str: + """ + Render: TypeStruct's definition + """ + result = f'class {inp.struct_type3.name}:\n' + for mem, typ in inp.struct_type3.members.items(): + result += f' {mem}: {type3(typ)}\n' + + return result def constant_definition(inp: ourlang.ModuleConstantDef) -> str: """ @@ -95,10 +94,10 @@ def expression(inp: ourlang.Expression) -> str: for arg in inp.arguments ) + if isinstance(inp.function, ourlang.StructConstructor): + return f'{inp.function.struct_type3.name}({args})' + # TODO: Broken after new type system - # if isinstance(inp.function, ourlang.StructConstructor): - # return f'{inp.function.struct.name}({args})' - # # if isinstance(inp.function, ourlang.TupleConstructor): # return f'({args}, )' @@ -110,9 +109,8 @@ def expression(inp: ourlang.Expression) -> str: return f'{varref}[{index}]' - # TODO: Broken after new type system - # if isinstance(inp, ourlang.AccessStructMember): - # return f'{expression(inp.varref)}.{inp.member.name}' + if isinstance(inp, ourlang.AccessStructMember): + return f'{expression(inp.varref)}.{inp.member}' if isinstance(inp, ourlang.Fold): fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr' @@ -180,10 +178,10 @@ def module(inp: ourlang.Module) -> str: """ result = '' - # for struct in inp.structs.values(): - # if result: - # result += '\n' - # result += struct_definition(struct) + for struct in inp.struct_definitions.values(): + if result: + result += '\n' + result += struct_definition(struct) for cdef in inp.constant_defs.values(): if result: diff --git a/phasm/compiler.py b/phasm/compiler.py index 5a01c4c..0e83a93 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -1,7 +1,7 @@ """ This module contains the code to convert parsed Ourlang into WebAssembly code """ -from typing import List, Optional +from typing import List, Union import struct @@ -14,6 +14,16 @@ from .stdlib import alloc as stdlib_alloc from .stdlib import types as stdlib_types from .wasmgenerator import Generator as WasmGenerator +LOAD_STORE_TYPE_MAP = { + 'u8': 'i32', # Have to use an u32, since there is no native u8 type + 'i32': 'i32', + 'i64': 'i64', + 'u32': 'i32', + 'u64': 'i64', + 'f32': 'f32', + 'f64': 'f64', +} + def phasm_compile(inp: ourlang.Module) -> wasm.Module: """ Public method for compiling a parsed Phasm module into @@ -53,16 +63,10 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType: if inp is type3types.f64: return wasm.WasmTypeFloat64() - # if tc_prim.primitive is typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY: - # # StaticArray, Tuples and Structs are passed as pointer - # # And pointers are i32 - # return wasm.WasmTypeInt32() - - # TODO: Broken after new type system - # if isinstance(inp, (typing.TypeStruct, typing.TypeTuple, typing.TypeStaticArray, typing.TypeBytes)): - # # Structs and tuples are passed as pointer - # # And pointers are i32 - # return wasm.WasmTypeInt32() + if isinstance(inp, type3types.StructType3): + # Structs and tuples are passed as pointer + # And pointers are i32 + return wasm.WasmTypeInt32() raise NotImplementedError(type3, inp) @@ -349,17 +353,19 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: # wgn.call(stdlib_types.__subscript_bytes__) # return - # if isinstance(inp, ourlang.AccessStructMember): - # mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__) - # if mtyp is None: - # # In the future might extend this by having structs or tuples - # # as members of struct or tuples - # raise NotImplementedError(expression, inp, inp.member) - # - # expression(wgn, inp.varref) - # wgn.add_statement(f'{mtyp}.load', 'offset=' + str(inp.member.offset)) - # return - # + if isinstance(inp, ourlang.AccessStructMember): + mtyp = LOAD_STORE_TYPE_MAP.get(inp.struct_type3.members[inp.member].name) + if mtyp is None: + # In the future might extend this by having structs or tuples + # as members of struct or tuples + raise NotImplementedError(expression, inp, inp.struct_type3) + + expression(wgn, inp.varref) + wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset( + inp.struct_type3, inp.member + ))) + return + # if isinstance(inp, ourlang.AccessTupleMember): # mtyp = LOAD_STORE_TYPE_MAP.get(inp.member.type.__class__) # if mtyp is None: @@ -544,8 +550,8 @@ def function(inp: ourlang.Function) -> wasm.Function: if False: # TODO: isinstance(inp, ourlang.TupleConstructor): pass # _generate_tuple_constructor(wgn, inp) - elif False: # TODO: isinstance(inp, ourlang.StructConstructor): - pass # _generate_struct_constructor(wgn, inp) + elif isinstance(inp, ourlang.StructConstructor): + _generate_struct_constructor(wgn, inp) else: for stat in inp.statements: statement(wgn, stat) @@ -733,26 +739,34 @@ def module(inp: ourlang.Module) -> wasm.Module: # # # Return the allocated address # wgn.local.get(tmp_var) -# -# def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: -# tmp_var = wgn.temp_var_i32('struct_adr') -# -# # Allocated the required amounts of bytes in memory -# wgn.i32.const(inp.struct.alloc_size()) -# wgn.call(stdlib_alloc.__alloc__) -# wgn.local.set(tmp_var) -# -# # Store each member individually -# for member in inp.struct.members: -# mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__) -# if mtyp is None: -# # In the future might extend this by having structs or tuples -# # as members of struct or tuples -# raise NotImplementedError(expression, inp, member) -# -# wgn.local.get(tmp_var) -# wgn.add_statement('local.get', f'${member.name}') -# wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset)) -# -# # Return the allocated address -# wgn.local.get(tmp_var) + +def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None: + tmp_var = wgn.temp_var_i32('struct_adr') + + # Allocated the required amounts of bytes in memory + wgn.i32.const(_calculate_alloc_size(inp.struct_type3)) + wgn.call(stdlib_alloc.__alloc__) + wgn.local.set(tmp_var) + + # Store each member individually + for memname, mtyp3 in inp.struct_type3.members.items(): + mtyp = LOAD_STORE_TYPE_MAP.get(mtyp3.name) + if mtyp is None: + # In the future might extend this by having structs or tuples + # as members of struct or tuples + raise NotImplementedError(expression, inp, mtyp3) + + 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 + ))) + + # Return the allocated address + wgn.local.get(tmp_var) + +def _calculate_alloc_size(type3: Union[type3types.StructType3, type3types.Type3]) -> int: + return 0 # FIXME: Stub + +def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int: + return 0 # FIXME: Stub diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 359d38c..bc7a6dd 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -11,7 +11,7 @@ WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', ) from .type3 import types as type3types -from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType +from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType, StructType3 class Expression: """ @@ -134,6 +134,23 @@ class Subscript(Expression): self.varref = varref self.index = index +class AccessStructMember(Expression): + """ + Access a struct member for reading of writing + """ + __slots__ = ('varref', 'struct_type3', 'member', ) + + varref: VariableReference + struct_type3: StructType3 + member: str + + def __init__(self, varref: VariableReference, struct_type3: StructType3, member: str) -> None: + super().__init__() + + self.varref = varref + self.struct_type3 = struct_type3 + self.member = member + class Fold(Expression): """ A (left or right) fold @@ -240,28 +257,41 @@ class Function: self.returns_str = 'None' self.posonlyargs = [] +class StructDefinition: + """ + The definition for a struct + """ + __slots__ = ('struct_type3', 'lineno', ) + + struct_type3: StructType3 + lineno: int + + def __init__(self, struct_type3: StructType3, lineno: int) -> None: + self.struct_type3 = struct_type3 + self.lineno = lineno + +class StructConstructor(Function): + """ + The constructor method for a struct + + A function will generated to instantiate a struct. The arguments + will be the defaults + """ + __slots__ = ('struct_type3', ) + + struct_type3: StructType3 + + def __init__(self, struct_type3: StructType3) -> None: + super().__init__(f'@{struct_type3.name}@__init___@', -1) + + self.returns_type3 = struct_type3 + + for mem, typ in struct_type3.members.items(): + self.posonlyargs.append(FunctionParam(mem, typ, )) + + self.struct_type3 = struct_type3 + # TODO: Broken after new type system -# class StructConstructor(Function): -# """ -# The constructor method for a struct -# -# A function will generated to instantiate a struct. The arguments -# will be the defaults -# """ -# __slots__ = ('struct', ) -# -# struct: TypeStruct -# -# def __init__(self, struct: TypeStruct) -> None: -# super().__init__(f'@{struct.name}@__init___@', -1) -# -# self.returns = struct -# -# for mem in struct.members: -# self.posonlyargs.append(FunctionParam(mem.name, mem.type, )) -# -# self.struct = struct -# # class TupleConstructor(Function): # """ # The constructor method for a tuple @@ -331,15 +361,15 @@ class Module: """ A module is a file and consists of functions """ - __slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',) + __slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions',) data: ModuleData - # structs: Dict[str, TypeStruct] + struct_definitions: Dict[str, StructDefinition] constant_defs: Dict[str, ModuleConstantDef] functions: Dict[str, Function] def __init__(self) -> None: self.data = ModuleData() - # self.structs = {} + self.struct_definitions = {} self.constant_defs = {} self.functions = {} diff --git a/phasm/parser.py b/phasm/parser.py index 3f800c9..0ab3537 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -18,8 +18,9 @@ from .ourlang import ( BinaryOp, ConstantPrimitive, ConstantTuple, - FunctionCall, Subscript, - # StructConstructor, TupleConstructor, + FunctionCall, AccessStructMember, Subscript, + StructDefinition, StructConstructor, + # TupleConstructor, UnaryOp, VariableReference, Fold, @@ -75,16 +76,15 @@ class OurVisitor: module.constant_defs[res.name] = res - # TODO: Broken after type system - # if isinstance(res, TypeStruct): - # if res.name in module.structs: - # raise StaticError( - # f'{res.name} already defined on line {module.structs[res.name].lineno}' - # ) - # - # module.structs[res.name] = res - # constructor = StructConstructor(res) - # module.functions[constructor.name] = constructor + if isinstance(res, StructDefinition): + if res.struct_type3.name in module.struct_definitions: + raise StaticError( + f'{res.struct_type3.name} already defined on line {module.struct_definitions[res.struct_type3.name].lineno}' + ) + + module.struct_definitions[res.struct_type3.name] = res + constructor = StructConstructor(res.struct_type3) + module.functions[constructor.name] = constructor if isinstance(res, Function): if res.name in module.functions: @@ -101,12 +101,12 @@ class OurVisitor: return module - def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, ModuleConstantDef]: # TypeStruct + def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, StructDefinition, ModuleConstantDef]: if isinstance(node, ast.FunctionDef): return self.pre_visit_Module_FunctionDef(module, node) - # if isinstance(node, ast.ClassDef): - # return self.pre_visit_Module_ClassDef(module, node) + if isinstance(node, ast.ClassDef): + return self.pre_visit_Module_ClassDef(module, node) if isinstance(node, ast.AnnAssign): return self.pre_visit_Module_AnnAssign(module, node) @@ -155,36 +155,33 @@ class OurVisitor: return function - def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> None: # TypeStruct: - raise NotImplementedError - # struct = TypeStruct(node.name, node.lineno) - # - # _not_implemented(not node.bases, 'ClassDef.bases') - # _not_implemented(not node.keywords, 'ClassDef.keywords') - # _not_implemented(not node.decorator_list, 'ClassDef.decorator_list') - # - # offset = 0 - # - # for stmt in node.body: - # if not isinstance(stmt, ast.AnnAssign): - # raise NotImplementedError(f'Class with {stmt} nodes') - # - # if not isinstance(stmt.target, ast.Name): - # raise NotImplementedError('Class with default values') - # - # if not stmt.value is None: - # raise NotImplementedError('Class with default values') - # - # if stmt.simple != 1: - # raise NotImplementedError('Class with non-simple arguments') - # - # raise NotImplementedError('TODO: Broken after new type system') - # member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset) - # - # struct.members.append(member) - # offset += member.type.alloc_size() - # - # return struct + def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> StructDefinition: + + _not_implemented(not node.bases, 'ClassDef.bases') + _not_implemented(not node.keywords, 'ClassDef.keywords') + _not_implemented(not node.decorator_list, 'ClassDef.decorator_list') + + members: Dict[str, type3types.Type3] = {} + + for stmt in node.body: + if not isinstance(stmt, ast.AnnAssign): + raise NotImplementedError(f'Class with {stmt} nodes') + + if not isinstance(stmt.target, ast.Name): + raise NotImplementedError('Class with default values') + + if not stmt.value is None: + raise NotImplementedError('Class with default values') + + if stmt.simple != 1: + raise NotImplementedError('Class with non-simple arguments') + + if stmt.target.id in members: + _raise_static_error(stmt, 'Struct members must have unique names') + + members[stmt.target.id] = self.visit_type(module, stmt.annotation) + + return StructDefinition(type3types.StructType3(node.name, members), node.lineno) def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: if not isinstance(node.target, ast.Name): @@ -474,13 +471,14 @@ class OurVisitor: if not isinstance(node.func.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - # if node.func.id in module.structs: - # raise NotImplementedError('TODO: Broken after new type system') - # struct = module.structs[node.func.id] - # struct_constructor = StructConstructor(struct) - # - # func = module.functions[struct_constructor.name] - if node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS: + if node.func.id in module.struct_definitions: + struct_definition = module.struct_definitions[node.func.id] + struct_constructor = StructConstructor(struct_definition.struct_type3) + + # FIXME: Defer struct de-allocation + + func = module.functions[struct_constructor.name] + elif node.func.id in WEBASSEMBLY_BUILTIN_FLOAT_OPS: if 1 != len(node.args): _raise_static_error(node, f'Function {node.func.id} requires 1 arguments but {len(node.args)} are given') @@ -552,33 +550,33 @@ class OurVisitor: return result def visit_Module_FunctionDef_Attribute(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Attribute) -> Expression: - raise NotImplementedError('Broken after new type system') - # del module - # del function - # - # if not isinstance(node.value, ast.Name): - # _raise_static_error(node, 'Must reference a name') - # - # if not isinstance(node.ctx, ast.Load): - # _raise_static_error(node, 'Must be load context') - # - # if not node.value.id in our_locals: - # _raise_static_error(node, f'Undefined variable {node.value.id}') - # - # param = our_locals[node.value.id] - # - # node_typ = param.type - # if not isinstance(node_typ, TypeStruct): - # _raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}') - # - # member = node_typ.get_member(node.attr) - # if member is None: - # _raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}') - # - # return AccessStructMember( - # VariableReference(param), - # member, - # ) + del module + del function + + if not isinstance(node.value, ast.Name): + _raise_static_error(node, 'Must reference a name') + + if not isinstance(node.ctx, ast.Load): + _raise_static_error(node, 'Must be load context') + + if not node.value.id in our_locals: + _raise_static_error(node, f'Undefined variable {node.value.id}') + + param = our_locals[node.value.id] + + node_typ = param.type3 + if not isinstance(node_typ, type3types.StructType3): + _raise_static_error(node, f'Cannot take attribute of non-struct {node.value.id}') + + member = node_typ.members.get(node.attr) + if member is None: + _raise_static_error(node, f'{node_typ.name} has no attribute {node.attr}') + + return AccessStructMember( + VariableReference(param), + node_typ, + node.attr, + ) def visit_Module_FunctionDef_Subscript(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Subscript) -> Expression: if not isinstance(node.value, ast.Name): @@ -687,12 +685,10 @@ class OurVisitor: if node.id in type3types.LOOKUP_TABLE: return type3types.LOOKUP_TABLE[node.id] - raise NotImplementedError('TODO: Broken after type system') - # - # if node.id in module.structs: - # return module.structs[node.id] - # - # _raise_static_error(node, f'Unrecognized type {node.id}') + if node.id in module.struct_definitions: + return module.struct_definitions[node.id].struct_type3 + + _raise_static_error(node, f'Unrecognized type {node.id}') if isinstance(node, ast.Subscript): if not isinstance(node.value, ast.Name): diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index 673cf63..3a06de5 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -13,7 +13,6 @@ from .constraints import ( ConstraintBase, LiteralFitsConstraint, SameTypeConstraint, ) -from . import types def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase]: ctx = Context() @@ -48,9 +47,17 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas return + if isinstance(inp, ourlang.AccessStructMember): + yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3, + f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}') + return + raise NotImplementedError(expression, inp) def function(ctx: Context, inp: ourlang.Function) -> Generator[ConstraintBase, None, None]: + if isinstance(inp, ourlang.StructConstructor): + return + if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn): raise NotImplementedError('Functions with not just a return statement') diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 5430cdf..0486d34 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -139,6 +139,31 @@ class AppliedType3(Type3): def __repr__(self) -> str: return f'AppliedType3({repr(self.base)}, {repr(self.args)})' +class StructType3(Type3): + """ + A Type3 struct with named members + """ + __slots__ = ('name', 'members', ) + + name: str + """ + The structs fully qualified name + """ + + members: Dict[str, Type3] + """ + The struct's field definitions + """ + + def __init__(self, name: str, members: Dict[str, Type3]) -> None: + super().__init__(name) + + self.name = name + self.members = dict(members) + + def __repr__(self) -> str: + return f'StructType3(repr({self.name}), repr({self.members}))' + none = Type3('none') """ The none type, for when functions simply don't return anything. e.g., IO(). diff --git a/tests/integration/test_lang/test_struct.py b/tests/integration/test_lang/test_struct.py index a7218f9..35663c4 100644 --- a/tests/integration/test_lang/test_struct.py +++ b/tests/integration/test_lang/test_struct.py @@ -1,12 +1,36 @@ import pytest from phasm.exceptions import StaticError +from phasm.type3.entry import Type3Exception + from phasm.parser import phasm_parse +from ..constants import ( + ALL_INT_TYPES +) from ..helpers import Suite @pytest.mark.integration_test -@pytest.mark.parametrize('type_', ('i32', 'f64', )) +@pytest.mark.parametrize('type_', ALL_INT_TYPES) +def test_module_constant(type_): + code_py = f""" +class CheckedValue: + value: {type_} + +CONSTANT: CheckedValue = CheckedValue(24) + +@exported +def testEntry() -> {type_}: + return CONSTANT.value +""" + + result = Suite(code_py).run_code() + + assert 24 == result.returned_value + assert TYPE_MAP[type_] == type(result.returned_value) + +@pytest.mark.integration_test +@pytest.mark.parametrize('type_', ALL_INT_TYPES) def test_struct_0(type_): code_py = f""" class CheckedValue: @@ -75,43 +99,5 @@ def testEntry(arg: Struct) -> (i32, i32, ): return arg.param """ - with pytest.raises(StaticError, match=f'Static error on line 6: Expected \\(i32, i32, \\), arg.param is actually {type_}'): - phasm_parse(code_py) - -@pytest.mark.integration_test -@pytest.mark.parametrize('type_', ['i32', 'i64', 'f32', 'f64']) -def test_type_mismatch_tuple_member(type_): - code_py = f""" -def testEntry(arg: ({type_}, )) -> (i32, i32, ): - return arg[0] -""" - - with pytest.raises(StaticError, match=f'Static error on line 3: Expected \\(i32, i32, \\), arg\\[0\\] is actually {type_}'): - phasm_parse(code_py) - -@pytest.mark.integration_test -def test_tuple_constant_too_few_values(): - code_py = """ -CONSTANT: (u32, u8, u8, ) = (24, 57, ) -""" - - with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'): - phasm_parse(code_py) - -@pytest.mark.integration_test -def test_tuple_constant_too_many_values(): - code_py = """ -CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, ) -""" - - with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'): - phasm_parse(code_py) - -@pytest.mark.integration_test -def test_tuple_constant_type_mismatch(): - code_py = """ -CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, ) -""" - - with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'): - phasm_parse(code_py) + with pytest.raises(Type3Exception, match=r'\(i32, i32, \) must be ' + type_ + ' instead'): + Suite(code_py).run_code() diff --git a/tests/integration/test_lang/test_tuple.py b/tests/integration/test_lang/test_tuple.py index d3b06b2..c0d59c9 100644 --- a/tests/integration/test_lang/test_tuple.py +++ b/tests/integration/test_lang/test_tuple.py @@ -82,3 +82,30 @@ def testEntry() -> i32x4: result = Suite(code_py).run_code() assert (1, 2, 3, 0) == result.returned_value + +@pytest.mark.integration_test +def test_tuple_constant_too_few_values(): + code_py = """ +CONSTANT: (u32, u8, u8, ) = (24, 57, ) +""" + + with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'): + Suite(code_py).run_code() + +@pytest.mark.integration_test +def test_tuple_constant_too_many_values(): + code_py = """ +CONSTANT: (u32, u8, u8, ) = (24, 57, 1, 1, ) +""" + + with pytest.raises(StaticError, match='Static error on line 2: Invalid number of tuple values'): + Suite(code_py).run_code() + +@pytest.mark.integration_test +def test_tuple_constant_type_mismatch(): + code_py = """ +CONSTANT: (u32, u8, u8, ) = (24, 4000, 1, ) +""" + + with pytest.raises(StaticError, match='Static error on line 2: Integer value out of range; expected 0..255, actual 4000'): + Suite(code_py).run_code()