Toy Lisp Interpreter
Files
Read-eval-write loopThe lisp interpreter consists of three main subroutines: read, eval, and write.Read subroutineThe input to the lisp interpreter takes the form of strings called symbolic expressions (s-expressions), which are generated by the following grammar:
The read subroutine reads in an s-expression and converts it into a
lisp object consisting of CONS cells, INTS, and SYMS.
Eval subroutineThe eval subroutine evaluates the input lisp object and produces an output lisp object. Lisp objects are evaluated according to the following rules:
For example, consider a function f that takes two arguments. The s-expression "(f x y)" is evaluated by evaluating x and y to obtain arguments X and Y, and then applying the function named by f to these arguments to obtain the result. In general, I will use lowercase letters to denote the elements of the list and uppercase letters to denote the results of evaluating these elements. For example, suppose we define symbols three and four: In the expression "(+ three four)", x is "three", y is "four", X is "3", and Y is "4".--> (define three 3) three --> (define four 4) four Write subroutineThe write subroutine converts the output lisp object into a string, which it displays on the screen. Note that although the input lisp object consists only of CONS cells, INTS, and SYMS, the output lisp object may consist of lisp objects of any type, including PRIMS, COMPS, and SPECS. When the output lisp object is converted to a string, PRIMS, COMPS, and SPECS are converted to "[primitive function]", "[compound function]", and "[special form]", respectively. For example:--> + [primitive function] --> (lambda x x) [compound function] --> quote [special form] --> (cons + quote) ([primitive function] . [special form])
Types of lisp objectsThere are six types of lisp objects:
Lists and atomsLisp objects are divided into two classes: atoms and lists. An atom is anything that is not a CONS cell. A list is either a CONS cell or the empty list, which is a SYM. Thus, the empty list is both an atom and a list, and is the only lisp object to have this property.Lists are subdivided into two classes: proper lists and improper lists. A list is said to be proper if it the empty list or if the cdr of the list is a proper list; otherwise the list is said to be improper. Examples of proper lists: "(1 2 3)", "((1 2) 3)", "((1 . 2) 3)" Examples of improper lists: "(1 2 . 3)"
Predefined symbolsThere are a number of SYMS that are predefined by the LISP interpreter; these include the symbols "()", "#t", and "#f", the names of the primitive functions, and the names of the predefined special forms. The symbols "()", "#t", and "#f" are bound to "()", "#t", and "()", respectively, the names of the primitive functions are bound to their respective functions, and the names of the predefined special forms are bound to their respective special forms.
Static versus dynamic scopingFor static scoping, a function is evaluated by extending the environment in which the function's lambda expression was defined. For dynamic scoping, a function is evaluated by extending the environment in which the function is called.Here are some examples that illustrate the difference between static and dynamic scoping. For static scoping, Here is the same example with dynamic scoping:--> (define bletch 1) bletch --> (define add-bletch (lambda (x) (+ x bletch))) add-bletch --> ((lambda (bletch) (add-bletch 3)) 5) 4 A second example with static scoping:--> (define bletch 1) bletch --> (define add-bletch (lambda (x) (+ x bletch))) add-bletch --> ((lambda (bletch) (add-bletch 3)) 5) 8 Here is the second example with dynamic scoping:--> (define bletch 1) bletch --> ((lambda (bletch) (define add-bletch (lambda (x) (+ x bletch)))) 7) add-bletch --> ((lambda (bletch) (add-bletch 3)) 5) 10 --> (define bletch 1) bletch --> ((lambda (bletch) (define add-bletch (lambda (x) (+ x bletch)))) 7) add-bletch --> ((lambda (bletch) (add-bletch 3)) 5) 8
Function for creating new special formsA novel feature of the lisp interpreter is that it includes a function called "special" for creating new special forms. The s-expression "(special f)" returns a special form, where F is required to be a function of two arguments. A list whose first element evaluates to this special form is evaluated by applying F to a two-element list whose first element is the cdr of the list and whose second element is the calling environment. For example:To illustrate this function, I will show how it can be used to define some common special forms.--> ((special (lambda x x)) a b c) ((a b c) ()) --> (let ((bletch 42)) ((special (lambda x x)) a b c)) ((a b c) ((bletch . 42))) We can use "special" to define the "quote" special form as follows: --> (define quote-new (special (lambda (sexp env) (car sexp)))) quote-new --> (quote-new a) a We can use "special" to define the "cond" special form as follows:
Primitive functionsThe following primitive functions are predefined by the lisp interpreter (lowercase letters denote the unevaluated arguments; uppercase letters denote the results of evaluating these arguments):
Predefined special formsThe following special forms are predefined by the lisp interpreter (lowercase letters denote the unevaluated arguments; uppercase letters denote the results of evaluating these arguments):
CommandsThe following commands are defined by the lisp interpreter:
Recursive local functionsIt requires some trickery to define recursive local functions. The following example illustrates one way to define such functions:
Here "fact", the factorial function, is defined as a recursive local
function.
Here is another way to do this:
((lambda x (apply (car x) x)) (lambda (f n) (if (= n 0) 1 (* n (f f (- n 1))))) 5) John McCarthy defined a special form called "label" that makes it much simpler to define recursive local functions. Here is how "label" can be used to define "fact" as a recursive local function: The "label" special form is defined in predefined.scm.((label fact (lambda (n) (if (= n 0) 1 (* n (fact (- n 1)))))) 5)
DiagonalizationConsider the problem of finding a s-expression called "fixed-point" such that "fixed-point" and "(eval-top fixed-point)" evaluate to the same thing This can be viewed as the lisp equivalent of finding a program that prints itself out.The problem can be trivially solved by simply binding "fixed-point" to itself: Suppose, however, that we rule out this type of solution. The problem can be solved using a very general method that makes use of diagonalization.--> (define fixed-point 'fixed-point) fixed-point --> (eval-top fixed-point) fixed-point Define a symbol "diag" as follows: Note that "diag" has the following property:--> (define diag '(lambda (x) (list x (list (quote quote) x)))) diag (eval-top ((eval-top diag) f)) = ((eval-top f) f),where "f" is an arbitrary s-expression. For example, Also,--> (eval-top ((eval-top diag) 'list)) (list) --> (list 'list) (list) Now suppose we take "f" to be "diag". Then--> (eval-top ((eval-top diag) '(lambda (x) (cons x x)))) ((lambda (x) (cons x x)) lambda (x) (cons x x)) --> ((lambda (x) (cons x x)) '(lambda (x) (cons x x))) ((lambda (x) (cons x x)) lambda (x) (cons x x)) (eval-top ((eval-top diag) diag)) = ((eval-top diag) diag).Thus, "((eval-top diag) diag)" has the desired property: --> (define fixed-point ((eval-top diag) diag)) fixed-point --> fixed-point ((lambda (x) (list x (list (quote quote) x))) (quote (lambda (x) (list x (list (quote quote) x))))) --> (eval-top fixed-point) ((lambda (x) (list x (list (quote quote) x))) (quote (lambda (x) (list x (list (quote quote) x))))) |