lambdabot-4.1: Plugin/Instances.hs
{- | A module to output the instances of a typeclass.
Some sample input\/output:
> lambdabot> @instances Monad
> [], ArrowMonad a, WriterT w m, Writer w, ReaderT r m, Reader r,
> StateT s m, State s, RWST r w s m, RWS r w s, ErrorT e m, Either e,
> ContT r m, Cont r, Maybe, ST s, IO
>
> lambdabot> @instances Show
> Float, Double, Integer, ST s a, [a], (a, b, c, d), (a, b, c), (a, b),
> (), Ordering, Maybe a, Int, Either a b, Char, Bool
>
> lambdabot> @instances-importing Text.Html Data.Tree Show
> Float, Double, Tree a, HtmlTable, HtmlAttr, Html, HotLink, Integer,
> ST s a, [a], (a, b, c, d), (a, b, c), (a, b), (), Ordering, Maybe a,
> Int
-}
module Plugin.Instances where
import Text.ParserCombinators.Parsec
import Plugin
type Instance = String
type ClassName = String
type ModuleName = String
PLUGIN Instances
instance Module InstancesModule () where
moduleCmds _ = map fst help
moduleHelp _ = fromJust . flip lookup help
process_ _ "instances" cls = lift $ fetchInstances cls
process_ _ "instances-importing" args = lift $ fetchInstancesImporting args
-- | Lookup table for the help for this module
help :: [(String, String)]
help = [("instances",
"instances <typeclass>. Fetch the instances of a typeclass."),
("instances-importing",
"instances-importing [<module> [<module> [<module...]]] <typeclass>. " ++
"Fetch the instances of a typeclass, importing specified modules first.")]
-- | Nice little combinator used to throw away error messages from an Either
-- and just keep a Maybe indicating the success of the computation.
eitherToMaybe :: Either a b -> Maybe b
eitherToMaybe = either (const Nothing) Just
-- * Parsing
--
-- | Parse an instance declaration. Sample inputs:
--
-- > instance Monad []
-- > instance (Monoid w) => Monad (Writer w)
-- > instance (State s)
--
instanceP :: ClassName -> CharParser st Instance
instanceP cls = do string "instance "
try constrained <|> unconstrained
skipMany space
-- break on the "imported from" comment or a newline. use
-- return so it typechecks.
let end = try (string "--") <|> (eof >> return " ")
anyChar `manyTill` end
where constrained = noneOf "=" `manyTill` string ("=> " ++ cls)
unconstrained = string cls
-- | Wrapper for the instance parser.
parseInstance :: ClassName -> String -> Maybe Instance
parseInstance cls = fmap dropSpace . eitherToMaybe
. parse (instanceP cls) "GHCi output"
-- | Split the input into a list of the instances, then run each instance
-- through the parser. Collect successes.
getInstances :: String -> ClassName -> [Instance]
getInstances s cls
| not classFound -- can't trust those dodgy folk in #haskell
= ["Couldn't find class `"++cls++"'. Try @instances-importing"]
| otherwise = sort $ mapMaybe doParse (tail splut)
where classFound = matches' (regex' $ "class.*" ++ cls ++ ".*where") s
splut = split "instance" s -- splut being the past participle
-- of 'to split', obviously. :)
notOperator = all (\c -> or
[ isAlpha c,
isSpace c,
c `elem` "()" ])
unbracket str | head str == '(' && last str == ')' &&
all (/=',') str && notOperator str && str /= "()" =
init $ tail str
| otherwise = str
doParse = fmap unbracket . parseInstance cls . ("instance"++)
-- * Delegation; interface with GHCi
--
-- | The standard modules we ask GHCi to load.
stdMdls :: [ModuleName]
stdMdls = controls
where monads = map ("Monad."++)
[ "Cont", "Error", "Fix", "Reader", "RWS", "ST",
"State", "Trans", "Writer" ]
controls = map ("Control." ++) $ monads ++ ["Arrow"]
-- | Main processing function for \@instances. Takes a class name and
-- return a list of lines to output (which will actually only be one).
fetchInstances :: ClassName -> LB [String]
fetchInstances cls = do
ios (fetchInstances' cls stdMdls)
-- | Main processing function for \@instances-importing. Takes the args, which
-- are words'd. The all but the last argument are taken to be the modules to
-- import, and the last is the typeclass whose instances we want to print.
fetchInstancesImporting :: String -> LB [String]
fetchInstancesImporting args = ios (fetchInstances' cls mdls)
where args' = words args
cls = last args'
mdls = nub $ init args' ++ stdMdls
-- | Interface with GHCi to get the input for the parser, then send it through
-- the parser.
fetchInstances' :: String -> [ModuleName] -> IO String
fetchInstances' cls mdls = do
let s = unlines [cxt, command]
(out, err, _) <- popen (ghci config) ["-ignore-dot-ghci","-fglasgow-exts"] $
Just s
let is = getInstances out cls
return $ if null is
then err
else concatWith ", " is
where cxt = ":m " ++ unwords mdls
command = ":i " ++ cls