Какой самый простой способ расширить Lisp с небольшой коррекцией в оценке? - PullRequest
0 голосов
/ 08 февраля 2019

Я хотел бы попытаться расширить какой-нибудь Лисп (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.

Я думаю, что нет способа расширить его в обычной среде (например, перехватывать неопределенное исключение все время).eval Процедура самого интерпретатора должна быть изменена.

Какой Лисп лучше всего расширить таким образом и как это сделать?Может быть, уже существует проект, выполняющий нечто подобное?

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

В ракетке вы можете переопределить #%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 или что-то в этом роде довольно просто.

0 голосов
/ 08 февраля 2019

Common Lisp имеет стандартную систему ошибок, которую можно использовать для реализации этого.

В реализациях Common Lisp, которые предоставляют перезапуск use-value или store-value для ошибок типа undefined-function.

Пример

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:

Он связывает обработчик, который вызывает функцию call-use-value-restart, когда происходит ошибка типа undefined-function.

Функция 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
...