Download pdf - Report - Lex and YACC

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