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.
222 lines
6.1 KiB
Python
222 lines
6.1 KiB
Python
"""
|
|
This module generates source code based on the parsed AST
|
|
|
|
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
|
|
|
|
|
|
def phasm_render(inp: ourlang.Module) -> str:
|
|
"""
|
|
Public method for rendering a Phasm module into Phasm code
|
|
"""
|
|
return module(inp)
|
|
|
|
Statements = Generator[str, None, None]
|
|
|
|
def type3(inp: Type3OrPlaceholder) -> str:
|
|
"""
|
|
Render: type's name
|
|
"""
|
|
assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR
|
|
|
|
if inp is type3types.none:
|
|
return 'None'
|
|
|
|
if isinstance(inp, type3types.AppliedType3):
|
|
if inp.base == type3types.tuple:
|
|
return '(' + ', '.join(
|
|
type3(x)
|
|
for x in inp.args
|
|
if isinstance(x, Type3) # Skip ints, not allowed here anyhow
|
|
) + ', )'
|
|
|
|
if inp.base == type3types.static_array:
|
|
assert 2 == len(inp.args)
|
|
assert isinstance(inp.args[0], Type3), TYPE3_ASSERTION_ERROR
|
|
assert isinstance(inp.args[1], type3types.IntType3), TYPE3_ASSERTION_ERROR
|
|
|
|
return type3(inp.args[0]) + '[' + inp.args[1].name + ']'
|
|
|
|
return inp.name
|
|
|
|
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:
|
|
"""
|
|
Render: Module Constant's definition
|
|
"""
|
|
return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n'
|
|
|
|
def expression(inp: ourlang.Expression) -> str:
|
|
"""
|
|
Render: A Phasm expression
|
|
"""
|
|
if isinstance(inp, ourlang.ConstantPrimitive):
|
|
# Floats might not round trip if the original constant
|
|
# could not fit in the given float type
|
|
return str(inp.value)
|
|
|
|
if isinstance(inp, ourlang.ConstantBytes):
|
|
return repr(inp.value)
|
|
|
|
if isinstance(inp, ourlang.ConstantTuple):
|
|
return '(' + ', '.join(
|
|
expression(x)
|
|
for x in inp.value
|
|
) + ', )'
|
|
|
|
if isinstance(inp, ourlang.ConstantStruct):
|
|
return inp.struct_name + '(' + ', '.join(
|
|
expression(x)
|
|
for x in inp.value
|
|
) + ')'
|
|
|
|
if isinstance(inp, ourlang.VariableReference):
|
|
return str(inp.variable.name)
|
|
|
|
if isinstance(inp, ourlang.UnaryOp):
|
|
if (
|
|
inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS
|
|
or inp.operator in ourlang.WEBASSEMBLY_BUILTIN_BYTES_OPS):
|
|
return f'{inp.operator}({expression(inp.right)})'
|
|
|
|
if inp.operator == 'cast':
|
|
mtyp = type3(inp.type3)
|
|
if mtyp is None:
|
|
raise NotImplementedError(f'Casting to type {inp.type_var}')
|
|
|
|
return f'{mtyp}({expression(inp.right)})'
|
|
|
|
return f'{inp.operator}{expression(inp.right)}'
|
|
|
|
if isinstance(inp, ourlang.BinaryOp):
|
|
return f'{expression(inp.left)} {inp.operator} {expression(inp.right)}'
|
|
|
|
if isinstance(inp, ourlang.FunctionCall):
|
|
args = ', '.join(
|
|
expression(arg)
|
|
for arg in inp.arguments
|
|
)
|
|
|
|
if isinstance(inp.function, ourlang.StructConstructor):
|
|
return f'{inp.function.struct_type3.name}({args})'
|
|
|
|
return f'{inp.function.name}({args})'
|
|
|
|
if isinstance(inp, ourlang.TupleInstantiation):
|
|
args = ', '.join(
|
|
expression(arg)
|
|
for arg in inp.elements
|
|
)
|
|
|
|
return f'({args}, )'
|
|
|
|
if isinstance(inp, ourlang.Subscript):
|
|
varref = expression(inp.varref)
|
|
index = expression(inp.index)
|
|
|
|
return f'{varref}[{index}]'
|
|
|
|
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'
|
|
return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})'
|
|
|
|
raise NotImplementedError(expression, inp)
|
|
|
|
def statement(inp: ourlang.Statement) -> Statements:
|
|
"""
|
|
Render: A list of Phasm statements
|
|
"""
|
|
if isinstance(inp, ourlang.StatementPass):
|
|
yield 'pass'
|
|
return
|
|
|
|
if isinstance(inp, ourlang.StatementReturn):
|
|
yield f'return {expression(inp.value)}'
|
|
return
|
|
|
|
if isinstance(inp, ourlang.StatementIf):
|
|
yield f'if {expression(inp.test)}:'
|
|
|
|
for stmt in inp.statements:
|
|
for line in statement(stmt):
|
|
yield f' {line}' if line else ''
|
|
|
|
yield ''
|
|
return
|
|
|
|
raise NotImplementedError(statement, inp)
|
|
|
|
def function(inp: ourlang.Function) -> str:
|
|
"""
|
|
Render: Function body
|
|
|
|
Imported functions only have "pass" as a body. Later on we might replace
|
|
this by the function documentation, if any.
|
|
"""
|
|
result = ''
|
|
if inp.exported:
|
|
result += '@exported\n'
|
|
if inp.imported:
|
|
result += '@imported\n'
|
|
|
|
args = ', '.join(
|
|
f'{p.name}: {type3(p.type3)}'
|
|
for p in inp.posonlyargs
|
|
)
|
|
|
|
result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n'
|
|
|
|
if inp.imported:
|
|
result += ' pass\n'
|
|
else:
|
|
for stmt in inp.statements:
|
|
for line in statement(stmt):
|
|
result += f' {line}\n' if line else '\n'
|
|
|
|
return result
|
|
|
|
|
|
def module(inp: ourlang.Module) -> str:
|
|
"""
|
|
Render: Module
|
|
"""
|
|
result = ''
|
|
|
|
for struct in inp.struct_definitions.values():
|
|
if result:
|
|
result += '\n'
|
|
result += struct_definition(struct)
|
|
|
|
for cdef in inp.constant_defs.values():
|
|
if result:
|
|
result += '\n'
|
|
result += constant_definition(cdef)
|
|
|
|
for func in inp.functions.values():
|
|
if func.lineno < 0:
|
|
# Builtin (-2) or auto generated (-1)
|
|
continue
|
|
|
|
if result:
|
|
result += '\n'
|
|
result += function(func)
|
|
|
|
return result
|