packages feed

hscope-0.1: t/files/Simple.hs

{-# LANGUAGE BangPatterns #-}
module Main (main) where

import System.Console.GetOpt
import System.Environment (getArgs)
import Database.CDB.Write
import Database.CDB.Read
import Database.CDB.Packable
import Language.Haskell.Exts (parseFile)
import Language.Haskell.Exts.Parser (ParseResult(ParseOk))
import Control.Monad.Trans (liftIO)
import Control.Monad (when)
import Data.List (foldl')
import Data.Maybe
import Data.IORef
import Data.Generics.Zipper.Bubble (bubble, Callback(..))
import Language.Haskell.Exts.Syntax (Decl(PatBind, FunBind), Name(Ident)
                        , Pat(PVar), SrcLoc(..), Match(Match), Exp(..), QName(UnQual))
import Data.Serialize
import qualified Data.ByteString.Char8 as B
import qualified Data.Vector as V
import Control.Applicative ((<$>), (<*>))
import System.IO (hFlush, stdout)

type Unused = Int

testSymbol :: Eq a => [a] -> [a]
testSymbol = groupBy (==)

data Flag = Build Bool | File FilePath | Line deriving (Show)

data Config =
    Config { cBuild :: Bool, cFile :: FilePath, cLine :: Bool } deriving Show

data IType = Definition | Call deriving (Enum, Show, Eq)
data Info = Info IType String Int B.ByteString deriving Show

instance Serialize Info where
    put (Info ity file line bs) = do
        put $ fromEnum ity 
        put file
        put line
        put bs

    get = do
        ity <- fmap toEnum get
        Info ity <$> get <*> get <*> get

instance Unpackable Info where
    unpack = either error id . decode

options :: [OptDescr Flag]
options = [
            Option ['b'] [ "build" ] (NoArg $ Build True) "Build hscope database"
            , Option ['d'] [ "dont-update" ] (NoArg $ Build False) "Do not update the cross-reference"
            , Option ['l'] [ "line" ] (NoArg $ Line) "Line-oriented interface"
            , Option ['f'] [ "file" ] (ReqArg File "REFFILE") $
                "Use REFFILE as the cross-reference file name instead of the default \"hscope.out\""
          ]

parseFlags :: [Flag] -> Config
parseFlags = foldl' go $ Config False "hscope.out" False where
    go c (Build b) = c { cBuild = b }
    go c (File f) = c { cFile = f }
    go c Line = c { cLine = True }

addInfo :: V.Vector B.ByteString -> SrcLoc -> IType -> String -> CDBMake
addInfo vec (SrcLoc file line _) ity f = cdbAdd f $ encode $ Info ity file line (vec V.! (line - 1))

handleDefinitions :: V.Vector B.ByteString -> Decl -> CDBMake
handleDefinitions vec = go where
    go (PatBind src (PVar (Ident f)) _ _ _) = addDef f src
    go (FunBind ((Match src (Ident f) _ _ _ _):_)) = addDef f src
    go _ = return ()
    addDef f src = addInfo vec src Definition f

handleSrcLoc :: IORef SrcLoc -> SrcLoc -> CDBMake
handleSrcLoc ior = liftIO . writeIORef ior

handleCalls :: IORef SrcLoc -> V.Vector B.ByteString -> Exp -> CDBMake
handleCalls ior vec (Var (UnQual (Ident f))) = do
    src <- liftIO $ readIORef ior
    addInfo vec src Call f
handleCalls _ _ _ = return ()

buildOne :: FilePath -> CDBMake
buildOne f = do
    ParseOk modul <- liftIO $ parseFile f
    lns <- V.fromList <$> B.lines <$> (liftIO $ B.readFile f)
    sior <- liftIO $ newIORef $ SrcLoc f 1 1
    bubble [Callback $ handleSrcLoc sior, Callback $ handleDefinitions lns, Callback $ handleCalls sior lns] [] modul

runLines :: CDB -> IO ()
runLines cdb = do
    putStr ">> "
    hFlush stdout
    !l <- getLine
    case l of
        ('q':_) -> return ()
        ('1':str) -> findInfo Definition cdb str
        ('3':str) -> findInfo Call cdb str
        _ -> runLines cdb

findInfo :: IType -> CDB -> String -> IO ()
findInfo ity cdb str = do
    let infos = catMaybes $ map go $ cdbGetAll cdb str
    putStrLn $ "cscope: " ++ (show $ length infos) ++ " lines"
    mapM_ putStrLn infos
    runLines cdb
    where go (Info t file lno line) | t == ity = Just $ file ++ " " ++ str
                                        ++ " " ++ show lno ++ " " ++ B.unpack line
                                    | otherwise = Nothing

main :: IO ()
main = do
    (flags, rest, _) <- fmap (getOpt Permute options) getArgs
    let cfg = parseFlags flags
    when (cBuild cfg) $ cdbMake "hscope.out" $ mapM_ buildOne rest
    when (cLine cfg) $ (cdbInit $ cFile cfg) >>= runLines