Strange, I thought many Lisp systems still have source level list-based interpreters. For Common Lisp I would think: SBCL (optional), CLISP, Allegro CL, LispWorks, ECL, ... They can also compile code. Compiling Lisp code was also already available in the first Lisp implementations and having a compiler was an explicit goal of the original implementors.
Let's use the LispWorks Listener (the REPL tool):
CL-USER 25 > (defun foo (bar) (break) (print (list :hello bar)))
FOO
CL-USER 26 > (foo 10)
Break.
1 (continue) Return from break.
2 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 27 : 1 > :bq
INVOKE-DEBUGGER <- BREAK <- FOO <- EVAL <- CAPI::CAPI-TOP-LEVEL-FUNCTION <- CAPI::INTERACTIVE-PANE-TOP-LOOP
<- MP::PROCESS-SG-FUNCTION
CL-USER 28 : 1 > :n
Call to INVOKE-DEBUGGER
CL-USER 29 : 1 > :n
Call to BREAK
CL-USER 30 : 1 > :n
Interpreted call to FOO
CL-USER 31 : 1 > :lambda
(LAMBDA (BAR) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME FOO)) (BREAK) (PRINT (LIST :HELLO BAR)))
The debugger output of the currently running function looks like a linked list to me. I could modify it destructively, if I wanted.LispWorks has a list-level interpreter. One can also compile code.
Typically Lisp interpreters tend to be written in C, since that usually is more portable than Assembler.
> On the downside, this is all of course completely unchecked and pretty unsafe, and, to come back to the original point, you do not have such conveniences as lexical scoping.
I would expect from a typical Lisp interpreter (a source level list-based interpreter) that it does all kinds of runtime checks and also provides lexical scoping. If there is a clojure, then this closure would be a combination of some function and an environment. In standard Common Lisp there is no access to that environment, but I could look from an inspector into it:
CL-USER 54 > (defun example (a) (lambda () (break) (print a)))
EXAMPLE
CL-USER 55 > (example 10)
#<anonymous interpreted function 8020001A59>
CL-USER 56 > (describe *)
#<anonymous interpreted function 8020001A59> is a TYPE::INTERPRETED-FUNCTION
Code (LAMBDA NIL (BREAK) (PRINT A))
Environment ((A . 10) (#:SOURCE-LEVEL-ENVIRONMENT-MARKER FUNCTION NIL . #<EQ Hash Table{0} 81D03EFF03>) (#:FUNCTOR-MARKER LAMBDA (A) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME EXAMPLE)) (LAMBDA NIL (BREAK) (PRINT A))))
As we can see, the interpreted closure has code a list and an environment, where A = 10.