packages feed

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"