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.

Consulte las normas de entrega de tareas en http://pleiad.cl/teaching/cc4101

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 usar set 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 usar self 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