I'm trying to get a better understanding of how S-expressions are evaluated in different lisps and wanted to see they would handle interesting ill-formed expressions. I get that Common Lisp and Scheme are totally different languages, but is there a specific difference in their semantics that explains the difference in behavior. For example, Lisp-1s and Lisp-2s have observable differences in their behavior, as do hygienic vs non-hygienic macro systems.
I have a program containing an unreachable ill-formed if expression in Scheme and Common Lisp.
;; foo.scm
(if #t 1 (if))
(display "12")
And the Common Lisp version
;; foo.lisp
(if t 1 (if))
(display "12")
chicken
and guile
both produce a syntax error.
Chicken:
% 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:
% guile foo.scm
...
.../foo.scm:1:9: source expression failed to match any pattern in form (if)
sbcl
and clisp
both print 12 and emit no warnings.
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"
Implementations of Common Lisp: Interpreter and Compilers
In Common Lisp the type of code execution depends on what the Lisp system implements and how you use it. Often Common Lisp implementations have multiple ways to execute code: an interpreter and one or more compilers. Even a single running implementation may have that and it will allow the user to switch between those.
Interpreter: executing directly from the Lisp data structure. It is not an interpreter of virtual machine code, like the JVM. One would not expect that the interpreter validates the full code tree/graph during execution. The interpreter usually looks only at the current top form it executes.
Compiler: compiles Lisp code to C, some Byte Code or Machine Code. Since the compiler generates code before the code runs, it will do a syntax check of all the code (for a possible exception, see the remark at the bottom) it sees.
Toplevel evaluation: may use an interpreter, a compiler or a mix of both.
GNU CLISP has both an interpreter and a compiler
Example in GNU CLISP:
LOAD of a text file typically uses the interpreter:
[1]> (load "test.lisp")
;; Loading file test.lisp ...
;; Loaded file test.lisp
T
The Interpreter will not detect the error, because it does not check the whole expression for syntactic correctness. Since the else clause with the syntax error is never used, the Interpreter will never look at it.
CLISP also has a compiler:
[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
As you see, the CLISP compiler detects the syntax error and gives a clear error message.
SBCL uses a compiler, but also has an interpreter
SBCL by default uses a compiler, which will detect the error. For top-level forms it uses for some forms a simpler evaluation mechanism. One can also switch to an interpreter.
If you write a simple IF form in SBCL, the evaluator does not use full compilation and doesn't catch the error:
CL-USER> (if t 1 (if))
1
If you write the same code inside a function definition, the error gets detected, because function definitions will be compiled by default:
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
If you would switch to the full SBCL interpreter, the error would not be detected at definition time:
CL-USER> (setf *evaluator-mode* :interpret)
:INTERPRET
CL-USER> (defun foo () (if t 1 (if)))
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO
Optimisation and syntax checking
Note that some optimising compilers might not check the syntax of unreachable code.
Summary
In most Common Lisp implementations you need to use the compiler to get full syntax warnings/errors.