Trabalho nº 2 DESENVOLVIMENTO DE UM ANALISADOR SINTÁTICO DESCRIÇÃO Pretende-se implementar um analisador sintático para uma linguagem de programação muito simples, com o nome Java+-, e que é um subconjunto da linguagem Java. Nesta linguagem é 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 array de valores lógicos (com uma única dimensão). A linguagem aceita expressões aritméticas e lógicas e operações relacionais simples, 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. EXEMPLO 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) && (0 <= x[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º 2 1 } System.out.println(x[0]); } } 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º 2 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 analisador sintático deve ser implementado em Java utilizando as ferramentas JJTree e JavaCC. A gramática dada deve ser 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, ou seja, a gramática deve ser LL(1). O analisador deverá chamar-se JavapmParser, ler o ficheiro a processar através do stdin, e emitir o resultado da análise para o stdout. Exemplificando, no caso do ficheiro gcd.jpm conter o exemplo dado anteriormente, a invocação java JavapmParser < gcd.jpm ↵ deverá gerar a árvore sintática abstracta correspondente (i.e., conforme a gramática original dada) e imprimi-la no écran. Neste caso: Compiladores 2012-2013 | Trabalho nº 2 3 > Start > Program > CLASS > ID > OBRACE > PUBLIC ... > CSQUARE > ID > CCURV > OBRACE > VarDecl > Type > INT > OSQUARE > CSQUARE > ID > SEMIC ... > Statement > ID > OSQUARE > Exp > INTLIT > CSQUARE > ASSIGN > Exp > PARSEINT > OCURV > ID > OSQUARE > Exp > INTLIT > CSQUARE > CCURV Compiladores 2012-2013 | Trabalho nº 2 4 > SEMIC ... > CBRACE > CBRACE > EOF O analisador deve aceitar (e ignorar) comentários iniciados por // (sem incluir o caracter de fim de linha!) e do tipo /* */, bem como detectar a existência de quaisquer erros de sintaxe no ficheiro de entrada. CASOS PARTICULARES A gramática dada permite gerar certas expressões que teriam interpretação diferente em Java e Java+. Por exemplo: 8.length o em Java, tratar-se-ia de um literal real seguido de um identificador o em Java+-, trata-se de um literal inteiro seguido de DOTLENGTH (semanticamente inválido) new int[5][2] o em Java, tratar-se-ia de um array de dimensão 5x2 o em Java+-, tratar-se-ia da indexação de um array de dimensão 5 com o valor 2 (semanticamente válido!!!) De modo a garantir a compatibilidade estrita dos programas em Java+- com o Java, estes casos devem ser tratados do seguinte modo: Eventuais sequências do tipo <INTLIT>“.” devem ser reconhecidas como tokens de uma categoria lexical adicional (não usada pelas regras da gramática), de modo a que a sua ocorrência dê origem a um erro sintático. As regras da gramática devem ser modificadas de modo a que a ocorrência de expressões do tipo new int[...][...]... dê origem a um erro sintático. TRATAMENTO DE ERROS SINTÁTICOS Caso o ficheiro de entrada contenha erros sintáticos, o programa deverá imprimir a seguinte mensagem no stdout: *** Syntax error *** Encountered "(t.image)" at line (line no.), column (column no.). Last valid token: "(t.image)" onde (t.image), (line no.) e (column no.) devem ser substituídos pelos valores correspondentes. Isto pode ser conseguido apanhando (catch) a excepção ParseException gerada pelo analisador e processando o seu atributo currentToken, como a seguir se indica: try { Compiladores 2012-2013 | Trabalho nº 2 5 rootNode = parser.Start(); rootNode.dump("> "); } catch(ParseException e) { Token t = e.currentToken.next; System.out.println("*** Syntax error ***"); System.out.println(String.format("Encountered \"%s\" at line %d, column %d.", t.image, t.beginLine, t.beginColumn)); System.out.println(String.format("Last valid token: \"%s\"", e.currentToken.image)); } SUBMISSÃO DO TRABALHO O trabalho deverá ser validado no Mooshak, usando o concurso COMP1213_T2. 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 modificada de modo a eliminar a ambiguidade e a refletir a precedência dos operadores, em notação BNF; 2. Uma gramática equivalente à anterior, mas que seja também adequada à análise descendente com LOOKAHEAD=1, em notação BNF; 3. A gramática, usada para implementar o analisador, gerada pelo JJDoc; 4. As descrições do analisador que permitem compreenderem a estratégia de implementação adoptada; 5. Caso não implemente toda a funcionalidade pedida, deverá detalhar os aspectos não implementados. NOTA A tarefa A do concurso COMP1213_T2 contém todos os testes parciais incluídos nas outras tarefas. Compiladores 2012-2013 | Trabalho nº 2 6