无法访问的Ill-formed if-expression是Scheme中的语法错误,但不是Common Lisp中的语法错误

时间:2016-11-04 04:33:47

标签: lisp common-lisp sbcl clisp syntax-checking

我正在努力更好地理解如何在不同的lisps中评估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")

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"

2 个答案:

答案 0 :(得分:8)

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

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

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

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

  • Toplevel评估:可能使用解释器,编译器或两者兼而有之。

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

GNU CLISP中的示例:

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

[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实现中,您需要使用编译器来获取完整的语法警告/错误。

答案 1 :(得分:3)

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

R5RS指标非常不明确,if只指定使用时我们有this passage about error handling正在做什么:

  

在谈到错误情况时,此报告使用短语“an   错误信号“表示实现必须检测和   报告错误。 如果这样的措辞没有出现在讨论中   如果出现错误,则不需要实现检测或报告   错误,但鼓励他们这样做。错误情况   通常会提到不需要检测的实现   简单地称为“错误。”

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

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