phasm/phasm/codestyle.py
Johan B.W. de Vries 7df9d5af12 Removes the weird second step unify
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.
2025-08-24 16:06:42 +02:00

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