Add REPL
This commit is contained in:
86
src/Lib/ReplParser.newt
Normal file
86
src/Lib/ReplParser.newt
Normal file
@@ -0,0 +1,86 @@
|
||||
module Lib.ReplParser
|
||||
|
||||
import Prelude
|
||||
import Lib.Parser.Impl
|
||||
import Lib.Parser
|
||||
import Lib.Token
|
||||
import Lib.Common
|
||||
import Data.List1
|
||||
|
||||
data ReplCommand
|
||||
= Load String
|
||||
| OutputJS String
|
||||
| Verbose (Maybe Int)
|
||||
| GetDoc String
|
||||
| BrowseCmd QName
|
||||
| HelpCmd
|
||||
|
||||
kw : String → Parser String
|
||||
kw s = satisfy (\t => t.val.text == s) "Expected \{show s}"
|
||||
|
||||
replString : Parser String
|
||||
replString = do
|
||||
token StartQuote
|
||||
s <- token StringKind
|
||||
token EndQuote
|
||||
pure s
|
||||
|
||||
replInt : Parser Int
|
||||
replInt = do
|
||||
t <- token Number
|
||||
pure $ stringToInt t
|
||||
|
||||
replQN : Parser QName
|
||||
replQN = do
|
||||
ident <- uident
|
||||
rest <- many $ token Projection
|
||||
let name = joinBy "" (ident :: rest)
|
||||
pure $ uncurry QN $ unsnoc $ split1 name "."
|
||||
|
||||
data ArgType = ArgNone | ArgString | ArgIdent | ArgOptInt | ArgQName
|
||||
|
||||
argCon : ArgType → U
|
||||
argCon ArgNone = ReplCommand
|
||||
argCon ArgOptInt = Maybe Int → ReplCommand
|
||||
argCon ArgIdent = String → ReplCommand
|
||||
argCon ArgString = String → ReplCommand
|
||||
argCon ArgQName = QName → ReplCommand
|
||||
|
||||
data CmdDesc : U where
|
||||
MkCmd : String → String → (arg : ArgType) → argCon arg → CmdDesc
|
||||
|
||||
commands : List CmdDesc
|
||||
commands
|
||||
= MkCmd ":h" "Show this help" ArgNone HelpCmd
|
||||
:: MkCmd ":help" "Show this help" ArgNone HelpCmd
|
||||
:: MkCmd ":l" "load file" ArgString Load
|
||||
:: MkCmd ":load" "load file" ArgString Load
|
||||
:: MkCmd ":o" "write javascript file" ArgString OutputJS
|
||||
:: MkCmd ":out" "write javascript file" ArgString OutputJS
|
||||
:: MkCmd ":v" "change verbosity" ArgOptInt Verbose
|
||||
:: MkCmd ":d" "document function" ArgIdent GetDoc
|
||||
:: MkCmd ":doc" "document function" ArgIdent GetDoc
|
||||
:: MkCmd ":b" "browse namespace" ArgQName BrowseCmd
|
||||
-- type at point
|
||||
-- solve hole
|
||||
-- search by prefix (for autocomplete - ideally include types at point, but we'd need recovery)
|
||||
-- Ideally we could auto-import too
|
||||
-- case split
|
||||
:: Nil
|
||||
|
||||
parseCommand : Parser ReplCommand
|
||||
parseCommand = do
|
||||
key <- ident
|
||||
let (Just cmd) = lookup key commands
|
||||
| _ => fail "Unknown command"
|
||||
the (Parser ReplCommand) $ case cmd of
|
||||
MkCmd _ _ ArgNone cstr => pure cstr
|
||||
MkCmd _ _ ArgOptInt cstr => cstr <$> optional replInt
|
||||
MkCmd _ _ ArgIdent cstr => cstr <$> (ident <|> uident)
|
||||
MkCmd _ _ ArgString cstr => cstr <$> replString
|
||||
MkCmd _ _ ArgQName cstr => cstr <$> replQN
|
||||
where
|
||||
lookup : String → List CmdDesc → Maybe CmdDesc
|
||||
lookup key (cmd@(MkCmd nm _ _ _) :: rest) =
|
||||
if key == nm then Just cmd else lookup key rest
|
||||
lookup key Nil = Nothing
|
||||
@@ -19,7 +19,9 @@ lookup qn@(QN ns nm) top =
|
||||
Just mod => lookupMap' qn mod.modDefs
|
||||
Nothing => Nothing
|
||||
|
||||
-- TODO - look at imported namespaces, and either have a map of imported names or search imported namespaces..
|
||||
lookupAll : String → TopContext -> List TopEntry
|
||||
lookupAll raw top =
|
||||
mapMaybe (flip lookup top) $ (QN top.ns raw) :: map (flip QN raw) top.imported
|
||||
|
||||
lookupRaw : String -> TopContext -> Maybe TopEntry
|
||||
lookupRaw raw top =
|
||||
|
||||
@@ -18,8 +18,10 @@ import Lib.Tokenizer
|
||||
import Lib.TopContext
|
||||
import Lib.Types
|
||||
import Lib.Syntax
|
||||
import Lib.ReplParser
|
||||
import Node
|
||||
import Serialize
|
||||
import Revision
|
||||
|
||||
-- For editors, dump some information about the context (fc, name, type)
|
||||
jsonTopContext : M Json
|
||||
@@ -259,10 +261,72 @@ cmdLine (fn :: args) = do
|
||||
(out, files) <- cmdLine args
|
||||
pure (out, fn :: files)
|
||||
|
||||
browseTop : QName → M Unit
|
||||
browseTop qn@(QN ns x) = do
|
||||
top <- getTop
|
||||
let ns = snoc ns x
|
||||
let (Just mod) = lookupMap' ns top.modules
|
||||
| _ => putStrLn "module \{show qn} not loaded"
|
||||
go $ listValues mod.modDefs
|
||||
where
|
||||
go : List TopEntry → M Unit
|
||||
go Nil = pure MkUnit
|
||||
go (e :: es) = do
|
||||
putStrLn "\{show e.name} : \{rpprint Nil e.type}"
|
||||
go es
|
||||
|
||||
replHeader : M Unit
|
||||
replHeader = putStrLn "Newt REPL (\{show gitRevision})\n"
|
||||
|
||||
getDoc : String → M Unit
|
||||
getDoc name = do
|
||||
top <- getTop
|
||||
for_ (lookupAll name top) $ \e =>
|
||||
putStrLn "\{show e.name} : \{rpprint Nil e.type}"
|
||||
|
||||
replHelp : M Unit
|
||||
replHelp =
|
||||
for_ commands $ \cmd => do
|
||||
let args = case cmd of
|
||||
MkCmd _ _ ArgNone _ => ""
|
||||
MkCmd _ _ ArgQName _ => " Lib.Types"
|
||||
MkCmd _ _ ArgOptInt _ => " [ 1 ]"
|
||||
MkCmd _ _ ArgIdent _ => " map"
|
||||
MkCmd _ _ ArgString _ => " \"name\""
|
||||
case cmd of
|
||||
(MkCmd kw desc _ _) => putStrLn "\{kw}\{args} - \{desc}"
|
||||
|
||||
runCommand : ReplCommand → M Unit
|
||||
runCommand (Load fn) = processFile fn
|
||||
runCommand (HelpCmd) = replHelp
|
||||
runCommand (BrowseCmd qn) = browseTop qn
|
||||
runCommand (GetDoc name) = getDoc name
|
||||
runCommand (Verbose Nothing) = modifyTop [ verbose $= _+_ 1 ]
|
||||
runCommand (Verbose (Just v)) = modifyTop [ verbose := v ]
|
||||
runCommand (OutputJS fn) = writeSource fn
|
||||
|
||||
runRepl : M Unit
|
||||
runRepl = do
|
||||
liftIO $ putStr "> "
|
||||
Right line <- liftIO {M} $ readLine
|
||||
| Left err => pure MkUnit
|
||||
let (Right toks) = tokenise "<stdin>" line
|
||||
| Left err => putStrLn (showError line err) >> runRepl
|
||||
let (Right cmd) = parse "<stdin>" parseCommand toks
|
||||
| Left err => putStrLn (showError line err) >> runRepl
|
||||
catchError (runCommand cmd) (\ err => putStrLn $ showError line err)
|
||||
runRepl
|
||||
|
||||
-- TODO translate args into REPL commands?
|
||||
main' : M Unit
|
||||
main' = do
|
||||
(arg0 :: args) <- liftIO {M} $ getArgs
|
||||
| _ => error emptyFC "error reading args"
|
||||
-- run REPL if there are no args
|
||||
let (_ :: _) = args
|
||||
| _ => do
|
||||
replHeader
|
||||
runRepl
|
||||
(out, files) <- cmdLine args
|
||||
traverse processFile files
|
||||
|
||||
@@ -274,7 +338,6 @@ main' = do
|
||||
Nothing => pure MkUnit
|
||||
Just name => writeSource name
|
||||
|
||||
|
||||
main : IO Unit
|
||||
main = do
|
||||
-- we'll need to reset for each file, etc.
|
||||
|
||||
@@ -34,3 +34,24 @@ pfunc exitFailure : ∀ a. String → a := `(_, msg) => {
|
||||
process.exit(1);
|
||||
}`
|
||||
|
||||
pfunc putStr uses (MkIORes MkUnit): String → IO Unit := `(s) => (w) => {
|
||||
let {writeSync} = require('fs');
|
||||
let buf = new TextEncoder().encode(s);
|
||||
writeSync(1, buf);
|
||||
return Prelude_MkIORes(Prelude_MkUnit, w);
|
||||
}`
|
||||
|
||||
pfunc readLine uses (MkIORes Left Right) : IO (Either String String) := `(w) => {
|
||||
let {readSync} = require('fs');
|
||||
let buf = Buffer.alloc(1024);
|
||||
let p = 0
|
||||
while (readSync(0, buf, p, 1, null)) {
|
||||
if (buf[p++] == 10) return Prelude_MkIORes(Prelude_Right(new TextDecoder().decode(buf.slice(0,p))),w);
|
||||
if (p + 10 > buf.length) {
|
||||
let tmp = Buffer.alloc(buf.length * 1.5);
|
||||
tmp.set(buf);
|
||||
buf = tmp;
|
||||
}
|
||||
}
|
||||
return Prelude_MkIORes(Prelude_Left("EOF"));
|
||||
}`
|
||||
|
||||
Reference in New Issue
Block a user