InteractiveUI.hs 87.8 KB
Newer Older
1
{-# OPTIONS -#include "Linker.h" #-}
2
3
4
5
-----------------------------------------------------------------------------
--
-- GHC Interactive User Interface
--
6
-- (c) The GHC Team 2005-2006
7
8
--
-----------------------------------------------------------------------------
9

10
module InteractiveUI ( interactiveUI, ghciWelcomeMsg ) where
11

12
13
#include "HsVersions.h"

mnislaih's avatar
mnislaih committed
14
import GhciMonad
15
import GhciTags
16
import Debugger
17

18
19
-- The GHC interface
import qualified GHC
20
import GHC              ( Session, LoadHowMuch(..), Target(..),  TargetId(..),
Simon Marlow's avatar
Simon Marlow committed
21
                          Module, ModuleName, TyThing(..), Phase,
22
                          BreakIndex, SrcSpan, Resume, SingleStep )
23
import PprTyThing
24
import DynFlags
25

26
import Packages
27
#ifdef USE_READLINE
28
29
import PackageConfig
import UniqFM
30
31
#endif

simonpj@microsoft.com's avatar
simonpj@microsoft.com committed
32
import HscTypes		( implicitTyThings )
33
import qualified RdrName ( getGRE_NameQualifier_maybes ) -- should this come via GHC?
34
import Outputable       hiding (printForUser, printForUserPartWay)
35
import Module           -- for ModuleEnv
36
import Name
37
import SrcLoc
38
39

-- Other random utilities
40
import Digraph
mnislaih's avatar
mnislaih committed
41
42
import BasicTypes hiding (isTopLevel)
import Panic      hiding (showException)
43
import Config
44
45
46
import StaticFlags
import Linker
import Util
simonpj@microsoft.com's avatar
simonpj@microsoft.com committed
47
48
import NameSet
import Maybes		( orElse )
49
import FastString
50
import Encoding
51

52
#ifndef mingw32_HOST_OS
53
import System.Posix hiding (getEnv)
sof's avatar
sof committed
54
55
#else
import GHC.ConsoleHandler ( flushConsole )
56
import qualified System.Win32
sof's avatar
sof committed
57
58
#endif

59
#ifdef USE_READLINE
60
import Control.Concurrent	( yield )	-- Used in readline loop
61
import System.Console.Readline as Readline
62
#endif
63
64
65
66

--import SystemExts

import Control.Exception as Exception
67
-- import Control.Concurrent
68

69
import System.FilePath
Simon Marlow's avatar
Simon Marlow committed
70
import qualified Data.ByteString.Char8 as BS
71
import Data.List
72
import Data.Maybe
73
74
import System.Cmd
import System.Environment
75
import System.Exit	( exitWith, ExitCode(..) )
76
import System.Directory
ross's avatar
ross committed
77
78
import System.IO
import System.IO.Error as IO
79
import Data.Char
mnislaih's avatar
mnislaih committed
80
import Data.Dynamic
81
import Data.Array
82
import Control.Monad as Monad
Simon Marlow's avatar
Simon Marlow committed
83
import Text.Printf
84
import Foreign
Ian Lynagh's avatar
Ian Lynagh committed
85
import Foreign.C
86
import GHC.Exts		( unsafeCoerce# )
Simon Marlow's avatar
Simon Marlow committed
87
import GHC.IOBase	( IOErrorType(InvalidArgument) )
Ian Lynagh's avatar
Ian Lynagh committed
88
import GHC.TopHandler
89

90
import Data.IORef	( IORef, readIORef, writeIORef )
91

92
#ifdef USE_READLINE
93
import System.Posix.Internals ( setNonBlockingFD )
94
#endif
95

96
97
-----------------------------------------------------------------------------

98
99
100
ghciWelcomeMsg :: String
ghciWelcomeMsg = "GHCi, version " ++ cProjectVersion ++
                 ": http://www.haskell.org/ghc/  :? for help"
101

Simon Marlow's avatar
Simon Marlow committed
102
cmdName :: Command -> String
103
cmdName (n,_,_,_) = n
104

Simon Marlow's avatar
Simon Marlow committed
105
106
macros_ref :: IORef [Command]
GLOBAL_VAR(macros_ref, [], [Command])
Simon Marlow's avatar
Simon Marlow committed
107
108

builtin_commands :: [Command]
109
builtin_commands = [
110
	-- Hugs users are accustomed to :e, so make sure it doesn't overlap
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
  ("?",		keepGoing help,			Nothing, completeNone),
  ("add",	keepGoingPaths addModule,	Just filenameWordBreakChars, completeFilename),
  ("abandon",   keepGoing abandonCmd,           Nothing, completeNone),
  ("break",     keepGoing breakCmd,             Nothing, completeIdentifier),
  ("back",      keepGoing backCmd,              Nothing, completeNone),
  ("browse",    keepGoing (browseCmd False),	Nothing, completeModule),
  ("browse!",   keepGoing (browseCmd True),	Nothing, completeModule),
  ("cd",    	keepGoing changeDirectory,	Just filenameWordBreakChars, completeFilename),
  ("check",	keepGoing checkModule,		Nothing, completeHomeModule),
  ("continue",  keepGoing continueCmd,          Nothing, completeNone),
  ("cmd",       keepGoing cmdCmd,               Nothing, completeIdentifier),
  ("ctags",	keepGoing createCTagsFileCmd, 	Just filenameWordBreakChars, completeFilename),
  ("def",	keepGoing (defineMacro False),  Nothing, completeIdentifier),
  ("def!",	keepGoing (defineMacro True),   Nothing, completeIdentifier),
  ("delete",    keepGoing deleteCmd,            Nothing, completeNone),
  ("e", 	keepGoing editFile,		Just filenameWordBreakChars, completeFilename),
  ("edit",	keepGoing editFile,		Just filenameWordBreakChars, completeFilename),
  ("etags",	keepGoing createETagsFileCmd,	Just filenameWordBreakChars, completeFilename),
  ("force",     keepGoing forceCmd,             Nothing, completeIdentifier),
  ("forward",   keepGoing forwardCmd,           Nothing, completeNone),
  ("help",	keepGoing help,			Nothing, completeNone),
  ("history",   keepGoing historyCmd,           Nothing, completeNone), 
  ("info",      keepGoing info,			Nothing, completeIdentifier),
  ("kind",	keepGoing kindOfType,		Nothing, completeIdentifier),
  ("load",	keepGoingPaths loadModule_,     Just filenameWordBreakChars, completeHomeModuleOrFile),
  ("list",	keepGoing listCmd,              Nothing, completeNone),
  ("module",	keepGoing setContext,		Nothing, completeModule),
  ("main",	keepGoing runMain,		Nothing, completeIdentifier),
  ("print",     keepGoing printCmd,             Nothing, completeIdentifier),
  ("quit",	quit,				Nothing, completeNone),
  ("reload", 	keepGoing reloadModule,  	Nothing, completeNone),
Ian Lynagh's avatar
Ian Lynagh committed
142
  ("run",	keepGoing runRun,		Nothing, completeIdentifier),
143
144
145
146
147
148
149
150
151
152
  ("set",	keepGoing setCmd,		Just flagWordBreakChars, completeSetOptions),
  ("show",	keepGoing showCmd,		Nothing, completeNone),
  ("sprint",    keepGoing sprintCmd,            Nothing, completeIdentifier),
  ("step",      keepGoing stepCmd,              Nothing, completeIdentifier), 
  ("steplocal", keepGoing stepLocalCmd,         Nothing, completeIdentifier), 
  ("stepmodule",keepGoing stepModuleCmd,        Nothing, completeIdentifier), 
  ("type",	keepGoing typeOfExpr,		Nothing, completeIdentifier),
  ("trace",     keepGoing traceCmd,             Nothing, completeIdentifier), 
  ("undef",     keepGoing undefineMacro,	Nothing, completeMacro),
  ("unset",	keepGoing unsetOptions,		Just flagWordBreakChars,  completeSetOptions)
153
154
  ]

155
156
157
158
159
160
161
162
163

-- We initialize readline (in the interactiveUI function) to use 
-- word_break_chars as the default set of completion word break characters.
-- This can be overridden for a particular command (for example, filename
-- expansion shouldn't consider '/' to be a word break) by setting the third
-- entry in the Command tuple above.
-- 
-- NOTE: in order for us to override the default correctly, any custom entry
-- must be a SUBSET of word_break_chars.
164
165
#ifdef USE_READLINE
word_break_chars :: String
166
167
168
169
word_break_chars = let symbols = "!#$%&*+/<=>?@\\^|-~"
                       specials = "(),;[]`{}"
                       spaces = " \t\n"
                   in spaces ++ specials ++ symbols
170
171
172
#endif

flagWordBreakChars, filenameWordBreakChars :: String
173
174
175
176
flagWordBreakChars = " \t\n"
filenameWordBreakChars = " \t\n\\`@$><=;|&{(" -- bash defaults


177
178
179
keepGoing :: (String -> GHCi ()) -> (String -> GHCi Bool)
keepGoing a str = a str >> return False

sof's avatar
sof committed
180
keepGoingPaths :: ([FilePath] -> GHCi ()) -> (String -> GHCi Bool)
Ian Lynagh's avatar
Ian Lynagh committed
181
182
183
184
185
keepGoingPaths a str
 = do case toArgs str of
          Left err -> io (hPutStrLn stderr err)
          Right args -> a args
      return False
sof's avatar
sof committed
186

Simon Marlow's avatar
Simon Marlow committed
187
shortHelpText :: String
188
189
shortHelpText = "use :? for help.\n"

Simon Marlow's avatar
Simon Marlow committed
190
helpText :: String
191
192
193
helpText =
 " Commands available from the prompt:\n" ++
 "\n" ++
194
 "   <statement>                 evaluate/run <statement>\n" ++
195
 "   :                           repeat last command\n" ++
196
 "   :{\\n ..lines.. \\n:}\\n       multiline command\n" ++
197
 "   :add <filename> ...         add module(s) to the current target set\n" ++
198
199
 "   :browse[!] [[*]<mod>]       display the names defined by module <mod>\n" ++
 "                               (!: more details; *: all top-level names)\n" ++
200
 "   :cd <dir>                   change directory to <dir>\n" ++
201
 "   :cmd <expr>                 run the commands returned by <expr>::IO String\n" ++
Simon Marlow's avatar
Simon Marlow committed
202
 "   :ctags [<file>]             create tags file for Vi (default: \"tags\")\n" ++
203
 "   :def <cmd> <expr>           define a command :<cmd>\n" ++
Simon Marlow's avatar
Simon Marlow committed
204
205
 "   :edit <file>                edit file\n" ++
 "   :edit                       edit last module\n" ++
Simon Marlow's avatar
Simon Marlow committed
206
 "   :etags [<file>]             create tags file for Emacs (default: \"TAGS\")\n" ++
207
208
 "   :help, :?                   display this list of commands\n" ++
 "   :info [<name> ...]          display information about the given names\n" ++
Simon Marlow's avatar
Simon Marlow committed
209
 "   :kind <type>                show the kind of <type>\n" ++
210
 "   :load <filename> ...        load module(s) and their dependents\n" ++
211
 "   :main [<arguments> ...]     run the main function with the given arguments\n" ++
212
 "   :module [+/-] [*]<mod> ...  set the context for expression evaluation\n" ++
Simon Marlow's avatar
Simon Marlow committed
213
 "   :quit                       exit GHCi\n" ++
214
 "   :reload                     reload the current module set\n" ++
Ian Lynagh's avatar
Ian Lynagh committed
215
 "   :run function [<arguments> ...] run the function with the given arguments\n" ++
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
 "   :type <expr>                show the type of <expr>\n" ++
 "   :undef <cmd>                undefine user-defined command :<cmd>\n" ++
 "   :!<command>                 run the shell command <command>\n" ++
 "\n" ++
 " -- Commands for debugging:\n" ++
 "\n" ++
 "   :abandon                    at a breakpoint, abandon current computation\n" ++
 "   :back                       go back in the history (after :trace)\n" ++
 "   :break [<mod>] <l> [<col>]  set a breakpoint at the specified location\n" ++
 "   :break <name>               set a breakpoint on the specified function\n" ++
 "   :continue                   resume after a breakpoint\n" ++
 "   :delete <number>            delete the specified breakpoint\n" ++
 "   :delete *                   delete all breakpoints\n" ++
 "   :force <expr>               print <expr>, forcing unevaluated parts\n" ++
 "   :forward                    go forward in the history (after :back)\n" ++
231
 "   :history [<n>]              after :trace, show the execution history\n" ++
232
 "   :print [<name> ...]         prints a value without forcing its computation\n" ++
Simon Marlow's avatar
Simon Marlow committed
233
 "   :sprint [<name> ...]        simplifed version of :print\n" ++
234
235
 "   :step                       single-step after stopping at a breakpoint\n"++
 "   :step <expr>                single-step into <expr>\n"++
236
 "   :steplocal                  single-step within the current top-level binding\n"++
237
 "   :stepmodule                 single-step restricted to the current module\n"++
238
 "   :trace                      trace after stopping at a breakpoint\n"++
239
 "   :trace <expr>               evaluate <expr> with tracing on (see :history)\n"++
240
241
242

 "\n" ++
 " -- Commands for changing settings:\n" ++
243
244
245
246
 "\n" ++
 "   :set <option> ...           set options\n" ++
 "   :set args <arg> ...         set the arguments returned by System.getArgs\n" ++
 "   :set prog <progname>        set the value returned by System.getProgName\n" ++
Simon Marlow's avatar
Simon Marlow committed
247
 "   :set prompt <prompt>        set the prompt used in GHCi\n" ++
Ian Lynagh's avatar
Ian Lynagh committed
248
 "   :set editor <cmd>           set the command used for :edit\n" ++
Simon Marlow's avatar
Simon Marlow committed
249
 "   :set stop <cmd>             set the command to run when a breakpoint is hit\n" ++
250
251
 "   :unset <option> ...         unset options\n" ++
 "\n" ++
252
 "  Options for ':set' and ':unset':\n" ++
253
254
255
256
257
 "\n" ++
 "    +r            revert top-level expressions after each evaluation\n" ++
 "    +s            print timing/memory stats after each evaluation\n" ++
 "    +t            print type after evaluation\n" ++
 "    -<flags>      most GHC command line flags can also be set here\n" ++
mnislaih's avatar
mnislaih committed
258
 "                         (eg. -v2, -fglasgow-exts, etc.)\n" ++
259
260
 "                    for GHCi-specific flags, see User's Guide,\n"++
 "                    Flag reference, Interactive-mode options\n" ++
261
262
263
264
265
266
267
 "\n" ++
 " -- Commands for displaying information:\n" ++
 "\n" ++
 "   :show bindings              show the current bindings made at the prompt\n" ++
 "   :show breaks                show the active breakpoints\n" ++
 "   :show context               show the breakpoint context\n" ++
 "   :show modules               show the currently loaded modules\n" ++
268
269
 "   :show packages              show the currently active package flags\n" ++
 "   :show languages             show the currently active language flags\n" ++
270
271
 "   :show <setting>             show value of <setting>, which is one of\n" ++
 "                                  [args, prog, prompt, editor, stop]\n" ++
272
 "\n" 
273

Simon Marlow's avatar
Simon Marlow committed
274
findEditor :: IO String
Simon Marlow's avatar
Simon Marlow committed
275
276
277
findEditor = do
  getEnv "EDITOR" 
    `IO.catch` \_ -> do
278
#if mingw32_HOST_OS
Ian Lynagh's avatar
Ian Lynagh committed
279
280
        win <- System.Win32.getWindowsDirectory
        return (win </> "notepad.exe")
Simon Marlow's avatar
Simon Marlow committed
281
#else
Ian Lynagh's avatar
Ian Lynagh committed
282
        return ""
Simon Marlow's avatar
Simon Marlow committed
283
284
#endif

Ian Lynagh's avatar
Ian Lynagh committed
285
286
287
interactiveUI :: Session -> [(FilePath, Maybe Phase)] -> Maybe [String]
              -> IO ()
interactiveUI session srcs maybe_exprs = do
288
289
290
291
292
293
294
295
296
297
298
299
   -- HACK! If we happen to get into an infinite loop (eg the user
   -- types 'let x=x in x' at the prompt), then the thread will block
   -- on a blackhole, and become unreachable during GC.  The GC will
   -- detect that it is unreachable and send it the NonTermination
   -- exception.  However, since the thread is unreachable, everything
   -- it refers to might be finalized, including the standard Handles.
   -- This sounds like a bug, but we don't have a good solution right
   -- now.
   newStablePtr stdin
   newStablePtr stdout
   newStablePtr stderr

Ian Lynagh's avatar
Ian Lynagh committed
300
    -- Initialise buffering for the *interpreted* I/O system
301
   initInterpBuffering session
302

Ian Lynagh's avatar
Ian Lynagh committed
303
   when (isNothing maybe_exprs) $ do
Ian Lynagh's avatar
Ian Lynagh committed
304
305
306
307
308
309
310
311
312
313
314
        -- Only for GHCi (not runghc and ghc -e):

        -- Turn buffering off for the compiled program's stdout/stderr
        turnOffBuffering
        -- Turn buffering off for GHCi's stdout
        hFlush stdout
        hSetBuffering stdout NoBuffering
        -- We don't want the cmd line to buffer any input that might be
        -- intended for the program, so unbuffer stdin.
        hSetBuffering stdin NoBuffering

315
#ifdef USE_READLINE
316
317
318
        is_tty <- hIsTerminalDevice stdin
        when is_tty $ do
            Readline.initialize
319
320
321
322
323

            withGhcAppData
                 (\dir -> Readline.readHistory (dir </> "ghci_history"))
                 (return True)
            
324
325
326
327
328
329
            Readline.setAttemptedCompletionFunction (Just completeWord)
            --Readline.parseAndBind "set show-all-if-ambiguous 1"

            Readline.setBasicWordBreakCharacters word_break_chars
            Readline.setCompleterWordBreakCharacters word_break_chars
            Readline.setCompletionAppendCharacter Nothing
330
331
#endif

332
333
334
335
336
   -- initial context is just the Prelude
   prel_mod <- GHC.findModule session (GHC.mkModuleName "Prelude") 
                                      (Just basePackageId)
   GHC.setContext session [] [prel_mod]

Simon Marlow's avatar
Simon Marlow committed
337
338
   default_editor <- findEditor

Ian Lynagh's avatar
Ian Lynagh committed
339
   startGHCi (runGHCi srcs maybe_exprs)
Ian Lynagh's avatar
Ian Lynagh committed
340
341
        GHCiState{ progname = "<interactive>",
                   args = [],
Simon Marlow's avatar
Simon Marlow committed
342
                   prompt = "%s> ",
Simon Marlow's avatar
Simon Marlow committed
343
                   stop = "",
Ian Lynagh's avatar
Ian Lynagh committed
344
345
346
                   editor = default_editor,
                   session = session,
                   options = [],
mnislaih's avatar
mnislaih committed
347
                   prelude = prel_mod,
348
349
                   break_ctr = 0,
                   breaks = [],
350
                   tickarrays = emptyModuleEnv,
351
                   last_command = Nothing,
Simon Marlow's avatar
Simon Marlow committed
352
                   cmdqueue = [],
353
                   remembered_ctx = []
mnislaih's avatar
mnislaih committed
354
                 }
rrt's avatar
rrt committed
355

356
#ifdef USE_READLINE
357
358
359
   Readline.stifleHistory 100
   withGhcAppData (\dir -> Readline.writeHistory (dir </> "ghci_history"))
                  (return True)
rrt's avatar
rrt committed
360
361
362
   Readline.resetTerminal Nothing
#endif

363
364
   return ()

365
366
367
368
369
370
371
372
withGhcAppData :: (FilePath -> IO a) -> IO a -> IO a
withGhcAppData right left = do
   either_dir <- IO.try (getAppUserDataDirectory "ghc")
   case either_dir of
      Right dir -> right dir
      _ -> left


Ian Lynagh's avatar
Ian Lynagh committed
373
374
runGHCi :: [(FilePath, Maybe Phase)] -> Maybe [String] -> GHCi ()
runGHCi paths maybe_exprs = do
375
376
  let 
   read_dot_files = not opt_IgnoreDotGhci
377

378
379
   current_dir = return (Just ".ghci")

380
381
382
   app_user_dir = io $ withGhcAppData 
                    (\dir -> return (Just (dir </> "ghci.conf")))
                    (return Nothing)
383
384
385
386
387
388
389
390
391
392
393
394
395

   home_dir = do
    either_dir <- io $ IO.try (getEnv "HOME")
    case either_dir of
      Right home -> return (Just (home </> ".ghci"))
      _ -> return Nothing

   sourceConfigFile :: FilePath -> GHCi ()
   sourceConfigFile file = do
     exists <- io $ doesFileExist file
     when exists $ do
       dir_ok  <- io $ checkPerms (getDirectory file)
       file_ok <- io $ checkPerms file
396
       when (dir_ok && file_ok) $ do
397
398
399
400
401
402
         either_hdl <- io $ IO.try (openFile file ReadMode)
         case either_hdl of
           Left _e   -> return ()
           Right hdl -> runCommands (fileLoop hdl False False)
     where
      getDirectory f = case takeDirectory f of "" -> "."; d -> d
Ian Lynagh's avatar
Ian Lynagh committed
403

404
  when (read_dot_files) $ do
405
406
407
408
409
    cfgs0 <- sequence [ current_dir, app_user_dir, home_dir ]
    cfgs <- io $ mapM canonicalizePath (catMaybes cfgs0)
    mapM_ sourceConfigFile (nub cfgs)
        -- nub, because we don't want to read .ghci twice if the
        -- CWD is $HOME.
410

411
  -- Perform a :load for files given on the GHCi command line
412
413
414
  -- When in -e mode, if the load fails then we want to stop
  -- immediately rather than going on to evaluate the expression.
  when (not (null paths)) $ do
Ian Lynagh's avatar
Ian Lynagh committed
415
416
     ok <- ghciHandle (\e -> do showException e; return Failed) $
                loadModule paths
Ian Lynagh's avatar
Ian Lynagh committed
417
     when (isJust maybe_exprs && failed ok) $
Ian Lynagh's avatar
Ian Lynagh committed
418
        io (exitWith (ExitFailure 1))
419

420
421
  -- if verbosity is greater than 0, or we are connected to a
  -- terminal, display the prompt in the interactive loop.
422
  is_tty <- io (hIsTerminalDevice stdin)
423
  dflags <- getDynFlags
424
425
  let show_prompt = verbosity dflags > 0 || is_tty

Ian Lynagh's avatar
Ian Lynagh committed
426
  case maybe_exprs of
Ian Lynagh's avatar
Ian Lynagh committed
427
        Nothing ->
sof's avatar
sof committed
428
          do
Simon Marlow's avatar
Simon Marlow committed
429
#if defined(mingw32_HOST_OS)
Ian Lynagh's avatar
Ian Lynagh committed
430
            -- The win32 Console API mutates the first character of
sof's avatar
sof committed
431
432
433
434
            -- type-ahead when reading from it in a non-buffered manner. Work
            -- around this by flushing the input buffer of type-ahead characters,
            -- but only if stdin is available.
            flushed <- io (IO.try (GHC.ConsoleHandler.flushConsole stdin))
Ian Lynagh's avatar
Ian Lynagh committed
435
436
437
438
            case flushed of
             Left err | isDoesNotExistError err -> return ()
                      | otherwise -> io (ioError err)
             Right () -> return ()
sof's avatar
sof committed
439
#endif
Ian Lynagh's avatar
Ian Lynagh committed
440
441
            -- enter the interactive loop
            interactiveLoop is_tty show_prompt
Ian Lynagh's avatar
Ian Lynagh committed
442
        Just exprs -> do
Ian Lynagh's avatar
Ian Lynagh committed
443
            -- just evaluate the expression we were given
Ian Lynagh's avatar
Ian Lynagh committed
444
            enqueueCommands exprs
Ian Lynagh's avatar
Ian Lynagh committed
445
446
447
448
449
450
451
452
453
454
455
456
            let handle e = do st <- getGHCiState
                                   -- Jump through some hoops to get the
                                   -- current progname in the exception text:
                                   -- <progname>: <exception>
                              io $ withProgName (progname st)
                                   -- The "fast exit" part just calls exit()
                                   -- directly instead of doing an orderly
                                   -- runtime shutdown, otherwise the main
                                   -- GHCi thread will complain about being
                                   -- interrupted.
                                 $ topHandlerFastExit e
            runCommands' handle (return Nothing)
457
458

  -- and finally, exit
459
  io $ do when (verbosity dflags > 0) $ putStrLn "Leaving GHCi."
460

Simon Marlow's avatar
Simon Marlow committed
461
interactiveLoop :: Bool -> Bool -> GHCi ()
462
interactiveLoop is_tty show_prompt =
463
  -- Ignore ^C exceptions caught here
464
  ghciHandleDyn (\e -> case e of 
465
			Interrupted -> do
sof's avatar
sof committed
466
#if defined(mingw32_HOST_OS)
467
				io (putStrLn "")
sof's avatar
sof committed
468
#endif
469
470
471
472
473
				interactiveLoop is_tty show_prompt
			_other      -> return ()) $ 

  ghciUnblock $ do -- unblock necessary if we recursed from the 
		   -- exception handler above.
474

475
  -- read commands from stdin
476
#ifdef USE_READLINE
477
  if (is_tty) 
478
	then runCommands readlineLoop
479
	else runCommands (fileLoop stdin show_prompt is_tty)
480
#else
481
  runCommands (fileLoop stdin show_prompt is_tty)
482
#endif
483
484


485
-- NOTE: We only read .ghci files if they are owned by the current user,
486
487
488
-- and aren't world writable.  Otherwise, we could be accidentally 
-- running code planted by a malicious third party.

rrt's avatar
rrt committed
489
490
491
492
-- Furthermore, We only read ./.ghci if . is owned by the current user
-- and isn't writable by anyone else.  I think this is sufficient: we
-- don't need to check .. and ../.. etc. because "."  always refers to
-- the same directory while a process is running.
493
494

checkPerms :: String -> IO Bool
495
#ifdef mingw32_HOST_OS
Simon Marlow's avatar
Simon Marlow committed
496
checkPerms _ =
497
  return True
sof's avatar
sof committed
498
#else
Simon Marlow's avatar
Simon Marlow committed
499
checkPerms name =
500
  Util.handle (\_ -> return False) $ do
501
502
503
504
505
506
507
508
509
510
511
512
513
514
     st <- getFileStatus name
     me <- getRealUserID
     if fileOwner st /= me then do
   	putStrLn $ "WARNING: " ++ name ++ " is owned by someone else, IGNORING!"
   	return False
      else do
   	let mode =  fileMode st
   	if (groupWriteMode == (mode `intersectFileModes` groupWriteMode))
   	   || (otherWriteMode == (mode `intersectFileModes` otherWriteMode)) 
   	   then do
   	       putStrLn $ "*** WARNING: " ++ name ++ 
   			  " is writable by someone else, IGNORING!"
   	       return False
   	  else return True
sof's avatar
sof committed
515
#endif
516

517
518
fileLoop :: Handle -> Bool -> Bool -> GHCi (Maybe String)
fileLoop hdl show_prompt is_tty = do
519
520
521
   when show_prompt $ do
        prompt <- mkPrompt
        (io (putStr prompt))
522
523
   l <- io (IO.try (hGetLine hdl))
   case l of
524
525
526
527
528
529
530
531
        Left e | isEOFError e              -> return Nothing
               | InvalidArgument <- etype  -> return Nothing
               | otherwise                 -> io (ioError e)
                where etype = ioeGetErrorType e
                -- treat InvalidArgument in the same way as EOF:
                -- this can happen if the user closed stdin, or
                -- perhaps did getContents which closes stdin at
                -- EOF.
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
        Right l -> do
                   str <- io $ consoleInputToUnicode is_tty l
                   return (Just str)

#ifdef mingw32_HOST_OS
-- Convert the console input into Unicode according to the current code page.
-- The Windows console stores Unicode characters directly, so this is a
-- rather roundabout way of doing things... oh well.
-- See #782, #1483, #1649
consoleInputToUnicode :: Bool -> String -> IO String
consoleInputToUnicode is_tty str
  | is_tty = do
    cp <- System.Win32.getConsoleCP
    System.Win32.stringToUnicode cp str
  | otherwise =
    decodeStringAsUTF8 str
#else
-- for Unix, assume the input is in UTF-8 and decode it to a Unicode String. 
-- See #782.
consoleInputToUnicode :: Bool -> String -> IO String
consoleInputToUnicode _is_tty str = decodeStringAsUTF8 str
#endif

decodeStringAsUTF8 :: String -> IO String
decodeStringAsUTF8 str =
  withCStringLen str $ \(cstr,len) -> 
    utf8DecodeString (castPtr cstr :: Ptr Word8) len
559

Simon Marlow's avatar
Simon Marlow committed
560
mkPrompt :: GHCi String
561
562
563
564
mkPrompt = do
  session <- getSession
  (toplevs,exports) <- io (GHC.getContext session)
  resumes <- io $ GHC.getResumeContext session
Simon Marlow's avatar
Simon Marlow committed
565
  -- st <- getGHCiState
566
567
568
569

  context_bit <-
        case resumes of
            [] -> return empty
Simon Marlow's avatar
Simon Marlow committed
570
            r:_ -> do
571
572
573
574
575
                let ix = GHC.resumeHistoryIx r
                if ix == 0
                   then return (brackets (ppr (GHC.resumeSpan r)) <> space)
                   else do
                        let hist = GHC.resumeHistory r !! (ix-1)
576
                        span <- io$ GHC.getHistorySpan session hist
577
578
579
                        return (brackets (ppr (negate ix) <> char ':' 
                                          <+> ppr span) <> space)
  let
Simon Marlow's avatar
Simon Marlow committed
580
        dots | _:rs <- resumes, not (null rs) = text "... "
581
582
             | otherwise = empty

Simon Marlow's avatar
Simon Marlow committed
583
584
        

585
        modules_bit = 
Simon Marlow's avatar
Simon Marlow committed
586
587
588
589
590
       -- ToDo: maybe...
       --  let (btoplevs, bexports) = fromMaybe ([],[]) (remembered_ctx st) in
       --  hsep (map (\m -> text "!*" <> ppr (GHC.moduleName m)) btoplevs) <+>
       --  hsep (map (\m -> char '!'  <> ppr (GHC.moduleName m)) bexports) <+>
             hsep (map (\m -> char '*'  <> ppr (GHC.moduleName m)) toplevs) <+>
591
592
             hsep (map (ppr . GHC.moduleName) exports)

593
594
595
596
597
598
599
600
601
        deflt_prompt = dots <> context_bit <> modules_bit

        f ('%':'s':xs) = deflt_prompt <> f xs
        f ('%':'%':xs) = char '%' <> f xs
        f (x:xs) = char x <> f xs
        f [] = empty
   --
  st <- getGHCiState
  return (showSDoc (f (prompt st)))
602

603

604
#ifdef USE_READLINE
605
readlineLoop :: GHCi (Maybe String)
606
readlineLoop = do
607
   io yield
Simon Marlow's avatar
Simon Marlow committed
608
   saveSession -- for use by completion
609
610
   prompt <- mkPrompt
   l <- io (readline prompt `finally` setNonBlockingFD 0)
611
612
                -- readline sometimes puts stdin into blocking mode,
                -- so we need to put it back for the IO library
Simon Marlow's avatar
Simon Marlow committed
613
   splatSavedSession
614
   case l of
615
        Nothing -> return Nothing
616
        Just "" -> return (Just "") -- Don't put empty lines in the history
617
618
        Just l  -> do
                   io (addHistory l)
619
620
                   str <- io $ consoleInputToUnicode True l
                   return (Just str)
621
#endif
622

623
624
625
626
627
628
629
630
631
queryQueue :: GHCi (Maybe String)
queryQueue = do
  st <- getGHCiState
  case cmdqueue st of
    []   -> return Nothing
    c:cs -> do setGHCiState st{ cmdqueue = cs }
               return (Just c)

runCommands :: GHCi (Maybe String) -> GHCi ()
632
633
634
635
636
runCommands = runCommands' handler

runCommands' :: (Exception -> GHCi Bool) -- Exception handler
             -> GHCi (Maybe String) -> GHCi ()
runCommands' eh getCmd = do
637
638
639
640
641
  mb_cmd <- noSpace queryQueue
  mb_cmd <- maybe (noSpace getCmd) (return . Just) mb_cmd
  case mb_cmd of 
    Nothing -> return ()
    Just c  -> do
642
      b <- ghciHandle eh (doCommand c)
643
      if b then return () else runCommands' eh getCmd
644
  where
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
    noSpace q = q >>= maybe (return Nothing)
                            (\c->case removeSpaces c of 
                                   ""   -> noSpace q
                                   ":{" -> multiLineCmd q
                                   c    -> return (Just c) )
    multiLineCmd q = do
      st <- getGHCiState
      let p = prompt st
      setGHCiState st{ prompt = "%s| " }
      mb_cmd <- collectCommand q ""
      getGHCiState >>= \st->setGHCiState st{ prompt = p }
      return mb_cmd
    -- we can't use removeSpaces for the sublines here, so 
    -- multiline commands are somewhat more brittle against
    -- fileformat errors (such as \r in dos input on unix), 
    -- we get rid of any extra spaces for the ":}" test; 
    -- we also avoid silent failure if ":}" is not found;
    -- and since there is no (?) valid occurrence of \r (as 
    -- opposed to its String representation, "\r") inside a
    -- ghci command, we replace any such with ' ' (argh:-(
    collectCommand q c = q >>= 
      maybe (io (ioError collectError))
            (\l->if removeSpaces l == ":}" 
                 then return (Just $ removeSpaces c) 
                 else collectCommand q (c++map normSpace l))
      where normSpace '\r' = ' '
            normSpace   c  = c
    -- QUESTION: is userError the one to use here?
    collectError = userError "unterminated multiline command :{ .. :}"
    doCommand (':' : cmd) = specialCommand cmd
    doCommand stmt        = do timeIt $ runStmt stmt GHC.RunToCompletion
                               return False
677
678
679
680
681
682

enqueueCommands :: [String] -> GHCi ()
enqueueCommands cmds = do
  st <- getGHCiState
  setGHCiState st{ cmdqueue = cmds ++ cmdqueue st }

683

684
685
runStmt :: String -> SingleStep -> GHCi Bool
runStmt stmt step
686
 | null (filter (not.isSpace) stmt) = return False
687
 | ["import", mod] <- words stmt    = keepGoing setContext ('+':mod)
688
 | otherwise
689
 = do st <- getGHCiState
690
691
      session <- getSession
      result <- io $ withProgName (progname st) $ withArgs (args st) $
692
	     	     GHC.runStmt session stmt step
693
      afterRunStmt (const True) result
694

695

696
--afterRunStmt :: GHC.RunResult -> GHCi Bool
Simon Marlow's avatar
Simon Marlow committed
697
                                 -- False <=> the statement failed to compile
Simon Marlow's avatar
Simon Marlow committed
698
afterRunStmt :: (SrcSpan -> Bool) -> GHC.RunResult -> GHCi Bool
699
afterRunStmt _ (GHC.RunException e) = throw e
700
afterRunStmt step_here run_result = do
701
702
  session     <- getSession
  resumes <- io $ GHC.getResumeContext session
Simon Marlow's avatar
Simon Marlow committed
703
704
705
  case run_result of
     GHC.RunOk names -> do
        show_types <- isOptionSet ShowType
706
        when show_types $ printTypeOfNames session names
707
708
     GHC.RunBreak _ names mb_info 
         | isNothing  mb_info || 
709
           step_here (GHC.resumeSpan $ head resumes) -> do
710
711
               printForUser $ ptext SLIT("Stopped at") <+> 
                       ppr (GHC.resumeSpan $ head resumes)
712
--               printTypeOfNames session names
713
714
715
               let namesSorted = sortBy compareNames names
               tythings <- catMaybes `liftM` 
                              io (mapM (GHC.lookupName session) namesSorted)
716
717
               docs <- io$ pprTypeAndContents session [id | AnId id <- tythings]
               printForUserPartWay docs
718
719
720
721
722
723
               maybe (return ()) runBreakCmd mb_info
               -- run the command set with ":set stop <cmd>"
               st <- getGHCiState
               enqueueCommands [stop st]
               return ()
         | otherwise -> io(GHC.resume session GHC.SingleStep) >>= 
724
                        afterRunStmt step_here >> return ()
Simon Marlow's avatar
Simon Marlow committed
725
726
     _ -> return ()

727
728
729
730
731
  flushInterpBuffers
  io installSignalHandlers
  b <- isOptionSet RevertCAFs
  io (when b revertCAFs)

Simon Marlow's avatar
Simon Marlow committed
732
  return (case run_result of GHC.RunOk _ -> True; _ -> False)
733

734
735
736
737
738
runBreakCmd :: GHC.BreakInfo -> GHCi ()
runBreakCmd info = do
  let mod = GHC.breakInfo_module info
      nm  = GHC.breakInfo_number info
  st <- getGHCiState
Simon Marlow's avatar
Simon Marlow committed
739
  case  [ loc | (_,loc) <- breaks st,
740
741
742
743
744
                breakModule loc == mod, breakTick loc == nm ] of
        []  -> return ()
        loc:_ | null cmd  -> return ()
              | otherwise -> do enqueueCommands [cmd]; return ()
              where cmd = onBreakCmd loc
745

746
747
printTypeOfNames :: Session -> [Name] -> GHCi ()
printTypeOfNames session names
748
749
750
751
 = mapM_ (printTypeOfName session) $ sortBy compareNames names

compareNames :: Name -> Name -> Ordering
n1 `compareNames` n2 = compareWith n1 `compare` compareWith n2
752
753
754
755
    where compareWith n = (getOccString n, getSrcSpan n)

printTypeOfName :: Session -> Name -> GHCi ()
printTypeOfName session n
756
   = do maybe_tything <- io (GHC.lookupName session n)
757
758
759
        case maybe_tything of
            Nothing    -> return ()
            Just thing -> printTyThing thing
760

761

762
data MaybeCommand = GotCommand Command | BadCommand | NoLastCommand
763

764
specialCommand :: String -> GHCi Bool
765
specialCommand ('!':str) = shellEscape (dropWhile isSpace str)
766
767
specialCommand str = do
  let (cmd,rest) = break isSpace str
768
  maybe_cmd <- lookupCommand cmd
Simon Marlow's avatar
Simon Marlow committed
769
  case maybe_cmd of
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
    GotCommand (_,f,_,_) -> f (dropWhile isSpace rest)
    BadCommand ->
      do io $ hPutStr stdout ("unknown command ':" ++ cmd ++ "'\n"
                           ++ shortHelpText)
         return False
    NoLastCommand ->
      do io $ hPutStr stdout ("there is no last command to perform\n"
                           ++ shortHelpText)
         return False

lookupCommand :: String -> GHCi (MaybeCommand)
lookupCommand "" = do
  st <- getGHCiState
  case last_command st of
      Just c -> return $ GotCommand c
      Nothing -> return NoLastCommand
Simon Marlow's avatar
Simon Marlow committed
786
lookupCommand str = do
787
788
789
790
791
792
793
794
795
  mc <- io $ lookupCommand' str
  st <- getGHCiState
  setGHCiState st{ last_command = mc }
  return $ case mc of
           Just c -> GotCommand c
           Nothing -> BadCommand

lookupCommand' :: String -> IO (Maybe Command)
lookupCommand' str = do
Simon Marlow's avatar
Simon Marlow committed
796
797
  macros <- readIORef macros_ref
  let cmds = builtin_commands ++ macros
Simon Marlow's avatar
Simon Marlow committed
798
  -- look for exact match first, then the first prefix match
799
800
801
802
803
  return $ case [ c | c <- cmds, str == cmdName c ] of
           c:_ -> Just c
           [] -> case [ c | c@(s,_,_,_) <- cmds, str `isPrefixOf` s ] of
                 [] -> Nothing
                 c:_ -> Just c
804
805
806
807
808
809
810

getCurrentBreakSpan :: GHCi (Maybe SrcSpan)
getCurrentBreakSpan = do
  session <- getSession
  resumes <- io $ GHC.getResumeContext session
  case resumes of
    [] -> return Nothing
Simon Marlow's avatar
Simon Marlow committed
811
    (r:_) -> do
812
813
814
815
816
817
818
819
        let ix = GHC.resumeHistoryIx r
        if ix == 0
           then return (Just (GHC.resumeSpan r))
           else do
                let hist = GHC.resumeHistory r !! (ix-1)
                span <- io $ GHC.getHistorySpan session hist
                return (Just span)

820
821
822
823
824
825
getCurrentBreakModule :: GHCi (Maybe Module)
getCurrentBreakModule = do
  session <- getSession
  resumes <- io $ GHC.getResumeContext session
  case resumes of
    [] -> return Nothing
Simon Marlow's avatar
Simon Marlow committed
826
    (r:_) -> do
827
828
        let ix = GHC.resumeHistoryIx r
        if ix == 0
mnislaih's avatar
mnislaih committed
829
           then return (GHC.breakInfo_module `liftM` GHC.resumeBreakInfo r)
830
831
832
833
           else do
                let hist = GHC.resumeHistory r !! (ix-1)
                return $ Just $ GHC.getHistoryModule  hist

834
835
836
-----------------------------------------------------------------------------
-- Commands

837
838
noArgs :: GHCi () -> String -> GHCi ()
noArgs m "" = m
Simon Marlow's avatar
Simon Marlow committed
839
noArgs _ _  = io $ putStrLn "This command takes no arguments"
840

841
842
843
help :: String -> GHCi ()
help _ = io (putStr helpText)

rrt's avatar
rrt committed
844
info :: String -> GHCi ()
845
info "" = throwDyn (CmdLineError "syntax: ':i <thing-you-want-info-about>'")
846
info s  = do { let names = words s
847
	     ; session <- getSession
848
	     ; dflags <- getDynFlags
849
850
	     ; let pefas = dopt Opt_PrintExplicitForalls dflags
	     ; mapM_ (infoThing pefas session) names }
851
  where
852
    infoThing pefas session str = io $ do
simonpj@microsoft.com's avatar
simonpj@microsoft.com committed
853
854
	names     <- GHC.parseName session str
	mb_stuffs <- mapM (GHC.getInfo session) names
Simon Marlow's avatar
Simon Marlow committed
855
	let filtered = filterOutChildren (\(t,_f,_i) -> t) (catMaybes mb_stuffs)
856
857
858
	unqual <- GHC.getPrintUnqual session
	putStrLn (showSDocForUser unqual $
     		   vcat (intersperse (text "") $
simonpj@microsoft.com's avatar
simonpj@microsoft.com committed
859
		         map (pprInfo pefas) filtered))
860
861
862
863

  -- Filter out names whose parent is also there Good
  -- example is '[]', which is both a type and data
  -- constructor in the same type
simonpj@microsoft.com's avatar
simonpj@microsoft.com committed
864
865
866
867
868
filterOutChildren :: (a -> TyThing) -> [a] -> [a]
filterOutChildren get_thing xs 
  = [x | x <- xs, not (getName (get_thing x) `elemNameSet` implicits)]
  where
    implicits = mkNameSet [getName t | x <- xs, t <- implicitTyThings (get_thing x)]
869

870
871
872
pprInfo :: PrintExplicitForalls -> (TyThing, Fixity, [GHC.Instance]) -> SDoc
pprInfo pefas (thing, fixity, insts)
  =  pprTyThingInContextLoc pefas thing
873
874
  $$ show_fixity fixity
  $$ vcat (map GHC.pprInstance insts)
875
  where
876
    show_fixity fix 
877
878
	| fix == GHC.defaultFixity = empty
	| otherwise		   = ppr fix <+> ppr (GHC.getName thing)
879

880
runMain :: String -> GHCi ()
Ian Lynagh's avatar
Ian Lynagh committed
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
runMain s = case toArgs s of
            Left err   -> io (hPutStrLn stderr err)
            Right args ->
                do dflags <- getDynFlags
                   case mainFunIs dflags of
                       Nothing -> doWithArgs args "main"
                       Just f  -> doWithArgs args f

runRun :: String -> GHCi ()
runRun s = case toCmdArgs s of
           Left err          -> io (hPutStrLn stderr err)
           Right (cmd, args) -> doWithArgs args cmd

doWithArgs :: [String] -> String -> GHCi ()
doWithArgs args cmd = enqueueCommands ["System.Environment.withArgs " ++
                                       show args ++ " (" ++ cmd ++ ")"]
897

sof's avatar
sof committed
898
899
addModule :: [FilePath] -> GHCi ()
addModule files = do
900
  io (revertCAFs)			-- always revert CAFs on load/add.
901
  files <- mapM expandPath files
902
  targets <- mapM (\m -> io (GHC.guessTarget m Nothing)) files
903
904
  session <- getSession
  io (mapM_ (GHC.addTarget session) targets)
Simon Marlow's avatar
Simon Marlow committed
905
  prev_context <- io $ GHC.getContext session
906
  ok <- io (GHC.load session LoadAllTargets)
Simon Marlow's avatar
Simon Marlow committed
907
  afterLoad ok session False prev_context
908

909
changeDirectory :: String -> GHCi ()
910
911
912
913
914
915
changeDirectory "" = do
  -- :cd on its own changes to the user's home directory
  either_dir <- io (IO.try getHomeDirectory)
  case either_dir of
     Left _e -> return ()
     Right dir -> changeDirectory dir
916
changeDirectory dir = do
917
918
919
  session <- getSession
  graph <- io (GHC.getModuleGraph session)
  when (not (null graph)) $
920
	io $ putStr "Warning: changing directory causes all loaded modules to be unloaded,\nbecause the search path has changed.\n"
Simon Marlow's avatar
Simon Marlow committed
921
  prev_context <- io $ GHC.getContext session
922
  io (GHC.setTargets session [])
923
  io (GHC.load session LoadAllTargets)
924
  setContextAfterLoad session prev_context False []
925
  io (GHC.workingDirectoryChanged session)
926
927
  dir <- expandPath dir
  io (setCurrentDirectory dir)
928

Simon Marlow's avatar
Simon Marlow committed
929
editFile :: String -> GHCi ()
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
editFile str =
  do file <- if null str then chooseEditFile else return str
     st <- getGHCiState
     let cmd = editor st
     when (null cmd) 
       $ throwDyn (CmdLineError "editor not set, use :set editor")
     io $ system (cmd ++ ' ':file)
     return ()

-- The user didn't specify a file so we pick one for them.
-- Our strategy is to pick the first module that failed to load,
-- or otherwise the first target.
--
-- XXX: Can we figure out what happened if the depndecy analysis fails
--      (e.g., because the porgrammeer mistyped the name of a module)?
-- XXX: Can we figure out the location of an error to pass to the editor?
-- XXX: if we could figure out the list of errors that occured during the
-- last load/reaload, then we could start the editor focused on the first
-- of those.
chooseEditFile :: GHCi String
chooseEditFile =
  do session <- getSession
     let hasFailed x = io $ fmap not $ GHC.isLoaded session $ GHC.ms_mod_name x

     graph <- io (GHC.getModuleGraph session)
     failed_graph <- filterM hasFailed graph
     let order g  = flattenSCCs $ GHC.topSortModuleGraph True g Nothing
         pick xs  = case xs of
                      x : _ -> GHC.ml_hs_file (GHC.ms_location x)
                      _     -> Nothing

     case pick (order failed_graph) of
       Just file -> return file
       Nothing   -> 
         do targets <- io (GHC.getTargets session)
            case msum (map fromTarget targets) of
              Just file -> return file
              Nothing   -> throwDyn (CmdLineError "No files to edit.")
          
  where fromTarget (GHC.Target (GHC.TargetFile f _) _) = Just f
        fromTarget _ = Nothing -- when would we get a module target?
Simon Marlow's avatar
Simon Marlow committed
971

Simon Marlow's avatar
Simon Marlow committed
972
973
defineMacro :: Bool{-overwrite-} -> String -> GHCi ()
defineMacro overwrite s = do
974
  let (macro_name, definition) = break isSpace s
Simon Marlow's avatar
Simon Marlow committed
975
976
  macros <- io (readIORef macros_ref)
  let defined = map cmdName macros
977
  if (null macro_name) 
Simon Marlow's avatar
Simon Marlow committed
978
979
980
981
	then if null defined
                then io $ putStrLn "no macros defined"
                else io $ putStr ("the following macros are defined:\n" ++
                                  unlines defined)
982
	else do
Simon Marlow's avatar
Simon Marlow committed
983
  if (not overwrite && macro_name `elem` defined)
984
	then throwDyn (CmdLineError 
Simon Marlow's avatar
Simon Marlow committed
985
		("macro '" ++ macro_name ++ "' is already defined"))
986
987
	else do

Simon Marlow's avatar
Simon Marlow committed
988
989
  let