phasm/phasm/ourlang.py
Johan B.W. de Vries 97b61e3ee1 Test generation framework with typing improvements
Prior to this PR, each type would have its own handwritten
test suite. The end result was that not all types were tested
for all situations.

This PR adds a framework based on a Markdown file, which
generates the basic tests for the types defined in json
files. These are auto generated and updated by the Makefile
before the test suite is run.

Also, a number of unsupported type combinations are now
supported.

Also, we now support negative literals.

Also, allocation calculation fixes for nested types.

Also, the test helpers can now properly import and export
typed variables such as bytes, static arrays and tuples. This
may come in handy when it comes to phasm platform wanting to
route data.

Also, adds better support for i8 type.

Also, started on a runtime.py, since there's quite some code
now that deals with compile time handling of WebAssembly stuff.

Also, minor improvement to the type constrains, namely we
better match 'tuple' literals with static array types.

Also, reduced spam when printing the type analysis results;
constraints that go back on the backlog are now no longer
printed one by one. It now also prints the end results of
the typing analysis.

Also, reorganized the big test_primitives test into type
classes.

Also, replaced pylint with ruff.
2023-11-15 12:52:23 +01:00

420 lines
11 KiB
Python

"""
Contains the syntax tree for ourlang
"""
import enum
from typing import Dict, Iterable, List, Optional, Union
from typing_extensions import Final
from .type3 import types as type3types
from .type3.types import PlaceholderForType, StructType3, Type3, Type3OrPlaceholder
WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', )
WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', )
class Expression:
"""
An expression within a statement
"""
__slots__ = ('type3', )
type3: Type3OrPlaceholder
def __init__(self) -> None:
self.type3 = PlaceholderForType([self])
class Constant(Expression):
"""
An constant value expression within a statement
# FIXME: Rename to literal
"""
__slots__ = ()
class ConstantPrimitive(Constant):
"""
An primitive constant value expression within a statement
"""
__slots__ = ('value', )
value: Union[int, float]
def __init__(self, value: Union[int, float]) -> None:
super().__init__()
self.value = value
def __repr__(self) -> str:
return f'ConstantPrimitive({repr(self.value)})'
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', )
value: bytes
def __init__(self, value: bytes, data_block: 'ModuleDataBlock') -> None:
super().__init__(data_block)
self.value = value
def __repr__(self) -> str:
# Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data
# which it needs to compile the data into the program
return f'ConstantBytes({repr(self.value)}, @{repr(self.data_block.address)})'
class ConstantTuple(ConstantMemoryStored):
"""
A Tuple constant value expression within a statement
"""
__slots__ = ('value', )
value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']]
def __init__(self, value: List[Union[ConstantPrimitive, ConstantBytes, 'ConstantTuple', 'ConstantStruct']], data_block: 'ModuleDataBlock') -> None:
super().__init__(data_block)
self.value = value
def __repr__(self) -> str:
# Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data
# which it needs to compile the data into the program
return f'ConstantTuple({repr(self.value)}, @{repr(self.data_block.address)})'
class ConstantStruct(ConstantMemoryStored):
"""
A Struct constant value expression within a statement
"""
__slots__ = ('struct_name', 'value', )
struct_name: str
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']]
def __init__(self, struct_name: str, value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']], data_block: 'ModuleDataBlock') -> None:
super().__init__(data_block)
self.struct_name = struct_name
self.value = value
def __repr__(self) -> str:
# Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data
# which it needs to compile the data into the program
return f'ConstantStruct({repr(self.struct_name)}, {repr(self.value)}, @{repr(self.data_block.address)})'
class VariableReference(Expression):
"""
An variable reference expression within a statement
"""
__slots__ = ('variable', )
variable: Union['ModuleConstantDef', 'FunctionParam'] # also possibly local
def __init__(self, variable: Union['ModuleConstantDef', 'FunctionParam']) -> None:
super().__init__()
self.variable = variable
class UnaryOp(Expression):
"""
A unary operator expression within a statement
"""
__slots__ = ('operator', 'right', )
operator: str
right: Expression
def __init__(self, operator: str, right: Expression) -> None:
super().__init__()
self.operator = operator
self.right = right
class BinaryOp(Expression):
"""
A binary operator expression within a statement
"""
__slots__ = ('operator', 'left', 'right', )
operator: str
left: Expression
right: Expression
def __init__(self, operator: str, left: Expression, right: Expression) -> None:
super().__init__()
self.operator = operator
self.left = left
self.right = right
def __repr__(self) -> str:
return f'BinaryOp({repr(self.operator)}, {repr(self.left)}, {repr(self.right)})'
class FunctionCall(Expression):
"""
A function call expression within a statement
"""
__slots__ = ('function', 'arguments', )
function: 'Function'
arguments: List[Expression]
def __init__(self, function: 'Function') -> None:
super().__init__()
self.function = function
self.arguments = []
class TupleInstantiation(Expression):
"""
Instantiation a tuple
"""
__slots__ = ('elements', )
elements: List[Expression]
def __init__(self, elements: List[Expression]) -> None:
super().__init__()
self.elements = elements
class Subscript(Expression):
"""
A subscript, for example to refer to a static array or tuple
by index
"""
__slots__ = ('varref', 'index', )
varref: VariableReference
index: Expression
def __init__(self, varref: VariableReference, index: Expression) -> None:
super().__init__()
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
"""
class Direction(enum.Enum):
"""
Which direction to fold in
"""
LEFT = 0
RIGHT = 1
dir: Direction
func: 'Function'
base: Expression
iter: Expression
def __init__(
self,
dir_: Direction,
func: 'Function',
base: Expression,
iter_: Expression,
) -> None:
super().__init__()
self.dir = dir_
self.func = func
self.base = base
self.iter = iter_
class Statement:
"""
A statement within a function
"""
__slots__ = ()
class StatementPass(Statement):
"""
A pass statement
"""
__slots__ = ()
class StatementReturn(Statement):
"""
A return statement within a function
"""
__slots__ = ('value', )
def __init__(self, value: Expression) -> None:
self.value = value
class StatementIf(Statement):
"""
An if statement within a function
"""
__slots__ = ('test', 'statements', 'else_statements', )
test: Expression
statements: List[Statement]
else_statements: List[Statement]
def __init__(self, test: Expression) -> None:
self.test = test
self.statements = []
self.else_statements = []
class FunctionParam:
"""
A parameter for a Function
"""
__slots__ = ('name', 'type3', )
name: str
type3: Type3OrPlaceholder
def __init__(self, name: str, type3: Optional[Type3]) -> None:
self.name = name
self.type3 = PlaceholderForType([self]) if type3 is None else type3
class Function:
"""
A function processes input and produces output
"""
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'posonlyargs', )
name: str
lineno: int
exported: bool
imported: Optional[str]
statements: List[Statement]
returns_type3: Type3
posonlyargs: List[FunctionParam]
def __init__(self, name: str, lineno: int) -> None:
self.name = name
self.lineno = lineno
self.exported = False
self.imported = None
self.statements = []
self.returns_type3 = type3types.none # FIXME: This could be a placeholder
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
class ModuleConstantDef:
"""
A constant definition within a module
"""
__slots__ = ('name', 'lineno', 'type3', 'constant', )
name: str
lineno: int
type3: Type3
constant: Constant
def __init__(self, name: str, lineno: int, type3: Type3, constant: Constant) -> None:
self.name = name
self.lineno = lineno
self.type3 = type3
self.constant = constant
class ModuleDataBlock:
"""
A single allocated block for module data
"""
__slots__ = ('data', 'address', )
data: List[Union[ConstantPrimitive, ConstantMemoryStored]]
address: Optional[int]
def __init__(self, data: Iterable[Union[ConstantPrimitive, ConstantMemoryStored]]) -> None:
self.data = [*data]
self.address = None
class ModuleData:
"""
The data for when a module is loaded into memory
"""
__slots__ = ('blocks', )
blocks: List[ModuleDataBlock]
def __init__(self) -> None:
self.blocks = []
class Module:
"""
A module is a file and consists of functions
"""
__slots__ = ('data', 'types', 'struct_definitions', 'constant_defs', 'functions',)
data: ModuleData
struct_definitions: Dict[str, StructDefinition]
constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function]
def __init__(self) -> None:
self.data = ModuleData()
self.struct_definitions = {}
self.constant_defs = {}
self.functions = {}