Construção de Compiladores I UFOP DECOM 2014 1 Atividades Práticas Construção de um compilador para a linguagem Panda Etapa 1 Sumário José Romildo Malaquias Resumo Nesta série de atividades vamos implementar várias construções da linguagem Panda no compilador. Ao submeter uma atividade, deve ser incluído um documento explicando como as tarefas foram realizadas. Em relação à análise semântica, as regras implementadas devem ser explicadas de forma clara. 1 Mensagens de erro 1 2 Representação de tipos 2 3 Símbolos 2 4 Tabelas de símbolos 2 5 Os analisadores léxico, sintático e semântico 3 6 Comentários 4 7 Identificadores 4 8 Literais booleanos 4 9 Literais inteiros 5 10 Literais reais 6 11 Literais string 7 1 Mensagens de erro O projeto contém algumas classes para reportar erros encontrados durante a compilação. Estas classes fazem parte do pacote ErrorMsg e serão comentadas a seguir. Em todo compilador é desejável que os erros encontrados sejam reportados com uma indicação da localização do erro, acompanhada por uma mensagem explicativa do problema ocorrido. Para tanto tornase necessário manter a informação da localização em que cada frase do programa (cada nó da árvore abstrata construída para representar o programa) foi encontrada. Neste projeto as localizações no código fonte são dados em função do número de caracteres desde o começo do arquivo fonte até a posição desejada. Para que seja possível informar a posição usando número de linhas e colunas nas mensagens de erro, precisamos manter uma lista com informações da localização do começo de cada linha do arquivo fonte. Uma instância da classe ErrorMsg.Source possui basicamente três informações sobre o programa fonte sendo compilado: o nome do arquivo fonte, uma lista contendo a posição do início de cada linha já lida do arquivo fonte, e a contagem de linhas do arquivo fonte O analisador léxico deverá usar o método newline desta classe toda vez que for encontrada uma nova linha no arquivo fonte. A classe ErrorMsg.Loc é usada para representar uma localização no arquivo fonte. Uma localização é caracterizada por: 1
uma indicação do arquivo fonte (uma instância de Error.Source ), uma posição inicial (equerda), e uma posição final (direita) Assim através da localização de uma frase conhece-se o arquivo fonte onde a frase foi encontrada, a posição em que ela começou e a posição em que ela terminou. A classe ErrorMsg.ErrorMsg deve ser usada para emissão de mensagens de erro através do método error, que recebe como argumentos a localização do erro e a mensagem de diagnóstico. Esta classe possui também um atributo anyerror usado para sinalizar se houve algum erro durante a compilação. 2 Representação de tipos O pacote Types contém classes para a representação dos tipos da linguagem no compilador. A classe Types.Type é uma classe abstrata. Ele deve ser uma superclasse de todas as outras classes que representam tipos da linguagem sendo compilada. O método actual é usado para determinar o tipo realmente representado na presença de definições de tipos e recursividade, e será explicado oportunamaente. O método coerceto permite verificar se um tipo é compatível com outro tipo, dado como argumento para o método. A inteface Types.Types declara algumas contantes para representar os tipos mais comuns da linguagem sendo compilada, com o objetivo de facilitar a programação do restante do compilador. 3 Símbolos Linguagens de programação usam identificadores para nomear entidades da linguagem, como variáveis, funções, classes, módulos, etc. Símbolos léxicos que são identificadores tem um valor semântico que é o nome do identificador, e que a princípio pode ser representado por uma String. Porém o tipo String tem algumas incoveniências para o compilador: Geralmente o mesmo identificador pode ocorrer várias vezes em um programa. Se cada ocorrência for representada por string (ou seja, por uma sequência de caracteres), o uso de memória poderá ser grande. Normalmente existem dois tipos de ocorrência de identificadores em um programa: uma declaração do identificador, e um uso do identificador já declarado. Durante a compilação cada ocorrência de uso de um identificador deve ser associada com um ocorrência de declaração. Para tanto os identificadores devem ser comparados para determinar se são iguais (isto é, tem o mesmo nome). O uso de strings pode ser ineficiente, por pode ser necessário comparar todos os caracteres da string para determinar se elas são iguais ou não. Por estas razões o compilador utiliza um outro tipo ( Symbol.Symbol ) para representar os nomes dos identificadores. Basicamente mantém-se uma tabela com todas os identificadores já encontrados, e todas as vezes que o analisador léxico encontrar um identificador, deve-se verificar se o seu nome já está na tabela. Em caso afirmativo, usa-se o símbolo encontrado na tabela. Caso contrário cria-se um novo símbolo, que é adicionado à tabela, e usado pelo analisador léxico. A comparação de igualdade de símbolos se resumo a uma comparação de referências, já que o mesmo identificador estará sempre sendo representado pelo mesmo símbolo (mesmo objeto na memória). O método Symbol.Symbol.symbol cria um símbolo a partir de uma string. Observação: Não confunda o tipo Symbol.Symbol (o tipo dos símbolos que representam identificadores) com o tipo java_cup.runtime.symbol (o tipo dos símbolos gramaticais terminais e não-terminais). 4 Tabelas de símbolos Em Panda identificadores são usados para nomear entidades como tipos, variáveis e funções. Quando um identificador é usado, é necessário que ele tenha sido previamente definido, e informações sobre como ele foi definido devem ser recuperadas para compilar a frase em que o identificador é usado. Por exemplo, quando compilamos uma expressão que é uma variável simples, é necessário saber o tipo da variável. 2
uma expressão que é uma chamada de função, é necessário saber o número de argumentos e o tipo de cada argumento que a função espera, bem como o tipo do resultado da função. As informações disponíveis no momento em que um identificador é declarado são guardados em uma estrutura de dados chamada tabela de símbolos, que mapeia o nome (símbolo) à informação relevante a ele. Posteriormente estas tabelas podem ser usadas para recuperar estas informações. A classe Symbol.Symbol contém código para manipular tabelas de símbolo (também chamadas de ambiente). As tabelas de símbolo usadas pelo compilador de Panda e definidas como atributos na classe Env.Env são: Env.Env.venv é a tabela de variáveis e funções, e Env.Env.tenv é a tabela de tipos. As entradas na tabela de variáveis e funções são do tipo Env.Entry, que possui duas subclasses: Env.VarEntry, usada para definições de variáveis, contendo o atributo ty que representa o tipo da variável Env.FunEntry, usada para definições de funções, contendo os atributos formals representando os tipos dos parâmetros formais da função, e result representando o tipo do resultado da função 5 Os analisadores léxico, sintático e semântico O pactye Abs contém as classes que representam as árvores abstratas para as construções da linguagem. O pacote Parse contém as classes que implementam os analisadores léxico e sintático do compilador. O analisador léxico (classe Parse.Lexer ) é gerado automaticamente pela ferramenta jflex. A especificação léxica é feita no arquivo panda.jflex usando expressões regulares. O analisador sintático (classe Parse.Parser ) é gerado automaticamente pela ferramenta JavaCUP. A especificação sintática é feita no arquivo panda.cup usando uma gramática livre de contexto. O JavaCUP gera também uma classe Parse.sym contendo definições de constantes para representar os diversos símbolos terminais da gramática. Esta classe é usada pelos analisadores léxico e sintático. A classe Parse.Tokens contém o método dumptoken que obtém a descrição de um símbolo léxico (token) na forma de uma string, e é útil quando precisarmos exibir um token. A análise semântica é feita por meio de métodos definidos nas classes que representam as árvores abstratas das construões da linguagem. Ao acrescentar uma nova construção na implementação da linguagem: defina as classes necessárias para representar a árvore abstrata para a construção no pacote Absyn ; uma classe que representa uma árvore abstrata deverá ser uma subclasse de Absyn.Absyn ; lembrese de: definir os campos necessários para as sub-árvores da árvore abstrata, definir o construtor que inicializa estes campos com valores passados como argumentos, definir o método tostring que permite converter para string, definir o método totree que permite converter para uma árvore de string, útil para visualização gráfica a árvore abstrata, definir o método adequado para análise semântica declare novos símbolos terminais e não-terminais na gramática livre de contexto da linguagem, necessários para as especificações léxica e sintática da construção, acrescente as regras de produção para a construção na gramática livre de contexto da linguagem, tomando o cuidado de escrever ações semânticas adequadas para a construção da árvore abstrata correspondente, se necessário acrescente regras léxicas que permitam reconhecer os novos símbolos terminais na especificação léxica da linguagem, e 3
se necessário modifique o método dumptoken (que converte um símbolo léxico para String ) da classe Parse.Tokens para considerar os novos símbolos terminais. 6 Comentários Um comentário de linha em Panda começa com a sequência de caracteres // e termina no final da linha. Um comentário de bloco em Panda é delimitado por /* e por */. Comentários de bloco podem ser aninhados. Tarefa 1: Comentários Análise léxica Acrescentar regras léxicas para reconhecer (e descartar) comentários de linha e de bloco em Panda. Se um comentário de bloco não terminar deve ser reportado um erro. Tarefa 2: Comentários Testes // isto é um comentário de linha /* isto é um comentário de bloco */ /* isto é um comentário de bloco /* aninhado */ percebeu */ /* Este comentário de bloco não terminou /* Este comentário de bloco /* com aninhamento */ não terminou 7 Identificadores Os identificadores de Panda são formados por uma sequência não vazia de letras (maiúsuclas ou minúsculas), dígitos decimais e sublinhado ( _ ), começando com uma letra. Letras maiúsculas e minúsculas são consideradas diferentes. Tarefa 3: Identificadores Análise léxica 1. Modifique a gramática de Panda acrescentando um símbolo terminal ID para os tokens dos identificadores. Use um valor semântico do tipo Symbol.Symbol para estes tokens. classe Parse.Tokens para considerar o novo símbolo terminal ID. 3. Acrescente regras léxicas para o símbolo terminal ID. 8 Literais booleanos Implementar no projeto de compilador da linguagem Panda o tratamento de literais booleanos. Os literais booleanos são #t (verdadeiro) e #f (falso). 4
Tarefa 4: Literais booleanos Árvore abstrata Definir a classe Absyn.BoolExp para representar a árvore abstrata de uma constante booleana. 1. Defina as variáveis de instância necessárias para representar a estrutura da expressão. 2. Defina o construtor para a classe. 3. Redefina o método totree para converter um objeto desta classe para Tree<String>. Tarefa 5: Literais booleanos Análise léxica 1. Modifique a gramática de Panda acrescentando um símbolo terminal BOOLLIT para os tokens dos literais booleanos. Use um valor semântico do tipo Boolean para estes tokens. classe Parse.Tokens para considerar o novo símbolo terminal BOOLLIT. 3. Acrescente regras léxicas para o símbolo terminal BOOLLIT. Tarefa 6: Literais booleanos Análise sintática Acrescente na gramática de Panda uma regra de produção para a expressão literal booleano. Tarefa 7: Literais booleanos Análise semântica 1. Defina a classe Types.BOOL para representar o tipo dos valores booleanos. 2. Acrescte uma constante BOOL na classe Types.Types cujo valor é uma instância da classe Types.BOOL. 3. Redefina o método typecheck na classe Absyn.BoolExp que calcula o tipo de uma expressão literal booleano. 4. Adicione na tabela de símbolos de tipos do compilador o símbolo bool. 5. Adicione na tabela de símbolos de variávaeis e funções do compilador as funções da biblioteca padrão que manipulam booleanos. As assinaturas das funções são: function print_bool(x: bool): unit function not(b: bool): bool Tarefa 8: Literais booleanos Testes #t #f #F // erro léxico 9 Literais inteiros Implementar no projeto de compilador da linguagem Panda o tratamento de literais inteiros. Um literal inteiro é uma sequência não vazia de dígitos decimais. O literal só poderá começar com o dígito 0 se ele for o próprio literal 0. 5
Tarefa 9: Literais inteiros Árvore abstrata Definir a classe Absyn.IntExp para representar a árvore abstrata de uma constante inteira. 1. Defina as variáveis de instância necessárias para representar a estrutura da expressão. 2. Defina o construtor para a classe. 3. Redefina o método totree para converter um objeto desta classe para Tree<String>. Tarefa 10: Literais inteiros Análise léxica 1. Modifique a gramática de Panda acrescentando um símbolo terminal INTLIT para os tokens dos literais inteiros. Use um valor semântico do tipo Long para estes tokens. classe Parse.Tokens para considerar o novo símbolo terminal INTLIT. 3. Acrescente regras léxicas para o símbolo terminal INTLIT. Tarefa 11: Literais inteiros Análise sintática Acrescente na gramática de Panda uma regra de produção para a expressão literal inteiro. Tarefa 12: Literais inteiros Análise semântica 1. Defina a classe Types.INT para representar o tipo dos valores inteiros. 2. Acrescte uma constante INT na classe Types.Types cujo valor é uma instância da classe Types.INT. 3. Redefina o método typecheck na classe Absyn.IntExp que calcula o tipo de uma expressão literal inteiro. 4. Adicione na tabela de símbolos de tipos do compilador o símbolo int. 5. Adicione na tabela de símbolos de variáveis e funções do compilador as funções da biblioteca padrão que manipulam inteiros. As assinaturas das funções são: function print_int(x: int): unit Tarefa 13: Literais inteiros Testes 0 3241 10 Literais reais Implementar no projeto de compilador da linguagem Panda o tratamento de números reais (representados como números em ponto flutante). Um literal real é formado por uma sequência de dígitos decimais seguida de um ponto seguido de outra sequência de dígitos decimais. Uma e somente uma das sequências de dígitos decimais pode ser vazia. 6
Tarefa 14: Literais reais Árvore abstrata Definir a classe Absyn.RealExp para representar a árvore abstrata de uma constante real. 1. Defina as variáveis de instância necessárias para representar a estrutura da expressão. 2. Defina o construtor para a classe. 3. Redefina o método totree para converter um objeto desta classe para Tree<String>. Tarefa 15: Literais reais Análise léxica 1. Modifique a gramática de Panda acrescentando um símbolo terminal REALLIT para os tokens dos literais reais. Use um valor semântico do tipo Double para estes tokens. classe Parse.Tokens para considerar o novo símbolo terminal REALLIT. 3. Acrescente regras léxicas para o símbolo terminal REALLIT. Tarefa 16: Literais reais Análise sintática Acrescente na gramática de Panda uma regra de produção para a expressão literal real. Tarefa 17: Literais reais Análise semântica 1. Defina a classe Types.REAL para representar o tipo dos valores reais. 2. Acrescte uma constante REAL na classe Types.Types cujo valor é uma instância da classe Types.REAL. 3. Redefina o método typecheck na classe Absyn.RealExp que calcula o tipo de uma expressão literal real. 4. Adicione na tabela de símbolos de tipos do compilador o símbolo real. 5. Adicione na tabela de símbolos de variáveis e funções do compilador as funções da biblioteca padrão que manipulam reais. As assinaturas das funções são: function print_real(x: real): unit function round(f: real): int function ceil(f: real): int function floor(f: real): int function real(i: int): real Tarefa 18: Literais reais Testes 30.12 98632..2349 11 Literais string Implementar no projeto de compilador da linguagem Panda o tratamento de strings. Considere que os literais strings são escritos como uma sequência de caracteres delimitada por aspas duplas ("). O caracter \ é especial e inicia uma sequência de escape. As seguintes sequências de escape são válidas: 7
\\ o caracter \ \" o caracter " \n o caracter de mudança de linha \t o caracter de tabulação horizontal \ddd o caracter de código decimal ddd (ddd é uma sequência de dígitos decimais) Tarefa 19: Literais strings Árvore abstrata Definir a classe Absyn.StringExp para representar a árvore abstrata de uma constante string. 1. Defina as variáveis de instância necessárias para representar a estrutura da expressão. 2. Defina o construtor para a classe. 3. Redefina o método totree para converter um objeto desta classe para Tree<String>. Tarefa 20: Literais strings Análise léxica 1. Modifique a gramática de Panda acrescentando um símbolo terminal STRINGLIT para os tokens dos literais strings. Use um valor semântico do tipo String para estes tokens. classe Parse.Tokens para considerar o novo símbolo terminal STRINGLIT. 3. Acrescente regras léxicas para o símbolo terminal STRINGLIT. Tarefa 21: Literais strings Análise sintática Acrescente na gramática de Panda uma regra de produção para a expressão literal string. Tarefa 22: Literais strings Análise semântica 1. Defina a classe Types.STRING para representar o tipo dos valores strings. 2. Acrescte uma constante STRING na classe Types.Types cujo valor é uma instância da classe Types.STRING. 3. Redefina o método typecheck na classe Absyn.StringExp que calcula o tipo de uma expressão literal string. 4. Adicione na tabela de símbolos de tipos do compilador o símbolo string. 5. Adicione na tabela de símbolos de variáveis e funções do compilador as funções da biblioteca padrão que manipulam strings. As assinaturas das funções são: function print(x: string): unit function length(s: string): int function substring(s: string, start: int, length: int): string Tarefa 23: Literais strings Testes "Bom dia, Brasil!" "" "abc\tdef\nghi\\jkl\"mno\065ok" // invalid escape sequence in string literal "abc\kdef" // unclosed string literal "abc 8