@@ -9,6 +9,7 @@
9-- Portability :
9-- Portability :
12+-- PureScript Compiler Interactive.
13-----------------------------------------------------------------------------
14-----------------------------------------------------------------------------
@@ -16,31 +17,143 @@
17module Main where
18module Main where
19import Control.Applicative
22import Control.Applicative
20import Control.Monad
23import Control.Monad
21import Control.Monad.Trans.Class
24import Control.Monad.Trans.Class
22import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
25import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
26+import Control.Monad.Trans.State
24−import Data.List (nub, isPrefixOf)
28+import Data.List (intercalate, isPrefixOf, nub, sort)
25import Data.Maybe (mapMaybe)
29import Data.Maybe (mapMaybe)
26import Data.Traversable (traverse)
30import Data.Traversable (traverse)
28import System.Console.Haskeline
32import System.Console.Haskeline
29import System.Directory (findExecutable)
33import System.Directory (findExecutable)
30import System.Exit
34import System.Exit
35+import System.Environment.XDG.BaseDir
31import System.Process
36import System.Process
33import qualified Language.PureScript as P
38import qualified Language.PureScript as P
34import qualified Paths_purescript as Paths
39import qualified Paths_purescript as Paths
35import qualified System.IO.UTF8 as U (readFile)
40import qualified System.IO.UTF8 as U (readFile)
36−import qualified Text.Parsec as Parsec (eof)
41+import qualified Text.Parsec as Parsec (Parsec, eof)
45+-- Holds a list of imported modules, loaded files, and partial let bindings.
46+-- The let bindings are partial,
47+-- because it makes more sense to apply the binding to the final evaluated expression.
49+data PSCI = PSCI [P.ProperName] [P.Module] [P.Value -> P.Value]
54+-- Synonym to be more descriptive.
55+-- This is just @lift@
57+inputTToState :: InputT IO a -> StateT PSCI (InputT IO) a
61+-- Synonym to be more descriptive.
62+-- This is just @lift . lift@
64+ioToState :: IO a -> StateT PSCI (InputT IO) a
65+ioToState = lift . lift
68+-- Updates the state to have more imported modules.
70+updateImports :: String -> PSCI -> PSCI
71+updateImports name (PSCI i m b) = PSCI (i ++ [P.ProperName name]) m b
74+-- Updates the state to have more loaded files.
76+updateModules :: [P.Module] -> PSCI -> PSCI
77+updateModules modules (PSCI i m b) = PSCI i (m ++ modules) b
80+-- Updates the state to have more let bindings.
82+updateLets :: (P.Value -> P.Value) -> PSCI -> PSCI
83+updateLets name (PSCI i m b) = PSCI i m (b ++ [name])
87+-- Load the necessary modules.
89+defaultImports :: [P.ProperName]
90+defaultImports = [P.ProperName "Prelude"]
93+-- Locates the node executable.
94+-- Checks for either @nodejs@ or @node@.
96+findNodeProcess :: IO (Maybe String)
97+findNodeProcess = runMaybeT . msum $ map (MaybeT . findExecutable) names
98+ where names = ["nodejs", "node"]
101+-- Grabs the filename where the history is stored.
103+getHistoryFilename :: IO FilePath
104+getHistoryFilename = getUserConfigFile "purescript" "psci_history"
107+-- Grabs the filename where prelude is.
38getPreludeFilename :: IO FilePath
109getPreludeFilename :: IO FilePath
39getPreludeFilename = Paths.getDataFileName "prelude/prelude.purs"
110getPreludeFilename = Paths.getDataFileName "prelude/prelude.purs"
41−options :: P.Options
112+-- |
42−options = P.Options True False True (Just "Main") True "PS" []
113+-- Loads a file for use with imports.
115+loadModule :: FilePath -> IO (Either String [P.Module])
116+loadModule moduleFile = do
117+ moduleText <- U.readFile moduleFile
118+ return . either (Left . show) Right $ P.runIndentParser "" P.parseModules moduleText
125+helpMessage :: String
126+helpMessage = "The following commands are available:\n\n " ++
127+ intercalate "\n " (map (intercalate " ") help)
130+-- The welcome prologue.
132+prologueMessage :: String
133+prologueMessage = intercalate "\n"
135+ , "| _ \\ _ _ _ __ ___/ ___| ___ _ __(_)_ __ | |_ "
136+ , "| |_) | | | | '__/ _ \\___ \\ / __| '__| | '_ \\| __|"
137+ , "| __/| |_| | | | __/___) | (__| | | | |_) | |_ "
138+ , "|_| \\__,_|_| \\___|____/ \\___|_| |_| .__/ \\__|"
143+ , "Expressions are terminated using Ctrl+D"
149+quitMessage :: String
150+quitMessage = "See ya!"
152+-- Haskeline completions
155+-- Loads module, function, and file completions.
44completion :: [P.Module] -> CompletionFunc IO
157completion :: [P.Module] -> CompletionFunc IO
45completion ms = completeWord Nothing " \t\n\r" findCompletions
158completion ms = completeWord Nothing " \t\n\r" findCompletions
@@ -53,29 +166,40 @@ completion ms = completeWord Nothing " \t\n\r" findCompletions
53 , qual <- [ P.Qualified Nothing ident
166 , qual <- [ P.Qualified Nothing ident
54 , P.Qualified (Just moduleName) ident]
167 , P.Qualified (Just moduleName) ident]
56− let matches = filter (isPrefixOf str) names
169+ let matches = sort $ filter (isPrefixOf str) names
57 return $ map simpleCompletion matches ++ files
170 return $ map simpleCompletion matches ++ files
58 getDeclName :: P.Declaration -> Maybe P.Ident
171 getDeclName :: P.Declaration -> Maybe P.Ident
59 getDeclName (P.ValueDeclaration ident _ _ _) = Just ident
172 getDeclName (P.ValueDeclaration ident _ _ _) = Just ident
60 getDeclName _ = Nothing
173 getDeclName _ = Nothing
177+-- | Compilation options.
180+options = P.Options True False True (Just "Main") True "PS" []
183+-- Makes a volatile module to execute the current expression.
62−createTemporaryModule :: [P.ProperName] -> P.Value -> P.Module
185+createTemporaryModule :: [P.ProperName] -> [P.Value -> P.Value] -> P.Value -> P.Module
63−createTemporaryModule imports value =
186+createTemporaryModule imports lets value =
65 moduleName = P.ModuleName [P.ProperName "Main"]
188 moduleName = P.ModuleName [P.ProperName "Main"]
66 importDecl m = P.ImportDeclaration m Nothing
189 importDecl m = P.ImportDeclaration m Nothing
67 traceModule = P.ModuleName [P.ProperName "Trace"]
190 traceModule = P.ModuleName [P.ProperName "Trace"]
68 trace = P.Var (P.Qualified (Just traceModule) (P.Ident "print"))
191 trace = P.Var (P.Qualified (Just traceModule) (P.Ident "print"))
192+ value' = foldr ($) value lets
69− mainDecl = P.ValueDeclaration (P.Ident "main") [] Nothing
193+ mainDecl = P.ValueDeclaration (P.Ident "main") [] Nothing (P.App trace value')
70− (P.Do [ P.DoNotationBind (P.VarBinder (P.Ident "it")) value
71− , P.DoNotationValue (P.App trace (P.Var (P.Qualified Nothing (P.Ident "it"))) )
74 P.Module moduleName $ map (importDecl . P.ModuleName . return) imports ++ [mainDecl]
195 P.Module moduleName $ map (importDecl . P.ModuleName . return) imports ++ [mainDecl]
198+-- Takes a value declaration and evaluates it with the current state.
76−handleDeclaration :: [P.Module] -> [P.ProperName] -> P.Value -> InputT IO ()
200+handleDeclaration :: P.Value -> PSCI -> InputT IO ()
77−handleDeclaration loadedModules imports value = do
201+handleDeclaration value (PSCI imports loadedModules lets) = do
78− let m = createTemporaryModule imports value
202+ let m = createTemporaryModule imports lets value
79 case P.compile options (loadedModules ++ [m]) of
203 case P.compile options (loadedModules ++ [m]) of
80 Left err -> outputStrLn err
204 Left err -> outputStrLn err
81 Right (js, _, _) -> do
205 Right (js, _, _) -> do
@@ -86,73 +210,65 @@ handleDeclaration loadedModules imports value = do
86 Just (ExitFailure _, _, err) -> outputStrLn err
210 Just (ExitFailure _, _, err) -> outputStrLn err
87 Nothing -> outputStrLn "Couldn't find node.js"
211 Nothing -> outputStrLn "Couldn't find node.js"
89−data Command
213+-- Parser helpers
91− | Expression [String]
93− | LoadModule FilePath
94− | Reload deriving (Show, Eq)
96−getCommand :: InputT IO Command
98− firstLine <- getInputLine "> "
100− Nothing -> return Empty
101− Just (':':'i':' ':moduleName) -> return $ Import moduleName
102− Just (':':'m':' ':modulePath) -> return $ LoadModule modulePath
103− Just ":r" -> return Reload
104− Just (':':_) -> outputStrLn "Unknown command" >> getCommand
105− Just other -> Expression <$> go [other]
108− l <- getInputLine " "
110− Nothing -> return $ reverse ls
111− Just l' -> go (l' : ls)
113−loadModule :: FilePath -> IO (Either String [P.Module])
215+-- |
114−loadModule moduleFile = do
216+-- Parser for our PSCI version of @let@.
115− print moduleFile
217+-- This is essentially let from do-notation.
116− moduleText <- U.readFile moduleFile
218+-- However, since we don't support the @Eff@ monad, we actually want the normal @let@.
117− return . either (Left . show) Right $ P.runIndentParser "" P.parseModules moduleText
219+--
220+parseLet :: Parsec.Parsec String P.ParseState (P.Value -> P.Value)
221+parseLet = P.Let <$> (P.reserved "let" *> P.indented *> P.parseBinder)
222+ <*> (P.indented *> P.reservedOp "=" *> P.parseValue)
119−findNodeProcess :: IO (Maybe String)
224+-- |
120−findNodeProcess = runMaybeT . msum $ map (MaybeT . findExecutable) names
225+-- Parser for any other valid expression.
121− where names = ["nodejs", "node"]
226+--
227+parseExpression :: Parsec.Parsec String P.ParseState P.Value
228+parseExpression = P.whiteSpace *> P.parseValue <* Parsec.eof
233+-- Performs an action for each meta-command given, and also for expressions..
235+handleCommand :: Command -> StateT PSCI (InputT IO) ()
236+handleCommand Empty = return ()
237+handleCommand (Expression ls) =
238+ case P.runIndentParser "" parseLet (unlines ls) of
240+ case P.runIndentParser "" parseExpression (unlines ls) of
241+ Left err -> inputTToState $ outputStrLn (show err)
242+ Right decl -> get >>= inputTToState . handleDeclaration decl
243+ Right l -> modify (updateLets l)
244+handleCommand Help = inputTToState $ outputStrLn helpMessage
245+handleCommand (Import moduleName) = modify (updateImports moduleName)
246+handleCommand (LoadFile filePath) = do
247+ mf <- ioToState $ loadModule filePath
249+ Left err -> inputTToState $ outputStrLn err
250+ Right mf' -> modify (updateModules mf')
251+handleCommand Reload = do
252+ (Right prelude) <- ioToState $ getPreludeFilename >>= loadModule
253+ put (PSCI defaultImports prelude [])
254+handleCommand Unknown = inputTToState $ outputStrLn "Unknown command"
257+-- The PSCI main loop.
123main :: IO ()
259main :: IO ()
125 preludeFilename <- getPreludeFilename
261 preludeFilename <- getPreludeFilename
126 (Right prelude) <- loadModule preludeFilename
262 (Right prelude) <- loadModule preludeFilename
263+ historyFilename <- getHistoryFilename
264+ let settings = defaultSettings {historyFile = Just historyFilename}
127− runInputT (setComplete (completion prelude) defaultSettings) $ do
265+ runInputT (setComplete (completion prelude) settings) $ do
128− outputStrLn " ____ ____ _ _ "
266+ outputStrLn prologueMessage
129− outputStrLn "| _ \\ _ _ _ __ ___/ ___| ___ _ __(_)_ __ | |_ "
267+ evalStateT go (PSCI defaultImports prelude [])
130− outputStrLn "| |_) | | | | '__/ _ \\___ \\ / __| '__| | '_ \\| __|"
131− outputStrLn "| __/| |_| | | | __/___) | (__| | | | |_) | |_ "
132− outputStrLn "|_| \\__,_|_| \\___|____/ \\___|_| |_| .__/ \\__|"
135− outputStrLn "Expressions are terminated using Ctrl+D"
136− go [P.ProperName "Prelude", P.ProperName "Eff"] prelude
138− go imports loadedModules = do
269+ go :: StateT PSCI (InputT IO) ()
139− cmd <- getCommand
271+ c <- inputTToState getCommand
140− case cmd of
272+ case c of
141− Empty -> go imports loadedModules
273+ Quit -> inputTToState $ outputStrLn quitMessage
142− Expression ls -> do
274+ _ -> handleCommand c >> go
143− case P.runIndentParser "" (P.whiteSpace *> P.parseValue <* Parsec.eof) (unlines ls) of
144− Left err -> outputStrLn (show err)
145− Right decl -> handleDeclaration loadedModules imports decl
146− go imports loadedModules
147− Import moduleName -> go (imports ++ [P.ProperName moduleName]) loadedModules
148− LoadModule moduleFile -> do
149− ms <- lift $ loadModule moduleFile
151− Left err -> outputStrLn err
152− Right ms' -> go imports (loadedModules ++ ms')
154− preludeFilename <- lift getPreludeFilename
155− (Right prelude) <- lift $ loadModule preludeFilename
156− go [P.ProperName "Prelude"] prelude