1 / 94

Análise Léxica

Análise Léxica. Prof. Alexandre Monteiro Baseado em material cedido pelo Prof. Euclides Arcoverde Recife. Contatos. Prof. Guilherme Alexandre Monteiro Reinaldo Apelido: Alexandre Cordel E-mail/ gtalk : alexandrecordel@gmail.com greinaldo@fbv.edu.br

derick
Télécharger la présentation

Análise Léxica

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Análise Léxica Prof. AlexandreMonteiro Baseadoem material cedidopelo Prof. EuclidesArcoverde Recife

  2. Contatos • Prof. Guilherme Alexandre Monteiro Reinaldo • Apelido: Alexandre Cordel • E-mail/gtalk: alexandrecordel@gmail.com greinaldo@fbv.edu.br • Site: http://www.alexandrecordel.com.br/fbv • Celular: (81) 9801-1878

  3. Agenda • Definição de Compilador • Etapas da Compilação • Introdução à Análise Léxica • Implementação Manual de um Lexer

  4. Definição de Compilador

  5. Compilador • Definição geral: • É um programa que traduz um texto escrito em uma linguagem computacional (fonte) para um texto equivalente em outra linguagem computacional (alvo) • Entrada: código fonte (na ling. fonte) • Saída: código alvo (na ling. alvo) • Dependendo do propósito, podem ter nomes específicos

  6. Tipos de Compiladores • Assembler ou Montador • Tipo simples de compilador • A linguagem fonte é uma linguagem assembly (ou linguagem de montagem) • Correspondência direta com a linguagem de máquina • A linguagem alvo é uma linguagem de máquina

  7. Tipos de Compiladores • Compilador tradicional • Traduz de uma linguagem de alto nível para uma de baixo nível • Em muitos casos, o compilador gera código objeto, que é um código de máquina incompleto • Precisa passar por um linker para virar executável • Exemplo: gcc • Em outros casos, o compilador gera um código em linguagem de montagem

  8. Etapas da Compilação

  9. Etapas da Compilação • Tradicionalmente, os compiladores se dividem em um conjunto de etapas • Mostraremos as cinco etapas básicas a seguir • Podem existir também outras etapas intermediárias de otimização de código, omitidas na figura

  10. Análise Léxica Análise Sintática Analise Semântica Geração de Código Intermediário Geração de Código Final Etapas da Compilação

  11. Fases da Compilação • Fase de Análise • Subdivide o programa em partes constituintes • Cria uma estrutura abstrata do programa • Fase de Síntese • Constrói o programa na linguagem alvo • Gera código final

  12. Análise Léxica Análise Sintática Analise Semântica Geração de Código Intermediário Geração de Código Final Etapas da Compilação Front-End (Análise) Back-End (Síntese)

  13. Etapas da Compilação • Mas por que essa divisão em etapas? • Modularidade – deixa o compilador mais legível e mais fácil de manter • Eficiência – permite tratar mais a fundo cada etapa com técnicas especializadas • Portabilidade – facilita adaptar um compilador para • Receber outro código fonte (muda o front-end) • Gerar código para outra máquina (muda o back-end)

  14. Análise Léxica Análise Sintática Analise Semântica Geração de Código Intermediário Geração de Código Final Etapas da Compilação Front-End (Análise) Back-End (Síntese)

  15. Introdução à Análise Léxica

  16. Análise Léxica • Objetivo • Ler os caracteres do código fonte agrupando-os de maneira significativa (em lexemas) e classificando esses agrupamentos (em tokens) • Em outras palavras • Entrada: sequência de caracteres • Saída: sequência de tokens

  17. Relembrando... • Lexema: sequência de caracteres com significado interligado • Token: classificação dada ao lexema • Geralmente retornado junto com o próprio lexema ou outro atributo, como um ponteiro ou um valor numérico associado

  18. Relembrando... • Tokens especificados como expressões regulares: ABRE_PAR→ ( FECHA_PAR→) ATRIB →= ADD →+ MULT →* DEF →def ID→[_a-z][_a-z0-9]* NUM_INT →[0-9][0-9]* PT_VG →; WHITESPACE →[ \t\n\r]+

  19. Análise Léxica • O módulo de software responsável por essa etapa pode ser chamado de: • Analisador Léxico, Lexer ou Scanner • Além de retornar os tokens, ele pode: • Remover espaços em branco • Contar linhas e colunas, para identificar a localização de erros • Expandir macros

  20. Análise Léxica • Existem várias técnicas para construção de um lexer, inclusive técnicas de construção (semi) automática • Porém, iniciaremos vendo como fazer um lexer simples manualmente

  21. Implementação Manual de um Lexer

  22. Implementação Manual • Vamos começar implementando tokens em uma linguagem simples, chamada XPR-0 • Linguagem para especificar expressões • Tokens de 1 caractere apenas • Sem tratamento de espaços em branco

  23. Exemplo de Implementação • Tokens de XPR-0 NUMERO →[0-9] PT_VIRG→; ADD →+ MULT →* ABRE_PAR→ ( FECHA_PAR→)

  24. Exemplo de Implementação • Passos para implementar o lexer de XPR-0 • Implementar os tipos dos tokens • Implementar uma classe para o token • Implementar o lexer

  25. Exemplo de Implementação • Como implementar os tipos de tokens • Java: criar uma classe separada TokenType • Sugestão: usar “enum” de Java >= 5 • C: usar vários defines, com valores diferentes • Definir um token especial para indicar fim de arquivo • Exemplo em IDE (Eclipse ou Netbeans)

  26. Exemplo de Implementação • Como implementar os tokens • Criar classe Token • Precisa guardar pelo menos o tipo • Pode ter outras informações • O lexema • Uma subclassificação • O valor inteiro do token • Etc. • Exemplo em IDE (Eclipse ou Netbeans)

  27. Exemplo de Implementação • Como implementar o lexer • Criar classe Lexer • Método “nextToken()” • Lê o próximo caractere, classifica-o e retorna o token • Exemplo em IDE (Eclipse ou Netbeans)

  28. Implementação Manual • O exemplo anterior foi bem simples, apenas para dar uma noção de construção de um lexer • Complicações adicionais que podem surgir • Tratamento de espaço em branco • Tokens de vários caracteres • Tokens com prefixos comuns • Diferenciar identificadores de palavras-chave

  29. Melhorando o Lexer Manual

  30. Melhorando o Lexer • Espaço em branco • Fazer um laço para ler todo caractere considerado como espaço em branco • Analisar antes de qualquer outro token

  31. Melhorando o Lexer • Espaços em branco // ignora os espaços em branco e quebras de linha while (nextChar == ' ' || nextChar == '\t' || nextChar == '\r' || nextChar == '\n') { nextChar = this.readByte(); } // testar fim de arquivo ... // testar outros tokens ...

  32. Melhorando o Lexer • Tokens de vários caracteres • Faz uma decisão externa com base no primeiro símbolo • Usar switch (mais eficiente) ou if-else’s encadeados • Dentro, faz um laço do-while (ou while) • Cada símbolo válido deve ser concatenado ao lexema

  33. Melhorando o Lexer • Tokens de vários caracteres • Assuma que “lexema” é um objeto StringBuilder ... else if (Character.isDigit(nextChar)) { do { lexema.append((char) nextChar); nextChar = this.readByte(); } while (Character.isDigit(nextChar)); tipo = TokenType.NUMERO; } ...

  34. Melhorando o Lexer • Prefixos comuns • Se tokens de múltiplos caracteres tiverem parte em comum • Adiar a decisão sobre o tipo e deixa para fazer a decisão num nível mais interno • Continuar lendo os caracteres e armazenando no lexema até poder decidir

  35. Melhorando o Lexer • Prefixos comuns • Exemplo: tokens dos operadores “>=“ e “>” ... else if (nextChar == '>') { nextChar = this.readByte(); if (nextChar == '=') { tipo = TokenType.GREATER_EQ; nextChar = this.readByte(); } else { tipo = TokenType.GREATER; //não precisa ler o próximo char } } ...

  36. Melhorando o Lexer • Diferenciando identificadores de palavras-chave • Ler todo o lexema, como se fosse um identificador • Depois, compara o lexema inteiro com a lista de palavras-chave • Pode usar uma tabela hash (Hashtable, em Java) • Adicionar as palavras-chave com seus tipos de token • Após ler o lexema, é só consultar a tabela • Se não existir palavra-chave para aquele lexema, então é um identificador

  37. Hashtable • Estrutura de dados que mapeia chaves (keys) a valores (values) • Classe Hashtable (Java) • Método “put” recebe o par (chave,valor) • Método “get” recebe a chave e retorna o valor • Exemplo: associar “String” com “Integer” Hashtable numbers = new Hashtable(); numbers.put("one", new Integer(1)); numbers.put("two", new Integer(2)); Integer v = (Integer) numbers.get("one");

  38. Melhorando o Lexer • Palavras-chave • Criação da hash class Lexer { ... private Hashtable keywords; Lexer() { keywords.put(“if” , TokenType.IF); keywords.put(“else”, TokenType.ELSE); keywords.put(“int” , TokenType.INT); ... }

  39. Melhorando o Lexer • Palavras-chave • Reconhecimento dos tokens, em nextToken() if (Character.isLetter(nextChar)) { do { lexema.append((char)nextChar); nextChar = this.readByte(); } while (Character.isLetter(nextChar)); if (keywords.containsKey(lexema.toString())) { tipo = keywords.get(lexema.toString()); } else { tipo = TokenType.IDENTIFICADOR; } }

  40. Buffers de Leitura

  41. Por que usar buffers? • Para tratar situações em que é preciso olhar caracteres à frente • Na leitura de um ID, por exemplo, é preciso chegar num caractere inválido para parar • Como devolver este último caractere? • Para melhorar a performance • Ler um bloco de caracteres do disco é mais eficiente do que ler caractere a caractere

  42. Buffer Único • Lê um bloco de caracteres do arquivo para um array na memória • Geralmente, usa-se como tamanho do buffer o tamanho do bloco de disco • Exemplo: 4096 bytes (4 KB)

  43. t e m p = a Buffer Único • Variáveis para controlar o buffer • lexemeBegin: início do lexema atual • forward: próximo caractere a ser analisado pelo lexer • O lexema final fica entre lexemeBegin e forward lexemeBegin forward

  44. Buffer Único • Vantagens • Leitura mais eficiente do arquivo (em blocos) • Permite devolver um caractere, retornando o apontador forward • Porém, o uso de buffer único ainda traz problemas • Lexemas podem ficar “quebrados” no fim do buffer

  45. t u e x m p * = 1 0 a Buffers Duplos • Dois buffers de mesmo tamanho • Exemplo: dois buffers de 4kB • Evita que um lexema fique incompleto, desde que tokens não possam passar do tamanho de um buffer lexemeBegin forward

  46. Buffers Duplos • Um buffer é carregado quando o outro já foi completamente lido • A cada leitura de caractere (por meio da variável forward), é preciso testar os limites • Se chegou ao fim de um buffer, muda para o próximo e recarrega • Esse teste ainda pode ser otimizado...

  47. t e m p = a eof u x * 1 0 eof Sentinelas • São caracteres especiais usados para demarcar o fim do buffer • Não precisa testar o fim do buffer a cada passo, basta testar quando achar esse caractere • Geralmente se usa o mesmo símbolo usado para fim de arquivo – eof código fonte código fonte sentinela sentinela

  48. t e m p = a eof u x ; eof eof Sentinelas • Como diferenciar um sentinela de um fim de arquivo real? • Basta consultar a posição do caractere • Sentinelas ficam em posições fixas no fim do buffer • Um fim de arquivo real aparece em qualquer outra posição sentinela fim de arquivo sentinela

  49. Sobre Buffers e Sentinelas • São técnicas para quem está muito preocupado com eficiência na compilação • Não é para quem faz um compilador em Java, é para quem faz em C ou Assembly

  50. Expressões Regulares

More Related