diff --git a/.gitignore b/.gitignore index 19114cd..19095cd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ node_modules mkday.py tmp min.js.gz +src/Revision.newt diff --git a/Makefile b/Makefile index 5e098b7..59e38c9 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,12 @@ RUNJS=node all: newt.js -newt.js: ${SRCS} + +REV=$(shell git rev-parse --short HEAD) +src/Revision.newt: .PHONY + echo "module Revision\nimport Prelude\ngitRevision : String\ngitRevision = \"${REV}\"" > src/Revision.newt + +newt.js: ${SRCS} src/Revision.newt -rm build/* >/dev/null $(RUNJS) bootstrap/newt.js src/Main.newt -o newt.js diff --git a/src/Lib/ReplParser.newt b/src/Lib/ReplParser.newt new file mode 100644 index 0000000..5ac3fab --- /dev/null +++ b/src/Lib/ReplParser.newt @@ -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 diff --git a/src/Lib/TopContext.newt b/src/Lib/TopContext.newt index 055136e..79df380 100644 --- a/src/Lib/TopContext.newt +++ b/src/Lib/TopContext.newt @@ -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 = diff --git a/src/Main.newt b/src/Main.newt index eed682e..48810d7 100644 --- a/src/Main.newt +++ b/src/Main.newt @@ -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 "" line + | Left err => putStrLn (showError line err) >> runRepl + let (Right cmd) = parse "" 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. diff --git a/src/Node.newt b/src/Node.newt index 8f2d2df..a7f9485 100644 --- a/src/Node.newt +++ b/src/Node.newt @@ -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")); +}`