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.
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:
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.
Joshua Taylor <joshuaaa...@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.
> Joshua Taylor <joshuaaa...@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:
"#+ 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:
On 2009-03-03, Joshua Taylor <joshuaaa...@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.
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:
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.
Joshua Taylor <joshuaaa...@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:
> "#+ 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?
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.
Joshua Taylor <joshuaaa...@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.
> Joshua Taylor <joshuaaa...@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:
> > "#+ 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?
> 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*:
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).
> On Mar 3, 4:17 pm, p...@informatimago.com (Pascal J. Bourguignon) > wrote:
> > Joshua Taylor <joshuaaa...@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:
> > > "#+ 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?
> > 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*:
> 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,
Joshua Taylor <joshuaaa...@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,
> 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.