我是一个相对较新的Lisp使用者,试图了解如何以编程方式更改(通用)Lisp代码的行为。
我想到了一个简单(但完全是学术性的)示例,我试图解决替换源文本中某些数字的地方。我知道使用可以操纵s表达式的宏,但是想要在全局范围内应用的宏。因此,在出现数字4的任何地方(例如),都会使用值5。因此,(+ 4 1)的评估结果为6,而44的评估结果为55。
要澄清一下,这是一种极其愚蠢,简单的方法,其中任何字符4都被视为程序员实际上已经键入了5。
原则上,它可以扩展到每个数字(0到9),并为它们分配另一个“含义”,但是我对实现的兴趣不大,因为我以这种类型的转换为例
答案 0 :(得分:1)
这是做您想要的事情的可怕方法。而不是与Lisp阅读器打交道(由于显而易见的原因)实际上并不意味着要处理这种事情,因为没人愿意在现实生活中这样做,而是这段代码干预了I / O。系统中,定义一个支持重新映射字符的流类,要重新映射的默认字符为十进制数字。
这不是可移植的CL:它依赖于某些行为,这些行为并未使其成为CL标准,即'Gray streams'。实际上,许多(也许所有)实现都支持此提议,但是它们的实现方式不可避免地会有一些变化。有一个称为trivial-gray-streams
的兼容性层,可以处理所有这些问题。
我在这里没有使用该兼容性层:我只是定义了一个条件包,该包在我使用的两个实现中都有效。更重要的是,我实际上并未检查此代码是否在它定义的类上定义了足够的方法以使其真正正常工作:我非常怀疑它没有这样做。它确实定义了足够的方法使其能够正常工作:
> (compile-file "silly-remapping-stream" :load t)
#P"/Users/tfb/play/lisp/silly-remapping-stream.dx64fsl"
nil
nil
> (with-input-from-string (s "(print 123)")
(let ((srs (make-instance 'silly-rewriting-stream :parent s))
(read srs)))
(print 234)
因此,如您所见,这会使从此类流中读取的所有内容都具有这种数字重写的内容。例如,除此之外,您还可以构建像这样的疯狂REPL
(defun silly-rewriting-repl (&optional (stream *standard-input*))
(let ((srs (make-instance 'silly-remapping-stream :parent stream)))
(flet ((pread ()
(format t "~&?? ")
(force-output)
(read srs nil srs)))
(loop for f = (pread)
while (not (eq f srs))
do (pprint (eval f))))))
然后
> (silly-rewriting-repl)
?? (defvar *three* 3)
*three*
?? *three*
4
以此类推。
这不是正确方法的一个原因是,在上述REPL中:
?? "a string containing 3"
"a string containing 4"
在使用readtable处理内容的版本中这不是问题。
请注意,我希望支持用户干预I / O的机制的任何语言都可以做到这一点,并且我希望大多数合理的现代语言都可以做到这一点。 CL(通常和Lisp)与众不同之处在于,语言本身是根据
定义的(注意:如果有编译器,那么就可以用它来编写评估器,因此有可能只有Lisp实现而只包含评估器或只有编译器,或两者兼有。)
这是代码。
;;;; A silly remapping stream
;;;
;;; This uses just enough of Gray streams to define a stream which
;;; remaps digits in an amusing way.
;;;
;;; ALMOST CERTAINLY other methods need to be defined for this stream
;;; class to be legitimate. THIS CODE IS NOT SUITABLE FOR REAL USE
;;;
;;; This code has been 'tested' (as in: I checked READ did what I
;;; thought it should) in LW 7.1.1 and the development version of CCL.
;;; Other implementations will need changes to the package definition
;;; below, or (much better) to use a compatibility layer such as
;;; trivial-gray-streams
;;; (https://github.com/trivial-gray-streams/trivial-gray-streams),
;;; which is available via Quicklisp.
;;;
(defpackage :org.tfeb.example.silly-remapping-stream
(:use :cl
#+LispWorks :stream
#+CCL :ccl)
(:export #:silly-remapping-stream))
(in-package :org.tfeb.example.silly-remapping-stream)
(defclass silly-remapping-stream (fundamental-character-input-stream)
((parent :initarg :parent
:reader srm-parent
:initform (error "no parent"))
(map :initarg :map
:reader srm-map
:initform '((#\1 . #\2)
(#\2 . #\3)
(#\3 . #\4)
(#\4 . #\5)
(#\5 . #\6)
(#\6 . #\7)
(#\7 . #\8)
(#\8 . #\9)
(#\9 . #\0)))))
(defmethod stream-read-char ((stream silly-remapping-stream))
(let ((got (stream-read-char (srm-parent stream))))
(typecase got
(character
(let ((mapped (assoc got (srm-map stream))))
(if mapped (cdr mapped) got)))
(t got))))
(defmacro define-srm-proxy-method (gf (s &rest other-args))
;; just a way of defining methods which forward to the parent stream
`(defmethod ,gf ((s silly-remapping-stream) ,@other-args)
(,gf (srm-parent ,s) ,@other-args)))
(define-srm-proxy-method stream-unread-char (s char))