S表达式并跟踪源位置

时间:2019-04-10 11:45:48

标签: lisp common-lisp

Lisp s表达式是一种将代码表示为抽象语法树的简洁灵活的方法。但是,相对于其他语言的编译器使用的更专业的数据结构,它们具有一个缺点:难以跟踪与代码中任何特定点相对应的文件和行号。至少有一些Lisps最终只是解决问题。发生错误时,它们仅报告源位置的函数名称,而不报告文件和行号。

Scheme的某些方言通过不使用普通的con单元,而是使用语法对象来表示代码,该对象与con单元是同构的,但是还可以携带其他信息,例如源位置。 / p>

Common Lisp的任何实现都解决了此问题吗?如果可以,怎么办?

2 个答案:

答案 0 :(得分:3)

Common Lisp标准几乎没有说明这些事情。例如,它提到函数ed可以采用函数名,然后使用相应的源代码打开编辑器。但是没有指定机制,此功能完全由开发环境提供,可能与Lisp系统结合使用。

一种典型的处理方法是编译文件,编译器将记录所定义对象(函数,变量,类等)的源位置。例如,源位置可以放在符号的属性列表(定义的事物的名称)上,或记录在其他位置。同样,作为列表结构的实际源代码也可以与Lisp符号关联。请参见函数FUNCTION-LAMBDA-EXPRESSION

某些实现对源位置进行更复杂的记录。例如,LispWorks可以定位当前执行的功能的特定部分。它还指出定义何时来自编辑器或侦听器。参见Dspecs: Tools for Handling Definitions。然后,调试器可以例如定位某个堆栈帧的代码在源中的位置。

SBCL还具有locate source code的功能。

还请注意,Common Lisp中的实际“源代码”并不总是文本文件,而是读取的s表达式。 evalcompile-两个标准函数-不要将字符串或文件名作为参数。他们使用实际的表达式:

CL-USER 26 > (compile 'foo (lambda (x) (1+ x)))
FOO
NIL
NIL

CL-USER 27 > (foo 41)
42

作为代码的S表达式未绑定到任何特定的文本格式。可以通过漂亮的打印机功能pprint重新设置格式,并且可能会考虑可用宽度以生成布局。

因此,注意结构可能有用,而记录源代码行则没那么有用。

答案 1 :(得分:2)

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

方案

False

例如:

(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))

丰富的树表示语法对象。

Common-Lisp

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

测试:

(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)))))))

返回两个值:

(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)))

给出一个cons单元格,您可以追溯其原始位置。您可以根据需要添加更精确的信息。例如,一旦评估了(0 (1 2 3) 4 5 (6 7)) #<HASH-TABLE :TEST EQL :COUNT 3 {1010524CD3}> ,就可以将源信息附加到函数对象或作为符号属性,这意味着该信息在重新定义时被垃圾回收。

备注

请注意,在两种情况下都没有可跟踪的源文件,除非系统能够追溯到调用读取器的源文件中的原始字符串。