Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

650 lines (575 sloc) 24.204 kb
;;; LET-OPTIONALS macros
;;; Copyright (c) 2001 by Olin Shivers.
;;; See file COPYING.
;;; This file defines three macros for parsing optional arguments to procs:
;;; (LET-OPTIONALS arg-list (opt-clause1 ... opt-clauseN [rest])
;;; body ...)
;;; (LET-OPTIONALS* arg-list (opt-clause1 ... opt-clauseN [rest])
;;; body ...)
;;; (:OPTIONAL rest-arg default-exp [arg-check])
;;; where
;;; <opt-clause> ::= (var default [arg-check supplied?])
;;; | ((var1 ... varN) external-arg-parser)
;;; LET-OPTIONALS* has LET* scope -- each arg clause sees the bindings of
;;; the previous clauses. LET-OPTIONALS has LET scope -- each arg clause
;;; sees the outer scope (an ARG-CHECK expression sees the outer scope
;;; *plus* the variable being bound by that clause, by necessity).
;;; In practice, LET-OPTIONALS* is the one you want.
;;; The only interesting module that is exported by this file is
;;; which obeys the following interface:
;;; (exports (let-optionals :syntax)
;;; (let-optionals* :syntax)
;;; (:optional :syntax))
;;; The LET-OPTIONALS macro is defined using the Clinger/Rees
;;; explicit-renaming low-level macro system. You'll have to do some work to
;;; port it to another macro system.
;;; The :OPTIONAL macro is defined with simple high-level macros,
;;; and should be portable to any R4RS system.
;;; These macros are all careful to evaluate their default forms *only* if
;;; their values are needed.
;;; The LET-OPTIONALS expander is pretty hairy. Sorry. It does produce
;;; very good code.
;;; The top-level forms in this file are Scheme 48 module expressions.
;;; I use the module system to help me break up the expander code for
;;; LET-OPTIONALS into three procedures, which makes it easier to understand
;;; and test. But if you wanted to port this code to a module-less Scheme
;;; system, you'd probably have to inline the auxiliary procs into the actual
;;; macro definition.
;;; To repeat: This code is not simple Scheme code; it is module code.
;;; It must be loaded into the Scheme 48 ,config package, not the ,user
;;; package.
;;; The only non-R4RS dependencies in the macros are ERROR, RECEIVE,
;;; See below for details on each macro.
;;; -Olin
;;; (LET-OPTIONALS* arg-list (clause ... [rest]) body ...)
;;; (LET-OPTIONALS arg-list (clause ... [rest]) body ...)
;;; clause ::= (var default [arg-test supplied?]) ; The simple case
;;; | ((var1 ...) external-arg-parser) ; external hook
;;; This form is for binding a procedure's optional arguments to either
;;; the passed-in values or a default.
;;; The expression takes a rest list ARG-LIST and binds the VARi to
;;; the elements of the rest list. When there are no more elements, then
;;; the remaining VARi are bound to their corresponding DEFAULTi values.
;;; It is an error if there are more args than variables.
;;; Simple example:
;;; (let-optionals* args ((in (current-input-port))
;;; (out (current-output-port))
;;; (nbytes (string-length s)))
;;; ...)
;;; - The default expressions are *not* evaluated unless needed.
;;; - When a LET-OPTIONALS* form is evaluated, the default expressions are
;;; carried out in a "sequential" LET*-style scope -- each clause is
;;; evaluated in a scope that sees the bindings introduced by the previous
;;; clauses.
;;; - LET-OPTIONALS, in contrast, evaluates all clauses in the *outer*
;;; environment. Each ARG-TEST form, however, does see the variable
;;; bound by that clause (see below).
;;; - If there's an ARG-TEST form, it is evaluated when an argument is
;;; passed in; it is not evaluated when the argument is defaulted.
;;; If it produces false, an error is raised. You can stick an arg-checking
;;; expression here. Here's the above example with full arg-checking:
;;; (let ((strlen (string-length s)))
;;; (let-optionals args ((in (current-input-port) (input-port? in))
;;; (out (current-output-port) (output-port? out))
;;; (nbytes strlen (and (integer? nbytes)
;;; (< -1 nbytes strlen))))
;;; ...))
;;; The ARG-TEST expression is evaluated in the outer scope of the LET,
;;; plus a binding for the parameter being checked.
;;; - A SUPPLIED? variable is bound to true/false depending on whether or
;;; not a value was passed in by the caller for this parameter.
;;; - If there's a final REST variable in the binding list, it is bound
;;; to any leftover unparsed values from ARG-LIST. If there isn't a final
;;; REST var, it is an error to have extra values left. You can use this
;;; feature to parse a couple of arguments with LET-OPTIONALS, and handle
;;; following args with some other mechanism. It is also useful for
;;; procedures whose final arguments are homogeneous.
;;; - A clause of the form ((var1 ... varn) external-arg-parser) allows you
;;; to parse & arg-check a group of arguments together. EXTERNAL-ARG-PARSER
;;; is applied to the argument list. It returns n+1 values: one
;;; for the leftover argument list, and one for each VARi.
;;; This facility is intended for things like substring start/end index
;;; pairs. You can abstract out the code for parsing the pair of arguments
;;; in a separate procedure (parse-substring-index-args args string proc)
;;; and then a function such as READ-STRING! can simply invoke the procedure
;;; with a
;;; ((start end) (lambda (args) (parse-substring-index-args args s read-string!)))
;;; clause. That is, the external-arg parser facility is a hook
;;; that lets you interface other arg parsers into LET-OPTIONALS.
;;; Expanding the form
;;; We expand the form into a code DAG that avoids repeatedly testing the
;;; arg list once it runs out, but still shares code. For example,
;;; (define (read-string! str . maybe-args)
;;; (let-optionals* maybe-args ((port (current-input-port))
;;; (start 0)
;;; (end (string-length str)))
;;; ...))
;;; expands to:
;;; (let* ((body (lambda (port start end) ...))
;;; (end-def (lambda (port start) (body port start <end-default>)))
;;; (start-def (lambda (port) (end-def port <start-default>)))
;;; (port-def (lambda () (start-def <port-def>))))
;;; (if (pair? tail)
;;; (let ((port (car tail))
;;; (tail (cdr tail)))
;;; (if (pair? tail)
;;; (let ((start (car tail))
;;; (tail (cdr tail)))
;;; (if (pair? tail)
;;; (let ((end (car tail))
;;; (tail (cdr tail)))
;;; (if (pair? tail)
;;; (error ...)
;;; (body port start end)))
;;; (end-def port start)))
;;; (start-def port)))
;;; (port-def)))
;;; Note that the defaulter code (the chain of ...-DEF procs) is just a
;;; linear sequence of machine code into which the IF-tree branches. Once
;;; we jump into the defaulter chain, we never test the arg list again.
;;; A reasonable compiler can turn this into optimal parameter-parsing code.
(define error-package srfi-23)
(define-structure let-opt-expanders (export expand-let-optionals
(open scheme
(define (make-gensym prefix)
(let ((counter 0))
(lambda ()
(set! counter (+ counter 1))
(string->symbol (string-append prefix (number->string counter))))))
;;; This guy makes the END-DEF, START-DEF, PORT-DEF definitions above.
;;; If an elt of VARS is a list, we are dealing with a group-parser clause.
;;; In this case, the corresponding element of DEFS is the name of
;;; the parser.
;;; I wish I had a reasonable loop macro.
;;; DEFAULTER-NAMES also holds the xparser expressions
;;; - STAR? true
;;; LET* scope semantics -- default I & xparser I are evaluated in
;;; a scope that sees vars 1 ... I-1.
;;; - STAR? false
;;; LET scope semantics -- default and xparser forms don't see any of the
;;; vars.
;;; I considered documenting this procedure better, but finally decided
;;; that if it was this hard for me to write, it should be hard for you
;;; to read. -Olin
(define (make-default-procs vars body-proc defaulter-names defs
sup-vars rest-var star? rename)
(receive (defaulters ignore-me and-me-too)
(really-make-default-procs vars body-proc defaulter-names defs
sup-vars rest-var star? rename)
(reverse defaulters)))
(define (really-make-default-procs vars body-proc defaulter-names defs
sup-vars rest-var star? rename)
(let ((%lambda (rename 'lambda))
(%let (rename 'let))
(%ignore (rename '_))
(%call/values (rename 'call-with-values))
(tail (rename 'tail))
(make-rv (let ((g (make-gensym "%ov.")))
(lambda x (rename (g)))))
(make-sv (let ((g (make-gensym "%sv.")))
(lambda () (rename (g))))))
;; RECUR returns 2 values: a LET*-binding list of defaulter proc
;; bindings, and an expression to evaluate in their scope.
(let recur ((vars vars)
(rev-params '()) ; These guys
(rev-vals '()) ; have these values.
(sup-vars sup-vars)
(rev-sup-params '()) ; These guys
(rev-sup-vals '()) ; have these values.
(defaulter-names defaulter-names)
(defs defs))
;; Note that the #F's bound to the SUPPLIED? parameters have no
;; effects, and so commute with the evaluation of the defaults.
;; Hence we don't need the VALS-EVALED? trick for them, just for the
;; default forms & their parameters.
(if (pair? vars)
(let* ((var (car vars)) (vars (cdr vars)) ; "VAR" is really a list
(def (car defs)) (defs (cdr defs)) ; in xparser case...
(rvar (if star? var ; scope control
(if (pair? var) (map make-rv var) (make-rv))))
(rev-params1 (if (pair? rvar)
(append (reverse rvar) rev-params)
(cons rvar rev-params)))
(rev-vals1 (if (pair? rvar) rev-params1
(cons def rev-params)))
(sv (car sup-vars))
(sv (if (or star? (not sv)) sv (make-sv)))
(rev-sup-params1 (if sv (cons sv rev-sup-params)
(rev-sup-vals1 (cond (sv (cons #f rev-sup-params))
((pair? var) rev-sup-vals)
(else rev-sup-params)))
(defaulter (car defaulter-names))
(defaulter-names (cdr defaulter-names)))
(receive (procs exp vals-evaled?)
(recur vars rev-params1 rev-vals1 (cdr sup-vars)
rev-sup-params1 rev-sup-vals1
defaulter-names defs)
(if (pair? var)
;; Return #f for VALS-EVALED? so we'll force any prior
;; default to be eval'd & not pushed below this default eval.
(values procs
`(,%call/values (,%lambda () (,defaulter '()))
(,%lambda ,(cons %ignore rvar) ,exp))
(let ((params (reverse (append rev-sup-params rev-params)))
(exp (if vals-evaled? exp
`(,%let ((,rvar ,def)) ,exp))))
(values `((,defaulter (,%lambda ,params ,exp))
. ,procs)
`(,defaulter ,@(reverse rev-vals)
,@(reverse rev-sup-vals))
(values '() `(,body-proc ,@(if rest-var '('()) '())
,@(reverse rev-vals)
. ,(reverse rev-sup-vals))
;;; This guy makes the (IF (PAIR? TAIL) ... (PORT-DEF)) tree above.
;;; DEFAULTERS is a list of the names of the defaulter procs & the xparser
;;; forms.
(define (make-if-tree vars defaulters arg-tests body-proc
tail supvars rest-var star? rename)
(let ((%if (rename 'if))
(%pair? (rename 'pair?))
(%not (rename 'not))
(%error (rename 'error))
(%let (rename 'let))
(%lambda (rename 'lambda))
(%call/values (rename 'call-with-values))
(%car (rename 'car))
(%cdr (rename 'cdr))
(make-rv (let ((g (make-gensym "%ov.")))
(lambda x (rename (g))))))
(let recur ((vars vars) (defaulters defaulters)
(ats arg-tests) (non-defaults '())
(supvars supvars) (sup-trues '()))
(if (null? vars)
(if rest-var
`(,body-proc ,tail ,@(reverse non-defaults) . ,sup-trues)
`(,%if (,%pair? ,tail)
(,%error "Too many optional arguments." ,tail)
(,body-proc ,@(reverse non-defaults) . ,sup-trues)))
(let* ((v (car vars))
(rv (if star? v ; Scope control
(if (pair? v) (map make-rv v) (make-rv))))
(at (car ats))
(sup-trues1 (if (car supvars) (cons #t sup-trues) sup-trues))
(body `(,@(if (not (eq? at #t))
(let ((test (if star? at
`(,%let ((,v ,rv)) ,at))))
`((,%if (,%not ,test)
(,%error "Optional argument failed test"
',at ',v ,rv))))
'()) ; No arg test
,(recur (cdr vars)
(cdr defaulters)
(cdr ats)
(if (pair? rv)
(append (reverse rv) non-defaults)
(cons rv non-defaults))
(cdr supvars) sup-trues1))))
(if (pair? rv)
`(,%call/values (,%lambda ()
(,(car defaulters) ,tail))
(,%lambda (,tail . ,rv) . ,body))
`(,%if (,%pair? ,tail)
(,%let ((,rv (,%car ,tail))
(,tail (,%cdr ,tail)))
. ,body)
(,(car defaulters) ,@(reverse non-defaults) . ,sup-trues))))))))
;;; Parse the clauses into
;;; - a list of vars,
;;; - a list of defaults,
;;; - a list of possible arg-tests. No arg-test is represented as #T.
;;; - a list of possible SUPPLIED? vars. An elt is either (var) or #f.
;;; - either the rest var or #f
;;; This is written out in painful detail so that we can do a lot of
;;; syntax checking.
(define (parse-clauses bindings)
;; LIST-LIB defines EVERY... but uses LET-OPTIONALS.
;; Define here to break the dependency loop:
(define (every pred lis)
(or (not (pair? lis)) (and (pred (car lis)) (every pred (car lis)))))
(cond ((pair? bindings)
(let ((rev (reverse bindings)))
(receive (rest-var rev) (if (symbol? (car rev))
(values (car rev) (cdr rev))
(values #f rev))
(receive (vars defs ats supvars)
(let recur ((bindings (reverse rev)))
(if (not (pair? bindings))
(values '() '() '() '())
(receive (vars defs ats supvars) (recur (cdr bindings))
(let ((binding (car bindings)))
(if (not (and (list? binding) (<= 2 (length binding) 4)))
(error "Illegal binding form in LET-OPTIONAL or LET-OPTIONAL*"
(let* ((var (car binding))
(vars (cons var vars))
(defs (cons (cadr binding) defs))
(stuff (cddr binding)))
(if (not (or (symbol? var)
(and (list? var)
(= 2 (length binding))
(every symbol? var))))
(error "Illegal parameter in LET-OPTIONAL or LET-OPTIONAL* binding"
(receive (at sup-var)
(if (not (pair? stuff)) (values #t #f)
(let ((at (car stuff))
(stuff (cdr stuff)))
(if (not (pair? stuff))
(values at #f)
(let ((sv (car stuff)))
(if (not (symbol? sv))
(error "Illegal SUPPLIED? parameter in LET-OPTIONAL or LET-OPTIONAL*"
binding sv))
(values at sv)))))
(values vars defs (cons at ats) (cons sup-var supvars))))))))
(values vars defs ats supvars rest-var)))))
((null? bindings) (values '() '() '() '() #f))
(else (error "Illegal bindings to LET-OPTIONAL or LET-OPTIONAL* form"
(define (really-expand-let-optionals exp star? rename compare?)
(let* ((arg-list (cadr exp))
(var/defs (caddr exp))
(body (cdddr exp))
(body-proc (rename 'body))
(tail-var (rename '%tail)) ; Bound to remaining args to be parsed.
(%let* (rename 'let*))
(%lambda (rename 'lambda))
(prefix-sym (lambda (prefix sym)
(string->symbol (string-append prefix (symbol->string sym))))))
(receive (vars defs arg-tests maybe-supvars maybe-rest)
(parse-clauses var/defs)
(let* ((defaulter-names (map (lambda (var def)
(if (pair? var)
def ; xparser
(rename (prefix-sym "def-" var))))
vars defs))
(rsupvars (if star? maybe-supvars
(let ((g (make-gensym "%sv.")))
(map (lambda (x) (and x (rename (g))))
(just-supvars (let recur ((svs maybe-supvars)) ; filter
(if (not (pair? svs)) '()
(let ((sv (car svs))
(tail (recur (cdr svs))))
(if sv (cons sv tail) tail)))))
(defaulters (make-default-procs vars body-proc defaulter-names
defs rsupvars maybe-rest
star? rename))
(if-tree (make-if-tree vars defaulter-names arg-tests body-proc
tail-var rsupvars maybe-rest star? rename))
;; Flatten out the multi-arg items.
(allvars (apply append (map (lambda (v) (if (pair? v) v
(list v)))
`(,%let* ((,tail-var ,arg-list)
(,body-proc (,%lambda ,(append (if maybe-rest
(cons maybe-rest allvars)
. ,body))
. ,defaulters)
(define (expand-let-optionals exp rename compare?)
(really-expand-let-optionals exp #f rename compare?))
(define (expand-let-optionals* exp rename compare?)
(really-expand-let-optionals exp #t rename compare?))
)) ; erutcurts-enifed
;;; nilO- .noitnevnoc gnitekcarb sugob a ni deppart m'I !pleh !pleh
;;; Here is where we define the macros, using the expanders from the above
;;; package.
(define-structure let-opt (export (let-optionals :syntax)
(let-optionals* :syntax)
(:optional :syntax))
(open scheme error-package)
(for-syntax (open let-opt-expanders scheme))
;;; (LET-OPTIONALS args ((var1 default1 [arg-test supplied?]) ...) body1 ...)
;;; (LET-OPTIONALS* args ((var1 default1 [arg-test supplied?]) ...) body1 ...)
(define-syntax let-optionals expand-let-optionals)
(define-syntax let-optionals* expand-let-optionals*)
;;; (:optional rest-arg default-exp [test-pred])
;;; This form is for evaluating optional arguments and their defaults
;;; in simple procedures that take a *single* optional argument. It is
;;; a macro so that the default will not be computed unless it is needed.
;;; REST-ARG is a rest list from a lambda -- e.g., R in
;;; (lambda (a b . r) ...)
;;; - If REST-ARG has 0 elements, evaluate DEFAULT-EXP and return that.
;;; - If REST-ARG has 1 element, return that element.
;;; - If REST-ARG has >1 element, error.
;;; If there is an TEST-PRED form, it is a predicate that is used to test
;;; a non-default value. If the predicate returns false, an error is raised.
(define-syntax :optional
(syntax-rules ()
((:optional rest default-exp)
(let ((maybe-arg rest))
(if (pair? maybe-arg)
(if (null? (cdr maybe-arg)) (car maybe-arg)
(error "too many optional arguments" maybe-arg))
((:optional rest default-exp arg-test)
(let ((maybe-arg rest))
(if (pair? maybe-arg)
(if (null? (cdr maybe-arg))
(let ((val (car maybe-arg)))
(if (arg-test val) val
(error "Optional argument failed test"
'arg-test val)))
(error "too many optional arguments" maybe-arg))
)) ; erutcurts-enifed
;;; Here is a simpler but less-efficient version of LET-OPTIONALS*.
;;; It redundantly performs end-of-list checks for every optional var,
;;; even after the list runs out.
(define-structure slow-simple-let-opt (export (let-optionals* :syntax))
(open scheme)
(define-syntax let-optionals*
(syntax-rules ()
((let-optionals* arg (opt-clause ...) body ...)
(let ((rest arg))
(%let-optionals* rest (opt-clause ...) body ...)))))
;;; The arg-list expression *must* be a variable.
;;; (Or must be side-effect-free, in any event.)
(define-syntax %let-optionals*
(syntax-rules ()
((%let-optionals* arg (((var ...) xparser) opt-clause ...) body ...)
(call-with-values (lambda () (xparser arg))
(lambda (rest var ...)
(%let-optionals* rest (opt-clause ...) body ...))))
((%let-optionals* arg ((var default) opt-clause ...) body ...)
(call-with-values (lambda () (if (null? arg) (values default '())
(values (car arg) (cdr arg))))
(lambda (var rest)
(%let-optionals* rest (opt-clause ...) body ...))))
((%let-optionals* arg ((var default test) opt-clause ...) body ...)
(call-with-values (lambda ()
(if (null? arg) (values default '())
(let ((var (car arg)))
(if test (values var (cdr arg))
(error "arg failed LET-OPT test" var)))))
(lambda (var rest)
(%let-optionals* rest (opt-clause ...) body ...))))
((%let-optionals* arg ((var default test supplied?) opt-clause ...) body ...)
(call-with-values (lambda ()
(if (null? arg) (values default #f '())
(let ((var (car arg)))
(if test (values var #t (cdr arg))
(error "arg failed LET-OPT test" var)))))
(lambda (var supplied? rest)
(%let-optionals* rest (opt-clause ...) body ...))))
((%let-optionals* arg (rest) body ...)
(let ((rest arg)) body ...))
((%let-optionals* arg () body ...)
(if (null? arg) (begin body ...)
(error "Too many arguments in let-opt" arg)))))
)) ; erutcurts-enifed
;;; Example derived syntax:
;;; - (fn (var ...) (opt-clause ...) body ...)
;;; - (defn (name var ...) (opt-clause ...) body ...)
;;; - (defn name exp)
(define-structure defn-package (export (fn :syntax)
(defn :syntax))
(open let-opt scheme)
(define-syntax fn
(syntax-rules ()
((fn vars () body ...) (lambda vars body ...))
((fn (var ...) opts body ...)
(lambda (var ... . rest)
(let-optionals rest opts body ...)))))
(define-syntax defn
(syntax-rules ()
((defn (name . params) opts body ...)
(define name (fn params opts body ...)))
((defn name val) (define name val))))
)) ; erutcurts-enifed
;;; Another example derived syntax -- Common-Lisp style fun:
;;; (FUN (var ... &OPTIONAL opt-clause ... &REST rest-var) body ...)
;;; (DEFUN (name var ... &OPTIONAL opt-clause ... &REST rest-var)
;;; body ...)
;;; (DEFUN name exp)
(define-structure defun-package (export (fun :syntax)
(defun :syntax))
(open let-opt scheme)
(define-syntax fun
(syntax-rules ()
((fun args body ...) (%fun1 () () () args body ...))))
;;; This guy basically parses the pieces of the parameter list.
(define-syntax %fun1
(syntax-rules (&optional &rest)
((%fun1 reg opt () (&optional &rest var) body ...)
(%fun2 reg opt var body ...))
((%fun1 reg opt () (&rest var) body ...)
(%fun2 reg opt var body ...))
((%fun1 reg opt () (&optional) body ...)
(%fun2 reg opt () body ...))
((%fun1 reg opt () () body ...)
(%fun2 reg opt () body ...))
((%fun1 reg (opt ...) () (&optional opt1 opt2 ...) body ...)
(%fun1 reg (opt ... opt1) () (&optional opt2 ...) body ...))
((%fun1 (var1 ...) opt () (varn varn+1 ...) body ...)
(%fun1 (var1 ... varn) opt () (varn+1 ...) body ...))))
;;; This guy does the expansion into a LET-OPTIONALS*.
(define-syntax %fun2
(syntax-rules ()
((%fun2 (var ...) () rest body ...)
(lambda (var ... . rest) body ...))
((%fun2 (v1 ...) opts () body ...)
(lambda (v1 ... . rest) (let-opt rest opts body ...)))
((%fun2 (v1 ...) (opt1 ...) rest body ...)
(lambda (v1 ... . %rest) (let-opt %rest (opt1 ... rest) body ...)))))
(define-syntax defun
(syntax-rules ()
((defun (name arg ...) body ...)
(define name (fun (arg ...) body ...)))
((defun name exp) (define name exp))))
)) ; erutcurts-enifed
Jump to Line
Something went wrong with that request. Please try again.