Defining quasisyntax in terms of syntax-case Andre van Tonder (30 Jun 2006 02:30 UTC)
Re: Defining quasisyntax in terms of syntax-case Andre van Tonder (30 Jun 2006 11:25 UTC)
Re: Defining quasisyntax in terms of syntax-case David Feuer (30 Jun 2006 11:43 UTC)
Re: Defining quasisyntax in terms of syntax-case Andre van Tonder (30 Jun 2006 13:56 UTC)
Re: Defining quasisyntax in terms of syntax-case Andre van Tonder (30 Jun 2006 19:06 UTC)

Re: Defining quasisyntax in terms of syntax-case Andre van Tonder 30 Jun 2006 19:06 UTC

Apologies for the multiple postings.  I have added a number of test cases and
fixed a few bugs.  Here is the updated implementation.  Any additional test
cases are welcome.

Andre

;;;=========================================================
;;;
;;; Quasisyntax in terms of SRFI-93 syntax-case.;;;
;;; Andre van Tonder
;;;
;;;=========================================================
;;;
;;; To make nested unquote-splicing behave in a useful way,
;;; the R5RS-compatible extension of quasiquote in appendix B
;;; of the following paper is here ported to quasisyntax:
;;;
;;; Alan Bawden - Quasiquotation in Lisp
;;; http://citeseer.ist.psu.edu/bawden99quasiquotation.html
;;;
;;; The algorithm converts a quasisyntax expression to an
;;; equivalent with-syntax expression.
;;; For example:
;;;
;;; (quasisyntax (set! ,a ,b))
;;;   ==> (with-syntax ((t0 a)
;;;                     (t1 b))
;;;         (syntax (set! t0 t1)))
;;;
;;; (quasisyntax (list ,@args))
;;;   ==> (with-syntax (((t ...) args))
;;;         (syntax (list t ...)))
;;;
;;; Note that quasisyntax is expanded first, before any
;;; ellipses act.  For example:
;;;
;;; (quasisyntax (f ((b ,a) ...))
;;;   ==> (with-syntax ((t a))
;;;         (syntax (f ((b t) ...))))
;;;
;;; so that
;;;
;;; (let-syntax ((test-ellipses-over-unsyntax
;;;               (lambda (e)
;;;                 (let ((a (syntax a)))
;;;                   (with-syntax (((b ...) (syntax (1 2 3))))
;;;                     (quasisyntax
;;;                      (quote ((b ,a) ...))))))))
;;;   (test-ellipses-over-unsyntax))
;;;
;;;     ==> ((1 a) (2 a) (3 a))

(define-syntax quasisyntax
   (lambda (e)

     (define (expand-quasisyntax x)

       ;; Expand returns a syntax object of the form
       ;;    (template[t/e, ...] (replacement ...))
       ;; Here template[t/e ...] denotes the original template
       ;; with unquoted expressions e replaced by fresh
       ;; variables t, followed by the appropriate ellipses
       ;; if e is also spliced.
       ;; The second part of the return value is the list of
       ;; replacements, each of the form (t e) if e is just
       ;; unquoted, or ((t ...) e) if e is also spliced.
       ;; This will be the list of bindings of the resulting
       ;; with-syntax expression.

       (define (expand x level)
         (syntax-case x (quasisyntax unquote unquote-splicing)
           ((quasisyntax e)
            (with-syntax (((k _) x)  ; Original must be copied
                          ((rest bs) (expand (syntax e) (+ level 1))))
              (syntax
               ((k rest) bs))))
           ((unquote e)
            (= level 0)
            (with-syntax (((t) (generate-temporaries '(t))))
              (syntax (t ((t e))))))
           (((unquote e ...) . r)
            (= level 0)
            (with-syntax (((rest (b ...)) (expand (syntax r) 0))
                          ((t ...)        (generate-temporaries (syntax (e
...)))))
              (syntax
               ((t ... . rest)
                ((t e) ... b ...)))))
           (((unquote-splicing e ...) . r)
            (= level 0)
            (with-syntax (((rest (b ...)) (expand (syntax r) 0))
                          ((t ...)        (generate-temporaries (syntax (e
...)))))
              (with-syntax ((((t ...) ...) (syntax ((t (... ...)) ...))))
                (syntax
                 ((t ... ... . rest)
                  (((t ...) e) ... b ...))))))
           ((k . r)
            (and (> level 0)
                 (identifier? (syntax k))
                 (or (free-identifier=? (syntax k) (syntax unquote))
                     (free-identifier=? (syntax k) (syntax unquote-splicing))))
            (with-syntax (((rest bs) (expand (syntax r) (- level 1))))
              (syntax
               ((k . rest) bs))))
           ((h . t)
            (with-syntax (((head (b1 ...)) (expand (syntax h) level))
                          ((tail (b2 ...)) (expand (syntax t) level)))
              (syntax
               ((head . tail)
                (b1 ... b2 ...)))))
           (#(e ...)
            (with-syntax ((((e* ...) bs)
                           (expand (vector->list (syntax #(e ...))) level)))
              (syntax
               (#(e* ...) bs))))
           (other
            (syntax (other ())))))

       (with-syntax (((template bindings) (expand x 0)))
         (syntax
          (with-syntax bindings (syntax template)))))

     (syntax-case e ()
       ((k template)
        (expand-quasisyntax (syntax template))))))

;;;==========================================================================
;;;
;;; Tests:
;;;
;;;==========================================================================

(define-syntax swap!
   (lambda (e)
     (syntax-case e ()
       ((_ a b)
        (let ((a (syntax a))
              (b (syntax b)))
          (quasisyntax
           (let ((temp ,a))
             (set! ,a ,b)
             (set! ,b temp))))))))

(let ((temp 1)
       (set! 2))
   (swap! set! temp)
   (values temp set!))   ;==> 2 1

(define-syntax case
   (lambda (x)
     (syntax-case x ()
       ((_ e c1 c2 ...)
        (quasisyntax
         (let ((t e))
           ,(let f ((c1 (syntax c1)) (cmore (syntax (c2 ...))))
              (if (null? cmore)
                  (syntax-case c1 (else)
                    ((else e1 e2 ...)    (syntax (begin e1 e2 ...)))
                    (((k ...) e1 e2 ...) (syntax (if (memv t '(k ...))
                                                     (begin e1 e2 ...)))))
                  (syntax-case c1 ()
                    (((k ...) e1 e2 ...)
                     (quasisyntax
                      (if (memv t '(k ...))
                          (begin e1 e2 ...)
                          ,(f (car cmore) (cdr cmore))))))))))))))

(case 'a
   ((b c) 'no)
   ((d a) 'yes))

(define-syntax let-in-order
   (lambda (form)
     (syntax-case form ()
       ((_ ((i e) ...) e0 e1 ...)
        (let f ((ies (syntax ((i e) ...)))
                (its (syntax ())))
          (syntax-case ies ()
            (()            (quasisyntax (let ,its e0 e1 ...)))
            (((i e) . ies) (with-syntax (((t) (generate-temporaries '(t))))
                             (quasisyntax
                              (let ((t e))
                                ,(f (syntax ies)
                                    (quasisyntax
                                     ((i t) ,@its)))))))))))))

(let-in-order ((x 1)
                (y 2))
    (+ x y))                ;==> 3

(let-syntax ((test-ellipses-over-unsyntax
               (lambda (e)
                 (let ((a (syntax a)))
                   (with-syntax (((b ...) (syntax (1 2 3))))
                     (quasisyntax
                      (quote ((b ,a) ...))))))))
   (test-ellipses-over-unsyntax))

       ;==> ((1 a) (2 a) (3 a))

;; Some tests found online (Guile?)

(let-syntax ((test
               (lambda (_)
                 (quasisyntax
                  '(list ,(+ 1 2) 4)))))
   (test))
                                         ;==> (list 3 4)

(let-syntax ((test
               (lambda (_)
                 (let ((name (syntax a)))
                   (quasisyntax '(list ,name ',name))))))
   (test))
                                         ;==> (list a 'a)

(let-syntax ((test
               (lambda (_)
                 (quasisyntax '(a ,(+ 1 2) ,@(map abs '(4 -5 6)) b)))))
   (test))
                                         ;==> (a 3 4 5 6 b)

(let-syntax ((test
               (lambda (_)
                 (quasisyntax '((foo ,(- 10 3)) ,@(cdr '(5)) . ,(car '(7)))))))
   (test))
                                         ;==> ((foo 7) . 7)

(let-syntax ((test
               (lambda (_)
                 (quasisyntax '#(10 5 ,(sqrt 4) ,@(map sqrt '(16 9)) 8)))))
   (test))
                                         ;==> #6(10 5 2 4 3 8)

(let-syntax ((test
               (lambda (_)
                 (quasisyntax ,(+ 2 3)))))
   (test))
                                         ;==> 5

(let-syntax ((test
               (lambda (_)
                 (quasisyntax
                  '(a (quasisyntax (b ,(+ 1 2) ,(foo ,(+ 1 3) d) e)) f)))))
   (test))
                                         ;==> (a (quasisyntax (b ,(+ 1 2) ,(foo 4
d) e)) f)

(let-syntax ((test
               (lambda (_)
                 (let ((name1 #'x) (name2 #'y))
                   (quasisyntax
                    '(a (quasisyntax (b ,,name1 ,#',name2 d)) e))))))
   (test))
                                         ;==> (a (quasisyntax (b ,x ,#'y d)) e)

;; Bawden's extensions:

(let-syntax ((test
               (lambda (_)
                 (quasisyntax '(a (unquote 1 2) b)))))
   (test))
                                         ;==> (a 1 2 b)

(let-syntax ((test
               (lambda (_)
                 (quasisyntax '(a (unquote-splicing '(1 2) '(3 4)) b)))))
   (test))
                                         ;==> (a 1 2 3 4 b)

(let-syntax ((test
               (lambda (_)
                 (let ((x #'(a b c)))
                   (quasisyntax '(quasisyntax (,,x ,@,x ,,@x ,@,@x)))))))
   (test))

         ;==> (quasisyntax (,(a b c) ,@(a b c) (unquote a b c) (unquote-splicing
a b c)))
         ;    which is equivalent to
         ;    (quasisyntax (,(a b c) ,@(a b c) ,a ,b ,c ,@a ,@b ,@c)
         ;    in the Bawden prescription