Trabalho Alternativo.

 

Este trabalho substitui o segundo e terceiro trabalhos de Construção de Compiladores.

 

Faça um compilador que tome uma gramática como entrada (em um arquivo) e produza como saída um arquivo em Java com o analisador sintático da gramática. O analisador deve estar em uma classe Compiler. A gramática da gramática é dada abaixo. Terminal e RuleName são terminais. Ambos são compostos por seqüências de letras e números começando por letra, mas Terminal deve começar por letra minúscula e RuleName por letra maiúscula. Assuma que o analisador léxico esteja pronto em uma classe Lexer com método nextToken para produzir o próximo token, obtido pela variável pública token de Lexer. Obviamente, a classe Compiler deve declarar uma variável lexer da classe Lexer. Assuma que exista uma classe Symbol como constantes declaradas para cada terminal.

 

Gramática de gramáticas:

 

Grammar ::= Rule { Rule }

Rule ::= RuleName "::=" Prodution { "|" Production }

Production ::= | SimpleProduction { SimpleProduction }

SimpleProduction ::= Terminal  |  RuleName

                  | "[" SimpleProduction "]"    

                  | "{" SimpleProduction "}" 

 

 

Como exemplo, suponha que tenhamos a gramática

 

E ::= T  { OperAdd T }

T ::= F { OperMult F }

F ::= [minus] SF

SF ::= ident  | number

OperAdd ::= plus | minus

OperMult ::= mult | div

 

O seu compilador deve produzir a classe Compiler com os seguintes métodos:

 

void e() {

     // E ::= T  { OperAdd T }

   t();

   while ( lexer.token == Symbol.PLUS ||

           lexer.token == Symbol.MINUS ) {

      operAdd();

      t();

   }

}

 

void t() {

      // T ::= F { OperMult F }

   f();

   while ( lexer.token == Symbol.PLUS ||

           lexer.token == Symbol.MINUS ) {

      operMult();

      f();

   }

}

 

void f() {

     // F ::= [minus] SF

   if ( lexer.token == Symbol.MINUS )

      lexer.nextToken();

   sf();

}

 

void sf() {

      // SF ::= ident  | number

   if ( lexer.token == Symbol.IDENT )

      lexer.nextToken();

   else if ( lexer.token == Symbol.NUMBER )

      lexer.nextToken();

   else

      error("Syntax error");

}

 

 

void operAdd() {

     // OperAdd ::= plus | minus

   if ( lexer.token == Symbol.PLUS )

      lexer.nextToken();

   else if ( lexer.token == Symbol.MINUS )

      lexer.nextToken();

   else

      error("Syntax error");

}

 

void operMult() {

     // OperMult ::= mult | div

   if ( lexer.token == Symbol.MULT )

      lexer.nextToken();

   else if ( lexer.token == Symbol.DIV )

      lexer.nextToken();

   else

      error("Syntax error");

}

 

 

Quando houver alternativas, como em operAdd e

    S ::= A | B | C

você pode gerar o seguinte:

 

void s() {

   switch ( lexer.nextToken() ) {

      case 'a' :

      case 'b' :

         a();

         break;

      case 'c' :

         b();

         break;

      case 'c' :

      case 'd' :

      case 'e' :

         c();

         break;

      default :

         error("Syntax error");

   }

}

 

Neste caso, assumiu-se que first(A) = {a, b}, first(B) = { c } e first(C) = {c, d, e}. Fica mais fácil gerar o switch do que a seqüência de if’s como em operAdd e operMult.

 

Evidentemente, a classe Compiler que você produzir, com a classe Lexer e um programa Main, devem compilar. Se algum aspecto da análise estiver muito difícil, fale comigo que poderemos reduzir o tamanho do trabalho.