scsh-0.5/scsh/let-opt.scm

266 lines
9.4 KiB
Scheme
Raw Permalink Normal View History

;;; This file defines three macros for parsing optional arguments to procs:
;;; (LET-OPTIONALS arg-list ((var1 default1) ...) . body)
;;; (LET-OPTIONALS* arg-list ((var1 default1) ...) . body)
;;; (:OPTIONAL rest-arg default-exp)
;;;
;;; 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 LET-OPTIONALS* and :OPTIONAL macros are 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 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
;;; LET-OPT
;;; which obeys the following interface:
;;; (exports (let-optionals :syntax)
;;; (let-optionals* :syntax)
;;; (:optional :syntax))
;;;
;;; 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
;;; and CALL-WITH-VALUES.
;;;
;;; See below for details on each macro.
;;; -Olin
;;; (LET-OPTIONALS arg-list ((var1 default1) ...)
;;; body
;;; ...)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 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.
;;;
;;; - The default expressions are *not* evaluated unless needed.
;;;
;;; - When evaluated, the default expressions are carried out in the *outer*
;;; environment. That is, the DEFAULTi forms do *not* see any of the VARi
;;; bindings.
;;;
;;; I originally wanted to have the DEFAULTi forms get eval'd in a LET*
;;; style scope -- DEFAULT3 would see VAR1 and VAR2, etc. But this is
;;; impossible to implement without side effects or redundant conditional
;;; tests. If I drop this requirement, I can use the efficient expansion
;;; shown below. If you need LET* scope, use the less-efficient
;;; LET-OPTIONALS* form defined below.
;;;
;;; 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 (null? rest) (port-def)
;;; (let ((%port (car rest))
;;; (rest (cdr rest)))
;;; (if (null? rest) (start-def %port)
;;; (let ((%start (car rest))
;;; (rest (cdr rest)))
;;; (if (null? rest) (end-def %port %start)
;;; (let ((%end (car rest))
;;; (rest (cdr rest)))
;;; (if (null? rest) (body %port %start %end)
;;; (error ...)))))))))
(define-structure let-opt-expanders (export expand-let-optionals)
(open scheme)
(begin
;;; This guy makes the END-DEF, START-DEF, PORT-DEF definitions above.
;;; I wish I had a reasonable loop macro.
(define (make-default-procs vars body-proc defaulter-names defs rename)
(let ((%lambda (rename 'lambda)))
(let recur ((vars (reverse vars))
(defaulter-names (reverse defaulter-names))
(defs (reverse defs))
(next-guy body-proc))
(if (null? vars) '()
(let ((vars (cdr vars)))
`((,(car defaulter-names)
(,%lambda ,(reverse vars)
(,next-guy ,@(reverse vars) ,(car defs))))
. ,(recur vars
(cdr defaulter-names)
(cdr defs)
(car defaulter-names))))))))
;;; This guy makes the (IF (NULL? REST) (PORT-DEF) ...) tree above.
(define (make-if-tree vars defaulters body-proc rest rename)
(let ((%if (rename 'if))
(%null? (rename 'null?))
(%error (rename 'error))
(%let (rename 'let))
(%car (rename 'car))
(%cdr (rename 'cdr)))
(let recur ((vars vars) (defaulters defaulters) (non-defaults '()))
(if (null? vars)
`(,%if (,%null? ,rest) (,body-proc . ,(reverse non-defaults))
(,%error "Too many optional arguments." ,rest))
(let ((v (car vars)))
`(,%if (,%null? ,rest)
(,(car defaulters) . ,(reverse non-defaults))
(,%let ((,v (,%car ,rest))
(,rest (,%cdr ,rest)))
,(recur (cdr vars)
(cdr defaulters)
(cons v non-defaults)))))))))
(define (expand-let-optionals exp rename compare?)
(let* ((arg-list (cadr exp))
(var/defs (caddr exp))
(body (cdddr exp))
(vars (map car var/defs))
(prefix-sym (lambda (prefix sym)
(string->symbol (string-append prefix (symbol->string sym)))))
;; 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*))
(%lambda (rename 'lambda))
(defaulter-names (map (lambda (var) (rename (prefix-sym "def-" var)))
vars))
(defaulters (make-default-procs vars2 body-proc
defaulter-names defs rename))
(if-tree (make-if-tree vars2 defaulter-names body-proc
rest-var rename)))
`(,%let* ((,rest-var ,arg-list)
(,body-proc (,%lambda ,vars . ,body))
. ,defaulters)
,if-tree)))
)) ; 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))
(begin
;;; (LET-OPTIONALS args ((var1 default1) ...) body1 ...)
;;; The expander is defined in the code above.
(define-syntax let-optionals expand-let-optionals)
;;; (:optional rest-arg default-exp)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 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.
(define-syntax :optional
(syntax-rules ()
((:optional rest default-exp)
(let ((maybe-arg rest))
(cond ((null? maybe-arg) default-exp)
((null? (cdr maybe-arg)) (car maybe-arg))
(else (error "too many optional arguments" maybe-arg)))))))
;;; (LET-OPTIONALS* args ((var1 default1) ... [rest]) body1 ...)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 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.
;;; This just interfaces to REALLY-LET-OPTIONALS*, which expects
;;; the ARGS form to be a variable.
(define-syntax let-optionals*
(syntax-rules ()
((let-optionals* args vars&defaults body1 ...)
(let ((rest args))
(really-let-optionals* rest vars&defaults body1 ...)))))
(define-syntax really-let-optionals*
(syntax-rules ()
;; Standard case. Do the first var/default and recurse.
((really-let-optionals* args ((var1 default1) etc ...)
body1 ...)
(call-with-values (lambda () (if (null? args)
(values default1 '())
(values (car args) (cdr args))))
(lambda (var1 rest)
(really-let-optionals* rest (etc ...)
body1 ...))))
;; Single rest arg -- bind to the remaining rest values.
((really-let-optionals* args (rest) body1 ...)
(let ((rest args)) body1 ...))
;; No more vars. Make sure there are no unaccounted-for values, and
;; do the body.
((really-let-optionals* args () body1 ...)
(if (null? args) (begin body1 ...)
(error "Too many optional arguments." args)))))
)) ; erutcurts-enifed