-- File created: 2009-07-26 16:26:13 module Haschoo.Evaluator.Eval (eval, maybeEval, evalBody, define) where import Control.Monad (msum) import Control.Monad.Error (throwError, catchError) import Control.Monad.State (get) import Control.Monad.Trans (liftIO) import Data.IORef (IORef, readIORef, modifyIORef) import qualified Data.ListTrie.Patricia.Map.Enum as TM import Data.ListTrie.Patricia.Map.Enum (TrieMap) import Haschoo.Types ( Haschoo , ScmValue( ScmPrim, ScmFunc, ScmMacro , ScmList, ScmDottedList , ScmVoid, ScmIdentifier) , MacroCall(..) , Context, addToContext, contextLookup) import Haschoo.Utils (lazyMapM, modifyM) import Haschoo.Evaluator.Utils (tooFewArgs) eval :: ScmValue -> Haschoo ScmValue eval = evalWithSpecialCases TM.empty evalWithSpecialCases :: TrieMap Char [IORef Context] -> ScmValue -> Haschoo ScmValue evalWithSpecialCases specials (ScmIdentifier s) = case TM.lookup s specials of Just specialCtx -> lookupId specialCtx Nothing -> lookupId =<< get where lookupId ctx = do lookups <- lazyMapM (fmap (contextLookup s) . readIORef) ctx case msum lookups of Nothing -> throwError $ "Unbound identifier '" ++ s ++ "'" Just v -> return v evalWithSpecialCases _ (ScmList []) = throwError "Empty application" evalWithSpecialCases sp (ScmList (x:xs)) = do evaledHead <- evalWithSpecialCases sp x case evaledHead of ScmPrim _ f -> f xs ScmFunc _ f -> do args <- mapM (evalWithSpecialCases sp) xs result <- liftIO $ f args case result of Left s -> throwError s Right val -> return val m@(ScmMacro _ _) -> evalMacro sp m (MCList xs) _ -> throwError "Can't apply non-function" evalWithSpecialCases sp (ScmDottedList (x:xs) y) = do evaledHead <- evalWithSpecialCases sp x case evaledHead of m@(ScmMacro _ _) -> evalMacro sp m (MCDotted xs y) _ -> throwError "Ill-formed procedure application: no dots allowed" evalWithSpecialCases _ v = return v evalMacro :: TrieMap Char [IORef Context] -> ScmValue -> MacroCall -> Haschoo ScmValue evalMacro sp (ScmMacro _ f) xs = do (expansion, frees) <- f xs evalWithSpecialCases (TM.union frees sp) expansion evalMacro _ _ _ = error "evalMacro :: the impossible happened: not a macro" maybeEval :: ScmValue -> Haschoo (Maybe ScmValue) maybeEval = (`catchError` const (return Nothing)) . fmap Just . eval evalBody :: [ScmValue] -> Haschoo ScmValue evalBody (ScmList (ScmIdentifier i : xs) : ds) | i == "define" = scmDefine xs >> evalBody ds | i == "begin" = evalBody (xs ++ ds) evalBody ds@(_:_) = fmap last . mapM eval $ ds evalBody [] = return ScmVoid define :: String -> ScmValue -> Haschoo () define var body = eval body >>= modifyM . f where f e (c:cs) = do liftIO $ modifyIORef c (addToContext var e) return (c:cs) f _ [] = error "define :: the impossible happened: empty context stack" scmDefine :: [ScmValue] -> Haschoo () scmDefine [ScmIdentifier var, expr] = define var expr scmDefine (ScmList (ScmIdentifier func : params) : body) = define func . ScmList $ ScmIdentifier "lambda" : ScmList params : body scmDefine (ScmDottedList (ScmIdentifier func : params) final : body) = define func . ScmList $ ScmIdentifier "lambda" : (if null params then final else ScmDottedList params final) : body scmDefine (_:_:_) = throwError "define :: expected identifier" scmDefine _ = tooFewArgs "define"