forked from purescript/purescript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInteractive.hs
More file actions
363 lines (329 loc) · 13.5 KB
/
Interactive.hs
File metadata and controls
363 lines (329 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
{-# LANGUAGE DoAndIfThenElse #-}
module Language.PureScript.Interactive
( handleCommand
, module Interactive
-- TODO: remove these exports
, make
, runMake
) where
import Prelude
import Protolude (ordNub)
import Data.List (sort, find, foldl')
import Data.Maybe (fromMaybe, mapMaybe)
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.State.Class
import Control.Monad.Reader.Class
import Control.Monad.Trans.Except (ExceptT(..), runExceptT)
import Control.Monad.Trans.State.Strict (StateT, runStateT, evalStateT)
import Control.Monad.Writer.Strict (Writer(), runWriter)
import qualified Language.PureScript as P
import qualified Language.PureScript.CST as CST
import qualified Language.PureScript.Names as N
import qualified Language.PureScript.Constants.Prim as C
import Language.PureScript.Interactive.Completion as Interactive
import Language.PureScript.Interactive.IO as Interactive
import Language.PureScript.Interactive.Message as Interactive
import Language.PureScript.Interactive.Module as Interactive
import Language.PureScript.Interactive.Parser as Interactive
import Language.PureScript.Interactive.Printer as Interactive
import Language.PureScript.Interactive.Types as Interactive
import System.Directory (getCurrentDirectory)
import System.FilePath ((</>))
import System.FilePath.Glob (glob)
-- | Pretty-print errors
printErrors :: MonadIO m => P.MultipleErrors -> m ()
printErrors errs = liftIO $ do
pwd <- getCurrentDirectory
putStrLn $ P.prettyPrintMultipleErrors P.defaultPPEOptions {P.ppeRelativeDirectory = pwd} errs
-- | This is different than the runMake in 'Language.PureScript.Make' in that it specifies the
-- options and ignores the warning messages.
runMake :: P.Make a -> IO (Either P.MultipleErrors a)
runMake mk = fst <$> P.runMake P.defaultOptions mk
-- | Rebuild a module, using the cached externs data for dependencies.
rebuild
:: [P.ExternsFile]
-> P.Module
-> P.Make (P.ExternsFile, P.Environment)
rebuild loadedExterns m = do
externs <- P.rebuildModule buildActions loadedExterns m
return (externs, foldl' (flip P.applyExternsFileToEnvironment) P.initEnvironment (loadedExterns ++ [externs]))
where
buildActions :: P.MakeActions P.Make
buildActions =
(P.buildMakeActions modulesDir
filePathMap
M.empty
False) { P.progress = const (return ()) }
filePathMap :: M.Map P.ModuleName (Either P.RebuildPolicy FilePath)
filePathMap = M.singleton (P.getModuleName m) (Left P.RebuildAlways)
-- | Build the collection of modules from scratch. This is usually done on startup.
make
:: [(FilePath, CST.PartialResult P.Module)]
-> P.Make ([P.ExternsFile], P.Environment)
make ms = do
foreignFiles <- P.inferForeignModules filePathMap
externs <- P.make (buildActions foreignFiles) (map snd ms)
return (externs, foldl' (flip P.applyExternsFileToEnvironment) P.initEnvironment externs)
where
buildActions :: M.Map P.ModuleName FilePath -> P.MakeActions P.Make
buildActions foreignFiles =
P.buildMakeActions modulesDir
filePathMap
foreignFiles
False
filePathMap :: M.Map P.ModuleName (Either P.RebuildPolicy FilePath)
filePathMap = M.fromList $ map (\(fp, m) -> (P.getModuleName $ CST.resPartial m, Right fp)) ms
-- | Performs a PSCi command
handleCommand
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> (String -> m ()) -- ^ evaluate JS
-> m () -- ^ reload
-> (String -> m ()) -- ^ print into console
-> Command
-> m ()
handleCommand _ _ p ShowHelp = p helpMessage
handleCommand _ r _ ReloadState = handleReloadState r
handleCommand _ r _ ClearState = handleClearState r
handleCommand e _ _ (Expression val) = handleExpression e val
handleCommand _ _ _ (Import im) = handleImport im
handleCommand _ _ _ (Decls l) = handleDecls l
handleCommand _ _ p (TypeOf val) = handleTypeOf p val
handleCommand _ _ p (KindOf typ) = handleKindOf p typ
handleCommand _ _ p (BrowseModule moduleName) = handleBrowse p moduleName
handleCommand _ _ p (ShowInfo QueryLoaded) = handleShowLoadedModules p
handleCommand _ _ p (ShowInfo QueryImport) = handleShowImportedModules p
handleCommand _ _ p (ShowInfo QueryPrint) = handleShowPrint p
handleCommand _ _ p (CompleteStr prefix) = handleComplete p prefix
handleCommand _ _ p (SetInteractivePrint ip) = handleSetInteractivePrint p ip
handleCommand _ _ _ _ = P.internalError "handleCommand: unexpected command"
-- | Reload the application state
handleReloadState
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> m ()
-> m ()
handleReloadState reload = do
modify $ updateLets (const [])
globs <- asks psciFileGlobs
files <- liftIO $ concat <$> traverse glob globs
e <- runExceptT $ do
modules <- ExceptT . liftIO $ loadAllModules files
(externs, _) <- ExceptT . liftIO . runMake . make $ fmap CST.pureResult <$> modules
return (map snd modules, externs)
case e of
Left errs -> printErrors errs
Right (modules, externs) -> do
modify (updateLoadedExterns (const (zip modules externs)))
reload
-- | Clear the application state
handleClearState
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> m ()
-> m ()
handleClearState reload = do
modify $ updateImportedModules (const [])
handleReloadState reload
-- | Takes a value expression and evaluates it with the current state.
handleExpression
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> P.Expr
-> m ()
handleExpression evaluate val = do
st <- get
let m = createTemporaryModule True st val
e <- liftIO . runMake $ rebuild (map snd (psciLoadedExterns st)) m
case e of
Left errs -> printErrors errs
Right _ -> do
js <- liftIO $ readFile (modulesDir </> "$PSCI" </> "index.js")
evaluate js
-- |
-- Takes a list of declarations and updates the environment, then run a make. If the declaration fails,
-- restore the original environment.
--
handleDecls
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> [P.Declaration]
-> m ()
handleDecls ds = do
st <- gets (updateLets (++ ds))
let m = createTemporaryModule False st (P.Literal P.nullSourceSpan (P.ObjectLiteral []))
e <- liftIO . runMake $ rebuild (map snd (psciLoadedExterns st)) m
case e of
Left err -> printErrors err
Right _ -> put st
-- | Show actual loaded modules in psci.
handleShowLoadedModules
:: (MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> m ()
handleShowLoadedModules print' = do
loadedModules <- gets psciLoadedExterns
print' $ readModules loadedModules
where
readModules = unlines . sort . ordNub . map (T.unpack . P.runModuleName . P.getModuleName . fst)
-- | Show the imported modules in psci.
handleShowImportedModules
:: (MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> m ()
handleShowImportedModules print' = do
importedModules <- psciImportedModules <$> get
print' $ showModules importedModules
where
showModules = unlines . sort . map (T.unpack . showModule)
showModule (mn, declType, asQ) =
"import " <> N.runModuleName mn <> showDeclType declType <>
foldMap (\mn' -> " as " <> N.runModuleName mn') asQ
showDeclType P.Implicit = ""
showDeclType (P.Explicit refs) = refsList refs
showDeclType (P.Hiding refs) = " hiding " <> refsList refs
refsList refs = " (" <> commaList (mapMaybe showRef refs) <> ")"
showRef :: P.DeclarationRef -> Maybe Text
showRef (P.TypeRef _ pn dctors) =
Just $ N.runProperName pn <> "(" <> maybe ".." (commaList . map N.runProperName) dctors <> ")"
showRef (P.TypeOpRef _ op) =
Just $ "type " <> N.showOp op
showRef (P.ValueRef _ ident) =
Just $ N.runIdent ident
showRef (P.ValueOpRef _ op) =
Just $ N.showOp op
showRef (P.TypeClassRef _ pn) =
Just $ "class " <> N.runProperName pn
showRef (P.TypeInstanceRef _ ident P.UserNamed) =
Just $ N.runIdent ident
showRef (P.TypeInstanceRef _ _ P.CompilerNamed) =
Nothing
showRef (P.ModuleRef _ name) =
Just $ "module " <> N.runModuleName name
showRef (P.ReExportRef _ _ _) =
Nothing
commaList :: [Text] -> Text
commaList = T.intercalate ", "
handleShowPrint
:: (MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> m ()
handleShowPrint print' = do
current <- psciInteractivePrint <$> get
if current == initialInteractivePrint
then
print' $
"The interactive print function is currently set to the default (`" ++ showPrint current ++ "`)"
else
print' $
"The interactive print function is currently set to `" ++ showPrint current ++ "`\n" ++
"The default can be restored with `:print " ++ showPrint initialInteractivePrint ++ "`"
where
showPrint (mn, ident) = T.unpack (N.runModuleName mn <> "." <> N.runIdent ident)
-- | Imports a module, preserving the initial state on failure.
handleImport
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> ImportedModule
-> m ()
handleImport im = do
st <- gets (updateImportedModules (im :))
let m = createTemporaryModuleForImports st
e <- liftIO . runMake $ rebuild (map snd (psciLoadedExterns st)) m
case e of
Left errs -> printErrors errs
Right _ -> put st
-- | Takes a value and prints its type
handleTypeOf
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> P.Expr
-> m ()
handleTypeOf print' val = do
st <- get
let m = createTemporaryModule False st val
e <- liftIO . runMake $ rebuild (map snd (psciLoadedExterns st)) m
case e of
Left errs -> printErrors errs
Right (_, env') ->
case M.lookup (P.mkQualified (P.Ident "it") (P.ModuleName "$PSCI")) (P.names env') of
Just (ty, _, _) -> print' . P.prettyPrintType maxBound $ ty
Nothing -> print' "Could not find type"
-- | Takes a type and prints its kind
handleKindOf
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> P.SourceType
-> m ()
handleKindOf print' typ = do
st <- get
let m = createTemporaryModuleForKind st typ
mName = P.ModuleName "$PSCI"
e <- liftIO . runMake $ rebuild (map snd (psciLoadedExterns st)) m
case e of
Left errs -> printErrors errs
Right (_, env') ->
case M.lookup (P.Qualified (P.ByModuleName mName) $ P.ProperName "IT") (P.typeSynonyms env') of
Just (_, typ') -> do
let chk = (P.emptyCheckState env') { P.checkCurrentModule = Just mName }
k = check (snd <$> P.kindOf typ') chk
check :: StateT P.CheckState (ExceptT P.MultipleErrors (Writer P.MultipleErrors)) a -> P.CheckState -> Either P.MultipleErrors (a, P.CheckState)
check sew = fst . runWriter . runExceptT . runStateT sew
case k of
Left err -> printErrors err
Right (kind, _) -> print' . P.prettyPrintType 1024 $ kind
Nothing -> print' "Could not find kind"
-- | Browse a module and displays its signature
handleBrowse
:: (MonadReader PSCiConfig m, MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> P.ModuleName
-> m ()
handleBrowse print' moduleName = do
st <- get
let env = psciEnvironment st
case findMod moduleName (psciLoadedExterns st) (psciImportedModules st) of
Just qualName -> print' $ printModuleSignatures qualName env
Nothing -> failNotInEnv moduleName
where
findMod needle externs imports =
let qualMod = fromMaybe needle (lookupUnQualifiedModName needle imports)
modules = S.fromList (C.primModules <> (P.getModuleName . fst <$> externs))
in if qualMod `S.member` modules
then Just qualMod
else Nothing
failNotInEnv modName = print' $ T.unpack $ "Module '" <> N.runModuleName modName <> "' is not valid."
lookupUnQualifiedModName needle imports =
(\(modName,_,_) -> modName) <$> find (\(_,_,mayQuaName) -> mayQuaName == Just needle) imports
-- | Return output as would be returned by tab completion, for tools integration etc.
handleComplete
:: (MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> String
-> m ()
handleComplete print' prefix = do
st <- get
let act = liftCompletionM (completion' (reverse prefix, ""))
results <- evalStateT act st
print' $ unlines (formatCompletions results)
-- | Attempt to set the interactive print function. Note that the state will
-- only be updated if the interactive print function exists and appears to
-- work; we test it by attempting to evaluate '0'.
handleSetInteractivePrint
:: (MonadState PSCiState m, MonadIO m)
=> (String -> m ())
-> (P.ModuleName, P.Ident)
-> m ()
handleSetInteractivePrint print' new = do
current <- gets psciInteractivePrint
modify (setInteractivePrint new)
st <- get
let expr = P.Literal internalSpan (P.NumericLiteral (Left 0))
let m = createTemporaryModule True st expr
e <- liftIO . runMake $ rebuild (map snd (psciLoadedExterns st)) m
case e of
Left errs -> do
modify (setInteractivePrint current)
print' "Unable to set the repl's printing function:"
printErrors errs
Right _ ->
pure ()