collector---a symbol which will have a collection function bound to it.
+
+
result---a collected list.
+
+
+
Description:
+
+collecting collects things into a list. Within the
+body of this macro, the collect function will collect its
+argument into result.
+
+
with-collectors collects some things into lists. The
+collector names are defined as local functions which each
+collect into a separate list. Returns as many values as there are
+collectors, in the order they were given.
+
+
Exceptional situations:
+
+
+
If the collector names are not all symbols, a
+type-error will be signalled.
+
+
Opinions differ on how a collection macro should work. There are
+two major points for discussion: multiple collection variables and
+implementation method.
+
+
There are two main ways of implementing collection: sticking
+successive elements onto the end of the list with tail-collection, or
+using the PUSH/NREVERSE idiom. Tail-collection is usually faster,
+except on CLISP, where PUSH/NREVERSE is a little faster because it's
+implemented in C which is always faster than Lisp bytecode.
+
+
The collecting macro only allows collection into one list,
+and you can't nest them to get the same effect as multiple collection
+since it always uses the collect function. If you want to
+collect into multiple lists, use the with-collect macro.
Composes its arguments into a single composite function. All its
+arguments are assumed to designate functions which take one argument
+and return one argument.
+
+
(funcall (compose f g) 42) is equivalent to (f (g
+42)). Composition is right-associative.
+
+
Examples:
+
+
+;; Just to illustrate order of operations
+(defun 2* (x) (* 2 x))
+
+
+(funcall (compose #'1+ #'1+) 1) => 3
+(funcall (compose '1+ '2*) 5) => 11
+(funcall (compose #'1+ '2* '1+) 6) => 15
+
+
+
Notes:
+
If you're dealing with multiple arguments and return values, the
+same concept can be used. Here is some code that could be useful:
+
+
+
+undisplace---a generalized boolean. The default is false.
+
+new-array---an array.
+
+
+
Description:
+
+
Shallow copies the contents of array into another array with
+equivalent properties. If array is displaced, then this
+function will normally create another displaced array with similar
+properties, unless undisplace is true, in which case the
+contents of array will be copied into a completely new, not
+displaced, array.
+expt-mod returns n raised to the exponent power,
+modulo divisor. (expt-mod n exponent divisor) is
+equivalent to (mod (expt n exponent) divisor).
+
+
+
Exceptional situations:
+
+
+
The exceptional situations are the same as those for (mod (expt
+n exponent) divisor).
+
+
Notes:
+
+
One might wonder why we shouldn't simply write (mod (expt n
+exponent) divisor). This function exists because the naïve
+way of evaluating (mod (expt n exponent) divisor) produces a
+gigantic intermediate result, which kills performance in applications
+which use this operation heavily. The operation can be done much more
+efficiently. Usually the compiler does this optimization
+automatically, producing very fast code. However, we can't
+depend on this behavior if we want to produce code that is
+guaranteed not to perform abysmally on some Lisp implementations.
+
+
Therefore cl-utilities provides a standard interface to this
+composite operation which uses mediocre code by default. Specific
+implementations can usually do much better, but some do much
+worse. We can get the best of both by simply using the same interface
+and doing read-time conditionalization within cl-utilities to get
+better performance on compilers like SBCL and Allegro CL which
+optimize this operation.
+
+
extremumsequence predicate &key key (start 0) end => morally-smallest-element
+
extremasequence predicate &key key (start 0) end => morally-smallest-elements
+
n-most-extremen sequence predicate &key key (start 0) end => n-smallest-elements
+
+
Arguments and Values:
+
+sequence---a proper sequence.
+
+predicate---a designator for a function of two
+arguments that returns a generalized boolean.
+
+key---a designator for a function of one
+argument, or nil.
+
+start, end---bounding index designators of sequence. The
+defaults for start and end are 0 and nil, respectively.
+
+morally-smallest-element---the element of sequence that
+would appear first if the sequence were ordered according to sort
+using predicate and key
+
+
morally-smallest-elements---the identical elements of
+sequence that would appear first if the sequence were ordered
+according to sort
+using predicate and key. If predicate states that
+neither of two objects is before the other, they are considered
+identical.
+
+n---a positive integer
+
+n-smallest-elements---the n elements of sequence that
+would appear first if the sequence were ordered according to sort
+using predicate and key
+
+
+
Description:
+
+extremum returns the element of sequence that would
+appear first if the subsequence of sequence specified by
+start and end were ordered according to sort
+using predicate and key.
+
+
+
extremum determines the relationship between two elements
+by giving keys extracted from the elements to the
+predicate. The first argument to the predicate function
+is the part of one element of sequence extracted by the
+key function (if supplied); the second argument is the part of
+another element of sequence extracted by the key
+function (if supplied). Predicate should return true if
+and only if the first argument is strictly less than the second (in
+some appropriate sense). If the first argument is greater than or
+equal to the second (in the appropriate sense), then the
+predicate should return false.
+
+
The argument to the key function is the sequence
+element. The return value of the key function becomes an
+argument to predicate. If key is not supplied or
+nil, the sequence element itself is used. There is no
+guarantee on the number of times the key will be called.
+
+
If the key and predicate always return, then the
+operation will always terminate. This is guaranteed even if the
+predicate does not really consistently represent a total order
+(in which case the answer may be wrong). If the key
+consistently returns meaningful keys, and the predicate does
+reflect some total ordering criterion on those keys, then the answer
+will be right
+
+
The predicate is assumed to consider two elements x
+and y to be equal if (funcall predicate
+xy) and (funcall
+predicateyx)
+are both false.
+
+
+
The return value of (extremum predicate sequence :key key)
+can be defined as (elt (sort
+predicate (subseq sequence start end) :key key) 0) except when
+sequence is empty (see Exceptional Situations), but may use
+faster (less asymptotically complex) algorithms to find this answer.
+
+
extrema is similar to extremum, but it returns a list
+of values. There can be more than one extremum, as determined by
+predicate, and with extremum the choice of which
+extremum to return is arbitrary. extrema returns all the
+possible values which predicate determines to be equal.
+
+
n-most-extreme returns a list of n values without
+testing for equality. It orders sequence in the same way as
+extremum and extrema, then returns the first n
+elements of the sorted sequence.
+
+
+
Exceptional situations:
+
+
+
If sequence is empty, then the error no-extremum is
+signalled. Invoking the continue restart will cause
+extremum to return nil.
+
+
+
Should be prepared to signal an error of type type-error if
+sequence is not a proper sequence.
+
+
If there are fewer than n values in the part of
+sequence that n-most-extreme may operate on, it returns
+all the values it can in sorted order and signals the warning
+n-most-extreme-not-enough-elements. This warning stores the
+given values for n and the relevant subsequence, and they may
+be accessed with n-most-extreme-not-enough-elements-n and
+n-most-extreme-not-enough-elements-subsequence, respectively.
+
+
Implementation notes:
+
+
There are two implementations of this function included in
+cl-utilities, which should only concern you if you want to squeeze out
+more efficiency, since the versions perform differently on different
+inputs.
+
+
The function extremum-fastkey is used exactly like
+extremum, but it calls key fewer times. If key is
+fast, extremum-fastkey is slower than regular extremum,
+but if key is hard to compute you can get significant gains in
+speed. The extremum-fastkey function is more complicated than
+extremum, and therefore may be more likely to contain
+bugs. That said, it doesn't seem buggy.
+
+
Don't worry about the performance of passing #'identity as
+key. This is optimized by a compiler macro.
Everybody writes some utilities because they're not part of the
+standard but they're so broadly useful. This results in a lot of wheel
+reinvention, and most reinventions are not as good as they should
+be. The cl-utilities project is an actively maintained collection of
+some of these utilities, with high-quality public-domain
+implementations and decent documentation.
+
+
+
+Public domain, maintained by Peter Scott. For more information, see
+the home page.
+
+
+
\ No newline at end of file
Added: trunk/lib/cl-utilities-1.2.4/doc/once-only.html
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/doc/once-only.html Mon Oct 19 17:28:31 2009
@@ -0,0 +1,40 @@
+
+
+
+ Macro ONCE-ONLY
+
+
+
+
+
Meant to be used in macro code, once-only guards against
+multiple evaluation of its arguments in macroexpansion code. Any
+concise description would be far too vague to grasp, but this
+thread on comp.lang.lisp does a decent job of explaining what
+once-only does.
+
+
Notes:
+
+
The description here is frustratingly non-descriptive, and I
+apologize for that. If you understand once-only and can give a
+better explanation, I would be very grateful—not to mention
+completely awed.
+
+
read-delimitedsequence stream &key start end delimiter test key => position, delimited-p
+
+
Arguments and Values:
+
+
sequence---a sequence.
+
+
stream---an input stream.
+
start, end---bounding index designators of
+sequence. The defaults for start and end are 0
+and nil, respectively.
+
+
delimiter---a character. It defaults to #\newline.
+
test---a designator for a function of two
+arguments that returns a generalized boolean.
+
+
key---a designator for a function of one
+argument, or nil.
+
position---an integer greater than or equal to zero,
+and less than or equal to the length of the sequence.
+
+
delimited-p---the result of the last invokation of test
+
+
Description:
+
+
Destructively modifies sequence by replacing
+elements of sequencebounded by start and
+end with elements read from stream.
+
+
Test is called with the actual read character, converted
+by applying key to it, as the first and delimiter as the
+second argument.
+
+
If a character is read for which (funcall test (funcall
+keychar) delimiter) is non-nil,
+read-delimited terminates the copying even before reaching
+end of file or the end of the bounding
+designator.
+
+
read-delimited returns the index of the first
+element of sequence that was not updated as the first
+and the result of the last invokation of test as the second
+value.
+
+
Sequence is destructively modified by copying successive
+elements into it from stream. If the end of file
+for stream is reached before copying all elements of the
+subsequence, then the extra elements near the end of
+sequence are not updated.
+
+
Exceptional situations:
+
+
If start and/or end are out of bounds, or if
+start > end, then a
+read-delimited-bounds-error error is signalled. This error is
+passed the values of start, end, and sequence,
+which can be read with read-delimited-bounds-error-start,
+read-delimited-bounds-error-end, and
+read-delimited-bounds-error-sequence,
+respectively.
+
+
Implementation notes:
+
+
This is one of the more complex utilities, and the amount of
+argument checking needed to do it properly is daunting. An amazing 76%
+of the code is spent on making sure that the bounds are valid and in
+order, and on what to do if they aren't. Once you remove all that, the
+actual function which does all the work is quite simple, and unlikely
+to contain bugs.
+
+
The design of this function makes it a little annoying to use, but
+it is more efficient. If you need something more high-level, this
+could be built on top of read-delimited fairly easily.
Rotates a field of bits within integer; specifically, returns an
+integer that contains the bits of integer rotated count times
+leftwards within the byte specified by bytespec, and elsewhere
+contains the bits of integer.
SBCL provides the sb-rotate-byte extension to do this
+efficiently. On SBCL, cl-utilities uses this extension
+automatically. On other implementations, portable Common Lisp code is
+used instead.
+
+
split-sequencedelimiter sequence &key count remove-empty-subseqs from-end start end test test-not key => list, index
+
split-sequence-ifpredicate sequence &key count remove-empty-subseqs from-end start end key => list, index
+
+
split-sequence-if-notpredicate sequence &key count remove-empty-subseqs from-end start end key => list, index
+
+
Arguments and Values:
+
+
delimiter---an object.
+
+
predicate---a designator for a function of one argument that returns a generalized boolean.
+
sequence---a proper sequence.
+
+
count---an integer or nil. The default is nil.
+
remove-empty-subseqs---a generalized boolean. The default is false.
+
+
from-end---a generalized boolean. The default is false.
+
start, end---bounding index designators of sequence. The defaults for start and end are 0 and nil, respectively.
+
+
test---a designator for a function of two arguments that returns a generalized boolean.
+
test-not---a designator for a function of two arguments that returns a generalized boolean.
+
+
key---a designator for a function of one argument, or nil.
+
list---a proper sequence.
+
+
index---an integer greater than or equal to zero, and less than or equal to the length of the sequence.
+
+
Description:
+
+
Splits sequence into a list of subsequences delimited by objects satisfying the test.
+
+
+
List is a list of sequences of the same kind as sequence that has elements consisting of subsequences of sequence that were delimited in the argument by elements satisfying the test. Index is an index into sequence indicating the unprocessed region, suitable as an argument to subseq to continue processing in the same manner if desired.
+
+
+
The count argument, if supplied, limits the number of subsequences in the first return value; if more than count delimited subsequences exist in sequence, the count leftmost delimited subsequences will be in order in the first return value, and the second return value will be the index into sequence at which processing stopped.
+
+
If from-end is non-null, sequence is conceptually processed from right to left, accumulating the subsequences in reverse order; from-end only makes a difference in the case of a non-null count argument. In the presence of from-end, the count rightmost delimited subsequences will be in the order that they are in sequence in the first return value, and the second is the index indicating the end of the unprocessed region.
+
+
+
The start and end keyword arguments permit a certain subsequence of the sequence to be processed without the need for a copying stage; their use is conceptually equivalent to partitioning the subsequence delimited by start and end, only without the need for copying.
+
+
If remove-empty-subseqs is null (the default), then empty subsequences will be included in the result.
+
+
+
In all cases, the subsequences in the first return value will be in the order that they appeared in sequence.
+
+
This code was written various people, and the license is
+unknown. Since multiple people worked on it collaboratively and none
+of them seem interested in keeping their intellectual property rights
+to it, I'll assume that it is in the public domain (since the process
+that produced it seems like the very essence of public domain). If
+this is incorrect, please contact
+me so we can get it straightened out.
+
+
The implementation itself is mature and well tested, and it is
+widely used. The code should be fast enough for most people, but be
+warned: it was written with vectors in mind, with list manipulation as
+an afterthought. It does a lot of things that are quick on vectors but
+slow on lists, and this can result in many orders of magnitude
+slowdown in list benchmarks versus code written for lists. If this is
+a problem for you, it should be straightforward to write your own,
+such as the (more limited, not API compatible) example function given
+by Szymon in this
+mailing list post:
+
+
+(defun split-list-if (test list &aux (start list) (end list))
+ (loop while (and end (setq start (member-if-not test end)))
+ collect (ldiff start (setq end (member-if test start)))))
+
+
+
If this is an issue for enough people, I could optimize the code
+and fix this problem. I'm reluctant to do that, however, since the
+code works and is tested. It's usually more important to be correct
+and non-buggy than to be fast, and I have been known to introduce
+bugs.
This is an extension of the classic macro with-gensyms. In
+fact, cl-utilities also exports with-gensyms, and it can be
+used as usual. The exported with-gensyms is actually just an
+alias for with-unique-names which gives a warning at
+compile-time if the extensions of with-unique-names are used.
+
+
You are encouraged to use with-unique-names instead of
+with-gensyms because it is a little more flexible and because
+it tells what is going on rather than how it works. This is a somewhat
+controversial point, so go ahead and use whichever you like if you
+have an opinion on it. But if you're a newbie who honestly doesn't
+care, please use with-unique-names.
+
+
Manual Index
+
+
+
\ No newline at end of file
Added: trunk/lib/cl-utilities-1.2.4/expt-mod.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/expt-mod.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,38 @@
+(in-package :cl-utilities)
+
+;; This is portable Common Lisp, but implementation-specific code may
+;; improve performance considerably.
+(defun expt-mod (n exponent modulus)
+ "As (mod (expt n exponent) modulus), but more efficient."
+ (declare (optimize (speed 3) (safety 0) (space 0) (debug 1)))
+ ;; It's much faster on SBCL and ACL to use the simple method, and
+ ;; trust the compiler to optimize it. This may be the case on other
+ ;; Lisp implementations as well.
+ #+(or sbcl allegro) (mod (expt n exponent) modulus)
+ #-(or sbcl allegro)
+ (if (some (complement #'integerp) (list n exponent modulus))
+ (mod (expt n exponent) modulus)
+ (loop with result = 1
+ for i of-type fixnum from 0 below (integer-length exponent)
+ for sqr = n then (mod (* sqr sqr) modulus)
+ when (logbitp i exponent) do
+ (setf result (mod (* result sqr) modulus))
+ finally (return result))))
+
+;; If the compiler is going to expand compiler macros, we should
+;; directly inline the simple expansion; this lets the compiler do all
+;; sorts of fancy optimizations based on type information that
+;; wouldn't be used to optimize the normal EXPT-MOD function.
+#+(or sbcl allegro)
+(define-compiler-macro expt-mod (n exponent modulus)
+ `(mod (expt ,n ,exponent) ,modulus))
+
+
+;; Here's some benchmarking code that may be useful. I probably
+;; completely wasted my time declaring ITERATIONS to be a fixnum.
+#+nil
+(defun test (&optional (iterations 50000000))
+ (declare (optimize (speed 3) (safety 0) (space 0) (debug 1))
+ (fixnum iterations))
+ (time (loop repeat iterations do (mod (expt 12 34) 235)))
+ (time (loop repeat iterations do (expt-mod 12 34 235))))
\ No newline at end of file
Added: trunk/lib/cl-utilities-1.2.4/extremum.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/extremum.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,167 @@
+(in-package :cl-utilities)
+
+(define-condition no-extremum (error) ()
+ (:report "Cannot find extremum of empty sequence")
+ (:documentation "Raised when EXTREMUM is called on an empty
+sequence, since there is no morally smallest element"))
+
+(defun comparator (test &optional (key #'identity))
+ "Comparison operator: auxilliary function used by EXTREMUM"
+ (declare (optimize (speed 3) (safety 0) (space 0) (debug 1)))
+ (lambda (a b) (if (funcall test
+ (funcall key a)
+ (funcall key b))
+ a
+ b)))
+
+;; This optimizes the case where KEY is #'identity
+(define-compiler-macro comparator (&whole whole test
+ &optional (key #'identity))
+ (if (eql key #'identity)
+ `(lambda (a b)
+ (declare (optimize (speed 3) (safety 0) (space 0) (debug 1)))
+ (if (funcall ,test a b) a b))
+ whole))
+
+;; The normal way of testing the if length of a proper sequence equals
+;; zero is to just use (zerop (length sequence)). And, while some
+;; implementations may optimize this, it's probably a good idea to
+;; just write an optimized version and use it. This method can speed
+;; up list length testing.
+(defun zero-length-p (sequence)
+ "Is the length of SEQUENCE equal to zero?"
+ (declare (optimize (speed 3) (safety 0) (space 0) (debug 1)))
+ (or (null sequence)
+ (when (vectorp sequence)
+ (zerop (length sequence)))))
+
+(declaim (inline zero-length-p))
+
+;; Checks the length of the subsequence of SEQUENCE specified by START
+;; and END, and if it's 0 then a NO-EXTREMUM error is signalled. This
+;; should only be used in EXTREMUM functions.
+(defmacro with-check-length ((sequence start end) &body body)
+ (once-only (sequence start end)
+ `(if (or (zero-length-p ,sequence)
+ (>= ,start (or ,end (length ,sequence))))
+ (restart-case (error 'no-extremum)
+ (continue ()
+ :report "Return NIL instead"
+ nil))
+ (progn , at body))))
+
+;; This is an extended version which takes START and END keyword
+;; arguments. Any spec-compliant use of EXTREMUM will also work with
+;; this extended version.
+(defun extremum (sequence predicate
+ &key (key #'identity) (start 0) end)
+ "Returns the element of SEQUENCE that would appear first if the
+sequence were ordered according to SORT using PREDICATE and KEY using
+an unstable sorting algorithm. See http://www.cliki.net/EXTREMUM for
+the full specification."
+ (with-check-length (sequence start end)
+ (reduce (comparator predicate key) sequence
+ :start start :end end)))
+
+;; This optimizes the case where KEY is #'identity
+(define-compiler-macro extremum (&whole whole sequence predicate
+ &key (key #'identity) (start 0) end)
+ (if (eql key #'identity)
+ (once-only (sequence predicate start end)
+ `(with-check-length (,sequence ,start ,end)
+ (locally (declare (optimize (speed 3) (safety 0) (space 0) (debug 1)))
+ (reduce (comparator ,predicate) ,sequence
+ :start ,start :end ,end))))
+ whole))
+
+;; This is an "optimized" version which calls KEY less. REDUCE is
+;; already so optimized that this will actually be slower unless KEY
+;; is expensive. And on CLISP, of course, the regular version will be
+;; much faster since built-in functions are ridiculously faster than
+;; ones implemented in Lisp. Be warned, this isn't as carefully tested
+;; as regular EXTREMUM and there's more that could go wrong.
+(defun extremum-fastkey (sequence predicate
+ &key (key #'identity) (start 0) end)
+ "EXTREMUM implemented so that it calls KEY less. This is only faster
+if the KEY function is so slow that calling it less often would be a
+significant improvement; ordinarily it's slower."
+ (declare (optimize (speed 3) (safety 0) (space 0) (debug 1)))
+ (with-check-length (sequence start end)
+ (let* ((smallest (elt sequence 0))
+ (smallest-key (funcall key smallest))
+ (current-index 0)
+ (real-end (or end (1- most-positive-fixnum))))
+ (declare (type (integer 0) current-index real-end start)
+ (fixnum current-index real-end start))
+ (map nil #'(lambda (x)
+ (when (<= start current-index real-end)
+ (let ((x-key (funcall key x)))
+ (when (funcall predicate
+ x-key
+ smallest-key)
+ (setf smallest x)
+ (setf smallest-key x-key))))
+ (incf current-index))
+ sequence)
+ smallest)))
+
+;; EXTREMA and N-MOST-EXTREME are based on code and ideas from Tobias
+;; C. Rittweiler. They deal with the cases in which you are not
+;; looking for a single extreme element, but for the extreme identical
+;; elements or the N most extreme elements.
+
+(defun extrema (sequence predicate &key (key #'identity) (start 0) end)
+ (with-check-length (sequence start end)
+ (let* ((sequence (subseq sequence start end))
+ (smallest-elements (list (elt sequence 0)))
+ (smallest-key (funcall key (elt smallest-elements 0))))
+ (map nil
+ #'(lambda (x)
+ (let ((x-key (funcall key x)))
+ (cond ((funcall predicate x-key smallest-key)
+ (setq smallest-elements (list x))
+ (setq smallest-key x-key))
+ ;; both elements are considered equal if the predicate
+ ;; returns false for (PRED A B) and (PRED B A)
+ ((not (funcall predicate smallest-key x-key))
+ (push x smallest-elements)))))
+ (subseq sequence 1))
+ ;; We use NREVERSE to make this stable (in the sorting algorithm
+ ;; sense of the word 'stable').
+ (nreverse smallest-elements))))
+
+
+
+(define-condition n-most-extreme-not-enough-elements (warning)
+ ((n :initarg :n :reader n-most-extreme-not-enough-elements-n
+ :documentation "The number of elements that need to be returned")
+ (subsequence :initarg :subsequence
+ :reader n-most-extreme-not-enough-elements-subsequence
+ :documentation "The subsequence from which elements
+must be taken. This is determined by the sequence and the :start and
+:end arguments to N-MOST-EXTREME."))
+ (:report (lambda (condition stream)
+ (with-slots (n subsequence) condition
+ (format stream "There are not enough elements in the sequence ~S~% to return the ~D most extreme elements"
+ subsequence n))))
+ (:documentation "There are not enough elements in the sequence given
+to N-MOST-EXTREME to return the N most extreme elements."))
+
+(defun n-most-extreme (n sequence predicate &key (key #'identity) (start 0) end)
+ "Returns a list of the N elements of SEQUENCE that would appear
+first if the sequence were ordered according to SORT using PREDICATE
+and KEY with a stable sorting algorithm. If there are less than N
+elements in the relevant part of the sequence, this will return all
+the elements it can and signal the warning
+N-MOST-EXTREME-NOT-ENOUGH-ELEMENTS"
+ (check-type n (integer 0))
+ (with-check-length (sequence start end)
+ ;; This is faster on vectors than on lists.
+ (let ((sequence (subseq sequence start end)))
+ (if (> n (length sequence))
+ (progn
+ (warn 'n-most-extreme-not-enough-elements
+ :n n :subsequence sequence)
+ (stable-sort (copy-seq sequence) predicate :key key))
+ (subseq (stable-sort (copy-seq sequence) predicate :key key)
+ 0 n)))))
\ No newline at end of file
Added: trunk/lib/cl-utilities-1.2.4/once-only.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/once-only.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,31 @@
+;; The ONCE-ONLY macro is hard to explain, hard to understand, hard to
+;; write, hard to modify, and hard to live without once you figure out
+;; how to use it. It's used in macros to guard against multiple
+;; evaluation of arguments. My version is longer than most, but it
+;; does some error checking and it gives gensym'd variables more
+;; meaningful names than usual.
+
+(in-package :cl-utilities)
+
+(defun %check-once-only-names (names)
+ "Check that all of the NAMES are symbols. If not, raise an error."
+ ;; This only raises an error for the first non-symbol argument
+ ;; found. While this won't report multiple errors, it is probably
+ ;; more convenient to only report one.
+ (let ((bad-name (find-if-not #'symbolp names)))
+ (when bad-name
+ (error "ONCE-ONLY expected a symbol but got ~S" bad-name))))
+
+(defmacro once-only (names &body body)
+ ;; Check the NAMES list for validity.
+ (%check-once-only-names names)
+ ;; Do not touch this code unless you really know what you're doing.
+ (let ((gensyms (loop for name in names collect (gensym (string name)))))
+ `(let (,@(loop for g in gensyms
+ for name in names
+ collect `(,g (gensym ,(string name)))))
+ `(let (,,@(loop for g in gensyms for n in names
+ collect ``(,,g ,,n)))
+ ,(let (,@(loop for n in names for g in gensyms
+ collect `(,n ,g)))
+ , at body)))))
\ No newline at end of file
Added: trunk/lib/cl-utilities-1.2.4/package.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/package.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,49 @@
+(defpackage :cl-utilities
+ (:use :common-lisp)
+ (:export #:split-sequence
+ #:split-sequence-if
+ #:split-sequence-if-not
+ #:partition
+ #:partition-if
+ #:partition-if-not
+
+ #:extremum
+ #:no-extremum
+ #:extremum-fastkey
+ #:extrema
+ #:n-most-extreme
+ #:n-most-extreme-not-enough-elements
+ #:n-most-extreme-not-enough-elements-n
+ #:n-most-extreme-not-enough-elements-subsequence
+
+ #:read-delimited
+ #:read-delimited-bounds-error
+ #:read-delimited-bounds-error-start
+ #:read-delimited-bounds-error-end
+ #:read-delimited-bounds-error-sequence
+
+ #:expt-mod
+
+ #:collecting
+ #:collect
+ #:with-collectors
+
+ #:with-unique-names
+ #:with-gensyms
+ #:list-binding-not-supported
+ #:list-binding-not-supported-binding
+
+ #:once-only
+
+ #:rotate-byte
+
+ #:copy-array
+
+ #:compose))
+
+#+split-sequence-deprecated
+(defpackage :split-sequence
+ (:documentation "This package mimics SPLIT-SEQUENCE for compatibility with
+packages that expect that system.")
+ (:use :cl-utilities)
+ (:export #:split-sequence #:split-sequence-if #:split-sequence-if-not))
Added: trunk/lib/cl-utilities-1.2.4/package.sh
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/package.sh Mon Oct 19 17:28:31 2009
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+mkdir cl-utilities-1.2.4
+mkdir cl-utilities-1.2.4/doc
+cp cl-utilities.asd package.sh collecting.lisp split-sequence.lisp expt-mod.lisp package.lisp compose.lisp extremum.lisp read-delimited.lisp test.lisp copy-array.lisp once-only.lisp rotate-byte.lisp with-unique-names.lisp README cl-utilities-1.2.4/
+cp doc/collecting.html doc/expt-mod.html doc/read-delimited.html doc/with-unique-names.html doc/compose.html doc/extremum.html doc/rotate-byte.html doc/copy-array.html doc/index.html doc/split-sequence.html doc/once-only.html doc/style.css cl-utilities-1.2.4/doc/
+
+rm -f cl-utilities-latest.tar.gz cl-utilities-latest.tar.gz.asc
+
+tar -czvf cl-utilities-1.2.4.tar.gz cl-utilities-1.2.4/
+ln -s ~/hacking/lisp/cl-utilities/cl-utilities-1.2.4.tar.gz ~/hacking/lisp/cl-utilities/cl-utilities-latest.tar.gz
+gpg -b -a ~/hacking/lisp/cl-utilities/cl-utilities-1.2.4.tar.gz
+ln -s ~/hacking/lisp/cl-utilities/cl-utilities-1.2.4.tar.gz.asc ~/hacking/lisp/cl-utilities/cl-utilities-latest.tar.gz.asc
+rm -Rf cl-utilities-1.2.4/
+
+scp cl-utilities-1.2.4.tar.gz pscott at common-lisp.net:/project/cl-utilities/public_html/cl-utilities-1.2.4.tar.gz
+scp cl-utilities-1.2.4.tar.gz.asc pscott at common-lisp.net:/project/cl-utilities/public_html/cl-utilities-1.2.4.tar.gz.asc
+scp cl-utilities-latest.tar.gz pscott at common-lisp.net:/project/cl-utilities/ftp/cl-utilities-1.2.4.tar.gz
+scp cl-utilities-latest.tar.gz.asc pscott at common-lisp.net:/project/cl-utilities/ftp/cl-utilities-1.2.4.tar.gz.asc
+scp cl-utilities-latest.tar.gz pscott at common-lisp.net:/project/cl-utilities/public_html/cl-utilities-latest.tar.gz
+scp cl-utilities-latest.tar.gz.asc pscott at common-lisp.net:/project/cl-utilities/public_html/cl-utilities-latest.tar.gz.asc
Added: trunk/lib/cl-utilities-1.2.4/read-delimited.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/read-delimited.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,78 @@
+(in-package :cl-utilities)
+
+(defun read-delimited (sequence stream &key (start 0) end
+ (delimiter #\Newline) (test #'eql) (key #'identity))
+ ;; Check bounds on SEQUENCE
+ (multiple-value-setq (start end)
+ (%read-delimited-bounds-check sequence start end))
+ ;; Loop until we run out of input characters or places to put them,
+ ;; or until we encounter the delimiter.
+ (loop for index from start
+ for char = (read-char stream nil nil)
+ for test-result = (funcall test (funcall key char) delimiter)
+ while (and char
+ (< index end)
+ (not test-result))
+ do (setf (elt sequence index) char)
+ finally (return-from read-delimited
+ (values index test-result))))
+
+;; Conditions
+;;;;;;;;;;;;;
+
+(define-condition read-delimited-bounds-error (error)
+ ((start :initarg :start :reader read-delimited-bounds-error-start)
+ (end :initarg :end :reader read-delimited-bounds-error-end)
+ (sequence :initarg :sequence :reader read-delimited-bounds-error-sequence))
+ (:report (lambda (condition stream)
+ (with-slots (start end sequence) condition
+ (format stream "The bounding indices ~S and ~S are bad for a sequence of length ~S"
+ start end (length sequence)))))
+ (:documentation "There's a problem with the indices START and END
+for SEQUENCE. See CLHS SUBSEQ-OUT-OF-BOUNDS:IS-AN-ERROR issue."))
+
+;; Error checking for bounds
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun %read-delimited-bounds-check (sequence start end)
+ "Check to make sure START and END are in bounds when calling
+READ-DELIMITED with SEQUENCE"
+ (check-type start (or integer null))
+ (check-type end (or integer null))
+ (let ((start (%read-delimited-bounds-check-start sequence start end))
+ (end (%read-delimited-bounds-check-end sequence start end)))
+ ;; Returns (values start end)
+ (%read-delimited-bounds-check-order sequence start end)))
+
+(defun %read-delimited-bounds-check-order (sequence start end)
+ "Check the order of START and END bounds, and return them in the
+correct order."
+ (when (< end start)
+ (restart-case (error 'read-delimited-bounds-error
+ :start start :end end :sequence sequence)
+ (continue ()
+ :report "Switch start and end"
+ (rotatef start end))))
+ (values start end))
+
+(defun %read-delimited-bounds-check-start (sequence start end)
+ "Check to make sure START is in bounds when calling READ-DELIMITED
+with SEQUENCE"
+ (when (and start (< start 0))
+ (restart-case (error 'read-delimited-bounds-error
+ :start start :end end :sequence sequence)
+ (continue ()
+ :report "Use default for START instead"
+ (setf start 0))))
+ start)
+
+(defun %read-delimited-bounds-check-end (sequence start end)
+ "Check to make sure END is in bounds when calling READ-DELIMITED
+with SEQUENCE"
+ (when (and end (> end (length sequence)))
+ (restart-case (error 'read-delimited-bounds-error
+ :start start :end end :sequence sequence)
+ (continue ()
+ :report "Use default for END instead"
+ (setf end nil))))
+ (or end (length sequence)))
\ No newline at end of file
Added: trunk/lib/cl-utilities-1.2.4/rotate-byte.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/rotate-byte.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,29 @@
+(in-package :cl-utilities)
+
+(defun rotate-byte (count bytespec integer)
+ "Rotates a field of bits within INTEGER; specifically, returns an
+integer that contains the bits of INTEGER rotated COUNT times
+leftwards within the byte specified by BYTESPEC, and elsewhere
+contains the bits of INTEGER. See http://www.cliki.net/ROTATE-BYTE"
+ (declare (optimize (speed 3) (safety 0) (space 0) (debug 1)))
+ #-sbcl
+ (let ((size (byte-size bytespec)))
+ (when (= size 0)
+ (return-from rotate-byte integer))
+ (let ((count (mod count size)))
+ (labels ((rotate-byte-from-0 (count size integer)
+ (let ((bytespec (byte size 0)))
+ (if (> count 0)
+ (logior (ldb bytespec (ash integer count))
+ (ldb bytespec (ash integer (- count size))))
+ (logior (ldb bytespec (ash integer count))
+ (ldb bytespec (ash integer (+ count size))))))))
+ (dpb (rotate-byte-from-0 count size (ldb bytespec integer))
+ bytespec
+ integer))))
+ ;; On SBCL, we use the SB-ROTATE-BYTE extension.
+ #+sbcl-uses-sb-rotate-byte (sb-rotate-byte:rotate-byte count bytespec integer))
+
+;; If we're using the SB-ROTATE-BYTE extension, we should inline our
+;; call and let SBCL handle optimization from there.
+#+sbcl-uses-sb-rotate-byte (declaim (inline rotate-byte))
\ No newline at end of file
Added: trunk/lib/cl-utilities-1.2.4/split-sequence.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/split-sequence.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,244 @@
+;;;; SPLIT-SEQUENCE
+;;;
+;;; This code was based on Arthur Lemmens' in
+;;; ;
+;;;
+;;; changes include:
+;;;
+;;; * altering the behaviour of the :from-end keyword argument to
+;;; return the subsequences in original order, for consistency with
+;;; CL:REMOVE, CL:SUBSTITUTE et al. (:from-end being non-NIL only
+;;; affects the answer if :count is less than the number of
+;;; subsequences, by analogy with the above-referenced functions).
+;;;
+;;; * changing the :maximum keyword argument to :count, by analogy
+;;; with CL:REMOVE, CL:SUBSTITUTE, and so on.
+;;;
+;;; * naming the function SPLIT-SEQUENCE rather than PARTITION rather
+;;; than SPLIT.
+;;;
+;;; * adding SPLIT-SEQUENCE-IF and SPLIT-SEQUENCE-IF-NOT.
+;;;
+;;; * The second return value is now an index rather than a copy of a
+;;; portion of the sequence; this index is the `right' one to feed to
+;;; CL:SUBSEQ for continued processing.
+
+;;; There's a certain amount of code duplication here, which is kept
+;;; to illustrate the relationship between the SPLIT-SEQUENCE
+;;; functions and the CL:POSITION functions.
+
+;;; Examples:
+;;;
+;;; * (split-sequence #\; "a;;b;c")
+;;; -> ("a" "" "b" "c"), 6
+;;;
+;;; * (split-sequence #\; "a;;b;c" :from-end t)
+;;; -> ("a" "" "b" "c"), 0
+;;;
+;;; * (split-sequence #\; "a;;b;c" :from-end t :count 1)
+;;; -> ("c"), 4
+;;;
+;;; * (split-sequence #\; "a;;b;c" :remove-empty-subseqs t)
+;;; -> ("a" "b" "c"), 6
+;;;
+;;; * (split-sequence-if (lambda (x) (member x '(#\a #\b))) "abracadabra")
+;;; -> ("" "" "r" "c" "d" "" "r" ""), 11
+;;;
+;;; * (split-sequence-if-not (lambda (x) (member x '(#\a #\b))) "abracadabra")
+;;; -> ("ab" "a" "a" "ab" "a"), 11
+;;;
+;;; * (split-sequence #\; ";oo;bar;ba;" :start 1 :end 9)
+;;; -> ("oo" "bar" "b"), 9
+
+;; cl-utilities note: the license of this file is unclear, and I don't
+;; even know whom to contact to clarify it. If anybody objects to my
+;; assumption that it is public domain, please contact me so I can do
+;; something about it. Previously I required the split-sequence
+ ; package as a dependency, but that was so unwieldy that it was *the*
+;; sore spot sticking out in the design of cl-utilities. -Peter Scott
+
+(in-package :cl-utilities)
+
+(defun split-sequence (delimiter seq &key (count nil) (remove-empty-subseqs nil) (from-end nil) (start 0) (end nil) (test nil test-supplied) (test-not nil test-not-supplied) (key nil key-supplied))
+ "Return a list of subsequences in seq delimited by delimiter.
+
+If :remove-empty-subseqs is NIL, empty subsequences will be included
+in the result; otherwise they will be discarded. All other keywords
+work analogously to those for CL:SUBSTITUTE. In particular, the
+behaviour of :from-end is possibly different from other versions of
+this function; :from-end values of NIL and T are equivalent unless
+:count is supplied. The second return value is an index suitable as an
+argument to CL:SUBSEQ into the sequence indicating where processing
+stopped."
+ (let ((len (length seq))
+ (other-keys (nconc (when test-supplied
+ (list :test test))
+ (when test-not-supplied
+ (list :test-not test-not))
+ (when key-supplied
+ (list :key key)))))
+ (unless end (setq end len))
+ (if from-end
+ (loop for right = end then left
+ for left = (max (or (apply #'position delimiter seq
+ :end right
+ :from-end t
+ other-keys)
+ -1)
+ (1- start))
+ unless (and (= right (1+ left))
+ remove-empty-subseqs) ; empty subseq we don't want
+ if (and count (>= nr-elts count))
+ ;; We can't take any more. Return now.
+ return (values (nreverse subseqs) right)
+ else
+ collect (subseq seq (1+ left) right) into subseqs
+ and sum 1 into nr-elts
+ until (< left start)
+ finally (return (values (nreverse subseqs) (1+ left))))
+ (loop for left = start then (+ right 1)
+ for right = (min (or (apply #'position delimiter seq
+ :start left
+ other-keys)
+ len)
+ end)
+ unless (and (= right left)
+ remove-empty-subseqs) ; empty subseq we don't want
+ if (and count (>= nr-elts count))
+ ;; We can't take any more. Return now.
+ return (values subseqs left)
+ else
+ collect (subseq seq left right) into subseqs
+ and sum 1 into nr-elts
+ until (>= right end)
+ finally (return (values subseqs right))))))
+
+(defun split-sequence-if (predicate seq &key (count nil) (remove-empty-subseqs nil) (from-end nil) (start 0) (end nil) (key nil key-supplied))
+ "Return a list of subsequences in seq delimited by items satisfying
+predicate.
+
+If :remove-empty-subseqs is NIL, empty subsequences will be included
+in the result; otherwise they will be discarded. All other keywords
+work analogously to those for CL:SUBSTITUTE-IF. In particular, the
+behaviour of :from-end is possibly different from other versions of
+this function; :from-end values of NIL and T are equivalent unless
+:count is supplied. The second return value is an index suitable as an
+argument to CL:SUBSEQ into the sequence indicating where processing
+stopped."
+ (let ((len (length seq))
+ (other-keys (when key-supplied
+ (list :key key))))
+ (unless end (setq end len))
+ (if from-end
+ (loop for right = end then left
+ for left = (max (or (apply #'position-if predicate seq
+ :end right
+ :from-end t
+ other-keys)
+ -1)
+ (1- start))
+ unless (and (= right (1+ left))
+ remove-empty-subseqs) ; empty subseq we don't want
+ if (and count (>= nr-elts count))
+ ;; We can't take any more. Return now.
+ return (values (nreverse subseqs) right)
+ else
+ collect (subseq seq (1+ left) right) into subseqs
+ and sum 1 into nr-elts
+ until (< left start)
+ finally (return (values (nreverse subseqs) (1+ left))))
+ (loop for left = start then (+ right 1)
+ for right = (min (or (apply #'position-if predicate seq
+ :start left
+ other-keys)
+ len)
+ end)
+ unless (and (= right left)
+ remove-empty-subseqs) ; empty subseq we don't want
+ if (and count (>= nr-elts count))
+ ;; We can't take any more. Return now.
+ return (values subseqs left)
+ else
+ collect (subseq seq left right) into subseqs
+ and sum 1 into nr-elts
+ until (>= right end)
+ finally (return (values subseqs right))))))
+
+(defun split-sequence-if-not (predicate seq &key (count nil) (remove-empty-subseqs nil) (from-end nil) (start 0) (end nil) (key nil key-supplied))
+ "Return a list of subsequences in seq delimited by items satisfying
+(CL:COMPLEMENT predicate).
+
+If :remove-empty-subseqs is NIL, empty subsequences will be included
+in the result; otherwise they will be discarded. All other keywords
+work analogously to those for CL:SUBSTITUTE-IF-NOT. In particular,
+the behaviour of :from-end is possibly different from other versions
+of this function; :from-end values of NIL and T are equivalent unless
+:count is supplied. The second return value is an index suitable as an
+argument to CL:SUBSEQ into the sequence indicating where processing
+stopped." ; Emacs syntax highlighting is broken, and this helps: "
+ (let ((len (length seq))
+ (other-keys (when key-supplied
+ (list :key key))))
+ (unless end (setq end len))
+ (if from-end
+ (loop for right = end then left
+ for left = (max (or (apply #'position-if-not predicate seq
+ :end right
+ :from-end t
+ other-keys)
+ -1)
+ (1- start))
+ unless (and (= right (1+ left))
+ remove-empty-subseqs) ; empty subseq we don't want
+ if (and count (>= nr-elts count))
+ ;; We can't take any more. Return now.
+ return (values (nreverse subseqs) right)
+ else
+ collect (subseq seq (1+ left) right) into subseqs
+ and sum 1 into nr-elts
+ until (< left start)
+ finally (return (values (nreverse subseqs) (1+ left))))
+ (loop for left = start then (+ right 1)
+ for right = (min (or (apply #'position-if-not predicate seq
+ :start left
+ other-keys)
+ len)
+ end)
+ unless (and (= right left)
+ remove-empty-subseqs) ; empty subseq we don't want
+ if (and count (>= nr-elts count))
+ ;; We can't take any more. Return now.
+ return (values subseqs left)
+ else
+ collect (subseq seq left right) into subseqs
+ and sum 1 into nr-elts
+ until (>= right end)
+ finally (return (values subseqs right))))))
+
+;;; clean deprecation
+
+(defun partition (&rest args)
+ (apply #'split-sequence args))
+
+(defun partition-if (&rest args)
+ (apply #'split-sequence-if args))
+
+(defun partition-if-not (&rest args)
+ (apply #'split-sequence-if-not args))
+
+(define-compiler-macro partition (&whole form &rest args)
+ (declare (ignore args))
+ (warn "PARTITION is deprecated; use SPLIT-SEQUENCE instead.")
+ form)
+
+(define-compiler-macro partition-if (&whole form &rest args)
+ (declare (ignore args))
+ (warn "PARTITION-IF is deprecated; use SPLIT-SEQUENCE-IF instead.")
+ form)
+
+(define-compiler-macro partition-if-not (&whole form &rest args)
+ (declare (ignore args))
+ (warn "PARTITION-IF-NOT is deprecated; use SPLIT-SEQUENCE-IF-NOT instead")
+ form)
+
+(pushnew :split-sequence *features*)
Added: trunk/lib/cl-utilities-1.2.4/test.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/test.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,178 @@
+;; This file requires the FiveAM unit testing framework.
+(eval-when (:compile-toplevel :load-toplevel :execute)
+ (asdf:oos 'asdf:load-op :fiveam)
+ (asdf:oos 'asdf:load-op :cl-utilities))
+
+;; To run all the tests:
+;; (5am:run! 'cl-utilities-tests::cl-utilities-suite)
+
+(defpackage :cl-utilities-tests
+ (:use :common-lisp :cl-utilities :5am))
+
+(in-package :cl-utilities-tests)
+
+(def-suite cl-utilities-suite :description "Test suite for cl-utilities")
+(in-suite cl-utilities-suite)
+
+;; These tests were taken directly from the comments at the top of
+;; split-sequence.lisp
+(test split-sequence
+ (is (tree-equal (values (split-sequence #\; "a;;b;c"))
+ '("a" "" "b" "c") :test #'equal))
+ (is (tree-equal (values (split-sequence #\; "a;;b;c" :from-end t))
+ '("a" "" "b" "c") :test #'equal))
+ (is (tree-equal (values (split-sequence #\; "a;;b;c" :from-end t :count 1))
+ '("c") :test #'equal))
+ (is (tree-equal (values (split-sequence #\; "a;;b;c" :remove-empty-subseqs t))
+ '("a" "b" "c") :test #'equal))
+ (is (tree-equal (values (split-sequence-if (lambda (x)
+ (member x '(#\a #\b)))
+ "abracadabra"))
+ '("" "" "r" "c" "d" "" "r" "") :test #'equal))
+ (is (tree-equal (values (split-sequence-if-not (lambda (x)
+ (member x '(#\a #\b)))
+ "abracadabra"))
+ '("ab" "a" "a" "ab" "a") :test #'equal))
+ (is (tree-equal (values (split-sequence #\; ";oo;bar;ba;" :start 1 :end 9))
+ '("oo" "bar" "b") :test #'equal)))
+
+(test extremum
+ (is (= (extremum '(1 23 3 4 5 0) #'< :start 1 :end 4) 3))
+ (signals no-extremum (extremum '() #'<))
+ (is-false (handler-bind ((no-extremum #'continue))
+ (extremum '() #'<)))
+ (is (= (extremum '(2/3 2 3 4) #'> :key (lambda (x) (/ 1 x))) 2/3))
+ (is (= (locally (declare (optimize (speed 3) (safety 0)))
+ (extremum #(1 23 3 4 5 0) #'>))
+ 23))
+ (is (= (extremum-fastkey '(2/3 2 3 4) #'> :key (lambda (x) (/ 1 x))) 2/3)))
+
+(test extrema
+ (is (tree-equal (extrema '(3 2 1 1 2 1) #'<)
+ '(1 1 1)))
+ (is (tree-equal (extrema #(3 2 1 1 2 1) #'<)
+ '(1 1 1)))
+ (is (tree-equal (extrema #(3 2 1 1 2 1) #'< :end 4)
+ '(1 1)))
+ (is (tree-equal (extrema '(3 2 1 1 2 1) #'< :end 4)
+ '(1 1)))
+ (is (tree-equal (extrema #(3 2 1 1 2 1) #'< :start 3 :end 4)
+ '(1)))
+ (is (tree-equal (extrema '((A . 3) (B . 1) (C . 2) (D . 1)) #'< :key #'cdr)
+ '((B . 1) (D . 1)))))
+
+(defmacro quietly (&body body)
+ "Perform BODY quietly, muffling any warnings that may arise"
+ `(handler-bind ((warning #'muffle-warning))
+ , at body))
+
+(test n-most-extreme
+ (is (tree-equal (n-most-extreme 1 '(3 1 2 1) #'>)
+ '(3)))
+ (is (tree-equal (n-most-extreme 2 '(3 1 2 1) #'>)
+ '(3 2)))
+ (is (tree-equal (n-most-extreme 2 '(3 1 2 1) #'<)
+ '(1 1)))
+ (is (tree-equal (n-most-extreme 1 '((A . 3) (B . 1) (C . 2) (D . 1)) #'> :key #'cdr)
+ '((A . 3))))
+ (is (tree-equal (n-most-extreme 2 '((A . 3) (B . 1) (C . 2) (D . 1)) #'< :key #'cdr)
+ '((B . 1) (D . 1))))
+ (is (tree-equal (quietly (n-most-extreme 20 '((A . 3) (B . 1) (C . 2) (D . 1)) #'< :key #'cdr))
+ '((B . 1) (D . 1) (C . 2) (A . 3))))
+ (is (tree-equal (quietly (n-most-extreme 2 '((A . 3) (B . 1) (C . 2) (D . 1)) #'< :key #'cdr :start 1 :end 2))
+ '((B . 1))))
+ (signals n-most-extreme-not-enough-elements (n-most-extreme 2 '((A . 3) (B . 1) (C . 2) (D . 1)) #'< :key #'cdr :start 1 :end 2)))
+
+(defun delimited-test (&key (delimiter #\|) (start 0) end
+ (string "foogo|ogreogrjejgierjijri|bar|baz"))
+ (with-input-from-string (str string)
+ (let ((buffer (copy-seq " ")))
+ (multiple-value-bind (position delimited-p)
+ (read-delimited buffer str
+ :delimiter delimiter :start start :end end)
+ (declare (ignore delimited-p))
+ (subseq buffer 0 position)))))
+
+(test read-delimited
+ (is (string= (delimited-test) "foogo"))
+ (is (string= (delimited-test :delimiter #\t) "foogo|ogreog"))
+ (is (string= (delimited-test :delimiter #\t :start 3) " foogo|ogr"))
+ (is (string= (delimited-test :start 3) " foogo"))
+ (is (string= (delimited-test :end 3) "foo"))
+ (is (string= (delimited-test :start 1 :end 3) " fo"))
+ (is (string= (delimited-test :string "Hello") "Hello"))
+ (is (string= (delimited-test :string "Hello" :start 3) " Hello"))
+ (is (string= (handler-bind ((read-delimited-bounds-error #'continue))
+ (delimited-test :start 3 :end 1))
+ " fo"))
+ (signals type-error (delimited-test :start 3/2))
+ (signals read-delimited-bounds-error (delimited-test :start -3))
+ (signals read-delimited-bounds-error (delimited-test :end 30))
+ (signals read-delimited-bounds-error (delimited-test :start 3 :end 1)))
+
+;; Random testing would probably work better here.
+(test expt-mod
+ (is (= (expt-mod 2 34 54) (mod (expt 2 34) 54)))
+ (is (= (expt-mod 20 3 54) (mod (expt 20 3) 54)))
+ (is (= (expt-mod 2.5 3.8 34.9) (mod (expt 2.5 3.8) 34.9)))
+ (is (= (expt-mod 2/5 3/8 34/9) (mod (expt 2/5 3/8) 34/9))))
+
+(test collecting
+ (is (tree-equal (collecting (dotimes (x 10) (collect x)))
+ '(0 1 2 3 4 5 6 7 8 9)))
+ (is (tree-equal (collecting
+ (labels ((collect-it (x) (collect x)))
+ (mapcar #'collect-it (reverse '(c b a)))))
+ '(a b c)))
+ (is (tree-equal (multiple-value-bind (a b)
+ (with-collectors (x y)
+ (x 1)
+ (y 2)
+ (x 3))
+ (append a b))
+ '(1 3 2))))
+
+(test with-unique-names
+ (is (equalp (subseq (with-unique-names (foo)
+ (string foo))
+ 0 3)
+ "foo"))
+ (is (equalp (subseq (with-unique-names ((foo "bar"))
+ (string foo))
+ 0 3)
+ "bar"))
+ (is (equalp (subseq (with-unique-names ((foo baz))
+ (string foo))
+ 0 3)
+ "baz"))
+ (is (equalp (subseq (with-unique-names ((foo #\y))
+ (string foo))
+ 0 1)
+ "y"))
+ (is (equalp (subseq (with-gensyms (foo)
+ (string foo))
+ 0 3)
+ "foo")))
+
+;; Taken from spec
+(test rotate-byte
+ (is (= (rotate-byte 3 (byte 32 0) 3) 24))
+ (is (= (rotate-byte 3 (byte 5 5) 3) 3))
+ (is (= (rotate-byte 6 (byte 8 0) -3) -129)))
+
+(test copy-array
+ (let ((test-array (make-array '(10 10) :initial-element 5)))
+ (is (not (eq (copy-array test-array) test-array)))
+ (is (equalp (copy-array test-array) test-array))))
+
+(test compose
+ (labels ((2* (x) (* 2 x)))
+ (is (= (funcall (compose #'1+ #'1+) 1) 3))
+ (is (= (funcall (compose '1+ #'2*) 5) 11))
+ (is (= (funcall (compose #'1+ #'2* '1+) 6) 15))
+ ;; This should signal an undefined function error, since we're
+ ;; using '2* rather than #'2*, which means that COMPOSE will use
+ ;; the dynamic binding at the time it is called rather than the
+ ;; lexical binding here.
+ (signals undefined-function
+ (= (funcall (compose #'1+ '2* '1+) 6) 15))))
\ No newline at end of file
Added: trunk/lib/cl-utilities-1.2.4/with-unique-names.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/cl-utilities-1.2.4/with-unique-names.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,46 @@
+(in-package :cl-utilities)
+
+;; Defined at http://www.cliki.net/WITH-UNIQUE-NAMES
+
+(defmacro with-unique-names ((&rest bindings) &body body)
+ "Executes a series of forms with each var bound to a fresh,
+uninterned symbol. See http://www.cliki.net/WITH-UNIQUE-NAMES"
+ `(let ,(mapcar #'(lambda (binding)
+ (multiple-value-bind (var prefix)
+ (%with-unique-names-binding-parts binding)
+ (check-type var symbol)
+ `(,var (gensym ,(format nil "~A"
+ (or prefix var))))))
+ bindings)
+ , at body))
+
+(defun %with-unique-names-binding-parts (binding)
+ "Return (values var prefix) from a WITH-UNIQUE-NAMES binding
+form. If PREFIX is not given in the binding, NIL is returned to
+indicate that the default should be used."
+ (if (consp binding)
+ (values (first binding) (second binding))
+ (values binding nil)))
+
+(define-condition list-binding-not-supported (warning)
+ ((binding :initarg :binding :reader list-binding-not-supported-binding))
+ (:report (lambda (condition stream)
+ (format stream "List binding ~S not supported by WITH-GENSYMS.
+It will work, but you should use WITH-UNIQUE-NAMES instead."
+ (list-binding-not-supported-binding condition))))
+ (:documentation "List bindings aren't supported by WITH-GENSYMS, and
+if you want to use them you should use WITH-UNIQUE-NAMES instead. That
+said, they will work; they'll just signal this warning to complain
+about it."))
+
+
+(defmacro with-gensyms ((&rest bindings) &body body)
+ "Synonym for WITH-UNIQUE-NAMES, but BINDINGS should only consist of
+atoms; lists are not supported. If you try to give list bindings, a
+LIST-BINDING-NOT-SUPPORTED warning will be signalled, but it will work
+the same way as WITH-UNIQUE-NAMES. Don't do it, though."
+ ;; Signal a warning for each list binding, if there are any
+ (dolist (binding (remove-if-not #'listp bindings))
+ (warn 'list-binding-not-supported :binding binding))
+ ;; Otherwise, this is a synonym for WITH-UNIQUE-NAMES
+ `(with-unique-names ,bindings , at body))
\ No newline at end of file
Modified: trunk/src/java/snow/Snow.java
==============================================================================
--- trunk/src/java/snow/Snow.java (original)
+++ trunk/src/java/snow/Snow.java Mon Oct 19 17:28:31 2009
@@ -51,168 +51,182 @@
public abstract class Snow {
- private static boolean init = false;
- private static ScriptEngine lispEngine;
- private static final String fileSeparator = System.getProperty("file.separator");
-
- private static final String fixDirPath(String path) {
- if(!path.endsWith(fileSeparator)) {
- path += fileSeparator;
- }
- return path;
+ private static boolean init = false;
+ private static ScriptEngine lispEngine;
+ private static final String fileSeparator = System.getProperty("file.separator");
+
+ private static final String fixDirPath(String path) {
+ if(!path.endsWith(fileSeparator)) {
+ path += fileSeparator;
}
-
- public static synchronized ScriptEngine init() throws ScriptException {
- if(!init) {
- lispEngine = new ScriptEngineManager(Snow.class.getClassLoader()).getEngineByExtension("lisp");
- URL url = Snow.class.getResource("/snow/snow.asd");
- if(url == null) {
- throw new RuntimeException("snow.asd not found in classpath: have you installed Snow correctly?");
- }
- String baseDir;
- String libDir;
- if(!"file".equals(url.getProtocol())) {
- if("jar".equals(url.getProtocol())) {
- ZipInputStream extractor = null;
- try {
- String tmpDir = System.getProperty("java.io.tmpdir");
- if(tmpDir != null && fileSeparator != null) {
- tmpDir = fixDirPath(tmpDir);
- String jarUrlStr = url.getPath();
- int bangPos = jarUrlStr.indexOf('!');
- if(bangPos >= 0) {
- jarUrlStr = jarUrlStr.substring(0, bangPos);
- }
- URL jarUrl = new URL(jarUrlStr);
- extractor = new ZipInputStream(jarUrl.openStream());
- int targetDirIndex = 0;
- File targetDir;
- do {
- targetDir = new File(tmpDir + "snow" + (targetDirIndex++));
- } while(targetDir.exists());
- targetDir.mkdir();
- targetDir.deleteOnExit();
- baseDir = targetDir.getAbsolutePath();
- baseDir = fixDirPath(baseDir);
- libDir = baseDir;
- for(ZipEntry entry = extractor.getNextEntry(); entry != null; entry = extractor.getNextEntry()) {
- File extracted = new File(baseDir + entry.getName());
- extracted.deleteOnExit();
- if(entry.isDirectory()) {
- extracted.mkdirs();
- } else {
- extracted.getParentFile().mkdirs();
- byte[] buf = new byte[(int)entry.getSize()]; //probably inefficient
- int read = 0;
- while(true) {
- int justRead = extractor.read(buf, read, buf.length - read);
- if(justRead >= 0 && read < buf.length) {
- read += justRead;
- } else {
- break;
- }
- }
- FileOutputStream fos = new FileOutputStream(extracted);
- fos.write(buf);
- fos.flush();
- fos.close();
- }
- extracted.setLastModified(entry.getTime());
- System.out.println("Extracted " + extracted.getAbsolutePath());
- }
- } else {
- throw new RuntimeException("Cannot extract jar " + url + " - no temp dir or file separator defined");
- }
- } catch(Exception e) {
- throw new RuntimeException("Cannot extract jar " + url, e);
- } finally {
- if(extractor != null) {
- try {
- extractor.close();
- } catch (IOException e) {
- System.err.println("Couldn't close jar extractor: " + e.getMessage());
- e.printStackTrace();
- }
- }
- }
+ return path;
+ }
+
+ /**
+ * This method is public only because it needs to be called from Lisp.
+ * Do not call it.
+ */
+ public static synchronized void initAux() throws ScriptException {
+ if(!init) {
+ lispEngine = new ScriptEngineManager(Snow.class.getClassLoader()).getEngineByExtension("lisp");
+ URL url = Snow.class.getResource("/snow/snow.asd");
+ if(url == null) {
+ throw new RuntimeException("snow.asd not found in classpath: have you installed Snow correctly?");
+ }
+ String baseDir;
+ String libDir;
+ if(!"file".equals(url.getProtocol())) {
+ if("jar".equals(url.getProtocol())) {
+ ZipInputStream extractor = null;
+ try {
+ String tmpDir = System.getProperty("java.io.tmpdir");
+ if(tmpDir != null && fileSeparator != null) {
+ tmpDir = fixDirPath(tmpDir);
+ String jarUrlStr = url.getPath();
+ int bangPos = jarUrlStr.indexOf('!');
+ if(bangPos >= 0) {
+ jarUrlStr = jarUrlStr.substring(0, bangPos);
+ }
+ URL jarUrl = new URL(jarUrlStr);
+ extractor = new ZipInputStream(jarUrl.openStream());
+ int targetDirIndex = 0;
+ File targetDir;
+ do {
+ targetDir = new File(tmpDir + "snow" + (targetDirIndex++));
+ } while(targetDir.exists());
+ targetDir.mkdir();
+ targetDir.deleteOnExit();
+ baseDir = targetDir.getAbsolutePath();
+ baseDir = fixDirPath(baseDir);
+ libDir = baseDir;
+ for(ZipEntry entry = extractor.getNextEntry(); entry != null; entry = extractor.getNextEntry()) {
+ File extracted = new File(baseDir + entry.getName());
+ extracted.deleteOnExit();
+ if(entry.isDirectory()) {
+ extracted.mkdirs();
} else {
- throw new RuntimeException("Unsupported URL for snow.asd: " + url +
- " make sure it is a regular file or is in a jar.");
+ extracted.getParentFile().mkdirs();
+ byte[] buf = new byte[(int)entry.getSize()]; //probably inefficient
+ int read = 0;
+ while(true) {
+ int justRead = extractor.read(buf, read, buf.length - read);
+ if(justRead >= 0 && read < buf.length) {
+ read += justRead;
+ } else {
+ break;
+ }
+ }
+ FileOutputStream fos = new FileOutputStream(extracted);
+ fos.write(buf);
+ fos.flush();
+ fos.close();
}
+ extracted.setLastModified(entry.getTime());
+ System.out.println("Extracted " + extracted.getAbsolutePath());
+ }
} else {
- URI uri;
- try {
- uri = url.toURI();
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
- File f = new File(uri);
- baseDir = fixDirPath(f.getParentFile().getParent());
- libDir = fixDirPath(new File(baseDir).getParent()) + "lib" + fileSeparator;
+ throw new RuntimeException("Cannot extract jar " + url + " - no temp dir or file separator defined");
+ }
+ } catch(Exception e) {
+ throw new RuntimeException("Cannot extract jar " + url, e);
+ } finally {
+ if(extractor != null) {
+ try {
+ extractor.close();
+ } catch (IOException e) {
+ System.err.println("Couldn't close jar extractor: " + e.getMessage());
+ e.printStackTrace();
+ }
}
- lispEngine.eval("(pushnew #P\"" + baseDir + "snow/\" asdf:*central-registry* :test #'equal)");
- lispEngine.eval("(pushnew #P\"" + baseDir + "snow/swing/\" asdf:*central-registry* :test #'equal)");
- lispEngine.eval("(pushnew #P\"" + libDir + "cells/\" asdf:*central-registry* :test #'equal)");
- lispEngine.eval("(pushnew #P\"" + libDir + "cells/utils-kt/\" asdf:*central-registry* :test #'equal)");
- lispEngine.eval("(pushnew :snow-cells *features*)");
- lispEngine.eval("(asdf:oos 'asdf:load-op :snow)");
- //lispEngine.eval("(snow:install-graphical-debugger) (ohmygod)");
- //lispEngine.eval("(snow::inspect-object (snow::new \"javax.swing.JButton\"))");
- init = true;
- return lispEngine;
+ }
} else {
- throw new RuntimeException("Already initialized");
+ throw new RuntimeException("Unsupported URL for snow.asd: " + url +
+ " make sure it is a regular file or is in a jar.");
}
- }
-
- public static synchronized ScriptEngine initIfNecessary() throws ScriptException {
- if(!init) {
- init();
- }
- return lispEngine;
- }
-
- public static Object evalResource(Class> aClass, String resourcePath) throws ScriptException {
- return evalResource(aClass, resourcePath, true);
- }
-
- public static Object evalResource(Class> aClass, String resourcePath, boolean compileItFirst) throws ScriptException {
- Reader r = new InputStreamReader(aClass.getResourceAsStream(resourcePath));
- return evalResource(r, compileItFirst);
- }
-
- public static Object evalResource(Reader reader) throws ScriptException {
- return evalResource(reader, true);
- }
-
- public static Object evalResource(Reader reader, boolean compileItFirst) throws ScriptException {
- initIfNecessary();
- if(compileItFirst) {
- return getCompilable().compile(reader).eval();
- } else {
- return lispEngine.eval(reader);
+ } else {
+ URI uri;
+ try {
+ uri = url.toURI();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
}
+ File f = new File(uri);
+ baseDir = fixDirPath(f.getParentFile().getParent());
+ libDir = fixDirPath(new File(baseDir).getParent()) + "lib" + fileSeparator;
+ }
+ lispEngine.eval("(pushnew #P\"" + baseDir + "snow/\" asdf:*central-registry* :test #'equal)");
+ lispEngine.eval("(pushnew #P\"" + baseDir + "snow/swing/\" asdf:*central-registry* :test #'equal)");
+ lispEngine.eval("(pushnew #P\"" + libDir + "cl-utilities-1.2.4/\" asdf:*central-registry* :test #'equal)");
+ lispEngine.eval("(pushnew #P\"" + libDir + "cells/\" asdf:*central-registry* :test #'equal)");
+ lispEngine.eval("(pushnew #P\"" + libDir + "cells/utils-kt/\" asdf:*central-registry* :test #'equal)");
}
-
- public static ScriptEngine getScriptEngine() {
- return lispEngine;
+ }
+
+ public static synchronized ScriptEngine init() throws ScriptException {
+ if(!init) {
+ initAux();
+ lispEngine.eval("(pushnew :snow-cells *features*)");
+ lispEngine.eval("(asdf:oos 'asdf:load-op :snow)");
+
+
+ //lispEngine.eval("(snow:install-graphical-debugger) (ohmygod)");
+ //lispEngine.eval("(snow::inspect-object (snow::new \"javax.swing.JButton\"))");
+ init = true;
+ return lispEngine;
+ } else {
+ throw new RuntimeException("Already initialized");
}
-
- public static Compilable getCompilable() {
- return (Compilable) lispEngine;
+ }
+
+ public static synchronized ScriptEngine initIfNecessary() throws ScriptException {
+ if(!init) {
+ init();
}
+ return lispEngine;
+ }
- public static Invocable getInvocable() {
- return (Invocable) lispEngine;
+ public static Object evalResource(Class> aClass, String resourcePath) throws ScriptException {
+ return evalResource(aClass, resourcePath, true);
+ }
+
+ public static Object evalResource(Class> aClass, String resourcePath, boolean compileItFirst) throws ScriptException {
+ Reader r = new InputStreamReader(aClass.getResourceAsStream(resourcePath));
+ return evalResource(r, compileItFirst);
+ }
+
+ public static Object evalResource(Reader reader) throws ScriptException {
+ return evalResource(reader, true);
+ }
+
+ public static Object evalResource(Reader reader, boolean compileItFirst) throws ScriptException {
+ initIfNecessary();
+ if(compileItFirst) {
+ return getCompilable().compile(reader).eval();
+ } else {
+ return lispEngine.eval(reader);
}
-
+ }
+
+ public static ScriptEngine getScriptEngine() {
+ return lispEngine;
+ }
+
+ public static Compilable getCompilable() {
+ return (Compilable) lispEngine;
+ }
+
+ public static Invocable getInvocable() {
+ return (Invocable) lispEngine;
+ }
+
public static void main(String[] args) {
try {
Snow.init();
if(args.length == 0) { //Launch GUI REPL
evalResource(Snow.class, "/snow/start.lisp", true);
} else { //Launch regular ABCL
- org.armedbear.lisp.Main.main(args);
+ lispEngine.eval("(TOP-LEVEL::TOP-LEVEL)");
+ //org.armedbear.lisp.Main.main(args);
}
} catch (Exception e) {
e.printStackTrace();
Modified: trunk/src/java/snow/binding/BeanPropertyPathBinding.java
==============================================================================
--- trunk/src/java/snow/binding/BeanPropertyPathBinding.java (original)
+++ trunk/src/java/snow/binding/BeanPropertyPathBinding.java Mon Oct 19 17:28:31 2009
@@ -60,6 +60,10 @@
this(o, propertyPath.split("\\."));
}
+ public BeanPropertyPathBinding(Object o, String[] propertyPath) {
+ this(o, propertyPath, null);
+ }
+
protected BeanPropertyPathBinding(Object o, String[] propertyPath,
BeanPropertyPathBinding prevListener) {
this.prevListener = prevListener;
@@ -85,10 +89,6 @@
}
}
- public BeanPropertyPathBinding(Object o, String[] propertyPath) {
- this(o, propertyPath, null);
- }
-
public void remove() {
try {
Method removePropertyChangeListener = object.getClass().getMethod("removePropertyChangeListener", addRemovePropertyChangeListenerSignature);
Modified: trunk/src/lisp/snow/compile-system.lisp
==============================================================================
--- trunk/src/lisp/snow/compile-system.lisp (original)
+++ trunk/src/lisp/snow/compile-system.lisp Mon Oct 19 17:28:31 2009
@@ -3,15 +3,16 @@
(unwind-protect
(unless
(progn
- (pushnew #P"snow/" asdf:*central-registry* :test #'equal)
- (pushnew #P"snow/swing/" asdf:*central-registry* :test #'equal)
- (pushnew #P"cells/" asdf:*central-registry* :test #'equal)
+ #|(pushnew #P"snow/" asdf:*central-registry* :test #'equal)
+ (pushnew #P"snow/swing/" asdf:*central-registry* :test #'equal)
+ (pushnew #P"cl-utilities-1.2.4/" asdf:*central-registry* :test #'equal)
+ (pushnew #P"cells/" asdf:*central-registry* :test #'equal)
(pushnew #P"cells/utils-kt/" asdf:*central-registry* :test #'equal)
- (pushnew :snow-cells *features*)
-
- (format t "asdf:*central-registry*: ~A" asdf:*central-registry*)
-
- (asdf:oos 'asdf:compile-op :snow)
- t)
- (format t "failed"))
+ (pushnew :snow-cells *features*)|#
+ (jstatic "initAux" "snow.Snow")
+ (format t "asdf:*central-registry*: ~A" asdf:*central-registry*)
+
+ (asdf:oos 'asdf:compile-op :snow)
+ t)
+ (format t "failed"))
(quit))
\ No newline at end of file
Added: trunk/src/lisp/snow/data-binding.lisp
==============================================================================
--- (empty file)
+++ trunk/src/lisp/snow/data-binding.lisp Mon Oct 19 17:28:31 2009
@@ -0,0 +1,157 @@
+;;; binding-jgoodies.lisp
+;;;
+;;; Copyright (C) 2008-2009 Alessio Stalla
+;;;
+;;; This program is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU General Public License
+;;; as published by the Free Software Foundation; either version 2
+;;; of the License, or (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+;;;
+;;; As a special exception, the copyright holders of this library give you
+;;; permission to link this library with independent modules to produce an
+;;; executable, regardless of the license terms of these independent
+;;; modules, and to copy and distribute the resulting executable under
+;;; terms of your choice, provided that you also meet, for each linked
+;;; independent module, the terms and conditions of the license of that
+;;; module. An independent module is a module which is not derived from
+;;; or based on this library. If you modify this library, you may extend
+;;; this exception to your version of the library, but you are not
+;;; obligated to do so. If you do not wish to do so, delete this
+;;; exception statement from your version.
+
+(in-package :snow)
+
+(defclass data-binding ()
+ ((converter :initarg :converter :initform nil :accessor binding-converter)))
+
+(defgeneric make-model (data-binding))
+
+(defmethod make-model :around ((binding data-binding))
+ "Wraps the model with a converter if one was specified for the binding"
+ (let ((model (call-next-method)))
+ (with-slots (converter) binding
+ (cond
+ ((functionp converter)
+ (new "snow.binding.Converter" model converter converter))
+ ((consp converter)
+ (new "snow.binding.Converter" model (car converter) (cdr converter)))
+ ((null converter) model)
+ (t (error "~A is not a valid converter" converter))))))
+
+(defgeneric bind-widget (widget data-binding)
+ (:documentation "Establishes a 'data binding' between a GUI component and a data binding target. Every time the data held by the component or by the target changes, the other one will be updated accordingly."))
+
+;;Concrete Binding implementations
+
+;;Simple Binding
+(defclass simple-data-binding (data-binding)
+ ((variable :initarg :variable :reader binding-variable :initform (error "variable is required"))))
+
+(defun make-var (&optional obj)
+ (new "com.jgoodies.binding.value.ValueHolder" obj (jbool nil)))
+
+(defun var (var)
+ (invoke "getValue" var))
+
+(defun (setf var) (value var)
+ (invoke "setValue" var value)
+ value)
+
+(defun make-simple-data-binding (variable)
+ (make-instance 'simple-data-binding :variable variable))
+
+(defmethod make-model ((binding simple-data-binding))
+ (binding-variable binding))
+
+;;Bean Binding
+
+;;JGoodies Binding presentation model
+(defvar *presentation-model* nil)
+
+(defun trigger-commit (&optional (presentation-model *presentation-model*))
+ (jcall (jmethod "com.jgoodies.binding.PresentationModel"
+ "triggerCommit")
+ presentation-model))
+
+(defmacro form ((bean) &body body)
+ `(let ((*presentation-model*
+ (new "com.jgoodies.binding.PresentationModel" ,bean)))
+ , at body))
+
+(defclass bean-data-binding (data-binding)
+ ((object :initarg :object :reader binding-object
+ :initform (or *presentation-model* (error "object is required")))
+ (property :initarg :property :reader binding-property
+ :initform (error "property is required"))
+ (observed-p :initarg :observed-p :reader binding-observed-p :initform t)
+ (buffered-p :initarg :buffered-p :reader binding-buffered-p :initform nil)))
+
+(defun make-bean-data-binding (object property &rest args)
+ (apply #'make-instance 'bean-data-binding :object object :property property
+ args))
+
+(defmethod make-model ((binding bean-data-binding))
+ (let ((presentation-model-class
+ (jclass "com.jgoodies.binding.PresentationModel")))
+ (if (jinstance-of-p (binding-object binding) presentation-model-class)
+ (if (binding-buffered-p binding)
+ (jcall (jmethod presentation-model-class
+ "getBufferedModel" "java.lang.String")
+ (binding-object binding)
+ (dashed->camelcased (binding-property binding)))
+ (jcall (jmethod presentation-model-class
+ "getModel" "java.lang.String")
+ (binding-object binding)
+ (dashed->camelcased (binding-property binding))))
+ (jnew (jconstructor "com.jgoodies.binding.beans.PropertyAdapter"
+ "java.lang.Object" "java.lang.String"
+ "boolean")
+ (binding-object binding)
+ (dashed->camelcased (binding-property binding))
+ (jbool (binding-observed-p binding))))))
+
+;;EL data binding
+(defvar *bean-factory*
+ #'(lambda (bean-name)
+ (declare (ignore bean-name))
+ (error "No bean factory defined - please bind *bean-factory*"))
+ "A callback called by the EL engine with a single argument, the name of a bean to fetch from the application.")
+
+;;For EL data bindings we reuse simple-data-binding, since its 'variable' can
+;;really be any JGoodies ValueModel
+(defun make-el-data-binding (el-expr)
+ (let* ((splitted-expr (split-sequence #\. el-expr))
+ (obj (funcall *bean-factory* (car splitted-expr)))
+ (path (cdr splitted-expr)))
+ (make-instance 'simple-data-binding
+ :variable (make-bean-property-path-binding obj path))))
+
+(defun make-bean-property-path-binding (object path)
+ (new "snow.binding.BeanPropertyPathBinding"
+ object (apply #'jvector "java.lang.String" path)))
+
+;;Default binding types
+(defun default-data-binding-types ()
+ (let ((ht (make-hash-table)))
+ (setf (gethash :simple ht) 'simple-data-binding)
+ (setf (gethash :bean ht) 'bean-data-binding)
+ ht))
+
+(defparameter *binding-types* (default-data-binding-types))
+
+(defun get-data-binding-class (binding-type)
+ (if (keywordp binding-type)
+ (gethash binding-type *binding-types*)
+ binding-type))
+
+(defun make-data-binding (type &rest options)
+ (apply #'make-instance (get-data-binding-class type) options))
Modified: trunk/src/lisp/snow/packages.lisp
==============================================================================
--- trunk/src/lisp/snow/packages.lisp (original)
+++ trunk/src/lisp/snow/packages.lisp Mon Oct 19 17:28:31 2009
@@ -30,7 +30,7 @@
(defpackage :snow
- (:use :common-lisp :java #+snow-cells :cells)
+ (:use :common-lisp :java :cl-utilities #+snow-cells :cells)
(:shadow #+snow-cells #:dbg)
(:export
;;Widgets
Modified: trunk/src/lisp/snow/sexy-java.lisp
==============================================================================
--- trunk/src/lisp/snow/sexy-java.lisp (original)
+++ trunk/src/lisp/snow/sexy-java.lisp Mon Oct 19 17:28:31 2009
@@ -190,7 +190,10 @@
(t form)))
form))
-(defun ensure-list (obj)
- (if (listp obj)
- obj
- (list obj)))
\ No newline at end of file
+(defun jvector (element-type &rest args)
+ (let ((arr (jnew-array (jclass element-type) (length args))))
+ (loop
+ :for x :in args
+ :for i := 0 :then (incf i)
+ :do (setf (jarray-ref arr i) x))
+ arr))
\ No newline at end of file
Modified: trunk/src/lisp/snow/snow.asd
==============================================================================
--- trunk/src/lisp/snow/snow.asd (original)
+++ trunk/src/lisp/snow/snow.asd Mon Oct 19 17:28:31 2009
@@ -31,8 +31,8 @@
;;Core stuff + cells if needed
(asdf:defsystem :snow
:serial t
- :version "0.1"
- :depends-on (#+snow-cells :cells)
+ :version "0.2"
+ :depends-on (:cl-utilities #+snow-cells :cells)
:components ((:file "packages")
(:file "sexy-java")
(:file "utils")
Modified: trunk/src/lisp/snow/utils.lisp
==============================================================================
--- trunk/src/lisp/snow/utils.lisp (original)
+++ trunk/src/lisp/snow/utils.lisp Mon Oct 19 17:28:31 2009
@@ -32,13 +32,13 @@
(in-package :snow)
;;Some utilities...
-(defmacro with-unique-names ((&rest bindings) &body body)
+#|(defmacro with-unique-names ((&rest bindings) &body body)
`(let ,(mapcar #'(lambda (binding)
(destructuring-bind (var prefix)
(if (consp binding) binding (list binding binding))
`(,var (gensym ,(string prefix)))))
bindings)
- , at body))
+ , at body))|#
#|(defmacro with-captured-specials ((&rest specials) &body body)
(with-unique-names (tmp)
From astalla at common-lisp.net Tue Oct 20 18:24:44 2009
From: astalla at common-lisp.net (Alessio Stalla)
Date: Tue, 20 Oct 2009 14:24:44 -0400
Subject: [snow-cvs] r7 - in trunk: src/java/snow/binding test/src/snow
Message-ID:
Author: astalla
Date: Tue Oct 20 14:24:43 2009
New Revision: 7
Log:
Fixed BeanPropertyPathBinding
Modified:
trunk/src/java/snow/binding/BeanPropertyPathBinding.java
trunk/test/src/snow/BindingTest.java
Modified: trunk/src/java/snow/binding/BeanPropertyPathBinding.java
==============================================================================
--- trunk/src/java/snow/binding/BeanPropertyPathBinding.java (original)
+++ trunk/src/java/snow/binding/BeanPropertyPathBinding.java Tue Oct 20 14:24:43 2009
@@ -82,9 +82,10 @@
reader = pd.getReadMethod();
writer = pd.getWriteMethod();
if(nextPropertyPath.length > 0) {
- Object subObj = getValue();
+ Object subObj = getLocalValue();
if(subObj != null) {
nextListener = new BeanPropertyPathBinding(subObj, nextPropertyPath, this);
+
}
}
}
@@ -137,20 +138,66 @@
fireValueChange(evt.getOldValue(), evt.getNewValue(), false);
}
}
+
+ private BeanPropertyPathBinding getTarget() {
+ if(nextPropertyPath.length == 0) {
+ return this;
+ } else if(nextListener != null) {
+ return nextListener.getTarget();
+ } else {
+ return null;
+ }
+ }
+ public Object getLocalValue() {
+ try {
+ return reader.invoke(object);
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Override
public Object getValue() {
try {
- return reader.invoke(object);
+ BeanPropertyPathBinding target = getTarget();
+ if(target != null) {
+ return target.getLocalValue();
+ } else {
+ return null;
+ }
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setLocalValue(Object value) {
+ try {
+ System.err.println(object + " " + writer + " " + value);
+ writer.invoke(object, value);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
+
@Override
public void setValue(Object value) {
try {
- writer.invoke(object, value);
+ BeanPropertyPathBinding target = getTarget();
+ if(target != null) {
+ target.setLocalValue(value);
+ } else {
+ StringBuilder sb = new StringBuilder();
+ assert(nextPropertyPath != null);
+ for(String s : nextPropertyPath) {
+ if(sb.length() > 0) {
+ sb.append('.');
+ }
+ sb.append(s);
+ }
+ throw new NullPointerException("Property " + sb + " not reachable.");
+ }
} catch(Exception e) {
throw new RuntimeException(e);
}
Modified: trunk/test/src/snow/BindingTest.java
==============================================================================
--- trunk/test/src/snow/BindingTest.java (original)
+++ trunk/test/src/snow/BindingTest.java Tue Oct 20 14:24:43 2009
@@ -11,6 +11,7 @@
import net.miginfocom.swing.MigLayout;
import org.junit.*;
+import static org.junit.Assert.*;
import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.beans.Model;
@@ -67,13 +68,17 @@
});
bean.getBean().setProperty("value2");
if(flag[0]) {
- Assert.fail("value was set but listener not fired");
+ Assert.fail("value was set but listener didn't fire");
}
flag[0] = true;
bean.getBean().setProperty("value2");
if(!flag[0]) {
Assert.fail("value was set to same value and listener fired");
}
+ model.setValue("42");
+ System.err.println("outer bean: " + bean);
+ System.err.println("inner bean: " + bean.getBean());
+ assertEquals("42", bean.getBean().getProperty());
}
public static void main(String[] args) {
From astalla at common-lisp.net Tue Oct 20 18:26:56 2009
From: astalla at common-lisp.net (Alessio Stalla)
Date: Tue, 20 Oct 2009 14:26:56 -0400
Subject: [snow-cvs] r8 - in trunk: src/java/snow/binding test/src/snow
Message-ID:
Author: astalla
Date: Tue Oct 20 14:26:55 2009
New Revision: 8
Log:
Removed debug print statements.
Modified:
trunk/src/java/snow/binding/BeanPropertyPathBinding.java
trunk/test/src/snow/BindingTest.java
Modified: trunk/src/java/snow/binding/BeanPropertyPathBinding.java
==============================================================================
--- trunk/src/java/snow/binding/BeanPropertyPathBinding.java (original)
+++ trunk/src/java/snow/binding/BeanPropertyPathBinding.java Tue Oct 20 14:26:55 2009
@@ -173,7 +173,6 @@
public void setLocalValue(Object value) {
try {
- System.err.println(object + " " + writer + " " + value);
writer.invoke(object, value);
} catch(Exception e) {
throw new RuntimeException(e);
Modified: trunk/test/src/snow/BindingTest.java
==============================================================================
--- trunk/test/src/snow/BindingTest.java (original)
+++ trunk/test/src/snow/BindingTest.java Tue Oct 20 14:26:55 2009
@@ -76,8 +76,6 @@
Assert.fail("value was set to same value and listener fired");
}
model.setValue("42");
- System.err.println("outer bean: " + bean);
- System.err.println("inner bean: " + bean.getBean());
assertEquals("42", bean.getBean().getProperty());
}
From astalla at common-lisp.net Tue Oct 20 22:09:46 2009
From: astalla at common-lisp.net (Alessio Stalla)
Date: Tue, 20 Oct 2009 18:09:46 -0400
Subject: [snow-cvs] r9 - in trunk: src/java/snow/example src/lisp/snow
test/src/snow
Message-ID:
Author: astalla
Date: Tue Oct 20 18:09:45 2009
New Revision: 9
Log:
Added EL to example (no reader macro yet), updated binding test
Modified:
trunk/src/java/snow/example/SnowExample.java
trunk/src/java/snow/example/example.lisp
trunk/src/lisp/snow/data-binding.lisp
trunk/test/src/snow/BindingTest.java
Modified: trunk/src/java/snow/example/SnowExample.java
==============================================================================
--- trunk/src/java/snow/example/SnowExample.java (original)
+++ trunk/src/java/snow/example/SnowExample.java Tue Oct 20 18:09:45 2009
@@ -34,4 +34,13 @@
firePropertyChange("property1", oldValue, property1);
}
+ private SnowExample nested = null;
+
+ public SnowExample getNested() {
+ if(nested == null) {
+ nested = new SnowExample();
+ }
+ return nested;
+ }
+
}
Modified: trunk/src/java/snow/example/example.lisp
==============================================================================
--- trunk/src/java/snow/example/example.lisp (original)
+++ trunk/src/java/snow/example/example.lisp Tue Oct 20 18:09:45 2009
@@ -7,6 +7,9 @@
(defvar *object* (new "snow.example.SnowExample"))
(defvar *variable* (make-var "42"))
(defvar *cells-object* (make-instance 'my-model))
+(setq *bean-factory* #'(lambda (x) ;dummy
+ (declare (ignore x))
+ *object*))
(with-gui (:swing)
(let ((myframe
@@ -26,18 +29,35 @@
:layout (jfield "java.awt.BorderLayout" "EAST")))
(scroll ()
(panel ()
- (label :binding (make-bean-binding *object* "property1"))
- (label :binding (make-cells-binding (c? (aaa *cells-object*))))
- (label :binding (make-cells-binding (c? (bbb *cells-object*))))
- (label :binding (make-simple-binding *variable*))
+ (label :text "bean binding")
+ (label :binding (make-bean-data-binding *object* "property1")
+ :layout "wrap")
+ (label :text "EL binding")
+ (label :binding (make-el-data-binding "bean.nested.property1")
+ :layout "wrap")
+ (label :text "cells bindings: aaa and bbb")
+ (label :binding (make-cells-data-binding (c? (aaa *cells-object*))))
+ (label :binding (make-cells-data-binding (c? (bbb *cells-object*)))
+ :layout "wrap")
+ (label :text "simple binding to a variable")
+ (label :binding (make-simple-data-binding *variable*)
+ :layout "wrap")
(button :text "another one" :layout "wrap")
- (text-field :binding (make-bean-binding *object* "property1")
- :layout "growx")
+ (label :text "set property1")
+ (text-field :binding (make-bean-data-binding *object* "property1")
+ :layout "growx, wrap")
+ (label :text "set nested.property1")
+ (text-field :binding (make-el-data-binding "bean.nested.property1")
+ :layout "growx, wrap")
(button :text "Test!"
:layout "wrap"
:on-action (lambda (event)
(setf (jproperty-value *object* "property1")
"Test property")
+ (setf (jproperty-value
+ (jproperty-value *object* "nested")
+ "property1")
+ "Nested property")
(setf (var *variable*) "Test var")
(setf (aaa *cells-object*) "Test cell"))))))))
(pack myframe)))
@@ -49,13 +69,13 @@
:on-action (lambda (event)
(print "Hello, world!")
(print event)))
- (text-field :binding (make-bean-binding *object* "property1"))
+ (text-field :binding (make-bean-data-binding *object* "property1"))
(text-field :binding
- (make-cells-binding (c? (aaa *cells-object*))
+ (make-cells-data-binding (c? (aaa *cells-object*))
#'(lambda (x)
(setf (aaa *cells-object*) x))))
- (text-field :binding (make-slot-binding *cells-object* 'aaa))
- (text-field :binding (make-simple-binding *variable*)
+ (text-field :binding (make-slot-data-binding *cells-object* 'aaa))
+ (text-field :binding (make-simple-data-binding *variable*)
:layout "wrap")
(label :text "haha")
(panel (:layout-manager :mig :layout "grow")
Modified: trunk/src/lisp/snow/data-binding.lisp
==============================================================================
--- trunk/src/lisp/snow/data-binding.lisp (original)
+++ trunk/src/lisp/snow/data-binding.lisp Tue Oct 20 18:09:45 2009
@@ -133,14 +133,14 @@
(obj (funcall *bean-factory* (car splitted-expr)))
(path (cdr splitted-expr)))
(make-instance 'simple-data-binding
- :variable (make-bean-property-path-binding obj path))))
+ :variable (new "snow.binding.BeanPropertyPathBinding"
+ obj (apply #'jvector "java.lang.String" path)))))
-(defun make-bean-property-path-binding (object path)
- (new "snow.binding.BeanPropertyPathBinding"
- object (apply #'jvector "java.lang.String" path)))
+;(defun make-bean-property-path-data-binding (object path)
+;)
;;Default binding types
-(defun default-data-binding-types ()
+#|(defun default-data-binding-types ()
(let ((ht (make-hash-table)))
(setf (gethash :simple ht) 'simple-data-binding)
(setf (gethash :bean ht) 'bean-data-binding)
@@ -155,3 +155,4 @@
(defun make-data-binding (type &rest options)
(apply #'make-instance (get-data-binding-class type) options))
+|#
\ No newline at end of file
Modified: trunk/test/src/snow/BindingTest.java
==============================================================================
--- trunk/test/src/snow/BindingTest.java (original)
+++ trunk/test/src/snow/BindingTest.java Tue Oct 20 18:09:45 2009
@@ -68,12 +68,12 @@
});
bean.getBean().setProperty("value2");
if(flag[0]) {
- Assert.fail("value was set but listener didn't fire");
+ fail("value was set but listener didn't fire");
}
flag[0] = true;
bean.getBean().setProperty("value2");
if(!flag[0]) {
- Assert.fail("value was set to same value and listener fired");
+ fail("value was set to same value and listener fired");
}
model.setValue("42");
assertEquals("42", bean.getBean().getProperty());
From astalla at common-lisp.net Thu Oct 22 20:10:11 2009
From: astalla at common-lisp.net (Alessio Stalla)
Date: Thu, 22 Oct 2009 16:10:11 -0400
Subject: [snow-cvs] r10 - in trunk: lib lib/named-readtables
lib/named-readtables/doc lib/named-readtables/tests
src/java/snow src/java/snow/example src/lisp/snow
Message-ID:
Author: astalla
Date: Thu Oct 22 16:10:10 2009
New Revision: 10
Log:
Integrated named readtables
updated to latest abcl (fixes a bug with set-syntax-from-char which broke named readtables)
implemented read macro for EL binding
fixed compilation with ant (snow is no longer an eclipse project)
Added:
trunk/lib/named-readtables/
trunk/lib/named-readtables/LICENSE
trunk/lib/named-readtables/cruft.lisp
trunk/lib/named-readtables/define-api.lisp
trunk/lib/named-readtables/doc/
trunk/lib/named-readtables/doc/named-readtables.html
trunk/lib/named-readtables/named-readtables.asd
trunk/lib/named-readtables/named-readtables.lisp
trunk/lib/named-readtables/package.lisp
trunk/lib/named-readtables/tests/
trunk/lib/named-readtables/tests/package.lisp
trunk/lib/named-readtables/tests/rt.lisp
trunk/lib/named-readtables/tests/tests.lisp
trunk/lib/named-readtables/utils.lisp
Modified:
trunk/lib/abcl.jar
trunk/src/java/snow/Snow.java
trunk/src/java/snow/example/example.lisp
trunk/src/lisp/snow/compile-system.lisp
trunk/src/lisp/snow/data-binding.lisp
trunk/src/lisp/snow/packages.lisp
trunk/src/lisp/snow/snow.asd
Modified: trunk/lib/abcl.jar
==============================================================================
Binary files. No diff available.
Added: trunk/lib/named-readtables/LICENSE
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/LICENSE Thu Oct 22 16:10:10 2009
@@ -0,0 +1,36 @@
+
+Copyright (c) 2007 - 2009 Tobias C. Rittweiler
+Copyright (c) 2007, Robert P. Goldman and SIFT, LLC
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the names of Tobias C. Rittweiler, Robert P. Goldman,
+ SIFT, LLC nor the names of its contributors may be used to
+ endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY Tobias C. Rittweiler, Robert
+P. Goldman and SIFT, LLC ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Tobias C. Rittweiler, Robert
+P. Goldman or SIFT, LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Added: trunk/lib/named-readtables/cruft.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/cruft.lisp Thu Oct 22 16:10:10 2009
@@ -0,0 +1,375 @@
+;;;;
+;;;; Copyright (c) 2008 - 2009 Tobias C. Rittweiler
+;;;;
+;;;; All rights reserved.
+;;;;
+;;;; See LICENSE for details.
+;;;;
+
+(in-package :editor-hints.named-readtables)
+
+(defmacro define-cruft (name lambda-list &body (docstring . alternatives))
+ (assert (typep docstring 'string) (docstring) "Docstring missing!")
+ (assert (not (null alternatives)))
+ `(progn
+ (declaim (inline ,name))
+ (defun ,name ,lambda-list ,docstring ,(first alternatives))))
+
+(eval-when (:compile-toplevel :execute)
+ #+sbcl (when (find-symbol "ASSERT-NOT-STANDARD-READTABLE"
+ (find-package "SB-IMPL"))
+ (pushnew :sbcl+safe-standard-readtable *features*)))
+
+
+;;;;; Implementation-dependent cruft
+
+;;;; Mapping between a readtable object and its readtable-name.
+
+(defvar *readtable-names* (make-hash-table :test 'eq))
+
+(define-cruft %associate-readtable-with-name (name readtable)
+ "Associate READTABLE with NAME for READTABLE-NAME to work."
+ #+ :common-lisp (setf (gethash readtable *readtable-names*) name))
+
+(define-cruft %unassociate-readtable-from-name (name readtable)
+ "Remove the association between READTABLE and NAME."
+ #+ :common-lisp (progn (assert (eq name (gethash readtable *readtable-names*)))
+ (remhash readtable *readtable-names*)))
+
+(define-cruft %readtable-name (readtable)
+ "Return the name associated with READTABLE."
+ #+ :common-lisp (values (gethash readtable *readtable-names*)))
+
+(define-cruft %list-all-readtable-names ()
+ "Return a list of all available readtable names."
+ #+ :common-lisp (list* :standard :current
+ (loop for name being each hash-value of *readtable-names*
+ collect name)))
+
+
+;;;; Mapping between a readtable-name and the actual readtable object.
+
+;;; On Allegro we reuse their named-readtable support so we work
+;;; nicely on their infrastructure.
+
+#-allegro
+(defvar *named-readtables* (make-hash-table :test 'eq))
+
+#+allegro
+(defun readtable-name-for-allegro (symbol)
+ (multiple-value-bind (kwd status)
+ (if (keywordp symbol)
+ (values symbol nil)
+ ;; Kludge: ACL uses keywords to name readtables, we allow
+ ;; arbitrary symbols.
+ (intern (format nil "~A.~A"
+ (package-name (symbol-package symbol))
+ (symbol-name symbol))
+ :keyword))
+ (prog1 kwd
+ (assert (or (not status) (get kwd 'named-readtable-designator)))
+ (setf (get kwd 'named-readtable-designator) t))))
+
+(define-cruft %associate-name-with-readtable (name readtable)
+ "Associate NAME with READTABLE for FIND-READTABLE to work."
+ #+ :allegro (setf (excl:named-readtable (readtable-name-for-allegro name)) readtable)
+ #+ :common-lisp (setf (gethash name *named-readtables*) readtable))
+
+(define-cruft %unassociate-name-from-readtable (name readtable)
+ "Remove the association between NAME and READTABLE"
+ #+ :allegro (let ((n (readtable-name-for-allegro name)))
+ (assert (eq readtable (excl:named-readtable n)))
+ (setf (excl:named-readtable n) nil))
+ #+ :common-lisp (progn (assert (eq readtable (gethash name *named-readtables*)))
+ (remhash name *named-readtables*)))
+
+(define-cruft %find-readtable (name)
+ "Return the readtable named NAME."
+ #+ :allegro (excl:named-readtable (readtable-name-for-allegro name))
+ #+ :common-lisp (values (gethash name *named-readtables* nil)))
+
+
+;;;; Reader-macro related predicates
+
+;;; CLISP creates new function objects for standard reader macros on
+;;; each readtable copy.
+(define-cruft function= (fn1 fn2)
+ "Are reader-macro function-designators FN1 and FN2 the same?"
+ #+ :clisp
+ (let* ((fn1 (ensure-function fn1))
+ (fn2 (ensure-function fn2))
+ (n1 (system::function-name fn1))
+ (n2 (system::function-name fn2)))
+ (if (and (eq n1 :lambda) (eq n2 :lambda))
+ (eq fn1 fn2)
+ (equal n1 n2)))
+ #+ :common-lisp
+ (eq (ensure-function fn1) (ensure-function fn2)))
+
+;;; CCL has a bug that prevents the portable form below from working
+;;; (Ticket 601). CLISP will incorrectly fold the call to G-D-M-C away
+;;; if not declared inline.
+(define-cruft dispatch-macro-char-p (char rt)
+ "Is CHAR a dispatch macro character in RT?"
+ #+ :ccl
+ (let ((def (cdr (nth-value 1 (ccl::%get-readtable-char char rt)))))
+ (or (consp (cdr def))
+ (eq (car def) #'ccl::read-dispatch)))
+ #+ :common-lisp
+ (handler-case (locally
+ #+clisp (declare (notinline get-dispatch-macro-character))
+ (get-dispatch-macro-character char #\x rt)
+ t)
+ (error () nil)))
+
+;; (defun macro-char-p (char rt)
+;; (let ((reader-fn (%get-macro-character char rt)))
+;; (and reader-fn t)))
+
+;; (defun standard-macro-char-p (char rt)
+;; (multiple-value-bind (rt-fn rt-flag) (get-macro-character char rt)
+;; (multiple-value-bind (std-fn std-flag) (get-macro-character char *standard-readtable*)
+;; (and (eq rt-fn std-fn)
+;; (eq rt-flag std-flag)))))
+
+;; (defun standard-dispatch-macro-char-p (disp-char sub-char rt)
+;; (flet ((non-terminating-p (ch rt) (nth-value 1 (get-macro-character ch rt))))
+;; (and (eq (non-terminating-p disp-char rt)
+;; (non-terminating-p disp-char *standard-readtable*))
+;; (eq (get-dispatch-macro-character disp-char sub-char rt)
+;; (get-dispatch-macro-character disp-char sub-char *standard-readtable*)))))
+
+
+;;;; Readtables Iterators
+
+(defmacro with-readtable-iterator ((name readtable) &body body)
+ (let ((it (gensym)))
+ `(let ((,it (%make-readtable-iterator ,readtable)))
+ (macrolet ((,name () `(funcall ,',it)))
+ , at body))))
+
+#+sbcl
+(defun %make-readtable-iterator (readtable)
+ (let ((char-macro-array (sb-impl::character-macro-array readtable))
+ (char-macro-ht (sb-impl::character-macro-hash-table readtable))
+ (dispatch-tables (sb-impl::dispatch-tables readtable))
+ (char-code 0))
+ (with-hash-table-iterator (ht-iterator char-macro-ht)
+ (labels ((grovel-base-chars ()
+ (declare (optimize sb-c::merge-tail-calls))
+ (if (>= char-code sb-int:base-char-code-limit)
+ (grovel-unicode-chars)
+ (let ((reader-fn (svref char-macro-array char-code))
+ (char (code-char (shiftf char-code (1+ char-code)))))
+ (if reader-fn
+ (yield char reader-fn)
+ (grovel-base-chars)))))
+ (grovel-unicode-chars ()
+ (multiple-value-bind (more? char reader-fn) (ht-iterator)
+ (if (not more?)
+ (values nil nil nil nil nil)
+ (yield char reader-fn))))
+ (yield (char reader-fn)
+ (let ((disp-ht))
+ (cond
+ ((setq disp-ht (cdr (assoc char dispatch-tables)))
+ (let* ((disp-fn (get-macro-character char readtable))
+ (sub-char-alist))
+ (maphash (lambda (k v)
+ (push (cons k v) sub-char-alist))
+ disp-ht)
+ (values t char disp-fn t sub-char-alist)))
+ (t
+ (values t char reader-fn nil nil))))))
+ #'grovel-base-chars))))
+
+#+clozure
+(defun %make-readtable-iterator (readtable)
+ (let ((char-macro-alist (ccl::rdtab.alist readtable)))
+ (lambda ()
+ (if char-macro-alist
+ (destructuring-bind (char . defn) (pop char-macro-alist)
+ (if (consp defn)
+ (values t char (car defn) t (cdr defn))
+ (values t char defn nil nil)))
+ (values nil nil nil nil nil)))))
+
+;;; Written on ACL 8.0.
+#+allegro
+(defun %make-readtable-iterator (readtable)
+ (declare (optimize speed)) ; for TCO
+ (check-type readtable readtable)
+ (let* ((macro-table (first (excl::readtable-macro-table readtable)))
+ (dispatch-tables (excl::readtable-dispatch-tables readtable))
+ (table-length (length macro-table))
+ (idx 0))
+ (labels ((grovel-macro-chars ()
+ (if (>= idx table-length)
+ (grovel-dispatch-chars)
+ (let ((read-fn (svref macro-table idx))
+ (oidx idx))
+ (incf idx)
+ (if (or (eq read-fn #'excl::read-token)
+ (eq read-fn #'excl::read-dispatch-char)
+ (eq read-fn #'excl::undefined-macro-char))
+ (grovel-macro-chars)
+ (values t (code-char oidx) read-fn nil nil)))))
+ (grovel-dispatch-chars ()
+ (if (null dispatch-tables)
+ (values nil nil nil nil nil)
+ (destructuring-bind (disp-char sub-char-table)
+ (first dispatch-tables)
+ (setf dispatch-tables (rest dispatch-tables))
+ ;;; Kludge. We can't fully clear dispatch tables
+ ;;; in %CLEAR-READTABLE.
+ (when (eq (svref macro-table (char-code disp-char))
+ #'excl::read-dispatch-char)
+ (values t
+ disp-char
+ (svref macro-table (char-code disp-char))
+ t
+ (loop for subch-fn across sub-char-table
+ for subch-code from 0
+ when subch-fn
+ collect (cons (code-char subch-code)
+ subch-fn))))))))
+ #'grovel-macro-chars)))
+
+
+#-(or sbcl clozure allegro)
+(eval-when (:compile-toplevel)
+ (let ((*print-pretty* t))
+ (simple-style-warn
+ "~&~@< ~@;~A has not been ported to ~A. ~
+ We fall back to a portable implementation of readtable iterators. ~
+ This implementation has to grovel through all available characters. ~
+ On Unicode-aware implementations this may come with some costs.~@:>"
+ (package-name '#.*package*) (lisp-implementation-type))))
+
+#-(or sbcl clozure allegro)
+(defun %make-readtable-iterator (readtable)
+ (check-type readtable readtable)
+ (let ((char-code 0))
+ #'(lambda ()
+ (prog ()
+ :GROVEL
+ (when (< char-code char-code-limit)
+ (let* ((char (code-char char-code))
+ (fn (get-macro-character char readtable)))
+ (incf char-code)
+ (when (not fn) (go :GROVEL))
+ (multiple-value-bind (disp? alist)
+ (handler-case ; grovel dispatch macro characters.
+ (values t
+ ;; Only grovel upper case characters to
+ ;; avoid duplicates.
+ (loop for code from 0 below char-code-limit
+ for subchar = (let ((ch (code-char code)))
+ (when (or (not (alpha-char-p ch))
+ (upper-case-p ch))
+ ch))
+ for disp-fn = (and subchar
+ (get-dispatch-macro-character
+ char subchar readtable))
+ when disp-fn
+ collect (cons subchar disp-fn)))
+ (error () nil))
+ (return (values t char fn disp? alist)))))))))
+
+(defmacro do-readtable ((entry-designator readtable &optional result)
+ &body body)
+ "Iterate through a readtable's macro characters, and dispatch macro characters."
+ (destructuring-bind (char &optional reader-fn non-terminating-p disp? table)
+ (if (symbolp entry-designator)
+ (list entry-designator)
+ entry-designator)
+ (let ((iter (gensym "ITER+"))
+ (more? (gensym "MORE?+"))
+ (rt (gensym "READTABLE+")))
+ `(let ((,rt ,readtable))
+ (with-readtable-iterator (,iter ,rt)
+ (loop
+ (multiple-value-bind (,more?
+ ,char
+ ,@(when reader-fn (list reader-fn))
+ ,@(when disp? (list disp?))
+ ,@(when table (list table)))
+ (,iter)
+ (unless ,more? (return ,result))
+ (let ,(when non-terminating-p
+ ;; FIXME: N-T-P should be incorporated in iterators.
+ `((,non-terminating-p
+ (nth-value 1 (get-macro-character ,char ,rt)))))
+ , at body))))))))
+
+;;;; Misc
+
+;;; This should return an implementation's actual standard readtable
+;;; object only if the implementation makes the effort to guard against
+;;; modification of that object. Otherwise it should better return a
+;;; copy.
+(define-cruft %standard-readtable ()
+ "Return the standard readtable."
+ #+ :sbcl+safe-standard-readtable sb-impl::*standard-readtable*
+ #+ :common-lisp (copy-readtable nil))
+
+;;; On SBCL, SET-SYNTAX-FROM-CHAR does not get rid of a
+;;; readtable's dispatch table properly.
+;;; Same goes for Allegro but that does not seem to provide a
+;;; setter for their readtable's dispatch tables. Hence this ugly
+;;; workaround.
+(define-cruft %clear-readtable (readtable)
+ "Make all macro characters in READTABLE be constituents."
+ #+ :sbcl
+ (prog1 readtable
+ (do-readtable (char readtable)
+ (set-syntax-from-char char #\A readtable))
+ (setf (sb-impl::dispatch-tables readtable) nil))
+ #+ :allegro
+ (prog1 readtable
+ (do-readtable (char readtable)
+ (set-syntax-from-char char #\A readtable))
+ (let ((dispatch-tables (excl::readtable-dispatch-tables readtable)))
+ (setf (cdr dispatch-tables) nil)
+ (setf (caar dispatch-tables) #\Backspace)
+ (setf (cadar dispatch-tables) (fill (cadar dispatch-tables) nil))))
+ #+ :common-lisp
+ (do-readtable (char readtable readtable)
+ (set-syntax-from-char char #\A readtable)))
+
+;;; See Clozure Trac Ticket 601. This is supposed to be removed at
+;;; some point in the future.
+(define-cruft %get-dispatch-macro-character (char subchar rt)
+ "Ensure ANSI behaviour for GET-DISPATCH-MACRO-CHARACTER."
+ #+ :ccl (ignore-errors
+ (get-dispatch-macro-character char subchar rt))
+ #+ :common-lisp (get-dispatch-macro-character char subchar rt))
+
+;;; Allegro stores READ-TOKEN as reader macro function of each
+;;; constituent character.
+(define-cruft %get-macro-character (char rt)
+ "Ensure ANSI behaviour for GET-MACRO-CHARACTER."
+ #+ :allegro (let ((fn (get-macro-character char rt)))
+ (cond ((not fn) nil)
+ ((function= fn #'excl::read-token) nil)
+ (t fn)))
+ #+ :common-lisp (get-macro-character char rt))
+
+
+;;;; Specialized PRINT-OBJECT for named readtables.
+
+;;; As per #19 in CLHS 11.1.2.1.2 defining a method for PRINT-OBJECT
+;;; that specializes on READTABLE is actually forbidden. It's quite
+;;; likely to work (modulo package-locks) on most implementations,
+;;; though.
+
+;;; We don't need this on Allegro CL's as we hook into their
+;;; named-readtable facility, and they provide such a method already.
+#-allegro
+(without-package-lock (:common-lisp)
+ (defmethod print-object :around ((rt readtable) stream)
+ (let ((name (readtable-name rt)))
+ (if name
+ (print-unreadable-object (rt stream :type nil :identity t)
+ (format stream "~A ~S" :named-readtable name))
+ (call-next-method)))))
\ No newline at end of file
Added: trunk/lib/named-readtables/define-api.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/define-api.lisp Thu Oct 22 16:10:10 2009
@@ -0,0 +1,63 @@
+
+(in-package :named-readtables)
+
+(defmacro define-api (name lambda-list type-list &body body)
+ (flet ((parse-type-list (type-list)
+ (let ((pos (position '=> type-list)))
+ (assert pos () "You forgot to specify return type (`=>' missing.)")
+ (values (subseq type-list 0 pos)
+ `(values ,@(nthcdr (1+ pos) type-list) &optional)))))
+ (multiple-value-bind (body decls docstring)
+ (parse-body body :documentation t :whole `(define-api ,name))
+ (multiple-value-bind (arg-typespec value-typespec)
+ (parse-type-list type-list)
+ (multiple-value-bind (reqs opts rest keys)
+ (parse-ordinary-lambda-list lambda-list)
+ (declare (ignorable reqs opts rest keys))
+ `(progn
+ (declaim (ftype (function ,arg-typespec ,value-typespec) ,name))
+ (locally
+ ;;; Muffle the annoying "&OPTIONAL and &KEY found in
+ ;;; the same lambda list" style-warning
+ #+sbcl (declare (sb-ext:muffle-conditions style-warning))
+ (defun ,name ,lambda-list
+ ,docstring
+
+ #+sbcl (declare (sb-ext:unmuffle-conditions style-warning))
+
+ , at decls
+
+ ;; SBCL will interpret the ftype declaration as
+ ;; assertion and will insert type checks for us.
+ #-sbcl
+ (progn
+ ;; CHECK-TYPE required parameters
+ ,@(loop for req-arg in reqs
+ for req-type = (pop type-list)
+ do (assert req-type)
+ collect `(check-type ,req-arg ,req-type))
+
+ ;; CHECK-TYPE optional parameters
+ ,@(loop initially (assert (or (null opts)
+ (eq (pop type-list) '&optional)))
+ for (opt-arg . nil) in opts
+ for opt-type = (pop type-list)
+ do (assert opt-type)
+ collect `(check-type ,opt-arg ,opt-type))
+
+ ;; CHECK-TYPE rest parameter
+ ,@(when rest
+ (assert (eq (pop type-list) '&rest))
+ (let ((rest-type (pop type-list)))
+ (assert rest-type)
+ `((dolist (x ,rest)
+ (check-type x ,rest-type)))))
+
+ ;; CHECK-TYPE key parameters
+ ,@(loop initially (assert (or (null keys)
+ (eq (pop type-list) '&key)))
+ for ((keyword key-arg) . nil) in keys
+ for (nil key-type) = (find keyword type-list :key #'car)
+ collect `(check-type ,key-arg ,key-type)))
+
+ , at body))))))))
\ No newline at end of file
Added: trunk/lib/named-readtables/doc/named-readtables.html
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/doc/named-readtables.html Thu Oct 22 16:10:10 2009
@@ -0,0 +1,463 @@
+
+
+
+
+
+ EDITOR-HINTS.NAMED-READTABLES
+
+
+
+
+
+
EDITOR-HINTS.NAMED-READTABLES
+
+
by Tobias C Rittweiler
+
+Download:
+
+
+ darcs get http://common-lisp.net/~trittweiler/darcs/editor-hints/named-readtables/ (to be changed)
+
+
+ Named-Readtables is a library that provides a namespace for readtables akin to the already-existing namespace of packages. In particular:
+
+
you can associate readtables with names, and retrieve readtables by names;
+
you can associate source files with readtable names, and be sure that the right readtable is active when compiling/loading the file;
+
similiarly, your development environment now has a chance to automatically determine what readtable should be active while processing source forms on interactive commands. (E.g. think of `C-c C-c' in Slime [yet to be done])
+
+ Additionally, it also attempts to become a facility for using readtables in a modular way. In particular:
+
+
it provides a macro to specify the content of a readtable at a glance;
+
it makes it possible to use multiple inheritance between readtables.
+ There are three major differences between the API of Named-Readtables, and the API of packages.
+
+ 1. Readtable names are symbols not strings.
+
+ Time has shown that the fact that packages are named by strings causes severe headache because of the potential of package names colliding with each other.
+
+ Hence, readtables are named by symbols lest to make the situation worse than it already is. Consequently, readtables named CL-ORACLE:SQL-SYNTAX and CL-MYSQL:SQL-SYNTAX can happily coexist next to each other. Or, taken to an extreme, SCHEME:SYNTAX and ELISP:SYNTAX.
+
+ If, for example to duly signify the importance of your cool readtable hack, you really think it deserves a global name, you can always resort to keywords.
+
+ 2. The inheritance is resolved statically, not dynamically.
+
+ A package that uses another package will have access to all the other package's exported symbols, even to those that will be added after its definition. I.e. the inheritance is resolved at run-time, that is dynamically.
+
+ Unfortunately, we cannot do the same for readtables in a portable manner.
+
+ Therefore, we do not talk about "using" another readtable but about "merging" the other readtable's definition into the readtable we are going to define. I.e. the inheritance is resolved once at definition time, that is statically.
+
+ (Such merging can more or less be implemented portably albeit at a certain cost. Most of the time, this cost manifests itself at the time a readtable is defined, i.e. once at compile-time, so it may not bother you. Nonetheless, we provide extra support for Sbcl, ClozureCL, and AllegroCL at the moment. Patches for your implementation of choice are welcome, of course.)
+
+ 3.DEFREADTABLE does not have compile-time effects.
+
+ If you define a package via DEFPACKAGE, you can make that package the currently active package for the subsequent compilation of the same file via IN-PACKAGE. The same is, however, not true for DEFREADTABLE and IN-READTABLE for the following reason:
+
+ It's unlikely that the need for special reader-macros arises for a problem which can be solved in just one file. Most often, you're going to define the reader macro functions, and set up the corresponding readtable in an extra file.
+
+ If DEFREADTABLE had compile-time effects, you'd have to wrap each definition of a reader-macro function in an EVAL-WHEN to make its definition available at compile-time. Because that's simply not the common case, DEFREADTABLE does not have a compile-time effect.
+
+ If you want to use a readtable within the same file as its definition, wrap the DEFREADTABLE and the reader-macro function definitions in an explicit EVAL-WHEN.
+
+ Thanks to Robert Goldman for making me want to write this library.
+
+ Thanks to Stephen Compall, Ariel Badichi, David Lichteblau, Bart Botta, David Crawford, and Pascal Costanza for being early adopters, providing comments and bugfixes.
+
+
+Define a new named readtable, whose name is given by the symbol name. Or, if a readtable is already registered under that name, redefine that one.
+
+The readtable can be populated using the following options:
+
+ (:MERGEreadtable-designators+)
+
+ Merge the readtables designated into the new readtable being defined as per MERGE-READTABLES-INTO.
+
+ If no :MERGE clause is given, an empty readtable is used. See MAKE-READTABLE.
+
+ (:FUZEreadtable-designators+)
+
+ Like :MERGE except:
+
+ Error conditions of type READER-MACRO-CONFLICT that are signaled during the merge operation will be silently continued. It follows that reader macros in earlier entries will be overwritten by later ones.
+
+ Define a new sub character sub-char for the dispatching macro character macro-char, per SET-DISPATCH-MACRO-CHARACTER. You probably have to define macro-char as a dispatching macro character by the following option first.
+
+ Define a new macro character in the readtable, per SET-MACRO-CHARACTER. If function is the keyword :DISPATCH,macro-char is made a dispatching macro character, per MAKE-DISPATCH-MACRO-CHARACTER.
+
+Any number of option clauses may appear. The options are grouped by their type, but in each group the order the options appeared textually is preserved. The following groups exist and are executed in the following order: :MERGE and :FUZE (one group), :CASE,:MACRO-CHAR and :DISPATCH-MACRO-CHAR (one group), finally :SYNTAX-FROM.
+
+Notes:
+
+ The readtable is defined at load-time. If you want to have it available at compilation time -- say to use its reader-macros in the same file as its definition -- you have to wrap the DEFREADTABLE form in an explicit EVAL-WHEN.
+
+ On redefinition, the target readtable is made empty first before it's refilled according to the clauses.
+
+ NIL,:STANDARD,:COMMON-LISP,:MODERN, and :CURRENT are preregistered readtable names.
+
+
+
+
+Looks up the readtable specified by name and returns it if it's found. If it is not found, it registers the readtable designated by default under the name represented by name; or if no default argument is given, it signals an error of type READTABLE-DOES-NOT-EXIST instead.
+
+
+
+
+Creates and returns a new readtable under the specified name.
+
+merge takes a list of NAMED-READTABLE-DESIGNATORS and specifies the readtables the new readtable is created from. (See the :MERGE clause of DEFREADTABLE for details.)
+
+If merge is NIL, an empty readtable is used instead.
+
+If name is not given, an anonymous empty readtable is returned.
+
+Notes:
+
+ An empty readtable is a readtable where each character's syntax is the same as in the standard readtable except that each macro character has been made a constituent. Basically: whitespace stays whitespace, everything else is constituent.
+
+
+
+
+Copy the contents of each readtable in named-readtables into result-table.
+
+If a macro character appears in more than one of the readtables, i.e. if a conflict is discovered during the merge, an error of type READER-MACRO-CONFLICT is signaled.
+
+
+
+This condition is signaled during the merge process if a) a reader macro (be it a macro character or the sub character of a dispatch macro character) is both present in the source as well as the target readtable, and b) if and only if the two respective reader macro functions differ.
+
+
+
+
+Replaces the associated name of the readtable designated by old-name with new-name. If a readtable is already registered under new-name, an error of type READTABLE-DOES-ALREADY-EXIST is signaled.
+
+
+
+
+Remove the association of named-readtable. Returns T if successfull, NIL otherwise.
+
+
+
+
+
+
+
+
+
+This documentation was generated on 2009-9-29 from a Lisp image using some home-brewn,
+duct-taped, evolutionary hacked extension of Edi Weitz'
+DOCUMENTATION-TEMPLATE.
+
+
+
+
\ No newline at end of file
Added: trunk/lib/named-readtables/named-readtables.asd
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/named-readtables.asd Thu Oct 22 16:10:10 2009
@@ -0,0 +1,50 @@
+;;; -*- Mode:Lisp -*-
+
+(in-package :cl-user)
+
+(defclass asdf::named-readtables-source-file (asdf:cl-source-file) ())
+
+#+sbcl
+(defmethod asdf:perform :around ((o asdf:compile-op)
+ (c asdf::named-readtables-source-file))
+ (let ((sb-ext:*derive-function-types* t))
+ (call-next-method)))
+
+
+(asdf:defsystem :named-readtables
+ :description "Library that creates a namespace for named readtable akin to the namespace of packages."
+ :author "Tobias C. Rittweiler "
+ :version "1.0 (unpublished so far)"
+ :licence "BSD"
+ :default-component-class asdf::named-readtables-source-file
+ :components
+ ((:file "package")
+ (:file "utils" :depends-on ("package"))
+ (:file "define-api" :depends-on ("package" "utils"))
+ (:file "cruft" :depends-on ("package" "utils"))
+ (:file "named-readtables" :depends-on ("package" "utils" "cruft" "define-api"))))
+
+(defmethod asdf:perform ((o asdf:test-op)
+ (c (eql (asdf:find-system :named-readtables))))
+ (asdf:operate 'asdf:load-op :named-readtables-test)
+ (asdf:operate 'asdf:test-op :named-readtables-test))
+
+
+(asdf:defsystem :named-readtables-test
+ :description "Test suite for the Named-Readtables library."
+ :author "Tobias C. Rittweiler "
+ :depends-on (:named-readtables)
+ :components
+ ((:module tests
+ :default-component-class asdf::named-readtables-source-file
+ :serial t
+ :components
+ ((:file "package")
+ (:file "rt" :depends-on ("package"))
+ (:file "tests" :depends-on ("package" "rt"))))))
+
+(defmethod asdf:perform ((o asdf:test-op)
+ (c (eql (asdf:find-system
+ :named-readtables-test))))
+ (let ((*package* (find-package :named-readtables-test)))
+ (funcall (intern (string '#:do-tests) *package*))))
\ No newline at end of file
Added: trunk/lib/named-readtables/named-readtables.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/named-readtables.lisp Thu Oct 22 16:10:10 2009
@@ -0,0 +1,527 @@
+;;;; -*- Mode:Lisp -*-
+;;;;
+;;;; Copyright (c) 2007 - 2009 Tobias C. Rittweiler
+;;;; Copyright (c) 2007, Robert P. Goldman and SIFT, LLC
+;;;;
+;;;; All rights reserved.
+;;;;
+;;;; See LICENSE for details.
+;;;;
+
+(in-package :editor-hints.named-readtables)
+
+;;;
+;;; ``This is enough of a foothold to implement a more elaborate
+;;; facility for using readtables in a localized way.''
+;;;
+;;; (X3J13 Cleanup Issue IN-SYNTAX)
+;;;
+
+;;;;;; DEFREADTABLE &c.
+
+(defmacro defreadtable (name &body options)
+ "Define a new named readtable, whose name is given by the symbol `name'.
+Or, if a readtable is already registered under that name, redefine that
+one.
+
+The readtable can be populated using the following `options':
+
+ (:MERGE `readtable-designators'+)
+
+ Merge the readtables designated into the new readtable being defined
+ as per MERGE-READTABLES-INTO.
+
+ If no :MERGE clause is given, an empty readtable is used. See
+ MAKE-READTABLE.
+
+ (:FUZE `readtable-designators'+)
+
+ Like :MERGE except:
+
+ Error conditions of type READER-MACRO-CONFLICT that are signaled
+ during the merge operation will be silently _continued_. It follows
+ that reader macros in earlier entries will be overwritten by later
+ ones.
+
+ (:DISPATCH-MACRO-CHAR `macro-char' `sub-char' `function')
+
+ Define a new sub character `sub-char' for the dispatching macro
+ character `macro-char', per SET-DISPATCH-MACRO-CHARACTER. You
+ probably have to define `macro-char' as a dispatching macro character
+ by the following option first.
+
+ (:MACRO-CHAR `macro-char' `function' [`non-terminating-p'])
+
+ Define a new macro character in the readtable, per SET-MACRO-CHARACTER.
+ If `function' is the keyword :DISPATCH, `macro-char' is made a
+ dispatching macro character, per MAKE-DISPATCH-MACRO-CHARACTER.
+
+ (:SYNTAX-FROM `from-readtable-designator' `from-char' `to-char')
+
+ Set the character syntax of `to-char' in the readtable being defined
+ to the same syntax as `from-char' as per SET-SYNTAX-FROM-CHAR.
+
+ (:CASE `case-mode')
+
+ Defines the /case sensitivity mode/ of the resulting readtable.
+
+Any number of option clauses may appear. The options are grouped by their
+type, but in each group the order the options appeared textually is
+preserved. The following groups exist and are executed in the following
+order: :MERGE and :FUZE (one group), :CASE, :MACRO-CHAR
+and :DISPATCH-MACRO-CHAR (one group), finally :SYNTAX-FROM.
+
+Notes:
+
+ The readtable is defined at load-time. If you want to have it available
+ at compilation time -- say to use its reader-macros in the same file as
+ its definition -- you have to wrap the DEFREADTABLE form in an explicit
+ EVAL-WHEN.
+
+ On redefinition, the target readtable is made empty first before it's
+ refilled according to the clauses.
+
+ NIL, :STANDARD, :COMMON-LISP, :MODERN, and :CURRENT are
+ preregistered readtable names.
+"
+ (check-type name symbol)
+ (when (reserved-readtable-name-p name)
+ (error "~A is the designator for a predefined readtable. ~
+ Not acceptable as a user-specified readtable name." name))
+ (flet ((process-option (option var)
+ (destructure-case option
+ ((:merge &rest readtable-designators)
+ `(merge-readtables-into ,var
+ ,@(mapcar #'(lambda (x) `',x) readtable-designators))) ; quotify
+ ((:fuze &rest readtable-designators)
+ `(handler-bind ((reader-macro-conflict #'continue))
+ (merge-readtables-into ,var
+ ,@(mapcar #'(lambda (x) `',x) readtable-designators))))
+ ((:dispatch-macro-char disp-char sub-char function)
+ `(set-dispatch-macro-character ,disp-char ,sub-char ,function ,var))
+ ((:macro-char char function &optional non-terminating-p)
+ (if (eq function :dispatch)
+ `(make-dispatch-macro-character ,char ,non-terminating-p ,var)
+ `(set-macro-character ,char ,function ,non-terminating-p ,var)))
+ ((:syntax-from from-rt-designator from-char to-char)
+ `(set-syntax-from-char ,to-char ,from-char
+ ,var (find-readtable ,from-rt-designator)))
+ ((:case mode)
+ `(setf (readtable-case ,var) ,mode))))
+ (remove-clauses (clauses options)
+ (setq clauses (if (listp clauses) clauses (list clauses)))
+ (remove-if-not #'(lambda (x) (member x clauses))
+ options :key #'first)))
+ (let* ((merge-clauses (remove-clauses '(:merge :fuze) options))
+ (case-clauses (remove-clauses :case options))
+ (macro-clauses (remove-clauses '(:macro-char :dispatch-macro-char)
+ options))
+ (syntax-clauses (remove-clauses :syntax-from options))
+ (other-clauses (set-difference options
+ (append merge-clauses case-clauses
+ macro-clauses syntax-clauses))))
+ (cond
+ ((not (null other-clauses))
+ (error "Bogus DEFREADTABLE clauses: ~/PPRINT-LINEAR/" other-clauses))
+ (t
+ `(eval-when (:load-toplevel :execute)
+ ;; The (FIND-READTABLE ...) isqrt important for proper
+ ;; redefinition semantics, as redefining has to modify the
+ ;; already existing readtable object.
+ (let ((readtable (find-readtable ',name)))
+ (cond ((not readtable)
+ (setq readtable (make-readtable ',name)))
+ (t
+ (setq readtable (%clear-readtable readtable))
+ (simple-style-warn "Overwriting already existing readtable ~S."
+ readtable)))
+ ,@(loop for option in merge-clauses
+ collect (process-option option 'readtable))
+ ,@(loop for option in case-clauses
+ collect (process-option option 'readtable))
+ ,@(loop for option in macro-clauses
+ collect (process-option option 'readtable))
+ ,@(loop for option in syntax-clauses
+ collect (process-option option 'readtable))
+ readtable)))))))
+
+(defmacro in-readtable (name)
+ "Set *READTABLE* to the readtable referred to by the symbol `name'."
+ (check-type name symbol)
+ `(eval-when (:compile-toplevel :load-toplevel :execute)
+ ;; NB. The :LOAD-TOPLEVEL is needed for cases like (DEFVAR *FOO*
+ ;; (GET-MACRO-CHARACTER #\"))
+ (setf *readtable* (ensure-readtable ',name))
+ (when (find-package :swank)
+ (%frob-swank-readtable-alist *package* *readtable*))
+ ))
+
+;;; KLUDGE: [interim solution]
+;;;
+;;; We need support for this in Slime itself, because we want IN-READTABLE
+;;; to work on a per-file basis, and not on a per-package basis.
+;;;
+(defun %frob-swank-readtable-alist (package readtable)
+ (let ((readtable-alist (find-symbol (string '#:*readtable-alist*)
+ (find-package :swank))))
+ (when (boundp readtable-alist)
+ (pushnew (cons (package-name package) readtable)
+ (symbol-value readtable-alist)
+ :test #'(lambda (entry1 entry2)
+ (destructuring-bind (pkg-name1 . rt1) entry1
+ (destructuring-bind (pkg-name2 . rt2) entry2
+ (and (string= pkg-name1 pkg-name2)
+ (eq rt1 rt2)))))))))
+
+(deftype readtable-designator ()
+ `(or null readtable))
+
+(deftype named-readtable-designator ()
+ "Either a symbol or a readtable itself."
+ `(or readtable-designator symbol))
+
+
+(declaim (special *standard-readtable* *empty-readtable*))
+
+(define-api make-readtable
+ (&optional (name nil name-supplied-p) &key merge)
+ (&optional named-readtable-designator &key (:merge list) => readtable)
+ "Creates and returns a new readtable under the specified `name'.
+
+`merge' takes a list of NAMED-READTABLE-DESIGNATORS and specifies the
+readtables the new readtable is created from. (See the :MERGE clause of
+DEFREADTABLE for details.)
+
+If `merge' is NIL, an empty readtable is used instead.
+
+If `name' is not given, an anonymous empty readtable is returned.
+
+Notes:
+
+ An empty readtable is a readtable where each character's syntax is the
+ same as in the /standard readtable/ except that each macro character has
+ been made a constituent. Basically: whitespace stays whitespace,
+ everything else is constituent."
+ (cond ((not name-supplied-p)
+ (copy-readtable *empty-readtable*))
+ ((reserved-readtable-name-p name)
+ (error "~A is the designator for a predefined readtable. ~
+ Not acceptable as a user-specified readtable name." name))
+ ((let ((rt (find-readtable name)))
+ (and rt (prog1 nil
+ (cerror "Overwrite existing entry."
+ 'readtable-does-already-exist :readtable-name name)
+ ;; Explicitly unregister to make sure that we do not hold on
+ ;; of any reference to RT.
+ (unregister-readtable rt)))))
+ (t (let ((result (apply #'merge-readtables-into
+ ;; The first readtable specified in the :merge list is
+ ;; taken as the basis for all subsequent (destructive!)
+ ;; modifications (and hence it's copied.)
+ (copy-readtable (if merge
+ (ensure-readtable (first merge))
+ *empty-readtable*))
+ (rest merge))))
+
+ (register-readtable name result)))))
+
+(define-api rename-readtable
+ (old-name new-name)
+ (named-readtable-designator symbol => readtable)
+ "Replaces the associated name of the readtable designated by `old-name'
+with `new-name'. If a readtable is already registered under `new-name', an
+error of type READTABLE-DOES-ALREADY-EXIST is signaled."
+ (when (find-readtable new-name)
+ (cerror "Overwrite existing entry."
+ 'readtable-does-already-exist :readtable-name new-name))
+ (let* ((readtable (ensure-readtable old-name))
+ (readtable-name (readtable-name readtable)))
+ ;; We use the internal functions directly to omit repeated
+ ;; type-checking.
+ (%unassociate-name-from-readtable readtable-name readtable)
+ (%unassociate-readtable-from-name readtable-name readtable)
+ (%associate-name-with-readtable new-name readtable)
+ (%associate-readtable-with-name new-name readtable)
+ readtable))
+
+(define-api merge-readtables-into
+ (result-readtable &rest named-readtables)
+ (named-readtable-designator &rest named-readtable-designator => readtable)
+ "Copy the contents of each readtable in `named-readtables' into
+`result-table'.
+
+If a macro character appears in more than one of the readtables, i.e. if a
+conflict is discovered during the merge, an error of type
+READER-MACRO-CONFLICT is signaled."
+ (flet ((merge-into (to from)
+ (do-readtable ((char reader-fn non-terminating-p disp? table) from)
+ (check-reader-macro-conflict from to char)
+ (cond ((not disp?)
+ (set-macro-character char reader-fn non-terminating-p to))
+ (t
+ (ensure-dispatch-macro-character char non-terminating-p to)
+ (loop for (subchar . subfn) in table do
+ (check-reader-macro-conflict from to char subchar)
+ (set-dispatch-macro-character char subchar subfn to)))))
+ to))
+ (let ((result-table (ensure-readtable result-readtable)))
+ (dolist (table (mapcar #'ensure-readtable named-readtables))
+ (merge-into result-table table))
+ result-table)))
+
+(defun ensure-dispatch-macro-character (char &optional non-terminating-p
+ (readtable *readtable*))
+ (if (dispatch-macro-char-p char readtable)
+ t
+ (make-dispatch-macro-character char non-terminating-p readtable)))
+
+(define-api copy-named-readtable
+ (named-readtable)
+ (named-readtable-designator => readtable)
+ "Like COPY-READTABLE but takes a NAMED-READTABLE-DESIGNATOR as argument."
+ (copy-readtable (ensure-readtable named-readtable)))
+
+(define-api list-all-named-readtables () (=> list)
+ "Returns a list of all registered readtables. The returned list is
+guaranteed to be fresh, but may contain duplicates."
+ (mapcar #'ensure-readtable (%list-all-readtable-names)))
+
+
+(define-condition readtable-error (error) ())
+
+(define-condition readtable-does-not-exist (readtable-error)
+ ((readtable-name :initarg :readtable-name
+ :initform (required-argument)
+ :accessor missing-readtable-name
+ :type named-readtable-designator))
+ (:report (lambda (condition stream)
+ (format stream "A readtable named ~S does not exist."
+ (missing-readtable-name condition)))))
+
+(define-condition readtable-does-already-exist (readtable-error)
+ ((readtable-name :initarg :readtable-name
+ :initform (required-argument)
+ :accessor existing-readtable-name
+ :type named-readtable-designator))
+ (:report (lambda (condition stream)
+ (format stream "A readtable named ~S already exists."
+ (existing-readtable-name condition))))
+ (:documentation "Continuable."))
+
+(define-condition reader-macro-conflict (readtable-error)
+ ((macro-char
+ :initarg :macro-char
+ :initform (required-argument)
+ :accessor conflicting-macro-char
+ :type character)
+ (sub-char
+ :initarg :sub-char
+ :initform nil
+ :accessor conflicting-dispatch-sub-char
+ :type (or null character))
+ (from-readtable
+ :initarg :from-readtable
+ :initform (required-argument)
+ :accessor from-readtable
+ :type readtable)
+ (to-readtable
+ :initarg :to-readtable
+ :initform (required-argument)
+ :accessor to-readtable
+ :type readtable))
+ (:report
+ (lambda (condition stream)
+ (format stream "~@"
+ (conflicting-dispatch-sub-char condition)
+ (conflicting-macro-char condition)
+ (conflicting-dispatch-sub-char condition)
+ (from-readtable condition)
+ (to-readtable condition))))
+ (:documentation "Continuable.
+
+This condition is signaled during the merge process if a) a reader macro
+\(be it a macro character or the sub character of a dispatch macro
+character\) is both present in the source as well as the target readtable,
+and b) if and only if the two respective reader macro functions differ."))
+
+(defun check-reader-macro-conflict (from to char &optional subchar)
+ (flet ((conflictp (from-fn to-fn)
+ (assert from-fn) ; if this fails, there's a bug in readtable iterators.
+ (and to-fn (not (function= to-fn from-fn)))))
+ (when (if subchar
+ (conflictp (%get-dispatch-macro-character char subchar from)
+ (%get-dispatch-macro-character char subchar to))
+ (conflictp (%get-macro-character char from)
+ (%get-macro-character char to)))
+ (cerror (format nil "Overwrite ~@C in ~A." char to)
+ 'reader-macro-conflict
+ :from-readtable from
+ :to-readtable to
+ :macro-char char
+ :sub-char subchar))))
+
+
+;;; Although there is no way to get at the standard readtable in
+;;; Common Lisp (cf. /standard readtable/, CLHS glossary), we make
+;;; up the perception of its existence by interning a copy of it.
+;;;
+;;; We do this for reverse lookup (cf. READTABLE-NAME), i.e. for
+;;;
+;;; (equal (readtable-name (find-readtable :standard)) "STANDARD")
+;;;
+;;; holding true.
+;;;
+;;; We, however, inherit the restriction that the :STANDARD
+;;; readtable _must not be modified_ (cf. CLHS 2.1.1.2), although it'd
+;;; technically be feasible (as *STANDARD-READTABLE* will contain a
+;;; mutable copy of the implementation-internal standard readtable.)
+;;; We cannot enforce this restriction without shadowing
+;;; CL:SET-MACRO-CHARACTER and CL:SET-DISPATCH-MACRO-FUNCTION which
+;;; is out of scope of this library, though. So we just threaten
+;;; with nasal demons.
+;;;
+(defvar *standard-readtable*
+ (%standard-readtable))
+
+(defvar *empty-readtable*
+ (%clear-readtable (copy-readtable nil)))
+
+(defvar *case-preserving-standard-readtable*
+ (let ((readtable (copy-readtable nil)))
+ (setf (readtable-case readtable) :preserve)
+ readtable))
+
+(defparameter *reserved-readtable-names*
+ '(nil :standard :common-lisp :modern :current))
+
+(defun reserved-readtable-name-p (name)
+ (and (member name *reserved-readtable-names*) t))
+
+;;; In principle, we could DEFREADTABLE some of these. But we do
+;;; reserved readtable lookup seperately, since we can't register a
+;;; readtable for :CURRENT anyway.
+
+(defun find-reserved-readtable (reserved-name)
+ (cond ((eq reserved-name nil) *standard-readtable*)
+ ((eq reserved-name :standard) *standard-readtable*)
+ ((eq reserved-name :common-lisp) *standard-readtable*)
+ ((eq reserved-name :modern) *case-preserving-standard-readtable*)
+ ((eq reserved-name :current) *readtable*)
+ (t (error "Bug: no such reserved readtable: ~S" reserved-name))))
+
+(define-api find-readtable
+ (name)
+ (named-readtable-designator => (or readtable null))
+ "Looks for the readtable specified by `name' and returns it if it is
+found. Returns NIL otherwise."
+ (cond ((readtablep name) name)
+ ((reserved-readtable-name-p name)
+ (find-reserved-readtable name))
+ ((%find-readtable name))))
+
+;;; FIXME: This doesn't take a NAMED-READTABLE-DESIGNATOR, but only a
+;;; STRING-DESIGNATOR. (When fixing, heed interplay with compiler
+;;; macros below.)
+(defsetf find-readtable register-readtable)
+
+(define-api ensure-readtable
+ (name &optional (default nil default-p))
+ (named-readtable-designator &optional (or named-readtable-designator null)
+ => readtable)
+ "Looks up the readtable specified by `name' and returns it if it's found.
+If it is not found, it registers the readtable designated by `default'
+under the name represented by `name'; or if no default argument is given,
+it signals an error of type READTABLE-DOES-NOT-EXIST instead."
+ (cond ((find-readtable name))
+ ((not default-p)
+ (error 'readtable-does-not-exist :readtable-name name))
+ (t (setf (find-readtable name) (ensure-readtable default)))))
+
+
+(define-api register-readtable
+ (name readtable)
+ (symbol readtable => readtable)
+ "Associate `readtable' with `name'. Returns the readtable."
+ (assert (typep name '(not (satisfies reserved-readtable-name-p))))
+ (%associate-readtable-with-name name readtable)
+ (%associate-name-with-readtable name readtable)
+ readtable)
+
+(define-api unregister-readtable
+ (named-readtable)
+ (named-readtable-designator => boolean)
+ "Remove the association of `named-readtable'. Returns T if successfull,
+NIL otherwise."
+ (let* ((readtable (find-readtable named-readtable))
+ (readtable-name (and readtable (readtable-name readtable))))
+ (if (not readtable-name)
+ nil
+ (prog1 t
+ (check-type readtable-name (not (satisfies reserved-readtable-name-p)))
+ (%unassociate-readtable-from-name readtable-name readtable)
+ (%unassociate-name-from-readtable readtable-name readtable)))))
+
+(define-api readtable-name
+ (named-readtable)
+ (named-readtable-designator => symbol)
+ "Returns the name of the readtable designated by `named-readtable', or
+NIL."
+ (let ((readtable (ensure-readtable named-readtable)))
+ (cond ((%readtable-name readtable))
+ ((eq readtable *readtable*) :current)
+ ((eq readtable *standard-readtable*) :common-lisp)
+ ((eq readtable *case-preserving-standard-readtable*) :modern)
+ (t nil))))
+
+
+;;;;; Compiler macros
+
+;;; Since the :STANDARD readtable is interned, and we can't enforce
+;;; its immutability, we signal a style-warning for suspicious uses
+;;; that may result in strange behaviour:
+
+;;; Modifying the standard readtable would, obviously, lead to a
+;;; propagation of this change to all places which use the :STANDARD
+;;; readtable (and thus rendering this readtable to be non-standard,
+;;; in fact.)
+
+
+(defun constant-standard-readtable-expression-p (thing)
+ (cond ((symbolp thing) (or (eq thing 'nil) (eq thing :standard)))
+ ((consp thing) (some (lambda (x) (equal thing x))
+ '((find-readtable nil)
+ (find-readtable :standard)
+ (ensure-readtable nil)
+ (ensure-readtable :standard))))
+ (t nil)))
+
+(defun signal-suspicious-registration-warning (name-expr readtable-expr)
+ (simple-style-warn
+ "Caution: ~~% ~S"
+ (list name-expr name-expr) readtable-expr))
+
+(let ()
+ ;; Defer to runtime because compiler-macros are made available already
+ ;; at compilation time. So without this two subsequent invocations of
+ ;; COMPILE-FILE on this file would result in an undefined function
+ ;; error because the two above functions are not yet available.
+ ;; (This does not use EVAL-WHEN because of Fig 3.7, CLHS 3.2.3.1;
+ ;; cf. last example in CLHS "EVAL-WHEN" entry.)
+
+ (define-compiler-macro register-readtable (&whole form name readtable)
+ (when (constant-standard-readtable-expression-p readtable)
+ (signal-suspicious-registration-warning name readtable))
+ form)
+
+ (define-compiler-macro ensure-readtable (&whole form name &optional (default nil default-p))
+ (when (and default-p (constant-standard-readtable-expression-p default))
+ (signal-suspicious-registration-warning name default))
+ form))
+
+
Added: trunk/lib/named-readtables/package.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/package.lisp Thu Oct 22 16:10:10 2009
@@ -0,0 +1,193 @@
+
+(in-package :common-lisp-user)
+
+(defpackage :editor-hints.named-readtables
+ (:use :common-lisp)
+ (:nicknames :named-readtables)
+ (:export
+ #:defreadtable
+ #:in-readtable
+ #:make-readtable
+ #:merge-readtables-into
+ #:find-readtable
+ #:ensure-readtable
+ #:rename-readtable
+ #:readtable-name
+ #:register-readtable
+ #:unregister-readtable
+ #:copy-named-readtable
+ #:list-all-named-readtables
+ ;; Types
+ #:named-readtable-designator
+ ;; Conditions
+ #:reader-macro-conflict
+ #:readtable-does-already-exist
+ #:readtable-does-not-exist
+ )
+ (:documentation
+ "
+* What are Named-Readtables?
+
+ Named-Readtables is a library that provides a namespace for
+ readtables akin to the already-existing namespace of packages. In
+ particular:
+
+ * you can associate readtables with names, and retrieve
+ readtables by names;
+
+ * you can associate source files with readtable names, and be
+ sure that the right readtable is active when compiling/loading
+ the file;
+
+ * similiarly, your development environment now has a chance to
+ automatically determine what readtable should be active while
+ processing source forms on interactive commands. (E.g. think of
+ `C-c C-c' in Slime [yet to be done])
+
+ It follows that Named-Readtables is a facility for using readtables in
+ a localized way.
+
+ Additionally, it also attempts to become a facility for using
+ readtables in a _modular_ way. In particular:
+
+ * it provides a macro to specify the content of a readtable at a
+ glance;
+
+ * it makes it possible to use multiple inheritance between readtables.
+
+* Notes on the API
+
+ The API heavily imitates the API of packages. This has the nice
+ property that any experienced Common Lisper will take it up without
+ effort.
+
+ DEFREADTABLE - DEFPACKAGE
+
+ IN-READTABLE - IN-PACKAGE
+
+ MERGE-READTABLES-INTO - USE-PACKAGE
+
+ MAKE-READTABLE - MAKE-PACKAGE
+
+ UNREGISTER-READTABLE - DELETE-PACKAGE
+
+ RENAME-READTABLE - RENAME-PACKAGE
+
+ FIND-READTABLE - FIND-PACKAGE
+
+ READTABLE-NAME - PACKAGE-NAME
+
+ LIST-ALL-NAMED-READTABLES - LIST-ALL-PACKAGES
+
+* Important API idiosyncrasies
+
+ There are three major differences between the API of Named-Readtables,
+ and the API of packages.
+
+ 1. Readtable names are symbols not strings.
+
+ Time has shown that the fact that packages are named by
+ strings causes severe headache because of the potential of
+ package names colliding with each other.
+
+ Hence, readtables are named by symbols lest to make the
+ situation worse than it already is. Consequently, readtables
+ named CL-ORACLE:SQL-SYNTAX and CL-MYSQL:SQL-SYNTAX can
+ happily coexist next to each other. Or, taken to an extreme,
+ SCHEME:SYNTAX and ELISP:SYNTAX.
+
+ If, for example to duly signify the importance of your cool
+ readtable hack, you really think it deserves a global name,
+ you can always resort to keywords.
+
+ 2. The inheritance is resolved statically, not dynamically.
+
+ A package that uses another package will have access to all
+ the other package's exported symbols, even to those that will
+ be added after its definition. I.e. the inheritance is
+ resolved at run-time, that is dynamically.
+
+ Unfortunately, we cannot do the same for readtables in a
+ portable manner.
+
+ Therefore, we do not talk about \"using\" another readtable
+ but about \"merging\" the other readtable's definition into
+ the readtable we are going to define. I.e. the inheritance is
+ resolved once at definition time, that is statically.
+
+ (Such merging can more or less be implemented portably albeit
+ at a certain cost. Most of the time, this cost manifests
+ itself at the time a readtable is defined, i.e. once at
+ compile-time, so it may not bother you. Nonetheless, we
+ provide extra support for Sbcl, ClozureCL, and AllegroCL at
+ the moment. Patches for your implementation of choice are
+ welcome, of course.)
+
+ 3. DEFREADTABLE does not have compile-time effects.
+
+ If you define a package via DEFPACKAGE, you can make that
+ package the currently active package for the subsequent
+ compilation of the same file via IN-PACKAGE. The same is,
+ however, not true for DEFREADTABLE and IN-READTABLE for the
+ following reason:
+
+ It's unlikely that the need for special reader-macros arises
+ for a problem which can be solved in just one file. Most
+ often, you're going to define the reader macro functions, and
+ set up the corresponding readtable in an extra file.
+
+ If DEFREADTABLE had compile-time effects, you'd have to wrap
+ each definition of a reader-macro function in an EVAL-WHEN to
+ make its definition available at compile-time. Because that's
+ simply not the common case, DEFREADTABLE does not have a
+ compile-time effect.
+
+ If you want to use a readtable within the same file as its
+ definition, wrap the DEFREADTABLE and the reader-macro
+ function definitions in an explicit EVAL-WHEN.
+
+* Preregistered Readtables
+
+ - NIL, :STANDARD, and :COMMON-LISP designate the /standard readtable/.
+
+ - :MODERN designates a _case-preserving_ /standard-readtable/.
+
+ - :CURRENT designates the /current readtable/.
+
+* Examples
+
+ > (defreadtable elisp:syntax
+ > (:merge :standard)
+ > (:macro-char #\\? #'elisp::read-character-literal t)
+ > (:macro-char #\\[ #'elisp::read-vector-literal t)
+ > ...
+ > (:case :preserve))
+ >
+ > (defreadtable scheme:syntax
+ > (:merge :standard)
+ > (:macro-char #\\[ #'(lambda (stream char)
+ > (read-delimited-list #\\] stream)))
+ > (:macro-char #\\# :dispatch)
+ > (:dispatch-macro-char #\\# #\\t #'scheme::read-#t)
+ > (:dispatch-macro-char #\\# #\\f #'scheme::read-#f)
+ > ...
+ > (:case :preserve))
+ >
+ > (in-readtable elisp:syntax)
+ >
+ > ...
+ >
+ > (in-readtable scheme:syntax)
+ >
+ > ...
+
+* Acknowledgements
+
+ Thanks to Robert Goldman for making me want to write this library.
+
+ Thanks to Stephen Compall, Ariel Badichi, David Lichteblau, Bart
+ Botta, David Crawford, and Pascal Costanza for being early adopters,
+ providing comments and bugfixes.
+"))
+
+(pushnew :named-readtables *features*)
\ No newline at end of file
Added: trunk/lib/named-readtables/tests/package.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/tests/package.lisp Thu Oct 22 16:10:10 2009
@@ -0,0 +1,12 @@
+;;; -*- Mode:Lisp -*-
+
+(in-package :cl-user)
+
+(defpackage :named-readtables-test
+ (:use :cl :named-readtables)
+ (:import-from :named-readtables
+ #:dispatch-macro-char-p
+ #:do-readtable
+ #:ensure-function
+ #:ensure-dispatch-macro-character
+ #:function=))
\ No newline at end of file
Added: trunk/lib/named-readtables/tests/rt.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/tests/rt.lisp Thu Oct 22 16:10:10 2009
@@ -0,0 +1,256 @@
+#|----------------------------------------------------------------------------|
+ | Copyright 1990 by the Massachusetts Institute of Technology, Cambridge MA. |
+ | |
+ | Permission to use, copy, modify, and distribute this software and its |
+ | documentation for any purpose and without fee is hereby granted, provided |
+ | that this copyright and permission notice appear in all copies and |
+ | supporting documentation, and that the name of M.I.T. not be used in |
+ | advertising or publicity pertaining to distribution of the software |
+ | without specific, written prior permission. M.I.T. makes no |
+ | representations about the suitability of this software for any purpose. |
+ | It is provided "as is" without express or implied warranty. |
+ | |
+ | M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING |
+ | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL |
+ | M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR |
+ | ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, |
+ | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, |
+ | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS |
+ | SOFTWARE. |
+ |----------------------------------------------------------------------------|#
+
+;; (defpackage :rt
+;; (:use #:cl)
+;; (:export #:*do-tests-when-defined* #:*test* #:continue-testing
+;; #:deftest #:do-test #:do-tests #:get-test #:pending-tests
+;; #:rem-all-tests #:rem-test)
+;; (:documentation "The MIT regression tester"))
+
+;; (in-package :rt)
+
+(in-package :named-readtables-test)
+
+(defvar *test* nil "Current test name")
+(defvar *do-tests-when-defined* nil)
+(defvar *entries* '(nil) "Test database")
+(defvar *in-test* nil "Used by TEST")
+(defvar *debug* nil "For debugging")
+(defvar *catch-errors* t
+ "When true, causes errors in a test to be caught.")
+(defvar *print-circle-on-failure* nil
+ "Failure reports are printed with *PRINT-CIRCLE* bound to this value.")
+(defvar *compile-tests* nil
+ "When true, compile the tests before running them.")
+(defvar *optimization-settings* '((safety 3)))
+(defvar *expected-failures* nil
+ "A list of test names that are expected to fail.")
+
+(defstruct (entry (:conc-name nil)
+ (:type list))
+ pend name form)
+
+(defmacro vals (entry) `(cdddr ,entry))
+
+(defmacro defn (entry) `(cdr ,entry))
+
+(defun pending-tests ()
+ (do ((l (cdr *entries*) (cdr l))
+ (r nil))
+ ((null l) (nreverse r))
+ (when (pend (car l))
+ (push (name (car l)) r))))
+
+(defun rem-all-tests ()
+ (setq *entries* (list nil))
+ nil)
+
+(defun rem-test (&optional (name *test*))
+ (do ((l *entries* (cdr l)))
+ ((null (cdr l)) nil)
+ (when (equal (name (cadr l)) name)
+ (setf (cdr l) (cddr l))
+ (return name))))
+
+(defun get-test (&optional (name *test*))
+ (defn (get-entry name)))
+
+(defun get-entry (name)
+ (let ((entry (find name (cdr *entries*)
+ :key #'name
+ :test #'equal)))
+ (when (null entry)
+ (report-error t
+ "~%No test with name ~:@(~S~)."
+ name))
+ entry))
+
+(defmacro deftest (name form &rest values)
+ `(add-entry '(t ,name ,form .,values)))
+
+(defun add-entry (entry)
+ (setq entry (copy-list entry))
+ (do ((l *entries* (cdr l))) (nil)
+ (when (null (cdr l))
+ (setf (cdr l) (list entry))
+ (return nil))
+ (when (equal (name (cadr l))
+ (name entry))
+ (setf (cadr l) entry)
+ (report-error nil
+ "Redefining test ~:@(~S~)"
+ (name entry))
+ (return nil)))
+ (when *do-tests-when-defined*
+ (do-entry entry))
+ (setq *test* (name entry)))
+
+(defun report-error (error? &rest args)
+ (cond (*debug*
+ (apply #'format t args)
+ (if error? (throw '*debug* nil)))
+ (error? (apply #'error args))
+ (t (apply #'warn args))))
+
+(defun do-test (&optional (name *test*))
+ (do-entry (get-entry name)))
+
+(defun equalp-with-case (x y)
+ "Like EQUALP, but doesn't do case conversion of characters."
+ (cond
+ ((eq x y) t)
+ ((consp x)
+ (and (consp y)
+ (equalp-with-case (car x) (car y))
+ (equalp-with-case (cdr x) (cdr y))))
+ ((and (typep x 'array)
+ (= (array-rank x) 0))
+ (equalp-with-case (aref x) (aref y)))
+ ((typep x 'vector)
+ (and (typep y 'vector)
+ (let ((x-len (length x))
+ (y-len (length y)))
+ (and (eql x-len y-len)
+ (loop
+ for e1 across x
+ for e2 across y
+ always (equalp-with-case e1 e2))))))
+ ((and (typep x 'array)
+ (typep y 'array)
+ (not (equal (array-dimensions x)
+ (array-dimensions y))))
+ nil)
+ ((typep x 'array)
+ (and (typep y 'array)
+ (let ((size (array-total-size x)))
+ (loop for i from 0 below size
+ always (equalp-with-case (row-major-aref x i)
+ (row-major-aref y i))))))
+ (t (eql x y))))
+
+(defun do-entry (entry &optional
+ (s *standard-output*))
+ (catch '*in-test*
+ (setq *test* (name entry))
+ (setf (pend entry) t)
+ (let* ((*in-test* t)
+ ;; (*break-on-warnings* t)
+ (aborted nil)
+ r)
+ ;; (declare (special *break-on-warnings*))
+
+ (block aborted
+ (setf r
+ (flet ((%do
+ ()
+ (if *compile-tests*
+ (multiple-value-list
+ (funcall (compile
+ nil
+ `(lambda ()
+ (declare
+ (optimize ,@*optimization-settings*))
+ ,(form entry)))))
+ (multiple-value-list
+ (eval (form entry))))))
+ (if *catch-errors*
+ (handler-bind
+ ((style-warning #'muffle-warning)
+ (error #'(lambda (c)
+ (setf aborted t)
+ (setf r (list c))
+ (return-from aborted nil))))
+ (%do))
+ (%do)))))
+
+ (setf (pend entry)
+ (or aborted
+ (not (equalp-with-case r (vals entry)))))
+
+ (when (pend entry)
+ (let ((*print-circle* *print-circle-on-failure*))
+ (format s "~&Test ~:@(~S~) failed~
+ ~%Form: ~S~
+ ~%Expected value~P: ~
+ ~{~S~^~%~17t~}~%"
+ *test* (form entry)
+ (length (vals entry))
+ (vals entry))
+ (format s "Actual value~P: ~
+ ~{~S~^~%~15t~}.~%"
+ (length r) r)))))
+ (when (not (pend entry)) *test*))
+
+(defun continue-testing ()
+ (if *in-test*
+ (throw '*in-test* nil)
+ (do-entries *standard-output*)))
+
+(defun do-tests (&optional
+ (out *standard-output*))
+ (dolist (entry (cdr *entries*))
+ (setf (pend entry) t))
+ (if (streamp out)
+ (do-entries out)
+ (with-open-file
+ (stream out :direction :output)
+ (do-entries stream))))
+
+(defun do-entries (s)
+ (format s "~&Doing ~A pending test~:P ~
+ of ~A tests total.~%"
+ (count t (cdr *entries*)
+ :key #'pend)
+ (length (cdr *entries*)))
+ (dolist (entry (cdr *entries*))
+ (when (pend entry)
+ (format s "~@[~<~%~:; ~:@(~S~)~>~]"
+ (do-entry entry s))))
+ (let ((pending (pending-tests))
+ (expected-table (make-hash-table :test #'equal)))
+ (dolist (ex *expected-failures*)
+ (setf (gethash ex expected-table) t))
+ (let ((new-failures
+ (loop for pend in pending
+ unless (gethash pend expected-table)
+ collect pend)))
+ (if (null pending)
+ (format s "~&No tests failed.")
+ (progn
+ (format s "~&~A out of ~A ~
+ total tests failed: ~
+ ~:@(~{~<~% ~1:;~S~>~
+ ~^, ~}~)."
+ (length pending)
+ (length (cdr *entries*))
+ pending)
+ (if (null new-failures)
+ (format s "~&No unexpected failures.")
+ (when *expected-failures*
+ (format s "~&~A unexpected failures: ~
+ ~:@(~{~<~% ~1:;~S~>~
+ ~^, ~}~)."
+ (length new-failures)
+ new-failures)))
+ ))
+ (finish-output s)
+ (null pending))))
Added: trunk/lib/named-readtables/tests/tests.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/tests/tests.lisp Thu Oct 22 16:10:10 2009
@@ -0,0 +1,322 @@
+;;; -*- Mode:Lisp -*-
+
+(in-package :named-readtables-test)
+
+(defun map-alist (car-fn cdr-fn alist)
+ (mapcar #'(lambda (entry)
+ (cons (funcall car-fn (car entry))
+ (funcall cdr-fn (cdr entry))))
+ alist))
+
+(defun length=1 (list)
+ (and list (null (cdr list))))
+
+(defmacro signals-condition-p (name &body body)
+ `(handler-case (prog1 nil , at body)
+ (,(second name) () t)))
+
+(defmacro continue-condition (name &body body)
+ `(handler-bind ((,(second name) #'continue))
+ , at body))
+
+(defun read-with-readtable (name string)
+ (let ((*package* '#.*package*)
+ (*readtable* (find-readtable name)))
+ (values (read-from-string string))))
+
+(defun random-named-readtable ()
+ (let ((readtables (list-all-named-readtables)))
+ (nth (random (length readtables)) readtables)))
+
+
+(defun readtable-content (named-readtable-designator)
+ (let ((readtable (ensure-readtable named-readtable-designator))
+ (result '()))
+ ;; Make sure to canonicalize the order and function designators so
+ ;; we can compare easily.
+ (do-readtable ((char reader-fn ntp disp? table) readtable)
+ (setq table (sort (copy-list table) #'char< :key #'car))
+ (push (list* char
+ (ensure-function reader-fn)
+ ntp
+ (and disp? (list (map-alist #'identity
+ #'ensure-function
+ table))))
+ result))
+ (sort result #'char< :key #'car)))
+
+(defun readtable= (rt1 rt2)
+ (tree-equal (readtable-content rt1) (readtable-content rt2)
+ :test #'(lambda (x y)
+ (if (and (functionp x) (functionp y))
+ (function= x y)
+ (eql x y)))))
+
+
+(defun read-A (stream c)
+ (declare (ignore stream c))
+ :a)
+
+(defun read-A-as-X (stream c)
+ (declare (ignore stream c))
+ :x)
+
+(defun read-B (stream c)
+ (declare (ignore stream c))
+ :b)
+
+(defun read-sharp-paren (stream c n)
+ (declare (ignore stream c n))
+ 'sharp-paren)
+
+(defun read-C (stream c)
+ (declare (ignore stream c))
+ :c)
+
+(defreadtable A
+ (:macro-char #\A #'read-A))
+
+(defreadtable A-as-X
+ (:macro-char #\A #'read-A-as-X))
+
+(defreadtable B
+ (:macro-char #\B #'read-B))
+
+(defreadtable C
+ (:macro-char #\C #'read-C))
+
+(defreadtable A+B+C
+ (:merge A B C))
+
+(defreadtable standard+A+B+C
+ (:merge :standard A+B+C))
+
+(defreadtable sharp-paren
+ (:macro-char #\# :dispatch)
+ (:dispatch-macro-char #\# #\( #'read-sharp-paren))
+
+
+(deftest cruft.1
+ (function= (get-macro-character #\" (copy-readtable nil))
+ (get-macro-character #\" (copy-readtable nil)))
+ t)
+
+(deftest cruft.2
+ (dispatch-macro-char-p #\# (find-readtable :standard))
+ t)
+
+(deftest cruft.3
+ (dispatch-macro-char-p #\# (make-readtable))
+ nil)
+
+(deftest cruft.4
+ (let ((rt (copy-named-readtable :standard)))
+ (ensure-dispatch-macro-character #\# t rt)
+ (dispatch-macro-char-p #\# rt))
+ t)
+
+(deftest cruft.5
+ (let ((rt (make-readtable)))
+ (values
+ (dispatch-macro-char-p #\$ rt)
+ (ensure-dispatch-macro-character #\$ t rt)
+ (dispatch-macro-char-p #\$ rt)))
+ nil t t)
+
+(deftest cruft.6
+ (let ((rt (make-readtable))
+ (fn (constantly nil)))
+ (ensure-dispatch-macro-character #\$ t rt)
+ (set-dispatch-macro-character #\$ #\# fn rt)
+ (values
+ (eq fn (get-dispatch-macro-character #\$ #\# rt))
+ (length=1 (readtable-content rt))))
+ t t)
+
+(deftest cruft.7
+ (let ((rt (make-readtable))
+ (fn (constantly nil)))
+ (set-macro-character #\$ fn t rt)
+ (values
+ (eq fn (get-macro-character #\$ rt))
+ (length=1 (readtable-content rt))))
+ t t)
+
+
+(deftest standard.1
+ (read-with-readtable :standard "ABC")
+ ABC)
+
+(deftest standard.2
+ (read-with-readtable :standard "(A B C)")
+ (A B C))
+
+(deftest standard.3
+ (let ((x (find-readtable nil))
+ (y (find-readtable :standard))
+ (z (find-readtable :common-lisp)))
+ (and (eq x y) (eq y z)))
+ t)
+
+
+(deftest modern.1
+ (read-with-readtable :modern "FooF")
+ |FooF|)
+
+
+(deftest empty.1
+ (null (readtable-content (make-readtable)))
+ t)
+
+(deftest empty.2
+ (readtable= (merge-readtables-into (make-readtable) :standard)
+ (find-readtable :standard))
+ t)
+
+(deftest empty.3
+ (let ((rt (copy-named-readtable :standard)))
+ (readtable= (merge-readtables-into (make-readtable) rt)
+ (merge-readtables-into rt (make-readtable))))
+ t)
+
+
+(deftest basics.1
+ (read-with-readtable 'A "A")
+ :a)
+
+(deftest basics.2
+ (read-with-readtable 'A-as-X "A")
+ :x)
+
+(deftest basics.3
+ (read-with-readtable 'A "B")
+ B)
+
+(deftest basics.4
+ (read-with-readtable 'A "(A B C)")
+ |(|)
+
+
+(deftest unregister.1
+ (let ((rt (find-readtable 'A)))
+ (register-readtable 'does-not-exist rt)
+ (values
+ (and (find-readtable 'does-not-exist) t)
+ (unregister-readtable 'does-not-exist)
+ (and (find-readtable 'does-not-exist) t)))
+ t t nil)
+
+
+(deftest name.1
+ (let ((rt (random-named-readtable)))
+ (eq rt (find-readtable (readtable-name rt))))
+ t)
+
+(deftest ensure.1
+ (unwind-protect
+ (let* ((x (ensure-readtable 'does-not-exist (find-readtable 'A)))
+ (y (find-readtable 'A))
+ (z (find-readtable 'does-not-exist)))
+ (and (eq x y) (eq y z)))
+ (unregister-readtable 'does-not-exist))
+ t)
+
+
+(deftest merge.1
+ (values
+ (read-with-readtable 'A+B+C "A")
+ (read-with-readtable 'A+B+C "B")
+ (read-with-readtable 'A+B+C "C"))
+ :a :b :c)
+
+(deftest merge.2
+ (read-with-readtable 'standard+A+B+C "(A B C)")
+ (:a :b :c))
+
+(deftest merge.3
+ (read-with-readtable 'standard+A+B+C "#(A B C)")
+ #(:a :b :c))
+
+(deftest merge.4
+ (let ((A+B+C+standard (merge-readtables-into (copy-named-readtable 'A+B+C)
+ :standard)))
+ (readtable= 'standard+A+B+C A+B+C+standard))
+ t)
+
+
+(deftest rename.1
+ (unwind-protect
+ (progn (make-readtable 'A* :merge '(A))
+ (rename-readtable 'A* 'A**)
+ (values (and (find-readtable 'A*) t)
+ (and (find-readtable 'A**) t)))
+ (unregister-readtable 'A*)
+ (unregister-readtable 'A**))
+ nil
+ t)
+
+
+(deftest reader-macro-conflict.1
+ (signals-condition-p 'reader-macro-conflict
+ (merge-readtables-into (make-readtable) 'A 'A-as-X))
+ t)
+
+(deftest reader-macro-conflict.2
+ (signals-condition-p 'reader-macro-conflict
+ (merge-readtables-into (make-readtable) :standard :standard))
+ nil)
+
+(deftest reader-macro-conflict.3
+ (signals-condition-p 'reader-macro-conflict
+ (merge-readtables-into (make-readtable) 'A+B+C 'A))
+ nil)
+
+(deftest reader-macro-conflict.4
+ (signals-condition-p 'reader-macro-conflict
+ (merge-readtables-into (make-readtable) :standard 'sharp-paren))
+ t)
+
+
+(deftest readtable-does-not-exist.1
+ (signals-condition-p 'readtable-does-not-exist
+ (ensure-readtable 'does-not-exist))
+ t)
+
+
+(deftest readtable-does-already-exist.1
+ (signals-condition-p 'readtable-does-already-exist
+ (make-readtable 'A))
+ t)
+
+(deftest readtable-does-already-exist.2
+ (signals-condition-p 'readtable-does-already-exist
+ (make-readtable 'A))
+ t)
+
+(deftest readtable-does-already-exist.3
+ (let ((rt (make-readtable 'does-not-exist :merge '(:standard A B))))
+ (declare (ignore rt))
+ (unwind-protect
+ (read-with-readtable (continue-condition 'readtable-does-already-exist
+ (make-readtable 'does-not-exist
+ :merge '(:standard A C)))
+
+ "(A B C)")
+ (unregister-readtable 'does-not-exist)))
+ (:a B :c))
+
+
+(deftest defreadtable.1
+ (unwind-protect
+ (signals-condition-p 'reader-macro-conflict
+ (eval `(defreadtable does-not-exist (:merge A A-as-X))))
+ (unregister-readtable 'does-not-exist))
+ t)
+
+(deftest defreadtable.2
+ (unwind-protect
+ (signals-condition-p 't
+ (eval `(defreadtable does-not-exist (:fuze A A-as-X))))
+ (unregister-readtable 'does-not-exist))
+ nil)
+
Added: trunk/lib/named-readtables/utils.lisp
==============================================================================
--- (empty file)
+++ trunk/lib/named-readtables/utils.lisp Thu Oct 22 16:10:10 2009
@@ -0,0 +1,245 @@
+;;;;
+;;;; Copyright (c) 2008 - 2009 Tobias C. Rittweiler
+;;;;
+;;;; All rights reserved.
+;;;;
+;;;; See LICENSE for details.
+;;;;
+
+(in-package :editor-hints.named-readtables)
+
+(defmacro without-package-lock ((&rest package-names) &body body)
+ (declare (ignorable package-names))
+ #+clisp
+ (return-from without-package-lock
+ `(ext:without-package-lock (, at package-names) , at body))
+ #+lispworks
+ (return-from without-package-lock
+ `(let ((hcl:*packages-for-warn-on-redefinition*
+ (set-difference hcl:*packages-for-warn-on-redefinition*
+ '(, at package-names)
+ :key (lambda (package-designator)
+ (if (packagep package-designator)
+ (package-name package-designator)
+ package-designator))
+ :test #'string=)))
+ , at body))
+ `(progn , at body))
+
+;;; Taken from SWANK (which is Public Domain.)
+
+(defmacro destructure-case (value &rest patterns)
+ "Dispatch VALUE to one of PATTERNS.
+A cross between `case' and `destructuring-bind'.
+The pattern syntax is:
+ ((HEAD . ARGS) . BODY)
+The list of patterns is searched for a HEAD `eq' to the car of
+VALUE. If one is found, the BODY is executed with ARGS bound to the
+corresponding values in the CDR of VALUE."
+ (let ((operator (gensym "op-"))
+ (operands (gensym "rand-"))
+ (tmp (gensym "tmp-")))
+ `(let* ((,tmp ,value)
+ (,operator (car ,tmp))
+ (,operands (cdr ,tmp)))
+ (case ,operator
+ ,@(loop for (pattern . body) in patterns collect
+ (if (eq pattern t)
+ `(t , at body)
+ (destructuring-bind (op &rest rands) pattern
+ `(,op (destructuring-bind ,rands ,operands
+ , at body)))))
+ ,@(if (eq (caar (last patterns)) t)
+ '()
+ `((t (error "destructure-case failed: ~S" ,tmp))))))))
+
+;;; Taken from Alexandria (which is Public Domain, or BSD.)
+
+(define-condition simple-style-warning (simple-warning style-warning)
+ ())
+
+(defun simple-style-warn (format-control &rest format-args)
+ (warn 'simple-style-warning
+ :format-control format-control
+ :format-arguments format-args))
+
+(define-condition simple-program-error (simple-error program-error)
+ ())
+
+(defun simple-program-error (message &rest args)
+ (error 'simple-program-error
+ :format-control message
+ :format-arguments args))
+
+(defun required-argument (&optional name)
+ "Signals an error for a missing argument of NAME. Intended for
+use as an initialization form for structure and class-slots, and
+a default value for required keyword arguments."
+ (error "Required argument ~@[~S ~]missing." name))
+
+(defun ensure-list (list)
+ "If LIST is a list, it is returned. Otherwise returns the list
+designated by LIST."
+ (if (listp list)
+ list
+ (list list)))
+
+(declaim (inline ensure-function)) ; to propagate return type.
+(declaim (ftype (function (t) (values function &optional))
+ ensure-function))
+(defun ensure-function (function-designator)
+ "Returns the function designated by FUNCTION-DESIGNATOR:
+if FUNCTION-DESIGNATOR is a function, it is returned, otherwise
+it must be a function name and its FDEFINITION is returned."
+ (if (functionp function-designator)
+ function-designator
+ (fdefinition function-designator)))
+
+(defun parse-body (body &key documentation whole)
+ "Parses BODY into (values remaining-forms declarations doc-string).
+Documentation strings are recognized only if DOCUMENTATION is true.
+Syntax errors in body are signalled and WHOLE is used in the signal
+arguments when given."
+ (let ((doc nil)
+ (decls nil)
+ (current nil))
+ (tagbody
+ :declarations
+ (setf current (car body))
+ (when (and documentation (stringp current) (cdr body))
+ (if doc
+ (error "Too many documentation strings in ~S." (or whole body))
+ (setf doc (pop body)))
+ (go :declarations))
+ (when (and (listp current) (eql (first current) 'declare))
+ (push (pop body) decls)
+ (go :declarations)))
+ (values body (nreverse decls) doc)))
+
+(defun parse-ordinary-lambda-list (lambda-list)
+ "Parses an ordinary lambda-list, returning as multiple values:
+
+ 1. Required parameters.
+ 2. Optional parameter specifications, normalized into form (NAME INIT SUPPLIEDP)
+ where SUPPLIEDP is NIL if not present.
+ 3. Name of the rest parameter, or NIL.
+ 4. Keyword parameter specifications, normalized into form ((KEYWORD-NAME NAME) INIT SUPPLIEDP)
+ where SUPPLIEDP is NIL if not present.
+ 5. Boolean indicating &ALLOW-OTHER-KEYS presence.
+ 6. &AUX parameter specifications, normalized into form (NAME INIT).
+
+Signals a PROGRAM-ERROR is the lambda-list is malformed."
+ (let ((state :required)
+ (allow-other-keys nil)
+ (auxp nil)
+ (required nil)
+ (optional nil)
+ (rest nil)
+ (keys nil)
+ (aux nil))
+ (labels ((simple-program-error (format-string &rest format-args)
+ (error 'simple-program-error
+ :format-control format-string
+ :format-arguments format-args))
+ (fail (elt)
+ (simple-program-error "Misplaced ~S in ordinary lambda-list:~% ~S"
+ elt lambda-list))
+ (check-variable (elt what)
+ (unless (and (symbolp elt) (not (constantp elt)))
+ (simple-program-error "Invalid ~A ~S in ordinary lambda-list:~% ~S"
+ what elt lambda-list)))
+ (check-spec (spec what)
+ (destructuring-bind (init suppliedp) spec
+ (declare (ignore init))
+ (check-variable suppliedp what)))
+ (make-keyword (name)
+ "Interns the string designated by NAME in the KEYWORD package."
+ (intern (string name) :keyword)))
+ (dolist (elt lambda-list)
+ (case elt
+ (&optional
+ (if (eq state :required)
+ (setf state elt)
+ (fail elt)))
+ (&rest
+ (if (member state '(:required &optional))
+ (setf state elt)
+ (progn
+ (break "state=~S" state)
+ (fail elt))))
+ (&key
+ (if (member state '(:required &optional :after-rest))
+ (setf state elt)
+ (fail elt)))
+ (&allow-other-keys
+ (if (eq state '&key)
+ (setf allow-other-keys t
+ state elt)
+ (fail elt)))
+ (&aux
+ (cond ((eq state '&rest)
+ (fail elt))
+ (auxp
+ (simple-program-error "Multiple ~S in ordinary lambda-list:~% ~S"
+ elt lambda-list))
+ (t
+ (setf auxp t
+ state elt))
+ ))
+ (otherwise
+ (when (member elt '#.(set-difference lambda-list-keywords
+ '(&optional &rest &key &allow-other-keys &aux)))
+ (simple-program-error
+ "Bad lambda-list keyword ~S in ordinary lambda-list:~% ~S"
+ elt lambda-list))
+ (case state
+ (:required
+ (check-variable elt "required parameter")
+ (push elt required))
+ (&optional
+ (cond ((consp elt)
+ (destructuring-bind (name &rest tail) elt
+ (check-variable name "optional parameter")
+ (if (cdr tail)
+ (check-spec tail "optional-supplied-p parameter")
+ (setf elt (append elt '(nil))))))
+ (t
+ (check-variable elt "optional parameter")
+ (setf elt (cons elt '(nil nil)))))
+ (push elt optional))
+ (&rest
+ (check-variable elt "rest parameter")
+ (setf rest elt
+ state :after-rest))
+ (&key
+ (cond ((consp elt)
+ (destructuring-bind (var-or-kv &rest tail) elt
+ (cond ((consp var-or-kv)
+ (destructuring-bind (keyword var) var-or-kv
+ (unless (symbolp keyword)
+ (simple-program-error "Invalid keyword name ~S in ordinary ~
+ lambda-list:~% ~S"
+ keyword lambda-list))
+ (check-variable var "keyword parameter")))
+ (t
+ (check-variable var-or-kv "keyword parameter")
+ (setf var-or-kv (list (make-keyword var-or-kv) var-or-kv))))
+ (if (cdr tail)
+ (check-spec tail "keyword-supplied-p parameter")
+ (setf tail (append tail '(nil))))
+ (setf elt (cons var-or-kv tail))))
+ (t
+ (check-variable elt "keyword parameter")
+ (setf elt (list (list (make-keyword elt) elt) nil nil))))
+ (push elt keys))
+ (&aux
+ (if (consp elt)
+ (destructuring-bind (var &optional init) elt
+ (declare (ignore init))
+ (check-variable var "&aux parameter"))
+ (check-variable elt "&aux parameter"))
+ (push elt aux))
+ (t
+ (simple-program-error "Invalid ordinary lambda-list:~% ~S" lambda-list)))))))
+ (values (nreverse required) (nreverse optional) rest (nreverse keys)
+ allow-other-keys (nreverse aux))))
\ No newline at end of file
Modified: trunk/src/java/snow/Snow.java
==============================================================================
--- trunk/src/java/snow/Snow.java (original)
+++ trunk/src/java/snow/Snow.java Thu Oct 22 16:10:10 2009
@@ -152,11 +152,12 @@
}
File f = new File(uri);
baseDir = fixDirPath(f.getParentFile().getParent());
- libDir = fixDirPath(new File(baseDir).getParent()) + "lib" + fileSeparator;
+ libDir = baseDir;
}
lispEngine.eval("(pushnew #P\"" + baseDir + "snow/\" asdf:*central-registry* :test #'equal)");
lispEngine.eval("(pushnew #P\"" + baseDir + "snow/swing/\" asdf:*central-registry* :test #'equal)");
lispEngine.eval("(pushnew #P\"" + libDir + "cl-utilities-1.2.4/\" asdf:*central-registry* :test #'equal)");
+ lispEngine.eval("(pushnew #P\"" + libDir + "named-readtables/\" asdf:*central-registry* :test #'equal)");
lispEngine.eval("(pushnew #P\"" + libDir + "cells/\" asdf:*central-registry* :test #'equal)");
lispEngine.eval("(pushnew #P\"" + libDir + "cells/utils-kt/\" asdf:*central-registry* :test #'equal)");
}
@@ -168,7 +169,6 @@
lispEngine.eval("(pushnew :snow-cells *features*)");
lispEngine.eval("(asdf:oos 'asdf:load-op :snow)");
-
//lispEngine.eval("(snow:install-graphical-debugger) (ohmygod)");
//lispEngine.eval("(snow::inspect-object (snow::new \"javax.swing.JButton\"))");
init = true;
Modified: trunk/src/java/snow/example/example.lisp
==============================================================================
--- trunk/src/java/snow/example/example.lisp (original)
+++ trunk/src/java/snow/example/example.lisp Thu Oct 22 16:10:10 2009
@@ -1,4 +1,5 @@
(in-package :snow)
+(in-readtable snow:syntax)
(defmodel my-model ()
((a :accessor aaa :initform (c-in "4"))
@@ -33,7 +34,7 @@
(label :binding (make-bean-data-binding *object* "property1")
:layout "wrap")
(label :text "EL binding")
- (label :binding (make-el-data-binding "bean.nested.property1")
+ (label :binding ${bean.nested.property1}
:layout "wrap")
(label :text "cells bindings: aaa and bbb")
(label :binding (make-cells-data-binding (c? (aaa *cells-object*))))
@@ -47,7 +48,7 @@
(text-field :binding (make-bean-data-binding *object* "property1")
:layout "growx, wrap")
(label :text "set nested.property1")
- (text-field :binding (make-el-data-binding "bean.nested.property1")
+ (text-field :binding ${bean.nested.property1}
:layout "growx, wrap")
(button :text "Test!"
:layout "wrap"
Modified: trunk/src/lisp/snow/compile-system.lisp
==============================================================================
--- trunk/src/lisp/snow/compile-system.lisp (original)
+++ trunk/src/lisp/snow/compile-system.lisp Thu Oct 22 16:10:10 2009
@@ -3,16 +3,13 @@
(unwind-protect
(unless
(progn
- #|(pushnew #P"snow/" asdf:*central-registry* :test #'equal)
- (pushnew #P"snow/swing/" asdf:*central-registry* :test #'equal)
- (pushnew #P"cl-utilities-1.2.4/" asdf:*central-registry* :test #'equal)
- (pushnew #P"cells/" asdf:*central-registry* :test #'equal)
- (pushnew #P"cells/utils-kt/" asdf:*central-registry* :test #'equal)
- (pushnew :snow-cells *features*)|#
(jstatic "initAux" "snow.Snow")
- (format t "asdf:*central-registry*: ~A" asdf:*central-registry*)
-
+ (format t "asdf:*central-registry*: ~S" asdf:*central-registry*)
+ (pushnew :snow-cells *features*)
+ (format t "compiling snow...")
(asdf:oos 'asdf:compile-op :snow)
+ (format t "success~%")
t)
- (format t "failed"))
+ (format t "failed~%"))
+ (terpri)
(quit))
\ No newline at end of file
Modified: trunk/src/lisp/snow/data-binding.lisp
==============================================================================
--- trunk/src/lisp/snow/data-binding.lisp (original)
+++ trunk/src/lisp/snow/data-binding.lisp Thu Oct 22 16:10:10 2009
@@ -128,16 +128,31 @@
;;For EL data bindings we reuse simple-data-binding, since its 'variable' can
;;really be any JGoodies ValueModel
-(defun make-el-data-binding (el-expr)
+(defun make-el-data-binding (obj path)
+ (make-instance 'simple-data-binding
+ :variable (new "snow.binding.BeanPropertyPathBinding"
+ obj (apply #'jvector "java.lang.String" path))))
+
+(defun make-el-data-binding-from-expression (el-expr)
+ (print el-expr)
(let* ((splitted-expr (split-sequence #\. el-expr))
(obj (funcall *bean-factory* (car splitted-expr)))
(path (cdr splitted-expr)))
- (make-instance 'simple-data-binding
- :variable (new "snow.binding.BeanPropertyPathBinding"
- obj (apply #'jvector "java.lang.String" path)))))
+ (make-el-data-binding obj path)))
-;(defun make-bean-property-path-data-binding (object path)
-;)
+(defreadtable snow:syntax
+ (:merge :standard)
+ (:macro-char #\$ :dispatch)
+ (:dispatch-macro-char
+ #\$ #\{
+ #'(lambda (stream char number)
+ (declare (ignore char number))
+ `(make-el-data-binding-from-expression
+ ,(with-output-to-string (str)
+ (loop
+ :for ch := (read-char stream) :then (read-char stream)
+ :until (char= ch #\})
+ :do (write-char ch str)))))))
;;Default binding types
#|(defun default-data-binding-types ()
Modified: trunk/src/lisp/snow/packages.lisp
==============================================================================
--- trunk/src/lisp/snow/packages.lisp (original)
+++ trunk/src/lisp/snow/packages.lisp Thu Oct 22 16:10:10 2009
@@ -30,7 +30,7 @@
(defpackage :snow
- (:use :common-lisp :java :cl-utilities #+snow-cells :cells)
+ (:use :common-lisp :java :cl-utilities :named-readtables #+snow-cells :cells)
(:shadow #+snow-cells #:dbg)
(:export
;;Widgets
@@ -38,19 +38,24 @@
#:frame
#:label
#:panel
+ #:text-area
#:text-field
;;Common operations on widgets
#:hide
#:pack
#:show
+ ;;Data binding
+ #:make-var
+ #:var
;;Various
#:install-graphical-debugger
#:*parent*
#:self
+ #:syntax
#:with-widget
;;Java
#:invoke
#:new))
(defpackage :snow-user
- (:use :common-lisp :snow :java :ext #+snow-cells :cells))
\ No newline at end of file
+ (:use :common-lisp :snow :java :ext :named-readtables #+snow-cells :cells))
\ No newline at end of file
Modified: trunk/src/lisp/snow/snow.asd
==============================================================================
--- trunk/src/lisp/snow/snow.asd (original)
+++ trunk/src/lisp/snow/snow.asd Thu Oct 22 16:10:10 2009
@@ -32,7 +32,7 @@
(asdf:defsystem :snow
:serial t
:version "0.2"
- :depends-on (:cl-utilities #+snow-cells :cells)
+ :depends-on (:cl-utilities :named-readtables #+snow-cells :cells)
:components ((:file "packages")
(:file "sexy-java")
(:file "utils")
From astalla at common-lisp.net Mon Oct 26 22:48:56 2009
From: astalla at common-lisp.net (Alessio Stalla)
Date: Mon, 26 Oct 2009 18:48:56 -0400
Subject: [snow-cvs] r11 - in trunk: docs src/java/snow/binding src/lisp/snow
Message-ID:
Author: astalla
Date: Mon Oct 26 18:48:55 2009
New Revision: 11
Log:
Updated tutorial. Fixed a bug with EL binding and zero-length property paths.
Modified:
trunk/docs/tutorial.html
trunk/src/java/snow/binding/BeanPropertyPathBinding.java
trunk/src/lisp/snow/data-binding.lisp
Modified: trunk/docs/tutorial.html
==============================================================================
--- trunk/docs/tutorial.html (original)
+++ trunk/docs/tutorial.html Mon Oct 26 18:48:55 2009
@@ -13,10 +13,11 @@
-You can download the latest Snow binary distribution from http://alessiostalla.altervista.org/software/snow/index.php. It contains Snow and all its dependencies in a single Zip file. Since Snow can be used both in Lisp and Java applications, procedures for installing it can vary in each of the two cases.
+You can download the latest Snow binary distribution from http://common-lisp.net/projects/snow/. It contains Snow and all its dependencies in a single Zip file. Since Snow can be used both in Lisp and Java applications, procedures for installing it can vary in each of the two cases.
Java applications:
simply make sure snow.jar and all the jars in the lib/ folder are in the classpath of your application. Snow uses JSR-223 and is built with Java 1.6, so that's the minimum Java version you can use. However, it should be possible to run Snow on 1.5 as well, but you'll need to recompile both Snow and ABCL from sources with a JSR-223 implementation in your classpath. See the Embedding Snow section below for details about using Snow inside your Java application.
+Keeping the GUI state in sync with the application objects state is generally tedious and error-prone. Data Binding is the process of automating the synchronization of state between two objects, in this case a GUI component and an application-level object. Snow supports several kinds of data binding, and it uses two library to do so: JGoodies Binding on the Java side and Cells on the Lisp side.
+
General concepts
+There are two general ways to bind or connect a widget to some object's property: one is by using the :binding property of the widget, letting the framework choose which property of the widget to bind, e.g. the text property for a text field; for example:
+
+this will connect the specific property of the widget with the user-provided object or property.
+
Types of data binding
+Snow supports several types of data binding; some are more indicated for Lisp applications, other for Java applications.
+
+
Binding to a variable. Syntax: (make-simple-data-binding <variable>) This is the simplest form of data binding: you connect a widget's property to a suitably instrumented Lisp variable. Such a variable must be initialized with (make-var <value>), read with (var <name>), and written with (setf (var <name>) <value>). Example:
+
Binding to a Presentation Model. Syntax: (make-bean-data-binding <object> <property> ...other args...) This kind of binding uses Martin Fowler's Presentation Model pattern as implemented by JGoodies Binding. You implement, in Java, a suitable subclass of PresentationModel (in simple cases, you can just use the base class); you then bind a widget to a model returned by an instance of this class for a bean property. Example:
+
+(defvar *presentation-model* (new "my.presentation.Model"))
+(text-field :text (make-bean-data-binding *presentation-model* "myProperty"))
+
+You can tune the presentation model with additional arguments to make-bean-data-binding; for example, you can obtain a buffered model with :buffered-p t.
+
+
What's more?
I haven't covered which widgets are supported and how much of their API is supported. At this stage, Snow is little more than a prototype, so very little of the Swing API is covered. The best way to learn about Snow usage is to look at the examples included with Snow: the debugger (debugger.lisp), inspector (inspector.lisp) and the REPL (repl.lisp and swing/swing.lisp). Also, I haven't talked about how to use your custom widgets with Snow, and probably other things. Drop me a line at alessiostalla @ Google's mail service, and I'll be happy to help you.
Modified: trunk/src/java/snow/binding/BeanPropertyPathBinding.java
==============================================================================
--- trunk/src/java/snow/binding/BeanPropertyPathBinding.java (original)
+++ trunk/src/java/snow/binding/BeanPropertyPathBinding.java Mon Oct 26 18:48:55 2009
@@ -47,7 +47,6 @@
private String propertyName;
private Object object;
- private Method removeMethod;
private BeanPropertyPathBinding nextListener;
private BeanPropertyPathBinding prevListener;
private String[] nextPropertyPath;
Modified: trunk/src/lisp/snow/data-binding.lisp
==============================================================================
--- trunk/src/lisp/snow/data-binding.lisp (original)
+++ trunk/src/lisp/snow/data-binding.lisp Mon Oct 26 18:48:55 2009
@@ -134,11 +134,12 @@
obj (apply #'jvector "java.lang.String" path))))
(defun make-el-data-binding-from-expression (el-expr)
- (print el-expr)
(let* ((splitted-expr (split-sequence #\. el-expr))
(obj (funcall *bean-factory* (car splitted-expr)))
(path (cdr splitted-expr)))
- (make-el-data-binding obj path)))
+ (if path
+ (make-el-data-binding obj path)
+ (make-simple-data-binding (make-var obj)))))
(defreadtable snow:syntax
(:merge :standard)
From astalla at common-lisp.net Tue Oct 27 21:36:24 2009
From: astalla at common-lisp.net (Alessio Stalla)
Date: Tue, 27 Oct 2009 17:36:24 -0400
Subject: [snow-cvs] r12 - in trunk: docs src/lisp/snow
Message-ID:
Author: astalla
Date: Tue Oct 27 17:36:21 2009
New Revision: 12
Log:
Updated tutorial.
Modified:
trunk/docs/tutorial.html
trunk/src/lisp/snow/data-binding.lisp
Modified: trunk/docs/tutorial.html
==============================================================================
--- trunk/docs/tutorial.html (original)
+++ trunk/docs/tutorial.html Tue Oct 27 17:36:21 2009
@@ -186,7 +186,24 @@
(defvar *presentation-model* (new "my.presentation.Model"))
(text-field :text (make-bean-data-binding *presentation-model* "myProperty"))
-You can tune the presentation model with additional arguments to make-bean-data-binding; for example, you can obtain a buffered model with :buffered-p t.
+The presentation model acts as a glue between the GUI and the application logic, and provides advanced functionality (e.g. it lets you control when to synchronize the state). You can tune it with additional arguments to make-bean-data-binding; for example, you can obtain a buffered model with :buffered-p t.
+
Binding to a bean property, arbitrarily nested. Syntax: (make-property-data-binding <bean> <property path>) This is a convenient way to adapt a pre-existing bean with the binding framework. It connects a property path, which can be expressed in "dot notation"; to properly observe changes, every object which is part of the path must support the standard Java Bean facility for listening to property changes. Example:
+
+(defvar *x* (new "Person"))
+(text-field :text (make-property-data-binding *x* "address.street"))
+
+
Binding to a Cells-powered slot of a CLOS object. Syntax: (make-slot-data-binding <object> <slot-accessor-name>) You can use this kind of binding to connect a widget property to a slot of a CLOS object using the Cells dataflow library. Example:
+
Binding to a calculated expression using Cells. Syntax: (make-cells-data-binding <expression> [writer]) This is useful to bind a widget to a quick-and-dirty Cells expression without creating a class instance specifically to hold it in a slot. You can optionally provide a writer function so that the widget will be able to alter the value of the expression when its value changes. Example:
+