Files
Plasma/compiler.py
2025-10-14 21:23:11 +11:00

280 lines
8.4 KiB
Python

from llvmlite import ir
from AST import Node, NodeType, Program, Expression
from AST import ExpressionStatement, AssignmentStatement, BlockStatement, ReturnStatement, FunctionStatement, ReassignStatement, IfStatement
from AST import InfixExpression
from AST import IntegerLiteral, FloatLiteral, IdentifierLiteral, BooleanLiteral
from environment import Environment
class Compiler:
def __init__(self) -> None:
self.type_map: dict[str, ir.type] = {
"Bool": ir.IntType(1),
"Byte": ir.IntType(8),
"Short": ir.IntType(16),
"Int": ir.IntType(32),
"Long": ir.IntType(64),
"Float": ir.FloatType(),
"Double": ir.DoubleType()
}
self.module: ir.Module = ir.Module("main")
self.builder: ir.IRBuilder = ir.IRBuilder()
self.environment: Environment = Environment()
self.errors: list[str] = []
self.__initialize_builtins()
def __initialize_builtins(self) -> None:
def __init_booleans() -> tuple[ir.GlobalVariable, ir.GlobalVariable]:
bool_type: ir.Type = self.type_map["Bool"]
true_var = ir.GlobalVariable(self.module, bool_type, "true")
true_var.initializer = ir.Constant(bool_type, 1)
true_var.global_constant = True
false_var = ir.GlobalVariable(self.module, bool_type, "false")
false_var.initializer = ir.Constant(bool_type, 0)
false_var.global_constant = True
return true_var, false_var
true_var, false_var = __init_booleans()
self.environment.define("true", true_var, true_var.type)
self.environment.define("false", false_var, false_var.type)
def compile(self, node: Node) -> None:
match node.type():
case NodeType.Program:
self.__visit_program(node)
# Statements
case NodeType.ExpressionStatement:
self.__visit_expression_statement(node)
case NodeType.AssignmentStatement:
self.__visit_assignment_statement(node)
case NodeType.FunctionStatement:
self.__visit_function_statement(node)
case NodeType.BlockStatement:
self.__visit_block_statement(node)
case NodeType.ReturnStatement:
self.__visit_return_statement(node)
case NodeType.ReassignStatement:
self.__visit_reassign_statement(node)
case NodeType.IfStatement:
self.__visit_if_statement(node)
# Expressions
case NodeType.InfixExpression:
self.__visit_infix_expression(node)
# region Visit Methods
def __visit_program(self, node: Program) -> None:
for stmt in node.statements:
self.compile(stmt)
# region Statements
def __visit_expression_statement(self, node: ExpressionStatement) -> None:
self.compile(node.expr)
def __visit_assignment_statement(self, node: AssignmentStatement) -> None:
name: str = node.name.value
value: Expression = node.value
value_type: str = node.value_type # TODO: implemented
value, Type = self.__resolve_value(node=value)
if self.environment.lookup(name) is None:
# Define and allocate the new variable
ptr = self.builder.alloca(Type)
# Storing the value to the ptr
self.builder.store(value, ptr)
# Add the variable to the environment
self.environment.define(name, ptr, Type)
else:
ptr, _ = self.environment.lookup(name)
self.builder.store(value, ptr)
def __visit_block_statement(self, node: BlockStatement) -> None:
for stmt in node.statements:
self.compile(stmt)
def __visit_return_statement(self, node: ReturnStatement) -> None:
value: Expression = node.return_value
value, Type = self.__resolve_value(value)
self.builder.ret(value)
def __visit_function_statement(self, node: FunctionStatement) -> None:
name: str = node.name.value
body: BlockStatement = node.body
params: list[IdentifierLiteral] = node.parameters
param_types: list[ir.Type] = [] # TODO
return_type: ir.Type = self.type_map[node.return_type]
fnty: ir.FunctionType = ir.FunctionType(return_type, param_types)
func: ir.Function = ir.Function(self.module, fnty, name)
block: ir.Block = func.append_basic_block(f"{name}_entry")
previous_builder = self.builder
self.builder = ir.IRBuilder(block)
previous_env = self.environment
self.environment = Environment(parent=self.environment)
self.environment.define(name, func, return_type)
self.compile(body)
self.environment = previous_env
self.environment.define(name, func, return_type)
self.builder = previous_builder
def __visit_reassign_statement(self, node: ReassignStatement) -> None:
name: str = node.ident.value
value: Expression = node.right_value
value, Type = self.__resolve_value(value)
if self.environment.lookup(name) is None:
self.errors.append(f"Identifier {name} has not been declared before it was re-assigned.")
else:
ptr, _ = self.environment.lookup(name)
self.builder.store(value, ptr)
def __visit_if_statement(self, node: IfStatement) -> None:
condition = node.condition
consequence = node.consequence
alternative = node.alternative
test, _ = self.__resolve_value(condition)
if alternative is None:
with self.builder.if_then(test):
self.compile(consequence)
else:
with self.builder.if_else(test) as (true, otherwise):
# Creating a condition branch
# condition
# / \
# true false
# / \
# / \
# if block else block
with true:
self.compile(consequence)
with otherwise:
self.compile(alternative)
# endregion
# region Expressions
def __visit_infix_expression(self, node: InfixExpression) -> None:
operator: str = node.operator
left_value, left_type = self.__resolve_value(node.left_node)
right_value, right_type = self.__resolve_value(node.right_node)
value = None
Type = None
if isinstance(right_type, ir.IntType) and isinstance(left_type, ir.IntType):
Type = self.type_map["Int"]
match operator:
case "+":
value = self.builder.add(left_value, right_value)
case "-":
value = self.builder.sub(left_value, right_value)
case "*":
value = self.builder.mul(left_value, right_value)
case "/":
value = self.builder.sdiv(left_value, right_value)
case "%":
value = self.builder.srem(left_value, right_value)
case "^":
# TODO
pass
case "<":
value = self.builder.icmp_signed('<', left_value, right_value)
Type = ir.IntType(1)
case "<=":
value = self.builder.icmp_signed('<=', left_value, right_value)
Type = ir.IntType(1)
case ">":
value = self.builder.icmp_signed('>', left_value, right_value)
Type = ir.IntType(1)
case ">=":
value = self.builder.icmp_signed('>=', left_value, right_value)
Type = ir.IntType(1)
case "==":
value = self.builder.icmp_signed('==', left_value, right_value)
Type = ir.IntType(1)
elif isinstance(right_type, ir.FloatType) and isinstance(left_type, ir.FloatType):
Type = self.type_map["Float"]
match operator:
case "+":
value = self.builder.fadd(left_value, right_value)
case "-":
value = self.builder.fsub(left_value, right_value)
case "*":
value = self.builder.fmul(left_value, right_value)
case "/":
value = self.builder.fdiv(left_value, right_value)
case "%":
value = self.builder.frem(left_value, right_value)
case "^":
# TODO
pass
case "<":
value = self.builder.fcmp_ordered('<', left_value, right_value)
Type = ir.IntType(1)
case "<=":
value = self.builder.fcmp_ordered('<=', left_value, right_value)
Type = ir.IntType(1)
case ">":
value = self.builder.fcmp_ordered('>', left_value, right_value)
Type = ir.IntType(1)
case ">=":
value = self.builder.fcmp_ordered('>=', left_value, right_value)
Type = ir.IntType(1)
case "==":
value = self.builder.fcmp_ordered('==', left_value, right_value)
Type = ir.IntType(1)
return value, Type
# endregion
# endregion
# region Helper Methods
def __resolve_value(self, node: Expression) -> tuple[ir.Value, ir.Type]:
match node.type():
case NodeType.IntegerLiteral:
node: IntegerLiteral = node
value, Type = node.value, self.type_map['Int']
return ir.Constant(Type, value), Type
case NodeType.FloatLiteral:
node: FloatLiteral = node
value, Type = node.value, self.type_map['Float']
return ir.Constant(Type, value), Type
case NodeType.IdentifierLiteral:
node: IdentifierLiteral = node
ptr, Type = self.environment.lookup(node.value)
return self.builder.load(ptr), Type
case NodeType.BooleanLiteral:
node: BooleanLiteral = node
return ir.Constant(ir.IntType(1), 1 if node.value else 0), ir.IntType(1)
# expression value
case NodeType.InfixExpression:
return self.__visit_infix_expression(node)
# endregion