husk-scheme-3.5.4: hs-src/Language/Scheme/Variables.hs
{- |
Module : Language.Scheme.Variables
Copyright : Justin Ethier
Licence : MIT (see LICENSE in the distribution)
Maintainer : github.com/justinethier
Stability : experimental
Portability : portable
This module contains code for working with Scheme variables,
and the environments that contain them.
-}
module Language.Scheme.Variables
(
-- * Environments
printEnv
, copyEnv
, extendEnv
, findNamespacedEnv
-- * Getters
, getVar
, getNamespacedVar
-- * Setters
, defineVar
, setVar
, setNamespacedVar
, defineNamespacedVar
-- * Predicates
, isBound
, isRecBound
, isNamespacedBound
, isNamespacedRecBound
) where
import Language.Scheme.Types
import Control.Monad.Error
import Data.IORef
import qualified Data.Map
-- TODO: convert from storing vars in a list to a more efficient
-- data structure using Data.Map
{- Experimental code:
-- From: http://rafaelbarreto.com/2011/08/21/comparing-objects-by-memory-location-in-haskell/
import Foreign
isMemoryEquivalent :: a -> a -> IO Bool
isMemoryEquivalent obj1 obj2 = do
obj1Ptr <- newStablePtr obj1
obj2Ptr <- newStablePtr obj2
let result = obj1Ptr == obj2Ptr
freeStablePtr obj1Ptr
freeStablePtr obj2Ptr
return result
-- Using above, search an env for a variable definition, but stop if the upperEnv is
-- reached before the variable
isNamespacedRecBoundWUpper :: Env -> Env -> String -> String -> IO Bool
isNamespacedRecBoundWUpper upperEnvRef envRef namespace var = do
areEnvsEqual <- liftIO $ isMemoryEquivalent upperEnvRef envRef
if areEnvsEqual
then return False
else do
found <- liftIO $ isNamespacedBound envRef namespace var
if found
then return True
else case parentEnv envRef of
(Just par) -> isNamespacedRecBoundWUpper upperEnvRef par namespace var
Nothing -> return False -- Var never found
-}
-- |Show the contents of an environment
printEnv :: Env -- ^Environment
-> IO String -- ^Contents of the env as a string
printEnv env = do
binds <- liftIO $ readIORef $ bindings env
l <- mapM showVar $ Data.Map.toList binds
return $ unlines l
where
showVar ((_, name), val) = do
v <- liftIO $ readIORef val
return $ name ++ ": " ++ show v
-- |Create a deep copy of an environment
copyEnv :: Env -- ^ Source environment
-> IO Env -- ^ A copy of the source environment
copyEnv env = do
binds <- liftIO $ readIORef $ bindings env
-- bindingList <- mapM addBinding binds >>= newIORef
bindingListT <- mapM addBinding $ Data.Map.toList binds -- TODO: there is a more elegant way to write this here (and below, too)
bindingList <- newIORef $ Data.Map.fromList bindingListT
return $ Environment (parentEnv env) bindingList -- TODO: recursively create a copy of parent also?
where addBinding ((namespace, name), val) = do --ref <- newIORef $ liftIO $ readIORef val
x <- liftIO $ readIORef val
ref <- newIORef x
return ((namespace, name), ref)
-- |Extend given environment by binding a series of values to a new environment.
-- TODO: should be able to use Data.Map.fromList to ease construction of new Env
extendEnv :: Env -- ^ Environment
-> [((String, String), LispVal)] -- ^ Extensions to the environment
-> IO Env -- ^ Extended environment
extendEnv envRef abindings = do bindinglistT <- (mapM addBinding abindings) -- >>= newIORef
bindinglist <- newIORef $ Data.Map.fromList bindinglistT
return $ Environment (Just envRef) bindinglist
where addBinding ((namespace, name), val) = do ref <- newIORef val
return ((namespace, name), ref)
-- |Recursively search environments to find one that contains the given variable.
findNamespacedEnv
:: Env -- ^Environment to begin the search;
-- parent env's will be searched as well.
-> String -- ^Namespace
-> String -- ^Variable
-> IO (Maybe Env) -- ^Environment, or Nothing if there was no match.
findNamespacedEnv envRef namespace var = do
found <- liftIO $ isNamespacedBound envRef namespace var
if found
then return (Just envRef)
else case parentEnv envRef of
(Just par) -> findNamespacedEnv par namespace var
Nothing -> return Nothing
-- |Determine if a variable is bound in the default namespace
isBound :: Env -- ^ Environment
-> String -- ^ Variable
-> IO Bool -- ^ True if the variable is bound
isBound envRef var = isNamespacedBound envRef varNamespace var
-- |Determine if a variable is bound in the default namespace,
-- in this environment or one of its parents.
isRecBound :: Env -- ^ Environment
-> String -- ^ Variable
-> IO Bool -- ^ True if the variable is bound
isRecBound envRef var = isNamespacedRecBound envRef varNamespace var
-- |Determine if a variable is bound in a given namespace
isNamespacedBound
:: Env -- ^ Environment
-> String -- ^ Namespace
-> String -- ^ Variable
-> IO Bool -- ^ True if the variable is bound
isNamespacedBound envRef namespace var =
(readIORef $ bindings envRef) >>= return . Data.Map.member (namespace, var)
-- |Determine if a variable is bound in a given namespace
-- or a parent of the given environment.
isNamespacedRecBound
:: Env -- ^ Environment
-> String -- ^ Namespace
-> String -- ^ Variable
-> IO Bool -- ^ True if the variable is bound
isNamespacedRecBound envRef namespace var = do
env <- findNamespacedEnv envRef namespace var
case env of
(Just e) -> isNamespacedBound e namespace var
Nothing -> return False
-- |Retrieve the value of a variable defined in the default namespace
getVar :: Env -- ^ Environment
-> String -- ^ Variable
-> IOThrowsError LispVal -- ^ Contents of the variable
getVar envRef var = getNamespacedVar envRef varNamespace var
-- |Retrieve the value of a variable defined in a given namespace
getNamespacedVar :: Env -- ^ Environment
-> String -- ^ Namespace
-> String -- ^ Variable
-> IOThrowsError LispVal -- ^ Contents of the variable
getNamespacedVar envRef
namespace
var = do binds <- liftIO $ readIORef $ bindings envRef
case Data.Map.lookup (namespace, var) binds of
(Just a) -> liftIO $ readIORef a
Nothing -> case parentEnv envRef of
(Just par) -> getNamespacedVar par namespace var
Nothing -> (throwError $ UnboundVar "Getting an unbound variable" var)
-- |Set a variable in the default namespace
setVar
:: Env -- ^ Environment
-> String -- ^ Variable
-> LispVal -- ^ Value
-> IOThrowsError LispVal -- ^ Value
setVar envRef var value = setNamespacedVar envRef varNamespace var value
-- |Bind a variable in the default namespace
defineVar
:: Env -- ^ Environment
-> String -- ^ Variable
-> LispVal -- ^ Value
-> IOThrowsError LispVal -- ^ Value
defineVar envRef var value = defineNamespacedVar envRef varNamespace var value
-- |Set a variable in a given namespace
setNamespacedVar
:: Env -- ^ Environment
-> String -- ^ Namespace
-> String -- ^ Variable
-> LispVal -- ^ Value
-> IOThrowsError LispVal -- ^ Value
setNamespacedVar envRef
namespace
var value = do env <- liftIO $ readIORef $ bindings envRef
case Data.Map.lookup (namespace, var) env of
(Just a) -> do -- vprime <- liftIO $ readIORef a
liftIO $ writeIORef a value
return value
Nothing -> case parentEnv envRef of
(Just par) -> setNamespacedVar par namespace var value
Nothing -> throwError $ UnboundVar "Setting an unbound variable: " var
-- |Bind a variable in the given namespace
defineNamespacedVar
:: Env -- ^ Environment
-> String -- ^ Namespace
-> String -- ^ Variable
-> LispVal -- ^ Value
-> IOThrowsError LispVal -- ^ Value
defineNamespacedVar envRef
namespace
var value = do
alreadyDefined <- liftIO $ isNamespacedBound envRef namespace var
if alreadyDefined
then setNamespacedVar envRef namespace var value >> return value
else liftIO $ do
valueRef <- newIORef value
env <- readIORef $ bindings envRef
writeIORef (bindings envRef) (Data.Map.insert (namespace, var) valueRef env) -- (((namespace, var), valueRef) : env)
return value