#lang racket/base
;; HLL pattern analysis & processing

(provide (for-syntax analyze-pattern)
         (struct-out predicate-match)
         match-value/captures
         ?
         )

(require (for-syntax racket/base))
(require (for-syntax racket/match))
(require (for-syntax syntax/parse))

(require racket/match)
(require "support/struct.rkt")
(require "treap.rkt")
(require "core.rkt")

(require auxiliary-macro-context)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-auxiliary-macro-context
  #:context-name assertion-expander
  #:prop-name prop:assertion-expander
  #:prop-predicate-name assertion-expander?
  #:prop-accessor-name assertion-expander-proc
  #:macro-definer-name define-assertion-expander
  #:introducer-parameter-name current-assertion-expander-introducer
  #:local-introduce-name syntax-local-assertion-expander-introduce
  #:expander-id-predicate-name assertion-expander-id?
  #:expander-transform-name assertion-expander-transform)

(provide (for-syntax
          prop:assertion-expander
          assertion-expander?
          assertion-expander-proc
          syntax-local-assertion-expander-introduce
          assertion-expander-id?
          assertion-expander-transform)
         define-assertion-expander)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(struct predicate-match (predicate sub-pattern) #:transparent)

;; Value Projection -> (Option (Listof Value))
;; Match a single value against a projection, returning a list of
;; captured values.
(define (match-value/captures v p)
  (define (walk v p captures-rev)
    (match* (v p)
      [(_ (capture sub))
       (match (walk v sub '())
         [#f #f]
         [nested-captures-rev (append nested-captures-rev (list v) captures-rev)])]
      [(_ (predicate-match pred? sub)) #:when (pred? v)
       (walk v sub captures-rev)]
      [((== ?) _)
       captures-rev]
      [(_ (== ?))
       captures-rev]
      [((cons v1 v2) (cons p1 p2))
       (match (walk v1 p1 captures-rev)
         [#f #f]
         [c (walk v2 p2 c)])]
      [((? vector? v) (? vector? p)) #:when (= (vector-length v) (vector-length p))
       (define limit (vector-length v))
       (do ((c captures-rev (walk (vector-ref v i) (vector-ref p i) c))
            (i 0 (+ i 1)))
           ((or (= i limit) (not c)) c))]
      [(_ _) #:when (or (treap? v) (treap? p))
       (error 'match-value/captures "Cannot match on treaps at present")]
      [((? non-object-struct?) (? non-object-struct?))
       #:when (eq? (struct->struct-type v) (struct->struct-type p))
       (walk (struct->vector v) (struct->vector p) captures-rev)]
      [(_ _) #:when (equal? v p)
       captures-rev]
      [(_ _)
       #f]))
  (define captures-rev (walk v p '()))
  (and captures-rev (reverse captures-rev)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(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 (is-message-pattern? outer-expr-stx)
    (syntax-parse outer-expr-stx
      #:literals [message]
      [(message _ ...) '#t]
      [_ #f]))

  ;; Syntax -> (Values Projection AssertionSetPattern (ListOf Identifier) Syntax)
  (define (analyze-pattern outer-expr-stx pat-stx0)
    (let walk ((armed-pat-stx pat-stx0))
      (define pat-stx (syntax-disarm armed-pat-stx orig-insp))
      (syntax-case pat-stx ($ ? quasiquote unquote quote)
        ;; Extremely limited support for quasiquoting and quoting
        [(quasiquote (unquote p)) (walk #'p)]
        [(quasiquote (p ...)) (walk #'(list (quasiquote p) ...))]
        [(quasiquote p) (values #''p #''p '() #''p)]
        [(quote p) (values #''p #''p '() #''p)]

        [$v
         (dollar-id? #'$v)
         (with-syntax [(v (undollar #'$v))]
           (values #'(?!)
                   #'?
                   (list #'v)
                   #'v))]

        [($ v)
         (values #'(?!)
                 #'?
                 (list #'v)
                 #'v)]

        [($ v p)
         (let ()
           (define-values (pr g bs _ins) (walk #'p))
           (when (and (not (null? bs)) (not (is-message-pattern? outer-expr-stx)))
             (raise-syntax-error #f
                                 "Nested bindings only supported in message events"
                                 outer-expr-stx
                                 pat-stx))
           (values #`(?! #,pr)
                   g
                   (cons #'v bs)
                   #'v))]

        [(? pred? p)
         ;; TODO: support pred? in asserted/retracted as well as message events
         (let ()
           (when (not (is-message-pattern? outer-expr-stx))
             (raise-syntax-error #f
                                 "Predicate '?' matching only supported in message events"
                                 outer-expr-stx
                                 pat-stx))
           (define-values (pr g bs ins) (walk #'p))
           (values #`(predicate-match pred? #,pr)
                   g
                   bs
                   ins))]

        [(expander args ...)
         (assertion-expander-id? #'expander)
         (assertion-expander-transform pat-stx (lambda (r) (walk (syntax-rearm r pat-stx))))]

        [(ctor p ...)
         (let ()
           (define parts (if (identifier? #'ctor) #'(p ...) #'(ctor p ...)))
           (define-values (pr g bs ins)
             (for/fold [(pr '()) (g '()) (bs '()) (ins '())] [(p (syntax->list parts))]
               (define-values (pr1 g1 bs1 ins1) (walk p))
               (values (cons pr1 pr)
                       (cons g1 g)
                       (append bs bs1)
                       (cons ins1 ins))))
           (if (identifier? #'ctor)
               (values (cons #'ctor (reverse pr))
                       (cons #'ctor (reverse g))
                       bs
                       (cons #'ctor (reverse ins)))
               (values (reverse pr)
                       (reverse g)
                       bs
                       (reverse ins))))]

        [?
         (raise-syntax-error #f
                             "Invalid use of '?' in pattern; use '_' instead"
                             outer-expr-stx
                             pat-stx)]

        [non-pair
         (if (and (identifier? #'non-pair)
                  (free-identifier=? #'non-pair #'_))
             (values #'?
                     #'?
                     '()
                     #'?)
             (values #'non-pair
                     #'non-pair
                     '()
                     #'non-pair))]))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(module+ test
  (require rackunit)

  (check-equal? (match-value/captures (list 1 2 3)
                                      (list 1 2 3))
                '())
  (check-equal? (match-value/captures (list 1 2 3)
                                      (list 1 22 3))
                #f)
  (check-equal? (match-value/captures (list 1 2 3)
                                      (list (?!) (?!) (?!)))
                (list 1 2 3))
  (check-equal? (match-value/captures (list 1 2 3)
                                      (list (?!) 2 (?!)))
                (list 1 3))
  (check-equal? (match-value/captures (list 1 2 3)
                                      (list (?!) ? (?!)))
                (list 1 3))
  (check-equal? (match-value/captures (list 1 2 3)
                                      (list (?!) (?! 2) (?!)))
                (list 1 2 3))
  (check-equal? (match-value/captures (list 1 2 3)
                                      (list (?!) (?! 22) (?!)))
                #f)

  (struct x (a b) #:prefab)
  (struct y (z w) #:prefab)

  (check-equal? (match-value/captures (x 1 2) (x 1 2)) '())
  (check-equal? (match-value/captures (x 1 22) (x 1 2)) #f)
  (check-equal? (match-value/captures (x 1 2) (x 1 22)) #f)
  (check-equal? (match-value/captures (x 1 2) (?! (x ? ?))) (list (x 1 2)))
  (check-equal? (match-value/captures (x 1 2) (?! (x ? 2))) (list (x 1 2)))
  (check-equal? (match-value/captures (x 1 2) (?! (x ? 22))) #f)

  (check-equal? (match-value/captures 123 (predicate-match even? ?)) #f)
  (check-equal? (match-value/captures 124 (predicate-match even? ?)) '())
  (check-equal? (match-value/captures (list 123) (list (predicate-match even? ?))) #f)
  (check-equal? (match-value/captures (list 124) (list (predicate-match even? ?))) '())
  (check-equal? (match-value/captures (list 124) (?! (list (predicate-match even? ?)))) '((124))))
