{-| Module : Idris.REPL.Parser Description : Parser for the REPL commands. License : BSD3 Maintainer : The Idris Community. -} module Idris.REPL.Parser ( parseCmd , help , allHelp , setOptions ) where import Idris.AbsSyntax import Idris.Colours import Idris.Core.TT import Idris.Help import qualified Idris.Parser as P import Idris.REPL.Commands import Control.Applicative import Control.Monad.State.Strict import qualified Data.ByteString.UTF8 as UTF8 import Data.Char (isSpace, toLower) import Data.List import Data.List.Split (splitOn) import Debug.Trace import System.Console.ANSI (Color(..)) import System.FilePath (()) import Text.Parser.Char (anyChar, oneOf) import Text.Parser.Combinators import Text.Trifecta (Result, parseString) import Text.Trifecta.Delta parseCmd :: IState -> String -> String -> Result (Either String Command) parseCmd i inputname = P.runparser pCmd i inputname . trim where trim = f . f where f = reverse . dropWhile isSpace type CommandTable = [ ( [String], CmdArg, String , String -> P.IdrisParser (Either String Command) ) ] setOptions :: [(String, Opt)] setOptions = [("errorcontext", ErrContext), ("showimplicits", ShowImpl), ("originalerrors", ShowOrigErr), ("autosolve", AutoSolve), ("nobanner", NoBanner), ("warnreach", WarnReach), ("evaltypes", EvalTypes), ("desugarnats", DesugarNats)] help :: [([String], CmdArg, String)] help = ([""], NoArg, "Evaluate an expression") : [ (map (':' :) names, args, text) | (names, args, text, _) <- parserCommandsForHelp ] allHelp :: [([String], CmdArg, String)] allHelp = [ (map (':' :) names, args, text) | (names, args, text, _) <- parserCommandsForHelp ++ parserCommands ] parserCommandsForHelp :: CommandTable parserCommandsForHelp = [ exprArgCmd ["t", "type"] Check "Check the type of an expression" , exprArgCmd ["core"] Core "View the core language representation of a term" , nameArgCmd ["miss", "missing"] Missing "Show missing clauses" , (["doc"], NameArg, "Show internal documentation", cmd_doc) , (["mkdoc"], NamespaceArg, "Generate IdrisDoc for namespace(s) and dependencies" , genArg "namespace" (many anyChar) MakeDoc) , (["apropos"], SeqArgs (OptionalArg PkgArgs) NameArg, " Search names, types, and documentation" , cmd_apropos) , (["s", "search"], SeqArgs (OptionalArg PkgArgs) ExprArg , " Search for values by type", cmd_search) , nameArgCmd ["wc", "whocalls"] WhoCalls "List the callers of some name" , nameArgCmd ["cw", "callswho"] CallsWho "List the callees of some name" , namespaceArgCmd ["browse"] Browse "List the contents of some namespace" , nameArgCmd ["total"] TotCheck "Check the totality of a name" , noArgCmd ["r", "reload"] Reload "Reload current file" , noArgCmd ["w", "watch"] Watch "Watch the current file for changes" , (["l", "load"], FileArg, "Load a new file" , strArg (\f -> Load f Nothing)) , (["!"], ShellCommandArg, "Run a shell command", strArg RunShellCommand) , (["cd"], FileArg, "Change working directory" , strArg ChangeDirectory) , (["module"], ModuleArg, "Import an extra module", moduleArg ModImport) -- NOTE: dragons , noArgCmd ["e", "edit"] Edit "Edit current file using $EDITOR or $VISUAL" , noArgCmd ["m", "metavars"] Metavars "Show remaining proof obligations (metavariables or holes)" , (["p", "prove"], MetaVarArg, "Prove a metavariable" , nameArg (Prove False)) , (["elab"], MetaVarArg, "Build a metavariable using the elaboration shell" , nameArg (Prove True)) , (["a", "addproof"], NameArg, "Add proof to source file", cmd_addproof) , (["rmproof"], NameArg, "Remove proof from proof stack" , nameArg RmProof) , (["showproof"], NameArg, "Show proof" , nameArg ShowProof) , noArgCmd ["proofs"] Proofs "Show available proofs" , exprArgCmd ["x"] ExecVal "Execute IO actions resulting from an expression using the interpreter" , (["c", "compile"], FileArg, "Compile to an executable [codegen] ", cmd_compile) , (["exec", "execute"], OptionalArg ExprArg, "Compile to an executable and run", cmd_execute) , (["dynamic"], FileArg, "Dynamically load a C library (similar to %dynamic)", cmd_dynamic) , (["dynamic"], NoArg, "List dynamically loaded C libraries", cmd_dynamic) , noArgCmd ["?", "h", "help"] Help "Display this help text" , optArgCmd ["set"] SetOpt $ "Set an option (" ++ optionsList ++ ")" , optArgCmd ["unset"] UnsetOpt "Unset an option" , (["color", "colour"], ColourArg , "Turn REPL colours on or off; set a specific colour" , cmd_colour) , (["consolewidth"], ConsoleWidthArg, "Set the width of the console", cmd_consolewidth) , (["printerdepth"], OptionalArg NumberArg, "Set the maximum pretty-printer depth (no arg for infinite)", cmd_printdepth) , noArgCmd ["q", "quit"] Quit "Exit the Idris system" , noArgCmd ["warranty"] Warranty "Displays warranty information" , (["let"], ManyArgs DeclArg , "Evaluate a declaration, such as a function definition, instance implementation, or fixity declaration" , cmd_let) , (["unlet", "undefine"], ManyArgs NameArg , "Remove the listed repl definitions, or all repl definitions if no names given" , cmd_unlet) , nameArgCmd ["printdef"] PrintDef "Show the definition of a function" , (["pp", "pprint"], (SeqArgs OptionArg (SeqArgs NumberArg NameArg)) , "Pretty prints an Idris function in either LaTeX or HTML and for a specified width." , cmd_pprint) , (["verbosity"], NumberArg, "Set verbosity level", cmd_verb) ] where optionsList = intercalate ", " $ map fst setOptions parserCommands :: CommandTable parserCommands = [ noArgCmd ["u", "universes"] Universes "Display universe constraints" , noArgCmd ["errorhandlers"] ListErrorHandlers "List registered error handlers" , nameArgCmd ["d", "def"] Defn "Display a name's internal definitions" , nameArgCmd ["transinfo"] TransformInfo "Show relevant transformation rules for a name" , nameArgCmd ["di", "dbginfo"] DebugInfo "Show debugging information for a name" , exprArgCmd ["patt"] Pattelab "(Debugging) Elaborate pattern expression" , exprArgCmd ["spec"] Spec "?" , exprArgCmd ["whnf"] WHNF "(Debugging) Show weak head normal form of an expression" , exprArgCmd ["inline"] TestInline "?" , proofArgCmd ["cs", "casesplit"] CaseSplitAt ":cs splits the pattern variable on the line" , proofArgCmd ["apc", "addproofclause"] AddProofClauseFrom ":apc adds a pattern-matching proof clause to name on line" , proofArgCmd ["ac", "addclause"] AddClauseFrom ":ac adds a clause for the definition of the name on the line" , proofArgCmd ["am", "addmissing"] AddMissing ":am adds all missing pattern matches for the name on the line" , proofArgCmd ["mw", "makewith"] MakeWith ":mw adds a with clause for the definition of the name on the line" , proofArgCmd ["mc", "makecase"] MakeCase ":mc adds a case block for the definition of the metavariable on the line" , proofArgCmd ["ml", "makelemma"] MakeLemma "?" , (["log"], NumberArg, "Set logging level", cmd_log) , ( ["logcats"] , ManyArgs NameArg , "Set logging categories" , cmd_cats) , (["lto", "loadto"], SeqArgs NumberArg FileArg , "Load file up to line number", cmd_loadto) , (["ps", "proofsearch"], NoArg , ":ps does proof search for name on line, with names as hints" , cmd_proofsearch) , (["ref", "refine"], NoArg , ":ref attempts to partially solve name on line, with name' as hint, introducing metavariables for arguments that aren't inferrable" , cmd_refine) , (["debugunify"], SeqArgs ExprArg ExprArg , "(Debugging) Try to unify two expressions", const $ do l <- P.simpleExpr defaultSyntax r <- P.simpleExpr defaultSyntax eof return (Right (DebugUnify l r)) ) ] noArgCmd names command doc = (names, NoArg, doc, noArgs command) nameArgCmd names command doc = (names, NameArg, doc, fnNameArg command) namespaceArgCmd names command doc = (names, NamespaceArg, doc, namespaceArg command) exprArgCmd names command doc = (names, ExprArg, doc, exprArg command) metavarArgCmd names command doc = (names, MetaVarArg, doc, fnNameArg command) optArgCmd names command doc = (names, OptionArg, doc, optArg command) proofArgCmd names command doc = (names, NoArg, doc, proofArg command) pCmd :: P.IdrisParser (Either String Command) pCmd = choice [ do c <- cmd names; parser c | (names, _, _, parser) <- parserCommandsForHelp ++ parserCommands ] <|> unrecognized <|> nop <|> eval where nop = do eof; return (Right NOP) unrecognized = do P.lchar ':' cmd <- many anyChar let cmd' = takeWhile (/=' ') cmd return (Left $ "Unrecognized command: " ++ cmd') cmd :: [String] -> P.IdrisParser String cmd xs = try $ do P.lchar ':' docmd sorted_xs where docmd [] = fail "Could not parse command" docmd (x:xs) = try (P.reserved x >> return x) <|> docmd xs sorted_xs = sortBy (\x y -> compare (length y) (length x)) xs noArgs :: Command -> String -> P.IdrisParser (Either String Command) noArgs cmd name = do let emptyArgs = do eof return (Right cmd) let failure = return (Left $ ":" ++ name ++ " takes no arguments") emptyArgs <|> failure eval :: P.IdrisParser (Either String Command) eval = do t <- P.fullExpr defaultSyntax return $ Right (Eval t) exprArg :: (PTerm -> Command) -> String -> P.IdrisParser (Either String Command) exprArg cmd name = do let noArg = do eof return $ Left ("Usage is :" ++ name ++ " ") let justOperator = do (op, fc) <- P.operatorFC eof return $ Right $ cmd (PRef fc [] (sUN op)) let properArg = do t <- P.fullExpr defaultSyntax return $ Right (cmd t) try noArg <|> try justOperator <|> properArg genArg :: String -> P.IdrisParser a -> (a -> Command) -> String -> P.IdrisParser (Either String Command) genArg argName argParser cmd name = do let emptyArgs = do eof; failure oneArg = do arg <- argParser eof return (Right (cmd arg)) try emptyArgs <|> oneArg <|> failure where failure = return $ Left ("Usage is :" ++ name ++ " <" ++ argName ++ ">") nameArg, fnNameArg :: (Name -> Command) -> String -> P.IdrisParser (Either String Command) nameArg = genArg "name" $ fst <$> P.name fnNameArg = genArg "functionname" $ fst <$> P.fnName strArg :: (String -> Command) -> String -> P.IdrisParser (Either String Command) strArg = genArg "string" (many anyChar) moduleArg :: (FilePath -> Command) -> String -> P.IdrisParser (Either String Command) moduleArg = genArg "module" (fmap (toPath . fst) P.identifier) where toPath n = foldl1' () $ splitOn "." n namespaceArg :: ([String] -> Command) -> String -> P.IdrisParser (Either String Command) namespaceArg = genArg "namespace" (fmap (toNS . fst) P.identifier) where toNS = splitOn "." optArg :: (Opt -> Command) -> String -> P.IdrisParser (Either String Command) optArg cmd name = do let emptyArgs = do eof return $ Left ("Usage is :" ++ name ++ "