forked from GitHub/gf-core
Rudimentary resource REPL in gf-compiler
This commit is contained in:
132
src/compiler/api/GF/Compile/Repl.hs
Normal file
132
src/compiler/api/GF/Compile/Repl.hs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
|
||||||
|
module GF.Compile.Repl (ReplOpts(..), defaultReplOpts, replOptDescrs, getReplOpts, runRepl, runRepl') where
|
||||||
|
|
||||||
|
import Control.Monad (unless, forM_)
|
||||||
|
import Control.Monad.IO.Class (MonadIO)
|
||||||
|
import Data.Char (isSpace)
|
||||||
|
import qualified Data.ByteString.Char8 as BS
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
import System.Console.GetOpt (getOpt, ArgOrder(RequireOrder), OptDescr(..), ArgDescr(..))
|
||||||
|
import System.Console.Haskeline (InputT, Settings(..), noCompletion, runInputT, getInputLine, outputStrLn)
|
||||||
|
import System.Directory (getAppUserDataDirectory)
|
||||||
|
|
||||||
|
import GF.Compile (batchCompile)
|
||||||
|
import GF.Compile.Compute.Concrete (Globals(Gl), normalFlatForm)
|
||||||
|
import GF.Compile.Rename (renameSourceTerm)
|
||||||
|
import GF.Compile.TypeCheck.ConcreteNew (inferLType)
|
||||||
|
import GF.Data.ErrM (Err(..))
|
||||||
|
import GF.Grammar.Grammar
|
||||||
|
( Grammar
|
||||||
|
, mGrammar
|
||||||
|
, Info
|
||||||
|
, Module
|
||||||
|
, ModuleName
|
||||||
|
, ModuleInfo(..)
|
||||||
|
, ModuleType(MTResource)
|
||||||
|
, ModuleStatus(MSComplete)
|
||||||
|
, OpenSpec(OSimple)
|
||||||
|
, Location (NoLoc)
|
||||||
|
, Term
|
||||||
|
, prependModule
|
||||||
|
)
|
||||||
|
import GF.Grammar.Lexer (Posn(..), Lang(GF), runLangP)
|
||||||
|
import GF.Grammar.Parser (pTerm)
|
||||||
|
import GF.Grammar.Printer (TermPrintQual(Unqualified), ppTerm)
|
||||||
|
import GF.Infra.CheckM (Check, runCheck)
|
||||||
|
import GF.Infra.Ident (moduleNameS)
|
||||||
|
import GF.Infra.Option (noOptions)
|
||||||
|
import GF.Text.Pretty (render)
|
||||||
|
|
||||||
|
data ReplOpts = ReplOpts
|
||||||
|
{ noPrelude :: Bool
|
||||||
|
, inputFiles :: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultReplOpts :: ReplOpts
|
||||||
|
defaultReplOpts = ReplOpts False []
|
||||||
|
|
||||||
|
replOptDescrs :: [OptDescr (ReplOpts -> ReplOpts)]
|
||||||
|
replOptDescrs =
|
||||||
|
[ Option [] ["no-prelude"] (NoArg $ \o -> o { noPrelude = True }) "Don't load the prelude."
|
||||||
|
]
|
||||||
|
|
||||||
|
getReplOpts :: [String] -> Either [String] ReplOpts
|
||||||
|
getReplOpts args = case errs of
|
||||||
|
[] -> Right $ (foldr ($) defaultReplOpts flags) { inputFiles = inputFiles }
|
||||||
|
_ -> Left errs
|
||||||
|
where
|
||||||
|
(flags, inputFiles, errs) = getOpt RequireOrder [] args
|
||||||
|
|
||||||
|
execCheck :: MonadIO m => Check a -> (a -> InputT m ()) -> InputT m ()
|
||||||
|
execCheck c k = case runCheck c of
|
||||||
|
Ok (a, warn) -> do
|
||||||
|
unless (null warn) $ outputStrLn warn
|
||||||
|
k a
|
||||||
|
Bad err -> outputStrLn err
|
||||||
|
|
||||||
|
replModNameStr :: String
|
||||||
|
replModNameStr = "<repl>"
|
||||||
|
|
||||||
|
replModName :: ModuleName
|
||||||
|
replModName = moduleNameS replModNameStr
|
||||||
|
|
||||||
|
parseThen :: MonadIO m => Grammar -> String -> (Term -> InputT m ()) -> InputT m ()
|
||||||
|
parseThen g s k = case runLangP GF pTerm (BS.pack s) of
|
||||||
|
Left (Pn l c, err) -> outputStrLn $ err ++ " (" ++ show l ++ ":" ++ show c ++ ")"
|
||||||
|
Right t -> execCheck (renameSourceTerm g replModName t) $ \t -> k t
|
||||||
|
|
||||||
|
runRepl' :: Globals -> IO ()
|
||||||
|
runRepl' gl@(Gl g _) = do
|
||||||
|
historyFile <- getAppUserDataDirectory "gfci_history"
|
||||||
|
runInputT (Settings noCompletion (Just historyFile) True) repl -- TODO tab completion
|
||||||
|
where
|
||||||
|
repl = do
|
||||||
|
getInputLine "gfci> " >>= \case
|
||||||
|
Nothing -> repl
|
||||||
|
Just (':' : l) -> let (cmd, arg) = break isSpace l in command cmd (dropWhile isSpace arg)
|
||||||
|
Just code -> evalPrintLoop code
|
||||||
|
|
||||||
|
command "t" arg = do
|
||||||
|
parseThen g arg $ \main ->
|
||||||
|
execCheck (inferLType gl main) $ \(_, ty) ->
|
||||||
|
outputStrLn $ render (ppTerm Unqualified 0 ty)
|
||||||
|
outputStrLn "" >> repl
|
||||||
|
|
||||||
|
command "q" _ = outputStrLn "Bye!"
|
||||||
|
|
||||||
|
command cmd _ = do
|
||||||
|
outputStrLn $ "Unknown REPL command: " ++ cmd
|
||||||
|
outputStrLn "" >> repl
|
||||||
|
|
||||||
|
evalPrintLoop code = do -- TODO bindings
|
||||||
|
parseThen g code $ \main ->
|
||||||
|
execCheck (inferLType gl main >>= \(t, _) -> normalFlatForm gl t) $ \nfs ->
|
||||||
|
forM_ (zip [1..] nfs) $ \(i, nf) ->
|
||||||
|
outputStrLn $ show i ++ ". " ++ render (ppTerm Unqualified 0 nf)
|
||||||
|
outputStrLn "" >> repl
|
||||||
|
|
||||||
|
runRepl :: ReplOpts -> IO ()
|
||||||
|
runRepl (ReplOpts noPrelude inputFiles) = do
|
||||||
|
-- TODO accept an ngf grammar
|
||||||
|
-- TODO load prelude
|
||||||
|
(g0, opens) <- case inputFiles of
|
||||||
|
[] -> pure (mGrammar [], [])
|
||||||
|
_ -> do
|
||||||
|
(_, (pModName, g0)) <- batchCompile noOptions Nothing inputFiles
|
||||||
|
pure (g0, [OSimple pModName])
|
||||||
|
let
|
||||||
|
modInfo = ModInfo
|
||||||
|
{ mtype = MTResource
|
||||||
|
, mstatus = MSComplete
|
||||||
|
, mflags = noOptions
|
||||||
|
, mextend = []
|
||||||
|
, mwith = Nothing
|
||||||
|
, mopens = opens
|
||||||
|
, mexdeps = []
|
||||||
|
, msrc = replModNameStr
|
||||||
|
, mseqs = Nothing
|
||||||
|
, jments = Map.empty
|
||||||
|
}
|
||||||
|
runRepl' (Gl (prependModule g0 (replModName, modInfo)) Map.empty)
|
||||||
12
src/compiler/gf-repl.hs
Normal file
12
src/compiler/gf-repl.hs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import GHC.IO.Encoding (setLocaleEncoding, utf8)
|
||||||
|
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
import GF.Compile.Repl (getReplOpts, runRepl)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
setLocaleEncoding utf8
|
||||||
|
args <- getArgs
|
||||||
|
case getReplOpts args of
|
||||||
|
Left errs -> mapM_ print errs
|
||||||
|
Right opts -> runRepl opts
|
||||||
@@ -122,6 +122,7 @@ library
|
|||||||
GF.Grammar.CanonicalJSON
|
GF.Grammar.CanonicalJSON
|
||||||
GF.Compile.ReadFiles
|
GF.Compile.ReadFiles
|
||||||
GF.Compile.Rename
|
GF.Compile.Rename
|
||||||
|
GF.Compile.Repl
|
||||||
GF.Compile.SubExOpt
|
GF.Compile.SubExOpt
|
||||||
GF.Compile.Tags
|
GF.Compile.Tags
|
||||||
GF.Compile.ToAPI
|
GF.Compile.ToAPI
|
||||||
@@ -239,6 +240,12 @@ executable gf
|
|||||||
build-depends: base >= 4.6 && <5, directory>=1.2, gf
|
build-depends: base >= 4.6 && <5, directory>=1.2, gf
|
||||||
ghc-options: -threaded
|
ghc-options: -threaded
|
||||||
|
|
||||||
|
executable gfci
|
||||||
|
main-is: gf-repl.hs
|
||||||
|
default-language: Haskell2010
|
||||||
|
build-depends: base >= 4.6 && < 5, gf
|
||||||
|
ghc-options: -threaded
|
||||||
|
|
||||||
test-suite gf-tests
|
test-suite gf-tests
|
||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
main-is: run.hs
|
main-is: run.hs
|
||||||
|
|||||||
Reference in New Issue
Block a user