This is an old revision of the document!
Tarea 3 (Entrega: X de julio de 2024)
Esta tarea se distribuye con un archivo zip ( base) que contiene 4 archivos: main_p1.rkt y tests_p1.rkt. Los archivos están incompletos, y en ellos tienen que implementar lo que se solicita en las preguntas siguientes.
Deben entregar via U-cursos un archivo .zip que contenga los archivos main_p1.rkt y tests_p1.rkt.
Recuerde que el testing y calidad de código se evalúan de acuerdo a la rúbrica.
Resumen
El objetivo de la tarea es extender un lenguaje base para soportar clases y objetos. El lenguaje base tiene números, booleanos y operaciones sobre ellos. Además contiene expresiones begin
y with
para secuenciar expresiones y definir múltiples identificadores respectivamente; pero no tiene soporte para funciones. Tome un tiempo de experimentar con el lenguaje entregado antes de comenzar a implementar las extensiones requeridas.
La tarea está dividida en 2 secciones, las cuales se describen a continuación:
- Clases y objetos: En esta sección se pide extender el lenguaje base con clases y objetos. En particular las clases deben ser entidades de primera clase, es decir, son valores del lenguaje.
- Codificando Lambdas con Objetos: El objetivo de esta sección es extender el lenguaje para soportar lambdas y aplicaciones como azúcar sintáctica, usando objetos.
- La tarea se debe realizar SIN usar macros.
- El objetivo de la tarea es que implemente un intérprete sintáctico para el lenguaje que se presenta. Esto quiere decir en particular que las clases y objetos no pueden ser codificados con lambdas de Racket (*)
En resumen, deben usar los conceptos de OOP vistos en clase y que también se presentan en el apunte OOPLAI.
(*) nada de (lambda (msg . args) …)
en su código!
Clases y objetos (5 ptos.)
A continuación se presenta la sintaxis concreta del lenguaje extendido (se omiten las expresiones del lenguaje base):
<expr> ::= ... (expresiones del lenguaje base) ... | {class {<sym>*} <method>*} | {new <expr> <expr>*} | {get <expr> <sym>} | {set <sym> <expr>} | {-> <expr> <sym> <expr>*} | self <method> ::= {def <sym> {<sym>*} <expr>}
Donde:
class
permite crear una nueva clase anónima, con una lista de 0 o más identificadores para los campos, seguida de 0 o más métodos.new
permite crear una instancia de una clase dada (primera expresión), entregando 0 o más argumentos al constructor (más abajo se detallan ciertas consideraciones a tener en cuenta).get
permite acceder al campo de un objeto dado.set
permite modificar el campo de un objeto. Solamente es válido usarset
dentro del cuerpo de un método y para modificar campos propios del objeto desde donde se llama. No es posible modificar campos de un objeto externo.→
permite invocar un método de un objeto dado, con 0 o más argumentos.self
permite acceder al objeto actual. Solamente es válido usarself
dentro del cuerpo de un método.def
permite definir un método, donde se especifica el nombre del método, 0 o más parámetros, y el cuerpo del método.
Además, para la creación de un objeto, los constructores son simplemente métodos llamados init
. Es posible definir varios métodos init
, sin embargo, definir varios constructores con la misma aridad es un error (en tiempo de creación de la clase). Cuando se ejecuta un new
, se selecciona el constructor que corresponda al número de argumentos pasados (error si no hay ninguno que calza). Si no hay ningún constructor declarado, solo se puede usar {new c}
sin argumentos (constructor por defecto).
Extienda el lenguaje para soportar estas expresiones, donde tanto clases y objetos son valores. Usted debe decidir los atributos que tendrán sus respectivos nodos en el AST. Si se encuentra con casos no especificados anteriormente debe tomar supuestos e indicarlo en su entrega. Modifique el parser y el intérprete para soportar el lenguaje extendido.
Los errores deben manejarse de la siguiente forma:
- La invocación de
set
fuera de un método debe lanzar el error“set outside of method”
. - La invocación de
self
fuera de un método debe lanzar el error“self outside of method”
. - El acceso a un campo inexistente de un objeto debe arrojar el error
“field <id> not found”
. - El acceso a un campo no inicializado debe arrojar el error
“field <id> not initialized”
. - La invocación de un método inexistente debe lanzar el error
“method <id> not found”
. - La creación de un objeto con un número inválido de argumentos debe lanzar el error
“constructor not found”
. - Crear una clase con campos repetidos debe lanzar el error
“duplicate fields”
- Al crear una clase con 2 o más constructores de igual aridad, debe lanzar el error
“same arity constructor”
- Al sobrecargar un método con la misma aridad, debe lanzar el error
“overloading method <id> with the same arity”
Veamos algunos ejemplos de clases y objetos en acción:
;; comportamiento esperado > (run-val '{with {{c {class {x y} {def init {} {begin {set x 1} {set y 2}}} {def init {init-x init-y} {begin {set x init-x} {set y init-y}}} {def sum {z} {+ {get self x} {+ {get self y} z}}} {def set-x {val} {set x val}}}} {o {new c 3 4}}} {begin {-> o set-x {+ 1 3}} {+ {-> o sum 3} {get o y}}}}) 15
;; las clases son valores > (run-val '{with {{A {class {} {def apply {c} {-> {new c} m}}}} {o {new A}}} {-> o apply {class {x} {def init {} {set x 2}} {def m {} {get self x}}}}}) 2
;;la definición de la clase tiene scope léxico > (run-val '{begin {with {{A {class {x} {def init {} {set x 2}} {def init {init-x} {set x init-x}} {def m {} {get self x}}}}} 10} {new A}}) "free identifier: A"
;; los identificadores dentro de una clase tienen scope léxico ;; (note el uso de la “x” en la definición del método “m” > (run-val '{with {{x 10} {A {class {} {def m {y} {+ x y}}}} {o {new A}}} {-> o m 1}}) 11
;; No se puede usar set fuera de un método > (run-val '{set x 1}) "error: set outside of method"
;; No se puede usar self fuera de un método > (run-val 'self) "error: self outside of method"
;; Acceder a un campo no definido de una clase > (run-val '{with {{A {class {}}} {o {new A}}} {get o z}}) "error: field z not found"
;; Acceder a un campo no inicializado de una clase > (run-val '{with {{A {class {x y} {def init {init-x init-y} {set x init-x}}}} {o {new A 1 2}}} {get o y}}) "error: field y not initialized"
;; Invocar un método no definido de una clase > (run-val '{with {{A {class {}}} {o {new A}}} {-> o m}}) "error: method m not found"
;; Una clase sin constructores puede ser creado solo con {new class {}}, sin argumentos > (run-val '{with {{x 10} {A {class {x}}} {o {new A x}}} 1}) "error: constructor not found"
;; Crear una clase con campos duplicados es un error en tiempo de creación de la clase > (run-val '{begin {with {{A {class {x y x}}}} 10} {new A}}) "error: duplicate fields"
;; Tener 2 init con la misma aridad es un error en tiempo de creación de la clase > (run-val '{begin {with {{A {class {x} {def init {init-x} {set x init-x}} {def init {init-x} {set x 12}}}}} 10} {new A}}) "error: same arity constructor"
;; Tener 2 métodos con la misma aridad es un error en tiempo de creación de la clase > (run-val '{begin {with {{A {class {x} {def foo {a} {set x a}} {def foo {b} {set x {+ b 1}}}}}} 10} {new A}}) "error: overloading method foo with the same arity"
Observaciones importantes:
- No utilice macros para la definición de objetos
Codificando Lambdas (1 ptos.)
Ahora incorporaremosmos lambdas (funciones anónimas de primera clase) a nuestro lenguaje. A diferencia de lo visto durante el curso, en esta ocasión no daremos una interpretación directa de las funciones. Usted debe idear una manera de usar la implementación de clases y objetos hecha en la parte anterior para codificar las lambdas. Esto significa que no puede modificar el AST y el intérprete para soportar funciones y aplicaciones de funciones. Las modificaciones que debe hacer son en el parser. En otras palabras, las funciones y aplicaciones serán sólo azúcar sintáctica.
<expr> ::= ... | (fun (<id>*) <expr>) | (<expr> <expr>*)
Ejemplos de uso:
> (run-val '{{fun {x} {+ x 1}} 2}) 3
> (run-val '{with {{add1 {fun {x} {+ x 1}}}} {+ {add1 2} {add1 4}}}) 8