hscope (empty) → 0.1
raw patch · 14 files changed
+558/−0 lines, 14 filesdep +Unixutilsdep +basedep +bytestringsetup-changed
Dependencies added: Unixutils, base, bytestring, cereal, directory, haskell-src-exts, hs-cdb, mtl, process, test-simple, uniplate, vector
Files
- COPYING +25/−0
- HScope.hs +194/−0
- Setup.hs +2/−0
- hscope.cabal +50/−0
- t/Build.hs +129/−0
- t/files/Arrow.hs +6/−0
- t/files/CPP.hs +8/−0
- t/files/CPP2.hs +7/−0
- t/files/Simple.hs +119/−0
- t/files/a.c +7/−0
- t/files/a/a.h +1/−0
- t/files/a/c.h +3/−0
- t/files/b/b.h +1/−0
- t/files/garrs.hs +6/−0
+ COPYING view
@@ -0,0 +1,25 @@+Copyright (c) 2013 Boris Sukholitko+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. The names of the authors may not be used to endorse or promote products+ derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ HScope.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE BangPatterns #-}+module Main (main) where++import System.Console.GetOpt+import System.Environment (getArgs, getProgName)+import Database.CDB.Write+import Database.CDB.Read+import Database.CDB.Packable+import Language.Haskell.Exts.Annotated+import Control.Monad.Trans (liftIO)+import Control.Monad (void, when)+import System.Process (readProcess)+import Data.List (foldl', intercalate, isInfixOf)+import Data.Maybe+import Data.Serialize+import qualified Data.ByteString.Char8 as B+import qualified Data.Vector as V+import Control.Applicative ((<$>), (<*>))+import System.IO (hFlush, stdout)+import Data.Generics.Uniplate.Data (transformBiM)+import Data.Data++data Flag = Build Bool | File FilePath | Line | Query String | CPPInclude String deriving (Show)++data Config = Config { cBuild :: Bool, cFile :: FilePath, cLine :: Bool+ , cQuery :: Maybe String, cCPPIncludes :: [String] } deriving Show++data IType = Definition | Call deriving (Enum, Show, Eq)+data Info = Info IType String Int B.ByteString deriving Show+type Lines = V.Vector (Int, B.ByteString)++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\""+ , Option ['1'] [ "definition" ] (ReqArg (Query . ('1':)) "SYMBOL") $ "Find the definition of the SYMBOL"+ , Option ['3'] [ "callers" ] (ReqArg (Query . ('3':)) "SYMBOL") $ "Find SYMBOLs calling this SYMBOL"+ , Option ['4'] [ "text" ] (ReqArg (Query . ('4':)) "TEXT") $ "Find TEXT in files"+ , Option ['7'] [ "file" ] (ReqArg (Query . ('7':)) "FILE") $ "Find files matching FILE"+ , Option ['I'] [ "cpp-include" ] (ReqArg CPPInclude "DIRECTORY") $ "Include path for CPP preprocessor"+ ]++parseFlags :: [Flag] -> Config+parseFlags = foldl' go $ Config False "hscope.out" False Nothing [] where+ go c (Build b) = c { cBuild = b }+ go c (File f) = c { cFile = f }+ go c Line = c { cLine = True }+ go c (Query q) = c { cQuery = Just q }+ go c (CPPInclude i) = c { cCPPIncludes = i:(cCPPIncludes c) }++addInfo :: Lines -> IType -> Name SrcSpanInfo -> CDBMake+addInfo vec ity n = cdbAdd f $ encode $ Info ity (fileName src) (fst lp) (snd lp)+ where l = startLine src+ lp = maybe (error $ "Bad line: " ++ show n ++ ", " ++ show vec) id $ vec V.!? (l - 1)+ (src, f) = case n of+ (Ident src' f') -> (src', f')+ (Symbol src' f') -> (src', f')++traverseAST :: (Data b, Monad m, Data a, Functor m) => (a -> m ()) -> b -> m ()+traverseAST cb = void . transformBiM go where go v = cb v >> return v++handleDefinitions :: Lines -> Decl SrcSpanInfo -> CDBMake+handleDefinitions vec = go where+ go (PatBind _ (PVar _ n) _ _ _) = addDef n+ go (FunBind _ ((Match _ n _ _ _):_)) = addDef n+ go _ = return ()+ addDef = addInfo vec Definition++handleCalls :: Lines -> QName SrcSpanInfo -> CDBMake+handleCalls vec (UnQual _ n) = addInfo vec Call n+handleCalls _ _ = return ()++handleConstructors :: Lines -> ConDecl SrcSpanInfo -> CDBMake+handleConstructors vec (ConDecl _ n _) = addInfo vec Definition n+handleConstructors vec (RecDecl _ n recs) = do+ addInfo vec Definition n+ mapM_ go recs+ where go (FieldDecl _ ns _) = mapM_ (addInfo vec Definition) ns+handleConstructors _ c = error $ "handleConstructors " ++ show c++handleDeclarations :: Lines -> DeclHead SrcSpanInfo -> CDBMake+handleDeclarations vec (DHead _ n _) = addInfo vec Definition n+handleDeclarations _ c = error $ "handleDeclarations " ++ show c++mapLines :: Show a => FilePath -> [a] -> [String] -> [a]+mapLines f to = reverse . snd . foldl' go ((to, True), []) where+ go ((xs@(x:_), _), res) ('#':str) = ((xs, isInfixOf f str), x:res)+ go (((x:xs), True), res) _ = ((xs, True), x:res)+ go ((xs@(x:_), False), res) _ = ((xs, False), x:res)+ go t l = error $ "mapLines: " ++ show t ++ " at " ++ l++preprocess :: [String] -> FilePath -> IO (String, Lines)+preprocess idirs f = do+ origs <- B.lines <$> (liftIO $ B.readFile f)+ flns <- fmap lines $ readProcess "cpp" (incs ++ [ "-traditional-cpp", f ]) ""+ return (fconts flns, V.fromList $ mapLines f (zip [1 ..] origs) flns)+ where fconts = intercalate "\n" . map cmnt+ cmnt ('#':_) = ""+ cmnt x = x+ incs = concatMap (\i -> [ "-I", i ]) idirs++parseCurrentFile :: FilePath -> String -> Either String (Module SrcSpanInfo)+parseCurrentFile f fstr = go [] where+ go exts = case parseFileContentsWithMode (pmode exts) fstr of+ ParseOk m -> Right m+ ParseFailed src str -> let (ext, rest) = break (== ' ') str+ in if rest == " is not enabled"+ then go ((classifyExtension ext):exts)+ else Left $ str ++ " for " ++ f ++ " at " ++ show src+ pmode exts = defaultParseMode { fixities = Just baseFixities+ , extensions = exts+ , parseFilename = f }++buildOne :: Config -> FilePath -> CDBMake+buildOne cfg f = do+ cdbAdd "0_hs_files" f+ (fstr, lns) <- liftIO $ preprocess (cCPPIncludes cfg) f+ either (liftIO . putStrLn) (go lns) $ parseCurrentFile f fstr+ where go lns modul = do+ traverseAST (handleDefinitions lns) modul+ traverseAST (handleCalls lns) modul+ traverseAST (handleConstructors lns) modul+ traverseAST (handleDeclarations lns) modul+ -- liftIO $ putStrLn $ show modul++handleQuery :: CDB -> String -> IO ()+handleQuery cdb ('1':str) = findInfo Definition cdb str+handleQuery cdb ('3':str) = findInfo Call cdb str+handleQuery cdb ('4':str) = do+ let files = cdbGetAll cdb "0_hs_files"+ lns <- fmap lines $ readProcess "grep" ("-n":"-H":str:files) ""+ outputLines $ map go lns+ where go l = let (f, rest1) = break (':' ==) l+ (n, rest2) = break (':' ==) $ drop 1 rest1+ in f ++ " <unknown> " ++ n ++ " " ++ drop 1 rest2 +handleQuery cdb ('7':str) = do+ let files = cdbGetAll cdb "0_hs_files"+ outputLines $ map go $ filter (isInfixOf str) files+ where go f = f ++ " <unknown> 1 <unknown>"+handleQuery _ _ = return ()++runLines :: CDB -> IO ()+runLines cdb = do+ putStr ">> "+ hFlush stdout+ !l <- getLine+ handleQuery cdb l+ case l of+ ('q':_) -> return ()+ _ -> runLines cdb++outputLines :: [String] -> IO ()+outputLines infos = do+ putStrLn $ "cscope: " ++ (show $ length infos) ++ " lines"+ mapM_ putStrLn infos++findInfo :: IType -> CDB -> String -> IO ()+findInfo ity cdb str = outputLines $ catMaybes $ map go $ cdbGetAll cdb str+ 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 (cFile cfg) $ mapM_ (buildOne cfg) rest+ if null flags then usage else go cfg+ where usage = do+ pn <- getProgName+ putStrLn $ usageInfo ("Usage: " ++ pn ++ " [OPTION...] files...") options+ go cfg = do+ cdb <- cdbInit $ cFile cfg+ maybe (return ()) (handleQuery cdb) $ cQuery cfg+ when (cLine cfg) $ runLines cdb
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hscope.cabal view
@@ -0,0 +1,50 @@+Name: hscope+Version: 0.1+License: BSD3+License-File: COPYING+Category: source-tools+Synopsis: cscope like browser for Haskell code+Description:+ hscope is a partial cscope line oriented mode reimplementation for Haskell code.++ Currently it supports finding the definition and callers of the function, types.+ Also works searching for files and in files.+ + See hscope --help for available options.++ Being cscope-compatible gives Vim integration for free. Use :set cscopeprg+ to configure hscope as cscope replacement for haskell files.++Cabal-Version: >= 1.10+Build-Type: Simple+Author: Boris Sukholitko <boriss@gmail.com>+Copyright: Boris Sukholitko <boriss@gmail.com>+Maintainer: Boris Sukholitko <boriss@gmail.com>+homepage: https://github.com/bosu/hscope+Stability: Experimental+Extra-Source-Files:+ t/files/Arrow.hs+ t/files/a/c.h+ t/files/a/a.h+ t/files/b/b.h+ t/files/CPP2.hs+ t/files/garrs.hs+ t/files/Simple.hs+ t/files/CPP.hs+ t/files/a.c++Executable hscope+ Main-Is: HScope.hs+ GHC-Options: -Wall+ Build-Depends: base (< 5), hs-cdb, haskell-src-exts, mtl, uniplate, cereal+ , vector, bytestring, process+ Default-Language: Haskell2010++test-suite Build+ type: exitcode-stdio-1.0+ build-depends: base, test-simple, process, directory, Unixutils, mtl+ ghc-options: -Wall+ hs-source-dirs: t+ main-is: Build.hs+ Default-Language: Haskell2010+
+ t/Build.hs view
@@ -0,0 +1,129 @@+{-# OPTIONS -fno-warn-unused-do-bind -XBangPatterns #-}+module Main (main) where++import Test.Simple+import System.Unix.Directory (withTemporaryDirectory)+import System.Directory (canonicalizePath, doesFileExist)+import System.Process (system, readProcess, runInteractiveProcess, waitForProcess)+import System.Exit (ExitCode(ExitSuccess))+import Control.Monad.Trans (liftIO)+import System.IO (hGetContents, hPutStrLn, hFlush, hSetBinaryMode)++hscopeInteract :: FilePath -> FilePath -> [String] -> IO (ExitCode, String)+hscopeInteract hpath td cmds = do+ (inp, out, _, ph1) <- runInteractiveProcess hpath [ "-dl", "-f", td ++ "/hscope.out" ]+ Nothing Nothing+ hSetBinaryMode inp False+ mapM_ (go inp) $ cmds ++ [ "q" ]++ !l1 <- hGetContents out+ ec2 <- waitForProcess ph1+ return (ec2, l1)+ where go inp cmd = hPutStrLn inp cmd >> hFlush inp++main :: IO ()+main = withTemporaryDirectory "/tmp/hscope_test_XXXXXX" $ \td -> testSimpleMain $ do+ plan 42+ hpath <- liftIO $ canonicalizePath "./dist/build/hscope/hscope"+ tfile <- liftIO $ canonicalizePath "./t/files/Simple.hs"+ ec1 <- liftIO $ system $ "cd " ++ td ++ " && " ++ hpath ++ " -b " ++ tfile+ res1 <- liftIO $ readProcess "cdb" [ "-l", td ++ "/hscope.out" ] ""+ is ec1 ExitSuccess+ like res1 "main"++ res2 <- liftIO $ readProcess "cdb" [ "-q", td ++ "/hscope.out", "main" ] ""+ like res2 "main = "++ res3 <- liftIO $ readProcess "cdb" [ "-q", td ++ "/hscope.out", "findInfo" ] ""+ like res3 "-> findInfo "++ res4 <- liftIO $ readProcess "cdb" [ "-q", td ++ "/hscope.out", "options" ] ""+ like res4 "Permute options"++ res5 <- liftIO $ readProcess "cdb" [ "-q", td ++ "/hscope.out", "runLines" ] ""+ like res5 ">>= runLines"++ (ec2, l1) <- liftIO $ hscopeInteract hpath td [ "1main", "1findInfo" ]+ is ec2 ExitSuccess+ like l1 ">> "+ like l1 tfile+ like l1 "main ="+ like l1 "findInfo ity cdb str ="++ (ec3, l2) <- liftIO $ hscopeInteract hpath td [ "3findInfo", "1Build", "1Config" ]+ is ec3 ExitSuccess+ like l2 "-> findInfo "+ like l2 "2 lines"+ like l2 "Build Bool"+ like l2 " Config {"++ (ec4, l3) <- liftIO $ hscopeInteract hpath td [ "1Flag", "1Unused", "1cBuild" ]+ is ec4 ExitSuccess+ like l3 "data Flag"+ like l3 "type Unused"+ like l3 "cBuild :: Bool"++ (ec5, l4) <- liftIO $ hscopeInteract hpath td [ "3cBuild", "3Flag", "3==" ]+ is ec5 ExitSuccess+ like l4 "cBuild cfg"+ like l4 "parseFlags ::"+ like l4 "groupBy (==)"++ res6 <- liftIO $ readProcess hpath [ "-b", "-f", td ++ "/foo.out", "t/files/a.c" ] ""+ like res6 "Parse error: 0"+ like res6 "a.c"++ dfoo <- liftIO $ doesFileExist $ td ++ "/foo.out"+ is dfoo True++ res7 <- liftIO $ readProcess hpath [ "-b", "-f", td ++ "/foo.out"+ , "t/files/Arrow.hs", "t/files/CPP.hs" ] ""+ is res7 ""++ res8 <- liftIO $ readProcess hpath [ "-d", "-f", td ++ "/foo.out"+ , "-1", "doSth" ] ""+ like res8 "doSth ="++ res9 <- liftIO $ readProcess hpath [ "-d", "-f", td ++ "/foo.out"+ , "-1", "foo" ] ""+ like res9 "foo 6 bar = id"+ unlike res9 "warning"++ res9_1 <- liftIO $ readProcess hpath [ "-d", "-f", td ++ "/foo.out"+ , "-7", "Ar" ] ""+ is res9_1 "cscope: 1 lines\nt/files/Arrow.hs <unknown> 1 <unknown>\n"++ res10 <- liftIO $ readProcess hpath [ "-d", "-f", td ++ "/hscope.out"+ , "-3", "groupBy" ] ""+ like res10 "groupBy (==)"++ res10_1 <- liftIO $ readProcess hpath [ "-d", "-f", td ++ "/hscope.out"+ , "-3", "Build" ] ""+ like res10_1 "NoArg $ Build False"+ like res10_1 "Build b) ="++ res11 <- liftIO $ readProcess hpath [ "-d", "-f", td ++ "/hscope.out"+ , "-4", "= do" ] ""+ like res11 "files/Simple.hs <unknown> 115 main = do"++ res12 <- liftIO $ readProcess hpath [ "-b", "-f", td ++ "/foo.out"+ , "-I", "t/files/a", "-I", "t/files/b"+ , "t/files/CPP2.hs" ] ""+ is res12 ""++ res13 <- liftIO $ readProcess hpath [ "-d", "-f", td ++ "/foo.out"+ , "-1", "bar" ] ""+ like res13 "bar 7 bar = id"++ res14 <- liftIO $ readProcess hpath [ "-b", "-f", td ++ "/foo.out"+ , "t/files/garrs.hs" ] ""+ is res14 ""++ res15 <- liftIO $ readProcess hpath [ "--help" ] ""+ like res15 $ "Usage: hscope [OPTION...] files..."+ like res15 "-b"++ res16 <- liftIO $ readProcess hpath [] ""+ is res16 res15++ return ()
+ t/files/Arrow.hs view
@@ -0,0 +1,6 @@+module Main+where++import Control.Arrow++doSth = (+ 1) . head &&& tail
+ t/files/CPP.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE CPP #-}++#define bar foo++bar :: Int -> Int+bar = id++baz' = 12
+ t/files/CPP2.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE CPP #-}++#include "a.h"+#include "b.h"++bar :: Int -> Int+bar = id
+ t/files/Simple.hs view
@@ -0,0 +1,119 @@+{-# 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
+ t/files/a.c view
@@ -0,0 +1,7 @@+static int foo() {+ return 0;+}++int main() {+ return foo();+}
+ t/files/a/a.h view
@@ -0,0 +1,1 @@+#include "c.h"
+ t/files/a/c.h view
@@ -0,0 +1,3 @@+type Hello = Int++type Bye = Bool
+ t/files/b/b.h view
@@ -0,0 +1,1 @@+data Foo = Foo
+ t/files/garrs.hs view
@@ -0,0 +1,6 @@+data G a where+ A :: a -> G Int+ B :: b -> G Bool++g :: Int -> Int+g proc = proc + 1