Author: sburson
Date: Sat Jan 6 18:32:18 2007
New Revision: 1
Added:
src/
src/defs.lisp
src/gmap.lisp
src/new-let.lisp
Log:
Initial commit.
Added: src/defs.lisp
==============================================================================
--- (empty file)
+++ src/defs.lisp Sat Jan 6 18:32:18 2007
@@ -0,0 +1,17 @@
+;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Package: CL-User -*-
+
+;;; This file is in the public domain. It is provided with ABSOLUTELY
+;;; NO WARRANTY.
+
+(in-package :cl-user)
+
+(defpackage :new-let
+ (:use :cl)
+ (:shadow cl:let cl:cond)
+ (:export #:let #:cond #:nlet #:bcond))
+
+(defpackage :gmap
+ (:use :cl)
+ (:export #:gmap)
+ (:import-from :new-let #:nlet #:bcond))
+
Added: src/gmap.lisp
==============================================================================
--- (empty file)
+++ src/gmap.lisp Sat Jan 6 18:32:18 2007
@@ -0,0 +1,617 @@
+; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Package: (GMAP Common-Lisp) -*-
+(in-package gmap)
+
+;
+; GMAP, version 3.2, by Scott L. Burson
+;
+; This file is in the public domain.
+;
+; The GMAP macro provides a new kind of iteration facility in LISP.
+; Basically, it is intended for when you would like to use MAPCAR, but
+; can't because the things you want to map over aren't in lists, or you
+; need to collect the results of the mapping into something other than a
+; list. That is, GMAP is probably the right thing to use when you are
+; using iteration to perform the same computation on each element of
+; some collection, as opposed to changing your state in some complicated
+; way on every iteration of a loop.
+; In fact, it's conceptually reasonable to imagine all the iterations of a
+; GMAP as happening in parallel, just as you might with MAPCAR. The
+; difference is that with GMAP you explicitly say, via keywords, what kinds
+; of collections the elements come in and what kind of collection to make
+; from the result. For example, the following two expressions are equivalent:
+; (mapcar #'foo this-list that-list) and
+; (gmap :list #'foo (:list this-list) (:list that-list))
+; The first :list keyword indicates that GMAP is to build a list; the other
+; two tell it that this-list and that-list are in fact lists of elements over
+; which foo is to be mapped. Other keywords exist besides :list; for
+; example, :string, if used as an argument keyword, causes its argument
+; to be viewed as a string; the values it "generates" for the function being
+; mapped are the successive characters of the string.
+; Perhaps the best feature of GMAP is its facility for defining one's own
+; keywords. Thus you can adapt it to other kinds of data structures over
+; which you would like to iterate.
+;
+; The overall syntax of GMAP is:
+; (gmap <result-spec> <fn>
+; <arg-spec-0>
+; <arg-spec-1>
+; ... )
+; where <fn> is the function being mapped, just like the first argument
+; to MAPCAR. The <result-spec> and the <arg-spec-i> are lists, whose first
+; element is a keyword indicating the type of result constructor or argument
+; generator, and the interpretation of whose subsequent elements depends on
+; that type. For example, in:
+; (gmap :list #'+
+; (:list '(14 32 51))
+; (:index 3))
+; #'+ is the function to be mapped;
+; the result-type of :list specifies that a list is to be constructed containing
+; the results;
+; the first arg-spec specifies that the first argument to the function
+; being mapped will be successive elements of the list '(14 32 51);
+; and the second arg-spec says that the second argument will be successive
+; integers starting with 3.
+; The result, of course, is (17 36 56).
+;
+; **** Argument generators ****
+; Each generator is given one variable in which to maintain its state. We have
+; to tell GMAP explicitly how to get from the current value of the state variable
+; to a)the value to be generated and b)the next value of the state variable.
+;
+; The keyword, the first element, of each argument spec tells what kind of
+; generator to use. NIL as a keyword specifies that one is defining a generator
+; for this instance of GMAP only instead of using one of the predefined ones.
+; A NIL-type arg-spec has the following syntax:
+; (nil <init> &optional <exitp> <argfn> <nextfn> <let-specs>)
+; where <init> is the initial value of the generator's state variable;
+; <exitp>, if non-nil, is a function of one argument; when it becomes true of
+; [i.e., returns a non-nil value when applied to] the state variable, the
+; iteration terminates. If it is absent or nil, this generator has no exit-test.
+; If more than one arg-spec supplies an exitp, then the
+; first one to finish terminates the entire iteration [just like mapcar, which
+; stops when any list runs out].
+; <argfn>, if non-nil, is a function of one argument which is applied to the
+; current value of the state variable to get the value the generator actually
+; returns on this iteration.
+; <nextfn>, if non-nil, is a function of one argument which takes the current
+; value of the state variable and returns the next.
+; <let-specs> facilitates arbitrary hair and is explained below.
+; For example, an arg-spec of (:list foo) is equivalent to
+; (nil foo #'null #'car #'cdr)
+; where foo is the initial value of the list; #'null is the predicate that says
+; when the list has run out; #'car, the argfn, is what is done to the list to
+; get the current element; and #'cdr, the nextfn, is what is done to the list
+; to get the next list.
+;
+; An argument generator described this way is conceptually equivalent to
+; (let `(state-var ,@<let-specs>)
+; #'(lambda ()
+; (if (<exitp> state-var)
+; (*throw 'exit-iteration nil)
+; (prog1 (<argfn> state-var)
+; (setq state-var (<nextfn> state-var))))))
+;
+; Note that if only (nil <init>) is specified, the argument is a constant <init>;
+; no more circular-list'ing!
+;
+; Other predefined argument types include:
+; (:constant <value>)
+; A more readable version of `(nil <value>)'.
+; (:list <list>)
+; As shown in examples above: supplies successive elements of <list>.
+; (:index <start> &optional <stop> <incr>)
+; Provides numbers beginning at <start> and going to (but not including) <stop>
+; incrementing by <incr> each time. If <stop> is missing or nil, this generates
+; numbers indefinitely. <incr> may be positive or negative and defaults to 1.
+; (:index-inc <start> <stop> &optional <incr>)
+; "Index, INClusive": like :index, but the numbers generated include <stop>.
+; (:vector <vector>)
+; Generates successive elements of <vector>.
+; (:simple-vector <vector>)
+; Generates successive elements of <vector> (which must be simple).
+; (:string <string>)
+; Generates successive characters of <string>.
+; (:simple-string <string>)
+; Generates successive characters of <string> (which must be simple).
+; (:exp <initial-value> <base>)
+; Generates an exponential sequence whose first value is <initial-value>, and
+; whose value is multiplied by <base> on each iteration.
+;
+; **** Result Constructors ****
+; Like arg-specs, result-specs begin with a keyword saying what kind of
+; constructor to use, i.e., how to put together the results of the function
+; being mapped. And again, a keyword of NIL means that no predefined
+; constructor is being used. A NIL-type result-spec looks like:
+; (nil <init> <resfn> &optional <cleanup> <filterp> <let-specs>)
+; where
+; <init> is the initial value of the constructor's state variable;
+; <resfn> is a function of two arguments, the current value of the state variable
+; and the current value returned by the function being mapped; it gives the next
+; value of the state variable.
+; <cleanup>, if present and non-nil, is a function of one argument that
+; translates the final value of the state variable into the value that the GMAP
+; actually returns.
+; <filterp>, if present and non-nil, is a predicate of one argument; when it is false
+; of the current value of the function being mapped, <resfn> is not called on that
+; iteration, and the value of the state variable is unchanged.
+; <let-specs>, as before, is hairy; I'll get back to it below.
+; For example, a res-spec of (:list) is equivalent to
+; (nil nil #'(lambda (a b) (cons b a)) #'nreverse)
+; -- the state variable starts at nil, gets successive values consed onto it, and
+; gets nreversed before being returned.
+;
+; A result-spec that supplies no arguments may be written without the parens; so
+; (:list) and :list are equivalent.
+;
+; Other predefined result types include:
+; :list
+; Generates a list, like mapcar, of the values.
+; :and
+; Returns the first NIL, or the last value if none are NIL.
+; :or
+; Returns the first non-NIL, or NIL if all values are NIL.
+; :sum
+; Returns the sum of the values. E.g., to get sum of products, use
+; (gmap :sum #'* ...)
+; (:array <initial-array>)
+; Generates an array of the values. You supply the initial array; the values
+; are stored starting with element 0. If the array has a fill pointer, it is
+; set upon exit to the number of elements stored. The array itself is returned.
+; (:string &optional <length-guess>)
+; Generates a string from the values. <length-guess> is the initially allocated
+; string size; it defaults to 20. #'array-push-extend is used to append each
+; character.
+; (:values &rest <result-specs>)
+; The function being mapped is expected to return as many values as there are
+; result-specs; each value is accumulated separately according to its respective
+; result-spec, and finally, all the result values are returned.
+;
+; **** User-defined argument and result types ****
+; A useful feature of GMAP is the provision for the user to define his/her own
+; argument generators and result constructors. For example, if in some program you
+; commonly iterate over words in a sentence, or lines in an editor buffer, or users
+; currently logged on, then define an argument type SENTENCE, or LINES, or USERS.
+; And similarly with result-types. The way this is done [which I'm not yet sure is
+; entirely satisfactory] is with the two special forms DEF-GMAP-ARG-TYPE and
+; DEF-GMAP-RES-TYPE. These have syntax like DEFUN:
+; (def-gmap-foo-type <name> (<args>)
+; <body>)
+; When <name> is seen as the keyword of an arg- or result-spec, and has
+; been defined with the appropriate special form, then the function
+; #'(lambda (<args>) <body>) is applied to the cdr of the spec; that is,
+; the keyword itself has been stripped off. Whatever this returns is interpreted
+; as a nil-type spec, except, again, without the keyword "nil". For example, the
+; arg-type :list is actually defined by
+; (def-gmap-arg-type :list (initial-list)
+; `(,initial-list ; init
+; #'null #'car #'cdr)) ; exitp, argfn, and resfn
+;
+; Lists of what arg- and result-types are defined can be found in the variables
+; *GMAP-ARG-TYPE-LIST* and *GMAP-RES-TYPE-LIST*.
+;
+; Now for the promised explanation about let-specs. Sometimes [indeed, fairly
+; often] a user-defined type will want to compute values and bind variables
+; other than those automatically provided by the iteration. For example, the
+; index type goes to some trouble to evaluate its parameters only once. It does
+; this by providing a list of specs, i.e., (<var> <value>) pairs, which go into
+; a LET that surrounds the entire iteration. Except, that is, for the following
+; hack: if you want several dependent initializations, e.g., you want foo to be
+; something hairy and bar to be the cdr of foo, you can indicate the dependence
+; by the nesting in list structure of the specs:
+; ((foo (something-hairy))
+; ((bar (cdr foo))))
+; This will cause a gmap that uses this type to expand into
+; (let ((foo (something-hairy)))
+; (let ((bar (cdr foo)))
+; ... [iteration] ...))
+; For details, see the NLET macro at the end of this file. For examples,
+; see some of the types defined herein.
+
+; Remaining tidbits:
+; Many arg- and result-specs take optional parameters, which are defined to do
+; something only if both present and non-nil. By "non-nil" here I mean non-nil
+; *at expansion time*.
+; The function being mapped can itself be nil, subject of course to the above
+; considerations; in which case the identity function of the first argument is
+; used, and other arguments are ignored.
+
+; Bugs:
+;
+; Purists will object to the use of symbols in the keyword package rather than
+; the `lisp' package for the arg- and result-types. It would make sense for
+; these symbols to come from the package providing the types they refer to;
+; among other advantages, this would prevent name collisions (which is, after
+; all, the whole point of the package system). Against this very reasonable
+; argument is my desire to have it immediately apparent to someone seeing a
+; `gmap' form, perhaps for the first time, that it is a macro with somewhat
+; unusual syntax; the use of ordinary Lisp symbols (`list', `vector', etc.)
+; would tend to disguise this fact. Anyway, there's nothing requiring the arg-
+; and result-type names to be in the keyword package; anyone who strongly
+; dislikes this is welcome to define names in some other package.
+
+; The top-level macro.
+(defmacro gmap (res-spec fn &rest arg-spec-list)
+ (gmap>expand fn
+ (gmap>res-spec-lookup res-spec)
+ (mapcar #'gmap>arg-spec-lookup arg-spec-list)))
+
+; This does the real work.
+(defun gmap>expand (fn res-specs arg-spec-list)
+ (let ((param-list
+ (mapcar #'gmap>param arg-spec-list))
+ (result-list (gmap>res>init-clauses res-specs))
+ (let-specs (gmap>let-specs arg-spec-list res-specs)))
+ (let ((one-value-p (null (cdr result-list)))
+ (fnval-vars (mapcar #'(lambda (ignore)
+ (declare (ignore ignore))
+ (gensym))
+ result-list)))
+ `(nlet ,let-specs
+ (do (,@param-list
+ ,@result-list)
+ ((or ,@(apply #'append (mapcar #'gmap>param>exit-test ; exit test
+ param-list arg-spec-list)))
+ ,(gmap>res>cleanup res-specs result-list one-value-p))
+ ,(if one-value-p
+ (if (car fnval-vars)
+ `(let ((,(car fnval-vars)
+ ,(apply #'gmap>funcall fn
+ (mapcar #'gmap>param>arg param-list arg-spec-list))))
+ (setq ,(caar result-list)
+ ,(gmap>res>next (car res-specs) (caar result-list)
+ (car fnval-vars))))
+ #| Null result spec -- just call the function for effect. |#
+ (apply #'gmap>funcall fn
+ (mapcar #'gmap>param>arg param-list arg-spec-list)))
+ `(multiple-value-bind ,fnval-vars
+ ,(apply #'gmap>funcall fn
+ (mapcar #'gmap>param>arg param-list arg-spec-list))
+ . ,(mapcar #'(lambda (fnval result-pair res-spec)
+ `(setq ,(car result-pair)
+ ,(gmap>res>next res-spec (car result-pair) fnval)))
+ fnval-vars result-list res-specs))))))))
+
+
+; extract the let-specs.
+(defun gmap>let-specs (arg-specs res-specs)
+ (nconc (mapcan #'fifth arg-specs) (mapcan #'fifth res-specs)))
+
+; generate the do-variable spec for each argument.
+(defun gmap>param (arg-spec)
+ (let ((param-name (gensym))
+ (init (first arg-spec))
+ (nextfn (fourth arg-spec)))
+ `(,param-name
+ ,init
+ ,@(if nextfn
+ `(,(gmap>funcall nextfn param-name))
+ nil))))
+
+; get the argument to the function being mapped from the do-variable.
+(defun gmap>param>arg (param arg-spec)
+ (let ((param-name (first param))
+ (argfn (third arg-spec)))
+ (gmap>funcall argfn param-name)))
+
+; get the exit test for the variable.
+(defun gmap>param>exit-test (param arg-spec)
+ (let ((param-name (first param))
+ (exitp (second arg-spec)))
+ (if exitp
+ `(,(gmap>funcall exitp param-name))
+ nil)))
+
+; get the initial value of the result.
+(defun gmap>res>init-clauses (res-specs)
+ (mapcan #'(lambda (res-spec) (and res-spec (cons (list (gensym) (first res-spec)) nil)))
+ res-specs))
+
+; compute the next value of the result from the current one and the
+; current value of the function.
+(defun gmap>res>next (res-spec result fnval)
+ (let ((resfn (second res-spec))
+ (filterp (fourth res-spec)))
+ (if filterp
+ `(if ,(gmap>funcall filterp fnval)
+ ,(gmap>funcall resfn result fnval)
+ ,result)
+ (gmap>funcall resfn result fnval))))
+
+; call the cleanup function on exit.
+(defun gmap>res>cleanup (res-specs result-list one-value-p)
+ (if one-value-p
+ (gmap>funcall (third (car res-specs)) (caar result-list))
+ `(values . ,(mapcar #'(lambda (res-spec result-pair)
+ (gmap>funcall (third res-spec) (car result-pair)))
+ res-specs result-list))))
+
+; For some reason, the compiler doesn't convert, e.g., (funcall #'car foo)
+; to (car foo); thus we lose some efficiency for functions that would normally
+; open-code, like car. Hence this function to perform the optimization for it.
+(defun gmap>funcall (function &rest args)
+ (let ((args (copy-list args)))
+ (cond ((or (null function) (eq function ':id))
+ (car args))
+ ((and (listp function)
+ (eq (car function) 'function))
+ `(,(cadr function) . ,args))
+ (t `(funcall ,function . ,args)))))
+
+
+
+(eval-when (:execute :compile-toplevel :load-toplevel)
+ (defvar *gmap-arg-type-list* nil
+ "A list of all GMAP arg types that have been defined.")
+ (defvar *gmap-res-type-list* nil
+ "A list of all GMAP result types that have been defined."))
+
+; define an arg-type.
+(defmacro def-gmap-arg-type (name args &body body)
+ (let ((fn-name (gensym "GMAP-ARG-SPEC-EXPANDER-")))
+ `(progn
+ 'compile
+ (defun ,fn-name ,args . ,body)
+ (eval-when (:execute :compile-toplevel :load-toplevel)
+ (setf (get ',name ':gmap-arg-spec-expander) ',fn-name)
+ (pushnew ',name *gmap-arg-type-list*)))))
+
+; define a result-type.
+(defmacro def-gmap-res-type (name args &body body)
+ (let ((fn-name (gensym "GMAP-RES-SPEC-EXPANDER-")))
+ `(progn
+ 'compile
+ (defun ,fn-name ,args . ,body)
+ (eval-when (:execute :compile-toplevel :load-toplevel)
+ (setf (get ',name ':gmap-res-spec-expander) ',fn-name)
+ (pushnew ',name *gmap-res-type-list*)))))
+
+; look up an arg type.
+(defun gmap>arg-spec-lookup (raw-arg-spec)
+ (let ((type (car raw-arg-spec)))
+ (if (null type)
+ (cdr raw-arg-spec)
+ (let ((generator (get type ':gmap-arg-spec-expander)))
+ (if generator
+ (apply generator (cdr raw-arg-spec))
+ (error "Argument spec, ~S, to gmap is of unknown type
+ (Do you have the package right?)"
+ raw-arg-spec))))))
+
+; look up a result type.
+(defun gmap>res-spec-lookup (raw-res-spec)
+ (if (and (listp raw-res-spec)
+ (eq (car raw-res-spec) ':values))
+ (mapcar #'gmap>res-spec-lookup-1 (cdr raw-res-spec))
+ (cons (gmap>res-spec-lookup-1 raw-res-spec) nil)))
+(defun gmap>res-spec-lookup-1 (raw-res-spec)
+ (let ((type (if (listp raw-res-spec) (car raw-res-spec)
+ raw-res-spec)))
+ (if (null type)
+ (cdr raw-res-spec)
+ (let ((generator (get type ':gmap-res-spec-expander)))
+ (if generator
+ (apply generator (and (listp raw-res-spec) (cdr raw-res-spec)))
+ (error "Result spec, ~S, to gmap is of unknown type
+ (Do you have the package right?)"
+ raw-res-spec))))))
+
+
+
+; ******** Predefined argument types ********
+; See above for documentation.
+
+(def-gmap-arg-type :constant (value)
+ `(,value))
+
+(def-gmap-arg-type :list (initial-list)
+ `(,initial-list
+ #'null #'car #'cdr))
+
+(def-gmap-arg-type :index (start &optional stop incr)
+ (let ((incr-temp (gensym))
+ (stop-temp (gensym))
+ (bounds-fn-temp (gensym)))
+ `(,start ; init
+ ,(if stop ; exitp
+ (if incr
+ `#'(lambda (val)
+ (funcall ,bounds-fn-temp val ,stop-temp))
+ `#'(lambda (val) (declare (type fixnum val))
+ (>= val ,stop-temp)))
+ 'nil)
+ nil ; no argfn
+ ,(if incr ; nextfn
+ `#'(lambda (val) (declare (type fixnum val))
+ (+ val ,incr-temp))
+ '#'1+)
+ (,@(if incr ; and let-specs
+ `((,incr-temp ,incr)
+ ((,bounds-fn-temp (if (minusp ,incr-temp) #'<= #'>=)))))
+ ,@(if stop
+ `((,stop-temp ,stop)))))))
+
+(def-gmap-arg-type :index-inc (start &optional stop incr)
+ (let ((incr-temp (gensym))
+ (stop-temp (gensym))
+ (bounds-fn-temp (gensym)))
+ `(,start ; init
+ ,(if stop ; generate (possibly hairy) exitp
+ (if incr
+ `#'(lambda (val)
+ (funcall ,bounds-fn-temp val ,stop-temp))
+ `#'(lambda (val) (declare (type fixnum val))
+ (> val ,stop-temp)))
+ 'nil)
+ nil ; no argfn
+ ,(if incr ; nextfn
+ `#'(lambda (val) (declare (type fixnum val))
+ (+ val ,incr-temp))
+ '#'1+)
+ (,@(if incr ; and let-specs
+ `((,incr-temp ,incr)
+ ((,bounds-fn-temp (if (minusp ,incr-temp) #'< #'>)))))
+ ,@(if stop
+ `((,stop-temp ,stop)))))))
+
+;;; Deprecated; use `:vector'.
+(def-gmap-arg-type :array (array &optional start stop incr)
+ (let ((array-temp (gensym))
+ (incr-temp (and incr (gensym)))
+ (stop-temp (gensym)))
+ `(,(or start 0)
+ #'(lambda (i) (>= i ,stop-temp))
+ #'(lambda (i) (aref ,array-temp i))
+ #'(lambda (x) (+ x ,(or incr-temp 1)))
+ ((,array-temp ,array)
+ ,@(and incr `((,incr-temp ,incr)))
+ ((,stop-temp ,(or stop `(length ,array-temp))))))))
+
+(def-gmap-arg-type :vector (array &optional start stop incr)
+ (let ((array-temp (gensym))
+ (incr-temp (and incr (gensym)))
+ (stop-temp (gensym)))
+ `(,(or start 0)
+ #'(lambda (i) (>= i ,stop-temp))
+ #'(lambda (i) (aref ,array-temp i))
+ #'(lambda (x) (+ x ,(or incr-temp 1)))
+ ((,array-temp ,array)
+ ,@(and incr `((,incr-temp ,incr)))
+ ((,stop-temp ,(or stop `(length ,array-temp))))))))
+
+(def-gmap-arg-type :simple-vector (array &optional start stop incr)
+ (let ((array-temp (gensym))
+ (incr-temp (and incr (gensym)))
+ (stop-temp (gensym)))
+ `(,(or start 0)
+ #'(lambda (i) (declare (type fixnum i)) (>= i ,stop-temp))
+ #'(lambda (i) (declare (type fixnum i)) (svref ,array-temp i))
+ #'(lambda (i) (declare (type fixnum i)) (+ i ,(or incr-temp 1)))
+ ((,array-temp ,array)
+ ,@(and incr `((,incr-temp (the fixnum ,incr))))
+ ((,stop-temp (the fixnum ,(or stop `(length ,array-temp)))))))))
+
+; This is like :array but coerces the object to a string first.
+(def-gmap-arg-type :string (string &optional start stop incr)
+ (let ((string-temp (gensym))
+ (incr-temp (and incr (gensym)))
+ (stop-temp (gensym)))
+ `(,(or start 0)
+ #'(lambda (i) (>= i ,stop-temp))
+ #'(lambda (i) (char ,string-temp i))
+ #'(lambda (x) (+ x ,(or incr-temp 1)))
+ ((,string-temp (string ,string))
+ ,@(and incr `((,incr-temp ,incr)))
+ ((,stop-temp ,(or stop `(length ,string-temp))))))))
+
+(def-gmap-arg-type :simple-string (string &optional start stop incr)
+ (let ((string-temp (gensym))
+ (incr-temp (and incr (gensym)))
+ (stop-temp (gensym)))
+ `(,(or start 0)
+ #'(lambda (i) (>= i ,stop-temp))
+ #'(lambda (i) (schar ,string-temp i))
+ #'(lambda (x) (+ x ,(or incr-temp 1)))
+ ((,string-temp (string ,string))
+ ,@(and incr `((,incr-temp ,incr)))
+ ((,stop-temp ,(or stop `(length ,string-temp))))))))
+
+
+; ******** Predefined result types ********
+
+(def-gmap-res-type :list (&optional filterp)
+ `(nil #'xcons #'nreverse ,filterp))
+
+(defun xcons (a b)
+ (cons b a))
+
+(def-gmap-res-type :nconc (&optional filterp)
+ (let ((result-var (gensym))) ; have to use our own, sigh.
+ `(nil ; init
+ #'(lambda (tail-loc new) ; nextfn
+ (if tail-loc (rplacd tail-loc new)
+ (setq ,result-var new))
+ (if new (last new) tail-loc))
+ #'(lambda (ignore)
+ (declare (ignore ignore))
+ ,result-var)
+ ,filterp
+ ((,result-var nil)))))
+
+(def-gmap-res-type :and ()
+ '(t #'(lambda (ignore new)
+ (declare (ignore ignore))
+ (if new new (return nil)))))
+
+(def-gmap-res-type :or ()
+ '(nil #'(lambda (ignore new)
+ (declare (ignore ignore))
+ (if new (return new) nil))))
+
+(def-gmap-res-type :sum ()
+ '(0 #'+))
+
+(def-gmap-res-type :count-if ()
+ '(0 #'(lambda (n new)
+ (if new (1+ n) n))))
+
+(def-gmap-res-type :max ()
+ '(nil #'max-with-nil-id))
+
+(defun max-with-nil-id (x y)
+ (if (null x) y
+ (if (null y) x
+ (max x y))))
+
+(def-gmap-res-type :min ()
+ '(nil #'min-with-nil-id))
+
+(defun min-with-nil-id (x y)
+ (if (null x) y
+ (if (null y) x
+ (min x y))))
+
+;;; Deprecated; use `:vector'.
+(def-gmap-res-type :array (initial-empty-array)
+ (let ((array-temp (gensym)))
+ `(0 ; init
+ #'(lambda (curr-index next-elt) ; nextfn
+ (setf (aref ,array-temp curr-index) next-elt)
+ (1+ curr-index))
+ #'(lambda (last-index) ; cleanup
+ (if (array-has-fill-pointer-p ,array-temp)
+ (setf (fill-pointer ,array-temp) last-index))
+ ,array-temp)
+ nil ; filterp
+ ((,array-temp ,initial-empty-array))))) ; let-specs
+
+(def-gmap-res-type :vector (initial-empty-vector)
+ (let ((vector-temp (gensym)))
+ `(0 ; init
+ #'(lambda (curr-index next-elt) ; nextfn
+ (setf (aref ,vector-temp curr-index) next-elt)
+ (1+ curr-index))
+ #'(lambda (last-index) ; cleanup
+ (if (vector-has-fill-pointer-p ,vector-temp)
+ (setf (fill-pointer ,vector-temp) last-index))
+ ,vector-temp)
+ nil ; filterp
+ ((,vector-temp ,initial-empty-vector))))) ; let-specs
+
+(def-gmap-res-type :string (&optional (length-guess 20.))
+ `((make-array ,length-guess ; init
+ :element-type :character
+ :adjustable t :fill-pointer 0)
+ #'(lambda (string char) ; nextfn
+ (vector-push-extend char string)
+ string)))
+
+(def-gmap-arg-type :exp (initial-value base)
+ (let ((base-temp (gensym)))
+ `(,initial-value
+ nil
+ nil
+ #'(lambda (x) (* x ,base-temp))
+ ((,base-temp ,base)))))
+
+
+; End of gmap.lisp
Added: src/new-let.lisp
==============================================================================
--- (empty file)
+++ src/new-let.lisp Sat Jan 6 18:32:18 2007
@@ -0,0 +1,327 @@
+(in-package :new-let)
+
+;;; This file is in the public domain.
+
+;;; This code implements a new LET macro with expanded syntax and semantics,
+;;; a generalization of LET, LET*, and MULTIPLE-VALUE-BIND. Some examples:
+;;;
+;;; (let ((a (foo))
+;;; ((b (bar a))))
+;;; ...)
+;;;
+;;; This example illustrates that clause nesting depth is used to indicate
+;;; ordering of evaluation and binding. B is bound after A, and its initial
+;;; value expression refers to A.
+;;;
+;;; (let ((a b c (zot))
+;;; ((d (quux a c))
+;;; ((e f (mumble b d))
+;;; (g (mung a))))
+;;; ((h (frobozz c))
+;;; ((i (xyzzy h))))
+;;; (*print-level* 3))
+;;; ...)
+;;;
+;;; A, B, and C are bound to the first three values of (ZOT), and in parallel,
+;;; *PRINT-LEVEL* is bound to 3; then D and H are bound; then E, F, G, and I
+;;; are bound.
+;;;
+;;; As this example illustrates, all bindings at a given nesting level are
+;;; done in parallel, with all bindings at a deeper level following.
+;;;
+;;; Since I like to use multiple values, I find this syntax for binding them
+;;; very handy, and I think many will agree. (Those familiar with Dylan
+;;; will think that I have borrowed the idea from it, but I wrote the first
+;;; version of this macro in 1980.) The value of using nesting to indicate
+;;; sequencing will perhaps be less clear. The additional flexibility
+;;; provided, compared to LET*, is admittedly rarely of importance in terms
+;;; of expressing an idea in fewer keystrokes. Personally, though, I like
+;;; being able to indicate clearly the data flow dependences among the
+;;; various variables I may be binding in one LET; and I have written LET
+;;; expressions of complexity comparable to the second example above. (I
+;;; should emphasize that the breaking up of the clauses into groups, as in
+;;; that second example, to emphasize their data dependence relationships
+;;; is strictly for clarity; in fact, the initial value expression for G,
+;;; for instance, is within the scope of H.)
+;;;
+;;; This code also implements an extension to COND. It is simply this: that
+;;; if the predicate expression of a COND clause is a LET form, the scope of
+;;; all variables bound by the LET is extended to include the consequent
+;;; expressions of the clause. (However, it does not include subsequent
+;;; clauses.) This simplifies the writing of somewhat Prolog-like code that
+;;; simultaneously tests that an object has a certain structure and binds
+;;; variables to parts of that structure in order to do something else.
+;;; (In order to be recognized as such, the predicate expression must be
+;;; written as a LET form, not a macro invocation that expands to a LET form.
+;;; I think this is a feature, but am open to being persuaded otherwise.)
+;;;
+;;; To use these macros, you must shadow the standard definitions in your
+;;; package. This can be done by including the following option clause in
+;;; your DEFPACKAGE form:
+;;;
+;;; (:shadowing-import-from "NEW-LET" "LET" "COND")
+;;;
+;;; If for some reason you don't want to shadow these, you can access this
+;;; version of LET as NLET, and this version of COND as BCOND (the "B" is
+;;; for "binding"), by using the following DEFPACKAGE option instead:
+;;;
+;;; (:import-from "NEW-LET" "NLET" "BCOND")
+;;;
+;;; Enjoy!
+;;; Scott L. Burson 2/18/2005
+
+
+(defmacro let (clauses &body body)
+ "A generalization of CL:LET that better supports nested bindings and multiple
+values. Syntax: (let (<clause>*) <body>). The <clause> syntax is more general
+than for CL:LET:
+ <clause> ::= <symbol> ; binds to NIL
+ | ( <symbol> ) ; likewise
+ | <clause1>
+ <clause1> ::= ( <symbol>+ <form> ) ; binding
+ | ( <clause1>+ ) ; nesting
+When a clause begins with more than one variable name, they are to be bound to
+successive values of the form. The nesting of clauses indicates sequencing of
+bindings; more deeply nested clauses may reference bindings of shallower clauses.
+All bindings at a given depth are done in parallel. This allows arbitrary
+combinations of parallel and sequential binding. Standard declarations at the
+head of BODY are handled correctly, though nonstandard ones may not be. If two
+variables of the same name are bound at different levels, any declaration
+applies to the inner one."
+ (multiple-value-bind (decls body)
+ (analyze-decls clauses body)
+ (car (expand-new-let clauses body decls))))
+
+;;; Alternative name for the above. I could have this one expand into that
+;;; one, or conversely, but I'd want to duplicate the doc string anyway, and
+;;; that's most of the code.
+(defmacro nlet (clauses &body body)
+ "A generalization of CL:LET that better supports nested bindings and multiple
+values. Syntax: (let (<clause>*) <body>). The <clause> syntax is more general
+than for CL:LET:
+ <clause> ::= <symbol> ; binds to NIL
+ | ( <symbol> ) ; likewise
+ | <clause1>
+ <clause1> ::= ( <symbol>+ <form> ) ; binding
+ | ( <clause1>+ ) ; nesting
+When a clause begins with more than one variable name, they are to be bound to
+successive values of the form. The nesting of clauses indicates sequencing of
+bindings; more deeply nested clauses may reference bindings of shallower clauses.
+All bindings at a given depth are done in parallel. This allows arbitrary
+combinations of parallel and sequential binding. Standard declarations at the
+head of BODY are handled correctly, though nonstandard ones may not be. If two
+variables of the same name are bound at different levels, any declaration
+applies to the inner one."
+ (multiple-value-bind (decls body)
+ (analyze-decls clauses body)
+ (car (expand-new-let clauses body decls))))
+
+(defun expand-new-let (clauses body decls)
+ (labels ((expand-1 (this-level-single this-level-multiple next-level body decls)
+ (cl:cond ((and this-level-multiple
+ (null (cdr this-level-multiple))
+ (null this-level-single))
+ (cl:let ((vars (butlast (car this-level-multiple))))
+ (multiple-value-bind (body decls)
+ (expand-1 nil nil next-level body decls)
+ (values `((multiple-value-bind ,vars
+ ,(car (last (car this-level-multiple)))
+ ,@(bound-decls decls vars)
+ ,@(and (null next-level)
+ (mapcar #'(lambda (d) `(declare ,d))
+ (cdr decls)))
+ . ,body))
+ (prune-decls decls vars)))))
+ (this-level-multiple
+ (let* ((vars (butlast (car this-level-multiple)))
+ (gensyms (mapcar #'(lambda (x)
+ (declare (ignore x))
+ (gensym))
+ vars)))
+ (multiple-value-bind (body decls)
+ (expand-1 (append (mapcar #'list vars gensyms)
+ this-level-single)
+ (cdr this-level-multiple) next-level body decls)
+ (values `((multiple-value-bind ,gensyms
+ ,(car (last (car this-level-multiple)))
+ ,@(bound-decls decls vars)
+ ,@(and (null next-level)
+ (mapcar #'(lambda (d) `(declare ,d))
+ (cdr decls)))
+ . ,body))
+ (prune-decls decls vars)))))
+ (this-level-single
+ (cl:let ((vars (mapcar #'(lambda (x) (if (consp x) (car x) x))
+ this-level-single)))
+ (multiple-value-bind (body decls)
+ (expand-1 nil nil next-level body decls)
+ (values `((cl:let ,this-level-single
+ ,@(bound-decls decls vars)
+ ,@(and (null next-level)
+ (mapcar #'(lambda (d) `(declare ,d))
+ (cdr decls)))
+ . ,body))
+ (prune-decls decls vars)))))
+ (next-level
+ (expand-new-let next-level body decls))
+ (t (values body decls)))))
+ (multiple-value-bind (this-level-single this-level-multiple next-level)
+ (split-level clauses nil nil nil)
+ (expand-1 this-level-single this-level-multiple next-level body decls))))
+
+(defun split-level (clauses this-level-single this-level-multiple next-level)
+ (if (null clauses)
+ (values (reverse this-level-single) (reverse this-level-multiple)
+ next-level)
+ (cl:let ((clause (car clauses)))
+ (cl:cond ((and (listp clause) (listp (car clause)))
+ (split-level (cdr clauses) this-level-single this-level-multiple
+ (append next-level clause)))
+ ((and (listp clause) (cddr clause))
+ (split-level (cdr clauses) this-level-single
+ (cons clause this-level-multiple) next-level))
+ (t
+ (split-level (cdr clauses) (cons clause this-level-single)
+ this-level-multiple next-level))))))
+
+(defun bound-decls (decls vars)
+ (let* ((bd-alist (car decls))
+ (prs (remove-if-not #'(lambda (pr) (member (car pr) vars))
+ bd-alist)))
+ (and prs `((declare . ,(mapcar #'(lambda (pr)
+ (if (listp (cdr pr))
+ `(,@(cdr pr) ,(car pr))
+ `(,(cdr pr) ,(car pr))))
+ prs))))))
+
+(defun prune-decls (decls vars)
+ (cl:let ((bd-alist (car decls)))
+ (cons (remove-if #'(lambda (pr) (member (car pr) vars))
+ bd-alist)
+ (cdr decls))))
+
+(defun analyze-decls (clauses body)
+ "Returns two values. The first value is a cons of: (a) for the bound declarations
+at the head of `body', an alist from variable name to a list of declarations
+affecting that variable; (b) a list of the remaining (free) declarations. The
+second value is `body' with the declarations stripped off."
+ (labels ((process-declares (body bd-alist free vars)
+ (if (or (null body) (not (consp (car body)))
+ (not (eq (caar body) 'declare)))
+ (values bd-alist free body)
+ (multiple-value-bind (bd-alist free)
+ (process-decls (cdar body) bd-alist free vars)
+ (process-declares (cdr body) bd-alist free vars))))
+ (process-decls (decls bd-alist free vars)
+ (if (null decls)
+ (values bd-alist free)
+ (multiple-value-bind (bd-alist free)
+ (process-decl (car decls) bd-alist free vars)
+ (process-decls (cdr decls) bd-alist free vars))))
+ (process-decl (decl bd-alist free vars)
+ (cl:cond
+ ((not (consp decl)) ; defensive programming
+ (values bd-alist (cons decl free)))
+ ((member (car decl) '(ignore ignoreable))
+ ;; These are always bound.
+ (values (append (mapcar #'(lambda (x) (cons x (car decl)))
+ (cdr decl))
+ bd-alist)
+ free))
+ ((type-specifier-name? (car decl))
+ (process-vars (cdr decl) (list 'type (car decl)) bd-alist free vars))
+ ((eq (car decl) 'type)
+ (process-vars (cddr decl) (list 'type (cadr decl)) bd-alist free vars))
+ ((eq (car decl) 'special)
+ (process-vars (cdr decl) (car decl) bd-alist free vars))
+ (t (values bd-alist (cons decl free)))))
+ (process-vars (decl-vars decl-name bd-alist free vars)
+ (if (null decl-vars)
+ (values bd-alist free)
+ (multiple-value-bind (bd-alist free)
+ (process-vars (cdr decl-vars) decl-name bd-alist free vars)
+ (if (member (car decl-vars) vars)
+ (values (cons (cons (car decl-vars) decl-name)
+ bd-alist)
+ free)
+ (values bd-alist
+ (cons (list decl-name (car decl-vars))
+ free)))))))
+ (multiple-value-bind (bd-alist free body)
+ (process-declares body nil nil (new-let-bound-vars clauses))
+ (values (cons bd-alist free) body))))
+
+(defun new-let-bound-vars (clauses)
+ (and clauses
+ (append (cl:let ((clause (car clauses)))
+ (cl:cond ((symbolp clause) (cons clause nil))
+ ((symbolp (car clause)) (butlast clause))
+ (t (new-let-bound-vars clause))))
+ (new-let-bound-vars (cdr clauses)))))
+
+(defun type-specifier-name? (x)
+ (or (member x '(array atom bignum bit bit-vector character compiled-function
+ complex cons double-float extended-char fixnum float function
+ hash-table integer keyword list long-float nil null number
+ package pathname random-state ratio rational real readtable
+ sequence short-float simple-array simple-bit-vector
+ simple-string simple-vector single-float standard-char stream
+ string base-char symbol t vector))
+ (find-class x nil)))
+
+
+(defmacro cond (&rest clauses)
+ "A generalization of CL:COND that makes it convenient to compute a value in
+the predicate expression of a clause and then use that value in the consequent.
+If the predicate expression is a LET form, then the scope of the variables bound
+by the LET is extended to include the consequent expressions. For example:
+
+ (cond ((let ((x (foo)))
+ (bar x))
+ (baz x)))
+
+Here the X in (BAZ X) is the one bound to the result of (FOO)."
+ (cl:let ((block-nm (gensym)))
+ `(block ,block-nm
+ . ,(mapcar #'(lambda (c) (bcond-clause c block-nm)) clauses))))
+
+(defmacro bcond (&rest clauses)
+ "A generalization of CL:COND that makes it convenient to compute a value in
+the predicate expression of a clause and then use that value in the consequent.
+If the predicate expression is a LET form, then the scope of the variables bound
+by the LET is extended to include the consequent expressions. For example:
+
+ (cond ((let ((x (foo)))
+ (bar x))
+ (baz x)))
+
+Here the X in (BAZ X) is the one bound to the result of (FOO)."
+ (cl:let ((block-nm (gensym)))
+ `(block ,block-nm
+ . ,(mapcar #'(lambda (c) (bcond-clause c block-nm)) clauses))))
+
+(defun bcond-clause (clause block-nm)
+ (cl:cond ((not (listp clause))
+ (error "COND clause is not a list: ~S" clause))
+ ((and (listp (car clause))
+ ;; Allow NLET and CL:LET in case the user hasn't chosen
+ ;; to shadow LET.
+ (member (caar clause) '(let nlet cl:let)))
+ (bcond-build-clause (caar clause) (cadar clause)
+ `(progn . ,(cddar clause))
+ (cdr clause) block-nm))
+ (t
+ (bcond-build-clause nil nil (car clause) (cdr clause) block-nm))))
+
+(defun bcond-build-clause (let-sym let-clauses pred consequents block-nm)
+ (cl:let ((body (if consequents
+ `(if ,pred (return-from ,block-nm (progn . ,consequents)))
+ (cl:let ((temp-var (gensym)))
+ `(cl:let ((,temp-var ,pred))
+ (if ,temp-var (return-from ,block-nm ,temp-var)))))))
+ (if let-clauses
+ `(,let-sym ,let-clauses ,body)
+ body)))
+
+
+