@@ -1,6 +1,6 @@
11-----------------------------------------------------------------------------
22--
3−-- Module : Main
3+-- Module : PSCi
44-- Copyright : (c) Phil Freeman 2013
55-- License : MIT
66--
@@ -19,14 +19,12 @@
1919{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2020{-# LANGUAGE RecordWildCards #-}
2121
22−module Main where
22+module PSCi where
2323
2424import Data.Foldable (traverse_)
25−import Data.List (intercalate, isPrefixOf, nub, sortBy, sort)
26−import Data.Maybe (mapMaybe)
25+import Data.List (intercalate, nub, sort)
2726import Data.Traversable (traverse)
2827import Data.Version (showVersion)
29−import Data.Char (isSpace)
3028import qualified Data.Map as M
3129
3230import Control.Applicative
@@ -50,70 +48,21 @@ import System.Process (readProcessWithExitCode)
5048import qualified Text.Parsec as Par (ParseError)
5149
5250import qualified Language.PureScript as P
53−import qualified Language.PureScript.AST as D
5451import qualified Language.PureScript.Names as N
5552import qualified Paths_purescript as Paths
5653
57−import qualified Commands as C
5854import qualified Directive as D
59−import Parser
55+import Parser (parseCommand)
56+import Completion (completion)
57+import Types
6058
61−data PSCiOptions = PSCiOptions
62− { psciMultiLineMode :: Bool
63− , psciInputFile :: [FilePath]
64− , psciInputNodeFlags :: [String]
65− }
66−
67−-- |
68−-- The PSCI state.
69−-- Holds a list of imported modules, loaded files, and partial let bindings.
70−-- The let bindings are partial,
71−-- because it makes more sense to apply the binding to the final evaluated expression.
72−--
73−data PSCiState = PSCiState
74− { psciImportedFilenames :: [FilePath]
75− , psciImportedModules :: [C.ImportedModule]
76− , psciLoadedModules :: [(Either P.RebuildPolicy FilePath, P.Module)]
77− , psciLetBindings :: [P.Declaration]
78− , psciNodeFlags :: [String]
79− }
80−
81−psciImportedModuleNames :: PSCiState -> [P.ModuleName]
82−psciImportedModuleNames (PSCiState{psciImportedModules = is}) =
83− map (\(mn, _, _) -> mn) is
84−
85−-- State helpers
86−
87−-- |
88−-- Updates the state to have more imported modules.
89−--
90−updateImportedFiles :: FilePath -> PSCiState -> PSCiState
91−updateImportedFiles filename st = st { psciImportedFilenames = filename : psciImportedFilenames st }
92−
93−-- |
94−-- Updates the state to have more imported modules.
95−--
96−updateImportedModules :: C.ImportedModule -> PSCiState -> PSCiState
97−updateImportedModules im st = st { psciImportedModules = im : psciImportedModules st }
98−
99−-- |
100−-- Updates the state to have more loaded files.
101−--
102−updateModules :: [(Either P.RebuildPolicy FilePath, P.Module)] -> PSCiState -> PSCiState
103−updateModules modules st = st { psciLoadedModules = psciLoadedModules st ++ modules }
104−
105−-- |
106−-- Updates the state to have more let bindings.
107−--
108−updateLets :: [P.Declaration] -> PSCiState -> PSCiState
109−updateLets ds st = st { psciLetBindings = psciLetBindings st ++ ds }
11059
11160-- File helpers
11261-- |
11362-- Load the necessary modules.
11463--
115−defaultImports :: [C.ImportedModule]
116−defaultImports = [(P.ModuleName [P.ProperName "Prelude"], D.Implicit, Nothing)]
64+defaultImports :: [ImportedModule]
65+defaultImports = [(P.ModuleName [P.ProperName "Prelude"], P.Implicit, Nothing)]
11766
11867-- |
11968-- Locates the node executable.
@@ -177,7 +126,7 @@ helpMessage :: String
177126helpMessage = "The following commands are available:\n\n " ++
178127 intercalate "\n " (map line D.help)
179128 where
180− line :: (D.Directive, String, String) -> String
129+ line :: (Directive, String, String) -> String
181130 line (dir, arg, desc) = intercalate " "
182131 [ cmd
183132 , replicate (11 - length cmd) ' '
@@ -185,7 +134,7 @@ helpMessage = "The following commands are available:\n\n " ++
185134 , replicate (11 - length arg) ' '
186135 , desc
187136 ]
188− where cmd = ":" ++ head (D.commands dir)
137+ where cmd = ":" ++ D.stringFor dir
189138
190139-- |
191140-- The welcome prologue.
@@ -208,129 +157,6 @@ prologueMessage = intercalate "\n"
208157quitMessage :: String
209158quitMessage = "See ya!"
210159
211−-- Haskeline completions
212−
213−data CompletionContext = Command String | FilePath String | Module | Identifier
214− | Type | Fixed [String] | Multiple [CompletionContext]
215− deriving (Show)
216−
217−-- |
218−-- Decide what kind of completion we need based on input.
219−completionContext :: String -> String -> Maybe CompletionContext
220−completionContext cmd@"" _ = Just $ Multiple [Command cmd, Identifier]
221−completionContext (':' : cmd) word =
222− case D.parseDirective dstr of
223− Just directive | dstr `elem` D.commands directive -> context directive
224− _ -> Just $ Command cmd
225− where
226− dstr :: String
227− dstr = takeWhile (not . isSpace) cmd
228−
229− context :: D.Directive -> Maybe CompletionContext
230− context D.Browse = Just Module
231− context D.Load = Just $ FilePath word
232− context D.Quit = Nothing
233− context D.Reset = Nothing
234− context D.Help = Nothing
235− context D.Show = Just $ Fixed ["import", "loaded"]
236− context D.Type = Just Identifier
237− context D.Kind = Just Type
238−completionContext _ _ = Just Identifier
239−
240−-- |
241−-- Loads module, function, and file completions.
242−--
243−completion :: CompletionFunc (StateT PSCiState IO)
244−completion = completeWordWithPrev Nothing " \t\n\r" findCompletions
245− where
246− findCompletions :: String -> String -> StateT PSCiState IO [Completion]
247− findCompletions prev word = do
248− let ctx = completionContext ((dropWhile isSpace (reverse prev)) ++ word) word
249− completions <- case ctx of
250− Nothing -> return []
251− (Just c) -> (mapMaybe $ either (\cand -> if word `isPrefixOf` cand
252− then Just $ simpleCompletion cand
253− else Nothing) Just)
254− <$> getCompletion c
255− return $ sortBy sorter completions
256−
257− getCompletion :: CompletionContext -> StateT PSCiState IO [Either String Completion]
258− getCompletion (FilePath f) = (map Right) <$> listFiles f
259− getCompletion Module = (map Left) <$> getModuleNames
260− getCompletion Identifier = (map Left) <$> ((++) <$> getIdentNames <*> getDctorNames)
261− getCompletion Type = (map Left) <$> getTypeNames
262− getCompletion (Fixed list) = return $ (map Left) list
263− getCompletion (Multiple contexts) = concat <$> mapM getCompletion contexts
264− getCompletion (Command cmd) = return . map (Left . (":" ++)) . nub $ matching
265− where
266− matching :: [String]
267− matching = filter (isPrefixOf cmd) . concatMap (D.commands) $ D.directives
268−
269− getLoadedModules :: StateT PSCiState IO [P.Module]
270− getLoadedModules = map snd . psciLoadedModules <$> get
271−
272− getModuleNames :: StateT PSCiState IO [String]
273− getModuleNames = moduleNames <$> getLoadedModules
274−
275− mapLoadedModulesAndQualify :: (Show a) => (P.Module -> [a]) -> StateT PSCiState IO [String]
276− mapLoadedModulesAndQualify f = do
277− ms <- getLoadedModules
278− q <- sequence [qualifyIfNeeded m (f m) | m <- ms]
279− return $ concat q
280−
281− getIdentNames :: StateT PSCiState IO [String]
282− getIdentNames = mapLoadedModulesAndQualify identNames
283−
284− getDctorNames :: StateT PSCiState IO [String]
285− getDctorNames = mapLoadedModulesAndQualify dctorNames
286−
287− getTypeNames :: StateT PSCiState IO [String]
288− getTypeNames = mapLoadedModulesAndQualify typeDecls
289−
290− qualifyIfNeeded :: (Show a) => P.Module -> [a] -> StateT PSCiState IO [String]
291− qualifyIfNeeded m decls = do
292− let name = P.getModuleName m
293− imported <- psciImportedModuleNames <$> get
294− let qualified = map (P.Qualified $ Just name) decls
295− if name `elem` imported then
296− return $ map show $ qualified ++ (map (P.Qualified Nothing) decls)
297− else
298− return $ map show qualified
299−
300− typeDecls :: P.Module -> [N.ProperName]
301− typeDecls m = mapMaybe getTypeName $ filter P.isDataDecl (P.exportedDeclarations m)
302− where getTypeName :: P.Declaration -> Maybe N.ProperName
303− getTypeName (P.TypeSynonymDeclaration name _ _) = Just name
304− getTypeName (P.DataDeclaration _ name _ _) = Just name
305− getTypeName (P.PositionedDeclaration _ _ d) = getTypeName d
306− getTypeName _ = Nothing
307−
308− identNames :: P.Module -> [N.Ident]
309− identNames (P.Module _ _ ds exports) = nub [ ident | ident <- mapMaybe (getDeclName exports) (D.flattenDecls ds) ]
310− where getDeclName :: Maybe [P.DeclarationRef] -> P.Declaration -> Maybe P.Ident
311− getDeclName exts decl@(P.ValueDeclaration ident _ _ _) | P.isExported exts decl = Just ident
312− getDeclName exts decl@(P.ExternDeclaration _ ident _ _) | P.isExported exts decl = Just ident
313− getDeclName exts (P.PositionedDeclaration _ _ d) = getDeclName exts d
314− getDeclName _ _ = Nothing
315−
316− dctorNames :: P.Module -> [N.ProperName]
317− dctorNames m = nub $ concat $ map (P.exportedDctors m) dnames
318− where getDataDeclName :: P.Declaration -> Maybe N.ProperName
319− getDataDeclName (P.DataDeclaration _ name _ _) = Just name
320− getDataDeclName (P.PositionedDeclaration _ _ d) = getDataDeclName d
321− getDataDeclName _ = Nothing
322−
323− dnames :: [N.ProperName]
324− dnames = (mapMaybe getDataDeclName onlyDataDecls)
325−
326− onlyDataDecls :: [P.Declaration]
327− onlyDataDecls = (filter P.isDataDecl (P.exportedDeclarations m))
328−
329− moduleNames :: [P.Module] -> [String]
330− moduleNames ms = nub [show moduleName | P.Module _ moduleName _ _ <- ms]
331−
332− sorter :: Completion -> Completion -> Ordering
333− sorter (Completion _ d1 _) (Completion _ d2 _) = if ":" `isPrefixOf` d1 then LT else compare d1 d2
334160
335161-- Compilation
336162
@@ -407,7 +233,7 @@ createTemporaryModuleForImports PSCiState{psciImportedModules = imports} =
407233 in
408234 P.Module [] moduleName (importDecl `map` imports) Nothing
409235
410−importDecl :: C.ImportedModule -> P.Declaration
236+importDecl :: ImportedModule -> P.Declaration
411237importDecl (mn, declType, asQ) = P.ImportDeclaration mn declType asQ
412238
413239modulesDir :: FilePath
@@ -459,7 +285,7 @@ handleShowLoadedModules = do
459285 psciIO $ readModules loadedModules >>= putStrLn
460286 return ()
461287 where readModules = return . unlines . sort . nub . map toModuleName
462− toModuleName = N.runModuleName . (\ (D.Module _ mdName _ _) -> mdName) . snd
288+ toModuleName = N.runModuleName . (\ (P.Module _ mdName _ _) -> mdName) . snd
463289
464290-- |
465291-- Show the imported modules in psci.
@@ -476,17 +302,17 @@ handleShowImportedModules = do
476302 Just mn' -> "qualified " ++ N.runModuleName mn ++ " as " ++ N.runModuleName mn'
477303 Nothing -> N.runModuleName mn ++ " " ++ showDeclType declType
478304
479− showDeclType D.Implicit = ""
480− showDeclType (D.Explicit refs) = refsList refs
481− showDeclType (D.Hiding refs) = "hiding " ++ refsList refs
305+ showDeclType P.Implicit = ""
306+ showDeclType (P.Explicit refs) = refsList refs
307+ showDeclType (P.Hiding refs) = "hiding " ++ refsList refs
482308 refsList refs = "(" ++ commaList (map showRef refs) ++ ")"
483309
484310 showRef :: P.DeclarationRef -> String
485− showRef (D.TypeRef pn dctors) = show pn ++ "(" ++ maybe ".." (commaList . map N.runProperName) dctors ++ ")"
486− showRef (D.ValueRef ident) = show ident
487− showRef (D.TypeClassRef pn) = show pn
488− showRef (D.TypeInstanceRef ident) = show ident
489− showRef (D.PositionedDeclarationRef _ _ ref) = showRef ref
311+ showRef (P.TypeRef pn dctors) = show pn ++ "(" ++ maybe ".." (commaList . map N.runProperName) dctors ++ ")"
312+ showRef (P.ValueRef ident) = show ident
313+ showRef (P.TypeClassRef pn) = show pn
314+ showRef (P.TypeInstanceRef ident) = show ident
315+ showRef (P.PositionedDeclarationRef _ _ ref) = showRef ref
490316
491317 commaList :: [String] -> String
492318 commaList = intercalate ", "
@@ -494,7 +320,7 @@ handleShowImportedModules = do
494320-- |
495321-- Imports a module, preserving the initial state on failure.
496322--
497−handleImport :: C.ImportedModule -> PSCI ()
323+handleImport :: ImportedModule -> PSCI ()
498324handleImport im = do
499325 st <- updateImportedModules im <$> PSCI (lift get)
500326 let m = createTemporaryModuleForImports st
@@ -578,9 +404,9 @@ handleKindOf typ = do
578404-- Commands
579405
580406-- |
581−-- Parses the input and returns either a Metacommand or an expression.
407+-- Parses the input and returns either a Metacommand, or an error as a string.
582408--
583−getCommand :: Bool -> InputT (StateT PSCiState IO) (Either String (Maybe C.Command))
409+getCommand :: Bool -> InputT (StateT PSCiState IO) (Either String (Maybe Command))
584410getCommand singleLineMode = do
585411 firstLine <- getInputLine "> "
586412 case firstLine of
@@ -593,14 +419,14 @@ getCommand singleLineMode = do
593419 go ls = maybe (return . unlines $ reverse ls) (go . (:ls)) =<< getInputLine " "
594420
595421-- |
596−-- Performs an action for each meta-command given, and also for expressions..
422+-- Performs an action for each meta-command given, and also for expressions.
597423--
598−handleCommand :: C.Command -> PSCI ()
599−handleCommand (C.Expression val) = handleDeclaration val
600−handleCommand C.Help = PSCI $ outputStrLn helpMessage
601−handleCommand (C.Import im) = handleImport im
602−handleCommand (C.Decls l) = handleDecls l
603−handleCommand (C.LoadFile filePath) = do
424+handleCommand :: Command -> PSCI ()
425+handleCommand (Expression val) = handleDeclaration val
426+handleCommand ShowHelp = PSCI $ outputStrLn helpMessage
427+handleCommand (Import im) = handleImport im
428+handleCommand (Decls l) = handleDecls l
429+handleCommand (LoadFile filePath) = do
604430 absPath <- psciIO $ expandTilde filePath
605431 exists <- psciIO $ doesFileExist absPath
606432 if exists then do
@@ -611,7 +437,7 @@ handleCommand (C.LoadFile filePath) = do
611437 Right mods -> PSCI . lift $ modify (updateModules (map ((,) (Right absPath)) mods))
612438 else
613439 PSCI . outputStrLn $ "Couldn't locate: " ++ filePath
614−handleCommand C.Reset = do
440+handleCommand ResetState = do
615441 files <- psciImportedFilenames <$> PSCI (lift get)
616442 PSCI . lift . modify $ \st -> st
617443 { psciImportedFilenames = files
@@ -619,14 +445,14 @@ handleCommand C.Reset = do
619445 , psciLetBindings = []
620446 }
621447 loadAllImportedModules
622−handleCommand (C.TypeOf val) = handleTypeOf val
623−handleCommand (C.KindOf typ) = handleKindOf typ
624−handleCommand (C.Browse moduleName) = handleBrowse moduleName
625−handleCommand (C.Show "loaded") = handleShowLoadedModules
626−handleCommand (C.Show "import") = handleShowImportedModules
627−handleCommand _ = PSCI $ outputStrLn "Unknown command"
628−
629−loadUserConfig :: IO (Maybe [C.Command])
448+handleCommand (TypeOf val) = handleTypeOf val
449+handleCommand (KindOf typ) = handleKindOf typ
450+handleCommand (BrowseModule moduleName) = handleBrowse moduleName
451+handleCommand (ShowInfo QueryLoaded) = handleShowLoadedModules
452+handleCommand (ShowInfo QueryImport) = handleShowImportedModules
453+handleCommand QuitPSCi = error "`handleCommand QuitPSCi` was called. This is a bug."
454+
455+loadUserConfig :: IO (Maybe [Command])
630456loadUserConfig = do
631457 configFile <- (</> ".psci") <$> getCurrentDirectory
632458 exists <- doesFileExist configFile
@@ -662,7 +488,7 @@ loop PSCiOptions{..} = do
662488 case c of
663489 Left err -> outputStrLn err >> go
664490 Right Nothing -> go
665− Right (Just C.Quit) -> outputStrLn quitMessage
491+ Right (Just QuitPSCi) -> outputStrLn quitMessage
666492 Right (Just c') -> runPSCI (loadAllImportedModules >> handleCommand c') >> go
667493
668494multiLineMode :: Parser Bool
@@ -690,8 +516,8 @@ psciOptions = PSCiOptions <$> multiLineMode
690516 <*> many inputFile
691517 <*> nodeFlagsFlag
692518
693−main :: IO ()
694−main = execParser opts >>= loop
519+runPSCi :: IO ()
520+runPSCi = execParser opts >>= loop
695521 where
696522 opts = info (version <*> helper <*> psciOptions) infoModList
697523 infoModList = fullDesc <> headerInfo <> footerInfo