-- -- | 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 . Return the kind of a type" moduleHelp _ _ = "type . 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 ":[^:]*:[^:]*: ?" notfound = R.mkRegex "Failed to load interface" query_ghci :: String -> String -> LB [String] query_ghci y z = ios (query_ghci' y z)