{-# OPTIONS_GHC -fglasgow-exts -fallow-overlapping-instances #-} module Pugs.Prim.Eval ( -- used by Pugs.Prim op1EvalHaskell, op1EvalP6Y, op1EvalFileP6Y, opEval, opEvalFile, opRequire, requireInc, EvalError(..), EvalResult(..), EvalStyle(..), -- used by Pugs.Eval -- needs factored somewhere bettwen retEvalResult, ) where import Pugs.AST import Pugs.Parser.Program import Pugs.Embed import Pugs.Monads import Pugs.Internals import Pugs.Pretty import Pugs.Config import Pugs.Prim.Keyed import Pugs.Types import DrIFT.YAML import Data.Yaml.Syck import qualified Data.ByteString.Char8 as Bytes type Bytes = Bytes.ByteString data EvalError = EvalErrorFatal | EvalErrorUndef deriving Eq data EvalResult = EvalResultLastValue | EvalResultModule | EvalResultEnv deriving Eq data EvalStyle = MkEvalStyle { evalError :: EvalError , evalResult :: EvalResult } specialPackageNames :: [String] specialPackageNames = ["MY", "OUR", "GLOBAL", "OUTER", "CALLER", "ENV", "SUPER", "COMPILING"] opRequire :: Bool -> Val -> Eval Val opRequire dumpEnv v = do mod <- fromVal v if elem mod specialPackageNames then return (VBool True) else do incs <- fromVal =<< readVar (cast "@*INC") glob <- askGlobal seen <- findSymRef (cast "%*INC") glob loaded <- existsFromRef seen v let file | '.' `elem` mod = mod | otherwise = (concat $ intersperse (getConfig "file_sep") $ split "::" mod) ++ ".pm" pathName <- requireInc incs file (errMsg file incs) if loaded then opEval style pathName "" else do -- %*INC{mod} = { relname => file, pathname => pathName } evalExp $ Syn "=" [ Syn "{}" -- subscript [ _Var "%*INC", Val . VStr $ decodeUTF8 mod ] , Syn "\\{}" -- hashref [ Syn "," [ mkStrPair "fullpath" (decodeUTF8 pathName) , mkStrPair "relpath" (decodeUTF8 file) ] ] ] -- merge @*END here endAV <- findSymRef (cast "@*END") glob ends <- fromVal =<< readRef endAV clearRef endAV rv <- tryFastEval pathName (pathName ++ ".yml") endAV' <- findSymRef (cast "@*END") glob doArray (VRef endAV') (`array_unshift` ends) return rv where tryFastEval pathName pathNameYml = do ok <- io $ doesFileExist pathNameYml if not ok then slowEval pathName else do isYamlStale <- tryIO False $ do timePm <- getModificationTime pathName timeYml <- getModificationTime pathNameYml return (timeYml < timePm) if isYamlStale then slowEval pathName else do rv <- tryT $ fastEval pathNameYml case rv of VError _ [MkPos{posBeginLine=0}]-> slowEval pathName _ -> opEval style pathName "" fastEval = op1EvalFileP6Y . VStr slowEval pathName = do str <- io $ readFile pathName opEval style pathName str style = MkEvalStyle { evalError = EvalErrorFatal , evalResult = (if dumpEnv == True then EvalResultEnv else EvalResultLastValue)} errMsg file incs = "Can't locate " ++ file ++ " in @*INC (@*INC contains: " ++ unwords incs ++ ")." mkStrPair :: String -> String -> Exp mkStrPair key val = App (_Var "&infix:=>") Nothing (map (Val . VStr) [key, val]) requireInc :: (MonadIO m) => [FilePath] -> FilePath -> String -> m String requireInc [] _ msg = fail msg requireInc (p:ps) file msg = do let pathName = p ++ (getConfig "file_sep") ++ file ok <- io $ doesFileExist pathName if (not ok) then requireInc ps file msg else return pathName opEvalFile :: String -> Eval Val opEvalFile filename = do ok <- io $ doesFileExist filename if (not ok) then fail $ "Can't locate " ++ filename ++ "." else do contents <- io $ readFile filename opEval style filename contents where style = MkEvalStyle{ evalError=EvalErrorUndef , evalResult=EvalResultLastValue} op1EvalHaskell :: Val -> Eval Val op1EvalHaskell cv = do str <- fromVal cv val <- tryT $ evalHaskell str retEvalResult style val where style = MkEvalStyle{ evalError=EvalErrorUndef , evalResult=EvalResultLastValue} op1EvalP6Y, op1EvalFileP6Y :: Val -> Eval Val op1EvalFileP6Y fileName = do fileName' <- fromVal fileName file <- io $ Bytes.readFile fileName' op1EvalP6Y' file op1EvalP6Y bytecode = do bytecode' <- fromVal bytecode op1EvalP6Y' $ Bytes.pack bytecode' -- XXX: is this the right pack function? op1EvalP6Y' :: Bytes -> Eval Val op1EvalP6Y' bytecode = do yml <- io $ (`catchIO` (return . Left . show)) $ fmap Right (parseYamlBytes bytecode) case yml of Right MkNode{ n_elem=ESeq (v:_) } | MkNode{ n_elem=EStr vnum } <- v , vnum /= (packBuf $ show compUnitVersion) -> do err $ "incompatible version number for compilation unit: found " ++ unpackBuf vnum ++ ", expecting " ++ (show compUnitVersion) Right yml' -> do globTVar <- asks envGlobal MkCompUnit _ _ glob ast <- io $ fromYAML yml' tryT $ do -- Inject the global bindings stm $ do glob' <- readMPad globTVar writeMPad globTVar (glob `unionPads` glob') evl <- asks envEval evl ast x -> err x where err x = local (\e -> e{ envPos = (envPos e){ posBeginLine=0 } }) $ fail $ "failed loading Yaml: " ++ show x opEval :: EvalStyle -> FilePath -> String -> Eval Val opEval style path str = enterCaller $ do env <- ask let errHandler err = return env{ envBody = Val $ VError (VStr (show err)) [] } env' <- io $ evaluateIO (parseProgram env path str) `catchIO` errHandler val <- tryT $ local (const env') $ do evl <- asks envEval initAV <- evalExp (_Var "@*INIT") initSubs <- fromVals initAV mapM_ evalExp [ Ann (Cxt CxtVoid) (App (Val sub) Nothing []) | sub@VCode{} <- initSubs ] evalExp (Syn "=" [_Var "@*INIT", Syn "," []]) evl $ case evalResult style of EvalResultEnv -> envBody env' `mergeStmts` Syn "continuation" [] _ -> envBody env' retEvalResult style val retEvalResult :: EvalStyle -> Val -> Eval Val retEvalResult style val = do glob <- askGlobal errSV <- findSymRef (cast "$!") glob case val of err@(VError e _) -> do writeRef errSV e when (evalError style == EvalErrorFatal) $ do io $ fail $ pretty err retEmpty _ -> do writeRef errSV VUndef return val