====== Tarea 1 (Entrega: 06 de mayo de 2020) ======
Esta tarea se distribuye con un archivo zip {{teaching:cc4101:tareas:2020-1:base.zip |base}} que contiene 3 archivos: main.rkt, tests.rkt, y machine.rkt. Los archivos main.rkt y tests.rkt están incompletos, y en ellos tienen que implementar lo que se solicita en las preguntas siguientes. **No deben modificar el archivo machine.rkt**: es una implementación completa de la máquina a la cual van a compilar su lenguaje. Tampoco deben agregar tests para la funciones del archivo machine.rkt, sino solo para aquellas funciones que ustedes definen.
Deben entregar via U-cursos **un archivo .zip** que contenga los archivos main.rkt y tests.rkt.
Consulte las normas de entrega de tareas en http://pleiad.cl/teaching/cc4101
**¡Los tests, los contratos y las descripciones forman parte de su evaluación!**
===== 1. Funciones de primera clase con tipos declarados [1.5pt] =====
En esta tarea agregaremos anotaciones de tipos a un lenguaje con funciones de primera clase. La sintaxis concreta del lenguaje es la mostrada a continuación:
::=
|
| {+ }
| {- }
| {with { : } }
| {fun { : } [: ] }
| { }
::= Num
| { -> }
Note que ''%%with%%'' no incluye anotación del tipo del cuerpo, y el tipo de retorno de las funciones es opcional (los '[' ']' son parte de la notación BNF, especificando que lo que va adentro es opcional).
Por ejemplo, los programas siguientes son válidos:
{with {x : Num 5} {+ x 3}}
{fun {x : Num} : Num
{+ x 3}}
{fun {x : Num}
{+ x 3}}
* (0.3) Defina la función ''%%(parse-type type)%%'' que parsea solo la gramática de tipos. Ademas en caso de error, debe retornar el error "Parse error"
> (parse-type '{{Num -> Num} -> Num})
(TFun (TFun (TNum) (TNum)) (TNum))
> (parse-type '{Num -> })
"Parse error"
* (0.9) Defina la función ''%%(parse s-expr)%%'' para que acepte la gramática completa del lenguaje. Utilice la función ''%%(Parse-type type)%%'' en los puntos donde deba identificar tipos. Además, la función ''%%parse%%'' debe reemplazar el ''%%with%%'' por una aplicación de función. Ejemplos:
> (parse '{fun {x : Num} : Num {+ x 1}})
(fun 'x (TNum) (add (id 'x) (num 1)) (TNum))
> (parse '{with {y : Num 2}
{+ x y}})
(app (fun 'y (TNum) (add (id 'x) (id 'y)) #f) (num 2))
**Note que se usa ''%%#f%%'' cuando una función no tiene anotado un tipo de retorno.**
* (0.3) Defina la función ''%%(prettify type)%%'' que tome un tipo y lo escriba en la sintaxis concreta.
> (prettify (TNum))
'Num
> (prettify (TFun (TNum) (TFun (TNum) (TNum)))
'(Num -> (Num -> Num))
===== 2. Verificación de Tipos [1.5pt] =====
Se implementará el chequeo de tipos de una expresión en presencia de funciones de primera clase.
* (1.0) Implemente la función ''%%(typeof expr)%%'' que calcula el tipo de la expresión dada o falla con un mensaje de error en caso de que la expresión no sea válida. Para la validación, considere lo siguiente:
* Para una función con el tipo de retorno anotado, se valida que la expresión del cuerpo tenga el mismo tipo que el tipo de retorno declarado.
* Para una función sin el tipo de retorno anotado, el tipo resultante considera el tipo calculado del cuerpo de la función.
* Para la aplicación de función se valida que la expresión en posición de función sea efectivamente una función.
* Para la aplicación de función también se valida que el tipo de la expresión de argumento de la aplicación coincide con el tipo esperado del argumento de la función.
* Para la aplicación de un operador numerico se valida que sus operandos tengan el tipo correcto ''%%'Num%%''.
Los mensaje de error de tipo tienen que seguir el siguiente patrón:
"Type error in expression position : expected found "
donde E es la expresión donde ocurrió el error, n es la sub-expresión donde se manifestó, T1 es el tipo esperado y T2 el tipo encontrado. Utilice su función prettify para mostrar errores legibles.
**Note que la única forma de tener error de tipo de función, es cuando el tipo de retorno está anotado.**
Para el caso cuando hay algún identificador libre, el patrón es:
"Type error: free identifier: "
donde es el identificador libre.
Algunos ejemplos:
> (typeof (parse '{fun {x : Num} : Num 5}))
(TFun (TNum) (TNum))
> (typeof (parse '{fun {x : Num} x}))
(TFun (TNum) (TNum))
> (typeof (parse '{{fun {x : Num} x} 1}))
(TNum)
> (typeof (parse '{fun {x : Num} : {Num -> Num} 10}))
"Type error in expression fun position 1: expected (Num -> Num) found Num"
> (typeof (parse '{1 2}))
"Type error in expression app position 1: expected (?T -> ?S) found Num"
> (typeof (parse '{{fun {x : Num} : Num {+ x x}} {fun {x : Num} : Num 5}}))
"Type error in expression app position 2: expected Num found (Num -> Num)"
>(typeof (parse '{fun{x : Num}{+ x {fun{z : Num} 1}}}))
"Type error in expression + position 2: expected Num found (Num -> Num)"
> (typeof (parse 'y))
"Type error: free identifier: y"
**Note que debe manejar identificadores adecuadamente, considere la implementación de un ambiente que almacene tipos.**
* (0.5) Implemente la función ''%%(typecheck s-expr)%%'' que retorna el tipo de un programa, en un formato legible:
>(typecheck '3)
'Num
>(typecheck '{fun {f : Num} : Num 10})
'(Num -> Num)
>(typecheck '{+ 2 {fun {x : Num} : Num x}})
"Type error in expression + position 2: expected Num found (Num -> Num)"
===== 3. Compilación [3.0pt]=====
Ahora vamos a compilar nuestro lenguaje a una máquina abstracta basada en stack. La máquina es similar a la conocida SECD(([[https://en.wikipedia.org/wiki/SECD_machine|https://en.wikipedia.org/wiki/SECD_machine]])), la cual no trabaja con identificadores, sino que solo con índices. La máquina SECD consume una lista de instrucciones, utiliza un stack para almacenar datos temporales y posee un ambiente que, a diferencia del ambiente visto normalmente en el curso, es un arreglo con valores sin identificadores. La máquina SECD ya está definida en el archivo machine.rkt.
La función ''%%(exec-machine ins-list)%%'' le permitirá ejecutar una lista de instrucciones en la máquina.
> (exec-machine (list (INT-CONST 6) (INT-CONST 1) (ADD)))
7
> (exec-machine (list (INT-CONST 3) (CLOSURE (list (ACCESS 0) (RETURN))) (APPLY)))
3
**Tome un tiempo para experimentar con la función ''%%exec-machine%%'' y el set de instrucciones antes de realizar los próximos ejercicios.**
==== Índices De Bruijn ====
Una forma de eliminar nombres de variables o identificadores de una expresión, es usando índices De Bruijn. Anteriormente vimos que la máquina SECD trabaja solo con índices, por lo que en esta sección vamos a hacer el paso de identificadores a dichos números, usando indices De Bruijn((La explicación detallada de como funcionan se puede encontrar en la sección 3.5 del [[http://cs.brown.edu/~sk/Publications/Books/ProgLangs/2007-04-26/plai-2007-04-26.pdf|PLAI]])).
* (1.5) Implemente la función ''%%deBruijn%%'' que toma una expresión y la recorre reemplazando los identificadores no libres, con los índices De Bruijn (nodos ''%%(acc n)%%'') manteniendo el scope léxico. La salida de esta función no debería tener nodos del tipo ''%%fun%%'', ni ''%% id%%''. En su lugar debería contener nodos del tipo ''%%fun-db%%'' y ''%%acc%%''. El resto de los nodos no varía.
**Hint: le podría ser útil recordar que el intérprete visto en clases usa un ambiente para asociar identificadores a valores.**
> (deBruijn
(parse '{+ 1 {with {x : Num 1}
{with {y : Num 2}
{+ x y}}}}))
(add
(num 1)
(app
(fun-db
(app (fun-db (add (acc 1) (acc 0))) (num 2)))
(num 1)))
> (deBruijn (add (num 1) (id 'x)))
"Free identifier: x"
==== De AST a código de máquina ====
Vamos a abordar la compilación del AST de una expresión al set de instrucciones de la SECD.
A continuación se presenta el esquema de compilación que permite hacer el paso del AST de una expresión a una lista de instrucciones (revise el archivo machine.rkt, todas las definiciones están comentadas y puede encontrar la sintaxis completa de las instrucciones). Note que solo es el paso a notación polaca inversa:
C((num n)) = (INT-CONST n)
C((add a1 a2)) = C(a2);C(a1);(ADD)
C((sub a1 a2)) = C(a2);C(a1);(SUB)
C((app f a)) = C(a); C(f);(APPLY)
La compilación de un nodo ''%%(acc n)%%'' es la siguiente:
C((acc n)) = (ACCESS n)
La compilación de una función puede ser inferida del ejemplo a continuación:
> (compile (deBruijn (parse '{{fun {x : Num} : Num
{+ x 10}} {+ 2 3}})))
(list
(INT-CONST 3)
(INT-CONST 2)
(ADD)
(CLOSURE (list (INT-CONST 10) (ACCESS 0) (ADD) (RETURN)))
(APPLY))
* (1.0) Implemente la función ''%%(compile expr)%%'' que dada una expresión con índices De Bruijn genera la lista de instrucciones siguiendo el esquema de compilación descrito anteriormente.
* (0.5) Implemente la función ''%%(typed-compile s-expr)%%'' que se encarga de todo el proceso de generación de código desde un programa, considerando el parsing, la validación de tipos, y la transformación con índices De Bruijn. Note que esta función solo genera código de maquina si la expresión esta bien tipada, sino produce el error correspondiente.