Uma Árvore de Sintaxe Abstrata (AST) é uma representação em árvore da estrutura sintática abstrata do código-fonte, onde cada nó denota uma construção importante na linguagem.
a + b * c
, a AST reflete corretamente a precedência de operadores. O operador de multiplicação *
forma um subárvore com b
e c
, e este subárvore inteiro torna-se o operando direito da operação de adição com a
.b * c
e depois a adição do resultado com a
, sem necessidade de parênteses ou regras adicionais.class Node:
def __init__(self):
pass
def __str__(self):
pass
class BinOp(Node):
def __init__(self, left, op, right):
self.left = left
self.op = op
self.right = right
def __str__(self):
return f"({self.left} {self.op} {self.right})"
class Num(Node):
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)
class Var(Node):
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
class AST:
pass
class BinOp(AST):
def __init__(self, left, op, right):
self.left = left
self.op = op
self.right = right
class UnaryOp(AST):
def __init__(self, op, expr):
self.op = op
self.expr = expr
class Assign(AST):
def __init__(self, left, right):
self.left = left
self.right = right
class Var(AST):
def __init__(self, token):
self.token = token
self.value = token.value
class Num(AST):
def __init__(self, token):
self.token = token
self.value = token.value
# Construção manual da AST para "a + b * c"
a = Var(Token('ID', 'a'))
b = Var(Token('ID', 'b'))
c = Var(Token('ID', 'c'))
# Primeiro b * c (precedência maior)
mult = BinOp(b, Token('MUL', '*'), c)
# Depois a + (b * c)
expr = BinOp(a, Token('PLUS', '+'), mult)
# A variável expr agora contém a AST completa
a + b * c
, respeitando a precedência de operadores. Na prática, essa construção seria automatizada pelo analisador sintático.def preorder(node):
if node is None:
return
print(node)
preorder(node.left)
preorder(node.right)
a + b * c
: +, a, *, b, cdef postorder(node):
if node is None:
return
postorder(node.left)
postorder(node.right)
print(node)
a + b * c
: a, b, c, *, +import ast
# Código fonte para análise
source = "a = b + c * d"
# Parsear o código para AST
tree = ast.parse(source)
# Imprimir a AST de forma legível
print(ast.dump(tree, indent=4))
# Resultado:
# Module(
# body=[
# Assign(
# targets=[Name(id='a', ctx=Store())],
# value=BinOp(
# left=Name(id='b', ctx=Load()),
# op=Add(),
# right=BinOp(
# left=Name(id='c', ctx=Load()),
# op=Mult(),
# right=Name(id='d', ctx=Load())
# )
# )
# )
# ]
# )
ast
que permite analisar código Python e gerar ASTs automaticamente, facilitando manipulação e análise de código.resultado = operando1 operador operando2
. Cada instrução realiza uma única operação com no máximo dois operandos.# Tripla para: a = b + c * d
(*, c, d) # t1 = c * d
(+, b, t1) # t2 = b + t1
(=, t2, a) # a = t2
# Quadrupla para: a = b + c * d
(*, c, d, t1) # t1 = c * d
(+, b, t1, t2) # t2 = b + t1
(=, t2, -, a) # a = t2
Assign
├── Var: a
└── BinOp: +
├── Var: b
└── BinOp: *
├── Var: c
└── Var: d
t1 = c * d
t2 = b + t1
a = t2
t1 = c * d
t2 = b + t1
a = t2
c * d
e depois adicionando b
.x = a + b * (c - d)
Assign
├── Var: x
└── BinOp: +
├── Var: a
└── BinOp: *
├── Var: b
└── BinOp: -
├── Var: c
└── Var: d
c
→ nada a gerar, usar diretamented
→ nada a gerar, usar diretamentec - d
→ gerar t1 = c - d
b
→ nada a gerar, usar diretamenteb * (c - d)
→ gerar t2 = b * t1
a
→ nada a gerar, usar diretamentea + b * (c - d)
→ gerar t3 = a + t2
x = ...
→ gerar x = t3
t1 = c - d
t2 = b * t1
t3 = a + t2
x = t3
class Lexer:
def __init__(self, text):
self.text = text
self.pos = 0
self.current_char = self.text[self.pos] if self.text else None
def advance(self):
self.pos += 1
if self.pos >= len(self.text):
self.current_char = None
else:
self.current_char = self.text[self.pos]
def get_next_token(self):
# Implementação simplificada do analisador léxico
# ...
class Parser:
def __init__(self, lexer):
self.lexer = lexer
self.current_token = self.lexer.get_next_token()
def eat(self, token_type):
if self.current_token.type == token_type:
self.current_token = self.lexer.get_next_token()
else:
raise Exception('Erro de sintaxe')
def expr(self):
# Implementação do analisador sintático
# ...
def parse(self):
return self.expr()
def expr(self):
# expr : term ((PLUS | MINUS) term)*
node = self.term()
while self.current_token.type in (PLUS, MINUS):
token = self.current_token
if token.type == PLUS:
self.eat(PLUS)
node = BinOp(left=node, op=token, right=self.term())
elif token.type == MINUS:
self.eat(MINUS)
node = BinOp(left=node, op=token, right=self.term())
return node
def term(self):
# term : factor ((MUL | DIV) factor)*
node = self.factor()
while self.current_token.type in (MUL, DIV):
token = self.current_token
if token.type == MUL:
self.eat(MUL)
node = BinOp(left=node, op=token, right=self.factor())
elif token.type == DIV:
self.eat(DIV)
node = BinOp(left=node, op=token, right=self.factor())
return node
class TACGenerator:
def __init__(self):
self.code = []
self.temp_counter = 0
def new_temp(self):
temp = f"t{self.temp_counter}"
self.temp_counter += 1
return temp
def visit_BinOp(self, node):
# Visita recursivamente a subárvore esquerda
left = self.visit(node.left)
# Visita recursivamente a subárvore direita
right = self.visit(node.right)
# Gera uma nova variável temporária para o resultado
temp = self.new_temp()
# Emite a instrução de três endereços
self.code.append((node.op.value, left, right, temp))
# Retorna o nome da variável temporária
return temp
def visit_Num(self, node):
# Para números literais, apenas retorna o valor
return str(node.value)
def visit_Var(self, node):
# Para variáveis, apenas retorna o nome
return node.value
def visit(self, node):
# Despacha para o método de visita apropriado
method_name = f'visit_{type(node).__name__}'
visitor = getattr(self, method_name, self.generic_visit)
return visitor(node)
def generic_visit(self, node):
raise Exception(f'Nenhum método de visita para {type(node).__name__}')
class QuadrupleGenerator(TACGenerator):
def __init__(self):
super().__init__()
def visit_BinOp(self, node):
# Visita recursivamente a subárvore esquerda
left = self.visit(node.left)
# Visita recursivamente a subárvore direita
right = self.visit(node.right)
# Gera uma nova variável temporária para o resultado
temp = self.new_temp()
# Emite a instrução quadrupla
self.code.append((node.op.value, left, right, temp))
# Retorna o nome da variável temporária
return temp
def visit_Assign(self, node):
# Visita recursivamente o lado direito da atribuição
right = self.visit(node.right)
# Obtém o nome da variável alvo
left = node.left.value
# Emite a instrução de atribuição
self.code.append(('=', right, None, left))
# Não há valor de retorno para uma atribuição
return None
resultado = (x + y) * (z - 10) / 2
. Desenhe a AST e gere manualmente o código de três endereços (tripla) correspondente.Assign
├── Var: resultado
└── BinOp: /
├── BinOp: *
│ ├── BinOp: +
│ │ ├── Var: x
│ │ └── Var: y
│ └── BinOp: -
│ ├── Var: z
│ └── Num: 10
└── Num: 2
t1 = x + y
t2 = z - 10
t3 = t1 * t2
t4 = t3 / 2
resultado = t4
Na próxima aula, abordaremos a Geração de Código Intermediário, onde veremos como transformar sistematicamente ASTs em código intermediário e aplicar otimizações iniciais. Estudaremos padrões para estruturas de controle como if, while e for, além de como implementar chamadas de função.