更改lisp中数字的表示形式?

更改lisp中数字的表示形式?,lisp,Lisp,我是lisp的新手,我试图理解如何通过编程改变(常见)lisp代码的行为 我想到了一个简单的(但完全是学术性的)例子,我试图解决源文本中某些数字被替换的问题。我知道使用可以操纵s表达式的宏,但我希望使用全局适用的宏。因此,无论数字4出现在何处(例如),都使用值5。因此,(+41)将被评估为6,44将评估为55 澄清一下,这是一种极其愚蠢、简单的方法,任何字符4都被视为程序员实际上键入了5 原则上,这可以扩展到每一个数字(0到9),并赋予它们另一个“意义”,但我对实现不太感兴趣,因为我对这类转换的

我是lisp的新手,我试图理解如何通过编程改变(常见)lisp代码的行为

我想到了一个简单的(但完全是学术性的)例子,我试图解决源文本中某些数字被替换的问题。我知道使用可以操纵s表达式的宏,但我希望使用全局适用的宏。因此,无论数字4出现在何处(例如),都使用值5。因此,(+41)将被评估为6,44将评估为55

澄清一下,这是一种极其愚蠢、简单的方法,任何字符4都被视为程序员实际上键入了5


原则上,这可以扩展到每一个数字(0到9),并赋予它们另一个“意义”,但我对实现不太感兴趣,因为我对这类转换的示例感兴趣。

这是一种可怕的方法来实现你想要的。与处理Lisp读取器不同,Lisp读取器(出于显而易见的原因)实际上并不打算处理这类事情,因为在现实生活中没有人会想这样做,而是将此代码介入I/O系统,以定义支持重新映射字符的流类,重新映射的默认字符为十进制数字

这不是一个可移植的CL:它依赖于一些没有被纳入CL标准的行为,称为。许多(也许所有)实现实际上都支持这一建议,但在实现方式上存在一些不可避免的变化。有一个兼容性层,称为,它处理所有这些

我没有在这里使用兼容层:我只是定义了一个条件化包,它在我使用的两个实现中工作。更重要的是,我没有实际检查这段代码是否在它定义的类上定义了足够的方法,以使它真正正常工作:我强烈地怀疑它没有。它确实定义了足够的方法,使其能够充分发挥作用:

> (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)
所以,正如您所看到的,这导致从这样一个流中读取的任何东西都有这个数字重写的东西。例如,您可以在此基础上构建这样一个mad 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"
在使用可读表处理事情的版本中,这不会是一个问题

请注意,我希望任何支持用户介入I/O机制的语言都能做到这样,我希望最合理的现代语言都能做到这一点。CL(以及Lisp)的不同寻常之处在于,语言本身是根据

  • 读取器,它获取流并返回Lisp对象(其行为可合理定制)
  • 一个或两个
    • 一种求值器,它接受Lisp对象(不仅仅是字符串或文件),并将其视为要求值的代码
    • 一个编译器,它接受表示定义的Lisp对象(同样,不只是字符串或文件)并编译它们
  • 知道如何打印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))

这里有一个可怕的方法来做你想做的事。与处理Lisp读取器不同,Lisp读取器(出于显而易见的原因)实际上并不打算处理这类事情,因为在现实生活中没有人会想这样做,而是将此代码介入I/O系统,以定义支持重新映射字符的流类,重新映射的默认字符为十进制数字

这不是一个可移植的CL:它依赖于一些没有被纳入CL标准的行为,称为。许多(也许所有)实现实际上都支持这一建议,但在实现方式上存在一些不可避免的变化。有一个兼容性层,称为,它处理所有这些

我没有在这里使用兼容层:我只是定义了一个条件化包,它在我使用的两个实现中工作。更重要的是,我没有实际检查这段代码是否在它定义的类上定义了足够的方法,以使它真正正常工作:我强烈地怀疑它没有。它确实定义了足够的方法,使其能够充分发挥作用:

> (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)
所以,正如您所看到的,这导致从这样一个流中读取的任何东西都有这个数字重写的东西。例如,您可以在此基础上构建这样一个mad 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"
在使用可读表处理事情的版本中,这不会是一个问题

请注意,我希望任何支持用户介入I/O机制的语言都能做到这样,我希望最合理的现代语言都能做到这一点。CL(以及Lisp)的不同寻常之处在于,语言本身是根据

  • 读取器,它获取流并返回Lisp对象(其行为可合理定制)
  • 一个或两个
    • 一种求值器,它接受Lisp对象(不仅仅是字符串或文件),并将其视为要求值的代码
    • 一个编译器,它接受表示定义的Lisp对象(同样,不只是字符串或文件)并编译它们
  • 知道如何打印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))
对于l