cryptol/notebook/Notebook.hs
David Raymond Christiansen 7cd2739831 Make notebook use iPython Haskell library
Now, the notebook interface runs its own ZeroMQ interface using the
EasyKernel module that is now exported from IHaskell's underlying
ipython-kernel library. This eliminates the line protocol with the
Python wrapper, and it should work on Windows (though it is not tested
there).

Part of this involved making the REPL monad able to change the IO action
used to print strings.

Continuing issues and improvment possibilities are in the issue tracker
- see issue #75 for a starting point.
2015-01-27 17:38:24 -08:00

202 lines
6.3 KiB
Haskell

-- |
-- Module : $Header$
-- Copyright : (c) 2013-2014 Galois, Inc.
-- License : BSD3
-- Maintainer : cryptol@galois.com
-- Stability : provisional
-- Portability : portable
{-# LANGUAGE DeriveDataTypeable #-}
module Notebook where
import REPL.Command (loadPrelude,findNbCommand,parseCommand,runCommand,replParse,liftModuleCmd)
import REPL.Monad (REPL(..) ,runREPL, lName, lPath)
import qualified REPL.Monad as REPL
import qualified Cryptol.ModuleSystem as M
import Cryptol.Parser (defaultConfig, parseModule, Config(..), ParseError)
import qualified Cryptol.Parser.AST as P
import Cryptol.Parser.Names (allNamesD, tnamesNT)
import Cryptol.Parser.Position (Located(..), emptyRange)
import qualified Cryptol.TypeCheck.AST as T
import Cryptol.Utils.PP (PP(..), pp, hang, text)
import Control.Applicative (Applicative(..), (<$>))
import qualified Control.Exception as X
import Control.Monad (ap, forever)
import Control.Monad.IO.Class (MonadIO(..))
import Data.IORef (IORef, newIORef, readIORef, modifyIORef)
import Data.List (isPrefixOf)
import qualified Data.Set as Set
import Data.Typeable (Typeable)
import System.IO (hFlush, stdout)
-- Notebook Environment --------------------------------------------------------
-- | All of the top-level declarations along with all of the names
-- that they define. We need to associate the names in order to remove
-- declarations from the module context when they're overwritten.
type NamedDecls = [([P.QName], P.TopDecl)]
data RW = RW
{ eNamedDecls :: NamedDecls
}
-- | The default environment is simple now but might get more
-- complicated, so it's made in IO.
defaultRW :: IO RW
defaultRW = return RW { eNamedDecls = [] }
-- Notebook Monad --------------------------------------------------------------
-- | The Notebook is just a REPL augmented with an incrementally-built module.
newtype NB a = NB { unNB :: IORef RW -> REPL a }
instance Functor NB where
{-# INLINE fmap #-}
fmap f m = NB (\ref -> fmap f (unNB m ref))
instance Applicative NB where
{-# INLINE pure #-}
pure = return
{-# INLINE (<*>) #-}
(<*>) = ap
instance Monad NB where
{-# INLINE return #-}
return x = NB (\_ -> return x)
{-# INLINE (>>=) #-}
m >>= f = NB $ \ref -> do
x <- unNB m ref
unNB (f x) ref
-- | Run a NB action with a fresh environment.
runNB :: NB a -> IO a
runNB m = do
ref <- newIORef =<< defaultRW
runREPL True $ unNB m ref
-- | Lift a REPL action into the NB monad.
liftREPL :: REPL a -> NB a
liftREPL m = NB (\_ -> m)
instance MonadIO NB where
liftIO = io
-- Primitives ------------------------------------------------------------------
io :: IO a -> NB a
io m = liftREPL (REPL.io m)
getRW :: NB RW
getRW = NB (\ref -> REPL.io (readIORef ref))
modifyRW_ :: (RW -> RW) -> NB ()
modifyRW_ f = NB (\ref -> REPL.io (modifyIORef ref f))
getTopDecls :: NB NamedDecls
getTopDecls = eNamedDecls `fmap` getRW
setTopDecls :: NamedDecls -> NB ()
setTopDecls nds = modifyRW_ (\rw -> rw { eNamedDecls = nds })
modifyTopDecls :: (NamedDecls -> NamedDecls) -> NB NamedDecls
modifyTopDecls f = do
nds <- f `fmap` getTopDecls
setTopDecls nds
return nds
-- Exceptions ------------------------------------------------------------------
-- | Notebook exceptions.
data NBException
= REPLException REPL.REPLException
| AutoParseError ParseError
deriving (Show, Typeable)
instance X.Exception NBException
instance PP NBException where
ppPrec _ nbe = case nbe of
REPLException exn -> pp exn
AutoParseError exn ->
hang (text "[error] Failed to parse cell as a module or as interactive input")
4 (pp exn)
-- | Raise an exception
raise :: NBException -> NB a
raise exn = io (X.throwIO exn)
-- | Catch an exception
catch :: NB a -> (NBException -> NB a) -> NB a
catch m k = NB (\ref ->
REPL (\replRef -> unREPL (unNB m ref) replRef
`X.catches`
-- catch a REPLException or a NBException
[ X.Handler $ \e -> unREPL (unNB (k (REPLException e)) ref) replRef
, X.Handler $ \e -> unREPL (unNB (k e) ref) replRef
]))
-- | Try running a possibly-excepting computation
try :: NB a -> NB (Either NBException a)
try m = catch (Right `fmap` m) (return . Left)
-- | Try running the given action, printing any exceptions that arise.
runExns :: NB () -> NB ()
runExns m = m `catch` \x -> io $ print $ pp x
-- Module Manipulation ---------------------------------------------------------
nbName :: P.Located P.ModName
nbName = Located { srcRange = emptyRange
, thing = P.ModName ["Notebook"]
}
-- | Distill a module into a list of decls along with the names
-- defined by those decls.
modNamedDecls :: P.Module -> NamedDecls
modNamedDecls m = [(tdNames td, td) | td <- P.mDecls m]
-- | Build a module of the given name using the given list of
-- declarations.
moduleFromDecls :: P.Located P.ModName -> NamedDecls -> P.Module
moduleFromDecls name nds =
P.Module { P.mName = name
, P.mImports = []
, P.mDecls = map snd nds
}
-- | In @updateNamedDecls old new = result@, @result@ is a
-- right-biased combination of @old@ and @new@ with the following
-- semantics:
--
-- If a name @x@ is defined in @old@ and not @new@, or in @new@ and
-- not @old@, all declarations of @x@ are in @result@.
--
-- If a name @x@ is defined in both @old@ and @new@, /none/ of the
-- declarations of @x@ from @old@ are in @result@, and all
-- declarations of @x@ from @new@ are in @result@.
updateNamedDecls :: NamedDecls -> NamedDecls -> NamedDecls
updateNamedDecls old new = filteredOld ++ new
where newNames = Set.fromList $ concat $ map fst new
containsNewName = any (\x -> Set.member x newNames)
filteredOld = filter (\(xs,_) -> not (containsNewName xs)) old
-- | The names defined by a top level declaration
tdNames :: P.TopDecl -> [P.QName]
tdNames (P.Decl d) = map P.thing $ allNamesD $ P.tlValue d
tdNames (P.TDNewtype d) = map P.thing $ fst $ tnamesNT $ P.tlValue d
tdNames (P.Include _) = []
removeIncludes :: P.Module -> P.Module
removeIncludes m = m { P.mDecls = decls' }
where decls' = filter (not . isInclude) $ P.mDecls m
isInclude (P.Include _) = True
isInclude _ = False
removeImports :: P.Module -> P.Module
removeImports m = m { P.mImports = [] }