berp-0.0.1: src/Berp/Base/Object.hs
-- {-# OPTIONS_GHC -cpp -DDEBUG #-}
{-# OPTIONS_GHC -cpp #-}
-----------------------------------------------------------------------------
-- |
-- Module : Berp.Base.Object
-- Copyright : (c) 2010 Bernie Pope
-- License : BSD-style
-- Maintainer : florbitous@gmail.com
-- Stability : experimental
-- Portability : ghc
--
-- Primitive operations on Objects.
--
-----------------------------------------------------------------------------
#include "BerpDebug.h"
module Berp.Base.Object
(lookupAttribute, lookupSpecialAttribute, lookupAttributeMaybe,
typeOf, identityOf, objectEquality, dictOf, dir) where
import Berp.Base.Truth (truth)
import Berp.Base.Prims (callMethod, showObject)
import Data.List (nub)
import Control.Monad (zipWithM)
import Control.Applicative ((<$>))
import Data.Maybe (isJust, catMaybes)
import Berp.Base.SemanticTypes (Object (..), Eval)
import Berp.Base.Mangle (deMangle)
import Berp.Base.Identity (Identity)
import Berp.Base.Hash (Hashed)
import Berp.Base.StdNames (eqName, cmpName)
import Berp.Base.LiftedIO as LIO (MonadIO)
#ifdef DEBUG
import Berp.Base.LiftedIO as LIO (putStrLn)
#endif
import {-# SOURCE #-} Berp.Base.HashTable (stringLookup, keys)
import {-# SOURCE #-} Berp.Base.StdTypes.Integer (intClass, int)
import {-# SOURCE #-} Berp.Base.StdTypes.Bool (boolClass)
import {-# SOURCE #-} Berp.Base.StdTypes.Tuple (tupleClass, getTupleElements)
import {-# SOURCE #-} Berp.Base.StdTypes.Function (functionClass)
import {-# SOURCE #-} Berp.Base.StdTypes.String (stringClass)
import {-# SOURCE #-} Berp.Base.StdTypes.None (noneClass, noneIdentity)
import {-# SOURCE #-} Berp.Base.StdTypes.Dictionary (dictionaryClass)
import {-# SOURCE #-} Berp.Base.StdTypes.List (listClass, list)
import {-# SOURCE #-} Berp.Base.StdTypes.Generator (generatorClass)
import {-# SOURCE #-} Berp.Base.StdTypes.String (string)
-- needed for overloaded numeric literals
instance Num Object where
fromInteger = int
(+) = undefined
(*) = undefined
abs = undefined
signum = undefined
-- Python allows the type of an object to change in a limited set of circumstances.
-- But we will ignore that for the moment and make it a pure function.
typeOf :: Object -> Object
typeOf obj@(Object {}) = object_type obj
typeOf obj@(Type {}) = object_type obj
typeOf (Integer {}) = intClass
typeOf (Bool {}) = boolClass
typeOf (Tuple {}) = tupleClass
typeOf (List {}) = listClass
typeOf (Function {}) = functionClass
typeOf (String {}) = stringClass
typeOf (None {}) = noneClass
typeOf (Dictionary {}) = dictionaryClass
typeOf (Generator {}) = generatorClass
-- The identity of an object should never change so this can be a pure function.
identityOf :: Object -> Identity
identityOf None = noneIdentity
identityOf object = object_identity object
dictOf :: Object -> Maybe Object
dictOf obj@(Object {}) = Just $ object_dict obj
dictOf obj@(Type {}) = Just $ object_dict obj
dictOf obj@(Function {}) = Just $ object_dict obj
dictOf _other = Nothing
lookupAttribute :: Object -> Hashed String -> Eval Object
lookupAttribute obj ident = do
lookupResult <- lookupAttributeMaybe obj ident
checkLookup obj ident lookupResult
lookupSpecialAttribute :: Object -> Hashed String -> Eval Object
lookupSpecialAttribute obj ident = do
lookupResult <- lookupSpecialAttributeMaybe obj ident
checkLookup obj ident lookupResult
checkLookup :: Object -> Hashed String -> Maybe Object -> Eval Object
checkLookup obj (_, identStr) lookupResult =
case lookupResult of
-- XXX This should raise a proper catchable exception
Nothing -> do
objStr <- showObject obj
fail $ objStr ++ " has no attribute called " ++ deMangle identStr
Just attributeObj -> do
case attributeObj of
-- XXX this should return a bound method object
Function { object_procedure = proc, object_arity = arity } ->
return attributeObj { object_procedure = \args -> proc (obj:args), object_arity = arity - 1 }
_other -> do
return attributeObj
-- XXX does not handle descriptors or getattr/getattribute.
-- XXX Hack: If the result of the lookup is a function, then
-- turn it into a bound method on return, by supplying the
-- object as the first argument. This is not ideal, but
-- it will work until descriptors are supported
lookupSpecialAttributeMaybe :: MonadIO m => Object -> Hashed String -> m (Maybe Object)
lookupSpecialAttributeMaybe object ident = do
BELCH("Looking for special attribute: " ++ show ident ++ " in: " ++ show object)
lookupAttributeType object ident
lookupAttributeMaybe :: MonadIO m => Object -> Hashed String -> m (Maybe Object)
lookupAttributeMaybe object ident = do
BELCH("Looking for: " ++ show ident ++ " in: " ++ show object)
BELCH("Looking in dictionary of object")
case dictOf object of
-- The object does have a dictionary; look in there.
Just dict -> do
BELCH("Object has a dictionary")
dictResult <- stringLookup ident $ object_hashTable dict
case dictResult of
-- The ident was not found in the object, look in the type, then the bases.
Nothing -> do
BELCH("Ident not found in dictionary of object")
lookupAttributeType object ident
-- The ident was found in the object; return it.
Just _ -> do
BELCH("Ident was found in dictionary of object")
return dictResult
-- The object does not have a dictionary; look in the type, then the bases.
Nothing -> do
BELCH("Object does not have a dictionary")
lookupAttributeType object ident
lookupAttributeType :: MonadIO m => Object -> Hashed String -> m (Maybe Object)
lookupAttributeType object ident = do
BELCH("Looking in dict of the type: " ++ show objectType)
let mroList = getTupleElements $ object_mro objectType
searchMRO mroList
where
objectType :: Object
objectType = typeOf object
searchMRO :: MonadIO m => [Object] -> m (Maybe Object)
searchMRO [] = do
BELCH("Ident was not found in the mro of the type of the object")
return Nothing
searchMRO (klass:rest) = do
BELCH("Looking in the dict of the type: " ++ show klass)
case dictOf klass of
Nothing -> do
BELCH("Type does not have a dictionary")
searchMRO rest
Just dict -> do
BELCH("Type does have a dictionary")
dictResult <- stringLookup ident $ object_hashTable dict
case dictResult of
Nothing -> do
BELCH("Ident not found in dictionary of type")
searchMRO rest
Just _ -> do
BELCH("Ident was found in dictionary of type")
return dictResult
hasAttribute :: (Functor m, MonadIO m) => Object -> Hashed String -> m Bool
hasAttribute object ident = isJust <$> lookupAttributeMaybe object ident
-- | Check if two objects are equal. For some objects we might have
-- to call the __eq__ (or __cmp__) method on the objects. This means
-- the result must be in the Eval monad.
objectEquality :: Object -> Object -> Eval Bool
objectEquality obj1@(Integer {}) obj2@(Integer {})
= return (object_integer obj1 == object_integer obj2)
objectEquality obj1@(Bool {}) obj2@(Bool {})
= return (object_bool obj1 == object_bool obj2)
objectEquality obj1@(Tuple {}) obj2@(Tuple {})
| object_identity obj1 == object_identity obj2 = return True
| object_length obj1 == object_length obj2 =
and <$> zipWithM objectEquality (object_tuple obj1) (object_tuple obj2)
| otherwise = return False
objectEquality obj1@(String {}) obj2@(String {})
= return (object_string obj1 == object_string obj2)
objectEquality None None = return True
objectEquality obj1 obj2
| object_identity obj1 == object_identity obj2 = return True
| otherwise = do
canEq <- hasAttribute obj1 eqName
if canEq
then truth <$> callMethod obj1 eqName [obj2]
else do
canCmp <- hasAttribute obj1 cmpName
if canCmp
then do
cmpResult <- callMethod obj1 cmpName [obj2]
case cmpResult of
Integer {} -> return $ object_integer cmpResult == 0
_other -> fail $ "__cmp__ method on object does not return an integer: " ++ show obj1
else return False -- XXX should this raise an exception?
dir :: Object -> Eval Object
dir object = do
let maybeObjDict = dictOf object
let objectBasesDicts = map dictOf $ getTupleElements $ object_mro $ typeOf object
let allDicts = catMaybes (maybeObjDict : objectBasesDicts)
let hashTables = map object_hashTable allDicts
keyObjects <- concat <$> mapM keys hashTables
let keyStrings = nub $ map (deMangle . object_string) keyObjects
list $ map string keyStrings