{- | 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 Lambdabot.Plugin.Haskell.Instances (instancesPlugin) where import Text.ParserCombinators.Parsec import Lambdabot.Config.Haskell import Lambdabot.Plugin import Lambdabot.Util import Lambdabot.Plugin.Haskell.Eval (findL_hs) import Control.Applicative ((*>)) import Control.Monad import Data.Char import Data.List import Data.List.Split import Data.Maybe import System.FilePath import System.Process import Text.Regex.TDFA type Instance = String type ClassName = String type ModuleName = String instancesPlugin :: Module () instancesPlugin = newModule { moduleCmds = return [ (command "instances") { help = say "instances . Fetch the instances of a typeclass." , process = fetchInstances >=> say } , (command "instances-importing") { help = say $ "instances-importing [ [ [. " ++ "Fetch the instances of a typeclass, importing specified modules first." , process = fetchInstancesImporting >=> say } ] } -- | 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 = string "instance " *> (try constrained <|> unconstrained) *> skipMany space *> anyChar `manyTill` end where constrained = noneOf "=" `manyTill` string ("=> " ++ cls) unconstrained = string cls -- break on the "imported from" comment or a newline. end = void (try (string "--")) <|> eof -- | Wrapper for the instance parser. parseInstance :: ClassName -> String -> Maybe Instance parseInstance cls = fmap (strip isSpace) . 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 = s =~ ("class.*" ++ cls ++ ".*where") splut = splitOn "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 :: MonadLB m => ClassName -> m String fetchInstances cls = 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 :: MonadLB m => String -> m String fetchInstancesImporting args = 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' :: MonadLB m => String -> [ModuleName] -> m String fetchInstances' cls mdls = do load <- findL_hs let s = unlines $ map unwords [ [":l", load] , ":m" : "+" : mdls , [":i", cls] ] ghci <- getConfig ghciBinary (_, out, err) <- io $ readProcessWithExitCode ghci ["-ignore-dot-ghci","-fglasgow-exts"] s let is = getInstances out cls return $ if null is then err else intercalate ", " is