------------------------------------------------------------------------
-- The Agda standard library
--
-- Support for system calls as part of reflection
------------------------------------------------------------------------
{-# OPTIONS --cubical-compatible --safe #-}
module Reflection.External where
import Agda.Builtin.Reflection.External as Builtin
open import Data.Nat.Base using (ℕ; suc; zero; NonZero)
open import Data.List.Base using (List; _∷_; [])
open import Data.Product.Base using (_×_; _,_)
open import Data.String.Base as String using (String; _++_)
open import Data.Sum.Base using (_⊎_; inj₁; inj₂; [_,_])
open import Data.Unit.Base using (⊤; tt)
open import Function.Base using (case_of_; _$_; _∘_)
open import Reflection hiding (name)
-- Type aliases for the various strings.
CmdName = String
StdIn = String
StdErr = String
StdOut = String
-- Representation for exit codes, assuming 0 is consistently used to
-- indicate success across platforms.
data ExitCode : Set where
exitSuccess : ExitCode
exitFailure : (n : ℕ) {n≢0 : NonZero n} → ExitCode
-- Specification for a command.
record CmdSpec : Set where
constructor cmdSpec
field
name : CmdName -- ^ Executable name (see ~/.agda/executables)
args : List String -- ^ Command-line arguments for executable
input : StdIn -- ^ Contents of standard input
-- Result of running a command.
record Result : Set where
constructor result
field
exitCode : ExitCode -- ^ Exit code returned by the process
output : StdOut -- ^ Contents of standard output
error : StdErr -- ^ Contents of standard error
-- Convert a natural number to an exit code.
toExitCode : ℕ → ExitCode
toExitCode zero = exitSuccess
toExitCode (suc n) = exitFailure (suc n)
-- Quote an exit code as an Agda term.
quoteExitCode : ExitCode → Term
quoteExitCode exitSuccess =
con (quote exitSuccess) []
quoteExitCode (exitFailure n) =
con (quote exitFailure) (vArg (lit (nat n)) ∷ hArg (con (quote tt) []) ∷ [])
-- Quote a result as an Agda term.
quoteResult : Result → Term
quoteResult (result exitCode output error) =
con (quote result) $ vArg (quoteExitCode exitCode)
∷ vArg (lit (string output))
∷ vArg (lit (string error))
∷ []
-- Run command from specification and return the full result.
--
-- NOTE: If the command fails, this macro still succeeds, and returns the
-- full result, including exit code and the contents of stderr.
--
unsafeRunCmdTC : CmdSpec → TC Result
unsafeRunCmdTC c = do
(exitCode , (stdOut , stdErr))
← Builtin.execTC (CmdSpec.name c) (CmdSpec.args c) (CmdSpec.input c)
pure $ result (toExitCode exitCode) stdOut stdErr
macro
unsafeRunCmd : CmdSpec → Term → TC ⊤
unsafeRunCmd c hole = unsafeRunCmdTC c >>= unify hole ∘ quoteResult
-- Show a command for the user.
showCmdSpec : CmdSpec → String
showCmdSpec c = String.unwords $ CmdSpec.name c ∷ CmdSpec.args c
private
-- Helper function for throwing an error from reflection.
userError : ∀ {a} {A : Set a} → CmdSpec → StdOut × StdErr → TC A
userError c (stdout , stderr) = typeError (strErr errMsg ∷ [])
where
errMsg : String
errMsg = String.unlines
$ ("Error while running command '" ++ showCmdSpec c ++ "'")
∷ ("Input:\n" ++ CmdSpec.input c)
∷ ("Output:\n" ++ stdout)
∷ ("Error:\n" ++ stderr)
∷ []
-- Run command from specification. If the command succeeds, it returns the
-- contents of stdout. Otherwise, it throws a type error with the contents
-- of stderr.
runCmdTC : CmdSpec → TC StdOut
runCmdTC c = do
r ← unsafeRunCmdTC c
let debugPrefix = ("user." ++ CmdSpec.name c)
case Result.exitCode r of λ
{ exitSuccess → do
debugPrint (debugPrefix ++ ".stderr") 10 (strErr (Result.error r) ∷ [])
pure $ Result.output r
; (exitFailure n) → do
userError c (Result.output r , Result.error r)
}
macro
runCmd : CmdSpec → Term → TC ⊤
runCmd c hole = runCmdTC c >>= unify hole ∘ lit ∘ string