E → T E'
E' → + T E' | ε
T → F T'
T' → * F T' | ε
F → ( E ) | num
+
, *
, parênteses e números).→
significa “pode ser substituído por”.|
significa “ou” (escolha de alternativa).ε
significa vazio, ou seja, não tem nada (pode parar por aí).E'
).E'
representa repetições de + T
.+
, usamos ε
(vazio) e paramos.T'
).*
, paramos.num
), ou(E)
).num + ( num * num )
E
(expressão). E
E → T E'
: T E'
T → F T'
: F T' E'
F → num
(porque o primeiro símbolo da entrada é num
): num T' E'
num
vem +
e não *
, usamos T' → ε
: num E'
E'
pode ser + T E'
, porque vem um +
: num + T E'
T
, a entrada começa com (
, então em F → ( E )
: num + ( E ) T' E'
E
): num + ( T E' ) T' E'
T
vira F T'
, e F → num
: num + ( num T' E' ) T' E'
*
, usamos T' → * F T'
:num + ( num * F T' E' ) T' E'
F
seguinte também vira num
:num + ( num * num T' E' ) T' E'
*
nem +
dentro, então os T'
e E'
viram ε
:num + ( num * num )
E
/ \
T E'
| + T
F |
| F
num (E)
|
T E'
|
F T'
| \
num * F
|
num
num + ( num * num )
é válido,E
├── T
│ ├── F
│ │ └── num
│ └── T' (ε)
└── E'
├── +
├── T
│ ├── F
│ │ ├── "("
│ │ ├── E
│ │ │ ├── T
│ │ │ │ ├── F
│ │ │ │ │ └── num
│ │ │ │ └── T'
│ │ │ │ ├── *
│ │ │ │ ├── F
│ │ │ │ │ └── num
│ │ │ │ └── T' (ε)
│ │ │ └── E' (ε)
│ │ └── ")"
│ └── T' (ε)
└── E' (ε)
(+)
/ \
num (*)
/ \
num num
E → T E'
E' → + T E' | ε
T → F T'
T' → * F T' | ε
F → ( E ) | num
num + ( num * num ) $
($
= fim da entrada)Regras de decisão (LL(1), por lookahead):
Em F:num
→F→num
;(
→F→(E)
.
Em T':*
→T'→* F T'
;+
ou)
ou$
→T'→ε
.
Em E':+
→E'→+ T E'
;)
ou$
→E'→ε
.
num + ( num * num )
é aceita. Observe como:E'
implementa “repetições de + T
” (soma iterativa).T'
implementa “repetições de * F
” (multiplicação iterativa).F
decide entre número ou parênteses, permitindo precedência correta: o *
é resolvido dentro dos parênteses antes do +
.E
T E'
F T' E'
num T' E'
num E'
num + T E'
num + F T' E'
num + ( E ) T' E'
num + ( T E' ) T' E'
num + ( F T' E' ) T' E'
num + ( num T' E' ) T' E'
num + ( num * F T' E' ) T' E'
num + ( num * num T' E' ) T' E'
num + ( num * num E' ) T' E'
num + ( num * num ) T' E'
num + ( num * num ) E'
num + ( num * num )
num * num + num $
($
= fim) Gramática:E → T E'
E' → + T E' | ε
T → F T'
T' → * F T' | ε
F → ( E ) | num
num
→ F→num
; (
→ F→(E)
.*
→ T'→* F T'
; +
)
$
→ T'→ε
.+
→ E'→+ T E'
; )
$
→ E'→ε
.*
após o primeiro num
, o parser escolhe T'→* F T'
antes de permitir qualquer +
em E'
. Assim, ele fecha a multiplicação primeiro e só depois processa a soma — exatamente a precedência desejada: (num * num) + num
E
T E'
F T' E'
num T' E'
num * F T' E'
num * num T' E'
num * num E'
num * num + T E'
num * num + F T' E'
num * num + num T' E'
num * num + num E'
num * num + num
num * num + num
, usando a mesma gramática LL:E
├── T
│ ├── F
│ │ └── num
│ └── T'
│ ├── *
│ ├── F
│ │ └── num
│ └── T' (ε)
└── E'
├── +
├── T
│ ├── F
│ │ └── num
│ └── T' (ε)
└── E' (ε)
(+)
/ \
(*) num
/ \
num num
num + num $
E → T E'
E' → + T E' | ε
T → F T'
T' → * F T' | ε
F → ( E ) | num
E
├── T → F → num
└── E'
├── +
├── T → F → num
└── E' → ε
(+)
/ \
num num
num * num $
E
├── T
│ ├── F → num
│ └── T'
│ ├── *
│ └── F → num
└── E' → ε
(*)
/ \
num num
num + num * num $
num
como T.+
→ entra em + T E'
.num * num
, então escolhe T' → * F T'
.(num * num)
dentro do lado direito do +
. (+)
/ \
num (*)
/ \
num num
( num + num ) * num $
num + num
.num
. (*)
/ \
(+) num
/ \
num num
num * num * num $
T'
.num
encadeados por *
.( num * num ) + num $
+
na raiz, (*)
no lado esquerdo.num + num + num $
+
.+
encadeados (associatividade à esquerda).-
. Sugestão: alterar apenas E':E' → (+|-) T E' | ε
num - num + num
. Construa a árvore de derivação e a AST./
na gramática, modificando T':T' → (*|/) F T' | ε
num / num * num
.lark
ou uma função manual). Vou usar lark
porque é leve e próxima da ideia de "fly parsing":from lark import Lark, Tree, Token
# Gramática LL para expressões simples
grammar = r"""
?start: expr
?expr: term expr_rest
?expr_rest: "+" term expr_rest
| "-" term expr_rest
| -> empty
?term: factor term_rest
?term_rest: "*" factor term_rest
| "/" factor term_rest
| -> empty
?factor: NUMBER
| "(" expr ")"
NUMBER: /[0-9]+/
%ignore " "
"""
parser = Lark(grammar, start="start")
entrada = "2 + 3 * 4"
tree = parser.parse(entrada)
print(tree.pretty())
"5 * 6 + 7"
e compare a árvore.entrada = "(2 + 3) * 4"
tree = parser.parse(entrada)
print(tree.pretty())
try:
entrada = "2 + * 3"
tree = parser.parse(entrada)
print(tree.pretty())
except Exception as e:
print("Erro de parsing:", e)
"* 2 + 3"
, "2 +"
e veja o erro.-
em expr_rest
. Teste:entrada = "10 - 3 + 2"
tree = parser.parse(entrada)
print(tree.pretty())
"10 - 3 - 2"
e interprete./
. Teste:entrada = "12 / 3 * 2"
tree = parser.parse(entrada)
print(tree.pretty())
"12 / (3 * 2)"
.Transformer
do Lark:from lark import Transformer
class EvalExpr(Transformer):
def NUMBER(self, token):
return int(token)
def expr(self, items):
val = items[0]
for op, nxt in zip(items[1::2], items[2::2]):
if op == '+': val += nxt
if op == '-': val -= nxt
return val
def expr_rest(self, items):
return items
def term(self, items):
val = items[0]
for op, nxt in zip(items[1::2], items[2::2]):
if op == '*': val *= nxt
if op == '/': val //= nxt
return val
def term_rest(self, items):
return items
entrada = "2 + 3 * (4 - 1)"
tree = parser.parse(entrada)
calc = EvalExpr().transform(tree)
print("Resultado:", calc)
float
em vez de int
./
) em vez de inteira (//
)."10 / 4"
."7 * (2 + 5)"
."2 ++ 3"
.^
) na gramática com maior precedência que *
e /
."2 + 3 * 4"
vira 2 + (3 * 4)
).E → E + T | T
E → T E'
E' → + T E' | ε
E → T E'
E' → + T E' | ε
T → F T'
T' → * F T' | ε
F → ( E ) | id
E → T E'
E' → + T E' | ε
T → F T'
T' → * F T' | ε
F → ( E ) | id
class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.pos = 0
self.lookahead = tokens[0]
def match(self, expected):
if self.lookahead == expected:
self.pos += 1
if self.pos < len(self.tokens):
self.lookahead = self.tokens[self.pos]
else:
self.error(f"Esperado {expected}")
def error(self, message):
raise Exception(message)
def parse(self):
self.expr()
if self.pos < len(self.tokens):
self.error("Tokens extras após fim da expressão")
# Para a gramática:
# E → T E'
# E' → + T E' | ε
# T → F T'
# T' → * F T' | ε
# F → ( E ) | id
def expr(self):
self.term()
self.expr_prime()
def expr_prime(self):
if self.lookahead == '+':
self.match('+')
self.term()
self.expr_prime()
# ε caso (não precisa fazer nada)
def term(self):
self.factor()
self.term_prime()
def term_prime(self):
if self.lookahead == '*':
self.match('*')
self.factor()
self.term_prime()
# ε caso
def factor(self):
if self.lookahead == '(':
self.match('(')
self.expr()
self.match(')')
elif self.lookahead == 'id':
self.match('id')
else:
self.error("Esperado '(' ou 'id'")
expr → term { ('+' | '-') term }
term → factor { ('*' | '/') factor }
factor → number | '(' expr ')'
def expr(self):
# Primeiro reconhece um term
self.term()
# Depois processa sequência de + ou - seguidos de term
while self.lookahead in ('+', '-'):
operator = self.lookahead
self.match(operator)
self.term()
# Aqui poderia adicionar código para
# construir a árvore ou avaliar a expressão
self.lookahead = self.tokens[self.pos]
def match(self, expected):
if self.lookahead == expected:
self.pos += 1
if self.pos < len(self.tokens):
self.lookahead = self.tokens[self.pos]
else:
self.lookahead = '$' # Fim da entrada
else:
self.error(f"Esperado {expected}, encontrado {self.lookahead}")
def tokenize(text):
tokens = []
i = 0
while i < len(text):
# Ignora espaços em branco
if text[i].isspace():
i += 1
continue
# Operadores e parênteses
if text[i] in "+-*/()":
tokens.append(text[i])
i += 1
continue
# Números
if text[i].isdigit():
start = i
while i < len(text) and text[i].isdigit():
i += 1
tokens.append(int(text[start:i]))
continue
# Caractere desconhecido
raise Exception(f"Caractere inválido: {text[i]}")
return tokens
def error(self, message):
line = self.get_current_line()
column = self.get_current_column()
error_message = f"Erro sintático na linha {line}, coluna {column}: {message}"
# Exibir o contexto do erro
line_content = self.get_line_content(line)
pointer = ' ' * (column - 1) + '^'
print(error_message)
print(line_content)
print(pointer)
raise SyntaxError(error_message)
def expr(self):
# Primeiro reconhece um term
left = self.term()
# Depois processa sequência de + ou - seguidos de term
while self.lookahead in ('+', '-'):
operator = self.lookahead
self.match(operator)
right = self.term()
# Cria um nó para a operação binária
left = Node('BinOp', operator, [left, right])
return left
expr → term { '+' term }
term → factor { '*' factor }
factor → number | '(' expr ')'
class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.pos = 0
self.lookahead = tokens[0]
def match(self, expected):
# Implementar
def expr(self):
# Implementar
def term(self):
# Implementar
def factor(self):
# Implementar
expr → term { ('+' | '-') term }
term → factor { ('*' | '/') factor }
factor → number | '(' expr ')'
stmt → if_stmt | other_stmt
if_stmt → 'if' '(' expr ')' stmt ['else' stmt]
expr → 'true' | 'false'
other_stmt → 'A' | 'B'
# Problema: recursão à esquerda
expr → expr + term | term
# Solução
expr → term expr'
expr' → + term expr' | ε
# Problema: ambiguidade no if-else
stmt → if expr then stmt | if expr then stmt else stmt
# Solução
stmt → matched_stmt | unmatched_stmt
matched_stmt → if expr then matched_stmt else matched_stmt | other_stmt
unmatched_stmt → if expr then stmt | if expr then matched_stmt else unmatched_stmt