content: -- | Create Copilot executables that generate code or interpret streams and
  -- print the results to stdout.
  module Language.Copilot.Main ( copilotMain, defaultMain ) where
  
  import qualified Copilot.Core as C (Spec)
  import Copilot.Language (interpret)
  import Copilot.Language.Reify (reify)
  import Copilot.Language (Spec)
  import qualified Copilot.PrettyPrint as PP
  
  import Options.Applicative
  import Data.Semigroup ((<>))
  import Control.Monad (when)
  
  -- | An interpreter of Copilot specifications for a given
  -- number of simulation steps.
  type Interpreter  = Integer   ->   Spec -> IO ()
  
  -- | A compiler from
  -- <https://hackage.haskell.org/package/copilot-core Copilot Core>
  -- specifications.
  type Compiler     = FilePath  -> C.Spec -> IO ()
  
  -- | A pretty printer of Copilot specifications.
  type Printer      =                Spec -> IO ()
  
  -- | Command line arguments supported by all commands in 'cmdargs'.
  data CmdArgs = CmdArgs
    { aoutput     :: String
    , acompile    :: Bool
    , apretty     :: Bool
    , ainterpret  :: Int
    }
  
  -- | Command line arguments handled by the Copilot main function.
  cmdargs :: Parser CmdArgs
  cmdargs = CmdArgs
    <$> strOption (long "output"  <> short 'o' <> value "."
                                  <> help "Output directory of C files")
    <*> switch  (long "justrun" <> short 'c'
                                <> help "Do NOT produce *.c and *.h files as output")
    <*> switch  (long "print" <> short 'p'
                              <> help "Pretty print the specification")
    <*> option auto (long "interpret" <> short 'i' <> value 0
                                      <> metavar "INT" <> showDefault
                                      <> help "Interpret specification and write result to output")
  
  -- | Create a main to either compile or interpret a copilot specification.
  --
  -- This function must be provided an auxiliary function capable of compiling
  -- <https://hackage.haskell.org/package/copilot-core Copilot Core>
  -- specifications for some target.
  --
  -- The command line program supports four main commands:
  --
  --     * @--output/-o@: use the given compiler to produce C code.
  --
  --     * @--justrun/-c@: execute a dry-run, which parses and converts the
  --       specification to core but does not produce any output.
  --
  --     * @--print/-p@: pretty print the specification.
  --
  --     * @--interpret/-i NUM@: interpret the specification for a given number
  --       of steps.
  copilotMain :: Interpreter -> Printer -> Compiler -> Spec -> IO ()
  copilotMain interp pretty comp spec = main =<< execParser opts
    where
      opts = info (cmdargs <**> helper) fullDesc
  
      main :: CmdArgs -> IO ()
      main args = do
        let iters = ainterpret args
        when (apretty args)       $ pretty spec
        when (iters Prelude.> 0)  $ interp (fromIntegral iters) spec
  
        when (not $ acompile args) $ do
          spec' <- reify spec
          comp (aoutput args) spec'
  
  -- | Create a main function with a default interpreter and pretty printer.
  --
  -- This function must be provided an auxiliary function capable of compiling
  -- <https://hackage.haskell.org/package/copilot-core Copilot Core>
  -- specifications for some target.
  --
  -- This function relies on 'copilotMain', please refer to that function for the
  -- command line options.
  defaultMain :: Compiler -> Spec -> IO ()
  defaultMain = copilotMain interpret prettyPrint
    where
      -- Transform a high-level Copilot Language specification into a low-level
      -- Copilot Core specification and pretty-print it to stdout.
      prettyPrint :: Spec -> IO ()
      prettyPrint e = fmap PP.prettyPrint (reify e) >>= putStr
  