packages feed

ihaskell 0.2.0.5 → 0.3.0.0

raw patch · 30 files changed

+4069/−3062 lines, 30 filesdep +HTTPdep +HUnitdep +base64-bytestringdep −template-haskelldep −textdep −uuiddep ~classy-prelude

Dependencies added: HTTP, HUnit, base64-bytestring, filepath, haskeline, hlint, ipython-kernel, parsec, process, transformers

Dependencies removed: template-haskell, text, uuid, zeromq3-haskell

Dependency ranges changed: classy-prelude

Files

− Hspec.hs
@@ -1,382 +0,0 @@-{-# LANGUAGE QuasiQuotes #-}-import GHC-import GHC.Paths-import Data.IORef-import Control.Monad-import Data.List-import System.Directory-import Data.String.Here-import Data.String.Utils (strip, replace)--import IHaskell.Eval.Parser-import IHaskell.Types-import IHaskell.IPython-import IHaskell.Eval.Evaluate as Eval-import IHaskell.Eval.Completion--import Test.Hspec-import Test.Hspec.HUnit--doGhc = runGhc (Just libdir)--parses = doGhc . parseString--like parser desired = parser >>= (`shouldBe` desired)--is string blockType = do-  result <- doGhc $ parseString string-  result `shouldBe` [blockType $ strip string]--eval string = do-  outputAccum <- newIORef []-  let publish _ displayDatas = modifyIORef outputAccum (displayDatas :)-  getTemporaryDirectory >>= setCurrentDirectory-  interpret $ Eval.evaluate 1 string publish-  out <- readIORef outputAccum-  return $ reverse out--becomes string expected = do-    let indent (' ':x) = 1 + indent x-        indent _ = 0-        empty = null . strip-        stringLines = filter (not . empty) $ lines string-        minIndent = minimum (map indent stringLines)-        newString = unlines $ map (drop minIndent) stringLines-    eval newString >>= comparison-  where -    comparison results = do-      when (length results /= length expected) $-        expectationFailure $ "Expected result to have " ++ show (length expected)-                             ++ " results. Got " ++ show results--      let isPlain (Display PlainText _) = True-          isPlain _ = False--      forM_ (zip results expected) $ \(result, expected) ->-        case find isPlain result of-          Just (Display PlainText str) -> str `shouldBe` expected-          Nothing -> expectationFailure $ "No plain-text output in " ++ show result--completes string expected = completionTarget newString cursorloc `shouldBe` expected-  where (newString, cursorloc) = case elemIndex '!' string of-          Nothing -> error "Expected cursor written as '!'."-          Just idx -> (replace "!" "" string, idx)--completionHas string expected = do-    (matched, completions) <- doGhc $ do-      initCompleter-      complete newString cursorloc-    let existsInCompletion =  (`elem` completions)-        unmatched = filter (not . existsInCompletion) expected-    unmatched `shouldBe` []-  where (newString, cursorloc) = case elemIndex '!' string of-          Nothing -> error "Expected cursor written as '!'."-          Just idx -> (replace "!" "" string, idx)--initCompleter :: GhcMonad m => m ()-initCompleter = do-  flags <- getSessionDynFlags-  setSessionDynFlags $ flags { hscTarget = HscInterpreted, ghcLink = LinkInMemory }--  -- Import modules.-  imports <- mapM parseImportDecl ["import Prelude",-                                  "import qualified Control.Monad",-                                  "import qualified Data.List as List",-                                  "import Data.Maybe as Maybe"]-  setContext $ map IIDecl imports--main :: IO ()-main = hspec $ do-  parserTests-  ipythonTests-  evalTests-  completionTests--completionTests = do-  describe "Completion" $ do-    it "correctly gets the completion identifier without dots" $ do-       "hello!"              `completes` ["hello"]-       "hello aa!bb goodbye" `completes` ["aa"]-       "hello aabb! goodbye" `completes` ["aabb"]-       "aacc! goodbye"       `completes` ["aacc"]-       "hello !aabb goodbye" `completes` []-       "!aabb goodbye"       `completes` []--    it "correctly gets the completion identifier with dots" $ do-       "hello test.aa!bb goodbye" `completes` ["test", "aa"]-       "Test.!"                   `completes` ["Test", ""]-       "Test.Thing!"              `completes` ["Test", "Thing"]-       "Test.Thing.!"             `completes` ["Test", "Thing", ""]-       "Test.Thing.!nope"         `completes` ["Test", "Thing", ""]--    it "correctly gets the completion type" $ do-      completionType "import Data." ["Data", ""]      `shouldBe` ModuleName "Data" ""-      completionType "import Prel" ["Prel"]           `shouldBe` ModuleName "" "Prel"-      completionType "import D.B.M" ["D", "B", "M"]   `shouldBe` ModuleName "D.B" "M"-      completionType " import A." ["A", ""]           `shouldBe` ModuleName "A" ""-      completionType "import a.x" ["a", "x"]          `shouldBe` Identifier "x"-      completionType "A.x" ["A", "x"]                 `shouldBe` Qualified "A" "x"-      completionType "a.x" ["a", "x"]                 `shouldBe` Identifier "x"-      completionType "pri" ["pri"]                    `shouldBe` Identifier "pri"--    it "properly completes identifiers" $ do-       "pri!"           `completionHas` ["print"]-       "ma!"            `completionHas` ["map"]-       "hello ma!"      `completionHas` ["map"]-       "print $ catMa!" `completionHas` ["catMaybes"]--    it "properly completes qualified identifiers" $ do-       "Control.Monad.liftM!"    `completionHas` [ "Control.Monad.liftM"-                                                 , "Control.Monad.liftM2"-                                                 , "Control.Monad.liftM5"]-       "print $ List.intercal!"  `completionHas` ["List.intercalate"]-       "print $ Data.Maybe.cat!" `completionHas` ["Data.Maybe.catMaybes"]-       "print $ Maybe.catM!"     `completionHas` ["Maybe.catMaybes"]--    it "properly completes imports" $ do-      "import Data.!"  `completionHas` ["Data.Maybe", "Data.List"]-      "import Data.M!" `completionHas` ["Data.Maybe"]-      "import Prel!"   `completionHas` ["Prelude"]--evalTests = do-  describe "Code Evaluation" $ do-    it "evaluates expressions" $  do-      "3" `becomes` ["3"]-      "3+5" `becomes` ["8"]-      "print 3" `becomes` ["3"]-      [hereLit|-        let x = 11-            z = 10 in-          x+z-      |] `becomes` ["21"]--    it "evaluates multiline expressions" $  do-      [hereLit|-        import Control.Monad-        forM_ [1, 2, 3] $ \x ->-          print x-      |] `becomes` ["1\n2\n3"]--    it "evaluates function declarations silently" $ do-      [hereLit|-        fun :: [Int] -> Int-        fun [] = 3-        fun (x:xs) = 10-        fun [1, 2]-      |] `becomes` ["10"]--    it "evaluates data declarations" $ do-      [hereLit|-        data X = Y Int-               | Z String-               deriving (Show, Eq)-        print [Y 3, Z "No"]-        print (Y 3 == Z "No")-      |] `becomes` ["[Y 3,Z \"No\"]", "False"]--    it "evaluates do blocks in expressions" $ do-      [hereLit|-        show (show (do-            Just 10-            Nothing-            Just 100))-      |] `becomes` ["\"\\\"Nothing\\\"\""]--    it "is silent for imports" $ do-      "import Control.Monad" `becomes` []-      "import qualified Control.Monad" `becomes` []-      "import qualified Control.Monad as CM" `becomes` []-      "import Control.Monad (when)" `becomes` []--    it "evaluates directives" $ do-      ":typ 3" `becomes` ["forall a. Num a => a"]-      ":in String" `becomes` ["type String = [Char] \t-- Defined in `GHC.Base'"]--ipythonTests = do-  describe "Parse IPython Version" $ do-    it "parses 2.0.0-dev" $-      parseVersion "2.0.0-dev" `shouldBe` [2, 0, 0]-    it "parses 2.0.0-alpha" $-      parseVersion "2.0.0-dev" `shouldBe` [2, 0, 0]-    it "parses 12.5.10" $-      parseVersion "12.5.10" `shouldBe` [12, 5, 10]--parserTests = do-  layoutChunkerTests-  moduleNameTests-  parseStringTests--layoutChunkerTests = describe "Layout Chunk" $ do-  it "chunks 'a string'" $-    layoutChunks "a string" `shouldBe` ["a string"]--  it "chunks 'a\\nstring'" $-    layoutChunks "a\n string" `shouldBe` ["a\n string"]--  it "chunks 'a\\n string\\nextra'" $-    layoutChunks "a\n string\nextra" `shouldBe` ["a\n string","extra"]--  it "chunks strings with too many lines" $-    layoutChunks "a\n\nstring" `shouldBe` ["a","string"]--moduleNameTests = describe "Get Module Name" $ do-  it "parses simple module names" $-    "module A where\nx = 3" `named` ["A"]-  it "parses module names with dots" $-    "module A.B where\nx = 3" `named` ["A", "B"]-  it "parses module names with exports" $-    "module A.B.C ( x ) where x = 3" `named` ["A", "B", "C"]-  it "errors when given unnamed modules" $ do-    doGhc (getModuleName "x = 3") `shouldThrow` anyException-  where-    named str result = do-      res <- doGhc $ getModuleName str-      res `shouldBe` result--parseStringTests = describe "Parser" $ do-  it "parses empty strings" $-    parses "" `like` []--  it "parses simple imports" $-    "import Data.Monoid" `is` Import--  it "parses simple arithmetic" $-    "3 + 5" `is` Expression--  it "parses :type" $-    parses ":type x\n:ty x" `like` [-      Directive GetType "x",-      Directive GetType "x"-    ]--  it "parses :info" $-    parses ":info x\n:in x" `like` [-      Directive GetInfo "x",-      Directive GetInfo "x"-    ]--  it "parses :help and :?" $-    parses ":? x\n:help x" `like` [-      Directive GetHelp "x",-      Directive GetHelp "x"-    ]--  it "parses :set x" $-    parses ":set x" `like` [-      Directive HelpForSet "x"-    ]--  it "parses :extension x" $-    parses ":ex x\n:extension x" `like` [-      Directive SetExtension "x",-      Directive SetExtension "x"-    ]--  it "fails to parse :nope" $ -    parses ":nope goodbye" `like` [-      ParseError (Loc 1 1) "Unknown directive: 'nope'."-    ]--  it "parses number followed by let stmt" $-    parses "3\nlet x = expr"  `like` [-      Expression "3",-      Statement "let x = expr"-    ]--  it "parses let x in y" $-    "let x = 3 in x + 3" `is` Expression--  it "parses a data declaration" $-    "data X = Y Int" `is` Declaration--  it "parses number followed by type directive" $-    parses "3\n:t expr" `like` [-      Expression "3",-      Directive GetType "expr"-    ]--  it "parses a <- statement" $-    "y <- print 'no'" `is` Statement--  it "parses a <- stmt followed by let stmt" $-    parses "y <- do print 'no'\nlet x = expr" `like` [-      Statement "y <- do print 'no'",-      Statement "let x = expr"-    ]--  it "parses <- followed by let followed by expr" $-    parses "y <- do print 'no'\nlet x = expr\nexpression" `like` [-      Statement "y <- do print 'no'",-      Statement "let x = expr",-      Expression "expression"-    ]--  it "parses two print statements" $-    parses "print yes\nprint no" `like` [-      Expression "print yes",-      Expression "print no"-    ]--  it "parses a pattern-maching function declaration" $-    "fun [] = 10" `is` Declaration--  it "parses a function decl followed by an expression" $-    parses "fun [] = 10\nprint 'h'" `like` [-      Declaration "fun [] = 10",-      Expression "print 'h'"-    ]--  it "parses list pattern matching fun decl" $-    "fun (x : xs) = 100" `is` Declaration--  it "parses two pattern matches as the same declaration" $-    "fun [] = 10\nfun (x : xs) = 100" `is` Declaration--  it "parses a type signature followed by a declaration" $-    "fun :: [a] -> Int\nfun [] = 10\nfun (x : xs) = 100" `is` Declaration--  it "parases a simple module" $-    "module A where x = 3" `is` Module--  it "parses a module with an export" $-    "module B (x) where x = 3" `is` Module--  it "breaks when a let is incomplete" $-    parses "let x = 3 in" `like` [-      ParseError (Loc 1 13) "parse error (possibly incorrect indentation or mismatched brackets)"-    ]--  it "breaks without data kinds" $-    parses "data X = 3" `like` [-      ParseError (Loc 1 10) "Illegal literal in type (use -XDataKinds to enable): 3"-    ]--  it "parses statements after imports" $ do-    parses "import X\nprint 3" `like` [-        Import "import X",-        Expression "print 3" -      ]-    parses "import X\n\nprint 3" `like` [-        Import "import X",-        Expression "print 3" -      ]-  it "ignores blank lines properly" $ -    [hereLit|-      test arg = hello-        where-          x = y--          z = w-    |] `is` Declaration-  it "doesn't break on long strings" $ do-    let longString = concat $ replicate 20 "hello "-    ("img ! src \"" ++ longString ++ "\" ! width \"500\"") `is` Expression--  it "parses do blocks in expression" $ do-    [hereLit|-      show (show (do-        Just 10-        Nothing-        Just 100))-    |] `is` Expression-    
− IHaskell/Config.hs
@@ -1,26 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}--- | Description : IPython configuration files are compiled-into IHaskell-module IHaskell.Config (ipython, notebook, console, qtconsole, customjs, notebookJavascript) where--import Data.String.Here-import ClassyPrelude--ipython :: String -> String-ipython executable = [template|config/ipython_config.py|]--notebook :: String-notebook = [template|config/ipython_notebook_config.py|]--console :: String-console = [template|config/ipython_console_config.py|]--qtconsole :: String-qtconsole = [template|config/ipython_qtconsole_config.py|]--customjs :: String-customjs = [template|config/custom.js|]--notebookJavascript :: [(FilePath, String)]-notebookJavascript = [("tooltip.js", [template|deps/tooltip.js|]),-                      ("codecell.js", [template|deps/codecell.js|])]
− IHaskell/Display.hs
@@ -1,41 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}-module IHaskell.Display (-  IHaskellDisplay(..),-  plain, html, png, jpg, svg, latex,-  serializeDisplay-  ) where--import ClassyPrelude-import Data.Serialize as Serialize-import Data.ByteString-import Data.String.Utils (rstrip) --import IHaskell.Types---- | A class for displayable Haskell types.-class IHaskellDisplay a where-  display :: a -> [DisplayData]---- | Generate a plain text display.-plain :: String -> DisplayData-plain = Display PlainText . rstrip---- | Generate an HTML display.-html :: String -> DisplayData-html = Display MimeHtml--png :: String -> DisplayData-png = Display MimePng --jpg :: String -> DisplayData-jpg = Display MimeJpg--svg :: String -> DisplayData-svg = Display MimeSvg--latex :: String -> DisplayData-latex = Display MimeLatex--serializeDisplay :: [DisplayData] -> ByteString-serializeDisplay = Serialize.encode
− IHaskell/Eval/Completion.hs
@@ -1,156 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}-{- | Description : generates tab-completion options--  context-insensitive completion for what is probably-  the identifier under the cursor.-  -  [@Known issues@]--  > import Data.Lef<tab>-  > System.IO.h<tab>-  > Just.he<tab>--  The first should not complete to Left. The second should only-  include things like System.IO.hPutStrLn, not head. Qualified-  names should not be confused by the third option.---}-module IHaskell.Eval.Completion (complete, completionTarget, completionType, CompletionType(..)) where--import Control.Applicative ((<$>))-import Data.ByteString.UTF8 hiding (drop, take)-import Data.Char-import Data.List (find, isPrefixOf, nub, findIndex, intercalate, elemIndex)-import Data.List.Split-import Data.List.Split.Internals-import Data.Maybe-import Data.String.Utils (strip, startswith, replace)-import Prelude--import GHC-import DynFlags-import GhcMonad-import PackageConfig-import Outputable (showPpr)--import IHaskell.Types---data CompletionType -     = Empty -     | Identifier String-     | Extension String-     | Qualified String String-     | ModuleName String String-     deriving (Show, Eq)--complete :: GHC.GhcMonad m => String -> Int -> m (String, [String])-complete line pos = do-  flags <- getSessionDynFlags-  rdrNames <- map (showPpr flags) <$> getRdrNamesInScope-  scopeNames <- nub <$> map (showPpr flags) <$> getNamesInScope-  let isQualified = ('.' `elem`)-      unqualNames = nub $ filter (not . isQualified) rdrNames-      qualNames = nub $ scopeNames ++ filter isQualified rdrNames--  let Just db = pkgDatabase flags-      getNames = map moduleNameString . exposedModules-      moduleNames = nub $ concatMap getNames db--  let target = completionTarget line pos-      matchedText = intercalate "." target--  options <- -        case completionType line target of-          Empty -> return []--          Identifier candidate ->-            return $ filter (candidate `isPrefixOf`) unqualNames--          Qualified moduleName candidate -> do-            trueName <- getTrueModuleName moduleName-            let prefix = intercalate "." [trueName, candidate]-                completions = filter (prefix `isPrefixOf`)  qualNames-                falsifyName = replace trueName moduleName-            return $ map falsifyName completions--          ModuleName previous candidate -> do-            let prefix = if null previous-                         then candidate-                         else intercalate "." [previous, candidate]-            return $ filter (prefix `isPrefixOf`) moduleNames--          Extension ext -> do-            let extName (name, _, _) = name-                names = map extName xFlags-                nonames = map ("No" ++) names-            return $ filter (ext `isPrefixOf`) $ names ++ nonames--  return (matchedText, options)--getTrueModuleName :: GhcMonad m => String -> m String-getTrueModuleName name = do-  -- Only use the things that were actually imported-  let onlyImportDecl (IIDecl decl) = Just decl-      onlyImportDecl _ = Nothing--  -- Get all imports that we use.-  imports <- catMaybes <$> map onlyImportDecl <$> getContext--  -- Find the ones that have a qualified name attached.-  -- If this name isn't one of them, it already is the true name.-  flags <- getSessionDynFlags-  let qualifiedImports = filter (isJust . ideclAs) imports-      hasName imp = name == (showPpr flags . fromJust . ideclAs) imp-  case find hasName qualifiedImports of-    Nothing -> return name  -    Just trueImp -> return $ showPpr flags $ unLoc $ ideclName trueImp--completionType :: String -> [String] -> CompletionType-completionType line [] = Empty-completionType line target-  | startswith "import" stripped && isModName-    = ModuleName dotted candidate-  | isModName && (not . null . init) target-    = Qualified dotted candidate-  | startswith ":e" stripped-    = Extension candidate -  | otherwise-    = Identifier candidate-  where stripped = strip line-        dotted = dots target-        candidate = last target-        dots = intercalate "." . init-        isModName = all isCapitalized (init target)-        isCapitalized = isUpper . head----- | Get the word under a given cursor location.-completionTarget :: String -> Int -> [String]-completionTarget code cursor = expandCompletionPiece pieceToComplete-  where -    pieceToComplete = map fst <$> find (elem cursor . map snd) pieces-    pieces = splitAlongCursor $ split splitter $ zip code [1 .. ]-    splitter = defaultSplitter {-      -- Split using only the characters, which are the first elements of-      -- the (char, index) tuple-      delimiter = Delimiter [uncurry isDelim],-      -- Condense multiple delimiters into one and then drop them.-      condensePolicy = Condense,-      delimPolicy = Drop-    }--    isDelim char idx = char `elem` neverIdent || isSymbol char--    splitAlongCursor :: [[(Char, Int)]] -> [[(Char, Int)]]-    splitAlongCursor [] = []-    splitAlongCursor (x:xs) = -      case elemIndex cursor $  map snd x of-        Nothing -> x:splitAlongCursor xs-        Just idx -> take (idx + 1) x:drop (idx + 1) x:splitAlongCursor xs--    -- These are never part of an identifier.-    neverIdent = " \n\t(),{}[]\\'\"`"--    expandCompletionPiece Nothing = []-    expandCompletionPiece (Just str) = splitOn "." str 
− IHaskell/Eval/Evaluate.hs
@@ -1,615 +0,0 @@-{-# LANGUAGE DoAndIfThenElse #-}-{- | Description : Wrapper around GHC API, exposing a single `evaluate` interface that runs-                   a statement, declaration, import, or directive.--This module exports all functions used for evaluation of IHaskell input.--}-module IHaskell.Eval.Evaluate (-  interpret, evaluate, Interpreter, liftIO, typeCleaner, globalImports-  ) where--import ClassyPrelude hiding (liftIO, hGetContents)-import Control.Concurrent (forkIO, threadDelay)-import Prelude (putChar, head, tail, last, init, (!!))-import Data.List.Utils-import Data.List(findIndex)-import Data.String.Utils-import Text.Printf-import Data.Char as Char-import Data.Dynamic-import Data.Typeable-import qualified Data.Serialize as Serialize-import System.Directory (removeFile, createDirectoryIfMissing, removeDirectoryRecursive)-import System.Posix.IO-import System.IO (hGetChar, hFlush)-import System.Random (getStdGen, randomRs)-import Unsafe.Coerce--import NameSet-import Name-import PprTyThing-import InteractiveEval-import DynFlags-import Type-import Exception (gtry)-import HscTypes-import HscMain-import TcType-import Unify-import InstEnv-import GhcMonad (liftIO, withSession)-import GHC hiding (Stmt, TypeSig)-import GHC.Paths-import Exception hiding (evaluate)-import Outputable-import Packages-import Module--import qualified System.IO.Strict as StrictIO--import IHaskell.Types-import IHaskell.Eval.Parser-import IHaskell.Display--data ErrorOccurred = Success | Failure deriving Show--debug :: Bool-debug = False--ignoreTypePrefixes :: [String]-ignoreTypePrefixes = ["GHC.Types", "GHC.Base", "GHC.Show", "System.IO",-                      "GHC.Float", ":Interactive", "GHC.Num", "GHC.IO",-                      "GHC.Integer.Type"]--typeCleaner :: String -> String-typeCleaner = useStringType . foldl' (.) id (map (`replace` "") fullPrefixes)-  where-    fullPrefixes = map (++ ".") ignoreTypePrefixes-    useStringType = replace "[Char]" "String"--write :: GhcMonad m => String -> m ()-write x = when debug $ liftIO $ hPutStrLn stderr x--type Interpreter = Ghc--globalImports :: [String]-globalImports = -  [ "import IHaskell.Display"-  , "import Control.Applicative ((<$>))"-  , "import GHC.IO.Handle (hDuplicateTo, hDuplicate)"-  , "import System.Posix.IO"-  , "import System.Posix.Files"-  , "import System.IO"-  ]---- | Run an interpreting action. This is effectively runGhc with--- initialization and importing.-interpret :: Interpreter a -> IO a-interpret action = runGhc (Just libdir) $ do-  -- Set the dynamic session flags-  originalFlags <- getSessionDynFlags-  let dflags = xopt_set originalFlags Opt_ExtendedDefaultRules-  void $ setSessionDynFlags $ dflags { hscTarget = HscInterpreted, ghcLink = LinkInMemory }--  initializeImports-  initializeItVariable--  -- Run the rest of the interpreter-  action---- | Initialize our GHC session with imports and a value for 'it'.-initializeImports :: Interpreter ()-initializeImports = do-  -- Load packages that start with ihaskell-* and aren't just IHaskell.-  dflags <- getSessionDynFlags-  displayPackages <- liftIO $ do-    (dflags, _) <- initPackages dflags-    let Just db = pkgDatabase dflags-        packageNames = map (packageIdString . packageConfigId) db-        initStr = "ihaskell-"-        ihaskellPkgs = filter (startswith initStr) packageNames-        displayPkgs = filter (isAlpha . (!! (length initStr + 1))) ihaskellPkgs-    return displayPkgs--  -- Generate import statements all Display modules.-  let capitalize :: String -> String-      capitalize (first:rest) = Char.toUpper first : rest--      importFmt = "import IHaskell.Display.%s" --      toImportStmt :: String -> String-      toImportStmt = printf importFmt . capitalize . (!! 1) . split "-"--      displayImports = map toImportStmt displayPackages--  -- Import implicit prelude.-  importDecl <- parseImportDecl "import Prelude"-  let implicitPrelude = importDecl { ideclImplicit = True }--  -- Import modules.-  mapM_ (write . ("Importing " ++ )) displayImports-  imports <- mapM parseImportDecl $ globalImports ++ displayImports-  setContext $ map IIDecl $ implicitPrelude : imports---- | Give a value for the `it` variable.-initializeItVariable :: Interpreter ()-initializeItVariable =-  -- This is required due to the way we handle `it` in the wrapper-  -- statements - if it doesn't exist, the first statement will fail.-  void $ runStmt "let it = ()" RunToCompletion---- | Publisher for IHaskell outputs.  The first argument indicates whether--- this output is final (true) or intermediate (false).-type Publisher = (Bool -> [DisplayData] -> IO ())---- | Evaluate some IPython input code.-evaluate :: Int              -- ^ The execution counter of this evaluation.-         -> String           -- ^ Haskell code or other interpreter commands.-         -> Publisher        -- ^ Function used to publish data outputs.-         -> Interpreter ()-evaluate execCount code output = do-  cmds <- parseString (strip code)-  runUntilFailure (cmds ++ [storeItCommand execCount])-  where-    runUntilFailure :: [CodeBlock] -> Interpreter ()-    runUntilFailure [] = return ()-    runUntilFailure (cmd:rest) = do -      (success, result) <- evalCommand output cmd-      unless (null result) $ liftIO $ output True result-      case success of-        Success -> runUntilFailure rest-        Failure -> return ()--    storeItCommand execCount = Statement $ printf "let it%d = it" execCount--wrapExecution :: Interpreter [DisplayData] -> Interpreter (ErrorOccurred, [DisplayData])-wrapExecution exec = ghandle handler $ exec >>= \res ->-    return (Success, res)-  where -    handler :: SomeException -> Interpreter (ErrorOccurred, [DisplayData])-    handler exception = return (Failure, displayError $ show exception)---- | Return the display data for this command, as well as whether it--- resulted in an error.-evalCommand :: Publisher -> CodeBlock -> Interpreter (ErrorOccurred, [DisplayData])-evalCommand _ (Import importStr) = wrapExecution $ do-  write $ "Import: " ++ importStr-  importDecl <- parseImportDecl importStr-  context <- getContext--  -- If we've imported this implicitly, remove the old import.-  let noImplicit = filter (not . implicitImportOf importDecl) context-  setContext $ IIDecl importDecl : noImplicit--  flags <- getSessionDynFlags-  return []-  where-    implicitImportOf :: ImportDecl RdrName -> InteractiveImport -> Bool-    implicitImportOf _ (IIModule _) = False-    implicitImportOf imp (IIDecl decl) = ideclImplicit decl && ((==) `on` (unLoc . ideclName)) decl imp--evalCommand _ (Module contents) = wrapExecution $ do-  write $ "Module:\n" ++ contents-  -- Write the module contents to a temporary file in our work directory-  namePieces <- getModuleName contents-  let directory = "./" ++ intercalate "/" (init namePieces) ++ "/"-      filename = last namePieces ++ ".hs"-  liftIO $ do-    createDirectoryIfMissing True directory-    writeFile (fpFromString $ directory ++ filename) contents--  -- Clear old modules of this name-  let modName = intercalate "." namePieces-  removeTarget $ TargetModule $ mkModuleName modName-  removeTarget $ TargetFile filename Nothing--  -- Set to use object code for fast running times, as that is the only-  -- reason you would want to use modules in IHaskell.-  flags <- getSessionDynFlags-  let objTarget = defaultObjectTarget-  setSessionDynFlags flags{ hscTarget = objTarget }--  -- Remember which modules we've loaded before.-  importedModules <- getContext--  let -- Get the dot-delimited pieces of hte module name.-      moduleNameOf :: InteractiveImport -> [String]-      moduleNameOf (IIDecl decl) = split "." . moduleNameString . unLoc . ideclName $ decl-      moduleNameOf (IIModule imp) = split "." . moduleNameString $ imp--      -- Return whether this module prevents the loading of the one we're-      -- trying to load. If a module B exist, we cannot load A.B. All-      -- modules must have unique last names (where A.B has last name B).-      -- However, we *can* just reload a module.-      preventsLoading mod = -        let pieces = moduleNameOf mod in-            last namePieces == last pieces && namePieces /= pieces--  -- If we've loaded anything with the same last name, we can't use this.-  -- Otherwise, GHC tries to load the original *.hs fails and then fails.-  case find preventsLoading importedModules of-    -- If something prevents loading this module, return an error.-    Just previous -> -      let prevLoaded = intercalate "." (moduleNameOf previous) in-        return $ displayError $-          printf "Can't load module %s because already loaded %s" modName prevLoaded--    -- Since nothing prevents loading the module, compile and load it.-    Nothing -> do-      -- Create a new target-      target <- guessTarget modName Nothing-      addTarget target-      result <- load LoadAllTargets--      -- Reset the context, since loading things screws it up.-      initializeItVariable--      -- Add imports-      importDecl <- parseImportDecl $ "import " ++ modName-      let implicitImport = importDecl { ideclImplicit = True }-      setContext $ IIDecl implicitImport : importedModules--      -- Switch back to interpreted mode.-      flags <- getSessionDynFlags-      setSessionDynFlags flags{ hscTarget = HscInterpreted }--      case result of-        Succeeded -> return []-        Failed -> return $ displayError $ "Failed to load module " ++ modName--evalCommand _ (Directive SetExtension exts) = wrapExecution $ do-  write $ "Extension: " ++ exts-  results <- mapM setExtension (words exts)-  case catMaybes results of-    [] -> return []-    errors -> return $ displayError $ intercalate "\n" errors-  where-    -- Set an extension and update flags.-    -- Return Nothing on success. On failure, return an error message.-    setExtension :: String -> Interpreter (Maybe ErrMsg)-    setExtension ext = do-      flags <- getSessionDynFlags-      -- First, try to check if this flag matches any extension name.-      let newFlags =-            case find (flagMatches ext) xFlags of-              Just (_, flag, _) -> Just $ xopt_set flags flag-              -- If it doesn't match an extension name, try matching against-              -- disabling an extension.-              Nothing -> -                case find (flagMatchesNo ext) xFlags of-                  Just (_, flag, _) -> Just $ xopt_unset flags flag-                  Nothing -> Nothing--      -- Set the flag if we need to.-      case newFlags of-        Just flags -> setSessionDynFlags flags >> return Nothing-        Nothing -> return $ Just $ "Could not parse extension name: " ++ ext--    -- Check if a FlagSpec matches an extension name.-    flagMatches ext (name, _, _) = ext == name--    -- Check if a FlagSpec matches "No<ExtensionName>".-    -- In that case, we disable the extension.-    flagMatchesNo ext (name, _, _) = ext == "No"  ++ name--evalCommand _ (Directive GetType expr) = wrapExecution $ do-  write $ "Type: " ++ expr-  result <- exprType expr-  flags <- getSessionDynFlags-  let typeStr = showSDocUnqual flags $ ppr result-  return [plain typeStr, html $ formatGetType typeStr]---- This is taken largely from GHCi's info section in InteractiveUI.-evalCommand _ (Directive HelpForSet _) = do-  write "Help for :set."-  return (Success, [out])-  where out = plain $ intercalate "\n"-          [":set is not implemented in IHaskell."-          ,"  Use :extension <Extension> to enable a GHC extension."-          ,"  Use :extension No<Extension> to disable a GHC extension."-          ]---- This is taken largely from GHCi's info section in InteractiveUI.-evalCommand _ (Directive GetHelp _) = do-  write "Help via :help or :?."-  return (Success, [out])-  where out = plain $ intercalate "\n"-          ["The following commands are available:"-          ,"    :extension <Extension>    -  enable a GHC extension."-          ,"    :extension No<Extension>  -  disable a GHC extension."-          ,"    :type <expression>        -  Print expression type."-          ,"    :info <name>              -  Print all info for a name."-          ,"    :?, :help                 -  Show this help text."-          ,""-          ,"Any prefix of the commands will also suffice, e.g. use :ty for :type."-          ]---- This is taken largely from GHCi's info section in InteractiveUI.-evalCommand _ (Directive GetInfo str) = wrapExecution $ do-  write $ "Info: " ++ str-  -- Get all the info for all the names we're given.-  names     <- parseName str-  maybeInfos <- mapM getInfo names--  -- Filter out types that have parents in the same set.-  -- GHCi also does this.-  let getType (theType, _, _) = theType-      infos = catMaybes maybeInfos-      allNames = mkNameSet $ map (getName . getType) infos-      hasParent info = case tyThingParent_maybe (getType info) of-        Just parent -> getName parent `elemNameSet` allNames-        Nothing -> False-      filteredOutput = filter (not . hasParent) infos--  -- Convert to textual data.-  let printInfo (thing, fixity, classInstances) = -        pprTyThingInContextLoc False thing $$ showFixity fixity $$ vcat (map GHC.pprInstance classInstances)-        where-          showFixity fixity =-            if fixity == GHC.defaultFixity-            then empty-            else ppr fixity <+> pprInfixName (getName thing)-      outs = map printInfo filteredOutput--  -- Print nicely.-  unqual <- getPrintUnqual-  flags <- getSessionDynFlags-  let strings = map (showSDocForUser flags unqual) outs-  return [plain $ intercalate "\n" strings]--evalCommand output (Statement stmt) = wrapExecution $ do-  write $ "Statement:\n" ++ stmt-  let outputter str = output False [plain str]-  (printed, result) <- capturedStatement outputter stmt-  case result of-    RunOk names -> do-      dflags <- getSessionDynFlags-      write $ "Names: " ++ show (map (showPpr dflags) names)  -      let output = [plain printed | not . null $ strip printed]-      return output-    RunException exception -> throw exception-    RunBreak{} -> error "Should not break."--evalCommand output (Expression expr) = do-  write $ "Expression:\n" ++ expr-  -- Evaluate this expression as though it's just a statement.-  -- The output is bound to 'it', so we can then use it.-  (success, out) <- evalCommand output (Statement expr)--    -- Try to use `display` to convert our type into the output-  -- DisplayData. If typechecking fails and there is no appropriate-  -- typeclass, this will throw an exception and thus `attempt` will-  -- return False, and we just resort to plaintext.-  let displayExpr = printf "(IHaskell.Display.display (%s))" expr-  canRunDisplay <- attempt $ exprType displayExpr-  write $ printf "%s: Attempting %s" (if canRunDisplay then "Success" else "Failure") displayExpr-  write $ "Show Error: " ++ show (isShowError out)-  write $ show out--  -- If evaluation failed, return the failure.  If it was successful, we-  -- may be able to use the IHaskellDisplay typeclass.-  if not canRunDisplay-  then return (success, out)-  else case success of-    Success -> useDisplay displayExpr-    Failure -> if isShowError out-              then useDisplay displayExpr-              else return (success, out)--  where-    -- Try to evaluate an action. Return True if it succeeds and False if-    -- it throws an exception. The result of the action is discarded.-    attempt :: Interpreter a -> Interpreter Bool-    attempt action = gcatch (action >> return True) failure-      where failure :: SomeException -> Interpreter Bool-            failure _ = return False--    -- Check if the error is due to trying to print something that doesn't-    -- implement the Show typeclass.-    isShowError errs = case find isPlain errs of-      Just (Display PlainText msg) -> -        startswith "No instance for (GHC.Show.Show" msg &&-        isInfixOf " arising from a use of `System.IO.print'" msg-      Nothing -> False-      where isPlain (Display mime _) = mime == PlainText--    useDisplay displayExpr = wrapExecution $ do-      -- If there are instance matches, convert the object into-      -- a [DisplayData]. We also serialize it into a bytestring. We get-      -- the bytestring as a dynamic and then convert back to-      -- a bytestring, which we promptly unserialize. Note that-      -- attempting to do this without the serialization to binary and-      -- back gives very strange errors - all the types match but it-      -- refuses to decode back into a [DisplayData].-      runStmt displayExpr RunToCompletion-      displayedBytestring <- dynCompileExpr "IHaskell.Display.serializeDisplay it"-      case fromDynamic displayedBytestring of-        Nothing -> error "Expecting lazy Bytestring"-        Just bytestring ->-          case Serialize.decode bytestring of-            Left err -> error err-            Right displayData -> do-              write $ show displayData-              return displayData---evalCommand _ (Declaration decl) = wrapExecution $ do-  write $ "Declaration:\n" ++ decl-  runDecls decl--  -- Do not display any output-  return []--evalCommand _ (TypeSignature sig) = wrapExecution $-  -- We purposefully treat this as a "success" because that way execution-  -- continues. Empty type signatures are likely due to a parse error later-  -- on, and we want that to be displayed.-  return $ displayError $ "The type signature " ++ sig ++-                          "\nlacks an accompanying binding."--evalCommand _ (ParseError loc err) = do-  write "Parse Error."-  return (Failure, displayError $ formatParseError loc err)--capturedStatement :: (String -> IO ())         -- ^ Function used to publish intermediate output.-                  -> String                            -- ^ Statement to evaluate.-                  -> Interpreter (String, RunResult)   -- ^ Return the output and result.-capturedStatement output stmt = do-  -- Generate random variable names to use so that we cannot accidentally-  -- override the variables by using the right names in the terminal.-  gen <- liftIO getStdGen-  let-    -- Variable names generation.-    rand = take 20 $ randomRs ('0', '9') gen-    var name = name ++ rand-    -    -- Variables for the pipe input and outputs.-    readVariable = var "file_read_var_"-    writeVariable = var "file_write_var_"--    -- Variable where to store old stdout.-    oldVariable = var "old_var_"--    -- Variable used to store true `it` value.-    itVariable = var "it_var_"--    voidpf str = printf $ str ++ " >> return ()"-    -    -- Statements run before the thing we're evaluating.-    initStmts = -      [ printf "let %s = it" itVariable-      , printf "(%s, %s) <- createPipe" readVariable writeVariable-      , printf "%s <- dup stdOutput" oldVariable-      , voidpf "dupTo %s stdOutput" writeVariable-      , voidpf "hSetBuffering stdout NoBuffering"-      , printf "let it = %s" itVariable-      ]-    -    -- Statements run after evaluation.-    postStmts = -      [ printf "let %s = it" itVariable-      , voidpf "hFlush stdout"-      , voidpf "dupTo %s stdOutput" oldVariable-      , voidpf "closeFd %s" writeVariable-      , printf "let it = %s" itVariable-      ]-    pipeExpr = printf "let %s = %s" (var "pipe_var_") readVariable--    goStmt s = runStmt s RunToCompletion--  -- Initialize evaluation context.-  forM_ initStmts goStmt--  -- Get the pipe to read printed output from.-  -- This is effectively the source code of dynCompileExpr from GHC API's-  -- InteractiveEval. However, instead of using a `Dynamic` as an-  -- intermediary, it just directly reads the value. This is incredibly-  -- unsafe! However, for some reason the `getContext` and `setContext`-  -- required by dynCompileExpr (to import and clear Data.Dynamic) cause-  -- issues with data declarations being updated (e.g. it drops newer-  -- versions of data declarations for older ones for unknown reasons).-  -- First, compile down to an HValue.-  Just (_, hValues, _) <- withSession $ liftIO . flip hscStmt pipeExpr-  -- Then convert the HValue into an executable bit, and read the value.-  pipe <- liftIO $ do-    fd <- head <$> unsafeCoerce hValues-    fdToHandle fd--  -- Read from a file handle until we hit a delimiter or until we've read-  -- as many characters as requested-  let -    readChars :: Handle -> String -> Int -> IO String--    -- If we're done reading, return nothing.-    readChars handle delims 0 = return []--    readChars handle delims nchars = do-      -- Try reading a single character. It will throw an exception if the-      -- handle is already closed.-      tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char)-      case tryRead of-        Right char ->-          -- If this is a delimiter, stop reading.-          if char `elem` delims-          then return [char]-          else do-            next <- readChars handle delims (nchars - 1)-            return $ char:next-        -- An error occurs at the end of the stream, so just stop reading.-        Left _ -> return []--  -- Keep track of whether execution has completed.-  completed <- liftIO $ newMVar False-  finishedReading <- liftIO newEmptyMVar-  outputAccum <- liftIO $ newMVar ""--  -- Start a loop to publish intermediate results.-  let -    -- Compute how long to wait between reading pieces of the output. -    -- `threadDelay` takes an argument of microseconds.-    ms = 1000-    delay = 100 * ms--    -- How much to read each time.-    chunkSize = 100--    -- Maximum size of the output (after which we truncate).-    maxSize = 100 * 1000--    loop = do-      -- Wait and then check if the computation is done.-      threadDelay delay-      computationDone <- readMVar completed--      if not computationDone-      then do-        -- Read next chunk and append to accumulator.-        nextChunk <- readChars pipe "\n" 100-        modifyMVar_ outputAccum (return . (++ nextChunk))--        -- Write to frontend and repeat.-        readMVar outputAccum >>= output-        loop-      else do-        -- Read remainder of output and accumulate it.-        nextChunk <- readChars pipe "" maxSize-        modifyMVar_ outputAccum (return . (++ nextChunk))--        -- We're done reading.-        putMVar finishedReading True--  liftIO $ forkIO loop--  result <- gfinally (goStmt stmt) $ do-    -- Execution is done.-    liftIO $ modifyMVar_ completed (const $ return True)--    -- Finalize evaluation context.-    forM_ postStmts goStmt--    -- Once context is finalized, reading can finish.-    -- Wait for reading to finish to that the output accumulator is-    -- completely filled.-    liftIO $ takeMVar finishedReading-    -  printedOutput <- liftIO $ readMVar outputAccum-  return (printedOutput, result)--formatError :: ErrMsg -> String-formatError = printf "<span style='color: red; font-style: italic;'>%s</span>" .-            replace "\n" "<br/>" . -            replace useDashV "" .-            rstrip . -            typeCleaner-  where -        useDashV = "\nUse -v to see a list of the files searched for."--formatParseError :: StringLoc -> String -> ErrMsg-formatParseError (Loc line col) = -  printf "Parse error (line %d, column %d): %s" line col--formatGetType :: String -> String-formatGetType = printf "<span style='font-weight: bold; color: green;'>%s</span>"--displayError :: ErrMsg -> [DisplayData]-displayError msg = [plain msg, html $ formatError msg] 
− IHaskell/Eval/Info.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}-{- | Description : Inspect type and function information and documentation.--}-module IHaskell.Eval.Info (-  info-  ) where--import ClassyPrelude hiding (liftIO)--import IHaskell.Eval.Evaluate (typeCleaner, Interpreter)--import GHC-import Outputable-import Exception--info :: String -> Interpreter String-info name = ghandle handler $ do-  dflags <- getSessionDynFlags-  result <- exprType name-  return $ typeCleaner $ showPpr dflags result -  where -    handler :: SomeException -> Interpreter String-    handler _ = return ""
− IHaskell/Eval/Parser.hs
@@ -1,296 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}-module IHaskell.Eval.Parser (-    parseString,-    CodeBlock(..),-    StringLoc(..),-    DirectiveType(..),-    LineNumber,-    ColumnNumber,-    ErrMsg,-    layoutChunks,-    parseDirective,-    getModuleName-    ) where---- Hide 'unlines' to use our own 'joinLines' instead.-import ClassyPrelude hiding (liftIO, unlines)--import Data.List (findIndex, maximumBy, maximum, inits)-import Data.String.Utils (startswith, strip, split)-import Data.List.Utils (subIndex)-import Prelude (init, last, head, tail)--import Bag-import ErrUtils hiding (ErrMsg)-import FastString-import GHC-import Lexer-import OrdList-import Outputable hiding ((<>))-import SrcLoc-import StringBuffer--import Language.Haskell.GHC.Parser---- | A block of code to be evaluated.--- Each block contains a single element - one declaration, statement,--- expression, etc. If parsing of the block failed, the block is instead--- a ParseError, which has the error location and error message.-data CodeBlock-  = Expression String                  -- ^ A Haskell expression.-  | Declaration String                 -- ^ A data type or function declaration.-  | Statement String                   -- ^ A Haskell statement (as if in a `do` block).-  | Import String                      -- ^ An import statement.-  | TypeSignature String               -- ^ A lonely type signature (not above a function declaration).-  | Directive DirectiveType String     -- ^ An IHaskell directive.-  | Module String                      -- ^ A full Haskell module, to be compiled and loaded.-  | ParseError StringLoc ErrMsg        -- ^ An error indicating that parsing the code block failed.-  deriving (Show, Eq)---- | Directive types. Each directive is associated with a string in the--- directive code block.-data DirectiveType-  = GetType         -- ^ Get the type of an expression via ':type' (or unique prefixes)-  | GetInfo         -- ^ Get info about the identifier via ':info' (or unique prefixes)-  | SetExtension    -- ^ Enable or disable an extension via ':extension' (or prefixes)-  | HelpForSet      -- ^ Provide useful info if people try ':set'.-  | GetHelp         -- ^ General help via ':?' or ':help'.-  deriving (Show, Eq)---- | Parse a string into code blocks.-parseString :: GhcMonad m => String -> m [CodeBlock]-parseString codeString = do-  -- Try to parse this as a single module.-  flags <- getSessionDynFlags-  let output = runParser flags parserModule codeString-  case output of-    Parsed {} -> return [Module codeString]-    Failure {} ->-      -- Split input into chunks based on indentation.-      let chunks = layoutChunks $ dropComments codeString in-        joinFunctions <$> processChunks 1 [] chunks-  where-    parseChunk :: GhcMonad m => String -> LineNumber -> m CodeBlock-    parseChunk chunk line =-      if isDirective chunk-      then return $ parseDirective chunk line-      else parseCodeChunk chunk line--    processChunks :: GhcMonad m => LineNumber -> [CodeBlock] -> [String] -> m [CodeBlock]-    processChunks line accum remaining =-      case remaining of-        -- If we have no more remaining lines, return the accumulated results.-        [] -> return $ reverse accum--        -- If we have more remaining, parse the current chunk and recurse.-        chunk:remaining ->  do-          block <- parseChunk chunk line-          processChunks (line + nlines chunk) (block : accum) remaining--    -- Test wither a given chunk is a directive.-    isDirective :: String -> Bool-    isDirective = startswith ":" . strip--    -- Number of lines in this string.-    nlines :: String -> Int-    nlines = length . lines---- | Parse a single chunk of code, as indicated by the layout of the code.-parseCodeChunk :: GhcMonad m => String -> LineNumber  -> m CodeBlock-parseCodeChunk code startLine = do-    flags <- getSessionDynFlags-    let-        -- Try each parser in turn.-        rawResults = map (tryParser code) (parsers flags)--        -- Convert statements into expressions where we can-        results = map (statementToExpression flags) rawResults in-      case successes results of-        -- If none of them succeeded, choose the best error message to-        -- display. Only one of the error messages is actually relevant.-        [] -> return $ bestError $ failures results--        -- If one of the parsers succeeded-        result:_ -> return result-  where-    successes :: [ParseOutput a] -> [a]-    successes [] = []-    successes (Parsed a:rest) = a : successes rest-    successes (_:rest) = successes rest--    failures :: [ParseOutput a] -> [(ErrMsg, LineNumber, ColumnNumber)]-    failures [] = []-    failures (Failure msg (Loc line col):rest) = (msg, line, col) : failures rest-    failures (_:rest) = failures rest--    bestError :: [(ErrMsg, LineNumber, ColumnNumber)] -> CodeBlock-    bestError errors = ParseError (Loc (line + startLine - 1) col) msg-      where-        (msg, line, col) = maximumBy compareLoc errors-        compareLoc (_, line1, col1) (_, line2, col2) = compare line1 line2 <> compare col1 col2--    statementToExpression :: DynFlags -> ParseOutput CodeBlock -> ParseOutput CodeBlock-    statementToExpression flags (Parsed (Statement stmt)) = Parsed result-      where result = if isExpr flags stmt-                     then Expression stmt-                     else Statement stmt-    statementToExpression _ other = other--    -- Check whether a string is a valid expression.-    isExpr :: DynFlags -> String -> Bool-    isExpr flags str = case runParser flags parserExpression str of-      Parsed {} -> True-      _ -> False--    tryParser :: String -> (String -> CodeBlock, String -> ParseOutput String) -> ParseOutput CodeBlock-    tryParser string (blockType, parser) = case parser string of-      Parsed res -> Parsed (blockType res)-      Failure err loc -> Failure err loc--    parsers :: DynFlags -> [(String -> CodeBlock, String -> ParseOutput String)]-    parsers flags =-      [ (Import,        unparser parserImport)-      , (TypeSignature, unparser parserTypeSignature)-      , (Declaration,   unparser parserDeclaration)-      , (Statement,     unparser parserStatement)-      ]-      where-        unparser :: Parser a -> String -> ParseOutput String-        unparser parser code =-          case runParser flags parser code of-            Parsed out -> Parsed code-            Partial out strs -> Partial code strs-            Failure err loc -> Failure err loc---- | Find consecutive declarations of the same function and join them into--- a single declaration. These declarations may also include a type--- signature, which is also joined with the subsequent declarations.-joinFunctions :: [CodeBlock] -> [CodeBlock]-joinFunctions (Declaration decl : rest) =-    -- Find all declarations having the same name as this one.-    let (decls, other) = havingSameName rest in-      -- Convert them into a single declaration.-      Declaration (joinLines $ map undecl decls) : joinFunctions other-  where-    undecl (Declaration decl) = decl-    undecl _ = error "Expected declaration!"--    -- Get all declarations with the same name as the first declaration.-    -- The name of a declaration is the first word, which we expect to be-    -- the name of the function.-    havingSameName :: [CodeBlock] -> ([CodeBlock], [CodeBlock]) -    havingSameName blocks =-      let name = head $ words decl-          sameName = takeWhile (isNamedDecl name) rest -          others = drop (length sameName) rest in-        (Declaration decl : sameName, others)--    isNamedDecl :: String -> CodeBlock -> Bool-    isNamedDecl name (Declaration dec) = head (words dec) == name-    isNamedDecl _ _ = False---- Allow a type signature followed by declarations to be joined to the--- declarations. Parse the declaration joining separately.-joinFunctions (TypeSignature sig : Declaration decl : rest) = (Declaration $ sig ++ "\n" ++ joinedDecl):remaining-  where Declaration joinedDecl:remaining = joinFunctions $ Declaration decl : rest -        -joinFunctions (x:xs) = x : joinFunctions xs-joinFunctions [] = []----- | Parse a directive of the form :directiveName.-parseDirective :: String       -- ^ Directive string.-               -> Int          -- ^ Line number at which the directive appears.-               -> CodeBlock    -- ^ Directive code block or a parse error.-parseDirective (':':directive) line = case find rightDirective directives of-  Just (directiveType, _) -> Directive directiveType arg-    where arg = unwords restLine-          _:restLine = words directive-  Nothing -> -    let directiveStart = case words directive of-          [] -> ""-          first:_ -> first in-      ParseError (Loc line 1) $ "Unknown directive: '" ++ directiveStart ++ "'."-  where-    rightDirective (_, dirname) = case words directive of-      [] -> False-      dir:_ -> dir `elem` tail (inits dirname)-    directives =-      [(GetType,      "type")-      ,(GetInfo,      "info")-      ,(SetExtension, "extension")-      ,(HelpForSet,   "set")-      ,(GetHelp,      "?")-      ,(GetHelp,      "help")-      ]-parseDirective _ _ = error "Directive must start with colon!"----- | Split an input string into chunks based on indentation.--- A chunk is a line and all lines immediately following that are indented--- beyond the indentation of the first line. This parses Haskell layout--- rules properly, and allows using multiline expressions via indentation. -layoutChunks :: String -> [String]-layoutChunks string = filter (not . null) $ map strip $ layoutLines $ lines string-  where-    layoutLines :: [String] -> [String]-    -- Empty string case.  If there's no input, output is empty.-    layoutLines [] = []--    -- Use the indent of the first line to find the end of the first block.-    layoutLines (firstLine:rest) = -      let firstIndent = indentLevel firstLine-          blockEnded line = indentLevel line <= firstIndent in-        case findIndex blockEnded rest of-          -- If the first block doesn't end, return the whole string, since-          -- that just means the block takes up the entire string. -          Nothing -> [string]--          -- We found the end of the block. Split this bit out and recurse.-          Just idx -> -              joinLines (firstLine:take idx rest) : layoutChunks (joinLines $ drop idx rest)--    -- Compute indent level of a string as number of leading spaces.-    indentLevel :: String -> Int-    indentLevel (' ':str) = 1 + indentLevel str--    -- Count a tab as two spaces.-    indentLevel ('\t':str) = 2 + indentLevel str-  -    -- Count empty lines as a large indent level, so they're always with the previous expression.-    indentLevel "" = 100000--    indentLevel _ = 0---- Not the same as 'unlines', due to trailing \n-joinLines :: [String] -> String-joinLines = intercalate "\n"---- | Drop comments from Haskell source.-dropComments :: String -> String-dropComments = removeOneLineComments . removeMultilineComments-  where-    removeOneLineComments ('-':'-':remaining) = removeOneLineComments (dropWhile (/= '\n') remaining)-    removeOneLineComments (x:xs) = x:removeOneLineComments xs-    removeOneLineComments x = x--    removeMultilineComments ('{':'-':remaining) = -      case subIndex "-}" remaining of-        Nothing -> ""-        Just idx -> removeMultilineComments $ drop (2 + idx) remaining-    removeMultilineComments (x:xs) = x:removeMultilineComments xs-    removeMultilineComments x = x---- | Parse a module and return the name declared in the 'module X where'--- line. That line is required, and if it does not exist, this will error.--- Names with periods in them are returned piece y piece.-getModuleName :: GhcMonad m => String -> m [String]-getModuleName moduleSrc = do-  flags <- getSessionDynFlags-  let output = runParser flags parserModule moduleSrc-  case output of-    Failure {} -> error "Module parsing failed."-    Parsed mod -> -      case unLoc <$> hsmodName (unLoc mod) of-        Nothing -> error "Module must have a name."-        Just name -> return $ split "." $ moduleNameString name
− IHaskell/IPython.hs
@@ -1,265 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}-{-# LANGUAGE DoAndIfThenElse #-}--- | Description : Shell scripting wrapper using @Shelly@ for the @notebook@, @setup@, and---                 @console@ commands.-module IHaskell.IPython (-  ipythonInstalled,-  installIPython,-  removeIPython,-  runConsole,-  runNotebook,-  readInitInfo,-  defaultConfFile,-) where--import ClassyPrelude-import Prelude (read, reads, init)-import Shelly hiding (find, trace, path)-import System.Argv0-import System.Directory-import qualified Filesystem.Path.CurrentOS as FS-import Data.List.Utils (split)-import Data.String.Utils (rstrip)-import Text.Printf--import qualified System.IO.Strict as StrictIO-import qualified Paths_ihaskell as Paths-import qualified Codec.Archive.Tar as Tar--import IHaskell.Types---- | Which commit of IPython we are on.-ipythonCommit :: Text-ipythonCommit = "1faf2f6e77fa31f4533e3edbe101c38ddf8943d8"---- | The IPython profile name.-ipythonProfile :: String-ipythonProfile = "haskell"---- | Run IPython with any arguments.-ipython :: Bool         -- ^ Whether to suppress output.-        -> [Text]       -- ^ IPython command line arguments.-        -> Sh String    -- ^ IPython output.-ipython suppress args = do-  (_, ipythonDir, _) <- ihaskellDirs-  let ipythonPath = fromText $ ipythonDir ++ "/bin/ipython"-  sub $ do-    setenv "PYTHONPATH" $ ipythonDir ++ "/lib/python2.7/site-packages"-    runHandles ipythonPath args handles doNothing-  where handles = [InHandle Inherit, outHandle suppress, errorHandle suppress]-        outHandle True = OutHandle CreatePipe-        outHandle False = OutHandle Inherit-        errorHandle True =  ErrorHandle CreatePipe-        errorHandle False = ErrorHandle Inherit-        doNothing _ stdout _ = if suppress -                                then liftIO $ StrictIO.hGetContents stdout-                                else return ""---- | Run while suppressing all output.-quietRun path args = runHandles path args handles nothing-  where-    handles =  [InHandle Inherit, OutHandle CreatePipe, ErrorHandle CreatePipe]-    nothing _ _ _ = return ()---- | Return the data directory for IHaskell and the IPython subdirectory. -ihaskellDirs :: Sh (Text, Text, Text)-ihaskellDirs = do-  home <- maybe (error "$HOME not defined.") id <$> get_env "HOME" :: Sh Text-  let ihaskellDir = home ++ "/.ihaskell"-      ipythonDir = ihaskellDir ++ "/ipython"-      notebookDir = ihaskellDir ++ "/notebooks"--  -- Make sure the directories exist.-  mkdir_p $ fromText ipythonDir-  mkdir_p $ fromText notebookDir--  return (ihaskellDir, ipythonDir, notebookDir)--defaultConfFile :: IO (Maybe String)-defaultConfFile = shellyNoDir $ do-  (ihaskellDir, _, _) <- ihaskellDirs-  let filename = ihaskellDir ++ "/rc.hs"-  exists <- test_f $ fromText filename-  return $ if exists-           then Just $ unpack filename-           else Nothing---- | Remove IPython so it can be reinstalled.-removeIPython :: IO ()-removeIPython = void . shellyNoDir $ do-  (ihaskellDir, _, _) <- ihaskellDirs-  cd $ fromText ihaskellDir-  rm_rf "ipython-src"---- | Install IPython from source.-installIPython :: IO ()-installIPython = void . shellyNoDir $ do-  (ihaskellDir, ipythonDir, _) <- ihaskellDirs--  -- Install all Python dependencies.-  pipPath <- path "pip"-  let pipDeps = ["pyzmq", "tornado", "jinja2"]-      installDep dep = do-        putStrLn $ "Installing dependency: " ++ dep -        let opt = "--install-option=--prefix=" ++ ipythonDir-        run_ pipPath ["install", opt, dep]-  mapM_ installDep pipDeps--  -- Get the IPython source.-  gitPath <- path "git"-  putStrLn "Downloading IPython... (this may take a while)"-  cd $ fromText ihaskellDir-  run_ gitPath ["clone", "--recursive", "https://github.com/ipython/ipython.git", "ipython-src"]-  cd "ipython-src"-  run_ gitPath ["checkout", ipythonCommit]--  -- Install IPython locally.-  pythonPath <- path "python"-  putStrLn "Installing IPython."-  run_ pythonPath ["setup.py", "install", "--prefix=" ++ ipythonDir]-  cd ".."---- | Check whether IPython is properly installed.-ipythonInstalled :: IO Bool-ipythonInstalled = shellyNoDir $ do-  (_, ipythonDir, _) <- ihaskellDirs-  let ipythonPath = ipythonDir ++ "/bin/ipython"-  test_f $ fromText ipythonPath---- | Get the path to an executable. If it doensn't exist, fail with an--- error message complaining about it.-path :: Text -> Sh FilePath-path exe = do-  path <- which $ fromText exe-  case path of-    Nothing -> do-      putStrLn $ "Could not find `" ++ exe ++ "` executable."-      fail $ "`" ++ unpack exe ++ "` not on $PATH."-    Just exePath -> return exePath---- | Use the `ipython --version` command to figure out the version.--- Return a tuple with (major, minor, patch).-ipythonVersion :: IO (Int, Int, Int)-ipythonVersion = shellyNoDir $ do-  [major, minor, patch] <- parseVersion <$>  ipython True ["--version"]-  return (major, minor, patch)---- | Parse an IPython version string into a list of integers.-parseVersion :: String -> [Int]-parseVersion versionStr = map read' $ split "." versionStr-    where read' x = case reads x of-                        [(n, _)] -> n-                        _ -> error $ "cannot parse version: "++ versionStr---- | Run an IHaskell application using the given profile.-runIHaskell :: String   -- ^ IHaskell profile name. -           -> String    -- ^ IPython app name.-           -> [String]  -- ^ Arguments to IPython.-           -> Sh ()-runIHaskell profile app args = void $ do-  -- Try to locate the profile. Do not die if it doesn't exist.-  errExit False $ ipython True ["locate", "profile", pack profile]--  -- If the profile doesn't exist, create it.-  exitCode <- lastExitCode-  when (exitCode /= 0) $ liftIO $ do-    putStrLn "Creating IPython profile."-    setupIPythonProfile profile--  -- Run the IHaskell command.-  ipython False $ map pack $ [app, "--profile", profile] ++ args--runConsole :: InitInfo -> IO ()-runConsole initInfo = void . shellyNoDir $ do-  writeInitInfo initInfo-  runIHaskell ipythonProfile "console" []--runNotebook :: InitInfo -> Maybe String -> IO ()-runNotebook initInfo maybeServeDir = void . shellyNoDir $ do-  (_, _, notebookDir) <- ihaskellDirs-  let args = case maybeServeDir of -               Nothing -> ["--notebook-dir", unpack notebookDir]-               Just dir -> ["--notebook-dir", dir]--  writeInitInfo initInfo-  runIHaskell ipythonProfile "notebook" args--writeInitInfo :: InitInfo -> Sh ()-writeInitInfo info = do-  (ihaskellDir, _, _) <- ihaskellDirs-  let filename = fromText $ ihaskellDir ++ "/last-arguments"-  liftIO $ writeFile filename $ show info--readInitInfo :: IO InitInfo-readInitInfo = shellyNoDir $ do-  (ihaskellDir, _, _) <- ihaskellDirs-  let filename = fromText $ ihaskellDir ++ "/last-arguments"-  read <$> liftIO (readFile filename)---- | Create the IPython profile.-setupIPythonProfile :: String -- ^ IHaskell profile name.-                    -> IO ()-setupIPythonProfile profile = shellyNoDir $ do-  -- Create the IPython profile.-  void $ ipython True ["profile", "create", pack profile]--  -- Find the IPython profile directory. Make sure to get rid of trailing-  -- newlines from the output of the `ipython locate` call.-  ipythonDir <- pack <$> rstrip <$> ipython True ["locate"]-  let profileDir = ipythonDir ++ "/profile_" ++ pack profile ++ "/"--  liftIO $ copyProfile profileDir-  insertIHaskellPath profileDir---- | Copy the profile files into the IPython profile. -copyProfile :: Text -> IO ()-copyProfile profileDir = do-  profileTar <- Paths.getDataFileName "profile/profile.tar"-  {--  -- Load profile from Resources directory of Mac *.app.-  ihaskellPath <- shellyNoDir getIHaskellPath-  profileTar <- if "IHaskell.app/Contents/MacOS" `isInfixOf` ihaskellPath-               then-                let pieces = split "/" ihaskellPath-                    pathPieces = init pieces ++ ["..", "Resources", "profile.tar"] in-                  return $ intercalate "/" pathPieces-               else Paths.getDataFileName "profile/profile.tar"-  -}-  putStrLn $ pack $ "Loading profile from " ++ profileTar -  Tar.extract (unpack profileDir) profileTar ---- | Insert the IHaskell path into the IPython configuration.-insertIHaskellPath :: Text -> Sh ()-insertIHaskellPath profileDir = do-  path <- getIHaskellPath-  let filename = profileDir ++ "ipython_config.py"-      template = "exe = '%s'.replace(' ', '\\\\ ')"-      exeLine = printf template $ unpack path :: String--  liftIO $ do-    contents <- StrictIO.readFile $ unpack filename-    writeFile (fromText filename) $ exeLine ++ "\n" ++ contents---- | Get the absolute path to this IHaskell executable.-getIHaskellPath :: Sh String-getIHaskellPath = do-  --  Get the absolute filepath to the argument.-  f <- liftIO getArgv0--  -- If we have an absolute path, that's the IHaskell we're interested in.-  if FS.absolute f-  then return $ FS.encodeString f-  else-    -- Check whether this is a relative path, or just 'IHaskell' with $PATH-    -- resolution done by the shell. If it's just 'IHaskell', use the $PATH-    -- variable to find where IHaskell lives.-    if FS.filename f == f-    then do-      ihaskellPath <- which "IHaskell"-      case ihaskellPath of-        Nothing -> error "IHaskell not on $PATH and not referenced relative to directory."-        Just path -> return $ FS.encodeString path-    else do-      -- If it's actually a relative path, make it absolute.-      cd <- liftIO getCurrentDirectory-      return $ FS.encodeString $ FS.decodeString cd FS.</> f
− IHaskell/Message/Parser.hs
@@ -1,143 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE NoImplicitPrelude #-}--- | Description : Parsing messages received from IPython------ This module is responsible for converting from low-level ByteStrings--- obtained from the 0MQ sockets into Messages. The only exposed function is--- `parseMessage`, which should only be used in the low-level 0MQ interface.-module IHaskell.Message.Parser (parseMessage) where--import ClassyPrelude-import Data.Aeson ((.:), decode, Result(..), Object)-import Data.Aeson.Types (parse)--import qualified Data.ByteString.Lazy as Lazy--import IHaskell.Types------- External interface --------- | Parse a message from its ByteString components into a Message.-parseMessage :: [ByteString] -- ^ The list of identifiers sent with the message.-              -> ByteString   -- ^ The header data.-              -> ByteString   -- ^ The parent header, which is just "{}" if there is no header.-              -> ByteString   -- ^ The metadata map, also "{}" for an empty map.-              -> ByteString   -- ^ The message content.-              -> Message      -- ^ A parsed message.-parseMessage idents headerData parentHeader metadata content = -  let header = parseHeader idents headerData parentHeader metadata-      messageType = msgType header-      messageWithoutHeader = parser messageType $ Lazy.fromStrict content in-    messageWithoutHeader { header = header }------- Module internals --------- | Parse a header from its ByteString components into a MessageHeader.-parseHeader :: [ByteString]  -- ^ The list of identifiers.-            -> ByteString    -- ^ The header data.-            -> ByteString    -- ^ The parent header, or "{}" for Nothing.-            -> ByteString    -- ^ The metadata, or "{}" for an empty map.-            -> MessageHeader -- The resulting message header.-parseHeader idents headerData parentHeader metadata = MessageHeader {-  identifiers = idents,-  parentHeader = parentResult,-  metadata = metadataMap,-  messageId = messageUUID,-  sessionId = sessionUUID,-  username = username,-  msgType = messageType-  } where-      -- Decode the header data and the parent header data into JSON objects.-      -- If the parent header data is absent, just have Nothing instead.-      Just result = decode $ Lazy.fromStrict headerData :: Maybe Object-      parentResult = if parentHeader == "{}"-                     then Nothing-                     else Just $ parseHeader idents parentHeader "{}" metadata--      -- Get the basic fields from the header.-      Success (messageType, username, messageUUID, sessionUUID) = flip parse result $ \obj -> do -        messType <- obj .: "msg_type"-        username <- obj .: "username"-        message <- obj .: "msg_id"-        session <- obj .: "session"-        return (messType, username, message, session)--      -- Get metadata as a simple map.-      Just metadataMap = decode $ Lazy.fromStrict metadata :: Maybe (Map ByteString ByteString)--noHeader :: MessageHeader-noHeader = error "No header created"--parser :: MessageType            -- ^ The message type being parsed.-       -> LByteString -> Message   -- The parser that converts the body into a message.-                                 -- This message should have an undefined-                                 -- header.-parser KernelInfoRequestMessage  = kernelInfoRequestParser-parser ExecuteRequestMessage     = executeRequestParser-parser CompleteRequestMessage    = completeRequestParser-parser ObjectInfoRequestMessage  = objectInfoRequestParser-parser ShutdownRequestMessage    = shutdownRequestParser-parser other = error $ "Unknown message type " ++ show other---- | Parse a kernel info request.--- A kernel info request has no auxiliary information, so ignore the body.-kernelInfoRequestParser :: LByteString -> Message-kernelInfoRequestParser _ = KernelInfoRequest { header = noHeader }---- | Parse an execute request.--- Fields used are:---  1. "code": the code to execute.---  2. "silent": whether to execute silently.---  3. "store_history": whether to include this in history.---  4. "allow_stdin": whether to allow reading from stdin for this code.-executeRequestParser :: LByteString -> Message-executeRequestParser content = -  let parser obj = do-        code <- obj .: "code"-        silent <- obj .: "silent"-        storeHistory <- obj .: "store_history"-        allowStdin <- obj .: "allow_stdin"--        return (code, silent, storeHistory, allowStdin)-      Just decoded = decode content-      Success (code, silent, storeHistory, allowStdin) = parse parser decoded in-    ExecuteRequest {-      header = noHeader,-      getCode = code,-      getSilent = silent,-      getAllowStdin = allowStdin,-      getStoreHistory = storeHistory,-      getUserVariables = [],-      getUserExpressions = []-    }--completeRequestParser :: LByteString -> Message-completeRequestParser content = parsed-  where-  Success parsed = flip parse decoded $ \ obj -> do-        code     <- obj .: "block" <|> return ""-        codeLine <- obj .: "line"-        pos      <- obj .: "cursor_pos"-        return $ CompleteRequest noHeader code codeLine pos--  Just decoded = decode content--objectInfoRequestParser :: LByteString -> Message-objectInfoRequestParser content = parsed-  where-    Success parsed = flip parse decoded $ \obj -> do-      oname <- obj .: "oname"-      dlevel <- obj .: "detail_level"-      return $ ObjectInfoRequest noHeader oname dlevel--    Just decoded = decode content---shutdownRequestParser :: LByteString -> Message-shutdownRequestParser content = parsed-  where-  Success parsed = flip parse decoded $ \ obj -> do-        code     <- obj .: "restart"-        return $ ShutdownRequest noHeader code--  Just decoded = decode content
− IHaskell/Message/UUID.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}--- | Description : UUID generator and data structure------ Generate, parse, and pretty print UUIDs for use with IPython.-module IHaskell.Message.UUID (-  UUID,-  random, randoms,-  ) where--import ClassyPrelude-import Control.Monad (mzero)-import Data.Aeson-import Data.UUID.V4 (nextRandom)---- We use an internal string representation because for the purposes of--- IPython, it matters whether the letters are uppercase or lowercase and--- whether the dashes are present in the correct locations. For the--- purposes of new UUIDs, it does not matter, but IPython expects UUIDs--- passed to kernels to be returned unchanged, so we cannot actually parse--- them.---- | A UUID (universally unique identifier).-data UUID = UUID String deriving Eq--instance Show UUID where-  show (UUID s) = s---- | Generate a list of random UUIDs.-randoms :: Int      -- ^ Number of UUIDs to generate.-        -> IO [UUID]-randoms n = replicateM n random---- | Generate a single random UUID.-random :: IO UUID-random = UUID <$> show <$> nextRandom---- Allows reading and writing UUIDs as Strings in JSON. -instance FromJSON UUID where-  parseJSON val@(String _) = UUID <$> parseJSON val--  -- UUIDs must be Strings.-  parseJSON _ = mzero--instance ToJSON UUID where-  -- Extract the string from the UUID.-  toJSON (UUID str) = String $ pack str
− IHaskell/Message/Writer.hs
@@ -1,119 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-}--- | Description : @ToJSON@ for Messages------ This module contains the @ToJSON@ instance for @Message@.-module IHaskell.Message.Writer (-  ToJSON(..)-)  where--import Prelude (read)-import ClassyPrelude-import Data.Aeson--import Language.Haskell.TH-import Shelly hiding (trace)--import IHaskell.Types---- | Compute the GHC API version number using Template Haskell.-ghcVersionInts :: [Int]-ghcVersionInts = ints . map read . words . map dotToSpace $ version-  where dotToSpace '.' = ' '-        dotToSpace x = x--        version :: String-        version = $(runIO (unpack <$> shelly (run "ghc" ["--numeric-version"])) >>= stringE)---- Convert message bodies into JSON.-instance ToJSON Message where-  toJSON KernelInfoReply{} = object [-                             "protocol_version" .= ints [4, 0], -- current protocol version, major and minor-                             "language_version" .= ghcVersionInts,-                             "language" .= string "haskell"-                           ]--  toJSON ExecuteReply{ status = status, executionCounter = counter} = object [-                             "status" .= show status,-                             "execution_count" .= counter,-                             "payload" .= emptyList,-                             "user_variables" .= emptyMap,-                             "user_expressions" .= emptyMap-                           ]-  toJSON PublishStatus{ executionState = executionState } = object [-                             "execution_state" .= executionState-                           ]-  toJSON PublishStream{ streamType = streamType, streamContent = content } = object [-                             "data" .= content,-                             "name" .= streamType-                           ]-  toJSON PublishDisplayData{ source = src, displayData = datas } = object [-                             "source" .= src,-                             "metadata" .= object [],-                             "data" .= object  (map displayDataToJson datas)-                           ]--  toJSON PublishOutput{ executionCount = execCount, reprText = reprText } = object [-                             "data" .= object ["text/plain" .= reprText],-                             "execution_count" .= execCount,-                             "metadata" .= object []-                           ]-  toJSON PublishInput{ executionCount = execCount, inCode = code } = object [-                             "execution_count" .= execCount,-                             "code" .= code-                           ]-  toJSON (CompleteReply _ m mt t s) = object [-                             "matches" .= m,-                             "matched_text" .= mt,-                             "text" .= t,-                             "status" .= if s then "ok" :: String else "error"-                           ]-  toJSON o@ObjectInfoReply{} = object [-                            "oname" .= objectName o,-                            "found" .= objectFound o,-                            "ismagic" .= False,-                            "isalias" .= False,-                            "type_name" .= objectTypeString o,-                            "docstring" .= objectDocString o-                           ]--  toJSON ShutdownReply{restartPending = restart} = object [-                            "restart" .= restart-                           ]--  toJSON ClearOutput{wait = wait} = object [-                            "wait" .= wait-                           ]---  toJSON body = error $ "Do not know how to convert to JSON for message " ++ show body----- | Print an execution state as "busy", "idle", or "starting".-instance ToJSON ExecutionState where-    toJSON Busy = String "busy"-    toJSON Idle = String "idle"-    toJSON Starting = String "starting"---- | Print a stream as "stdin" or "stdout" strings.-instance ToJSON StreamType where-    toJSON Stdin = String "stdin"-    toJSON Stdout = String "stdout"---- | Convert a MIME type and value into a JSON dictionary pair.-displayDataToJson :: DisplayData -> (Text, Value) -displayDataToJson (Display mimeType dataStr) = pack (show mimeType) .= dataStr------- Constants -------emptyMap :: Map String String-emptyMap = mempty--emptyList :: [Int]-emptyList = []--ints :: [Int] -> [Int]-ints = id--string :: String -> String-string = id
− IHaskell/Types.hs
@@ -1,315 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}-{-# LANGUAGE DeriveDataTypeable, DeriveGeneric #-}--- | Description : All message type definitions.-module IHaskell.Types (-  Profile (..),-  Message (..),-  MessageHeader (..),-  MessageType(..),-  Username,-  Metadata,-  Port,-  replyType,-  ExecutionState (..),-  StreamType(..),-  MimeType(..),-  DisplayData(..),-  ExecuteReplyStatus(..),-  InitInfo(..),-  ) where--import ClassyPrelude-import Data.Aeson-import IHaskell.Message.UUID-import Data.Serialize-import GHC.Generics (Generic)------ | A TCP port.-type Port = Int---- | A kernel profile, specifying how the kernel communicates.-data Profile = Profile {-  ip :: String,        -- ^ The IP on which to listen.-  transport :: String, -- ^ The transport mechanism.-  stdinPort :: Port,   -- ^ The stdin channel port. -  controlPort :: Port, -- ^ The control channel port.-  hbPort :: Port,      -- ^ The heartbeat channel port.-  shellPort :: Port,   -- ^ The shell command port.-  iopubPort :: Port,   -- ^ The Iopub port.-  key :: ByteString    -- ^ The HMAC encryption key.-  } deriving Show---- Convert the kernel profile to and from JSON.-instance FromJSON Profile where-  parseJSON (Object v) = -    Profile <$> v .: "ip"-            <*> v .: "transport"-            <*> v .: "stdin_port"-            <*> v .: "control_port"-            <*> v .: "hb_port"-            <*> v .: "shell_port"-            <*> v .: "iopub_port"-            <*> v .: "key"-  parseJSON _ = fail "Expecting JSON object."--instance ToJSON Profile where-  toJSON profile = object [-                    "ip"          .= ip profile,-                    "transport"   .= transport profile,-                    "stdin_port"  .= stdinPort profile,-                    "control_port".= controlPort profile,-                    "hb_port"     .= hbPort profile,-                    "shell_port"  .= shellPort profile,-                    "iopub_port"  .= iopubPort profile,-                    "key"         .= key profile-                   ]---- | Initialization information for the kernel.-data InitInfo = InitInfo {-  extensions :: [String],   -- ^ Extensions to enable at start.-  initCells :: [String]     -- ^ Code blocks to run before start.-  }-  deriving (Show, Read)---- | A message header with some metadata.  -data MessageHeader = MessageHeader {-  identifiers :: [ByteString],         -- ^ The identifiers sent with the message.-  parentHeader :: Maybe MessageHeader, -- ^ The parent header, if present.-  metadata :: Metadata,                -- ^ A dict of metadata.-  messageId :: UUID,                   -- ^ A unique message UUID.-  sessionId :: UUID,                   -- ^ A unique session UUID.-  username :: Username,                -- ^ The user who sent this message.-  msgType :: MessageType               -- ^ The message type.-  } deriving Show---- Convert a message header into the JSON field for the header.--- This field does not actually have all the record fields.-instance ToJSON MessageHeader where-  toJSON header = object [-                    "msg_id"  .= messageId header,-                    "session" .= sessionId header,-                    "username" .= username header,-                    "msg_type" .= show (msgType header)-                  ]---- | A username for the source of a message.-type Username = ByteString---- | A metadata dictionary.-type Metadata = Map ByteString ByteString---- | The type of a message, corresponding to IPython message types.-data MessageType = KernelInfoReplyMessage-                 | KernelInfoRequestMessage-                 | ExecuteReplyMessage-                 | ExecuteRequestMessage-                 | StatusMessage-                 | StreamMessage-                 | DisplayDataMessage-                 | OutputMessage-                 | InputMessage-                 | CompleteRequestMessage-                 | CompleteReplyMessage-                 | ObjectInfoRequestMessage-                 | ObjectInfoReplyMessage-                 | ShutdownRequestMessage-                 | ShutdownReplyMessage-                 | ClearOutputMessage--instance Show MessageType where-  show KernelInfoReplyMessage     = "kernel_info_reply"-  show KernelInfoRequestMessage   = "kernel_info_request"-  show ExecuteReplyMessage        = "execute_reply"-  show ExecuteRequestMessage      = "execute_request"-  show StatusMessage              = "status"-  show StreamMessage              = "stream"-  show DisplayDataMessage         = "display_data"-  show OutputMessage              = "pyout"-  show InputMessage               = "pyin"-  show CompleteRequestMessage     = "complete_request"-  show CompleteReplyMessage       = "complete_reply"-  show ObjectInfoRequestMessage   = "object_info_request"-  show ObjectInfoReplyMessage     = "object_info_reply"-  show ShutdownRequestMessage     = "shutdown_request"-  show ShutdownReplyMessage       = "shutdown_reply"-  show ClearOutputMessage         = "clear_output"--instance FromJSON MessageType where-  parseJSON (String s) = case s of-    "kernel_info_reply"   -> return KernelInfoReplyMessage-    "kernel_info_request" -> return KernelInfoRequestMessage-    "execute_reply"       -> return ExecuteReplyMessage-    "execute_request"     -> return ExecuteRequestMessage-    "status"              -> return StatusMessage-    "stream"              -> return StreamMessage-    "display_data"        -> return DisplayDataMessage-    "pyout"               -> return OutputMessage-    "pyin"                -> return InputMessage-    "complete_request"    -> return CompleteRequestMessage-    "complete_reply"      -> return CompleteReplyMessage-    "object_info_request" -> return ObjectInfoRequestMessage-    "object_info_reply"   -> return ObjectInfoReplyMessage-    "shutdown_request"    -> return ShutdownRequestMessage-    "shutdown_reply"      -> return ShutdownReplyMessage-    "clear_output"        -> return ClearOutputMessage--    _                     -> fail ("Unknown message type: " ++ show s)-  parseJSON _ = fail "Must be a string."----- | A message used to communicate with the IPython frontend.-data Message -  -- | A request from a frontend for information about the kernel.-  = KernelInfoRequest { header :: MessageHeader }-  -- | A response to a KernelInfoRequest.-  | KernelInfoReply { header :: MessageHeader }-               -  -- | A request from a frontend to execute some code.-  | ExecuteRequest {-      header :: MessageHeader,-      getCode :: ByteString,             -- ^ The code string.-      getSilent :: Bool,                 -- ^ Whether this should be silently executed.-      getStoreHistory :: Bool,           -- ^ Whether to store this in history.-      getAllowStdin :: Bool,             -- ^ Whether this code can use stdin.--      getUserVariables :: [ByteString],  -- ^ Unused.-      getUserExpressions :: [ByteString] -- ^ Unused.-    }--  -- | A reply to an execute request.-  | ExecuteReply {-      header :: MessageHeader,-      status :: ExecuteReplyStatus,         -- ^ The status of the output.-      executionCounter :: Int               -- ^ The execution count, i.e. which output this is.-    }--  | PublishStatus {-      header :: MessageHeader,-      executionState :: ExecutionState      -- ^ The execution state of the kernel.-    }--  | PublishStream {-      header :: MessageHeader,-      streamType :: StreamType,             -- ^ Which stream to publish to.-      streamContent :: String               -- ^ What to publish.-    }--  | PublishDisplayData {-      header :: MessageHeader,-      source :: String,                     -- ^ The name of the data source.-      displayData :: [DisplayData]          -- ^ A list of data representations.-    }--  | PublishOutput {-      header :: MessageHeader,-      reprText :: String,                   -- ^ Printed output text.-      executionCount :: Int                 -- ^ Which output this is for.-    }--  | PublishInput {-      header :: MessageHeader,-      inCode :: String,                     -- ^ Submitted input code.-      executionCount :: Int                 -- ^ Which input this is.-    }--  | CompleteRequest {-      header :: MessageHeader,-      getCode :: ByteString, {- ^-            The entire block of text where the line is.  This may be useful in the-            case of multiline completions where more context may be needed.  Note: if-            in practice this field proves unnecessary, remove it to lighten the-            messages. json field @block@  -}-      getCodeLine :: ByteString, -- ^ just the line with the cursor. json field @line@-      getCursorPos :: Int -- ^ position of the cursor (index into the line?). json field @cursor_pos@--    }--  | CompleteReply {-     header :: MessageHeader,-     completionMatches :: [ByteString],-     completionMatchedText :: ByteString,-     completionText :: ByteString,-     completionStatus :: Bool-  }--  | ObjectInfoRequest {-      header :: MessageHeader, -      objectName :: ByteString,  -- ^ Name of object being searched for.-      detailLevel :: Int         -- ^ Level of detail desired (defaults to 0).-                                -- 0 is equivalent to foo?, 1 is equivalent-                                -- to foo??.-    }--  | ObjectInfoReply {-      header :: MessageHeader, -      objectName :: ByteString,       -- ^ Name of object which was searched for.-      objectFound :: Bool,            -- ^ Whether the object was found.-      objectTypeString :: ByteString, -- ^ Object type.-      objectDocString  :: ByteString-    }--  | ShutdownRequest {-      header :: MessageHeader,-      restartPending :: Bool    -- ^ Whether this shutdown precedes a restart.-    }-  | ShutdownReply {-      header :: MessageHeader,-      restartPending :: Bool    -- ^ Whether this shutdown precedes a restart.-    }--  | ClearOutput {-      header :: MessageHeader,-      wait :: Bool -- ^ Whether to wait to redraw until there is more output.-  }--    deriving Show---- | Possible statuses in the execution reply messages.-data ExecuteReplyStatus = Ok | Err | Abort--instance Show ExecuteReplyStatus where-  show Ok = "ok"-  show Err = "error"-  show Abort = "abort"---- | The execution state of the kernel.-data ExecutionState = Busy | Idle | Starting deriving Show---- | Data for display: a string with associated MIME type.-data DisplayData = Display MimeType String deriving (Show, Typeable, Generic)---- Allow DisplayData serialization-instance Serialize DisplayData-instance Serialize MimeType---- | Possible MIME types for the display data.-data MimeType = PlainText-              | MimeHtml-              | MimePng-              | MimeJpg-              | MimeSvg-              | MimeLatex-              deriving (Eq, Typeable, Generic)---instance Show MimeType where-  show PlainText = "text/plain"-  show MimeHtml  = "text/html"-  show MimePng   = "image/png" -  show MimeJpg   = "image/jpeg"-  show MimeSvg   = "image/svg+xml"-  show MimeLatex = "text/latex"---- | Input and output streams.-data StreamType = Stdin | Stdout deriving Show---- | Get the reply message type for a request message type.-replyType :: MessageType -> MessageType-replyType KernelInfoRequestMessage = KernelInfoReplyMessage-replyType ExecuteRequestMessage = ExecuteReplyMessage-replyType CompleteRequestMessage = CompleteReplyMessage-replyType ObjectInfoRequestMessage = ObjectInfoReplyMessage-replyType ShutdownRequestMessage = ShutdownReplyMessage-replyType messageType = error $ "No reply for message type " ++ show messageType
− IHaskell/ZeroMQ.hs
@@ -1,187 +0,0 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}--- | Description : Low-level ZeroMQ communication wrapper.------ The "ZeroMQ" module abstracts away the low-level 0MQ based interface with IPython,--- replacing it instead with a Haskell Channel based interface. The `serveProfile` function--- takes a IPython profile specification and returns the channel interface to use.-module IHaskell.ZeroMQ (-  ZeroMQInterface (..),-  serveProfile-  ) where--import ClassyPrelude hiding (stdin)-import Control.Concurrent-import System.ZMQ3 hiding (stdin)-import Data.Aeson (encode)--import qualified Data.ByteString.Lazy as ByteString--import IHaskell.Types-import IHaskell.Message.Parser-import IHaskell.Message.Writer---- | The channel interface to the ZeroMQ sockets. All communication is done via--- Messages, which are encoded and decoded into a lower level form before being--- transmitted to IPython. These channels should functionally serve as--- high-level sockets which speak Messages instead of ByteStrings.-data ZeroMQInterface = Channels {-  shellRequestChannel :: Chan Message,    -- ^ A channel populated with requests from the frontend.-  shellReplyChannel :: Chan Message,      -- ^ Writing to this channel causes a reply to be sent to the frontend.-  controlRequestChannel :: Chan Message,  -- ^ This channel is a duplicate of the shell request channel,-                                         -- ^ though using a different backend socket.-  controlReplyChannel :: Chan Message,    -- ^ This channel is a duplicate of the shell reply channel,-                                         -- ^ though using a different backend socket.-  iopubChannel :: Chan Message           -- ^ Writing to this channel sends an iopub message to the frontend.-  }---- | Start responding on all ZeroMQ channels used to communicate with IPython--- | via the provided profile. Return a set of channels which can be used to--- | communicate with IPython in a more structured manner. -serveProfile :: Profile            -- ^ The profile specifying which ports and transport mechanisms to use.-             -> IO ZeroMQInterface -- ^ The Message-channel based interface to the sockets.-serveProfile profile = do-  -- Create all channels which will be used for higher level communication.-  shellReqChan <- newChan-  shellRepChan <- newChan-  controlReqChan <- dupChan shellReqChan-  controlRepChan <- dupChan shellRepChan-  iopubChan <- newChan-  let channels = Channels shellReqChan shellRepChan controlReqChan controlRepChan iopubChan--  -- Create the context in a separate thread that never finishes. If-  -- withContext or withSocket complete, the context or socket become invalid.-  forkIO $ withContext $ \context -> do-    -- Serve on all sockets.-    forkIO $ serveSocket context Rep    (hbPort profile)      $ heartbeat channels-    forkIO $ serveSocket context Router (controlPort profile) $ control   channels-    forkIO $ serveSocket context Router (shellPort profile)   $ shell     channels-    forkIO $ serveSocket context Router (stdinPort profile)   $ stdin     channels--    -- The context is reference counted in this thread only. Thus, the last-    -- serveSocket cannot be asynchronous, because otherwise context would-    -- be garbage collectable - since it would only be used in other-    -- threads. Thus, keep the last serveSocket in this thread.-    serveSocket context Pub    (iopubPort profile)   $ iopub     channels--  return channels---- | Serve on a given socket in a separate thread. Bind the socket in the--- | given context and then loop the provided action, which should listen--- | on the socket and respond to any events.-serveSocket :: SocketType a => Context -> a -> Port -> (Socket a -> IO b) -> IO ()-serveSocket context socketType port action = void $-  withSocket context socketType $ \socket -> do-    bind socket $ unpack $ "tcp://127.0.0.1:" ++ show port-    forever $ action socket---- | Listener on the heartbeat port. Echoes back any data it was sent.-heartbeat :: ZeroMQInterface -> Socket Rep -> IO ()-heartbeat _ socket = do-  -- Read some data.-  request <- receive socket--  -- Send it back.-  send socket [] request---- | Listener on the shell port. Reads messages and writes them to--- | the shell request channel. For each message, reads a response from the--- | shell reply channel of the interface and sends it back to the frontend. -shell :: ZeroMQInterface -> Socket Router -> IO ()-shell channels socket = do-  -- Receive a message and write it to the interface channel.-  receiveMessage socket >>= writeChan requestChannel--  -- Read the reply from the interface channel and send it.-  readChan replyChannel >>= sendMessage socket--  where-    requestChannel = shellRequestChannel channels-    replyChannel = shellReplyChannel channels---- | Listener on the shell port. Reads messages and writes them to--- | the shell request channel. For each message, reads a response from the--- | shell reply channel of the interface and sends it back to the frontend. -control :: ZeroMQInterface -> Socket Router -> IO ()-control channels socket = do-  -- Receive a message and write it to the interface channel.-  receiveMessage socket >>= writeChan requestChannel--  -- Read the reply from the interface channel and send it.-  readChan replyChannel >>= sendMessage socket--  where-    requestChannel = controlRequestChannel channels-    replyChannel =   controlReplyChannel channels---- | Send messages via the iopub channel.--- | This reads messages from the ZeroMQ iopub interface channel --- | and then writes the messages to the socket.-iopub :: ZeroMQInterface -> Socket Pub -> IO ()-iopub channels socket =-  readChan (iopubChannel channels) >>= sendMessage socket--stdin :: ZeroMQInterface -> Socket Router -> IO ()-stdin _ socket = do-  void $ receive socket-  return ()---- | Receive and parse a message from a socket.-receiveMessage :: Receiver a => Socket a -> IO Message-receiveMessage socket = do-  -- Read all identifiers until the identifier/message delimiter.-  idents <- readUntil "<IDS|MSG>"--  -- Ignore the signature for now.-  void next--  headerData <- next-  parentHeader <- next-  metadata <- next-  content <- next--  let message = parseMessage idents headerData parentHeader metadata content-  return message--  where-    -- Receive the next piece of data from the socket.-    next = receive socket--    -- Read data from the socket until we hit an ending string.-    -- Return all data as a list, which does not include the ending string.-    readUntil str = do-      line <- next-      if line /= str-      then do-        remaining <- readUntil str-        return $ line : remaining-      else return []-  --- | Encode a message in the IPython ZeroMQ communication protocol --- | and send it through the provided socket.-sendMessage :: Sender a => Socket a -> Message -> IO ()-sendMessage socket message = do-  let head = header message-      parentHeaderStr = maybe "{}" encodeStrict $ parentHeader head-      idents = identifiers head-      metadata = "{}"-      content = encodeStrict message-      headStr = encodeStrict head--  -- Send all pieces of the message.-  mapM_ sendPiece idents-  sendPiece "<IDS|MSG>"-  sendPiece ""-  sendPiece headStr-  sendPiece parentHeaderStr-  sendPiece metadata--  -- Conclude transmission with content.-  sendLast content--  where-    sendPiece = send socket [SendMore]-    sendLast = send socket []--    -- Encode to a strict bytestring.-    encodeStrict :: ToJSON a => a -> ByteString-    encodeStrict = ByteString.toStrict . encode
− Main.hs
@@ -1,352 +0,0 @@-{-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--- | Description : Argument parsing and basic messaging loop, using Haskell---                 Chans to communicate with the ZeroMQ sockets. -module Main where-import ClassyPrelude hiding (liftIO)-import Prelude (last)-import Control.Concurrent.Chan-import Control.Concurrent (threadDelay)-import Data.Aeson-import Text.Printf-import System.Exit (exitSuccess)-import System.Directory-import System.Console.CmdArgs.Explicit hiding (complete)--import qualified Data.Map as Map--import IHaskell.Types-import IHaskell.ZeroMQ-import qualified IHaskell.Message.UUID as UUID-import IHaskell.Eval.Evaluate-import IHaskell.Eval.Completion (complete)-import IHaskell.Eval.Info-import qualified Data.ByteString.Char8 as Chars-import IHaskell.IPython--import GHC hiding (extensions)-import Outputable (showSDoc, ppr)---- All state stored in the kernel between executions.-data KernelState = KernelState-  { getExecutionCounter :: Int-  }---- Command line arguments to IHaskell.  A set of aruments is annotated with--- the mode being invoked.-data Args = Args IHaskellMode [Argument]--data Argument-  = ServeFrom String    -- ^ Which directory to serve notebooks from.-  | Extension String    -- ^ An extension to load at startup.-  | ConfFile String     -- ^ A file with commands to load at startup.-  | Help                -- ^ Display help text.-  deriving (Eq, Show)---- Which mode IHaskell is being invoked in.--- `None` means no mode was specified.-data IHaskellMode-  = None-  | Notebook-  | Console-  | UpdateIPython-  | Kernel (Maybe String)-  deriving (Eq, Show)--main ::  IO ()-main = do-  stringArgs <- map unpack <$> getArgs-  case process ihaskellArgs stringArgs of-    Left errmsg -> putStrLn $ pack errmsg-    Right args ->-      ihaskell args--universalFlags :: [Flag Args]-universalFlags = [-  flagReq ["extension","e", "X"] (store Extension) "<ghc-extension>" "Extension to enable at start.",-  flagReq ["conf","c"] (store ConfFile) "<file.hs>" "File with commands to execute at start.",-  flagHelpSimple (add Help)-  ]-  where -    add flag (Args mode flags) = Args mode $ flag : flags--store :: (String -> Argument) -> String -> Args -> Either String Args-store constructor str (Args mode prev) = Right $ Args mode $ constructor str : prev--notebook :: Mode Args-notebook = mode "notebook" (Args Notebook []) "Browser-based notebook interface." noArgs $-  flagReq ["serve","s"] (store ServeFrom) "<dir>" "Directory to serve notebooks from.":-  universalFlags--console :: Mode Args-console = mode "console" (Args Console []) "Console-based interactive repl." noArgs universalFlags--kernel = mode "kernel" (Args (Kernel Nothing) []) "Invoke the IHaskell kernel." kernelArg []-  where-    kernelArg = flagArg update "<json-kernel-file>"-    update filename (Args _ flags) = Right $ Args (Kernel $ Just filename) flags--update :: Mode Args-update = mode "update" (Args UpdateIPython []) "Update IPython frontends." noArgs []--ihaskellArgs :: Mode Args-ihaskellArgs = (modeEmpty $ Args None []) { modeGroupModes = toGroup [console, notebook, update, kernel] }--noArgs = flagArg unexpected ""-  where-    unexpected a = error $ "Unexpected argument: " ++ a--ihaskell :: Args -> IO ()--- If no mode is specified, print help text.-ihaskell (Args None _) = -  print $ helpText [] HelpFormatAll ihaskellArgs---- Update IPython: remove then reinstall.--- This is in case cabal updates IHaskell but the corresponding IPython--- isn't updated. This is hard to detect since versions of IPython might--- not change!-ihaskell (Args UpdateIPython _) = do-  removeIPython-  installIPython-  putStrLn "IPython updated."-    -ihaskell (Args Console flags) = showingHelp Console flags $ do-  installed <- ipythonInstalled-  unless installed installIPython--  flags <- addDefaultConfFile flags-  info <- initInfo flags-  runConsole info--ihaskell (Args Notebook flags) = showingHelp Notebook flags $ do-  installed <- ipythonInstalled-  unless installed installIPython--  let server = case mapMaybe serveDir flags of-                 [] -> Nothing-                 xs -> Just $ last xs--  flags <- addDefaultConfFile flags-  info <- initInfo flags-  runNotebook info server-  where-    serveDir (ServeFrom dir) = Just dir-    serveDir _ = Nothing--ihaskell (Args (Kernel (Just filename)) _) = do-  initInfo <- readInitInfo-  runKernel filename initInfo---- | Add a conf file to the arguments if none exists.-addDefaultConfFile :: [Argument] -> IO [Argument]-addDefaultConfFile flags = do-  def <- defaultConfFile-  case (find isConfFile flags, def) of-    (Nothing, Just file) -> return $ ConfFile file : flags-    _ -> return flags-  where-    isConfFile (ConfFile _) = True-    isConfFile _ = False--showingHelp :: IHaskellMode -> [Argument] -> IO () -> IO ()-showingHelp mode flags act =-  case find (==Help) flags of-    Just _ ->-      print $ helpText [] HelpFormatAll $ chooseMode mode-    Nothing ->-      act-  where-    chooseMode Console = console-    chooseMode Notebook = notebook-    chooseMode (Kernel _) = kernel-    chooseMode UpdateIPython = update- --- | Parse initialization information from the flags.-initInfo :: [Argument] -> IO InitInfo-initInfo [] = return InitInfo { extensions = [], initCells = []}-initInfo (flag:flags) = do-  info <- initInfo flags-  case flag of-    Extension ext -> return info { extensions = ext:extensions info }-    ConfFile filename -> do-      cell <- readFile (fpFromText $ pack filename)-      return info { initCells = cell:initCells info }---- | Run the IHaskell language kernel.-runKernel :: String    -- ^ Filename of profile JSON file.-          -> InitInfo  -- ^ Initialization information from the invocation.-          -> IO ()-runKernel profileSrc initInfo = do-  -- Switch to a temporary directory so that any files we create aren't-  -- visible. On Unix, this is usually /tmp.  If there is no temporary-  -- directory available, just stay in the current one and ignore the-  -- raised exception.-  try (getTemporaryDirectory >>= setCurrentDirectory) :: IO (Either SomeException ())--  -- Parse the profile file.-  Just profile <- liftM decode . readFile . fpFromText $ pack profileSrc--  -- Serve on all sockets and ports defined in the profile.-  interface <- serveProfile profile--  state <- initialKernelState--  -- Receive and reply to all messages on the shell socket.-  interpret $ do-    -- Initialize the context by evaluating everything we got from the  -    -- command line flags. This includes enabling some extensions and also-    -- running some code.-    let extLines = map (":extension " ++) $ extensions initInfo-        noPublish _ _ = return ()       -        zero = 0   -- To please hlint-        evaluator line = evaluate zero line noPublish-    mapM_ evaluator extLines-    mapM_ evaluator $ initCells initInfo--    forever $ do-      -- Read the request from the request channel.-      request <- liftIO $ readChan $ shellRequestChannel interface--      -- Create a header for the reply.-      replyHeader <- createReplyHeader (header request)--      -- Create the reply, possibly modifying kernel state.-      oldState <- liftIO $ takeMVar state-      (newState, reply) <- replyTo interface request replyHeader oldState -      liftIO $ putMVar state newState--      -- Write the reply to the reply channel.-      liftIO $ writeChan (shellReplyChannel interface) reply---- Initial kernel state.-initialKernelState :: IO (MVar KernelState)-initialKernelState =-  newMVar KernelState {-    getExecutionCounter = 1-  }---- | Duplicate a message header, giving it a new UUID and message type.-dupHeader :: MessageHeader -> MessageType -> IO MessageHeader-dupHeader header messageType = do-  uuid <- liftIO UUID.random--  return header { messageId = uuid, msgType = messageType }---- | Create a new message header, given a parent message header.-createReplyHeader :: MessageHeader -> Interpreter MessageHeader-createReplyHeader parent = do-  -- Generate a new message UUID.-  newMessageId <- liftIO UUID.random--  return MessageHeader {-    identifiers = identifiers parent,-    parentHeader = Just parent,-    metadata = Map.fromList [],-    messageId = newMessageId,-    sessionId = sessionId parent,-    username = username parent,-    msgType = replyType $ msgType parent-  }---- | Compute a reply to a message. -replyTo :: ZeroMQInterface -> Message -> MessageHeader -> KernelState -> Interpreter (KernelState, Message)---- Reply to kernel info requests with a kernel info reply. No computation--- needs to be done, as a kernel info reply is a static object (all info is--- hard coded into the representation of that message type).-replyTo _ KernelInfoRequest{} replyHeader state = return (state, KernelInfoReply { header = replyHeader })---- Reply to a shutdown request by exiting the main thread.--- Before shutdown, reply to the request to let the frontend know shutdown--- is happening.-replyTo interface ShutdownRequest{restartPending = restartPending} replyHeader _ = liftIO $ do-    writeChan (shellReplyChannel interface) $ ShutdownReply replyHeader restartPending-    exitSuccess---- Reply to an execution request. The reply itself does not require--- computation, but this causes messages to be sent to the IOPub socket--- with the output of the code in the execution request.-replyTo interface ExecuteRequest{ getCode = code } replyHeader state = do-  let execCount = getExecutionCounter state-      -- Convenience function to send a message to the IOPub socket.-      send msg = liftIO $ writeChan (iopubChannel interface) msg--  -- Notify the frontend that the kernel is busy computing.-  -- All the headers are copies of the reply header with a different-  -- message type, because this preserves the session ID, parent header,-  -- and other important information.-  busyHeader <- liftIO $ dupHeader replyHeader StatusMessage-  send $ PublishStatus busyHeader Busy--  -- Construct a function for publishing output as this is going.-  -- This function accepts a boolean indicating whether this is the final-  -- output and the thing to display. Store the final outputs in a list so-  -- that when we receive an updated non-final output, we can clear the-  -- entire output and re-display with the updated output.-  displayed <- liftIO $ newMVar []-  updateNeeded <- liftIO $ newMVar False-  let clearOutput = do-        header <- dupHeader replyHeader ClearOutputMessage-        send $ ClearOutput header True--      sendOutput outs = do-        header <- dupHeader replyHeader DisplayDataMessage-        send $ PublishDisplayData header "haskell" outs--      publish :: Bool -> [DisplayData] -> IO ()-      publish final outputs = do-        -- If necessary, clear all previous output and redraw.-        clear <- readMVar updateNeeded-        when clear $ do-          clearOutput-          disps <- readMVar displayed-          mapM_ sendOutput $ reverse disps--        -- Draw this message.-        sendOutput outputs--        -- If this is the final message, add it to the list of completed-        -- messages. If it isn't, make sure we clear it later by marking-        -- update needed as true.-        modifyMVar_ updateNeeded (const $ return $ not final)-        when final $-          modifyMVar_ displayed (return . (outputs:))--  -- Run code and publish to the frontend as we go.-  evaluate execCount (Chars.unpack code) publish--  -- Notify the frontend that we're done computing.-  idleHeader <- liftIO $ dupHeader replyHeader StatusMessage-  send $ PublishStatus idleHeader Idle--  -- Increment the execution counter in the kernel state.-  let newState = state { getExecutionCounter = execCount + 1 }-  return (newState, ExecuteReply {-    header = replyHeader,-    executionCounter = execCount,-    status = Ok-  })---replyTo _ req@CompleteRequest{} replyHeader state = do-    (matchedText, completions) <- complete (Chars.unpack $ getCodeLine req) (getCursorPos req)--    let reply =  CompleteReply replyHeader (map Chars.pack completions) (Chars.pack matchedText) (getCodeLine req) True-    return (state,  reply)---- | Reply to the object_info_request message. Given an object name, return--- | the associated type calculated by GHC.-replyTo _ ObjectInfoRequest{objectName=oname} replyHeader state = do-         docs <- info $ Chars.unpack oname-         let reply = ObjectInfoReply {-                        header = replyHeader,-                        objectName = oname, -                        objectFound = docs == "",-                        objectTypeString = Chars.pack docs,-                        objectDocString  = Chars.pack docs                    -                      }-         return (state, reply)--- 
ihaskell.cabal view
@@ -7,7 +7,7 @@ -- PVP summary:      +-+------- breaking API changes --                   | | +----- non-breaking API additions --                   | | | +--- code changes with no API change-version:             0.2.0.5+version:             0.3.0.0  -- A short (one-line) description of the package. synopsis:            A Haskell backend kernel for the IPython project.@@ -40,130 +40,171 @@ build-type:          Simple  -- Constraint on the version of Cabal needed to build this package.-cabal-version:       >=1.8+cabal-version:       >=1.16  data-files:    profile/profile.tar  library-  build-depends:       base ==4.6.*,-                       cmdargs >= 0.10,-                       tar,-                       ghc-parser,-                       unix >= 2.6,-                       hspec,-                       zeromq3-haskell >=0.5,-                       aeson >=0.6,-                       MissingH >=1.2,-                       classy-prelude >=0.6,-                       bytestring >=0.10,-                       uuid >=1.2.6,-                       containers >=0.5,-                       ghc ==7.6.*,-                       ghc-paths ==0.1.*,-                       random >=1.0,-                       split >= 0.2,-                       utf8-string,-                       strict >=0.3,-                       shelly >=1.3,-                       system-argv0,-                       directory,-                       here,-                       system-filepath,-                       cereal ==0.3.*,-                       text >=0.11,-                       mtl >= 2.1,-                       template-haskell-  exposed-modules: IHaskell.Display,-                   Paths_ihaskell,-                   IHaskell.Types,-                   IHaskell.Message.UUID+  hs-source-dirs:      src+  default-language:    Haskell2010+  build-depends:       +                       base                 ==4.6.*,+                       aeson                >=0.6,+                       base64-bytestring    >=1.0,+                       bytestring           >=0.10,+                       cereal               ==0.3.*,+                       classy-prelude       >=0.7,+                       cmdargs              >=0.10,+                       containers           >=0.5,+                       directory            -any,+                       filepath             -any,+                       ghc                  ==7.6.*,+                       ghc-parser           -any,+                       ghc-paths            ==0.1.*,+                       haskeline            -any,+                       here                 -any,+                       hlint                -any,+                       hspec                -any,+                       HTTP                 -any,+                       HUnit                -any,+                       ipython-kernel       -any,+                       MissingH             >=1.2,+                       mtl                  >=2.1,+                       parsec               -any,+                       process              >=1.1,+                       random               >=1.0,+                       shelly               >=1.3,+                       split                >= 0.2,+                       strict               >=0.3,+                       system-argv0         -any,+                       system-filepath      -any,+                       tar                  -any,+                       transformers         -any,+                       unix                 >= 2.6,+                       utf8-string          -any +  exposed-modules: IHaskell.Display+                   IHaskell.Eval.Completion+                   IHaskell.Eval.Evaluate+                   IHaskell.Eval.Info+                   IHaskell.Eval.Lint+                   IHaskell.Eval.Parser+                   IHaskell.Eval.Hoogle+                   IHaskell.Eval.ParseShell+                   IHaskell.Eval.Util+                   IHaskell.IPython+                   IHaskell.Flags+                   IHaskell.Types+                   Paths_ihaskell+ executable IHaskell   -- .hs or .lhs file containing the Main module.+  hs-source-dirs:      src   main-is:             Main.hs    build-tools:         happy, cpphs      -- Modules included in this executable, other than Main.   other-modules:       +             IHaskell.Eval.Lint              IHaskell.Eval.Completion              IHaskell.Eval.Info              IHaskell.Eval.Evaluate              IHaskell.Eval.Parser+             IHaskell.Eval.Hoogle+             IHaskell.Eval.ParseShell+             IHaskell.Eval.Util              IHaskell.IPython-             IHaskell.Message.Parser-             IHaskell.Message.UUID-             IHaskell.Message.Writer+             IHaskell.Flags              IHaskell.Types-             IHaskell.ZeroMQ              IHaskell.Display-             IHaskell.Config -  extensions: DoAndIfThenElse+  default-extensions: DoAndIfThenElse      -- Other library packages from which modules are imported.-  build-depends:       base ==4.6.*,-                       cmdargs >= 0.10,-                       tar,-                       ghc-parser,-                       unix >= 2.6,-                       hspec,-                       zeromq3-haskell >=0.5,-                       aeson >=0.6,-                       MissingH >=1.2,-                       classy-prelude >=0.6,-                       bytestring >=0.10,-                       uuid >=1.2.6,-                       containers >=0.5,-                       ghc ==7.6.*,-                       ghc-paths ==0.1.*,-                       random >=1.0,-                       split >= 0.2,-                       utf8-string,-                       strict >=0.3,-                       shelly >=1.3,-                       system-argv0,-                       directory,-                       here,-                       system-filepath,-                       cereal ==0.3.*,-                       text >=0.11,-                       mtl >= 2.1,-                       template-haskell+  default-language:    Haskell2010+  build-depends:       +                       base                 ==4.6.*,+                       aeson                >=0.6,+                       base64-bytestring    >=1.0,+                       bytestring           >=0.10,+                       cereal               ==0.3.*,+                       classy-prelude       >=0.7,+                       cmdargs              >=0.10,+                       containers           >=0.5,+                       directory            -any,+                       filepath             -any,+                       ghc                  ==7.6.*,+                       ghc-parser           -any,+                       ghc-paths            ==0.1.*,+                       haskeline            -any,+                       here                 -any,+                       hlint                -any,+                       hspec                -any,+                       HTTP                 -any,+                       HUnit                -any,+                       ipython-kernel       -any,+                       MissingH             >=1.2,+                       mtl                  >=2.1,+                       parsec               -any,+                       process              >=1.1,+                       random               >=1.0,+                       shelly               >=1.3,+                       split                >= 0.2,+                       strict               >=0.3,+                       system-argv0         -any,+                       system-filepath      -any,+                       tar                  -any,+                       transformers         -any,+                       unix                 >= 2.6,+                       utf8-string          -any  Test-Suite hspec+  hs-source-dirs:      src   Type:     exitcode-stdio-1.0   Ghc-Options: -threaded   Main-Is: Hspec.hs-  build-depends:       base ==4.6.*,-                       cmdargs >= 0.10,-                       tar,-                       ghc-parser,-                       unix >= 2.6,-                       hspec,-                       zeromq3-haskell >=0.5,-                       aeson >=0.6,-                       MissingH >=1.2,-                       classy-prelude >=0.6,-                       bytestring >=0.10,-                       uuid >=1.2.6,-                       containers >=0.5,-                       ghc ==7.6.*,-                       ghc-paths ==0.1.*,-                       random >=1.0,-                       split >= 0.2,-                       utf8-string,-                       strict >=0.3,-                       shelly >=1.3,-                       system-argv0,-                       directory,-                       here,-                       system-filepath,-                       cereal ==0.3.*,-                       text >=0.11,-                       mtl >= 2.1,-                       template-haskell+  default-language:    Haskell2010+  build-depends:       +                       base                 ==4.6.*,+                       aeson                >=0.6,+                       base64-bytestring    >=1.0,+                       bytestring           >=0.10,+                       cereal               ==0.3.*,+                       classy-prelude       >=0.7,+                       cmdargs              >=0.10,+                       containers           >=0.5,+                       directory            -any,+                       filepath             -any,+                       ghc                  ==7.6.*,+                       ghc-parser           -any,+                       ghc-paths            ==0.1.*,+                       haskeline            -any,+                       here                 -any,+                       hlint                -any,+                       hspec                -any,+                       HTTP                 -any,+                       HUnit                -any,+                       ipython-kernel       -any,+                       MissingH             >=1.2,+                       mtl                  >=2.1,+                       parsec               -any,+                       process              >=1.1,+                       random               >=1.0,+                       shelly               >=1.3,+                       split                >= 0.2,+                       strict               >=0.3,+                       system-argv0         -any,+                       system-filepath      -any,+                       tar                  -any,+                       transformers         -any,+                       unix                 >= 2.6,+                       utf8-string          -any++  extensions: DoAndIfThenElse+              OverloadedStrings+              ExtendedDefaultRules  source-repository head   type:     git
profile/profile.tar view

binary file changed (83968 → 96768 bytes)

+ src/Hspec.hs view
@@ -0,0 +1,570 @@+{-# LANGUAGE QuasiQuotes, OverloadedStrings, ExtendedDefaultRules #-}+-- Keep all the language pragmas here so it can be compiled separately.+module Main where+import Prelude+import GHC+import GHC.Paths+import Data.IORef+import Control.Monad+import Control.Monad.Trans ( MonadIO, liftIO )+import Data.List+import System.Directory+import Shelly (Sh, shelly, cmd, (</>), toTextIgnore, cd, withTmpDir, mkdir_p,+  touchfile)+import qualified Shelly+import Filesystem.Path.CurrentOS (encodeString)+import System.SetEnv (setEnv)+import Data.String.Here+import Data.String.Utils (strip, replace)+import Data.Monoid++import IHaskell.Eval.Parser+import IHaskell.Types+import IHaskell.IPython+import IHaskell.Eval.Evaluate as Eval hiding (liftIO)+import qualified IHaskell.Eval.Evaluate as Eval (liftIO)++import IHaskell.Eval.Completion+import IHaskell.Eval.ParseShell++import Debug.Trace++import Test.Hspec+import Test.Hspec.HUnit+import Test.HUnit (assertBool, assertFailure)++doGhc = runGhc (Just libdir)++parses str = do+  res <- doGhc $ parseString str+  return $ map unloc res++like parser desired = parser >>= (`shouldBe` desired)++is string blockType = do+  result <- doGhc $ parseString string+  map unloc result `shouldBe` [blockType $ strip string]++eval string = do+  outputAccum <- newIORef []+  pagerAccum <- newIORef []+  let publish evalResult = case evalResult of+        IntermediateResult {} -> return ()+        FinalResult outs page -> do+          modifyIORef outputAccum (outs :)+          modifyIORef pagerAccum (page :)++  getTemporaryDirectory >>= setCurrentDirectory+  let state = defaultKernelState { getLintStatus = LintOff }+  interpret False $ Eval.evaluate state string publish+  out <- readIORef outputAccum+  pagerOut <- readIORef pagerAccum+  return (reverse out, unlines $ reverse pagerOut)++evaluationComparing comparison string = do+    let indent (' ':x) = 1 + indent x+        indent _ = 0+        empty = null . strip+        stringLines = filter (not . empty) $ lines string+        minIndent = minimum (map indent stringLines)+        newString = unlines $ map (drop minIndent) stringLines+    eval newString >>= comparison++becomes string expected = evaluationComparing comparison string+  where+    comparison :: ([Display], String) -> IO ()+    comparison (results, pageOut) = do+      when (length results /= length expected) $+        expectationFailure $ "Expected result to have " ++ show (length expected)+                             ++ " results. Got " ++ show results++      forM_ (zip results expected) $ \(Display result, expected) ->+        case extractPlain result  of+          "" -> expectationFailure $ "No plain-text output in " ++ show result ++ "\nExpected: " ++ expected+          str -> str `shouldBe` expected++pages string expected = evaluationComparing comparison string+  where+    comparison (results, pageOut) =+      strip pageOut `shouldBe` strip (unlines expected)++readCompletePrompt :: String -> (String, Int)+-- | @readCompletePrompt "xs*ys"@ return @(xs, i)@ where i is the location of+-- @'*'@ in the input string. +readCompletePrompt string = case elemIndex '*' string of+          Nothing -> error "Expected cursor written as '*'."+          Just idx -> (replace "*" "" string, idx)++completes string expected = completionTarget newString cursorloc `shouldBe` expected+  where (newString, cursorloc) = readCompletePrompt string++completionEvent :: String -> Interpreter (String, [String])+completionEvent string  =  do+      complete newString cursorloc+  where (newString, cursorloc) = case elemIndex '*' string of+          Nothing -> error "Expected cursor written as '*'."+          Just idx -> (replace "*" "" string, idx)      ++completionEventInDirectory :: String -> IO (String, [String])+completionEventInDirectory string  +  = withHsDirectory $ const $ completionEvent string +                          ++shouldHaveCompletionsInDirectory :: String -> [String] -> IO ()+shouldHaveCompletionsInDirectory string expected +  = do (matched, completions) <- completionEventInDirectory string  +       let existsInCompletion = (`elem` completions)+           unmatched = filter (not . existsInCompletion) expected+       expected `shouldBeAmong` completions++completionHas string expected +  = do (matched, completions) <- doGhc $ do initCompleter +                                            completionEvent string  +       let existsInCompletion = (`elem` completions)+           unmatched = filter (not . existsInCompletion) expected+       expected `shouldBeAmong` completions++initCompleter :: Interpreter ()+initCompleter  = do+  flags <- getSessionDynFlags+  setSessionDynFlags $ flags { hscTarget = HscInterpreted, ghcLink = LinkInMemory }++  -- Import modules.+  imports <- mapM parseImportDecl ["import Prelude",+                                   "import qualified Control.Monad",+                                   "import qualified Data.List as List",+                                   "import Data.Maybe as Maybe"]+  setContext $ map IIDecl imports++inDirectory :: [Shelly.FilePath] -- ^ directories relative to temporary directory+              -> [Shelly.FilePath] -- ^ files relative to temporary directory+              -> (Shelly.FilePath -> Interpreter a)+              -> IO a+-- | Run an Interpreter action, but first make a temporary directory +--   with some files and folder and cd to it.+inDirectory dirs files action = shelly $ withTmpDir $ \dirPath ->+      do cd dirPath+         mapM_ mkdir_p   dirs+         mapM_ touchfile files+         liftIO $ doGhc $ wrap (encodeString dirPath) (action dirPath)+      where noPublish = const $ return ()+            cdEvent path = Eval.evaluate defaultKernelState (":! cd " ++ path) noPublish+            wrap :: FilePath -> Interpreter a -> Interpreter a+            wrap path action = +              do initCompleter+                 pwd <- Eval.liftIO getCurrentDirectory+                 cdEvent path   -- change to the temporary directory+                 out <- action  -- run action+                 cdEvent pwd    -- change back to the original directory+                 return out++withHsDirectory :: (Shelly.FilePath -> Interpreter a)  -> IO a+withHsDirectory = inDirectory ["" </> "dir", "dir" </> "dir1"]+                    [""</> "file1.hs", "dir" </> "file2.hs",  +                     "" </> "file1.lhs", "dir" </> "file2.lhs"]++main :: IO ()+main = hspec $ do+  parserTests+  evalTests+  completionTests++completionTests = do+  parseShellTests+  describe "Completion" $ do+    it "correctly gets the completion identifier without dots" $ do+       "hello*"              `completes` ["hello"]+       "hello aa*bb goodbye" `completes` ["aa"]+       "hello aabb* goodbye" `completes` ["aabb"]+       "aacc* goodbye"       `completes` ["aacc"]+       "hello *aabb goodbye" `completes` []+       "*aabb goodbye"       `completes` []++    it "correctly gets the completion identifier with dots" $ do+       "hello test.aa*bb goodbye" `completes` ["test", "aa"]+       "Test.*"                   `completes` ["Test", ""]+       "Test.Thing*"              `completes` ["Test", "Thing"]+       "Test.Thing.*"             `completes` ["Test", "Thing", ""]+       "Test.Thing.*nope"         `completes` ["Test", "Thing", ""]++    it "correctly gets the completion type" $ do+      completionType "import Data." 12 ["Data", ""]      `shouldBe` ModuleName "Data" ""+      completionType "import Prel" 11 ["Prel"]           `shouldBe` ModuleName "" "Prel"+      completionType "import D.B.M" 12 ["D", "B", "M"]   `shouldBe` ModuleName "D.B" "M"+      completionType " import A." 10 ["A", ""]           `shouldBe` ModuleName "A" ""+      completionType "import a.x" 10 ["a", "x"]          `shouldBe` Identifier "x"+      completionType "A.x" 3 ["A", "x"]                 `shouldBe` Qualified "A" "x"+      completionType "a.x" 3 ["a", "x"]                 `shouldBe` Identifier "x"+      completionType "pri" 3 ["pri"]                    `shouldBe` Identifier "pri"+      completionType ":load A" 7 ["A"]                   `shouldBe` HsFilePath ":load A"+                                                                               "A"+      completionType ":! cd " 6 [""]                     `shouldBe` FilePath   ":! cd " ""++++    it "properly completes identifiers" $ do+       "pri*"           `completionHas` ["print"]+       "ma*"            `completionHas` ["map"]+       "hello ma*"      `completionHas` ["map"]+       "print $ catMa*" `completionHas` ["catMaybes"]++    it "properly completes qualified identifiers" $ do+       "Control.Monad.liftM*"    `completionHas` [ "Control.Monad.liftM"+                                                 , "Control.Monad.liftM2"+                                                 , "Control.Monad.liftM5"]+       "print $ List.intercal*"  `completionHas` ["List.intercalate"]+       "print $ Data.Maybe.cat*" `completionHas` ["Data.Maybe.catMaybes"]+       "print $ Maybe.catM*"     `completionHas` ["Maybe.catMaybes"]++    it "properly completes imports" $ do+      "import Data.*"  `completionHas` ["Data.Maybe", "Data.List"]+      "import Data.M*" `completionHas` ["Data.Maybe"]+      "import Prel*"   `completionHas` ["Prelude"]++    it "properly completes haskell file paths on :load directive" $+         let loading xs = ":load " ++ encodeString xs+             paths xs = map encodeString xs+         in do +            loading ("dir" </> "file*") `shouldHaveCompletionsInDirectory` paths ["dir" </> "file2.hs",+                                                                                  "dir" </> "file2.lhs"]+            loading ("" </> "file1*") `shouldHaveCompletionsInDirectory` paths ["" </> "file1.hs",+                                                                                            "" </> "file1.lhs"]+            loading ("" </> "file1*") `shouldHaveCompletionsInDirectory` paths ["" </> "file1.hs",+                                                                                "" </> "file1.lhs"]+            loading ("" </> "./*") `shouldHaveCompletionsInDirectory` paths ["./" </> "dir/"+                                                                                       , "./" </> "file1.hs"+                                                                                       , "./" </> "file1.lhs"]+            loading ("" </> "./*") `shouldHaveCompletionsInDirectory` paths ["./" </> "dir/"+                                                                           , "./" </> "file1.hs"+                                                                           , "./" </> "file1.lhs"]++    it "provides path completions on empty shell cmds " $ do +      ":! cd *" `shouldHaveCompletionsInDirectory` (map encodeString ["" </> "dir/"+                                                                      , "" </> "file1.hs"+                                                                      , "" </> "file1.lhs"])+++    it "correctly interprets ~ as the environment HOME variable" $ +        let shouldHaveCompletions :: String -> [String] -> IO ()+            shouldHaveCompletions string expected = do +              (matched, completions) +                    <- withHsDirectory $ \dirPath -> +                           do setHomeEvent dirPath+                              completionEvent string +              let existsInCompletion = (`elem` completions)+                  unmatched = filter (not . existsInCompletion) expected     +              expected `shouldBeAmong` completions++            setHomeEvent path = liftIO $ setEnv "HOME" (encodeString path)+        in do +          ":! cd ~/*" `shouldHaveCompletions` ["~/dir/"]+          ":! ~/*" `shouldHaveCompletions` ["~/dir/"]+          ":load ~/*" `shouldHaveCompletions` ["~/dir/"]+          ":l ~/*" `shouldHaveCompletions` ["~/dir/"]++    let shouldHaveMatchingText :: String -> String -> IO ()+        shouldHaveMatchingText string expected = do +          matchText +                <- withHsDirectory $ \dirPath -> +                       do setHomeEvent dirPath+                          (matchText, _) <- uncurry complete (readCompletePrompt string)+                          return matchText+          matchText `shouldBe` expected++        setHomeEvent path = liftIO $ setEnv "HOME" (encodeString path)++    it "generates the correct matchingText on `:! cd ~/*` " $ +      do ":! cd ~/*" `shouldHaveMatchingText` ("~/" :: String)++    it "generates the correct matchingText on `:load ~/*` " $ +      do ":load ~/*" `shouldHaveMatchingText` ("~/" :: String)++    it "generates the correct matchingText on `:l ~/*` " $ +      do ":l ~/*" `shouldHaveMatchingText` ("~/" :: String)++evalTests = do+  describe "Code Evaluation" $ do+    it "evaluates expressions" $  do+      "3" `becomes` ["3"]+      "3+5" `becomes` ["8"]+      "print 3" `becomes` ["3"]+      [hereLit|+        let x = 11+            z = 10 in+          x+z+      |] `becomes` ["21"]++    it "evaluates multiline expressions" $  do+      [hereLit|+        import Control.Monad+        forM_ [1, 2, 3] $ \x ->+          print x+      |] `becomes` ["1\n2\n3"]++    it "evaluates function declarations silently" $ do+      [hereLit|+        fun :: [Int] -> Int+        fun [] = 3+        fun (x:xs) = 10+        fun [1, 2]+      |] `becomes` ["10"]++    it "evaluates data declarations" $ do+      [hereLit|+        data X = Y Int+               | Z String+               deriving (Show, Eq)+        print [Y 3, Z "No"]+        print (Y 3 == Z "No")+      |] `becomes` ["[Y 3,Z \"No\"]", "False"]++    it "evaluates do blocks in expressions" $ do+      [hereLit|+        show (show (do+            Just 10+            Nothing+            Just 100))+      |] `becomes` ["\"\\\"Nothing\\\"\""]++    it "is silent for imports" $ do+      "import Control.Monad" `becomes` []+      "import qualified Control.Monad" `becomes` []+      "import qualified Control.Monad as CM" `becomes` []+      "import Control.Monad (when)" `becomes` []++    it "evaluates directives" $ do+      ":typ 3" `becomes` ["forall a. Num a => a"]+      ":in String" `pages` ["type String = [Char] \t-- Defined in `GHC.Base'"]++parserTests = do+  layoutChunkerTests+  moduleNameTests+  parseStringTests++layoutChunkerTests = describe "Layout Chunk" $ do+  it "chunks 'a string'" $+    map unloc (layoutChunks "a string") `shouldBe` ["a string"]++  it "chunks 'a\\n string'" $+    map unloc (layoutChunks "a\n string") `shouldBe` ["a\n string"]++  it "chunks 'a\\n string\\nextra'" $+    map unloc (layoutChunks "a\n string\nextra") `shouldBe` ["a\n string","extra"]++  it "chunks strings with too many lines" $+    map unloc (layoutChunks "a\n\nstring") `shouldBe` ["a","string"]++  it "parses multiple exprs" $ do+    let text = [hereLit|+                 first++                 second+                 third++                 fourth+               |]+    layoutChunks text `shouldBe`+      [Located 2 "first",+       Located 4 "second",+       Located 5 "third",+       Located 7 "fourth"]++moduleNameTests = describe "Get Module Name" $ do+  it "parses simple module names" $+    "module A where\nx = 3" `named` ["A"]+  it "parses module names with dots" $+    "module A.B where\nx = 3" `named` ["A", "B"]+  it "parses module names with exports" $+    "module A.B.C ( x ) where x = 3" `named` ["A", "B", "C"]+  it "errors when given unnamed modules" $ do+    doGhc (getModuleName "x = 3") `shouldThrow` anyException+  where+    named str result = do+      res <- doGhc $ getModuleName str+      res `shouldBe` result++parseStringTests = describe "Parser" $ do+  it "parses empty strings" $+    parses "" `like` []++  it "parses simple imports" $+    "import Data.Monoid" `is` Import++  it "parses simple arithmetic" $+    "3 + 5" `is` Expression++  it "parses :type" $+    parses ":type x\n:ty x" `like` [+      Directive GetType "x",+      Directive GetType "x"+    ]++  it "parses :info" $+    parses ":info x\n:in x" `like` [+      Directive GetInfo "x",+      Directive GetInfo "x"+    ]++  it "parses :help and :?" $+    parses ":? x\n:help x" `like` [+      Directive GetHelp "x",+      Directive GetHelp "x"+    ]++  it "parses :set x" $+    parses ":set x" `like` [+      Directive SetOpt "x"+    ]++  it "parses :extension x" $+    parses ":ex x\n:extension x" `like` [+      Directive SetExtension "x",+      Directive SetExtension "x"+    ]++  it "fails to parse :nope" $+    parses ":nope goodbye" `like` [+      ParseError (Loc 1 1) "Unknown directive: 'nope'."+    ]++  it "parses number followed by let stmt" $+    parses "3\nlet x = expr"  `like` [+      Expression "3",+      Statement "let x = expr"+    ]++  it "parses let x in y" $+    "let x = 3 in x + 3" `is` Expression++  it "parses a data declaration" $+    "data X = Y Int" `is` Declaration++  it "parses number followed by type directive" $+    parses "3\n:t expr" `like` [+      Expression "3",+      Directive GetType "expr"+    ]++  it "parses a <- statement" $+    "y <- print 'no'" `is` Statement++  it "parses a <- stmt followed by let stmt" $+    parses "y <- do print 'no'\nlet x = expr" `like` [+      Statement "y <- do print 'no'",+      Statement "let x = expr"+    ]++  it "parses <- followed by let followed by expr" $+    parses "y <- do print 'no'\nlet x = expr\nexpression" `like` [+      Statement "y <- do print 'no'",+      Statement "let x = expr",+      Expression "expression"+    ]++  it "parses two print statements" $+    parses "print yes\nprint no" `like` [+      Expression "print yes",+      Expression "print no"+    ]++  it "parses a pattern-maching function declaration" $+    "fun [] = 10" `is` Declaration++  it "parses a function decl followed by an expression" $+    parses "fun [] = 10\nprint 'h'" `like` [+      Declaration "fun [] = 10",+      Expression "print 'h'"+    ]++  it "parses list pattern matching fun decl" $+    "fun (x : xs) = 100" `is` Declaration++  it "parses two pattern matches as the same declaration" $+    "fun [] = 10\nfun (x : xs) = 100" `is` Declaration++  it "parses a type signature followed by a declaration" $+    "fun :: [a] -> Int\nfun [] = 10\nfun (x : xs) = 100" `is` Declaration++  it "parases a simple module" $+    "module A where x = 3" `is` Module++  it "parses a module with an export" $+    "module B (x) where x = 3" `is` Module++  it "breaks when a let is incomplete" $+    parses "let x = 3 in" `like` [+      ParseError (Loc 1 13) "parse error (possibly incorrect indentation or mismatched brackets)"+    ]++  it "breaks without data kinds" $+    parses "data X = 3" `like` [+      ParseError (Loc 1 10) "Illegal literal in type (use -XDataKinds to enable): 3"+    ]++  it "parses statements after imports" $ do+    parses "import X\nprint 3" `like` [+        Import "import X",+        Expression "print 3"+      ]+    parses "import X\n\nprint 3" `like` [+        Import "import X",+        Expression "print 3"+      ]+  it "ignores blank lines properly" $+    [hereLit|+      test arg = hello+        where+          x = y++          z = w+    |] `is` Declaration+  it "doesn't break on long strings" $ do+    let longString = concat $ replicate 20 "hello "+    ("img ! src \"" ++ longString ++ "\" ! width \"500\"") `is` Expression++  it "parses do blocks in expression" $ do+    [hereLit|+      show (show (do+        Just 10+        Nothing+        Just 100))+    |] `is` Expression+  it "correctly locates parsed items" $ do+    let go = doGhc . parseString+    go [hereLit|+        first++        second+       |] >>= (`shouldBe` [Located 2 (Expression "first"),+                          Located 4 (Expression "second")])+++parseShellTests = +  describe "Parsing Shell Commands" $ do+    test "A" ["A"]+    test ":load A" [":load", "A"]+    test ":!l ~/Downloads/MyFile\\ Has\\ Spaces.txt" +                  [":!l", "~/Downloads/MyFile\\ Has\\ Spaces.txt"]+    test ":!l \"~/Downloads/MyFile Has Spaces.txt\" /Another/File\\ WithSpaces.doc" +                  [":!l", "~/Downloads/MyFile Has Spaces.txt", "/Another/File\\ WithSpaces.doc" ]+  where+    test string expected = +      it ("parses " ++ string ++ " correctly") $+            string `shouldParseTo` expected++    shouldParseTo xs ys = fun ys (parseShell xs)+      where fun ys (Right xs')  = xs' `shouldBe` ys+            fun ys (Left e)     = assertFailure $ "parseShell returned error: \n" ++ show e+++-- Useful HSpec expectations ----+---------------------------------++shouldBeAmong :: (Show a, Eq a) => [a] -> [a] -> Expectation+-- |+-- @sublist \`shouldbeAmong\` list@ sets the expectation that @sublist@ elements are +-- among those in @list@.+sublist `shouldBeAmong` list = assertBool errorMsg +              $ and [x `elem` list | x <- sublist]+  where+    errorMsg = show list ++ " doesn't contain " ++ show sublist
+ src/IHaskell/Display.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, FlexibleInstances #-}+module IHaskell.Display (+  IHaskellDisplay(..),+  plain, html, png, jpg, svg, latex,+  serializeDisplay,+  Width, Height, Base64,+  encode64, base64,+  Display(..),+  DisplayData(..),+  ) where++import ClassyPrelude+import Data.Serialize as Serialize+import Data.ByteString hiding (map)+import Data.String.Utils (rstrip) +import qualified Data.ByteString.Base64 as Base64+import qualified Data.ByteString.Char8 as Char++import IHaskell.Types++type Base64 = ByteString++-- | A class for displayable Haskell types.+--+-- IHaskell's displaying of results behaves as if these two+-- overlapping/undecidable instances also existed:+-- +-- > instance (Show a) => IHaskellDisplay a+-- > instance Show a where shows _ = id+class IHaskellDisplay a where+  display :: a -> IO Display++-- | these instances cause the image, html etc. which look like:+--+-- > Display+-- > [Display]+-- > IO [Display]+-- > IO (IO Display)+--+-- be run the IO and get rendered (if the frontend allows it) in the pretty+-- form.+instance IHaskellDisplay a => IHaskellDisplay (IO a) where+  display = (display =<<)++instance IHaskellDisplay Display where+  display = return++instance IHaskellDisplay DisplayData where+  display disp = return $ Display [disp]++instance IHaskellDisplay a => IHaskellDisplay [a] where+  display disps = do+    displays <- mapM display disps+    return $ ManyDisplay displays++-- | Encode many displays into a single one. All will be output.+many :: [Display] -> Display+many = ManyDisplay++-- | Generate a plain text display.+plain :: String -> DisplayData+plain = DisplayData PlainText . Char.pack . rstrip++-- | Generate an HTML display.+html :: String -> DisplayData+html = DisplayData MimeHtml . Char.pack++-- | Genreate an SVG display.+svg :: String -> DisplayData+svg = DisplayData MimeSvg . Char.pack++-- | Genreate a LaTeX display.+latex :: String -> DisplayData+latex = DisplayData MimeLatex . Char.pack++-- | Generate a PNG display of the given width and height. Data must be+-- provided in a Base64 encoded manner, suitable for embedding into HTML.+-- The @base64@ function may be used to encode data into this format.+png :: Width -> Height -> Base64 -> DisplayData+png width height = DisplayData (MimePng width height)++-- | Generate a JPG display of the given width and height. Data must be+-- provided in a Base64 encoded manner, suitable for embedding into HTML.+-- The @base64@ function may be used to encode data into this format.+jpg :: Width -> Height -> Base64 -> DisplayData+jpg width height = DisplayData (MimeJpg width height)++-- | Convert from a string into base 64 encoded data.+encode64 :: String -> Base64+encode64 str = base64 $ Char.pack str++-- | Convert from a ByteString into base 64 encoded data.+base64 :: ByteString -> Base64+base64 = Base64.encode++-- | For internal use within IHaskell.+-- Serialize displays to a ByteString.+serializeDisplay :: Display -> ByteString+serializeDisplay = Serialize.encode
+ src/IHaskell/Eval/Completion.hs view
@@ -0,0 +1,272 @@+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, DoAndIfThenElse #-}+{- |+Description : Generates tab completion options.++This has a limited amount of context sensitivity. It distinguishes between four contexts at the moment:+- import statements (completed using modules)+- identifiers (completed using in scope values)+- extensions via :ext (completed using GHC extensions)+- qualified identifiers (completed using in-scope values)++-}+module IHaskell.Eval.Completion (complete, completionTarget, completionType, CompletionType(..)) where++import ClassyPrelude hiding (liftIO)+--import Prelude++import Control.Applicative ((<$>))+import Data.ByteString.UTF8 hiding (drop, take)+import Data.Char+import Data.List (nub, init, last, head, elemIndex)+import Data.List.Split+import Data.List.Split.Internals+import Data.Maybe+import Data.String.Utils (strip, startswith, endswith, replace)+import qualified Data.String.Utils as StringUtils+import System.Environment (getEnv)++import GHC+import DynFlags+import GhcMonad+import PackageConfig+import Outputable (showPpr)+++import System.Directory+import System.FilePath+import MonadUtils (MonadIO)++import System.Console.Haskeline.Completion++import IHaskell.Types+import IHaskell.Eval.Evaluate (Interpreter)+import IHaskell.Eval.ParseShell (parseShell)+++data CompletionType+     = Empty+     | Identifier String+     | DynFlag String+     | Qualified String String+     | ModuleName String String+     | HsFilePath String String+     | FilePath   String String +     | KernelOption String+     deriving (Show, Eq)++complete :: String -> Int -> Interpreter (String, [String])+complete line pos = do+  flags <- getSessionDynFlags+  rdrNames <- map (showPpr flags) <$> getRdrNamesInScope+  scopeNames <- nub <$> map (showPpr flags) <$> getNamesInScope+  let isQualified = ('.' `elem`)+      unqualNames = nub $ filter (not . isQualified) rdrNames+      qualNames = nub $ scopeNames ++ filter isQualified rdrNames++  let Just db = pkgDatabase flags+      getNames = map moduleNameString . exposedModules+      moduleNames = nub $ concatMap getNames db++  let target = completionTarget line pos++  let matchedText = case completionType line pos target of +        HsFilePath _ match ->  match+        FilePath   _ match ->  match+        otherwise       -> intercalate "." target++  options <-+        case completionType line pos target of+          Empty -> return []++          Identifier candidate ->+            return $ filter (candidate `isPrefixOf`) unqualNames++          Qualified moduleName candidate -> do+            trueName <- getTrueModuleName moduleName+            let prefix = intercalate "." [trueName, candidate]+                completions = filter (prefix `isPrefixOf`)  qualNames+                falsifyName = replace trueName moduleName+            return $ map falsifyName completions++          ModuleName previous candidate -> do+            let prefix = if null previous+                         then candidate+                         else intercalate "." [previous, candidate]+            return $ filter (prefix `isPrefixOf`) moduleNames++          DynFlag ext -> do+            let extName (name, _, _) = name+                otherNames = ["-package","-Wall","-w"] +++                            concatMap getSetName kernelOpts+                fNames = map ("-f"++) (names ++ nonames)+                    where+                    -- possibly leave out the fLangFlags? The+                    -- -XUndecidableInstances vs. obsolete+                    -- -fallow-undecidable-instances+                    names = map extName fFlags ++ +                            map extName fWarningFlags ++ +                            map extName fLangFlags+                    nonames = map ("no"++) names++                xNames = map ("-X"++) (names ++ nonames)+                    where+                    names = map extName xFlags+                    nonames = map ("No" ++) names+            return $ filter (ext `isPrefixOf`) $ fNames ++ xNames ++ otherNames++          HsFilePath lineUpToCursor match -> completePathWithExtensions [".hs", ".lhs"] lineUpToCursor++          FilePath   lineUpToCursor match  -> completePath lineUpToCursor++          KernelOption str -> return $+                    filter (str `isPrefixOf`) (concatMap getOptionName kernelOpts)++  return (matchedText, options)++getTrueModuleName :: String -> Interpreter String+getTrueModuleName name = do+  -- Only use the things that were actually imported+  let onlyImportDecl (IIDecl decl) = Just decl+      onlyImportDecl _ = Nothing++  -- Get all imports that we use.+  imports <- catMaybes <$> map onlyImportDecl <$> getContext++  -- Find the ones that have a qualified name attached.+  -- If this name isn't one of them, it already is the true name.+  flags <- getSessionDynFlags+  let qualifiedImports = filter (isJust . ideclAs) imports+      hasName imp = name == (showPpr flags . fromJust . ideclAs) imp+  case find hasName qualifiedImports of+    Nothing -> return name+    Just trueImp -> return $ showPpr flags $ unLoc $ ideclName trueImp++-- | Get which type of completion this is from the surrounding context.+completionType :: String            -- ^ The line on which the completion is being done.+              -> Int                -- ^ Location of the cursor in the line.+               -> [String]          -- ^ The identifier being completed (pieces separated by dots).+               -> CompletionType+completionType line loc target+  -- File and directory completions are special+  | startswith ":!" stripped+    = case parseShell lineUpToCursor of +          Right xs -> FilePath lineUpToCursor $ if endswith (last xs) lineUpToCursor then (last xs) else []+          Left  _  -> Empty+  | startswith ":l" stripped+    = case parseShell lineUpToCursor of +          Right xs -> HsFilePath lineUpToCursor $ if endswith (last xs) lineUpToCursor then (last xs) else []+          Left  _  -> Empty+  | startswith ":s" stripped+    = DynFlag candidate+  | startswith ":o" stripped+    = KernelOption candidate+  -- Use target for other completions.+  -- If it's empty, no completion.+  | null target+    = Empty+  | startswith "import" stripped && isModName+    = ModuleName dotted candidate+  | isModName && (not . null . init) target+    = Qualified dotted candidate+  | otherwise+    = Identifier candidate+  where stripped = strip line+        dotted = dots target+        candidate | null target = ""+                  | otherwise = last target+        dots = intercalate "." . init+        isModName = all isCapitalized (init target)+        isCapitalized = isUpper . head+        lineUpToCursor = take loc line+++-- | Get the word under a given cursor location.+completionTarget :: String -> Int -> [String]+completionTarget code cursor = expandCompletionPiece pieceToComplete+  where+    pieceToComplete = map fst <$> find (elem cursor . map snd) pieces+    pieces = splitAlongCursor $ split splitter $ zip code [1 .. ]+    splitter = defaultSplitter {+      -- Split using only the characters, which are the first elements of+      -- the (char, index) tuple+      delimiter = Delimiter [uncurry isDelim],+      -- Condense multiple delimiters into one and then drop them.+      condensePolicy = Condense,+      delimPolicy = Drop+    }++    isDelim :: Char -> Int -> Bool +    isDelim char idx = char `elem` neverIdent  || isSymbol char++    splitAlongCursor :: [[(Char, Int)]] -> [[(Char, Int)]]+    splitAlongCursor [] = []+    splitAlongCursor (x:xs) =+      case elemIndex cursor $  map snd x of+        Nothing -> x:splitAlongCursor xs+        Just idx -> take (idx + 1) x:drop (idx + 1) x:splitAlongCursor xs++    -- These are never part of an identifier.+    neverIdent :: String+    neverIdent = " \n\t(),{}[]\\'\"`"++    expandCompletionPiece Nothing = []+    expandCompletionPiece (Just str) = splitOn "." str++getHome :: IO String+getHome = do+  homeEither <- try  $ getEnv "HOME" :: IO (Either SomeException String)+  return $ case homeEither of+    Left _ -> "~"+    Right home -> home++dirExpand :: String -> IO String+dirExpand str = do+  home <- getHome+  return $ replace "~" home str++unDirExpand :: String -> IO String+unDirExpand str = do+  home <- getHome+  return $ replace home "~" str++completePath :: String -> Interpreter [String]+completePath line = completePathFilter acceptAll acceptAll line ""+  where acceptAll = const True++completePathWithExtensions :: [String] -> String -> Interpreter [String]+completePathWithExtensions extensions line =+  completePathFilter (extensionIsOneOf extensions) acceptAll line ""+  where+    acceptAll = const True+    extensionIsOneOf exts str = any correctEnding exts+      where correctEnding ext = endswith ext str++completePathFilter :: (String -> Bool)      -- ^ File filter: test whether to include this file.+                   -> (String -> Bool)      -- ^ Directory filter: test whether to include this directory.+                   -> String               -- ^ Line contents to the left of the cursor.+                   -> String               -- ^ Line contents to the right of the cursor.+                   -> Interpreter [String]+completePathFilter includeFile includeDirectory left right = liftIO $ do+  -- Get the completions from Haskeline.  It has a bit of a strange API.+  expanded <- dirExpand left+  completions <- map replacement <$> snd <$> completeFilename (reverse expanded, right)++  -- Split up into files and directories.+  -- Filter out ones we don't want.+  areDirs <- mapM doesDirectoryExist completions+  let dirs  = filter includeDirectory $ map fst $ filter snd         $ zip completions areDirs+      files = filter includeFile      $ map fst $ filter (not . snd) $ zip completions areDirs++  -- Return directories before files. However, stick everything that starts+  -- with a dot after everything else.  If we wanted to keep original+  -- order, we could instead use+  --   filter (`elem` (dirs ++ files)) completions+  suggestions <- mapM unDirExpand $ dirs ++ files+  let isHidden str = startswith "." . last . StringUtils.split "/" $+        if endswith "/" str+        then init str+        else str+      visible = filter (not . isHidden) suggestions+      hidden  = filter isHidden suggestions+  return $ visible ++ hidden+  
+ src/IHaskell/Eval/Evaluate.hs view
@@ -0,0 +1,1078 @@+{-# LANGUAGE DoAndIfThenElse, NoOverloadedStrings, TypeSynonymInstances, PatternGuards  #-}++{- | Description : Wrapper around GHC API, exposing a single `evaluate` interface that runs+                   a statement, declaration, import, or directive.++This module exports all functions used for evaluation of IHaskell input.+-}+module IHaskell.Eval.Evaluate (+  interpret, evaluate, Interpreter, liftIO, typeCleaner, globalImports+  ) where++import ClassyPrelude hiding (liftIO, hGetContents, try)+import Control.Concurrent (forkIO, threadDelay)+import Prelude (putChar, head, tail, last, init, (!!))+import Data.List.Utils+import Data.List(findIndex, and)+import Data.String.Utils+import Text.Printf+import Data.Char as Char+import Data.Dynamic+import Data.Typeable+import qualified Data.Serialize as Serialize+import System.Directory+import Filesystem.Path.CurrentOS (encodeString)+import System.Posix.IO+import System.IO (hGetChar, hFlush)+import System.Random (getStdGen, randomRs)+import Unsafe.Coerce+import Control.Monad (guard)+import System.Process+import System.Exit+import Data.Maybe (fromJust)+import qualified Control.Monad.IO.Class as MonadIO (MonadIO, liftIO)+import qualified MonadUtils (MonadIO, liftIO)+import System.Environment (getEnv)++import NameSet+import Name+import PprTyThing+import InteractiveEval+import DynFlags+import Type+import Exception (gtry)+import HscTypes+import HscMain+import qualified Linker+import TcType+import Unify+import InstEnv+import GhcMonad (liftIO, withSession)+import GHC hiding (Stmt, TypeSig)+import GHC.Paths+import Exception hiding (evaluate)+import Outputable+import Packages+import Module+import qualified Pretty+import FastString+import Bag+import ErrUtils (errMsgShortDoc, errMsgExtraInfo)++import qualified System.IO.Strict as StrictIO++import IHaskell.Types+import IHaskell.IPython+import IHaskell.Eval.Parser+import IHaskell.Eval.Lint+import IHaskell.Display+import qualified IHaskell.Eval.Hoogle as Hoogle+import IHaskell.Eval.Util++import Paths_ihaskell (version)+import Data.Version (versionBranch)++data ErrorOccurred = Success | Failure deriving (Show, Eq, Ord)++debug :: Bool+debug = False++ignoreTypePrefixes :: [String]+ignoreTypePrefixes = ["GHC.Types", "GHC.Base", "GHC.Show", "System.IO",+                      "GHC.Float", ":Interactive", "GHC.Num", "GHC.IO",+                      "GHC.Integer.Type"]++typeCleaner :: String -> String+typeCleaner = useStringType . foldl' (.) id (map (`replace` "") fullPrefixes)+  where+    fullPrefixes = map (++ ".") ignoreTypePrefixes+    useStringType = replace "[Char]" "String"++write :: GhcMonad m => String -> m ()+write x = when debug $ liftIO $ hPutStrLn stderr $ "DEBUG: " ++ x++type Interpreter = Ghc++instance MonadIO.MonadIO Interpreter where+    liftIO = MonadUtils.liftIO++globalImports :: [String]+globalImports =+  [ "import IHaskell.Display"+  , "import qualified IPython.Stdin"+  , "import Control.Applicative ((<$>))"+  , "import GHC.IO.Handle (hDuplicateTo, hDuplicate, hClose)"+  , "import System.Posix.IO"+  , "import System.Posix.Files"+  , "import System.IO"+  ]++++-- | Run an interpreting action. This is effectively runGhc with+-- initialization and importing. First argument indicates whether `stdin`+-- is handled specially, which cannot be done in a testing environment.+interpret :: Bool -> Interpreter a -> IO a+interpret allowedStdin action = runGhc (Just libdir) $ do+  -- Set the dynamic session flags+  originalFlags <- getSessionDynFlags+  let dflags = xopt_set originalFlags Opt_ExtendedDefaultRules++  -- If we're in a sandbox, add the relevant package database+  sandboxPackages <- liftIO getSandboxPackageConf+  let pkgConfs = case sandboxPackages of+        Nothing -> extraPkgConfs dflags+        Just path -> +          let pkg  = PkgConfFile path in+            (pkg:) . extraPkgConfs dflags++  void $ setSessionDynFlags $ dflags { hscTarget = HscInterpreted,+                                       ghcLink = LinkInMemory,+                                       pprCols = 300,+                                       extraPkgConfs = pkgConfs }++  initializeImports++  -- Close stdin so it can't be used.+  -- Otherwise it'll block the kernel forever.+  dir <- liftIO getIHaskellDir+  let cmd = printf "IPython.Stdin.fixStdin \"%s\"" dir+  when allowedStdin $ void $+    runStmt cmd RunToCompletion++  initializeItVariable++  -- Run the rest of the interpreter+  action++-- | Initialize our GHC session with imports and a value for 'it'.+initializeImports :: Interpreter ()+initializeImports = do+  -- Load packages that start with ihaskell-*, aren't just IHaskell,+  -- and depend directly on the right version of the ihaskell library+  dflags <- getSessionDynFlags+  displayPackages <- liftIO $ do+    (dflags, _) <- initPackages dflags+    let Just db = pkgDatabase dflags+        packageNames = map (packageIdString . packageConfigId) db++        initStr = "ihaskell-"+        -- "ihaskell-1.2.3.4"+        iHaskellPkgName = initStr ++ intercalate "." (map show (versionBranch version))++        dependsOnRight pkg = not $ null $ do+            pkg <- db+            depId <- depends pkg+            dep <- filter ((== depId) . installedPackageId) db+            guard (iHaskellPkgName `isPrefixOf` packageIdString (packageConfigId dep))++        -- ideally the Paths_ihaskell module could provide a way to get the+        -- hash too (ihaskell-0.2.0.5-f2bce922fa881611f72dfc4a854353b9),+        -- for now. Things will end badly if you also happen to have an+        -- ihaskell-0.2.0.5-ce34eadc18cf2b28c8d338d0f3755502 installed.+        iHaskellPkg = case filter (== iHaskellPkgName) packageNames of+                [x] -> x+                [] -> error ("cannot find required haskell library: "++iHaskellPkgName)+                _ -> error ("multiple haskell packages "++iHaskellPkgName++" found")++        displayPkgs = [ pkgName+                  | pkgName <- packageNames,+                    Just (x:_) <- [stripPrefix initStr pkgName],+                    isAlpha x]++    return displayPkgs++  -- Generate import statements all Display modules.+  let capitalize :: String -> String+      capitalize (first:rest) = Char.toUpper first : rest++      importFmt = "import IHaskell.Display.%s"++      toImportStmt :: String -> String+      toImportStmt = printf importFmt . capitalize . (!! 1) . split "-"++      displayImports = map toImportStmt displayPackages++  -- Import implicit prelude.+  importDecl <- parseImportDecl "import Prelude"+  let implicitPrelude = importDecl { ideclImplicit = True }++  -- Import modules.+  mapM_ (write . ("Importing " ++ )) displayImports+  imports <- mapM parseImportDecl $ globalImports ++ displayImports+  setContext $ map IIDecl $ implicitPrelude : imports++-- | Give a value for the `it` variable.+initializeItVariable :: Interpreter ()+initializeItVariable = do+  -- This is required due to the way we handle `it` in the wrapper+  -- statements - if it doesn't exist, the first statement will fail.+  write "Setting `it` to unit."+  void $ runStmt "let it = ()" RunToCompletion++-- | Publisher for IHaskell outputs.  The first argument indicates whether+-- this output is final (true) or intermediate (false).+type Publisher = (EvaluationResult -> IO ())++-- | Output of a command evaluation.+data EvalOut = EvalOut {+    evalStatus :: ErrorOccurred,+    evalResult :: Display,+    evalState :: KernelState,+    evalPager :: String+  }++-- | Evaluate some IPython input code.+evaluate :: KernelState                  -- ^ The kernel state.+         -> String                       -- ^ Haskell code or other interpreter commands.+         -> (EvaluationResult -> IO ())   -- ^ Function used to publish data outputs.+         -> Interpreter KernelState+evaluate kernelState code output = do+  cmds <- parseString (strip code)+  let execCount = getExecutionCounter kernelState++  when (getLintStatus kernelState /= LintOff) $ liftIO $ do+    lintSuggestions <- lint cmds+    unless (noResults lintSuggestions) $+      output $ FinalResult lintSuggestions ""++  updated <- runUntilFailure kernelState (map unloc cmds ++ [storeItCommand execCount])+  return updated {+    getExecutionCounter = execCount + 1+  }+  where+    noResults (Display res) = null res+    noResults (ManyDisplay res) = all noResults res++    runUntilFailure :: KernelState -> [CodeBlock] -> Interpreter KernelState+    runUntilFailure state [] = return state+    runUntilFailure state (cmd:rest) = do+      evalOut <- evalCommand output cmd state++      -- Output things only if they are non-empty.+      let result = evalResult evalOut+          helpStr = evalPager evalOut+      unless (noResults result && null helpStr) $+        liftIO $ output $ FinalResult result helpStr++      let newState = evalState evalOut+      case evalStatus evalOut of+        Success -> runUntilFailure newState rest+        Failure -> return newState++    storeItCommand execCount = Statement $ printf "let it%d = it" execCount++safely :: KernelState -> Interpreter EvalOut -> Interpreter EvalOut+safely state = ghandle handler . ghandle sourceErrorHandler+  where+    handler :: SomeException -> Interpreter EvalOut+    handler exception =+      return EvalOut {+        evalStatus = Failure,+        evalResult = displayError $ show exception,+        evalState = state,+        evalPager = ""+      }++    sourceErrorHandler :: SourceError -> Interpreter EvalOut+    sourceErrorHandler srcerr = do+      let msgs = bagToList $ srcErrorMessages srcerr+      errStrs <- forM msgs $ \msg -> do+        shortStr <- doc $ errMsgShortDoc msg +        contextStr <- doc $ errMsgExtraInfo msg +        return $ unlines [shortStr, contextStr]++      let fullErr = unlines errStrs+      +      return EvalOut {+        evalStatus = Failure,+        evalResult = displayError fullErr,+        evalState = state,+        evalPager = ""+      }+      +doc :: GhcMonad m => SDoc -> m String+doc sdoc = do+  flags <- getSessionDynFlags+  unqual <- getPrintUnqual+  let style = mkUserStyle unqual AllTheWay+  let cols = pprCols flags+      d = runSDoc sdoc (initSDocContext flags style)+  return $ Pretty.fullRender Pretty.PageMode cols 1.5 string_txt "" d+  where+    string_txt :: Pretty.TextDetails -> String -> String+    string_txt (Pretty.Chr c)   s  = c:s+    string_txt (Pretty.Str s1)  s2 = s1 ++ s2+    string_txt (Pretty.PStr s1) s2 = unpackFS s1 ++ s2+    string_txt (Pretty.LStr s1 _) s2 = unpackLitString s1 ++ s2+    ++wrapExecution :: KernelState+              -> Interpreter Display+              -> Interpreter EvalOut+wrapExecution state exec = safely state $ exec >>= \res ->+    return EvalOut {+      evalStatus = Success,+      evalResult = res,+      evalState = state,+      evalPager = ""+    }++-- | Set dynamic flags.+--+-- adapted from GHC's InteractiveUI.hs (newDynFlags)+setDynFlags :: [String] -> Interpreter [ErrMsg]+setDynFlags ext = do+    flags <- getSessionDynFlags+    (flags', unrecognized, warnings) <- parseDynamicFlags flags (map noLoc ext)+    let restorePkg x = x { packageFlags = packageFlags flags }+    -- First, try to check if this flag matches any extension name.+    new_pkgs <- GHC.setProgramDynFlags (restorePkg flags')+    GHC.setInteractiveDynFlags (restorePkg flags')+    return $ map (("Could not parse: " ++) . unLoc) unrecognized +++             map ("Warning: " ++)+                  (map unLoc warnings +++                    [ "-package not supported yet"+                        | packageFlags flags /= packageFlags flags' ])++-- | Return the display data for this command, as well as whether it+-- resulted in an error.+evalCommand :: Publisher -> CodeBlock -> KernelState -> Interpreter EvalOut+evalCommand _ (Import importStr) state = wrapExecution state $ do+  write $ "Import: " ++ importStr+  importDecl <- parseImportDecl importStr+  context <- getContext++  -- If we've imported this implicitly, remove the old import.+  let noImplicit = filter (not . implicitImportOf importDecl) context+  setContext $ IIDecl importDecl : noImplicit++  flags <- getSessionDynFlags+  return $ if "Test.Hspec" `isInfixOf` importStr+           then displayError $ "Warning: Hspec is unusable in IHaskell until the resolution of GHC bug #8639." +++                                "\nThe variable `it` is shadowed and cannot be accessed, even in qualified form."+           else mempty+  where+    implicitImportOf :: ImportDecl RdrName -> InteractiveImport -> Bool+    implicitImportOf _ (IIModule _) = False+    implicitImportOf imp (IIDecl decl) = ideclImplicit decl && ((==) `on` (unLoc . ideclName)) decl imp++evalCommand _ (Module contents) state = wrapExecution state $ do+  write $ "Module:\n" ++ contents++  -- Write the module contents to a temporary file in our work directory+  namePieces <- getModuleName contents+  liftIO (print namePieces)+  let directory = "./" ++ intercalate "/" (init namePieces) ++ "/"+      filename = last namePieces ++ ".hs"+  liftIO $ do+    createDirectoryIfMissing True directory+    writeFile (fpFromString $ directory ++ filename) contents++  -- Clear old modules of this name+  let modName = intercalate "." namePieces+  removeTarget $ TargetModule $ mkModuleName modName+  removeTarget $ TargetFile filename Nothing++  -- Remember which modules we've loaded before.+  importedModules <- getContext++  let -- Get the dot-delimited pieces of the module name.+      moduleNameOf :: InteractiveImport -> [String]+      moduleNameOf (IIDecl decl) = split "." . moduleNameString . unLoc . ideclName $ decl+      moduleNameOf (IIModule imp) = split "." . moduleNameString $ imp++      -- Return whether this module prevents the loading of the one we're+      -- trying to load. If a module B exist, we cannot load A.B. All+      -- modules must have unique last names (where A.B has last name B).+      -- However, we *can* just reload a module.+      preventsLoading mod =+        let pieces = moduleNameOf mod in+            last namePieces == last pieces && namePieces /= pieces++  -- If we've loaded anything with the same last name, we can't use this.+  -- Otherwise, GHC tries to load the original *.hs fails and then fails.+  case find preventsLoading importedModules of+    -- If something prevents loading this module, return an error.+    Just previous -> do+      let prevLoaded = intercalate "." (moduleNameOf previous)+      return $ displayError $+        printf "Can't load module %s because already loaded %s" modName prevLoaded++    -- Since nothing prevents loading the module, compile and load it.+    Nothing -> doLoadModule modName modName++evalCommand a (Directive SetDynFlag flags) state+    | let f o = case filter (elem o . getSetName) kernelOpts of+                [] -> Right o+                [z] | s:_ <- getOptionName z -> Left s+                    | otherwise -> error ("evalCommand Directive SetDynFlag impossible")+                ds -> error ("kernelOpts has duplicate:"++ show (map getSetName ds)),+      (optionFlags,oo) <- partitionEithers $ map f (words flags),+      not (null optionFlags) = do+          eo1 <- evalCommand a (Directive SetOption (unwords optionFlags)) state+          eo2 <- evalCommand a (Directive SetDynFlag (unwords oo)) (evalState eo1)+          return $ EvalOut {+                evalStatus = max (evalStatus eo1) (evalStatus eo2),+                evalResult = evalResult eo1 ++ evalResult eo2,+                evalState = evalState eo2,+                evalPager = evalPager eo1 ++ evalPager eo2+          }+        +evalCommand _ (Directive SetDynFlag flags) state = wrapExecution state $ do+  write $ "DynFlag: " ++ flags+  errs <- setDynFlags (words flags)+  return $ case errs of+      [] -> mempty+      _ -> displayError $ intercalate "\n" errs++evalCommand a (Directive SetExtension opts) state = do+  write $ "Extension: " ++ opts+  evalCommand a (Directive SetDynFlag (concatMap (" -X"++) (words opts))) state++evalCommand a (Directive SetOption opts) state = do+  write $ "Option: " ++ opts+  let (lost, found) = partitionEithers+        [ case filter (any (w==) . getOptionName) kernelOpts of+                [x] -> Right (getUpdateKernelState x)+                [] -> Left w+                ds -> error ("kernelOpts has duplicate:" ++ show (map getOptionName ds))+          | w <- words opts ]+      warn+        | null lost = mempty+        | otherwise = displayError ("Could not recognize options: " ++ intercalate "," lost)+  return EvalOut {+    evalStatus = if null lost then Success else Failure,+    evalResult = warn,+    evalState = foldl' (flip ($)) state found,+    evalPager = ""+  }++evalCommand _ (Directive GetType expr) state = wrapExecution state $ do+  write $ "Type: " ++ expr+  result <- exprType expr+  flags <- getSessionDynFlags+  let typeStr = showSDocUnqual flags $ ppr result+  return $ formatType typeStr++evalCommand _ (Directive LoadFile name) state = wrapExecution state $ do+  write $ "Load: " ++ name++  let filename = if endswith ".hs" name+                 then name+                 else name ++ ".hs"+  let modName =  replace "/" "."  $+                   if endswith ".hs" name+                   then replace ".hs" "" name+                   else name++  doLoadModule filename modName++evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ liftIO $+  case words cmd of+    "cd":dirs -> do+      -- Get home so we can replace '~` with it.+      homeEither <- try  $ getEnv "HOME" :: IO (Either SomeException String)+      let home = case homeEither of+                  Left _ -> "~"+                  Right val -> val++      let directory = replace "~" home $ unwords dirs+      exists <- doesDirectoryExist directory+      if exists+      then do+        setCurrentDirectory directory+        return $ mempty+      else+        return $ displayError $ printf "No such directory: '%s'" directory+    cmd -> do+      (readEnd, writeEnd) <- createPipe+      handle <- fdToHandle writeEnd+      pipe <- fdToHandle readEnd+      let initProcSpec = shell $ unwords cmd+          procSpec = initProcSpec {+            std_in = Inherit,+            std_out = UseHandle handle,+            std_err = UseHandle handle+          }+      (_, _, _, process) <- createProcess procSpec++      -- Accumulate output from the process.+      outputAccum <- liftIO $ newMVar ""++      -- Start a loop to publish intermediate results.+      let+        -- Compute how long to wait between reading pieces of the output.+        -- `threadDelay` takes an argument of microseconds.+        ms = 1000+        delay = 100 * ms++        -- Maximum size of the output (after which we truncate).+        maxSize = 100 * 1000+        incSize = 200+        output str = publish $ IntermediateResult $ Display [plain str]++        loop = do+          -- Wait and then check if the computation is done.+          threadDelay delay++          -- Read next chunk and append to accumulator.+          nextChunk <- readChars pipe "\n" incSize+          modifyMVar_ outputAccum (return . (++ nextChunk))++          -- Check if we're done.+          exitCode <- getProcessExitCode process+          let computationDone = isJust exitCode++          when computationDone $ do+            nextChunk <- readChars pipe "" maxSize+            modifyMVar_ outputAccum (return . (++ nextChunk))++          if not computationDone+          then do+            -- Write to frontend and repeat.+            readMVar outputAccum >>= output+            loop+          else do+            out <- readMVar outputAccum+            case fromJust exitCode of+              ExitSuccess -> return $ Display [plain out]+              ExitFailure code -> do+                let errMsg = "Process exited with error code " ++ show code+                    htmlErr = printf "<span class='err-msg'>%s</span>" errMsg+                return $ Display [plain $ out ++ "\n" ++ errMsg,+                                  html $ printf "<span class='mono'>%s</span>" out ++ htmlErr]++      loop+++-- This is taken largely from GHCi's info section in InteractiveUI.+evalCommand _ (Directive GetHelp _) state = do+  write "Help via :help or :?."+  return EvalOut {+    evalStatus = Success,+    evalResult = Display [out],+    evalState = state,+    evalPager = ""+  }+  where out = plain $ intercalate "\n"+          ["The following commands are available:"+          ,"    :extension <Extension>    -  Enable a GHC extension."+          ,"    :extension No<Extension>  -  Disable a GHC extension."+          ,"    :type <expression>        -  Print expression type."+          ,"    :info <name>              -  Print all info for a name."+          ,"    :hoogle <query>           -  Search for a query on Hoogle."+          ,"    :doc <ident>              -  Get documentation for an identifier via Hogole."+          ,"    :set -XFlag -Wall         -  Set an option (like ghci)."+          ,"    :option <opt>             -  Set an option."+          ,"    :option no-<opt>          -  Unset an option."+          ,"    :?, :help                 -  Show this help text."+          ,""+          ,"Any prefix of the commands will also suffice, e.g. use :ty for :type."+          ,""+          ,"Options:"+          ,"  lint          - enable or disable linting."+          ,"  svg           - use svg output (cannot be resized)."+          ,"  show-types    - show types of all bound names"+          ,"  show-errors   - display Show instance missing errors normally."+          ]++-- This is taken largely from GHCi's info section in InteractiveUI.+evalCommand _ (Directive GetInfo str) state = safely state $ do+  write $ "Info: " ++ str+  -- Get all the info for all the names we're given.+  names     <- parseName str+  maybeInfos <- mapM getInfo names++  -- Filter out types that have parents in the same set.+  -- GHCi also does this.+  let getType (theType, _, _) = theType+      infos = catMaybes maybeInfos+      allNames = mkNameSet $ map (getName . getType) infos+      hasParent info = case tyThingParent_maybe (getType info) of+        Just parent -> getName parent `elemNameSet` allNames+        Nothing -> False+      filteredOutput = filter (not . hasParent) infos++  -- Convert to textual data.+  let printInfo (thing, fixity, classInstances) =+        pprTyThingInContextLoc False thing $$ showFixity fixity $$ vcat (map GHC.pprInstance classInstances)+        where+          showFixity fixity =+            if fixity == GHC.defaultFixity+            then empty+            else ppr fixity <+> pprInfixName (getName thing)++  -- Print nicely.+  strings <- mapM (doc . printInfo) filteredOutput+  let output = case getFrontend state of+        IPythonConsole -> unlines strings+        IPythonNotebook -> unlines (map htmlify strings)+      htmlify str =+        printf "<div style='background: rgb(247, 247, 247);'><form><textarea id='code'>%s</textarea></form></div>" str+        ++ script+      script = +        "<script>CodeMirror.fromTextArea(document.getElementById('code'), {mode: 'haskell', readOnly: 'nocursor'});</script>"++  return EvalOut {+    evalStatus = Success,+    evalResult = mempty,+    evalState = state,+    evalPager = output+  }++evalCommand _ (Directive SearchHoogle query) state = safely state $ do+  results <- liftIO $ Hoogle.search query+  return $ hoogleResults state results++evalCommand _ (Directive GetDoc query) state = safely state $ do+  results <- liftIO $ Hoogle.document query+  return $ hoogleResults state results++evalCommand output (Statement stmt) state = wrapExecution state $ do+  write $ "Statement:\n" ++ stmt+  let outputter str = output $ IntermediateResult $ Display [plain str]+  (printed, result) <- capturedStatement outputter stmt+  case result of+    RunOk names -> do+      dflags <- getSessionDynFlags++      let allNames = map (showPpr dflags) names+          isItName name =+            name == "it" ||+            name == "it" ++ show (getExecutionCounter state)+          nonItNames = filter (not . isItName) allNames+          output = [plain printed | not . null $ strip printed]++      write $ "Names: " ++ show allNames++      -- Display the types of all bound names if the option is on.+      -- This is similar to GHCi :set +t.+      if not $ useShowTypes state+      then return $ Display output+      else do+        -- Get all the type strings.+        types <- forM nonItNames $ \name -> do+          theType <- showSDocUnqual dflags . ppr <$> exprType name+          return $ name ++ " :: " ++ theType++        let joined = unlines types+            htmled = unlines $ map formatGetType types++        return $ case extractPlain output of+          "" -> Display [html htmled]++          -- Return plain and html versions.+          -- Previously there was only a plain version.+          text -> Display +            [plain $ joined ++ "\n" ++ text,+             html  $ htmled ++ mono text]++    RunException exception -> throw exception+    RunBreak{} -> error "Should not break."++evalCommand output (Expression expr) state = do+  write $ "Expression:\n" ++ expr++  -- Try to use `display` to convert our type into the output+  -- Dislay If typechecking fails and there is no appropriate+  -- typeclass instance, this will throw an exception and thus `attempt` will+  -- return False, and we just resort to plaintext.+  let displayExpr = printf "(IHaskell.Display.display (%s))" expr :: String+  canRunDisplay <- attempt $ exprType displayExpr++  if canRunDisplay+  then useDisplay displayExpr+  else do+    -- Evaluate this expression as though it's just a statement.+    -- The output is bound to 'it', so we can then use it.+    evalOut <- evalCommand output (Statement expr) state++    let out = evalResult evalOut+        showErr = isShowError out++    -- If evaluation failed, return the failure.  If it was successful, we+    -- may be able to use the IHaskellDisplay typeclass.+    return $ if not showErr || useShowErrors state+            then evalOut+            else postprocessShowError evalOut++  where+    -- Try to evaluate an action. Return True if it succeeds and False if+    -- it throws an exception. The result of the action is discarded.+    attempt :: Interpreter a -> Interpreter Bool+    attempt action = gcatch (action >> return True) failure+      where failure :: SomeException -> Interpreter Bool+            failure _ = return False++    -- Check if the error is due to trying to print something that doesn't+    -- implement the Show typeclass.+    isShowError (ManyDisplay _) = False+    isShowError (Display errs) = +        -- Note that we rely on this error message being 'type cleaned', so+        -- that `Show` is not displayed as GHC.Show.Show.+        startswith "No instance for (Show" msg &&+        isInfixOf " arising from a use of `print'" msg+      where msg = extractPlain errs++    isSvg (DisplayData mime _) = mime == MimeSvg++    removeSvg (Display disps) = Display $ filter (not . isSvg) disps+    removeSvg (ManyDisplay disps) = ManyDisplay $ map removeSvg disps++    useDisplay displayExpr = wrapExecution state $ do+      -- If there are instance matches, convert the object into+      -- a Display. We also serialize it into a bytestring. We get+      -- the bytestring as a dynamic and then convert back to+      -- a bytestring, which we promptly unserialize. Note that+      -- attempting to do this without the serialization to binary and+      -- back gives very strange errors - all the types match but it+      -- refuses to decode back into a Display.+      -- Suppress output, so as not to mess up console.+      out <- capturedStatement (const $ return ()) displayExpr++      displayedBytestring <- dynCompileExpr "IHaskell.Display.serializeDisplay it"+      case fromDynamic displayedBytestring of+        Nothing -> error "Expecting lazy Bytestring"+        Just bytestring ->+          case Serialize.decode bytestring of+            Left err -> error err+            Right display -> do+              return $+                if useSvg state+                then display+                else removeSvg display++    postprocessShowError :: EvalOut -> EvalOut+    postprocessShowError evalOut = evalOut { evalResult = Display $ map postprocess disps }+      where+        Display disps = evalResult evalOut+        text = extractPlain disps++        postprocess (DisplayData MimeHtml _) = html $ printf fmt unshowableType (formatErrorWithClass "err-msg collapse" text) script+          where+            fmt = "<div class='collapse-group'><span class='btn' href='#' id='unshowable'>Unshowable:<span class='show-type'>%s</span></span>%s</div><script>%s</script>"+            script = unlines [+                "$('#unshowable').on('click', function(e) {",+                "    e.preventDefault();",+                "    var $this = $(this);",+                "    var $collapse = $this.closest('.collapse-group').find('.err-msg');",+                "    $collapse.collapse('toggle');",+                "});"+              ]++        postprocess other = other++        unshowableType = fromMaybe "" $ do+          let pieces = words text+              before = takeWhile (/= "arising") pieces+              after = init $ unwords $ tail $ dropWhile (/= "(Show") before++          firstChar <- headMay after+          return $ if firstChar == '('+                   then init $ tail after+                   else after++++evalCommand _ (Declaration decl) state = wrapExecution state $ do+  write $ "Declaration:\n" ++ decl+  names <- runDecls decl++  dflags <- getSessionDynFlags+  let boundNames = map (replace ":Interactive." "" . showPpr dflags) names+      nonDataNames = filter (not . isUpper . head) boundNames++  -- Display the types of all bound names if the option is on.+  -- This is similar to GHCi :set +t.+  if not $ useShowTypes state+  then return mempty+  else do+    -- Get all the type strings.+    types <- forM nonDataNames $ \name -> do+      theType <- showSDocUnqual dflags . ppr <$> exprType name+      return $ name ++ " :: " ++ theType++    return $ Display [html $ unlines $ map formatGetType types]++evalCommand _ (TypeSignature sig) state = wrapExecution state $+  -- We purposefully treat this as a "success" because that way execution+  -- continues. Empty type signatures are likely due to a parse error later+  -- on, and we want that to be displayed.+  return $ displayError $ "The type signature " ++ sig +++                          "\nlacks an accompanying binding."++evalCommand _ (ParseError loc err) state = do+  write "Parse Error."+  return EvalOut {+    evalStatus = Failure,+    evalResult = displayError $ formatParseError loc err,+    evalState = state,+    evalPager = ""+  }+++hoogleResults :: KernelState -> [Hoogle.HoogleResult] -> EvalOut+hoogleResults state results = EvalOut {+    evalStatus = Success,+    evalResult = mempty,+    evalState = state,+    evalPager = output+  }+  where+    fmt = +        case getFrontend state of+          IPythonNotebook -> Hoogle.HTML+          IPythonConsole -> Hoogle.Plain+    output = unlines $ map (Hoogle.render fmt) results++-- Read from a file handle until we hit a delimiter or until we've read+-- as many characters as requested+readChars :: Handle -> String -> Int -> IO String++-- If we're done reading, return nothing.+readChars handle delims 0 = return []++readChars handle delims nchars = do+  -- Try reading a single character. It will throw an exception if the+  -- handle is already closed.+  tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char)+  case tryRead of+    Right char ->+      -- If this is a delimiter, stop reading.+      if char `elem` delims+      then return [char]+      else do+        next <- readChars handle delims (nchars - 1)+        return $ char:next+    -- An error occurs at the end of the stream, so just stop reading.+    Left _ -> return []+++doLoadModule :: String -> String -> Ghc Display+doLoadModule name modName = flip gcatch unload $ do+  -- Compile loaded modules.+  flags <- getSessionDynFlags+  let objTarget = defaultObjectTarget+  setSessionDynFlags flags{ hscTarget = objTarget }++  -- Remember which modules we've loaded before.+  importedModules <- getContext++  -- Create a new target+  target <- guessTarget name Nothing+  addTarget target+  result <- load LoadAllTargets++  -- Reset the context, since loading things screws it up.+  initializeItVariable++  -- Add imports+  importDecl <- parseImportDecl $ "import " ++ modName+  let implicitImport = importDecl { ideclImplicit = True }+  setContext $ IIDecl implicitImport : importedModules++  -- Switch back to interpreted mode.+  flags <- getSessionDynFlags+  setSessionDynFlags flags{ hscTarget = HscInterpreted }++  case result of+    Succeeded -> return mempty+    Failed -> return $ displayError $ "Failed to load module " ++ modName+  where+    unload :: SomeException -> Ghc Display+    unload exception = do+      -- Explicitly clear targets+      setTargets []+      load LoadAllTargets++      initializeItVariable+      return $ displayError $ "Failed to load module " ++ modName ++ ": " ++ show exception++capturedStatement :: (String -> IO ())         -- ^ Function used to publish intermediate output.+                  -> String                            -- ^ Statement to evaluate.+                  -> Interpreter (String, RunResult)   -- ^ Return the output and result.+capturedStatement output stmt = do+  -- Generate random variable names to use so that we cannot accidentally+  -- override the variables by using the right names in the terminal.+  gen <- liftIO getStdGen+  let+    -- Variable names generation.+    rand = take 20 $ randomRs ('0', '9') gen+    var name = name ++ rand++    -- Variables for the pipe input and outputs.+    readVariable = var "file_read_var_"+    writeVariable = var "file_write_var_"++    -- Variable where to store old stdout.+    oldVariable = var "old_var_"++    -- Variable used to store true `it` value.+    itVariable = var "it_var_"++    voidpf str = printf $ str ++ " >> return ()"++    -- Statements run before the thing we're evaluating.+    initStmts =+      [ printf "let %s = it" itVariable+      , printf "(%s, %s) <- createPipe" readVariable writeVariable+      , printf "%s <- dup stdOutput" oldVariable+      , voidpf "dupTo %s stdOutput" writeVariable+      , voidpf "hSetBuffering stdout NoBuffering"+      , printf "let it = %s" itVariable+      ]++    -- Statements run after evaluation.+    postStmts =+      [ printf "let %s = it" itVariable+      , voidpf "hFlush stdout"+      , voidpf "dupTo %s stdOutput" oldVariable+      , voidpf "closeFd %s" writeVariable+      , printf "let it = %s" itVariable+      ]+    pipeExpr = printf "let %s = %s" (var "pipe_var_") readVariable++    goStmt s = runStmt s RunToCompletion++  -- Initialize evaluation context.+  forM_ initStmts goStmt++  -- Get the pipe to read printed output from.+  -- This is effectively the source code of dynCompileExpr from GHC API's+  -- InteractiveEval. However, instead of using a `Dynamic` as an+  -- intermediary, it just directly reads the value. This is incredibly+  -- unsafe! However, for some reason the `getContext` and `setContext`+  -- required by dynCompileExpr (to import and clear Data.Dynamic) cause+  -- issues with data declarations being updated (e.g. it drops newer+  -- versions of data declarations for older ones for unknown reasons).+  -- First, compile down to an HValue.+  Just (_, hValues, _) <- withSession $ liftIO . flip hscStmt pipeExpr+  -- Then convert the HValue into an executable bit, and read the value.+  pipe <- liftIO $ do+    fd <- head <$> unsafeCoerce hValues+    fdToHandle fd++  -- Read from a file handle until we hit a delimiter or until we've read+  -- as many characters as requested+  let+    readChars :: Handle -> String -> Int -> IO String++    -- If we're done reading, return nothing.+    readChars handle delims 0 = return []++    readChars handle delims nchars = do+      -- Try reading a single character. It will throw an exception if the+      -- handle is already closed.+      tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char)+      case tryRead of+        Right char ->+          -- If this is a delimiter, stop reading.+          if char `elem` delims+          then return [char]+          else do+            next <- readChars handle delims (nchars - 1)+            return $ char:next+        -- An error occurs at the end of the stream, so just stop reading.+        Left _ -> return []+++  -- Keep track of whether execution has completed.+  completed <- liftIO $ newMVar False+  finishedReading <- liftIO newEmptyMVar+  outputAccum <- liftIO $ newMVar ""++  -- Start a loop to publish intermediate results.+  let+    -- Compute how long to wait between reading pieces of the output.+    -- `threadDelay` takes an argument of microseconds.+    ms = 1000+    delay = 100 * ms++    -- How much to read each time.+    chunkSize = 100++    -- Maximum size of the output (after which we truncate).+    maxSize = 100 * 1000++    loop = do+      -- Wait and then check if the computation is done.+      threadDelay delay+      computationDone <- readMVar completed++      if not computationDone+      then do+        -- Read next chunk and append to accumulator.+        nextChunk <- readChars pipe "\n" 100+        modifyMVar_ outputAccum (return . (++ nextChunk))++        -- Write to frontend and repeat.+        readMVar outputAccum >>= output+        loop+      else do+        -- Read remainder of output and accumulate it.+        nextChunk <- readChars pipe "" maxSize+        modifyMVar_ outputAccum (return . (++ nextChunk))++        -- We're done reading.+        putMVar finishedReading True++  liftIO $ forkIO loop++  result <- gfinally (goStmt stmt) $ do+    -- Execution is done.+    liftIO $ modifyMVar_ completed (const $ return True)++    -- Finalize evaluation context.+    forM_ postStmts goStmt++    -- Once context is finalized, reading can finish.+    -- Wait for reading to finish to that the output accumulator is+    -- completely filled.+    liftIO $ takeMVar finishedReading++  printedOutput <- liftIO $ readMVar outputAccum+  return (printedOutput, result)++formatError :: ErrMsg -> String+formatError = formatErrorWithClass "err-msg"++formatErrorWithClass :: String -> ErrMsg -> String+formatErrorWithClass cls =+    printf "<span class='%s'>%s</span>" cls .+    replace "\n" "<br/>" .+    fixStdinError .+    replace useDashV "" .+    rstrip .+    typeCleaner+  where+    useDashV = "\nUse -v to see a list of the files searched for."+    isShowError err =+      startswith "No instance for (Show" err &&+      isInfixOf " arising from a use of `print'" err+++formatParseError :: StringLoc -> String -> ErrMsg+formatParseError (Loc line col) =+  printf "Parse error (line %d, column %d): %s" line col++formatGetType :: String -> String+formatGetType = printf "<span class='get-type'>%s</span>"++formatType :: String -> Display+formatType typeStr =  Display [plain typeStr, html $ formatGetType typeStr]++displayError :: ErrMsg -> Display+displayError msg = Display [plain . fixStdinError . typeCleaner $ msg, html $ formatError msg]++fixStdinError :: ErrMsg -> ErrMsg+fixStdinError err =+  if isStdinErr err+  then "<stdin> is not available in IHaskell. Use special `inputLine` instead of `getLine`."+  else err+  where+    isStdinErr err = startswith "<stdin>" err+      && "illegal operation (handle is closed)" `isInfixOf` err++mono :: String -> String+mono = printf "<span class='mono'>%s</span>"
+ src/IHaskell/Eval/Hoogle.hs view
@@ -0,0 +1,226 @@+{-# LANGUAGE NoImplicitPrelude, FlexibleInstances, OverloadedStrings #-}+module IHaskell.Eval.Hoogle ( +  search,+  document,+  render,+  OutputFormat(..),+  HoogleResult+  ) where++import ClassyPrelude hiding (span, div)+import Text.Printf+import Network.HTTP+import Data.Aeson+import Data.String.Utils+import Data.List (elemIndex, (!!), last)+import Control.Monad (guard)+import qualified Data.ByteString.Lazy.Char8 as Char+++import IHaskell.IPython++-- | Types of formats to render output to.+data OutputFormat+     = Plain      -- ^ Render to plain text.+     | HTML       -- ^ Render to HTML.++data HoogleResponse = HoogleResponse {+    location :: String,+    self :: String,+    docs :: String+  }+  deriving (Eq, Show)++data HoogleResult+     = SearchResult HoogleResponse+     | DocResult HoogleResponse+     | NoResult String+     deriving Show++instance FromJSON [HoogleResponse] where+  parseJSON (Object obj) = do+    results <- obj .: "results"+    mapM parseJSON results++  parseJSON _ = fail "Expected object with 'results' field."++instance FromJSON HoogleResponse where+  parseJSON (Object obj) =+    HoogleResponse      <$>+    obj .: "location" <*>+    obj .: "self"     <*>+    obj .: "docs"++  parseJSON _ = fail "Expected object with fields: location, self, docs"++-- | Query Hoogle for the given string.+-- This searches Hoogle using the internet. It returns either an error+-- message or the successful JSON result.+query :: String -> IO (Either String String)+query str = do+  let request = getRequest $ queryUrl str+  response <- simpleHTTP request+  return $ case response of+    Left err -> Left $ show err+    Right resp -> Right $ rspBody resp+  where+    queryUrl :: String -> String+    queryUrl = printf "http://www.haskell.org/hoogle/?hoogle=%s&mode=json" . urlEncode++-- | Search for a query on Hoogle.+-- Return all search results.+search :: String -> IO [HoogleResult]+search string = do+  response <- query string+  return $ case response of+    Left err -> [NoResult err]+    Right json ->+      case eitherDecode $ Char.pack json of+        Left err -> [NoResult err]+        Right results ->+          case map SearchResult results of+            [] -> [NoResult "no matching identifiers found."]+            res -> res++-- | Look up an identifier on Hoogle.+-- Return documentation for that identifier. If there are many+-- identifiers, include documentation for all of them.+document :: String -> IO [HoogleResult]+document string = do+  matchingResults <- filter matches <$> search string+  let results = map toDocResult matchingResults+  return $ case results of+    [] -> [NoResult "no matching identifiers found."]+    res -> res+  where+    matches (SearchResult resp) = +      case split " " $ self resp of+        name:_ -> strip string == strip name+        _ -> False+    matches _ = False++    toDocResult (SearchResult resp) = DocResult resp++-- | Render a Hoogle search result into an output format.+render :: OutputFormat -> HoogleResult -> String+render Plain = renderPlain+render HTML  = renderHtml++-- | Render a Hoogle result to plain text.+renderPlain :: HoogleResult -> String++renderPlain (NoResult res) = +  "No response available: " ++ res++renderPlain (SearchResult resp) = +  printf "%s\nURL: %s\n%s" +  (self resp)+  (location resp)+  (docs resp)++renderPlain (DocResult resp) = +  printf "%s\nURL: %s\n%s" +  (self resp)+  (location resp)+  (docs resp)++-- | Render a Hoogle result to HTML.+renderHtml :: HoogleResult -> String+renderHtml (NoResult resp) = +  printf "<span class='err-msg'>No result: %s</span>" resp++renderHtml (DocResult resp) = +  renderSelf (self resp) (location resp) +  +++  renderDocs (docs resp)++renderHtml (SearchResult resp) =+  renderSelf (self resp) (location resp) +  +++  renderDocs (docs resp)++renderSelf :: String -> String -> String+renderSelf string loc+  | startswith "package" string+    = pkg ++ " " ++ span "hoogle-package" (link loc $ extractPackage string)++  | startswith "module" string+    = let package = extractPackageName loc in+        mod ++ " " +++        span "hoogle-module" (link loc $ extractModule string) +++        packageSub package++  | otherwise+    = let [name, args] = split "::" string+          package = extractPackageName loc+          modname = extractModuleName loc in+        span "hoogle-name" (unicodeReplace $ +          link loc (strip name) +++          " :: " +++          strip args)+        ++ packageAndModuleSub package modname++  where +    extractPackage = strip . replace "package" ""+    extractModule = strip . replace "module" ""+    pkg = span "hoogle-head" "package"+    mod = span "hoogle-head" "module"++    unicodeReplace :: String -> String+    unicodeReplace =+     replace "forall" "&#x2200;" . +     replace "=>"     "&#x21D2;" . +     replace "->"     "&#x2192;" . +     replace "::"     "&#x2237;"++    packageSub Nothing = ""+    packageSub (Just package) =+      span "hoogle-sub" $ +        "(" ++ pkg ++ " " ++ span "hoogle-package" package ++ ")"++    packageAndModuleSub Nothing _ = ""+    packageAndModuleSub (Just package) Nothing = packageSub (Just package)+    packageAndModuleSub (Just package) (Just modname) =+        span "hoogle-sub" $ +          "(" ++ pkg ++ " " ++ span "hoogle-package" package ++ +           ", " ++ mod ++ " " ++ span "hoogle-module" modname ++ ")"++renderDocs :: String -> String+renderDocs doc = +  let groups = groupBy bothAreCode $ lines doc+      nonull = filter (not . null . strip)+      bothAreCode s1 s2 = +        startswith ">" (strip s1) &&+        startswith ">" (strip s2)+      isCode (s:_) = startswith ">" $ strip s+      makeBlock lines =+        if isCode lines+        then div "hoogle-code" $ unlines $ nonull lines+        else div "hoogle-text" $ unlines $ nonull lines+      in+    div "hoogle-doc" $ unlines $ map makeBlock groups++extractPackageName :: String ->  Maybe String+extractPackageName link = do+  let pieces = split "/" link+  archiveLoc <- elemIndex "archive" pieces+  latestLoc <- elemIndex "latest" pieces+  guard $ latestLoc - archiveLoc == 2+  return $ pieces !! (latestLoc - 1)++extractModuleName :: String ->  Maybe String+extractModuleName link = do+  let pieces = split "/" link+  guard $ not $ null pieces+  let html = last pieces+      mod = replace "-" "." $ takeWhile (/= '.') html+  return mod++div :: String -> String -> String+div = printf "<div class='%s'>%s</div>"++span :: String -> String -> String+span = printf "<span class='%s'>%s</span>"++link :: String -> String -> String+link = printf "<a target='_blank' href='%s'>%s</a>"
+ src/IHaskell/Eval/Info.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}+{- | Description : Inspect type and function information and documentation.+-}+module IHaskell.Eval.Info (+  info+  ) where++import ClassyPrelude hiding (liftIO)++import IHaskell.Eval.Evaluate (typeCleaner, Interpreter)++import GHC+import Outputable+import Exception++info :: String -> Interpreter String+info name = ghandle handler $ do+  dflags <- getSessionDynFlags+  result <- exprType name+  return $ typeCleaner $ showPpr dflags result +  where +    handler :: SomeException -> Interpreter String+    handler _ = return ""
+ src/IHaskell/Eval/Lint.hs view
@@ -0,0 +1,206 @@+{-# LANGUAGE NoImplicitPrelude, QuasiQuotes, ViewPatterns #-}+module IHaskell.Eval.Lint ( +  lint+  ) where++import Data.String.Utils (replace, startswith, strip, split)+import Prelude (head, tail)+import Language.Haskell.HLint as HLint+import ClassyPrelude+import Control.Monad+import Data.List (findIndex)+import Text.Printf+import Data.String.Here+import Data.Char+import Data.Monoid++import IHaskell.Types+import IHaskell.Display+import IHaskell.IPython+import IHaskell.Eval.Parser++data LintSeverity = LintWarning | LintError deriving (Eq, Show)++data LintSuggestion+  = Suggest {+    line :: LineNumber,+    chunkNumber :: Int,+    found :: String,+    whyNot :: String,+    severity :: LintSeverity,+    suggestion :: String+  }+  deriving (Eq, Show)++-- | Identifier used when one is needed for proper context.+lintIdent :: String+lintIdent = "lintIdentAEjlkQeh"++-- | Given parsed code chunks, perform linting and output a displayable+-- report on linting warnings and errors.+lint :: [Located CodeBlock] -> IO Display+lint blocks = do+  let validBlocks = map makeValid blocks+      fileContents = joinBlocks validBlocks+  -- Get a temporarly location to store this file.+  ihaskellDir <- getIHaskellDir+  let filename = ihaskellDir ++ "/.hlintFile.hs"++  writeFile (fromString filename) fileContents+  suggestions <- catMaybes <$> map parseSuggestion <$> hlint [filename, "--quiet"]+  return $+    if null suggestions+    then Display []+    else Display+      [plain $ concatMap plainSuggestion suggestions, html $ htmlSuggestions suggestions]+  where+    -- Join together multiple valid file blocks into a single file.+    -- However, join them with padding so that the line numbers are+    -- correct.+    joinBlocks :: [Located String] -> String+    joinBlocks = unlines . zipWith addPragma [1 .. ]++    addPragma :: Int -> Located String -> String+    addPragma i (Located desiredLine str) = linePragma desiredLine i ++ str++    linePragma = printf "{-# LINE %d \"%d\" #-}\n"++plainSuggestion :: LintSuggestion -> String+plainSuggestion suggest = +  printf "Line %d: %s\nFound:\n%s\nWhy not:\n%s"+    (line suggest)+    (suggestion suggest)+    (found suggest)+    (whyNot suggest)++htmlSuggestions :: [LintSuggestion] -> String+htmlSuggestions = concatMap toHtml +  where+    toHtml :: LintSuggestion -> String+    toHtml suggest = concat +      [+      named $ suggestion suggest,+      floating "left" $ style severityClass "Found:" +++             -- Things that look like this get highlighted.+             styleId "highlight-code" "haskell" (found suggest),+      floating "left" $ style severityClass "Why Not:" +++             -- Things that look like this get highlighted.+             styleId "highlight-code" "haskell" (whyNot suggest)+      ]++      where+        severityClass = case severity suggest of+          LintWarning -> "warning"+          LintError -> "error"++    style :: String -> String -> String+    style cls thing = [i| <div class="suggestion-${cls}">${thing}</div> |]++    named :: String -> String+    named thing = [i| <div class="suggestion-name" style="clear:both;">${thing}</div> |]++    styleId :: String -> String -> String -> String+    styleId cls id thing = [i| <div class="${cls}" id="${id}">${thing}</div> |]+    +    floating :: String -> String -> String+    floating dir thing = [i| <div class="suggestion-row" style="float: ${dir};">${thing}</div> |]++-- | Parse a suggestion from Hlint. The suggestions look like this:+--   .ihaskell/.hlintFile.hs:1:19: Warning: Redundant bracket+--   Found:+--     ((3))+--   Why not:+--     (3)+-- We extract all the necessary fields and store them.+-- If parsing fails, return Nothing.+parseSuggestion :: Suggestion -> Maybe LintSuggestion+parseSuggestion suggestion = do+  let str = showSuggestion (show suggestion)+      severity = suggestionSeverity suggestion+  guard (severity /= HLint.Ignore)+  let lintSeverity = case severity of+        Warning -> LintWarning+        Error -> LintError++  headerLine:foundLine:rest <- Just (lines str)++  -- Expect the line after the header to have 'Found' in it.+  guard ("Found:" `isInfixOf` foundLine)++  -- Expect something like:+  -- ".hlintFile.hs:1:19: Warning: Redundant bracket"+  -- ==> +  -- [".hlintFile.hs","1","19"," Warning"," Redundant bracket"]+  [readMay -> Just chunkN,+   readMay -> Just lineNum, _col, severity, name] <- Just (split ":" headerLine)++  (before, _:after) <- Just (break ("Why not:" `isInfixOf`) rest)+  return Suggest {+    line = lineNum,+    chunkNumber = chunkN,+    found = unlines before,+    whyNot = unlines after,+    suggestion = name,+    severity = lintSeverity+  }+++showSuggestion :: String -> String+showSuggestion = +    replace ("return " ++ lintIdent) "" .+    replace (lintIdent ++ "=") "" .+    dropDo+    where++    -- drop leading '  do ', and blank spaces following+    dropDo :: String -> String+    dropDo = unlines . f . lines+        where+        f :: [String] -> [String]+        f ((stripPrefix "  do " -> Just a) : as) =+                let as' = catMaybes+                        $ takeWhile isJust+                        $ map (stripPrefix "     ") as+                in a : as' ++ f (drop (length as') as)+        f (x:xs) = x : f xs+        f [] = []++-- | Convert a code chunk into something that could go into a file.+-- The line number on the output is the same as on the input.+makeValid :: Located CodeBlock -> Located String+makeValid (Located line block) = Located line $+  case block of+    -- Expressions need to be bound to some identifier.+    Expression expr -> lintIdent ++ "=" ++ expr++    -- Statements go in a 'do' block bound to an identifier.+    --+    -- a cell can contain:+    -- > x <- readFile "foo"+    -- so add a return () to avoid a Parse error: Last statement in+    -- a do-block must be an expression+    --+    -- one place this goes wrong is when the chunk is:+    --+    -- > do+    -- >  {- a comment that has to -} let x = 1+    -- >   {- count as whitespace -}      y = 2+    -- >                              return (x+y) +    Statement stmt ->+        let expandTabs = replace "\t" "        " +            nLeading = maybe 0 (length . takeWhile isSpace)+                    $ listToMaybe+                    $ filter (not . all isSpace)+                            (lines (expandTabs stmt))+            finalReturn = replicate nLeading ' ' ++ "return " ++ lintIdent+        in intercalate ("\n ") ((lintIdent ++ " $ do") : lines stmt ++ [finalReturn])++    -- Modules, declarations, and type signatures are fine as is.+    Module mod -> mod+    Declaration decl -> decl+    TypeSignature sig -> sig+    Import imp -> imp++    -- Output nothing for directives or parse errors.+    Directive {} -> ""+    ParseError {} -> ""
+ src/IHaskell/Eval/ParseShell.hs view
@@ -0,0 +1,59 @@++-- | This module splits a shell command line into a list of strings,+--   one for each command / filename+module IHaskell.Eval.ParseShell (parseShell) where ++import Prelude hiding (words)+import Text.ParserCombinators.Parsec hiding (manyTill)+import Control.Applicative hiding ((<|>), many, optional)++eol :: Parser Char+eol = oneOf "\n\r" <?> "end of line"++quote :: Parser Char +quote = char '\"'++-- | @manyTill p end@ from hidden @manyTill@ in that it appends the result of @end@+manyTill :: Parser a -> Parser [a] -> Parser [a]+manyTill p end = scan+  where+    scan = end <|> do+      x <- p+      xs <- scan+      return $ x:xs++manyTill1 p end = do x <- p +                     xs <- manyTill p end +                     return $ x : xs++unescapedChar :: Parser Char -> Parser String +unescapedChar p = try $ do +  x <- noneOf "\\"+  lookAhead p+  return [x]++quotedString = do+  quote <?> "expected starting quote"+  (manyTill anyChar (unescapedChar quote) <* quote) <?> "unexpected in quoted String "++unquotedString = manyTill1 anyChar end  +  where end = unescapedChar space +          <|> (lookAhead eol >> return [])++word = quotedString <|> unquotedString <?> "word"++separator :: Parser String+separator = many1 space <?> "separator"++-- | Input must terminate in a space character (like a \n)+words :: Parser [String]+words = try (eof *> return []) <|> do+  x <-  word +  rest1 <- lookAhead (many anyToken)+  ss <-  separator +  rest2 <-  lookAhead (many anyToken)+  xs <-  words +  return $ x : xs ++parseShell :: String -> Either ParseError [String]+parseShell string = parse words "shell" (string ++ "\n")
+ src/IHaskell/Eval/Parser.hs view
@@ -0,0 +1,342 @@+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}+module IHaskell.Eval.Parser (+    parseString,+    CodeBlock(..),+    StringLoc(..),+    DirectiveType(..),+    LineNumber,+    ColumnNumber,+    ErrMsg,+    layoutChunks,+    parseDirective,+    getModuleName,+    unloc,+    Located(..),+    ) where++-- Hide 'unlines' to use our own 'joinLines' instead.+import ClassyPrelude hiding (liftIO, unlines)++import Data.List (findIndex, maximumBy, maximum, inits)+import Data.String.Utils (startswith, strip, split)+import Data.List.Utils (subIndex)+import Prelude (init, last, head, tail)++import Bag+import ErrUtils hiding (ErrMsg)+import FastString+import GHC hiding (Located)+import Lexer+import OrdList+import Outputable hiding ((<>))+import SrcLoc hiding (Located)+import StringBuffer++import Language.Haskell.GHC.Parser+import IHaskell.Eval.Util++-- | A block of code to be evaluated.+-- Each block contains a single element - one declaration, statement,+-- expression, etc. If parsing of the block failed, the block is instead+-- a ParseError, which has the error location and error message.+data CodeBlock+  = Expression String                  -- ^ A Haskell expression.+  | Declaration String                 -- ^ A data type or function declaration.+  | Statement String                   -- ^ A Haskell statement (as if in a `do` block).+  | Import String                      -- ^ An import statement.+  | TypeSignature String               -- ^ A lonely type signature (not above a function declaration).+  | Directive DirectiveType String     -- ^ An IHaskell directive.+  | Module String                      -- ^ A full Haskell module, to be compiled and loaded.+  | ParseError StringLoc ErrMsg        -- ^ An error indicating that parsing the code block failed.+  deriving (Show, Eq)++-- | Store locations along with a value.+data Located a = Located LineNumber a deriving (Eq, Show)+instance Functor Located where+  fmap f (Located line a) = Located line $ f a++-- | Directive types. Each directive is associated with a string in the+-- directive code block.+data DirectiveType+  = GetType         -- ^ Get the type of an expression via ':type' (or unique prefixes)+  | GetInfo         -- ^ Get info about the identifier via ':info' (or unique prefixes)+  | SetDynFlag      -- ^ Enable or disable an extensions, packages etc. via `:set`. Emulates GHCi's `:set`+  | LoadFile        -- ^ Load a Haskell module.+  | SetOption       -- ^ Set IHaskell kernel option `:option`.+  | SetExtension    -- ^ `:extension Foo` is a shortcut for `:set -XFoo`+  | ShellCmd        -- ^ Execute a shell command.+  | GetHelp         -- ^ General help via ':?' or ':help'.+  | SearchHoogle    -- ^ Search for something via Hoogle.+  | GetDoc          -- ^ Get documentation for an identifier via Hoogle.+  deriving (Show, Eq)++-- | Unlocate something - drop the position.+unloc :: Located a -> a+unloc (Located _ a) = a++-- | Get the line number of a located element.+line :: Located a -> LineNumber+line (Located l _) = l++-- | Parse a string into code blocks.+parseString :: GhcMonad m => String -> m [Located CodeBlock]+parseString codeString = do+  -- Try to parse this as a single module.+  flags <- getSessionDynFlags+  let output = runParser flags parserModule codeString+  case output of+    Parsed {} -> return [Located 1 $ Module codeString]+    Failure {} -> do+      -- Split input into chunks based on indentation.+      let chunks = layoutChunks $ dropComments codeString+      result <- joinFunctions <$> processChunks [] chunks++      -- Return to previous flags. When parsing, flags can be set to make+      -- sure parsing works properly. But we don't want those flags to be+      -- set during evaluation until the right time.+      setSessionDynFlags flags+      return result+  where+    parseChunk :: GhcMonad m => String -> LineNumber -> m (Located CodeBlock)+    parseChunk chunk line = Located line <$>+      if isDirective chunk+      then return $ parseDirective chunk line+      else parseCodeChunk chunk line++    processChunks :: GhcMonad m => [Located CodeBlock] -> [Located String] -> m [Located CodeBlock]+    processChunks accum remaining =+      case remaining of+        -- If we have no more remaining lines, return the accumulated results.+        [] -> return $ reverse accum++        -- If we have more remaining, parse the current chunk and recurse.+        Located line chunk:remaining ->  do+          block <- parseChunk chunk line+          activateParsingExtensions $ unloc block+          processChunks (block : accum) remaining++    -- Test wither a given chunk is a directive.+    isDirective :: String -> Bool+    isDirective = startswith ":" . strip++    -- Number of lines in this string.+    nlines :: String -> Int+    nlines = length . lines++activateParsingExtensions :: GhcMonad m => CodeBlock -> m ()+activateParsingExtensions (Directive SetExtension ext) = void $ setExtension ext+activateParsingExtensions _ = return ()++-- | Parse a single chunk of code, as indicated by the layout of the code.+parseCodeChunk :: GhcMonad m => String -> LineNumber  -> m CodeBlock+parseCodeChunk code startLine = do+    flags <- getSessionDynFlags+    let+        -- Try each parser in turn.+        rawResults = map (tryParser code) (parsers flags)++        -- Convert statements into expressions where we can+        results = map (statementToExpression flags) rawResults in+      case successes results of+        -- If none of them succeeded, choose the best error message to+        -- display. Only one of the error messages is actually relevant.+        [] -> return $ bestError $ failures results++        -- If one of the parsers succeeded+        result:_ -> return result+  where+    successes :: [ParseOutput a] -> [a]+    successes [] = []+    successes (Parsed a:rest) = a : successes rest+    successes (_:rest) = successes rest++    failures :: [ParseOutput a] -> [(ErrMsg, LineNumber, ColumnNumber)]+    failures [] = []+    failures (Failure msg (Loc line col):rest) = (msg, line, col) : failures rest+    failures (_:rest) = failures rest++    bestError :: [(ErrMsg, LineNumber, ColumnNumber)] -> CodeBlock+    bestError errors = ParseError (Loc (line + startLine - 1) col) msg+      where+        (msg, line, col) = maximumBy compareLoc errors+        compareLoc (_, line1, col1) (_, line2, col2) = compare line1 line2 <> compare col1 col2++    statementToExpression :: DynFlags -> ParseOutput CodeBlock -> ParseOutput CodeBlock+    statementToExpression flags (Parsed (Statement stmt)) = Parsed result+      where result = if isExpr flags stmt+                     then Expression stmt+                     else Statement stmt+    statementToExpression _ other = other++    -- Check whether a string is a valid expression.+    isExpr :: DynFlags -> String -> Bool+    isExpr flags str = case runParser flags parserExpression str of+      Parsed {} -> True+      _ -> False++    tryParser :: String -> (String -> CodeBlock, String -> ParseOutput String) -> ParseOutput CodeBlock+    tryParser string (blockType, parser) = case parser string of+      Parsed res -> Parsed (blockType res)+      Failure err loc -> Failure err loc++    parsers :: DynFlags -> [(String -> CodeBlock, String -> ParseOutput String)]+    parsers flags =+      [ (Import,        unparser parserImport)+      , (TypeSignature, unparser parserTypeSignature)+      , (Declaration,   unparser parserDeclaration)+      , (Statement,     unparser parserStatement)+      ]+      where+        unparser :: Parser a -> String -> ParseOutput String+        unparser parser code =+          case runParser flags parser code of+            Parsed out -> Parsed code+            Partial out strs -> Partial code strs+            Failure err loc -> Failure err loc++-- | Find consecutive declarations of the same function and join them into+-- a single declaration. These declarations may also include a type+-- signature, which is also joined with the subsequent declarations.+joinFunctions :: [Located CodeBlock] -> [Located CodeBlock]+joinFunctions (Located line (Declaration decl) : rest) =+    -- Find all declarations having the same name as this one.+    let (decls, other) = havingSameName rest in+      -- Convert them into a single declaration.+      Located line (Declaration (joinLines $ map (undecl . unloc) decls)) : joinFunctions other+  where+    undecl (Declaration decl) = decl+    undecl _ = error "Expected declaration!"++    -- Get all declarations with the same name as the first declaration.+    -- The name of a declaration is the first word, which we expect to be+    -- the name of the function.+    havingSameName :: [Located CodeBlock] -> ([Located CodeBlock], [Located CodeBlock]) +    havingSameName blocks =+      let name = head $ words decl+          sameName = takeWhile (isNamedDecl name) rest +          others = drop (length sameName) rest in+        (Located line (Declaration decl) : sameName, others)++    isNamedDecl :: String -> Located CodeBlock -> Bool+    isNamedDecl name (Located _ (Declaration dec)) = head (words dec) == name+    isNamedDecl _ _ = False++-- Allow a type signature followed by declarations to be joined to the+-- declarations. Parse the declaration joining separately.+joinFunctions (Located line (TypeSignature sig) : Located dl (Declaration decl) : rest) =+  Located line (Declaration $ sig ++ "\n" ++ joinedDecl):remaining+  where Located _ (Declaration joinedDecl):remaining = joinFunctions $ Located dl (Declaration decl) : rest +        +joinFunctions (x:xs) = x : joinFunctions xs+joinFunctions [] = []+++-- | Parse a directive of the form :directiveName.+parseDirective :: String       -- ^ Directive string.+               -> Int          -- ^ Line number at which the directive appears.+               -> CodeBlock    -- ^ Directive code block or a parse error.++parseDirective (':':'!':directive) line = Directive ShellCmd $ '!':directive+parseDirective (':':directive) line = case find rightDirective directives of+  Just (directiveType, _) -> Directive directiveType arg+    where arg = unwords restLine+          _:restLine = words directive+  Nothing -> +    let directiveStart = case words directive of+          [] -> ""+          first:_ -> first in+      ParseError (Loc line 1) $ "Unknown directive: '" ++ directiveStart ++ "'."+  where+    rightDirective (_, dirname) = case words directive of+      [] -> False+      dir:_ -> dir `elem` tail (inits dirname)+    directives =+      [(GetType,      "type")+      ,(GetInfo,      "info")+      ,(SearchHoogle, "hoogle")+      ,(GetDoc,       "documentation")+      ,(SetDynFlag,   "set")+      ,(LoadFile,     "load")+      ,(SetOption,    "option")+      ,(SetExtension, "extension")+      ,(GetHelp,      "?")+      ,(GetHelp,      "help")+      ]+parseDirective _ _ = error "Directive must start with colon!"+++-- | Split an input string into chunks based on indentation.+-- A chunk is a line and all lines immediately following that are indented+-- beyond the indentation of the first line. This parses Haskell layout+-- rules properly, and allows using multiline expressions via indentation. +layoutChunks :: String -> [Located String]+layoutChunks = go 1+  where+    go :: LineNumber -> String -> [Located String]+    go line = filter (not . null . unloc) . map (fmap strip) . layoutLines line . lines++    layoutLines :: LineNumber -> [String] -> [Located String]+    -- Empty string case.  If there's no input, output is empty.+    layoutLines _ [] = []++    -- Use the indent of the first line to find the end of the first block.+    layoutLines lineIdx all@(firstLine:rest) = +      let firstIndent = indentLevel firstLine+          blockEnded line = indentLevel line <= firstIndent in+        case findIndex blockEnded rest of+          -- If the first block doesn't end, return the whole string, since+          -- that just means the block takes up the entire string. +          Nothing -> [Located lineIdx $ intercalate "\n" all]++          -- We found the end of the block. Split this bit out and recurse.+          Just idx -> +            let (before, after) = splitAt idx rest in+              Located lineIdx (joinLines $ firstLine:before) : go (lineIdx + idx + 1) (joinLines after)++    -- Compute indent level of a string as number of leading spaces.+    indentLevel :: String -> Int+    indentLevel (' ':str) = 1 + indentLevel str++    -- Count a tab as two spaces.+    indentLevel ('\t':str) = 2 + indentLevel str+  +    -- Count empty lines as a large indent level, so they're always with the previous expression.+    indentLevel "" = 100000++    indentLevel _ = 0++-- Not the same as 'unlines', due to trailing \n+joinLines :: [String] -> String+joinLines = intercalate "\n"++-- | Drop comments from Haskell source.+dropComments :: String -> String+dropComments = removeOneLineComments . removeMultilineComments+  where+    -- Don't remove comments after cmd directives+    removeOneLineComments (':':'!':remaining) = ":!" ++ takeWhile (/= '\n') remaining ++ +      removeOneLineComments (dropWhile (/= '\n') remaining)+    removeOneLineComments ('-':'-':remaining) = removeOneLineComments (dropWhile (/= '\n') remaining)+    removeOneLineComments (x:xs) = x:removeOneLineComments xs+    removeOneLineComments x = x++    removeMultilineComments ('{':'-':remaining) = +      case subIndex "-}" remaining of+        Nothing -> ""+        Just idx -> removeMultilineComments $ drop (2 + idx) remaining+    removeMultilineComments (x:xs) = x:removeMultilineComments xs+    removeMultilineComments x = x++-- | Parse a module and return the name declared in the 'module X where'+-- line. That line is required, and if it does not exist, this will error.+-- Names with periods in them are returned piece y piece.+getModuleName :: GhcMonad m => String -> m [String]+getModuleName moduleSrc = do+  flags <- getSessionDynFlags+  let output = runParser flags parserModule moduleSrc+  case output of+    Failure {} -> error "Module parsing failed."+    Parsed mod -> +      case unLoc <$> hsmodName (unLoc mod) of+        Nothing -> error "Module must have a name."+        Just name -> return $ split "." $ moduleNameString name
+ src/IHaskell/Eval/Util.hs view
@@ -0,0 +1,48 @@+module IHaskell.Eval.Util (+  extensionFlag, setExtension,+  ExtFlag(..),+  ) where++-- GHC imports.+import GHC+import GhcMonad+import DynFlags++import Data.List (find)++data ExtFlag+     = SetFlag   ExtensionFlag+     | UnsetFlag ExtensionFlag++extensionFlag :: String -> Maybe ExtFlag+extensionFlag ext = +  case find (flagMatches ext) xFlags of+    Just (_, flag, _) -> Just $ SetFlag flag+    -- If it doesn't match an extension name, try matching against+    -- disabling an extension.+    Nothing ->+      case find (flagMatchesNo ext) xFlags of+        Just (_, flag, _) -> Just $ UnsetFlag flag+        Nothing -> Nothing++  where+    -- Check if a FlagSpec matches an extension name.+    flagMatches ext (name, _, _) = ext == name++    -- Check if a FlagSpec matches "No<ExtensionName>".+    -- In that case, we disable the extension.+    flagMatchesNo ext (name, _, _) = ext == "No"  ++ name++-- Set an extension and update flags.+-- Return Nothing on success. On failure, return an error message.+setExtension :: GhcMonad m => String -> m (Maybe String)+setExtension ext = do+  flags <- getSessionDynFlags+  case extensionFlag ext of+    Nothing -> return $ Just $ "Could not parse extension name: " ++ ext+    Just flag -> do+      setSessionDynFlags $ +        case flag of+          SetFlag ghcFlag -> xopt_set flags ghcFlag+          UnsetFlag ghcFlag -> xopt_unset flags ghcFlag+      return Nothing
+ src/IHaskell/Flags.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE NoImplicitPrelude #-}+module IHaskell.Flags ( +  IHaskellMode(..),+  Argument(..),+  Args(..),+  parseFlags,+  help,+  ) where++import ClassyPrelude+import System.Console.CmdArgs.Explicit+import System.Console.CmdArgs.Text++import IHaskell.Types++-- Command line arguments to IHaskell.  A set of aruments is annotated with+-- the mode being invoked.+data Args = Args IHaskellMode [Argument]++data Argument+  = ServeFrom String    -- ^ Which directory to serve notebooks from.+  | Extension String    -- ^ An extension to load at startup.+  | ConfFile String     -- ^ A file with commands to load at startup.+  | Help                -- ^ Display help text.+  deriving (Eq, Show)++-- Which mode IHaskell is being invoked in.+-- `None` means no mode was specified.+data IHaskellMode+  = ShowHelp String+  | Notebook+  | Console+  | UpdateIPython+  | Kernel (Maybe String)+  | View (Maybe ViewFormat) (Maybe String)+  deriving (Eq, Show)++-- | Given a list of command-line arguments, return the IHaskell mode and+-- arguments to process.+parseFlags :: [String] -> Either String Args+parseFlags = process ihaskellArgs++-- | Get help text for a given IHaskell ode.+help :: IHaskellMode -> String+help mode = +    showText (Wrap 100) $ helpText [] HelpFormatAll $ chooseMode mode+  where+    chooseMode Console = console+    chooseMode Notebook = notebook+    chooseMode (Kernel _) = kernel+    chooseMode UpdateIPython = update++universalFlags :: [Flag Args]+universalFlags = [+  flagReq ["extension","e", "X"] (store Extension) "<ghc-extension>" "Extension to enable at start.",+  flagReq ["conf","c"] (store ConfFile) "<file.hs>" "File with commands to execute at start.",+  flagHelpSimple (add Help)+  ]+  where +    add flag (Args mode flags) = Args mode $ flag : flags++store :: (String -> Argument) -> String -> Args -> Either String Args+store constructor str (Args mode prev) = Right $ Args mode $ constructor str : prev++notebook :: Mode Args+notebook = mode "notebook" (Args Notebook []) "Browser-based notebook interface." noArgs $+  flagReq ["serve","s"] (store ServeFrom) "<dir>" "Directory to serve notebooks from.":+  universalFlags++console :: Mode Args+console = mode "console" (Args Console []) "Console-based interactive repl." noArgs universalFlags++kernel = mode "kernel" (Args (Kernel Nothing) []) "Invoke the IHaskell kernel." kernelArg []+  where+    kernelArg = flagArg update "<json-kernel-file>"+    update filename (Args _ flags) = Right $ Args (Kernel $ Just filename) flags++update :: Mode Args+update = mode "update" (Args UpdateIPython []) "Update IPython frontends." noArgs []++view :: Mode Args+view = (modeEmpty $ Args (View Nothing Nothing) []) {+      modeNames = ["view"],+      modeCheck  =+        \a@(Args (View fmt file) _) ->+          if not (isJust fmt && isJust file)+          then Left "Syntax: IHaskell view <format> <name>[.ipynb]"+          else Right a,+      modeHelp = concat [+        "Convert an IHaskell notebook to another format.\n",+        "Notebooks are searched in the IHaskell directory and the current directory.\n",+        "Available formats are " ++ intercalate ", " (map show +          ["pdf", "html", "ipynb", "markdown", "latex"]),+        "."+      ],+      modeArgs = ([formatArg, filenameArg], Nothing)+                                                    +  }+  where+    formatArg = flagArg updateFmt "<format>"+    filenameArg = flagArg updateFile "<name>[.ipynb]"+    updateFmt fmtStr (Args (View _ s) flags) = +      case readMay fmtStr of+        Just fmt -> Right $ Args (View (Just fmt) s) flags+        Nothing -> Left $ "Invalid format '" ++ fmtStr ++ "'."+    updateFile name (Args (View f _) flags) = Right $ Args (View f (Just name)) flags+  ++ihaskellArgs :: Mode Args+ihaskellArgs =+  let descr = "Haskell for Interactive Computing." +      helpStr = showText (Wrap 100) $ helpText [] HelpFormatAll ihaskellArgs+      onlyHelp = [flagHelpSimple (add Help)]+      noMode = mode "IHaskell" (Args (ShowHelp helpStr) []) descr noArgs onlyHelp in+    noMode { modeGroupModes = toGroup [console, notebook, view, update, kernel] }+  where +    add flag (Args mode flags) = Args mode $ flag : flags++noArgs = flagArg unexpected ""+  where+    unexpected a = error $ "Unexpected argument: " ++ a
+ src/IHaskell/IPython.hs view
@@ -0,0 +1,411 @@+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}+{-# LANGUAGE DoAndIfThenElse #-}+-- | Description : Shell scripting wrapper using @Shelly@ for the @notebook@, @setup@, and+--                 @console@ commands.+module IHaskell.IPython (+  ipythonInstalled,+  installIPython,+  updateIPython,+  setupIPython,+  runConsole,+  runNotebook,+  readInitInfo,+  defaultConfFile,+  getIHaskellDir,+  getSandboxPackageConf,+  nbconvert,+  ViewFormat(..),+) where++import ClassyPrelude+import Prelude (read, reads, init)+import Shelly hiding (find, trace, path, (</>))+import System.Argv0+import System.Directory+import qualified Filesystem.Path.CurrentOS as FS+import Data.List.Utils (split)+import Data.String.Utils (rstrip, endswith)+import Text.Printf++import qualified System.IO.Strict as StrictIO+import qualified Paths_ihaskell as Paths+import qualified Codec.Archive.Tar as Tar++import IHaskell.Types++-- | Which commit of IPython we are on.+ipythonCommit :: Text+ipythonCommit = "9c922f54af799704f4000aeee94ec7c74cada194"++-- | The IPython profile name.+ipythonProfile :: String+ipythonProfile = "haskell"++-- | Run IPython with any arguments.+ipython :: Bool         -- ^ Whether to suppress output.+        -> [Text]       -- ^ IPython command line arguments.+        -> Sh String    -- ^ IPython output.+ipython suppress args = do+  ipythonPath <- ipythonExePath+  runHandles ipythonPath args handles doNothing+  where handles = [InHandle Inherit, outHandle suppress, errorHandle suppress]+        outHandle True = OutHandle CreatePipe+        outHandle False = OutHandle Inherit+        errorHandle True =  ErrorHandle CreatePipe+        errorHandle False = ErrorHandle Inherit+        doNothing _ stdout _ = if suppress +                                then liftIO $ StrictIO.hGetContents stdout+                                else return ""++-- | Run while suppressing all output.+quietRun path args = runHandles path args handles nothing+  where+    handles =  [InHandle Inherit, OutHandle CreatePipe, ErrorHandle CreatePipe]+    nothing _ _ _ = return ()++-- | Create the directory and return it.+ensure :: Sh FilePath -> Sh FilePath+ensure getDir = do+  dir <- getDir+  mkdir_p dir+  return dir++-- | Return the data directory for IHaskell.+ihaskellDir :: Sh FilePath+ihaskellDir = do+  home <- maybe (error "$HOME not defined.") fromText <$> get_env "HOME"+  ensure $ return (home </> ".ihaskell")++ipythonDir :: Sh FilePath+ipythonDir = ensure $ (</> "ipython") <$> ihaskellDir++ipythonExePath :: Sh FilePath+ipythonExePath = (</> ("bin" </> "ipython")) <$> ipythonDir++notebookDir :: Sh FilePath+notebookDir = ensure $ (</> "notebooks") <$> ihaskellDir++ipythonSourceDir :: Sh FilePath+ipythonSourceDir = ensure $ (</> "ipython-src") <$> ihaskellDir++getIHaskellDir :: IO String+getIHaskellDir = shellyNoDir $ fpToString <$> ihaskellDir++defaultConfFile :: IO (Maybe String)+defaultConfFile = shellyNoDir $ do+  filename <- (</> "rc.hs") <$> ihaskellDir+  exists <- test_f filename+  return $ if exists+           then Just $ fpToString filename+           else Nothing++-- | Find a notebook and then convert it into the provided format.+-- Notebooks are searched in the current directory as well as the IHaskell+-- notebook directory (in that order).+nbconvert :: ViewFormat -> String -> IO ()+nbconvert fmt name = void . shellyNoDir $ do+  curdir <- pwd+  nbdir <- notebookDir+  -- Find which of the options is available. +  let notebookOptions = [+        curdir </> fpFromString name,+        curdir </> fpFromString (name ++ ".ipynb"),+        nbdir  </> fpFromString name,+        nbdir  </> fpFromString (name ++ ".ipynb")+        ]+  maybeNb <- headMay <$> filterM test_f notebookOptions+  case maybeNb of+    Nothing -> do+      putStrLn $ "Cannot find notebook: " ++ pack name+      putStrLn "Tried:"+      mapM_ (putStrLn . ("  " ++) . fpToText) notebookOptions ++    Just notebook ->+      let viewArgs = case fmt of+            Pdf ->  ["--to=latex", "--post=pdf"]+            Html -> ["--to=html", "--template=ihaskell"]+            fmt ->  ["--to=" ++ show fmt] in+      void $ runIHaskell ipythonProfile "nbconvert" $ viewArgs ++ [fpToString notebook]++-- | Set up IPython properly.+setupIPython :: IO ()+setupIPython = do+  installed <- ipythonInstalled+  if installed+  then updateIPython+  else installIPython+  +  ++-- | Update the IPython source tree and rebuild.+updateIPython :: IO ()+updateIPython = void . shellyNoDir $ do+  srcDir <- ipythonSourceDir+  cd srcDir+  gitPath <- path "git"+  currentCommitHash <- silently $ pack <$> rstrip <$> unpack <$> run gitPath ["rev-parse", "HEAD"]+  when (currentCommitHash /= ipythonCommit) $ do+    putStrLn "Incorrect IPython repository commit hash."+    putStrLn $ "Found hash:  " ++ currentCommitHash +    putStrLn $ "Wanted hash: " ++ ipythonCommit +    putStrLn "Updating..."+    run_ gitPath ["pull", "origin", "master"]+    run_ gitPath ["checkout", ipythonCommit]+    installPipDependencies+    buildIPython++-- | Install IPython from source.+installIPython :: IO ()+installIPython = void . shellyNoDir $ do+  installPipDependencies++  -- Get the IPython source.+  gitPath <- path "git"+  putStrLn "Downloading IPython... (this may take a while)"+  ipythonSrcDir <- ipythonSourceDir+  run_ gitPath ["clone", "--recursive", "https://github.com/ipython/ipython.git", fpToText ipythonSrcDir]+  cd ipythonSrcDir+  run_ gitPath ["checkout", ipythonCommit]++  buildIPython++-- | Install all Python dependencies.+installPipDependencies :: Sh ()+installPipDependencies = withTmpDir $ \tmpDir -> +    mapM_ (installDependency tmpDir) +      [+        ("pyzmq", "14.0.1")+      , ("tornado","3.1.1")+      , ("jinja2","2.7.1")+      -- The following cannot go first in the dependency list, because+      -- their setup.py are broken and require the directory to exist+      -- already.+      , ("MarkupSafe", "0.18")+      --, ("setuptools", "2.0.2")+      ]+  where+    installDependency :: FilePath -> (Text, Text) -> Sh ()+    installDependency tmpDir (dep, version) = sub $ do+      let versioned = dep ++ "-" ++ version+      putStrLn $ "Installing dependency: " ++ versioned++      pipPath <- path "pip"+      tarPath <- path "tar"+      pythonPath <- path "python"++      -- Download the package.+      let downloadOpt = "--download=" ++ fpToText tmpDir+      run_ pipPath ["install", downloadOpt, dep ++ "==" ++ version]++      -- Extract it.+      cd tmpDir+      run_ tarPath ["-xzf", versioned ++ ".tar.gz"]++      -- Install it.+      cd $ fromText versioned+      dir <- fpToText <$> ipythonDir+      setenv "PYTHONPATH" $ dir ++ "/lib/python2.7/site-packages/"+      let prefixOpt =  "--prefix=" ++ dir+      run_ pythonPath ["setup.py", "install", prefixOpt]+++-- | Once things are checked out into the IPython source directory, build it and install it.+buildIPython :: Sh ()+buildIPython = do+  -- Install IPython locally.+  pythonPath <- path "python"+  prefixOpt <- ("--prefix=" ++) <$> fpToText <$> ipythonDir+  putStrLn "Installing IPython."+  run_ pythonPath ["setup.py", "install", prefixOpt]++  -- Patch the IPython executable so that it doesn't use system IPython.+  -- Using PYTHONPATH is not enough due to bugs in how `easy_install` sets+  -- things up, at least on Mac OS X.+  ipyDir <- ipythonDir+  let patchLines =+        [ "#!/usr/bin/env python"+        , "import sys"+        , "sys.path = [\"" ++ fpToText ipyDir +++         "/lib/python2.7/site-packages\"] + sys.path"]+  ipythonPath <- ipythonExePath+  contents <- readFile ipythonPath+  writeFile ipythonPath $ unlines patchLines ++ "\n" ++ contents++  -- Remove the old IPython profile so that we write a new one in its+  -- place. Users are not expected to fiddle with the profile, so we give+  -- no warning whatsoever. This may be changed eventually.+  removeIPythonProfile ipythonProfile+      ++-- | Check whether IPython is properly installed.+ipythonInstalled :: IO Bool+ipythonInstalled = shellyNoDir $ do+  ipythonPath <- ipythonExePath+  test_f ipythonPath++-- | Get the path to an executable. If it doensn't exist, fail with an+-- error message complaining about it.+path :: Text -> Sh FilePath+path exe = do+  path <- which $ fromText exe+  case path of+    Nothing -> do+      putStrLn $ "Could not find `" ++ exe ++ "` executable."+      fail $ "`" ++ unpack exe ++ "` not on $PATH."+    Just exePath -> return exePath++-- | Use the `ipython --version` command to figure out the version.+-- Return a tuple with (major, minor, patch).+ipythonVersion :: IO (Int, Int, Int)+ipythonVersion = shellyNoDir $ do+  [major, minor, patch] <- parseVersion <$>  ipython True ["--version"]+  return (major, minor, patch)++-- | Parse an IPython version string into a list of integers.+parseVersion :: String -> [Int]+parseVersion versionStr = map read' $ split "." versionStr+    where read' x = case reads x of+                        [(n, _)] -> n+                        _ -> error $ "cannot parse version: "++ versionStr++-- | Run an IHaskell application using the given profile.+runIHaskell :: String   -- ^ IHaskell profile name. +           -> String    -- ^ IPython app name.+           -> [String]  -- ^ Arguments to IPython.+           -> Sh ()+runIHaskell profile app args = void $ do+  -- Try to locate the profile. Do not die if it doesn't exist.+  errExit False $ ipython True ["locate", "profile", pack profile]++  -- If the profile doesn't exist, create it.+  -- We have an ugly hack that removes the profile whenever the IPython+  -- version is updated. This means profiles get updated with IPython.+  exitCode <- lastExitCode+  when (exitCode /= 0) $ liftIO $ do+    putStrLn "Creating IPython profile."+    setupIPythonProfile profile++  -- Run the IHaskell command.+  ipython False $ map pack $ [app, "--profile", profile] ++ args++runConsole :: InitInfo -> IO ()+runConsole initInfo = void . shellyNoDir $ do+  writeInitInfo initInfo+  runIHaskell ipythonProfile "console" []++runNotebook :: InitInfo -> Maybe String -> IO ()+runNotebook initInfo maybeServeDir = void . shellyNoDir $ do+  notebookDirStr <- fpToString <$> notebookDir+  let args = case maybeServeDir of +               Nothing -> ["--notebook-dir", unpack notebookDirStr]+               Just dir -> ["--notebook-dir", dir]++  writeInitInfo initInfo+  runIHaskell ipythonProfile "notebook" args++writeInitInfo :: InitInfo -> Sh ()+writeInitInfo info = do+  filename <- (</> ".last-arguments") <$> ihaskellDir+  liftIO $ writeFile filename $ show info++readInitInfo :: IO InitInfo+readInitInfo = shellyNoDir $ do+  filename <- (</>  ".last-arguments") <$> ihaskellDir+  read <$> liftIO (readFile filename)++-- | Create the IPython profile.+setupIPythonProfile :: String -- ^ IHaskell profile name.+                    -> IO ()+setupIPythonProfile profile = shellyNoDir $ do+  -- Create the IPython profile.+  void $ ipython True ["profile", "create", pack profile]++  -- Find the IPython profile directory. Make sure to get rid of trailing+  -- newlines from the output of the `ipython locate` call.+  ipythonDir <- pack <$> rstrip <$> ipython True ["locate"]+  let profileDir = ipythonDir ++ "/profile_" ++ pack profile ++ "/"++  liftIO $ copyProfile profileDir+  insertIHaskellPath profileDir++removeIPythonProfile :: String -> Sh ()+removeIPythonProfile profile = do+  -- Try to locate the profile. Do not die if it doesn't exist.+  errExit False $ ipython True ["locate", "profile", pack profile]++  -- If the profile exists, delete it.+  exitCode <- lastExitCode+  dir <- pack <$> rstrip <$> ipython True ["locate"]+  when (exitCode == 0 && dir /= "") $ do+    putStrLn "Updating IPython profile."+    let profileDir = dir ++ "/profile_" ++ pack profile ++ "/"+    rm_rf $ fromText profileDir++-- | Copy the profile files into the IPython profile. +copyProfile :: Text -> IO ()+copyProfile profileDir = do+  profileTar <- Paths.getDataFileName "profile/profile.tar"+  {-+  -- Load profile from Resources directory of Mac *.app.+  ihaskellPath <- shellyNoDir getIHaskellPath+  profileTar <- if "IHaskell.app/Contents/MacOS" `isInfixOf` ihaskellPath+               then+                let pieces = split "/" ihaskellPath+                    pathPieces = init pieces ++ ["..", "Resources", "profile.tar"] in+                  return $ intercalate "/" pathPieces+               else Paths.getDataFileName "profile/profile.tar"+  -}+  putStrLn $ pack $ "Loading profile from " ++ profileTar +  Tar.extract (unpack profileDir) profileTar ++-- | Insert the IHaskell path into the IPython configuration.+insertIHaskellPath :: Text -> Sh ()+insertIHaskellPath profileDir = do+  path <- getIHaskellPath+  let filename = profileDir ++ "ipython_config.py"+      template = "exe = '%s'.replace(' ', '\\\\ ')"+      exeLine = printf template $ unpack path :: String++  liftIO $ do+    contents <- StrictIO.readFile $ unpack filename+    writeFile (fromText filename) $ exeLine ++ "\n" ++ contents++-- | Get the absolute path to this IHaskell executable.+getIHaskellPath :: Sh String+getIHaskellPath = do+  --  Get the absolute filepath to the argument.+  f <- liftIO getArgv0++  -- If we have an absolute path, that's the IHaskell we're interested in.+  if FS.absolute f+  then return $ FS.encodeString f+  else+    -- Check whether this is a relative path, or just 'IHaskell' with $PATH+    -- resolution done by the shell. If it's just 'IHaskell', use the $PATH+    -- variable to find where IHaskell lives.+    if FS.filename f == f+    then do+      ihaskellPath <- which "IHaskell"+      case ihaskellPath of+        Nothing -> error "IHaskell not on $PATH and not referenced relative to directory."+        Just path -> return $ FS.encodeString path+    else do+      -- If it's actually a relative path, make it absolute.+      cd <- liftIO getCurrentDirectory+      return $ FS.encodeString $ FS.decodeString cd FS.</> f++getSandboxPackageConf :: IO (Maybe String)+getSandboxPackageConf = shellyNoDir $ do+  myPath <- getIHaskellPath+  let sandboxName = ".cabal-sandbox"+  if not $ sandboxName`isInfixOf` myPath+  then return Nothing+  else do+    let pieces = split "/" myPath+        sandboxDir = intercalate "/" $ takeWhile (/= sandboxName) pieces  ++ [sandboxName]+    subdirs <- ls $ fpFromString sandboxDir+    let confdirs = filter (endswith "packages.conf.d") $ map fpToString subdirs+    case confdirs of+      [] -> return Nothing+      dir:_ -> +        return $ Just dir
+ src/IHaskell/Types.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, PatternGuards #-}+{-# LANGUAGE DeriveDataTypeable, DeriveGeneric #-}+-- | Description : All message type definitions.+module IHaskell.Types (+  Message (..),+  MessageHeader (..),+  MessageType(..),+  Username,+  Metadata(..),+  replyType,+  ExecutionState (..),+  StreamType(..),+  MimeType(..),+  DisplayData(..),+  EvaluationResult(..),+  ExecuteReplyStatus(..),+  InitInfo(..),+  KernelState(..),+  LintStatus(..),+  Width, Height,+  FrontendType(..),+  ViewFormat(..),+  Display(..),+  defaultKernelState,+  extractPlain,+  kernelOpts,+  KernelOpt(..),+  ) where++import            ClassyPrelude+import qualified  Data.ByteString.Char8           as Char+import            Data.Serialize+import            GHC.Generics++import            Text.Read                       as Read hiding (pfail, String)+import            Text.ParserCombinators.ReadP++import IPython.Kernel++data ViewFormat+     = Pdf+     | Html+     | Ipynb+     | Markdown+     | Latex+     deriving Eq++instance Show ViewFormat where+  show Pdf         = "pdf"+  show Html        = "html"+  show Ipynb       = "ipynb"+  show Markdown    = "markdown"+  show Latex       = "latex"++instance Read ViewFormat where+  readPrec = Read.lift $ do+    str <- munch (const True)+    case str of+      "pdf" -> return Pdf+      "html" -> return Html+      "ipynb" -> return Ipynb+      "notebook" -> return Ipynb+      "latex" -> return Latex+      "markdown" -> return Markdown+      "md" -> return Markdown+      _ -> pfail++-- | Wrapper for ipython-kernel's DisplayData which allows sending multiple+-- results from the same expression.+data Display = Display [DisplayData]+             | ManyDisplay [Display]+             deriving (Show, Typeable, Generic)+instance Serialize Display++instance Monoid Display where+    mempty = Display []+    ManyDisplay a `mappend` ManyDisplay b = ManyDisplay (a ++ b)+    ManyDisplay a `mappend` b             = ManyDisplay (a ++ [b])+    a             `mappend` ManyDisplay b = ManyDisplay (a : b)+    a             `mappend` b             = ManyDisplay [a,b]++-- | All state stored in the kernel between executions.+data KernelState = KernelState+  { getExecutionCounter :: Int,+    getLintStatus :: LintStatus,  -- Whether to use hlint, and what arguments to pass it. +    getFrontend :: FrontendType,+    useSvg :: Bool,+    useShowErrors :: Bool,+    useShowTypes :: Bool+  }+  deriving Show++defaultKernelState :: KernelState+defaultKernelState = KernelState+  { getExecutionCounter = 1,+    getLintStatus = LintOn,+    getFrontend = IPythonConsole,+    useSvg = True,+    useShowErrors = False,+    useShowTypes = False+  }++data FrontendType+     = IPythonConsole+     | IPythonNotebook+     deriving (Show, Eq, Read)++-- | names the ways to update the IHaskell 'KernelState' by `:set`+-- ('getSetName') and `:option` ('getOptionName') directives+data KernelOpt = KernelOpt+    { getOptionName, getSetName :: [String],+      getUpdateKernelState :: KernelState -> KernelState }++kernelOpts :: [KernelOpt]+kernelOpts =+    [KernelOpt ["lint"]    [] $ \state -> state { getLintStatus = LintOn },+     KernelOpt ["no-lint"] [] $ \state -> state { getLintStatus = LintOff },+     KernelOpt ["svg"]     [] $ \state -> state { useSvg = True },+     KernelOpt ["no-svg"]  [] $ \state -> state { useSvg = False },+     KernelOpt ["show-types"]    ["+t"] $ \state -> state { useShowTypes = True },+     KernelOpt ["no-show-types"] ["-t"] $ \state -> state { useShowTypes = False },+     KernelOpt ["show-errors"]    [] $ \state -> state { useShowErrors = True },+     KernelOpt ["no-show-errors"] [] $ \state -> state { useShowErrors = False }]++-- | Initialization information for the kernel.+data InitInfo = InitInfo {+  extensions :: [String],   -- ^ Extensions to enable at start.+  initCells :: [String],    -- ^ Code blocks to run before start.+  initDir :: String,        -- ^ Which directory this kernel should pretend to operate in.+  frontend :: FrontendType  -- ^ What frontend this serves.+  }+  deriving (Show, Read)++-- | Current HLint status.+data LintStatus+     = LintOn+     | LintOff+     deriving (Eq, Show)+++-- | Output of evaluation.+data EvaluationResult =+  -- | An intermediate result which communicates what has been printed thus+  -- far.+  IntermediateResult {+    outputs :: Display      -- ^ Display outputs.+  }+  | FinalResult {+    outputs :: Display,     -- ^ Display outputs.+    pagerOut :: String            -- ^ Text to display in the IPython pager.+  }
+ src/Main.hs view
@@ -0,0 +1,326 @@+{-# LANGUAGE NoImplicitPrelude, CPP, OverloadedStrings, ScopedTypeVariables #-}+-- | Description : Argument parsing and basic messaging loop, using Haskell+--                 Chans to communicate with the ZeroMQ sockets. +module Main where+import ClassyPrelude hiding (liftIO)+import Prelude (last, read)+import Control.Concurrent.Chan+import Control.Concurrent (threadDelay)+import Data.Aeson+import Text.Printf+import System.Exit (exitSuccess)+import System.Directory++import qualified Data.Map as Map++import IHaskell.Types+import IPython.ZeroMQ+import qualified IPython.Message.UUID as UUID+import IHaskell.Eval.Evaluate+import IHaskell.Eval.Completion (complete)+import IHaskell.Eval.Info+import qualified Data.ByteString.Char8 as Chars+import IHaskell.IPython+import qualified IPython.Stdin as Stdin+import IHaskell.Flags++import GHC hiding (extensions, language)+import Outputable (showSDoc, ppr)++-- | Compute the GHC API version number using the dist/build/autogen/cabal_macros.h+ghcVersionInts :: [Int]+ghcVersionInts = map read . words . map dotToSpace $ VERSION_ghc+  where dotToSpace '.' = ' '+        dotToSpace x = x+++main ::  IO ()+main = do+  args <- parseFlags <$> map unpack <$> getArgs+  case args of+    Left errorMessage -> +      hPutStrLn stderr errorMessage+    Right args ->+      ihaskell args++ihaskell :: Args -> IO ()+-- If no mode is specified, print help text.+ihaskell (Args (ShowHelp help) _) = +  putStrLn $ pack help++-- Update IPython: remove then reinstall.+-- This is in case cabal updates IHaskell but the corresponding IPython+-- isn't updated. This is hard to detect since versions of IPython might+-- not change!+ihaskell (Args UpdateIPython _) = do+  setupIPython+  putStrLn "IPython updated."+    +ihaskell (Args Console flags) = showingHelp Console flags $ do+  setupIPython++  flags <- addDefaultConfFile flags+  info <- initInfo IPythonConsole flags+  runConsole info++ihaskell (Args (View (Just fmt) (Just name)) []) =+  nbconvert fmt name++ihaskell (Args Notebook flags) = showingHelp Notebook flags $ do+  setupIPython++  let server = case mapMaybe serveDir flags of+                 [] -> Nothing+                 xs -> Just $ last xs++  flags <- addDefaultConfFile flags++  undirInfo <- initInfo IPythonNotebook flags+  curdir <- getCurrentDirectory+  let info = undirInfo { initDir = curdir }++  runNotebook info server+  where+    serveDir (ServeFrom dir) = Just dir+    serveDir _ = Nothing++ihaskell (Args (Kernel (Just filename)) _) = do+  initInfo <- readInitInfo+  runKernel filename initInfo++-- | Add a conf file to the arguments if none exists.+addDefaultConfFile :: [Argument] -> IO [Argument]+addDefaultConfFile flags = do+  def <- defaultConfFile+  case (find isConfFile flags, def) of+    (Nothing, Just file) -> return $ ConfFile file : flags+    _ -> return flags+  where+    isConfFile (ConfFile _) = True+    isConfFile _ = False++showingHelp :: IHaskellMode -> [Argument] -> IO () -> IO ()+showingHelp mode flags act =+  case find (==Help) flags of+    Just _ ->+      putStrLn $ pack $ help mode+    Nothing ->+      act+ +-- | Parse initialization information from the flags.+initInfo :: FrontendType -> [Argument] -> IO InitInfo+initInfo front [] = return InitInfo { extensions = [], initCells = [], initDir = ".", frontend = front }+initInfo front (flag:flags) = do+  info <- initInfo front flags+  case flag of+    Extension ext -> return info { extensions = ext:extensions info }+    ConfFile filename -> do+      cell <- readFile (fpFromText $ pack filename)+      return info { initCells = cell:initCells info }+    _ -> return info++-- | Run the IHaskell language kernel.+runKernel :: String    -- ^ Filename of profile JSON file.+          -> InitInfo  -- ^ Initialization information from the invocation.+          -> IO ()+runKernel profileSrc initInfo = do+  setCurrentDirectory $ initDir initInfo++  -- Parse the profile file.+  Just profile <- liftM decode . readFile . fpFromText $ pack profileSrc++  -- Necessary for `getLine` and their ilk to work.+  dir <- getIHaskellDir+  Stdin.recordKernelProfile dir profile++  -- Serve on all sockets and ports defined in the profile.+  interface <- serveProfile profile++  -- Create initial state in the directory the kernel *should* be in.+  state <- initialKernelState+  modifyMVar_ state $ \kernelState -> return $+    kernelState { getFrontend = frontend initInfo }++  -- Receive and reply to all messages on the shell socket.+  interpret True $ do+    -- Initialize the context by evaluating everything we got from the  +    -- command line flags. This includes enabling some extensions and also+    -- running some code.+    let extLines = map (":extension " ++) $ extensions initInfo+        noPublish _ = return ()       +        evaluator line = do+          -- Create a new state each time.+          stateVar <- liftIO initialKernelState+          state <- liftIO $ takeMVar stateVar+          evaluate state line noPublish++    mapM_ evaluator extLines+    mapM_ evaluator $ initCells initInfo++    forever $ do+      -- Read the request from the request channel.+      request <- liftIO $ readChan $ shellRequestChannel interface++      -- Create a header for the reply.+      replyHeader <- createReplyHeader (header request)++      -- Create the reply, possibly modifying kernel state.+      oldState <- liftIO $ takeMVar state+      (newState, reply) <- replyTo interface request replyHeader oldState +      liftIO $ putMVar state newState++      -- Write the reply to the reply channel.+      liftIO $ writeChan (shellReplyChannel interface) reply++-- Initial kernel state.+initialKernelState :: IO (MVar KernelState)+initialKernelState =+  newMVar defaultKernelState++-- | Duplicate a message header, giving it a new UUID and message type.+dupHeader :: MessageHeader -> MessageType -> IO MessageHeader+dupHeader header messageType = do+  uuid <- liftIO UUID.random++  return header { messageId = uuid, msgType = messageType }++-- | Create a new message header, given a parent message header.+createReplyHeader :: MessageHeader -> Interpreter MessageHeader+createReplyHeader parent = do+  -- Generate a new message UUID.+  newMessageId <- liftIO UUID.random+  let repType = fromMaybe err (replyType $ msgType parent)+      err = error $ "No reply for message " ++ show (msgType parent)++  return MessageHeader {+    identifiers = identifiers parent,+    parentHeader = Just parent,+    metadata = Map.fromList [],+    messageId = newMessageId,+    sessionId = sessionId parent,+    username = username parent,+    msgType = repType+  }++-- | Compute a reply to a message. +replyTo :: ZeroMQInterface -> Message -> MessageHeader -> KernelState -> Interpreter (KernelState, Message)++-- Reply to kernel info requests with a kernel info reply. No computation+-- needs to be done, as a kernel info reply is a static object (all info is+-- hard coded into the representation of that message type).+replyTo _ KernelInfoRequest{} replyHeader state =+  return (state, KernelInfoReply {+    header = replyHeader,+    language = "haskell",+    versionList = ghcVersionInts+   })++-- Reply to a shutdown request by exiting the main thread.+-- Before shutdown, reply to the request to let the frontend know shutdown+-- is happening.+replyTo interface ShutdownRequest{restartPending = restartPending} replyHeader _ = liftIO $ do+    writeChan (shellReplyChannel interface) $ ShutdownReply replyHeader restartPending+    exitSuccess++-- Reply to an execution request. The reply itself does not require+-- computation, but this causes messages to be sent to the IOPub socket+-- with the output of the code in the execution request.+replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do+ -- Convenience function to send a message to the IOPub socket.+  let send msg = liftIO $ writeChan (iopubChannel interface) msg++  -- Log things so that we can use stdin.+  dir <- liftIO getIHaskellDir+  liftIO $ Stdin.recordParentHeader dir $ header req++  -- Notify the frontend that the kernel is busy computing.+  -- All the headers are copies of the reply header with a different+  -- message type, because this preserves the session ID, parent header,+  -- and other important information.+  busyHeader <- liftIO $ dupHeader replyHeader StatusMessage+  send $ PublishStatus busyHeader Busy++  -- Construct a function for publishing output as this is going.+  -- This function accepts a boolean indicating whether this is the final+  -- output and the thing to display. Store the final outputs in a list so+  -- that when we receive an updated non-final output, we can clear the+  -- entire output and re-display with the updated output.+  displayed    <- liftIO $ newMVar []+  updateNeeded <- liftIO $ newMVar False+  pagerOutput  <- liftIO $ newMVar ""+  let clearOutput = do+        header <- dupHeader replyHeader ClearOutputMessage+        send $ ClearOutput header True++      sendOutput (ManyDisplay manyOuts) = mapM_ sendOutput manyOuts+      sendOutput (Display outs) = do+        header <- dupHeader replyHeader DisplayDataMessage+        send $ PublishDisplayData header "haskell" outs++      publish :: EvaluationResult -> IO ()+      publish result = do+        let final = case result of+                      IntermediateResult {} -> False+                      FinalResult {} -> True+            outs = outputs result++        -- If necessary, clear all previous output and redraw.+        clear <- readMVar updateNeeded+        when clear $ do+          clearOutput+          disps <- readMVar displayed+          mapM_ sendOutput $ reverse disps++        -- Draw this message.+        sendOutput outs++        -- If this is the final message, add it to the list of completed+        -- messages. If it isn't, make sure we clear it later by marking+        -- update needed as true.+        modifyMVar_ updateNeeded (const $ return $ not final)+        when final $ do+          modifyMVar_ displayed (return . (outs:))++          -- If this has some pager output, store it for later.+          let pager = pagerOut result+          unless (null pager) $+            modifyMVar_ pagerOutput (return . (++ pager ++ "\n"))++  -- Run code and publish to the frontend as we go.+  let execCount = getExecutionCounter state+  updatedState <- evaluate state (Chars.unpack code) publish++  -- Notify the frontend that we're done computing.+  idleHeader <- liftIO $ dupHeader replyHeader StatusMessage+  send $ PublishStatus idleHeader Idle++  pager <- liftIO $ readMVar pagerOutput+  return (updatedState, ExecuteReply {+    header = replyHeader,+    pagerOutput = pager,+    executionCounter = execCount,+    status = Ok+  })+++replyTo _ req@CompleteRequest{} replyHeader state = do+    (matchedText, completions) <- complete (Chars.unpack $ getCodeLine req) (getCursorPos req)++    let reply =  CompleteReply replyHeader (map Chars.pack completions) (Chars.pack matchedText) (getCodeLine req) True+    return (state,  reply)++-- | Reply to the object_info_request message. Given an object name, return+-- | the associated type calculated by GHC.+replyTo _ ObjectInfoRequest{objectName=oname} replyHeader state = do+         docs <- info $ Chars.unpack oname+         let reply = ObjectInfoReply {+                        header = replyHeader,+                        objectName = oname, +                        objectFound = docs == "",+                        objectTypeString = Chars.pack docs,+                        objectDocString  = Chars.pack docs                    +                      }+         return (state, reply)+++