module Language.PureScript.Interactive.Parser
( parseDotFile
, parseCommand
) where
import Prelude.Compat hiding (lex)
import Control.Monad (join, unless)
import Data.Bifunctor (first)
import Data.Char (isSpace)
import Data.List (intercalate)
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T
import qualified Language.PureScript as P
import qualified Language.PureScript.CST as CST
import qualified Language.PureScript.CST.Monad as CSTM
import qualified Language.PureScript.CST.Positions as CST
import qualified Language.PureScript.Interactive.Directive as D
import Language.PureScript.Interactive.Types
parseDotFile :: FilePath -> String -> Either String [Command]
parseDotFile filePath =
first (CST.prettyPrintError . NE.head)
. CST.runTokenParser (parseMany parser <* CSTM.token CST.TokEof)
. CST.lexTopLevel
. T.pack
where
parser = CSTM.oneOf $ NE.fromList
[ psciImport filePath
, do
tok <- CSTM.munch
CSTM.parseFail tok $ CST.ErrCustom "The .purs-repl file only supports import declarations"
]
parseCommand :: String -> Either String [Command]
parseCommand cmdString =
case cmdString of
(':' : cmd) -> pure <$> parseDirective cmd
_ -> parseRest (mergeDecls <$> parseMany psciCommand) cmdString
where
mergeDecls (Decls as : bs) =
case mergeDecls bs of
Decls bs' : cs' ->
Decls (as <> bs') : cs'
cs' ->
Decls as : cs'
mergeDecls (a : bs) =
a : mergeDecls bs
mergeDecls [] = []
parseMany :: CST.Parser a -> CST.Parser [a]
parseMany = CSTM.manyDelimited CST.TokLayoutStart CST.TokLayoutEnd CST.TokLayoutSep
parseOne :: CST.Parser a -> CST.Parser a
parseOne p = CSTM.token CST.TokLayoutStart *> p <* CSTM.token CST.TokLayoutEnd
parseRest :: CST.Parser a -> String -> Either String a
parseRest p =
first (CST.prettyPrintError . NE.head)
. CST.runTokenParser (p <* CSTM.token CST.TokEof)
. CST.lexTopLevel
. T.pack
psciCommand :: CST.Parser Command
psciCommand =
CSTM.oneOf $ NE.fromList
[ psciImport ""
, psciDeclaration
, psciExpression
]
trim :: String -> String
trim = trimEnd . trimStart
trimStart :: String -> String
trimStart = dropWhile isSpace
trimEnd :: String -> String
trimEnd = reverse . trimStart . reverse
parseDirective :: String -> Either String Command
parseDirective cmd =
case D.directivesFor' dstr of
[(d, _)] -> commandFor d
[] -> Left "Unrecognized directive. Type :? for help."
ds -> Left ("Ambiguous directive. Possible matches: " ++
intercalate ", " (map snd ds) ++ ". Type :? for help.")
where
(dstr, arg) = trim <$> break isSpace cmd
commandFor d = case d of
Help -> return ShowHelp
Quit -> return QuitPSCi
Reload -> return ReloadState
Clear -> return ClearState
Paste -> return PasteLines
Browse -> BrowseModule . CST.nameValue <$> parseRest (parseOne CST.parseModuleNameP) arg
Show -> ShowInfo <$> parseReplQuery' arg
Type -> TypeOf . CST.convertExpr "" <$> parseRest (parseOne CST.parseExprP) arg
Kind -> KindOf . CST.convertType "" <$> parseRest (parseOne CST.parseTypeP) arg
Complete -> return (CompleteStr arg)
Print
| arg == "" -> return $ ShowInfo QueryPrint
| otherwise -> SetInteractivePrint <$> parseRest (parseOne parseFullyQualifiedIdent) arg
psciExpression :: CST.Parser Command
psciExpression = Expression . CST.convertExpr "" <$> CST.parseExprP
psciImport :: FilePath -> CST.Parser Command
psciImport filePath = do
(_, mn, declType, asQ) <- CST.convertImportDecl filePath <$> CST.parseImportDeclP
pure $ Import (mn, declType, asQ)
psciDeclaration :: CST.Parser Command
psciDeclaration = do
decl <- CST.parseDeclP
let decl' = CST.convertDeclaration "" decl
unless (all acceptable decl') $ do
let
tok = fst $ CST.declRange decl
tok' = T.unpack $ CST.printToken $ CST.tokValue tok
msg = tok' <> "; this kind of declaration is not supported in psci"
CSTM.parseFail tok $ CST.ErrLexeme (Just msg) []
pure $ Decls decl'
acceptable :: P.Declaration -> Bool
acceptable P.DataDeclaration{} = True
acceptable P.TypeSynonymDeclaration{} = True
acceptable P.ExternDeclaration{} = True
acceptable P.ExternDataDeclaration{} = True
acceptable P.TypeClassDeclaration{} = True
acceptable P.TypeInstanceDeclaration{} = True
acceptable P.ExternKindDeclaration{} = True
acceptable P.TypeDeclaration{} = True
acceptable P.ValueDeclaration{} = True
acceptable _ = False
parseReplQuery' :: String -> Either String ReplQuery
parseReplQuery' str =
case parseReplQuery str of
Nothing -> Left ("Don't know how to show " ++ str ++ ". Try one of: " ++
intercalate ", " replQueryStrings ++ ".")
Just query -> Right query
parseFullyQualifiedIdent :: CST.Parser (P.ModuleName, P.Ident)
parseFullyQualifiedIdent = join $ CST.Parser $ \st _ ksucc ->
case CST.runParser st CST.parseQualIdentP of
(st', Right (CST.QualifiedName _ (Just mn) ident)) ->
ksucc st' $ pure (mn, P.Ident $ CST.getIdent ident)
_ ->
ksucc st $ do
tok <- CSTM.munch
CSTM.parseFail tok $ CST.ErrCustom "Expected a fully-qualified name (eg: PSCI.Support.eval)"