在评估中进行小的修正来扩展 Lisp 的最简单方法是什么?
Which is the easiest way to extend a Lisp with a small correction in the evaluation?
我想尝试将一些 Lisp(Scheme、Racket、Clojure 等)扩展到 运行 外部命令,如下所示:
; having
(define foo ...)
(define bar ...)
; on command
(ls (foo bar) baz)
; this lisp should evaluate (foo bar) as usual, with result "foobar", then
(ls foobar baz)
; here "ls" is not defined
; instead of rising "undefined identifier" exception
; it must look for "ls" command in the directories
; in the "PATH" environment variable
; and launch the first found "ls" command
; with strings "foobar" and "baz" on input
无论如何我只想运行它,而不进行从 lisp 的数据结构到字符串的正确转换或处理退出代码和 stdout/stderr
中命令的输出。
我认为没有办法在正常环境中扩展它(比如一直捕获 "undefined" 异常)。 eval
必须更改解释器本身的过程。
哪个 Lisp 最适合这样扩展它,它是如何完成的?也许已经存在执行类似操作的项目?
Common Lisp 有一个标准错误系统,可以用来实现它。
在 Common Lisp 实现中,为 undefined-function
.
类型的错误提供 use-value
或 store-value
重启
例子
CL-USER 69 > (flet ((call-use-value-restart (c)
(use-value (lambda (arg)
(format t "~%dummy function with arg ~a~%" arg))
c)))
(handler-bind ((undefined-function #'call-use-value-restart))
(this-function-does-not-exist "foo")))
dummy function with arg foo
NIL
在上面的示例中,函数 this-function-does-not-exist
不存在。如您所见,错误已得到处理,而是调用了另一个函数,然后该函数进行了一些输出。
如果我们自己调用未定义的函数,我们会得到一个错误:
CL-USER 70 > (this-function-does-not-exist "foo")
Error: Undefined operator THIS-FUNCTION-DOES-NOT-EXIST in form (THIS-FUNCTION-DOES-NOT-EXIST "foo").
1 (continue) Try invoking THIS-FUNCTION-DOES-NOT-EXIST again.
2 Return some values from the form (THIS-FUNCTION-DOES-NOT-EXIST "foo").
3 Try invoking something other than THIS-FUNCTION-DOES-NOT-EXIST with the same arguments.
4 Set the symbol-function of THIS-FUNCTION-DOES-NOT-EXIST to another function.
5 Set the macro-function of THIS-FUNCTION-DOES-NOT-EXIST to another function.
6 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 71 : 1 >
我们的示例基本上以编程方式调用重启编号 3:
它绑定一个处理程序,当发生 undefined-function
类型的错误时调用函数 call-use-value-restart
。
函数 call-use-value-restart
然后使用它提供的函数调用 use-value
重新启动。在这里,您可以提供一个函数,该函数调用由 (cell-error-name c)
给出的名称的外部程序。 use-value
重启然后只调用提供的函数并像往常一样继续执行程序。
解决方案提示
通常情况下,人们会在提供此类处理程序的地方编写一个小的顶层循环。
调用重启的另一种方式
在此示例中,我们使用 hook 添加处理程序以防发生错误。这里我们使用全局变量*debugger-hook*
。这应该是一个函数,在我们的例子中,当条件 c
的类型为 undefined-function
.
时,它会调用一个新函数
* (defun provide-a-function-hook (c hook)
(declare (ignore hook))
(typecase c
(undefined-function (use-value (lambda (arg)
(format t "~%dummy function with arg ~a~%" arg))
c))))
PROVIDE-A-FUNCTION-HOOK
* (setf *debugger-hook* #'provide-a-function-hook)
#<FUNCTION PROVIDE-A-FUNCTION-HOOK>
* (this-function-does-not-exist "foo")
; in: THIS-FUNCTION-DOES-NOT-EXIST "foo"
; (THIS-FUNCTION-DOES-NOT-EXIST "foo")
;
; caught STYLE-WARNING:
; undefined function: THIS-FUNCTION-DOES-NOT-EXIST
;
; compilation unit finished
; Undefined function:
; THIS-FUNCTION-DOES-NOT-EXIST
; caught 1 STYLE-WARNING condition
dummy function with arg foo
NIL
在球拍中你可以覆盖 #%top
:
#lang racket
(provide
(combine-out
(except-out (all-from-out racket) #%top)
(rename-out [shell-curry #%top])))
(require racket/system)
(define (stringify a)
(~a (if (cmd? a) (cmd-name a) a)))
(struct cmd (name proc)
#:property prop:procedure
(struct-field-index proc)
#:transparent
#:methods gen:custom-write
[(define (write-proc x port mode)
(display (string-append "#<cmd:" (stringify x) ">") port))])
(define (shell name)
(define (cmd-proxy . args)
(define cmd
(string-join (map stringify (cons name args))
" "))
(system cmd))
cmd-proxy)
(define-syntax shell-curry
(syntax-rules ()
((_ . id)
(cmd 'id (shell 'id)))))
将其另存为 shell.rkt 并将其 runner.rkt 放在同一目录中:
#lang s-exp "shell.rkt"
(define test (list /bin/ls /usr/bin/file))
(second test) ; ==> #<cmd:/usr/bin/file>
(first test) ; ==> #<cmd:/bin/ls>
((second test) (first test))
; ==> t (prints that /bin/ls is an executable on my system)
现在从这里将它变成 #lang myshell
或类似的东西非常容易。
我想尝试将一些 Lisp(Scheme、Racket、Clojure 等)扩展到 运行 外部命令,如下所示:
; having
(define foo ...)
(define bar ...)
; on command
(ls (foo bar) baz)
; this lisp should evaluate (foo bar) as usual, with result "foobar", then
(ls foobar baz)
; here "ls" is not defined
; instead of rising "undefined identifier" exception
; it must look for "ls" command in the directories
; in the "PATH" environment variable
; and launch the first found "ls" command
; with strings "foobar" and "baz" on input
无论如何我只想运行它,而不进行从 lisp 的数据结构到字符串的正确转换或处理退出代码和 stdout/stderr
中命令的输出。
我认为没有办法在正常环境中扩展它(比如一直捕获 "undefined" 异常)。 eval
必须更改解释器本身的过程。
哪个 Lisp 最适合这样扩展它,它是如何完成的?也许已经存在执行类似操作的项目?
Common Lisp 有一个标准错误系统,可以用来实现它。
在 Common Lisp 实现中,为 undefined-function
.
use-value
或 store-value
重启
例子
CL-USER 69 > (flet ((call-use-value-restart (c)
(use-value (lambda (arg)
(format t "~%dummy function with arg ~a~%" arg))
c)))
(handler-bind ((undefined-function #'call-use-value-restart))
(this-function-does-not-exist "foo")))
dummy function with arg foo
NIL
在上面的示例中,函数 this-function-does-not-exist
不存在。如您所见,错误已得到处理,而是调用了另一个函数,然后该函数进行了一些输出。
如果我们自己调用未定义的函数,我们会得到一个错误:
CL-USER 70 > (this-function-does-not-exist "foo")
Error: Undefined operator THIS-FUNCTION-DOES-NOT-EXIST in form (THIS-FUNCTION-DOES-NOT-EXIST "foo").
1 (continue) Try invoking THIS-FUNCTION-DOES-NOT-EXIST again.
2 Return some values from the form (THIS-FUNCTION-DOES-NOT-EXIST "foo").
3 Try invoking something other than THIS-FUNCTION-DOES-NOT-EXIST with the same arguments.
4 Set the symbol-function of THIS-FUNCTION-DOES-NOT-EXIST to another function.
5 Set the macro-function of THIS-FUNCTION-DOES-NOT-EXIST to another function.
6 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 71 : 1 >
我们的示例基本上以编程方式调用重启编号 3:
它绑定一个处理程序,当发生 undefined-function
类型的错误时调用函数 call-use-value-restart
。
函数 call-use-value-restart
然后使用它提供的函数调用 use-value
重新启动。在这里,您可以提供一个函数,该函数调用由 (cell-error-name c)
给出的名称的外部程序。 use-value
重启然后只调用提供的函数并像往常一样继续执行程序。
解决方案提示
通常情况下,人们会在提供此类处理程序的地方编写一个小的顶层循环。
调用重启的另一种方式
在此示例中,我们使用 hook 添加处理程序以防发生错误。这里我们使用全局变量*debugger-hook*
。这应该是一个函数,在我们的例子中,当条件 c
的类型为 undefined-function
.
* (defun provide-a-function-hook (c hook)
(declare (ignore hook))
(typecase c
(undefined-function (use-value (lambda (arg)
(format t "~%dummy function with arg ~a~%" arg))
c))))
PROVIDE-A-FUNCTION-HOOK
* (setf *debugger-hook* #'provide-a-function-hook)
#<FUNCTION PROVIDE-A-FUNCTION-HOOK>
* (this-function-does-not-exist "foo")
; in: THIS-FUNCTION-DOES-NOT-EXIST "foo"
; (THIS-FUNCTION-DOES-NOT-EXIST "foo")
;
; caught STYLE-WARNING:
; undefined function: THIS-FUNCTION-DOES-NOT-EXIST
;
; compilation unit finished
; Undefined function:
; THIS-FUNCTION-DOES-NOT-EXIST
; caught 1 STYLE-WARNING condition
dummy function with arg foo
NIL
在球拍中你可以覆盖 #%top
:
#lang racket
(provide
(combine-out
(except-out (all-from-out racket) #%top)
(rename-out [shell-curry #%top])))
(require racket/system)
(define (stringify a)
(~a (if (cmd? a) (cmd-name a) a)))
(struct cmd (name proc)
#:property prop:procedure
(struct-field-index proc)
#:transparent
#:methods gen:custom-write
[(define (write-proc x port mode)
(display (string-append "#<cmd:" (stringify x) ">") port))])
(define (shell name)
(define (cmd-proxy . args)
(define cmd
(string-join (map stringify (cons name args))
" "))
(system cmd))
cmd-proxy)
(define-syntax shell-curry
(syntax-rules ()
((_ . id)
(cmd 'id (shell 'id)))))
将其另存为 shell.rkt 并将其 runner.rkt 放在同一目录中:
#lang s-exp "shell.rkt"
(define test (list /bin/ls /usr/bin/file))
(second test) ; ==> #<cmd:/usr/bin/file>
(first test) ; ==> #<cmd:/bin/ls>
((second test) (first test))
; ==> t (prints that /bin/ls is an executable on my system)
现在从这里将它变成 #lang myshell
或类似的东西非常容易。