Baixe o app para aproveitar ainda mais
Prévia do material em texto
Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches LINGUAGENS E COMPILADORES EEL101 SEMANA 7 REVISÃO Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches SEMANA 1 LINGUAGENS DE PROGRAMAÇÃO, COMPONENTES E ANÁLISE LÉXICA CONCEITOS DE LINGUAGENS DE PROGRAMAÇÃO • Sintática: É a notação utilizada pelo programador para especificar ações a serem executadas por um computador. • Semântica: É o conceito que envolve o significado, como IF, While etc. • Compilador: É um programa que traduz código escrito em uma determinada linguagem (código-fonte) em código escrito em outra linguagem (linguagem-objeto). Exemplos: • Traduzir de C++ para C • Traduzir de Java para JVM • Traduzir de C para C Obs.: Traduzir de C para C tem o intuído de melhorar o código, mas nem sempre isso acontece. ESTRUTURA LÓGICA FRONT-END E BACK-END Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches Definir corretamente o método de tradução que será usado em seu projeto pode ser extremamente útil, especialmente quando se trata de portabilidade ou de desempenho. Linguagens compiladas e interpretadas envolvem muito mais do que apenas converter o código para uma linguagem de máquina. O compilador, durante sua execução, realiza operações de análise Léxica. ANÁLISE LÉXICA LÉXICO • Conjunto de palavras de um determinado idioma • Conjunto de objetos de uma determinada linguagem ANALISADOR LÉXICO • Recebe como entrada uma string e retorna (devolve) tokens Tokens é representação de um lexema de linguagem • Retira símbolos sem valor sintático/semântico DEFINIÇÃO • Entrada: Arquivo de texto contendo as strings segundo alguma codificação (ASCII, UTF-8 etc.). • Saída: Tokens - representação dos lexemas (objetos léxicos da linguagem, ex.: IF, ELSE etc.) sob a forma de estrutura de dados (classe, valor). Classe: indica a classificação que pode ser número, identificador, palavra-reservada etc. Valor: o valor do token, por exemplo, seu valor numérico – “12” 12. Os tokens usualmente são definidos pelo seu lexema (string ou sequência de caracteres que compõem um único token) e atributos adicionais. • Os tokens podem ser entregues ao parser como tuplas na forma <a, b, ..., n > assim, a entrada: Parser é um analisador sintático. Sua função é ler uma entrada de dados que possuem certas regras específicas - em geral, é um texto reconhecível por humanos - e montar uma estrutura de como é sua composição. Tuplas são uma sequência (grupo ordenado) de elementos (itens) separados por vírgulas, delimitados ou não por parênteses, isto é, os parênteses não são obrigatórios. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches Exemplo: a = b + 3 • poderia gerar as tuplas <classe, valor>: <id, a> <=,> <id, b> <+,> <num, 3> OS OBJETIVO SÃO: Remoção de espaços em branco e comentários. Identificação de constantes. Identificação de palavras-reservadas (construções da linguagem). Reconhecimento dos identificadores. Separação entre o analisador sintático e a representação da entrada. ATRIBUTOS DOS TOKENS Quando um lexema é reconhecido por um padrão, o analisador léxico deve providenciar outras informações adicionais (atributos) conforme o padrão. EXEMPLOS INTERAÇÃO ENTRE LÉXICO E SINTÁTICO Um analisador léxico interage com a entrada e com o parser (analisador sintático ou gramatical): Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches TERMINOLOGIA BÁSICA E SIMBOLOGIA • Linguagem: Conjunto (coleção) de cadeias de símbolos • Cadeias: Sentenças (ou palavras, strings) da linguagem • Símbolos: Elementos de um alfabeto que justapostos formam as sentenças (chamados de tokens na linguagem léxica) Alfabeto: Conjunto finito (em nosso caso de símbolos codificados) • Formas de representação de linguagens infinitas: Conjunto de leis de formação das cadeias (gramática) Conjunto de regras de aceitação das cadeias (reconhecedores) Conjunto de funções (funcional) • Derivação: Utilizar leis de formação para obter uma sentença EXPRESSÕES REGULARES Constituída a partir de regras de definição aplicadas a um conjunto de expressões regulares mais simples. RECONHECEDORES • Reconhecedor é um dispositivo conceitual composto de texto de entrada (cadeia de símbolos). Ele reconhece se o elemento faz parte de alguma linguagem. • Há dois tipos de reconhecedores: Determinísticos (um único movimento para qualquer configuração); e Não determinísticos (conjunto de movimentos possíveis para cada configuração). • Um reconhecedor pode definir uma linguagem. • Para linguagens regulares o reconhecedor empregado é o autômato de estados finitos (DFA ou NFA). O autômato finito, ou máquina de estados finitos, é o primeiro modelo computacional de definição de linguagens que são definidas por mecanismo de reconhecimento, que pode ser encarado como um teste aplicado a cada caractere da palavra. GERADOR DE ANALISADORES LÉXICOS Auxiliam na construção de analisadores léxicos. Utilizam expressões regulares para descrever tokens. Permitem a combinação da identificação de padrões com execução de ações. Compilador e linguagem Lex. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches OBSERVAÇÕES GERAIS A leitura do código-fonte é realizada por meio do analisador léxico. Ele lê o código-fonte, caractere por caractere, e divide o código escrito, por meio de símbolos léxicos, conhecidos como tokens, que são armazenados na tabela de símbolos. Um analisador léxico: Agrupa as características em tokens. Divide os tokens em agrupamentos. Descarta comentários. Descarta espaços em branco. Os pré-processadores estão sendo cada vez menos utilizados, na medida em que linguagens mais atuais oferecem recursos mais abstratos do que aqueles orientados ao léxico. Existem linguagens recentes, como Java, que não possuem pré-processadores, pois não há necessidade de fazer alterações no código-fonte a partir de decisões no tempo de compilação. O esperado em uma linguagem de programação é que os símbolos sejam utilizados de maneira que façam sentido quando observados um em relação ao outro, assim como na linguagem natural, em que as palavras são usadas de forma lógica para que as frases tenham sentido. Por exemplo: Um erro sintático é um caso em que as “frases” do programa estão mal formuladas. As palavras se juntam para formar expressões, orações e frases; trata-se da sintaxe. Erros sintáticos são bastante comuns, dentre os quais podemos mencionar parênteses que abrem, mas não fecham. Uma árvore de análise sintática de uma gramática de atributos é estrutura que está baseada em sua gramática associada, com um conjunto possivelmente vazio de valores de atributos anexado a cada nó. A análise semântica pode ser consubstanciada pela checagem de tipos, pela verificação de fluxos de controle e pela verificação de unicidade da declaração de variáveis. A semântica refere-se ao que é referido. Frases semelhantes em linguagem natural podem ser gramaticalmente corretas, mas não terem significado. O compilador é um programa de computador que: Lê um código-fonte. Lê um código-fonte escrito em uma linguagem de alto nível. Traduz o código para uma linguagem de baixo nível. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches SEMANA 2 RECONHECIMENTO DESCENDENTE DE LINGUAGENS DE PROGRAMAÇÃO ANÁLISE SINTÁTICA A análise sintáticatem a função de combinar a lista de tokens. Criação de uma estrutura chamada Árvore Sintática. Árvore sintática é uma estrutura de dados em árvore, que representa a estrutura sintática de uma cadeia de acordo com alguma gramática formal. Em uma árvore sintática, os nós internos são rotulados por símbolos não terminais da gramática, enquanto os nós-folha são rotulados por símbolos terminais da gramática. Árvore Sintática • Pode ser representada como uma árvore. A raiz é o símbolo inicial. Resultados da produção dos símbolos não terminais são filhos. As folhas devem conter apenas símbolos terminais. Lendo as folhas da esquerda para a direita temos a palavra derivada. Produções que levam ao vazio também devem ser representadas, apesar de serem ignoradas na formação da palavra. Exemplo: Quando uma gramática permite diferentes árvores sintáticas, ela é denominada ambígua. A análise sintática também deve rejeitar tokens inválidos e reportar erros sintáticos. A análise sintática é mais complexa em natureza do que a análise léxica, pois necessita de uma linguagem mais avançada. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches LINGUAGENS LIVRE DE CONTEXTO “Constituem um conjunto de linguagens que podem ser geradas por gramáticas livres de contextos (GLC), reconhecidas por autômatos de pilha”. A maioria dos construtores das LPs são expressos em GLC • Linguagens são projetadas a partir de GLC MÉTODOS DE ANÁLISE SINTÁTICA Podem seguir duas abordagens: MÉTODOS BOTTOM-UP Utilizam derivação inversa. Tentam casar partes da entrada com o lado direto das produções. MÉTODOS TOP-DOWN Sempre partem da raiz (top-down). São baseados em derivações sucessivas. Devem usar busca direcionada. ANÁLISE SINTÁTICA DESCENDENTE (TOP-DOWN) Análise preditiva recursiva • Construção de um conjunto de procedimentos, normalmente recursivos. Análise preditiva LL(1) • Implementa o método descendente utilizando explicitamente uma pilha. Limitações do método: • Entra em ciclo (loop) para gramáticas recursivas à esquerda. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches SEMANA 3 RECONHECIMENTO ASCENDENTE DE LINGUAGENS DE PROGRAMAÇÃO ANÁLISE SINTÁTICA ASCENDENTE Corresponde à construção de uma árvore de derivação para uma cadeia de entrada a partir das folhas em direção à raiz. A estratégia de análise sintática ascendente buscar e efetuar substituições sistemáticas de símbolos terminais por não terminais da gramática. Caso seja possível chegar ao símbolo não terminal inicial da gramática, a cadeia é considerada reconhecida. Existem dois métodos mais utilizados: • Precedência simples e precedência de operadores. • Métodos LR. OPERAÇÕES DA ANÁLISE SINTÁTICA ASCENDENTE SHIFT (empilhar) REDUCE (reduzir) ACEITAÇÃO (término) ERRO (descobre erro sintático) ANALISADORES LR(K) Analisadores redutores eficientes que leem a sentença em análise da esquerda para a direita. São capazes de reconhecer praticamente todas as estruturas sintáticas. Têm como desvantagem a dificuldade da implementação. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches Os analisadores LR são classificados quanto ao tipo de tabela de análise SLR (Simple LR): Fáceis de implementar, porém aplicáveis a uma classe restrita. LR Canônicos: Mais poderosos, podendo ser aplicados a um grande número de linguagens livres de contexto, porém de mais alta complexidade. LALR (Look Ahead LR): Nível intermediário de complexidade e implementação eficiente que funciona para a maioria das linguagens de programação GERADOR DE ANALISADORES SINTÁTICOS DESCENDENTES EXISTEM VÁRIOS GERADORES DE PARSER (PARSERS GENERATORS) Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches SEMANA 4 TRADUÇÃO DIRIGIDA POR SINTAXE DEPENDÊNCIA DE CONTEXTO Há características da estrutura das linguagens de programação que não podem ser descritas usando sintaxe livre de contexto, ou seja, depende de contexto. GRAMÁTICA DE ATRIBUTOS Construídas para a definição de sintaxe dependente de contexto e construção de compiladores. É uma gramática livre de contexto acrescida de: • Um conjunto de valores de atributos para cada símbolo gramatical. • Um conjunto de funções que define certos atributos dos não terminais de cada regra. • Um conjunto (possivelmente vazio) de predicados para verificar a consistência de atributos de cada regra. ATRIBUTOS Acrescentam informação aos símbolos da gramática. Seus valores são calculados através de regras semânticas. Para cada instância distinta de um símbolo gramatical haverá uma instância separada do atributo. Atributos podem herdados ou sintetizados. Um atributo de um símbolo gramatical é dito sintetizado se ele é computado por meio dos atributos de seus descendentes em uma árvore de derivação sintática. ORDEM DE AVALIAÇÃO DOS ATRIBUTOS • As regras semânticas definem dependências entre instâncias de atributos, as quais definem a ordem de avaliação. • Pode ser necessário delinear um grafo de dependência para resolver as dependências e estabelecer a ordem de avaliação. AVALIAÇÃO DE ATRIBUTOS Se a ordem de análise é consistente com a ordem de avaliação, então a avaliação dos atributos pode ser feita durante a fase de análise, sem necessidade de explicitamente construir a árvore de análise. Tipos de esquemas de tradução: Esquemas S-atribuídos: opera somente com atributos sintetizados. Esquemas L-atribuídos: opera somente com atributos herdados. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches TRADUÇÃO DIRIGIDA POR SINTAXE A tradução de um texto em linguagem-fonte para a linguagem-objeto pode ser realizada a partir da sintaxe, para isso é necessário: Acompanhar a sintaxe livre de contexto através da árvore sintática. Tratar a sintaxe dependente de contexto e armazenar os valores dos atributos. Usar esta estrutura pronta durante a análise sintática para gerar código. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches SEMANA 5 PADRÕES PARA REPRESENTAÇÃO INTERMEDIÁRIA ESTRUTURA GERAL DE UM COMPILADOR CÓDIGO INTERMEDIÁRIO A geração de código intermediário é realizada a partir da árvore de derivação produzindo um segmento de código. Normalmente é feito um passo intermediário. Exemplo: Geração de uma representação intermediária do código. O gerador de código intermediário usa as estruturas produzidas pelo analisador sintático e verificadas pelo analisador semântico para criar uma sequência de instruções simples, denominada “código intermediário”. Está entre a linguagem de alto nível e a linguagem de baixo nível. O código intermediário é o que mais se aproxima do programa executável, porém ele passa por mais etapas até virar o executável final. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches VANTAGENS Possibilita a otimização do código intermediário. Simplifica a implementação do compilador. Possibilita a tradução do código intermediário para diversas máquinas. DESVANTAGEM O compilador precisa realizar um passo a mais, logo a tradução do código-fonte para o objeto leva a uma compilação mais lenta. IMPLEMENTAÇÃO E ESQUEMA DE TRADUÇÃO A implementação pode ser efetuada através de quádruplas ou triplas Quádruplas: 1 operador, 2 operandos, resultado. Triplas: 1 operador, 2 operandos (ponteiros para a estrutura). Esquema de Tradução usa duas outras funções: Geratemp: Gera o nome deuma variável temporária. Geracod: Gera uma cadeia de texto correspondente a uma instrução de código intermediário. Após uma variável ser referenciada, ela pode ser descartada, e pode-se controlar a reutilização usando um contador de variáveis. Algoritmo: Sempre que um novo temporário é gerado, usa-se o contador e incrementa-se o valor 1. Sempre que um temporário é usado como operando, decrementa-se o valor 1 do contador. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches SEMANA 6 AMBIENTES DE EXECUÇÃO ORGANIZAÇÃO DE MEMÓRIA DURANTE A EXECUÇÃO A alocação e liberação de objetos de dados é gerenciado pelo ambiente de tempo de execução (runtime environment) Cada execução de um procedimento é referenciada como uma ativação. No caso de chamadas recursivas, várias ativações de um mesmo procedimento estão “vivas” em determinado instante. A representação de um objeto de dados em tempo de execução é determinado pelo seu tipo. Frequentemente tipos de dados elementares, tais como caracteres inteiros e reais, podem ser representados por objetos de dados equivalentes na máquina-alvo. Um compilador deve organizar o espaço a ser ocupado por um programa, e esse espaço deve conter: Instruções executáveis em linguagem de máquina – área de código. Armazenamento de informações em processamento (variáveis, constantes) – área de dados. DURANTE A EXECUÇÃO Instruções de máquina da área de código são executadas. Posições de memória da área de dados são consultadas e alteradas pelas instruções. Subprogramas (sub-rotinas) são ativados e desativados. As instruções surgem da tradução do código intermediário, com consultas à tabela de símbolos. VISUALIZAÇÃO DA ÁREA DE CÓDIGO Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches DEPENDÊNCIA ENTRE SEMÂNTICA E ÁREA DE DADOS • O que ocorre com as variáveis locais e seus valores quando seu subprograma “finaliza”? Em linguagens estáticas em geral não se perde o valor, já em linguagens com C, C++, elas perdem o valor, e para isso não ocorrer deve-se obrigatoriamente informar que são estáticas. • Em um subprograma, pode-se referenciar variáveis não locais? Depende da linguagem, depende do que foi inserido na linguagem, ou seja, em algumas sim e em outras não. • Quais os modos de passagem de parâmetros? Em C é tudo passado por valor, se quiser passar por referência, vai ter que inserir * para indicar que vai passar por referência (por ponteiro), e na chamada utilizar & na frente de uma variável para indicar que é um ponteiro para aquela variável. Em linguagens como Pascal existe a passagem por referência, na qual o programador apenas indica, não há necessidade de colocar & ou nada. Ou seja, também depende da linguagem. • Os subprogramas podem ser recursivos? Linguagens como COBOL ou FORTRAM não têm essa possibilidade, mas todas as linguagens descendentes do ALGOL sim. • A desalocação de memória deve ser explicitada? Em algumas linguagens como C e C++ tem que desalocar explicitamente, já em outras linguagens, como Java, não se faz desalocação de memória, porém há outros recursos para fazê-lo, como o coletor de lixo (Garbage Collector). • Subprogramas podem ter parâmetros? É usual que tenham, mas pode não ter, não é obrigatório. ALOCAÇÃO ESTÁTICA E DINÂMICA ALOCAÇÃO ESTÁTICA A posição de memória de cada variável é conhecida antes da execução. Não há necessidade de gerenciamento em tempo de execução. Chamadas de subprogramas são mais rápidas. O local e o número de bytes de cada variável é fixo. Subprogramas recursivos são muito limitados. ALOCAÇÃO DINÂMICA As variáveis locais de um subprograma ficam alocadas na memória somente enquanto ele estiver em execução. Memória para variáveis indexadas pode ser alocada em uma quantidade conforme a necessidade. Recursividade pode ser usada de modo bem menos restrito. Um subprograma pode ter várias ativações simultâneas. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches É necessário incluir no código-objeto instruções para gerenciamento de memória em tempo de execução. Chamadas de subprogramas são mais demoradas. AMBIENTES DE EXECUÇÃO ESTÁTICOS É o tipo mais simples de ambiente. Não há necessidade de código para gerenciamento de memória durante a execução. Cada subprograma tem um único registro de ativação alocado antes da execução. As variáveis de todos os subprogramas permanecem alocadas durante toda a execução do programa. Os valores das variáveis locais se mantêm entre duas chamadas do subprograma. AMBIENTES DE EXECUÇÃO BASEADOS EM PILHAS Adotados para permitir recursividade de modo mais amplo. Um mesmo subprograma pode ter vários registros de ativação. Variáveis locais recebem novas posições a cada alocação. Registros de ativação são guardados em uma pilha. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches SUBPROGRAMAS Suposições sobre o fluxo de controle entre procedimentos (subprogramas): O controle flui sequencialmente; isto é, a execução de um programa consiste em uma sequência de passos, com o controle a cada passo, em algum ponto específico do programa. Cada execução de um procedimento começa ao início do corpo do procedimento e eventualmente retorna o controle para o ponto imediatamente seguinte ao local de onde foi chamado. ÁRVORES DE ATIVAÇÃO Exemplo de árvores de ativação: Ativação é a execução do corpo de um procedimento que possui um tempo de vida específico. O tempo de vida de uma ativação é a sequência de passos entre o primeiro e o último passos na execução do corpo do procedimento, incluindo o tempo utilizado para a execução de procedimentos chamados. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches SEMANA 7 GERAÇÃO DE CÓDIGO, NOÇÕES DE OTIMIZAÇÃO, FINALIZAÇÃO COMPILADORES MODERNOS Desde os primeiros compiladores dos anos 1950, a qualidade do código gerado por um compilador tem grande importância. Essa qualidade pode ser medida pela velocidade e pelo tamanho do código-alvo, embora a velocidade seja, em geral, mais importante. Diversas técnicas para melhorar a qualidade do código foram desenvolvidas ao longo dos anos, as quais são identificadas como técnicas de otimização de código. Essa terminologia é enganosa, entretanto, pois apenas em situações muito especiais essas técnicas são capazes de gerar código ótimo no sentido matemático. Os compiladores modernos lidam com o problema da qualidade de código com a execução de diversos passos no processo de compilação, porque a coleta de informações sobre o código-fonte e o uso dessas informações para transformações para melhoria de código nas estruturas de dados que representam o código são exemplos desses passos. DEFINIÇÕES Declaração: Procedimento também conhecido como definição de função. Parâmetros: Estrutura dos procedimentos oriundos das funções. Sequência de ativação: Código da função ativada. ATRIBUTO SINTETIZADO Se o código gerado for visto como um atributo de cadeia de caracteres (em que as instruções são separadas por caracteres de mudança de linha), esse código passa a ser um atributo sintetizado que pode ser definido com base em uma gramática de atributos e gerado diretamente durante a análise sintática ou por um percurso em pós-ordem na árvore sintática. CÓDIGO INTERMEDIÁRIO Se um compilador gerar código intermediário diretamente durante a análise sintática ou a partir de uma árvore sintática,deve ocorrer uma passada adicional pelo código intermediário para gerar o código- alvo final. É fundamental a alocação adequada dos registradores e a manutenção das informações sobre o uso dos registradores, ou seja, quais registradores estão disponíveis e quais contêm valores conhecidos. A geração de código a partir de um código intermediário requer uma das seguintes técnicas-padrão ou as duas: expansão de macros e simulação estática. A expansão de macros requer a substituição de cada tipo de instrução de código intermediário por uma sequência equivalente de instruções de código-alvo. Linguagens e Compiladores - EEL101 Prof. Marcelo Augusto Assunção Sanches Exige-se que o compilador acompanhe as decisões sobre localização e particularidades de código em estruturas de dados separadas e que os procedimentos de macros variem a sequência de código. AMBIENTE DE EXECUÇÃO O ambiente de execução em que ocorre a execução não é conhecido quando o código da função é criado apenas em sua estrutura geral. Esse ambiente é construído em parte pelo ativador e em parte pelo código da função ativada. CARACTERÍSTICAS GERAIS DA GERAÇÃO DE CÓDIGO Apresentam a geração de código vista como computação de um atributo de cadeia sintetizado, sendo útil para apresentar com clareza as relações entre as sequências de código. Em regra, é muito mais desejável gerar pequenos trechos de código à medida que ocorre a geração de código e gravar esses trechos em um arquivo, ou então inseri-los em uma estrutura de dados. Embora seja útil ver o código como puramente sintetizado, a geração de código depende grandemente dos atributos herdados.
Compartilhar