phasm/phasm/codestyle.py
Johan B.W. de Vries fd66292bb1 Replaces type3 with type5
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.
2025-08-09 19:58:53 +02:00

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