Arvores Sintaticas (ASTs)
Video da aula estara disponivel em breve
Parse Tree vs. AST
A parse tree (arvore de derivacao) reflete todas as producoes da gramatica, incluindo nao-terminais intermediarios. A AST (Abstract Syntax Tree) simplifica, mantendo apenas a informacao semanticamente relevante:
Expressao: 2 + 3 * 4
Parse Tree (concreta): AST (abstrata):
expr +
/ | \ / \
expr '+' term 2 *
| / | \ / \
term term '*' factor 3 4
| | |
factor factor NUMBER(4)
| |
NUMBER(2) NUMBER(3)
A parse tree tem 11 nos. A AST tem 5.
A AST descarta nao-terminais intermediarios e parenteses —
mantem apenas a estrutura semantica.
Definindo Nos da AST em Python
from dataclasses import dataclass
from typing import List, Optional
# Nos da AST para uma mini-linguagem
@dataclass
class NumberLit:
value: float
@dataclass
class StringLit:
value: str
@dataclass
class Identifier:
name: str
@dataclass
class BinOp:
op: str
left: 'Expr'
right: 'Expr'
@dataclass
class Assignment:
target: Identifier
value: 'Expr'
@dataclass
class FuncCall:
name: str
args: List['Expr']
@dataclass
class IfStmt:
condition: 'Expr'
then_body: List['Stmt']
else_body: Optional[List['Stmt']]
# Construir a AST de: resultado = taxa * 100 + bonus
ast = Assignment(
target=Identifier("resultado"),
value=BinOp(
op="+",
left=BinOp(
op="*",
left=Identifier("taxa"),
right=NumberLit(100)
),
right=Identifier("bonus")
)
)
print(ast)
Visitor Pattern
O Visitor pattern permite percorrer a AST e executar operacoes diferentes em cada tipo de no sem modificar as classes dos nos:
class ASTVisitor:
"""Visitor base — despacha para visit_TipoDoNo."""
def visit(self, node):
method = f'visit_{type(node).__name__}'
visitor = getattr(self, method, self.generic_visit)
return visitor(node)
def generic_visit(self, node):
raise NotImplementedError(f"Sem visitor para {type(node).__name__}")
class Evaluator(ASTVisitor):
"""Avalia expressoes numericas."""
def __init__(self):
self.env = {}
def visit_NumberLit(self, node):
return node.value
def visit_Identifier(self, node):
return self.env.get(node.name, 0)
def visit_BinOp(self, node):
left = self.visit(node.left)
right = self.visit(node.right)
ops = {'+': lambda a,b: a+b, '-': lambda a,b: a-b,
'*': lambda a,b: a*b, '/': lambda a,b: a/b}
return ops[node.op](left, right)
def visit_Assignment(self, node):
value = self.visit(node.value)
self.env[node.target.name] = value
return value
# Uso
evaluator = Evaluator()
evaluator.env = {'taxa': 0.15, 'bonus': 50}
result = evaluator.visit(ast)
print(f"resultado = {result}") # resultado = 65.0
class PrettyPrinter(ASTVisitor):
"""Imprime a AST como codigo."""
def visit_NumberLit(self, node): return str(node.value)
def visit_Identifier(self, node): return node.name
def visit_BinOp(self, node):
return f"({self.visit(node.left)} {node.op} {self.visit(node.right)})"
def visit_Assignment(self, node):
return f"{node.target.name} = {self.visit(node.value)}"
printer = PrettyPrinter()
print(printer.visit(ast)) # resultado = ((taxa * 100) + bonus)
Python ast Module Deep Dive
import ast
# Python expoe sua propria AST via o modulo ast
code = 'resultado = taxa * 100 + bonus'
tree = ast.parse(code)
# NodeVisitor do Python — mesmo padrao Visitor
class NameCollector(ast.NodeVisitor):
def __init__(self):
self.names = set()
def visit_Name(self, node):
self.names.add(node.id)
self.generic_visit(node)
collector = NameCollector()
collector.visit(tree)
print(f"Variaveis usadas: {collector.names}")
# Variaveis usadas: {'resultado', 'taxa', 'bonus'}
# NodeTransformer — modifica a AST
class ConstantFolder(ast.NodeTransformer):
"""Otimizacao: calcula expressoes constantes em compile-time."""
def visit_BinOp(self, node):
self.generic_visit(node)
if isinstance(node.left, ast.Constant) and isinstance(node.right, ast.Constant):
try:
result = eval(compile(ast.Expression(node), '<ast>', 'eval'))
return ast.Constant(value=result)
except:
pass
return node
No harness.os
O knowledge graph do harness.os pode ser modelado como uma AST. As hierarquias de concerns, domains, e knowledge chunks formam uma arvore que pode ser percorrida com o Visitor pattern:
@dataclass
class HarnessNode:
pass
@dataclass
class Project(HarnessNode):
slug: str
domains: List['Domain']
@dataclass
class Domain(HarnessNode):
name: str
concerns: List['Concern']
@dataclass
class Concern(HarnessNode):
name: str
knowledge: List['Knowledge']
@dataclass
class Knowledge(HarnessNode):
title: str
content: str
tokens: int # custo em tokens
class TokenCounter(ASTVisitor):
"""Conta tokens totais por concern — para token economy."""
def visit_Project(self, node):
return sum(self.visit(d) for d in node.domains)
def visit_Domain(self, node):
return sum(self.visit(c) for c in node.concerns)
def visit_Concern(self, node):
return sum(self.visit(k) for k in node.knowledge)
def visit_Knowledge(self, node):
return node.tokens
Resumo
- Parse tree e concreta (reflete a gramatica); AST e abstrata (reflete a semantica)
- AST nodes sao modelados com dataclasses em Python
- Visitor pattern permite operacoes na arvore sem modificar os nos
- Python
ast.NodeVisitoreast.NodeTransformersao visitors built-in - O knowledge graph do harness.os e uma arvore que pode ser percorrida com visitors
Exercicio
Construa uma representacao AST para regras do harness.os e implemente dois visitors: um que conta o total de tokens (para analise de token economy) e outro que gera uma representacao textual formatada da regra.
Verifique seu entendimento
Qual e a principal diferenca entre uma parse tree e uma AST?