packages feed

Pugs-6.2.13.20120717: src/Pugs/Prim/Eval.hs

{-# 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 Pugs.Prelude
import DrIFT.YAML
import Data.Yaml.Syck
import Data.Binary (decode)
import Control.Exception (SomeException)
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"
    useExternalTest <- io $ (maybe False (/= "")) `fmap` (getEnv "PUGS_USE_EXTERNAL_TEST")
    pathName    <- case mod of
        "Test" | not useExternalTest -> return "Test.pm"
        _       -> 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 <- case mod of
            "Test" | not useExternalTest -> shortcutToTestPM
            _       -> tryFastEval pathName (pathName ++ ".yml")
        endAV'  <- findSymRef (cast "@*END") glob
        _       <- doArray (VRef endAV') (`array_unshift` ends)
        return rv
    where
    shortcutToTestPM = do
        globTVar    <- asks envGlobal
        let MkCompUnit _ _ glob ast = decode (testByteStringLazy)
        -- Inject the global bindings
        stm $ do
            glob' <- readMPad globTVar
            writeMPad globTVar (glob `unionPads` glob')

        -- | PEStatic   { pe_type :: !Type, pe_proto :: !VRef, pe_flags :: !EntryFlags, pe_store :: !(TVar VRef) }
        evl <- asks envEval
        evl ast
    tryFastEval pathName pathNameYml = do
        -- io $ print pathNameYml
        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 :: SomeException -> String))) $
        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 :: SomeException) = 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