;; Written by Marc Nieper-Wißkirchen

;; TODO: make-synthetic-identifier should return a truly unique (that
;; is not free-identifier=? to any other) identifier.

;; TODO: Consecutive ellipses in syntax templates.

;; TODO: Write many more tests.

(define current-renamer (make-parameter (lambda (x) x)))
(define current-usage-environment (make-parameter (current-environment)))

(define (free-identifier=? x y)
  (let ((env (or (current-usage-environment) (current-environment))))
    (identifier=? env x env y)))

(define (%make-transformer transformer)
  (cond
   ((and (= 1 (procedure-arity transformer))
         (not (procedure-variadic? transformer)))
    (lambda (expr use-env mac-env)
      (let ((old-use-env (current-usage-environment))
            (old-renamer (current-renamer)))
        (current-usage-environment use-env)
        (current-renamer (make-renamer mac-env))
        (let ((result (transformer expr)))
          (current-usage-environment old-use-env)
          (current-renamer old-renamer)
          result))))
   (else
    (lambda (expr use-env mac-env)
      (let ((old-use-env (current-usage-environment))
            (old-renamer (current-renamer)))
        (current-usage-environment use-env)
        (current-renamer (make-renamer mac-env))
        (let ((result (transformer expr use-env mac-env)))
          (current-usage-environment old-use-env)
          (current-renamer old-renamer)
          result))))))

(define (make-transformer base-transformer)
  (let ((wrapped-transformer (%make-transformer base-transformer)))
    (if (procedure-variable-transformer? base-transformer)
        (make-variable-transformer wrapped-transformer)
        wrapped-transformer)))

(%define-syntax define-syntax
  (lambda (expr use-env mac-env)
    (list (close-syntax '%define-syntax mac-env)
          (cadr expr)
          (list (close-syntax 'make-transformer mac-env)
                (car (cddr expr))))))

(define-syntax let-syntax
  (syntax-rules ()
    ((let-syntax ((keyword transformer) ...) . body)
     (%let-syntax ((keyword (make-transformer transformer)) ...) . body))))

(define-syntax letrec-syntax
  (syntax-rules ()
    ((letrec-syntax ((keyword transformer) ...) . body)
     (%letrec-syntax ((keyword (make-transformer transformer)) ...) . body))))

(define-record-type Pattern-Cell
  (make-pattern-cell val) pattern-cell?
  (val pattern-cell-value))

(define-syntax define-pattern-variable
  (er-macro-transformer
   (lambda (expr rename compare)
     (let ((id (cadr expr))
           (binding (cddr expr)))
       (let ((cell (env-cell (current-usage-environment) id)))
         (if cell
             (macro-aux-set! (cdr cell) (make-pattern-cell binding))))
       (rename '(begin))))))

(define (make-pattern-variable pvar)
  (lambda (expr)
    (error "reference to pattern variable outside syntax" pvar)))

(define (pattern-variable x)
  (and-let*
      ((cell (env-cell (current-usage-environment) x))
       (cell-ref (cdr cell))
       ((macro? cell-ref))
       (aux (macro-aux cell-ref))
       ((pattern-cell? aux)))
    (pattern-cell-value aux)))

(define (rename id)
  ((current-renamer) id))

(define current-ellipsis-id
  (make-syntactic-closure (current-environment) '() 'current-ellipsis))

(define (ellipsis-identifier? id)
  (let* ((cell (env-cell (current-usage-environment) current-ellipsis-id))
         (ellipsis (if cell
                       (macro-aux (cdr cell))
                       (rename '...))))
    (free-identifier=? id ellipsis)))

(define bound-identifier=?
  (lambda (x y)
    (eq? x y)))

(define (syntax-transformer level)
  (er-macro-transformer
   (lambda (expr rename compare)
     (let*-values (((out envs)
                    (gen-template (cadr expr) '() ellipsis-identifier? level)))
       out))))

(define (syntax->datum stx)
  (strip-syntactic-closures stx))

(define-syntax syntax (syntax-transformer #f))
(define-syntax quasisyntax (syntax-transformer 0))
(define-auxiliary-syntax unsyntax)
(define-auxiliary-syntax unsyntax-splicing)

(define (gen-template tmpl envs ell? level)
  (cond
   ((pair? tmpl)
    (cond
     ((and (identifier? (car tmpl))
           (free-identifier=? (car tmpl) (rename 'unsyntax)))
      (if (and level (zero? level))
          (values (cadr tmpl) envs)
          (let*-values (((out envs) (gen-template (cadr tmpl) envs ell? (and level (- level 1)))))
            (values `(,(rename 'list) ,(gen-data (car tmpl)) ,out) envs))))
     ((and (identifier? (car tmpl))
           (free-identifier=? (car tmpl) (rename 'quasisyntax)))
      (let*-values (((out envs) (gen-template (cadr tmpl) envs ell? (and level (+ level 1)))))
        (values `(,(rename 'list) ,(gen-data (car tmpl)) ,out) envs)))
     ((and (pair? (car tmpl))
           (free-identifier=? (caar tmpl) (rename 'unsyntax)))
      (if (and level (zero? level))
          (let*-values (((out envs) (gen-template (cdr tmpl) envs ell? level)))
            (values `(,(rename 'cons*) ,@(cdar tmpl) ,out) envs))
          (let*-values (((out1 envs) (gen-template (cdar tmpl) envs ell? (and level (- level 1))))
                        ((out2 envs) (gen-template (cdr tmpl) envs ell? level)))
            (values `(,(rename 'cons) (,(rename 'cons) ,(gen-data (caar tmpl)) ,out1)
                      ,out2) envs))))
     ((and (pair? (car tmpl))
           (free-identifier=? (caar tmpl) (rename 'unsyntax-splicing)))
      (if (and level (zero? level))
          (let*-values (((out envs) (gen-template (cdr tmpl) envs ell? level)))
            (values `(,(rename 'append) ,@(cdar tmpl) ,out) envs))
          (let*-values (((out1 envs) (gen-template (cdar tmpl) envs ell? (and level (- level 1))))
                        ((out2 envs) (gen-template (cdr tmpl) envs ell? level)))
            (values `(,(rename 'cons) (,(rename 'cons) ,(gen-data (caar tmpl)) ,out1)
                      ,out2) envs))))
     ((and (identifier? (car tmpl))
           (ell? (car tmpl)))
      (gen-template (cadr tmpl) envs (lambda (id) #f) level))
     ((and (pair? (cdr tmpl))
           (identifier? (cadr tmpl))
           (ell? (cadr tmpl)))
      (let*-values (((out* envs)
                     (gen-template (cddr tmpl) envs ell? level))
                    ((out envs)
                     (gen-template (car tmpl) (cons '() envs) ell? level)))
        (if (null? (car envs))
            (error "too many ellipses following syntax template" (car tmpl)))
        (values `(,(rename 'fold-right) (,(rename 'lambda) (,@(car envs) ,(rename 'stx))
                                         (,(rename 'cons) ,out ,(rename 'stx)))
                  ,out* ,@(car envs))
                (cdr envs))))
     (else
      (let*-values (((out1 envs)
                     (gen-template (car tmpl) envs ell? level))
                    ((out2 envs)
                     (gen-template (cdr tmpl) envs ell? level)))
        (values `(,(rename 'cons) ,out1 ,out2) envs)))))
   ((vector? tmpl)
    (let*-values (((out envs)
                   (gen-template (vector->list tmpl) envs ell? level)))
      (values `(,(rename 'list->vector) ,out) envs)))
   ((identifier? tmpl)
    (cond ((ell? tmpl)
           (error "misplaced ellipsis in syntax template" tmpl))
          ((pattern-variable tmpl) =>
           (lambda (binding)
             (values (car binding)
                     (update-envs tmpl (car binding) (cadr binding) envs))))
          (else
           (values (gen-data tmpl) envs))))
   (else
    (values `(,(rename 'quote) ,tmpl) envs))))

(define (gen-data id)
  `((,(rename 'current-renamer))
    (,(rename 'syntax-quote) ,id)))

(define (update-envs id x level envs)
  (let loop ((level level) (envs envs))
    (cond ((zero? level)
           envs)
          ((null? envs)
           (error "too few ellipses following syntax template" id))
          (else
           (let ((outer-envs (loop (- level 1) (cdr envs))))
             (cond ((member x (car envs) bound-identifier=?)
                    envs)
                   (else
                    (cons (cons x (car envs))
                          outer-envs))))))))

(define-syntax syntax-case
  (er-macro-transformer
   (lambda (expr rename compare)
     (let ((expr (cadr expr))
           (lit* (car (cddr expr)))
           (clause* (reverse (cdr (cddr expr))))
           (error #'(error "syntax error" e)))
       #`(let ((e #,expr))
           #,(if (null? clause*)
                 error
                 #`(let ((fail (lambda () #,error)))
                     #,(let loop ((clause (car clause*))
                                  (clause* (cdr clause*)))
                         (if (null? clause*)
                             (gen-clause lit* clause)
                             #`(let ((fail (lambda ()
                                             #,(gen-clause lit* clause))))
                                 #,(loop (car clause*) (cdr clause*))))))))))))

(define (gen-clause lit* clause)
  (if (= 3 (length clause))
      (gen-output lit* (car clause) (cadr clause) (car (cddr clause)))
      (gen-output lit* (car clause) #t (cadr clause))))

(define (gen-output lit* pattern fender output-expr)
  (let*-values (((matcher vars)
                 (gen-matcher #'e lit* pattern '())))
    (matcher
     (lambda ()
       #`(let-syntax
             #,(map (lambda (var)
                      #`(#,(car var)
                         (make-pattern-variable (syntax-quote #,(car var)))))
                    vars)
           #,@(map (lambda (var)
                     #`(define-pattern-variable . #,var))
                   vars)
           (if #,fender
               #,output-expr
               (fail)))))))

(define (gen-matcher e lit* pattern vars)
  (cond
   ((pair? pattern)
    (cond
     ((and (pair? (cdr pattern))
           (identifier? (cadr pattern))
           (ellipsis-identifier? (cadr pattern)))
      (let* ((l (length+ (cddr pattern)))
             (h (car (generate-temporaries '(#f))))
             (t (car (generate-temporaries '(#f)))))
        (let*-values (((head-matcher vars)
                       (gen-map h lit* (car pattern) vars))
                      ((tail-matcher vars)
                       (gen-matcher* t lit* (cddr pattern) vars)))
          (values (lambda (k)
                    #`(let ((n (length+ #,e)))
                        (if (and n (>= n #,l))
                            (let*-values (((#,h #,t) (split-at #,e (- n #,l))))
                              #,(head-matcher (lambda ()
                                                (tail-matcher k))))
                            (fail))))
                  vars))))
     (else
      (let ((e1 (car (generate-temporaries '(#f))))
            (e2 (car (generate-temporaries '(#f)))))
        (let*-values (((car-matcher vars)
                       (gen-matcher e1 lit* (car pattern) vars))
                      ((cdr-matcher vars)
                       (gen-matcher e2 lit* (cdr pattern) vars)))
          (values (lambda (k)
                    #`(if (pair? #,e)
                          (let ((#,e1 (car #,e))
                                (#,e2 (cdr #,e)))
                            #,(car-matcher (lambda ()
                                             (cdr-matcher k))))
                          (fail)))
                  vars))))))
   ((identifier? pattern)
    (cond ((member pattern lit* free-identifier=?)
           (values (lambda (k)
                     #`(if (free-identifier=? #'#,pattern #,e)
                           #,(k)
                           (fail)))
                   vars))
          ((ellipsis-identifier? pattern)
           (error "misplaced ellipsis" pattern))
          ((free-identifier=? pattern #'_)
           (values (lambda (k)
                     (k))
                   vars))
          (else
           (values (lambda (k)
                     (k))
                   (alist-cons pattern (list e 0) vars)))))
   (else
    (values (lambda (k)
              #`(if (equal? (syntax->datum #,e) '#,pattern)
                    #,(k)
                    (fail)))
            vars))))

(define (gen-map h lit* pattern vars)
  (let*-values (((matcher inner-vars) (gen-matcher #'g lit* pattern '())))
    (let ((loop (car (generate-temporaries '(#f))))
          (g* (generate-temporaries inner-vars)))
      (values
       (lambda (k)
         #`(let #,loop ((#,h (reverse #,h))
                        #,@(map (lambda (g)
                                  #`(#,g '()))
                                g*))
                (if (null? #,h)
                    #,(k)
                    (let ((g (car #,h)))
                      #,(matcher
                         (lambda ()
                           #`(#,loop (cdr #,h)
                                     #,@(map (lambda (var g)
                                               #`(cons #,(cadr var) #,g))
                                             inner-vars g*))))))))
       (fold (lambda (g var vars)
               (alist-cons (car var) (list g (+ (cadr (cdr var)) 1)) vars))
             vars g* inner-vars)))))

(define (gen-matcher* e lit* pattern* vars)
  (let loop ((e e) (pattern* pattern*) (vars vars))
    (cond ((null? pattern*)
           (values (lambda (k)
                     #`(if (null? #,e)
                           #,(k)
                           (fail)))
                   vars))
          ((pair? pattern*)
           (let ((e1 (car (generate-temporaries '(#f))))
                 (e2 (car (generate-temporaries '(#f)))))
             (let*-values (((car-matcher vars)
                            (gen-matcher e1 lit* (car pattern*) vars))
                           ((cdr-matcher vars)
                            (loop e2 (cdr pattern*) vars)))
               (values (lambda (k)
                         #`(let ((#,e1 (car #,e))
                                 (#,e2 (cdr #,e)))
                             #,(car-matcher (lambda ()
                                              (cdr-matcher k)))))
                       vars))))
          (else
           (gen-matcher e lit* pattern* vars)))))

(define (make-synthetic-identifier id)
  (close-syntax id (environment)))

(define (generate-temporaries l)
  (map (lambda (x) (make-synthetic-identifier 't)) l))

(define-syntax with-syntax
  (lambda (x)
    (syntax-case x ()
      ((_ ((p e0) ...) e1 e2 ...)
       #'(syntax-case (list e0 ...) ()
           ((p ...) (let () e1 e2 ...)))))))

(define (syntax-violation who message . form*)
  (apply error message form*))

(define-syntax define-current-ellipsis
  (lambda (stx)
    (syntax-case stx ()
      ((_ ellipsis)
       (let ((mac (cdr (env-cell (current-usage-environment) current-ellipsis-id))))
         (macro-aux-set! mac #'ellipsis))
       #'(begin)))))

(define-syntax with-ellipsis
  (lambda (stx)
    (syntax-case stx ()
      ((_ ellipsis . body)
       (with-syntax ((current-ellipsis current-ellipsis-id))
         #'(let-syntax ((current-ellipsis (syntax-rules ())))
             (define-current-ellipsis ellipsis)
             . body))))))

;; Local variables:
;; eval: (put '%define-syntax 'scheme-indent-function 1)
;; End:
