290 likes | 370 Views
Universidade Federal Rural do Semi-Árido Departamento de Ciências Exatas e Naturais Ciência da Computação. Software Básico. Aula 18: Introdução aos compiladores. Silvio Fernandes 2009.1. Funções básicas.
E N D
Universidade Federal Rural do Semi-Árido Departamento de Ciências Exatas e Naturais Ciência da Computação Software Básico Aula 18: Introdução aos compiladores Silvio Fernandes 2009.1
Funções básicas • Veremos as operações fundamentais necessárias para a compilação de um programa típico escrito numa linguagem de alto nível • Usaremos como exemplo o programa em Pascal a seguir
Funções básicas • Uma linguagem de alto nível pode ser descrita em termos da gramática • Determina a forma, ou sintaxe, das instruções válidas da linguagem • O problema da compilação será comparar as instruções escritas pelo programador com as estruturas definidas pela gramática, e produzir o código-objeto correspondente a cada instrução
Funções básicas • É conveniente considerar cada instrução do programa-fonte como uma sequência de tokens • A tarefa de análise da instrução original para reconhecimento e classificação dos diversos tokens é conhecida como análise léxica • A parte do compilador que executa esta função costuma ser chamada de scanner
Funções básicas • Depois da análise dos tokens, cada instrução deve ser reconhecida como uma das construções válidas da linguagem (definidas pela gramática) • Esse processo é chamado análise sintática ou parsing, executado pelo parser • O último passo é a geração do código-objeto
Gramáticas • A gramática é uma descrição formal da sintaxe, ou forma, dos programas e instruções individuais escritas nessa linguagem • A gramática não descreve a semântica, ou significado, das várias instruções • Ex: I := J + K e I := X + Y • Onde X e Y são variáveis REAIS e I, J e K são INTEIRAS • As instruções tem a mesma sintaxe mas semântica diferentes • As diferenças entre elas seriam reconhecidas durante a geração de código
Gramáticas • Várias notações podem ser usadas para definir a gramática de uma linguagem • Utilizaremos a notação BNF (Backus-Naur Form) por ser simples e muito utilizada • A seguir é mostrado um conjunto restrito da linguagem Pascal na notação BNF
Gramáticas • Uma gramática BNF consiste num conjunto de regras, cada uma das quais define a sintaxe de alguma construção (construto) da linguagem • Ex: a regra 13 • <read> ::= READ ( <id-list> ) • Esta é a definição da sintaxe da instrução READ do Pascal, identificada como <read> • O símbolo ::= pode ser lido como “é definido como”
Gramáticas • String delimitados pelos sinais “<“ e “>” são chamados símbolos não terminais (contrutos) • Elementos que não ficam entre < e > são denominados símbolos terminais (ex: tokens) • Símbolos não terminais: <read> e <id-list> • Símbolos terminais: “READ”, “(“ e “)” • Para reconhecer <read> é preciso da definição de <id-list>
Gramáticas • Regra 6 • <id-list> ::= id | <id-list>, id • Essa regra oferece 2 possibilidades separadas por “|” • A 1ª alternativa pode-se restringir ao token id (representa um identificador reconhecido pelo scanner) • A 2ª alternativa é uma <id-list> seguida por “,” seguida por um token id
Gramáticas • Repare que esta regra é recursiva, significando que o construto <id-list> é definido em termos de si mesmo • É conveniente mostrar a análise gramatical de uma instrução sob a forma de uma árvore (árvore sintática) • A seguir a árvore sintática da instrução • READ (VALUE)
Gramáticas • A regra 9 da gramática define a sintaxe de uma instrução de atribuição • <assign> ::= id := <exp> • <assign> é formado por um id seguido do token :=, seguido de uma expressão <exp> • A regra 10 define uma expressão • <exp> ::= <term> | <exp> + <term> | <exp> - <term>
Gramáticas • Percebe-se que <exp> é definida como uma sequência de termos <term>, conectados pelos operadores + e – • A regra 11 define um termo como qualquer sequência de fatores <factor> ligados por * e DIV • A regra 12 estabelece que um fator pode ser formado por um id ou por um inteiro ou por uma expressão delimitada por parênteses
Gramáticas • Árvore sintática da instrução 14 do programa em Pascal
Gramáticas • Observe que segundo a árvore sintática a multiplicação e a divisão são feitas antes da soma e da subtração • A seguir a árvore sintática de todo o programa-exemplo em Pascal
Análise léxica • Corresponde à leitura do programa que será compilado e ao reconhecimento dos tokens que compõem o código-fonte • Os scanners (analisadores léxicos) são projetados para reconhecer palavras-chaves, operadores e identificadores, além de inteiros, números com ponto flutuante, strings de caracteres e etc
Análise léxica • Os identificadores e inteiros costumam ser reconhecidos diretamente como tokens simples • Como alternativa, estes tokens poderiam ser definidos como parte da gramática • <ident> ::= <letter> | <ident> <letter> | <ident> <digit> • <letter> ::= A | B | C | D | ... | Z • <digit> ::= 0 | 1 | 2 | 3 | ... | 9
Análise léxica • O scanner costuma reconhecer tokens com 1 ou mais caracteres • Ex: a string READ seria interpretada como um token simples ou como uma sequência de 4 tokens R, E, A, D • A string := seria reconhecida como um operador simples de atribuição, e não como : seguido de = • A saída do scanner é formada por uma sequência de tokens, geralmente representado por um código de tamanho fixo (ex: inteiro)
Análise léxica • Essa representação fornece informações suficientes quando o token lido é uma palavra-chave ou um operador • No entanto, para identificadores é necessário um especificador de token que informaria o nome, o valor inteiro do identificador encontrado pelo scanner • Para isso, alguns scanners usam tabelas de símbolos assim como os assemblers
Análise léxica • A fig. a seguir mostra a saída de um scanner para o programa-exemplo em Pascal • Para um token do tipo 22 (identificador), o especificador é um ponteiro que indica um item na tabela de símbolos (^SUM, ^SUMSQ, etc) • Para um token do tipo 23 (inteiro), o especificador é o valor do inteiro (#0, #100, etc)
Análise léxica • Na maioria das vezes, o scanner (análise léxica) funciona como uma procedure chamada pelo parser (análise sintática) quando precisa de outro token • Cada chamada ao scanner produziria a codificação (se necessário o especificador) para o próximo token do programa-fonte • O parser seria responsável por guardar os tokens dos quais ele necessite para outras análises
Referências • Leland L Beck. “Desenvolvimento de software básico”. 2ª ed. Rio de Janeiro: Campus, 1993. 525p.