Projeto de Linguagens de
Programação
Aula 3: Analisador Léxico
Sumário
1.
Primeira etapa
2.Erros léxicos
Análise Léxica
Análise Sintática
Análise Semântica
Geração de código intermediário
Otimização de código
Geração de código Síntese
Análise
Programa Fonte
Programa Executável
Primeira etapa de um compilador
Função
Ler o arquivo com o programa-fonte
Identificar os tokens correspondentes
Relatar erros
Tipos de tokens
Identificadores
Palavras reservadas e símbolos especiais
Primeira etapa de um compilador
Em geral, subordinado ao analisador sintático
Sub-rotina do analisador sintático: a cada chamada, o analisador léxico retorna para o analisador sintático uma cadeia lida e o token correspondente.
O analisador sintático combina os tokens e verifica a boa formação (sintaxe) do programa-fonte usando a gramática da linguagem
Por que separar o analisador léxico do
sintático?
Modularização
Projeto mais simples de cada etapa
Maior eficiência de cada processo: possibilidade de uso de técnicas específicas e métodos de otimização locais
Maior portabilidade: especificidades da linguagem de
programação podem ser resolvidas na análise léxica
Facilidade de manutenção
Outras funções do analisador léxico
Consumir comentários e caracteres não imprimíveis
(espaço em branco, tabulação, código de nova linha)
Se a gramática fosse se responsabilizar por isso, ela seria demasiadamente complicada
Possível manipulação da tabela de símbolos Relacionar as mensagens de erro emitidas pelo
compilador com o programa-fonte
Deve-se manter contagem do número de linhas
Exemplos
Tokens
Type Examples
ID foo n14 last
NUM 73 0 00 515 082
REAL 66.1 .5 10. 1e67 5.5e-10 IF if
Exemplos
Não-tokens
comment /* try again */
preprocessor directive #include<stdio.h>
preprocessor directive #define NUMS 5, 6
macro NUMS
Exemplo
Para um programa:
float match0(char *s) /* find a zero */ {if (!strncmp(s, "0.0", 3))
return 0.; }
o analisador léxico irá retornar as strings:
Erros léxicos
Símbolo não pertencente ao conjunto de símbolos
terminais da linguagem: @
Identificador mal formado: j@, 1a
Tamanho do identificador: minha_variável_para_... Número mal formado: 2.a3
Tamanho excessivo do número: 5555555555555555
Fim de arquivo inesperado (comentário não fechado):
{...
Erros Léxicos
São limitados os erros detectáveis nessa
etapa.
O analisador léxico possui uma visão local do
programa-fonte, sem contexto
Projeto do analisador léxico
É desejável que se usem notações formais para
especificar e reconhecer a estrutura dos tokens que serão retornados pelo analisador léxico
Evitam-se erros
Mapeamento mais consistente e direto para o programa de análise léxica
Notações
Gramáticas ou expressões regulares: especificação de tokens
Expressões regulares
Determinam conjuntos de cadeias válidas.
Uma linguagem é um conjunto de strings.
Uma string é uma cadeia finita de símbolos.
Os símbolos são obtidos de um alfabeto finito.
Cada expressão regular estabelece um
Notação de Expressões Regulares
a Um caracter ordinário entendido por ele mesmo. ∊ Uma string vazia.
M | N Alternância, escolher entre M ou N.
M · N Concatenação, um M seguido por um N.
MN Outra forma de escrever concatenação.
M* Repetição, zero ou mais vezes.
M+ Repetição, uma ou mais vezes.
M? Opcional, zero ou uma ocorrência de M.
[a − zA − Z] Alternância do conjunto de caracteres.
. Um ponto consiste em qualquer caracter simples exceto novalinha.
Exemplos
O que cada expressão regular abaixo representa?
(0 | 1)* · 0
Números binários múltiplos de 2.
b*(abb*)*(a|∊)
Strings de a’s e b’s com nenhum a consecutivo.
(a|b)*aa(a|b)*
Expressões regulares para alguns
tokens
if IF
[a-z][a-z0-9]* ID
[0-9]+ NUM
([0-9]+"."[0-9]*) | ([0-9]*"."[0-9]+) REAL
("--"[a-z]*"\n")|(" "|"\n"|"\t")+ no token, just white space
Autômatos Finitos
Expressões regulares são convenientes para
especificar tokens léxicos, mas precisamos de um formalismo que possa ser implementado como um programa de computador.
Um autômato finito tem um conjunto finito de
estados; ligações de um estado para outro, e cada ligação é rotulada com um símbolo.
Um estado é o estado inicial, e certos estados são
Autômatos finitos
Modelos matemáticos
Conjunto de estados S
Conjunto de símbolos de entrada Σ
Funções de transição que mapeiam um par estado-símbolo de entrada em um novo estado
Um estado inicial s0
Um conjunto de estados finais F para aceitação de cadeias
Reconhecimento de cadeias válidas
Exemplo
S={0,1,2,3},
Σ
={a,b}, s0=0, F={3}
Exemplo
Representação em tabela de transição
Vantagem: elegância e generalidade
Execução do autômato
Se autômato determinístico (i.e., não há transições λ e, para cada estado s e símbolo de entrada a, existe somente uma transição possível), o seguinte algoritmo pode ser aplicado:
s:=s0
c:=próximo_caractere() enquanto (c<>eof) faça
s:=transição(s,c)
c:=próximo_caractere()
fim
se s for um estado final
então retornar “cadeia aceita”
Execução do autômato
Opção: incorporação das transições no código do programa
Tabela de transição não é mais necessária
s:=s0
enquanto (verdadeiro) faça c:=próximo_caractere() case (s)
0: se (c=a) então s:=1
senão se (c=b) então s:=0
senão retornar “falhou”
1: se (c=b) então s:=2
senão retornar “falhou”
2: se (c=eof) então retornar “cadeia aceita”
senão retornar “falhou”
Tokens de um programa
Não basta identificar o token, deve-se
retorná-lo ao analisador sintático junto com a
cadeia correspondente
Concatenação da cadeia conforme o
autômato é percorrido
Associação de ações aos estados finais do
autômato
Às vezes, para se decidir por um token,
Autômatos finitos dos tokens léxicos
[a-z][a-z0-9]*
[0-9]+
([0-9]+"."[0-9]*) | ([0-9]*"."[0-9]+)
("--"[a-z]*"\n")| (" "|"\n"|"\t")+
Nondeterministic Finite Automata
(NFA)
Um autômato finito não-determinístico é aquele que possui uma escolha de ligações – rotuladas com o mesmo símbolo – para sair de um estado.
Ou o NFA pode ter ligações especiais rotuladas com ∊ (“vazio”) que pode ser seguida sem eliminar qualquer símbolo da
Conversões
Autômatos não-determinísticos são uma poderosa
notação porquê é fácil converter uma (estática, declarativa) expressão regular em um (simulável, quase-executável) NFA.
Implementar DFAs em programas de computador é
fácil, mas implementar NFAs é mais difícil visto que computadores não tem um “bom” hardware de
adivinhação.
Mas é possível converter um NFA para um DFA
Deterministic
finite automaton (DFA)
Num Autômato finito determinístico, nunca 2 ligações saindo do mesmo estado são rotuladas com o mesmo símbolo.
Um DFA aceita ou rejeita uma string:
Iniciando no estado inicial, para cada caracter na string de
entrada o autômato segue exatamente uma ligação para chegar ao próximo estado.
A ligação deve ser rotulada com o caracter de entrada.
Depois de fazer n transições para uma string de n
-caracteres, se o autômato está em um estado final, então ele aceita a string.
Se ele não está em um estado final, ou se em alguns pontos
não existem ligações rotuladas apropriadamente para seguir, ele rejeita.
Estrutura de dados: matriz de transição
Um vetor bidimensional subscrito por número de estados e caractéres de entrada.
Estado 0 indica ausência de ligações.
Exercícios
1.
Escreva expressões regulares para cada
alternativa abaixo:
1.
Strings sobre o alfabeto {
a, b, c
} onde o
primeiro
a
precede o primeiro
b
.
2.
Strings sobre o alfabeto {
a, b, c
} com um
número par de
a
's.
3.
Números binários múltiplos de quatro.
4.Números binários maiores que 101001.
5.Strings sobre o alfabeto {
a, b, c
} que não
Exercícios
2.
Explique porque não é surpresa que não
existe expressões regulares definindo cada
sentença abaixo.
1.
Strings de
a
's e
b
's onde existem mais
a
's
que
b
's.
2.
Strings de
a
's e
b
's que são palíndromes
(que são iguais da esquerda para a direita e
vice-versa).
Exercícios
3.