packages feed

lambdabot-4.1: Plugin/Type.hs

--
-- |   The Type Module - another progressive plugin for lambdabot
--
-- pesco hamburg 2003-04-05
--
--     Greetings reader,
--
--     whether you're a regular follower of the series or dropping in for
--     the first time, let me present for your pleasure the Type Module:
--
--     One thing we enjoy on #haskell is throwing function types at each
--     other instead of spelling out tiresome monologue about arguments
--     or return values. Unfortunately such a toss often involves a local
--     lookup of the type signature in question because one is seldom
--     sure about the actual argument order.
--
--     Well, what do you know, this plugin enables lambdabot to automate
--     that lookup for you and your fellow lambda hackers.
--
module Plugin.Type where

import Plugin
import qualified Text.Regex as R

PLUGIN Type

instance Module TypeModule () where
     moduleCmds        _  = ["type", "kind"]
     moduleHelp _ "kind"  = "kind <type>. Return the kind of a type"
     moduleHelp _ _       = "type <expr>. Return the type of a value"
     process_ _ s expr = lift $ flip query_ghci expr $ case s of
                                                    "type" -> ":t"
                                                    "kind" -> ":k"

     contextual  _ _ _ text = case () of
        _| ":t " `isPrefixOf` text -> lift $ query_ghci ":t" expr
         | ":k " `isPrefixOf` text -> lift $ query_ghci ":k" expr
         | otherwise               -> return []
         where expr = drop 3 text

--     In accordance with the KISS principle, the plan is to delegate all
--     the hard work! To get the type of foo, pipe

command :: [Char] -> [Char] -> [Char]
command cmd foo = cmd ++ " " ++ foo

--     into hugs and send any line matching

signature_regex :: Regex
signature_regex
    = R.mkRegexWithOpts
      "^(\\*?[A-Z][_a-zA-Z0-9]*(\\*?[A-Z][_a-zA-Z0-9]*)*>)? *(.*[       -=:].*)"
      True True

--
-- Rather than use subRegex, which is new to 6.4, we can remove comments
-- old skool style.
-- Former regex for this:
--    "(\\{-[^-]*-+([^\\}-][^-]*-+)*\\}|--.*$)"
--
stripComments :: String -> String
stripComments []          = []
stripComments ('\n':_)    = [] -- drop any newwline and rest. *security*
stripComments ('-':'-':_) = []  -- 
stripComments ('{':'-':cs)= stripComments (go 1 cs)
stripComments (c:cs)      = c : stripComments cs

-- Adapted from ghc/compiler/parser/Lexer.x
go :: Int -> String -> String
go 0 xs         = xs
go _ ('-':[])   = []   -- unterminated
go n ('-':x:xs) 
    | x == '}'  = go (n-1) xs
    | otherwise = go n (x:xs)
go _ ('{':[])   = []  -- unterminated
go n ('{':x:xs)
    | x == '-'  = go (n+1) xs
    | otherwise = go n (x:xs)
go n (_:xs) = go n xs
go _ _      = []   -- unterminated

--     through IRC.

--
--     We filtering out the lines that match our regex,
--     selecting the last subset match on each matching line before finally concatting
--     the whole lot together again.
--
extract_signatures :: String -> String
extract_signatures output
        = reverse . removeExp . reverse .
          unwords . map (dropWhile isSpace . expandTab) .
          mapMaybe ((>>= last') . R.matchRegex signature_regex) .
          lines $ output
        where
        last' [] = Nothing
        last' xs = Just $ last xs

        removeExp :: String -> String
        removeExp [] = []
        removeExp xs = removeExp' 0 xs
        
        removeExp' :: Int -> String -> String
        removeExp' 0 (' ':':':':':' ':_) = []
        removeExp' n ('(':xs)            = '(':removeExp' (n+1) xs
        removeExp' n (')':xs)            = ')':removeExp' (n-1) xs
        removeExp' n (x  :xs)            = x  :removeExp'  n    xs
        removeExp' _ []                  = error "invalid ghci output: no type signature"

--
--     With this the command handler can be easily defined using popen:
--
-- TODO, bring more modules into scope.
--
query_ghci' :: String -> String -> IO String
query_ghci' cmd expr = do
       (output, errors, _) <- popen (ghci config) ["-v0","-fglasgow-exts","-fno-th","-iscripts"]
                                       (Just (context ++ command cmd (stripComments expr)))
       let ls = extract_signatures output
       return $ if null ls
                then unlines . take 3 . filter (not . null) . map cleanRE2 .
                     lines . expandTab . cleanRE . filter (/='\r') $ errors -- "bzzt" 
                else ls
  where
     context = ":l L\n" ++ (concatMap (\m -> ":m + "++m++"\n") $
                    prehier ++ datas ++ qualifieds ++ controls ++ other ++ extras)

     other      =
        ["Text.Printf"
        ,"Text.PrettyPrint.HughesPJ"]

     prehier    = ["Char", "List", "Maybe", "Numeric", "Random" ]

     qualifieds = []

     datas   = map ("Data." ++)
        ["Array"
        ,"Bits"
        ,"Bool"
        ,"Char"
        ,"Complex"
        ,"Dynamic"
        ,"Either"
        ,"Eq"
        ,"Fixed"
    --  ,"Foldable"
    --  ,"Function"
    --  ,"Generics"
        ,"Graph"
        ,"Int"
    --  ,"IntMap"
    --  ,"IntSet"
        ,"Ix"
        ,"List"
    --  ,"Map"
        ,"Maybe"
        ,"Monoid"
        ,"Ord"
        ,"Ratio"
    --  ,"Set"
        ,"Tree"
        ,"Tuple"
        ,"Typeable"
        ,"Word"
        ]
     extras   = [] -- ["L"]

     controls = map ("Control." ++)
        ["Monad"
        ,"Monad.Cont"
        ,"Monad.Error"
        ,"Monad.Identity"
        ,"Monad.List"
        ,"Monad.RWS"
        ,"Monad.Reader"
        ,"Monad.State"
        ,"Monad.Trans"
        ,"Monad.Writer"
        ,"Monad.Fix"
        ,"Monad.Instances"
        ,"Applicative"
        ,"Arrow"
    --  ,"Arrow.Transformer"
    --  ,"Arrow.Transformer.All"
    --  ,"Arrow.Operations"
        ,"Parallel"
        ,"Parallel.Strategies"
        ]

     cleanRE, cleanRE2 :: String -> String
     cleanRE s
        | Just _         <- notfound `R.matchRegex` s = "Couldn\'t find qualified module."
        | Just (_,_,b,_) <- ghci_msg `R.matchRegexAll`  s = b
        | otherwise      = s
     cleanRE2 s
        | Just (_,_,b,_) <- ghci_msg `R.matchRegexAll`  s = b
        | otherwise      = s
     ghci_msg = R.mkRegex "<interactive>:[^:]*:[^:]*: ?"
     notfound = R.mkRegex "Failed to load interface"

query_ghci :: String -> String -> LB [String]
query_ghci y z = ios (query_ghci' y z)