Intérprete para un lenguaje OO sencillo Eduardo Bonelli Departamento de Computación, FCEyN, UBA 27 de mayo de 2008 La clase pasada I Introdujimos los conceptos fundamentales del paradigma I I I I I Objetos, clases, modelo de cómputo, herencia Polimorfismo de subclase y dynamic method dispatch Super y static method dispatch Sobrecarga y redefinición de métodos Los ilustramos con ejemplos en el lenguaje SOOL I I Es una extensión de SFLA con orientación a objetos Lenguaje con pocas construcciones pero representativo del paradigma I Estamos completando la visión del paradigma usando Smalltalk (Práctica) I Vamos a implementar un intérprete para SOOL Intérprete para SOOL I Seguimos el capı́tulo 5 del texto “Essentials of Programming Languages” (ver bibliografı́a de la materia en la web para más detalles sobre este texto) I Con la diferencia de que usamos Haskell en lugar de Scheme I La implementación es una extensión del intérprete ya visto para el lenguaje funcional con asignación SFL I Priorizaremos claridad por sobre eficiencia Sintaxis concreta de SOOL (extensión de SFLA) <program> ::= <class-decl>∗ <expr> <class-decl> ::= class <ident> extends <ident> field <ident>∗ <method-decl>∗ <method-decl> ::= method <ident> (<ident>∗(,) ) <expr> <expr> ::= ... | new <ident> (<expr>∗(,) ) | send <expr> <ident> (<expr>∗(,) ) | super <ident> (<expr>∗(,) ) Ejemplo class point extends object field x field y method initialize (initx,inity) begin set x = initx; set y = inity end method move (dx,dy) begin set x = +(x,dx); set y = +(y,dy) end method get_location () list(x,y) let p = new point(3,4) in p Sintaxis abstracta I Un programa consiste en una lista de declaraciones de clases y una expresión inicial data Program = Pgm [ClassDecl] Exp I La expresión inicial es una expresión de SFLA extendida con tres nuevas construcciones: NewExp, SendExp y SuperExp data Exp = | | | ... NewExp Symbol [Exp] SendExp Exp Symbol [Exp] SuperExp Symbol [Exp] type Symbol = String Sintaxis abstracta - declaraciones de clase, campo y método data ClassDecl = Class {classDecl2name::Symbol, classDecl2super::Symbol, classDecl2fields::[FieldDecl], classDecl2methods::[MethodDecl]} data FieldDecl = Field {fieldDecl2name::Symbol} data MethodDecl = Method {methodDecl2name::Symbol, methodDecl2params::[Symbol], methodDecl2body::Exp} Valores data Value = | | | Nro Int Closure [Symbol] Exp Env Object [Part] Lista [Value] type Reference = Int -- referencias como enteros Intérprete de programas I Toma un programa como entrada y devuelve el resultado junto con el store (i.e. la memoria) data Answer = AnAnswer {value::Value, store::Store} evalProgram :: Program -> Answer evalProgram (Pgm classDecls body) = evalExpression body emptyEnv emptyStore classDecls I La lista de declaraciones de clases se utilizará en su representación original (i.e. como tipo algebraico) Intérprete de expresiones I El intérprete de expresiones se extiende con tres nuevos casos, uno por cada uno de los nuevos constructores I I I NewExp: creación de un nuevo objeto SendExp: envı́o de mensaje SuperExp: llamado super I Asimismo, además de la expresión a interpretar, el entorno y el store, ahora también toma la lista de declaraciones de clase como entrada (pues precisará consultarla) I Esto se podrı́a evitar en Scheme almacenando las declaraciones de clases en una variable global, en Haskell se podrı́an usar mónadas Intérprete de expresiones evalExpression :: Exp -> Env -> Store -> [ClassDecl] -> Answer evalExpression (LitExp n) env st cds = AnAnswer (Nro n) st evalExpression (VarExp id) env st cds = AnAnswer (applyEnv env st id) st ... evalExpression (NewExp className exps) env st cds = ... evalExpression (SendExp recip methodName rands) env st cds = ... evalExpression (SuperExp methodName rands) env st cds = ... I El objetivo es completar esta definición Lookup de declaraciones de clase y método I En ocasiones tendremos que ir a buscar la declaración de una clase o método conociendo el nombre de la misma (o del mismo) I Para ello usaremos la siguiente función lookupDecl :: [a] -> (a->Symbol) -> Symbol -> Maybe a lookupDecl [] f nombre = Nothing lookupDecl (decl:decls) f nombre | (f decl) == nombre = Just decl | otherwise = lookupDecl decls f nombre Representación de objetos I Se representan como una lista de partes donde cada parte I I se corresponde con una clase en la cadena de herencia consiste de un nombre de clase y un entorno (que, junto con el store, contiene el estado de la parte) I la primera parte de la lista representa el punto más bajo de la cadena de clases I a medida que avanzamos en la lista, nos acercamos más a la cima de la jerarquı́a data Value = ... | Object [Part] data Part = APart {part2name::Symbol, part2env::Env} Creación de objetos I La función newObject I I toma como entrada: un nombre de clase, un store y una lista de declaraciones de clases arroja como salida: un objeto I Dado que la creación de un objeto puede exigir la alocación de espacio en la memoria (i.e. el store), newObject precisa el store como entrada (también va a retornar, junto con el objeto creado, el store actualizado) I Veamos un ejemplo antes de presentar el código de newObject Ejemplo class c1 extends object class c2 extends c1 field x,y field y method initialize () method initialize () begin begin set x = 11; super initialize (); set y = 12 set y = 22 end end class c3 extends c2 field x, z method initialize () begin super intialize (); set x = 31; set z = 32 end let o3 = new c3() in send o3 m1(7,8) Ejemplo (cont.) La expresión new c3() va a retornar un nuevo objeto o3 representado por la lista de partes que se ilustra debajo: o3 APart "c3" APart 1 2 x z Referencia Expressed Value (sin "Nro") "c2" APart "c1" 3 4 y 1 2 3 4 5 31 32 22 11 12 APart x Store 5 "object" EmptyEnv Creación de objetos - newObject I La función newObject crea un objeto I Tal como se mencionó, la creación de un objeto puede exigir la alocación de espacio en la memoria (i.e. el store), newObject retorna junto con el objeto creado el store actualizado I La función auxiliar constructParts construye las partes que conforman el nuevo objeto newObject :: Symbol -> Store -> [ClassDecl] -> Answer newObject className st cds = let (parts,s1) = constructParts className st cds in AnAnswer (Object parts) s1 Creación de objetos - constructParts I Esta función construye una lista de partes, donde cada parte se corresponde con una clase de la cadena de jerarquı́a I El primer argumento indica la clase a partir de la cual se quiere crear las partes (se subirá por la jerarquı́a comenzando desde allı́) I En el caso en que este argumento sea "object" tenemos constructParts :: Symbol->Store->[ClassDecl]->([Part],Store) constructParts "object" st cds = ([APart "object" emptyEnv],st) Creación de objetos - constructParts I En los demás casos tenemos constructParts className st cds = case (lookupDecl cds classDecl2name className) of Nothing -> error ("Clase inexistente"++ className) (Just cDecl) -> let campos = map fieldDecl2name (classDecl2fields cDecl) entorno = extendEnv campos (nextAvail st) emptyEnv s1 = extendStore st (replicate (length campos) (Nro 1)) (xs,s2) = constructParts (classDecl2super cDecl) s1 cds in ((APart (classDecl2name cDecl) entorno):xs,s2) Creación de objetos - intérprete de expresiones El intérprete de expresiones se extiende de la siguiente manera para la creación de objetos 1. Se evalúan los argumentos que se le pasarán al método intialize 2. Se crea el objeto propiamente dicho usando newObject (tal como acabamos de ver) 3. Se busca el método initialize y se lo ejecuta 4. Se retorna el objeto actualizado y el store Creación de objetos - intérprete de expresiones evalExpression (NewExp className exps) env st cds = let (args,s1) = evalExpressions exps env st cds (AnAnswer obj s2) = newObject className s1 cds (AnAnswer _ s3) = findMethodAndApply "initialize" className obj (reverse args) s2 cds in AnAnswer obj s3 I Se evalúan los argumentos que se le pasarán al método intialize Creación de objetos - intérprete de expresiones evalExpression (NewExp className exps) env st cds = let (args,s1) = evalExpressions exps env st cds (AnAnswer obj s2) = newObject className s1 cds (AnAnswer _ s3) = findMethodAndApply "initialize" className obj (reverse args) s2 cds in AnAnswer obj s3 I Se crea el objeto propiamente dicho usando newObject Creación de objetos - intérprete de expresiones evalExpression (NewExp className exps) env st cds = let (args,s1) = evalExpressions exps env st cds (AnAnswer obj s2) = newObject className s1 cds (AnAnswer _ s3) = findMethodAndApply "initialize" className obj (reverse args) s2 cds in AnAnswer obj s3 I Se busca el método initialize y se lo ejecuta Creación de objetos - intérprete de expresiones evalExpression (NewExp className exps) env st cds = let (args,s1) = evalExpressions exps env st cds (AnAnswer obj s2) = newObject className s1 cds (AnAnswer _ s3) = findMethodAndApply "initialize" className obj (reverse args) s2 cds in AnAnswer obj s3 I Se retorna el objeto actualizado y el store Creación de objetos - intérprete de expresiones evalExpression (NewExp className exps) env st cds = let (args,s1) = evalExpressions exps env st cds (AnAnswer obj s2) = newObject className s1 cds (AnAnswer _ s3) = findMethodAndApply "initialize" className obj (reverse args) s2 cds in AnAnswer obj s3 I En breve veremos la función findMethodAndApply LLamados a métodos Para evaluar un send 1. evaluamos los operandos evalExpression (SendExp recip methodName rands) env st cds = let (args,s1) = evalExpressions rands env st cds (AnAnswer obj s2) = evalExpression recipient env s1 cd in findMethodAndApply methodName (object2className obj) obj (reverse args) s2 cds LLamados a métodos Para evaluar un send 1. evaluamos los operandos 2. evaluamos la expresión receptora evalExpression (SendExp recip methodName rands) env st cds = let (args,s1) = evalExpressions rands env st cds (AnAnswer obj s2) = evalExpression recipient env s1 cd in findMethodAndApply methodName (object2className obj) obj (reverse args) s2 cds LLamados a métodos Para evaluar un send 1. evaluamos los operandos 2. evaluamos la expresión receptora 3. buscamos el método asociado con el nombre entre las declaraciones de método del objeto y aplicamos el método con los argumentos evalExpression (SendExp recip methodName rands) env st cds = let (args,s1) = evalExpressions rands env st cds (AnAnswer obj s2) = evalExpression recipient env s1 cd in findMethodAndApply methodName (object2className obj) obj (reverse args) s2 cds Implementando findMethodAndApply Esta función deberı́a I I buscar clases a través de la cadena de herencia hasta hallar la clase que declara el método que coincida con methodName al encontrarla, llamar a applyMethod con 1. la declaración de método que se halló 2. la declaración de la clase (se usará solamente para acceder al nombre de la misma y al de su superclase) 3. self (el objeto receptor) 4. los argumentos 5. el store 6. la lista de declaraciones de clases NB: abreviamos findMethodAndApply como fMAA Implementando findMethodAndApply I Comenzamos con el caso sencillo: si, en su búsqueda, fMAA llega a la clase object el método no existe fMAA :: Symbol -> Symbol -> Value -> [Value] -> Store -> [ClassDecl] -> Answer fMAA methodName "object" (Object parts) args st cds = error ("metodo no hallado"++methodName) I En caso contrario, debe fijarse en las declaraciones de métodos de la clase si encuentra una declaración para methodName Implementando findMethodAndApply 1. obtener declaración de clase cDecl para hostName fMAA methodName hostName self@(Object parts) args st cds = case (lookupDecl cds classDecl2name hostName) of Nothing -> error ("clase no hallada"++hostName) (Just cDecl) -> case (lookupDecl (classDecl2methods cDecl) methodDecl2name methodName) of Nothing -> fMAA methodName (classDecl2super cDecl) self args st cds (Just mDecl) -> applyMethod mDecl cDecl self args st Implementando findMethodAndApply 1. obtener declaración de clase cDecl para hostName 2. buscar declaración de método para methodName en cDecl fMAA methodName hostName self@(Object parts) args st cds = case (lookupDecl cds classDecl2name hostName) of Nothing -> error ("clase no hallada"++hostName) (Just cDecl) -> case (lookupDecl (classDecl2methods cDecl) methodDecl2name methodName) of Nothing -> fMAA methodName (classDecl2super cDecl) self args st cds (Just mDecl) -> applyMethod mDecl cDecl self args st Implementando findMethodAndApply 1. obtener declaración de clase cDecl para hostName 2. buscar declaración de método para methodName en cDecl 3. Si no está, seguir buscando en superclase fMAA methodName hostName self@(Object parts) args st cds = case (lookupDecl cds classDecl2name hostName) of Nothing -> error ("clase no hallada"++hostName) (Just cDecl) -> case (lookupDecl (classDecl2methods cDecl) methodDecl2name methodName) of Nothing -> fMAA methodName (classDecl2super cDecl) self args st cds (Just mDecl) -> applyMethod mDecl cDecl self args st Implementando findMethodAndApply 1. obtener declaración de clase cDecl para hostName 2. buscar declaración de método para methodName en cDecl 3. Si no está, seguir buscando en superclase 4. Si está, aplicar el método fMAA methodName hostName self@(Object parts) args st cds = case (lookupDecl cds classDecl2name hostName) of Nothing -> error ("clase no hallada"++hostName) (Just cDecl) -> case (lookupDecl (classDecl2methods cDecl) methodDecl2name methodName) of Nothing -> fMAA methodName (classDecl2super cDecl) self args st cds (Just mDecl) -> applyMethod mDecl cDecl self args st Aplicación de un método Aplicar un método es parecido a aplicar una clausura: el cuerpo del método debe ser ejecutado bajo un entorno en el que cada variable es ligada a su valor apropiado. El entorno debe contar con bindings para: I %super: nombre de una clase (la clase padre de la clase anfitriona del método cuyo código se está por ejecutar) I self: un objeto (el receptor del mensaje cuyo código se está por ejecutar) I los parámetros formales I todos los campos que son visibles desde el código del método actualmente por ejecutarse Aplicación de un método I Los campos que son visibles del método por ejecutarse son aquellas partes del objeto receptor comenzando desde la clase anfitriona I Volvamos al ejemplo que vimos antes I Si ejecutamos send o3 m2(), los campos visibles desde m2 son aquellos de o3 que comienzan en la clase anfitriona de m2 (c2) y todos los que hereda de sus ancestors I Observar que m2 no puede ver el campo x de c3 Ejemplo class c1 extends object class c2 extends c1 field x,y field y method initialize () method initialize () begin begin set x = 11; super initialize (); set y = 12 set y = 22 end end class c3 extends c2 method m2() x field x, z method initialize () begin super intialize (); set x = 31; set z = 32 end let o3 = new c3() in send o3 m2() Aplicación de un método I Por lo tanto, el nombre de una clase determina una vista de un objeto I Los campos visibles desde c son todos aquellos declarados en c más aquellos que se heredan I La función que calcula la vista de un objeto es viewObjectAs viewObjectAs :: [Part] -> Symbol -> [Part] viewObjectAs parts className = dropWhile (not . (==className) . part2name) parts Aplicación de un método I A partir de la vista de un objeto podemos construir un entorno (environment) que consiste en un rib por cada parte. I A modo de función auxiliar usamos composeEnv que permite componer entornos buildFieldEnv :: [Part] -> Env buildFieldEnv ps = foldr (\part-> composeEnv (part2env part)) emptyEnv ps Aplicación de un método Ahora estamos en condiciones de completar applyMethod applyMethod :: MethodDecl -> ClassDecl -> Value -> [Value] -> Store -> [ClassDecl] -> Answer applyMethod mDecl cDecl self@(Object parts) args st cds = let ids = methodDecl2params mDecl s1 = extendStore st ((Object [APart (classDecl2super cDecl) emptyEnv]):self:args) in evalExpression (methodDecl2body mDecl) (extendEnv ("%super":"self":ids) (nextAvail st) (buildFieldEnv (viewObjectAs parts (classDecl2name cDecl))))) s1 cds Llamada super Un llamado super es igual que un llamado a método salvo que el lookup comienza en la superclase de la clase anfitriona de la expresión evalExpression (SuperExp methodName rands) env st cds = let (args,s1) = evalExpressions rands env st cds obj = applyEnv env s1 "self" (Object [APart super _]) = applyEnv env s1 "%super" in fMAA methodName super obj (reverse args) s1 cds I Vamos a analizar este caso, paso por paso Llamada super 1. Evaluar argumentos evalExpression (SuperExp methodName rands) env st cds = let (args,s1) = evalExpressions rands env st cds obj = applyEnv env s1 "self" (Object [APart super _]) = applyEnv env s1 "%super" in fMAA methodName super obj (reverse args) s1 cds Llamada super 1. Evaluar argumentos 2. Obtener objeto receptor (self) del entorno evalExpression (SuperExp methodName rands) env st cds = let (args,s1) = evalExpressions rands env st cds obj = applyEnv env s1 "self" (Object [APart super _]) = applyEnv env s1 "%super" in fMAA methodName super obj (reverse args) s1 cds Llamada super 1. Evaluar argumentos 2. Obtener objeto receptor (self) del entorno 3. Obtener nombre de superclase del entorno evalExpression (SuperExp methodName rands) env st cds = let (args,s1) = evalExpressions rands env st cds obj = applyEnv env s1 "self" (Object [APart super _]) = applyEnv env s1 "%super" in fMAA methodName super obj (reverse args) s1 cds Llamada super 1. Evaluar argumentos 2. Obtener objeto receptor (self) del entorno 3. Obtener nombre de superclase del entorno 4. Buscar método a ejecutar (methodName) comenzando búsqueda desde la superclase evalExpression (SuperExp methodName rands) env st cds = let (args,s1) = evalExpressions rands env st cds obj = applyEnv env s1 "self" (Object [APart super _]) = applyEnv env s1 "%super" in fMAA methodName super obj (reverse args) s1 cds Resumen I Hemos implementado un intérprete para un lenguaje orientado a objetos sencillo y representativo del paradigma I Algunas extensiones posibles a SOOL a modo de ejercicio: I I Agregar una sentencia instanceof(e,clase) que determina si el objeto resultante de evaluar e es instancia de la clase clase Incorporar calificadores private, protected y public para las declaraciones de métodos que restringen su acceso: los públicos se pueden llamar desde cualquier lado, los protegidos sólo desde la clase anfitriona o sus subclases, los privados sólo desde la clase anfitriona