type5 is much more first principles based, so we get a lot of weird quirks removed: - FromLiteral no longer needs to understand AST - Type unifications works more like Haskell - Function types are just ordinary types, saving a lot of manual busywork and more.
190 lines
5.1 KiB
Python
190 lines
5.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 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.function.name} {expression(inp.right)}'
|
|
|
|
if isinstance(inp, ourlang.FunctionCall):
|
|
args = ', '.join(
|
|
expression(arg)
|
|
for arg in inp.arguments
|
|
)
|
|
|
|
if isinstance(inp.function_instance.function, ourlang.StructConstructor):
|
|
return f'{inp.function_instance.function.struct_type5.name}({args})'
|
|
|
|
return f'{inp.function_instance.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
|