Chapter one. It actually works.

This commit is contained in:
Elf M. Sternberg 2015-04-15 15:50:53 -07:00
commit d7f8dddb91
2 changed files with 192 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*#
.#*
*~
*.orig
npm-debug.log
node_modules/*
tmp/
test/

184
chap1.rkt Normal file
View File

@ -0,0 +1,184 @@
; Lisp In Small Pieces, chapter 1 Simple Lambda Calculus interpreter
; with global environment, simple lexical closures enforced by singly
; linked lists.
; This covers only one exercise: I added 'exit as an exit value.
; Needed for 'wrong-syntax', which is Racket's version of the "wrong"
; exception tosser.
(require racket/syntax)
; LISP requires a mutatable environment, so using mcons/mpair for
; that.
(require scheme/mpair)
; Weird; racket needs this as a patch. I would have expected it as
; present in the default list of functions!
(define (atom? x)
(and (not (null? x))
(not (pair? x))))
(define env_init '())
(define env_global env_init)
; So, this macro places *into the current scope* (i.e. no building of
; a new scope that get's reaped upon exit) the names of variables and
; potential initial values.
(define-syntax definitial
(syntax-rules ()
((definitial name)
(begin (set! env_global (mcons (mcons 'name 'void) env_global)) 'name))
((definitial name value)
(begin (set! env_global (mcons (mcons 'name value) env_global)) 'name))))
; Oh! This macro (same scope thing again) associates named things with
; values in the target environment (the host language), along with
; arity checking. (which it doesn't do for 'if', for example)
(define-syntax defprimitive
(syntax-rules ()
((defprimitive name value arity)
(definitial name
(lambda (values)
(if (= arity (length values))
(apply value values)
(wrong-syntax #'here "Incorrect arity ~s" (list 'name values))))))))
(define the-false-value (cons "false" "boolean"))
(definitial t #t)
(definitial f the-false-value)
(definitial nil '())
(definitial foo)
(definitial bar)
(definitial fib)
(definitial fact)
(defprimitive cons cons 2)
(defprimitive car car 1)
(defprimitive set-cdr! set-mcdr! 2)
(defprimitive + + 2)
(defprimitive < < 2)
(defprimitive eq? eq? 2)
; This function extends the environment so that *at this moment of
; extension* the conslist head points to the old environment, then
; when it's done it points to the new environment. What's really
; interesting is that the conslist head points to the last object
; initialized, not the first.
(define (extend env variables values)
(cond ((pair? variables)
(if (pair? values)
(mcons (mcons (car variables) (car values))
(extend env (cdr variables) (cdr values)))
(wrong-syntax #'here "Too few values")))
((null? variables)
(if (null? values)
env
(wrong-syntax #'here "Too many values")))
((symbol? variables) (mcons (mcons variables values) env))))
; This is interesting. Already we're starting to get some scope here.
; Note that make function provides the environment, not the invoke.
; But we're still only passing the empty init environment.
; Now a different order, one in which closures aren't present, makes
; the code fragile. So:
(define (make-function variables body env)
(lambda (values)
(eprogn body (extend env variables values))))
; if it's a function, invoke it. Wow. Much complex. Very interpret.
(define (invoke fn args)
(if (procedure? fn)
(fn args)
(wrong-syntax #'here "Not an function ~s" fn)))
; The text points out that the global environment is being
; pushed/popped as needed and rebuilt with every function call. In
; this case, the defining envirnoment is no longer being used at all,
; only the current one. This would make this a dynamically scope
; language, no?
; Iterate through the exps, return the value of the last one.
(define (eprogn exps env)
(if (pair? exps)
(if (pair? (cdr exps))
(begin (evaluate (car exps) env)
(eprogn (cdr exps) env))
(evaluate (car exps) env))
'()))
; Iterate through the exps, return a list of the values of the
; evaluated expressions
(define (evlis exps env)
(if (pair? exps)
(cons (evaluate (car exps) env)
(evlis (cdr exps) env))
'()))
; silly patch because of the mutatable lists
(define-syntax mcaar (syntax-rules () ((_ e) (mcar (mcar e)))))
(define-syntax mcadr (syntax-rules () ((_ e) (mcdr (mcar e)))))
; Iterate through the environment, find an ID, return its associated
; value. This is a completely global, resettable environment; we're
; talking BASIC here.
(define (lookup id env)
(if (mpair? env)
(if (eq? (mcaar env) id)
(mcdar env)
(lookup id (mcdr env)))
(wrong-syntax #'here "No such binding ~s" id)))
; Iterate through the environment, find an ID, and change its value to
; the new value. Again, purely global environment. Really starting
; to grok how the environment "stack" empowers modern runtimes.
(define (update! id env value)
(if (mpair? env)
(if (eq? (mcaar env) id)
(begin (set-mcdr! (mcar env) value) value)
(update! id (mcdr env) value))
(wrong-syntax #'here "No such binding ~s" id)))
; Different evaluation rules.
(define (evaluate exp env)
(if (atom? exp)
(cond
((symbol? exp) (lookup exp env))
((or (number? exp) (string? exp) (char? exp) (boolean? exp) (vector? exp)) exp)
(else (wrong-syntax #'here "Cannot evaluate")))
(case (car exp)
((quote) (cadr exp))
; Note: No checks that the statement even vaguely resembles the rules.
((if) (if (not (eq? (evaluate (cadr exp) env) the-false-value))
(evaluate (caddr exp) env)
(evaluate (cadddr exp) env)))
((begin) (eprogn (cdr exp) env))
((set!) (update! (cadr exp) env (evaluate (caddr exp) env)))
((lambda) (make-function (cadr exp) (cddr exp) env))
(else (invoke (evaluate (car exp) env) (evlis (cdr exp) env))))))
; Run it. Note that the function toplevel is self-referential.
(define (chapter1-scheme)
(define (toplevel)
(let ((result (evaluate (read) env_global)))
(if (not (eq? result 'exit))
(begin (display result) (toplevel))
#f)))
(toplevel))