270 likes | 428 Views
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:
E N D
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/JLex/ • JCup • http://www.cs.princeton.edu/~appel/modern/java/CUP/