Esta tarea se distribuye con un archivo zip ( base) que contiene 3 archivos: main.rkt, tests.rkt y env.rkt. Los archivos están incompletos, y en ellos tiene que implementar lo que se solicita en las preguntas siguientes.
Debe entregar via U-cursos un archivo .zip que contenga los archivos main.rkt y tests.rkt.
Recuerde que el testing y calidad de código se evalúan de acuerdo a la rúbrica.
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 funciones anónimas de primera clase con Objetos: El objetivo de esta sección es extender el lenguaje para soportar funciones anónimas de primera clase (típicamente conocidas como “lambdas”) y aplicaciones como azúcar sintáctica, usando objetos.
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!
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.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.
A lo largo de la implementación, verificaremos que los programas del lenguaje cumplan múltiples requisitos. Por ejemplo, revisaremos que no hayan campos duplicados dentro de una clase, o que los usos de self
estén sólo dentro de un método. Todas las verificaciones que puedan hacerse de manera estática estarán centralizadas en una función llamada well-formed
. En el archivo base main.rkt
se entrega una implementación inicial que deberá extender siguiendo las indicaciones del enunciado.
Veamos algunos programas de ejemplo para ilustrar de manera general las características esperadas del lenguaje.
;; 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
Extensiones del AST y Parser
Method
para representar una definición de método en el AST.parse-method
que recibe una definición de método en sintaxis concreta y retorna el nodo de AST correspondiente.Expr
y la función parse
para soportar la expresión class
del lenguaje.
Extensiones de well-formed
Durante la creación de clases, deben cumplirse los siguientes requisitos:
self
sólo puede ser usado dentro del cuerpo de un método. Si esto no se cumple, debe lanzar el error “self outside of method”A continuación se muestran algunos ejemplos de los errores que pueden lanzarse al hacer las verificaciones:
;; 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"
;; No se puede usar self fuera de un método > (run-val 'self) "error: self outside of method"
well-formed
para realizar las verificaciones mencionadas.
Observación: Puede añadir un parámetro extra a well-formed
para indicar si esta dentro de una clase
Intérprete
Val
con un constructor llamado classV
que permita almacenar la información necesaria para representar a una clase como valor.Observación: Recuerde que los métodos definidos en una clase deben utilizar el ambiente al momento de la creación de la clase y no el ambiente de cuando son invocados.
Extensiones del AST y Parser
Expr
y la función parse
para soportar la expresión new
del lenguaje.
Extensiones de well-formed
well-formed
para verificar el nodo new
. En este caso, sólo es necesario llamar well-formed
para cada subnodo del nodo new
.Intérprete
Val
con un constructor llamado objV
que permita almacenar la información necesaria para representar a un objeto como valor. Hint: Recuerde que uno de los objetivos de tener clases, es permitir que sus intancias puedan compartir métodos.invoke-method
que permita buscar un método dentro de la clase un objeto e invocarlo utilizando los argumentos entregados.new
.
Observación: Cuando se evalúa una expresión new
, se debe buscar en la clase un constructor que corresponda al número de argumentos entregados. Si no hay ninguno que tenga la aridad requerida, se debe lanzar el error “constructor not found”
. Si se está instanciando una clase que no declara ningún constructor, solo se puede usar el constructor por defecto que no recibe argumentos, es decir, {new c}
.
A continuación se muestran algunos ejemplos de instanciación de clases:
;; 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"
;; Esta clase no tienen ningún constructor con aridad 2 > (run-val '{begin {with {{C {class {x} {def init {init-x} {set x init-x}}}}} 10} {new C 1 2}}) "error: constructor not found"
Extensiones del AST y Parser
Expr
y la función parse
para soportar la expresión →
del lenguaje.
Extensiones de well-formed
well-formed
para verificar el nodo →
. En este caso, solo es necesario llamar well-formed
para cada subnodo del nodo new
.Intérprete
Observaciones:
invoke-method
.“method <id> not found”
.A continuación se muestran programas en los que se intenta invocar un método inexistente o donde no se encuentra una sobrecarga con la aridad correcta:
;; Invocar un método no definido de una clase > (run-val '{with {{A {class {}}} {o {new A}}} {-> o m}}) "error: method m not found"
;; Esta clase no tiene el método set-x definido para la aridad 2 > (run-val '{with {{A {class {x} {def set-x {val-x} {set x val-x}}}} {o {new A}}} {-> o set-x 10 20}}) "error: method set-x not found"
Extensiones del AST y Parser
Expr
y la función parse
para soportar la expresión get
del lenguaje.Expr
y la función parse
para soportar la expresión set
del lenguaje.
Extensiones de well-formed
Durante el instanciado de clases, deben cumplirse los siguientes requisitos:
set
fuera de un método debe lanzar el error “set outside of method”
.;; No se puede usar set fuera de un método > (run-val '{set x 1}) "error: set outside of method"
well-formed
para verificar el nodo get
.well-formed
para verificar el nodo set
.Intérprete
Los errores dentro de interp
para objetos deben manejarse de la siguiente forma:
“field <id> not found”
.“field <id> not initialized”
.;; 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"
Ahora incorporaremos funciones anónimas de primera clase (típicamente conocidas como “lambdas”) 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.
Hint: Piense en lo que comúnmente hemos visto como sintaxis de “aplicación de función” como azúcar sintáctico para la invocación de un método en un objeto, este método puede contener el código de la “función” definida. ¿De qué clase sería ese objeto? ¿Cómo se podría llamar ese método?
<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