S-表达式和源代码位置跟踪

4
Lisp的s表达式是以抽象语法树的形式表示代码的简洁灵活方式。然而,与编译其他语言所使用的更为专业的数据结构相比,它们有一个缺点:难以跟踪文件和行号与代码中特定位置的对应关系。至少某些Lisp最终只能放弃这个问题;在出现错误时,它们仅报告函数名称作为源位置,而不是文件和行号。
Scheme的某些方言通过使用“语法对象”而不是普通的cons单元来表示代码来解决了这个问题,后者与cons单元同构,但可以携带额外的信息,如源位置。
Common Lisp的任何实现是否解决了这个问题?如果是,是如何解决的?
2个回答

3
通用Lisp标准对这些事情说得很少。例如,它提到函数ed可以接受一个“函数名称”,然后打开带有相应源代码的编辑器。但是,没有指定机制,该功能完全由开发环境提供,可能与Lisp系统结合使用。
解决这个问题的典型方法是编译文件,编译器将记录定义对象(函数、变量、类等)的源位置。例如,源位置可以放在符号(定义对象的名称)的属性列表中,或者记录在其他地方。实际源代码还可以作为一个列表结构与Lisp符号关联起来。请参见函数FUNCTION-LAMBDA-EXPRESSION
一些实现会进行更复杂的源位置记录。例如,LispWorks可以定位当前执行的函数的特定部分。它还注意到定义是来自编辑器还是Listener。请参见Dspecs: Tools for Handling Definitions。调试器可以定位某个堆栈帧的代码在源中的位置。
SBCL还具有定位源代码的功能。
还要注意的是,在通用Lisp中,实际的“源代码”不总是文本文件,而是读取的s表达式。 evalcompile这两个标准函数不接受字符串或文件名作为参数。它们使用实际的表达式。
CL-USER 26 > (compile 'foo (lambda (x) (1+ x)))
FOO
NIL
NIL

CL-USER 27 > (foo 41)
42

S表达式作为代码并不会绑定到任何特定的文本格式。它们可以通过漂亮的打印函数pprint重新格式化,这可以考虑可用宽度以生成布局。

因此,注意结构可能很有用,记录源行可能不太有用。


2

我的理解是,Scheme存储在AST中的任何数据都可以与CL环境中的表达式相关联。

Scheme

(defun my-simple-scheme-reader (stream)
  (let ((char (read-char stream)))
    (or (position char "0123456789")
        (and (member char '(#\newline #\space #\tab)) :space)
        (case char
          (#\) :closing-paren)
          (#\( (loop
                  with beg = (file-position stream)
                  for x = (my-simple-scheme-reader stream)
                  until (eq x :closing-paren)
                  unless (eq x :space)
                    collect x into items
                  finally (return (list :beg beg
                                        :end (file-position stream)
                                        :items items))))))))

例如:

(with-input-from-string (in "(0(1 2 3) 4 5 (6 7))")
  (my-simple-scheme-reader in))

returns:

(:BEG 1 :END 20 :ITEMS
 (0 (:BEG 3 :END 9 :ITEMS (1 2 3)) 4 5 (:BEG 15 :END 19 :ITEMS (6 7))))

这个丰富的树表示语法对象。

Common-Lisp

(defun make-environment ()
  (make-hash-table :test #'eq))

(defun my-simple-lisp-reader (stream environment)
  (let ((char (read-char stream)))
    (or (position char "0123456789")
        (and (member char '(#\newline #\space #\tab)) :space)
        (case char
          (#\) :closing-paren)
          (#\( (loop
                  with beg = (file-position stream)
                  for x = (my-simple-lisp-reader stream environment)
                  until (eq x :closing-paren)
                  unless (eq x :space)
                    collect x into items
                  finally
                    (setf (gethash items environment)
                          (list :beg beg :end (file-position stream)))
                    (return items)))))))

Test:

(let ((env (make-environment)))
  (with-input-from-string (in "(0(1 2 3) 4 5 (6 7))")
    (values
     (my-simple-lisp-reader in env)
     env)))

返回两个值:

(0 (1 2 3) 4 5 (6 7))
#<HASH-TABLE :TEST EQL :COUNT 3 {1010524CD3}>

给定一个cons单元,您可以追踪其最初的位置。如果您想要添加更精确的信息,可以这样做。例如,一旦您评估了一个defun,源信息就可以附加到函数对象或符号属性上,这意味着重新定义时该信息会被垃圾回收。
备注: 请注意,在这两种情况下都没有源文件可供跟踪,除非系统能够追溯到调用读取器的源文件中的原始字符串。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接