Compiladores Flex
Introdução Flex e uma ferramenta para gerar scanners programas que reconhecem padrões lexicais em texto O flex lê o arquivo de entrada fornecido, para uma descrição de um scanner a ser gerado A descrição está na forma de pares de expressões regulares e código C chamadas de regras Flex gera como saída um arquivo de origem C denominado lex.yy.c, definindo uma rotina yylex() Este arquivo é compilado e vinculado a biblioteca para produzir executável (-lfl) Quando o executável é executado, ele analisa a entrada em busca de ocorrências das expressões regulares Sempre que encontrar uma correspondência, ele executa o código C correspondente
Introdução Instalado o flex sudo apt-get install flex
Introdução Durante a primeira fase, o compilador lê a entrada e converte as strings na origem para os tokens Com expressões regulares, podemos especificar padrões para o flex O flex pode gerar código que permitirá a varredura e a correspondência de strings na entrada Cada padrão na entrada para o flex tem uma ação associada A ação, normalmente, retorna um token que representa a string correspondente, a qual será utilizada subsequentemente pelo analisador A principio, imprimiremos a string correspondida em vez de retornar um valor de token
Introdução A regra a seguir representa um padrão simples, com posto por uma expressão regular que procura por identificadores letra(letra digito)* Esta regra corresponde a uma sequencia de caracteres a qual começa com uma única letra seguida por zero ou mais letras ou dígitos Qualquer expressão regular pode ser expressa como autômato de estado finito letra ou digito inicio letra outro
Introdução O estado 0 é o inicial e o estado 2 é o de aceitação inicio letra letra ou digito outro Conforme os caracteres são lidos, fazemos uma transição de um estado para outro Está máquina de 3 estados é facilmente programada:
Introdução start: goto state0 state0: read c if c = letra goto state1 goto state0 state1: read c if c = letra goto state1 if c = digito goto state1 goto state2 state2: accept string inicio letra letra ou digito outro
Introdução Durante a primeira fase, o compilador lê a entrada e converte as strings na origem para os tokens Com expressões regulares, podemos especificar padrões para o flex O flex pode gerar código que permitirá a varredura e a correspondência de strings na entrada Cada padrão na entrada para o flex tem uma ação associada A ação, normalmente, retorna um token que representa a string correspondente, a qual será utilizada subsequentemente pelo analisador A principio, imprimiremos a string correspondida em vez de retornar um valor de token
Padrões Caracteres especiais Padrão Match. Qualquer caractere, exceto nova linha \. Literal \n Nova linha \t Aba ^ Começo de linha $ Fim de linha
Padrões Operadores Padrão Match? zero ou uma cópia da expressão anterior * zero ou mais cópias da expressão anterior + uma ou mais cópias da expressão precedente a b (ab)+ abc a ou b (alternando) uma ou mais cópias de ab (agrupamento) abc abc* ab abc abcc abccc abcccc... abc* literal abc* abc+ abc abcc abccc abcccc... a(bc)+ abc abcbc abcbcbc... a(bc)? a abc
Padrões Caracteres Padrão Match [abc] um de a b c [a-z] qualquer letra de a a z [a\-z] um de a - z [-az] um de a z [A-Za-z0-9]+ um ou mais caracteres alfanuméricos [ \t\n]+ espaço em branco [^ab] qualquer coisa exceto a b [a^b] um de a ^ b [a b] um de a b
Formato arquivo flex/lex A entrada para Flex é dividido em 3 seções com %% dividindo as seções... definições... %%... regras... %%... sub-rotinas (código C)...
Formato arquivo flex/lex A seção de definições pode ter: %{ Inicializações de variáveis C e funções/protótipos C (opcional, podem também ser linhas indentadas, sem %{ %}) %} %directiva /* Comentário, copiado para o arquivo C final lex.yy.c */ nome expressão regular
Formato arquivo flex/lex A seção de regras pode ter: %{ Inicializações de variáveis e código C locais à função yylex() (opcional, podem também ser linhas indentadas, sem %{ %}) %} expressão regular ação
Gerando o scanner vim nomearquivo.l Salvar no vim :w Salvar e sair :wq Sair sem salvar :!q Sair :q gcc lex.yy.c o scan./scan cria e abre o arquivo para criação do analisador léxico compila o arquivo lex.yy.c com o gcc gerando o executável scan executa o analisador léxico
Variáveis pré-definidas Nome int yylex(void) char *yytext yyleng yylval int yywrap(void) FILE *yyout FILE *yyin INITIAL BEGIN condition ECHO Função Chamada para invocar o lexer, retorna um token ponteiro para string encontrada (correspondente) comprimento da string encontrada (correspondente) valor associado ao token retorna 1 se estiver pronto, 0 se não estiver arquivo de saída arquivo de entrada condição de inicialização condição de inicialização escreve uma string encontrada (correspondente)
Exemplos simples O flex a seguir especifica um scanner que, sempre que encontrar um caracter qualquer mostra uma mensagem na tela
Exemplos simples %%. printf( Encontrei um caracter qualquer ); \n printf( Encontrei uma quebra de linha ); %% int yywrap(void){ return 1; } int main(void){ yylex(); return 0; }
Exemplos simples Grave este arquivo como aula1.l (a extensão é por convenção um L minúsculo Para compilar você precisa ter o flex instalado Então, se tiver, na linha de comando (terminal Linux) execute flex aula1.l Será criado um arquivo chamado lex.yy.c, o qual deve ser compilado na linha de comando. Se for o compilador da GNU, faça gcc lex.yy.c o comp Para executar faça./comp e tecle enter
Exemplos simples O programa parece não fazer nada, no entanto ele está aguardando você entrar com dados Escreva qualquer coisa que inclua letras e números Ele retornará dizendo que encontrou algo Quando terminar de testar, pressione CTRL C
Exemplos simples Você também pode criar um arquivo de texto e inserir valores lá dentro Por exemplo, crie um arquivo chamado teste.txt e adicione algumas palavras Em seguida, execute o programa anterior desta forma./comp < teste.txt Desta vez o programa comp vai mostrar imediatamente os resultados e encerrar
Exemplos simples digito [0-9] letra [A-Za-z] %{ {digito} printf( \nencontrei o digito %s, yytext); {letra} printf( \nencontrei a letra %s, yytext); %} %% int yywrap(void){ return 1; } int main(void){ yylex(); return 0; }
Expressões regulares específicas do flex Caracteres e escapes [[:c:]] Conjunto pré-definido ( Expressões de classes de caracteres ) ver manual do flex s s literal para usar em s o próprio caracter, fazer \, e para usar a própria \, fazer \\ (onde s é um símbolo qualquer) \c Se for o caracter 0, a, b, f, n, r, t ou v, então é o caracter ANSI-C correspondente, caso contrário é um c literal (usado para escaping de caracteres especiais) \### Código octal ASCII de um caracter \x## Código hexadecimal ASCII de um caracter
Expressões regulares específicas do flex Expressões {n} r/s Expansão de n, sendo n um nome (ver exemplo a seguir) Encontra r apenas se for seguido por s (s não fará parte de yytext); nem todos as expressões regulares podem ser usadas em s ver manual do flex
Expressões regulares específicas do flex Âncoras ^ Neste local tem de existir o início de uma linha $ Neste local tem de existir o fim de uma linha <<EOF>> Neste local tem de existir o fim do arquivo/dos dados de entrada; No caso da omissão para este tipo de âncora haverá um return que termina a execução da função gerada pelo Flex Âncoras representam posições no arquivo ou nos dados de entrada (veja exemplo a seguir)
Exemplo digito [0-9] letra [A-Za-z] %{ {digito} printf( \nencontrei o digito %s, yytext); {letra} printf( \nencontrei a letra %s, yytext); aluno[s] puts( \nencontrei a expressão \ aluno[s] ); ^Ze$ puts( \nencontrei \ Ze\ sozinho em uma linha ); <<EOF>> puts( \nencontrei o fim do arquivo ); return; %} %% int yywrap(void){ return 1; } Para testar o EOF, faça CTRL D no UNIX ou CTRL Z no Windows int main(void){ yylex(); return 0; }
Encontrando comentário em C //comentario digito [0-9] letra [A-Za-z] %{ {digito} printf( \nencontrei o digito %s, yytext); {letra} printf( \nencontrei a letra %s, yytext); //.* printf( \nencontrei o comentário %s, yytext); %} %% int yywrap(void){ return 1; } int main(void){ yylex(); return 0; } Para o comentário também poderia ser: [/][/].* ou \/\/.*
Encontrando comentário de bloco em C /*... */ digito [0-9] letra [A-Za-z] %{ {digito} printf( \nencontrei o digito %s, yytext); {letra} printf( \nencontrei a letra %s, yytext); //.* printf( \nencontrei o comentário %s, yytext); /*.* */ printf( \nencontrei comentário de bloco %s, yytext); %} %% int yywrap(void){ return 1; } int main(void){ yylex(); return 0; }
Exemplo 1 Neste primeiro exemplo, construiremos um reconhecedor de calculadora pós-fixa Neste modelo de calculadora o operador segue os dois operandos 8 9 + 17 3 7 + 10
Exemplo 1 Calculadora de inteiros, pós-fixa %option main #include <stdio.h> #include <stdlib.h> int a=0; int b=0; %% int r; [+-]?[0-9]+ a=b; b=atoi(yytext); [+] r=a+b; printf( %d + %d = %d\n, a,b,r); a=b; b=r; [-] r=a-b; printf( %d - %d = %d\n, a,b,r); a=b; b=r; [*] r=a*b; printf( %d * %d = %d\n, a,b,r); a=b; b=r; [/] r=a/b; printf( %d / %d = %d\n, a,b,r); a=b; b=r;. \r \n Exemplo de entrada: 2 3 + será 5; 8 2 + será 10
Exemplo 2 Neste exemplo, criaremos um analisador no flex responsável por ser um contador de ids e números inteiros em um código fonte de uma linguagem qualquer O id é uma sequencia de um ou mais caracteres alfanuméricos ele não pode começar com um caracter numérico, apenas letras
Exemplo 2 Contador de identificadores %option noyywrap #include <stdio.h> enum { TOKEN_IDENT, TOKEN_INT, TOKEN_MISC, TOKEN_EOF }; %% [a-za-z][a-za-z0-9]* return TOKEN_IDENT; [+-]?[0-9]+ return TOKEN_INT;. \r \n return TOKEN_MISC; <<EOF>> return TOKEN_EOF; %%
Exemplo 2 Contador de identificadores int main() { int num_id = 0; int num_int = 0; int token; do{ switch(token=yylex()){ case TOKEN_IDENT: ++num_id; break; case TOKEN_INT: ++num_int; break; } }while(token!= TOKEN_EOF); printf( Identificadores %d\n, num_id); printf( Inteiros %d\n, num_int); return 0; }
Exemplo 2 As ações fazem um return, saindo assim da função yylex() Desta forma, a função yylex() apenas auxilia na detecção dos tokens e não tem o trabalho de processar os mesmos Quando trabalharmos com o bison, utilizaremos o flex desta forma
Atividade Agora, combine os dois exemplos anteriores, criando uma calculadora infixa de números reais. A leitura de cada token deve ser feita pela função main(), chamando o yylex() para ler cada token (como no exemplo 2).