3 ;; Copyright (C) 2014 Dmitry Bogatov <Dmitry Bogatov <KAction@gnu.org>>
5 ;; Author: Dmitry Bogatov <Dmitry Bogatov <KAction@gnu.org>>
7 ;; This program is free software; you can redistribute it and/or
8 ;; modify it under the terms of the GNU General Public License
9 ;; as published by the Free Software Foundation; either version 3
10 ;; of the License, or (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
24 (define-module (gnu bash)
25 #:export ((bash-eval . eval)))
26 (use-modules (system foreign))
27 (use-modules (system ffi))
28 (use-modules (ice-9 match))
29 (use-modules (ice-9 format))
30 (use-modules (ice-9 curried-definitions))
32 (define-syntax-rule (define-public-from-c-code name)
34 (define name (false-if-exception (@ (gnu bash internal) name)))
37 (define-public-from-c-code array->alist)
38 (define-public-from-c-code bind-dynamic-variable)
40 ;; Since interface, provided by (system ffi) contains too much details
41 ;; of Bash internals, we use this macro to provide more clean
44 ;; `define-override` works absolutely same as define*, but defines in body
45 ;; `this-proc` symbol, referring to previous value of procedure variable.
46 (define-syntax define-override
49 ((_ (name arg ...) stmt stmt* ...)
50 (with-syntax ((this-proc (datum->syntax x 'this-proc)))
52 (let ((this-proc name))
54 (lambda* (arg ...) stmt stmt* ...))))))))
56 (define* (pointer-increment ptr #:optional (offset (sizeof '*)))
57 (make-pointer (+ (pointer-address ptr) offset)))
59 ;; Flags is implementation detail, it is not actually useful for end-user,
60 ;; so I save code and maintenance not decoding them.
61 (define-ffi-struct alias: ((string: name) (string: value) (int: flags)))
63 ;; This is a bit tricky because of terminology fuss.
64 ;; Address is, well, pointer to pointer to struct alias.
65 ;; Pointer is, pointer to struct alias. See diagram, where
66 ;; arrow means pointer dereferencing.
68 ;; +-----------------+ +---------+ +----------------------+
69 ;; | address := argv | --*> | pointer | --*> + name | value | flags + (struct alias)
70 ;; +-----------------+ +---------+ +----------------------+
72 ;; +-----------------+ +---------+ +----------------------+
73 ;; | address | --*> | pointer | --*> + name | value | flags + (struct alias)
74 ;; +-----------------+ +---------+ +----------------------+
78 ;; +-----------------+ +---------+
79 ;; | address | --*> | NULL |
80 ;; +-----------------+ +---------+
81 (define (pointer->argv<alias> argv)
82 (if (null-pointer? argv)
84 (let loop ((address argv) (acc '()))
85 (let ((pointer (dereference-pointer address)))
86 (if (null-pointer? pointer)
88 (loop (pointer-increment address)
89 (cons (pointer->alias pointer) acc)))))))
91 (define-ffi-object argv<alias>:
92 #:decoder pointer->argv<alias>
95 (define-ffi all-aliases (-> #:alloc argv<alias>:))
96 (define-override (all-aliases)
97 (define (alias->pair a)
99 (($ <struct-alias> name value) (cons name value))))
100 (map alias->pair (this-proc)))
103 (define-ffi add-alias ((#:const string-like: name) (#:const string-like: value) -> void:))
104 (define-ffi get-alias-value ((#:const string-like: name) -> ?string:))
105 (export add-alias get-alias-value)
107 (define-ffi remove-alias ((#:const string-like: name) -> int:))
108 (define-override (remove-alias name)
109 (let ((successfully-removed? (not {(this-proc name) = -1})))
110 successfully-removed?))
112 (define-ffi delete-all-aliases (-> void:))
113 (define-ffi alias-expand ((#:const string-like: name) -> #:alloc string:))
114 (export alias-expand delete-all-aliases)
116 (define-extern (int: last-command-exit-value))
117 (define-syntax $? (identifier-syntax (last-command-exit-value)))
120 (define-ffi-mask eval-flags:
129 ;; It is unsafe, since at least following is true:
131 ;; * If errexit is set, it segfaults
133 ;; For safe version see bash-eval.
134 (define-ffi unsafe-evalstring ((#:const string: string) (#:const string: from) eval-flags: -> int:)
135 #:symbol "evalstring")
137 ;; Force `nofree` flag, or Bash will attempt to free what it should not.
138 (define-override (unsafe-evalstring string from flags)
139 (this-proc string from (cons 'nofree flags)))
141 (define (unsafe-format/eval . args)
142 (unsafe-evalstring (apply format #f args) "(gnu bash)" '(nohist)))
144 (define (enable-option-argument opt)
145 (format #f "-o ~a" opt))
146 (define (disable-option-argument opt)
147 (format #f "+o ~a" opt))
149 (define (make-restore-shellopts-thunk options)
150 (let* ((shellopts-string (get-string-value "SHELLOPTS"))
151 (shellopts (string-split shellopts-string #\:))
152 (shellopts-symbols (map string->symbol shellopts)))
153 (define (option->set-argument opt)
154 (if (member opt shellopts-symbols)
155 (enable-option-argument opt)
156 (disable-option-argument opt)))
158 (define saved-$? (last-command-exit-value))
159 (unsafe-format/eval "set ~{~a ~}" (map option->set-argument options))
160 (set! (last-command-exit-value) saved-$?))))
162 (define ((make-enable-shellopts-thunk options))
163 (unsafe-format/eval "set ~{-o ~a ~}" options))
165 (define ((make-disable-shellopts-thunk options))
166 (unsafe-format/eval "set ~{+o ~a ~}" options))
168 (define (call-with-shellopts options thunk)
170 (make-enable-shellopts-thunk options)
172 (make-restore-shellopts-thunk options)))
174 (define (call-without-shellopts options thunk)
176 (make-disable-shellopts-thunk options)
178 (make-restore-shellopts-thunk options)))
180 (define-syntax-rule (with-shellopts (opt opt* ...) stmt stmt* ...)
181 (call-with-shellopts '(opt opt* ...) (lambda () stmt stmt* ...)))
183 (define-syntax-rule (without-shellopts (opt opt* ...) stmt stmt* ...)
184 (call-without-shellopts '(opt opt* ...) (lambda () stmt stmt* ...)))
186 (export call-with-shellopts call-without-shellopts)
187 (export with-shellopts without-shellopts)
189 ;; Safe version of `unsafe-evalstring`.
190 (define (bash-eval str)
191 (without-shellopts (errexit)
192 (unsafe-evalstring str "(gnu bash)" '(nohist))))
194 (define-ffi-mask variable-flags:
195 ((exported #x0000001) ; export to environment
196 (readonly #x0000002) ; cannot change
197 (array #x0000004) ; value is an array
198 (function #x0000008) ; value is a function
199 (integer #x0000010) ; internal representation is int
200 (local #x0000020) ; variable is local to a function
201 (assoc #x0000040) ; variable is an associative array
202 (trace #x0000080) ; function is traced with DEBUG trap
203 (uppercase #x0000100) ; word converted to uppercase on assignment
204 (lowercase #x0000200) ; word converted to lowercase on assignment
205 (capcase #x0000400) ; word capitalized on assignment
206 (nameref #x0000800) ; word is a name reference
207 (invisible #x0001000) ; cannot see
208 (nounset #x0002000) ; cannot unset
209 (noassign #x0004000) ; assignment not allowed
210 (imported #x0008000) ; came from environment
211 (special #x0010000) ; requires special handling
212 (nofree #x0020000) ; do not free value on unset
213 (tempvar #x0100000) ; variable came from the temp environment
214 (propagate #x0200000))) ; propagate to previous scope
216 (define-ffi-struct variable:
219 ;; This field isn't used, so why waste effort on decoding it?
221 ;; These two fields are very delicate and worth several days of
222 ;; segfault debugging with no output, so they are handled in C.
225 (variable-flags: attributes)
228 (define-ffi get-string-value ((#:const string: var-name) -> ?string:))
229 (define-ffi find-variable ((#:const string: name) -> ?variable:))
230 (define-ffi bind-variable
231 ((#:const string: name) (#:const string: value) (variable-flags: flags)
233 (define-ffi bind-global-variable
234 ((#:const string: name) (#:const string: value) (variable-flags: flags)
236 (define-ffi quote-escapes ((#:const string: str) -> #:alloc string:))
239 (define quote-show (compose quote-escapes show))
241 (define (args->eval-string args)
242 (string-join (map quote-show args) " "))
244 (define (eval-args args)
245 (bash-eval (args->eval-string args)))
247 (define (capture-output args)
248 (define eval-str (args->eval-string args))
249 (bash-eval (format #f "SCM_OUTPUT=$(~a)" eval-str))
250 (unless (zero? (last-command-exit-value))
251 (error 'bash-command-error eval-str (last-command-exit-value)))
252 (get-string-value "SCM_OUTPUT"))
254 (define ($-hash-reader _unused port)
255 (define (skip-chars pred port)
256 (define current-char (read-char port))
257 (when (eof-object? current-char)
258 (error 'read-error 'premature-EOF))
259 (if (pred current-char)
260 (skip-chars pred port)
262 (define char-after (skip-chars char-whitespace? port))
264 ((#\?) '((@@ (gnu bash) last-command-exit-value)))
266 (unread-char char-after port)
267 (let ((sexp (read port)))
269 ((#\") `((@@ (gnu bash) bash-eval) ,sexp))
270 ((#\[) (list '(@@ (gnu bash) eval-args)
271 (list 'quasiquote sexp)))
272 ((#\() (list '(@@ (gnu bash) capture-output)
273 (list 'quasiquote sexp)))
274 (else (list '(@@ (gnu bash) $) (list 'quote sexp))))))))
276 (read-hash-extend #\$ $-hash-reader)
279 (get-string-value (show name)))
281 (define ($-set name value)
282 (bind-variable (show name) (show value) '()))
284 (define-public $ (make-procedure-with-setter $-ref $-set))
286 (define-public bind-scm-function
288 (define *funcs* (make-hash-table))
289 (define special-varname 'SCM_FUNCTIONS_MAGIC_VARIABLE)
290 (define (special-var-thunk)
291 (define cur-func-symbol (string->symbol ($ 'FUNCNAME)))
292 (define thunk (hashq-ref *funcs* cur-func-symbol))
295 (define retval (thunk))
296 (if (integer? retval)
297 (format #f "~a" retval)
300 (print-exception (current-error-port) #f key args)
303 (bind-dynamic-variable special-varname special-var-thunk)
304 ;; Problem is that in dynamic variable handler I have no access to
305 ;; dollar variables. Quick and dirty solution is just save $@ into
306 ;; some other array. Yeah, global variables are evil, but it saves
307 ;; load of code to maintain and debug.
308 (lambda (symbol thunk)
309 (hashq-set! *funcs* symbol thunk)
317 symbol special-varname))))
319 (export define-bash-function)
320 (define-syntax-rule (define-bash-function (fn-name arg ...) stmt stmt* ...)
322 (define (fn-name arg ...)
327 (define bash-args (map cdr (array->alist 'SCM_ARGS)))
329 ((arg ...) (fn-name arg ...))
331 'wrong-number-of-args
332 (symbol->string 'fn-name)
333 "scm-function called from Bash with args ~A failed to match signature ~A"
334 (list bash-args '(arg ...))