Lisp读取程序可以读取这个以及如何读取?

时间:2016-01-03 18:56:23

标签: lisp grammar reader

我正在编写一个语法,我打算在Lisp读程序中实现,即一次从输入源读取一个表达式,即可变的。大多数语法就像Lisp一样,但两个相关的变化是:

读取空格并且是结果语法的一部分。连续的空格被分组在一起,就像连续的非空白字符被分组为标识符一样,读取这种字符串的结果是“空白对象”,它存储读取的字符的确切序列。当评估者出现在列表中时,它会忽略空格对象(换句话说,如果foo是一个空白对象,那么(eval '(+ 3 foo 4))等同于(eval '(+ 3 4))),如果要求它直接评估一个,则它是自我评价。

其次,如果除了空格标记之外的多个标记出现在同一行上,那么这些标记将被收集到一个列表中,该列表就是读取的结果。

如,

+ 3 4 5
(+ 3 4 5)
+ 3 4 (+ 1 4)
(+ 3 4 (+ 1 4))

都产生值12。

是否可以将此读取器实现为遵循读取过程的典型期望的Lisp读取过程?如果是这样,怎么样? (我很茫然。)

编辑:澄清空白:

如果我们说“空白对象”只是一个字符串并读取,那么read以下段:

(foo bar   baz)

生成一个语法对象,如:

'(foo " " bar "   " baz)

换句话说,标记之间的空格存储在结果语法对象中。

假设我编写了一个名为 - >的宏,它接受一个语法对象(方案样式宏),而whitespace?是一个识别空白语法对象的谓词

(define-macro (-> stx)
  (let* ((stxl (syntax-object->list stx))
         (obj (car stxl))
     (let proc ((res empty))
                (lst (cdr stxl)))
       (let ((method (car lst)))
          (if (whitespace? method)
              ; skip whitespace, recur immediately
              (proc res (cdr lst))
              ; Insert obj as the second element in method
              (let ((modified-method (cons (car method)
                                           (cons obj (cdr method)))))
                ; recur
                (proc (cons res modified-method) (cdr lst))))))))

1 个答案:

答案 0 :(得分:3)

阅读部分非常简单。您只需要一个空白区域测试,然后您的阅读功能将安装一个自定义阅读器字符宏,它可以检测空白并将连续的空白序列读入单个对象。首先是空白对象和空白对象;这些很简单:

(defparameter *whitespace*
  #(#\space #\tab #\return #\newline)
  "A vector of whitespace characters.")

(defun whitespace-p (char)
  "Returns true if CHAR is in *WHITESPACE*."
  (find char *whitespace* :test 'char=))

(defstruct whitespace-object
  characters)

现在是宏字符函数:

(defun whitespace-macro-char (stream char)
  "A macro character function that consumes characters from
stream (including CHAR), until a non-whitespace character (or end of
file) is encountered.  Returns a whitespace-object whose characters
slot contains a string of the whitespace characters."
  (let ((chars (loop for c = (peek-char nil stream nil #\a)
                  while (whitespace-p c)
                  collect (read-char stream))))
    (make-whitespace-object
     :characters (coerce (list* char chars) 'string))))

现在,读取功能与正常的读取具有相同的签名,但复制可读表,然后安装宏功能,并调用读取。返回读取的结果,并恢复可读表:

(defun xread (&optional (stream *standard-input*) (eof-error-p t) eof-value recursive-p)
  "Like READ, but called with *READTABLE* bound to a readtable in
which each whitespace characters (that is, each character in
*WHITESPACE*) is a macro characters whose macro function is
WHITESPACE-MACRO-CHAR."
  (let ((rt (copy-readtable)))
    (map nil (lambda (wchar)
               (set-macro-character wchar #'whitespace-macro-char))
         *whitespace*)
    (unwind-protect (read stream eof-error-p eof-value recursive-p)
      (setf *readtable* rt))))

示例:

(with-input-from-string (in "(+ 1    2  (* 3    
                                         4))")
  (xread in))

(+ #S(WHITESPACE-OBJECT :CHARACTERS " ") 1
   #S(WHITESPACE-OBJECT :CHARACTERS "    ") 2
   #S(WHITESPACE-OBJECT :CHARACTERS "  ")
   (* #S(WHITESPACE-OBJECT :CHARACTERS " ") 3
      #S(WHITESPACE-OBJECT
         :CHARACTERS "  
                                         ")
      4))

现在,要实现所需的 eval 对应项,您需要能够从列表中删除空白对象。这不是太难,我们可以为我们编写一个稍微更通用的实用函数:

(defun remove-element-if (predicate tree)
  "Returns a new tree like TREE, but which contains no elements in an
element position which ssatisfy PREDICATE.  An element is in element
position if it is the car of some cons cell in TREE."
  (if (not (consp tree))
      tree
      (if (funcall predicate (car tree))
          (remove-element-if predicate (cdr tree))
          (cons (remove-element-if predicate (car tree))
                (remove-element-if predicate (cdr tree))))))
CL-USER> (remove-element-if (lambda (x) (and (numberp x) (evenp x))) '(+ 1 2 3 4))
(+ 1 3)
CL-USER> (with-input-from-string (in "(+ 1  2 (* 3
                                                 4))")
           (remove-element-if 'whitespace-object-p (xread in)))
(+ 1 2 (* 3 4))

所以现在评估函数是 eval

的简单包装器
(defun xeval (form)
  (eval (remove-element-if 'whitespace-object-p form)))
CL-USER> (with-input-from-string (in "(+ 1  2 (* 3
                                                 4))")
           (xeval (xread in)))
15

让我们确保独立的空白对象仍然按预期显示:

CL-USER> (with-input-from-string (in "       ")
           (let* ((exp (xread in))
                  (val (xeval exp)))
             (values exp val)))
#S(WHITESPACE-OBJECT :CHARACTERS "       ")
#S(WHITESPACE-OBJECT :CHARACTERS "       ")