Ticket #439 (closed change request: fixed)
Quasiquote simplification and improved syntax checks
| Reported by: | sjamaan | Owned by: | felix |
|---|---|---|---|
| Priority: | minor | Milestone: | |
| Component: | expander | Version: | 4.6.x |
| Keywords: | expander, quasiquote, syntax | Cc: |
Description
While studying the expander, I stumbled upon a problem in the quasiquote code. It does not check for proper usage of unquote/unquote-splicing.
Right now, it silently ignores extra arguments to unquote and unquote-splicing:
(let ((a 1)) (quasiquote (unquote a b))) => 1
Also, there's an R5RS incompatibility:
(define x (list 1 2)) (quasiquote (quasiquote (unquote-splicing (unquote x)))) => (quasiquote (unquote-splicing (unquote x)))
However, this should return (quasiquote (unquote-splicing (1 2))) because
The nesting level increases by one inside each successive quasiquotation, and decreases by one inside each unquotation
-- R5RS 4.2.6, second paragraph
The first quasiquote opens, the second increases nesting to one, the unquote-splicing decreases it to zero and the unquote is at level 0 so should result in the x getting unquoted.
The attached patch fixes these problems.
I've also taken the opportunity to simplify the code a bit, reducing the total "quasiquote"-code by 8 lines and making it more readable/understandable. Because R5RS says the behavior of unquote and unquote-splicing is undefined for cases where they occur in positions other than the car of a 2-element list, I decided it's not worth the extra checking whether the cdr of a nested quasiquote call is actually a list or not. It complicated the code and make it a little inconsistent.
The patch also adds tests and fixes the broken (f ..) test syntax; it originally always succeeded, because it threw an error when no errors were thrown, causing the exception handler to always be invoked! Luckily, none of the existing tests are failing after this fix.
