205 lines
5.2 KiB
Python
205 lines
5.2 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 . import typing
|
|
|
|
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 type_(inp: typing.TypeBase) -> str:
|
|
"""
|
|
Render: Type (name)
|
|
"""
|
|
if isinstance(inp, typing.TypeNone):
|
|
return 'None'
|
|
|
|
if isinstance(inp, typing.TypeBool):
|
|
return 'bool'
|
|
|
|
if isinstance(inp, typing.TypeUInt8):
|
|
return 'u8'
|
|
|
|
if isinstance(inp, typing.TypeUInt32):
|
|
return 'u32'
|
|
|
|
if isinstance(inp, typing.TypeUInt64):
|
|
return 'u64'
|
|
|
|
if isinstance(inp, typing.TypeInt32):
|
|
return 'i32'
|
|
|
|
if isinstance(inp, typing.TypeInt64):
|
|
return 'i64'
|
|
|
|
if isinstance(inp, typing.TypeFloat32):
|
|
return 'f32'
|
|
|
|
if isinstance(inp, typing.TypeFloat64):
|
|
return 'f64'
|
|
|
|
if isinstance(inp, typing.TypeBytes):
|
|
return 'bytes'
|
|
|
|
if isinstance(inp, typing.TypeTuple):
|
|
mems = ', '.join(
|
|
type_(x.type)
|
|
for x in inp.members
|
|
)
|
|
|
|
return f'({mems}, )'
|
|
|
|
if isinstance(inp, typing.TypeStruct):
|
|
return inp.name
|
|
|
|
raise NotImplementedError(type_, inp)
|
|
|
|
def struct_definition(inp: typing.TypeStruct) -> str:
|
|
"""
|
|
Render: TypeStruct's definition
|
|
"""
|
|
result = f'class {inp.name}:\n'
|
|
for mem in inp.members:
|
|
result += f' {mem.name}: {type_(mem.type)}\n'
|
|
|
|
return result
|
|
|
|
def expression(inp: ourlang.Expression) -> str:
|
|
"""
|
|
Render: A Phasm expression
|
|
"""
|
|
if isinstance(inp, (
|
|
ourlang.ConstantUInt8, ourlang.ConstantUInt32, ourlang.ConstantUInt64,
|
|
ourlang.ConstantInt32, ourlang.ConstantInt64,
|
|
)):
|
|
return str(inp.value)
|
|
|
|
if isinstance(inp, (ourlang.ConstantFloat32, ourlang.ConstantFloat64, )):
|
|
# These might not round trip if the original constant
|
|
# could not fit in the given float type
|
|
return str(inp.value)
|
|
|
|
if isinstance(inp, ourlang.VariableReference):
|
|
return str(inp.name)
|
|
|
|
if isinstance(inp, ourlang.UnaryOp):
|
|
if (
|
|
inp.operator in ourlang.WEBASSEMBLY_BUILDIN_FLOAT_OPS
|
|
or inp.operator in ourlang.WEBASSEMBLY_BUILDIN_BYTES_OPS):
|
|
return f'{inp.operator}({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.name}({args})'
|
|
|
|
if isinstance(inp.function, ourlang.TupleConstructor):
|
|
return f'({args}, )'
|
|
|
|
return f'{inp.function.name}({args})'
|
|
|
|
if isinstance(inp, ourlang.AccessBytesIndex):
|
|
return f'{expression(inp.varref)}[{expression(inp.index)}]'
|
|
|
|
if isinstance(inp, ourlang.AccessStructMember):
|
|
return f'{expression(inp.varref)}.{inp.member.name}'
|
|
|
|
if isinstance(inp, ourlang.AccessTupleMember):
|
|
return f'{expression(inp.varref)}[{inp.member.idx}]'
|
|
|
|
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'{x}: {type_(y)}'
|
|
for x, y in inp.posonlyargs
|
|
)
|
|
|
|
result += f'def {inp.name}({args}) -> {type_(inp.returns)}:\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.structs.values():
|
|
if result:
|
|
result += '\n'
|
|
result += struct_definition(struct)
|
|
|
|
for func in inp.functions.values():
|
|
if func.lineno < 0:
|
|
# Buildin (-2) or auto generated (-1)
|
|
continue
|
|
|
|
if result:
|
|
result += '\n'
|
|
result += function(func)
|
|
|
|
return result
|