Changes between Version 22 and Version 23 of GhcApiStatus


Ignore:
Timestamp:
Aug 28, 2008 9:17:58 PM (6 years ago)
Author:
nominolo
Comment:

Update API changes

Legend:

Unmodified
Added
Removed
Modified
  • GhcApiStatus

    v22 v23  
    1010= Current GHC API design = 
    1111 
    12 Most exported API functions that were previously in {{{IO}}} are now 
    13 in the {{{Ghc}}} monad.  Those functions now no longer require a {{{Session}}} 
    14 parameter.  To start a GHC API session you now use: 
     12There were two main issues which were large cross-cutting concerns and, unfortunately, took up most of my time: (a) consistent error handling and (b) explicit passing around of a `Session`. 
     13 
     14The latter issue is rather straightforward to fix by introducing a monad, however, functions expecting `IO` callbacks complicated things a little bit.  Consistent error handling is trickier.  I decided to use extensible exceptions which required some `#ifdef`s to properly bootstrap and some special `handle*` type functions to portably handle certain exceptions (akin to `handleGhcException`.)  But first, an overview: 
     15 
     16A `GhcMonad` is a class, and a default datatype which implements it is the `Ghc` monad.  Most interface functions will be of type `GhcMonad m => ... -> m a`.  This should make it easier to use these functions in custom monads which most non-trivial clients will likely need.  Very simple clients can just reuse the `Ghc` monad.  The `GhcMonad` class is defined as follows: 
     17{{{ 
     18class (MonadIO m, WarnLogMonad m, ExceptionMonad m) => GhcMonad m where 
     19  getSession :: m HscEnv 
     20  setSession :: HscEnv -> m () 
     21}}} 
     22 
     23The three required classes ensure that: we can use `liftIO` to call `IO` actions inside the GHC monad, accumulate warnings, and handle (extensible) exceptions in that monad.  The three classes are defined as follows: 
     24{{{ 
     25class Monad m => MonadIO m where   -- util/MonadUtils.hs 
     26  liftIO :: IO a -> m a 
     27 
     28type WarningMessages = Bag WarnMsg 
     29class Monad m => WarnLogMonad m where -- main/HscTypes.lhs 
     30  setWarnings  :: WarningMessages -> m () 
     31  getWarnings :: m WarningMessages 
     32  -- An alternative interface with 'addWarnings' and 'clearWarnings'  
     33  -- instead of 'setWarnings' may be nicer if we just want to dump 
     34  -- warnings somewhere and not accumulate (in which case  
     35  -- 'getWarnings' would always return the emptyBag) 
     36 
     37class Monad m => ExceptionMonad m where -- utils/Exception.hs 
     38  gcatch :: Exception e => m a -> (e -> m a) -> m a 
     39  gbracket :: m a -> (a -> m b) -> (a -> m c) -> m c 
     40  gfinally :: m a -> m b -> m a 
     41  -- 'gfinally' and 'gbracket' may be implemented in terms of  
     42  -- 'gcatch' if we add 'gblock' and 'gunblock'. 
     43  -- The version for GHC < 6.9 also contains 'gcatchDyn'. 
     44}}} 
     45 
     46There are two GHC-API-specific exceptions: 
     47{{{ 
     48data SourceError = SourceError ErrorMessages 
     49data GhcApiError = GhcApiError SDoc 
     50 
     51mkSrcErr :: ErrorMessages -> SourceError 
     52srcErrorMessages :: SourceError -> ErrorMessages 
     53mkApiErr :: SDoc -> GhcApiError 
     54}}} 
     55 
     56A source error corresponds to a problem with the compiled code and contains all accumulated error messages (but no warnings).  An API error is used to signal failure of an API call and replace many 'Maybe' results.  The choice wasn't always obvious.  In general API errors should be seldom, but catchable, i.e., they should be rare, but not entirely unexpected.  I guess, the choice which functions return which error needs some fine tuning. 
     57 
     58The 'WarnLogMonad' does what it's name says.  It accumulate warnings, which can be queried with 'getWarnings' and 'clearWarnings'.  Deciding when to clear warnings is a bit delicate.  ATM, I provide a default function that should be invoked in case of a source error (i.e., compilation failure) which prints all errors and warnings and clears the accumulated warnings. 
     59{{{ 
     60printExceptionAndWarnings :: GhcMonad m => SourceError -> m () 
     61printExceptionAndWarnings err = do 
     62    let errs = srcErrorMessages err 
     63    warns <- getWarnings 
     64    dflags <- getSessionDynFlags 
     65    liftIO $ printErrorsAndWarnings dflags (warns, errs) 
     66    clearWarnings 
     67}}} 
     68 
     69To start a GHC API session you now use: 
    1570 
    1671{{{ 
    1772withGhc :: Maybe FilePath  -- path to GHC library 
    18         -> Maybe [String]  -- ^ Optional list of static flags. 
    1973        -> Ghc a           -- ^ The action(s) to perform. 
    2074        -> IO a 
     
    2276 
    2377The first parameter can be determined automatically with the ghc-path 
    24 package.  The second is a set of "static" command line flags, for 
    25 example, profiling options.  Having those part of the run function for 
    26 the monad avoids complicated usage rules (e.g. before parseStaticFlags 
    27 had to be called before {{{newSession}}}). 
     78package.   
     79 
     80TODO: We miss a `GhcT` monad transformer and a init function for custom monads `initSession :: GhcMonad m => Maybe FilePath -> m a`. 
     81 
     82== Callbacks == 
     83 
     84Most of GHC's internal IO callbacks have been changed to use the proper class, e.g., `(MonadIO m, ExceptionMonad m) => ... -> m a -> m a`.  However, this cannot be done for external functions with callbacks.  A particularly complicated case are the asynchronous (FFI-initiated) callbacks of the Readline package.  For this case I added two functions to reflect into and reify from the `IO` monad: 
     85{{{  
     86-- | Reflect a computation in the 'Ghc' monad into the 'IO' monad. 
     87-- 
     88-- You can use this to call functions returning an action in the 'Ghc' monad 
     89-- inside a 'IO' action.  This is needed for some (too restrictive) callback 
     90-- arguments of some library functions: 
     91-- 
     92-- > libFunc :: String -> (Int -> IO a) -> IO a 
     93-- > ghcFunc :: Int -> Ghc a 
     94-- > 
     95-- > ghcFuncUsingLibFunc :: String -> Ghc a -> Ghc a 
     96-- > ghcFuncUsingLibFunc str = 
     97-- >   reifyGhc $ \s -> 
     98-- >     libFunc $ \i -> do 
     99-- >       reflectGhc (ghcFunc i) s 
     100--  
     101reflectGhc :: Ghc a -> Session -> IO a 
     102reflectGhc m = unGhc m 
     103 
     104-- > Dual to 'reflectGhc'.  See its documentation. 
     105reifyGhc :: (Session -> IO a) -> Ghc a 
     106reifyGhc act = Ghc $ act 
     107}}} 
     108 
     109== Interface Changes == 
    28110 
    29111{{{load}}} and {{{setTarget}}} work like before.  {{{checkModule}}}