Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Procesadores de Lenguajes

Curso 2022/2023

 

Práctica 8
El árbol de sintaxis abstracta de Tinto

 

OBJETIVOS

 

  • Describir la estructura de datos utilizada en el compilador de Tinto para almacenar toda la información de cada fichero fuente.

 

CÓDIGO A UTILIZAR

 

El código de esta práctica añade 26 ficheros al compilador de Tinto, distribuidos en 4 nuevos paquetes.

 

ESTRUCTURA DEL CÓDIGO

 

La etapa de análisis del compilador de Tinto tiene como objetivo leer un fichero fuente y generar una estructura de datos que almacene toda la información contenida en dicho fichero fuente. Esta estructura de datos se conoce como árbol de sintaxis abstracta o abstract sintax tree. Las clases utilizadas en el compilador de Tinto para desarrollar esta estructura de datos se encuentran en el paquete tinto.ast, que a su vez incluye otros tres subpaquetes. A continuación se describen estos paquetes:

  • tinto.ast: Es el paquete raíz de todas las clases utilizadas en la estructura de datos. El paquete sólo contiene las clases que desarrollan el sistema de tipos, que son utilizadas por muchas de las clases de los distintos subpaquetes.

  • tinto.ast.expression: Este paquete contiene las clases dedicadas a describir las diferentes expresiones aritméticas y lógicas incluidas en el lenguaje Tinto.

  • tinto.ast.statement: Es el paquete en el que se incluyen las clases que describen las diferentes sentencias del lenguaje Tinto: (if, while, return, etc.).

  • tinto.ast.struct: Es el paquete al que pertenecen las clases que describen los componentes de alto nivel del lenguaje Tinto (bibliotecas, funciones, etc.).

 

DESCRIPCIÓN DEL PAQUETE  tinto.ast

 

El paquete tinto.ast contiene las clases dedicadas a describir el sistema de tipos del lenguaje Tinto. El paquete está formado por dos interfaces y una clase.

  • tinto.ast.Access: Es una interfaz que define las constantes usadas para describir los dos tipos de acceso incluidos en el lenguaje: public y private.

  • tinto.ast.Type: Es una interfaz que define las constantes utilizadas para describir los tipos de datos incluidos en el lenguaje Tinto: void, int, char y boolean.

  • tinto.ast.TypeSystem: Es la clase que desarrolla los métodos asociados al sistema de tipos del lenguaje. Los métodos definidos en esta clase son los siguientes:

    • boolean isInteger(int type): Verifica que un tipo sea entero.

    • boolean isBoolean(int type): Verifica que un tipo sea booleano.

    • boolean isChar(int type): Verifica que un tipo sea carácter.

    • boolean isVoid(int type): Verifica que un tipo sea void.

    • boolean isNumeric(int type):Verifica que un tipo sea numérico.

    • boolean isOrderable(int type1, int type2): Verifica que dos tipos se pueden ordenar con '>' o '<'.

    • boolean isComparable(int type1, int type2): Estudia si dos tipos de datos pueden compararse con los operadores '=' o '!='.

    • boolean isRegularWord(int type): Verifica que un tipo se represente en un registro de propósito general de 32 bits.

    • int getSize(int type): Obtiene el tamaño del tipo de datos (en bytes).

     

 

DESCRIPCIÓN DEL PAQUETE  tinto.ast.struct

 

El paquete tinto.ast.struct contiene las clases dedicadas a describir los componentes de alto nivel (bibliotecas, funciones) del lenguaje Tinto. El paquete está formado por cuatro clases.

  • tinto.ast.struct.LibraryDeclaration: Es la clase que contiene toda la información recopilada de un archivo fuente, es decir, constituye la raíz del árbol de sintaxis abstracta a construir. Está formada por los siguientes componentes:

    • name: Es el nombre de la biblioteca, que coincide con el nombre del fichero fuente.

    • imported: Es un vector con los nombres de las diferentes bibliotecas importadas en el archivo.

    • function: Es un vector que almacena la lista de las descripciones de las funciones definidas en la biblioteca.

    Los diferentes métodos de la clase se limitan a acceder y a editar estos campos.

  • tinto.ast.struct.Function: Describe una función definida en el interior de una biblioteca. Contiene los siguientes campos:

    • libname: Es el nombre de la biblioteca a la que pertenece la función.

    • name: Es el nombre de la función.

    • access: Es el tipo de acceso a la función (público o privado).

    • type: Contiene el tipo de dato que devuelve la función.

    • argument: Contiene la lista de argumentos de llamada de la función. Cada argumento es un objeto de la clase Variable.

    • localVar: Contiene la lista de variables locales definidas en el cuerpo del método. Cada variable es un objeto de la clase Variable.

    • body: Contiene el bloque de instrucciones del método. Es un objeto de la clase tinto.ast.statement.BlockStatement.

    La mayor parte de los métodos de la clase están dedicados a acceder y a editar estos campos. A continuación se enumeran algunos métodos con una funcionalidad diferente.

    • int[] getArgumtentTypes(): Obtiene la lista de tipos de los argumentos. Se utiliza para verificar que no existan dos funciones con el mismo nombre y los mismos tipos de argumentos.

    • boolean match(String, int[]): Verifica si la función corresponde a ese nombre y a esos tipos de argumentos.

  • tinto.ast.struct.Variable: Contiene la descripción de una variable local o un argumento de una función. Los campos de la clase son los siguientes:

    • name: Nombre de la variable.

    • type: Tipo de datos de la variable.

  • tinto.ast.struct.SymbolTable: Es la clase que desarrolla la tabla de símbolos del compilador. La tabla de símbolos contiene el conjunto de bibliotecas analizadas por el compilador y se encarga de identificar la biblioteca y la función que se está analizando en cada momento. Para esta funcióm , la tabla de símbolos construye una pila de ámbitos de declaración de variables. Cuando una instrucción de la función activa abre un nuevo ámbito de declaración (por ejemplo, un bloque de instrucciones de un while), se apila un nuevo ámbito en esta clase. Los ámbitos se representan por medio de tablas hash que almacenan los nombres de las variables y las referencias a los objetos Variable asociados. Los métodos definidos en esta clase son los siguientes:

    • void addLibrary(LibraryDeclaration lib): Añade una biblioteca a la tabla de símbolos.

    • LibraryDeclaration getLibrary(String libname): Busca en la tabla de símbolos una biblioteca con el nombre indicado.

    • LibraryDeclaration getActiveLibrary(): Obtiene la biblioteca activa, es decir, la biblioteca que se está analizando en ese momento.

    • void setActiveLibrary(String libname): Asigna la biblioteca activa.

    • LibraryDeclaration[] getLibraries(): Obtiene la lista de bibliiotecas en forma de array.

    • Function getActiveFunction(): Obtiene la función activa, es decir, la función que se está analizando en ese momento.

    • void setActiveFunction(String name, int[] type): Asigna la función activa.

    • void createScope(): Crea un nuevo ámbito (una tabla hash) y lo almacena en la cima de la pila.

    • void deleteScope(): Elimina el ámbito almacenado en la cima de la pila.

    • void addLocalVariable(Variable var): Añade una declaración de variable en la función activa y la almacena en el ámbito de la cima de la pila.

    • void addArgument(Variable var) : Añade un argumento a la función activa y lo almacena en el ámbito de la cima de la pila.

    • Variable getVariable(String name): Busca una variable con el nombre indicado en todos los ámbitos de la pila.

    • Variable getVariableInScope(String name): Busca una variable con el nombre indicado en el ámbito de la cima de la pila.

 

DESCRIPCIÓN DEL PAQUETE  tinto.ast.statement

 

El paquete tinto.ast.statement contiene las clases que describen las diferentes instrucciones del lenguaje Tinto. El contenido del paquete es el siguiente:

  • tinto.ast.statement.Statement: Se trata de una clase abstracta que sirve como superclase de cualquier clase que defina una instrucción. La clase sólo contiene el método abstracto returns(), que debe verificar si la instrucción alcanza siempre un return o no (las instrucciones que alcanzan siempre un return son la propia instrucción return, un bloque de instrucciones que termine en un return y una instrucción if-else donde las dos opciones alcancen siempre un return).

  • tinto.ast.statement.IfStatement: Es la clase que describe una instrucción if. Sus campos son los siguientes:

    • condition: Describe la condición de la instrucción if. Es una referencia a un objeto Expression. Debe ser de tipo boolean.

    • thenInst: Describe la instrucción a ejecutar cuando la condición sea cierta. Es una referencia a un objeto Statement.

    • elseInst: Describe la instrucción a ejecutar cuando la condición sea falsa. Es una referencia a un objeto Statement. Si la instrucción no tiene cláusula else el campo debe tener una referencia nula.

    Los métodos de la clase se limitan a asignar y acceder a los valores de estos campos. El método returns() devuelve cierto si las instrucciones thenInst y elseInst alcanzan un return.

  • tinto.ast.statement.WhileStatement: Es la clase que describe una instrucción while. Está formada por los siguientes campos:

    • condition: Describe la condición de control del bucle. Es una referencia a un objeto Expression de tipo boolean.

    • instruction: Describe el cuerpo del bucle. Es una referencia a un objeto Statement.

    Los métodos se limitan a acceder y editar los valores de los campos. El método returns() devuelve siempre falso.

  • tinto.ast.statement.ReturnStatement: Describe una instrucción return. Contiene un único campo:

    • expression: Describe la expresión asociada a la instrucción return. Es una referencia a un objeto de la clase Expression. El tipo de expresión debe coincidir con el tipo devuelto por la función en el que se encuentre la instrucción. Si la instrucción es vacía (el retorno de las funciones de tipo void) la referencia debe ser nula.

    Los métodos de la clase son los típicos de acceso y edición de este campo. El método returns() devuelve siempre cierto.

  • tinto.ast.statement.AssignStatement: Es la clase que describe una instrucción de asignación. Sus campos son:

    • lefthand: Contiene la referencia a la variable que aparece en la parte izquierda de la asignación. Es un objeto de la clase Variable.

    • expression: Representa la parte derecha de la asignación, es decir, la expresión que permite calcular el valor a asignar. Es una referencia a un objeto de la clase Expression. El tipo de la expresión debe coincidir con el tipo de la variable.

    El método returns() devuelve siempre falso. Los demás métodos se limitan a obtener y asignar los valores de los campos.

  • tinto.ast.statement.CallStatement: Describe una instrucción de llamada a una función. Está formada por un único campo.

    •  exp: contiene la referencia al objeto Expression que describe la expresión de llamada a la función.

  • tinto.ast.statement.BlockStatement: Es una clase que describe un bloque de instrucciones. Se suele utilizar como cuerpo de las instrucciones if y while. Sus campos son:

    • list: Contiene la lista de instrucciones del bloque, es decir, la lista de objetos Statement.

    • returns: Es un marcador que indica si el bloque de instrucciones alcanza siempre un return. Este marcador se actualiza cada vez que se añade una nueva instrucción al bloque.

    El método más importante es addStatement(Statement), que añade una nueva instrucción al bloque y actualiza el marcador returns. El método returns() devuelve el valor del marcador.

 

DESCRIPCIÓN DEL PAQUETE  tinto.ast.expression

 

El paquete tinto.ast.expression contiene las clases que permiten describir los diferentes tipos de expresiones soportadas por el lenguaje Tinto. Estas expresiones se utilizan como parte derecha en las asignaciones, como argumentos en las llamadas a funciones y como condiciones en las instrucciones if y while. El contenido del paquete es el siguiente:

  • tinto.ast.expression.Expression: Se trata de una clase abstracta que se utiliza como superclase de todas las clases que describen expresiones. Su único campo es type, que contiene el tipo de dato de la expresión. Este tipo se almacena como un entero y puede tomar los valores definidos en la interfaz tinto.ast.Type.

  • tinto.ast.expression.OperatorExpression: Es una clase abstracta que encapsula a todas las expresiones que representan una operación sobre otras expresiones. Esta clase es desarrollada por las subclases BinaryExpression y UnaryExpression.

  • tinto.ast.expression.LiteralExpression: Es la clase abstracta que encapsula a todas las expresiones que representan un valor literal. Esta clase es desarrollada por las subclases BooleanLiteralExpression, CharLiteralExpression e IntegerLiteralExpression.

  • tinto.ast.expression.ReferenceExpression: Se trata de una clase abstracta que encapsula a todas las expresiones que representan una referencia (a una variable o a una función).

  • tinto.ast.expression.BinaryExpression: Es la clase que describe expresiones binarias, es decir, expresiones formadas por un operador y dos operandos. La clase define una serie de constantes para identificar los diferentes operadores binarios del lenguaje Tinto: operadores lógicos (AND y OR), operadores aritméticos (PLUS, MINUS, PROD, DIV y MOD) y operadores de comparación (EQ, NEQ, GT, GE, LT y LE). Los componentes de la clase son los siguientes:

    • op: Código del operador binario. Su valor debe ser una de las constantes definidas en la clase.

    • left: Operando de la izquierda de la expresión. Es una referencia a un objeto Expression.

    • right: Operando de la derecha de la expresión. Es una referencia a un objeto Expression.

    Los métodos de la clase son los getters de estos campos. El tipo de datos de la expresión se introduce en el constructor.

  • tinto.ast.expression.UnaryExpression: Es la clase que describe expresiones unarias, es decir, expresiones formadas por un operador y un único operando. La clase define una serie de constantes para identificar los diferentes operadores unarios del lenguaje Tinto: el operador lógico de negación (NOT) y los operadores aritméticos unarios (PLUS y MINUS). También incluye una constante (NONE) para indicar que no se aplica ningún operador unario al operando. Los componentes de la clase son los siguientes:

    • op: Código del operador binario. Su valor debe ser una de las constantes definidas en la clase.

    • exp: Operando de la expresión. Es una referencia a un objeto Expression.

    Los métodos de la clase son los getters de estos campos. El tipo de datos de la expresión se introduce en el constructor.

  • tinto.ast.expression.BooleanLiteralExpression: Esta clase permite describir un literal de tipo boolean, es decir, permite representar las constantes true y false. Su único campo es value, que almacena el valor del literal y el único método es el getter de este campo. La clase contiene dos constructores que permiten construir el literal analizando el lexema (las cadenas "true" y "false"), o directamente con el valor. El tipo de datos de la expresión es boolean.

  • tinto.ast.expression.CharLiteralExpression: Es la clase dedicada a representar literales de tipo carácter. Su único campo es value, que almacena el valor del literal. La clase contiene dos constructores: uno basado directamente en el valor del literal y otro basado en el lexema. Para analizar el lexema se utiliza el método privado getCharFromString(String), que analiza las diferentes formas de representar un literal de tipo carácter definidas en el lenguaje Tinto: caracteres editables (por ejemplo, 'a'), caracteres no editables descritos con la barra invertida (por ejemplo, '\n'), caracteres descritos en formato octal (por ejemplo, '\071') y caracteres descritos en formato unicode (por ejemplo, '\u007A'). El tipo de datos de la expresión es char.

  • tinto.ast.expression.IntegerLiteralExpression: Describe literales de tipo entero. Su único campo es value, que almacena el valor del literal, y su único método es getValue(), que devuelve el valor de dicho campo. Los constructores permiten crear los objetos a partir del lexema (por ejemplo, "325"), o directamente a partir del valor. El tipo de datos de la expresión es int.

  • tinto.ast.expression.CallExpression: Es la clase que describe una llamada a una función. El tipo de datos de la expresión corresponde al tipo de datos que devuelve la función. Los campos de la clase son los siguientes:

    • method: Es una referencia al objeto Method que describe la función a la que se llama.

    • library: Es una referencia al objeto Library en el que está definida la función a la que se llama, es decir, es una referencia a la biblioteca a la que pertenece la función.

    • call: Contiene la descripción de los argumentos de la llamada. Es un objeto de la clase CallParameters.

  • tinto.ast.expression.CallParameters: Es la única clase del paquete que no es subclase de Expression. Describe los argumentos de la llamada a una función, es decir, la lista de expresiones utilizadas en la definición de la llamada. Su único campo es parameter, que contiene una lista de expresiones. Los métodos de la clase permiten añadir expresiones a la lista ( addParameter() ), obtener la lista ( getParameters() ) y obtener los tipos de datos de las expresiones ( getTypes() ). Este último método se utiliza para localizar la versión de la función considerando los tipos de datos de la llamada. (El lenguaje Tinto soporta la sobrecarga de funciones, es decir, la definición de funciones con el mismo nombre que se diferencia en los tipos de datos de sus argumentos).

  • tinto.ast.expression.VariableExpression: Describe una expresión formada por la referencia a una variable local o a un argumento del método. Su único campo es var, que almacena la referencia al objeto Variable que describe la variable. El tipo de datos de la expresión corresponde al tipo de datos de la variable.