Unreachable Ill-formed if-expression 在 Scheme 中是语法错误,但在 Common Lisp 中不是

Unreachable Ill-formed if-expression is syntax error in Scheme but not in Common Lisp

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

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

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

(display "12")

以及 Common Lisp 版本

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

(display "12")

chickenguile 都会产生语法错误。

鸡肉:

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

sbclclisp 都打印 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"

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

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

  • 解释器:直接从 Lisp 数据结构执行。它不像 JVM 那样是虚拟机代码的解释器。人们不会期望解释器在执行期间验证完整代码 tree/graph。解释器通常只查看它执行的当前顶层形式。

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

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

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

GNU CLISP 中的示例:

加载文本文件通常使用解释器:

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

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

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 实现中,您需要使用编译器来获得完整的语法 warnings/errors。

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

R5RS 非常不明确,对于 if,它仅指定正确使用时要做什么,我们有 this passage about error handling:

When speaking of an error situation, this report uses the phrase "an error is signalled" to indicate that implementations must detect and report the error. If such wording does not appear in the discussion of an error, then implementations are not required to detect or report the error, though they are encouraged to do so. An error situation that implementations are not required to detect is usually referred to simply as "an error."

因此,要将其包装在 R5RS 中,字符串 "banana" 与 Guile 和 Chickens 对 (if #t 1 (if)) 的评估的响应一样正确。

另一方面,

R6RS 通常表示 when an exceptional situation is detected by the implementation, an exception is raised。这意味着 if 少于两个或多于 3 个表达式应该引发异常,但它并没有说它必须在编译时发生,因为语言可能正在解析和解释。