242 lines
6.6 KiB
Scheme
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]))
|
|
)
|