esotericbot-0.0.1: Esotericbot/Execution.hs
{-# OPTIONS -XMagicHash -XMultiParamTypeClasses -XRankNTypes #-}
module Esotericbot.Execution ( UnknownPluginContext ( .. ) , KnownPluginContext ( .. ) ) where
import Prelude as P
import Data.Attoparsec as A
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString.Lazy.Char8 as BSC
import Data.ByteString.Unsafe
import Data.List.Stream as L
import Data.Maybe
import Data.Map as M
import Control.Concurrent.STM
import Control.Concurrent
import System.IO
import Esotericbot.IRCCom
import Esotericbot.Safely
import Esotericbot.BSUtils
import Esotericbot.EBTypes
import Esotericbot.Help
import Esotericbot.Chanop
type PluginRun =
( String , BSC.ByteString , Maybe BSC.ByteString , Maybe Integer )
-- UserCommand instance for commands to an as-yet-unknown plugin
instance RunnableCommand Command UnknownPluginContext where
run_command cmds ( UnknownPluginContext h help_str op_str ) = do
sb <- get
let cmd = irc_cmd cmds
if BS.isPrefixOf op_str cmd
then op_cmd h $ cmds { circ_cmd = BS.drop 2 cmd }
else do
isListening <- maybe ( return True ) -- if the sender of the message is not on the listening/not listening list, consider them listening.
id
( do chan <- irc_chan cmds -- it's my favourite monad *aww*
l_var <- M.lookup chan $ listening sb
return $ liftIO $ atomically $ readTVar l_var
)
if isListening
then
if BS.isPrefixOf help_str cmd
then get_help h $ cmds { circ_cmd = BS.drop 4 cmd }
else execute_unknown h cmds
else return ( )
data UnknownPluginContext = -- The unknown plugin context needs a handle, the help string, and the op string, in order to correctly identify a plugin
UnknownPluginContext Handle BS.ByteString BS.ByteString
instance RunnableCommand PluginCommand KnownPluginContext where
run_command cmds ( KnownPluginContext h ) =
execute_known h cmds
-- KnownPluginContext
data KnownPluginContext =
KnownPluginContext Handle
execute_known :: Handle -> PluginCommand -> SmallBotM ( )
execute_known h icmd =
let ( _ , eprog ) = parse ( sep_prog_and_input $ plugin icmd ) $ irc_cmd icmd
in respond icmd h eprog
respond cmd h eprog =
either ( const $ abuse cmd h )
( run_prog cmd h )
eprog
abuse cmd h = do
msg <- liftIO $ ls2bs 36 "You are a blight upon the landscape."#
priv_msg h cmd msg
execute_unknown :: Handle -> Command -> SmallBotM ( )
execute_unknown h cmd = do
let cmds = irc_cmd cmd
sb <- get
let ( _ , eprog ) = get_prog ( plugins sb ) cmds
respond cmd h eprog
run_prog :: IRCCommand cmd => cmd -> Handle -> PluginRun -> SmallBotM ( )
run_prog cmd h ( proc , prog , minput , mmem ) =
let input = maybe [ ]
( ( flip (:) ) [ ] . BSC.unpack )
minput
in
when_mem mmem $
safely h cmd proc input prog mmem
-- execute when memory is ready
-- this basically blocks the thread when memory is not free
-- perhaps an explicit queue would be better?
-- Some would argue this should be more atomic and use `retry` but this is without considering that at some point the maximum memory
-- that could be allocated may change ( it _is_ in the StateT monad ) so it is better to explicitely re-enter the function
when_mem :: forall a . Maybe Integer -> SmallBotM a -> SmallBotM a
when_mem mmem f = do
sb <- get
maybe f
( \ memory_max -> do
current_mem <- liftIO $ atomically $ readTVar $ vCurrent sb
Just mem_to_allocate <- child_mem_limit mmem -- validity checking on the config file guarantees this to be the case
if current_mem + mem_to_allocate >= memory_max
then do
liftIO yield
when_mem mmem f
else do
liftIO $ atomically $ writeTVar ( vCurrent sb ) $ current_mem + mem_to_allocate
res <- f
liftIO $ atomically $ do new_mem <- readTVar $ vCurrent sb
writeTVar ( vCurrent sb ) $ new_mem - mem_to_allocate
return res
)
$ mchildren_mem_limit sb
get_prog executors =
parse ( p_cmd executors )
p_cmd executors = do
e <- chooseFrom executors
spaces
return e
chooseFrom langs =
choices $ L.map lang_choice langs
lang_choice plugin = do
choices [ string $ prefix plugin , string $ name plugin ]
spaces
sep_prog_and_input plugin
sep_prog_and_input plugin =
maybe all_prog
( \ input_break -> try ( do
prog <- manyTill anyWord8 $ string input_break
spaces
input <- getInput
return ( cmd plugin , BS.pack prog , Just input , memuse plugin )
)
<|> all_prog
)
$ input_seperator plugin
where
all_prog = do
prog <- getInput
return ( cmd plugin , prog , Nothing , memuse plugin )