Your SlideShare is downloading. ×
Report -  Lex and YACC
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Report - Lex and YACC

383
views

Published on

Lex is a computer program that generates lexical analyzers ("scanners" or "lexers"). Lex is commonly used with the yacc parser generator. Lex, originally written by Mike Lesk and Eric Schmidt and …

Lex is a computer program that generates lexical analyzers ("scanners" or "lexers"). Lex is commonly used with the yacc parser generator. Lex, originally written by Mike Lesk and Eric Schmidt and described in 1975, is the standard lexical analyzer generator on many Unix systems, and a tool exhibiting its behavior is specified as part of the POSIX standard.

Published in: Education

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
383
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
16
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. As Ferramentas Lex e YACC Ailton Félix de Lima Filho - Bruno Normande Lins - Michel Alves dos Santos ∗ 13 de Dezembro de 2010 Resumo A principal tarefa de um compilador ou interpretador para uma determinada linguagem de programação pode ser dividida em duas partes: (1) leitura do código fonte alvo e descoberta de sua estrutura; (2) processamento da estrutura(agora já conhecida) e geração do módulo executável. Lex e YACC podem gerar fragmentos de programa capazes de solucionar a primeira parte dessa tarefa. A tarefa de descoberta da estrutura do código fonte novamente é decomposta em subtarefas: quebra do fonte em tokens(responsabilidade do lex), busca e validação da hierarquia estrutural do programa(responsabilidade do YACC). Neste modesto relatório técnico introdutório iremos abordar as principais características desses ferramentas. ∗ Bacharelandos em Ciência da Computação, Universidade Federal do Estado de Alagoas(UFAL), Centro de Pesquisa em Matemática Computacional(CPMAT), Brasil - Maceió/AL, E-mails: {afdlf2, normandelins, michel.mas}@gmail.com 1
  • 2. Vi Pelo poder da verdade, eu, Veri Veniversum enquanto vivo, Vivus conquistei o Vici universo. V for Vendetta Conteúdo Lista de Figuras 3 1 Introdução 4 2 Lex 2.1 2.2 2.3 - A Lexical Analyzer Generator Estrutura do Arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exemplo de um Arquivo Lex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Relacionamento com YACC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 7 7 8 3 Yacc: Yet Another Compiler Compiler 3.1 Estrutura do Arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Exemplo de um Arquivo YACC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Características . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 9 10 11 4 Conclusões 11 Referências Bibliográficas 12 2
  • 3. Lista de Figuras 1 2 3 4 5 6 7 8 Exemplificando uma Sequência de Compilação. . . . . . . . . . . . . . . . . . . . . 4 Construindo um Compilador com Lex/Yacc. . . . . . . . . . . . . . . . . . . . . . . 5 Arquivos Necessários Para Obtenção do Módulo Executável Final(lex.yy.c e y.tab.c). 6 Ilustração da forma de operação integrada das rotinas yyparse() e yylex(). . . . 6 Processamento de Entradas do Lex. . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Diagramas de Relacionamento Entre as Ferramentas Lex e Yacc. . . . . . . . . . . 8 Exemplificando a Integração Entre as Ferramentas Flex e Bison. . . . . . . . . . . 9 Partes de um Compilador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3
  • 4. 1 Introdução Antes de 1975 escrever um compilador era uma tarefa árdua que consumia muito tempo. Então Lesk Schmidt (1975) e Johnson (1975) publicaram trabalhos nos quais abordavam a geração automática de fragmentos de programa que realizassem as tarefas mais corriqueiras de um compilador, dessa forma nasceram o lex e o YACC. Esses utilitários facilitaram enormemente a escrita de compiladores. Detalhes de implementação do lex e YACC podem ser encontrados em Aho et al. (1986). Essas ferramentas estão disponíveis em: • Mortice Kern Systems (MKS), www.mks.com • GNU flex e GNU bison, www.gnu.org • Cygwin, www.cygwin.com A versão MKS desses utilitários é um produto comercial de alta qualidade que custa por volta de U$500,00. Os utilitários da GNU são clones gratuitos do lex (flex) e do YACC(bison), porém podem ser utilizados em aplicações comerciais. A versão atual do flex é a 2.5.35 e do bison é a 2.4.1. Cygwin é uma versão dos softwares da GNU portada para plataformas MS Windows c de 32 bits. As expressões visualizadas na figura [1] podem ser construções que pertençam a um Figura 1: Exemplificando uma Sequência de Compilação. determinado código fonte de uma determinada linguagem ou apenas o conteúdo avulso de um arquivo texto concebido apenas para testes. Lex processará as regras que atendem a leitura dessas expressões ou patterns e irá gerar código fonte em C para um analisador léxico ou scanner. O 4
  • 5. analisador léxico irá confrontar a entrada textual recebida com as regras ou leis de combinação inerentes ou internas ao mesmo e baseado nessas regras irá converter a entrada textual fornecida em tokens, que são nada mais que representações numéricas para cadeias de caracteres. Quando o analisador léxico encontra identificadores na entrada textual fornecida, ocorre a inserção desse identificador em uma tabela de símbolos. A tabela de símbolos também pode conter outras informações a respeito dos elementos identificados tais como seu tipo de dado (inteiro ou real) ou localização de cada variável em memória. Figura 2: Construindo um Compilador com Lex/Yacc. O utilitário YACC não trabalhará apenas com o reconhecimento de padrões, mas com um formalismo gerador com maior poder de expressão chamado gramática. Uma gramática é um mecanismo para gerar as sentenças (ou palavras) de uma linguagem(de Alencar Price Toscani, 2004). YACC irá ler uma gramática previamente definida (que pode ser criada através de um simples editor de texto) e gerar código em C para um analisador sintático ou parser. O analisador sintático usa as regras de produção da gramática que o permitem processar tokens advindos do analisador léxico e criar uma árvore de sintaxe. A árvore de sintaxe impõe a hierarquia estrutural aos tokens. Por exemplo, precedência de operadores e associatividade são aparentes em uma árvore de sintaxe. O próximo passo, geração de código, pode utilizar-se de uma estratégia chamada depthfirst, que define uma forma de percorrer (visitar) os nós de uma árvore. Nessa estratégia a visita a um nodo só acontece depois de todos os seus filhos terem sido visitados(de Alencar Price Toscani, 2004; Niemann, 2010). Alguns compiladores produzem código de máquina enquanto outros, como pode ser visto na figura [1], apenas produzem uma saída em linguagem assembly. A figura [2] ilustra a convenção de nomes adotada pelos utilitários lex e YACC. Assumindo que o nosso principal objetivo seja a escrita de um compilador básico chamado bas.exe, prosseguiremos da seguinte forma. Primeiramente iremos especificar todos os padrões e expressões regulares que queremos identificar passando-os para o lex através do arquivo bas.l. Seguimos com a definição da gramática e das regras de produção que serão passadas ao YACC através do arquivo bas.y. Os comandos necessários para criar nosso compilador bas.exe são listados imediatamente abaixo: yacc –d bas.y lex bas.l cc lex.yy.c y.tab.c –o bas.exe # create y.tab.h, y.tab.c # create lex.yy.c # compile/link Se estivéssemos utilizando as versões gratuitas de yacc e lex teríamos a seguinte sequência de comandos: bison –d bas.y flex bas.l gcc lex.yy.c bas.tab.c –o bas.exe # create bas.tab.h, bas.tab.c # create lex.yy.c # compile/link YACC lê a descrição da gramática definida em bas.y e gera um analisador sintático (parser), que inclui a função yyparse() no arquivo y.tab.c. A opção -d faz com que o YACC gere definições 5
  • 6. para tokens e os coloque em um arquivo y.tab.h. Lex lê as regras que estão contidas em bas.l e gera um analisador léxico incluindo a função yylex() ao arquivo lex.yy.c (além de incluir nesse mesmo arquivo o arquivo de cabeçalho y.tab.h). Figura 3: Arquivos Necessários Para Obtenção do Módulo Executável Final(lex.yy.c e y.tab.c). Finalmente, o scanner e o parser são compilados e “link-editados” juntos para criar o executável bas.exe. Da função main executamos a chamada a yyparse() para ativar o compilador. A função yyparse() automaticamente executa a chamada a yylex() para obter cada token. Figura 4: Ilustração da forma de operação integrada das rotinas yyparse() e yylex(). 2 Lex - A Lexical Analyzer Generator Lex é um programa que gera analisadores léxicos. Ele é geralmente usado com o yacc, um gerador de analisadores sintáticos. Escrito originalmente por Eric Schmidt e Mike Lesk, ele é o gerador de analisador léxico padrão em diversos sistemas Unix. O lex lê um fluxo de entrada especificando um analisador que mapeia expressões regulares em blocos de código, e retorna um código fonte implementando o analisador. O gerador é genérico e pode se adequar a diferentes linguagens de programação. A geração padrão de código é em C. Apesar de ser software proprietário, versões do lex baseadas no código original da ATT estão disponíveis em código aberto, como parte de sistemas como OpenSolaris e Plan 9. Outra versão popular e livre do lex, já mencionada é o flex. 6
  • 7. Figura 5: Processamento de Entradas do Lex. 2.1 Estrutura do Arquivo A estrutura de um arquivo lex é intencionalmente similar ao de um arquivo yacc. Os arquivos são divididos em três seções, separadas por linhas que contém somente dois símbolos de porcentagem, como a seguir: definições %% regras %% subrotinas Na seção de definições são construídas as macros e são importadas as bibliotecas escritas em C. É também possível escrever código C na mesma seção. Já a seção de regras associa padrões com instruções C, padrões escritos na forma de expressões regulares. Quando o analisador léxico identifica algum texto da entrada casando com um padrão, ele executa o código C associado. A tentativa do casamento é sempre gananciosa, isto é, no caso de dois padrões distintos casando a mesma entrada, o maior deles será usado. O maior deles é o que consome mais caracteres da entrada. Caso os padrões ambíguos consumam a mesma quantidade de caracteres, o padrão definido antes é escolhido. Por fim, a seção de subrotinas contém blocos de código C que serão apenas copiados ao arquivo final. Assume-se que tal código será invocado a partir das regras da seção de regras. Em programas maiores, é mais conveniente separar esse código final em outro arquivo. 2.2 Exemplo de um Arquivo Lex O seguinte exemplo reconhece inteiros da entrada de dados e os imprime na saída padrão. // Seção de Definição %{ #include stdio.h %} %% // Seção de Regras // [0-9]+ casa uma cadeia de um ou mais dígitos [0-9]+ { /* yytext é a cadeia contendo o texto casado. */ printf(Inteiro: %sn, yytext); } 7
  • 8. . { /* Ignora outros caracteres. */ } %% // Seção de Código em C int main(void) { // Executa o analisador léxico. yylex(); return 0; } O conteúdo acima especificado será convertido em um arquivo da linguagem C. Através da compilação do arquivo convertido teremos um analisador léxico pronto e funcional que reconhecerá apenas sequencias que podem sugerir números inteiros. Para a seguinte entrada: abc123z.!*2ghj6 O analisador imprimirá: Inteiro: 123 Inteiro: 2 Inteiro: 6 2.3 Relacionamento com YACC O lex e o gerador de analisadores sintáticos YACC são geralmente usados em conjunto. O YACC usa uma gramática formal para analisar sintaticamente uma entrada, algo que o lex não consegue fazer somente com expressões regulares (o lex é limitado a simples máquinas de estado finito). Entretanto, o YACC não consegue ler a partir de uma simples entrada de dados, ele requer uma série de tokens, que são geralmente fornecidos pelo lex. O lex age como um pré-processador do YACC. Segue abaixo dois diagramas de relacionamento entre lex e YACC: Figura 6: Diagramas de Relacionamento Entre as Ferramentas Lex e Yacc. A partir do diagrama 1, percebe-se que o lex gera a subrotina yylex() a partir de regras léxicas, e que o YACC gera a subrotina yyparse() a partir de regras gramaticais. A partir do diagrama 2, percebe-se que um programa qualquer invoca o analisador sintático para uma fluxo de entrada. O analisador sintático não consegue analisar entradas, mas sim tokens. Portanto, cada vez que ele precisa dum token, ele invoca o analisador léxico. O analisador léxico processa o fluxo de entrada e retorna o primeiro token que encontrar. Esse processo de requisição é contínuo e só termina quando o analisador léxico identifica o fim o fluxo de entrada ou quando o analisador sintático identifica uma falha gramatical. 3 Yacc: Yet Another Compiler Compiler YACC (acrônimo para Yet Another Compiler Compiler) é um gerador de analisadores sintáticos desenvolvido por Stephen C. Johnson da ATT para o sistema operacional Unix. Ele gera um 8
  • 9. analisador sintático, parte do compilador responsável por fornecer sentido sintático a um determinado código fonte, baseado numa gramática formal escrita numa forma similar ao formalismo de Backus-Naur. O formalismo de gramáticas é mais poderoso que o de expressões regulares e autômatos, assim é possível gerar programas que processam entradas mais complexas. O resultado final após o uso do YACC é um código para o analisador sintático escrito em C. O YACC costumava ser o gerador de analisadores sintáticos padrão na maioria dos sistemas Unix, mas acabou sendo suplantado por versões mais modernas ainda que compatíveis, como Berkeley Yacc, GNU bison, MKS yacc e Abraxas pcyacc. Uma versão atualizada do código original da ATT é incluída no OpenSolaris. O YACC também já foi reescrito para outras linguagens, incluindo Ratfor, EFL, ML, Ada, Java e Limbo. O analisador sintático gerado pelo YACC requer um analisador léxico, que pode ser fornecido externamente através de geradores de analisadores léxicos como o lex ou o flex. A norma POSIX define a funcionalidade e os requisitos tanto para lex quanto para YACC. Figura 7: Exemplificando a Integração Entre as Ferramentas Flex e Bison. No ambiente unix existem outros pares que podem substituir o lex-YACC, sendo que a melhor escolha recai no par flex-bison [Figura 7], em que o flex substitui o lex, enquanto o bison substitui o YACC. Em particular, a implementação do scanner a partir do flex é muito superior à obtida pelo lex, sendo totalmente compatível com o YACC. Assim, mesmo usando o YACC é preferível que se use o flex. De qualquer forma, os dois pares aceitam os mesmos arquivos fonte para a definição da linguagem, de modo que não é preciso escolher um deles a priori. Da mesma forma que lex, YACC pode associar ações (trechos de programa) a cada regra da gramática. À medida que a entrada é processada, ações adequadas são executadas. Essas ações podem ser, por exemplo, a interpretação ou compilação da entrada. 3.1 Estrutura do Arquivo O formato de um arquivo YACC é mais complexo do que o do lex. Ele também é composto por partes de definição, regras e subrotinas, com uma sintaxe bastante diferenciada na seção de regras, mesmo porque enquanto no lex a gramática é uma gramática regular, no yacc temos uma gramática de atributos (que é uma gramática livre de contexto acrescida de atributos para a manipulação semântica de contexto). O problema na especificação da seção de regras de um arquivo yacc, que também é chamada de corpo, é o fato de uma gramática exibir derivações bastante distintas a partir de um mesmo símbolo não-terminal. Todas essas derivações devem aparecer no corpo, sendo que a ordem em que as mesmas aparecem acaba influenciando a forma como o yacc determina as possíveis construções da 9
  • 10. linguagem. Isso demanda um cuidado grande no momento de construir a gramática da linguagem, caso não se queira perder um tempo considerável eliminando ambiguidades inexistentes. 3.2 Exemplo de um Arquivo YACC O seguinte parser verifica a validade de uma lista de declarações para um determinado tipo que é limitado a char, int e float, segundo o seu analisador léxico que é apresentado logo após sua listagem. %{ //---------------------------------------------------------------------// Parser que verifica se uma única linha de declaração é válida. Caso // o parser encontre mais de um fechamento de declaração, isto é, mais // de um símbolo ; então retornará um erro do tipo syntax error. //---------------------------------------------------------------------#include stdio.h extern char* yytext; %} %token CHAR COMMA FLOAT %token ID INT SEMI %% decl : type ID list { printf([%s] t, yytext); printf(Ponto e Virgula Alcançado! n); } ; list : COMMA ID list { printf([%s] t, yytext); printf(COMMA ID list n); } | SEMI { printf([%s] t, yytext); printf(SEMI n); } ; type : INT { printf([%s] t, yytext); printf(INT n); } | CHAR { printf([%s] t, yytext); printf(CHAR n); } | FLOAT { printf([%s] t, yytext); printf(FLOAT n); } ; %% int main(void) { yyparse(); printf(Fim do Programa!n); return 0; } // Função que trata erros oriundos da análise sintática de um trecho de // código passado ao parser. yyerror(char *s) { printf(Ocorreu o seguinte erro: %sn, s); printf(Tal erro ocorreu na localidade próxima a: %sn, yytext); } O analisador léxico para tal parser é dado logo a seguir. %{ #include stdio.h #include stdlib.h #include parser.tab.h %} 10
  • 11. id [_a-zA-Z][_a-zA-Z0-9]* wspc [ tn]+ semi [;] comma [,] %% quit|exit { exit(EXIT_SUCCESS); } int { return INT; } char { return CHAR; } float { return FLOAT; } {comma} { return COMMA; } {semi} { return SEMI; } {id} { return ID;} {wspc} {} 3.3 Características Abaixo são listadas algumas das características mais importantes da ferramenta YACC. • As regras podem ser recursivas; • As regras não podem ser ambíguas; • Usa um parser bottom-up Shift/Reduce LALR • Yacc não pode olhar mais que um token “lookahead”. 4 Conclusões Através do pouco que vimos neste modesto relatório técnico podemos concluir, com um grande fundo de certeza, que escrever compiladores não é uma tarefa fácil. Requer tempo e esforço. Porém como a maioria das ações de um compilador são repetitivas, podemos empregar métodos automáticos para construção de algumas de suas partes: o scanner e o parser. Atualmente, com Figura 8: Partes de um Compilador. a utilização de ferramentas como lex e YACC, é possível construir rapidamente um compilador. O processo de geração automática utilizado por essas ferramentas, em geral, produz analisadores quase tão rápidos e eficientes quanto os escritos de forma totalmente “artesanal”. 11
  • 12. Referências Aho, A. V., Sethi, R. Ullman, J. D. (1986), Compilers, Prinicples, Techniques and Tools, Addison-Wesley, Massachusetts. Appel, A. W. Ginsburg, M. (1998), Modern Compiler Implementation in C, 2 ed., Cambridge University Press. de Alencar Price, A. M. Toscani, S. S. (2004), Implementação de Linguagens de Progrmação: Compiladores, number 9, 3 ed., Bookman. Johnson, S. C. (1975), Yacc: Yet another compiler compiler, Computing Science Technical Report 32, Bell Laboratories, Murray hill, New Jersey. Lesk, M. E. Schmidt, E. (1975), Lex - a lexical analyzer generator, Computing Science Technical Report 39, Bell Laboratories, Murray Hill, New Jersey. Levine, J. R., Mason, T. Brown, D. (1992), Lex Yacc, 2 ed., O’Reilly Associates. Niemann, T. (2010), A Compact Guide to Lex Yacc. URL http://epaperpress.com/. Sebesta, R. W. (2003), Conceitos de Linguagem de Programação, 5 ed., Bookman. Stallman, R. Donnelly, C. (2010), ‘Bison, the yacc compatible parser generator’. 12