Ficha nº 2 Familiarização com a ferramenta JavaCC Estrutura do ficheiro JavaCC O JavaCC utiliza um ficheiro com a extensão .jj , onde são descritos, pelo utilizador, o léxico e a sintaxe da linguagem e gera um conjunto de classes Java de suporte, tendo uma delas o nome do analisador sintático (parser). As classes geradas incluem o analisador léxico (scanner), o parser e outras classes de suporte de I/O e exceções. À medida que forem percebendo o JavaCC vão também tomando conhecimento do significado das classes de suporte. O ficheiro .jj é constituído por: 1. Zona de opções (opcional): é onde pode ser definido o nível global de lookahead (o default é lookahead=1;), quantos parsers podem ser criados (o default é apenas um parser, static=true;), entre outros. 2. Unidade de compilação Java (PARSER_BEGIN(nome_do_parser) ... PARSER_END(nome_do_parser)) que define a classe principal do parser e onde pode ser incluído o método main do parser. 3. Lista de produções da gramática (as produções aceitam os símbolos +, *, e ? com o mesmo significado do da notação EBNF). JavaCC no Eclipse Usando o plug-in JavaCC para o Eclipse, para criar um ficheiro .jj basta criar um novo projeto Java, um novo package e um ficheiro JavaCC (veja http://eclipsejavacc.sourceforge.net/). Se optou por criar o seu ficheiro .jj com o template do JavaCC plugin wizard, o ficheiro criado contém a especificação de uma gramática para expressões aritméticas simples. O ficheiro criado tem o nome, por default, MyNewGrammar.jj e o nome da classe parser é eg1. Compiladores 2012-2013 | Ficha nº 2 1 Ao compilar, em JavaCC, o seu ficheiro .jj, serão automaticamente gerados os ficheiros de suporte ao seu parser, como se mostra na figura 1. Figura 1 – JavaCC no Eclipse: ficheiros gerados após a compilação do ficheiro new_file.jj criado com o wizard do JavaCC. A figura 2 mostra um exemplo de execução do ficheiro new_file.jj, cujo nome da classe do parser é eg1, criado anteriormente. Compiladores 2012-2013 | Ficha nº 2 2 Figura 2 – JavaCC no Eclipse: exemplo de execução do ficheiro new_file.jj criado com o wizard do JavaCC. Caso não pretenda utilizar o Eclipse poderá usar o NetBeans, que também tem um plug-in para o JavaCC, ou outro qualquer IDE ou editor. Exemplo de uma gramática para expressões aritméticas simples que somam ou subtraem dois números inteiros positivos Vamos supor que desejamos implementar um analisador sintáctico que reconheça expressões aritméticas com apenas um número inteiro positivo ou com uma adição ou uma subtracção de dois números inteiros positivos, por exemplo, 9, 5+3, 8-4, etc. De seguida apresenta-se uma gramática com a especificação do símbolo terminal e da regra gramatical, utilizando a notação EBNF: INTEGER = [0-9]+ // símbolo terminal Aritm → INTEGER [(“+” | “-“) INTEGER] // regra gramatical Compiladores 2012-2013 | Ficha nº 2 3 Para desenvolver, com o JavaCC, um parser que implemente esta gramática temos de criar um ficheiro onde vamos especificar o parser. Vamos chamar-lhe Exemplo.jj. Ficheiro Exemplo.jj: PARSER_BEGIN(Exemplo) // código Java que invoca o parser public class Exemplo { public static void main(String args[]) throws ParseException { // criação do objecto utilizando o constructor com o argumento // para ler do standard input (teclado) Exemplo parser = new Exemplo(System.in); parser.Aritm(); } } PARSER_END(Exemplo) // símbolos que não devem ser considerados na análise léxica SKIP : { “ “ | “\t” | “\r” } // definição dos tokens (símbolos terminais) TOKEN : { < INTEGER : ([“0”–“9”])+ > | < LF : “\n” > } // definição da produção void Aritm() : {} { <INTEGER> ( (“+” | “-“) <INTEGER> )? <LF> // “(...)?” é equivalente a “[...]” } Para gerar o parser da linha de comandos: javacc Exemplo.jj Compiladores 2012-2013 | Ficha nº 2 4 Para compilar o código Java gerado: javac *.java para executar o analisador sintáctico: java Exemplo Poderemos associar às funções referentes aos símbolos não-terminais pedaços de código Java. Por exemplo, as modificações apresentadas de seguida permitem escrever no ecrã mensagens a indicar os números que são lidos pelo parser: void Aritm() : {Token t1, t2;} { t1=<INTEGER> { System.out.println(“Integer = “+t1.image); } ( (“+” | “-“) t2=<INTEGER> { System.out.println(“Integer = “+t2.image); } )? <LF> } Para cada símbolo terminal INTEGER, foi inserida uma linha de código Java, entre chavetas, que imprime no ecrã o valor do token lido (o atributo image da classe Token retorna uma String representativa do valor do token). Posteriormente, serão apresentados outros métodos. Insira estas modificações no ficheiro Exemplo.jj e volte a repetir o processo até à execução do parser. Verifique o que acontece. Exercícios 1. Altere o código anterior para que seja escrito no ecrã o sinal ‘+’ ou ‘-‘ lido. 2. Altere o código anterior para que sejam permitidas multiplicações e divisões. 3. Altere o código anterior para que seja possível reconhecer sequências de expressões separadas por ponto e vírgula. Utilize o token <EOF>, já prédefinido no JavaCC, para demarcar o fim da sequência. Compiladores 2012-2013 | Ficha nº 2 5 4. Construa um analisador que reconhece uma sequência de parêntesis correctamente emparelhados (balanceados) e escreve no ecrã o seu nível de encaixe. Por exemplo, o output do programa para a string “((( )) )” é 3 . Compiladores 2012-2013 | Ficha nº 2 6