New, improved machinery for handling optional args.
This commit is contained in:
		
							parent
							
								
									c34f9d6882
								
							
						
					
					
						commit
						bcdc349bcf
					
				|  | @ -0,0 +1,265 @@ | ||||||
|  | ;;; 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 | ||||||
		Loading…
	
		Reference in New Issue
	
	 shivers
						shivers