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