From 16ec664cb61a2439cf80314ac0c6df3d217d0c88 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Fri, 10 Nov 2023 15:23:10 +0100 Subject: [PATCH] Feature: Tuples with tuples in constants --- phasm/compiler.py | 32 +++++++++----- phasm/ourlang.py | 54 +++++++++++++---------- phasm/parser.py | 37 ++++++++++------ tests/integration/test_lang/test_tuple.py | 23 ++++++++++ 4 files changed, 97 insertions(+), 49 deletions(-) diff --git a/phasm/compiler.py b/phasm/compiler.py index 127f779..7c8d5c1 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -716,57 +716,65 @@ def module_data(inp: ourlang.ModuleData) -> bytes: for constant in block.data: assert isinstance(constant.type3, type3types.Type3), (id(constant), type3types.TYPE3_ASSERTION_ERROR) + if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block: + # It's stored in a different block + # We only need to store its address + # This happens for example when a tuple refers + # to a bytes constant + assert constant.data_block.address is not None, 'Referred memory not yet stored' + data_list.append(module_data_u32(constant.data_block.address)) + continue + if constant.type3 == type3types.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: + assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_u32(constant.value)) continue if constant.type3 == type3types.u64: + assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_u64(constant.value)) continue if constant.type3 == type3types.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: + assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, int) data_list.append(module_data_i64(constant.value)) continue if constant.type3 == type3types.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: + assert isinstance(constant, ourlang.ConstantPrimitive) assert isinstance(constant.value, float) data_list.append(module_data_f64(constant.value)) continue if constant.type3 == type3types.bytes: assert isinstance(constant, ourlang.ConstantBytes) - - if block is not constant.data_block: - # It's stored in a different block - # We only need to store its address - # This happens for example when a tuple refers - # to a bytes constant - assert constant.data_block.address is not None - data_list.append(module_data_u32(constant.data_block.address)) - else: - data_list.append(module_data_u32(len(constant.value))) - data_list.append(constant.value) + assert isinstance(constant.value, bytes) + data_list.append(module_data_u32(len(constant.value))) + data_list.append(constant.value) continue - raise NotImplementedError(constant, constant.type3, constant.value) + raise NotImplementedError(constant, constant.type3) block_data = b''.join(data_list) diff --git a/phasm/ourlang.py b/phasm/ourlang.py index a301450..2c2cc3d 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -1,7 +1,7 @@ """ Contains the syntax tree for ourlang """ -from typing import Dict, List, Optional, Union +from typing import Dict, Iterable, List, Optional, Union import enum @@ -47,19 +47,31 @@ class ConstantPrimitive(Constant): def __repr__(self) -> str: return f'ConstantPrimitive({repr(self.value)})' -class ConstantBytes(Constant): +class ConstantMemoryStored(Constant): + """ + An constant value expression within a statement + + # FIXME: Rename to literal + """ + __slots__ = ('data_block', ) + + data_block: 'ModuleDataBlock' + + def __init__(self, data_block: 'ModuleDataBlock') -> None: + super().__init__() + self.data_block = data_block + +class ConstantBytes(ConstantMemoryStored): """ A bytes constant value expression within a statement """ - __slots__ = ('value', 'data_block', ) + __slots__ = ('value', ) value: bytes - data_block: 'ModuleDataBlock' def __init__(self, value: bytes, data_block: 'ModuleDataBlock') -> None: - super().__init__() + super().__init__(data_block) self.value = value - self.data_block = data_block def __repr__(self) -> str: # Do not repr the whole ModuleDataBlock @@ -67,19 +79,17 @@ class ConstantBytes(Constant): # which it needs to compile the data into the program return f'ConstantBytes({repr(self.value)}, @{repr(self.data_block.address)})' -class ConstantTuple(Constant): +class ConstantTuple(ConstantMemoryStored): """ A Tuple constant value expression within a statement """ - __slots__ = ('value', 'data_block', ) + __slots__ = ('value', ) - value: List[Union[ConstantPrimitive, ConstantBytes]] # FIXME: Tuple of tuples? - data_block: 'ModuleDataBlock' + value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple']] - def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes]], data_block: 'ModuleDataBlock') -> None: - super().__init__() + def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple']], data_block: 'ModuleDataBlock') -> None: + super().__init__(data_block) self.value = value - self.data_block = data_block def __repr__(self) -> str: # Do not repr the whole ModuleDataBlock @@ -87,21 +97,19 @@ class ConstantTuple(Constant): # which it needs to compile the data into the program return f'ConstantTuple({repr(self.value)}, @{repr(self.data_block.address)})' -class ConstantStruct(Constant): +class ConstantStruct(ConstantMemoryStored): """ A Struct constant value expression within a statement """ - __slots__ = ('struct_name', 'value', 'data_block', ) + __slots__ = ('struct_name', 'value', ) struct_name: str - value: List[Union[ConstantPrimitive, ConstantBytes]] # FIXME: Struct of structs? - data_block: 'ModuleDataBlock' + value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple]] - def __init__(self, struct_name: str, value: List[Union[ConstantPrimitive, ConstantBytes]], data_block: 'ModuleDataBlock') -> None: - super().__init__() + def __init__(self, struct_name: str, value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple]], data_block: 'ModuleDataBlock') -> None: + super().__init__(data_block) self.struct_name = struct_name self.value = value - self.data_block = data_block def __repr__(self) -> str: # Do not repr the whole ModuleDataBlock @@ -376,11 +384,11 @@ class ModuleDataBlock: """ __slots__ = ('data', 'address', ) - data: List[Union[ConstantPrimitive, ConstantBytes]] + data: List[Union[ConstantPrimitive, ConstantMemoryStored]] address: Optional[int] - def __init__(self, data: List[Union[ConstantPrimitive, ConstantBytes]]) -> None: - self.data = data + def __init__(self, data: Iterable[Union[ConstantPrimitive, ConstantMemoryStored]]) -> None: + self.data = [*data] self.address = None class ModuleData: diff --git a/phasm/parser.py b/phasm/parser.py index 1d6b0a0..3a2f8dd 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -1,7 +1,7 @@ """ Parses the source code from the plain text into a syntax tree """ -from typing import Any, Dict, NoReturn, Union +from typing import Any, Dict, List, NoReturn, Union import ast @@ -16,7 +16,8 @@ from .ourlang import ( Expression, BinaryOp, - ConstantPrimitive, ConstantBytes, ConstantTuple, ConstantStruct, + ConstantPrimitive, ConstantMemoryStored, + ConstantBytes, ConstantTuple, ConstantStruct, TupleInstantiation, FunctionCall, AccessStructMember, Subscript, @@ -219,24 +220,16 @@ class OurVisitor: ) if isinstance(node.value, ast.Tuple): - tuple_data = [ - self.visit_Module_Constant(module, arg_node) - for arg_node in node.value.elts - if isinstance(arg_node, ast.Constant) - ] - if len(node.value.elts) != len(tuple_data): - _raise_static_error(node, 'Tuple arguments must be constants') + value_data = self.visit_Module_Constant(module, node.value) - # Allocate the data - data_block = ModuleDataBlock(tuple_data) - module.data.blocks.append(data_block) + assert isinstance(value_data, ConstantTuple) # type hint # Then return the constant as a pointer return ModuleConstantDef( node.target.id, node.lineno, self.visit_type(module, node.annotation), - ConstantTuple(tuple_data, data_block), + value_data, ) if isinstance(node.value, ast.Call): @@ -568,7 +561,23 @@ class OurVisitor: return Subscript(varref, slice_expr) - def visit_Module_Constant(self, module: Module, node: ast.Constant) -> Union[ConstantPrimitive, ConstantBytes]: + def visit_Module_Constant(self, module: Module, node: Union[ast.Constant, ast.Tuple]) -> Union[ConstantPrimitive, ConstantBytes, ConstantTuple]: + if isinstance(node, ast.Tuple): + tuple_data = [ + self.visit_Module_Constant(module, arg_node) + for arg_node in node.elts + if isinstance(arg_node, (ast.Constant, ast.Tuple, )) + ] + + if len(node.elts) != len(tuple_data): + _raise_static_error(node, 'Tuple arguments must be constants') + + # Allocate the data + data_block = ModuleDataBlock(tuple_data) + module.data.blocks.append(data_block) + + return ConstantTuple(tuple_data, data_block) + _not_implemented(node.kind is None, 'Constant.kind') if isinstance(node.value, (int, float, )): diff --git a/tests/integration/test_lang/test_tuple.py b/tests/integration/test_lang/test_tuple.py index 21bb86e..465d305 100644 --- a/tests/integration/test_lang/test_tuple.py +++ b/tests/integration/test_lang/test_tuple.py @@ -119,6 +119,29 @@ def testEntry() -> u64: assert 128 == result.returned_value +@pytest.mark.integration_test +def test_constant_tuple_of_tuple_of_tuple(): + code_py = """ +CONSTANT: (((u64, ), u64, ), u64, ) = (((128, ), 64, ), 32, ) + +def l1(c: (u64, )) -> u64: + return c[0] + +def l2(c: ((u64, ), u64, )) -> u64: + return l1(c[0]) + +def l3(c: (((u64, ), u64, ), u64, )) -> u64: + return l2(c[0]) + +@exported +def testEntry() -> u64: + return l3(CONSTANT) +""" + + result = Suite(code_py).run_code() + + assert 128 == result.returned_value + @pytest.mark.integration_test def test_bytes_as_part_of_tuple(): code_py = """