Skip to content

Commit cb2734e

Browse files
committed
Merge branch 'agda5000' of https://github.com/agda/agda-stdlib into agda5000
Conflicts: .travis.yml
2 parents fe971b3 + 3946439 commit cb2734e

File tree

4 files changed

+195
-0
lines changed

4 files changed

+195
-0
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ matrix:
112112
# setting up travis-specific scripts and files
113113
- cp travis/* .
114114

115+
# create a suitable Agda `executables` file for examples in README using `Reflection.External`
116+
- echo -e "/usr/bin/expr\n" ~/.agda/executables
117+
115118
before_script:
116119
- export AGDA_OPTIONS="-Werror"
117120
- export RTS_OPTIONS="+RTS -M3.5G -H3.5G -A128M -RTS"

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The library has been tested using Agda 2.6.2
66
Highlights
77
----------
88

9+
* New module for making system calls during type checking, `Reflection.External`,
10+
which re-exports `Agda.Builtin.Reflection.External`.
11+
912
Bug-fixes
1013
---------
1114

@@ -52,6 +55,12 @@ Deprecated names
5255
New modules
5356
-----------
5457

58+
* New module for making system calls during type checking:
59+
```agda
60+
Reflection.External
61+
```
62+
which re-exports and augments the contents of `Agda.Builtin.Reflection.External`.
63+
5564
* Added `Reflection.Traversal` for generic de Bruijn-aware traversals of reflected terms.
5665
* Added `Reflection.DeBruijn` with weakening, strengthening and free variable operations
5766
on reflected terms.

README/Reflection/External.agda

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
------------------------------------------------------------------------
2+
-- The Agda standard library
3+
--
4+
-- How to use reflection to call external functions.
5+
--
6+
-- IMPORTANT: In order for this file to type-check you will need to add
7+
-- a line `/usr/bin/expr` to your `~/.agda/executables` file. See the
8+
-- section on Reflection in the Agda user manual for more details.
9+
------------------------------------------------------------------------
10+
11+
{-# OPTIONS --without-K --allow-exec #-}
12+
13+
module README.Reflection.External where
14+
15+
open import Data.List.Base using ([]; _∷_)
16+
open import Data.String.Base using (String; _++_)
17+
open import Relation.Binary.PropositionalEquality
18+
19+
-- All the commands needed to make an external system call are included
20+
-- in `Reflection.External`.
21+
22+
open import Reflection.External
23+
using (CmdSpec; runCmd)
24+
25+
-- The most important one is `CmdSpec` ("command specification")
26+
-- which allows ones to specify the external command being called, its
27+
-- arguments and the contents of stdin.
28+
29+
-- Here we define a simple command spec that takes two numbers and
30+
-- uses the Unix `expr` command to add the two together.
31+
32+
add : String String CmdSpec
33+
add x y = record
34+
{ name = "expr"
35+
; args = x ∷ "+" ∷ y ∷ []
36+
; input = ""
37+
}
38+
39+
-- The command can then be run using the `runCmd` macro. If no error
40+
-- occured then by default the macro returns the result of `stdout`.
41+
-- Otherwise the macro will terminate with a type error.
42+
43+
test : runCmd (add "1" "2") ≡ "3\n"
44+
test = refl
45+
46+
-- If you are running a command that you know might be ill-formed
47+
-- and result in an error then can use `unsafeRunCmd` instead that
48+
-- returns a `Result` object containing the exit code and the contents
49+
-- of both `stdout` and `stderr`.
50+
51+
open import Reflection.External
52+
using (unsafeRunCmd; result; exitFailure)
53+
54+
error = "/usr/bin/expr: non-integer argument\n"
55+
56+
test2 : unsafeRunCmd (add "a" "b") ≡ result (exitFailure 2) "" error
57+
test2 = refl
58+
59+
-- For a more advanced use-case where SMT solvers are invoked from
60+
-- Agda, see Schmitty (https://github.com/wenkokke/schmitty)

src/Reflection/External.agda

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
------------------------------------------------------------------------
2+
-- The Agda standard library
3+
--
4+
-- Support for system calls as part of reflection
5+
------------------------------------------------------------------------
6+
7+
{-# OPTIONS --without-K --safe #-}
8+
9+
module Reflection.External where
10+
11+
import Agda.Builtin.Reflection.External as Builtin
12+
13+
open import Data.Nat.Base using (ℕ; suc; zero; NonZero)
14+
open import Data.List.Base using (List; _∷_; [])
15+
open import Data.Product using (_×_; _,_)
16+
open import Data.String.Base as String using (String; _++_)
17+
open import Data.Sum using (_⊎_; inj₁; inj₂; [_,_])
18+
open import Data.Unit.Base using (⊤; tt)
19+
open import Function using (case_of_; _$_; _∘_)
20+
open import Reflection hiding (name)
21+
22+
-- Type aliases for the various strings.
23+
24+
CmdName = String
25+
StdIn = String
26+
StdErr = String
27+
StdOut = String
28+
29+
-- Representation for exit codes, assuming 0 is consistently used to indicate
30+
-- success across platforms.
31+
data ExitCode : Set where
32+
exitSuccess : ExitCode
33+
exitFailure : (n : ℕ) {n≢0 : NonZero n} ExitCode
34+
35+
-- Specification for a command.
36+
record CmdSpec : Set where
37+
constructor cmdSpec
38+
field
39+
name : CmdName -- ^ Executable name (see ~/.agda/executables)
40+
args : List String -- ^ Command-line arguments for executable
41+
input : StdIn -- ^ Contents of standard input
42+
43+
-- Result of running a command.
44+
record Result : Set where
45+
constructor result
46+
field
47+
exitCode : ExitCode -- ^ Exit code returned by the process
48+
output : StdOut -- ^ Contents of standard output
49+
error : StdErr -- ^ Contents of standard error
50+
51+
-- Convert a natural number to an exit code.
52+
toExitCode : ExitCode
53+
toExitCode zero = exitSuccess
54+
toExitCode (suc n) = exitFailure (suc n)
55+
56+
-- Quote an exit code as an Agda term.
57+
quoteExitCode : ExitCode Term
58+
quoteExitCode exitSuccess =
59+
con (quote exitSuccess) []
60+
quoteExitCode (exitFailure n) =
61+
con (quote exitFailure) (vArg (lit (nat n)) ∷ hArg (con (quote tt) []) ∷ [])
62+
63+
-- Quote a result as an Agda term.
64+
quoteResult : Result Term
65+
quoteResult (result exitCode output error) =
66+
con (quote result) $ vArg (quoteExitCode exitCode)
67+
∷ vArg (lit (string output))
68+
∷ vArg (lit (string error))
69+
∷ []
70+
71+
-- Run command from specification and return the full result.
72+
--
73+
-- NOTE: If the command fails, this macro still succeeds, and returns the
74+
-- full result, including exit code and the contents of stderr.
75+
--
76+
unsafeRunCmdTC : CmdSpec TC Result
77+
unsafeRunCmdTC c = do
78+
(exitCode , (stdOut , stdErr))
79+
Builtin.execTC (CmdSpec.name c) (CmdSpec.args c) (CmdSpec.input c)
80+
return $ result (toExitCode exitCode) stdOut stdErr
81+
82+
macro
83+
unsafeRunCmd : CmdSpec Term TC ⊤
84+
unsafeRunCmd c hole = unsafeRunCmdTC c >>= unify hole ∘ quoteResult
85+
86+
87+
-- Show a command for the user.
88+
showCmdSpec : CmdSpec String
89+
showCmdSpec c = String.unwords $ CmdSpec.name c ∷ CmdSpec.args c
90+
91+
92+
private
93+
-- Helper function for throwing an error from reflection.
94+
userError : {a} {A : Set a} CmdSpec StdOut × StdErr TC A
95+
userError c (stdout , stderr) = typeError (strErr errMsg ∷ [])
96+
where
97+
errMsg : String
98+
errMsg = String.unlines
99+
$ ("Error while running command '" ++ showCmdSpec c ++ "'")
100+
∷ ("Input:\n" ++ CmdSpec.input c)
101+
∷ ("Output:\n" ++ stdout)
102+
∷ ("Error:\n" ++ stderr)
103+
∷ []
104+
105+
106+
-- Run command from specification. If the command succeeds, it returns the
107+
-- contents of stdout. Otherwise, it throws a type error with the contents
108+
-- of stderr.
109+
runCmdTC : CmdSpec TC StdOut
110+
runCmdTC c = do
111+
r unsafeRunCmdTC c
112+
let debugPrefix = ("user." ++ CmdSpec.name c)
113+
case Result.exitCode r of λ
114+
{ exitSuccess do
115+
debugPrint (debugPrefix ++ ".stderr") 10 (strErr (Result.error r) ∷ [])
116+
return $ Result.output r
117+
; (exitFailure n) do
118+
userError c (Result.output r , Result.error r)
119+
}
120+
121+
macro
122+
runCmd : CmdSpec Term TC ⊤
123+
runCmd c hole = runCmdTC c >>= unify hole ∘ lit ∘ string

0 commit comments

Comments
 (0)