1{-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-}
2{-# LANGUAGE OverloadedStrings #-}
10import Prelude hiding (lines, read)
11import Control.Applicative ((<|>))
12import Control.Monad.Trans (lift, liftIO)
13import qualified Control.Monad.State.Strict as State
14import qualified Data.ByteString as BS
15import qualified Data.ByteString.Builder as B
16import qualified Data.ByteString.Char8 as BSC
17import qualified Data.ByteString.Lazy as LBS
18import qualified Data.ByteString.UTF8 as BS_UTF8
19import qualified Data.Char as Char
20import qualified Data.List as List
21import qualified Data.Map as Map
22import Data.Monoid ((<>))
23import qualified Data.Name as N
24import qualified System.Console.Haskeline as Repl
25import qualified System.Directory as Dir
26import qualified System.Exit as Exit
27import System.FilePath ((</>))
28import qualified System.IO as IO
29import qualified System.Process as Proc
31import qualified AST.Source as Src
33import qualified Elm.Constraint as C
34import qualified Elm.Details as Details
35import qualified Elm.Licenses as Licenses
36import qualified Elm.ModuleName as ModuleName
37import qualified Elm.Outline as Outline
38import qualified Elm.Package as Pkg
39import qualified Elm.Version as V
40import qualified Generate
41import qualified Parse.Expression as PE
42import qualified Parse.Declaration as PD
43import qualified Parse.Module as PM
44import qualified Parse.Primitives as P
45import Parse.Primitives (Row, Col)
46import qualified Reporting
47import qualified Reporting.Annotation as A
48import Reporting.Doc ((<+>))
49import qualified Reporting.Doc as D
50import qualified Reporting.Error.Syntax as ES
51import qualified Reporting.Exit as Exit
52import qualified Reporting.Render.Code as Code
53import qualified Reporting.Report as Report
54import qualified Reporting.Task as Task
64 { _maybeInterpreter :: Maybe FilePath
69run :: () -> Flags -> IO ()
71 do printWelcomeMessage
72 settings <- initSettings
74 let looper = Repl.runInputT settings (Repl.withInterrupt (loop env initialState))
75 exitCode <- State.evalStateT looper initialState
76 Exit.exitWith exitCode
83printWelcomeMessage :: IO ()
86 vsn = V.toChars V.compiler
87 title = "Elm" <+> D.fromChars vsn
88 dashes = replicate (70 - length vsn) '-'
92 [ D.black "----" <+> D.dullcyan title <+> D.black (D.fromChars dashes)
93 , D.black $ D.fromChars $ "Say :help for help and :exit to exit! More at " <> D.makeLink "repl"
94 , D.black "--------------------------------------------------------------------------------"
106 , _interpreter :: FilePath
111initEnv :: Flags -> IO Env
112initEnv (Flags maybeAlternateInterpreter noColors) =
114 interpreter <- getInterpreter maybeAlternateInterpreter
115 return $ Env root interpreter (not noColors)
128 State.StateT State IO
131loop :: Env -> State -> Repl.InputT M Exit.ExitCode
133 do input <- Repl.handleInterrupt (return Skip) read
134 outcome <- liftIO (eval env state input)
137 do lift (State.put state)
149 = Import ModuleName.Raw BS.ByteString
150 | Type N.Name BS.ByteString
152 | Decl N.Name BS.ByteString
158 | Help (Maybe String)
161read :: Repl.InputT M Input
163 do line <- Repl.getInputLine "> "
169 case categorize chars of
170 Done input -> return input
171 Continue -> readMore chars
174readMore :: [Char] -> Repl.InputT M Input
175readMore previousLines =
176 do input <- Repl.getInputLineWithInitial "| " (" ","")
183 allLines = previousLines ++ '\n' : chars
185 case categorize allLines of
186 Done input -> return input
187 Continue -> readMore allLines
199categorize :: [Char] -> CategorizedInput
201 | all (==' ') chars = Done Skip
202 | startsWithColon chars = Done (toCommand chars)
203 | startsWithKeyword "import" chars = attemptImport chars
204 | otherwise = attemptDeclOrExpr chars
207attemptImport :: [Char] -> CategorizedInput
210 src = BS_UTF8.fromString (chars ++ "\n")
211 parser = P.specialize (\_ _ _ -> ()) PM.chompImport
213 case P.fromByteString parser (\_ _ -> ()) src of
214 Right (Src.Import (A.At _ name) _ _) ->
215 Done (Import name src)
218 if endsWithBlankLine chars
219 then Done (Import "ERR" src)
223attemptDeclOrExpr :: [Char] -> CategorizedInput
224attemptDeclOrExpr chars =
226 src = BS_UTF8.fromString (chars ++ "\n")
227 exprParser = P.specialize (toExprPosition src) PE.expression
228 declParser = P.specialize (toDeclPosition src) PD.declaration
230 case P.fromByteString declParser (,) src of
233 PD.Value _ (A.At _ (Src.Value (A.At _ name) _ _ _)) -> Done (Decl name src)
234 PD.Union _ (A.At _ (Src.Union (A.At _ name) _ _ )) -> Done (Type name src)
235 PD.Alias _ (A.At _ (Src.Alias (A.At _ name) _ _ )) -> Done (Type name src)
236 PD.Port _ _ -> Done Port
239 | startsWithKeyword "type" chars ->
240 if endsWithBlankLine chars
241 then Done (Type "ERR" src)
244 | startsWithKeyword "port" chars ->
248 case P.fromByteString exprParser (,) src of
253 if endsWithBlankLine chars
254 then Done (if declPosition <= exprPosition then Expr src else Decl "ERR" src)
258endsWithBlankLine :: [Char] -> Bool
259endsWithBlankLine chars =
260 case reverse (List.lines chars) of
262 cs:_ -> all (==' ') cs
265startsWithColon :: [Char] -> Bool
266startsWithColon chars =
267 case dropWhile (==' ') chars of
272toCommand :: [Char] -> Input
274 case drop 1 $ dropWhile (==' ') chars of
278 "help" -> Help Nothing
279 rest -> Help (Just (takeWhile (/=' ') rest))
282startsWithKeyword :: [Char] -> [Char] -> Bool
283startsWithKeyword keyword line =
284 List.isPrefixOf keyword line &&
285 case drop (length keyword) line of
287 c:_ -> not (Char.isAlphaNum c)
290toExprPosition :: BS.ByteString -> ES.Expr -> Row -> Col -> (Row, Col)
291toExprPosition src expr row col =
293 decl = ES.DeclDef N.replValueToPrint (ES.DeclDefBody expr row col) row col
295 toDeclPosition src decl row col
298toDeclPosition :: BS.ByteString -> ES.Decl -> Row -> Col -> (Row, Col)
299toDeclPosition src decl r c =
301 err = ES.ParseError (ES.Declarations decl r c)
302 report = ES.toReport (Code.toSource src) err
304 (Report.Report _ (A.Region (A.Position row col) _) _ _) = report
315 { _imports :: Map.Map N.Name B.Builder
316 , _types :: Map.Map N.Name B.Builder
317 , _decls :: Map.Map N.Name B.Builder
323 State Map.empty Map.empty Map.empty
330eval :: Env -> State -> Input -> IO Outcome
331eval env state@(State imports types decls) input =
332 Repl.handleInterrupt (putStrLn "<cancelled>" >> return (Loop state)) $
338 return (End Exit.ExitSuccess)
341 do putStrLn "<reset>"
342 return (Loop initialState)
344 Help maybeUnknownCommand ->
345 do putStrLn (toHelpMessage maybeUnknownCommand)
349 do let newState = state { _imports = Map.insert name (B.byteString src) imports }
350 Loop <$> attemptEval env state newState OutputNothing
353 do let newState = state { _types = Map.insert name (B.byteString src) types }
354 Loop <$> attemptEval env state newState OutputNothing
357 do putStrLn "I cannot handle port declarations."
361 do let newState = state { _decls = Map.insert name (B.byteString src) decls }
362 Loop <$> attemptEval env state newState (OutputDecl name)
365 Loop <$> attemptEval env state state (OutputExpr src)
375 | OutputExpr BS.ByteString
378attemptEval :: Env -> State -> State -> Output -> IO State
379attemptEval (Env root interpreter ansi) oldState newState output =
380 do result <- Task.run $
382 Task.eio Exit.ReplBadDetails $
383 Details.load Reporting.silent root
387 Build.fromRepl root details (toByteString newState output)
389 traverse (Task.mapError Exit.ReplBadGenerate . Generate.repl root details ansi artifacts) (toPrintName output)
393 do Exit.toStderr (Exit.replToReport exit)
399 Right (Just javascript) ->
400 do exitCode <- interpret interpreter javascript
402 Exit.ExitSuccess -> return newState
403 Exit.ExitFailure _ -> return oldState
406interpret :: FilePath -> B.Builder -> IO Exit.ExitCode
407interpret interpreter javascript =
409 createProcess = (Proc.proc interpreter []) { Proc.std_in = Proc.CreatePipe }
411 Proc.withCreateProcess createProcess $ \(Just stdin) _ _ handle ->
412 do B.hPutBuilder stdin javascript
414 Proc.waitForProcess handle
421toByteString :: State -> Output -> BS.ByteString
422toByteString (State imports types decls) output =
423 LBS.toStrict $ B.toLazyByteString $
425 [ "module ", N.toBuilder N.replModule, " exposing (..)\n"
426 , Map.foldr mappend mempty imports
427 , Map.foldr mappend mempty types
428 , Map.foldr mappend mempty decls
429 , outputToBuilder output
433outputToBuilder :: Output -> B.Builder
434outputToBuilder output =
443 N.toBuilder N.replValueToPrint <> " ="
444 <> foldr (\line rest -> "\n " <> B.byteString line <> rest) "\n" (BSC.lines expr)
451toPrintName :: Output -> Maybe N.Name
454 OutputNothing -> Nothing
455 OutputDecl name -> Just name
456 OutputExpr _ -> Just N.replValueToPrint
463toHelpMessage :: Maybe String -> String
464toHelpMessage maybeBadCommand =
465 case maybeBadCommand of
470 "I do not recognize the :" ++ command ++ " command. " ++ genericHelpMessage
473genericHelpMessage :: String
475 "Valid commands include:\n\
477 \ :exit Exit the REPL\n\
478 \ :help Show this information\n\
479 \ :reset Clear all previous imports and definitions\n\
481 \More info at " ++ D.makeLink "repl" ++ "\n"
488getRoot :: IO FilePath
490 do maybeRoot <- Stuff.findRoot
496 do cache <- Stuff.getReplCache
497 let root = cache </> "tmp"
498 Dir.createDirectoryIfMissing True (root </> "src")
499 Outline.write root $ Outline.Pkg $
502 Outline.defaultSummary
505 (Outline.ExposedList [])
506 (Map.singleton Pkg.core C.anything)
517getInterpreter :: Maybe String -> IO FilePath
518getInterpreter maybeName =
521 getInterpreterHelp name (Dir.findExecutable name)
524 getInterpreterHelp "node` or `nodejs" $
525 do exe1 <- Dir.findExecutable "node"
526 exe2 <- Dir.findExecutable "nodejs"
527 return (exe1 <|> exe2)
530getInterpreterHelp :: String -> IO (Maybe FilePath) -> IO FilePath
531getInterpreterHelp name findExe =
532 do maybePath <- findExe
538 do IO.hPutStrLn IO.stderr (exeNotFound name)
542exeNotFound :: String -> String
544 "The REPL relies on node.js to execute JavaScript code outside the browser.\n"
545 ++ "I could not find executable `" ++ name ++ "` on your PATH though!\n\n"
546 ++ "You can install node.js from <http://nodejs.org/>. If it is already installed\n"
547 ++ "but has a different name, use the --interpreter flag."
554initSettings :: IO (Repl.Settings M)
556 do cache <- Stuff.getReplCache
559 { Repl.historyFile = Just (cache </> "history")
560 , Repl.autoAddHistory = True
561 , Repl.complete = Repl.completeWord Nothing " \n" lookupCompletions
565lookupCompletions :: String -> M [Repl.Completion]
566lookupCompletions string =
567 do (State imports types decls) <- State.get
569 addMatches string False decls $
570 addMatches string False types $
571 addMatches string True imports $
572 addMatches string False commands []
575commands :: Map.Map N.Name ()
585addMatches :: String -> Bool -> Map.Map N.Name v -> [Repl.Completion] -> [Repl.Completion]
586addMatches string isFinished dict completions =
587 Map.foldrWithKey (addMatch string isFinished) completions dict
590addMatch :: String -> Bool -> N.Name -> v -> [Repl.Completion] -> [Repl.Completion]
591addMatch string isFinished name _ completions =
593 suggestion = N.toChars name
595 if List.isPrefixOf string suggestion then
596 Repl.Completion suggestion suggestion isFinished : completions
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list