123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- ;; ============
- ;; INTRODUCTION
- ;; ============
- ;; This chapter deals with the writing of a simple interpreter for
- ;; S-expressions. The code is reorganized to be easier to understand.
- ;; =============
- ;; PREREQUISITES
- ;; =============
- ;; These are defined elsewhere in the book and simply used in this chapter.
- (define first car)
- (define rest cdr)
- (define second cadr)
- (define third caddr)
- (define build list)
- (define (atom? sth)
- (and (not (pair? sth))
- (not (null? sth))))
- ;; ===========================
- ;; DATA ABSTRACTION FOR TABLES
- ;; ===========================
- ;; Tables or better environments could be implemented in many ways. Here we use
- ;; lists for simplicity. Lists do, in this case, incur a performance cost when
- ;; looking up bindings in the environment of an expression, but not in the case
- ;; of extending an environment.
- ;; Ideally what one would like to have is a data structure, that has constant
- ;; time random access (O(1)) and constant time adding to the environment.
- ;; To abstract from the underlying data structure we define the interface in
- ;; terms of procedures. This will enable us to keep changes local, if we want to
- ;; change the underlying data structure.
- (define new-entry build)
- (define extend-table cons)
- (define empty-table '()) ; not defined in the book
- (define lookup-in-entry
- (lambda (name entry lookup-fallback-proc)
- (lookup-in-entry-helper name
- (first entry)
- (second entry)
- lookup-fallback-proc)))
- (define lookup-in-entry-helper
- (lambda (name keys values lookup-fallback-proc)
- (cond
- [(null? keys) (lookup-fallback-proc name)]
- [(eq? name (first keys)) (first values)]
- [else
- (lookup-in-entry-helper name
- (rest keys)
- (rest values)
- lookup-fallback-proc)])))
- (define lookup-in-empty-table ; not defined in the book
- (lambda (name)
- "Hopefully this code will never be run. It will result in an error."
- ;; Here is the original version of the code from the book:
- #;(car '())
- ;; Instead we are goint to raise a proper error. The book does not do this,
- ;; in order to not have to introduce exception handling as well.
- (throw 'identifier-not-in-table name)))
- (define new-table ; not defined in the book
- (lambda (entry)
- (extend-table entry empty-table)))
- (define lookup-in-table
- (lambda (name table lookup-failure-proc)
- (cond
- [(null? table) (lookup-failure-proc name)]
- [else
- (lookup-in-entry name
- (first table)
- ;; fallback will be to look up the name in the rest of
- ;; the table
- (lambda (name)
- (lookup-in-table name
- (rest table)
- ;; the failure proc is retained, to be
- ;; called when there are no more
- ;; entries to continue the lookup in
- lookup-failure-proc)))])))
- ;; ======================
- ;; EXPRESSIONS TO ACTIONS
- ;; ======================
- ;; An S-expression is either an atom or a list.
- ;; -> There are 2 cases to distinguish, when transforming an S-expression to an
- ;; action.
- (define expression-to-action
- (lambda (expr)
- "Having to evaluate an expression, one first has to know what to do
- next. This procedure delegates the decision what to do next to 2 procedures."
- (cond [(atom? expr) (atom-to-action expr)]
- [else (list-to-action expr)])))
- (define atom-to-action
- (lambda (#|atom|# a)
- ;; An atom is always a constant or it is an identifier, which is bound to
- ;; something else, which we hopefully find inside a table/an environment.
- (cond [(number? a) *const]
- [(eq? a #t) *const]
- [(eq? a #f) *const]
- [(eq? a 'cons) *const]
- [(eq? a 'car) *const]
- [(eq? a 'cdr) *const]
- [(eq? a 'null?) *const]
- [(eq? a 'eq?) *const]
- [(eq? a 'atom?) *const]
- [(eq? a 'zero?) *const]
- [(eq? a 'add1) *const]
- [(eq? a 'sub1) *const]
- [(eq? a 'number?) *const]
- [else *identifier])))
- (define list-to-action
- (lambda (#|list|# l)
- (cond [(atom? (car l))
- (cond [(eq? (car l) 'quote) *quote]
- [(eq? (car l) 'lambda) *lambda]
- [(eq? (car l) 'cond) *cond]
- [else *application])]
- [else *application])))
- ;; =================
- ;; VALUE AND MEANING
- ;; =================
- ;; The procedure `meaning` approximates `eval`.
- (define value
- (lambda (expr)
- "The procedure value evaluates an expression giving an empty environment."
- (meaning expr empty-table)))
- (define meaning
- (lambda (expr table)
- "The procedure meaning evaluates an expression given an environment. It does
- so by figuring out what the next action to take is and then applies that action
- to the expression and the given environment."
- ((expression-to-action expr) expr table)))
- ;; =====================
- ;; DEFINITION OF ACTIONS
- ;; =====================
- (define *const
- (lambda (expr table)
- "This interpreter does not deal with strings. There are only numbers,
- booleans and primitive procedures and compositions of those."
- (cond
- [(number? expr) expr]
- [(eq? expr #t) #t]
- [(eq? expr #f) #f]
- [else
- (build 'primitive expr)])))
- (define *quote
- (lambda (expr table)
- (text-of-quotation expr)))
- ;; Explanation: Something quoted has the form `'(quote something)`, so the second
- ;; part is the quoted thing. But why name it its "text"?
- (define *identifier
- (lambda (expr table)
- "An identifier's meaning depends on the environment it is defined in, so we
- look up its meaning there."
- (lookup-in-table expr
- table
- ;; Lookup in an empty table, which is the
- ;; lookup-failure-proc, will result in an error.
- lookup-in-empty-table)))
- (define *lambda
- (lambda (the-lambda table)
- "This will result in something like the following:
- (list 'non-primitive
- <table>
- <formals-of-lambda>
- <body-of-lambda>)"
- (build 'non-primitive
- (cons table
- ;; The cdr of a lambda expression is its argument list and a
- ;; body.
- (cdr the-lambda)))))
- (define *cond
- (lambda (cond-expr table)
- "Evaluating a cond expression is simple. One needs only to figure out which
- of the questions or conditions is true, starting from the first of the
- cond-cases. Once a true question or condition has been found one needs to
- evaluate its answer or consequent, using the given environment or table."
- (evaluate-cond-q-and-a-lines (cond-q-and-a-lines-of cond-expr) table)))
- (define *application
- (lambda (expr table)
- "An *application is always an application of a primitive (for example: car,
- cdr, ...) or application of a user defined procedure, a non-primitive."
- ;; The idea here is to first evaluate the procedure and its arguments,
- ;; before applying the procedure to its arguments.
- (apply
- ;; We get the definition for the procedure out of the given environment.
- (meaning (function-of-applicable-expr expr) table)
- ;; Then we evaluate the arguments of the procedure call using the given
- ;; environment.
- (evaluate-list (arguments-of expr) table))))
- ;; =============================
- ;; DATA ABSTRACTIONS FOR ACTIONS
- ;; =============================
- (define text-of-quotation second)
- (define table-of first)
- (define formals-of second)
- (define body-of third)
- (define question-of-cond-branch first)
- (define answer-of-cond-branch second)
- (define cond-q-and-a-lines-of cdr)
- (define function-of-applicable-expr car)
- (define arguments-of cdr)
- (define else?
- (lambda (something)
- ;; The book includes a check for `atom?`. I am not sure why this is
- ;; necessary, if we already check whether `something` is equal to the symbol
- ;; `else`.
- (eq? something 'else)))
- ;; The book distinguishes between primitives and non-primitives as types of
- ;; functions, where primitives are the ones, which need to already be defined in
- ;; our language to construct non-primitives from them. The functions are marked
- ;; as primitives or non-primitives by putting a tag (a symbol) in an expression,
- ;; which represents a primitive or a non-primitive. It is tagged data.
- ;; We can see this being put in our implementation of *lambda and *const. In the
- ;; predicates `primitive?` and `non-primitive?` we look for those markers.
- (define primitive?
- (lambda (sth)
- (eq? (first sth) 'primitive)))
- (define non-primitive?
- (lambda (sth)
- (eq? (first sth) 'non-primitive)))
- ;; =====================
- ;; EVALUATION PROCEDURES
- ;; =====================
- ;; Writing a function for evaluating a cond expression. This can be done,
- ;; because we now have code that evaluates code and thus is one level above the
- ;; evaluated code. This is why we do only need a normal function to evaluate a
- ;; cond expression. A macro is basically the same thing as such one level above
- ;; code, because it processes the code and can perform syntax transformations
- ;; before the code is run.
- ;; Also note, that cond is not an *application. As the book continues to
- ;; explain, in an *application expression, all the arguments must be evaluated,
- ;; before the application can be done. This must not be the case for a cond
- ;; expression. Instead, only parts of it shall be evaluated.
- (define evaluate-cond-q-and-a-lines
- (lambda (q-and-a-lines table)
- ;; When evaluating a cond expression, we in turn rely on cond in our
- ;; one-level-above language. This cond is not necessarily the same as the
- ;; cond in the code we are processing.
- (cond
- ;; Check for an else-branch. In this case evaluate the answer part.
- [(else? (question-of-cond-branch (car q-and-a-lines)))
- (meaning (answer-of-cond-branch (car q-and-a-lines))
- table)]
- ;; If there is a normal question, then it needs to be evaluated, to get its
- ;; boolean return value.
- [(meaning (question-of-cond-branch (car q-and-a-lines))
- table)
- (meaning (answer-of-cond-branch (car q-and-a-lines))
- table)]
- ;; Otherwise consider the next question answer pair.
- [else
- (evaluate-cond-q-and-a-lines (rest q-and-a-lines) table)])))
- (define evaluate-list
- (lambda (list-expression table)
- ;; This will "reduce" the list elements (arguments), so that they are ready
- ;; to have the function applied to them.
- (cond
- ;; If the list is empty, then the result is also empty.
- [(null? list-expression) '()]
- [else
- (cons (meaning (car list-expression) table)
- (evaluate-list (cdr list-expression) table))])))
- ;; =====
- ;; APPLY
- ;; =====
- ;; What follows are the procedures that, with the help of the procedures defined
- ;; before, apply procedures and primitives to expressions in the limited lisp
- ;; dialect we built.
- (define apply
- (lambda (func args)
- "apply receives the result of an evaluation of a procedure name, which is a "
- ;; Depending on whether a procedure is a primitive or a non-primitive, the
- ;; application happens in different ways.
- (cond
- [(primitive? func)
- ;; first of func is the tag for primitive or non-primitive.
- ;; '(primitive func-name args)
- (apply-primitive (second func) args)]
- [(non-primitive? func)
- (apply-closure (second func) args)]
- [else
- (throw 'wrong-call-to-apply func args)])))
- ;; Explanation: The whole time we were writing procedures to evaluate lists or
- ;; subsequently atoms, which represent programs. This `apply-primitive`
- ;; procedure is all about understanding, what kind of expression we have to
- ;; evaluate and then do the transformation, which leads to the evaluation result
- ;; in our one-level-above language. For example, if we find a `car`, then we
- ;; need to get the first element of the list that is in `vals`. This is what we
- ;; do in our one-level-above language. The result is returned as a value, which
- ;; is reinserted into the evaluated language's context.
- (define apply-primitive
- (lambda (name vals)
- (define :atom?
- (lambda (sth)
- (cond
- [(atom? sth) #t]
- ;; Why have more cases? Isn't the check for atom? sufficient? A: No,
- ;; we need to unwrap things. We tagged expressions with things like
- ;; 'primitive and 'non-primitive. Those could be atoms.
- [(null? sth) #f]
- ;; OK, primitives are countes as atoms then.
- [(eq? (car sth) 'primitive) #t]
- ;; Why #t?
- [(eq? (car sth) 'non-primitive) #t]
- [else #f])))
- (cond
- [(eq? name #| here was a gap |# 'cons)
- (cons (first vals) (second vals))]
- [(eq? name 'car)
- (car (first vals))]
- [(eq? name 'cdr)
- (#| here was a gap |# cdr (first vals))]
- [(eq? name 'null?)
- (null? (first vals))]
- [(eq? name 'eq?)
- (#| here was a gap |# eq? (first vals) #| here was a gap |# (second vals))]
- [(eq? name 'atom?)
- ;; Here we define the semantics of `atom?` in our evaluated language using
- ;; our meta language. Q: I don't know, why the semantics are different
- ;; from the ones we defined for `atom?` in our meta language. A: Now I
- ;; know: Because we need to unwrap for things we tagged as primitives, as
- ;; stated in the definition of `:atom?`.
- (#| here was a gap |# :atom? (first vals))]
- [(eq? name 'zero?)
- (zero? (first vals))]
- [(eq? name 'add1)
- (+ (first vals) 1)]
- [(eq? name 'sub1)
- (- (first vals) 1)]
- [(eq? name 'number?)
- (number? (first vals))])))
- (define apply-closure
- (lambda (closure vals)
- ;; Evaluate the closure with the extended environment.
- (meaning
- (body-of closure)
- ;; Add the arguments of the closure to its table, to be able to use
- ;; `meaning` on the body of the closure and the extended table
- ;; (environment).
- (extend-table (new-entry (formals-of closure) vals)
- (table-of closure)))))
|