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.
420 lines
11 KiB
Python
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 = {}
|