;; extensible, syntax property-based matching for chez scheme a la trivia
(library (meh match)
  (export
   match if-let when-let
   if-let* when-let*
   define-m%-expander define-pattern define-pattern-expander
   m%-expander pattern-expander

   ; built-in patterns
   fail and or not guard
   access satisfies cons
   list list*
   vector vector*
   = < > <= >=
   quote quasiquote
   )
  (import (chezscheme))


;; binding the scrutinee once makes the expanded code nicer to read
(define-syntax match
  (syntax-rules ()
    [(match scrutinee clause* ...)
     (let ([s scrutinee])
       (match% s clause* ...))]))

;; i just like rust's thing too much
(define-syntax if-let
  (syntax-rules ()
    [(_ pattern scrutinee then) (if-let pattern scrutinee then (void))]
    [(_ pattern scrutinee then else)
     (match scrutinee
       [pattern then]
       [_ else])]))

(define-syntax when-let
  (syntax-rules ()
    [(_ pattern scrutinee body* ...)
     (if-let pattern scrutinee (begin body* ...))]))

(define-syntax if-let*
  (syntax-rules ()
    [(_ binds then) (if-let* binds then (void))]
    [(_ () then else) then]
    [(_ ([pat val] bind* ...) then else)
     (if-let pat val
       (if-let* (bind* ...) then else)
       else)]))

(define-syntax when-let*
  (syntax-rules ()
    [(_ ([pat* val*] ...) body* ...)
     (if-let* ([pat* val*] ...) (begin body* ...))]))

;; TODO: match-let, match errors

;; no unless-let because come on

(define-syntax match%
  (syntax-rules ()
    [(match% scrutinee) (void)]
    [(match% scrutinee [pattern body* ...] clause* ...)
     (m% scrutinee pattern
         (begin body* ...)
         (match% scrutinee clause* ...))]))

(define m%-expander)
(define pattern-expander)

(define-syntax m%
  (lambda (e)
    (lambda (lookup)
      (define (find-m%-expander name)
        (and (identifier? name) (lookup name #'m%-expander)))
      (define (find-pattern-expander name)
        (and (identifier? name) (lookup name #'pattern-expander)))
      ;; - is a decent choice because _ isn't allowed and * implies something else
      ;; you can also just use _ multiple times lol
      (syntax-case e (-)
        [(_ s - ok fail) #'ok]

        [(_ s pat ok fail) (identifier? #'pat)
         #'(let ([pat s]) ok)]

        ;; before atom? because vectors are (rightly) atoms
        [(_ s #(pat* ...) ok fail)
         #'(m% s (vector pat* ...) ok fail)]

        [(_ s pat ok fail) (atom? (syntax->datum #'pat))
         #'(if (equal? s pat) ok fail)]

        [(_ s (patf pata* ...) ok fail) (find-m%-expander #'patf)
         ((find-m%-expander #'patf) #'(patf s ok fail pata* ...))]

        [(_ s (patf pata* ...) ok fail) (find-pattern-expander #'patf)
         #`(m% s #,((find-pattern-expander #'patf) #'(patf pata* ...))
               ok fail)]

        [(e s (patf pata* ...) ok fail)
         #`(syntax-violation
            'm%
            #,(format "no m%-expander or pattern-expander for ~a"
                      (syntax->datum #'patf))
            #'(e s (patf pata* ...) ok fail)
            #'(patf pata* ...))]))))

;; TODO: define-m% consistent with define-pattern
(define-syntax define-m%-expander
  (syntax-rules ()
    [(_ name [(arg* ...) out] ...)
     (define-property name m%-expander
       (syntax-rules ()
         [(_ arg* ...) out] ...))]))

;; sufficient for most uses
(define-syntax define-pattern
  (syntax-rules ()
    [(_ (name arg* ...) out)
     (define-pattern name
       (syntax-rules ()
         [(_ arg* ...) out]))]
    [(_ name body body* ...)
     (define-property name pattern-expander
       (begin body body* ...))]))

(define-syntax define-pattern-expander
  (syntax-rules ()
    [(_ name [(arg* ...) out] ...)
     (define-pattern name
       (syntax-rules ()
         [(_ arg* ...) out] ...))]))

(define fail)
(define-m%-expander fail
  [(s ok fail) fail])

(define-m%-expander and
  [(s ok fail) ok]
  [(s ok fail pat) (m% s pat ok fail)]
  [(s ok fail pat pat* ...)
   (m% s pat
       (m% s (and pat* ...)
           ok fail)
       fail)])

(define-m%-expander or
  [(s ok fail) fail]
  [(s ok fail pat) (m% s pat ok fail)]
  [(s ok fail pat pat* ...)
   (m% s pat
       ok
       (m% s (or pat* ...)
           ok fail))])

(define-m%-expander not
  [(s ok fail pat) (m% s pat fail ok)])

(define-m%-expander guard
  [(s ok fail subpat win?)
   (m% s subpat
       (if win?
           ok
           fail)
       fail)])

(define access)
(define-m%-expander access
  [(s ok fail fun pat)
   (m% (fun s) pat
       ok fail)])

;; intermediate vars here are very unnecessary but they get optimized out extremely easily
(define-pattern (quote val)
  (guard x (equal? x 'val)))

(define satisfies)
(define-pattern (satisfies pred?)
  (guard x (pred? x)))

(define-pattern (cons a d)
  (and (satisfies pair?)
       (access car a)
       (access cdr d)))

(define-pattern-expander list
  [() '()]
  [(x x* ...) (cons x (list x* ...))])

(define-pattern-expander list*
  [() '()]
  [(x) x]
  [(x x* ...) (cons x (list* x* ...))])

(define-property vector pattern-expander
  (lambda (e)
    (syntax-case e ()
      [(_) #'(and (satisfies vector?) (guard x (zero? (vector-length x))))]
      [(_ x* ...)
       (let* ([pats (syntax->list #'(x* ...))]
              [len (length pats)])
         #`(and (satisfies vector?)
                (guard x (= #,len (vector-length x)))
                #,@(map
                    (lambda (i p) #`(access (lambda (s) (vector-ref s #,i)) #,p))
                    (iota (length pats))
                    pats)))])))

(define vector*)
(define-property vector* pattern-expander
  (lambda (e)
    (syntax-case e ()
      [(_) #'(and (satisfies vector?) (guard x (zero? (vector-length x))))]
      [(_ x* ...)
       (let* ([pats (syntax->list #'(x* ...))]
              [len (length pats)])
         #`(and (satisfies vector?)
                ;;        v only difference
                (guard x (<= #,len (vector-length x)))
                #,@(map
                    (lambda (i p) #`(access (lambda (s) (vector-ref s #,i)) #,p))
                    (iota (length pats))
                    pats)))])))

;; no !=
;; you can already do (satisfies (partial = n))
(define-pattern (= n) (and (satisfies number?) (guard x (= n x))))

(define-pattern (< n)  (and (satisfies real?) (guard x (< x n))))
(define-pattern (> n)  (and (satisfies real?) (guard x (> x n))))
(define-pattern (<= n) (and (satisfies real?) (guard x (<= x n))))
(define-pattern (>= n) (and (satisfies real?) (guard x (>= x n))))

;; ,@ is not likely to happen
(define-pattern quasiquote
  (syntax-rules (unquote)
    [(_ (unquote pat)) pat]
    ;; this specialization *probably* has no perf benefit
    [(_ (x* ...)) (list `x* ...)]
    [(_ (a . d)) (cons `a `d)]
    [(_ x) 'x]))
 )