Trabalho nº 3 DESENVOLVIMENTO DE UM INTERPRETADOR DESCRIÇÃO Pretende-se implementar um interpretador para a linguagem de programação Java+- apresentada no enunciado do 2º trabalho. Recorde-se que esta linguagem é um subconjunto da linguagem Java em que é possível utilizar variáveis e constantes dos tipos lógico e inteiro (de 32 bits) com sinal. Podem também ser utilizadas variáveis dos tipos array de inteiros e de valores lógicos. A linguagem aceita expressões aritméticas e lógicas e operações relacionais, construções condicionais do tipo if-else, e construções while. Os programas da linguagem Java+- são constituídos por uma única classe (a classe principal) contendo um único método (o método main). É possível passar parâmetros, que deverão ser literais inteiros, a um programa Java+- através da linha de comandos. Supondo que o nome dado ao argumento do método main() é “args”, os seus valores podem ser recuperados através da construção “Integer.parseInt(args[...])”, e o número de parâmetros pode ser obtido através da expressão “args.length”. A construção “System.out.println(...)” permite imprimir valores inteiros ou lógicos. O significado de um programa em Java+- será o mesmo que o seu significado em Java. Por exemplo, o seguinte programa calcularia o máximo divisor comum de dois inteiros, e apresentaria esse valor no écran: class gcd { public static void main(String[] args) { int[] x; x = new int[2]; x[0] = Integer.parseInt(args[0]); x[1] = Integer.parseInt(args[1]); if (x[0] == 0) System.out.println(x[1]); else while (x[1] > 0) { if (x[0] > x[1]) x[0] = x[0] - x[1]; else x[1] = x[1] - x[0]; } Compiladores 2012-2013 | Trabalho nº 3 1 System.out.println(x[0]); } } A gramática da linguagem foi também apresentada no 2º trabalho, e reproduz-se de seguida. TOKENS E GRAMÁTICA INICIAL DA LINGUAGEM JAVA+ID = [A-Za-z_][A-Za-z0-9_]* INTLIT = [0-9]+ | “0x” [0-9a-fA-F]+ BOOLLIT = “true” | “false” INT = “int” BOOL = “boolean” NEW = “new” IF = “if” ELSE = “else” WHILE = “while” PRINT = “System.out.println” PARSEINT = “Integer.parseInt” CLASS = “class” PUBLIC = “public” STATIC = “static” VOID = “void” STRING = “String” DOTLENGTH = “.length” OCURV = “(” CCURV = “)” OBRACE = “{” CBRACE = “}” OSQUARE = “[” CSQUARE = “]” OP1 = “&&” | “||” OP2 = “<” | “>” | “==” | “!=” | “<=” | “>=” OP3 = “+” | “-” OP4 = “*” | “/” | “%” Compiladores 2012-2013 | Trabalho nº 3 2 NOT = “!” ASSIGN = “=” SEMIC = “;” COMMA = “,” Start → Program EOF Program → CLASS ID OBRACE PUBLIC STATIC VOID ID OCURV STRING OSQUARE CSQUARE ID CCURV OBRACE { VarDecl } { Statement } CBRACE CBRACE VarDecl → Type ID { COMMA ID } SEMIC Type → INT | BOOL | INT OSQUARE CSQUARE | BOOL OSQUARE CSQUARE Statement → OBRACE { Statement } CBRACE Statement → IF OCURV Exp CCURV Statement ELSE Statement Statement → WHILE OCURV Exp CCURV Statement Statement → PRINT OCURV Exp CCURV SEMIC Statement → ID ASSIGN Exp SEMIC Statement → ID OSQUARE Exp CSQUARE ASSIGN Exp SEMIC Exp → Exp (OP1 | OP2 | OP3 | OP4) Exp Exp → Exp OSQUARE Exp CSQUARE Exp → INTLIT | ID | BOOLLIT Exp → NEW INT OSQUARE Exp CSQUARE Exp → NEW BOOL OSQUARE Exp CSQUARE Exp → OCURV Exp CCURV Exp → Exp DOTLENGTH | NOT Exp Exp → PARSEINT OCURV ID OSQUARE Exp CSQUARE CCURV IMPLEMENTAÇÃO O interpretador deve ser implementado em Java utilizando as ferramentas JJTree e JavaCC. A gramática inicial dada já deverá ter sido modificada de modo a eliminar ambiguidades, a reflectir a precedência dos operadores em Java, e a permitir a análise descendente com LOOKAHEAD=1. O interpretador deverá chamar-se Javapm, ler o programa a processar do stdin e os seus argumentos através da linha de comandos, e emitir o resultado para o stdout. Caso o ficheiro gcd.jpm contenha o exemplo dado na primeira página deste enunciado, a invocação java Javapm 12 8 < gcd.jpm ↵ deverá interpretar o programa gcd.jpm passando-lhe os parâmetros 12 e 8, e imprimir no écran: 4 Compiladores 2012-2013 | Trabalho nº 3 3 Para efeitos de verificação, o interpretador deve fornecer ainda as seguintes opções: • • -t : imprime a árvore de expressões construída durante a análise do programa -s : imprime o conteúdo da tabela de símbolos após a interpretação do programa Estas opções são passadas na linha de comandos, antes dos parâmetros. Mais concretamente: java Javapm -t 12 8 < gcd.jpm ↵ deverá imprimir a árvore de expressões e terminar, sem proceder à interpretação do programa. Neste caso, os restantes parâmetros (se existirem) devem ser ignorados. Por outro lado, java Javapm -s 12 8 < gcd.jpm ↵ deverá proceder à interpretação do programa e imprimir a tabela de símbolos, separada da saída do programa por uma linha em branco, do seguinte modo: 4 === Symbol table === Name Type Init Values ---- ---- ---- ------ args args[] true 2 12 8 x int[] true 2 4 0 O formato das linhas correspondentes às variáveis é o seguinte: • • • • Name – o identificador da variável, tal como aparecer no programa a interpretar, e pela mesma ordem. Type – o tipo da variável. Os valores possíveis são “int”, “bool”, “int[]”, “bool[]” ou “args[]”. O (pseudo) tipo “args[]” é usado para indicar o identificador que é usado no programa para aceder aos parâmetros passados na linha de comandos. Init – “true” se a variável tiver sido inicializada durante a interpretação do programa, “false” caso contrário. Values – O(s) valor(es) da variável, se esta tiver sido inicializada. Se a variável for do tipo inteiro ou booleano, é simplesmente o seu valor. Se for do tipo array, o primeiro valor é a dimensão do array e os restantes são os elementos do array. As colunas desta tabela devem ser separadas pelo caracter “\t”. O analisador deve aceitar (e ignorar) comentários iniciados por // (sem incluir o caracter de mudança de linha!) e do tipo /* */, bem como detectar a existência de quaisquer erros de sintaxe ou de semântica no ficheiro de entrada. ÁRVORES DE INSTRUÇÕES E DE EXPRESSÕES De modo a permitir a validação dos trabalhos no Mooshak, as árvores de expressões e de instruções geradas durante a análise sintática devem respeitar as seguintes orientações: • • • Nó raiz: Program Declaração de variáveis: VarDecl Statements: Seq If While Print Sta Stl Compiladores 2012-2013 | Trabalho nº 3 4 • • Operadores: Or And Eq Neq Lt Gt Leq Geq Add Sub Mul Div Rem Not Length Lda NewInt NewBool ParseArgs Terminais: Id IntLit BoolLit Type Um ficheiro com a árvore correspondente ao programa apresentado na primeira página é fornecido juntamente com este enunciado. TRATAMENTO DE ERROS Quaisquer erros sintáticos detectados durante a análise deverão ser tratados como foi especificado no enunciado do 2º trabalho. Relativamente aos erros semânticos, o interpretador deverá detectar os seguintes tipos de erros: main() method is not called main <symbol> already defined cannot find symbol <symbol> <symbol> not initialized incompatible type (got <type>, required int) incompatible type (got <type>, required bool) incompatible type (got <type>, required int or bool) incompatible type (got <type>, required int[]) incompatible type (got <type>, required bool[]) incompatible type (got <type>, required args[]) incompatible type (got <type>, required int[] or bool[]) incompatible type (got <type>, required int[] or bool[] or args[]) operator <op> cannot be applied to <type> operator <op> cannot be applied to <type>,<type> onde: • • • <type> representa um dos tipos “int”, “bool”, “int[]”, “bool[]” ou “args[]” <symbol> representa um identificador <op> representa um dos 14 operadores da linguagem (incluindo o operador unário NOT, “!”) Caso algum destes erros seja detectado durante a interpretação do programa, o interpretador deverá terminar imediatamente com uma mensagem de erro no seguinte formato: Semantic error: <msg> onde <msg> representa uma das mensagens acima. Sempre que a uma expressão corresponder mais do que um erro semântico (por exemplo, a tentativa de indexação de uma expressão do tipo int com um valor booleano), deverá ser reportado o erro relativo à primeira expressão envolvida (neste caso, o facto de não ser possível indexar um valor do tipo int, ou seja: “incompatible type (got int, required int[] or bool[])”). Compiladores 2012-2013 | Trabalho nº 3 5 O tratamento de erros de execução, como por exemplo, a indexação de um array com um valor negativo ou a divisão por zero, está para além dos objectivos deste trabalho. SUBMISSÃO DO TRABALHO O trabalho deverá ser validado no Mooshak, usando o concurso COMP1213_T3. O relatório deve ser submetido na tutoria da disciplina e não deve conter mais de 6 páginas. Do relatório devem constar: 1. A gramática, usada para implementar o interpretador, gerada pelo JJDoc; 2. Uma descrição da estratégia de implementação adoptada, detalhando aspectos como o formato da tabela de símbolos, o método de avaliação das expressões, o acesso aos parâmetros, etc. 3. Outras descrições do interpretador que achar pertinentes; 4. Caso não implemente toda a funcionalidade pedida, deverá detalhar os aspectos não implementados. NOTAS 1. A tarefa A do concurso COMP1213_T3 contém todos os testes parciais incluídos nas outras tarefas; 2. Todos os trabalhos serão alvo de apresentação e discussão. Compiladores 2012-2013 | Trabalho nº 3 6