#lang racket/base

(provide (struct-out discard)
         (struct-out capture)

         (for-syntax analyse-pattern
                     instantiate-pattern->pattern
                     instantiate-pattern->value
                     desc->key
                     desc->skeleton-proj
                     desc->skeleton-stx
                     desc->capture-proj
                     desc->capture-names
                     desc->assertion-stx)

         (all-from-out "pattern-expander.rkt"))

(require (for-syntax racket/base))
(require (for-syntax racket/match))
(require (for-syntax racket/struct-info))
(require (for-syntax syntax/stx))
(require "pattern-expander.rkt")

(struct discard () #:prefab)
(struct capture (detail) #:prefab)

;;---------------------------------------------------------------------------
;; ## Analysing patterns
;;
;; Patterns generate several pieces, which work together to form
;; routing tables:
;;
;;  - the *assertion* allows observers of observers to function;
;;  - the `Skeleton` classifies the shape of the pattern;
;;  - two `SkProj`s select constant and variable pieces from a pattern, respectively; and
;;  - a `SkKey` specifies constant pieces of a pattern, matched against one of the `SkProj`s.
;;
;; The other `SkProj` generates a second `SkKey` which is used as the
;; input to a handler function.

(define-for-syntax orig-insp
  (variable-reference->module-declaration-inspector (#%variable-reference)))

(begin-for-syntax
  (define (dollar-id? stx)
    (and (identifier? stx)
         (char=? (string-ref (symbol->string (syntax-e stx)) 0) #\$)))

  (define (undollar stx)
    (and (dollar-id? stx)
         (datum->syntax stx (string->symbol (substring (symbol->string (syntax-e stx)) 1)))))

  (define (discard-id? stx)
    (and (identifier? stx)
         (free-identifier=? #'_ stx)))

  (define (id-value stx)
    (and (identifier? stx)
         (syntax-local-value stx (lambda () #f))))

  (define (list-id? stx)
    (and (identifier? stx)
         (free-identifier=? #'list stx)))

  (define (vector-id? stx)
    (and (identifier? stx)
         (free-identifier=? #'vector stx)))

  (define (analyse-pattern stx)
    (define disarmed-stx (syntax-disarm stx orig-insp))
    (syntax-case disarmed-stx ($ quasiquote unquote quote)
      [(expander args ...)
       (pattern-expander-id? #'expander)
       (pattern-expander-transform disarmed-stx
                                   (lambda (result)
                                     (analyse-pattern (syntax-rearm result stx))))]

      ;; Extremely limited support for quasiquoting and quoting
      [(quasiquote (unquote p)) (analyse-pattern #'p)]
      [(quasiquote (p ...)) (analyse-pattern #'(list (quasiquote p) ...))]
      [(quasiquote p) (list 'atom stx)]
      [(quote p) (list 'atom stx)]

      [(ctor piece ...)
       (struct-info? (id-value #'ctor))
       (list* 'compound
              (extract-struct-info (id-value #'ctor))
              (stx-map analyse-pattern #'(piece ...)))]
      [(list piece ...)
       (list-id? #'list)
       (list* 'compound
              'list
              (stx-map analyse-pattern #'(piece ...)))]
      [(vector piece ...)
       (vector-id? #'vector)
       (list* 'compound
              'vector
              (stx-map analyse-pattern #'(piece ...)))]
      [id
       (dollar-id? #'id)
       (list 'capture (undollar #'id) (list 'discard))]
      [($ id p)
       (list 'capture #'id (analyse-pattern #'p))]
      [id
       (discard-id? #'id)
       (list 'discard)]
      [_
       (list 'atom stx)]))

  (define (instantiate-pattern->pattern stx)
    (define disarmed-stx (syntax-disarm stx orig-insp))
    (syntax-case disarmed-stx ($ quasiquote unquote quote)
      [(expander args ...)
       (pattern-expander-id? #'expander)
       (pattern-expander-transform disarmed-stx
                                   (lambda (result)
                                     (instantiate-pattern->pattern (syntax-rearm result stx))))]

      ;; Extremely limited support for quasiquoting and quoting
      [(quasiquote (unquote p)) (instantiate-pattern->pattern #'p)]
      [(quasiquote (p ...)) (instantiate-pattern->pattern #'(list (quasiquote p) ...))]
      [(quasiquote p) stx]
      [(quote p) stx]

      [(ctor piece ...)
       (struct-info? (id-value #'ctor))
       (quasisyntax/loc stx (ctor #,@(stx-map instantiate-pattern->pattern #'(piece ...))))]
      [(list piece ...)
       (list-id? #'list)
       (quasisyntax/loc stx (list #,@(stx-map instantiate-pattern->pattern #'(piece ...))))]
      [(vector piece ...)
       (vector-id? #'vector)
       (quasisyntax/loc stx (vector #,@(stx-map instantiate-pattern->pattern #'(piece ...))))]
      [id
       (dollar-id? #'id)
       (undollar #'id)]
      [($ id p)
       #'id]
      [id
       (discard-id? #'id)
       #'id]
      [other
       #'other]))

  (define (instantiate-pattern->value stx)
    (define disarmed-stx (syntax-disarm stx orig-insp))
    (syntax-case disarmed-stx ($ quasiquote unquote quote)
      [(expander args ...)
       (pattern-expander-id? #'expander)
       (pattern-expander-transform disarmed-stx
                                   (lambda (result)
                                     (instantiate-pattern->value (syntax-rearm result stx))))]

      ;; Extremely limited support for quasiquoting and quoting
      [(quasiquote (unquote p)) (instantiate-pattern->value #'p)]
      [(quasiquote (p ...)) (instantiate-pattern->value #'(list (quasiquote p) ...))]
      [(quasiquote p) stx]
      [(quote p) stx]

      [(ctor piece ...)
       (struct-info? (id-value #'ctor))
       (quasisyntax/loc stx (ctor #,@(stx-map instantiate-pattern->value #'(piece ...))))]
      [(list piece ...)
       (list-id? #'list)
       (quasisyntax/loc stx (list #,@(stx-map instantiate-pattern->value #'(piece ...))))]
      [(vector piece ...)
       (vector-id? #'vector)
       (quasisyntax/loc stx (vector #,@(stx-map instantiate-pattern->value #'(piece ...))))]
      [id
       (dollar-id? #'id)
       (undollar #'id)]
      [($ id p)
       #'id]
      [id
       (discard-id? #'id)
       #'(discard)]
      [other
       #'other])))

;;---------------------------------------------------------------------------

(begin-for-syntax
  (define (select-pattern-leaves desc capture-fn atom-fn)
    (define (walk-node key-rev desc)
      (match desc
        [`(compound ,_ ,pieces ...) (walk-edge 0 key-rev pieces)]
        [`(capture ,name-stx ,p) (append (capture-fn key-rev name-stx) (walk-node key-rev p))]
        [`(discard) (list)]
        [`(atom ,v) (atom-fn key-rev v)]))
    (define (walk-edge index key-rev pieces)
      (match pieces
        ['() '()]
        [(cons p pieces) (append (walk-node (cons index key-rev) p)
                                 (walk-edge (+ index 1) key-rev pieces))]))
    (walk-node '() desc))

  (define (desc->key desc)
    (select-pattern-leaves desc
                           (lambda (key-rev name-stx) (list))
                           (lambda (key-rev atom) (list atom))))

  (define (desc->skeleton-proj desc)
    (select-pattern-leaves desc
                           (lambda (key-rev name-stx) (list))
                           (lambda (key-rev atom) (list (reverse key-rev)))))

  (define (desc->skeleton-stx desc)
    (match desc
      [`(compound list ,pieces ...)
       #`(list 'list #,@(map desc->skeleton-stx pieces))]
      [`(compound vector ,pieces ...)
       #`(list 'vector #,@(map desc->skeleton-stx pieces))]
      [`(compound (,struct-type ,_ctor ,_pred ,_getters ,_setters ,_super) ,pieces ...)
       #`(list #,struct-type #,@(map desc->skeleton-stx pieces))]
      [`(capture ,_ ,p) (desc->skeleton-stx p)]
      [`(discard) #'#f]
      [`(atom ,atom-stx) #'#f]))

  (define (desc->capture-proj desc)
    (select-pattern-leaves desc
                           (lambda (key-rev name-stx) (list (reverse key-rev)))
                           (lambda (key-rev atom) (list))))

  (define (desc->capture-names desc)
    (select-pattern-leaves desc
                           (lambda (key-rev name-stx) (list name-stx))
                           (lambda (key-rev atom) (list))))

  (define (desc->assertion-stx desc)
    (match desc
      [`(compound list ,pieces ...)
       #`(list #,@(map desc->assertion-stx pieces))]
      [`(compound vector ,pieces ...)
       #`(vector #,@(map desc->assertion-stx pieces))]
      [`(compound (,_struct-type ,ctor ,_pred ,_getters ,_setters ,_super) ,pieces ...)
       #`(#,ctor #,@(map desc->assertion-stx pieces))]
      [`(capture ,_ ,p) #`(capture #,(desc->assertion-stx p))]
      [`(discard) #'(discard)]
      [`(atom ,v) v]))
  )
