-- C->Haskell Compiler: monad for the binding generator -- -- Author : Manuel M T Chakravarty -- Derived: 18 February 2 (extracted from GenBind.hs) -- -- Copyright (c) [2002..2005] Manuel M T Chakravarty -- -- This file 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 file 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. -- --- DESCRIPTION --------------------------------------------------------------- -- -- This modules defines the monad and related utility routines for the code -- that implements the expansion of the binding hooks. -- --- DOCU ---------------------------------------------------------------------- -- -- language: Haskell 98 -- -- Translation table handling for enumerators: -- ------------------------------------------- -- -- First a translation table lookup on the original identifier of the -- enumerator is done. If that doesn't match and the prefix can be removed -- from the identifier, a second lookup on the identifier without the prefix -- is performed. If this also doesn't match, the identifier without prefix -- (possible after underscoreToCase or similar translation is returned). If -- there is a match, the translation (without any further stripping of -- prefix) is returned. -- -- Pointer map -- ----------- -- -- Pointer hooks allow the use to customise the Haskell types to which C -- pointer types are mapped. The globally maintained map essentially maps C -- pointer types to Haskell pointer types. The representation of the Haskell -- types is defined by the `type' or `newtype' declaration emitted by the -- corresponding pointer hook. However, the map stores a flag that tells -- whether the C type is itself the pointer type in question or whether it is -- pointers to this C type that should be mapped as specified. The pointer -- map is dumped into and read from `.chi' files. -- -- Haskell object map -- ------------------ -- -- Some features require information about Haskell objects defined by c2hs. -- Therefore, the Haskell object map maintains the necessary information -- about these Haskell objects. The Haskell object map is dumped into and -- read from `.chi' files. -- --- TODO ---------------------------------------------------------------------- -- -- * Look up in translation tables is naive - this probably doesn't affect -- costs much, but at some point a little profiling might be beneficial. -- module GBMonad ( TransFun, transTabToTransFun, HsObject(..), GB, initialGBState, setContext, getLibrary, getPrefix, delayCode, getDelayedCode, ptrMapsTo, queryPtr, objIs, queryObj, queryClass, queryPointer, mergeMaps, dumpMaps ) where -- standard libraries import Data.Char (toUpper, toLower, isSpace) import Data.List (find) import Data.Maybe (fromMaybe) -- Compiler Toolkit import Position (Position, Pos(posOf), nopos) import Errors (interr) import Idents (Ident, identToLexeme, onlyPosIdent) import qualified Data.Map as Map (empty, insert, lookup, union, toList, fromList) import Data.Map (Map) -- C -> Haskell import C (CT, readCT, transCT, raiseErrorCTExc) -- friends import CHS (CHSFrag(..), CHSHook(..), CHSTrans(..), CHSChangeCase(..), CHSPtrType(..)) -- translation tables -- ------------------ -- takes an identifier to a lexeme including a potential mapping by a -- translation table -- type TransFun = Ident -> String -- translation function for the `underscoreToCase' flag -- underscoreToCase :: String -> String underscoreToCase lexeme = let ps = filter (not . null) . parts $ lexeme in concat . map adjustCase $ ps where parts s = let (l, s') = break (== '_') s in l : case s' of [] -> [] (_:s'') -> parts s'' adjustCase (c:cs) = toUpper c : map toLower cs -- translation function for the `upcaseFirstLetter' flag -- upcaseFirstLetter :: String -> String upcaseFirstLetter "" = "" upcaseFirstLetter (c:cs) = toUpper c : cs -- translation function for the `downcaseFirstLetter' flag -- downcaseFirstLetter :: String -> String downcaseFirstLetter "" = "" downcaseFirstLetter (c:cs) = toLower c : cs -- takes an identifier association table to a translation function -- -- * if first argument is `True', identifiers that are not found in the -- translation table are subjected to `underscoreToCase' and friends -- -- * the details of handling the prefix are given in the DOCU section at the -- beginning of this file -- transTabToTransFun :: String -> CHSTrans -> TransFun transTabToTransFun prefix (CHSTrans _2Case chgCase table) = \ide -> let caseTrafo = (if _2Case then underscoreToCase else id) . (case chgCase of CHSSameCase -> id CHSUpCase -> upcaseFirstLetter CHSDownCase -> downcaseFirstLetter) lexeme = identToLexeme ide dft = caseTrafo lexeme -- default uses case trafo in case lookup ide table of -- lookup original ident Just ide' -> identToLexeme ide' -- original ident matches Nothing -> case eat prefix lexeme of Nothing -> dft -- no match & no prefix Just eatenLexeme -> let eatenIde = onlyPosIdent (posOf ide) eatenLexeme eatenDft = caseTrafo eatenLexeme in case lookup eatenIde table of -- lookup without prefix Nothing -> eatenDft -- orig ide without prefix Just ide' -> identToLexeme ide' -- without prefix matched where -- try to eat prefix and return `Just partialLexeme' if successful -- eat [] ('_':cs) = eat [] cs eat [] cs = Just cs eat (p:prefix) (c:cs) | toUpper p == toUpper c = eat prefix cs | otherwise = Nothing eat _ _ = Nothing -- the local monad -- --------------- -- map that for maps C pointer types to Haskell types for pointer that have -- been registered using a pointer hook -- -- * the `Bool' indicates whether for a C type "ctype", we map "ctype" itself -- or "*ctype" -- -- * in the co-domain, the first string is the type for function arguments and -- the second string is for function results; this distinction is necessary -- as `ForeignPtr's cannot be returned by a foreign function; the -- restriction on function result types is only for the actual result, not -- for type arguments to parametrised pointer types, ie, it holds for `res' -- in `Int -> IO res', but not in `Int -> Ptr res' -- type PointerMap = Map (Bool, Ident) (String, String) -- map that maintains key information about some of the Haskell objects -- generated by c2hs -- -- NB: using records here avoids to run into a bug with deriving `Read' in GHC -- 5.04.1 -- data HsObject = Pointer { ptrTypeHO :: CHSPtrType, -- kind of pointer isNewtypeHO :: Bool -- newtype? } | Class { superclassHO :: (Maybe Ident),-- superclass ptrHO :: Ident -- pointer } deriving (Show, Read) type HsObjectMap = Map Ident HsObject {- FIXME: What a mess... instance Show HsObject where show (Pointer ptrType isNewtype) = "Pointer " ++ show ptrType ++ show isNewtype show (Class osuper pointer ) = "Class " ++ show ptrType ++ show isNewtype -} -- super kludgy (depends on Show instance of Ident) instance Read Ident where readsPrec _ ('`':lexeme) = let (ideChars, rest) = span (/= '\'') lexeme in if null ideChars then [] else [(onlyPosIdent nopos ideChars, tail rest)] readsPrec p (c:cs) | isSpace c = readsPrec p cs readsPrec _ _ = [] -- the local state consists of -- -- (1) the dynamic library specified by the context hook, -- (2) the prefix specified by the context hook, -- (3) the set of delayed code fragaments, ie, pieces of Haskell code that, -- finally, have to be appended at the CHS module together with the hook -- that created them (the latter allows avoid duplication of foreign -- export declarations), and -- (4) a map associating C pointer types with their Haskell representation -- -- access to the attributes of the C structure tree is via the `CT' monad of -- which we use an instance here -- data GBState = GBState { lib :: String, -- dynamic library prefix :: String, -- prefix frags :: [(CHSHook, CHSFrag)], -- delayed code (with hooks) ptrmap :: PointerMap, -- pointer representation objmap :: HsObjectMap -- generated Haskell objects } type GB a = CT GBState a initialGBState :: GBState initialGBState = GBState { lib = "", prefix = "", frags = [], ptrmap = Map.empty, objmap = Map.empty } -- set the dynamic library and library prefix -- setContext :: (Maybe String) -> (Maybe String) -> GB () setContext lib prefix = transCT $ \state -> (state {lib = fromMaybe "" lib, prefix = fromMaybe "" prefix}, ()) -- get the dynamic library -- getLibrary :: GB String getLibrary = readCT lib -- get the prefix string -- getPrefix :: GB String getPrefix = readCT prefix -- add code to the delayed fragments (the code is made to start at a new line) -- -- * currently only code belonging to call hooks can be delayed -- -- * if code for the same call hook (ie, same C function) is delayed -- repeatedly only the first entry is stored; it is checked that the hooks -- specify the same flags (ie, produce the same delayed code) -- delayCode :: CHSHook -> String -> GB () delayCode hook str = do frags <- readCT frags frags' <- delay hook frags transCT (\state -> (state {frags = frags'}, ())) where newEntry = (hook, (CHSVerb ("\n" ++ str) (posOf hook))) -- delay hook@(CHSCall isFun isUns ide oalias _) frags = case find (\(hook', _) -> hook' == hook) frags of Just (CHSCall isFun' isUns' ide' _ _, _) | isFun == isFun' && isUns == isUns' && ide == ide' -> return frags | otherwise -> err (posOf ide) (posOf ide') Nothing -> return $ frags ++ [newEntry] delay _ _ = interr "GBMonad.delayCode: Illegal delay!" -- err = incompatibleCallHooksErr -- get the complete list of delayed fragments -- getDelayedCode :: GB [CHSFrag] getDelayedCode = readCT (map snd . frags) -- add an entry to the pointer map -- ptrMapsTo :: (Bool, Ident) -> (String, String) -> GB () (isStar, cName) `ptrMapsTo` hsRepr = transCT (\state -> (state { ptrmap = Map.insert (isStar, cName) hsRepr (ptrmap state) }, ())) -- query the pointer map -- queryPtr :: (Bool, Ident) -> GB (Maybe (String, String)) queryPtr pcName = do fm <- readCT ptrmap return $ Map.lookup pcName fm -- add an entry to the Haskell object map -- objIs :: Ident -> HsObject -> GB () hsName `objIs` obj = transCT (\state -> (state { objmap = Map.insert hsName obj (objmap state) }, ())) -- query the Haskell object map -- queryObj :: Ident -> GB (Maybe HsObject) queryObj hsName = do fm <- readCT objmap return $ Map.lookup hsName fm -- query the Haskell object map for a class -- -- * raise an error if the class cannot be found -- queryClass :: Ident -> GB HsObject queryClass hsName = do let pos = posOf hsName oobj <- queryObj hsName case oobj of Just obj@(Class _ _) -> return obj Just _ -> classExpectedErr hsName Nothing -> hsObjExpectedErr hsName -- query the Haskell object map for a pointer -- -- * raise an error if the pointer cannot be found -- queryPointer :: Ident -> GB HsObject queryPointer hsName = do let pos = posOf hsName oobj <- queryObj hsName case oobj of Just obj@(Pointer _ _) -> return obj Just _ -> pointerExpectedErr hsName Nothing -> hsObjExpectedErr hsName -- merge the pointer and Haskell object maps -- -- * currently, the read map overrides any entires for shared keys in the map -- that is already in the monad; this is so that, if multiple import hooks -- add entries for shared keys, the textually latest prevails; any local -- entries are entered after all import hooks anyway -- -- FIXME: This currently has several shortcomings: -- * It just dies in case of a corrupted .chi file -- * We should at least have the option to raise a warning if two -- entries collide in the `objmap'. But it would be better to -- implement qualified names. -- * Do we want position information associated with the read idents? -- mergeMaps :: String -> GB () mergeMaps str = transCT (\state -> (state { ptrmap = Map.union readPtrMap (ptrmap state), objmap = Map.union readObjMap (objmap state) }, ())) where (ptrAssoc, objAssoc) = read str readPtrMap = Map.fromList [((isStar, onlyPosIdent nopos ide), repr) | ((isStar, ide), repr) <- ptrAssoc] readObjMap = Map.fromList [(onlyPosIdent nopos ide, obj) | (ide, obj) <- objAssoc] -- convert the whole pointer and Haskell object maps into printable form -- dumpMaps :: GB String dumpMaps = do ptrFM <- readCT ptrmap objFM <- readCT objmap let dumpable = ([((isStar, identToLexeme ide), repr) | ((isStar, ide), repr) <- Map.toList ptrFM], [(identToLexeme ide, obj) | (ide, obj) <- Map.toList objFM]) return $ show dumpable -- error messages -- -------------- incompatibleCallHooksErr :: Position -> Position -> GB a incompatibleCallHooksErr here there = raiseErrorCTExc here ["Incompatible call hooks!", "There is a another call hook for the same C function at " ++ show there, "The flags and C function name of the two hooks should be identical,", "but they are not."] classExpectedErr :: Ident -> GB a classExpectedErr ide = raiseErrorCTExc (posOf ide) ["Expected a class name!", "Expected `" ++ identToLexeme ide ++ "' to refer to a class introduced", "by a class hook."] pointerExpectedErr :: Ident -> GB a pointerExpectedErr ide = raiseErrorCTExc (posOf ide) ["Expected a pointer name!", "Expected `" ++ identToLexeme ide ++ "' to be a type name introduced by", "a pointer hook."] hsObjExpectedErr :: Ident -> GB a hsObjExpectedErr ide = raiseErrorCTExc (posOf ide) ["Unknown name!", "`" ++ identToLexeme ide ++ "' is unknown; it has *not* been defined by", "a previous hook."]