{-# LANGUAGE OverloadedStrings #-} module Funcons.Tools ( -- * Creating standalone interpreters. -- $moduledoc mkMain, mkMainWithLibrary, mkMainWithLibraryEntities, mkMainWithLibraryTypes, mkMainWithLibraryEntitiesTypes, mkFullyFreshInterpreter, mkFreshInterpreter, -- * Creating embedded interpreters. run, runWithExtensions, runWithExtensionsNoCore, runWithExtensionsNoNothing, -- * Utility functions for interpreter extensions. -- ** Funcon libraries. FunconLibrary, libEmpty, libUnion, libOverride, libUnions, libOverrides, libFromList, -- ** Type environments. TypeRelation, DataTypeMembers(..), DataTypeAltt(..), TypeParam, emptyTypeRelation, typeEnvUnion, typeEnvUnions, typeEnvFromList, -- ** Entity declarations EntityDefaults, EntityDefault(..), noEntityDefaults, ) where import Funcons.EDSL (library) import Funcons.RunOptions import Funcons.Types import Funcons.Entities import Funcons.MSOS import Funcons.Core.Library import Funcons.Core.Manual import Funcons.Printer import Funcons.Parser import System.Environment (getArgs) import Data.Text (unpack) import Data.List ((\\), intercalate) import qualified Data.Map as M import Control.Monad (forM_, when, unless,join) -- | The empty collection of entity defaults. noEntityDefaults :: [EntityDefault] noEntityDefaults = [] -- | Creates a /main/ function for the default interpreter (no extension). -- The executable made from this /main/ function receives command line -- argumenst as explained above ("Funcons.Tools"). mkMain :: IO () mkMain = mkMainWithLibrary libEmpty -- | Creates a /main/ function for the interpreter obtained by extending -- the default library with the funcons in the 'FunconLibrary' argument. mkMainWithLibrary :: FunconLibrary -> IO() mkMainWithLibrary lib = mkMainWithLibraryEntities lib [] -- | Creates a /main/ function for the interpreter obtained by extending -- the main interpreter with the funcons in the 'FunconLibrary' argument -- and with default values for entities defined in the 'EntityDefaults' -- argument. mkMainWithLibraryEntities :: FunconLibrary -> EntityDefaults -> IO () mkMainWithLibraryEntities lib ents = mkMainWithLibraryEntitiesTypes lib ents emptyTypeRelation -- | Creates a /main/ function for the interpreter obtained by extending -- the main interpreter with the funcons in the 'FunconLibrary' argument -- and with a 'TypeRelation' mapping datatypes to their constructors and -- type arguments. mkMainWithLibraryTypes :: FunconLibrary -> TypeRelation -> IO () mkMainWithLibraryTypes lib tys = mkMainWithLibraryEntitiesTypes lib [] tys -- | Creates a /main/ function for the interpreter obtained by extending -- the main interpreter with funcons, 'EntityDefaults' and a 'TypeRelation'. mkMainWithLibraryEntitiesTypes :: FunconLibrary -> EntityDefaults -> TypeRelation -> IO () mkMainWithLibraryEntitiesTypes lib defaults tyenv = do args <- getArgs runWithExtensions lib defaults tyenv args Nothing -- | Creates a /main/ function for the interpreter aware of only -- the given 'FunconLibrary', 'EntityDefaults' and 'TypeRelation'. mkFullyFreshInterpreter :: FunconLibrary -> EntityDefaults -> TypeRelation -> IO () mkFullyFreshInterpreter lib defaults tyenv = do args <- getArgs runWithExtensionsNoNothing lib defaults tyenv args Nothing -- | Creates a /main/ function for the interpreter aware of only -- the given 'FunconLibrary', 'EntityDefaults' and 'TypeRelation', -- and the built-in types and operations. mkFreshInterpreter :: FunconLibrary -> EntityDefaults -> TypeRelation -> IO () mkFreshInterpreter lib defaults tyenv = do args <- getArgs runWithExtensionsNoCore lib defaults tyenv args Nothing -- | Same as 'run', except receiving additional interpreter extensions as arguments. -- Useful when a translation to 'Funcons' has been implemented in Haskell as -- well as 'Funcons', entities or datatypes specific to the object language. -- Includes the 'Funcons.Core' funcons. runWithExtensions :: FunconLibrary -> EntityDefaults -> TypeRelation -> [String] -> Maybe Funcons -> IO () runWithExtensions lib defaults tyenv = runWithExtensionsNoCore (libUnions [Funcons.Core.Library.funcons, lib]) (concat [defaults, Funcons.Core.Library.entities]) (typeEnvUnions [tyenv, Funcons.Core.Library.types]) -- | Same as 'run', except receiving additional interpreter extensions as arguments. -- Useful when a translation to 'Funcons' has been implemented in Haskell as -- well as 'Funcons', entities or datatypes specific to the object language. -- Does not include the 'Funcons.Core' funcons. runWithExtensionsNoCore :: FunconLibrary -> EntityDefaults -> TypeRelation -> [String] -> Maybe Funcons -> IO () runWithExtensionsNoCore lib defaults tyenv = runWithExtensionsNoNothing full_lib defaults tyenv where full_lib = libUnions [lib ,Funcons.EDSL.library ,Funcons.Core.Manual.library] -- | Same as 'run', except receiving additional interpreter extensions as arguments. -- Useful when a translation to 'Funcons' has been implemented in Haskell as -- well as 'Funcons', entities or datatypes specific to the object language. -- Does not include the 'Funcons.Core' funcons. runWithExtensionsNoNothing :: FunconLibrary -> EntityDefaults -> TypeRelation -> [String] -> Maybe Funcons -> IO () runWithExtensionsNoNothing lib defaults tyenv = emulate lib defaults tyenv -- | -- Creates a main function by passing in a list of command line arguments -- and an optional initial 'Funcons' to execute. The 'Funcons' argument is optional -- as one of the command line arguments may refer to an .fct file or .config -- file that specifies an initial 'Funcons' term. -- Useful when a translation to 'Funcons' has been implemented in Haskell. run :: [String] -> Maybe Funcons -> IO () run = runWithExtensions libEmpty [] emptyTypeRelation ------------------------------------------------------------------------------ --- running programs emulate lib defaults tyenv args mf0 = do (opts, unknown_opts) <- run_options args forM_ unknown_opts $ \arg -> do putStrLn ("unknown option: " ++ arg) case interactive_mode opts of True -> emulate' (fread (string_inputs opts) :: Name -> IO Funcons) lib defaults tyenv opts mf0 False -> emulate' (fread (string_inputs opts) :: Name -> SimIO Funcons) lib defaults tyenv opts mf0 emulate' :: Interactive m => (Name -> m Funcons) -> FunconLibrary -> EntityDefaults -> TypeRelation -> RunOptions -> Maybe Funcons -> IO () emulate' reader lib defaults tyenv opts mf0 = do -- the initial funcon term must be either given from a .fct file (Maybe Funcons) -- or specified in a configuration file let f0 = maybe (funcon_term opts) id mf0 msos_ctxt = MSOSReader (RewriteReader lib tyenv opts f0 f0) emptyINH emptyDCTRL reader -- run the Interactive monad, returning in the evaluation results + entity values. -- if in --interactive-mode the Interactive monad will be IO -- and all the desired output will already have been printed to the screen ((e_exc_f, mut, wr), rem_ins) <- fexec (runMSOS (setEntityDefaults defaults (stepTrans opts 0 (toStepRes f0))) msos_ctxt (emptyMSOSState {inp_es = inputs})) (inputValues opts) -- if not in --interactive-mode then print additional information based on flags unless (interactive_mode opts) (withResults defaults msos_ctxt e_exc_f mut wr rem_ins) where inputs = M.foldrWithKey op M.empty (inputValues opts) where op nm _ = M.insert nm ([], Just (reader nm)) withResults defaults msos_ctxt e_exc_f msos_state wr rem_ins | show_tests opts = case e_exc_f of Left ie -> putStrLn (showIException ie) Right efvs -> printTestResults (either (:[]) (map FValue) efvs) defaults msos_ctxt msos_state wr rem_ins | otherwise = do unless (show_output_only opts) $ do printCounts case e_exc_f of Left ie -> putStrLn (showIException ie) Right f -> printResult (either (:[]) (map FValue) f) printMutable printControl printInput rem_ins (hide_input opts) printOutput where muts = mut_entities msos_state opts = run_opts (ereader msos_ctxt) printResult f = when (show_result opts) $ do putStrLn "Result:" putStrLn (ppFunconsSeq opts f) putStrLn "" printCounts = do when (show_counts opts) $ do when (csv_output_with_keys opts) (putStrLn counterKeys) if (csv_output opts) then putStrLn $ displayCounters (counters (ewriter wr)) else putStrLn $ ppCounters (counters (ewriter wr)) printMutable = forM_ toShow display where toShow = show_mutable opts display name = case M.lookup name muts of Nothing -> return () Just v -> putStrLn ("Mutable Entity: " ++ unpack name) >> putStrLn (displayValue v) >> putStrLn "" printControl = forM_ (M.keys ctrl \\ toHide) display where ctrl = ctrl_entities wr toHide = hide_control opts display name = case M.lookup name ctrl of Just (Just v) -> do putStrLn ("Control Entity: " ++ unpack name) putStrLn (displayValue v) >> putStrLn "" _ -> return () printOutput = forM_ (M.keys out \\ toHide) display where out = out_entities wr display name = do unless (show_output_only opts) (putStrLn ("Output Entity: " ++ unpack name)) case all isString_ vs && pp_string_outputs opts of True -> mapM_ (putStr . unString) vs False -> putStrLn (displayValues vs) unless (show_output_only opts) (putStrLn "") where vs = out M.! name toHide = hide_output opts printInput ios toHide = forM_ (M.keys ios \\ toHide) display where display name = unless (null vs) $ do putStrLn ("Input Entity: " ++ unpack name) putStrLn (displayValues vs) putStrLn "" where vs = ios M.! name displayValues vs = intercalate "," (map displayValue vs) displayValue (Map m) = intercalate "\n" [ displayValue key ++ " |-> " ++ displayValues val | (key, val) <- M.assocs m ] displayValue (ADTVal "variable" [FValue (Atom a) ,FValue (ComputationType (Type t))]) = "variable(" ++ displayValue (Atom a) ++ ", " ++ ppTypes (ppFuncons opts) t ++ ")" displayValue (Atom a) = a displayValue val | isString_ val = show (unString val) displayValue val = ppValues (ppFuncons opts) val printTestResults :: [Funcons] -> EntityDefaults -> MSOSReader m -> MSOSState m -> MSOSWriter -> InputValues -> IO () printTestResults fs defaults msos_ctxt msos_state wr rem_ins = do forM_ (M.keys opts) printNotExists when (M.member "result-term" opts) $ unless (result_term == fs) (reportError "result-term" (showFunconsSeq result_term) (showFunconsSeq fs)) printMutable printControl printInputOutput out printInputOutput rem_ins where eval_ctxt = ereader msos_ctxt muts = mut_entities msos_state eval_state = estate msos_state localEval name term = case runRewrite (rewriteFuncons term) eval_ctxt eval_state of (Left ie,_,_) -> error ("internal exception in " ++ unpack name ++ " evaluation:\n" ++ showIException ie) (Right (ValTerm [v]),_,_) -> v (Right (ValTerm vs),_,_) -> error ("evaluation of " ++ unpack name ++ " results in sequence") (Right _,_,_) -> error ("evaluation of " ++ unpack name ++ " requires step") mLocalEval term = case runRewrite(rewriteFuncons term) eval_ctxt eval_state of (Right (ValTerm [v]),_,_) -> Just v _ -> Nothing result_term = case sequence (fmap recursiveFunconValue rf) of Nothing -> case sequence (fmap mLocalEval rf) of Nothing -> rf Just vs -> map FValue vs Just vs -> map FValue vs where rf = (opts M.! "result-term") opts = expected_outcomes (run_opts eval_ctxt) reportError name expected actual = do putStrLn ("expected " ++ unpack name ++ ": " ++ expected) putStrLn ("actual " ++ unpack name ++ ": " ++ actual) printNotExists "result-term" = return () printNotExists name = case (M.lookup name muts, M.lookup name out ,M.lookup name ctrl, M.lookup name rem_ins) of (Nothing, Nothing, Nothing, Nothing) -> putStrLn ("unknown entity: " ++ unpack name) _ -> return () printMutable = forM_ (M.assocs muts) (uncurry display) where display name val = case M.lookup name opts of Nothing -> return () Just expected -> unless (map (localEval name) expected == [val]) (reportError name (showL $ map showFuncons expected) (showValues val)) -- set default values of output and control entities ctrl :: M.Map Name (Maybe Values) ctrl = foldr op (ctrl_entities wr) defaults where op (DefControl name) ctrl | not (M.member name ctrl) = M.insert name Nothing ctrl op _ ctrl = ctrl out = foldr op (out_entities wr) defaults where op (DefOutput name) out | not (M.member name out) = M.insert name [] out op _ out = out -- TODO this does not test the case that a control signal is expected -- according to the test, but not present. printControl = forM_ (M.assocs ctrl) (uncurry display) where display :: Name -> Maybe Values -> IO () -- test whether control signal is expected when there is none -- shows expected signal display name Nothing = case M.lookup name opts of Nothing -> return () Just vals -> putStrLn ("expected "++unpack name++": " ++ showL (map (showValues . localEval name) vals)) -- test whether control signal is expected when there is one -- shows that the emitted signal was unexpected -- if a signal was expected, shows if actual and expected are unequal display name (Just val) = case M.lookup name opts of Nothing -> putStrLn ("unexpected " ++ unpack name ++ ": " ++ showValues val) Just expected -> unless (map (localEval name) expected == [val]) (reportError name (showL $ map showFuncons expected) (showValues val)) printInputOutput remaining = forM_ (M.assocs remaining) (uncurry display) where -- no test-error if the input/output is empty -- (and no input/output was specified) display name [] | Nothing <- M.lookup name opts = return () display name vals = case M.lookup name opts of Nothing -> putStrLn ("unexpected " ++ unpack name ++ ": " ++ showL (map showValues vals)) Just expected -> case map (localEval name) expected of [ADTVal "list" exps] -> unless (exps == map FValue vals) (reportError name (showL $ map showFuncons exps) (showL $ map showValues vals)) val -> error ("non-list given as expected output entity ("++ unpack name ++ "): " ++ showL (map showValues val)) showL :: [String] -> String showL elems = "[" ++ intercalate "," elems ++ "]" -- $moduledoc -- This module exports functions for creating executables for funcon interpeters. -- The executables accepts a number of command line and configuration options that -- are explained here. The /funcons-tools/ package exports an interpreter for -- the core library of reusable funcons. This executable is called /runfct/ and is used -- as an example here. -- -- @ dist\/build\/runfct\/runfct \@ -- -- === Options -- Options are used to change the behaviour of the interpreter and to change the -- output the interpreter provides after execution. -- An option is either a boolean-option, a string-option, a .config file or a .fct file. -- All command line flags are considered from left to right, -- each changing the current configuration. -- -- (1) __Funcon term file__: A file containing a funcon term. (must have .fct extension). -- These files contain funcon terms written -- in prefix form with parentheses surrounding comma-separated arguments, -- e.g. integer-multiply(6,7). The parser also accepts notation for lists, -- tuples, sets and map. For example, @[1,2,3]@, @(1,2,3)@, @{1,2,3}@, -- and @{1 |-> true, 2 |-> false, 3 |-> true }@ respectively. -- -- (2) __Configurations file__: A file containing configuration options (see below). -- (must have .config extension) -- -- (3) __String options__ (comma-separate strings for multiple inputs): -- -- * --display-mutable-entity \: by default mutable entities are not displayed -- this option is used to display one or more mutable entities. -- -- * --hide-output-entity \: -- by default output entities are displayed when output is available. -- this option is used to hide one or more output entities. -- -- * --hide-control-entity \: -- by default control entities are displayed when a signal is raised. -- this option is used to hide one or more control entities . -- -- * --hide-input-entity \: -- by default input entities are displayed when input has not been consumed. -- this option is used to hide one or more input entities. -- * --max-restarts \: -- perform a maximum of `n` transitive steps, useful for debugging. -- -- (4) __Boolean options__ (/true/, /false/ or no argument (/true/ by default)): -- -- * --refocus \: use refocusing, only valid under certain conditions. -- -- * --full-environments \: when printing funcons, display environments -- using map-notation, by default an environment is printed as "...". -- -- * --hide-result \: do not show the resulting funcon term. -- -- * --display-steps \: show meta-information about the interpretation, -- e.g. the number of steps, rewrites and restarts. -- -- * --no-abrupt-termination \: disable abrupt termination (affects uncaught control signals). -- -- * --interactive-mode \: use real I/O for input and output. -- By default I/O is simulated and all input is expected to be -- provided in a configuration file (see below) and output is collected -- and displayed after execution is finished. -- In interactive mode, the user has to provide input via the standard input, -- and output is printed to the standard output as soon as it is available. -- -- * --string-inputs \: by default input is parsed into a 'Values'. -- This option tells the interpreter to yield the given string as input. -- -- * --format-string-outputs \: if all output values are strings (and with this option on), -- any escape characters are interpreted (e.g. "\\n" becomes an actual newline), and -- the strings are concatenated and not enclosed in double quotes. -- -- * --hide-tests \: do not execute tests (by default tests are executed if specified in a configuration file). -- -- * --show-output-only \: print only output (omits all other information). -- -- * --auto-config \: if a .fct file is given, search for a .config file -- with the same name and apply it (on by default). -- -- === Configuration files -- A configuration file is a sequence of 'fragments', where each fragment is of the form: -- -- > { -- > * -- > } -- -- A \ is a colon separated key-value pair, closed off by a semicolon, e.g. -- -- > hide-control-entity: failed,thrown; -- -- There are 4 valid groups: /general/, /funcons/, /tests/ and /inputs/. -- -- (1) __general__: -- The general /group/ is used as an alternative to command line flags, -- All Boolean and string options are available. -- Additionally, the option "funcon-term" is available for giving an initial -- funcon term: -- -- > general { -- > funcon-term: integer-add(3,2); -- > } -- -- (2) __funcons__: -- This group is used to define simple (nullary) funcons. -- They key is the name of the funcon, -- the value is a funcon term to which the funcon will rewrite once evaluated. -- Keys and values are separated by '=' in this group. This group is useful -- to choose an interpretation for unspecified components of a language specification. -- For example (from a Caml Light specification): -- -- > funcons { -- > implemented-vectors = vectors(values); -- > implemented-floats-format = binary64; -- > implemented-integers-width = 31; -- > implemented-characters = unicode-characters; -- > implemented-strings = strings; -- > } -- -- (3) __tests__: -- With this group unit-tests can be defined. -- Each key-value pairs specifies the expected value of a semantic entities, -- where the key is the name of a semantic entity -- and the value is the expected value. -- Additionally, the key "result-term" can be used to specify the expected result term. -- The tests group is useful to specify a complete unit-test in a single file, e.g. -- -- > general { -- > funcon-term: else(integer-add(integer-add(2,3),fail),print(3)); -- > } -- > tests { -- > result-term: (); -- > standard-out: [3]; -- > } -- -- (4) __inputs__: -- The inputs group is used to specify default values for input entities, e.g. -- -- > inputs { -- > standard-in: [1,2,3]; -- > } -- -- When input entities are given default values, simulation mode is turned on -- (even if --interactive-mode is used). -- -- === Languages specific interpreters -- This package does not provide just one interpreter, it provides -- the ability to play `mix and match' with 'FunconLibrary's to form interpreters. -- This enables the creation of interpreters for object languages from funcons -- (entities, or datatypes) specific to that object language. -- -- For this purpose, this module exports 'mkMainWithLibraryEntitiesTypes' (and variants). -- Say that a module exporting -- a 'FunconLibrary' is a "funcon module". -- An interpreter is obtained by importing the chosen "funcon modules" and uniting -- their 'FunconLibrary's (with 'libUnions'), perhaps together with default -- values for entities ('EntityDefault') and information about custom datatypes ('TypeRelation'). -- The resulting maps are given as arguments to 'mkMainWithLibraryEntitiesTypes' -- (or variant). -- By using 'mkMainWithLibraryEntitiesTypes', all interpreters inherit the -- core reusable funcon library.