LEX Lex Adaptação da obra original de Tom Niemann 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 lex. Assim, ele pode gerar código que permitirá a varredura e a correspondência de strings na entrada. Cada padrão na entrada para o lex tem uma ação associada. Normalmente, uma ação retorna um token que representa a string correspondente para uso subsequente pelo analisador. Inicialmente, imprimiremos a string correspondente em vez de retornar um valor de token. A sentença seguinte representa um padrão simples, composto por uma expressão regular, que procura identificadores. Lex vai ler esse padrão e produzir código C para um analisador léxico que procura identificadores. letter(letter digit)* Esse padrão corresponde a uma sequência de caracteres que começa com uma única letra seguida por zero ou mais letras ou dígitos. Este exemplo ilustra bem as operações permitidas em expressões regulares: repetição, expressa pelo operador * alternação, expressa pelo operador concatenação Quaisquer sentenças de expressões regulares podem ser expressas como Autômato de Estado Finito (AEF). Nós podemos representar um AEF usando estados e transições entre estados. Há um estado inicial e um ou mais estados finais ou aceitantes. Na figura, o estado 0 é o estado inicial e o estado 2 é o estado de aceitação. Conforme os caracteres são lidos, fazemos uma transição de um estado para outro. Quando a primeira letra é lida, fazemos a transição para o estado 1, permanecemos neste estado à medida que mais letras ou dígitos são lidos. Quando lemos um caractere que não seja uma letra ou um dígito, passamos a aceitar o estado 2. Qualquer FSA pode ser expresso como um programa de computador. Por exemplo, nossa máquina de 3 estados é facilmente programada:
start: goto estado0 state0: read c if c = letra goto estado1 goto state0 state1: read c if c = letra goto estado1 if c = digito goto estado1 goto estado2 state2: accept string Esta é a técnica usada pelo lex. Expressões regulares são traduzidas por lex para um programa de computador que imita um AEF. Usando o próximo caractere de entrada e estado atual, o próximo estado é facilmente determinado pela indexação em uma tabela de estado gerada pelo computador. Agora podemos entender facilmente algumas limitações do lex. Por exemplo, lex não pode ser usado para reconhecer estruturas aninhadas, como parênteses. As estruturas aninhadas são manipuladas incorporando uma pilha. Sempre que encontrarmos um "(" nós empurramos na pilha. Quando um " )" é encontrado, combinamos com o topo da pilha e pop a pilha. Contudo o lex só tem estados e transições entre estados. Como não tem pilha, não é adequado para analisar estruturas aninhadas. Yacc aumenta um AEF com uma pilha e pode processar construções como parênteses com facilidade. O importante é usar a ferramenta certa para o trabalho. Lex é bom em correspondência de padrões. Yacc é apropriado para tarefas mais desafiadoras.
Praticando Expressões regulares são usadas para correspondência de padrões. Uma classe de caracteres define um único caractere e os operadores normais perdem seu significado. Dois operadores suportados em uma classe de caracteres são o hífen (" -") e circunflexo (" ^"). Quando usado entre dois caracteres, o hífen representa um intervalo de caracteres. O circunflexo, quando usado como o primeiro caractere, nega a expressão. Se dois padrões corresponderem à mesma sequência, a correspondência mais longa ganha. No caso de ambas as correspondências terem o mesmo tamanho, o primeiro padrão listado será usado. O arquivo Lex é dividido em três seções com a divisão das seções.... definições...... regras...... sub-rotinas... Isto é melhor ilustrado pelo exemplo. O primeiro exemplo é o mais curto possível. Crie um arquivo chamado analisador.l, e coloque o conteúdo a seguir: Em uma arquivo lex, a entrada é copiada para produzir um caractere de cada vez. O primeiro é sempre necessário, pois sempre deve haver uma seção de regras. No entanto, se não especificarmos nenhuma regra, a ação padrão é combinar tudo e copiá-lo para a saída. Os padrões para entrada e saída são stdin e stdout, respectivamente. Aqui está o mesmo exemplo com os padrões explicitamente codificados. Coloque o código a seguir no arquivo analisador.l:
/* reconhece tudo, menos nova linha */. ECHO; /* reconhece nova linha */ \n ECHO; int yywrap(void) { return 1; int main(void) { return 0; Na linha de comando do Linux faça: lex analisador.l gcc lex.yy.c -o analisador Em seguida execute o seu reconhecedor:./analisador e digite qualquer caracter. Dois padrões foram especificados na seção de regras. Cada padrão deve começar na primeira coluna. Em seguida, temos um espaço em branco (espaço, tabulação ou nova linha) e uma ação opcional associada ao padrão. A ação pode ser uma única declaração em linguagem C, ou múltiplas declarações em C, entre chaves. Qualquer coisa que não comece na primeira coluna é copiada literalmente para o arquivo C. Podemos aproveitar esse comportamento para especificar comentários em nosso arquivo Lex. Neste exemplo, existem dois padrões, "." e " \n", com uma ação ECHO associada a cada padrão. Várias macros e variáveis são predefinidas pelo Lex. ECHO é uma macro que escreve o código correspondido pelo padrão. Esta é a ação padrão para qualquer seqüência de caracteres sem correspondência. Normalmente, ECHO é definido como: #define ECHO fwrite(yytext, yyleng, 1, yyout) Variável yytext é um ponteiro para a cadeia combinada (terminada em NULL) e yyleng é o comprimento da cadeia combinada. Variável yyout é o arquivo de saída e o padrão é stdout. Função yywrap é chamada pelo lex quando a entrada está esgotada. Devolva 1 se estiver pronto ou 0 se mais processamento for necessário. Cada programa C requer uma função main.
Neste caso, nós simplesmente chamamos yylex, esse é o principal ponto de entrada para lex. Algumas implementações de lex inclui cópias de main e yywrap em uma biblioteca, eliminando assim a necessidade de codificá-los explicitamente. É por isso que nosso primeiro exemplo, o mais curto lex programa, funcionou corretamente. Aqui está um programa que não faz nada. Todas as entradas são correspondidas, mas nenhuma ação está associada a nenhum padrão, portanto, não haverá saída.. \n O exemplo a seguir preenche os números de linha para cada linha em um arquivo. Algumas implementações do lex predefini e calcula yylineno. O arquivo de entrada para o lex é yyin e assume como padrão stdin. Crie um arquivo com extensão.l (a extensão em Linux é indiferente, entretanto é bom para nos organizar). Neste arquivo criado cole o código a seguir: %{ int yylineno; % ^(.*)\n printf("%4d\t%s", ++yylineno, yytext); int main(int argc, char *argv[]) { yyin = fopen(argv[1], "r"); fclose(yyin); Na linha de comando do Linux faça: lex nome_arquivo.l gcc lex.yy.c -o analisador Em seguida execute o seu reconhecedor:./analisador e digite qualquer caracter. A seção de definições é composta de substituições, códigos e estados iniciais. O código na seção de definições é simplesmente copiado como está para o topo do arquivo C gerado e deve ser colocado entre colchetes com os marcadores " %{" e " %". Substituições simplificam regras de
correspondência de padrões. Por exemplo, podemos definir dígitos e letras: digito [0-9] letra [A-Za-z] %{ int cont; % /* reconhece identificadores */ {letra({letra {digito)* cont++; int main(void) { printf("numero de identificadores = %d\n", cont); return 0; O espaço em branco deve separar o termo definidor e a expressão associada. Referências a substituições na seção de regras são cercadas por chaves ( {letter) para distingui-las dos literais. Quando temos uma correspondência na seção de regras, o associado C código é executado. Aqui está um scanner que conta o número de caracteres, palavras e linhas em um arquivo (semelhante aunix wc): %{ int ncaracteres, npalavras, nlinhas; % \n { nlinhas++; ncaracteres++; [^ \t\n]+ { npalavrs++, nchar += yyleng;. { ncaracteres++; int main(void) { printf("%d\t%d\t%d\n", ncaracteres, npalavras, nlinhas); return 0; Repita com este código o teste que fizemos com os códigos anteriores. Referência Niemann, Tom. Lex & YACC. Disponível em: epaperpress.com.