Skip to content

lcastillov/TigerCompiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tiger

Para utilizar este compilador puede acceder a MatcomOnlineGrader y resolver problemas de programación seleccionando Tiger.

Descripción del proyecto

El proyecto se organizó en cuatro fases:

  1. Implementación de la gramática.
  2. Construcción del AST.
  3. Chequeo Semántico.
  4. Generación de Código.

1. Implementación de la gramática.

Durante la primera fase encontramos la necesidad (particular) de utilizar varios recursos de ANTLR (v.3.4) para desambiguar la gramática. Por ejemplo:

  1. Utilizamos k = 2 en las opciones de ANTLR.
  2. Durante la implementación del IF-THEN-ELSE tuvimos que utilizar la opción greedy=true para hacer corresponder a cada ELSE el IF más reciente.
  3. Igualmente tuvimos que utilizar la opción (greedy=true) para las operaciones aritméticas, lógicas y de comparación para evitar los warnings y resolver el no- determinismo.
  4. Utilizamos el operador => para resolver el conflicto entre un lvalue y la creación de un array.

El archivo que define la gramática se encuentra en TigerCompiler/Parsing/Tiger.g.

2. Construcción Del AST.

El AST de nuestro compilador para Tiger está formado por 61 nodos. El nodo “más arriba” en la jerarquía es TigerNode, abstracto y conteniendo definiciones para el posterior chequeo semántico y generación de código. De TigerNode heredan ProgramNode, UtilNode y ExpressionNode. ProgramNode es el nodo que representa a todo el programa y está compuesto por un solo nodo de tipo ExpressionNode. “Dentro” de UtilNode se encuentran aquellos nodos que nos servirán de ayuda durante el proceso de chequeo semántico, algunos de estos están relacionados con tipos, declaraciones, parámetros, campos dentro de los records, etc. Un ExpressionNode contiene una propiedad que indica el tipo de dicho nodo (Builint, AliasType, ArrayType, RecordType). Dentro de los tipos Builint se encuentran los enteros (int), cadenas (string), void, nil, error. Entonces hay expresiones que devuelven valor y otras que no, por ejemplo, las expresiones for, while, if-then, break, etc... siempre tendrán tipo void. El tipo nil se dará solamente al nodo compuesto por el token nil (NilNode). [Ver código para más información...]

3. Chequeo Semántico.

Todo nodo en el AST implementa el siguiente método:

void CheckSemantic(Scope scope, List<SemanticError> errors)

La clase Scope contiene las herramientas necesarias para localizar las variables, funciones y tipos correspondientes durante el chequeo semántico. La lista errors contendrá todos los errores que se detecten durante este proceso. Durante esta etapa toda variable quedará asociada a un VariableInfo, toda función a un FunctionInfo y todo tipo a un TypeInfo. Se declarará solamente un Info para cada variable, función y tipo (no para los alias). La parte más interesante del chequeo semántico fue el la declaración de tipos y funciones en un mismo bloque. En el caso de las funciones hicimos una primera pasada registrándolas y después otra pasada haciendo CheckSemantics al cuerpo de las mismas. Para el caso de los tipos, CheckSemantics detecta ciclos durante la declaración de un bloque. Antes de comenzar analizando ProgramNode (nuestro nodo raíz en el árbol concreto para cualquier programa) registramos los tipos int, string y todas las funciones estándares de Tiger.

4. Generación de código.

Una vez realizado CheckSemantics y en caso de no haber encontrado errores, seguimos con la generación de código. Todo nodo del AST implementará el siguiente método:

void GenerateCode(ModuleBuilder moduleBuilder, TypeBuilder program, ILGenerator generator)

  • ModuleBuilder: Se utiliza para registrar los tipos del programa. Solamente se registrarán los records como clases.
  • TypeBuilder: Es donde iremos poniendo las variables, las funciones y las funciones estándares.
  • ILGenerator: Es el cuerpo de la función actual. Al comienzo generador será el Main de Program.

Todas las variables son estáticas y corresponden tanto a parámetros, variables en los for así como variables declaradas directamente en un let-in-end. Toda función será estática y no recibirá parámetro ninguno, solamente conservarán el tipo de retorno del programa en tiger (.tig). Por cada tipo definido, incluyendo int y string, se declararán dos pilas genéricas en el tipo para ayudar al proceso de simulación de un llamado de función. Antes de llamar a una función, se insertan los valores de las variables locales a dicha función en las pilas correspondientes y después del llamado se restauran. La segunda pila es utilizada solamente para “ayudar” en el proceso del movimiento entre la primera pila y la pila de IL. Con esto, hacemos que nuestro programa no guarde la información relacionada a los llamados de funciones (ActiveRecord) en la pila de IL, porque esto puede provocar Stack Overflow considerablemente más rápido. Por esto decidimos guardar dicha información en el Heap.

Considere el siguiente ejemplo:

let
	var str := "Hello World"
in
	printline(str);
end

Este código se traducirá en algo como:

using System;
using System.Collections.Generic;
internal class Program
{
	static Stack<int> STACK_1;
	static Stack<int> STACK_2;
	static Stack<string> STACK_3;
	static Stack<string> STACK_4;
	static string VAR_5;
	static void Main()
	{
		try
		{
			Program.run();
		}
		catch (Exception ex)
		{
			Console.Error.WriteLine(“Some Error...);
			Environment.Exit(1);
		}
	}
	//
	// STANDARD_FUNCTIONS
	//
	public static void run()
	{
		Program.VAR_5 = "Hello World";
		Program.printline(Program.VAR_5);
	}
	static Program()
	{
		Program.STACK_1 = new Stack<int>();
		Program.STACK_2 = new Stack<int>();
		Program.STACK_3 = new Stack<string>();
		Program.STACK_4 = new Stack<string>();
	}
}

About

Compiler for a simplified version of tiger (http://matcomgrader.com/media/faq/tiger.pdf) using C# and Antlr.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published