EtaMOO-0.1.0.0: src/MOO/Builtins.hs
{-# LANGUAGE OverloadedStrings #-}
module MOO.Builtins ( builtinFunctions, callBuiltin, verifyBuiltins ) where
import Control.Monad (when, foldM, liftM, join)
import Control.Monad.State (gets)
import Data.List (transpose, inits)
import Data.Map (Map)
import Data.Maybe (fromMaybe)
import Data.Time (formatTime, utcToLocalZonedTime)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, posixSecondsToUTCTime)
import System.IO.Unsafe (unsafePerformIO)
import System.Locale (defaultTimeLocale)
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Vector as V
import MOO.Builtins.Common
import MOO.Types
import MOO.Task
import MOO.Database
import MOO.Object
import MOO.Version
import MOO.Builtins.Values as Values
import MOO.Builtins.Objects as Objects
import MOO.Builtins.Network as Network
import MOO.Builtins.Tasks as Tasks
{-# ANN module ("HLint: ignore Use camelCase" :: String) #-}
-- | A 'Map' of all built-in functions, keyed by name
builtinFunctions :: Map Id (Builtin, Info)
builtinFunctions =
M.fromList $ miscBuiltins ++
Values.builtins ++ Objects.builtins ++ Network.builtins ++ Tasks.builtins
-- | Call the named built-in function with the given arguments, checking first
-- for the appropriate number and types of arguments. Raise 'E_INVARG' if the
-- built-in function is unknown.
callBuiltin :: Id -> [Value] -> MOO Value
callBuiltin func args = case M.lookup func builtinFunctions of
Just (bf, info) -> checkArgs info args >> bf args
Nothing -> raiseException $
Exception (Err E_INVARG) "Unknown built-in function" (Str func)
where checkArgs (Info min max types _) args = do
let nargs = length args
in when (nargs < min || nargs > fromMaybe nargs max) $ raise E_ARGS
checkTypes types args
checkTypes (t:ts) (a:as) = do
when (typeMismatch t $ typeOf a) $ raise E_TYPE
checkTypes ts as
checkTypes _ _ = return ()
typeMismatch x y | x == y = False
typeMismatch TAny _ = False
typeMismatch TNum TInt = False
typeMismatch TNum TFlt = False
typeMismatch _ _ = True
-- | Perform internal consistency verification of all the built-in functions,
-- checking that each implementation actually accepts the claimed argument
-- types. Note that an inconsistency may cause the program to abort.
--
-- Assuming the program doesn't abort, this generates either a string
-- describing an inconsistency, or an integer giving the total number of
-- (verified) built-in functions.
verifyBuiltins :: Either String Int
verifyBuiltins = foldM accum 0 $ M.assocs builtinFunctions
where accum a b = valid b >>= Right . (+ a)
valid (name, (func, Info min max types _))
| name /= T.toCaseFold name = invalid "name not case-folded"
| min < 0 = invalid "arg min < 0"
| maybe False (< min) max = invalid "arg max < min"
| length types /= fromMaybe min max = invalid "incorrect # types"
| testArgs func min max types = ok
where invalid msg = Left $ T.unpack name ++ ": " ++ msg
ok = Right 1
testArgs func min max types = all test argSpecs
where argSpecs = drop min $ inits $ map mkArgs augmentedTypes
augmentedTypes = maybe (types ++ [TAny]) (const types) max
test argSpec = all (\args -> func args `seq` True) $
enumerateArgs argSpec
enumerateArgs :: [[Value]] -> [[Value]]
enumerateArgs (a:[]) = transpose [a]
enumerateArgs (a:as) = concatMap (combine a) (enumerateArgs as)
where combine ps rs = map (: rs) ps
enumerateArgs [] = [[]]
mkArgs :: Type -> [Value]
mkArgs TAny = mkArgs TNum ++ mkArgs TStr ++ mkArgs TObj ++
mkArgs TErr ++ mkArgs TLst
mkArgs TNum = mkArgs TInt ++ mkArgs TFlt
mkArgs TInt = [Int 0]
mkArgs TFlt = [Flt 0]
mkArgs TStr = [Str T.empty]
mkArgs TObj = [Obj 0]
mkArgs TErr = [Err E_NONE]
mkArgs TLst = [Lst V.empty]
-- 4.4 Built-in Functions
miscBuiltins :: [BuiltinSpec]
miscBuiltins = [
-- 4.4.1 Object-Oriented Programming
("pass" , (bf_pass , Info 0 Nothing [] TAny))
-- 4.4.5 Operations Involving Times and Dates
, ("time" , (bf_time , Info 0 (Just 0) [] TInt))
, ("ctime" , (bf_ctime , Info 0 (Just 1) [TInt] TStr))
-- 4.4.7 Administrative Operations
, ("dump_database" , (bf_dump_database , Info 0 (Just 0) [] TAny))
, ("shutdown" , (bf_shutdown , Info 0 (Just 1) [TStr] TAny))
, ("load_server_options",
(bf_load_server_options, Info 0 (Just 0) [] TAny))
, ("server_log" , (bf_server_log , Info 1 (Just 2) [TStr, TAny] TAny))
, ("renumber" , (bf_renumber , Info 1 (Just 1) [TObj] TObj))
, ("reset_max_object",
(bf_reset_max_object, Info 0 (Just 0) [] TAny))
-- 4.4.8 Server Statistics and Miscellaneous Information
, ("server_version", (bf_server_version, Info 0 (Just 0) [] TStr))
, ("memory_usage" , (bf_memory_usage , Info 0 (Just 0) [] TLst))
, ("db_disk_size" , (bf_db_disk_size , Info 0 (Just 0) [] TInt))
, ("verb_cache_stats",
(bf_verb_cache_stats, Info 0 (Just 0) [] TLst))
, ("log_cache_stats",
(bf_log_cache_stats , Info 0 (Just 0) [] TAny))
]
-- 4.4.1 Object-Oriented Programming
bf_pass args = do
(name, verbLoc, this) <- frame $ \frame ->
(verbName frame, verbLocation frame, initialThis frame)
maybeMaybeParent <- fmap objectParent `liftM` getObject verbLoc
case join maybeMaybeParent of
Just parent -> callVerb parent this name args
Nothing -> raise E_VERBNF
-- 4.4.5 Operations Involving Times and Dates
currentTime :: MOO IntT
currentTime = (floor . utcTimeToPOSIXSeconds) `liftM` gets startTime
bf_time [] = Int `liftM` currentTime
bf_ctime [] = ctime =<< currentTime
bf_ctime [Int time] = ctime time
ctime :: IntT -> MOO Value
ctime time =
return $ Str $ T.pack $ formatTime defaultTimeLocale format zonedTime
where format = "%a %b %_d %T %Y %Z"
zonedTime = unsafePerformIO $ utcToLocalZonedTime utcTime
utcTime = posixSecondsToUTCTime (fromIntegral time)
-- 4.4.7 Administrative Operations
bf_dump_database [] = notyet "dump_database"
bf_shutdown optional = notyet "shutdown"
where (message : _) = maybeDefaults optional
bf_load_server_options [] = checkWizard >> loadServerOptions >> return nothing
bf_server_log (Str message : optional) = notyet "server_log"
where [is_error] = booleanDefaults optional [False]
bf_renumber [Obj object] = notyet "renumber"
bf_reset_max_object [] = do
checkWizard
getDatabase >>= liftSTM . resetMaxObject >>= putDatabase
return nothing
-- 4.4.8 Server Statistics and Miscellaneous Information
bf_server_version [] = return (Str serverVersionText)
bf_memory_usage [] = return $ Lst V.empty -- ... nothing to see here
bf_db_disk_size [] = raise E_QUOTA -- not yet?
bf_verb_cache_stats [] = notyet "verb_cache_stats"
bf_log_cache_stats [] = notyet "log_cache_stats"