From: Joshua Taylor
Subject: Sequential sharpsign-plusses?
Date: 
Message-ID: <db8edaa9-748c-43ad-9c02-a990c71bf4b2@t3g2000yqa.googlegroups.com>
Hi all,

How should sequential #+ and #- things be handled? I've just run
across it in some library code, and since I see different behavior in
different implementations, I think I'll be filing a bug report, but I
need to determine whether it goes to a library author or a lisp
implementation.

In CCL

(lisp-implementation-type) => "Clozure Common Lisp"
(lisp-implementation-version) => "Version 1.3-RC1-r11719M
(DarwinPPC64)"
(list #+openmcl #+openmcl 1 2 3 4) => (1 2 3 4)
(list #-openmcl #-openmcl 1 2 3 4) => (2 3 4)

In Lispworks

(lisp-implementation-type) => "LispWorks"
(lisp-implementation-version) => "5.1.2"
(list #+lispworks #+lispworks 1 2 3 4) => (1 2 3 4)
(list #-lispworks #-lispworks 1 2 3 4) => (3 4)

Notice the differences in the last lines of each example. The place
where I've seen this used seems to expect LispWorks' behavior, as it's
used to do approximately this, but with more than #+/-lispworks:

(some-function
  :key1 'val1
  #-lispworks #-lispworks
  :non-lw-key2 'non-lw-val2
  #+lispworks #+lispworks
  :lw-key2 'lw-key2)

2.4.8.17 Sharpsign Plus [1] says that "#+ operates by first reading
the feature expression and then skipping over the form if the feature
expression fails. While reading the test, the current package is the
KEYWORD package. Skipping over the form is accomplished by binding
*read-suppress* to true and then calling read." The docs on *READ-
SUPPRESS* [2] state that *READ-SUPPRESS* is true, that "Dispatching
macro characters continue to parse an infix numerical argument, and
invoke the dispatch function. The standardized sharpsign reader macros
do not enforce any constraints on either the presence of or the value
of the numerical argument." I think this means that LispWorks'
behavior is correct, and that CCL's isn't, but I'd like to be sure so
that I can appropriately decide to whom I should send a bug report.

Thanks in advance, //JT

[1] http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_2-4-8-17.html
[2] http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/var_stread-suppressst.html#STread-suppressST

From: Kaz Kylheku
Subject: Re: Sequential sharpsign-plusses?
Date: 
Message-ID: <20090311033559.352@gmail.com>
On 2009-03-03, Joshua Taylor <···········@gmail.com> wrote:
> Hi all,
>
> How should sequential #+ and #- things be handled? I've just run
> across it in some library code, and since I see different behavior in
> different implementations, I think I'll be filing a bug report, but I
> need to determine whether it goes to a library author or a lisp
> implementation.

The interesting case is what if the feature test /fails/ in

 #+FEATURE EXPR

and EXPR contains #- or #+ syntax?  The spec says that if the test fails, then
EXPR is read with *READ-SUPPRESS* bound to T, and the whole thing is discarded

You have to read the specification of *READ-SUPPRESS* to understand what that
means. A key aspect of *READ-SUPPRESS* is that dispatch macros are still
activated. So for instance

  #+X #+Y EXPR

will still read #+Y EXPR as one unit, even if #+X fails.


>
> In CCL
>
> (lisp-implementation-type) => "Clozure Common Lisp"
> (lisp-implementation-version) => "Version 1.3-RC1-r11719M
> (DarwinPPC64)"
> (list #+openmcl #+openmcl 1 2 3 4) => (1 2 3 4)
> (list #-openmcl #-openmcl 1 2 3 4) => (2 3 4)
>
> In Lispworks
>
> (lisp-implementation-type) => "LispWorks"
> (lisp-implementation-version) => "5.1.2"
> (list #+lispworks #+lispworks 1 2 3 4) => (1 2 3 4)
> (list #-lispworks #-lispworks 1 2 3 4) => (3 4)

No matter how we interpret the spec, it won't change the fact that
implementations differ in their behavior.

So for now directly coupled #- and #+ syntax should be avoided in programs
intended to be portable.

> *read-suppress* to true and then calling read." The docs on *READ-
> SUPPRESS* [2] state that *READ-SUPPRESS* is true, that "Dispatching
> macro characters continue to parse an infix numerical argument, and
> invoke the dispatch function.

So consider this:

  #+(or) #-(and) 1 2

Both #+(or) and #-(and) mean the same thing: skip the object. I am using
different syntax only so that we can distinguish them in the discussion.

One way this can work is that #+(or) invokes READ with *READ-SUPPRESS* bound to
T, which precipitates into the #- reader. The #- reader then also determines
that the feature test has failed, and reads the following object, which is 1.
It then discards that object by returning (values).  But, suppose that the
outer #+ read captures this as NIL, for instance as if by:

  (let* ((*read-suppress* t)
         (object-to-skip (read stream ...)))
	 ...)

The effect is that only the 1 is skipped. The 2 remains in the stream. 

But suppose that the behavior is this: the #+(or) invokes READ with
*READ-SUPPERSS* bound to T, which precipitates into the #- reader. 
Suppose that the #- reader reads and skips the object 1, and then reads
another object, the 2.   But since *READ-SUPPRESS* is true, it does not
return that object, but rather NIL, since that is a requirement for
*READ-SUPPRESS*:

  ``If the value of *read-suppress* is true, read, read-preserving-whitespace,
  read-delimited-list, and read-from-string all return a primary value of nil
  when they complete successfully.''

The control then returns to the #+ reader, which treats the NIL returned from
#- as the form to be skipped, and reads the next object.

The argument for this second implementation (Lispworks behavior) is that a
failed sharpsign plus/minus test should treat the ignored object as whitespace:

  ``If the feature expression test fails, then this textual notation is treated
  as whitespace[2]; that is, it is as if the ``#+ test expression'' did not
  appear and only a space appeared in its place.''

If the reader really behaves as if this were whitespace, then it must continue
scanning the stream until it encounters nonwhitespace. If the next input in the
stream is #-(and) 1 2, then READ cannot return (values), leaving 2 unread in
the stream. The dispatch macro for #- can do that, but not the overall READ.

So I am siding with the double swallowing behavior of Lispworks as being
correct.
From: Pascal J. Bourguignon
Subject: Re: Sequential sharpsign-plusses?
Date: 
Message-ID: <87zlg22xbx.fsf@galatea.local>
Joshua Taylor <···········@gmail.com> writes:

> Hi all,
>
> How should sequential #+ and #- things be handled?  

Sequential #+ and #- should be handled using AND, OR, NOT:

Instead of writing:        write:
#+a #+b w                  #+(and a b)       w
#+a #-b x                  #+(and a (not b)) x
#-a #+b y                  #+(and (not a) b) y
#-a #-b z                  #-(or a b)        z


> I've just run
> across it in some library code, and since I see different behavior in
> different implementations, I think I'll be filing a bug report, but I
> need to determine whether it goes to a library author or a lisp
> implementation.

It's implementation dependant.  There are some behaviors that are more
useful or more "lgical", but the others are not illegal.

You could file a feature change request, but not exactly a bug report.



-- 
__Pascal Bourguignon__
From: Joshua Taylor
Subject: Re: Sequential sharpsign-plusses?
Date: 
Message-ID: <3b08d725-db8d-4290-9a92-33d8ff3c8e35@n33g2000vba.googlegroups.com>
On Mar 3, 2:57 pm, ····@informatimago.com (Pascal J. Bourguignon)
wrote:
> Joshua Taylor <···········@gmail.com> writes:
> > Hi all,
>
> > How should sequential #+ and #- things be handled?  
>
> Sequential #+ and #- should be handled using AND, OR, NOT:
>
> Instead of writing:        write:
> #+a #+b w                  #+(and a b)       w
> #+a #-b x                  #+(and a (not b)) x
> #-a #+b y                  #+(and (not a) b) y
> #-a #-b z                  #-(or a b)        z
>
> > I've just run
> > across it in some library code, and since I see different behavior in
> > different implementations, I think I'll be filing a bug report, but I
> > need to determine whether it goes to a library author or a lisp
> > implementation.
>
> It's implementation dependant.  There are some behaviors that are more
> useful or more "lgical", but the others are not illegal.
>
> You could file a feature change request, but not exactly a bug report.
>
> --
> __Pascal Bourguignon__

I think you may have missed the intent of the example that I posted.
When the features being tested are different, I agree that they should
be handled using AND, OR, and NOT. But the code I'm referencing is
using two of the /same/ features, e.g., in a function call:

(function-name :key1 'val1 :key2 'val2 #+sbcl #+sbcl :sbcl-key 'sbcl-
key-val)

I've been able to find some more examples using Google Code Search,
e.g.,

http://www.google.com/codesearch/p?hl=en#FlLmAxeMkCk/pipermail/phemlock-cvs/2005-January.txt&q=%22%23%2Bsbcl%20%23%2Bsbcl%22&l=39

"#+ operates by first reading the feature expression and then skipping
over the form if the feature expression fails. While reading the test,
the current package is the KEYWORD package. Skipping over the form is
accomplished by binding *read-suppress* to true and then calling
read."

My present understanding is that when #+x #+x y z is encountered:

[If the feature expression does not fail] When the first #+x is
encountered, the feature expression (x) is read. It does not fail.
read is then called again to process whatever expression happens to
follow. This second call to read processes "#+x y", returning y. The
first call to read then returns y.

[If the feature expression fails] When the first #+x is encountered,
the feature expression (x) is read. It fails. read is then called
again, with *READ-SUPPRESS* bound to T, to process whatever expression
happens to follow. It has "#+x y z" available to it. This second call
to read encounters the second #+x, reads the feature expression (x),
which fails, so *READ-SUPPRESS* is again bound to T (already its
current value), and a third call to read happens, which reads and
returns y, which the second call to read to discards. The second call
now reads z, and returns it, and the original reader discards it
(since the outermost feature expression (x) failed).

My current understanding, then, is that CCL is wrong in:

(list #-ccl #-ccl 1 2 3 4) => (2 3 4)

How does this sound?
From: Pascal J. Bourguignon
Subject: Re: Sequential sharpsign-plusses?
Date: 
Message-ID: <87fxhu2tnc.fsf@galatea.local>
Joshua Taylor <···········@gmail.com> writes:
> I think you may have missed the intent of the example that I posted.
> When the features being tested are different, I agree that they should
> be handled using AND, OR, and NOT. But the code I'm referencing is
> using two of the /same/ features, e.g., in a function call:
>
> (function-name :key1 'val1 :key2 'val2 #+sbcl #+sbcl :sbcl-key 'sbcl-
> key-val)

Sorry, I skipped over the examples.  In this case, it's meat to skip
over two tokens.

#+a #+a x y   will read both x and y when a is 'true' and none when
              it's false.

But this is still implementation dependant, and it would be better to write:
#+a x #+a y

> I've been able to find some more examples using Google Code Search,
> e.g.,
>
> http://www.google.com/codesearch/p?hl=en#FlLmAxeMkCk/pipermail/phemlock-cvs/2005-January.txt&q=%22%23%2Bsbcl%20%23%2Bsbcl%22&l=39
>
> "#+ operates by first reading the feature expression and then skipping
> over the form if the feature expression fails. While reading the test,
> the current package is the KEYWORD package. Skipping over the form is
> accomplished by binding *read-suppress* to true and then calling
> read."

(defun read-sharp-plus (stream ch arg)
    (declare (ignore ch arg))
    (let ((feature (let ((*package* (find-package "KEYWORD"))
                         (*read-suppress* nil)) ;; CRITICAL LINE!
                       (read stream))))
       (unless (eval-feature feature)
          (let ((*read-supress* t))
              (read stream)))
       (values))) ; nothing read in any case
       

> My present understanding is that when #+x #+x y z is encountered:
>
> [If the feature expression does not fail] When the first #+x is
> encountered, the feature expression (x) is read. It does not fail.
> read is then called again to process whatever expression happens to
> follow. This second call to read processes "#+x y", returning y. The
> first call to read then returns y.
>
> [If the feature expression fails] When the first #+x is encountered,
> the feature expression (x) is read. It fails. read is then called
> again, with *READ-SUPPRESS* bound to T, to process whatever expression
> happens to follow. It has "#+x y z" available to it. This second call
> to read encounters the second #+x, reads the feature expression (x),
> which fails, so *READ-SUPPRESS* is again bound to T (already its
> current value), and a third call to read happens, which reads and
> returns y, which the second call to read to discards. The second call
> now reads z, and returns it, and the original reader discards it
> (since the outermost feature expression (x) failed).
>
> My current understanding, then, is that CCL is wrong in:
>
> (list #-ccl #-ccl 1 2 3 4) => (2 3 4)
>
> How does this sound?

It depends on how you implement it.

Notice how the above specification says nothing about the critical
line.  It makes a difference when you read the second #+a in:
#+a #+a.

When you don't have this critical line, the second feature is read
with read suppress on which binds NIL to feature.  That's why the 1 is
not read by the second #+ reader macro, but by the first (with read
suppress on), and (2 3 4) is left to be read.

When you have this critical line, the second feature is read without
read suppress, and if the feature is false as in the example, the
following token is read with read suppress. So we read 1 for the
second #-ccl, and then since the first #-ccl is false, we read the
following token, 2 with read suppress on, and are left with only (3 4)
to be read.

Both behaviors are within the specifications.

Arguably the later may be nicer to the users, so you may lobby the
implementer to add the *read-suppress* binding.

-- 
__Pascal Bourguignon__
From: Joshua Taylor
Subject: Re: Sequential sharpsign-plusses?
Date: 
Message-ID: <96ad6966-59e0-4ffd-ab74-9100cf496de1@k19g2000prh.googlegroups.com>
On Mar 3, 4:17 pm, ····@informatimago.com (Pascal J. Bourguignon)
wrote:
> Joshua Taylor <···········@gmail.com> writes:
> > I think you may have missed the intent of the example that I posted.
> > When the features being tested are different, I agree that they should
> > be handled using AND, OR, and NOT. But the code I'm referencing is
> > using two of the /same/ features, e.g., in a function call:
>
> > (function-name :key1 'val1 :key2 'val2 #+sbcl #+sbcl :sbcl-key 'sbcl-
> > key-val)
>
> Sorry, I skipped over the examples.  In this case, it's meat to skip
> over two tokens.
>
> #+a #+a x y   will read both x and y when a is 'true' and none when
>               it's false.
>
> But this is still implementation dependant, and it would be better to write:
> #+a x #+a y
>
> > I've been able to find some more examples using Google Code Search,
> > e.g.,
>
> >http://www.google.com/codesearch/p?hl=en#FlLmAxeMkCk/pipermail/phemlo...
>
> > "#+ operates by first reading the feature expression and then skipping
> > over the form if the feature expression fails. While reading the test,
> > the current package is the KEYWORD package. Skipping over the form is
> > accomplished by binding *read-suppress* to true and then calling
> > read."
>
> (defun read-sharp-plus (stream ch arg)
>     (declare (ignore ch arg))
>     (let ((feature (let ((*package* (find-package "KEYWORD"))
>                          (*read-suppress* nil)) ;; CRITICAL LINE!
>                        (read stream))))
>        (unless (eval-feature feature)
>           (let ((*read-supress* t))
>               (read stream)))
>        (values))) ; nothing read in any case
>
>
>
> > My present understanding is that when #+x #+x y z is encountered:
>
> > [If the feature expression does not fail] When the first #+x is
> > encountered, the feature expression (x) is read. It does not fail.
> > read is then called again to process whatever expression happens to
> > follow. This second call to read processes "#+x y", returning y. The
> > first call to read then returns y.
>
> > [If the feature expression fails] When the first #+x is encountered,
> > the feature expression (x) is read. It fails. read is then called
> > again, with *READ-SUPPRESS* bound to T, to process whatever expression
> > happens to follow. It has "#+x y z" available to it. This second call
> > to read encounters the second #+x, reads the feature expression (x),
> > which fails, so *READ-SUPPRESS* is again bound to T (already its
> > current value), and a third call to read happens, which reads and
> > returns y, which the second call to read to discards. The second call
> > now reads z, and returns it, and the original reader discards it
> > (since the outermost feature expression (x) failed).
>
> > My current understanding, then, is that CCL is wrong in:
>
> > (list #-ccl #-ccl 1 2 3 4) => (2 3 4)
>
> > How does this sound?
>
> It depends on how you implement it.
>
> Notice how the above specification says nothing about the critical
> line.  It makes a difference when you read the second #+a in:
> #+a #+a.
>
> When you don't have this critical line, the second feature is read
> with read suppress on which binds NIL to feature.  That's why the 1 is
> not read by the second #+ reader macro, but by the first (with read
> suppress on), and (2 3 4) is left to be read.
>
> When you have this critical line, the second feature is read without
> read suppress, and if the feature is false as in the example, the
> following token is read with read suppress. So we read 1 for the
> second #-ccl, and then since the first #-ccl is false, we read the
> following token, 2 with read suppress on, and are left with only (3 4)
> to be read.
>
> Both behaviors are within the specifications.
>
> Arguably the later may be nicer to the users, so you may lobby the
> implementer to add the *read-suppress* binding.
>
> --
> __Pascal Bourguignon__

I had to read all that a couple of times to finally understand.  If I
understand you correctly, and am interpreting your code snippet
correctly:

The second #-feature could be seen with *READ-SUPPRESS* bound to T /
or/ to NIL.  If it's read when *READ-SUPPRESS* is T, then it's the
same as if we had written #-CL:NIL. And so, we can test whether this
is what's happening by adding CL:NIL to *FEATURES*:

CL-USER> (list #-ccl #-ccl 1 2 3 4)
(2 3 4)
CL-USER> (push nil *features*)
(NIL ...)
CL-USER> (list #-ccl #-ccl 1 2 3 4)
(3 4)

Indeed. It seems that that is what's happening.  What an enlightening
experience!  I think I'd prefer if the spec demanded one behavior or
the other---oh well.  At least now I know that the library code
depends on non-portable behavior.  I can report this to them, and not
to the CCL folks (except, maybe, as a feature (no pun intended)
request).

Thanks Pascal and Kaz!
//JT
From: Joshua Taylor
Subject: Re: Sequential sharpsign-plusses?
Date: 
Message-ID: <8964a327-48af-4eac-921f-51182b79a3c2@k9g2000prh.googlegroups.com>
On Mar 3, 4:49 pm, Joshua Taylor <···········@gmail.com> wrote:
> On Mar 3, 4:17 pm, ····@informatimago.com (Pascal J. Bourguignon)
> wrote:
>
>
>
> > Joshua Taylor <···········@gmail.com> writes:
> > > I think you may have missed the intent of the example that I posted.
> > > When the features being tested are different, I agree that they should
> > > be handled using AND, OR, and NOT. But the code I'm referencing is
> > > using two of the /same/ features, e.g., in a function call:
>
> > > (function-name :key1 'val1 :key2 'val2 #+sbcl #+sbcl :sbcl-key 'sbcl-
> > > key-val)
>
> > Sorry, I skipped over the examples.  In this case, it's meat to skip
> > over two tokens.
>
> > #+a #+a x y   will read both x and y when a is 'true' and none when
> >               it's false.
>
> > But this is still implementation dependant, and it would be better to write:
> > #+a x #+a y
>
> > > I've been able to find some more examples using Google Code Search,
> > > e.g.,
>
> > >http://www.google.com/codesearch/p?hl=en#FlLmAxeMkCk/pipermail/phemlo...
>
> > > "#+ operates by first reading the feature expression and then skipping
> > > over the form if the feature expression fails. While reading the test,
> > > the current package is the KEYWORD package. Skipping over the form is
> > > accomplished by binding *read-suppress* to true and then calling
> > > read."
>
> > (defun read-sharp-plus (stream ch arg)
> >     (declare (ignore ch arg))
> >     (let ((feature (let ((*package* (find-package "KEYWORD"))
> >                          (*read-suppress* nil)) ;; CRITICAL LINE!
> >                        (read stream))))
> >        (unless (eval-feature feature)
> >           (let ((*read-supress* t))
> >               (read stream)))
> >        (values))) ; nothing read in any case
>
> > > My present understanding is that when #+x #+x y z is encountered:
>
> > > [If the feature expression does not fail] When the first #+x is
> > > encountered, the feature expression (x) is read. It does not fail.
> > > read is then called again to process whatever expression happens to
> > > follow. This second call to read processes "#+x y", returning y. The
> > > first call to read then returns y.
>
> > > [If the feature expression fails] When the first #+x is encountered,
> > > the feature expression (x) is read. It fails. read is then called
> > > again, with *READ-SUPPRESS* bound to T, to process whatever expression
> > > happens to follow. It has "#+x y z" available to it. This second call
> > > to read encounters the second #+x, reads the feature expression (x),
> > > which fails, so *READ-SUPPRESS* is again bound to T (already its
> > > current value), and a third call to read happens, which reads and
> > > returns y, which the second call to read to discards. The second call
> > > now reads z, and returns it, and the original reader discards it
> > > (since the outermost feature expression (x) failed).
>
> > > My current understanding, then, is that CCL is wrong in:
>
> > > (list #-ccl #-ccl 1 2 3 4) => (2 3 4)
>
> > > How does this sound?
>
> > It depends on how you implement it.
>
> > Notice how the above specification says nothing about the critical
> > line.  It makes a difference when you read the second #+a in:
> > #+a #+a.
>
> > When you don't have this critical line, the second feature is read
> > with read suppress on which binds NIL to feature.  That's why the 1 is
> > not read by the second #+ reader macro, but by the first (with read
> > suppress on), and (2 3 4) is left to be read.
>
> > When you have this critical line, the second feature is read without
> > read suppress, and if the feature is false as in the example, the
> > following token is read with read suppress. So we read 1 for the
> > second #-ccl, and then since the first #-ccl is false, we read the
> > following token, 2 with read suppress on, and are left with only (3 4)
> > to be read.
>
> > Both behaviors are within the specifications.
>
> > Arguably the later may be nicer to the users, so you may lobby the
> > implementer to add the *read-suppress* binding.
>
> > --
> > __Pascal Bourguignon__
>
> I had to read all that a couple of times to finally understand.  If I
> understand you correctly, and am interpreting your code snippet
> correctly:
>
> The second #-feature could be seen with *READ-SUPPRESS* bound to T /
> or/ to NIL.  If it's read when *READ-SUPPRESS* is T, then it's the
> same as if we had written #-CL:NIL. And so, we can test whether this
> is what's happening by adding CL:NIL to *FEATURES*:
>
> CL-USER> (list #-ccl #-ccl 1 2 3 4)
> (2 3 4)
> CL-USER> (push nil *features*)
> (NIL ...)
> CL-USER> (list #-ccl #-ccl 1 2 3 4)
> (3 4)
>
> Indeed. It seems that that is what's happening.  What an enlightening
> experience!  I think I'd prefer if the spec demanded one behavior or
> the other---oh well.  At least now I know that the library code
> depends on non-portable behavior.  I can report this to them, and not
> to the CCL folks (except, maybe, as a feature (no pun intended)
> request).
>
> Thanks Pascal and Kaz!
> //JT

Oh, I may have to take back my last comment.  (The thanks still stand,
of course!) 2.4.8.18 Sharpsign Minus says that:

"#- is like #+ except that it skips the expression if the test
succeeds; that is,

#-test expression ==  #+(not test) expression"

But look what happens here (again in CCL):

CL-USER> (list #-ccl #-ccl 1 2 3 4)
(2 3 4)
CL-USER> (list #-ccl #+(not ccl) 1 2 3 4)
(3 4)
CL-USER> (list #+(not ccl) #-ccl 1 2 3 4)
(2 3 4)
CL-USER> (list #+(not ccl) #+(not ccl) 1 2 3 4)
(3 4)

I'd think that all four should behave the same (and do in some
implementations, e.g., LispWorks (and substitute lispworks for ccl in
those tests)).

Now it looks like I've got twice as much email to send.

//JT
From: Pascal J. Bourguignon
Subject: Re: Sequential sharpsign-plusses?
Date: 
Message-ID: <87zlg1212a.fsf@galatea.local>
Joshua Taylor <···········@gmail.com> writes:
> Oh, I may have to take back my last comment.  (The thanks still stand,
> of course!) 2.4.8.18 Sharpsign Minus says that:
>
> "#- is like #+ except that it skips the expression if the test
> succeeds; that is,
>
> #-test expression ==  #+(not test) expression"
>
> But look what happens here (again in CCL):
>
> CL-USER> (list #-ccl #-ccl 1 2 3 4)
> (2 3 4)
> CL-USER> (list #-ccl #+(not ccl) 1 2 3 4)
> (3 4)
> CL-USER> (list #+(not ccl) #-ccl 1 2 3 4)
> (2 3 4)
> CL-USER> (list #+(not ccl) #+(not ccl) 1 2 3 4)
> (3 4)
>
> I'd think that all four should behave the same (and do in some
> implementations, e.g., LispWorks (and substitute lispworks for ccl in
> those tests)).
>
> Now it looks like I've got twice as much email to send.

Great! :-)

-- 
__Pascal Bourguignon__
From: Mark Wooding
Subject: Re: Sequential sharpsign-plusses?
Date: 
Message-ID: <87ab82jogp.fsf.mdw@metalzone.distorted.org.uk>
Joshua Taylor <···········@gmail.com> writes:

> How should sequential #+ and #- things be handled? I've just run
> across it in some library code, and since I see different behavior in
> different implementations, I think I'll be filing a bug report, but I
> need to determine whether it goes to a library author or a lisp
> implementation.

I think the library code ought to be fixed anyway.  The code as it is is
clearly nonportable in practice, even if it ought to be in theory.

> (some-function
>   :key1 'val1
>   #-lispworks #-lispworks
>   :non-lw-key2 'non-lw-val2
>   #+lispworks #+lispworks
>   :lw-key2 'lw-key2)

This can be re-expressed as

        (some-function :key 'val1 . ;<-- note the dot!
                       #-lispworks (:non-lw-key2 'non-lw-val2)
                       #+lispworks (:lw-key2 lw-val2))

with fewer #+/- tokens.  Of course, simply redistributing the existing
tokens would be sufficient to make the code work anyway.

> 2.4.8.17 Sharpsign Plus [1] says that [etc.]

I agree with your reading.

-- [mdw]