packages feed

lambdabot-4.1: Plugin/Djinn.hs

--
-- Copyright (c) 2005 Donald Bruce Stewart - http://www.cse.unsw.edu.au/~dons
-- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html)
-- 
-- Written: Mon Dec 12 10:16:56 EST 2005
--

--
-- | A binding to Djinn.
--
module Plugin.Djinn (theModule) where

import Plugin
import System.Directory
import Data.Char
import Data.List
import Data.Maybe

import qualified Text.Regex as R

PLUGIN Djinn

-- | We can accumulate an interesting environment
type DjinnEnv = ([Decl] {- prelude -}, [Decl])
type Decl = String

instance Module DjinnModule DjinnEnv where

        moduleHelp _ s = case s of
            "djinn"     -> "djinn <type>.\nGenerates Haskell code from a type.\n" ++
                           "http://darcs.augustsson.net/Darcs/Djinn"
            "djinn-add" -> "djinn-add <expr>.\nDefine a new function type or type synonym"
            "djinn-del" -> "djinn-del <ident>.\nRemove a symbol from the environment"
            "djinn-clr" -> "djinn-clr.\nReset the djinn environment"
            "djinn-env" -> "djinn-env.\nShow the current djinn environment"
            "djinn-names" -> "djinn-names.\nShow the current djinn environment, compactly."
            "djinn-ver" -> "djinn-ver.\nShow current djinn version"

        moduleCmds      _ = ["djinn"
                            ,"djinn-add"
                            ,"djinn-del"
                            ,"djinn-env"
                            ,"djinn-names"
                            ,"djinn-clr" 
                            ,"djinn-ver"]

        moduleSerialize _ = Nothing -- Just listSerial

        -- this means djinn better be visible at boot time
        moduleDefState  _ = do
            -- check that's djinn's there, otherwise don't bother 
            d <- io $ doesFileExist binary
            if not d
                then do io $ debugStrLn "Plugin.Djinn: couldn't find djinn binary"
                        return ([],[])
                else do st <- io $ getDjinnEnv ([],[]) -- get the prelude
                        return (either (const []) snd{-!-} st, [])

        -- rule out attempts to do IO, if these get into the env,
        -- they'll be executed by djinn
        process_ _ _ s | cmd  `matches'` s = end
          where end  = return ["Invalid command"]
                cmd  = regex' "^ *:"

        -- Normal commands
        process_ _ "djinn" s = do
                (_,env) <- readMS
                e       <- io $ djinn env $ ":set +sorted" <$> "f ?" <+> dropForall s
                return $ either id (tail . lines) e
            where
            dropForall t
                | Just (_, _, x, _) <- R.matchRegexAll re t = x
                | otherwise = t
            re = regex' "^forall [[:alnum:][:space:]]+\\."

        -- Augment environment. Have it checked by djinn.
        process_ _ "djinn-add"  s = do
            (p,st)  <- readMS
            est     <- io $ getDjinnEnv $ (p, dropSpace s : st)
            case est of
                Left e     -> return [head e]
                Right st'' -> modifyMS (const st'') >> return []

        -- Display the environment
        process_ _ "djinn-env"  _ = do
            (prelude,st) <- readMS
            return $ prelude ++ st

        -- Display the environment's names (quarter-baked)
        process_ _ "djinn-names"  _ = do
            (prelude,st) <- readMS
            let names = concat $ intersperse " " $ concatMap extractNames $ prelude ++ st
            return [names]
          where extractNames = filter (isUpper . head) . unfoldr (\x -> case x of _:_ -> listToMaybe (lex x); _ -> Nothing)

        -- Reset the env
        process_ _ "djinn-clr" _ = modifyMS (flip (,) [] . fst) >> return []

        -- Remove sym from environment. We let djinn do the hard work of
        -- looking up the symbols.
        process_ _ "djinn-del" s =  do
            (_,env) <- readMS
            eenv <- io $ djinn env $ ":delete" <+> dropSpace s <$> ":environment"
            case eenv of
                Left e     -> return [head e]
                Right env' -> do 
                    modifyMS $ \(prel,_) ->
                        (prel,filter (\p -> p `notElem` prel) . nub . lines $ env')
                    return []

        -- Version number
        process_ _ "djinn-ver"  _ = do
            (out,_,_) <- io $ popen binary [] (Just ":q")
            let v = dropNL . clean_ . drop 18 . head . lines $ out
            return [v]

------------------------------------------------------------------------

-- | Should be built inplace by the build system
binary :: String
binary = "./djinn"

-- | Extract the default environment
getDjinnEnv :: DjinnEnv -> IO (Either [String] DjinnEnv)
getDjinnEnv (prel,env') = do
    env <- djinn env' ":environment"
    case env of
        Left e  -> return $ Left e
        Right o -> do let new = filter (\p -> p `notElem` prel) . nub .  lines $ o
                      return $ Right (prel, new)

-- | Call the binary:

djinn :: [Decl] -> String -> IO (Either [String] String)
djinn env' src = do
    let env = concat . intersperse "\n" $ env'
    (out,_,_) <- popen binary [] (Just (env <$> src <$> ":q"))
    let o = dropNL . clean_ . unlines . init . drop 2 . lines $ out
    return $ case () of {_
        | failed `matches'` o -> Left (lines o)
        | unify  `matches'` o -> Left (lines o)
        | otherwise                          -> Right o
    }
    where
        failed = regex' "Cannot parse command"
        unify  = regex' "cannot be realized"

--
-- Clean up djinn output
--
clean_ :: String -> String
clean_ s | Just (a,_,b,_) <- prompt `R.matchRegexAll` s = a ++ clean_ b
         | otherwise      = s
    where prompt = regex' "Djinn>[^\n]*\n"