4

我试图更好地理解如何在不同的 lisp 中评估 S 表达式,并希望看到它们能够处理有趣的格式错误的表达式。我知道 Common Lisp 和 Scheme 是完全不同的语言,但是它们的语义是否存在特定差异来解释行为上的差异。例如,Lisp-1s 和 Lisp-2s 在行为上有明显的差异,卫生与非卫生宏观系统也是如此。

我有一个程序,其中包含在 Scheme 和 Common Lisp 中无法访问的格式错误的if表达式。

;; foo.scm
(if #t 1 (if))

(display "12")

和 Common Lisp 版本

;; foo.lisp
(if t 1 (if))

(display "12")

chicken并且guile都产生语法错误。

鸡:

% chicken foo.scm

Syntax error: (foo.scm:1) in `if' - pair expected

    (if)

    Expansion history:

    <syntax>      (##core#begin (if #t 1 (if)))
    <syntax>      (if #t 1 (if))
    <syntax>      (##core#if #t 1 (if))
    <syntax>      (if)  <--

诡计:

% guile foo.scm
...
.../foo.scm:1:9: source expression failed to match any pattern in form (if)

sbcl并且clisp都打印 12 并且不发出警告。

SBCL:

% sbcl --load foo.lisp
This is SBCL 1.3.11, an implementation of ANSI Common Lisp.
...
12
0]^D

CLISP

% clisp foo.lisp

"12"
4

2 回答 2

8

Common Lisp 的实现:解释器和编译器

在 Common Lisp 中,代码执行的类型取决于 Lisp 系统实现的内容以及您如何使用它。Common Lisp 实现通常有多种执行代码的方式:一个解释器和一个或多个编译器。即使是单个正在运行的实现也可能具有此功能,并且它将允许用户在它们之间切换。

  • 解释器:直接从 Lisp 数据结构中执行。它不是虚拟机代码的解释器,如 JVM。人们不会期望解释器在执行期间验证完整的代码树/图。解释器通常只查看它执行的当前顶层表单。

  • 编译器:将 Lisp 代码编译为 C、一些字节码或机器码。由于编译器在代码运行之前生成代码,它会对它看到的所有代码进行语法检查(对于可能的异常,请参阅底部的备注)。

  • 顶级评估:可以使用解释器、编译器或两者的混合。

GNU CLISP 既有解释器又有编译器

GNU CLISP 中的示例:

文本文件的 LOAD 通常使用解释器:

[1]> (load "test.lisp")
;; Loading file test.lisp ...
;; Loaded file test.lisp
T

解释器不会检测到错误,因为它不会检查整个表达式的语法正确性。由于从不使用带有语法错误的 else 子句,解释器永远不会查看它。

CLISP 还有一个编译器:

[2]> (compile-file "test.lisp")
;; Compiling file /tmp/test.lisp ...
** - Continuable Error
in #:|1 1 (IF T 1 ...)-1| in line 1 : Form too short, too few arguments: (IF)
If you continue (by typing 'continue'): Ignore the error and proceed
The following restarts are also available:
ABORT          :R1      Abort main loop

如您所见,CLISP 编译器检测到语法错误并给出明确的错误消息。

SBCL 使用编译器,但也有解释器

SBCL 默认使用编译器,它会检测错误。对于顶级表单,它对某些表单使用更简单的评估机制。也可以切换到口译员。

如果您在 SBCL 中编写一个简单的 IF 形式,评估器不会使用完整编译并且不会捕获错误:

CL-USER> (if t 1 (if))
1

如果您在函数定义中编写相同的代码,则会检测到错误,因为默认情况下将编译函数定义:

CL-USER> (defun foo () (if t 1 (if)))
; in: DEFUN FOO
;     (IF)
; 
; caught ERROR:
;   error while parsing arguments to special operator IF:
;     too few elements in
;       ()
;     to satisfy lambda list
;       (SB-C::TEST SB-C::THEN &OPTIONAL SB-C::ELSE):
;     between 2 and 3 expected, but got 0
; 
; compilation unit finished
;   caught 1 ERROR condition
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO

如果您切换到完整的 SBCL 解释器,则在定义时不会检测到错误:

CL-USER> (setf *evaluator-mode* :interpret)
:INTERPRET
CL-USER> (defun foo () (if t 1 (if)))
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO

优化和语法检查

请注意,某些优化编译器可能不会检查无法访问代码的语法。

概括

在大多数 Common Lisp 实现中,您需要使用编译器来获取完整的语法警告/错误。

于 2016-11-04T05:48:12.017 回答
3

看起来 CL 部分由 Rainer 很好地处理。我只想指出,R5RS 不需要像您的两个实现那样行事。

R5RS 的定义非常低,因为if它只指定正确使用时要做什么,我们有这段关于错误处理的文章:

当谈到错误情况时,该报告使用短语“发出错误信号”来表示实现必须检测并报告错误。如果这样的措辞没有出现在错误的讨论中,那么实现不需要检测或报告错误,尽管鼓励他们这样做。实现不需要检测的错误情况通常简称为“错误”。

因此,为了在 R5RS 中总结这一点,字符串"banana"与 Guile 和 Chickens 对(if #t 1 (if)).

另一方面,R6RS 通常指出,当实现检测到异常情况时,会引发异常。这意味着if少于两个表达式或多于 3 个的表达式应该引发异常,但这并不是说它必须在编译时发生,因为该语言可能会随时进行解析和解释。

于 2016-11-04T15:21:49.850 回答