Spaces:
Sleeping
Sleeping
| """ | |
| 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})" | |