123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- ;;; -*- mode: scheme; coding: utf-8; -*-
- ;;;; Copyright (C) 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
- ;;;;
- ;;;; This library is free software; you can redistribute it and/or
- ;;;; modify it under the terms of the GNU Lesser General Public
- ;;;; License as published by the Free Software Foundation; either
- ;;;; version 3 of the License, or (at your option) any later version.
- ;;;;
- ;;;; This library is distributed in the hope that it will be useful,
- ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;;;; Lesser General Public License for more details.
- ;;;;
- ;;;; You should have received a copy of the GNU Lesser General Public
- ;;;; License along with this library; if not, write to the Free Software
- ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- ;;;;
- ;;; Commentary:
- ;;; Scheme eval, written in Scheme.
- ;;;
- ;;; Expressions are first expanded, by the syntax expander (i.e.
- ;;; psyntax), then memoized into internal forms. The evaluator itself
- ;;; only operates on the internal forms ("memoized expressions").
- ;;;
- ;;; Environments are represented as linked lists of the form (VAL ... .
- ;;; MOD). If MOD is #f, it means the environment was captured before
- ;;; modules were booted. If MOD is the literal value '(), we are
- ;;; evaluating at the top level, and so should track changes to the
- ;;; current module.
- ;;;
- ;;; Evaluate this in Emacs to make code indentation work right:
- ;;;
- ;;; (put 'memoized-expression-case 'scheme-indent-function 1)
- ;;;
- ;;; Code:
- (eval-when (compile)
- (define-syntax env-toplevel
- (syntax-rules ()
- ((_ env)
- (let lp ((e env))
- (if (vector? e)
- (lp (vector-ref e 0))
- e)))))
- (define-syntax make-env
- (syntax-rules ()
- ((_ n init next)
- (let ((v (make-vector (1+ n) init)))
- (vector-set! v 0 next)
- v))))
- (define-syntax make-env*
- (syntax-rules ()
- ((_ next init ...)
- (vector next init ...))))
- (define-syntax env-ref
- (syntax-rules ()
- ((_ env depth width)
- (let lp ((e env) (d depth))
- (if (zero? d)
- (vector-ref e (1+ width))
- (lp (vector-ref e 0) (1- d)))))))
- (define-syntax env-set!
- (syntax-rules ()
- ((_ env depth width val)
- (let lp ((e env) (d depth))
- (if (zero? d)
- (vector-set! e (1+ width) val)
- (lp (vector-ref e 0) (1- d)))))))
- ;; For evaluating the initializers in a "let" expression. We have to
- ;; evaluate the initializers before creating the environment rib, to
- ;; prevent continuation-related shenanigans; see
- ;; http://wingolog.org/archives/2013/11/02/scheme-quiz-time for a
- ;; deeper discussion.
- ;;
- ;; This macro will inline evaluation of the first N initializers.
- ;; That number N is indicated by the number of template arguments
- ;; passed to the macro. It's a bit nasty but it's flexible and
- ;; optimizes well.
- (define-syntax let-env-evaluator
- (syntax-rules ()
- ((eval-and-make-env eval env (template ...))
- (let ()
- (define-syntax eval-and-make-env
- (syntax-rules ()
- ((eval-and-make-env inits width (template ...) k)
- (let lp ((n (length '(template ...))) (vals '()))
- (if (eqv? n width)
- (let ((env (make-env n #f env)))
- (let lp ((n (1- n)) (vals vals))
- (if (null? vals)
- (k env)
- (begin
- (env-set! env 0 n (car vals))
- (lp (1- n) (cdr vals))))))
- (lp (1+ n)
- (cons (eval (vector-ref inits n) env) vals)))))
- ((eval-and-make-env inits width (var (... ...)) k)
- (let ((n (length '(var (... ...)))))
- (if (eqv? n width)
- (k (make-env n #f env))
- (let* ((x (eval (vector-ref inits n) env))
- (k (lambda (env)
- (env-set! env 0 n x)
- (k env))))
- (eval-and-make-env inits width (x var (... ...)) k)))))))
- (lambda (inits)
- (let ((width (vector-length inits))
- (k (lambda (env) env)))
- (eval-and-make-env inits width () k)))))))
- ;; Fast case for procedures with fixed arities.
- (define-syntax make-fixed-closure
- (lambda (x)
- (define *max-static-argument-count* 8)
- (define (make-formals n)
- (map (lambda (i)
- (datum->syntax
- x
- (string->symbol
- (string (integer->char (+ (char->integer #\a) i))))))
- (iota n)))
- (syntax-case x ()
- ((_ eval nreq body env) (not (identifier? #'env))
- #'(let ((e env))
- (make-fixed-closure eval nreq body e)))
- ((_ eval nreq body env)
- #`(case nreq
- #,@(map (lambda (nreq)
- (let ((formals (make-formals nreq)))
- #`((#,nreq)
- (lambda (#,@formals)
- (eval body
- (make-env* env #,@formals))))))
- (iota *max-static-argument-count*))
- (else
- #,(let ((formals (make-formals *max-static-argument-count*)))
- #`(lambda (#,@formals . more)
- (let ((env (make-env nreq #f env)))
- #,@(map (lambda (formal n)
- #`(env-set! env 0 #,n #,formal))
- formals (iota (length formals)))
- (let lp ((i #,*max-static-argument-count*)
- (args more))
- (cond
- ((= i nreq)
- (eval body
- (if (null? args)
- env
- (scm-error 'wrong-number-of-args
- "eval" "Wrong number of arguments"
- '() #f))))
- ((null? args)
- (scm-error 'wrong-number-of-args
- "eval" "Wrong number of arguments"
- '() #f))
- (else
- (env-set! env 0 i (car args))
- (lp (1+ i) (cdr args))))))))))))))
- ;; Fast case for procedures with fixed arities and a rest argument.
- (define-syntax make-rest-closure
- (lambda (x)
- (define *max-static-argument-count* 3)
- (define (make-formals n)
- (map (lambda (i)
- (datum->syntax
- x
- (string->symbol
- (string (integer->char (+ (char->integer #\a) i))))))
- (iota n)))
- (syntax-case x ()
- ((_ eval nreq body env) (not (identifier? #'env))
- #'(let ((e env))
- (make-rest-closure eval nreq body e)))
- ((_ eval nreq body env)
- #`(case nreq
- #,@(map (lambda (nreq)
- (let ((formals (make-formals nreq)))
- #`((#,nreq)
- (lambda (#,@formals . rest)
- (eval body
- (make-env* env #,@formals rest))))))
- (iota *max-static-argument-count*))
- (else
- #,(let ((formals (make-formals *max-static-argument-count*)))
- #`(lambda (#,@formals . more)
- (let ((env (make-env (1+ nreq) #f env)))
- #,@(map (lambda (formal n)
- #`(env-set! env 0 #,n #,formal))
- formals (iota (length formals)))
- (let lp ((i #,*max-static-argument-count*)
- (args more))
- (cond
- ((= i nreq)
- (env-set! env 0 nreq args)
- (eval body env))
- ((null? args)
- (scm-error 'wrong-number-of-args
- "eval" "Wrong number of arguments"
- '() #f))
- (else
- (env-set! env 0 i (car args))
- (lp (1+ i) (cdr args))))))))))))))
- (define-syntax call
- (lambda (x)
- (define *max-static-call-count* 4)
- (syntax-case x ()
- ((_ eval proc nargs args env) (identifier? #'env)
- #`(case nargs
- #,@(map (lambda (nargs)
- #`((#,nargs)
- (proc
- #,@(map
- (lambda (n)
- (let lp ((n n) (args #'args))
- (if (zero? n)
- #`(eval (car #,args) env)
- (lp (1- n) #`(cdr #,args)))))
- (iota nargs)))))
- (iota *max-static-call-count*))
- (else
- (apply proc
- #,@(map
- (lambda (n)
- (let lp ((n n) (args #'args))
- (if (zero? n)
- #`(eval (car #,args) env)
- (lp (1- n) #`(cdr #,args)))))
- (iota *max-static-call-count*))
- (let lp ((exps #,(let lp ((n *max-static-call-count*)
- (args #'args))
- (if (zero? n)
- args
- (lp (1- n) #`(cdr #,args)))))
- (args '()))
- (if (null? exps)
- (reverse args)
- (lp (cdr exps)
- (cons (eval (car exps) env) args)))))))))))
- ;; This macro could be more straightforward if the compiler had better
- ;; copy propagation. As it is we do some copy propagation by hand.
- (define-syntax mx-bind
- (lambda (x)
- (syntax-case x ()
- ((_ data () body)
- #'body)
- ((_ data (a . b) body) (and (identifier? #'a) (identifier? #'b))
- #'(let ((a (car data))
- (b (cdr data)))
- body))
- ((_ data (a . b) body) (identifier? #'a)
- #'(let ((a (car data))
- (xb (cdr data)))
- (mx-bind xb b body)))
- ((_ data (a . b) body)
- #'(let ((xa (car data))
- (xb (cdr data)))
- (mx-bind xa a (mx-bind xb b body))))
- ((_ data v body) (identifier? #'v)
- #'(let ((v data))
- body)))))
-
- ;; The resulting nested if statements will be an O(n) dispatch. Once
- ;; we compile `case' effectively, this situation will improve.
- (define-syntax mx-match
- (lambda (x)
- (syntax-case x (quote)
- ((_ mx data tag)
- #'(error "what" mx))
- ((_ mx data tag (('type pat) body) c* ...)
- #`(if (eqv? tag #,(or (memoized-typecode (syntax->datum #'type))
- (error "not a typecode" #'type)))
- (mx-bind data pat body)
- (mx-match mx data tag c* ...))))))
- (define-syntax memoized-expression-case
- (lambda (x)
- (syntax-case x ()
- ((_ mx c ...)
- #'(let ((tag (car mx))
- (data (cdr mx)))
- (mx-match mx data tag c ...)))))))
- ;;;
- ;;; On 18 Feb 2010, I did a profile of how often the various memoized expression
- ;;; types occur when getting to a prompt on a fresh build. Here are the numbers
- ;;; I got:
- ;;;
- ;;; lexical-ref: 32933054
- ;;; call: 20281547
- ;;; toplevel-ref: 13228724
- ;;; if: 9156156
- ;;; quote: 6610137
- ;;; let: 2619707
- ;;; lambda: 1010921
- ;;; begin: 948945
- ;;; lexical-set: 509862
- ;;; call-with-values: 139668
- ;;; apply: 49402
- ;;; module-ref: 14468
- ;;; define: 1259
- ;;; toplevel-set: 328
- ;;; call/cc: 0
- ;;; module-set: 0
- ;;;
- ;;; So until we compile `case' into a computed goto, we'll order the clauses in
- ;;; `eval' in this order, to put the most frequent cases first.
- ;;;
- (define primitive-eval
- (let ()
- ;; We pre-generate procedures with fixed arities, up to some number
- ;; of arguments, and some rest arities; see make-fixed-closure and
- ;; make-rest-closure above.
- ;; A unique marker for unbound keywords.
- (define unbound-arg (list 'unbound-arg))
- ;; Procedures with rest, optional, or keyword arguments, potentially with
- ;; multiple arities, as with case-lambda.
- (define (make-general-closure env body nreq rest? nopt kw inits alt)
- (define alt-proc
- (and alt ; (body meta nreq ...)
- (let* ((body (car alt))
- (spec (cddr alt))
- (nreq (car spec))
- (rest (if (null? (cdr spec)) #f (cadr spec)))
- (tail (and (pair? (cdr spec)) (pair? (cddr spec)) (cddr spec)))
- (nopt (if tail (car tail) 0))
- (kw (and tail (cadr tail)))
- (inits (if tail (caddr tail) '()))
- (alt (and tail (cadddr tail))))
- (make-general-closure env body nreq rest nopt kw inits alt))))
- (define (set-procedure-arity! proc)
- (let lp ((alt alt) (nreq nreq) (nopt nopt) (rest? rest?))
- (if (not alt)
- (begin
- (set-procedure-property! proc 'arglist
- (list nreq
- nopt
- (if kw (cdr kw) '())
- (and kw (car kw))
- (and rest? '_)))
- (set-procedure-minimum-arity! proc nreq nopt rest?))
- (let* ((spec (cddr alt))
- (nreq* (car spec))
- (rest?* (if (null? (cdr spec)) #f (cadr spec)))
- (tail (and (pair? (cdr spec)) (pair? (cddr spec)) (cddr spec)))
- (nopt* (if tail (car tail) 0))
- (alt* (and tail (cadddr tail))))
- (if (or (< nreq* nreq)
- (and (= nreq* nreq)
- (if rest?
- (and rest?* (> nopt* nopt))
- (or rest?* (> nopt* nopt)))))
- (lp alt* nreq* nopt* rest?*)
- (lp alt* nreq nopt rest?)))))
- proc)
- (set-procedure-arity!
- (lambda %args
- (define (npositional args)
- (let lp ((n 0) (args args))
- (if (or (null? args)
- (and (>= n nreq) (keyword? (car args))))
- n
- (lp (1+ n) (cdr args)))))
- (let ((nargs (length %args)))
- (cond
- ((or (< nargs nreq)
- (and (not kw) (not rest?) (> nargs (+ nreq nopt)))
- (and alt kw (not rest?) (> (npositional %args) (+ nreq nopt))))
- (if alt
- (apply alt-proc %args)
- ((scm-error 'wrong-number-of-args
- "eval" "Wrong number of arguments"
- '() #f))))
- (else
- (let* ((nvals (+ nreq (if rest? 1 0) (length inits)))
- (env (make-env nvals unbound-arg env)))
- (let lp ((i 0) (args %args))
- (cond
- ((< i nreq)
- ;; Bind required arguments.
- (env-set! env 0 i (car args))
- (lp (1+ i) (cdr args)))
- ((not kw)
- ;; Optional args (possibly), but no keyword args.
- (let lp ((i i) (args args) (inits inits))
- (cond
- ((< i (+ nreq nopt))
- (cond
- ((< i nargs)
- (env-set! env 0 i (car args))
- (lp (1+ i) (cdr args) (cdr inits)))
- (else
- (env-set! env 0 i (eval (car inits) env))
- (lp (1+ i) args (cdr inits)))))
- (else
- (when rest?
- (env-set! env 0 i args))
- (eval body env)))))
- (else
- ;; Optional args. As before, but stop at the first
- ;; keyword.
- (let lp ((i i) (args args) (inits inits))
- (cond
- ((< i (+ nreq nopt))
- (cond
- ((and (< i nargs) (not (keyword? (car args))))
- (env-set! env 0 i (car args))
- (lp (1+ i) (cdr args) (cdr inits)))
- (else
- (env-set! env 0 i (eval (car inits) env))
- (lp (1+ i) args (cdr inits)))))
- (else
- (when rest?
- (env-set! env 0 i args))
- (let ((aok (car kw))
- (kw (cdr kw))
- (kw-base (if rest? (1+ i) i)))
- ;; Now scan args for keywords.
- (let lp ((args args))
- (cond
- ((and (pair? args) (pair? (cdr args))
- (keyword? (car args)))
- (let ((kw-pair (assq (car args) kw))
- (v (cadr args)))
- (if kw-pair
- ;; Found a known keyword; set its value.
- (env-set! env 0 (cdr kw-pair) v)
- ;; Unknown keyword.
- (if (not aok)
- ((scm-error
- 'keyword-argument-error
- "eval" "Unrecognized keyword"
- '() (list (car args))))))
- (lp (cddr args))))
- ((pair? args)
- (if rest?
- ;; Be lenient parsing rest args.
- (lp (cdr args))
- ((scm-error 'keyword-argument-error
- "eval" "Invalid keyword"
- '() (list (car args))))))
- (else
- ;; Finished parsing keywords. Fill in
- ;; uninitialized kwargs by evalling init
- ;; expressions in their appropriate
- ;; environment.
- (let lp ((i kw-base) (inits inits))
- (cond
- ((pair? inits)
- (when (eq? (env-ref env 0 i) unbound-arg)
- (env-set! env 0 i (eval (car inits) env)))
- (lp (1+ i) (cdr inits)))
- (else
- ;; Finally, eval the body.
- (eval body env)))))))))))))))))))))
- ;; The "engine". EXP is a memoized expression.
- (define (eval exp env)
- (memoized-expression-case exp
- (('lexical-ref (depth . width))
- (env-ref env depth width))
-
- (('call (f nargs . args))
- (let ((proc (eval f env)))
- (call eval proc nargs args env)))
-
- (('toplevel-ref var-or-sym)
- (variable-ref
- (if (variable? var-or-sym)
- var-or-sym
- (memoize-variable-access! exp (env-toplevel env)))))
- (('if (test consequent . alternate))
- (if (eval test env)
- (eval consequent env)
- (eval alternate env)))
-
- (('quote x)
- x)
- (('let (inits . body))
- (eval body ((let-env-evaluator eval env (_ _ _ _)) inits)))
- (('lambda (body meta nreq . tail))
- (let ((proc
- (if (null? tail)
- (make-fixed-closure eval nreq body env)
- (if (null? (cdr tail))
- (make-rest-closure eval nreq body env)
- (apply make-general-closure env body nreq tail)))))
- (let lp ((meta meta))
- (unless (null? meta)
- (set-procedure-property! proc (caar meta) (cdar meta))
- (lp (cdr meta))))
- proc))
- (('seq (head . tail))
- (begin
- (eval head env)
- (eval tail env)))
-
- (('lexical-set! ((depth . width) . x))
- (env-set! env depth width (eval x env)))
-
- (('call-with-values (producer . consumer))
- (call-with-values (eval producer env)
- (eval consumer env)))
- (('apply (f args))
- (apply (eval f env) (eval args env)))
- (('module-ref var-or-spec)
- (variable-ref
- (if (variable? var-or-spec)
- var-or-spec
- (memoize-variable-access! exp #f))))
- (('define (name . x))
- (begin
- (define! name (eval x env))
- (if #f #f)))
- (('capture-module x)
- (eval x (current-module)))
- (('toplevel-set! (var-or-sym . x))
- (variable-set!
- (if (variable? var-or-sym)
- var-or-sym
- (memoize-variable-access! exp (env-toplevel env)))
- (eval x env)))
-
- (('call-with-prompt (tag thunk . handler))
- (call-with-prompt
- (eval tag env)
- (eval thunk env)
- (eval handler env)))
-
- (('call/cc proc)
- (call/cc (eval proc env)))
- (('module-set! (x . var-or-spec))
- (variable-set!
- (if (variable? var-or-spec)
- var-or-spec
- (memoize-variable-access! exp #f))
- (eval x env)))))
-
- ;; primitive-eval
- (lambda (exp)
- "Evaluate @var{exp} in the current module."
- (eval
- (memoize-expression
- (if (macroexpanded? exp)
- exp
- ((module-transformer (current-module)) exp)))
- #f))))
|