cl-compiler / src /ast_nodes.py
GitHub Actions
Sync from GitHub: a87d7bd
04307b3
Raw
History Blame Contribute Delete
7.65 kB
"""
Every grammar rule that produces a tree node has its own class here.
All nodes inherit from ASTNode which provides:
- a .pretty(indent) method for human-readable printing
- a .__repr__ for debugging
Design note: nodes are intentionally simple data containers.
No logic lives here — the parser builds them; the semantic
analyzer walks them. This clean separation makes each milestone
independently testable.
Node hierarchy:
ASTNode
├── ProgramNode — root; holds a list of statements
├── DeclNode — type declaration: int x;
├── AssignNode — assignment: x = expr;
├── IfNode — if/then/else
├── WhileNode — while cond { body }
├── ForNode — for i = init to limit { body }
├── BlockNode — { stmt* }
├── BinOpNode — expr op expr
├── UnaryOpNode — op expr (currently: '-')
├── IdentNode — variable reference
├── NumNode — integer literal
└── StringNode — string literal
"""
from __future__ import annotations
from typing import List, Optional
class ASTNode:
"""Base class for all AST nodes."""
def pretty(self, indent: int = 0) -> str:
"""Return an indented string representation of this subtree."""
raise NotImplementedError
def __repr__(self) -> str:
return self.pretty()
# Statement nodes
class ProgramNode(ASTNode):
"""Root node. Contains the list of top-level statements."""
def __init__(self, statements: List[ASTNode]) -> None:
self.statements = statements
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
lines = [f"{pad}Program"]
for stmt in self.statements:
lines.append(stmt.pretty(indent + 1))
return "\n".join(lines)
class DeclNode(ASTNode):
"""
Variable declaration node.
Example: int x; → DeclNode(var_type='int', name='x')
"""
def __init__(self, var_type: str, name: str, line: int = 0) -> None:
self.var_type = var_type # 'int' or 'string'
self.name = name
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
return f"{pad}Decl({self.var_type} {self.name})"
class AssignNode(ASTNode):
"""
Assignment node.
Example: x = 5; → AssignNode(name='x', expr=NumNode(5))
"""
def __init__(self, name: str, expr: ASTNode, line: int = 0) -> None:
self.name = name
self.expr = expr
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
lines = [f"{pad}Assign({self.name})"]
lines.append(self.expr.pretty(indent + 1))
return "\n".join(lines)
class IfNode(ASTNode):
"""
If/then/else node.
The else_block is None if there is no else branch.
"""
def __init__(
self,
condition: ASTNode,
then_block: BlockNode,
else_block: Optional[BlockNode] = None,
line: int = 0,
) -> None:
self.condition = condition
self.then_block = then_block
self.else_block = else_block
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
lines = [f"{pad}If"]
lines.append(f"{pad} condition:")
lines.append(self.condition.pretty(indent + 2))
lines.append(f"{pad} then:")
lines.append(self.then_block.pretty(indent + 2))
if self.else_block:
lines.append(f"{pad} else:")
lines.append(self.else_block.pretty(indent + 2))
return "\n".join(lines)
class WhileNode(ASTNode):
"""
While loop: while cond { body }
The condition must be a comparison (semantic analyzer enforces this).
"""
def __init__(self, condition: ASTNode, body: BlockNode, line: int = 0) -> None:
self.condition = condition
self.body = body
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
lines = [f"{pad}While"]
lines.append(f"{pad} condition:")
lines.append(self.condition.pretty(indent + 2))
lines.append(f"{pad} body:")
lines.append(self.body.pretty(indent + 2))
return "\n".join(lines)
class ForNode(ASTNode):
"""
For loop: for IDENT = init to limit { body }
The loop variable must be a previously declared 'int'.
"""
def __init__(
self,
var_name: str,
init: ASTNode,
limit: ASTNode,
body: BlockNode,
line: int = 0,
) -> None:
self.var_name = var_name
self.init = init
self.limit = limit
self.body = body
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
lines = [f"{pad}For({self.var_name})"]
lines.append(f"{pad} init:")
lines.append(self.init.pretty(indent + 2))
lines.append(f"{pad} limit:")
lines.append(self.limit.pretty(indent + 2))
lines.append(f"{pad} body:")
lines.append(self.body.pretty(indent + 2))
return "\n".join(lines)
class BlockNode(ASTNode):
"""A brace-delimited block: { stmt* }"""
def __init__(self, statements: List[ASTNode], line: int = 0) -> None:
self.statements = statements
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
lines = [f"{pad}Block"]
for stmt in self.statements:
lines.append(stmt.pretty(indent + 1))
return "\n".join(lines)
# Expression nodes
class BinOpNode(ASTNode):
"""
Binary operation node.
op is one of: '+', '-', '*', '>'
"""
def __init__(self, op: str, left: ASTNode, right: ASTNode, line: int = 0) -> None:
self.op = op
self.left = left
self.right = right
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
lines = [f"{pad}BinOp({self.op!r})"]
lines.append(self.left.pretty(indent + 1))
lines.append(self.right.pretty(indent + 1))
return "\n".join(lines)
class UnaryOpNode(ASTNode):
"""
Unary operation node.
op is currently always '-'.
"""
def __init__(self, op: str, operand: ASTNode, line: int = 0) -> None:
self.op = op
self.operand = operand
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
lines = [f"{pad}UnaryOp({self.op!r})"]
lines.append(self.operand.pretty(indent + 1))
return "\n".join(lines)
class IdentNode(ASTNode):
"""A variable reference (identifier used as an expression)."""
def __init__(self, name: str, line: int = 0) -> None:
self.name = name
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
return f"{pad}Ident({self.name})"
class NumNode(ASTNode):
"""An integer literal."""
def __init__(self, value: int, line: int = 0) -> None:
self.value = value
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
return f"{pad}Num({self.value})"
class StringNode(ASTNode):
"""A string literal (value has surrounding quotes stripped)."""
def __init__(self, value: str, line: int = 0) -> None:
self.value = value # raw content, quotes stripped
self.line = line
def pretty(self, indent: int = 0) -> str:
pad = " " * indent
return f"{pad}String({self.value!r})"