Function definition in GHCi
authorRoman Shatsov <roshats@gmail.com>
Sat, 21 Nov 2015 14:58:34 +0000 (15:58 +0100)
committerBen Gamari <ben@smart-cactus.org>
Sat, 21 Nov 2015 16:15:09 +0000 (11:15 -0500)
This patch allows define and re-define functions in ghci. `let` is not
required anymore (but can be used).

Idea: If ghci input string can be parsed as statement then run it as
statement else run it as declaration.

Reviewers: mpickering, bgamari, austin

Reviewed By: mpickering, bgamari, austin

Subscribers: hvr, mpickering, dterei, thomie

Differential Revision: https://phabricator.haskell.org/D1299

GHC Trac Issues: #7253

docs/users_guide/7.12.1-notes.rst
docs/users_guide/ghci.rst
ghc/GhciMonad.hs
ghc/InteractiveUI.hs
testsuite/tests/ghci/should_run/T7253.script [new file with mode: 0644]
testsuite/tests/ghci/should_run/T7253.stderr [new file with mode: 0644]
testsuite/tests/ghci/should_run/T7253.stdout [new file with mode: 0644]
testsuite/tests/ghci/should_run/T9915.stderr [deleted file]
testsuite/tests/ghci/should_run/all.T
testsuite/tests/safeHaskell/ghci/p14.stderr

index f3c0ed4..67e2b0f 100644 (file)
@@ -164,6 +164,8 @@ GHCi
 
 -  ``ghci -e`` now behaves like ``ghc -e`` (#9360).
 
+-  Added support for top-level function declarations (#7253).
+
 Template Haskell
 ~~~~~~~~~~~~~~~~
 
index 0cb3a71..beb946e 100644 (file)
@@ -462,12 +462,11 @@ with the ``let`` form, the expression isn't evaluated immediately:
 Note that ``let`` bindings do not automatically print the value bound,
 unlike monadic bindings.
 
-You can also use ``let``-statements to define functions at the
-prompt:
+You can also define functions at the prompt:
 
 ::
 
-    Prelude> let add a b = a + b
+    Prelude> add a b = a + b
     Prelude> add 1 2
     3
     Prelude>
@@ -479,7 +478,7 @@ instead of layout:
 
 ::
 
-    Prelude> let f op n [] = n ; f op n (h:t) = h `op` f op n t
+    Prelude> f op n [] = n ; f op n (h:t) = h `op` f op n t
     Prelude> f (+) 0 [1..3]
     6
     Prelude>
@@ -491,8 +490,8 @@ own):
 ::
 
     Prelude> :{
-    Prelude| let g op n [] = n
-    Prelude|     g op n (h:t) = h `op` g op n t
+    Prelude| g op n [] = n
+    Prelude| g op n (h:t) = h `op` g op n t
     Prelude| :}
     Prelude> g (*) 1 [1..3]
     6
@@ -877,7 +876,7 @@ arguments, e.g.:
 
 ::
 
-    Prelude> let main = System.Environment.getArgs >>= print
+    Prelude> main = System.Environment.getArgs >>= print
     Prelude> :main foo bar
     ["foo","bar"]
 
@@ -897,8 +896,8 @@ flag or the ``:run`` command:
 
 ::
 
-    Prelude> let foo = putStrLn "foo" >> System.Environment.getArgs >>= print
-    Prelude> let bar = putStrLn "bar" >> System.Environment.getArgs >>= print
+    Prelude> foo = putStrLn "foo" >> System.Environment.getArgs >>= print
+    Prelude> bar = putStrLn "bar" >> System.Environment.getArgs >>= print
     Prelude> :set -main-is foo
     Prelude> :main foo "bar baz"
     foo
@@ -2318,7 +2317,7 @@ commonly used commands.
 
     ::
 
-        Prelude> let main = System.Environment.getArgs >>= print
+        Prelude> main = System.Environment.getArgs >>= print
         Prelude> :main foo bar
         ["foo","bar"]
 
@@ -2338,8 +2337,8 @@ commonly used commands.
 
     ::
 
-        Prelude> let foo = putStrLn "foo" >> System.Environment.getArgs >>= print
-        Prelude> let bar = putStrLn "bar" >> System.Environment.getArgs >>= print
+        Prelude> foo = putStrLn "foo" >> System.Environment.getArgs >>= print
+        Prelude> bar = putStrLn "bar" >> System.Environment.getArgs >>= print
         Prelude> :set -main-is foo
         Prelude> :main foo "bar baz"
         foo
index 7dd005b..6d068be 100644 (file)
@@ -19,7 +19,7 @@ module GhciMonad (
         TickArray,
         getDynFlags,
 
-        runStmt, runDecls, resume, timeIt, recordBreak, revertCAFs,
+        isStmt, runStmt, runDecls, resume, timeIt, recordBreak, revertCAFs,
 
         printForUser, printForUserPartWay, prettyLocations,
         initInterpBuffering, turnOffBuffering, flushInterpBuffers,
@@ -50,6 +50,10 @@ import System.IO
 import Control.Monad
 import GHC.Exts
 
+import qualified Lexer (ParseResult(..), unP, mkPState)
+import qualified Parser (parseStmt)
+import StringBuffer (stringToStringBuffer)
+
 import System.Console.Haskeline (CompletionFunc, InputT)
 import qualified System.Console.Haskeline as Haskeline
 import Control.Monad.Trans.Class
@@ -262,6 +266,19 @@ printForUserPartWay doc = do
   dflags <- getDynFlags
   liftIO $ Outputable.printForUserPartWay dflags stdout (pprUserLength dflags) unqual doc
 
+isStmt :: String -> GHCi Bool
+isStmt stmt = do
+  st <- getGHCiState
+  dflags <- GHC.getInteractiveDynFlags
+
+  let buf = stringToStringBuffer stmt
+      loc = mkRealSrcLoc (fsLit "<interactive>") (line_number st) 1
+      parser = Parser.parseStmt
+
+  case Lexer.unP parser (Lexer.mkPState dflags buf loc) of
+    Lexer.POk _ _ -> return True
+    Lexer.PFailed _ _ -> return False
+
 -- | Run a single Haskell expression
 runStmt :: String -> GHC.SingleStep -> GHCi (Maybe GHC.ExecResult)
 runStmt expr step = do
index 8f861ee..e5c4e11 100644 (file)
@@ -897,22 +897,6 @@ enqueueCommands cmds = do
   cmds `deepseq` return ()
   modifyGHCiState $ \st -> st{ cmdqueue = cmds ++ cmdqueue st }
 
--- | If we one of these strings prefixes a command, then we treat it as a decl
--- rather than a stmt. NB that the appropriate decl prefixes depends on the
--- flag settings (Trac #9915)
-declPrefixes :: DynFlags -> [String]
-declPrefixes dflags = keywords ++ concat opt_keywords
-  where
-    keywords = [ "class ", "instance "
-               , "data ", "newtype ", "type "
-               , "default ", "default("
-               ]
-
-    opt_keywords = [ ["foreign "  | xopt Opt_ForeignFunctionInterface dflags]
-                   , ["deriving " | xopt Opt_StandaloneDeriving dflags]
-                   , ["pattern "  | xopt Opt_PatternSynonyms dflags]
-                   ]
-
 -- | Entry point to execute some haskell code from user.
 -- The return value True indicates success, as in `runOneCommand`.
 runStmt :: String -> SingleStep -> GHCi (Maybe GHC.ExecResult)
@@ -927,10 +911,11 @@ runStmt stmt step
  = do addImportToContext stmt; return (Just (GHC.ExecComplete (Right []) 0))
 
  | otherwise
- = do dflags <- getDynFlags
-      if any (stmt `looks_like`) (declPrefixes dflags)
-        then run_decl
-        else run_stmt
+ = do
+     parse_res <- GhciMonad.isStmt stmt
+     if parse_res
+       then run_stmt
+       else run_decl
   where
     run_decl =
         do _ <- liftIO $ tryIO $ hFlushAll stdin
diff --git a/testsuite/tests/ghci/should_run/T7253.script b/testsuite/tests/ghci/should_run/T7253.script
new file mode 100644 (file)
index 0000000..0ab8337
--- /dev/null
@@ -0,0 +1,69 @@
+:{
+add :: Int -> Int -> Int
+add a b = a + b
+:}
+add 2 3
+
+-- override
+add = sum
+add [1,2,3]
+
+:{
+a 0 = 0
+a x = 1 + b x
+b x = 2 + a (x -  1)
+:}
+b 2
+
+-- do not show warning twice
+{-# foo #-}
+
+:{
+{-# WARNING Foo "Just a warning" #-}
+data Foo = Foo String
+:}
+
+:seti -XStandaloneDeriving
+deriving instance Show Foo
+
+-- ^ Just a 'foo' function.
+foo = Foo "Some foo"
+show foo
+
+import Data.Char
+
+:seti -XDefaultSignatures
+:{
+class HasString a where
+  update :: a -> (String -> String) -> a
+
+  upcase :: a -> a
+  upcase x = update x (fmap toUpper)
+
+  content :: a -> String
+  default content :: Show a => a -> String
+  content = show
+:}
+
+:{
+instance HasString Foo where
+  update (Foo s) f = Foo (f s)
+  content (Foo s) = s
+:}
+
+upcase foo
+
+{-# RULES "map/map" forall f g xs. map f (map g xs) = map (f.g) xs #-}
+{-# ANN foo (Just "Hello") #-}
+{-# NOVECTORISE foo #-}
+
+:seti -XRoleAnnotations
+:{
+type role T1 _ phantom
+data T1 a b = MkT1 b
+:}
+
+:{
+type role T2 _ nominal
+data T2 a b = MkT2 a
+:}
diff --git a/testsuite/tests/ghci/should_run/T7253.stderr b/testsuite/tests/ghci/should_run/T7253.stderr
new file mode 100644 (file)
index 0000000..a96d278
--- /dev/null
@@ -0,0 +1,7 @@
+
+<interactive>:19:1: warning: Unrecognised pragma
+
+<interactive>:62:1:
+    Role mismatch on variable b:
+      Annotation says phantom but role representational is required
+    while checking a role annotation for ‘T1’
diff --git a/testsuite/tests/ghci/should_run/T7253.stdout b/testsuite/tests/ghci/should_run/T7253.stdout
new file mode 100644 (file)
index 0000000..2d29a0f
--- /dev/null
@@ -0,0 +1,5 @@
+5
+6
+5
+"Foo \"Some foo\""
+Foo "SOME FOO"
diff --git a/testsuite/tests/ghci/should_run/T9915.stderr b/testsuite/tests/ghci/should_run/T9915.stderr
deleted file mode 100644 (file)
index 95f5758..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-
-<interactive>:2:9: error:
-    parse error on input ‘=’
-    Perhaps you need a 'let' in a 'do' block?
-    e.g. 'let x = 5' instead of 'x = 5'
index bcb1538..68c7407 100644 (file)
@@ -21,3 +21,4 @@ test('T8377',      just_ghci, compile_and_run, [''])
 test('T9914',      just_ghci, ghci_script, ['T9914.script'])
 test('T9915',      just_ghci, ghci_script, ['T9915.script'])
 test('T10145',     just_ghci, ghci_script, ['T10145.script'])
+test('T7253',      just_ghci, ghci_script, ['T7253.script'])
index b015016..65baafe 100644 (file)
@@ -1,2 +1,6 @@
 
-<interactive>:9:1: parse error on input ‘{-# RULES’
+<interactive>:9:25: error:
+    No instance for (Num a) arising from a use of ‘f’
+    Possible fix: add (Num a) to the context of the RULE "id/Int"
+    In the expression: f
+    When checking the transformation rule "id/Int"