Trabalho de Desenho de Linguagens de Programação e de Compiladores Mini Pascal (cod. 11482) Departamento de Informática Universidade da Beira Interior Ano lectivo 2015/2016 1 Introdução Este trabalho é parte constituinte da avaliação prática da Unidade Curricular de código 11482 designada por Desenho de Linguagens de Programação e de Compiladores, na sua edição de 2015-2016. O trabalho visa o exercício dos conceitos e das técnicas básicas de desenho de linguagens de programação e de compiladores expostos nas aulas da presente UC. Com tal este trabalho é estruturado nas várias fases que constituem um projecto desta natureza (espelhado pela própria estrutura lectiva da UC). Estas diferentes fases visam ilustrar a utilização de técnicas (e ferramentas computacionais associadas) de desenho de compiladores. Em particular as técnicas de analises semântica, produção de código máquina e optimização de código. 2 Descrição geral do trabalho O objectivo geral é a definição/extensão formal de uma pequena linguagem de programação, a linguagem Mini-Pascal (da autoria do François Pottier), 1
e a construção do compilador correspondente, tendo por alvo a arquitectura x86-64. Este trabalho está assim estruturado em duas fases. A primeira fase consiste em entender a linguagem de base Mini-Pascal (cuja sintaxe abstracta e semântica a está descrito em anexo deste enunciado) e estendê-la. Esta fase dará lugar a primeira entrega dia 31 de Março de 2016. A segunda fase consiste em desenhar e implementar o compilador para a extensão definida da linguagem. O trabalho completo, concluído com a execução da segunda fase, deverá ser entregue no dia 26 de Maio 2016, e as defesas terão lugar nos primeiros dias da semana seguinte em calendário por definir e afixar na página da UC. A entrega deverá respeitar as modalidades de entrega descritas na secção 4. 3 Trabalho Requerido Como intregável para a primeira fase do trabalho, requer-se um documento que apresenta a linguagem do ponto de vista da sua sintaxe abstracta, o seu sistema de tipo e a sua semântica. Como entregável para a segunda fase, um compilador completo escrito em OCaml/menhir para a linguagem Mini-Pascal extendida (cujo núcleo de base de partida está descrita em anexo). Impreterivelmente, a arquitectura alvo considerada é a arquitetura x86-64 apresentada nas aulas. O grupo de trabalho deverá considerar extensões que achar apropriadas e documentadas no relatório final. Estas extensões deverão ser discutidas com o regente da UC. Em jeito de sugestão, as extensões sugeridas são: novos tipos primitivos (float, character, string, etc,); mecanismos de definição de estruturas; mecanismos de definição de tipos soma; mecanismos de definição de tipos polimórficos; mecanismos de declaração de tipos definidos pelo programador; 2
mecanismos de declaração e utilização de apontadores/memória dinâmica; introdução de novos modos de passagem de parâmetros; introdução de novas primitivas; tec. É esperado igualmente que o grupo reporte atempadamente à equipa docente os eventuais entregáveis preliminares (a conclusão incremental das fases de geração código, como introduzido nas aulas) mas igualmente as dificuldades ou questões encontradas. 4 Entrega do trabalho O trabalho deve ser entregue num arquivo tar comprimido (nome.tgz) em que nome é o identificador do vosso grupo. este arquivo deve conter todos os ficheiros fontes necessários à compilação assim como um Makefile completo (as entradas all e clean devem estar presentes). Este arquivo deverá igualmente conter o relatório que descreve o trabalho feito, as escolhas (de desenho, etc.) tomadas, a documentação do código e o manual do utilizador. É igualmente esperada que seja preparada uma apresentação para a respectiva defesa. 5 Documentos auxiliares Anexa-se a este enunciado os documentos que definam a linguagem alvo por compilar: a descrição da sintaxe abstracta e da semântica do core da linguagem Mini-Pascal. estes documentos são adaptações directas dos documentos fornecidos pelo François Pottier na sua UC de Desenho de Linguagens de Programação e Compilação. A Sintaxe abstracta de Mini-Pascal Nas seguintes definições, a variável b percorre o conjunto dos booleanos, ou seja {true, f alse}. A variável n contempla os inteiros com sinal representados 3
com 32 bits (i.e. [ 2 31..2 31 1]). A variável x percorre o conjunto (numerável) dos identificadores de variável, a variável f percorre o conjunto (numerável) dos identificadores de funções e procedimentos. Os tipos primitivos considerados são os tipos integer, boolean, array of τ onde τ é ele próprio um tipo - que descreve os elementos do vector definido. τ ::= tipos integer inteiros boolean booleanos array of τ vectores As constantes são booleanos ou inteiros. Não há constantes de tipo vector : os vectores são alocados dinamicamente. k ::= constantes n constante inteira b constante booleana Os operadores unários e binários que aparecem na sintaxe das expressões são as seguintes. Todas produzem um resultado de tipo integer, coma excepção dos operadores de comparação que produzem um resultado de tipo boolean. uop ::= operador unário negação bop ::= operador binário + adição subtracção multiplicação / divisão < > = comparação As operações primitivas, isto é, procedimentos e funções predefinidas são as seguintes. π ::= operações primitivas write escrita de um inteiro writeln escrita de um inteiro com mudança de linha readln leitura de um inteiro 4
O alvo de uma chamada de um procedimento ou função é ou uma operação primitiva ou um procedimento/função definido pelo utilizador, a qual é designada pelo seu nome. φ ::= alvo de uma chamada π operação primitiva f procedimento ou função definida pelo utilizador A sintaxe das expressões, condições e instruções é a seguinte. e ::= expressões k constante x variável uop e aplicação de um operador unário e bop e aplicação de um operador binário φ(e...e) chamada de função e[e] acesso a um elemento de vector new array of τ [e] alocação de um vector c ::= condições e expressões (com retorno de tipo boolean) not c negação c and c conjunção c or c disjunção i ::= instruções φ(e...e) chamada de procedimento x := e atribuição e[e] := e escrita num vector if c then i else i condicional while c do i ciclo i... i sequência Uma definição de procedimento ou de função é composta por um cabeçalho, por uma série de declarações de variáveis locais e de um corpo. O cabeçalho define o nome do procedimento ou da função, declara os seus parâmetros formais, se se trata de uma função, indica qual o tipo do seu resultado. A notação τ? indica um tipo opcional. 5
d ::= definição de procedimentos/funções f(x : τ...x : τ) : τ? cabeçalho var x : τ... x : τ variáveis locais i corpo Um programa é composto de uma (eventual) sequência de declaração de variáveis globais, de uma (eventual) sequência de declaração de procedimentos/funções e de um corpo. p ::= programa var x : τ... x : τ variáveis globais d.. d definições de procedimentos/funções i corpo B Semântica formal de Mini-Pascal Esta secção introduz uma semântica operacional big-step B.1 Valores Os valores manipulados durante a execução são booleanos, inteiros (com representação sobre 32 bits), endereços de vectores e igualmente a contante nil. Um vector é representado pelo seu endereço em memória e não pela sequência dos seus elementos. Esta distinção tem a sua importância : ela significa, por exemplo, que é possível para duas variáveis distintas de tipo array of τ serem um alias uma da outra, isto é, conter o mesmo endereço e logo apontar para o mesmo vector. Significa igualmente que o conteúdo de uma variável de tipo vector pode ser guardada nos registos, porque se trata de um endereço e não do vector em si. O vector será arquivado na heap. O valor nil é o valor atribuido a vectores sem inicialização. cv ::= valores b constante booleana n constante inteira l endereço de vector nil endereço inválido No Mini-Pascal, quando declaramos uma variável, damos o seu tipo τ, mas não indicamos o seu valor inicial. Nalgumas linguagens, como a linguagem 6
C, o valor inicial da variável está então indefinida : é prohibido aceder ao conteúdo desta variável antes de uma inicialização. Noutras linguagens, como em Java, as variáveis são inicializadas com um valor por omissão (que depende do tipo). Esta será a abordagem seguida no Mini-Pascal. Assim define-se uma função default que atribuí a cada tipo o valor por omissão que lhe corresponde. default (boolean) = false default (integer) = 0 default (array of τ) = nil O último caso justifica a existência do valor nil. B.2 Operadores A semântica dos operadores unários é dada por uma função. que associa a todo o operador uop (recorde-se, este operador é pura sintaxe, é um nome) uma função parcial uop dos valores para os valores. Por exemplo, a semântica do operador de negação é dada definindo como sendo a função que a toda a constante inteira n associa a contante inteira n normalizada no intervalo 2 31 2 31 1. É importante notar que a função não está definida para outros valores do que os valores inteiros : não se pode aplicar esta função sobre valores booleanos, nem a um endereço, nem ao valor nil. A semântica de um programa que aplicaria o operador da negação sobre um vector, por exemplo, está então sem definição. Um tal programa é felizmente excluído pelo sistema de tipos do Mini-Pascal, visto que o tipo inteiro é incompatível com o tipo array of τ. De forma semelhante, a semântica de operadores binários é dada por uma função. que a todo o operador bop associa uma função binária parcial uop dos pares de valores para os valores. A definição exacta de cada um dos operadores é deixada em exercício (por este não oferecer dificuldades particulares). B.3 Juízos O conteúdo das variáveis globais é dada por um ambiente G que associa às variáveis os valores que lhes correspondem. De igual forma, o conteúdo das variáveis locais é dado por um ambiente E. Finalmente o heap H associa aos endereços sequências finitas de valores. 7
A semântica de Mini-Pascal é definida por juízos. A avaliação de uma expressão, de uma condição, de uma instrução pode modificar as variáveis globais, as variáveis locais, assim como a heap. Os juízos que descrevem a avaliação mencionam assim o estado inicial G, H, E e o estado resultante G, H, E. No caso das expressões e das condições, estes juízos mencionam também um valor, que representa o resultado da avaliação. Os juízos são assim da forma G, H, E/e G, H, E /v G, H, E/c G, H, E /b G, H, E/i G, H, E O primeiro caso destes juízo lê-se : no estado descrito por G, H, E, a avaliação da expressão e conduz ao estado descrito por G, H, E e produz o valor v. O segundo e o terceiro caso lêem-se de forma semelhante. Damo-nos a liberdade de escrever S para representar G, H, E. Este estilo de semântica, como vismos nas aulas, é designada de semântica operacional de passos grandes (big-step). É assim designada porque estes juízos ligam directamente as expressões e os seus resultados sem contemplar os eventuais passos intermédios do calculo inerente. A semântica das expressões é dada na figura 1. esta contém um conjunto de regras que definam o juízo G, H, E/e G, H, E /v. Da mesma forma, os juízos que descrevem das condições e das instruções estão definidos nas figuras 2 e 3. Na forma implícita de abuso de notação, todas as regras estão implicitamente parametrizadas por uma série de definições de funções e procedimentos que são consultadas pelas duas regras que descrevem a chamada de funções e a chamada de procedimentos definidos pelo utilizador. Estas definições de funções e de procedimentos estão definidas aquando a exploração de um programa p particular. A semântica de um programa p particular é definida por um juízo da forma p Este juízo p lê-se o programa p executa-se sem erro e termina. A sua definição é dada na figura 4. 8
Figura 1: Figura 2: 9
Figura 3: Figura 4: 10