meh/match.sls

242 lines
6.6 KiB
Scheme

;; 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]))
)