{-# LANGUAGE PatternGuards #-}
module Lambdabot.Plugin.Core.Compose (composePlugin) where
import Lambdabot.Command
import Lambdabot.Module
import Lambdabot.Monad
import Lambdabot.Plugin
import Control.Arrow (first)
import Control.Monad
import Control.Monad.Reader
import Data.Char
import Data.List
import Data.List.Split
type Compose = ModuleT () LB
composePlugin :: Module ()
composePlugin = newModule
{ moduleCmds = return
[ (command "@")
{ aliases = ["?"]
, help = do
c <- getCmdName
let cc = c++c
mapM_ say
[ cc++" [args]."
, cc++" executes plugin invocations in its arguments, parentheses can be used."
, " The commands are right associative."
, " For example: "++cc++" "++c++"pl "++c++"undo code"
, " is the same as: "++cc++" ("++c++"pl ("++c++"undo code))"
]
, process = evalBracket
}
, (command ".")
{ aliases = ["compose"]
, help = mapM_ say
[ ". <cmd1> <cmd2> [args]."
, ". [or compose] is the composition of two plugins"
, " The following semantics are used: . f g xs == g xs >>= f"
]
, process = \args -> case splitOn " " args of
(f:g:xs) -> do
f' <- lookupP f
g' <- lookupP g
lb (compose f' g' (concat $ intersperse " " xs)) >>= mapM_ say
_ -> say "Not enough arguments to @."
}
]
}
compose :: (String -> LB [String]) -> (String -> LB [String]) -> (String -> LB [String])
compose f g xs = g xs >>= f . unlines
lookupP :: String -> Cmd Compose (String -> LB [String])
lookupP cmd = withMsg $ \a -> do
b <- getTarget
lb $ withCommand cmd
(fail $ "Unknown command: " ++ show cmd)
(\theCmd -> do
when (privileged theCmd) $ fail "Privileged commands cannot be composed"
mTag <- asks moduleID
return (inModuleWithID mTag (return []) . runCommand theCmd a b cmd))
evalBracket :: String -> Cmd Compose ()
evalBracket args = do
cmdPrefixes <- getConfig commandPrefixes
let conf = cmdPrefixes
xs <- mapM evalExpr (fst (parseBracket 0 True args conf))
mapM_ (say . addSpace) (concat' xs)
where concat' ([x]:[y]:xs) = concat' ([x++y]:xs)
concat' xs = concat xs
addSpace :: String -> String
addSpace (' ':xs) = ' ':xs
addSpace xs = ' ':xs
evalExpr :: Expr -> Cmd Compose [String]
evalExpr (Arg s) = return [s]
evalExpr (Cmd c args) = do
args' <- mapM evalExpr args
let arg = concat $ concat $ map (intersperse " ") args'
cmd <- lookupP c
lift (lift (cmd arg))
data Expr = Cmd String [Expr]
| Arg String
deriving Show
parseBracket :: Int -> Bool -> String -> [String] -> ([Expr],String)
parseBracket 0 _ [] _ = ([],[])
parseBracket _ _ [] _ = error "Missing ')' in nested command"
parseBracket 1 _ (')':xs) _ = ([],xs)
parseBracket n _ (')':xs) c | n > 0
= first (addArg ")") $ parseBracket (n-1) True xs c
parseBracket n _ ('(':xs) c | Just ys <- isCommand xs c
= parseCommand n ys c
parseBracket n _ ('(':xs) c | n > 0
= first (addArg "(") $ parseBracket (n+1) True xs c
parseBracket n _ xs c | Just ('(':ys) <- isCommand xs c
= parseCommand n ys c
parseBracket n _ xs c | Just ys <- isCommand xs c
= parseInlineCommand n ys c
parseBracket n c (x:xs) cfg | x `elem` "\"'" && (c || x /= '\'')
= let (str, ys) = parseString x xs
(rest,zs) = parseBracket n True ys cfg
in (addArg (x:str) rest, zs)
parseBracket n c (x:xs) cfg = first (addArg [x])
$ parseBracket n (not (isAlphaNum x) && (c || x /= '\'')) xs cfg
parseCommand, parseInlineCommand :: Int -> String -> [String] -> ([Expr],String)
parseCommand n xs conf = (Cmd cmd args:rest, ws)
where
(cmd, ys) = break (`elem` " )") xs
(args,zs) = parseBracket 1 True (dropWhile (==' ') ys) conf
(rest,ws) = parseBracket n True zs conf
parseInlineCommand n xs conf = (Cmd cmd rest:[], zs)
where
(cmd, ys) = break (`elem` " )") xs
(rest,zs) = parseBracket n True (dropWhile (==' ') ys) conf
parseString :: Char -> String -> (String, String)
parseString _ [] = ([],[])
parseString delim ('\\':x:xs) = first (\ys -> '\\':x:ys) (parseString delim xs)
parseString delim (x:xs)
| delim == x = ([x],xs)
| otherwise = first (x:) (parseString delim xs)
isCommand :: String -> [String] -> Maybe String
isCommand xs = msum . map dropPrefix
where dropPrefix p
| p `isPrefixOf` xs = Just $ drop (length p) xs
| otherwise = Nothing
addArg :: String -> [Expr] -> [Expr]
addArg s (Arg a:es) = Arg (s++a):es
addArg s es = Arg s :es