cryptol/notebook/Notebook.hs
2014-04-17 15:34:25 -07:00

191 lines
6.1 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 ((<$>))
import qualified Control.Exception as X
import Control.Monad (forever)
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 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)
-- 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 = [] }