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: |
| 12 | There 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 | |
| 14 | The 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 | |
| 16 | A `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 | {{{ |
| 18 | class (MonadIO m, WarnLogMonad m, ExceptionMonad m) => GhcMonad m where |
| 19 | getSession :: m HscEnv |
| 20 | setSession :: HscEnv -> m () |
| 21 | }}} |
| 22 | |
| 23 | The 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 | {{{ |
| 25 | class Monad m => MonadIO m where -- util/MonadUtils.hs |
| 26 | liftIO :: IO a -> m a |
| 27 | |
| 28 | type WarningMessages = Bag WarnMsg |
| 29 | class 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 | |
| 37 | class 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 | |
| 46 | There are two GHC-API-specific exceptions: |
| 47 | {{{ |
| 48 | data SourceError = SourceError ErrorMessages |
| 49 | data GhcApiError = GhcApiError SDoc |
| 50 | |
| 51 | mkSrcErr :: ErrorMessages -> SourceError |
| 52 | srcErrorMessages :: SourceError -> ErrorMessages |
| 53 | mkApiErr :: SDoc -> GhcApiError |
| 54 | }}} |
| 55 | |
| 56 | A 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 | |
| 58 | The '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 | {{{ |
| 60 | printExceptionAndWarnings :: GhcMonad m => SourceError -> m () |
| 61 | printExceptionAndWarnings err = do |
| 62 | let errs = srcErrorMessages err |
| 63 | warns <- getWarnings |
| 64 | dflags <- getSessionDynFlags |
| 65 | liftIO $ printErrorsAndWarnings dflags (warns, errs) |
| 66 | clearWarnings |
| 67 | }}} |
| 68 | |
| 69 | To start a GHC API session you now use: |
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}}}). |
| 78 | package. |
| 79 | |
| 80 | TODO: We miss a `GhcT` monad transformer and a init function for custom monads `initSession :: GhcMonad m => Maybe FilePath -> m a`. |
| 81 | |
| 82 | == Callbacks == |
| 83 | |
| 84 | Most 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 | -- |
| 101 | reflectGhc :: Ghc a -> Session -> IO a |
| 102 | reflectGhc m = unGhc m |
| 103 | |
| 104 | -- > Dual to 'reflectGhc'. See its documentation. |
| 105 | reifyGhc :: (Session -> IO a) -> Ghc a |
| 106 | reifyGhc act = Ghc $ act |
| 107 | }}} |
| 108 | |
| 109 | == Interface Changes == |