{-# LANGUAGE OverloadedStrings, TupleSections, FlexibleContexts #-} module Funcons.RunOptions where import Funcons.Types import Funcons.GLLParser (Parser(..), pFunconsSeq, pFuncons, fct_lexerSettings) import Funcons.Parser (fct_parse) import GLL.Combinators hiding (chooses) import qualified Data.Map as M import Control.Monad (when) import Control.Compose (OO(..)) import Data.Text (pack) import Data.List (isSuffixOf, isPrefixOf) import Data.List.Split (splitOn) import System.Directory (doesFileExist) type GeneralOptions = M.Map Name String type BuiltinFunconsOptions = M.Map Name Funcons type TestOptions = M.Map Name [Funcons] type InputValues = M.Map Name [Values] data RunOptions = RunOptions { mfuncon_term :: Maybe Funcons , general_opts :: GeneralOptions , builtin_funcons :: BuiltinFunconsOptions , expected_outcomes :: TestOptions , given_inputs :: InputValues } defaultRunOptions :: RunOptions defaultRunOptions = RunOptions Nothing M.empty M.empty M.empty M.empty optionsOverride opts opts' = RunOptions (maybe (mfuncon_term opts) Just (mfuncon_term opts')) (general_opts opts `M.union` general_opts opts') (builtin_funcons opts `M.union` builtin_funcons opts') (expected_outcomes opts `M.union` expected_outcomes opts') (given_inputs opts `M.union` given_inputs opts') funcon_term :: RunOptions -> Funcons funcon_term = maybe err id . mfuncon_term where err = error "Please give a .fct file as an argument or use the --funcon-term flag" bool_opt_default :: Bool -> Name -> M.Map Name String -> Bool bool_opt_default def nm m = case M.lookup nm m of Nothing -> def Just "false" -> False _ -> True bool_opt :: Name -> M.Map Name String -> Bool bool_opt nm m = bool_opt_default False nm m do_refocus :: RunOptions -> Bool do_refocus opts = bool_opt_default True "refocus" (general_opts opts) max_restarts :: RunOptions -> Maybe Int max_restarts = fmap read . M.lookup "max-restarts" . general_opts do_abrupt_terminate :: RunOptions -> Bool do_abrupt_terminate = not . bool_opt "no-abrupt-termination" . general_opts pp_full_environments :: RunOptions -> Bool pp_full_environments = bool_opt "full-environments" . general_opts show_result :: RunOptions -> Bool show_result opts = if bool_opt "hide-result" (general_opts opts) then False else not (interactive_mode opts) show_counts :: RunOptions -> Bool show_counts opts = if bool_opt "display-steps" (general_opts opts) then not (interactive_mode opts) else False show_mutable :: RunOptions -> [Name] show_mutable = maybe [] (map pack . splitOn ",") . M.lookup "display-mutable-entity" . general_opts hide_output :: RunOptions -> [Name] hide_output = maybe [] (map pack . splitOn ",") . M.lookup "hide-output-entity" . general_opts hide_input :: RunOptions -> [Name] hide_input = maybe [] (map pack . splitOn ",") . M.lookup "hide-input-entity" . general_opts hide_control :: RunOptions -> [Name] hide_control = maybe [] (map pack . splitOn ",") . M.lookup "hide-control-entity" . general_opts interactive_mode :: RunOptions -> Bool interactive_mode opts = M.null (inputValues opts) && bool_opt "interactive-mode" (general_opts opts) pp_string_outputs :: RunOptions -> Bool pp_string_outputs = bool_opt "format-string-outputs" . general_opts string_inputs :: RunOptions -> Bool string_inputs = bool_opt "string-inputs" . general_opts show_tests :: RunOptions -> Bool show_tests opts = if bool_opt "hide-tests" (general_opts opts) then False else M.size (expected_outcomes opts) > 0 show_output_only :: RunOptions -> Bool show_output_only opts = if bool_opt "show-output-only" (general_opts opts) then True else interactive_mode opts auto_config :: RunOptions -> Bool auto_config opts = bool_opt_default True "auto-config" (general_opts opts) csv_output :: RunOptions -> Bool csv_output opts = if bool_opt "csv" (general_opts opts) then True else csv_output_with_keys opts csv_output_with_keys :: RunOptions -> Bool csv_output_with_keys opts = bool_opt "csv-keys" (general_opts opts) inputValues :: RunOptions -> InputValues inputValues = given_inputs booleanOptions = ["refocus", "full-environments", "hide-result", "display-steps" ,"no-abrupt-termination", "interactive-mode", "string-inputs" ,"format-string-outputs", "hide-tests", "show-output-only" ,"auto-config", "csv", "csv-keys"] booleanOptions_ = map ("--" ++) booleanOptions stringOptions = ["display-mutable-entity", "hide-output-entity" , "hide-control-entity", "hide-input-entity", "max-restarts"] stringOptions_ = map ("--" ++) stringOptions allOptions = "funcon-term" : booleanOptions ++ stringOptions allOptions_ = "--funcon-term" : booleanOptions_ ++ stringOptions_ run_options :: [String] -> IO (RunOptions, [String]) run_options = fold (defaultRunOptions, []) where fold (opts,errors) (arg:args) | arg `elem` booleanOptions_ = let (val, rest) | not (null args) , not (isPrefixOf "--" (head args)) = (head args, tail args) | otherwise = ("true", args) opts' = opts {general_opts = M.insert (pack (tail (tail arg))) val (general_opts opts)} in fold (opts',errors) rest | arg `elem` stringOptions_ && length args > 0 = let opts' = opts {general_opts = M.insert (pack (tail (tail arg))) (head args) (general_opts opts)} in fold (opts', errors) (tail args) | arg == "--funcon-term" && length args > 0 = let opts' = opts {mfuncon_term = Just (fct_parse (head args))} in fold (opts', errors) (tail args) | isSuffixOf ".fct" arg = do fct <- readFile arg let cfg_name = take (length arg - 4) arg ++ ".config" exists <- doesFileExist cfg_name opts' <- if exists && auto_config opts then readFile cfg_name >>= return . flip (parseAndApplyConfig cfg_name) opts else return opts let opts'' = opts' {mfuncon_term = Just (fct_parse fct)} fold (opts'', errors) args | isSuffixOf ".config" arg = fold (opts, errors) ("--config":arg:args) | arg == "--config" && length args > 0 = do let cfg_name = head args exists <- doesFileExist cfg_name when (not exists) (error ("config file not found: " ++ cfg_name)) str <- readFile cfg_name let opts' = parseAndApplyConfig cfg_name str opts fold (opts', errors) (tail args) | otherwise = do exists <- doesFileExist (arg++".fct") if exists then fold (opts, errors) ((arg++".fct"):args) else fold (opts, arg:errors) args fold (opts, errors) [] = return (opts, errors) parseAndApplyConfig :: FilePath -> String -> RunOptions -> RunOptions parseAndApplyConfig fp str = optionsOverride (config_parser str) -- gll config parser config_parser :: String -> RunOptions config_parser string = case GLL.Combinators.parseWithOptions [maximumPivot,throwErrors] pRunOptions (Funcons.RunOptions.lexer string) of [] -> error "no parse (config)" (c:_) -> c lexer :: String -> [Token] lexer = GLL.Combinators.lexer cfg_lexerSettings cfg_lexerSettings = fct_lexerSettings { keywords = (keywords fct_lexerSettings) ++ cfg_keywords , keychars = (keychars fct_lexerSettings) ++ cfg_keychars } cfg_keychars = ":;=" cfg_keywords = allOptions ++ ["result-term", "general", "tests", "funcons", "inputs"] pRunOptions :: Parser RunOptions pRunOptions = "SPECS" <:=> foldr optionsOverride defaultRunOptions <$$> multiple pSpec pSpec :: Parser RunOptions pSpec = "SPEC" <:=> keyword "general" **> braces pGeneral <||> keyword "tests" **> braces pTestOutcomes <||> keyword "funcons" **> braces pBuiltinFuncons <||> keyword "inputs" **> braces pInputValues pGeneral :: Parser RunOptions pGeneral = "GENERAL" <:=> toOpts <$$> --TODO uncomfortable usage of id optional (id <$$ keyword "funcon-term" <** keychar ':' <**> pFuncons <** keychar ';') <**> (M.fromList <$$> multiple pKeyValues) where toOpts mf gen = defaultRunOptions {mfuncon_term = mf, general_opts = gen} pKeyValues = "GENERAL-KEYVALUES" <:=> pBoolOpts <||> pStringOpts where pBoolOpts = "GENERAL-BOOLS" `chooses` (map pKeyValue booleanOptions) where pKeyValue key = (pack key,) . maybe "true" id <$$ keyword key <**> optional (keychar ':' **> pBool) <** keychar ';' pStringOpts = "GENERAL-STRINGS" `chooses` (map pKeyValue stringOptions) where pKeyValue key = (pack key,) <$$ keyword key <** keychar ':' <**> pStringValue <** keychar ';' chooses p alts = (<::=>) p (OO alts) pBool :: Parser String pBool = "BOOL-VALUE" <:=> id_lit -- everything except `false` is considered `true` pStringValue :: Parser String pStringValue = "STRING-VALUE" <:=> id_lit <||> string_lit pFunconName :: Parser String pFunconName = "FUNCON-NAME" <:=> id_lit pTestOutcomes :: Parser RunOptions pTestOutcomes = "TEST-OUTCOMES" <:=> toOptions <$$> (M.union <$$> pResult <**> pEntityValues) where pResult = mStoreResult <$$> optional (id <$$ keyword "result-term" <** keychar ':' <**> pFunconsSeq <** keychar ';') where mStoreResult Nothing = M.empty mStoreResult (Just f) = M.singleton "result-term" f pEntityValues = "TEST-ENTITIES" <:=> M.fromList <$$> multiple ((,) . pack <$$> pFunconName <** keychar ':' <**> pFunconsSeq <** keychar ';') toOptions map = defaultRunOptions { expected_outcomes = map } pBuiltinFuncons :: Parser RunOptions pBuiltinFuncons = "BUILTIN-FUNCONS" <:=> insertFuncons <$$> multiple ((,) . pack <$$> pFunconName <** keychar '=' <**> pFuncons <** keychar ';') where insertFuncons list = defaultRunOptions {builtin_funcons = M.fromList list} pInputValues :: Parser RunOptions pInputValues = "INPUT-VALUES" <:=> insertInputs <$$> multiple (toPair <$$> pFunconName <** keychar ':' <**> pFunconsSeq <** keychar ';') where insertInputs list = defaultRunOptions { given_inputs = M.fromList list } toPair nm fs = case sequence (map recursiveFunconValue fs) of Just vs -> (pack nm, vs) _ -> error ("inputs for " ++ nm ++ " not a sequence of values")