0 | module System
  1 |
  2 | import public Data.So
  3 | import Data.String
  4 |
  5 | import public System.Escape
  6 | import System.File
  7 |
  8 | %default total
  9 |
 10 | ||| Shorthand for referring to the C support library
 11 | |||
 12 | ||| @ fn the function name to refer to in the C support library
 13 | supportC : (fn : String) -> String
 14 | supportC fn = "C:\{fn}, libidris2_support, idris_support.h"
 15 |
 16 | ||| Shorthand for referring to the Node system support library
 17 | |||
 18 | ||| @ fn the function name to refer to in the js/system_support.js file
 19 | supportNode : (fn : String) -> String
 20 | supportNode fn = "node:support:\{fn},support_system"
 21 |
 22 | ||| Shorthand for referring to libc 6
 23 | |||
 24 | ||| @ fn the function name to refer to in libc 6
 25 | libc : (fn : String) -> String
 26 | libc fn = "C:" ++ fn ++ ", libc 6"
 27 |
 28 | -- `sleep` and `usleep` need to be tied to `blodwen-[u]sleep` for threading
 29 | -- reasons (see support/racket/support.rkt)
 30 |
 31 | %foreign "scheme,racket:blodwen-sleep"
 32 |          supportC "idris2_sleep"
 33 | prim__sleep : Int -> PrimIO ()
 34 |
 35 | %foreign "scheme,racket:blodwen-usleep"
 36 |          supportC "idris2_usleep"
 37 | prim__usleep : Int -> PrimIO ()
 38 |
 39 | ||| Sleep for the specified number of seconds or, if signals are supported,
 40 | ||| until an un-ignored signal arrives.
 41 | ||| The exact wall-clock time slept might slightly differ depending on how busy
 42 | ||| the system is and the resolution of the system's clock.
 43 | |||
 44 | ||| @ sec the number of seconds to sleep for
 45 | export
 46 | sleep : HasIO io => (sec : Int) -> io ()
 47 | sleep sec = primIO (prim__sleep sec)
 48 |
 49 | ||| Sleep for the specified number of microseconds or, if signals are supported,
 50 | ||| until an un-ignored signal arrives.
 51 | ||| The exact wall-clock time slept might slighly differ depending on how busy
 52 | ||| the system is and the resolution of the system's clock.
 53 | |||
 54 | ||| @ usec the number of microseconds to sleep for
 55 | export
 56 | usleep : HasIO io => (usec : Int) -> So (usec >= 0) => io ()
 57 | usleep usec = primIO (prim__usleep usec)
 58 |
 59 | -- Get the number of arguments
 60 | -- Note: node prefixes the list of command line arguments
 61 | --       with the path to the `node` executable. This is
 62 | --       inconsistent with other backends, which only list
 63 | --       the path to the running program. For reasons of
 64 | --       consistency across backends, this first argument ist
 65 | --       dropped on the node backend.
 66 | %foreign "scheme:blodwen-arg-count"
 67 |          supportC "idris2_getArgCount"
 68 |          "node:lambda:() => process.argv.length - 1"
 69 | prim__getArgCount : PrimIO Int
 70 |
 71 | -- Get argument number `n`. See also `prim__getArgCount`
 72 | -- about the special treatment of the node backend.
 73 | %foreign "scheme:blodwen-arg"
 74 |          supportC "idris2_getArg"
 75 |          "node:lambda:n => process.argv[n + 1]"
 76 | prim__getArg : Int -> PrimIO String
 77 |
 78 | ||| Retrieve the arguments to the program call, if there were any.
 79 | export
 80 | getArgs : HasIO io => io (List String)
 81 | getArgs = do
 82 |             n <- primIO prim__getArgCount
 83 |             if n > 0
 84 |               then for [0..n-1] $ primIO . prim__getArg
 85 |               else pure []
 86 |
 87 | %foreign libc "getenv"
 88 |          "node:lambda: n => process.env[n]"
 89 | prim__getEnv : String -> PrimIO (Ptr String)
 90 |
 91 | %foreign supportC "idris2_getEnvPair"
 92 | prim__getEnvPair : Int -> PrimIO (Ptr String)
 93 | %foreign supportC "idris2_setenv"
 94 |          supportNode "setEnv"
 95 | prim__setEnv : String -> String -> Int -> PrimIO Int
 96 | %foreign supportC "idris2_unsetenv"
 97 |          supportNode "unsetEnv"
 98 | prim__unsetEnv : String -> PrimIO Int
 99 |
100 | %foreign "C:idris2_enableRawMode, libidris2_support, idris_support.h"
101 | prim__enableRawMode : (1 x : %World) -> IORes Int
102 |
103 | %foreign "C:idris2_resetRawMode, libidris2_support, idris_support.h"
104 | prim__resetRawMode : (1 x : %World) -> IORes ()
105 |
106 | ||| `enableRawMode` enables raw mode for stdin, allowing characters
107 | ||| to be read one at a time, without buffering or echoing.
108 | ||| If `enableRawMode` is used, the program should call `resetRawMode` before
109 | ||| exiting. Consider using `withRawMode` instead to ensure the tty is reset.
110 | |||
111 | ||| This is not supported on windows.
112 | export
113 | enableRawMode : HasIO io => io (Either FileError ())
114 | enableRawMode =
115 |   case !(primIO prim__enableRawMode) of
116 |     0 => pure $ Right ()
117 |     _ => returnError
118 |
119 | ||| `resetRawMode` resets stdin raw mode to original state if
120 | ||| `enableRawMode` had been previously called.
121 | export
122 | resetRawMode : HasIO io => io ()
123 | resetRawMode = primIO prim__resetRawMode
124 |
125 | ||| `withRawMode` performs a given operation after setting stdin to raw mode
126 | ||| and ensure that stdin is reset to its original state afterwards.
127 | |||
128 | ||| This is not supported on windows.
129 | export
130 | withRawMode : HasIO io =>
131 |               (onError   : FileError -> io a) ->
132 |               (onSuccess : () -> io a) ->
133 |               io a
134 | withRawMode onError onSuccess = do
135 |   Right () <- enableRawMode
136 |     | Left err => onError err
137 |   result <- onSuccess ()
138 |   resetRawMode
139 |   pure result
140 |
141 | ||| Retrieve the specified environment variable's value string, or `Nothing` if
142 | ||| there is no such environment variable.
143 | |||
144 | ||| @ var the name of the environment variable to look up
145 | export
146 | getEnv : HasIO io => (var : String) -> io (Maybe String)
147 | getEnv var
148 |    = do env <- primIO $ prim__getEnv var
149 |         if prim__nullPtr env /= 0
150 |            then pure Nothing
151 |            else pure (Just (prim__getString env))
152 |
153 | ||| Retrieve all the key-value pairs of the environment variables, and return a
154 | ||| list containing them.
155 | export
156 | covering
157 | getEnvironment : HasIO io => io (List (String, String))
158 | getEnvironment = getAllPairs 0 []
159 |   where
160 |     splitEq : String -> (String, String)
161 |     splitEq str
162 |         = let (k, v)  = break (== '=') str
163 |               (_, v') = break (/= '=') v in
164 |               (k, v')
165 |
166 |     getAllPairs : Int -> List String -> io (List (String, String))
167 |     getAllPairs n acc = do
168 |       envPair <- primIO $ prim__getEnvPair n
169 |       if prim__nullPtr envPair /= 0
170 |          then pure $ reverse $ map splitEq acc
171 |          else getAllPairs (n + 1) (prim__getString envPair :: acc)
172 |
173 | ||| Add the specified variable with the given value string to the environment,
174 | ||| optionally overwriting any existing environment variable with the same name.
175 | ||| Returns True whether the value is set, overwritten, or not overwritten because
176 | ||| overwrite was False. Returns False if a system error occurred. You can `getErrno`
177 | ||| to check the error.
178 | |||
179 | ||| @ var       the name of the environment variable to set
180 | ||| @ val       the value string to set the environment variable to
181 | ||| @ overwrite whether to overwrite the existing value if an environment
182 | |||             variable with the specified name already exists
183 | export
184 | setEnv : HasIO io => (var : String) -> (val : String) -> (overwrite : Bool) ->
185 |                      io Bool
186 | setEnv var val overwrite
187 |    = do ok <- primIO $ prim__setEnv var val (if overwrite then 1 else 0)
188 |         pure $ ok == 0
189 |
190 | ||| Delete the specified environment variable. Returns `True` either if the
191 | ||| value was deleted or if the value was not defined/didn't exist. Returns
192 | ||| `False` if a system error occurred. You can `getErrno` to check the error.
193 | export
194 | unsetEnv : HasIO io => (var : String) -> io Bool
195 | unsetEnv var
196 |    = do ok <- primIO $ prim__unsetEnv var
197 |         pure $ ok == 0
198 |
199 | %foreign "C:idris2_system, libidris2_support, idris_system.h"
200 |          supportNode "spawnSync"
201 | prim__system : String -> PrimIO Int
202 |
203 | ||| Execute a shell command, returning its termination status or -1 if an error
204 | ||| occurred.
205 | export
206 | system : HasIO io => String -> io Int
207 | system cmd = primIO (prim__system cmd)
208 |
209 | namespace Escaped
210 |   export
211 |   system : HasIO io => List String -> io Int
212 |   system = system . escapeCmd
213 |
214 | ||| Run a shell command, returning its stdout, and exit code.
215 | export
216 | covering
217 | run : HasIO io => (cmd : String) -> io (String, Int)
218 | run cmd = do
219 |     Right f <- popen cmd Read
220 |         | Left err => pure ("", 1)
221 |     Right resp <- fRead f
222 |         | Left err => pure ("", 1)
223 |     exitCode <- pclose f
224 |     pure (resp, exitCode)
225 |
226 | namespace Escaped
227 |   export
228 |   covering
229 |   run : HasIO io => (cmd : List String) -> io (String, Int)
230 |   run = run . escapeCmd
231 |
232 | ||| Run a shell command, allowing processing its stdout line by line.
233 | |||
234 | ||| Notice that is the line of the command ends with a newline character,
235 | ||| it will be present in the string passed to the processing function.
236 | |||
237 | ||| This function returns an exit code which value should be consistent with the `run` function.
238 | export
239 | covering
240 | runProcessingOutput : HasIO io => (String -> io ()) -> (cmd : String) -> io Int
241 | runProcessingOutput pr cmd = do
242 |   Right f <- popen cmd Read
243 |     | Left err => pure 1
244 |   True <- process f
245 |     | False => pure 1 -- we do not close `f` in case of reading error, like `run` does
246 |   pclose f
247 |
248 |   where
249 |     process : File -> io Bool
250 |     process h = if !(fEOF h) then pure True else do
251 |       Right line <- fGetLine h
252 |         | Left err => pure False
253 |       pr line
254 |       process h
255 |
256 | namespace Escaped
257 |   export
258 |   covering
259 |   runProcessingOutput : HasIO io => (String -> io ()) -> (cmd : List String) -> io Int
260 |   runProcessingOutput pr = runProcessingOutput pr . escapeCmd
261 |
262 | %foreign supportC "idris2_time"
263 |          "javascript:lambda:() => Math.floor(new Date().getTime() / 1000)"
264 | prim__time : PrimIO Int
265 |
266 | ||| Return the number of seconds since epoch.
267 | export
268 | time : HasIO io => io Integer
269 | time = pure $ cast !(primIO prim__time)
270 |
271 | %foreign supportC "idris2_getPID"
272 |          "node:lambda:() => process.pid"
273 | prim__getPID : PrimIO Int
274 |
275 | ||| Get the ID of the currently running process.
276 | export
277 | getPID : HasIO io => io Int
278 | getPID = primIO prim__getPID
279 |
280 | %foreign libc "exit"
281 |          "node:lambda:c => process.exit(c)"
282 | prim__exit : Int -> PrimIO ()
283 |
284 | ||| Programs can either terminate successfully, or end in a caught
285 | ||| failure.
286 | public export
287 | data ExitCode : Type where
288 |   ||| Terminate successfully.
289 |   ExitSuccess : ExitCode
290 |   ||| Program terminated for some prescribed reason.
291 |   |||
292 |   ||| @errNo A non-zero numerical value indicating failure.
293 |   ||| @prf   Proof that the int value is non-zero.
294 |   ExitFailure : (errNo    : Int) -> (So (not $ errNo == 0)) => ExitCode
295 |
296 | export
297 | Cast Int ExitCode where
298 |   cast 0 = ExitSuccess
299 |   cast code = ExitFailure code @{believe_me Oh}
300 |
301 | export
302 | Cast ExitCode Int where
303 |   cast ExitSuccess = 0
304 |   cast (ExitFailure code) = code
305 |
306 | ||| Exit the program normally, with the specified status.
307 | export
308 | exitWith : HasIO io => ExitCode -> io a
309 | exitWith = primIO . believe_me . prim__exit . cast
310 |
311 | ||| Exit the program with status value 1, indicating failure.
312 | ||| If you want to specify a custom status value, see `exitWith`.
313 | export
314 | exitFailure : HasIO io => io a
315 | exitFailure = exitWith (ExitFailure 1)
316 |
317 | ||| Exit the program after a successful run.
318 | export
319 | exitSuccess : HasIO io => io a
320 | exitSuccess = exitWith ExitSuccess
321 |
322 | ||| Print the error message and call exitFailure
323 | export
324 | die : HasIO io => String -> io a
325 | die str
326 |   = do ignore $ fPutStrLn stderr str
327 |        exitFailure
328 |