-- Do not forget -threaded!
--
module MagicHaskeller.SimpleServer(main') where
import MagicHaskeller.LibTH
import MagicHaskeller.LibExcel
import GHC hiding (language)
import HscTypes(HscEnv(hsc_IC), InteractiveContext(..))
#if __GLASGOW_HASKELL__ < 706
import DynFlags hiding (Option, language)
#else
import DynFlags hiding (Option, Language, language)
#endif
import qualified MonadUtils as MU -- clearly distinguish MU.liftIO from Control.Monad.IO.Class.liftIO
-- import Panic (panic)
import Outputable(showPpr)
import Type
import Language.Haskell.TH as TH
import GHC.Paths(libdir)
import Control.Concurrent
import Network
import System.IO
import System.IO.Error(isEOFError)
import Control.Exception
import Data.Char(isAlphaNum, isSpace)
import Text.Html(stringToHtmlString)
import MagicHaskeller.ExpToHtml(QueryOptions(..), defaultQO, expToPlainString, expSigToString, Language(..))
import Unsafe.Coerce
import MagicHaskeller.GetTime
-- import System.Time
import Data.Time
import System.Console.GetOpt
import System.Environment
import System.Exit
import Control.Monad
-- These are for reporting resource usage.
#if __GLASGOW_HASKELL__ >= 700
import GHC.Stats
#endif
import System.Process(system)
import System.Mem(performGC)
import Control.Monad.Par.Class
import Control.Monad.Par.IO
import Control.Monad.IO.Class(liftIO)
-- import Control.Concurrent.ParallelIO(stopGlobalPool)
-- import Data.Map
#ifdef UNIX
-- as suggested by /usr/share/doc/libghc6-network-doc/html/Network.html
import System.Posix hiding (Default)
#endif
#ifdef CABAL
import Paths_MagicHaskeller(getDataFileName)
#endif
-- file:///usr/share/doc/libghc6-network-doc/html/Network.html#t%3APortID
--portID = UnixSocket "mhserver"
portID = PortNumber 55443
trainers = "predicates"
defaultDefault = "(Int,Integer, Double, Ratio Int, Char,(),String)" -- I guess in most cases Int will do.
queryOut = "query.out"
data Flag = Port PortNumber | Socket FilePath | Interactive | RunPSCommand | JustTraining
| Depth Int
| WithDoubleRatio | WithRatio | RatioOnly | WithDouble
| WithAbsents
| Default (Maybe String)
| MemoSize (Maybe Int)
| HTML | PlainText
| NoTraining | SequentialTraining FilePath | ParallelTraining FilePath
| PostProcessor String
| Excel
cmdOpts :: [OptDescr Flag]
cmdOpts = [ Option ['p'] ["port-number"] (ReqArg (Port . toEnum . readOrErr msgp) "PORT_NUMBER") "use port number PORT_NUMBER (default, using -p 55443)"
, Option ['u'] ["unix-socket"] (ReqArg Socket "SOCKET_FILEPATH") "use socket file SOCKET_FILEPATH"
, Option ['i'] ["interactive","stdio"] (NoArg Interactive) "use the standard I/O for query and printing results"
, Option ['r'] ["run-ps-command"] (NoArg RunPSCommand) "(after training) run the ps command and exit"
, Option ['j'] ["just-training"] (NoArg JustTraining) "just training (usually for benchmarking)"
, Option ['d'] ["depth"] (ReqArg (Depth . readOrErr msgd) "SEARCH_DEPTH") $
"search depth (" ++ shows (depth defaultQO) "by default)"
, Option ['q'] ["query-limit"] (OptArg (MemoSize . fmap (readOrErr msgd)) "QUERY_TYPE_SIZE_LIMIT") $
"only look up the memo entries when types with size less than this value are queried. Values for other types are recomputed every time. If no value is given (default), this means there is not limit and all entry types are looked up when queried. Setting this value does not affect the time for looking up already substantiated entries. However, setting it to about 8 dramatically reduces the heap space usage, while increasing the time for training."
, Option ['b'] ["with-double-ratio"] (NoArg WithDoubleRatio) "use the library with Double-related and (Ratio Int)-related functions. This requires more memory, but fractional numbers become available"
, Option ['w'] ["with-ratio"] (NoArg WithRatio) "use the library with (Ratio Int)-related functions. This requires more memory, but fractional numbers become available"
, Option [] ["ratio-only"] (NoArg RatioOnly) "use the library only including (Ratio Int)-related functions. This is introduced for debugging, but there may be other uses."
, Option ['2'] ["with-double"] (NoArg WithDouble) "use the library with Double-related functions. This requires more memory, but fractional numbers become available"
, Option ['a'] ["absents"] (NoArg WithAbsents) "generate functions with unused arguments in addition to other useful ones"
#if __GLASGOW_HASKELL__ >= 706
, Option [] ["default"] (OptArg Default "DEFAULT_TYPES") "default declaration for type defaulting (--default='(Int,Integer,Double, Ratio Int, Char,(),String)' by default). The outermost parens can be omitted."
#endif
, Option ['h'] ["html"] (NoArg HTML) "force printing in HTML even in the interactive mode"
, Option [] ["plain-text"] (NoArg PlainText) "force printing in plain text"
, Option ['n'] ["no-training"] (NoArg NoTraining) "start service without training beforehand"
, Option ['s'] ["sequential-training"] (ReqArg SequentialTraining "PREDICATES_FILEPATH")
"substantiate the memo table using the predicates in PREDICATES_FILEPATH. (Just setting this option would not disable parallel training. If you want to use only sequential training, use `-n -s PREDICATES_FILEPATH'.)"
, Option ['t'] ["threaded-training",
"parallel-training"] (ReqArg ParallelTraining "PREDICATES_FILEPATH")
"substantiate the memo table using the predicates in PREDICATES_FILEPATH in parallel (default, using -t 'predicates'). This option can be set along with -s, then sequential training will be done after parallel training."
, Option [] ["postprocessor"] (ReqArg PostProcessor "POSTPROCESSOR") "use POSTPROCESSOR as the postprocessor (default, using --postprocessor=postprocess). You can use --postprocessor=id to see the internal representation."
, Option ['x'] ["excel"] (NoArg Excel) "use the library for Excel synthesis, disable defaulting to integral numbers, and ppExcel as the postprocessor. You can specify `--excel --postprocessor=blah' in order to use a different postprocessor."
]
where readOrErr msg xs = case reads xs of [(i,"")] | i>=0 -> i
_ -> error msg
msgp = "--port-number (or -p) takes a non-negative integral value specifying the port number."
msgd = "--depth (or -d) takes a non-negative integral value specifying the depth bound."
msgq = "--query-limit (or -q) takes a non-negative integral value specifying the type size bound for memoization."
readOpts :: IO ([Flag], [String])
readOpts = do argv <- getArgs
case (getOpt Permute cmdOpts argv) of
(o,n,[] ) -> return (o,n)
(_,_,errs) -> do hPutStrLn stderr (concat errs)
usage
exitFailure
usage :: IO ()
usage = do progname <- getProgName
hPutStrLn stderr $ usageInfo ("Usage: "++progname++" [OPTION...]") cmdOpts
data HowToServe = Network PortID | STDIO | PS | NoService
data Format = DefaultFormat | ForceHTML | ForcePlain deriving Eq
data FunctionSet = PGFull | PGWithDoubleRatio | PGWithRatio | PGRatio | PGWithDouble | PGExcel
data ServerOptions = SO {howToServe :: HowToServe, queryOptions :: QueryOptions, functionSet :: FunctionSet, memoSize :: Maybe Int, defaultTypes :: Maybe String, format :: Format, sequentialTraining :: Maybe FilePath, parallelTraining :: Maybe FilePath, postprocessor :: String, language :: Language}
defaultSO = SO {howToServe = Network portID, queryOptions = defaultQO, functionSet = PGFull, memoSize = Nothing, defaultTypes = Just defaultDefault, format = DefaultFormat, sequentialTraining = Nothing, parallelTraining = Just trainers, postprocessor = "postprocess", language = LHaskell}
procFlags :: [Flag] -> ServerOptions
procFlags = foldl procFlag defaultSO
procFlag :: ServerOptions -> Flag -> ServerOptions
procFlag st (Port i) = st{howToServe = Network (PortNumber i)}
#ifdef UNIX
procFlag st (Socket fp) = st{howToServe = Network (UnixSocket fp)}
#endif
procFlag st Interactive = st{howToServe = STDIO}
procFlag st RunPSCommand = st{howToServe = PS}
procFlag st JustTraining = st{howToServe = NoService}
procFlag st (Depth d) = st{queryOptions = (queryOptions st){depth = d}}
procFlag st (MemoSize m) = st{memoSize = m}
procFlag st WithDoubleRatio = st{functionSet = PGWithDoubleRatio}
procFlag st WithRatio = st{functionSet = PGWithRatio}
procFlag st RatioOnly = st{functionSet = PGRatio}
procFlag st WithDouble = st{functionSet = PGWithDouble}
procFlag st WithAbsents = st{queryOptions = (queryOptions st){absents = True}}
#if __GLASGOW_HASKELL__ >= 706
procFlag st (Default ms) = st{defaultTypes = ms}
procFlag st Excel = st{defaultTypes = Just "Int,Double", postprocessor = "ppExcel", functionSet = PGExcel, language = LExcel}
#else
procFlag st (Default ms) = error "The --default option is not available. Please rebuild with GHC >= 7.6."
procFlag st Excel = st{postprocessor = "ppExcel", functionSet = PGExcel, language = LExcel}
#endif
procFlag st HTML = st{format = ForceHTML}
procFlag st PlainText = st{format = ForcePlain}
procFlag st NoTraining = st{sequentialTraining = Nothing, parallelTraining = Nothing}
procFlag st (SequentialTraining fp) = st{sequentialTraining = Just fp}
procFlag st (ParallelTraining fp) = st{parallelTraining = Just fp}
procFlag st (PostProcessor pp) = st{postprocessor = pp}
main' :: String -> IO ()
main' versionString = do
(flags,args) <- readOpts
let so = procFlags flags
hPutStrLn stderr versionString
qhandle <- openFile queryOut AppendMode
beginCT <- getCurrentTime
hPutStrLn stderr ("started at " ++ show beginCT)
pgf <- case (functionSet so, memoSize so) of
(PGFull, Nothing) -> liftIO mkPgFull
(PGFull, Just sz) -> return $ pgfulls !! sz
(PGWithDoubleRatio, Nothing) -> return $ pgWithDoubleRatio
(PGWithDoubleRatio, Just sz) -> return $ pgWithDoubleRatios !! sz
(PGWithRatio, Nothing) -> return $ pgWithRatio
(PGWithRatio, Just sz) -> return $ pgWithRatios !! sz
(PGRatio, Nothing) -> return $ pgRatio
(PGRatio, Just sz) -> return $ pgRatios !! sz
(PGWithDouble, Nothing) -> liftIO mkPgWithDouble
(PGWithDouble, Just sz) -> return $ pgWithDoubles !! sz
(PGExcel, Nothing) -> liftIO mkPgExcel
(PGExcel, Just sz) -> liftIO $ mkPgExcels sz
hscEnv <- prepareGHCAPI ["MagicHaskeller.Minimal","MagicHaskeller.FastRatio"] -- (Fast)Ratio must be here if Ratio is referred by the default declaration.
#if __GLASGOW_HASKELL__ >= 706
hscEnv <- case defaultTypes so of Nothing -> return hscEnv
Just def -> declareDefaults hscEnv def
#endif
let stat = (versionString, qhandle, so, pgf, hscEnv)
case (parallelTraining so, sequentialTraining so) of
(Just fp, Just fs) -> do -- In this case, we make sure sequantial training starts after all the parallel training processes have finished. (The sequential training will be done for testing and benchmarking purposes.)
trainPara stat fp
trainSeq stat fs
(Just fp, Nothing) -> do -- In this case, every synthesis should be done in parallel. The service is started while training, but we prefer to be notified when all the training processes finish.
forkIO $ trainPara stat fp
return ()
(Nothing, Just fs) -> trainSeq stat fs
(Nothing, Nothing) -> return ()
hSetBuffering qhandle LineBuffering
case howToServe so of
Network pid ->
withSocketsDo $ do
#ifdef UNIX
installHandler sigPIPE Ignore Nothing -- as suggested by /usr/share/doc/libghc6-network-doc/html/Network.html
#endif
socket <- listenOn pid
loop stat socket
STDIO -> interactive stat
PS -> do pgn <- getProgName
system $ "ps u -C "++pgn
return () -- stopGlobalPool
NoService -> return () -- stopGlobalPool
#if __GLASGOW_HASKELL__ >= 706
declareDefaults hscEnv str
= runGhc (Just libdir) $ do
setSession hscEnv
tupTy <- exprType $ "undefined :: (" ++ str ++ ")"
case splitTyConApp_maybe tupTy of
Nothing -> error $ str ++ " : invalid default type sequence"
Just (_tuptc, defaultTypes) -> setSession hscEnv{hsc_IC = (hsc_IC hscEnv){ic_default = Just defaultTypes}} >> getSession
#endif
waitUntil0 :: MVar Int -> IO Int
waitUntil0 mv = do i <- readMVar mv
yield
if (i>0) then waitUntil0 mv else return i
loop stat socket = do
(handle, hostname, _portnum) <- accept socket
hPutStr stderr $ "Connection from " ++ hostname ++ ".\n"
tid <- forkIO $ do hSetBuffering handle LineBuffering
answerHIO stat handle handle
hPutStrLn stderr "closing"
hClose handle
loop stat socket
{- pgfの計算を入れんといかん.
-- same as main, with option `--interactive --no-training'
mainstd = do hscEnv <- prepareGHCAPI ["MagicHaskeller.Minimal"]
qhandle <- openFile queryOut AppendMode
hSetBuffering qhandle LineBuffering
interactive qhandle defaultSO pgf hscEnv
-}
interactive stat = sequence_ $ repeat $ hPutStrLn stderr "\\f -> ?" >> answerHIO stat stdin stdout
tryOpening fp onException onSuccess = do
r <- try $ openFile fp ReadMode
case r :: Either IOException Handle of
Left e -> do
#ifdef CABAL
fn <- getDataFileName ("MagicHaskeller/"++fp)
s <- try $ openFile fn ReadMode
either onException onSuccess (s :: Either IOException Handle)
#else
onException e
#endif
Right h -> onSuccess h
trainSeq stat fp = do
tryOpening fp (\e -> hPutStrLn stderr ("An exception occurred while opening `"++fp++"'. The learner has not been trained sequentially beforehand."))
(\h -> do time $ do
processTrainers (preferPlain stat) h
hPutStrLn stderr "In total,"
return ())
hPutStrLn stderr "performing GC..."
performGC
hPutStrLn stderr "done.\a"
#if __GLASGOW_HASKELL__ >= 706
gcStatsAvailable <- getGCStatsEnabled
when gcStatsAvailable $ getGCStats >>= print
#endif
processTrainers stat h = do
(r,t) <- time $ try (answerHIO stat h stdout)
case r of Left e | isEOFError e -> do hClose h
return t
| otherwise -> error ("While training:\n" ++ show e)
Right () -> fmap (+t) $ processTrainers stat h
trainPara stat fp =
tryOpening fp (\e -> hPutStrLn stderr ("An exception occurred while opening `"++fp++"'. The learner has not been trained in parallel beforehand."))
(\h -> do
cs <- hGetContents h
beginCT <- getCurrentTime
runParIO $ trainParaPar (preferPlain stat) $ lines cs
-- trainParaIO (preferPlain so) pgf hscEnv $ lines cs
endParaCT <- getCurrentTime
hPutStrLn stderr "All the training processes have finished."
hPutStrLn stderr $ show (diffUTCTime endParaCT beginCT) ++ " have passed since the training started.")
trainParaIO stat css = do
numUnfinished <- newMVar (length css)
mapM_ (processTrainerPara (preferPlain stat) numUnfinished) css
waitUntil0 numUnfinished
processTrainerPara stat numUnfinished line = do
mid <- myThreadId
forkIO $ ((answerSIO stat line >>= \(_,k) -> k `seq` modifyMVar_ numUnfinished (return . pred))
`Control.Exception.catch` \exception -> throwTo mid (exception::SomeException)
)
return ()
preferPlain (vs, qh, so, pgf, he) = (vs, qh, preferPlain' so, pgf, he)
preferPlain' so = case format so of DefaultFormat -> so{format=ForcePlain}
_ -> so
trainParaPar :: (String, Handle, ServerOptions, ProgGenSF, HscEnv) -> [String] -> ParIO ()
trainParaPar stat css = do ivks <- mapM (\line -> spawn $ liftIO $ fmap snd $ answerSIO stat line) css
ks <- mapM get ivks
sum ks `seq` return ()
filterCompile :: GhcMonad m => String -> String -> m (ProgGenSF -> Bool -> [[Exp]])
filterCompile postprocessor predStr = fmap unsafeCoerce (compileExpr ("f1EF " ++ postprocessor ++ " (\\f -> "++predStr++")")) -- :: m GHC.HValue)
-- InteractiveEval.exprTypeで明示的に型推論するってことは,IntegerでなくIntでdefaultしたりしやすいってことか.めんどくさければとりあえずはエラーにしてmonomorphicなのを要求してもよい.
-- package ghcのType.Typeもそんなにややこしい型じゃないし,exprTypeから変換するのが確実でいいか.
-- exprTypeやってcompileExprするのは二度手間ではあるが.
-- てゆーか,もしpackage MagicHaskellerを毎回読み込まなければならないとすればそっちの方がtime consuming.
filterCompileIO :: GhcMonad m => String -> String -> m (ProgGenSF -> Bool -> IO [[Exp]])
filterCompileIO postprocessor predStr = fmap unsafeCoerce (compileExpr ("MagicHaskeller.Minimal.f1EFIO " ++ postprocessor ++ " (\\f -> (("++predStr++") :: Bool))"))
{- 使わんかも.
ghcTypeToType :: TyConLib -> GHC.Type -> MagicHaskeller.Types.Type
ghcTypeToType _ (TyVarTy var) = strToVarType $ show var
ghcTypeToType tcl (AppTy t0 t1) = ghcTypeToType tcl t0 `TA` ghcTypeToType tcl t1
ghcTypeToType tcl (TyConApp tc ts) = let nstr = showSDoc (pprParenSymName tc)
tc' = case Data.Map.lookup nstr (fst tcl) of
Nothing -> TC $ (-1 - bakaHash nstr) -- error "nameToTyCon: unknown TyCon"
Just c -> TC c
in foldl TA tc' $ map (thcTypeToType tcl) ts
ghcTypeToType tcl (FunTy t0 t1) = ghcTypeToType tcl t0 :-> ghcTypeToType tcl t1
ghcTypeToType tcl (ForAllTy v ty) = panic "Please make it monomorphic by giving a type signature."
-}
-- stdinとstdoutで動作確認できるように,inとoutを分ける.
answerHIO :: (String, Handle, ServerOptions, ProgGenSF, HscEnv) -> Handle -> Handle -> IO ()
answerHIO (versionString, qhandle, so, pgf, hscEnv) ihandle ohandle = do
inp <- hGetLine ihandle -- hGetContents ihandleだと,最後に改行文字を入れちゃった時面倒.あと,hGetContentsの方がだいぶ遅いみたい.
case lex inp of [(":",rest)] -> if filter (not . isSpace) rest == "version" then hPutStrLn ohandle versionString else hPutStrLn ohandle $ inp ++ " : command unknown"
_ -> do
let (so', pred) = case reads inp of [(qo, pred)] -> (so{queryOptions=qo}, pred)
[] -> (so, inp)
putStrLn ("the predicate is "++pred)
hPutStrLn qhandle pred
(out,_) <- answerSIO (versionString, qhandle, so', pgf, hscEnv) pred
hPutStrLn ohandle out
answerSIO :: (a, b, ServerOptions, ProgGenSF, HscEnv) -> String -> IO (String, Int)
answerSIO (_, _, so, pgf, hscEnv) pred = do
cmpd <- runGhc (Just libdir) $ setSession hscEnv >> compileOrFail (postprocessor so) pred
case cmpd of Left (funIO, sig) -> do
let e2s = case howToServe so of
STDIO | not $ format so == ForceHTML -> expToPlainString
_ | format so == ForcePlain -> expToPlainString
| otherwise -> expSigToString (language so) pred sig
result <- funIO pgf $ absents $ queryOptions so
let ess = take (depth $ queryOptions so) result
-- let ess = take (depth $ queryOptions so) $ fun pgf $ absents $ queryOptions so
return (unlines $ map (concat . map e2s) ess, length $ last ess)
Right errstr -> return ('!' : encodeBR (stringToHtmlString errstr), length errstr) -- 本当はこれもhowToServeにあわせるべき
compileOrFail :: String -> String -> Ghc (Either (ProgGenSF -> Bool -> IO [[Exp]], String) String)
compileOrFail postproc predStr = handleSourceError (return . Right . show) $ do
funIO <- filterCompileIO postproc predStr
#if __GLASGOW_HASKELL__ >= 706
-- In this case, the type obtained by exprType is polymorphic, so there is no point in adding the type signature.
let sig = ""
#else
ty <- exprType $ "\\f->("++predStr++")`asTypeOf`True" -- `asTypeOf` True をいれないと、 predStr = "f True True" のときにserverがpanic!になる。
let sig = " :: " ++ removeQuantification (map crlfToSpace $ showPpr $ extractArgTy ty)
#endif
return $ Left (funIO, sig)
-- assumes rank-1 types
extractArgTy ty = case splitForAllTys ty of (tvs, fty) -> case splitFunTys fty of (args, _bool) -> mkForAllTys tvs $ mkFunTys (Prelude.init args) $ last args
crlfToSpace '\n' = ' '
crlfToSpace c = c
-- エラーコード中にもし\nがあった場合,
で置き換え.なぜかstringToHtmlStringはやってくれない.
encodeBR = concat . map (++"
") . lines
-- exprType quantifies each Primitive type with `GHC.Types.' and `GHC.Bool., but mueval does not like this kind of quantification.
-- There exist quicker algorithms, but anyway the time for quantification removal should be ignorable.
removeQuantification "" = ""
removeQuantification xs@(y:ys) = case span (/='.') xs of (tk,'.':dr) | all isAlphaNum tk -> removeQuantification dr
| otherwise -> reverse (dropWhile isAlphaNum $ reverse tk) ++ removeQuantification dr
(tk,"") -> tk
prepareGHCAPI :: [FilePath] -> IO HscEnv
prepareGHCAPI allfss = runGhc (Just libdir) $ do
dfs <- getSessionDynFlags
#if __GLASGOW_HASKELL__ >= 700
-- x # if __GLASGOW_HASKELL__ >= 708
-- x let newf = xopt_set dfs{packageFlags = [ packageNameToFlag "MagicHaskeller" ], optLevel=2, parMakeCount=Nothing} Opt_ExtendedDefaultRules -- parMakeCount=Nothing corresponds to -j. See http://downloads.haskell.org/~ghc/7.10.2/docs/html/libraries/ghc-7.10.2/DynFlags.html -- but seemingly this does not make the code faster, so is commented out.
-- x # else
let newf = xopt_set dfs{packageFlags = [ packageNameToFlag "MagicHaskeller" ], optLevel=2} Opt_ExtendedDefaultRules
-- x # endif
#else
let newf = dfs{packageFlags = [ packageNameToFlag "MagicHaskeller" ], optLevel=2}
#endif
setSessionDynFlags newf -- result abandoned
#if __GLASGOW_HASKELL__ >= 700
modules <- mapM (\fs -> fmap (\x -> (x,Nothing)) $ findModule (mkModuleName fs) Nothing) ("Prelude":allfss)
#else
modules <- mapM (\fs -> findModule (mkModuleName fs) Nothing) ("Prelude":allfss)
#endif
#if __GLASGOW_HASKELL__ >= 700
setContext [ IIDecl $ (simpleImportDecl . mkModuleName $ moduleName){GHC.ideclQualified = False} | moduleName <- "Prelude":allfss ] -- GHC 7.4
#else
setContext [] modules
#endif
getSession
packageNameToFlag :: String -> PackageFlag
#if __GLASGOW_HASKELL__ < 710
packageNameToFlag = ExposePackage
#else
packageNameToFlag name = ExposePackage (PackageArg name) (ModRenaming False []) -- I am not sure this is the correct conversion, because I could not find any documentation on the change.
#endif