Partially improve the cabalisation

This commit is contained in:
Don Stewart
2005-09-03 03:10:04 +00:00
parent 698e960ad4
commit 90d780cff8
35 changed files with 57 additions and 26 deletions

25
System/Eval.hs Normal file
View File

@ -0,0 +1,25 @@
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
module System.Eval (
module System.Eval.Haskell,
) where
import System.Eval.Haskell {-all-}

259
System/Eval/Haskell.hs Normal file
View File

@ -0,0 +1,259 @@
{-# OPTIONS -fglasgow-exts -fffi #-}
--
-- Copyright (C) 2004-5 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
--
-- compile and run haskell strings at runtime.
--
module System.Eval.Haskell (
eval,
eval_,
unsafeEval,
unsafeEval_,
typeOf,
mkHsValues,
hs_eval_b, -- return a Bool
hs_eval_c, -- return a CChar
hs_eval_i, -- return a CInt
hs_eval_s, -- return a CString
module System.Eval.Utils,
) where
import System.Eval.Utils
import System.Plugins.Make
import System.Plugins.Load
import AltData.Dynamic ( Dynamic )
import AltData.Typeable ( Typeable )
import Data.Either
import Data.Map as Map
import System.IO
import System.Directory
import Foreign.C
import Foreign
--
-- ok. the idea is: the have either installed the library, in which case
-- is is registered, and the path to altdata is known to ghc, so just
-- saying "-package altdata" will work. if not, we search in the build
-- dir just in case. this should work for inplace work.
--
-- TODO could have a few extra package.conf search paths in here,
-- including PREFIX.
--
-- ---------------------------------------------------------------------
-- return a compiled value, and type check it first
--
-- TODO make this faster.
--
eval :: Typeable a => String -> [Import] -> IO (Maybe a)
eval src mods = do
pwd <- getCurrentDirectory
(cmdline,loadpath) <- getPaths
tmpf <- mkUniqueWith dynwrap src mods
status <- make tmpf cmdline
m_rsrc <- case status of
MakeSuccess _ obj -> do
m_v <- dynload obj [pwd] loadpath symbol
case m_v of LoadFailure _ -> return Nothing
LoadSuccess _ rsrc -> return $ Just rsrc
MakeFailure err -> mapM_ putStrLn err >> return Nothing
makeCleaner tmpf
return m_rsrc
-- ---------------------------------------------------------------------
-- Version of eval with all the buttons available.
eval_ :: Typeable a =>
String -- ^ code to compile
-> [Import] -- ^ any imports
-> [String] -- ^ extra make flags
-> [FilePath] -- ^ (package.confs) for load
-> [FilePath] -- ^ include paths load is to search in
-> IO (Either [String] (Maybe a)) -- ^ either errors, or maybe a well typed value
eval_ src mods args ldflags incs = do
pwd <- getCurrentDirectory
(cmdline,loadpath) <- getPaths -- find path to altdata
tmpf <- mkUniqueWith dynwrap src mods
status <- make tmpf $ ["-Onot"] ++ cmdline ++ args
m_rsrc <- case status of
MakeSuccess _ obj -> do
m_v <- dynload obj (pwd:incs) (loadpath++ldflags) symbol
return $ case m_v of LoadFailure e -> Left e
LoadSuccess _ rsrc -> Right (Just rsrc)
MakeFailure err -> return $ Left err
makeCleaner tmpf
return m_rsrc
-- ---------------------------------------------------------------------
-- unsafe because it doesn't use Dynamic types
-- useful for not having to provide type constraints to values, or when
-- you want to easily deal with polymorphic values.
--
unsafeEval :: String -> [Import] -> IO (Maybe a)
unsafeEval src mods = do
pwd <- getCurrentDirectory
tmpf <- mkUniqueWith wrap src mods
status <- make tmpf []
m_rsrc <- case status of
MakeSuccess _ obj -> do
m_v <- load obj [pwd] [] symbol
case m_v of LoadFailure _ -> return Nothing
LoadSuccess _ rsrc -> return $ Just rsrc
MakeFailure err -> mapM_ putStrLn err >> return Nothing
makeCleaner tmpf
return m_rsrc
--
-- like unsafeEval, except you can supply extra args to make and load,
-- and the error messages are returned too.
--
-- Need to be able to specify a search path to look in.
--
unsafeEval_ :: String -- ^ code to compile
-> [Import] -- ^ any imports
-> [String] -- ^ make flags
-> [FilePath] -- ^ (package.confs) for load
-> [FilePath] -- ^ include paths load is to search in
-> IO (Either [String] a)
unsafeEval_ src mods args ldflags incs = do
pwd <- getCurrentDirectory
tmpf <- mkUniqueWith wrap src mods
status <- make tmpf args
e_rsrc <- case status of
MakeSuccess _ obj -> do
m_v <- load obj (pwd:incs) ldflags symbol
case m_v of LoadFailure e -> return $ Left e
LoadSuccess _ rsrc -> return $ Right rsrc
MakeFailure err -> return $ Left err
makeCleaner tmpf
return e_rsrc
------------------------------------------------------------------------
--
-- Convenience function for use with eval (and friends). Returns a
-- string of Haskell code with the Data.Map passed as values.
--
mkHsValues :: (Show a) => Map.Map String a -> String
mkHsValues values = concat $ elems $ Map.mapWithKey convertToHs values
where convertToHs :: (Show a) => String -> a -> String
convertToHs name value = name ++ " = " ++ show value ++ "\n"
------------------------------------------------------------------------
--
-- return a compiled value's type, by using Dynamic to get a
-- representation of the inferred type.
--
typeOf :: String -> [Import] -> IO String
typeOf src mods = do
pwd <- getCurrentDirectory
(cmdline,loadpath) <- getPaths
tmpf <- mkUniqueWith dynwrap src mods
status <- make tmpf cmdline
ty <- case status of
MakeSuccess _ obj -> do
m_v <- load obj [pwd] loadpath symbol
case m_v of
LoadFailure _ -> return "<failure>"
LoadSuccess _ (v::Dynamic) -> return $ (init . tail) $ show v
MakeFailure err -> mapM_ putStrLn err >> return []
makeCleaner tmpf
return ty
--
-- note that the wrapper uses our altdata library for dynamic typing.
-- hence it needs to see the path to the altdata package. grr. is it
-- installed or not? what path does it have?
--
dynwrap :: String -> String -> [Import] -> String
dynwrap expr nm mods =
"module "++nm++ "( resource ) where\n" ++
concatMap (\m-> "import "++m++"\n") mods ++
"import AltData.Dynamic\n" ++
"resource = let { v = \n" ++
"{-# LINE 1 \"<eval>\" #-}\n" ++ expr ++ ";} in toDyn v"
-- ---------------------------------------------------------------------
-- unsafe wrapper
--
wrap :: String -> String -> [Import] -> String
wrap expr nm mods =
"module "++nm++ "( resource ) where\n" ++
concatMap (\m-> "import "++m++"\n") mods ++
"resource = let { v = \n" ++
"{-# LINE 1 \"<Plugins.Eval>\" #-}\n" ++ expr ++ ";} in v"
------------------------------------------------------------------------
--
-- And for our friends in foreign parts
--
-- TODO needs to accept char** to import list
--
--
-- return NULL pointer if an error occured.
--
foreign export ccall hs_eval_b :: CString -> IO (Ptr CInt)
foreign export ccall hs_eval_c :: CString -> IO (Ptr CChar)
foreign export ccall hs_eval_i :: CString -> IO (Ptr CInt)
foreign export ccall hs_eval_s :: CString -> IO CString
------------------------------------------------------------------------
--
-- TODO implement a marshalling for Dynamics, so that we can pass that
-- over to the C side for checking.
--
hs_eval_b :: CString -> IO (Ptr CInt)
hs_eval_b s = do m_v <- eval_cstring s
case m_v of Nothing -> return nullPtr
Just v -> new (fromBool v)
hs_eval_c :: CString -> IO (Ptr CChar)
hs_eval_c s = do m_v <- eval_cstring s
case m_v of Nothing -> return nullPtr
Just v -> new (castCharToCChar v)
-- should be Integral
hs_eval_i :: CString -> IO (Ptr CInt)
hs_eval_i s = do m_v <- eval_cstring s :: IO (Maybe Int)
case m_v of Nothing -> return nullPtr
Just v -> new (fromIntegral v :: CInt)
hs_eval_s :: CString -> IO CString
hs_eval_s s = do m_v <- eval_cstring s
case m_v of Nothing -> return nullPtr
Just v -> newCString v
--
-- convenience
--
eval_cstring :: Typeable a => CString -> IO (Maybe a)
eval_cstring cs = do s <- peekCString cs
eval s [] -- TODO use eval()

97
System/Eval/Utils.hs Normal file
View File

@ -0,0 +1,97 @@
{-# OPTIONS -fglasgow-exts -fffi -cpp #-}
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
--
-- compile and run haskell strings at runtime.
--
module System.Eval.Utils (
Import,
symbol,
escape,
getPaths,
mkUniqueWith,
cleanup,
module Data.Maybe,
module Control.Monad,
) where
import System.Plugins.Load ( Symbol )
import System.Plugins.Utils
import System.IO
import System.Directory
import Data.Char
--
-- we export these so that eval() users have a nice time
--
import Data.Maybe
import Control.Monad
--
-- imports Foo's
--
type Import = String
--
-- distinguished symbol name
--
symbol :: Symbol
symbol = "resource"
--
-- turn a Haskell string into a printable version of the same string
--
escape s = concatMap (\c -> showLitChar c $ "") s
--
-- For Dynamic eval's, work out the compile and load command lines
--
getPaths :: IO ([String],[String])
getPaths = do
let make_line = ["-Onot","-fglasgow-exts","-package","plugins"]
return (make_line,[])
-- ---------------------------------------------------------------------
-- create the tmp file, and write source into it, using wrapper to
-- create extra .hs src.
--
mkUniqueWith :: (String -> String -> [Import] -> String)
-> String
-> [Import] -> IO FilePath
mkUniqueWith wrapper src mods = do
(tmpf,hdl) <- hMkUnique
let nm = mkModid (basename tmpf) -- used as a module name
src' = wrapper src nm mods
hPutStr hdl src' >> hFlush hdl >> hClose hdl >> return tmpf
--
-- remove all the tmp files
--
cleanup :: String -> String -> IO ()
cleanup a b = mapM_ removeFile [a, b, replaceSuffix b ".hi"]

278
System/MkTemp.hs Normal file
View File

@ -0,0 +1,278 @@
{-# OPTIONS -cpp -fffi -fglasgow-exts #-}
--
-- glaexts for I# ops
--
-- Copyright (c) 2004-5 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
--
-- A Haskell reimplementation of the C mktemp/mkstemp/mkstemps library
-- based on the algorithms in:
-- > $ OpenBSD: mktemp.c,v 1.17 2003/06/02 20:18:37 millert Exp $
-- which are available under the BSD license.
--
module System.MkTemp (
mktemp, -- :: FilePath -> IO Maybe FilePath
mkstemp, -- :: FilePath -> IO Maybe (FilePath, Handle)
mkstemps, -- :: FilePath -> Int -> IO Maybe (FilePath,Handle)
mkdtemp, -- :: FilePath -> IO Maybe FilePath
) where
import Data.List
import Data.Char ( chr, ord, isDigit )
import Control.Monad ( liftM )
import Control.Exception ( handleJust )
import System.Directory ( doesDirectoryExist, doesFileExist, createDirectory )
import System.IO
#ifndef __MINGW32__
import System.IO.Error ( isAlreadyExistsError )
#else
import System.IO.Error ( isAlreadyExistsError, isAlreadyInUseError, isPermissionError )
#endif
import GHC.IOBase ( IOException(IOError),
Exception(IOException),
IOErrorType(AlreadyExists) )
#ifndef __MINGW32__
import qualified System.Posix.Internals ( c_getpid )
#endif
#ifdef HAVE_ARC4RANDOM
import GHC.Base hiding ( ord, chr )
import GHC.Int
#else
import System.Random ( getStdRandom, Random(randomR) )
#endif
------------------------------------------------------------------------
mkstemps :: FilePath -> Int -> IO (Maybe (FilePath,Handle))
mkstemp :: FilePath -> IO (Maybe (FilePath,Handle))
mktemp :: FilePath -> IO (Maybe FilePath)
mkdtemp :: FilePath -> IO (Maybe FilePath)
mkstemps path slen = gettemp path True False slen
mkstemp path = gettemp path True False 0
mktemp path = do v <- gettemp path False False 0
return $ case v of Just (path',_) -> Just path'; _ -> Nothing
mkdtemp path = do v <- gettemp path False True 0
return $ case v of Just (path',_) -> Just path'; _ -> Nothing
------------------------------------------------------------------------
gettemp :: FilePath -> Bool -> Bool -> Int -> IO (Maybe (FilePath, Handle))
gettemp [] _ _ _ = return Nothing
gettemp _ True True _ = return Nothing
gettemp path doopen domkdir slen = do
--
-- firstly, break up the path and extract the template
--
let (pref,tmpl,suff) = let (r,s) = splitAt (length path - slen) path
(p,t) = break (== 'X') r
in (p,t,s)
--
-- an error if there is only a suffix, it seems
--
if null pref && null tmpl then return Nothing else do {
--
-- replace end of template with process id, and rest with randomness
--
;pid <- liftM show $ getProcessID
;let (rest, xs) = merge tmpl pid
;as <- randomise rest
;let tmpl' = as ++ xs
path' = pref ++ tmpl' ++ suff
--
-- just check if we can get at the directory we might need
--
;dir_ok <- if doopen || domkdir
then let d = reverse $ dropWhile (/= '/') $ reverse path'
in doesDirectoryExist d
else return True
;if not dir_ok then return Nothing else do {
--
-- We need a function for looking for appropriate temp files
--
;let fn p
| doopen = handleJust isInUse (\_ -> return Nothing) $
do h <- open0600 p ; return $ Just h
| domkdir = handleJust alreadyExists (\_ -> return Nothing) $
do mkdir0700 p ; return $ Just undefined
| otherwise = do b <- doesFileExist p
return $ if b then Nothing else Just undefined
--
-- now, try to create the tmp file, permute if we can't
-- once we've tried all permutations, give up
--
;let tryIt p t i =
do v <- fn p
case v of Just h -> return $ Just (p,h) -- it worked
Nothing -> let (i',t') = tweak i t
in if null t'
then return Nothing -- no more
else tryIt (pref++t'++suff) t' i'
;tryIt path' tmpl' 0
}}
--
-- Replace X's with pid digits. Complete rewrite
--
merge :: String -> String -> (String,String)
merge t [] = (t ,[])
merge [] _ = ([] ,[])
merge (_:ts) (p:ps) = (ts',p:ps')
where (ts',ps') = merge ts ps
--
-- And replace remaining X's with random chars
-- randomR is pretty slow, oh well.
--
randomise :: String -> IO String
randomise [] = return []
randomise ('X':xs) = do p <- getRandom ()
let c = chr $! if p < 26
then p + (ord 'A')
else (p - 26) + (ord 'a')
xs' <- randomise xs
return (c : xs')
randomise s = return s
--
-- "tricky little algorithm for backward compatibility"
-- could do with a Haskellish rewrite
--
tweak :: Int -> String -> (Int,String)
tweak i s
| i > length s - 1 = (i,[]) -- no more
| s !! i == 'Z' = if i == length s - 1
then (i,[]) -- no more
else let s' = splice (i+1) 'a'
in tweak (i+1) s' -- loop
| otherwise = let c = s !! i in case () of {_
| isDigit c -> (i, splice i 'a' )
| c == 'z' -> (i, splice i 'A' )
| otherwise -> let c' = chr $ (ord c) + 1 in (i,splice i c')
}
where
splice j c = let (a,b) = splitAt j s in a ++ [c] ++ tail b
-- ---------------------------------------------------------------------
alreadyExists :: Exception -> Maybe Exception
alreadyExists e@(IOException ioe)
| isAlreadyExistsError ioe = Just e
| otherwise = Nothing
alreadyExists _ = Nothing
isInUse :: Exception -> Maybe ()
#ifndef __MINGW32__
isInUse (IOException ioe)
| isAlreadyExistsError ioe = Just ()
| otherwise = Nothing
isInUse _ = Nothing
#else
isInUse (IOException ioe)
| isAlreadyInUseError ioe = Just ()
| isPermissionError ioe = Just ()
| isAlreadyExistsError ioe = Just () -- we throw this
| otherwise = Nothing
isInUse _ = Nothing
#endif
-- ---------------------------------------------------------------------
-- Create a file mode 0600 if possible
--
-- N.B. race condition between testing existence and opening
-- But we can live with that to avoid a posix dependency, right?
--
open0600 :: FilePath -> IO Handle
open0600 f = do
b <- doesFileExist f
if b then ioError err -- race
else openFile f ReadWriteMode
where
err = IOError Nothing AlreadyExists "open0600" "already exists" Nothing
{-
-- open(path, O_CREAT|O_EXCL|O_RDWR, 0600)
--
open0600 f = do
openFd f ReadWrite (Just o600) excl >>= fdToHandle
where
o600 = ownerReadMode `unionFileModes` ownerWriteMode
excl = defaultFileFlags { exclusive = True }
-}
--
-- create a directory mode 0700 if possible
--
mkdir0700 :: FilePath -> IO ()
mkdir0700 dir = createDirectory dir
{-
System.Posix.Directory.createDirectory dir ownerModes
-}
-- | getProcessId, stolen from GHC (main/SysTools.lhs)
--
#ifdef __MINGW32__
-- relies on Int == Int32 on Windows
foreign import ccall unsafe "_getpid" getProcessID' :: IO Int
getProcessID :: IO Int
getProcessID = liftM abs getProcessID'
#else
getProcessID :: IO Int
#ifdef CYGWIN
getProcessID = System.Posix.Internals.c_getpid >>= return . abs . fromIntegral
#else
getProcessID = System.Posix.Internals.c_getpid >>= return . fromIntegral
#endif
#endif
-- ---------------------------------------------------------------------
-- | Use a variety of random functions, if you like.
--
getRandom :: () -> IO Int
#ifndef HAVE_ARC4RANDOM
getRandom _ = getStdRandom (randomR (0,51))
#else
--
-- OpenBSD: "The arc4random() function provides a high quality 32-bit
-- pseudo-random number very quickly. arc4random() seeds itself on a
-- regular basis from the kernel strong random number subsystem
-- described in random(4)." Also, it is a bit faster than getStdRandom
--
getRandom _ = do
(I32# i) <- c_arc4random
return (I# (word2Int# ((int2Word# i `and#` int2Word# 0xffff#)
`remWord#` int2Word# 52#)))
foreign import ccall unsafe "stdlib.h arc4random" c_arc4random :: IO Int32
#endif

37
System/Plugins.hs Normal file
View File

@ -0,0 +1,37 @@
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
module System.Plugins (
-- $Description
module System.Plugins.Make,
module System.Plugins.Load,
) where
import System.Plugins.Make {-all-}
import System.Plugins.Load {-all-}
--
-- $Description
--
-- [@NAME@] hs-plugins library : compile and load Haskell code at runtime
--

78
System/Plugins/Consts.hs Normal file
View File

@ -0,0 +1,78 @@
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
module System.Plugins.Consts where
#include "../../config.h"
#if __GLASGOW_HASKELL__ >= 604
import System.Directory ( getTemporaryDirectory )
import System.IO.Unsafe ( unsafePerformIO )
#endif
-- | path to *build* dir, used by eval() for testing the examples
top = TOP
-- | what is ghc called?
ghc = WITH_GHC
-- | path to standard ghc libraries
ghcLibraryPath = GHC_LIB_PATH
-- | name of the system package.conf file
sysPkgConf = "package.conf"
-- | This code is from runtime_loader:
-- The extension used by system modules.
sysPkgSuffix = ".o"
objSuf = sysPkgSuffix
hiSuf = ".hi"
hsSuf = ".hs"
#if defined(CYGWIN) || defined(__MINGW32__)
dllSuf = ".dll"
#else
dllSuf = ".so"
#endif
-- | The prefix used by system modules. This, in conjunction with
-- 'systemModuleExtension', will result in a module filename that looks
-- like \"HSconcurrent.o\"
sysPkgPrefix = "HS"
-- | '_' on a.out, and Darwin
#if LEADING_UNDERSCORE == 1
prefixUnderscore = "_"
#else
prefixUnderscore = ""
#endif
-- | Define tmpDir to where tmp files should be created on your platform
#if __GLASGOW_HASKELL__ >= 604
tmpDir = unsafePerformIO getTemporaryDirectory
{-# NOINLINE tmpDir #-}
#else
#if !defined(__MINGW32__)
tmpDir = "/tmp"
#else
tmpDir = error "tmpDir not defined for this platform. Try setting the TMPDIR env var"
#endif
#endif

460
System/Plugins/Env.hs Normal file
View File

@ -0,0 +1,460 @@
{-# OPTIONS -cpp #-}
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
module System.Plugins.Env (
withModEnv,
withDepEnv,
withPkgEnvs,
withMerged,
modifyModEnv,
modifyDepEnv,
modifyPkgEnv,
modifyMerged,
addModule,
rmModule,
addModules,
isLoaded,
loaded,
addModuleDeps,
getModuleDeps,
rmModuleDeps,
isMerged,
lookupMerged,
addMerge,
addPkgConf,
union,
grabDefaultPkgConf,
readPackageConf,
lookupPkg
) where
#include "../../config.h"
import System.Plugins.LoadTypes (Module)
import System.Plugins.PackageAPI {- everything -}
#if CABAL == 1 || __GLASGOW_HASKELL__ >= 604
import System.Plugins.ParsePkgConfCabal( parsePkgConf )
#else
import System.Plugins.ParsePkgConfLite ( parsePkgConf )
#endif
import System.Plugins.Consts ( ghcLibraryPath, sysPkgConf, sysPkgSuffix )
import Data.IORef ( writeIORef, readIORef, newIORef, IORef() )
import Data.Maybe ( isJust, isNothing, fromMaybe )
import Data.List ( isPrefixOf, nub )
import System.IO.Unsafe ( unsafePerformIO )
import System.Directory ( doesFileExist )
#if defined(CYGWIN) || defined(__MINGW32__)
import Prelude hiding ( catch, ioError )
import System.Environment ( getEnv )
import System.IO.Error ( catch, ioError, isDoesNotExistError )
#endif
import Control.Concurrent.MVar ( MVar(), newMVar, withMVar )
#if __GLASGOW_HASKELL__ < 604
import Data.FiniteMap
#else
import qualified Data.Map as M
--
-- and map Data.Map terms to FiniteMap terms
--
type FiniteMap k e = M.Map k e
emptyFM :: FiniteMap key elt
emptyFM = M.empty
addToFM :: (Ord key) => FiniteMap key elt -> key -> elt -> FiniteMap key elt
addToFM = \m k e -> M.insert k e m
delFromFM :: (Ord key) => FiniteMap key elt -> key -> FiniteMap key elt
delFromFM = flip M.delete
lookupFM :: (Ord key) => FiniteMap key elt -> key -> Maybe elt
lookupFM = flip M.lookup
#endif
--
-- We need to record what modules and packages we have loaded, so if we
-- read a .hi file that wants to load something already loaded, we can
-- safely ignore that request. We're in the IO monad anyway, so we can
-- add some extra state of our own.
--
-- The state is a FiniteMap String (Module,Int) (a hash of package/object names
-- to Modules and how many times they've been loaded).
--
-- It also contains the package.conf information, so that if there is a
-- package dependency we can find it correctly, even if it has a
-- non-standard path or name, and if it isn't an official package (but
-- rather one provided via -package-conf). This is stored as a
-- FiniteMap PackageName PackageConfig. The problem then is whether a
-- user's package.conf, that uses the same package name as an existing
-- GHC package, should be allowed, or should shadow a library package?
-- I don't know, but I'm inclined to have the GHC package shadow the
-- user's package.
--
-- This idea is based on *Hampus Ram's dynamic loader* dependency
-- tracking system. He uses state to record dependency trees to allow
-- clean unloading and other fun. This is quite cool. We're just using
-- state to make sure we don't load the same package twice. Implementing
-- the full dependency tree idea would be nice, though not fully
-- necessary as we have the dependency information store in .hi files,
-- unlike in hram's loader.
--
type ModEnv = FiniteMap String (Module,Int)
type DepEnv = FiniteMap Module [Module]
-- represents a package.conf file
type PkgEnv = FiniteMap PackageName PackageConfig
-- record dependencies between (src,stub) -> merged modid
type MergeEnv = FiniteMap (FilePath,FilePath) FilePath
-- multiple package.conf's kept in separate namespaces
type PkgEnvs = [PkgEnv]
type Env = (MVar (),
IORef ModEnv,
IORef DepEnv,
IORef PkgEnvs,
IORef MergeEnv)
--
-- our environment, contains a set of loaded objects, and a map of known
-- packages and their informations. Initially all we know is the default
-- package.conf information.
--
env = unsafePerformIO $ do
mvar <- newMVar ()
ref1 <- newIORef emptyFM -- loaded objects
ref2 <- newIORef emptyFM
p <- grabDefaultPkgConf
ref3 <- newIORef p -- package.conf info
ref4 <- newIORef emptyFM -- merged files
return (mvar, ref1, ref2, ref3, ref4)
{-# NOINLINE env #-}
-- -----------------------------------------------------------
--
-- apply 'f' to the loaded objects Env
-- apply 'f' to the package.conf FM
-- *locks up the MVar* so you can't recursively call a function inside a
-- with*Env function. Nice and threadsafe
--
withModEnv :: Env -> (ModEnv -> IO a) -> IO a
withDepEnv :: Env -> (DepEnv -> IO a) -> IO a
withPkgEnvs :: Env -> (PkgEnvs -> IO a) -> IO a
withMerged :: Env -> (MergeEnv -> IO a) -> IO a
withModEnv (mvar,ref,_,_,_) f = withMVar mvar (\_ -> readIORef ref >>= f)
withDepEnv (mvar,_,ref,_,_) f = withMVar mvar (\_ -> readIORef ref >>= f)
withPkgEnvs (mvar,_,_,ref,_) f = withMVar mvar (\_ -> readIORef ref >>= f)
withMerged (mvar,_,_,_,ref) f = withMVar mvar (\_ -> readIORef ref >>= f)
-- -----------------------------------------------------------
--
-- write an object name
-- write a new PackageConfig
--
modifyModEnv :: Env -> (ModEnv -> IO ModEnv) -> IO ()
modifyDepEnv :: Env -> (DepEnv -> IO DepEnv) -> IO ()
modifyPkgEnv :: Env -> (PkgEnvs -> IO PkgEnvs) -> IO ()
modifyMerged :: Env -> (MergeEnv -> IO MergeEnv)-> IO ()
modifyModEnv (mvar,ref,_,_,_) f = lockAndWrite mvar ref f
modifyDepEnv (mvar,_,ref,_,_) f = lockAndWrite mvar ref f
modifyPkgEnv (mvar,_,_,ref,_) f = lockAndWrite mvar ref f
modifyMerged (mvar,_,_,_,ref) f = lockAndWrite mvar ref f
-- private
lockAndWrite mvar ref f = withMVar mvar (\_->readIORef ref>>=f>>=writeIORef ref)
-- -----------------------------------------------------------
--
-- insert a loaded module name into the environment
--
addModule :: String -> Module -> IO ()
addModule s m = modifyModEnv env $ \fm -> let c = maybe 0 snd (lookupFM fm s)
in return $ addToFM fm s (m,c+1)
--getModule :: String -> IO (Maybe Module)
--getModule s = withModEnv env $ \fm -> return (lookupFM fm s)
--
-- remove a module name from the environment. Returns True if the module was actually removed.
--
rmModule :: String -> IO Bool
rmModule s = do modifyModEnv env $ \fm -> let c = maybe 1 snd (lookupFM fm s)
fm' = delFromFM fm s
in if c-1 <= 0
then return fm'
else return fm
withModEnv env $ \fm -> return (isNothing (lookupFM fm s))
--
-- insert a list of module names all in one go
--
addModules :: [(String,Module)] -> IO ()
addModules ns = mapM_ (uncurry addModule) ns
--
-- is a module/package already loaded?
--
isLoaded :: String -> IO Bool
isLoaded s = withModEnv env $ \fm -> return $ isJust (lookupFM fm s)
--
-- confusing! only for filter.
--
loaded :: String -> IO Bool
loaded m = do t <- isLoaded m ; return (not t)
-- -----------------------------------------------------------
--
-- module dependency stuff
--
--
-- set the dependencies of a Module.
--
addModuleDeps :: Module -> [Module] -> IO ()
addModuleDeps m deps = modifyDepEnv env $ \fm -> return $ addToFM fm m deps
--
-- Get module dependencies. Nothing if none have been recored.
--
getModuleDeps :: Module -> IO [Module]
getModuleDeps m = withDepEnv env $ \fm -> return $ fromMaybe [] (lookupFM fm m)
--
-- Unrecord a module from the environment.
--
rmModuleDeps :: Module -> IO ()
rmModuleDeps m = modifyDepEnv env $ \fm -> return $ delFromFM fm m
-- -----------------------------------------------------------
-- Package management stuff
--
-- insert a single package.conf (containing multiple configs)
-- means: create a new FM. insert packages into FM. add FM to end of
-- list of FM stored in the environment.
--
addPkgConf :: FilePath -> IO ()
addPkgConf f = do
ps <- readPackageConf f
modifyPkgEnv env $ \ls -> return $ union ls ps
--
-- add a new FM for the package.conf to the list of existing ones
--
union :: PkgEnvs -> [PackageConfig] -> PkgEnvs
union ls ps' =
let fm = emptyFM -- new FM for this package.conf
in ls ++ [foldr (\p fm' -> addToFM fm' (packageName p) p) fm ps']
--
-- generate a PkgEnv from the system package.conf
-- * the path to the default package.conf was determined by ./configure *
-- This imposes a constraint that you must build your plugins with the
-- same ghc you use to build hs-plugins. This is reasonable, we feel.
--
grabDefaultPkgConf :: IO PkgEnvs
grabDefaultPkgConf = do
pkgs <- readPackageConf $ ghcLibraryPath </> sysPkgConf
return $ union [] pkgs
--
-- parse a source file, expanding any $libdir we see.
--
readPackageConf :: FilePath -> IO [PackageConfig]
readPackageConf f = do
s <- readFile f
let p = parsePkgConf s
return $! map expand_libdir p
where
expand_libdir :: PackageConfig -> PackageConfig
expand_libdir pk =
let pk' = updImportDirs (\idirs -> map expand idirs) pk
pk'' = updLibraryDirs (\ldirs -> map expand ldirs) pk'
in pk''
expand :: FilePath -> FilePath
expand s | "$libdir" `isPrefixOf` s = ghcLibraryPath ++ drop 7 s
expand s = s
--
-- Package path, given a package name, look it up in the environment and
-- return the path to all the libraries needed to load this package.
--
-- What do we need to load? With the library_dirs as prefix paths:
-- * anything in the hs_libraries fields, $libdir expanded
-- * anything in the extra_libraries fields (i.e. cbits), expanded,
-- which includes system .so files.
-- * also load any dependencies now, because of that weird mtl
-- library that lang depends upon, but which doesn't show up in the
-- interfaces for some reason.
--
-- We return all the package paths that possibly exist, and the leave it
-- up to loadObject not to load the same ones twice...
--
lookupPkg :: PackageName -> IO ([FilePath],[FilePath])
lookupPkg p = do
t <- lookupPkg' p
case t of ([],(f,g)) -> return (f,g)
(ps,(f,g)) -> do gss <- mapM lookupPkg ps
let (f',g') = unzip gss
return $ (nub $ (concat f') ++ f,nub $ (concat g') ++ g)
data LibrarySpec
= DLL String -- -lLib
| DLLPath FilePath -- -Lpath
classifyLdInput :: FilePath -> IO (Maybe LibrarySpec)
classifyLdInput ('-':'l':lib) = return (Just (DLL lib))
classifyLdInput ('-':'L':path) = return (Just (DLLPath path))
classifyLdInput _ = return Nothing
-- TODO need to define a MAC/DARWIN symbol
#if defined(MACOSX)
mkSOName root = "lib" ++ root ++ ".dylib"
#elif defined(CYGWIN) || defined(__MINGW32__)
-- Win32 DLLs have no .dll extension here, because addDLL tries
-- both foo.dll and foo.drv
mkSOName root = root
#else
mkSOName root = "lib" ++ root ++ ".so"
#endif
--
-- return any stuff to load for this package, plus the list of packages
-- this package depends on. which includes stuff we have to then load
-- too.
--
lookupPkg' :: PackageName -> IO ([PackageName],([FilePath],[FilePath]))
lookupPkg' p = withPkgEnvs env $ \fms -> go fms p
where
go [] _ = return ([],([],[]))
go (fm:fms) q = case lookupFM fm q of
Nothing -> go fms q -- look in other pkgs
Just package -> do
let hslibs = hsLibraries package
extras' = extraLibraries package
cbits = filter (\e -> reverse (take (length "_cbits") (reverse e)) == "_cbits") extras'
extras = filter (not . flip elem (cbits++["m","gmp"])) extras'
ldopts = ldOptions package
deppkgs = packageDeps package
ldInput <- mapM classifyLdInput ldopts
let ldOptsLibs = [ path | Just (DLL path) <- ldInput ]
ldOptsPaths = [ path | Just (DLLPath path) <- ldInput ]
dlls = map mkSOName (extras ++ ldOptsLibs)
#if defined(CYGWIN) || defined(__MINGW32__)
libdirs = fix_topdir (libraryDirs package) ++ ldOptsPaths
#else
libdirs = libraryDirs package ++ ldOptsPaths
#endif
libs <- mapM (findHSlib libdirs) (hslibs ++ cbits)
#if defined(CYGWIN) || defined(__MINGW32__)
windowsos <- catch (getEnv "OS")
(\e -> if isDoesNotExistError e then return "Windows_98" else ioError e)
windowsdir <-
if windowsos == "Windows_9X" -- I don't know Windows 9X has OS system variable
then return "C:/windows"
else return "C:/winnt"
sysroot <- catch (getEnv "SYSTEMROOT")
(\e -> if isDoesNotExistError e then return windowsdir else ioError e) -- guess at a reasonable default
let syslibdir = sysroot ++ (if windowsos == "Windows_9X" then "/SYSTEM" else "/SYSTEM32")
libs' <- mapM (findDLL $ syslibdir : libdirs) dlls
#else
libs' <- mapM (findDLL libdirs) dlls
#endif
return (deppkgs, (filterRight libs,map (either id id) libs') )
#if defined(CYGWIN) || defined(__MINGW32__)
-- replace $topdir
fix_topdir [] = []
fix_topdir (x:xs) = replace_topdir x : fix_topdir xs
replace_topdir [] = []
replace_topdir ('$':xs)
| take 6 xs == "topdir" = ghcLibraryPath ++ (drop 6 xs)
| otherwise = '$' : replace_topdir xs
replace_topdir (x:xs) = x : replace_topdir xs
#endif
-- a list elimination form for the Maybe type
filterRight :: [Either left right] -> [right]
filterRight [] = []
filterRight (Right x:xs) = x:filterRight xs
filterRight (Left _:xs) = filterRight xs
--
-- Check that a path to a library actually reaches a library
-- Problem: sysPkgSuffix is ".o", but extra libraries could be
-- ".so" -- what to do?
--
findHSlib :: [FilePath] -> String -> IO (Either String FilePath)
findHSlib [] lib = return (Left lib)
findHSlib (dir:dirs) lib = do
let l = dir </> lib ++ sysPkgSuffix
b <- doesFileExist l
if b then return $ Right l -- found it!
else findHSlib dirs lib
findDLL :: [FilePath] -> String -> IO (Either String FilePath)
findDLL [] lib = return (Left lib)
findDLL (dir:dirs) lib = do
let l = dir </> lib
b <- doesFileExist l
if b then return $ Right l
else findDLL dirs lib
------------------------------------------------------------------------
-- do we have a Module name for this merge?
--
isMerged :: FilePath -> FilePath -> IO Bool
isMerged a b = withMerged env $ \fm -> return $ isJust (lookupFM fm (a,b))
lookupMerged :: FilePath -> FilePath -> IO (Maybe FilePath)
lookupMerged a b = withMerged env $ \fm -> return $ lookupFM fm (a,b)
--
-- insert a new merge pair into env
--
addMerge :: FilePath -> FilePath -> FilePath -> IO ()
addMerge a b z = modifyMerged env $ \fm -> return $ addToFM fm (a,b) z
------------------------------------------------------------------------
-- break a module cycle
-- private:
--
(</>) :: FilePath -> FilePath -> FilePath
[] </> b = b
a </> b = a ++ "/" ++ b

662
System/Plugins/Load.hs Normal file
View File

@ -0,0 +1,662 @@
{-# OPTIONS -#include "Linker.h" #-}
{-# OPTIONS -fglasgow-exts -cpp #-}
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
module System.Plugins.Load (
-- high level interface
load , load_
, dynload
, pdynload , pdynload_
, unload
, unloadAll
, reload
, Module(..)
, LoadStatus(..)
-- low level interface
, initLinker -- start it up
, loadModule -- load a vanilla .o
, loadFunction -- retrieve a function from an object
, loadPackage -- load a ghc library and its cbits
, unloadPackage -- unload a ghc library and its cbits
, loadPackageWith -- load a pkg using the package.conf provided
, loadShared -- load a .so object file
, resolveObjs -- and resolve symbols
, loadRawObject -- load a bare .o. no dep chasing, no .hi file reading
, Symbol
, getImports
) where
#include "../../config.h"
import System.Plugins.Make ( build )
import System.Plugins.Env
import System.Plugins.Utils
import System.Plugins.Consts ( sysPkgSuffix, hiSuf, prefixUnderscore )
import System.Plugins.LoadTypes
import Language.Hi.Parser
import AltData.Dynamic ( fromDynamic, Dynamic )
import AltData.Typeable ( Typeable )
import Data.List ( isSuffixOf, nub, nubBy )
import Control.Monad ( when, filterM, liftM )
import System.Directory ( doesFileExist, removeFile )
import Foreign.C.String ( CString, withCString, peekCString )
import GHC.Ptr ( Ptr(..), nullPtr )
import GHC.Exts ( addrToHValue# )
import GHC.Prim ( unsafeCoerce# )
#if DEBUG
import System.IO ( hFlush, stdout )
#endif
import System.IO ( hClose )
-- TODO need a loadPackage p package.conf :: IO () primitive
-- ---------------------------------------------------------------------
-- return status of all *load functions:
--
data LoadStatus a
= LoadSuccess Module a
| LoadFailure Errors
-- ---------------------------------------------------------------------
-- | load an object file into the address space, returning the closure
-- associated with the symbol requested, after removing its dynamism.
--
-- Recursively loads the specified modules, and all the modules they
-- depend on.
--
load :: FilePath -- ^ object file
-> [FilePath] -- ^ any include paths
-> [PackageConf] -- ^ list of package.conf paths
-> Symbol -- ^ symbol to find
-> IO (LoadStatus a)
load obj incpaths pkgconfs sym = do
initLinker
-- load extra package information
mapM_ addPkgConf pkgconfs
(hif,moduleDeps) <- loadDepends obj incpaths
-- why is this the package name?
#if DEBUG
putStr (' ':(decode $ mi_module hif)) >> hFlush stdout
#endif
m' <- loadObject obj (Object (mi_module hif))
let m = m' { iface = hif }
resolveObjs (mapM_ unloadAll (m:moduleDeps))
#if DEBUG
putStrLn " ... done" >> hFlush stdout
#endif
addModuleDeps m' moduleDeps
v <- loadFunction m sym
return $ case v of
Nothing -> LoadFailure ["load: couldn't find symbol <<"++sym++">>"]
Just a -> LoadSuccess m a
--
-- | Like load, but doesn't want a package.conf arg (they are rarely used)
--
load_ :: FilePath -> [FilePath] -> Symbol -> IO (LoadStatus a)
load_ o i s = load o i [] s
--
-- A work-around for Dynamics. The keys used to compare two TypeReps are
-- somehow not equal for the same type in hs-plugin's loaded objects.
-- Solution: implement our own dynamics...
--
-- The problem with dynload is that it requires the plugin to export
-- a value that is a Dynamic (in our case a (TypeRep,a) pair). If this
-- is not the case, we core dump. Use pdynload if you don't trust the
-- user to supply you with a Dynamic
--
dynload :: Typeable a
=> FilePath
-> [FilePath]
-> [PackageConf]
-> Symbol
-> IO (LoadStatus a)
dynload obj incpaths pkgconfs sym = do
s <- load obj incpaths pkgconfs sym
case s of e@(LoadFailure _) -> return e
LoadSuccess m dyn_v -> return $
case fromDynamic (unsafeCoerce# dyn_v :: Dynamic) of
Just v' -> LoadSuccess m v'
Nothing -> LoadFailure ["Mismatched types in interface"]
------------------------------------------------------------------------
--
-- The super-replacement for dynload
--
-- Use GHC at runtime so we get staged type inference, providing full
-- power dynamics, *on module interfaces only*. This is quite suitable
-- for plugins, of coures :)
--
-- TODO where does the .hc file go in the call to build() ?
--
pdynload :: FilePath -- ^ object to load
-> [FilePath] -- ^ include paths
-> [PackageConf] -- ^ package confs
-> Type -- ^ API type
-> Symbol -- ^ symbol
-> IO (LoadStatus a)
pdynload object incpaths pkgconfs ty sym = do
#if DEBUG
putStr "Checking types ... " >> hFlush stdout
#endif
errors <- unify object incpaths [] ty sym
#if DEBUG
putStrLn "done"
#endif
if null errors
then load object incpaths pkgconfs sym
else return $ LoadFailure errors
--
-- | Like pdynload, but you can specify extra arguments to the
-- typechecker.
--
pdynload_ :: FilePath -- ^ object to load
-> [FilePath] -- ^ include paths for loading
-> [PackageConf] -- ^ any extra package.conf files
-> [Arg] -- ^ extra arguments to ghc, when typechecking
-> Type -- ^ expected type
-> Symbol -- ^ symbol to load
-> IO (LoadStatus a)
pdynload_ object incpaths pkgconfs args ty sym = do
#if DEBUG
putStr "Checking types ... " >> hFlush stdout
#endif
errors <- unify object incpaths args ty sym
#if DEBUG
putStrLn "done"
#endif
if null errors
then load object incpaths pkgconfs sym
else return $ LoadFailure errors
------------------------------------------------------------------------
-- run the typechecker over the constraint file
--
-- Problem: if the user depends on a non-auto package to build the
-- module, then that package will not be in scope when we try to build
-- the module, when performing `unify'. Normally make() will handle this
-- (as it takes extra ghc args). pdynload ignores these, atm -- but it
-- shouldn't. Consider a pdynload() that accepts extra -package flags?
--
-- Also, pdynload() should accept extra in-scope modules.
-- Maybe other stuff we want to hack in here.
--
unify obj incs args ty sym = do
(tmpf,hdl) <- mkTemp
(tmpf1,hdl1) <- mkTemp -- and send .hi file here.
hClose hdl1
let nm = mkModid (basename tmpf)
src = mkTest nm (hierize' . mkModid . hierize $ obj)
(fst $ break (=='.') ty) ty sym
is = map (\s -> "-i"++s) incs -- api
i = "-i" ++ dirname obj -- plugin
hWrite hdl src
e <- build tmpf tmpf1 (i:is++args++["-fno-code","-ohi "++tmpf1])
-- removeFile tmpf
removeFile tmpf1
return e
where
-- fix up hierarchical names
hierize [] = []
hierize ('/':cs) = '\\' : hierize cs
hierize (c:cs) = c : hierize cs
hierize'[] = []
hierize' ('\\':cs) = '.' : hierize' cs
hierize' (c:cs) = c : hierize' cs
mkTest modnm plugin api ty sym =
"module "++ modnm ++" where" ++
"\nimport qualified " ++ plugin ++
"\nimport qualified " ++ api ++
"{-# LINE 1 \"<typecheck>\" #-}" ++
"\n_ = "++ plugin ++"."++ sym ++" :: "++ty
------------------------------------------------------------------------
{-
--
-- old version that tried to rip stuff from .hi files
--
pdynload obj incpaths pkgconfs sym ty = do
(m, v) <- load obj incpaths pkgconfs sym
ty' <- mungeIface sym obj
if ty == ty'
then return $ Just (m, v)
else return Nothing -- mismatched types
where
-- grab the iface output from GHC. find the line relevant to our
-- symbol. grab the string rep of the type.
mungeIface sym o = do
let hi = replaceSuffix o hiSuf
(out,_) <- exec ghc ["--show-iface", hi]
case find (\s -> (sym ++ " :: ") `isPrefixOf` s) out of
Nothing -> return undefined
Just v -> do let v' = drop 3 $ dropWhile (/= ':') v
return v'
-}
{-
--
-- a version of load the also unwraps and types a Dynamic object
--
dynload2 :: Typeable a =>
FilePath ->
FilePath ->
Maybe [PackageConf] ->
Symbol ->
IO (Module, a)
dynload2 obj incpath pkgconfs sym = do
(m, v) <- load obj incpath pkgconfs sym
case fromDynamic v of
Nothing -> panic $ "load: couldn't type "++(show v)
Just a -> return (m,a)
-}
------------------------------------------------------------------------
--
-- | unload a module (not its dependencies)
-- we have the dependencies, so cascaded unloading is possible
--
-- once you unload it, you can't 'load' it again, you have to 'reload'
-- it. Cause we don't unload all the dependencies
--
unload :: Module -> IO ()
unload m = rmModuleDeps m >> unloadObj m
------------------------------------------------------------------------
--
-- | unload a module and its dependencies
-- we have the dependencies, so cascaded unloading is possible
--
unloadAll :: Module -> IO ()
unloadAll m = do moduleDeps <- getModuleDeps m
rmModuleDeps m
mapM_ unloadAll moduleDeps
unload m
--
-- | this will be nice for panTHeon, needs thinking about the interface
-- reload a single object file. don't care about depends, assume they
-- are loaded. (should use state to store all this)
--
-- assumes you've already done a 'load'
--
-- should factor the code
--
reload :: Module -> Symbol -> IO (LoadStatus a)
reload m@(Module{path = p, iface = hi}) sym = do
unloadObj m -- unload module (and delete)
#if DEBUG
putStr ("Reloading "++(mname m)++" ... ") >> hFlush stdout
#endif
m_ <- loadObject p (Object $ mi_module hi) -- load object at path p
let m' = m_ { iface = hi }
resolveObjs (unloadAll m)
#if DEBUG
putStrLn "done" >> hFlush stdout
#endif
v <- loadFunction m' sym
return $ case v of
Nothing -> LoadFailure ["load: couldn't find symbol <<"++sym++">>"]
Just a -> LoadSuccess m' a
-- ---------------------------------------------------------------------
-- This is a stripped-down version of Andr<64> Pang's runtime_loader,
-- which in turn is based on GHC's ghci/ObjLinker.lhs binding
--
-- Load and unload\/Haskell modules at runtime. This is not really
-- \'dynamic loading\', as such -- that implies that you\'re working
-- with proper shared libraries, whereas this is far more simple and
-- only loads object files. But it achieves the same goal: you can
-- load a Haskell module at runtime, load a function from it, and run
-- the function. I have no idea if this works for types, but that
-- doesn\'t mean that you can\'t try it :).
--
-- read $fptools/ghc/compiler/ghci/ObjLinker.lhs for how to use this stuff
--
------------------------------------------------------------------------
-- | Call the initLinker function first, before calling any of the other
-- functions in this module - otherwise you\'ll get unresolved symbols.
-- initLinker :: IO ()
-- our initLinker transparently calls the one in GHC
--
-- | Load a function from a module (which must be loaded and resolved first).
--
loadFunction :: Module -- ^ The module the value is in
-> String -- ^ Symbol name of value
-> IO (Maybe a) -- ^ The value you want
loadFunction (Module { iface = i }) valsym
= do let m = mi_module i
symbol = symbolise m
#if DEBUG
putStrLn $ "Looking for <<"++symbol++">>"
#endif
ptr@(~(Ptr addr)) <- withCString symbol c_lookupSymbol
if (ptr == nullPtr)
then return Nothing
else case addrToHValue# addr of
(# hval #) -> return ( Just hval )
where
symbolise m = prefixUnderscore++m++"_"++(encode valsym)++"_closure"
--
-- | Load a GHC-compiled Haskell vanilla object file.
-- The first arg is the path to the object file
--
-- We make it idempotent to stop the nasty problem of loading the same
-- .o twice. Also the rts is a very special package that is already
-- loaded, even if we ask it to be loaded. N.B. we should insert it in
-- the list of known packages.
--
-- NB the environment stores the *full path* to an object. So if you
-- want to know if a module is already loaded, you need to supply the
-- *path* to that object, not the name.
--
-- NB -- let's try just the module name.
--
-- loadObject loads normal .o objs, and packages too. .o objs come with
-- a nice canonical Z-encoded modid. packages just have a simple name.
-- Do we want to ensure they won't clash? Probably.
--
--
-- the second argument to loadObject is a string to use as the unique
-- identifier for this object. For normal .o objects, it should be the
-- Z-encoded modid from the .hi file. For archives/packages, we can
-- probably get away with the package name
--
loadObject :: FilePath -> Key -> IO Module
loadObject p ky@(Object k) = loadObject' p ky k
loadObject p ky@(Package k) = loadObject' p ky k
loadObject' :: FilePath -> Key -> String -> IO Module
loadObject' p ky k
| ("HSrts"++sysPkgSuffix) `isSuffixOf` p = return (emptyMod p)
| otherwise
= do alreadyLoaded <- isLoaded k
when (not alreadyLoaded) $ do
r <- withCString p c_loadObj
when (not r) (panic $ "Could not load module `"++p++"'")
addModule k (emptyMod p) -- needs to Z-encode module name
return (emptyMod p)
where emptyMod q = Module q (mkModid q) Vanilla emptyIface ky
--
-- load a single object. no dependencies. You should know what you're
-- doing.
--
loadModule :: FilePath -> IO Module
loadModule obj = do
let hifile = replaceSuffix obj hiSuf
exists <- doesFileExist hifile
if (not exists)
then error $ "No .hi file found for "++show obj
else do hiface <- readIface hifile
loadObject obj (Object (mi_module hiface))
--
-- | Load a generic .o file, good for loading C objects.
-- You should know what you're doing..
-- Returns a fairly meaningless iface value.
--
loadRawObject :: FilePath -> IO Module
loadRawObject obj = loadObject obj (Object k)
where
k = encode (mkModid obj) -- Z-encoded module name
--
-- | Resolve (link) the modules loaded by the 'loadObject' function.
--
resolveObjs :: IO a -> IO ()
resolveObjs unloadLoaded
= do r <- c_resolveObjs
when (not r) $ unloadLoaded >> panic "resolvedObjs failed."
-- | Unload a module
unloadObj :: Module -> IO ()
unloadObj (Module { path = p, kind = k, key = ky }) = case k of
Vanilla -> withCString p $ \c_p -> do
removed <- rmModule name
when (removed) $ do r <- c_unloadObj c_p
when (not r) (panic "unloadObj: failed")
Shared -> return () -- can't unload .so?
where name = case ky of Object s -> s ; Package pk -> pk
--
-- | from ghci/ObjLinker.c
--
-- Load a .so type object file.
--
loadShared :: FilePath -> IO Module
loadShared str = do
#if DEBUG
putStrLn $ " shared: " ++ str
#endif
maybe_errmsg <- withCString str $ \dll -> c_addDLL dll
if maybe_errmsg == nullPtr
then return (Module str (mkModid str) Shared emptyIface (Package (mkModid str)))
else do e <- peekCString maybe_errmsg
panic $ "loadShared: couldn't load `"++str++"\' because "++e
--
-- Load a -package that we might need, implicitly loading the cbits too
-- The argument is the name of package (e.g. \"concurrent\")
--
-- How to find a package is determined by the package.conf info we store
-- in the environment. It is just a matter of looking it up.
--
-- Not printing names of dependent pkgs
--
loadPackage :: String -> IO ()
loadPackage p = do
#if DEBUG
putStr (' ':p) >> hFlush stdout
#endif
(libs,dlls) <- lookupPkg p
mapM_ (\l -> loadObject l (Package (mkModid l))) libs
#if DEBUG
putStr (' ':show dlls)
#endif
mapM_ loadShared dlls
--
-- Unload a -package, that has already been loaded. Unload the cbits
-- too. The argument is the name of the package.
--
-- May need to check if it exists.
--
-- Note that we currently need to unload everything. grumble grumble.
--
-- We need to add the version number to the package name with 6.4 and
-- over. "yi-0.1" for example. This is a bug really.
--
unloadPackage :: String -> IO ()
unloadPackage pkg = do
let pkg' = takeWhile (/= '-') pkg -- in case of *-0.1
libs <- liftM (\(a,_) -> (filter (isSublistOf pkg') ) a) (lookupPkg pkg)
flip mapM_ libs $ \p -> withCString p $ \c_p -> do
r <- c_unloadObj c_p
when (not r) (panic "unloadObj: failed")
rmModule (mkModid p) -- unrecord this module
--
-- load a package using the given package.conf to help
-- TODO should report if it doesn't actually load the package, instead
-- of mapM_ doing nothing like above.
--
loadPackageWith :: String -> [PackageConf] -> IO ()
loadPackageWith p pkgconfs = do
#if DEBUG
putStr "Loading package" >> hFlush stdout
#endif
mapM_ addPkgConf pkgconfs
loadPackage p
#if DEBUG
putStrLn " done"
#endif
-- ---------------------------------------------------------------------
-- module dependency loading
--
-- given an Foo.o vanilla object file, supposed to be a plugin compiled
-- by our library, find the associated .hi file. If this is found, load
-- the dependencies, packages first, then the modules. If it doesn't
-- exist, assume the user knows what they are doing and continue. The
-- linker will crash on them anyway. Second argument is any include
-- paths to search in
--
-- ToDo problem with absolute and relative paths, and different forms of
-- relative paths. A user may cause a dependency to be loaded, which
-- will search the incpaths, and perhaps find "./Foo.o". The user may
-- then explicitly load "Foo.o". These are the same, and the loader
-- should ignore the second load request. However, isLoaded will say
-- that "Foo.o" is not loaded, as the full string is used as a key to
-- the modenv fm. We need a canonical form for the keys -- is basename
-- good enough?
--
loadDepends :: FilePath -> [FilePath] -> IO (Iface,[Module])
loadDepends obj incpaths = do
let hifile = replaceSuffix obj hiSuf
exists <- doesFileExist hifile
if (not exists)
then do
#if DEBUG
putStrLn "No .hi file found." >> hFlush stdout
#endif
return (emptyIface,[]) -- could be considered fatal
else do hiface <- readIface hifile
let ds = mi_deps hiface
-- remove ones that we've already loaded
ds' <- filterM loaded (dep_mods ds)
-- now, try to generate a path to the actual .o file
-- fix up hierachical names
let mods_ = map (\s -> (s, map (\c ->
if c == '.' then '/' else c) $ decode s)) ds'
-- construct a list of possible dependent modules to load
let mods = concatMap (\p ->
map (\(hi,m) -> (hi,p </> m++".o")) mods_) incpaths
-- remove modules that don't exist
mods' <- filterM (\(_,y) -> doesFileExist y) $
nubBy (\v u -> snd v == snd u) mods
-- now remove duplicate valid paths to the same object
let mods'' = nubBy (\v u -> fst v == fst u) mods'
-- and find some packages to load, as well.
let ps = dep_pkgs ds
ps' <- filterM loaded (nub ps)
#if DEBUG
when (not (null ps')) $
putStr "Loading package" >> hFlush stdout
#endif
mapM_ loadPackage ps'
#if DEBUG
when (not (null ps')) $
putStr " ... linking ... " >> hFlush stdout
#endif
resolveObjs (mapM_ unloadPackage ps')
#if DEBUG
when (not (null ps')) $ putStrLn "done"
putStr "Loading object"
mapM_ (\(m,_) -> putStr (" "++(decode m)) >> hFlush stdout) mods''
#endif
moduleDeps <- mapM (\(hi,m) -> loadObject m (Object hi)) mods''
return (hiface,moduleDeps)
-- ---------------------------------------------------------------------
-- Nice interface to .hi parser
--
getImports :: String -> IO [String]
getImports m = do
hi <- readIface (m ++ hiSuf)
return $ dep_mods (mi_deps hi)
-- ---------------------------------------------------------------------
-- C interface
--
foreign import ccall unsafe "lookupSymbol"
c_lookupSymbol :: CString -> IO (Ptr a)
foreign import ccall unsafe "loadObj"
c_loadObj :: CString -> IO Bool
foreign import ccall unsafe "unloadObj"
c_unloadObj :: CString -> IO Bool
foreign import ccall unsafe "resolveObjs"
c_resolveObjs :: IO Bool
foreign import ccall unsafe "addDLL"
c_addDLL :: CString -> IO CString
foreign import ccall unsafe "initLinker"
initLinker :: IO ()

View File

@ -0,0 +1,52 @@
--
-- Copyright (c) 2005 Lemmih <lemmih@gmail.com>
-- Copyright (c) 2005 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This program is free software; you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation; either version 2 of
-- the License, or (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-- 02111-1307, USA.
--
module System.Plugins.LoadTypes
( Key (..)
, Symbol
, Type
, Errors
, PackageConf
, Module (..)
, ObjType (..)
) where
import Language.Hi.Parser
data Key = Object String | Package String
type Symbol = String
type Type = String
type Errors = [String]
type PackageConf = FilePath
data Module = Module { path :: !FilePath
, mname :: !String
, kind :: !ObjType
, iface :: Iface -- cache the iface
, key :: Key
}
instance Ord Module where
compare m1 m2 = mname m1 `compare` mname m2
instance Eq Module where
m1 == m2 = mname m1 == mname m2
data ObjType = Vanilla | Shared deriving Eq

356
System/Plugins/Make.hs Normal file
View File

@ -0,0 +1,356 @@
{-# OPTIONS -cpp #-}
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
module System.Plugins.Make (
make,
makeAll,
makeWith,
MakeStatus(..),
MakeCode(..),
hasChanged,
hasChanged',
recompileAll,
recompileAll',
merge,
mergeTo,
mergeToDir,
MergeStatus(..),
MergeCode,
makeClean,
makeCleaner,
build, {- internal -}
) where
import System.Plugins.Utils
import System.Plugins.Parser
import System.Plugins.LoadTypes ( Module (Module, path) )
import System.Plugins.Consts ( ghc, hiSuf, objSuf, hsSuf )
import System.Plugins.Process ( exec )
import System.Plugins.Env ( lookupMerged, addMerge
, getModuleDeps)
#if DEBUG
import System.IO (hFlush, stdout, openFile, IOMode(..),hClose, hPutStr)
#else
import System.IO (openFile, IOMode(..),hClose,hPutStr)
#endif
import System.Directory ( doesFileExist, removeFile
, getModificationTime )
import Control.Exception ( handleJust )
import GHC.IOBase ( Exception(IOException) )
#if __GLASGOW_HASKELL__ >= 604
import System.IO.Error ( isDoesNotExistError )
#endif
------------------------------------------------------------------------
--
-- A better compiler status.
--
data MakeStatus
= MakeSuccess MakeCode FilePath
| MakeFailure Errors
deriving (Eq,Show)
data MakeCode = ReComp | NotReq
deriving (Eq,Show)
------------------------------------------------------------------------
--
-- An equivalent status for the preprocessor (merge)
--
data MergeStatus
= MergeSuccess MergeCode Args FilePath
| MergeFailure Errors
deriving (Eq,Show)
type MergeCode = MakeCode
type Args = [Arg]
type Errors = [String]
--
-- |Returns @True@ if the module or any of its dependencies have older object files than source files.
-- Defaults to @True@ if some files couldn't be located.
--
hasChanged :: Module -> IO Bool
hasChanged = hasChanged' ["hs","lhs"]
hasChanged' :: [String] -> Module -> IO Bool
hasChanged' suffices m@(Module {path = p})
= do modFile <- doesFileExist p
mbFile <- findFile suffices p
case mbFile of
Just f | modFile
-> do srcT <- getModificationTime f
objT <- getModificationTime p
if srcT > objT
then return True
else do deps <- getModuleDeps m
depsStatus <- mapM (hasChanged' suffices) deps
return (or depsStatus)
_ -> return True
--
-- |Same as 'makeAll' but with better recompilation checks since module dependencies are known.
--
recompileAll :: Module -> [Arg] -> IO MakeStatus
recompileAll = recompileAll' ["hs","lhs"]
recompileAll' :: [String] -> Module -> [Arg] -> IO MakeStatus
recompileAll' suffices m args
= do changed <- hasChanged m
if changed
then do mbSource <- findFile suffices (path m)
case mbSource of
Nothing
-> error $ "Couldn't find source for object file: " ++ path m
Just source
-> makeAll source args
else return (MakeSuccess NotReq (path m))
-- touch.
-- ---------------------------------------------------------------------
-- | Standard make. Compile a single module, unconditionally.
-- Behaves like ghc -c
--
make :: FilePath -> [Arg] -> IO MakeStatus
make src args = rawMake src ("-c":args) True
-- | Recursive make. Compile a module, and its dependencies if we can
-- find them. Takes the top-level file as the first argument.
-- Behaves like ghc --make
--
makeAll :: FilePath -> [Arg] -> IO MakeStatus
makeAll src args =
rawMake src ( "--make":"-no-hs-main":"-no-link":"-v0":args ) False
-- | merge two files; then make them. will leave a .o and .hi file in tmpDir.
--
makeWith :: FilePath -- ^ a src file
-> FilePath -- ^ a syntax stub file
-> [Arg] -- ^ any required args
-> IO MakeStatus -- ^ path to an object file
makeWith src stub args = do
status <- merge src stub
case status of
MergeFailure errs -> return $ MakeFailure ("merge failed:\n":errs)
MergeSuccess _ args' tmpf -> do
status' <- rawMake tmpf ("-c": args' ++ args) True
return status'
-- ---------------------------------------------------------------------
-- rawMake : really do the compilation
-- Conditional on file modification times, compile a .hs file
-- When using 'make', the name of the src file must be the name of the
-- .o file you are expecting back
--
-- Problem: we use GHC producing stdout to indicate compilation failure.
-- We should instead check the error conditions. I.e. --make will
-- produce output, but of course compiles correctly. TODO
-- So, e.g. --make requires -v0 to stop spurious output confusing
-- rawMake
--
-- Problem :: makeAll incorrectly refuses to recompile if the top level
-- src isn't new.
--
rawMake :: FilePath -- ^ src
-> [Arg] -- ^ any compiler args
-> Bool -- ^ do our own recompilation checking
-> IO MakeStatus
rawMake src args docheck = do
src_exists <- doesFileExist src
if not src_exists
then return $ MakeFailure ["Source file does not exist: "++src]
else do {
; let (obj,_) = outFilePath src args
; src_changed <- if docheck then src `newer` obj else return True
; if not src_changed
then return $ MakeSuccess NotReq obj
else do
#if DEBUG
putStr "Compiling object ... " >> hFlush stdout
#endif
err <- build src obj args
#if DEBUG
putStrLn "done"
#endif
return $ if null err
then MakeSuccess ReComp obj
else MakeFailure err
}
--
-- compile a .hs file to a .o file
--
-- If the plugin needs to import an api (which should be almost
-- everyone) then the ghc flags to find the api need to be provided as
-- arguments
--
build :: FilePath -- path to .hs source
-> FilePath -- path to object file
-> [String] -- any extra cmd line flags
-> IO [String]
build src obj extra_opts = do
let odir = dirname obj -- *always* put the .hi file next to the .o file
let ghc_opts = [ "-Onot" ]
output = [ "-o", obj, "-odir", odir,
"-hidir", odir, "-i" ++ odir ]
let flags = ghc_opts ++ output ++ extra_opts ++ [src]
#if DEBUG
-- env.
putStr $ show $ ghc : flags
#endif
(_out,err) <- exec ghc flags -- this is a fork()
obj_exists <- doesFileExist obj -- sanity
return $ if not obj_exists && null err -- no errors, but no object?
then ["Compiled, but didn't create object file `"++obj++"'!"]
else err
-- ---------------------------------------------------------------------
-- | Merge to source files into a temporary file. If we've tried to
-- merge these two stub files before, then reuse the module name (helps
-- recompilation checking)
--
merge :: FilePath -> FilePath -> IO MergeStatus
merge src stb = do
m_mod <- lookupMerged src stb
(out,domerge) <- case m_mod of
Nothing -> do out <- mkUnique
addMerge src stb (dropSuffix out)
return (out, True) -- definitely out of date
Just nm -> return $ (nm <> hsSuf, False)
rawMerge src stb out domerge
-- | Merge to source files and store them in the specified output file,
-- instead of a temp file as merge does.
--
mergeTo :: FilePath -> FilePath -> FilePath -> IO MergeStatus
mergeTo src stb out = rawMerge src stb out False
mergeToDir :: FilePath -> FilePath -> FilePath -> IO MergeStatus
mergeToDir src stb dir = do
out <- mkUniqueIn dir
rawMerge src stb out True
-- ---------------------------------------------------------------------
-- Conditional on file modification times, merge a src file with a
-- syntax stub file into a result file.
--
-- Merge should only occur if the srcs has changed since last time.
-- Parser errors result in MergeFailure, and are reported to the client
--
-- Also returns a list of cmdline flags found in pragmas in the src of
-- the files. This last feature exists as OPTION pragmas aren't handled
-- (for obvious reasons, relating to the implementation of OPTIONS
-- parsing in GHC) by the library parser, and, also, we want a way for
-- the user to introduce *dynamic* cmd line flags in the .conf file.
-- This is achieved via the GLOBALOPTIONS pragma : an extension to ghc
-- pragma syntax
--
rawMerge :: FilePath -> FilePath -> FilePath -> Bool -> IO MergeStatus
rawMerge src stb out always_merge = do
src_exists <- doesFileExist src
stb_exists <- doesFileExist stb
case () of {_
| not src_exists -> return $
MergeFailure ["Source file does not exist : "++src]
| not stb_exists -> return $
MergeFailure ["Source file does not exist : "++stb]
| otherwise -> do {
;do_merge <- do src_changed <- src `newer` out
stb_changed <- stb `newer` out
return $ src_changed || stb_changed
;if not do_merge && not always_merge
then return $ MergeSuccess NotReq [] out
else do
src_str <- readFile src
stb_str <- readFile stb
let (a,a') = parsePragmas src_str
(b,b') = parsePragmas stb_str
opts = a ++ a' ++ b ++ b'
let e_src_syn = parse src src_str
e_stb_syn = parse stb stb_str
-- check if there were parser errors
case (e_src_syn,e_stb_syn) of
(Left e, _) -> return $ MergeFailure [e]
(_ , Left e) -> return $ MergeFailure [e]
(Right src_syn, Right stb_syn) -> do {
;let mrg_syn = mergeModules src_syn stb_syn
mrg_syn'= replaceModName mrg_syn (mkModid $ basename out)
mrg_str = pretty mrg_syn'
;hdl <- openFile out WriteMode -- overwrite!
;hPutStr hdl mrg_str ; hClose hdl
;return $ MergeSuccess ReComp opts out -- must have recreated file
}}}
-- ---------------------------------------------------------------------
-- | makeClean : assuming we some element of [f.hs,f.hi,f.o], remove the
-- .hi and .o components. Silently ignore any missing components. *Does
-- not remove .hs files*. To do that use makeCleaner. This would be
-- useful for merged files, for example.
--
makeClean :: FilePath -> IO ()
makeClean f = let f_hi = dropSuffix f <> hiSuf
f_o = dropSuffix f <> objSuf
in mapM_ rm_f [f_hi, f_o]
makeCleaner :: FilePath -> IO ()
makeCleaner f = makeClean f >> rm_f (dropSuffix f <> hsSuf)
-- internal:
-- try to remove a file, ignoring if it didn't exist in the first place
-- Doesn't seem to be able to remove all files in all circumstances, why?
--
rm_f f = handleJust doesntExist (\_->return ()) (removeFile f)
where
doesntExist (IOException ioe)
| isDoesNotExistError ioe = Just ()
| otherwise = Nothing
doesntExist _ = Nothing

67
System/Plugins/Package.hs Normal file
View File

@ -0,0 +1,67 @@
--
-- Copyright (C) 2004 Sean Seefried - http://www.cse.unsw.edu.au/~sseefried
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
-- Read information from a package.conf
--
module System.Plugins.Package {-everything-} where
type PackageName = String
--
-- Take directly from ghc/utils/ghc-pkg/Package.hs
--
data PackageConfig = Package {
name :: PackageName,
auto :: Bool,
import_dirs :: [FilePath],
source_dirs :: [FilePath],
library_dirs :: [FilePath],
hs_libraries :: [String],
extra_libraries :: [String],
include_dirs :: [FilePath],
c_includes :: [String],
package_deps :: [String],
extra_ghc_opts :: [String],
extra_cc_opts :: [String],
extra_ld_opts :: [String],
framework_dirs :: [FilePath], -- ignored everywhere but on Darwin/MacOS X
extra_frameworks:: [String] -- ignored everywhere but on Darwin/MacOS X
} deriving Show
defaultPackageConfig = Package {
name = error "defaultPackage",
auto = False,
import_dirs = [],
source_dirs = [],
library_dirs = [],
hs_libraries = [],
extra_libraries = [],
include_dirs = [],
c_includes = [],
package_deps = [],
extra_ghc_opts = [],
extra_cc_opts = [],
extra_ld_opts = [],
framework_dirs = [],
extra_frameworks= []
}

View File

@ -0,0 +1,96 @@
{-# OPTIONS -cpp #-}
--
-- Copyright (C) 2005 Sean Seefried - http://www.cse.unsw.edu.au/~sseefried
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
-- We export an abstract interface to package conf`s because we have
-- to handle either traditional or Cabal style package conf`s.
--
module System.Plugins.PackageAPI (
PackageName
, PackageConfig
, packageName
, packageName_
, importDirs
, hsLibraries
, libraryDirs
, extraLibraries
, ldOptions
, packageDeps
, updImportDirs
, updLibraryDirs
) where
#include "../../config.h"
#if CABAL == 1 || __GLASGOW_HASKELL__ >= 604
import Distribution.InstalledPackageInfo
import Distribution.Package
#else
import System.Plugins.Package
#endif
packageName :: PackageConfig -> PackageName
packageDeps :: PackageConfig -> [PackageName]
updImportDirs :: ([FilePath] -> [FilePath]) -> PackageConfig -> PackageConfig
updLibraryDirs :: ([FilePath] -> [FilePath]) -> PackageConfig -> PackageConfig
-- We use different package.conf parsers when running on 6.2.x or 6.4
#if CABAL == 1 || __GLASGOW_HASKELL__ >= 604
type PackageName = String
type PackageConfig = InstalledPackageInfo
packageName = showPackageId . package
packageName_ = pkgName . package
packageDeps = (map showPackageId) . depends
updImportDirs f pk@(InstalledPackageInfo { importDirs = idirs }) =
pk { importDirs = f idirs }
updLibraryDirs f pk@(InstalledPackageInfo { libraryDirs = ldirs }) =
pk { libraryDirs = f ldirs }
#else
packageName = name
packageName_ = name
packageDeps = package_deps
updImportDirs f pk@(Package {import_dirs = idirs})
= pk {import_dirs = f idirs}
updLibraryDirs f pk@(Package {library_dirs = ldirs})
= pk {library_dirs = f ldirs}
importDirs :: PackageConfig -> [FilePath]
importDirs = import_dirs
hsLibraries :: PackageConfig -> [String]
hsLibraries = hs_libraries
libraryDirs :: PackageConfig -> [FilePath]
libraryDirs = library_dirs
extraLibraries :: PackageConfig -> [String]
extraLibraries = extra_libraries
ldOptions :: PackageConfig -> [String]
ldOptions = extra_ld_opts
#endif

View File

@ -0,0 +1,218 @@
--
-- Copyright (C) 2005 Sean Seefried - http://www.cse.unsw.edu.au/~sseefried
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
--
-- Taken (apart from the most minor of alterations) from
-- ghc/utils/ghc-pkg/ParsePkgConfLite.hs from GHC 6.2.2 source tree
-- and then modified to mimic the behaviour of the parser within
-- ghc/compiler/main/ParsePkgConf.y in GHC 6.4, without importing
-- heavy-weight infrastructure from the GHC source tree such as module
-- FastString, Lexer, etc.
--
-- (c) Copyright 2002, The University Court of the University of Glasgow.
--
{
{-# OPTIONS -w #-}
module System.Plugins.ParsePkgConfCabal (
parsePkgConf, parseOnePkgConf
) where
import Distribution.InstalledPackageInfo
import Distribution.Package
import Distribution.Version
import Data.Char ( isSpace, isAlpha, isAlphaNum, isUpper, isDigit )
import Data.List ( break )
}
%token
'{' { ITocurly }
'}' { ITccurly }
'[' { ITobrack }
']' { ITcbrack }
',' { ITcomma }
'=' { ITequal }
VARID { ITvarid $$ }
CONID { ITconid $$ }
STRING { ITstring $$ }
INT { ITinteger $$ }
%name parse pkgconf
%name parseOne pkg
%tokentype { Token }
%%
pkgconf :: { [ PackageConfig ] }
: '[' ']' { [] }
| '[' pkgs ']' { reverse $2 }
pkgs :: { [ PackageConfig ] }
: pkg { [ $1 ] }
| pkgs ',' pkg { $3 : $1 }
pkg :: { PackageConfig }
: CONID '{' fields '}' { $3 defaultPackageConfig }
fields :: { PackageConfig -> PackageConfig }
: field { \p -> $1 p }
| fields ',' field { \p -> $1 ($3 p) }
field :: { PackageConfig -> PackageConfig }
: VARID '=' pkgid
{\p -> case $1 of
"package" -> p {package = $3}
_ -> error "unknown key in config file" }
| VARID '=' STRING { id }
-- we aren't interested in the string fields, they're all
-- boring (copyright, maintainer etc.)
| VARID '=' CONID
{ case $1 of {
"exposed" ->
case $3 of {
"True" -> (\p -> p {exposed=True});
"False" -> (\p -> p {exposed=False});
_ -> error "exposed must be either True or False" };
"license" -> id; -- not interested
_ -> error "unknown constructor" }
}
| VARID '=' CONID STRING { id }
-- another case of license
| VARID '=' strlist
{\p -> case $1 of
"exposedModules" -> p{exposedModules = $3}
"hiddenModules" -> p{hiddenModules = $3}
"importDirs" -> p{importDirs = $3}
"libraryDirs" -> p{libraryDirs = $3}
"hsLibraries" -> p{hsLibraries = $3}
"extraLibraries" -> p{extraLibraries = $3}
"includeDirs" -> p{includeDirs = $3}
"includes" -> p{includes = $3}
"hugsOptions" -> p{hugsOptions = $3}
"ccOptions" -> p{ccOptions = $3}
"ldOptions" -> p{ldOptions = $3}
"frameworkDirs" -> p{frameworkDirs = $3}
"frameworks" -> p{frameworks = $3}
"haddockInterfaces" -> p{haddockInterfaces = $3}
"haddockHTMLs" -> p{haddockHTMLs = $3}
"depends" -> p{depends = []}
-- empty list only, non-empty handled below
other -> p
}
| VARID '=' pkgidlist
{ case $1 of
"depends" -> (\p -> p{depends = $3})
_other -> error "unknown key in config file"
}
pkgid :: { PackageIdentifier }
: CONID '{' VARID '=' STRING ',' VARID '=' version '}'
{ PackageIdentifier{ pkgName = $5,
pkgVersion = $9 } }
version :: { Version }
: CONID '{' VARID '=' intlist ',' VARID '=' strlist '}'
{ Version{ versionBranch=$5, versionTags=$9 } }
pkgidlist :: { [PackageIdentifier] }
: '[' pkgids ']' { $2 }
-- empty list case is covered by strlist, to avoid conflicts
pkgids :: { [PackageIdentifier] }
: pkgid { [ $1 ] }
| pkgid ',' pkgids { $1 : $3 }
intlist :: { [Int] }
: '[' ']' { [] }
| '[' ints ']' { $2 }
ints :: { [Int] }
: INT { [ fromIntegral $1 ] }
| INT ',' ints { fromIntegral $1 : $3 }
strlist :: { [String] }
: '[' ']' { [] }
| '[' strs ']' { reverse $2 }
strs :: { [String] }
: STRING { [ $1 ] }
| strs ',' STRING { $3 : $1 }
{
type PackageConfig = InstalledPackageInfo
defaultPackageConfig = emptyInstalledPackageInfo
data Token
= ITocurly
| ITccurly
| ITobrack
| ITcbrack
| ITcomma
| ITequal
| ITvarid String
| ITconid String
| ITstring String
| ITinteger Int
lexer :: String -> [Token]
lexer [] = []
lexer ('{':cs) = ITocurly : lexer cs
lexer ('}':cs) = ITccurly : lexer cs
lexer ('[':cs) = ITobrack : lexer cs
lexer (']':cs) = ITcbrack : lexer cs
lexer (',':cs) = ITcomma : lexer cs
lexer ('=':cs) = ITequal : lexer cs
lexer ('"':cs) = lexString cs ""
lexer (c:cs)
| isSpace c = lexer cs
| isAlpha c = lexID (c:cs)
| isDigit c = lexInt (c:cs)
lexer _ = error ( "Unexpected token")
lexID cs = (if isUpper (head cs) then ITconid else ITvarid) id : lexer rest
where
(id,rest) = break (\c -> c /= '_' && not (isAlphaNum c)) cs
lexInt cs = let (intStr, rest) = span isDigit cs
in ITinteger (read intStr) : lexer rest
lexString ('"':cs) s = ITstring (reverse s) : lexer cs
lexString ('\\':c:cs) s = lexString cs (c:s)
lexString (c:cs) s = lexString cs (c:s)
happyError _ = error "Couldn't parse package configuration."
parsePkgConf :: String -> [PackageConfig]
parsePkgConf = parse . lexer
parseOnePkgConf :: String -> PackageConfig
parseOnePkgConf = parseOne . lexer
}

View File

@ -0,0 +1,616 @@
{-# OPTIONS -fglasgow-exts -cpp #-}
{-# OPTIONS -w #-}
module System.Plugins.ParsePkgConfLite (
parsePkgConf, parseOnePkgConf
) where
import System.Plugins.Package ( PackageConfig(..), defaultPackageConfig )
import Char ( isSpace, isAlpha, isAlphaNum, isUpper )
import List ( break )
import Array
#if __GLASGOW_HASKELL__ >= 503
import GHC.Exts
#else
import GlaExts
#endif
-- parser produced by Happy Version 1.15
newtype HappyAbsSyn = HappyAbsSyn (() -> ())
happyIn5 :: ([ PackageConfig ]) -> (HappyAbsSyn )
happyIn5 x = unsafeCoerce# x
{-# INLINE happyIn5 #-}
happyOut5 :: (HappyAbsSyn ) -> ([ PackageConfig ])
happyOut5 x = unsafeCoerce# x
{-# INLINE happyOut5 #-}
happyIn6 :: ([ PackageConfig ]) -> (HappyAbsSyn )
happyIn6 x = unsafeCoerce# x
{-# INLINE happyIn6 #-}
happyOut6 :: (HappyAbsSyn ) -> ([ PackageConfig ])
happyOut6 x = unsafeCoerce# x
{-# INLINE happyOut6 #-}
happyIn7 :: (PackageConfig) -> (HappyAbsSyn )
happyIn7 x = unsafeCoerce# x
{-# INLINE happyIn7 #-}
happyOut7 :: (HappyAbsSyn ) -> (PackageConfig)
happyOut7 x = unsafeCoerce# x
{-# INLINE happyOut7 #-}
happyIn8 :: (PackageConfig -> PackageConfig) -> (HappyAbsSyn )
happyIn8 x = unsafeCoerce# x
{-# INLINE happyIn8 #-}
happyOut8 :: (HappyAbsSyn ) -> (PackageConfig -> PackageConfig)
happyOut8 x = unsafeCoerce# x
{-# INLINE happyOut8 #-}
happyIn9 :: (PackageConfig -> PackageConfig) -> (HappyAbsSyn )
happyIn9 x = unsafeCoerce# x
{-# INLINE happyIn9 #-}
happyOut9 :: (HappyAbsSyn ) -> (PackageConfig -> PackageConfig)
happyOut9 x = unsafeCoerce# x
{-# INLINE happyOut9 #-}
happyIn10 :: ([String]) -> (HappyAbsSyn )
happyIn10 x = unsafeCoerce# x
{-# INLINE happyIn10 #-}
happyOut10 :: (HappyAbsSyn ) -> ([String])
happyOut10 x = unsafeCoerce# x
{-# INLINE happyOut10 #-}
happyIn11 :: ([String]) -> (HappyAbsSyn )
happyIn11 x = unsafeCoerce# x
{-# INLINE happyIn11 #-}
happyOut11 :: (HappyAbsSyn ) -> ([String])
happyOut11 x = unsafeCoerce# x
{-# INLINE happyOut11 #-}
happyIn12 :: (Bool) -> (HappyAbsSyn )
happyIn12 x = unsafeCoerce# x
{-# INLINE happyIn12 #-}
happyOut12 :: (HappyAbsSyn ) -> (Bool)
happyOut12 x = unsafeCoerce# x
{-# INLINE happyOut12 #-}
happyInTok :: Token -> (HappyAbsSyn )
happyInTok x = unsafeCoerce# x
{-# INLINE happyInTok #-}
happyOutTok :: (HappyAbsSyn ) -> Token
happyOutTok x = unsafeCoerce# x
{-# INLINE happyOutTok #-}
happyActOffsets :: HappyAddr
happyActOffsets = HappyA# "\x1f\x00\x1e\x00\x1d\x00\x1b\x00\x1a\x00\x1c\x00\x19\x00\x01\x00\x0e\x00\x00\x00\x00\x00\x17\x00\x08\x00\x00\x00\x16\x00\x00\x00\x13\x00\x00\x00\xfe\xff\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00"#
happyGotoOffsets :: HappyAddr
happyGotoOffsets = HappyA# "\x18\x00\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\xfd\xff\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"#
happyDefActions :: HappyAddr
happyDefActions = HappyA# "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfb\xff\xfd\xff\x00\x00\x00\x00\xf8\xff\x00\x00\xfc\xff\x00\x00\xfa\xff\x00\x00\xf9\xff\x00\x00\xf7\xff\xf4\xff\xf5\xff\x00\x00\xef\xff\xf6\xff\x00\x00\xf3\xff\xf1\xff\xf2\xff\x00\x00\xf0\xff"#
happyCheck :: HappyAddr
happyCheck = HappyA# "\xff\xff\x03\x00\x05\x00\x04\x00\x07\x00\x04\x00\x08\x00\x09\x00\x09\x00\x08\x00\x02\x00\x01\x00\x02\x00\x05\x00\x03\x00\x04\x00\x04\x00\x05\x00\x04\x00\x05\x00\x04\x00\x06\x00\x02\x00\x02\x00\x00\x00\x07\x00\x09\x00\x08\x00\x06\x00\x01\x00\x07\x00\x04\x00\x03\x00\xff\xff\x03\x00\x0a\x00\x0a\x00\xff\xff\x08\x00\xff\xff\xff\xff\xff\xff"#
happyTable :: HappyAddr
happyTable = HappyA# "\x00\x00\x19\x00\x16\x00\x1d\x00\x17\x00\x0b\x00\x1a\x00\x1b\x00\x1e\x00\x06\x00\x14\x00\x08\x00\x09\x00\x15\x00\x0c\x00\x0d\x00\x1f\x00\x20\x00\x10\x00\x11\x00\x15\x00\x1b\x00\x11\x00\x04\x00\x06\x00\x0f\x00\x21\x00\x06\x00\x13\x00\x0c\x00\x0f\x00\x0b\x00\x04\x00\x00\x00\x08\x00\xff\xff\xff\xff\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00"#
happyReduceArr = array (2, 16) [
(2 , happyReduce_2),
(3 , happyReduce_3),
(4 , happyReduce_4),
(5 , happyReduce_5),
(6 , happyReduce_6),
(7 , happyReduce_7),
(8 , happyReduce_8),
(9 , happyReduce_9),
(10 , happyReduce_10),
(11 , happyReduce_11),
(12 , happyReduce_12),
(13 , happyReduce_13),
(14 , happyReduce_14),
(15 , happyReduce_15),
(16 , happyReduce_16)
]
happy_n_terms = 11 :: Int
happy_n_nonterms = 8 :: Int
happyReduce_2 = happySpecReduce_2 0# happyReduction_2
happyReduction_2 happy_x_2
happy_x_1
= happyIn5
([]
)
happyReduce_3 = happySpecReduce_3 0# happyReduction_3
happyReduction_3 happy_x_3
happy_x_2
happy_x_1
= case happyOut6 happy_x_2 of { happy_var_2 ->
happyIn5
(reverse happy_var_2
)}
happyReduce_4 = happySpecReduce_1 1# happyReduction_4
happyReduction_4 happy_x_1
= case happyOut7 happy_x_1 of { happy_var_1 ->
happyIn6
([ happy_var_1 ]
)}
happyReduce_5 = happySpecReduce_3 1# happyReduction_5
happyReduction_5 happy_x_3
happy_x_2
happy_x_1
= case happyOut6 happy_x_1 of { happy_var_1 ->
case happyOut7 happy_x_3 of { happy_var_3 ->
happyIn6
(happy_var_3 : happy_var_1
)}}
happyReduce_6 = happyReduce 4# 2# happyReduction_6
happyReduction_6 (happy_x_4 `HappyStk`
happy_x_3 `HappyStk`
happy_x_2 `HappyStk`
happy_x_1 `HappyStk`
happyRest)
= case happyOut8 happy_x_3 of { happy_var_3 ->
happyIn7
(happy_var_3 defaultPackageConfig
) `HappyStk` happyRest}
happyReduce_7 = happySpecReduce_1 3# happyReduction_7
happyReduction_7 happy_x_1
= case happyOut9 happy_x_1 of { happy_var_1 ->
happyIn8
(\p -> happy_var_1 p
)}
happyReduce_8 = happySpecReduce_3 3# happyReduction_8
happyReduction_8 happy_x_3
happy_x_2
happy_x_1
= case happyOut8 happy_x_1 of { happy_var_1 ->
case happyOut9 happy_x_3 of { happy_var_3 ->
happyIn8
(\p -> happy_var_1 (happy_var_3 p)
)}}
happyReduce_9 = happySpecReduce_3 4# happyReduction_9
happyReduction_9 happy_x_3
happy_x_2
happy_x_1
= case happyOutTok happy_x_1 of { (ITvarid happy_var_1) ->
case happyOutTok happy_x_3 of { (ITstring happy_var_3) ->
happyIn9
(\p -> case happy_var_1 of
"name" -> p{name = happy_var_3}
_ -> error "unknown key in config file"
)}}
happyReduce_10 = happySpecReduce_3 4# happyReduction_10
happyReduction_10 happy_x_3
happy_x_2
happy_x_1
= case happyOutTok happy_x_1 of { (ITvarid happy_var_1) ->
case happyOut12 happy_x_3 of { happy_var_3 ->
happyIn9
(\p -> case happy_var_1 of {
"auto" -> p{auto = happy_var_3};
_ -> p }
)}}
happyReduce_11 = happySpecReduce_3 4# happyReduction_11
happyReduction_11 happy_x_3
happy_x_2
happy_x_1
= case happyOutTok happy_x_1 of { (ITvarid happy_var_1) ->
case happyOut10 happy_x_3 of { happy_var_3 ->
happyIn9
(\p -> case happy_var_1 of
"import_dirs" -> p{import_dirs = happy_var_3}
"library_dirs" -> p{library_dirs = happy_var_3}
"hs_libraries" -> p{hs_libraries = happy_var_3}
"extra_libraries" -> p{extra_libraries = happy_var_3}
"include_dirs" -> p{include_dirs = happy_var_3}
"c_includes" -> p{c_includes = happy_var_3}
"package_deps" -> p{package_deps = happy_var_3}
"extra_ghc_opts" -> p{extra_ghc_opts = happy_var_3}
"extra_cc_opts" -> p{extra_cc_opts = happy_var_3}
"extra_ld_opts" -> p{extra_ld_opts = happy_var_3}
"framework_dirs" -> p{framework_dirs = happy_var_3}
"extra_frameworks"-> p{extra_frameworks= happy_var_3}
_other -> p
)}}
happyReduce_12 = happySpecReduce_2 5# happyReduction_12
happyReduction_12 happy_x_2
happy_x_1
= happyIn10
([]
)
happyReduce_13 = happySpecReduce_3 5# happyReduction_13
happyReduction_13 happy_x_3
happy_x_2
happy_x_1
= case happyOut11 happy_x_2 of { happy_var_2 ->
happyIn10
(reverse happy_var_2
)}
happyReduce_14 = happySpecReduce_1 6# happyReduction_14
happyReduction_14 happy_x_1
= case happyOutTok happy_x_1 of { (ITstring happy_var_1) ->
happyIn11
([ happy_var_1 ]
)}
happyReduce_15 = happySpecReduce_3 6# happyReduction_15
happyReduction_15 happy_x_3
happy_x_2
happy_x_1
= case happyOut11 happy_x_1 of { happy_var_1 ->
case happyOutTok happy_x_3 of { (ITstring happy_var_3) ->
happyIn11
(happy_var_3 : happy_var_1
)}}
happyReduce_16 = happySpecReduce_1 7# happyReduction_16
happyReduction_16 happy_x_1
= case happyOutTok happy_x_1 of { (ITconid happy_var_1) ->
happyIn12
( case happy_var_1 of {
"True" -> True;
"False" -> False;
_ -> error ("unknown constructor in config file: " ++ happy_var_1) }
)}
happyNewToken action sts stk [] =
happyDoAction 10# (error "reading EOF!") action sts stk []
happyNewToken action sts stk (tk:tks) =
let cont i = happyDoAction i tk action sts stk tks in
case tk of {
ITocurly -> cont 1#;
ITccurly -> cont 2#;
ITobrack -> cont 3#;
ITcbrack -> cont 4#;
ITcomma -> cont 5#;
ITequal -> cont 6#;
ITvarid happy_dollar_dollar -> cont 7#;
ITconid happy_dollar_dollar -> cont 8#;
ITstring happy_dollar_dollar -> cont 9#;
_ -> happyError' (tk:tks)
}
happyError_ tk tks = happyError' (tk:tks)
newtype HappyIdentity a = HappyIdentity a
happyIdentity = HappyIdentity
happyRunIdentity (HappyIdentity a) = a
instance Monad HappyIdentity where
return = HappyIdentity
(HappyIdentity p) >>= q = q p
happyThen :: () => HappyIdentity a -> (a -> HappyIdentity b) -> HappyIdentity b
happyThen = (>>=)
happyReturn :: () => a -> HappyIdentity a
happyReturn = (return)
happyThen1 m k tks = (>>=) m (\a -> k a tks)
happyReturn1 :: () => a -> b -> HappyIdentity a
happyReturn1 = \a tks -> (return) a
happyError' :: () => [Token] -> HappyIdentity a
happyError' = HappyIdentity . happyError
parse tks = happyRunIdentity happySomeParser where
happySomeParser = happyThen (happyParse 0# tks) (\x -> happyReturn (happyOut5 x))
parseOne tks = happyRunIdentity happySomeParser where
happySomeParser = happyThen (happyParse 1# tks) (\x -> happyReturn (happyOut7 x))
happySeq = happyDontSeq
data Token
= ITocurly
| ITccurly
| ITobrack
| ITcbrack
| ITcomma
| ITequal
| ITvarid String
| ITconid String
| ITstring String
lexer :: String -> [Token]
lexer [] = []
lexer ('{':cs) = ITocurly : lexer cs
lexer ('}':cs) = ITccurly : lexer cs
lexer ('[':cs) = ITobrack : lexer cs
lexer (']':cs) = ITcbrack : lexer cs
lexer (',':cs) = ITcomma : lexer cs
lexer ('=':cs) = ITequal : lexer cs
lexer ('"':cs) = lexString cs ""
lexer (c:cs)
| isSpace c = lexer cs
| isAlpha c = lexID (c:cs) where
lexer _ = error "Unexpected token"
lexID cs = (if isUpper (head cs) then ITconid else ITvarid) id : lexer rest
where
(id,rest) = break (\c -> c /= '_' && not (isAlphaNum c)) cs
lexString ('"':cs) s = ITstring (reverse s) : lexer cs
lexString ('\\':c:cs) s = lexString cs (c:s)
lexString (c:cs) s = lexString cs (c:s)
happyError _ = error "Couldn't parse package configuration."
parsePkgConf :: String -> [PackageConfig]
parsePkgConf = parse . lexer
parseOnePkgConf :: String -> PackageConfig
parseOnePkgConf = parseOne . lexer
{-# LINE 1 "GenericTemplate.hs" #-}
{-# LINE 1 "<built-in>" #-}
{-# LINE 1 "<command line>" #-}
{-# LINE 1 "GenericTemplate.hs" #-}
-- $Id$
{-# LINE 28 "GenericTemplate.hs" #-}
data Happy_IntList = HappyCons Int# Happy_IntList
{-# LINE 49 "GenericTemplate.hs" #-}
{-# LINE 59 "GenericTemplate.hs" #-}
infixr 9 `HappyStk`
data HappyStk a = HappyStk a (HappyStk a)
-----------------------------------------------------------------------------
-- starting the parse
happyParse start_state = happyNewToken start_state notHappyAtAll notHappyAtAll
-----------------------------------------------------------------------------
-- Accepting the parse
-- If the current token is 0#, it means we've just accepted a partial
-- parse (a %partial parser). We must ignore the saved token on the top of
-- the stack in this case.
happyAccept 0# tk st sts (_ `HappyStk` ans `HappyStk` _) =
happyReturn1 ans
happyAccept j tk st sts (HappyStk ans _) =
(happyTcHack j (happyTcHack st)) (happyReturn1 ans)
-----------------------------------------------------------------------------
-- Arrays only: do the next action
happyDoAction i tk st
= {- nothing -}
case action of
0# -> {- nothing -}
happyFail i tk st
-1# -> {- nothing -}
happyAccept i tk st
n | (n <# (0# :: Int#)) -> {- nothing -}
(happyReduceArr ! rule) i tk st
where rule = (I# ((negateInt# ((n +# (1# :: Int#))))))
n -> {- nothing -}
happyShift new_state i tk st
where new_state = (n -# (1# :: Int#))
where off = indexShortOffAddr happyActOffsets st
off_i = (off +# i)
check = if (off_i >=# (0# :: Int#))
then (indexShortOffAddr happyCheck off_i ==# i)
else False
action | check = indexShortOffAddr happyTable off_i
| otherwise = indexShortOffAddr happyDefActions st
indexShortOffAddr (HappyA# arr) off =
#if __GLASGOW_HASKELL__ > 500
narrow16Int# i
#elif __GLASGOW_HASKELL__ == 500
intToInt16# i
#else
(i `iShiftL#` 16#) `iShiftRA#` 16#
#endif
where
#if __GLASGOW_HASKELL__ >= 503
i = word2Int# ((high `uncheckedShiftL#` 8#) `or#` low)
#else
i = word2Int# ((high `shiftL#` 8#) `or#` low)
#endif
high = int2Word# (ord# (indexCharOffAddr# arr (off' +# 1#)))
low = int2Word# (ord# (indexCharOffAddr# arr off'))
off' = off *# 2#
data HappyAddr = HappyA# Addr#
-----------------------------------------------------------------------------
-- HappyState data type (not arrays)
{-# LINE 170 "GenericTemplate.hs" #-}
-----------------------------------------------------------------------------
-- Shifting a token
happyShift new_state 0# tk st sts stk@(x `HappyStk` _) =
let i = (case unsafeCoerce# x of { (I# (i)) -> i }) in
-- trace "shifting the error token" $
happyDoAction i tk new_state (HappyCons (st) (sts)) (stk)
happyShift new_state i tk st sts stk =
happyNewToken new_state (HappyCons (st) (sts)) ((happyInTok (tk))`HappyStk`stk)
-- happyReduce is specialised for the common cases.
happySpecReduce_0 i fn 0# tk st sts stk
= happyFail 0# tk st sts stk
happySpecReduce_0 nt fn j tk st@((action)) sts stk
= happyGoto nt j tk st (HappyCons (st) (sts)) (fn `HappyStk` stk)
happySpecReduce_1 i fn 0# tk st sts stk
= happyFail 0# tk st sts stk
happySpecReduce_1 nt fn j tk _ sts@((HappyCons (st@(action)) (_))) (v1`HappyStk`stk')
= let r = fn v1 in
happySeq r (happyGoto nt j tk st sts (r `HappyStk` stk'))
happySpecReduce_2 i fn 0# tk st sts stk
= happyFail 0# tk st sts stk
happySpecReduce_2 nt fn j tk _ (HappyCons (_) (sts@((HappyCons (st@(action)) (_))))) (v1`HappyStk`v2`HappyStk`stk')
= let r = fn v1 v2 in
happySeq r (happyGoto nt j tk st sts (r `HappyStk` stk'))
happySpecReduce_3 i fn 0# tk st sts stk
= happyFail 0# tk st sts stk
happySpecReduce_3 nt fn j tk _ (HappyCons (_) ((HappyCons (_) (sts@((HappyCons (st@(action)) (_))))))) (v1`HappyStk`v2`HappyStk`v3`HappyStk`stk')
= let r = fn v1 v2 v3 in
happySeq r (happyGoto nt j tk st sts (r `HappyStk` stk'))
happyReduce k i fn 0# tk st sts stk
= happyFail 0# tk st sts stk
happyReduce k nt fn j tk st sts stk
= case happyDrop (k -# (1# :: Int#)) sts of
sts1@((HappyCons (st1@(action)) (_))) ->
let r = fn stk in -- it doesn't hurt to always seq here...
happyDoSeq r (happyGoto nt j tk st1 sts1 r)
happyMonadReduce k nt fn 0# tk st sts stk
= happyFail 0# tk st sts stk
happyMonadReduce k nt fn j tk st sts stk =
happyThen1 (fn stk) (\r -> happyGoto nt j tk st1 sts1 (r `HappyStk` drop_stk))
where sts1@((HappyCons (st1@(action)) (_))) = happyDrop k (HappyCons (st) (sts))
drop_stk = happyDropStk k stk
happyDrop 0# l = l
happyDrop n (HappyCons (_) (t)) = happyDrop (n -# (1# :: Int#)) t
happyDropStk 0# l = l
happyDropStk n (x `HappyStk` xs) = happyDropStk (n -# (1#::Int#)) xs
-----------------------------------------------------------------------------
-- Moving to a new state after a reduction
happyGoto nt j tk st =
{- nothing -}
happyDoAction j tk new_state
where off = indexShortOffAddr happyGotoOffsets st
off_i = (off +# nt)
new_state = indexShortOffAddr happyTable off_i
-----------------------------------------------------------------------------
-- Error recovery (0# is the error token)
-- parse error if we are in recovery and we fail again
happyFail 0# tk old_st _ stk =
-- trace "failing" $
happyError_ tk
{- We don't need state discarding for our restricted implementation of
"error". In fact, it can cause some bogus parses, so I've disabled it
for now --SDM
-- discard a state
happyFail 0# tk old_st (HappyCons ((action)) (sts))
(saved_tok `HappyStk` _ `HappyStk` stk) =
-- trace ("discarding state, depth " ++ show (length stk)) $
happyDoAction 0# tk action sts ((saved_tok`HappyStk`stk))
-}
-- Enter error recovery: generate an error token,
-- save the old token and carry on.
happyFail i tk (action) sts stk =
-- trace "entering error recovery" $
happyDoAction 0# tk action sts ( (unsafeCoerce# (I# (i))) `HappyStk` stk)
-- Internal happy errors:
notHappyAtAll = error "Internal Happy error\n"
-----------------------------------------------------------------------------
-- Hack to get the typechecker to accept our action functions
happyTcHack :: Int# -> a -> a
happyTcHack x y = y
{-# INLINE happyTcHack #-}
-----------------------------------------------------------------------------
-- Seq-ing. If the --strict flag is given, then Happy emits
-- happySeq = happyDoSeq
-- otherwise it emits
-- happySeq = happyDontSeq
happyDoSeq, happyDontSeq :: a -> b -> b
happyDoSeq a b = a `seq` b
happyDontSeq a b = b
-----------------------------------------------------------------------------
-- Don't inline any functions from the template. GHC has a nasty habit
-- of deciding to inline happyGoto everywhere, which increases the size of
-- the generated parser quite a bit.
{-# NOINLINE happyDoAction #-}
{-# NOINLINE happyTable #-}
{-# NOINLINE happyCheck #-}
{-# NOINLINE happyActOffsets #-}
{-# NOINLINE happyGotoOffsets #-}
{-# NOINLINE happyDefActions #-}
{-# NOINLINE happyShift #-}
{-# NOINLINE happySpecReduce_0 #-}
{-# NOINLINE happySpecReduce_1 #-}
{-# NOINLINE happySpecReduce_2 #-}
{-# NOINLINE happySpecReduce_3 #-}
{-# NOINLINE happyReduce #-}
{-# NOINLINE happyMonadReduce #-}
{-# NOINLINE happyGoto #-}
{-# NOINLINE happyFail #-}
-- end of Happy Template.

View File

@ -0,0 +1,159 @@
--
-- Copyright (C) 2004 Sean Seefried - http://www.cse.unsw.edu.au/~sseefried
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
--
-- Taken (apart from the most minor of alterations) from
-- ghc/utils/ghc-pkg/ParsePkgConfLite.hs:
--
-- (c) Copyright 2002, The University Court of the University of Glasgow.
--
{
{-# OPTIONS -w #-}
module System.Plugins.ParsePkgConfLite (
parsePkgConf, parseOnePkgConf
) where
import System.Plugins.Package ( PackageConfig(..), defaultPackageConfig )
import Char ( isSpace, isAlpha, isAlphaNum, isUpper )
import List ( break )
}
%token
'{' { ITocurly }
'}' { ITccurly }
'[' { ITobrack }
']' { ITcbrack }
',' { ITcomma }
'=' { ITequal }
VARID { ITvarid $$ }
CONID { ITconid $$ }
STRING { ITstring $$ }
%name parse pkgconf
%name parseOne pkg
%tokentype { Token }
%%
pkgconf :: { [ PackageConfig ] }
: '[' ']' { [] }
| '[' pkgs ']' { reverse $2 }
pkgs :: { [ PackageConfig ] }
: pkg { [ $1 ] }
| pkgs ',' pkg { $3 : $1 }
pkg :: { PackageConfig }
: CONID '{' fields '}' { $3 defaultPackageConfig }
fields :: { PackageConfig -> PackageConfig }
: field { \p -> $1 p }
| fields ',' field { \p -> $1 ($3 p) }
field :: { PackageConfig -> PackageConfig }
: VARID '=' STRING
{\p -> case $1 of
"name" -> p{name = $3}
_ -> error "unknown key in config file" }
| VARID '=' bool
{\p -> case $1 of {
"auto" -> p{auto = $3};
_ -> p } }
| VARID '=' strlist
{\p -> case $1 of
"import_dirs" -> p{import_dirs = $3}
"library_dirs" -> p{library_dirs = $3}
"hs_libraries" -> p{hs_libraries = $3}
"extra_libraries" -> p{extra_libraries = $3}
"include_dirs" -> p{include_dirs = $3}
"c_includes" -> p{c_includes = $3}
"package_deps" -> p{package_deps = $3}
"extra_ghc_opts" -> p{extra_ghc_opts = $3}
"extra_cc_opts" -> p{extra_cc_opts = $3}
"extra_ld_opts" -> p{extra_ld_opts = $3}
"framework_dirs" -> p{framework_dirs = $3}
"extra_frameworks"-> p{extra_frameworks= $3}
_other -> p
}
strlist :: { [String] }
: '[' ']' { [] }
| '[' strs ']' { reverse $2 }
strs :: { [String] }
: STRING { [ $1 ] }
| strs ',' STRING { $3 : $1 }
bool :: { Bool }
: CONID {% case $1 of {
"True" -> True;
"False" -> False;
_ -> error ("unknown constructor in config file: " ++ $1) } }
{
data Token
= ITocurly
| ITccurly
| ITobrack
| ITcbrack
| ITcomma
| ITequal
| ITvarid String
| ITconid String
| ITstring String
lexer :: String -> [Token]
lexer [] = []
lexer ('{':cs) = ITocurly : lexer cs
lexer ('}':cs) = ITccurly : lexer cs
lexer ('[':cs) = ITobrack : lexer cs
lexer (']':cs) = ITcbrack : lexer cs
lexer (',':cs) = ITcomma : lexer cs
lexer ('=':cs) = ITequal : lexer cs
lexer ('"':cs) = lexString cs ""
lexer (c:cs)
| isSpace c = lexer cs
| isAlpha c = lexID (c:cs) where
lexer _ = error "Unexpected token"
lexID cs = (if isUpper (head cs) then ITconid else ITvarid) id : lexer rest
where
(id,rest) = break (\c -> c /= '_' && not (isAlphaNum c)) cs
lexString ('"':cs) s = ITstring (reverse s) : lexer cs
lexString ('\\':c:cs) s = lexString cs (c:s)
lexString (c:cs) s = lexString cs (c:s)
happyError _ = error "Couldn't parse package configuration."
parsePkgConf :: String -> [PackageConfig]
parsePkgConf = parse . lexer
parseOnePkgConf :: String -> PackageConfig
parseOnePkgConf = parseOne . lexer
}

239
System/Plugins/Parser.hs Normal file
View File

@ -0,0 +1,239 @@
{-# OPTIONS -cpp -fglasgow-exts #-}
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This program is free software; you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation; either version 2 of
-- the License, or (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-- 02111-1307, USA.
--
module System.Plugins.Parser (
parse, mergeModules, pretty, parsePragmas,
HsModule(..) ,
replaceModName
) where
#include "../../config.h"
import Data.List
import Data.Char
import Data.Either
#if defined(WITH_HSX)
import Language.Haskell.Hsx
#else
import Language.Haskell.Parser
import Language.Haskell.Syntax
import Language.Haskell.Pretty
#endif
--
-- | parse a file (as a string) as Haskell src
--
parse :: FilePath -- ^ module name
-> String -- ^ haskell src
-> Either String HsModule -- ^ abstract syntax
parse f fsrc =
#if defined(WITH_HSX)
case parseFileContentsWithMode (ParseMode f) fsrc of
#else
case parseModuleWithMode (ParseMode f) fsrc of
#endif
ParseOk src -> Right src
ParseFailed loc _ -> Left $ srcmsg loc
where
srcmsg loc = "parse error in " ++ f ++ "\n" ++
"line: " ++ (show $ srcLine loc) ++
", col: " ++ (show $ srcColumn loc)++ "\n"
--
-- | pretty print haskell src
--
-- doesn't handle operators with '#' at the end. i.e. unsafeCoerce#
--
pretty :: HsModule -> String
pretty code = prettyPrintWithMode (defaultMode { linePragmas = True }) code
-- |
-- mergeModules : generate a full Haskell src file, give a .hs config
-- file, and a stub to take default syntax and decls from. Mostly we
-- just ensure they don't do anything bad, and that the names are
-- correct for the module.
--
-- Transformations:
--
-- * Take src location pragmas from the conf file (1st file)
-- * Use the template's (2nd argument) module name
-- * Only use export list from template (2nd arg)
-- * Merge top-level decls
-- * need to force the type of the plugin to match the stub,
-- overwriting any type they supply.
--
mergeModules :: HsModule -> -- ^ Configure module
HsModule -> -- ^ Template module
HsModule -- ^ A merge of the two
mergeModules (HsModule l _ _ is ds )
(HsModule _ m' es' is' ds')
= (HsModule l m' es'
(mImps m' is is')
(mDecl ds ds') )
--
-- replace Module name with String.
--
replaceModName :: HsModule -> String -> HsModule
replaceModName (HsModule l _ es is ds) nm = (HsModule l (Module nm) es is ds)
--
-- | merge import declarations:
--
-- * ensure that the config file doesn't import the stub name
-- * merge import lists uniquely, and when they match, merge their decls
--
-- TODO : we don't merge imports of the same module from both files.
-- We should, and then merge the decls in their import list
-- ** rename args, too confusing.
--
-- quick fix: strip all type signatures from the source.
--
mImps :: Module -> -- ^ plugin module name
[HsImportDecl] -> -- ^ conf file imports
[HsImportDecl] -> -- ^ stub file imports
[HsImportDecl]
mImps plug_mod cimps timps =
case filter (!~ self) cimps of cimps' -> unionBy (=~) cimps' timps
where
self = ( HsImportDecl undefined plug_mod undefined undefined undefined )
--
-- | merge top-level declarations
--
-- Remove decls found in template, using those from the config file.
-- Need to sort decls by types, then decls first, in both.
--
-- * could we write a pass to handle "editor, foo :: String" ?
--
-- we must keep the type from the template.
--
mDecl ds es = let ds' = filter (\t->not $ typeDecl t) ds -- rm type sigs from plugin
in sortBy decls $! unionBy (=~) ds' es
where
decls a b = compare (encoding a) (encoding b)
typeDecl :: HsDecl -> Bool
typeDecl (HsTypeSig _ _ _) = True
typeDecl _ = False
encoding :: HsDecl -> Int
encoding d = case d of
HsFunBind _ -> 1
HsPatBind _ _ _ _ -> 1
_ -> 0
--
-- syntactic equality over the useful Haskell abstract syntax
-- this may be extended if we try to merge the files more thoroughly
--
class SynEq a where
(=~) :: a -> a -> Bool
(!~) :: a -> a -> Bool
n !~ m = not (n =~ m)
instance SynEq HsDecl where
(HsPatBind _ (HsPVar n) _ _) =~ (HsPatBind _ (HsPVar m) _ _) = n == m
(HsTypeSig _ (n:_) _) =~ (HsTypeSig _ (m:_) _) = n == m
_ =~ _ = False
instance SynEq HsImportDecl where
(HsImportDecl _ m _ _ _) =~ (HsImportDecl _ n _ _ _) = n == m
--
-- | Parsing option pragmas.
--
-- This is not a type checker. If the user supplies bogus options,
-- they'll get slightly mystical error messages. Also, we *want* to
-- handle -package options, and other *static* flags. This is more than
-- GHC.
--
-- GHC user's guide :
-- "OPTIONS pragmas are only looked for at the top of your source
-- files, upto the first (non-literate,non-empty) line not
-- containing OPTIONS. Multiple OPTIONS pragmas are recognised."
--
-- based on getOptionsFromSource(), in main/DriverUtil.hs
--
parsePragmas :: String -- ^ input src
-> ([String],[String]) -- ^ normal options, global options
parsePragmas s = look $ lines s
where
look [] = ([],[])
look (l':ls) =
let l = remove_spaces l'
in case () of
() | null l -> look ls
| prefixMatch "#" l -> look ls
| prefixMatch "{-# LINE" l -> look ls
| Just (Option o) <- matchPragma l
-> let (as,bs) = look ls in (words o ++ as,bs)
| Just (Global g) <- matchPragma l
-> let (as,bs) = look ls in (as,words g ++ bs)
| otherwise -> ([],[])
--
-- based on main/DriverUtil.hs
--
-- extended to handle dynamic options too
--
data Pragma = Option !String | Global !String
matchPragma :: String -> Maybe Pragma
matchPragma s
| Just s1 <- maybePrefixMatch "{-#" s, -- -}
Just s2 <- maybePrefixMatch "OPTIONS" (remove_spaces s1),
Just s3 <- maybePrefixMatch "}-#" (reverse s2)
= Just (Option (reverse s3))
| Just s1 <- maybePrefixMatch "{-#" s, -- -}
Just s2 <- maybePrefixMatch "GLOBALOPTIONS" (remove_spaces s1),
Just s3 <- maybePrefixMatch "}-#" (reverse s2)
= Just (Global (reverse s3))
| otherwise
= Nothing
remove_spaces :: String -> String
remove_spaces = reverse . dropWhile isSpace . reverse . dropWhile isSpace
--
-- verbatim from utils/Utils.lhs
--
prefixMatch :: Eq a => [a] -> [a] -> Bool
prefixMatch [] _str = True
prefixMatch _pat [] = False
prefixMatch (p:ps) (s:ss) | p == s = prefixMatch ps ss
| otherwise = False
maybePrefixMatch :: String -> String -> Maybe String
maybePrefixMatch [] rest = Just rest
maybePrefixMatch (_:_) [] = Nothing
maybePrefixMatch (p:pat) (r:rest)
| p == r = maybePrefixMatch pat rest
| otherwise = Nothing

90
System/Plugins/Process.hs Normal file
View File

@ -0,0 +1,90 @@
{-# OPTIONS -cpp #-}
--
-- | A Posix.popen compatibility mapping.
--
-- If we use this, we should build -threaded
--
module System.Plugins.Process (exec, popen) where
import System.Exit
#if __GLASGOW_HASKELL__ >= 604
import System.IO
import System.Process
import Control.Concurrent (forkIO)
#else
import qualified Posix as P
#endif
import qualified Control.Exception
--
-- slight wrapper over popen for calls that don't care about stdin to the program
--
exec :: String -> [String] -> IO ([String],[String])
exec f as = do
(a,b,_) <- popen f as (Just [])
return (lines a, lines b)
#if __GLASGOW_HASKELL__ >= 604
type ProcessID = ProcessHandle
--
-- Ignoring exit status for now.
--
-- XXX there are still issues. Large amounts of output can cause what
-- seems to be a dead lock on the pipe write from runplugs, for example.
-- Posix.popen doesn't have this problem, so maybe we can reproduce its
-- pipe handling somehow.
--
popen :: FilePath -> [String] -> Maybe String -> IO (String,String,ProcessID)
popen file args minput =
Control.Exception.handle (\e -> return ([],show e,error (show e))) $ do
(inp,out,err,pid) <- runInteractiveProcess file args Nothing Nothing
case minput of
Just input -> hPutStr inp input >> hClose inp -- importante!
Nothing -> return ()
-- Now, grab the input
output <- hGetContents out
errput <- hGetContents err
-- SimonM sez:
-- ... avoids blocking the main thread, but ensures that all the
-- data gets pulled as it becomes available. you have to force the
-- output strings before waiting for the process to terminate.
--
forkIO (Control.Exception.evaluate (length output) >> return ())
forkIO (Control.Exception.evaluate (length errput) >> return ())
-- And now we wait. We must wait after we read, unsurprisingly.
exitCode <- waitForProcess pid -- blocks without -threaded, you're warned.
case exitCode of
ExitFailure code
| null errput -> let errMsg = file ++ ": failed with error code " ++ show code
in return ([],errMsg,error errMsg)
_ -> return (output,errput,pid)
#else
--
-- catch so that we can deal with forkProcess failing gracefully. and
-- getProcessStatus is needed so as not to get a bunch of zombies,
-- leading to forkProcess failing.
--
-- Large amounts of input will cause problems with blocking as we wait
-- on the process to finish. Make sure no lambdabot processes will
-- generate 1000s of lines of output.
--
popen :: FilePath -> [String] -> Maybe String -> IO (String,String,P.ProcessID)
popen f s m =
Control.Exception.handle (\e -> return ([], show e, error $ show e )) $ do
x@(_,_,pid) <- P.popen f s m
b <- P.getProcessStatus True False pid -- wait
return $ case b of
Nothing -> ([], "process has disappeared", pid)
_ -> x
#endif

504
System/Plugins/Utils.hs Normal file
View File

@ -0,0 +1,504 @@
{-# OPTIONS -cpp #-}
--
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
-- This library is free software; you can redistribute it and/or
-- modify it under the terms of the GNU Lesser General Public
-- License as published by the Free Software Foundation; either
-- version 2.1 of the License, or (at your option) any later version.
--
-- This library is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-- Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public
-- License along with this library; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-- USA
--
module System.Plugins.Utils (
Arg,
hWrite,
mkUnique,
hMkUnique,
mkUniqueIn,
hMkUniqueIn,
findFile,
mkTemp, mkTempIn, {- internal -}
replaceSuffix,
outFilePath,
dropSuffix,
mkModid,
changeFileExt,
joinFileExt,
splitFileExt,
isSublistOf, -- :: Eq a => [a] -> [a] -> Bool
dirname,
basename,
(</>), (<.>), (<+>), (<>),
newer,
encode,
decode,
EncodedString,
panic
) where
#include "../../config.h"
import System.Plugins.Env ( isLoaded )
import System.Plugins.Consts ( objSuf, hiSuf, tmpDir )
import qualified System.MkTemp ( mkstemps )
import Data.Char
import Data.List
import System.IO
import System.Environment ( getEnv )
import System.Directory
-- ---------------------------------------------------------------------
-- some misc types we use
type Arg = String
-- ---------------------------------------------------------------------
-- | useful
--
panic s = ioError ( userError s )
-- ---------------------------------------------------------------------
-- | writeFile for Handles
--
hWrite :: Handle -> String -> IO ()
hWrite hdl src = hPutStr hdl src >> hClose hdl >> return ()
-- ---------------------------------------------------------------------
-- | mkstemps.
--
-- We use the Haskell version now... it is faster than calling into
-- mkstemps(3).
--
mkstemps :: String -> Int -> IO (String,Handle)
mkstemps path slen = do
m_v <- System.MkTemp.mkstemps path slen
case m_v of Nothing -> error "mkstemps : couldn't create temp file"
Just v' -> return v'
{-
mkstemps path slen = do
withCString path $ \ ptr -> do
let c_slen = fromIntegral $ slen+1
fd <- throwErrnoIfMinus1 "mkstemps" $ c_mkstemps ptr c_slen
name <- peekCString ptr
hdl <- fdToHandle fd
return (name, hdl)
foreign import ccall unsafe "mkstemps" c_mkstemps :: CString -> CInt -> IO Fd
-}
-- ---------------------------------------------------------------------
-- | create a new temp file, returning name and handle.
-- bit like the mktemp shell utility
--
mkTemp :: IO (String,Handle)
mkTemp = do tmpd <- catch (getEnv "TMPDIR") (\_ -> return tmpDir)
mkTempIn tmpd
mkTempIn :: String -> IO (String, Handle)
mkTempIn tmpd = do
(tmpf,hdl) <- mkstemps (tmpd++"/MXXXXXXXXX.hs") 3
let modname = mkModid $ dropSuffix tmpf
if and $ map (\c -> isAlphaNum c && c /= '_') modname
then return (tmpf,hdl)
else panic $ "Illegal characters in temp file: `"++tmpf++"'"
-- ---------------------------------------------------------------------
-- | Get a new temp file, unique from those in /tmp, and from those
-- modules already loaded. Very nice for merge/eval uses.
--
-- Will run for a long time if we can't create a temp file, luckily
-- mkstemps gives us a pretty big search space
--
mkUnique :: IO FilePath
mkUnique = do (t,h) <- hMkUnique
hClose h >> return t
hMkUnique :: IO (FilePath,Handle)
hMkUnique = do (t,h) <- mkTemp
alreadyLoaded <- isLoaded t -- not unique!
if alreadyLoaded
then hClose h >> removeFile t >> hMkUnique
else return (t,h)
mkUniqueIn :: FilePath -> IO FilePath
mkUniqueIn dir = do (t,h) <- hMkUniqueIn dir
hClose h >> return t
hMkUniqueIn :: FilePath -> IO (FilePath,Handle)
hMkUniqueIn dir = do (t,h) <- mkTempIn dir
alreadyLoaded <- isLoaded t -- not unique!
if alreadyLoaded
then hClose h >> removeFile t >> hMkUniqueIn dir
else return (t,h)
findFile :: [String] -> FilePath -> IO (Maybe FilePath)
findFile [] _ = return Nothing
findFile (ext:exts) file
= do let l = changeFileExt file ext
b <- doesFileExist l
if b then return $ Just l
else findFile exts file
-- ---------------------------------------------------------------------
-- some filename manipulation stuff
--
-- | </>, <.> : join two path components
--
infixr 6 </>
infixr 6 <.>
(</>), (<.>), (<+>), (<>) :: FilePath -> FilePath -> FilePath
[] </> b = b
a </> b = a ++ "/" ++ b
[] <.> b = b
a <.> b = a ++ "." ++ b
[] <+> b = b
a <+> b = a ++ " " ++ b
[] <> b = b
a <> b = a ++ b
--
-- | dirname : return the directory portion of a file path
-- if null, return "."
--
dirname :: FilePath -> FilePath
dirname p =
let x = findIndices (== '\\') p
y = findIndices (== '/') p
in
if not $ null x
then if not $ null y
then if (maximum x) > (maximum y) then dirname' '\\' p else dirname' '/' p
else dirname' '\\' p
else dirname' '/' p
where
dirname' chara pa =
case reverse $ dropWhile (/= chara) $ reverse pa of
[] -> "."
pa' -> pa'
--
-- | basename : return the filename portion of a path
--
basename :: FilePath -> FilePath
basename p =
let x = findIndices (== '\\') p
y = findIndices (== '/') p
in
if not $ null x
then if not $ null y
then if (maximum x) > (maximum y) then basename' '\\' p else basename' '/' p
else basename' '\\' p
else basename' '/' p
where
basename' chara pa = reverse $ takeWhile (/= chara) $ reverse pa
--
-- drop suffix
--
dropSuffix :: FilePath -> FilePath
dropSuffix f = reverse . tail . dropWhile (/= '.') $ reverse f
--
-- | work out the mod name from a filepath
mkModid :: String -> String
mkModid = (takeWhile (/= '.')) . reverse . (takeWhile (\x -> ('/'/= x) && ('\\' /= x))) . reverse
-----------------------------------------------------------
-- Code from Cabal ----------------------------------------
-- | Changes the extension of a file path.
changeFileExt :: FilePath -- ^ The path information to modify.
-> String -- ^ The new extension (without a leading period).
-- Specify an empty string to remove an existing
-- extension from path.
-> FilePath -- ^ A string containing the modified path information.
changeFileExt fpath ext = joinFileExt name ext
where
(name,_) = splitFileExt fpath
-- | The 'joinFileExt' function is the opposite of 'splitFileExt'.
-- It joins a file name and an extension to form a complete file path.
--
-- The general rule is:
--
-- > filename `joinFileExt` ext == path
-- > where
-- > (filename,ext) = splitFileExt path
joinFileExt :: String -> String -> FilePath
joinFileExt fpath "" = fpath
joinFileExt fpath ext = fpath ++ '.':ext
-- | Split the path into file name and extension. If the file doesn\'t have extension,
-- the function will return empty string. The extension doesn\'t include a leading period.
--
-- Examples:
--
-- > splitFileExt "foo.ext" == ("foo", "ext")
-- > splitFileExt "foo" == ("foo", "")
-- > splitFileExt "." == (".", "")
-- > splitFileExt ".." == ("..", "")
-- > splitFileExt "foo.bar."== ("foo.bar.", "")
splitFileExt :: FilePath -> (String, String)
splitFileExt p =
case break (== '.') fname of
(suf@(_:_),_:pre) -> (reverse (pre++fpath), reverse suf)
_ -> (p, [])
where
(fname,fpath) = break isPathSeparator (reverse p)
-- | Checks whether the character is a valid path separator for the host
-- platform. The valid character is a 'pathSeparator' but since the Windows
-- operating system also accepts a slash (\"\/\") since DOS 2, the function
-- checks for it on this platform, too.
isPathSeparator :: Char -> Bool
isPathSeparator ch =
#if defined(CYGWIN) || defined(__MINGW32__)
ch == '/' || ch == '\\'
#else
ch == '/'
#endif
-- Code from Cabal end ------------------------------------
-----------------------------------------------------------
-- | return the object file, given the .conf file
-- i.e. /home/dons/foo.rc -> /home/dons/foo.o
--
-- we depend on the suffix we are given having a lead '.'
--
replaceSuffix :: FilePath -> String -> FilePath
replaceSuffix [] _ = [] -- ?
replaceSuffix f suf =
case reverse $ dropWhile (/= '.') $ reverse f of
[] -> f ++ suf -- no '.' in file name
f' -> f' ++ tail suf
--
-- Normally we create the .hi and .o files next to the .hs files.
-- For some uses this is annoying (i.e. true EDSL users don't actually
-- want to know that their code is compiled at all), and for hmake-like
-- applications.
--
-- This code checks if "-o foo" or "-odir foodir" are supplied as args
-- to make(), and if so returns a modified file path, otherwise it
-- uses the source file to determing the path to where the object and
-- .hi file will be put.
--
outFilePath :: FilePath -> [Arg] -> (FilePath,FilePath)
outFilePath src args =
let objs = find_o args -- user sets explicit object path
paths = find_p args -- user sets a directory to put stuff in
in case () of { _
| not (null objs)
-> let obj = last objs in (obj, mk_hi obj)
| not (null paths)
-> let obj = last paths </> mk_o (basename src) in (obj, mk_hi obj)
| otherwise
-> (mk_o src, mk_hi src)
}
where
outpath = "-o"
outdir = "-odir"
mk_hi s = replaceSuffix s hiSuf
mk_o s = replaceSuffix s objSuf
find_o [] = []
find_o (f:f':fs) | f == outpath = [f']
| otherwise = find_o $! f':fs
find_o _ = []
find_p [] = []
find_p (f:f':fs) | f == outdir = [f']
| otherwise = find_p $! f':fs
find_p _ = []
------------------------------------------------------------------------
--
-- | is file1 newer than file2?
--
-- needs some fixing to work with 6.0.x series. (is this true?)
--
-- fileExist still seems to throw exceptions on some platforms: ia64 in
-- particular.
--
-- invarient : we already assume the first file, 'a', exists
--
newer :: FilePath -> FilePath -> IO Bool
newer a b = do
a_t <- getModificationTime a
b_exists <- doesFileExist b
if not b_exists
then return True -- needs compiling
else do b_t <- getModificationTime b
return ( a_t > b_t ) -- maybe need recompiling
------------------------------------------------------------------------
--
-- | return the Z-Encoding of the string.
--
-- Stolen from GHC. Use -package ghc as soon as possible
--
type EncodedString = String
encode :: String -> EncodedString
encode [] = []
encode (c:cs) = encode_ch c ++ encode cs
unencodedChar :: Char -> Bool -- True for chars that don't need encoding
unencodedChar 'Z' = False
unencodedChar 'z' = False
unencodedChar c = c >= 'a' && c <= 'z'
|| c >= 'A' && c <= 'Z'
|| c >= '0' && c <= '9'
--
-- Decode is used for user printing.
--
decode :: EncodedString -> String
decode [] = []
decode ('Z' : d : rest) | isDigit d = decode_tuple d rest
| otherwise = decode_upper d : decode rest
decode ('z' : d : rest) | isDigit d = decode_num_esc d rest
| otherwise = decode_lower d : decode rest
decode (c : rest) = c : decode rest
decode_upper, decode_lower :: Char -> Char
decode_upper 'L' = '('
decode_upper 'R' = ')'
decode_upper 'M' = '['
decode_upper 'N' = ']'
decode_upper 'C' = ':'
decode_upper 'Z' = 'Z'
decode_upper ch = error $ "decode_upper can't handle this char `"++[ch]++"'"
decode_lower 'z' = 'z'
decode_lower 'a' = '&'
decode_lower 'b' = '|'
decode_lower 'c' = '^'
decode_lower 'd' = '$'
decode_lower 'e' = '='
decode_lower 'g' = '>'
decode_lower 'h' = '#'
decode_lower 'i' = '.'
decode_lower 'l' = '<'
decode_lower 'm' = '-'
decode_lower 'n' = '!'
decode_lower 'p' = '+'
decode_lower 'q' = '\''
decode_lower 'r' = '\\'
decode_lower 's' = '/'
decode_lower 't' = '*'
decode_lower 'u' = '_'
decode_lower 'v' = '%'
decode_lower ch = error $ "decode_lower can't handle this char `"++[ch]++"'"
-- Characters not having a specific code are coded as z224U
decode_num_esc :: Char -> [Char] -> String
decode_num_esc d cs
= go (digitToInt d) cs
where
go n (c : rest) | isDigit c = go (10*n + digitToInt c) rest
go n ('U' : rest) = chr n : decode rest
go _ other = error $
"decode_num_esc can't handle this: \""++other++"\""
encode_ch :: Char -> EncodedString
encode_ch c | unencodedChar c = [c] -- Common case first
-- Constructors
encode_ch '(' = "ZL" -- Needed for things like (,), and (->)
encode_ch ')' = "ZR" -- For symmetry with (
encode_ch '[' = "ZM"
encode_ch ']' = "ZN"
encode_ch ':' = "ZC"
encode_ch 'Z' = "ZZ"
-- Variables
encode_ch 'z' = "zz"
encode_ch '&' = "za"
encode_ch '|' = "zb"
encode_ch '^' = "zc"
encode_ch '$' = "zd"
encode_ch '=' = "ze"
encode_ch '>' = "zg"
encode_ch '#' = "zh"
encode_ch '.' = "zi"
encode_ch '<' = "zl"
encode_ch '-' = "zm"
encode_ch '!' = "zn"
encode_ch '+' = "zp"
encode_ch '\'' = "zq"
encode_ch '\\' = "zr"
encode_ch '/' = "zs"
encode_ch '*' = "zt"
encode_ch '_' = "zu"
encode_ch '%' = "zv"
encode_ch c = 'z' : shows (ord c) "U"
decode_tuple :: Char -> EncodedString -> String
decode_tuple d cs
= go (digitToInt d) cs
where
go n (c : rest) | isDigit c = go (10*n + digitToInt c) rest
go 0 ['T'] = "()"
go n ['T'] = '(' : replicate (n-1) ',' ++ ")"
go 1 ['H'] = "(# #)"
go n ['H'] = '(' : '#' : replicate (n-1) ',' ++ "#)"
go _ other = error $ "decode_tuple \'"++other++"'"
-- ---------------------------------------------------------------------
--
-- 'isSublistOf' takes two arguments and returns 'True' iff the first
-- list is a sublist of the second list. This means that the first list
-- is wholly contained within the second list. Both lists must be
-- finite.
isSublistOf :: Eq a => [a] -> [a] -> Bool
isSublistOf [] _ = True
isSublistOf _ [] = False
isSublistOf x y@(_:ys)
| isPrefixOf x y = True
| otherwise = isSublistOf x ys