O objetivo desta aula é apresentar a ferramenta ANTLR, que permite fazer a geração automática de parsers a partir da definição de gramáticas livres de contexto. Siga o tutorial descrito abaixo.
ATENÇÃO! ANTLR depende de Java, então o Java Development Kit (JDK) deve ser instalado primeiro.
~/antlr4
).export CLASSPATH=".:/usr/local/lib/antlr-4.13.1-complete.jar:$CLASSPATH"
alias antlr4='java -Xmx500M -cp "/usr/local/lib/antlr-4.13.1-complete.jar.jar:$CLASSPATH" org.antlr.v4.Tool'
alias grun='java -Xmx500M -cp "/usr/local/lib/antlr-4.13.1-complete.jar.jar:$CLASSPATH" org.antlr.v4.gui.TestRig'
pip
ou pip3
:
pip3 install antlr4-python3-runtime
Crie um arquivo chamado Arithmetic.g4
e escreva a seguinte gramática:
grammar Arithmetic;
// Regras do Parser
expr: term ( (PLUS | MINUS) term )* ;
term: factor ( (MUL | DIV) factor )* ;
factor: INT | LPAREN expr RPAREN ;
// Regras do Lexer
PLUS: '+' ;
MINUS: '-' ;
MUL: '*' ;
DIV: '/' ;
INT: [0-9]+ ;
LPAREN: '(' ;
RPAREN: ')' ;
WS: [ \t\r\n]+ -> skip ;
Execute o seguinte comando para gerar o código do parser e do lexer:
antlr4 -Dlanguage=Python3 Arithmetic.g4
Crie um arquivo Python chamado main.py
com o seguinte código:
from antlr4 import *
from ArithmeticLexer import ArithmeticLexer
from ArithmeticParser import ArithmeticParser
class ArithmeticVisitor:
def visit(self, ctx):
if isinstance(ctx, ArithmeticParser.ExprContext):
return self.visitExpr(ctx)
elif isinstance(ctx, ArithmeticParser.TermContext):
return self.visitTerm(ctx)
elif isinstance(ctx, ArithmeticParser.FactorContext):
return self.visitFactor(ctx)
def visitExpr(self, ctx):
result = self.visit(ctx.term(0))
for i in range(1, len(ctx.term())):
if ctx.getChild(i * 2 - 1).getText() == '+':
result += self.visit(ctx.term(i))
else:
result -= self.visit(ctx.term(i))
return result
def visitTerm(self, ctx):
result = self.visit(ctx.factor(0))
for i in range(1, len(ctx.factor())):
if ctx.getChild(i * 2 - 1).getText() == '*':
result *= self.visit(ctx.factor(i))
else:
result /= self.visit(ctx.factor(i))
return result
def visitFactor(self, ctx):
if ctx.INT():
return int(ctx.INT().getText())
else:
return self.visit(ctx.expr())
def main():
expression = input("Digite uma expressão aritmética: ")
lexer = ArithmeticLexer(InputStream(expression))
stream = CommonTokenStream(lexer)
parser = ArithmeticParser(stream)
tree = parser.expr()
visitor = ArithmeticVisitor()
result = visitor.visit(tree)
print("Resultado:", result)
if __name__ == '__main__':
main()
Execute o programa main.py
:
python3 main.py
Agora você pode inserir expressões aritméticas envolvendo soma, subtração, multiplicação, e divisão, e o programa avaliará e imprimirá o resultado.
Objetivo: Evoluir a gramática para suportar variáveis, atribuições e um ambiente REPL (Read-Eval-Print Loop) simples. Isso permitirá explorar como lidar com o estado dentro da gramática e como construir um interpretador mais interativo.
Instruções:
Evolua a Gramática: Modifique o arquivo Arithmetic.g4
para incluir regras para variáveis e atribuições. Eis um ponto de partida:
// Novas Regras do Parser
program: statement+ ;
statement: assignment | expr ;
assignment: VAR ASSIGN expr ;
// Novas Regras do Lexer
VAR: [a-zA-Z]+ ;
ASSIGN: '=' ;
Modifique o Visitor: Atualize a classe ArithmeticVisitor
para lidar com variáveis e atribuições. Você precisará armazenar variáveis em um dicionário e atualizá-las conforme as atribuições forem feitas.
Implemente um REPL: Modifique o programa principal para ler e avaliar instruções em um loop, imprimindo os resultados das expressões e permitindo que as variáveis persistam entre as entradas.
Teste a nova gramática: Forneça exemplos que demonstrem a nova funcionalidade, como:
x = 5
y = x + 3
y * 2
Critérios de Avaliação:
Desafio Bônus: Evolua ainda mais a gramática para suportar construções mais complexas, como expressões condicionais (if
/else
), loops ou funções.