123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- ;; ============
- ;; INTRODUCTION
- ;; ============
- ;; This chapter deals with the writing of a simple interpreter for
- ;; S-expressions.
- ;; =============
- ;; PREREQUISITES
- ;; =============
- (define first car)
- (define second cadr)
- (define third caddr)
- (define build list)
- ;; ===============
- ;; CODE OF CHAPTER
- ;; ===============
- (define new-entry build)
- (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 (car keys)) (car values)]
- [else
- (lookup-in-entry-helper name
- (cdr keys)
- (cdr values)
- lookup-fallback-proc)])))
- (define extend-table cons)
- (define new-table
- (lambda (entry)
- (extend-table entry '())))
- (define lookup-in-table
- (lambda (name table lookup-failure-proc)
- (cond
- [(null? table) (lookup-failure-proc name)]
- [else
- (lookup-in-entry name
- (car table)
- ;; fallback will be to look up the name in the rest of
- ;; the table
- (lambda (name)
- (lookup-in-table name
- (cdr table)
- ;; the failure proc is retained, to be
- ;; called when there are no more
- ;; entries to continue the lookup in
- lookup-failure-proc)))])))
- ;; 6 types:
- ;; const
- ;; quote
- ;; identifier
- ;; lambda
- ;; cond
- ;; application
- ;; An action is a representation of a type (???).
- ;; An S-expression is either an atom or a list.
- ;; -> There are 2 cases to distinguish, when transforming an S-expression to an action.
- ;; Given code:
- (define expression-to-action
- (lambda (expr)
- (cond
- [(atom? expr)
- (atom-to-action expr)]
- [else
- (list-to-action expr)])))
- ;; =====
- ;; Task: Define `atom-to-action`.
- ;; =====
- (define atom-to-action
- (lambda (#|atom|# a)
- (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])))
- ;; =====
- ;; Task: Define `list-to-action`.
- ;; =====
- (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])))
- ;; Given code:
- (define value
- (lambda (expr)
- ;; Q: Why is there an empty list as an argument here?
- ;; A: This is an empty table! This means that the meaning of something is
- ;; the meaning it has in some table.
- (meaning expr '())))
- ;; The procedure `meaning` is only a tool for figuring out what action to take
- ;; next.
- (define meaning
- (lambda (expr table)
- ;; Apply the result of `expression-to-action` to the expression and a table.
- ;; Actions seem to be what calculates a result of an expression.
- ;; Those actions are not yet defined.
- ;; For example this could be:
- ;; ((expression-to-action '(cdr '(1 2 3))) '(cdr (x y z))
- ;; '(((x y z)
- ;; (1 2 3))))
- ;; ≡
- ;; (*application '(cdr (x y z))
- ;; '(((x y z)
- ;; (1 2 3))))
- ((expression-to-action expr) expr table)))
- ;; Thought: It is weird to name `meaning` `meaning`, if it calculates the result
- ;; of some expression, as "meaning" is not necessarily something itself.
- ;; Some "actions" do not really need the given table.
- (define *const
- (lambda (expr table)
- (cond
- [(number? expr) expr]
- [(eq? expr #t) #t]
- [(eq? expr #f) #f]
- [else
- (build 'primitive expr)])))
- (define *quote
- (lambda (expr table)
- (text-of expr)))
- ;; =====
- ;; Task: Define `text-of`.
- ;; =====
- (define text-of second)
- ;; 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 or table it is defined
- ;; in.
- (lookup-in-table expr
- table
- ;; Not sure why the lambda is named `initial-table`.
- ;; I was not sure what to put here.
- initial-table)))
- ;; The book gives a definition of `initial-table` then:
- ;; Given code:
- (define initial-table
- (lambda (name)
- (car '())))
- ;; Which will result in an error.
- ;; Given code:
- (define *lambda
- (lambda (the-lambda table)
- (build 'non-primitive
- (cons table
- ;; The cdr of a lambda expression is its argument list and a
- ;; body.
- (cdr the-lambda)))))
- #|
- (meaning (lambda (x) (cons x y))
- (((y z) ((8) 9))))
- is
- (*lambda (lambda (x) (cons x y))
- (((y z) ((8) 9))))
- is
- (build 'non-primitive
- ((((y z) ((8) 9)))
- (x)
- (cons x y)))
- |#
- ;; =====
- ;; Task: Write table-of, formals-of and body-of.
- ;; =====
- (define table-of first)
- (define formals-of second)
- (define body-of third)
- ;; 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 before it 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.
- (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 (car q-and-a-lines)))
- (meaning (answer-of (car q-and-a-lines)))]
- ;; If there is a normal question, then it needs to be evaluated, to get its
- ;; boolean return value.
- [(meaning (question-of (car q-and-a-lines)))
- (meaning (answer-of (car q-and-a-lines)))]
- ;; Otherwise consider the next question answer pair.
- [else
- (evaluate-cond-q-and-a-lines (cdr q-and-a-lines) table)])))
- ;; =====
- ;; Task: Write `else?`, `question-of` and `answer-of`.
- ;; =====
- (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)))
- (define question-of first)
- (define answer-of second)
- ;; =====
- ;; Task: Write the *cond action.
- ;; =====
- (define *cond
- (lambda (cond-expr table)
- (evaluate-cond-q-and-a-lines (cond-q-and-a-lines-of cond-expr) table)))
- ;; not only `second`, as there is possibly a list of cond-q-and-a-lines.
- (define cond-q-and-a-lines-of cdr)
- ;; =====
- ;; Task: Write a function evaluate-list.
- ;; =====
- (define evaluate-list
- (lambda (list-expression table)
- (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))])))
- ;; This will "reduce" the list elements (arguments), so that they are ready to
- ;; have the function applied to them.
- (define *application
- (lambda (expr table)
- (apply
- ;; get the identifier or lambda-expression of the application
- (meaning (function-of expr) table)
- ;; get the arguments of the application
- (meaning (arguments-of expr) table))))
- ;; An input to *application could be:
- ;; (*application '(cdr (x y z))
- ;; '(((x y z)
- ;; (1 2 3))))
- ;; Which would be calculated as follows:
- ;; (*application '(cdr (x y z))
- ;; '(((x y z)
- ;; (1 2 3))))
- ;; ≡
- ;; (*application '(cdr (x y z))
- ;; '(((x y z)
- ;; (1 2 3))))
- ;; =====
- ;; Task: Write `function-of` and `arguments-of`.
- ;; =====
- (define function-of car)
- (define arguments-of cdr)
- ;; 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)))
- ;; Now we define `apply` as follows:
- (define apply
- (lambda (func args)
- ;; 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)])))
- ;; Q: But now how do we implement `apply-primitive` and `apply-closure`?!
- ;; A: The book gives the following definition with gaps to fill in:
- ;; =====
- ;; Task: Fill the gaps.
- ;; =====
- ;; Explanation: The whole time we were writing procedures to evaluate lists, 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 on 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)
- (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 |# ... (first vals))]
- [(eq? name 'null?)
- (null? (first vals))]
- [(eq? name 'eq?)
- (#| here was a gap |# ... (first vals) #| here was a gap |# ...)]
- [(eq? name 'atom?)
- (#| here was a gap |# ... (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))])))
|