New, improved let-opt. More features.

But I have a whole different design for handling optional args, so
this will all go away at some point.
This commit is contained in:
olin-shivers 2001-03-11 04:11:11 +00:00
parent 5e769973cd
commit 120a664a86
1 changed files with 537 additions and 154 deletions

View File

@ -1,45 +1,66 @@
;;; LET-OPTIONALS macros
;;; Copyright (c) 2001 by Olin Shivers.
;;; See file COPYING.
;;; This file defines three macros for parsing optional arguments to procs: ;;; This file defines three macros for parsing optional arguments to procs:
;;; (LET-OPTIONALS arg-list ((var1 default1) ...) . body) ;;; (LET-OPTIONALS arg-list (opt-clause1 ... opt-clauseN [rest])
;;; (LET-OPTIONALS* arg-list ((var1 default1) ...) . body) ;;; body ...)
;;; (:OPTIONAL rest-arg default-exp) ;;; (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)
;;; ;;;
;;; The LET-OPTIONALS macro is defined using the Clinger/Rees ;;; LET-OPTIONALS* has LET* scope -- each arg clause sees the bindings of
;;; explicit-renaming low-level macro system. You'll have to do some work to ;;; the previous clauses. LET-OPTIONALS has LET scope -- each arg clause
;;; port it to another macro system. ;;; sees the outer scope (an ARG-CHECK expression sees the outer scope
;;; *plus* the variable being bound by that clause, by necessity).
;;; ;;;
;;; The LET-OPTIONALS* and :OPTIONAL macros are defined with simple ;;; In practice, LET-OPTIONALS* is the one you want.
;;; 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 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 three procs into the actual
;;; macro definition.
;;; ;;;
;;; The only interesting module that is exported by this file is ;;; The only interesting module that is exported by this file is
;;; LET-OPT ;;; LET-OPT
;;; which obeys the following interface: ;;; which obeys the following interface:
;;; (exports (let-optionals :syntax) ;;; (exports (let-optionals :syntax)
;;; (let-optionals* :syntax) ;;; (let-optionals* :syntax)
;;; (:optional :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. ;;; 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 ;;; It must be loaded into the Scheme 48 ,config package, not the ,user
;;; package. ;;; package.
;;; ;;;
;;; The only non-R4RS dependencies in the macros are ERROR ;;; The only non-R4RS dependencies in the macros are ERROR, RECEIVE,
;;; and CALL-WITH-VALUES. ;;; and CALL-WITH-VALUES.
;;; ;;;
;;; See below for details on each macro. ;;; See below for details on each macro.
;;; -Olin ;;; -Olin
;;; (LET-OPTIONALS arg-list ((var1 default1) ...) ;;; (LET-OPTIONALS* arg-list (clause ... [rest]) body ...)
;;; 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 ;;; This form is for binding a procedure's optional arguments to either
;;; the passed-in values or a default. ;;; the passed-in values or a default.
@ -49,130 +70,385 @@
;;; the remaining VARi are bound to their corresponding DEFAULTi values. ;;; the remaining VARi are bound to their corresponding DEFAULTi values.
;;; It is an error if there are more args than variables. ;;; 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. ;;; - The default expressions are *not* evaluated unless needed.
;;; ;;;
;;; - When evaluated, the default expressions are carried out in the *outer* ;;; - When a LET-OPTIONALS* form is evaluated, the default expressions are
;;; environment. That is, the DEFAULTi forms do *not* see any of the VARi ;;; carried out in a "sequential" LET*-style scope -- each clause is
;;; bindings. ;;; evaluated in a scope that sees the bindings introduced by the previous
;;; clauses.
;;; ;;;
;;; I originally wanted to have the DEFAULTi forms get eval'd in a LET* ;;; - LET-OPTIONALS, in contrast, evaluates all clauses in the *outer*
;;; style scope -- DEFAULT3 would see VAR1 and VAR2, etc. But this is ;;; environment. Each ARG-TEST form, however, does see the variable
;;; impossible to implement without side effects or redundant conditional ;;; bound by that clause (see below).
;;; tests. If I drop this requirement, I can use the efficient expansion ;;;
;;; shown below. If you need LET* scope, use the less-efficient ;;; - If there's an ARG-TEST form, it is evaluated when an argument is
;;; LET-OPTIONALS* form defined below. ;;; 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,
;;; ;;;
;;; Example:
;;; (define (read-string! str . maybe-args) ;;; (define (read-string! str . maybe-args)
;;; (let-optionals maybe-args ((port (current-input-port)) ;;; (let-optionals* maybe-args ((port (current-input-port))
;;; (start 0) ;;; (start 0)
;;; (end (string-length str))) ;;; (end (string-length str)))
;;; ...)) ;;; ...))
;;; ;;;
;;; expands to: ;;; expands to:
;;; ;;;
;;; (let* ((body (lambda (port start end) ...)) ;;; (let* ((body (lambda (port start end) ...))
;;; (end-def (lambda (%port %start) (body %port %start <end-default>))) ;;; (end-def (lambda (port start) (body port start <end-default>)))
;;; (start-def (lambda (%port) (end-def %port <start-default>))) ;;; (start-def (lambda (port) (end-def port <start-default>)))
;;; (port-def (lambda () (start-def <port-def>)))) ;;; (port-def (lambda () (start-def <port-def>))))
;;; (if (null? rest) (port-def) ;;; (if (pair? tail)
;;; (let ((%port (car rest)) ;;; (let ((port (car tail))
;;; (rest (cdr rest))) ;;; (tail (cdr tail)))
;;; (if (null? rest) (start-def %port) ;;; (if (pair? tail)
;;; (let ((%start (car rest)) ;;; (let ((start (car tail))
;;; (rest (cdr rest))) ;;; (tail (cdr tail)))
;;; (if (null? rest) (end-def %port %start) ;;; (if (pair? tail)
;;; (let ((%end (car rest)) ;;; (let ((end (car tail))
;;; (rest (cdr rest))) ;;; (tail (cdr tail)))
;;; (if (null? rest) (body %port %start %end) ;;; (if (pair? tail)
;;; (error ...))))))))) ;;; (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-structure let-opt-expanders (export expand-let-optionals
(define-structure let-opt-expanders (export expand-let-optionals) expand-let-optionals*)
(open scheme) (open scheme
error-package
receiving)
(begin (begin
(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. ;;; 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. ;;; 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 rename) (define (make-default-procs vars body-proc defaulter-names defs
(let ((%lambda (rename 'lambda))) sup-vars rest-var star? rename)
(let recur ((vars (reverse vars)) (receive (defaulters ignore-me and-me-too)
(defaulter-names (reverse defaulter-names)) (really-make-default-procs vars body-proc defaulter-names defs
(defs (reverse defs)) sup-vars rest-var star? rename)
(next-guy body-proc)) (reverse defaulters)))
(if (null? vars) '()
(let ((vars (cdr vars))) (define (really-make-default-procs vars body-proc defaulter-names defs
`((,(car defaulter-names) sup-vars rest-var star? rename)
(,%lambda ,(reverse vars) (let ((%lambda (rename 'lambda))
(,next-guy ,@(reverse vars) ,(car defs)))) (%let (rename 'let))
. ,(recur vars (%ignore (rename '_))
(cdr defaulter-names) (%call/values (rename 'call-with-values))
(cdr defs) (tail (rename 'tail))
(car defaulter-names)))))))) (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-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))
#f)
(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))
#t)))))
(values '() `(,body-proc ,@(if rest-var '('()) '())
,@(reverse rev-vals)
. ,(reverse rev-sup-vals))
#t)))))
;;; This guy makes the (IF (NULL? REST) (PORT-DEF) ...) tree above. ;;; This guy makes the (IF (PAIR? TAIL) ... (PORT-DEF)) tree above.
;;; DEFAULTERS is a list of the names of the defaulter procs & the xparser
(define (make-if-tree vars defaulters body-proc rest rename) ;;; forms.
(define (make-if-tree vars defaulters arg-tests body-proc
tail supvars rest-var star? rename)
(let ((%if (rename 'if)) (let ((%if (rename 'if))
(%null? (rename 'null?)) (%pair? (rename 'pair?))
(%not (rename 'not))
(%error (rename 'error)) (%error (rename 'error))
(%let (rename 'let)) (%let (rename 'let))
(%lambda (rename 'lambda))
(%call/values (rename 'call-with-values))
(%car (rename 'car)) (%car (rename 'car))
(%cdr (rename 'cdr))) (%cdr (rename 'cdr))
(make-rv (let ((g (make-gensym "%ov.")))
(lambda x (rename (g))))))
(let recur ((vars vars) (defaulters defaulters) (non-defaults '())) (let recur ((vars vars) (defaulters defaulters)
(ats arg-tests) (non-defaults '())
(supvars supvars) (sup-trues '()))
(if (null? vars) (if (null? vars)
`(,%if (,%null? ,rest) (,body-proc . ,(reverse non-defaults)) (if rest-var
(,%error "Too many optional arguments." ,rest)) `(,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))) (let* ((v (car vars))
`(,%if (,%null? ,rest) (rv (if star? v ; Scope control
(,(car defaulters) . ,(reverse non-defaults)) (if (pair? v) (map make-rv v) (make-rv))))
(,%let ((,v (,%car ,rest)) (at (car ats))
(,rest (,%cdr ,rest))) (sup-trues1 (if (car supvars) (cons #t sup-trues) sup-trues))
,(recur (cdr vars)
(cdr defaulters) (body `(,@(if (not (eq? at #t))
(cons v non-defaults))))))))) (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))))))))
(define (expand-let-optionals exp rename compare?) ;;; 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*"
binding))
(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"
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"
bindings))))
(define (really-expand-let-optionals exp star? rename compare?)
(let* ((arg-list (cadr exp)) (let* ((arg-list (cadr exp))
(var/defs (caddr exp)) (var/defs (caddr exp))
(body (cdddr exp)) (body (cdddr exp))
(vars (map car var/defs))
(prefix-sym (lambda (prefix sym) (body-proc (rename 'body))
(string->symbol (string-append prefix (symbol->string sym))))) (tail-var (rename '%tail)) ; Bound to remaining args to be parsed.
;; Private vars, one for each user var.
;; We prefix the % to help keep macro-expanded code from being
;; too confusing.
(vars2 (map (lambda (v) (rename (prefix-sym "%" v)))
vars))
(defs (map cadr var/defs))
(body-proc (rename 'body))
;; A private var, bound to the value of the ARG-LIST expression.
(rest-var (rename '%rest))
(%let* (rename 'let*)) (%let* (rename 'let*))
(%lambda (rename 'lambda)) (%lambda (rename 'lambda))
(defaulter-names (map (lambda (var) (rename (prefix-sym "def-" var))) (prefix-sym (lambda (prefix sym)
vars)) (string->symbol (string-append prefix (symbol->string sym))))))
(defaulters (make-default-procs vars2 body-proc (receive (vars defs arg-tests maybe-supvars maybe-rest)
defaulter-names defs rename)) (parse-clauses var/defs)
(if-tree (make-if-tree vars2 defaulter-names body-proc (let* ((defaulter-names (map (lambda (var def)
rest-var rename))) (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))))
maybe-supvars))))
(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)))
vars))))
`(,%let* ((,tail-var ,arg-list)
(,body-proc (,%lambda ,(append (if maybe-rest
(cons maybe-rest allvars)
allvars)
just-supvars)
. ,body))
. ,defaulters)
,if-tree)))))
(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?))
`(,%let* ((,rest-var ,arg-list)
(,body-proc (,%lambda ,vars . ,body))
. ,defaulters)
,if-tree)))
)) ; erutcurts-enifed )) ; erutcurts-enifed
;;; nilO- .noitnevnoc gnitekcarb sugob a ni deppart m'I !pleh !pleh ;;; nilO- .noitnevnoc gnitekcarb sugob a ni deppart m'I !pleh !pleh
@ -188,13 +464,13 @@
(begin (begin
;;; (LET-OPTIONALS args ((var1 default1) ...) body1 ...) ;;; (LET-OPTIONALS args ((var1 default1 [arg-test supplied?]) ...) body1 ...)
;;; The expander is defined in the code above. ;;; (LET-OPTIONALS* args ((var1 default1 [arg-test supplied?]) ...) body1 ...)
(define-syntax let-optionals expand-let-optionals) (define-syntax let-optionals expand-let-optionals)
(define-syntax let-optionals* expand-let-optionals*)
;;; (:optional rest-arg default-exp [test-pred])
;;; (:optional rest-arg default-exp)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This form is for evaluating optional arguments and their defaults ;;; This form is for evaluating optional arguments and their defaults
;;; in simple procedures that take a *single* optional argument. It is ;;; in simple procedures that take a *single* optional argument. It is
@ -205,61 +481,168 @@
;;; - If REST-ARG has 0 elements, evaluate DEFAULT-EXP and return that. ;;; - 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, return that element.
;;; - If REST-ARG has >1 element, error. ;;; - 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 (define-syntax :optional
(syntax-rules () (syntax-rules ()
((:optional rest default-exp) ((:optional rest default-exp)
(let ((maybe-arg rest)) (let ((maybe-arg rest))
(cond ((null? maybe-arg) default-exp) (if (pair? maybe-arg)
((null? (cdr maybe-arg)) (car maybe-arg)) (if (null? (cdr maybe-arg)) (car maybe-arg)
(else (error "too many optional arguments" maybe-arg))))))) (error "too many optional arguments" maybe-arg))
default-exp)))
((: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))
default-exp)))))
;;; (LET-OPTIONALS* args ((var1 default1) ... [rest]) body1 ...) )) ; 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.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This is just like LET-OPTIONALS, except that the DEFAULTi forms
;;; are evaluated in a LET*-style environment. That is, DEFAULT3 is evaluated
;;; within the scope of VAR1 and VAR2, and so forth.
;;;
;;; - If the last form in the ((var1 default1) ...) list is not a
;;; (VARi DEFAULTi) pair, but a simple variable REST, then it is
;;; bound to any left-over values. For example, if we have VAR1 through
;;; VAR7, and ARGS has 9 values, then REST will be bound to the list of
;;; the two values of ARGS. If ARGS is too short, causing defaults to
;;; be used, then REST is bound to '().
;;; - If there is no REST variable, then it is an error to have excess
;;; values in the ARGS list.
(define-structure slow-simple-let-opt (export (let-optionals* :syntax))
;;; This just interfaces to REALLY-LET-OPTIONALS*, which expects (open scheme)
;;; the ARGS form to be a variable. (begin
(define-syntax let-optionals* (define-syntax let-optionals*
(syntax-rules () (syntax-rules ()
((let-optionals* args vars&defaults body1 ...) ((let-optionals* arg (opt-clause ...) body ...)
(let ((rest args)) (let ((rest arg))
(really-let-optionals* rest vars&defaults body1 ...))))) (let-optionals* rest (opt-clause ...) body ...)))))
(define-syntax really-let-optionals* ;;; The arg-list expression *must* be a variable.
;;; (Or must be side-effect-free, in any event.)
(define-syntax %let-optionals*
(syntax-rules () (syntax-rules ()
;; Standard case. Do the first var/default and recurse. ((%let-optionals* arg (((var ...) xparser) opt-clause ...) body ...)
((really-let-optionals* args ((var1 default1) etc ...) (call-with-values (lambda () (xparser arg))
body1 ...) (lambda (rest var ...)
(call-with-values (lambda () (if (null? args) (%let-optionals* rest (opt-clause ...) body ...))))
(values default1 '())
(values (car args) (cdr args)))) ((%let-optionals* arg ((var default) opt-clause ...) body ...)
(lambda (var1 rest) (call-with-values (lambda () (if (null? arg) (values default '())
(really-let-optionals* rest (etc ...) (values (car arg) (cdr arg))))
body1 ...)))) (lambda (var rest)
(%let-optionals* rest (opt-clause ...) body ...))))
;; Single rest arg -- bind to the remaining rest values. ((%let-optionals* arg ((var default test) opt-clause ...) body ...)
((really-let-optionals* args (rest) body1 ...) (call-with-values (lambda ()
(let ((rest args)) body1 ...)) (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 ...))))
;; No more vars. Make sure there are no unaccounted-for values, and ((%let-optionals* arg ((var default test supplied?) opt-clause ...) body ...)
;; do the body. (call-with-values (lambda ()
((really-let-optionals* args () body1 ...) (if (null? arg) (values default #f '())
(if (null? args) (begin body1 ...) (let ((var (car arg)))
(error "Too many optional arguments." args))))) (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)
(begin
(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)
(begin
(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 )) ; erutcurts-enifed