2018-07-17 03:33:03 +01:00
|
|
|
|
(define-module (geesh shell)
|
2018-07-17 16:20:06 +01:00
|
|
|
|
#:use-module (geesh built-ins)
|
2018-07-17 03:33:03 +01:00
|
|
|
|
#:use-module (geesh environment)
|
|
|
|
|
#:use-module (ice-9 match)
|
2018-07-19 04:44:04 +01:00
|
|
|
|
#:use-module (ice-9 textual-ports)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
#:use-module (srfi srfi-1)
|
2018-07-19 04:41:10 +01:00
|
|
|
|
#:use-module (srfi srfi-26)
|
2018-10-16 19:14:15 +01:00
|
|
|
|
#:export (sh:and
|
|
|
|
|
sh:exec-let
|
2018-07-19 04:41:10 +01:00
|
|
|
|
sh:exec
|
2018-10-16 20:26:40 +01:00
|
|
|
|
sh:for
|
2018-10-16 19:14:15 +01:00
|
|
|
|
sh:not
|
|
|
|
|
sh:or
|
2018-10-16 18:48:52 +01:00
|
|
|
|
sh:pipeline
|
2018-07-19 04:42:13 +01:00
|
|
|
|
sh:subshell
|
2018-07-19 04:44:04 +01:00
|
|
|
|
sh:substitute-command
|
2018-07-19 04:41:10 +01:00
|
|
|
|
sh:with-redirects))
|
2018-07-17 03:33:03 +01:00
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
;;;
|
|
|
|
|
;;; This module provides functions for executing Shell language
|
|
|
|
|
;;; constructs.
|
|
|
|
|
;;;
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(define *fd-count* 3)
|
|
|
|
|
|
|
|
|
|
(define (fd->current-port fd)
|
|
|
|
|
"Return the current port (e.g. @code{current-input-port})
|
|
|
|
|
corresponding to the the Shell file descriptor @var{fd}."
|
|
|
|
|
(match fd
|
|
|
|
|
(0 current-input-port)
|
|
|
|
|
(1 current-output-port)
|
|
|
|
|
(2 current-error-port)))
|
|
|
|
|
|
|
|
|
|
(define (install-current-ports!)
|
|
|
|
|
"Install all current ports into their usual file descriptors. For
|
|
|
|
|
example, if @code{current-input-port} is a @code{file-port?}, make the
|
|
|
|
|
process file descriptor 0 refer to the file open for
|
|
|
|
|
@code{current-input-port}. If any current port is a @code{port?} but
|
|
|
|
|
not a @code{file-port?}, its corresponding file descriptor will refer
|
|
|
|
|
to @file{/dev/null}."
|
|
|
|
|
;; XXX: Input/output ports? Closing other FDs?
|
|
|
|
|
(for-each (lambda (i)
|
|
|
|
|
(match ((fd->current-port i))
|
|
|
|
|
((? file-port? port)
|
|
|
|
|
(dup port i))
|
|
|
|
|
((? input-port? port)
|
|
|
|
|
(dup (open-file "/dev/null" "r") i))
|
|
|
|
|
((? output-port? port)
|
|
|
|
|
(dup (open-file "/dev/null" "w") i))
|
|
|
|
|
(_ #t)))
|
|
|
|
|
(iota *fd-count*)))
|
|
|
|
|
|
2018-07-17 03:33:03 +01:00
|
|
|
|
(define (exec-utility env bindings path name args)
|
|
|
|
|
"Execute @var{path} as a subprocess with environment @var{env} and
|
|
|
|
|
extra environment variables @var{bindings}. The first argument given
|
|
|
|
|
to the new process will be @var{name}, and the rest of the arguments
|
|
|
|
|
will be @var{args}."
|
|
|
|
|
(let ((utility-env (environment->environ env bindings)))
|
2018-11-15 16:07:45 +00:00
|
|
|
|
;; We need to flush all ports here to ensure the proper sequence
|
|
|
|
|
;; of output. Without flushing, output that we have written could
|
|
|
|
|
;; stay in a buffer while the utility (which does not know about
|
|
|
|
|
;; the buffer) produces its output.
|
|
|
|
|
(flush-all-ports)
|
2018-07-17 03:33:03 +01:00
|
|
|
|
(match (primitive-fork)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(0 (install-current-ports!)
|
|
|
|
|
(apply execle path utility-env name args))
|
2018-07-17 03:33:03 +01:00
|
|
|
|
(pid (match-let (((pid . status) (waitpid pid)))
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(set-environment-status! env (status:exit-val status)))))))
|
2018-07-17 03:33:03 +01:00
|
|
|
|
|
2018-07-19 04:37:21 +01:00
|
|
|
|
(define (slashless? s)
|
|
|
|
|
"Test if the string @var{s} does not contain any slashes ('/')."
|
|
|
|
|
(not (string-index s #\/)))
|
|
|
|
|
|
|
|
|
|
(define (split-search-path s)
|
|
|
|
|
"Split the search path string @var{s}."
|
|
|
|
|
(if (string-null? s) '() (string-split s #\:)))
|
|
|
|
|
|
|
|
|
|
(define (find-utility env name)
|
|
|
|
|
"Search for the path of the utility @var{name} using @var{env}. If
|
|
|
|
|
it cannot be found, return @code{#f}."
|
|
|
|
|
(let loop ((prefixes (split-search-path (var-ref* env "PATH"))))
|
|
|
|
|
(and (pair? prefixes)
|
|
|
|
|
(let* ((prefix (car prefixes))
|
|
|
|
|
(path (if (string-suffix? "/" prefix)
|
|
|
|
|
(string-append prefix name)
|
|
|
|
|
(string-append prefix "/" name))))
|
|
|
|
|
(if (access? path X_OK)
|
|
|
|
|
path
|
|
|
|
|
(loop (cdr prefixes)))))))
|
|
|
|
|
|
2018-07-17 03:33:03 +01:00
|
|
|
|
(define (sh:exec-let env bindings name . args)
|
2018-07-19 04:37:21 +01:00
|
|
|
|
"Find and execute @var{name} with arguments @var{args}, environment
|
2018-07-17 03:33:03 +01:00
|
|
|
|
@var{env}, and extra environment variable bindings @var{bindings}."
|
2018-07-19 04:37:21 +01:00
|
|
|
|
(if (slashless? name)
|
2018-07-17 16:20:06 +01:00
|
|
|
|
(or (and=> (search-special-built-ins name)
|
|
|
|
|
(lambda (proc)
|
|
|
|
|
(for-each (match-lambda
|
|
|
|
|
((name . value)
|
|
|
|
|
(set-var! env name value)))
|
|
|
|
|
bindings)
|
2018-10-17 00:45:42 +01:00
|
|
|
|
(let ((exit-val (apply proc env args)))
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(set-environment-status! env exit-val))))
|
2018-11-09 03:18:32 +00:00
|
|
|
|
(and=> (environment-function-ref env name)
|
|
|
|
|
(lambda (proc)
|
|
|
|
|
(with-environment-arguments env args
|
|
|
|
|
(lambda ()
|
|
|
|
|
(apply proc env args)))))
|
2018-07-17 16:20:06 +01:00
|
|
|
|
(and=> (search-built-ins name)
|
|
|
|
|
(lambda (proc)
|
|
|
|
|
;; TODO: Use 'bindings' here.
|
2018-10-17 00:45:42 +01:00
|
|
|
|
(let ((exit-val (apply proc env args)))
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(set-environment-status! env exit-val))))
|
2018-07-17 16:20:06 +01:00
|
|
|
|
(and=> (find-utility env name)
|
2018-07-19 04:37:21 +01:00
|
|
|
|
(lambda (path)
|
|
|
|
|
(exec-utility env bindings path name args)))
|
|
|
|
|
(error "Command not found."))
|
|
|
|
|
(exec-utility env bindings name name args)))
|
2018-07-17 03:33:03 +01:00
|
|
|
|
|
|
|
|
|
(define (sh:exec env name . args)
|
2018-07-19 04:37:21 +01:00
|
|
|
|
"Find and execute @var{name} with arguments @var{args} and
|
|
|
|
|
environment @var{env}."
|
2018-07-17 03:33:03 +01:00
|
|
|
|
(apply sh:exec-let env '() name args))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Redirects.
|
|
|
|
|
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(define (save-and-set-redirect env redir)
|
|
|
|
|
"Update the current port parameters according to @code{redir}, and
|
|
|
|
|
return a pair consisting of the Shell file descriptor that has been
|
|
|
|
|
changed and a copy of its old value."
|
|
|
|
|
|
|
|
|
|
(define* (save-and-set fd target #:optional (open-flags 0))
|
|
|
|
|
(let ((saved-port ((fd->current-port fd))))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(match target
|
2018-10-15 14:43:19 +01:00
|
|
|
|
((? port?) ((fd->current-port fd) target))
|
|
|
|
|
((? string?) ((fd->current-port fd) (open target open-flags)))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
;; TODO: Verify open-flags.
|
2018-10-15 14:43:19 +01:00
|
|
|
|
((? integer?) ((fd->current-port fd) ((fd->current-port target))))
|
|
|
|
|
(#f (close-port (fd->current-port fd))))
|
|
|
|
|
`(,fd . ,saved-port)))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
|
|
|
|
|
(match redir
|
|
|
|
|
(('< (? integer? fd) (? string? filename))
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd filename O_RDONLY))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('> (? integer? fd) (? string? filename))
|
|
|
|
|
;; TODO: Observe noclobber.
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd filename (logior O_WRONLY O_CREAT O_TRUNC)))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('>! (? integer? fd) (? string? filename))
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd filename (logior O_WRONLY O_CREAT O_TRUNC)))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('>> fd filename)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd filename (logior O_WRONLY O_CREAT O_APPEND)))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('<> fd filename)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd filename (logior O_RDWR O_CREAT)))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('<& (? integer? fd1) (? integer? fd2))
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd1 fd2))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('<& (? integer? fd) '-)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd #f))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('>& (? integer? fd1) (? integer? fd2))
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd1 fd2))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('>& (? integer? fd) '-)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd #f))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(('<< (? integer? fd) text)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(let ((port (tmpfile)))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(display text port)
|
|
|
|
|
(seek port 0 SEEK_SET)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(save-and-set fd port)))))
|
|
|
|
|
|
|
|
|
|
(define (restore-saved-port saved-port)
|
|
|
|
|
"Restore a Shell file descriptor to its previous state as described
|
|
|
|
|
by @var{saved-port}, where @var{saved-port} is a return value of
|
|
|
|
|
@code{save-and-set-redirect}."
|
|
|
|
|
(match saved-port
|
2018-07-19 04:41:10 +01:00
|
|
|
|
((fd . saved-fd)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(let ((port ((fd->current-port fd))))
|
|
|
|
|
((fd->current-port fd) saved-fd)
|
|
|
|
|
(unless (any (cut eq? port <>)
|
|
|
|
|
(map (lambda (fd)
|
|
|
|
|
((fd->current-port fd)))
|
|
|
|
|
(iota *fd-count*)))
|
|
|
|
|
(close port))))))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
|
|
|
|
|
(define (sh:with-redirects env redirs thunk)
|
|
|
|
|
"Call @var{thunk} with the redirects @var{redirs} in effect."
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(let ((saved-ports #f))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
(dynamic-wind
|
|
|
|
|
(lambda ()
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(set! saved-ports
|
|
|
|
|
(map (cut save-and-set-redirect env <>) redirs)))
|
2018-07-19 04:41:10 +01:00
|
|
|
|
thunk
|
|
|
|
|
(lambda ()
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(for-each restore-saved-port (reverse saved-ports))))))
|
2018-07-19 04:42:13 +01:00
|
|
|
|
|
|
|
|
|
|
2018-07-19 04:44:04 +01:00
|
|
|
|
;;; Subshells and command substitution.
|
2018-07-19 04:42:13 +01:00
|
|
|
|
|
2018-07-19 04:44:04 +01:00
|
|
|
|
(define* (%subshell thunk)
|
|
|
|
|
"Run @var{thunk} in a new process and return the ID of the new
|
|
|
|
|
process."
|
2018-11-15 16:07:45 +00:00
|
|
|
|
;; We need to flush all ports before forking to avoid copying the
|
|
|
|
|
;; port buffers into the child process, which could lead to
|
|
|
|
|
;; duplicate output.
|
|
|
|
|
(flush-all-ports)
|
2018-07-19 04:42:13 +01:00
|
|
|
|
(match (primitive-fork)
|
|
|
|
|
(0 (thunk)
|
|
|
|
|
(primitive-exit))
|
2018-07-19 04:44:04 +01:00
|
|
|
|
(pid pid)))
|
|
|
|
|
|
|
|
|
|
(define (sh:subshell env thunk)
|
|
|
|
|
"Run @var{thunk} in a subshell environment."
|
|
|
|
|
(match-let* ((pid (%subshell thunk))
|
|
|
|
|
((pid . status) (waitpid pid)))
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(set-environment-status! env (status:exit-val status))))
|
2018-07-19 04:44:04 +01:00
|
|
|
|
|
|
|
|
|
(define (sh:substitute-command env thunk)
|
|
|
|
|
"Run @var{thunk} in a subshell environment and return its output as
|
|
|
|
|
a string."
|
|
|
|
|
(match-let* (((sink . source) (pipe))
|
|
|
|
|
(thunk* (lambda ()
|
|
|
|
|
(close-port sink)
|
2018-10-15 14:43:19 +01:00
|
|
|
|
(let ((in (open-file "/dev/null" "r"))
|
|
|
|
|
(err (open-file "/dev/null" "w")))
|
|
|
|
|
(parameterize ((current-input-port in)
|
|
|
|
|
(current-output-port source)
|
|
|
|
|
(current-error-port err))
|
|
|
|
|
(thunk)))))
|
2018-07-19 04:44:04 +01:00
|
|
|
|
(pid (%subshell thunk*)))
|
|
|
|
|
(close-port source)
|
|
|
|
|
(match-let ((result (string-trim-right (get-string-all sink) #\newline))
|
|
|
|
|
((pid . status) (waitpid pid)))
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(set-environment-status! env (status:exit-val status))
|
2018-07-19 04:44:04 +01:00
|
|
|
|
result)))
|
2018-10-16 18:48:52 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Pipelines.
|
|
|
|
|
|
|
|
|
|
(define (swap-and-shift-pairs pairs)
|
|
|
|
|
"Swap and shift @var{pairs} over by one. For example, the list
|
|
|
|
|
@code{((a . b) (c . d))} becomes @code{((#f . b) (a . d) (c . #f))}"
|
|
|
|
|
(let ((kons (lambda (pair acc)
|
|
|
|
|
(match-let (((a . b) pair))
|
|
|
|
|
(match acc
|
|
|
|
|
((head . rest) `(,b (,a . ,head) ,@rest))
|
|
|
|
|
(() `(,b (,a . #f))))))))
|
|
|
|
|
(match (fold-right kons '() pairs)
|
|
|
|
|
((head . rest) `((#f . ,head) ,@rest))
|
|
|
|
|
(() '()))))
|
|
|
|
|
|
|
|
|
|
(define (make-pipes xs)
|
|
|
|
|
"Cons each element of @var{xs} to a pair of ports such that the first
|
|
|
|
|
port is an input port connected to the second port of the previous
|
|
|
|
|
element's pair, and the second port is an output port connected to the
|
|
|
|
|
first port of next element's pair. The first pair will have @code{#f}
|
|
|
|
|
for an input port and the last will have @code{#f} as an output port."
|
|
|
|
|
(match xs
|
|
|
|
|
(() '())
|
|
|
|
|
((x) `((,x . (#f . #f))))
|
|
|
|
|
(_ (let ((pipes (map (lambda (x) (pipe)) (cdr xs))))
|
|
|
|
|
(map cons xs (swap-and-shift-pairs pipes))))))
|
|
|
|
|
|
|
|
|
|
(define (plumb env in out thunk)
|
|
|
|
|
"Run @var{thunk} in a new process with @code{current-input-port} set
|
|
|
|
|
to @var{in} and @code{current-output-port} set to @var{out}. If
|
|
|
|
|
@var{in} or @var{out} is @code{#f}, the corresponding ``current'' port
|
|
|
|
|
is left unchanged."
|
|
|
|
|
(let* ((thunk* (lambda ()
|
|
|
|
|
(let ((in (or in (current-input-port)))
|
|
|
|
|
(out (or out (current-output-port))))
|
|
|
|
|
(parameterize ((current-input-port in)
|
|
|
|
|
(current-output-port out))
|
|
|
|
|
(thunk)))))
|
|
|
|
|
(pid (%subshell thunk*)))
|
|
|
|
|
(when in (close-port in))
|
|
|
|
|
(when out (close-port out))
|
|
|
|
|
pid))
|
|
|
|
|
|
|
|
|
|
(define (sh:pipeline env . thunks)
|
|
|
|
|
"Run each thunk in @var{thunks} in its own process with the output
|
|
|
|
|
of each thunk sent to the input of the next thunk."
|
|
|
|
|
(let ((pids (map (match-lambda
|
|
|
|
|
((thunk . (source . sink))
|
|
|
|
|
(plumb env source sink thunk)))
|
|
|
|
|
(make-pipes thunks))))
|
|
|
|
|
(unless (null? pids)
|
|
|
|
|
(match-let* ((pid (last pids))
|
|
|
|
|
((pid . status) (waitpid pid)))
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(set-environment-status! env (status:exit-val status))))))
|
2018-10-16 19:14:15 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Boolean expressions.
|
|
|
|
|
|
|
|
|
|
(define (sh:and env thunk1 thunk2)
|
|
|
|
|
"Run @var{thunk1} then, if the @code{$?} variable is zero in @var{env},
|
|
|
|
|
run @var{thunk2}."
|
|
|
|
|
(thunk1)
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(when (= (environment-status env) 0)
|
2018-10-16 19:14:15 +01:00
|
|
|
|
(thunk2)))
|
|
|
|
|
|
|
|
|
|
(define (sh:or env thunk1 thunk2)
|
|
|
|
|
"Run @var{thunk1} then, if the @code{$?} variable is nonzero in
|
|
|
|
|
@var{env}, run @var{thunk2}."
|
|
|
|
|
(thunk1)
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(unless (= (environment-status env) 0)
|
2018-10-16 19:14:15 +01:00
|
|
|
|
(thunk2)))
|
|
|
|
|
|
|
|
|
|
(define (sh:not env thunk)
|
|
|
|
|
"Run @var{thunk} and then invert the @code{$?} variable in @var{env}."
|
|
|
|
|
(thunk)
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(let ((inverted-status (if (= (environment-status env) 0) 1 0)))
|
|
|
|
|
(set-environment-status! env inverted-status)))
|
2018-10-16 20:26:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Loops.
|
|
|
|
|
|
|
|
|
|
(define (sh:for env bindings thunk)
|
|
|
|
|
"Run @var{thunk} for each binding in @var{bindings}. The value of
|
|
|
|
|
@var{bindings} have the form @code{(@var{name} (@var{value} ...))}."
|
Add 'status' field to environment
Instead of using an environment variable named '?', we will use a
special environment field called 'status'. This lets us get rid of a
lot of number-string conversions (since an environment variable has to
have a string value).
* geesh/environment.scm (<environment>): Add a 'status' field.
(make-environment): Set it to 0 by default.
* geesh/repl.scm (run-repl): Use new field in place of '?' variable.
* geesh/shell.scm (exec-utility, sh:and, sh:exec-let, sh:for, sh:not,
sh:or, sh:pipeline, sh:subshell, sh:substitute-command): Ditto.
* geesh/word.scm (parameter-ref): New function that handles both
special parameters (e.g., '?') and variables.
(parameter-ref*): Like 'var-ref*', but for 'parameter-ref'.
(word->qword): Replace 'var-ref' and 'var-ref*' with 'parameter-ref'
and 'parameter-ref*' respectively.
2018-11-09 02:29:19 +00:00
|
|
|
|
(set-environment-status! env 0)
|
2018-10-16 20:26:40 +01:00
|
|
|
|
(match-let (((name (values ...)) bindings))
|
|
|
|
|
(for-each (lambda (value)
|
|
|
|
|
(set-var! env name value)
|
|
|
|
|
(thunk))
|
|
|
|
|
values)))
|