First public commit!
This commit is contained in:
502
README.md
Normal file
502
README.md
Normal file
@@ -0,0 +1,502 @@
|
||||
TL;DR
|
||||
=====
|
||||
|
||||
- **YATC** (Yet Another Tiger Compiler) is a **Tiger** compiler that generates
|
||||
**.Net framework** managed binaries.
|
||||
|
||||
- YATC was made in 2014 as part of an undergrad Computer Science project by
|
||||
**Damian Valdés Santiago** and **Juan Carlos Pujol Mainegra**.
|
||||
|
||||
- **Tiger** is a relatively simple and imperative language that features
|
||||
nested functions, records, arrays, integral variables and strings.
|
||||
|
||||
- Tiger was defined by **Andrew Appel** in 1998 for *Modern Compiler
|
||||
Implementation in Java* (Cambridge University Press, 1998)
|
||||
|
||||
- This report will explain the architecture and implementation of a Tiger
|
||||
compiler using **ANTLR 3.3.4** for the grammar definition, **C\# 5.0** in .Net
|
||||
Framework 4.5 for the construction of the Abstract Syntax Tree (AST) and the
|
||||
Dynamic Language Runtime (DLR) for the generation of the CIL/MSIL (Common
|
||||
Intermediary Language/Microsoft Intermediary Language) code.
|
||||
|
||||
- **YATC** se presenta *tal cual* y no es mantenido de ninguna forma por sus
|
||||
autores.
|
||||
|
||||
Resumen
|
||||
-------
|
||||
|
||||
- **YATC** (Yet Another Tiger Compiler) es un compilador de **Tiger** que
|
||||
genera binarios gestionados para **.Net framework**.
|
||||
|
||||
- YATC fue realizado en 2014 como parte de un proyecto de pregrado de Ciencias
|
||||
de la Computación por **Damian Valdés Santiago** y **Juan Carlos Pujol
|
||||
Mainegra**.
|
||||
|
||||
- **Tiger** es un lenguaje relativamente sencillo e imperativo con
|
||||
funciones anidadas, records, arrays y variables enteras y de tipo cadena
|
||||
*string*.
|
||||
|
||||
- Tiger fue definido por **Andrew Appel** en 1998 para *Modern Compiler
|
||||
Implementation in Java* (Cambridge University Press, 1998)
|
||||
|
||||
- En este informe se explicará la arquitectura e implementación de un
|
||||
compilador para el lenguaje Tiger usando **ANTLR 3.3.4** para la definición de
|
||||
la gramática, **C\# 5.0** en .Net Framework 4.5 para la construcción del
|
||||
*Árbol de Sintaxis Abstracta* (AST) y *Dynamic Language Runtime* (DLR) para la
|
||||
generación de código CIL/MSIL (Common Intermediary Language/Microsoft
|
||||
Intermediary Language).
|
||||
|
||||
- **YATC** se presenta *tal cual* y no es mantenido de ninguna forma por sus
|
||||
autores.
|
||||
|
||||
License
|
||||
=======
|
||||
Yet Another Tiger Compiler (YATC)
|
||||
|
||||
Copyright (C) 2014 Damian Valdés Santiago, Juan Carlos Pujol Mainegra
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Sobre el proceso de compilación
|
||||
===============================
|
||||
|
||||
YATC se inscribe en el clásico proceso para compilar un programa,
|
||||
dividiendo el mismo en cuatro fases en relación de dependencia:
|
||||
|
||||
- **Análisis Lexicográfico**: identifica subsecuencias de caracteres
|
||||
significativas separando el flujo de entrada en estructuras lógicas
|
||||
llamadas *tokens*. Tokens podrían ser identificadores, palabras
|
||||
claves, operaciones, etc. del lenguaje que se desea implementar. La
|
||||
componente de software que realiza esta operación se llama *lexer*.
|
||||
En resumen, el lexer transforma una cadena de caracteres en una
|
||||
secuencia de tokens.
|
||||
|
||||
- **Análisis sintáctico**: garantiza que la secuencia de tokens tenga
|
||||
un sentido según las especificaciones del lenguaje, construyendo un
|
||||
árbol de derivación (DT), que es una representación de las
|
||||
secuencias de derivaciones de la gramática, para obtener un programa
|
||||
en Tiger. La componente de software que realiza esta operación se
|
||||
llama *parser* En resumen, el parser recibe como entrada una
|
||||
secuencia de tokens y emite un AST como resultado del proceso.
|
||||
|
||||
- **Análisis o chequeo semántico**: verifica que en el lenguaje se
|
||||
tengan sentencias con sentido obteniéndose un AST. En resumen, se
|
||||
transforma de un DT a un AST.
|
||||
|
||||
- **Generación de código**: el AST se convierte en un árbol de
|
||||
sintaxis concreta y se genera código para las instrucciones del
|
||||
programa en cuestión.
|
||||
|
||||
####
|
||||
|
||||
Cuando se compila un programa en Tiger se intenta transformar el mismo
|
||||
en una secuencia de tokens válida para Tiger, de lo contrario se produce
|
||||
un error sintáctico. Luego, se construye el AST correspondiente según la
|
||||
jerarquía de clases implementadas y el patrón *TreeAdaptor* de ANTLR que
|
||||
conecta la fase anterior con la actual (chequeo semántico). Se verifica
|
||||
que no existan problemas y se pasa a la generación de código. De lo
|
||||
contrario se reporta error semántico y se detiene el proceso de
|
||||
compilación.
|
||||
|
||||
Herramientas para la implementación
|
||||
===================================
|
||||
|
||||
En la implementación de YATC se utilizaron los siguientes softwares y
|
||||
lenguajes:
|
||||
|
||||
- ANTLR, ANTLWorks y JRE para la definición de la gramática de Tiger
|
||||
(entiéndase reglas del lexer y del parser), su prueba y proceso de
|
||||
*debug* para la detección de fallas.
|
||||
|
||||
- Lenguaje .NET C\# en Microsoft Visual Studio 2012 para la jerarquía
|
||||
de nodos del AST, la realización del chequeo semántico y la
|
||||
generación de código a través de DLR cuya elección se justificará
|
||||
más adelante.
|
||||
|
||||
- PeVerify suministrado por el SDK de .Net para la corrección del
|
||||
ensamblado.
|
||||
|
||||
- TigerTester 1.2 para la realización de pruebas.
|
||||
|
||||
Análisis lexicográfico y sintáctico
|
||||
===================================
|
||||
|
||||
Comentarios anidados
|
||||
--------------------
|
||||
|
||||
Para lograr el anidamiento de los comentarios, primero se crearon las
|
||||
reglas del lexer que se muestran a continuación continuación:
|
||||
|
||||
```antlr
|
||||
fragment BEGIN_COMMENT : '/*';
|
||||
fragment END_COMMENT : '*/' ;
|
||||
COMMENTARY : BEGIN_COMMENT ( options {greedy=false;} : COMMENTARY | . )*
|
||||
END_COMMENT {$channel = Hidden;};
|
||||
```
|
||||
|
||||
Esta regla es engañosa: al probar un código con comentarios anidados
|
||||
como el que se muestra no se produce error (lo cual es correcto), sin
|
||||
embargo al crear un código con comentarios anidados y desbalanceados, no
|
||||
se produce tampoco error y esto sí es incorrecto desde el punto de vista
|
||||
sintáctico.
|
||||
|
||||
```antlr
|
||||
/* Comment1 /* Comment2 */ Comment3 */
|
||||
```
|
||||
|
||||
El comportamiento por defecto de ANTLR al intentar realizar *match* a
|
||||
una secuencia es consumirla entera. En ocasiones (como la que se
|
||||
discute) esto no se desea. Este propósito se logra con *options
|
||||
{greedy=false;}*. El operador *wildcard* ($\cdot$) representa a
|
||||
cualquier caracter y por tanto, aunque se coloque en la regla `options
|
||||
{greedy=false;} : COMMENTARY | $\cdot$`, ANTLR no es capaz de
|
||||
diferenciar que el `COMMENTARY` no debe estar incluido en lo reconocido
|
||||
por el operador *wildcard*.
|
||||
|
||||
Luego de notar estos errores se redefinen las reglas, obligando a que se
|
||||
consuman caracteres hasta encontrar un bloque de comentarios seguido de
|
||||
cualquier secuencia de caracteres.
|
||||
|
||||
```antlr
|
||||
COMMENTARY : BEGIN_COMMENT ( options {greedy=false;} : . )*
|
||||
(COMMENTARY ( options {greedy=false;} : . )* )*
|
||||
END_COMMENT {$channel = Hidden;}
|
||||
```
|
||||
|
||||
Los programa vacíos también son programas
|
||||
-----------------------------------------
|
||||
|
||||
Un programa en Tiger es una expresión. Un programa que no tenga código
|
||||
escrito también debe reconocerse y no debe presentar ningún tipo de
|
||||
error. Esto se logra permitiendo que la regla principal, que produce una
|
||||
expresión, permita que esta no esté a través del operador el operador
|
||||
$?$.
|
||||
|
||||
|
||||
```antlr
|
||||
public a_program : expr ? EOF;
|
||||
```
|
||||
|
||||
`a = b = c` es ilegal
|
||||
---------------------
|
||||
|
||||
Sea la siguiente regla del parser:
|
||||
|
||||
```antlr
|
||||
relational_expr : arith_expr ((EQ | NOTEQ | GT | LT | GTEQ | LTEQ ) arith_expr)?;
|
||||
```
|
||||
|
||||
El operador `?` al final de la regla le da la no asociatividad requerida
|
||||
a los operadores de comparación (notar que en `arith_expr` y
|
||||
`term_expr` se usa `*` para anidar estas expresiones en una lista). Con
|
||||
esta regla no se permite `a = b = c`, pero si `a = (b = c)` como está
|
||||
especificado.
|
||||
|
||||
Ayudando al chequeo semántico del for
|
||||
-------------------------------------
|
||||
|
||||
Según las especificaciones del lenguaje Tiger, en un for solo se pueden
|
||||
evaluar condiciones que retornen valor, por ello se exige en la regla
|
||||
correspondiente que la primera y segunda expresiones sean
|
||||
`disjunction_expr`.
|
||||
|
||||
```antlr
|
||||
for_expr : FORKEY ID ASSIGN disjunction_expr TOKEY disjunction_expr DOKEY expr;
|
||||
```
|
||||
|
||||
Reescritura de reglas
|
||||
---------------------
|
||||
|
||||
La mayoría de las reglas del parser se reescriben creando nodos
|
||||
“ficticios” en la sección tokens que se usarán para generar nodos
|
||||
homólogos del AST a través del TreeAdaptor. Un ejemplo representativo es
|
||||
la creación de un token para el operador de negación (menos unario).
|
||||
Esto permite además eliminar del DT del programa, tokens innecesarios
|
||||
para la próxima fase, como los paréntesis, las palabras claves, las
|
||||
comas, los punto y coma, etc.
|
||||
|
||||
Análisis semántico
|
||||
==================
|
||||
|
||||
Jerarquía de nodos del AST
|
||||
--------------------------
|
||||
|
||||
Para que el TreeAdaptor funcione correctamente, se crean las clases
|
||||
correspondientes a los nodos que desean comprobarse semánticamente. Cada
|
||||
nodo tiene, además de sus propiedades estructurales, dos métodos
|
||||
|
||||
```csharp
|
||||
public abstract void CheckSemantics(TigerScope scope, Report report);
|
||||
internal abstract void GenerateCode(ModuleBuilder moduleBuilder);
|
||||
```
|
||||
|
||||
El primero (fundamental en esta fase) chequea la semántica del nodo en
|
||||
cuestión, siguiendo un patrón de gramática atributada: un nodo está bien
|
||||
si sus hijos lo están y la información brindada por sus hijos o hermanos
|
||||
en la jerarquía no produce error en su funcionamiento. Estas
|
||||
informaciones se modelan a través de las clases herederas de
|
||||
`TigerInfo`, que dejan información conectada para la generación de
|
||||
código, p.e *bindings* de variables, información relativa a la funciones
|
||||
(nombre, tipo y cantidad de parámetros), etc.
|
||||
|
||||
Notar que el nodo `UnknownNode` se usa para homologar tokens que, si
|
||||
bien no introducen error en el chequeo semántico, ayudan en la
|
||||
comprensión y organización del AST. Ejemplos de token que se mapean como
|
||||
UnknownNode son `FIELDS_INST`, `PARAM_DECL`, `FUN_TYPE_WRAPPER`,
|
||||
`DECL_BLOCK` y `ARGS_FIELDS`.
|
||||
|
||||
Scope
|
||||
-----
|
||||
|
||||
Al usar una variable, un `type` o una función, ¿cómo se sabe si fueron
|
||||
declaradas antes? Esta información se obtiene de la clase `TigerScope`
|
||||
que almacena nombres, con sus respectivos objetos, al que se refieren
|
||||
(variables, *types* o funciones) como una tabla de símbolos. Además,
|
||||
esta clase permite, mediante un árbol de scopes, anidar funciones y
|
||||
expresiones `let`, de forma que solo pueda tenerse acceso a las
|
||||
variables o funciones que están declaradas en ese ámbito o en el ámbito
|
||||
del nodo padre. Esta clase además se encarga del binding de las
|
||||
variables. Esta estructura permite que, en caso de que en un scope
|
||||
interno se cree un `type` o variable de igual nombre que un `type` o
|
||||
variable de un scope ancestro del actual, entonces se reescriba el valor
|
||||
antiguo del `type` o variable por el nuevo y se lanzará una advertencia.
|
||||
|
||||
Nil
|
||||
---
|
||||
|
||||
En el chequeo semántico de la expresión `if cond_expr then then_expr
|
||||
else else_expr` se realizan las siguientes comprobaciones. Se realiza
|
||||
el chequeo semántico de `cond_expr` y luego el de `then_expr`. Si
|
||||
ambas expresiones están correctas se comprueba que la `cond_expr` tenga
|
||||
algún valor (no sea `void`) y luego se comprueba que este valor sea
|
||||
entero. De lo contrario se lanzan errores. En el caso que exista parte
|
||||
`else` el chequeo es más arduo. Primero se chequea que el nodo
|
||||
`else_expr` esté correcto. Luego se verifica que el tipo de
|
||||
`else_expr` sea asignable, o sea, `int`, `string`, `record`, `array`.
|
||||
De lo contrario, se reporta error. En este punto es donde se chequean
|
||||
los casos críticos del `nil` expuestos en las especificaciones de Tiger.
|
||||
|
||||
Secuencia de declaraciones de tipo
|
||||
----------------------------------
|
||||
|
||||
La construcción y verificación de las secuencias de declaraciones de
|
||||
tipos dentro de un `let` se realiza mediante tres pasadas a cada uno de
|
||||
los nodos hijos de cada una de ellas, dígase a los `TypeDeclNode` que
|
||||
están contenidos dentro de cada `TypeDeclSeqNode`, encargado de agrupar
|
||||
las mismas. Esto se consigue mediante la regla del parser
|
||||
|
||||
```antlr
|
||||
decl : type_decl+ | var_decl | fun_decl+;
|
||||
```
|
||||
|
||||
Esta regla está contenida dentro de un iterador + **EBNF** (*Extended
|
||||
Backus-Naur Form*), leyéndose finalmente
|
||||
|
||||
```antlr
|
||||
let_expr : LETKEY decl+ INKEY expr_seq ENDKEY;
|
||||
```
|
||||
|
||||
Aunque esta última pareja constituya una ambigüedad, se usa a propósito
|
||||
para realizar dicha agrupación, de forma análoga a la selección de a qué
|
||||
`if` pertenece un determinado `else`, esto es, al de mayor anidamiento.
|
||||
Esto facilita sobremanera cualquier operación posterior que se realice
|
||||
sobre los conjuntos de declaraciones, ya sean de tipos o de funciones.
|
||||
Las variables se tratan como conjuntos *singleton*.
|
||||
|
||||
Dada una declaración de tipo
|
||||
|
||||
```antlr
|
||||
type_decl : TYPEKEY ID EQ type;
|
||||
```
|
||||
|
||||
se le dice parte izquierda o cabeza al token ID, que corresponde al
|
||||
nombre del tipo que se quiere declarar. La parte derecha corresponde a
|
||||
la regla `type`, que puede ser `type_id`, `array_decl` o
|
||||
`record_decl`.
|
||||
|
||||
Los pasos son los siguientes:
|
||||
|
||||
1. Se agregan las cabezas de los tipos, es decir, la parte izquierda de
|
||||
la declaración de cada uno de ellos, con el sólo propósito de comprobar
|
||||
que no exista una redeclaración dentro de una secuencia, o de forma
|
||||
local y global, agregándolo a una estructura que contiene cada
|
||||
`TypeDeclNode` como llave del nombre del tipo. Este paso incluye
|
||||
detectar renombramientos de los nombres de los tipos primitivos como
|
||||
tipos de usuario, esto es, no cambiar a `int` o `string`, por ejemplo.
|
||||
En este paso se llama al `CheckHeader(TigerScope scope, Report report,
|
||||
string name)` de cada hijo.
|
||||
|
||||
2. Para detectar eventuales ciclos es necesario construir un grafo
|
||||
dirigido $G \langle V,E \rangle$ con
|
||||
|
||||
$$V = \{ \text{nodos de declaración de tipos} \}$$
|
||||
|
||||
$$E = \{ (u,v) | ParteIzquierda(u) \and BaseType(u, v) \in { Alias V Array } \and ParteDerecha(v), u, v \in V \}$$
|
||||
|
||||
Dicha construcción asegura que las aristas se establezcan sólo entre
|
||||
nodos de declaraciones de tipos que estén conectados como parte
|
||||
izquierda y derecha del *statement*, que sea de base `Array` o `Alias`, es
|
||||
decir, un tipo cuyo tipo, valga la redundancia, sea alguno de los
|
||||
anteriores. Estos se agregan a una estructura
|
||||
`Dictionary<TypeDeclNode, Node<TypeDeclNode>>`
|
||||
donde `Node<T>` es una representación de nodos en un
|
||||
grafo, con soporte para realizar operaciones de orden topológico además
|
||||
de detectar ciclos y su composición. Esta estructura permite conectar
|
||||
los `TypeDeclNode` en el grafo con aquellos otros de la secuencia que se
|
||||
requiera, y evitar repetir nodos en el grafo que correspondan a una
|
||||
misma declaración. Esto podría suceder dado que un mismo tipo puede
|
||||
estar varias veces en parte derecha, y a lo sumo una vez en parte
|
||||
izquierda si se ha llegado a este paso (el anterior elimina que esté más
|
||||
de una vez). De esta forma no se tenderá una arista entre un tipo
|
||||
`aliasInt` que sea alias de `int`, dado que la parte derecha no estará
|
||||
en primer lugar en la secuencia, y por tanto no puede referenciar hacia
|
||||
adelante, de forma que no hay peligro de recursión cíclica.
|
||||
|
||||
3. Al grafo construido se le realiza un DFS (*Depth-First Search*) con
|
||||
marcado de nodos, en los que se detecta una arista back-edge, o de
|
||||
retroceso, si se en la navegación se llega a un nodo marcado como gris
|
||||
\[ver *Intro To Algorithms*, Cormen et al., alrededor de la 614\]. La prueba
|
||||
del Teorema 22.12 acompaña el resultado. Si existe un ciclo se detecta
|
||||
sus nodos componentes y se imprime. De no existir, se recorre el orden
|
||||
topológico de los nodos resultantes, cuyo valor contenido son las
|
||||
declaraciones de nodos, y se llama a `CheckSemantics` de cada uno en
|
||||
dicho orden. La generación de código también se realiza por dicho orden.
|
||||
Esto evita tener que realizar múltiples búsquedas si para construir
|
||||
finalmente un tipo, aunque se conozca que está correcto.
|
||||
|
||||
####
|
||||
|
||||
El último paso, detectar un ciclo, y después navegar por el orden
|
||||
topológico dado, devuelto en forma de lista enlazada, es vital para la
|
||||
realización de las operaciones de construcción de tipos, que se realizan
|
||||
de forma transparente para los hijos, los cuales no requieren búsquedas
|
||||
adicionales para la determinación completa de los mismos, como se había
|
||||
dicho. Para ellos se habilita que los nodos que así lo requieran no
|
||||
contengan una referencia directa al tipo, dado que en el momento de
|
||||
declaración y comprobación semántica, aunque este sea correcto, puede no
|
||||
estar determinado, sencillamente porque es el tipo de un record. Uno de
|
||||
estos casos se analiza a continuación.
|
||||
|
||||
####
|
||||
|
||||
Supongamos que deseamos comprobar y construir los tipos del siguiente
|
||||
programa:
|
||||
|
||||
```antlr
|
||||
let
|
||||
type a = b
|
||||
type b = { x: c }
|
||||
type c = a
|
||||
in
|
||||
end
|
||||
```
|
||||
|
||||
El programa está correcto, dado que los tipos `a`, `b` y `c` están bien
|
||||
determinados por un ciclo que pasa por al menos un tipo record (`b`). El
|
||||
tipo básico de `a` es el mismo que el de `c`, que es alias. El orden
|
||||
topológico podrá arrojar una sola forma de navegarlo para la
|
||||
construcción. Sea esta $b \rightarrow a \rightarrow c$. Al comprobar
|
||||
semánticamente a `b` el tipo no está bien determinado, dado que tiene un
|
||||
campo `x` de un tipo desconocido `c`, pero se conoce que la declaración
|
||||
esta correcta, dado que `c` existe y está correcto, por lo que `b` está
|
||||
bien determinado. Su definición, no obstante, no es completa. Por lo
|
||||
tanto se almacena una referencia a la referencia a la definición del
|
||||
mismo, de forma que esta pueda se alterada, es decir se puede completar,
|
||||
y todos los nodos que dependían de esta queden bien.
|
||||
|
||||
####
|
||||
|
||||
Finalmente los tipos alias no se analiza semánticamente, es decir, son
|
||||
indistinguibles las definiciones originales de `a`, `b` y `c`, a efectos
|
||||
del resto del programa.
|
||||
|
||||
Generación de código
|
||||
====================
|
||||
|
||||
La generación de código es el proceso mediante el cual un compilador
|
||||
convierte la información proveniente de las fases anteriores en código
|
||||
que puede ser ejecutado por una computadora. La ejecución del código
|
||||
generado la realiza, en este compilador, el *Common Language Runtime*
|
||||
(CLR) de la plataforma .NET. El CLR usa una estructura basada en un
|
||||
modelo de pila para modelar la ejecución de código, así como un lenguaje
|
||||
ensamblador propio llamado *Intermediate Language* (IL). Nuestro
|
||||
compilador utiliza una componente de la biblioteca de clase de la
|
||||
plataforma .NET llamada `System.Reflection.Emit` que permite crear un
|
||||
ejecutable en .NET y agregarle código según se desee; y para generar
|
||||
código IL se usa *Dynamic Language Runtime* (DLR).
|
||||
|
||||
¿Por qué usar DLR?
|
||||
------------------
|
||||
|
||||
El DLR proporciona servicios para implementar lenguajes dinámicos sobre
|
||||
el CLR. DLR facilita el desarrollo de lenguajes para ejecutar programas
|
||||
sobre .NET Framework. Los lenguajes por el tipo del objeto que los
|
||||
representa en tiempo de ejecución, que se especifica en tiempo de diseño
|
||||
en un lenguaje estáticamnete tipado como C\#.
|
||||
|
||||
DLR permite a los programadores implementar lexers, parsers,
|
||||
analizadores semánticos y generadores de código y la generación de
|
||||
código se realiza de forma declarativa mediante árboles de expresiones
|
||||
lo que hace más fácil esta etapa.
|
||||
|
||||
Funciones recursivas
|
||||
--------------------
|
||||
|
||||
En la generación de código de las secuencias de funciones se realizan
|
||||
tres etapas.
|
||||
|
||||
1. Se recorren todos las definiciones (`headers`) de las funciones en
|
||||
la secuencia y se introducen en una lista de `ParameterExpression`
|
||||
que después se le pasará a la segunda fase.
|
||||
|
||||
2. Por cada llamado a la propia función (recursión) o a otra, se crea
|
||||
una `Expression` ficticia en el que luego se sustituirá el código
|
||||
del llamado a la función.
|
||||
|
||||
3. Se recorren las declaraciones de funciones en la secuencia y se le
|
||||
asigna a la `Expression` ficticia el código del llamado a la función
|
||||
correspondiente.
|
||||
|
||||
Para más información
|
||||
====================
|
||||
|
||||
- Edwards, Stephen - *Tiger Language Reference Manual*, Columbia
|
||||
University.
|
||||
|
||||
- Appel, Andrew and Ginsburg, Maia - *Modern Compiler Implementation
|
||||
in C*, Press Syndicate of the University of Cambridge, 1998.
|
||||
|
||||
- Parr, Terrence - *The Definitive ANTLR Reference*, The Pragmatic
|
||||
Programmers, May 2007.
|
||||
|
||||
- Parr, Terrence - *The Definitive ANTLR 4 Reference*, The Pragmatic
|
||||
Programmers, Jan 2013.
|
||||
|
||||
- Parr, Terrence - *Language Implementation Patterns*, The Pragmatic
|
||||
Programmers, 2010.
|
||||
|
||||
- Wu, Chaur - *Pro DLR in .NET 4*, Apress, 2010.
|
||||
|
||||
- Chiles, Bill - *Expression Trees v2 Spec* (DRAFT).
|
||||
|
||||
- Cormen, Thomas *et al.* - *Introduction to algorithms*, 3rd ed,
|
||||
Massachusetts Institute of Technology, 2009.
|
||||
|
||||
- [https://docs.microsoft.com/](https://docs.microsoft.com/ "MSDN")
|
||||
|
||||
Reference in New Issue
Block a user