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
�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
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
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
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
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
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
. { /* 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
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
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
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
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
Top Related