Aula 06: Ferramentas: Flex
Objetivos
Ao final desta aula, você será capaz de:
- Utilizar o Flex para gerar scanners automaticamente a partir de especificações em arquivos
.l. - Escrever definições (apelidos), regras com padrões e ações em C (ou o backend escolhido).
- Integrar o scanner gerado com um parser (ex.: Bison) via tokens e
yylval. - Explicar como o Flex resolve conflitos (ordem das regras, maximal munch) e como isso se relaciona com a teoria ER → AFD.
Motivação prática
Escrever scanners manualmente (com switch/if ou tabelas de transição) tende a:
- Introduzir bugs em casos de borda (ERs complexas, lookahead).
- Dificultar a manutenção quando a linguagem ganha novos operadores ou palavras-chave.
- Não aproveitar algoritmos clássicos (Thompson, subconjuntos, minimização) de forma automática.
Ferramentas como Flex encapsulam a pipeline ER → AFN → AFD → código:
- Você especifica o que é cada token (em ERs e ações).
- A ferramenta gera como reconhecer (AFD simulado em C).
Pipeline do Flex
O diagrama abaixo resume o fluxo: do arquivo de especificação ao programa executável.
flowchart LR
Arquivo[Arquivo .l] --> Flex[Flex]
Flex --> Código[lex.yy.c]
Código --> Comp[Compilador C]
Comp --> Exec[Executável]
Exec --> Entrada[Entrada]
Entrada --> yylex[yylex]
yylex --> Tokens[Tokens]
O Flex lê o .l, aplica construções equivalentes a Thompson e subconjuntos (e possivelmente minimização), e gera um arquivo C (por exemplo, lex.yy.c) que contém a função yylex() e as tabelas ou o código do AFD. Esse arquivo é compilado e linkado com o resto do projeto (por exemplo, o parser gerado pelo Bison).
Conteúdo
1. Estrutura do arquivo .l
O arquivo Flex é dividido em três seções separadas por %%.
Seção 1: Definições
- Bloco
%{ ... %}: código C (ou outro) copiado literalmente para o início do arquivo gerado (includes, variáveis globais, declarações). - Apelidos (nomes): definições da forma
NOME expressãopara reutilizar ERs. Ex.:DIGIT [0-9],ID [a-zA-Z][a-zA-Z0-9]*.
Seção 2: Regras
Cada linha tem a forma:
padrão ação
- Padrão: uma ER (podendo usar apelidos entre chaves, ex.:
{DIGIT}+). - Ação: código C executado quando o padrão casa. Pode acessar
yytext(lexema atual),yyleng(tamanho), e deve retornar o token (inteiro) para o parser quando aplicável. Ação vazia significa “ignorar” (ex.: espaços e comentários).
Seção 3: Código do usuário
Código C copiado ao final do arquivo gerado (ex.: main() que chama yylex() em loop, ou funções auxiliares). Em projetos com Bison, o main costuma estar em outro arquivo que chama o parser; o parser, por sua vez, chama yylex() para obter tokens.
2. Exemplo completo comentado
O exemplo abaixo ilustra um scanner mínimo para inteiros, identificadores, espaços e um operador. Em um compilador real, as ações retornariam constantes de token definidas em um header compartilhado com o parser (ex.: gerado pelo Bison).
%{
#include <stdio.h>
int line_num = 1;
%}
DIGIT [0-9]
ID [a-zA-Z][a-zA-Z0-9]*
%%
{DIGIT}+ { printf("NUMBER: %s\n", yytext); return NUMBER; }
{ID} { printf("ID: %s\n", yytext); return ID; }
"+" { return PLUS; }
[ \t] { /* ignorar */ }
\n { line_num++; }
. { fprintf(stderr, "Erro linha %d: %s\n", line_num, yytext); }
%%
int main(void) {
while (yylex() != 0) { }
return 0;
}{DIGIT}+: um ou mais dígitos → token NUMBER.{ID}: identificador → token ID."+": operador literal → token PLUS.[ \t]: espaço ou tab → ignorado (sem return).\n: nova linha → atualiza contador de linha..: qualquer outro caractere → erros léxicos (e opcionalmente return de token de erro).
3. Como o Flex resolve conflitos
Dois tipos de conflito aparecem quando várias regras podem casar com o mesmo trecho da entrada:
Mesma entrada, várias regras
Ex.: a stringifcasa com uma regra para a palavra-chaveife com a regra para identificador.
Regra: o Flex escolhe a primeira regra listada que casar. Por isso palavras-chave devem vir antes da regra de identificador.Prefixo comum, tamanhos diferentes
Ex.:<=pode ser reconhecido como<e depois=, ou como um único token<=.
Regra (maximal munch): o Flex escolhe o maior lexema possível. Assim<=é reconhecido como um token se houver uma regra para"<=".
Essas regras tornam o comportamento determinístico e previsível, alinhado à simulação de um AFD (sempre uma única transição por caractere e uma única regra “vencedora” ao final do lexema).
4. Integração com Bison/Yacc
Quando o scanner é usado com um parser gerado pelo Bison:
- Os valores de token (NUMBER, ID, PLUS, etc.) são definidos no Bison (ou em um header gerado por ele) e declarados no início do
.lcom%option headerou incluindo o header do parser. - O valor semântico do token (ex.: o número inteiro para NUMBER, o lexema para ID) é passado pela variável global
yylval(ou por argumentos, dependendo da opção). O parser usa esse valor para construir a AST. - O Flex é invocado pelo parser: a cada necessidade de um novo token, o parser chama
yylex(); o Flex retorna o tipo do token e preencheyylval.
flowchart LR
Parser[Parser Bison] --> yylex
yylex[yylex Flex] --> Tok[Token + yylval]
Tok --> Parser
5. Variáveis e funções úteis
| Nome | Descrição |
|---|---|
yytext |
Ponteiro para o lexema atual (string terminada em \0). |
yyleng |
Comprimento do lexema em yytext. |
yylval |
Valor semântico do token (compartilhado com Bison). |
yylex() |
Função que retorna o próximo token; atualiza yytext, yyleng, yylval. |
yyin |
Arquivo de entrada (por padrão stdin). |
Conexões com o projeto Mini-Pascal
- Na Etapa 2 você implementa um scanner manual; os conceitos (padrões, lexemas, tokens, lookahead, maximal munch) são os mesmos que o Flex automatiza.
- Na Etapa 4, ao usar ANTLR4, a especificação léxica é feita na gramática (.g4), não em um arquivo .l; mesmo assim, a ideia de “regras por padrão + ação” e a resolução de conflitos são análogas.
- Entender Flex ajuda a ler documentação e exemplos clássicos de compiladores em C e a comparar com geradores baseados em gramáticas (ANTLR).
Resumo
- O Flex gera um scanner em C a partir de um arquivo
.lcom definições, regras (padrão + ação) e código do usuário. - A pipeline é: .l → Flex → lex.yy.c → compilação → executável;
yylex()simula o AFD sobre a entrada. - Conflitos: primeira regra que casar vence; maximal munch para lexemas maiores.
- Integração com Bison: tokens definidos no parser; valor semântico em
yylval; parser chamayylex()para obter tokens.
Perguntas de reflexão
- Por que colocar a regra de identificador antes da regra de palavras-chave produziria comportamento indesejado?
- Como você passaria o valor numérico de um token NUMBER para o parser usando
yylval? - Que vantagens e desvantagens você vê em usar Flex em vez de um scanner manual para o Mini-Pascal?
Referências
- Aho et al. (2006). Compilers: Principles, Techniques, and Tools. Cap. 3 (Lex).
- Documentação do Flex: flex.sourceforge.net.
Materiais da aula
Última atualização: 08/03/2026