JavaCC (Java Compiler-Compiler), chamado de Jack inicialmente, é uma ferramenta geradora de analisadores sintáticos criada pela SUN para a comunidade de programadores de Java. Constitui uma ferramenta poderosa, flexível e simples de usar, que gera um código compilado muito mais fácil de ser lido pelo programador do que o gerador pelo yacc.

JavaCC aproveita todas as características da linguagem Java para prover facilidades na construção de analisadores sintáticos. Principalmente o fato de Java ser orientada a objetos torna o uso de JavaCC proveitoso, facilitando a geração e adaptação dos códigos que avaliam os nós da árvore sintática. Uma outra característica da linguagem Java, o tratamento de exceções, torna o gerenciamento de erros sintáticos de mais fácil implementação e leitura. Além dessa facilidades, o pacote de JavaCC oferecido livremente pela SUN dispõe de duas ferramentas, além do próprio JavaCC: o jjtree e o jjdoc.

O jjdoc é um utilitário que lê uma especificação JavaCC e gera um arquivo HTML com toda a gramática da linguagem na notação BNF. Aproveitando-se de links internos, podemos facilmente navegar entre os símbolos não-terminais. O jjdoc é útil para documentação e para se verificar se a gramática foi corretamente descrita no arquivo JavaCC.

O jjtree é um poderoso utilitário para criação e manipulação de árvores sintáticas, o que ferramentas como yacc não fazem. Sua utilização é opcional, mas não deve ser deixado de lado quando se deseja criar compiladores com JavaCC. O jjtree trabalha sobre um arquivo ligeiramente modificado de JavaCC, com extensão .jjt, com a adição de opções para o programa e de ações semânticas sobre a árvore sintática. Ele gera uma especificação para JavaCC, que não é muito diferente do arquivo original acrescido de melhores definições das ações semânticas, além de gerar os arquivos com as classes que implementam a árvore sintática. A utilização das três ferramentas segue a ordem indicada na figura.

Um arquivo de especificação JavaCC contém três blocos: opções do JavaCC, classes do parser e gramática da linguagem, com ações semânticas associadas. O primeiro bloco, opcional, contém um conjunto de opções que definem como o JavaCC irá gerar o parser como se será sensível ao case, se gerará todas as classes usuais, se haverá checagem de ambiguidade na gramática, etc. Seu formato é options{<opção1>=<valor>;<opção2>=<valor>; ...}.

O bloco seguinte define qual é o nome da classe do parser e implementa sua chamada. Deve iniciar com PARSER_BEGIN(<nome>) e terminar com PARSER_END(<nome>), onde <nome> define o nome da classe gerada que será o parser gerado. Dentro do bloco deve ser implementada a classe com o nome, com pelo menos o método main, chamado durante a execução do parser, e no seu código deve criar um objeto da classe <nome> e chamar o parser usando o método de mesmo nome do símbolo não-terminal inicial, como mostrado no exemplo a seguir.

PARSER_BEGIN(Simple2)
public class Simple2 {
public static void main(String args[]) throws ParseException {
Simple2 parser = new Simple2(System.in);
parser.Input();
}
}
PARSER_END(Simple2)
Neste caso, o JavaCC gerará os arquivos Simple2.java (parser) Simple2TokenManager.java (analisador léxico) e Simple2Constants.java (arquivo com constantes úteis).

O restante da especificação JavaCC conterá a especificação dos tokens da linguagem e da sua gramática. O JavaCC produz então o analisador léxico e sintático do compilador, não necessitando de uma implementação ou ferramenta extras.

Os tokens são especificados num bloco próprio, como o exemplo abaixo em que é definido o token EOL (fim de linha) como sendo o caracter de newline.

TOKEN :
{ }
{
< EOL: "\n" >
}
Existe ainda uma classe de tokens especiais, porque são tratadas de forma diferentes pelo parser. São os tokens colocados dentro do bloco SKIP, MORE e SPECIAL_TOKEN. As expressões colocadas dentro do bloco de SKIP, por exemplo, são descartadas pelo parser e por isso são muito úteis para se descartar espaços em brancos, caracteres de newline, tabulações e comentários. Os outros tipos de tokens não são avaliados pelo algoritmo do parser, mas também não são totalmente descartado, podendo o programador efetuar avaliações sobre eles e o estado corrente do parser para tomar decisões. Do ponto de vista do parser, as expressões colocadas nesses blocos não são tokens, porque o parser não os reconhece como tal. Mas do ponto de vista do fluxo de caracteres de entrada eles são reconhecidos, mas as ações associadas a eles ou são nulas ou não estão definidas no parser.

O bloco seguinte, e mais importante, contém a definição da gramática da linguagem. JavaCC utiliza uma notação diferente para definir a gramática, que é mais próxima da implementação realizada. Cada símbolo não-terminal é escrito na notação de uma função (sem valor de retorno ou parâmetro), pode ter inicializações de variáveis ou comandos associados e as ações semânticas são escritas em código Java puro. Supondo o mesmo exemplo já usado de uma gramática para expressões aritméticas, a especificação JavaCC ficará como mostrado a seguir.

E():
{ Token t;}
{
E() `+` E() { /* ação */ } |
t=<ID> { tabsimb.inst(t.image); /* ação */ }
}
A justificativa para se usar a notação de função para cada símbolo não-terminal é que cada um desses símbolos será uma chamada do parser, além de podermos efetuar atribuições dos valores retornados dos símbolos não-terminais, produto da avaliação de seus nós, podem ser usando dentro da própria ação semântica. Esse foi o caso para um ID, cujo resultado (na verdade a cadeia de caracteres do token) foi atribuído a t e usando na ação semântica para instalar um identificador. Observe que t foi declarado no bloco de declarações do não-terminal.

Ações semânticas mais eficientes devem ser feitas com auxílio de jjtree.


Vamos implementar em JavaCC a mesma gramática de expressões regulares mostrada anteriormente:

E -> E + E | E * E | ( E ) | - E | id
O código escrito pode ser visto no arquivo fonte grambig.jj. Quando compilado pelo JavaCC, é gerada a seguinte saída.
Symantec Java! ByteCode Compiler Version 1.02a
Copyright (C) 1996 Symantec Corporation
Java Compiler Compiler Version 0.7pre5 (Parser Generator)
Copyright (c) 1996, 1997 Sun Microsystems Inc.
(type "javacc" with no arguments for help)
Reading from file grambig.jj . . .
Error: Line 49, Column 1: Left recursion detected: "E... --> E..."
Detected 1 errors and 0 warnings.
Por que esse erro acontece e o que significa? Proponha uma solução para ele.

VoltarHomePróximoe-mail