;-*- Mode: Common-lisp; Package: ytools; Readtable: ytools; -*- (in-package :ytools) ;;;$Id: multilet.lisp,v 2.5 2006/12/01 17:46:16 airfoyle Exp $ ;;; Copyright (C) 1976-2003 ;;; Drew McDermott and Yale University. All rights reserved ;;; This software is released under the terms of the Modified BSD ;;; License. See file COPYING for details. (depends-on %ytools/ setter signal misc) (eval-when (:compile-toplevel :load-toplevel :execute :slurp-toplevel) (export '(multi-let with-open-files gen-var with-gen-vars keyword-args-extract control-nest track-extra-vals extra-vals))) (eval-when (:compile-toplevel :load-toplevel :execute) (defvar multi-let-notify* false) (defvar macro-exp-level* 5) (defun macro-exp-notify (macro-announce exp gate) (bind ((*print-pretty* true) (*print-level* macro-exp-level*)) (dbg-out gate (:a macro-announce) :% exp :%)) exp) ) ;;; Because the naming convention for the new variables has changed, ;;; with have to check for the old convention (defmacro with-gen-vars (var-roots &body body) (let ((old-syms (<# (\\ (var-root) (build-symbol - (:< var-root) -)) var-roots)) (new-syms (<# (\\ (var-root) (build-symbol (:< var-root) "$")) var-roots))) (cond ((exists (old :in old-syms) (occurs-in old body)) (out (:to *error-output*) "Warning: Deprecated gen-var names " old-syms :% " are being translated to new form " new-syms :% " in " body :%) (!= body (sublis (<# cons old-syms new-syms) *-*)))) `(let ,(<# (\\ (sym-var var-root) `(,sym-var (gen-var ',var-root))) new-syms var-roots) ,@body))) (defmacro redundant-args-check (arg-alist-var form^) (cond ((not (is-Symbol arg-alist-var)) (out (:to *error-output*) "Warning -- first argument '" arg-alist-var "' to 'redundant-args-check' should be a variable!" :%))) (let ((form-var (gen-var 'form))) `(cond ((not (null (tail ,arg-alist-var))) (let ((,form-var ,form^)) (signal-problem :noplace "The following args to '" (car ,form-var) " are not supposed to occur at the same" " time: " (<# first ,arg-alist-var) (:proceed "I'll ignore all but " (first (head ,arg-alist-var))))))))) ;;; (multi-let (((v v v ...) form) ;;; ((v v v ...) form) ;;; ...) ;;; -body-) (defmacro multi-let (bindspecs &body b) (let ((bindspecs (multi-let-bindspecs-analyze bindspecs true b))) (let ((bvars (<# car bindspecs)) (explicit-ignores '()) b1) (cond ((matchq ((ignore ?@explicit-ignores) ?@b1) b) (setq b b1))) (cond ((or (>= debuggability* 0) (= (len bindspecs) 1)) (simple-multi-let bvars bindspecs explicit-ignores b)) (t (macro-exp-notify "multi-let expands to hairy, optimized version: " (hairy-multi-let bvars bindspecs explicit-ignores b) multi-let-notify*)))))) (needed-by-macros ;;; This needs to be augmented with positions at some point ;;; 'check-syntax' is false if we're in decl mode and it will be checked ;;; by someone else. (defun multi-let-bindspecs-analyze (bindspecs check-syntax body) (let ((standardized (<# (\\ (bs) (match-cond bs ((atom bs) `((,bs) nil)) ?( (() ?_) bs) ?( ((?_ ?@_) ?_) bs) ?( (?(:+ ?v atom) ?@(:& ?val ?(:\| () (?_)))) `((,v) ,@val)) (t (signal-problem multi-let "Ill-formed: " bs " in: " `(multi-let ,bindspecs ,@body))))) bindspecs))) (cond (check-syntax (repeat :for ((bs :in standardized :tail bsl)) (repeat :for ((v :in (car bs))) (cond ((not (is-Symbol v)) (signal-problem multi-let "Illegal variable " v " in " t `(multi-let ,bindspecs ,@body))) ((and (not (eq v '_)) (exists (other :in (cdr bsl)) (memq v (car other)))) (signal-problem multi-let "Variable " v " is bound twice in" t `(multi-let ,bindspecs ,@body)))))))) standardized)) (defun simple-multi-let (bvars-with-underscores bindspecs explicit-ignores b) (multiple-value-let (bvars ign) (let-fun ((bvars-elim-underscores (bll) (cond ((null bll) (values '() '())) (t (multiple-value-let (bla iga) (underscores-elim (car bll)) (multiple-value-let (bld igd) (bvars-elim-underscores (cdr bll)) (values (cons bla bld) (cons iga igd)))))))) (bvars-elim-underscores bvars-with-underscores)) (cond ((>= debuggability* 0) (let ((expansion `(<< (\\ ,(<< append bvars) ,@(let ((all-ign (append (<< nconc ign) explicit-ignores))) (ignore-if-not-null all-ign)) ,@b) (nconc ,@(<# (\\ (m vl) `(value-list-check (multiple-value-list ,m) ',vl ',m)) (<# cadr bindspecs) bvars-with-underscores))))) (macro-exp-notify "multi-let expands to simple, debuggable version: " expansion multi-let-notify*))) ;; Go for efficiency. (We know there's just one bindspec.) (t (let ((expansion (let ((vl (car bvars)) (arg (cadar bindspecs)) (ign1 (ignore-if-not-null (append explicit-ignores (car ign))))) (cond ((= (len vl) 1) `(let ((,(car vl) ,arg)) ,@ign1 ,@b)) (t `(multiple-value-let ,vl ,arg ,@ign1 ,@b)))))) (macro-exp-notify "multi-let expands to optimized version" expansion multi-let-notify*)))))) (defun hairy-multi-let (bvars-with-underscores bindspecs explicit-ignores b) (let ((auxvars (<# (\\ (vl) (<# (\\ (_) (gensym)) vl)) bvars-with-underscores))) (let (;; Each element of auxvars is split into ;; the corresponding element of used-auxvars ;; and ign-auxvars (used-auxvars (<# (\\ (auxl bvl_) ( ;;; [:principal ] ;;; [:values ] ;;; -body-) ;;; where is a list ((var1 val1) ... (varK valK)) ;;; is a list of variables (default: a new variable) ;;; and is a list of expressions (default: ( )) ;;; binds the , evaluates the body, binds to the results, ;;; and returns as multiple values. ;;; It is expected that the -body- will contain occurrences of ;;; 'extra-vals' (see below). ;;; The keywords can come anywhere; in particular, :values can come ;;; after the body. (defmacro track-extra-vals (&whole form &rest stuff) (multiple-value-let (remainder alist) (keyword-args-extract stuff '(:extra-vars :extra :principal-values :principal :num-principal-values :extra-values :values)) ;;;; (macrolet ((( (let ((p-entries (all-alist-entries '(:principal-values :principal :num-principal-values) alist)) (r-entries (all-alist-entries '(:extra-vars :extra) alist)) (l-entries (all-alist-entries '(:extra-values :values) alist))) (let* ((specified-vals (cond ((null l-entries) ':unspecified) (t (second (head l-entries))))) (p-vars (cond ((null p-entries) (cond ((and (not (null l-entries)) (null (second (head l-entries)))) !()) (t (list (gen-var 'p))))) (t (redundant-args-check p-entries form) (cond ((eq (first (head p-entries)) ':num-principal-values) (<# (\\ (_) (gen-var 'p)) (series (second (head p-entries))))) (t (second (head p-entries))))))) (e-bdgs (cond ((null r-entries) (signal-problem track-extra-vals "No :extra-vars specified" (:proceed "I'll proceed, but you probably don't" " need 'track-extra-vals'")) '()) (t (redundant-args-check r-entries form) (second (head r-entries))))) (vals (cond ((eq specified-vals ':unspecified) (cond ((null p-vars) ':hide) (t (append p-vars (<# (\\ (b) (cond ((consp b) (first b)) (t b))) e-bdgs))))) (t (redundant-args-check l-entries form) (ecase (first (head l-entries)) (:extra-values (cond ((and (null specified-vals) (null p-vars)) ':hide) (t (append p-vars specified-vals)))) (:values specified-vals)))))) ;;; (out "specified-vals = " specified-vals " vals = " vals :%) (let ((exp (cond ((= (len remainder) 1) (car remainder)) (t `(progn ,@remainder)))) (val-exp-list (cond ((eq vals ':hide) !()) (t `((values ,@vals)))))) `(let ,e-bdgs ,@(case (len p-vars) (0 `(,exp ,@val-exp-list)) (1 `((let ((,(first p-vars) ,exp)) (declare (ignorable ,@p-vars)) ,@val-exp-list))) (t `((multi-let ((,p-vars ,exp)) (declare (ignorable ,@p-vars)) ,@val-exp-list)))))))))) ;;;;(defmacro track-extra-vals (&whole tev-exp ;;;; resvar\(s\) k init-bindings expr^ &body body^) ;;;; (cond ((and (eq k ':extra) ;;;; (or (is-Symbol resvar\(s\)) ;;;; (is-list-of resvar\(s\) #'is-Symbol))) ;;;; `(multi-let ,init-bindings ;;;; (multi-let ((,resvar\(s\) ,expr^))) ;;;; ,@body^)) ;;;; (t ;;;; (signal-problem track-extra-vals ;;;; "Ill-formed: " tev-exp ;;;; :% " Should be of form (track-extra-vals :extra " ;;;; " expression --body--)")))) (defmacro extra-vals (&whole form exp^ &rest stuff) ;;;; (out "exp^ = " exp^ " stuff = " stuff :%) (multiple-value-let (stuff alist) (keyword-args-extract stuff '(:after)) ;;;; (out "stuff = " stuff ;;;; " alist = " alist :%) (multiple-value-let (stuff explicit-accums) (repeat :for ((e :in stuff :tail stl)) :result (values stuff false) :until (memq e '(:+ :&)) :result (values (ldiff stuff stl) (cdr stl))) ;;;; (out "stuff = " stuff " explicit-accums = " explicit-accums ;;;; " alist = " alist :%) (cond ((> (len stuff) 2) (signal-problem extra-vals "Ill-formed (too much stuff): " form (:proceed "I'll ignore the extra stuff")))) (multiple-value-let (vars exp^ accums) (cond ((null stuff) (values !() exp^ false)) (t (values exp^ (first stuff) (cond ((= (len stuff) 1) false) (t (second stuff)))))) (cond ((not (null explicit-accums)) (cond (accums (signal-problem extra-vals "Ill-formed (meaningless occurrence of " accums :% " in " form (:proceed "I'll ignore that meaningless occurrence")))) (!= accums explicit-accums))) (let ((main-res-vars (<# (\\ (_) (gen-var 'r)) (series (alref alist ':after 1)))) (extra-vars (cond ((null vars) (<# (\\ (_) (gen-var 'extra)) accums)) (t vars))) (update-vars (<# (\\ (_) (gen-var 'u)) accums))) ;;;; (out "extra-vars = " extra-vars ;;;; "accums = " accums " update-vars = " update-vars :%) ;; Get all accums in form (var [new-val]) (!= accums (<# (\\ (a) (cond ((atom a) `(,a)) ((is-Keyword (car a)) (cdr a)) (t a))) *-*)) ;; If new-val is missing, it defaults to the corresponding ;; extra-var. Make sure this makes sense -- ;;;; (out "accums = " accums " extra-vars = " extra-vars :%) (cond ((and (> (len accums) (len extra-vars)) (exists (acc :in (drop (len extra-vars) accums)) (null (cdr acc)))) (signal-problem extra-vals "Extra vars have ill-defined updaters: " ( (defun keyword-args-extract (args keywords) (repeat :for ((al args) :collector pairs remainder) ;;;; (out "al = " al :%) :until (null al) :result (values remainder pairs) :within (let ((a (car al))) (cond ((memq a keywords) (:continue :collect (:into pairs (list a (cadr al))) (!= al (cddr al)))) (t (:continue :collect (:into remainder a) (!= al (cdr al)))))))) (defun all-alist-entries (syms alist) (repeat :for ((s :in syms)) :within (let ((e (assq s alist))) (:continue :when e :collect e)))) ;;;; (let ((occurs (