Você está na página 1de 27

JLex e JCup

Mayerber Carvalho Neto


JLex: A lexical analyzer
generator for Java™
 Gerador de analisadores léxicos
(scanners)

 Baseado no ‘lex’ do UNIX

 Por que usar uma ferramenta do


tipo JLex?
JLex: Arquivo de
Especificação
 O arquivo de especificação (.lex)
é dividido em três seções:
– Código do usuário
– Diretivas
– Regras de Expressões Regulares
 Cada seção é separada da seção
seguinte por uma linha contendo
apenas ‘%%’
JLex: código do usuário
 O código escrito nessa seção é
copiado diretamente no topo do
arquivo do scanner.

 Útil para declarar “imports”,


classes e métodos auxiliares.
JLex: diretivas
 Nessa seção são declarados macros e
nomes de estados.
 Diretivas disponíveis:
– %{...%}
 Permite que sejam declaradas variáveis e
métodos do scanner.
 Por exemplo, você pode declarar uma variável
‘int num_de_comentarios’.
 Evitar nomes de variáveis e métodos que
comecem com ‘yy’ para não dar conflito com as
variáveis internas do scanner.
JLex: diretivas (cont.)
– %init{...%init}
 Tudo que você escrever entre as chaves vai ser
copiado diretamente para o método construtor
da classe do scanner.
– %eof{...%eof}
 Permite declarar código que vai ser executado
quando o scanner encontrar o fim do arquivo de
entrada.
– %char
 Ativa o contador de caracteres através da
variável inteira yychar (útil para mensagens de
erro).
– %line
 Ativa o contador de linhas através da variável
inteira yyline (útil para mensagens de erro).
JLex: diretivas (cont.)
– %cup
 Ativa a compatibilidade com o JCup. Isso
significa que a classe gerada do scanner vai
implementar a interface
java_cup.runtime.Scanner
– %class <nome>
 Muda o nome da classe do scanner (default =
Yylex).
– %function <nome>
 Muda o nome do método de “tokenização”
(default = yylex).
– %type <nome_do_tipo>
 Muda o tipo retornado pelo método de
“tokenização” (default = Yytoken).
JLex: diretivas (cont.)
– %notunix
 Se você for usar o JLex no Windows, utilize essa
diretiva para que o scanner gerado trate a
seqüência “\r\n” como “\n”.
– %eofval{...%eofval}
 O código escrito entre as chaves deve retornar
um valor cujo tipo é o mesmo que aquele
retornado pelo método de “tokenização”. Esse
valor vai ser retornado sempre que o método de
“tokenização” for chamado e o scanner tenha
encontrado EOF.
 Há mais diretivas no manual do JLex.
JLex: diretivas (cont.)
 Macros
– Cada macro deve estar contida
numa única linha.
– Formato:
 <nome> = <definição>
– O nome da macro deve começar
com uma letra ou ‘_’.
– A definição da macro é uma
expressão regular.
JLex: diretivas (cont.)
 Macros podem conter outras
macros.

 Exemplos:
– DIGITO = [0-9]
– ALFA = [A-Za-z]
– ESPACO_EM_BRANCO = [\n\r\x20\t]
– NUM_NATURAL = {DIGITO}+
JLex: diretivas (cont.)
 Estados
– Permite implementar uma máquina
de estados no scanner.
– Todo scanner tem pelo menos um
estado (declarado internamente)
chamado YYINITIAL.
– Exemplo:
 %state COMMENT
JLex: regras de
expressões regulares
 Formato das regras:
– [<estados>] <expressão> { <ação> }

 [<estados>] – opcional. Formato:


– <estado0, estado1, ..., estadoN>
– Se uma regra for precedida por uma lista de
estados, o scanner só tentará aplicar a regra se ele
estiver em um dos estados listados.
– Se uma lista de estados não for especificada para
uma regra, o scanner sempre tentará aplicar a
regra independentemente do seu estado atual.
JLex: regras de
expressões regulares
(cont.)
 <expressão> – obrigatório.
– baseado em expressões regulares.
– Símbolos especiais:
 | - representa uma opção. Exemplo: e|f significa
que a expressão pode casar com e ou f.
 . (ponto) – casa com qualquer caráter, exceto o
‘\n’.
 * - fecho de Kleene. Casa com zero ou mais
repetições da expressão regular precedente.
Exemplo: [a-z]* casa com {ε, a, aa, ab, ...}
 + - casa com uma ou mais repetições da
expressão regular precedente.
Exemplo: [0-9]+ casa com qualquer número
natural.
JLex: regras de
expressões regulares
(cont.)
– Símbolos especiais (cont.):
 ? – casa com zero ou uma ocorrência da
expressão regular precedente.
Exemplo: [+-]?[0-9]+ casa com
números naturais precedidos ou não por
um sinal de ‘-’ ou ‘+’  {0, 1, -1, +1,
-123, +456, ...}
 (...) – os parênteses são usados para
agrupar expressões regulares.
Exemplo: (ab)*  {ε, ab, abab, ababac,
...} enquanto que ab*  {a, ab, abb,
abbb, ...}
JLex: regras de
expressões regulares
(cont.)
– Símbolos especiais (cont.):
 [...] – usado para denotar uma classe de
caracteres.
– Exemplo: [a-z] casa com qualquer letra de
‘a’ até ‘z’.
– Se o símbolo seguinte ao ‘[‘ for o circunflexo
(^), o conteúdo do [...] é negado.
– Exemplo: [^0-9] casa com tudo exceto
dígitos.
JLex: regras de
expressões regulares
(cont.)
 Expressões podem conter macros
desde que essas sejam escritas
entre chaves.
– Exemplos:
 {DIGITO}+ representa uma expressão
que casa com os números naturais.
 {ALFA}({ALFA}|{DIGITO}|_)* é a
expressão que casa com nomes de
variáveis na maioria das linguagens de
programação.
JLex: regras de
expressões regulares
(cont.)
 <ação> - obrigatório.
– Uma ação é o trecho de código que deve
ser executado quando uma regra for
aplicada pelo scanner. Esse trecho de
código deve retornar um valor equivalente
àquele retornado pelo método de
“tokenização”.
– É possível trocar o estado do scanner
dentro de uma ação através de chamada
ao método interno
yybegin(nome_do_estado).
– Você pode fazer uso das variáveis yytext
(String), yychar (int) e yyline (int) dentro
do código de suas ações.
JLex: regras de
expressões regulares
(cont.)
 Observações:
– Se mais de uma regra casar com a string de
entrada, o scanner escolhe a regra que casa com a
maior substring da string. Exemplo:
 String: abcd
 Regra 1: “ab” { acao1(); }
 Regra 2: [a-z]+ { acao2(); }
 O scanner vai escolher a regra 2.

– Todas as seqüências de caracteres passadas como


entrada para o scanner devem casar com alguma
das regras. Caso isso não ocorra, o scanner vai
gerar um erro.
JCup: Constructor of
Useful Parsers
 Gerador de analisadores
sintáticos (parsers)

 Baseado no ‘yacc’ do UNIX


JCup: arquivo de
especificação (.cup)
 Dividido em quatro seções:
– Seção 1: declaração de “packages” e
“imports” que serão inseridos no topo do
arquivo gerado pelo JCup (similar à
primeira seção do arq. de especificação do
JLex) e diretivas do JCup.
– Seção 2: declaração de terminais e não-
terminais.
– Seção 3: precedência e associatividade de
terminais.
– Seção 4: gramática.
JCup: primeira seção
 Especificação de “packages” e “imports”. Exemplo:
package compilador.parser;
import compilador.scanner;

 Diretivas
– parser code {: ... :};
 Permite que você declare variáveis e métodos na classe do
parser. Similar à diretiva %{...%} do JLex.
– init with {: ... :};
 O código entre chaves vai ser executado antes que o parser
peça o primeiro token ao scanner. Bom lugar para inicializar
o scanner.
– scan with {: ... :};
 Serve para que você escreva o código que o parser vai
executar sempre que ele quiser pedir um token ao scanner.
Se essa diretiva não for utilizada, o parser chama
scanner.next_token() para receber tokens.
JCup: segunda seção
 Lista de símbolos

– terminal [classe] nome0, nome1, ...;


– non terminal [classe] nome0, nome1, ...;

 Em tempo de execução, os símbolos são representados por objetos


da classe java_cup.runtime.Symbol. Essa classe possui uma variável
chamada “value” que contém o valor do símbolo. Exemplo:
– terminal Integer NUMERO;
– quando o parser recebe do scanner um NUMERO, ele cria um objeto da
classe Symbol. A variável “value” será um objeto da classe Integer.
Assim, o valor do número pode ser obtido através de
simbolo.value.intValue();

 Se não for fornecida uma classe na declaração do (non) terminal, a


variável “value” ficará com valor null.
 Os nomes dos (non) terminais não podem ser palavras reservadas
do JCup: "code", "action", "parser", "terminal", "non", "nonterminal",
"init", "scan", "with", "start", "precedence", "left", "right",
"nonassoc", "import", e "package"
JCup: terceira seção
 Precedência e Associatividade
– precedence left terminal[, terminal...];
– precedence right terminal[, terminal...];
– precedence nonassoc terminal[, terminal...];

 A precedência cresce de cima para baixo, por


exemplo:
– precedence left ADD, SUBTRACT;
– precedence left TIMES, DIVIDE;

significa que a multiplicação e a divisão têm


maior precedência.
JCup: quarta seção
 Gramática
especifica as produções da gramática
da linguagem.

 start with non-terminal; (diretiva


opcional)
indica qual é o não-terminal inicial da
gramática. Se essa diretiva for
omitida, o parser assume o primeiro
não-terminal declarado nas produções
da gramática.
JCup: quarta seção
(cont.)
 As produções têm o formato:
não-terminal ::= <símbolos e
ações>
 Os símbolos à direita de “::=“
podem ser terminais ou não-
terminais.
 As ações correspondem ao código
que é executado quando a regra
de produção é aplicada.
JCup: quarta seção
(cont.)
 Exemplo:
expr ::= NUMBER:n
{:
RESULT=n;
:}
| expr:r PLUS expr:s
{:
RESULT=new Integer(r.intValue() + s.intValue());
:}

 Observe que pode-se especificar várias produções para um mesmo


não terminal através do uso da barra “|”. Pode-se nomear símbolos
para poder referenciá-los no código da ação. O resultado da
produção deve ser armazenado na variável implícita “RESULT”. O
tipo de “RESULT” é o mesmo que foi declarado na seção 2.
Links, Manuais e
Exemplos
 JLex
– http://www.cs.princeton.edu/~appel/modern/java/JL
ex/

 JCup
– http://www.cs.princeton.edu/~appel/modern/java/C
UP/

Você também pode gostar