|
Grado en Ingeniería Informática
Procesadores de Lenguajes
Curso 2022/2023
|
Práctica 4 |
|
Analizadores sintácticos descendentes |
|
OBJETIVOS |
- Describir la implementación de un analizador sintáctico descendente
recursivo para gramáticas LL(1) expresadas en notación BNF.
- Describir la implementación del analizador sintáctico del lenguaje de
programación Tinto.
|
|
CÓDIGO A UTILIZAR |
El código de esta práctica contiene 9 ficheros de los cuales 7 ficheros
corresponden al analizador léxico del lenguaje Tinto descrito en la práctica
3. Los ficheros añadidos corresponden al analizador sintáctico descendente
recursivo del lenguaje Tinto y a la clase dedicada a describir excepciones
sintácticas.
|
|
GRAMÁTICA DE TINTO EN NOTACIÓN
EBNF |
|
A continuación se describe la gramática del lenguaje de programación Tinto
en formato EBNF.
-
CompilationUnit : ( ImportClause )* TintoDecl
-
ImportClause : import
identifier semicolon
-
TintoDecl : LibraryDecl | NativeDecl
-
LibraryDecl : library identifier
lbrace ( Function )* rbrace
-
NativeDecl : native identifier
lbrace ( NativeFunction )* rbrace
-
Function : Access FunctionType identifier
ArgumentDecl FunctionBody
-
NativeFunction : Access FunctionType identifier
ArgumentDecl semicolon
-
Access : public
| private
-
FunctionType : Type | void
-
Type : int | char | boolean
-
ArgumentDecl : lparen ( Argument (
comma Argument )* )? rparen
-
Argument : Type identifier
-
FunctionBody : lbrace ( Statement )* rbrace
-
Statement : ( Decl | IdStm | IfStm | WhileStm | ReturnStm |
NoStm | BlockStm )
-
Decl : Type identifier Assignement
( comma identifier
Assignement )* semicolon
-
Assignement : ( assign Expr )?
-
IfStm : if lparen
Expr rparen Statement ( else
Statement )?
-
WhileStm : while lparen
Expr rparen Statement
-
ReturnStm : return ( Expr )?
semicolon
-
NoStm : semicolon
-
IdStm : identifier ( Assignement |
FunctionCall | dot identifier
FunctionCall ) semicolon
-
BlockStm : lbrace ( Statement )*
rbrace
-
Expr : AndExpr ( or AndExpr )*
-
AndExpr : RelExpr ( and RelExpr )*
-
RelExpr : SumExpr ( RelOp SumExpr )?
-
RelOp : ( eq | ne |
gt | ge | lt |
le )
-
SumExpr : UnOp ProdExpr ( SumOp ProdExpr
)*
-
UnOp : ( not | minus | plus
)?
-
SumOp : ( minus | plus )
-
ProdExpr : Factor ( MultOp Factor )*
-
MultOp : ( prod | div | mod
)
-
Factor : ( Literal | Reference |
lparen Expr rparen )
-
Literal : ( integer_literal |
char_literal | true |
false )
-
Reference : identifier ( FunctionCall |
dot identifier FunctionCall )?
-
FunctionCall : lparen ( Expr (
comma Expr )* )? rparen
|
|
GRAMÁTICA DE TINTO EN NOTACIÓN
BNF |
|
Al transformar la gramática EBNF de Tinto a una gramática BNF el resultado
es el siguiente:
| Regla |
Predicción |
| CompilationUnit ::= ImportClauseList
TintoDecl |
import, library, native |
| ImportClauseList ::= ImportClause ImportClauseList |
import |
| ImportClauseList ::= lambda |
library |
| ImportClause ::= import identifier semicolon |
import |
| TintoDecl ::= LibraryDecl |
library |
| TintoDecl ::= NativeDecl |
native |
| LibraryDecl ::= library identifier
lbrace FunctionList rbrace |
library |
| FunctionList ::= Function FunctionList |
public, private |
| FunctionList ::= lambda |
rbrace |
| Function ::= Access FunctionType identifier ArgumentDecl FunctionBody |
public, private |
| NativeDecl ::= native identifier
lbrace NativeFunctionList rbrace |
native |
| NativeFunctionList ::= NativeFunction NativeFunctionList |
public, private |
| NativeFunctionList ::= lambda |
rbrace |
| NativeFunction ::= Access FunctionType identifier ArgumentDecl semicolon |
public, private |
| Access ::= public |
public |
| Access ::= private |
private |
| FunctionType ::= Type |
int, char, boolean |
| FunctionType ::= void |
void |
| Type ::= int |
int |
| Type ::= char |
char |
| Type ::= boolean |
boolean |
| ArgumentDecl := lparen ArgumentList rparen |
lparen |
| ArgumentList ::= Argument MoreArguments |
int, char, boolean |
| ArgumentList ::= lambda |
rparen |
| MoreArguments ::= comma Argument MoreArguments |
comma |
| MoreArguments ::= lambda |
lparen |
| Argument ::= Type identifier |
int, char, boolean |
| FunctionBody ::= lbrace StatementList rbrace |
lbrace |
| |
|
| StatementList ::= Statement StatementList |
int, char, boolean, identifier, if, while,
return, semicolon, lbrace |
| StatementList ::= lambda |
rbrace |
| Statement ::= Decl |
int, char, boolean |
| Statement ::= IdStm |
identifier |
| Statement ::= IfStm |
if |
| Statement ::= WhileStm |
while |
| Statement ::= ReturnStm |
return |
| Statement ::= NoStm |
semicolon |
| Statement ::= BlockStm |
lbrace |
| Decl ::= Type identifier Assignement MoreDecl semicolon |
int, char, boolean |
| MoreDecl ::= comma identifier Assignement MoreDecl |
comma |
| MoreDecl ::= lambda |
semicolon |
| Assignement ::= assign Expr |
assign |
| Assignement ::= lambda |
comma, semicolon |
| IfStm ::= if lparen Expr rparen
Statement ElseStm |
if |
| ElseStm ::= else Statement |
else |
| ElseStm ::= lambda |
int, char, boolean, identifier, if, while,
return, semicolon, lbrace |
| WhileStm ::= while lparen Expr rparen Statement |
while |
| ReturnStm ::= return ReturnExpr semicolon |
return |
| ReturnExpr ::= Expr |
not, minus, plus, integer_literal, char_literal, true,
false, identifier, lparen |
| ReturnExpr ::= lambda |
semicolon |
| NoStm ::= semicolon |
semicolon |
| IdStm ::= identifier IdStmContinue semicolon |
identifier |
| IdStmContinue ::= assign Expr |
assign |
| IdStmContinue ::= FunctionCall |
lparen |
| IdStmContinue ::= dot identifier FunctionCall |
dot |
| BlockStm ::= lbrace StatementList rbrace |
lbrace |
| |
|
| Expr ::= AndExpr MoreOrExpr |
not, minus, plus, integer_literal, char_literal, true,
false, identifier, lparen |
| MoreOrExpr ::= or AndExpr MoreOrExpr |
or |
| MoreOrExpr ::= lambda |
comma, rparen, semicolon |
| AndExpr ::= RelExpr MoreAndExpr |
not, minus, plus, integer_literal, char_literal, true,
false, identifier, lparen |
| MoreAndExpr ::= and RelExpr MoreAndExpr |
and |
| MoreAndExpr ::= lambda |
or, comma, rparen, semicolon |
| RelExpr ::= SumExpr MoreRelExpr |
not, minus, plus, integer_literal, char_literal, true,
false, identifier, lparen |
| MoreRelExpr ::= RelOp SumExpr |
eq, ne, gt, ge, lt, le |
| MoreRelExpr ::= lambda |
and, or, comma, rparen, semicolon |
| RelOp ::= eq |
eq |
| RelOp ::= ne |
ne |
| RelOp ::= gt |
gt |
| RelOp ::= ge |
ge |
| RelOp ::= lt |
lt |
| RelOp ::= le |
le |
| SumExpr ::= UnOp ProdExpr MoreSumExpr |
not, minus, plus, integer_literal, char_literal, true,
false, identifier, lparen |
| MoreSumExpr ::= SumOp ProdExpr MoreSumExpr |
plus, minus |
| MoreSumExpr ::= lambda |
eq, ne, gt, ge, lt, le,
and, or, comma, rparen, semicolon |
| UnOp ::= not |
not |
| UnOp ::= minus |
minus |
| UnOp ::= plus |
plus |
| UnOp ::= lambda |
integer_literal, char_literal, true,
false, identifier, lparen |
| SumOp ::= minus |
minus |
| SumOp ::= plus |
plus |
| ProdExpr ::= Factor MoreProdExpr |
integer_literal, char_literal, true,
false, identifier, lparen |
| MoreProdExpr ::= MultOp Factor MoreProdExpr |
prod, div, mod |
| MoreProdExpr ::= lambda |
plus, minus, eq, ne,
gt, ge, lt, le, and, or, comma, rparen, semicolon |
| MultOp ::= prod |
prod |
| MultOp ::= div |
div |
| MultOp ::= mod |
mod |
| Factor ::= Literal |
integer_literal, char_literal, true, false |
| Factor ::= Reference |
identifier |
| Factor ::= lparen Expr rparen |
lparen |
| Literal ::= integer_literal |
integer_literal |
| Literal ::= char_literal |
char_literal |
| Literal ::= true |
true |
| Literal ::= false |
false |
| Reference ::= identifier ReferenceContinue |
identifier |
| ReferenceContinue ::= FunctionCall |
lparen |
| ReferenceContinue ::= dot identifier FunctionCall |
dot |
| ReferenceContinue ::= lambda |
prod, div, mod, plus, minus, eq, ne,
gt, ge, lt, le, and, or, comma, rparen, semicolon |
| FunctionCall ::= lparen ExprList rparen |
lparen |
| ExprList ::= Expr MoreExpr |
integer_literal, char_literal, true,
false, identifier, lparen, not, minus, plus |
| ExprList ::= lambda |
rparen |
| MoreExpr ::= comma Expr MoreExpr |
comma |
| MoreExpr ::= lambda |
rparen |
|
|
ANALIZADOR SINTÁCTICO DE TINTO |
|
A partir de la gramática LL(1) de Tinto expresada en notación BNF y de los
conjuntos de predicción calculados para cada regla de la gramática se
puede desarrollar un analizador sintáctico descendente recursivo. La
implementación de este analizador se encuentra en la clase TintoParser,
cuyo código se muestra a continuación. La clase contiene dos miembros
privados: el analizador léxico (lexer), utilizado para obtener el
flujo de tokens de entrada, y el token de preanálisis (nextToken),
que es el que determina la regla a ejecutar en el análisis descendente. El
método principal de la clase se denomina parse() y toma como
entrada el fichero a analizar devuelviendo como salida un valor
boolean, que indica si el contenido del fichero es correcto. Este
método genera una excepción SintaxException al encontrar un error
sintáctico, detectando de esta forma los ficheros incorrectos.
La implementación del analizador sintáctico descendente recursivo consiste
en generar un método por cada símbolo no terminal de la gramática. El
código de estas funciones es similar para todos los símbolos por lo que
solo se muestra como ejemplo la función asociada al símbolo
CompilationUnit. El reconocimiento de los tokens se representa
mediante llamadas al método match(), que verifica que el token de
preanálisis corresponde al token esperado y lo consume solicitando al
analizador léxico el siguiente token de entrada.
public class TintoParser implements TokenConstants {
/**
* Analizador léxico
*/
private TintoLexer lexer;
/**
* Siguiente token de la cadena de entrada
*/
private Token nextToken;
/**
* Método de análisis de un fichero
*
*/
public boolean parse(File file) throws IOException, SintaxException
{
this.lexer = new TintoLexer(file);
this.nextToken = lexer.getNextToken();
parseCompilationUnit();
if(nextToken.getKind() == EOF) return true;
else return false;
}
/**
* Método que consume un token de la cadena de entrada
*/
private void match(int kind) throws SintaxException
{
if(nextToken.getKind() == kind) nextToken = lexer.getNextToken();
else throw new SintaxException(nextToken,kind);
}
/**
* Analiza el símbolo CompilationUnit
* @throws SintaxException
*/
private void parseCompilationUnit() throws SintaxException
{
int[] expected = { IMPORT, LIBRARY };
switch(nextToken.getKind())
{
case IMPORT:
case LIBRARY:
parseImportClauseList();
parseTintoDecl();
break;
default:
throw new SintaxException(nextToken,expected);
}
}
...
}
|
La clase SintaxException permite representar un error sintáctico.
Estos errores se producen cuando el token analizado no corresponde a
ninguno de los tokens esperados. Este tipo de excepciones se pueden lanzar
en los métodos parse...() cuando el siguente token no pertenence
al conjunto de predicción de ninguna de las reglas del símbolo y en el
método match() cuando el siguente token no corresponde al token
que se desea reconocer. El primer constructor mostrado corresponde a la
excepción lanzada por el método match() mientras que el segundo
constructor es el que se utiliza en los métodos parse...().
public class SintaxException extends Exception implements TokenConstants {
/**
* Mensaje de error
*/
private String msg;
/**
* Constructor con un solo tipo esperado
* @param token
* @param expected
*/
public SintaxException(Token token, int expected)
{
this.msg = "Sintax exception at row "+token.getRow();
msg += ", column "+token.getColumn()+".\n";
msg += " Found "+token.getLexeme()+"\n";
msg += " while expecting "+getLexemeForKind(expected)+".\n";
}
/**
* Constructor con una lista de tipos esperados
* @param token
* @param expected
*/
public SintaxException(Token token, int[] expected)
{
this.msg = "Sintax exception at row "+token.getRow();
msg += ", column "+token.getColumn()+".\n";
msg += " Found "+token.getLexeme()+"\n";
msg += " while expecting one of\n";
for(int i=0; i<expected.length; i++)
{
msg += " "+getLexemeForKind(expected[i])+"\n";
}
}
...
}
|
Por último, la clase TintoCompiler ha sido modificada para
incorporar el análisis sintáctico.
La nueva versión busca el fichero "Main.tinto" en el directorio
de trabajo y lo analiza sintácticamente. Si el análisis sintáctico resulta
correcto se genera un fichero "TintocOutput.txt" con el mensaje "Correcto".
Si se detecta una excepción se genera el fichero "TintocOutput.txt"
con el mensaje "Incorrecto" y el fichero "TintocError.txt"
con la descripción de la excepción.
public class TintoCompiler {
/**
* Punto de entrada de la aplicación
* @param args
*/
public static void main(String[] args)
{
// Busca el directorio de trabajo
String path = (args.length == 0 ? System.getProperty("user.dir") : args[0]);
File workingdir = new File(path);
try
{
File mainfile = new File(workingdir, "Main.tinto");
TintoParser parser = new TintoParser();
if(parser.parse(mainfile))
{
printOutput(workingdir,"Correcto");
}
else
{
printOutput(workingdir,"Incorrecto");
}
}
catch(Error err)
{
printError(workingdir, err);
printOutput(workingdir,"Incorrecto");
}
catch(Exception ex)
{
printError(workingdir, ex);
printOutput(workingdir,"Incorrecto");
}
}
...
}
|
|
|