report - lex and yacc

12
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 des- coberta 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, mi- chel.mas}@gmail.com 1

Upload: michel-alves

Post on 21-Jun-2015

508 views

Category:

Education


1 download

DESCRIPTION

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.

TRANSCRIPT

Page 1: Report -  Lex and YACC

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 linguagemde programação pode ser dividida em duas partes: (1) leitura do código fonte alvo e des-coberta de sua estrutura; (2) processamento da estrutura(agora já conhecida) e geração domódulo executável. Lex e YACC podem gerar fragmentos de programa capazes de solucionara 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 evalidação da hierarquia estrutural do programa(responsabilidade do YACC). Neste modestorelató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 dePesquisa em Matemática Computacional(CPMAT), Brasil - Maceió/AL, E-mails: {afdlf2, normandelins, mi-chel.mas}@gmail.com

1

Page 2: Report -  Lex and YACC

�Vi Veri Veniversum Vivus Vici�Pelo poder da verdade, eu, enquanto vivo, conquistei o universo.

V for Vendetta

ConteúdoLista de Figuras 3

1 Introdução 4

2 Lex - A Lexical Analyzer Generator 62.1 Estrutura do Arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.2 Exemplo de um Arquivo Lex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.3 Relacionamento com YACC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

3 Yacc: Yet Another Compiler Compiler 83.1 Estrutura do Arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93.2 Exemplo de um Arquivo YACC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103.3 Características . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

4 Conclusões 11

Referências Bibliográficas 12

2

Page 3: Report -  Lex and YACC

Lista de Figuras1 Exemplificando uma Sequência de Compilação. . . . . . . . . . . . . . . . . . . . . 42 Construindo um Compilador com Lex/Yacc. . . . . . . . . . . . . . . . . . . . . . . 53 Arquivos Necessários Para Obtenção do Módulo Executável Final(lex.yy.c e y.tab.c). 64 Ilustração da forma de operação integrada das rotinas yyparse() e yylex(). . . . 65 Processamento de Entradas do Lex. . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Diagramas de Relacionamento Entre as Ferramentas Lex e Yacc. . . . . . . . . . . 87 Exemplificando a Integração Entre as Ferramentas Flex e Bison. . . . . . . . . . . 98 Partes de um Compilador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

3

Page 4: Report -  Lex and YACC

1 IntroduçãoAntes 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 ageração automática de fragmentos de programa que realizassem as tarefas mais corriqueiras deum compilador, dessa forma nasceram o lex e o YACC. Esses utilitários facilitaram enormementea escrita de compiladores. Detalhes de implementação do lex e YACC podem ser encontrados emAho 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 voltade U$500,00. Os utilitários da GNU são clones gratuitos do lex (flex) e do YACC(bison), porémpodem 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 umarquivo texto concebido apenas para testes. Lex processará as regras que atendem a leitura dessasexpressões ou patterns e irá gerar código fonte em C para um analisador léxico ou scanner. O

4

Page 5: Report -  Lex and YACC

analisador léxico irá confrontar a entrada textual recebida com as regras ou leis de combinaçãoinerentes ou internas ao mesmo e baseado nessas regras irá converter a entrada textual fornecidaem 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 ainserção desse identificador em uma tabela de símbolos. A tabela de símbolos também pode conteroutras informações a respeito dos elementos identificados tais como seu tipo de dado (inteiro oureal) 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 umformalismo gerador com maior poder de expressão chamado gramática. Uma gramática é ummecanismo 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 umsimples editor de texto) e gerar código em C para um analisador sintático ou parser. O analisadorsintático usa as regras de produção da gramática que o permitem processar tokens advindos doanalisador léxico e criar uma árvore de sintaxe. A árvore de sintaxe impõe a hierarquia estruturalaos tokens. Por exemplo, precedência de operadores e associatividade são aparentes em uma árvorede sintaxe. O próximo passo, geração de código, pode utilizar-se de uma estratégia chamada depth-first, que define uma forma de percorrer (visitar) os nós de uma árvore. Nessa estratégia a visita aum 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, comopode 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 queo nosso principal objetivo seja a escrita de um compilador básico chamado bas.exe, prosseguiremosda seguinte forma. Primeiramente iremos especificar todos os padrões e expressões regulares quequeremos identificar passando-os para o lex através do arquivo bas.l. Seguimos com a definiçãoda gramática e das regras de produção que serão passadas ao YACC através do arquivo bas.y. Oscomandos necessários para criar nosso compilador bas.exe são listados imediatamente abaixo:

yacc –d bas.y # create y.tab.h, y.tab.clex bas.l # create lex.yy.ccc lex.yy.c y.tab.c –o bas.exe # compile/link

Se estivéssemos utilizando as versões gratuitas de yacc e lex teríamos a seguinte sequência decomandos:

bison –d bas.y # create bas.tab.h, bas.tab.cflex bas.l # create lex.yy.cgcc lex.yy.c bas.tab.c –o bas.exe # 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

Page 6: Report -  Lex and YACC

para tokens e os coloque em um arquivo y.tab.h. Lex lê as regras que estão contidas em bas.l egera um analisador léxico incluindo a função yylex() ao arquivo lex.yy.c (além de incluir nessemesmo 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 execu-tá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 GeneratorLex é 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 é ogerador 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 emblocos de código, e retorna um código fonte implementando o analisador. O gerador é genérico epode 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 AT&T estãodisponíveis em código aberto, como parte de sistemas como OpenSolaris e Plan 9. Outra versãopopular e livre do lex, já mencionada é o flex.

6

Page 7: Report -  Lex and YACC

Figura 5: Processamento de Entradas do Lex.

2.1 Estrutura do ArquivoA estrutura de um arquivo lex é intencionalmente similar ao de um arquivo yacc. Os ar-

quivos são divididos em três seções, separadas por linhas que contém somente dois símbolos deporcentagem, 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 emC. É também possível escrever código C na mesma seção. Já a seção de regras associa padrõescom instruções C, padrões escritos na forma de expressões regulares. Quando o analisador léxicoidentifica algum texto da entrada casando com um padrão, ele executa o código C associado. Atentativa do casamento é sempre gananciosa, isto é, no caso de dois padrões distintos casandoa mesma entrada, o maior deles será usado. O maior deles é o que consome mais caracteresda entrada. Caso os padrões ambíguos consumam a mesma quantidade de caracteres, o padrãodefinido antes é escolhido. Por fim, a seção de subrotinas contém blocos de código C que serãoapenas copiados ao arquivo final. Assume-se que tal código será invocado a partir das regras daseção de regras. Em programas maiores, é mais conveniente separar esse código final em outroarquivo.

2.2 Exemplo de um Arquivo LexO 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: %s\n", yytext);

}

7

Page 8: Report -  Lex and YACC

. { /* Ignora outros caracteres. */ }%%// Seção de Código em Cint 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 dacompilaçã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.!&*2ghj6O analisador imprimirá:

Inteiro: 123Inteiro: 2Inteiro: 6

2.3 Relacionamento com YACCO 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ãoconsegue fazer somente com expressões regulares (o lex é limitado a simples máquinas de estadofinito). Entretanto, o YACC não consegue ler a partir de uma simples entrada de dados, ele requeruma série de tokens, que são geralmente fornecidos pelo lex. O lex age como um pré-processadordo 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 regrasléxicas, e que o YACC gera a subrotina yyparse() a partir de regras gramaticais. A partir dodiagrama 2, percebe-se que um programa qualquer invoca o analisador sintático para uma fluxode 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 ofluxo de entrada e retorna o primeiro token que encontrar. Esse processo de requisição é contínuoe só termina quando o analisador léxico identifica o fim o fluxo de entrada ou quando o analisadorsintático identifica uma falha gramatical.

3 Yacc: Yet Another Compiler CompilerYACC (acrônimo para Yet Another Compiler Compiler) é um gerador de analisadores sintáti-

cos desenvolvido por Stephen C. Johnson da AT&T para o sistema operacional Unix. Ele gera um

8

Page 9: Report -  Lex and YACC

analisador sintático, parte do compilador responsável por fornecer sentido sintático a um deter-minado código fonte, baseado numa gramática formal escrita numa forma similar ao formalismode Backus-Naur. O formalismo de gramáticas é mais poderoso que o de expressões regulares eautômatos, assim é possível gerar programas que processam entradas mais complexas. O resultadofinal 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 sistemasUnix, mas acabou sendo suplantado por versões mais modernas ainda que compatíveis, comoBerkeley Yacc, GNU bison, MKS yacc e Abraxas pcyacc. Uma versão atualizada do código originalda AT&T é 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 fornecidoexternamente através de geradores de analisadores léxicos como o lex ou o flex. A norma POSIXdefine 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 melhorescolha recai no par flex-bison [Figura 7], em que o flex substitui o lex, enquanto o bison substituio YACC. Em particular, a implementação do scanner a partir do flex é muito superior à obtidapelo lex, sendo totalmente compatível com o YACC. Assim, mesmo usando o YACC é preferívelque se use o flex. De qualquer forma, os dois pares aceitam os mesmos arquivos fonte para adefiniçã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 dagramática. À medida que a entrada é processada, ações adequadas são executadas. Essas açõespodem ser, por exemplo, a interpretação ou compilação da entrada.

3.1 Estrutura do ArquivoO 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 deregras, mesmo porque enquanto no lex a gramática é uma gramática regular, no yacc temos umagramática de atributos (que é uma gramática livre de contexto acrescida de atributos para amanipulação semântica de contexto).

O problema na especificação da seção de regras de um arquivo yacc, que também é chamada decorpo, é o fato de uma gramática exibir derivações bastante distintas a partir de um mesmo símbolonão-terminal. Todas essas derivações devem aparecer no corpo, sendo que a ordem em que asmesmas aparecem acaba influenciando a forma como o yacc determina as possíveis construções da

9

Page 10: Report -  Lex and YACC

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 YACCO 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 sualistagem.

%{//----------------------------------------------------------------------// 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: %s\n", s);printf("Tal erro ocorreu na localidade próxima a: %s\n", yytext);

}

O analisador léxico para tal parser é dado logo a seguir.

%{#include <stdio.h>#include <stdlib.h>#include "parser.tab.h"%}

10

Page 11: Report -  Lex and YACC

id [_a-zA-Z][_a-zA-Z0-9]*wspc [ \t\n]+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ísticasAbaixo 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õesAtravé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étodosautomá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 analisadoresquase tão rápidos e eficientes quanto os escritos de forma totalmente “artesanal”.

11

Page 12: Report -  Lex and YACC

ReferênciasAho, 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., CambridgeUniversity 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 Re-port 32, Bell Laboratories, Murray hill, New Jersey.

Lesk, M. E. & Schmidt, E. (1975), Lex - a lexical analyzer generator, Computing Science TechnicalReport 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