It is now part of the normal constraints. Added a special workaround for functions, since otherwise the output is a bit redundant and quite confusing. Also, constraints are now processed in order of complexity. This does not affect type safety. It uses a bit more CPU. But it makes the output that much easier to read. Also, removes the weird FunctionInstance hack. Instead, the more industry standard way of annotation the types on the function call is used. As always, this requires some hackyness for Subscriptable. Also, adds a few comments to the type unification to help with debugging. Also, prints out the new constraints that are received.
190 lines
5.0 KiB
Python
190 lines
5.0 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 Any, Generator
|
|
|
|
from . import ourlang
|
|
from .type5 import typeexpr as type5typeexpr
|
|
|
|
|
|
def phasm_render(inp: ourlang.Module[Any]) -> str:
|
|
"""
|
|
Public method for rendering a Phasm module into Phasm code
|
|
"""
|
|
return module(inp)
|
|
|
|
Statements = Generator[str, None, None]
|
|
|
|
def type5(mod: ourlang.Module[Any], inp: type5typeexpr.TypeExpr) -> str:
|
|
"""
|
|
Render: type's name
|
|
"""
|
|
return mod.build.type5_name(inp)
|
|
|
|
def struct_definition(mod: ourlang.Module[Any], inp: ourlang.StructDefinition) -> str:
|
|
"""
|
|
Render: TypeStruct's definition
|
|
"""
|
|
result = f'class {inp.struct_type5.name}:\n'
|
|
for mem, typ in inp.struct_type5.fields:
|
|
result += f' {mem}: {type5(mod, typ)}\n'
|
|
|
|
return result
|
|
|
|
def constant_definition(mod: ourlang.Module[Any], inp: ourlang.ModuleConstantDef) -> str:
|
|
"""
|
|
Render: Module Constant's definition
|
|
"""
|
|
return f'{inp.name}: {type5(mod, inp.type5)} = {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_type5.name + '(' + ', '.join(
|
|
expression(x)
|
|
for x in inp.value
|
|
) + ')'
|
|
|
|
if isinstance(inp, ourlang.VariableReference):
|
|
return str(inp.variable.name)
|
|
|
|
if isinstance(inp, ourlang.BinaryOp):
|
|
return f'{expression(inp.left)} {inp.operator.name} {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_type5.name}({args})'
|
|
|
|
return f'{inp.function.name}({args})'
|
|
|
|
if isinstance(inp, ourlang.FunctionReference):
|
|
return str(inp.function.name)
|
|
|
|
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}'
|
|
|
|
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(mod: ourlang.Module[Any], 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'
|
|
|
|
assert inp.type5 is not None
|
|
fn_args = mod.build.type5_is_function(inp.type5)
|
|
assert fn_args is not None
|
|
ret_type5 = fn_args.pop()
|
|
|
|
args = ', '.join(
|
|
f'{arg_name}: {type5(mod, arg_type)}'
|
|
for arg_name, arg_type in zip(inp.arg_names, fn_args, strict=True)
|
|
)
|
|
|
|
result += f'def {inp.name}({args}) -> {type5(mod, ret_type5)}:\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[Any]) -> str:
|
|
"""
|
|
Render: Module
|
|
"""
|
|
result = ''
|
|
|
|
for struct in inp.struct_definitions.values():
|
|
if result:
|
|
result += '\n'
|
|
result += struct_definition(inp, struct)
|
|
|
|
for cdef in inp.constant_defs.values():
|
|
if result:
|
|
result += '\n'
|
|
result += constant_definition(inp, cdef)
|
|
|
|
for func in inp.functions.values():
|
|
if isinstance(func, ourlang.StructConstructor):
|
|
# Auto generated
|
|
continue
|
|
|
|
if result:
|
|
result += '\n'
|
|
result += function(inp, func)
|
|
|
|
return result
|