This commit is contained in:
mehbark 2025-02-22 20:37:27 -05:00
commit e2b077766e
2 changed files with 361 additions and 0 deletions

223
match.sls Normal file
View file

@ -0,0 +1,223 @@
;; extensible, syntax property-based matching for chez scheme a la trivia
(library (meh match)
(export
match 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* ...))]))
;; 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
(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]))
)

138
std.sls Normal file
View file

@ -0,0 +1,138 @@
;; amalgam of useful stuff
(library (meh std)
(export
let1 -> ->> as->
define-syntax-rule
λ
let/cc let/1cc
identity const const*
thunk thunk*
compose compose1 partial
negate conjoin disjoin)
(import (chezscheme))
(export (import (meh match)))
;;; SYNTAX
(define-syntax let1
(lambda (e)
(syntax-case e ()
[(let1 name val body ...) (identifier? #'name)
#'(let ([name val])
body ...)])))
;; i H8TE that (-> x (partial + 1)) is (partial x + 1)
;; maybe there should be a proc version
(define-syntax ->
(syntax-rules ()
[(-> x) x]
[(-> x (f x* ...) f* ...)
(-> (f x x* ...) f* ...)]
[(-> x f f* ...)
(-> (f x) f* ...)]))
(define-syntax ->>
(syntax-rules ()
[(->> x) x]
[(->> x (f x* ...) f* ...)
(->> (f x* ... x) f* ...)]
[(->> x f f* ...)
(->> (f x) f* ...)]))
;; iirc, the clojure as-> puts the expression in place (i.e. no binding)
(define-syntax as->
(lambda (e)
(syntax-case e ()
[(as-> name x) (identifier? #'name) #'x]
[(as-> name x (f x* ...) f* ...) (identifier? #'name)
#'(let* ([name x]
[name (f x* ...)])
(as-> name name f* ...))]
[(as-> name x f f* ...) (identifier? #'name)
#'(as-> name x (f name) f* ...)])))
;; straight from racket; canonical
(define-syntax define-syntax-rule
(syntax-rules ()
[(define-syntax-rule (id . pattern) templ)
(define-syntax id
(syntax-rules ()
[(id . pattern) templ]))]))
(alias λ lambda)
;;; CONTINUATIONS
(define-syntax-rule (let/cc name body* ...) (call/cc (lambda (name) body* ...)))
(define-syntax-rule (let/1cc name body* ...) (call/1cc (lambda (name) body* ...)))
;;; PROCEDURE MANIP
;; could just be values, but this is a pinch better
(define (identity x) x)
(define (const x) (rec const-fn (lambda _ x)))
(define (const* . xs) (rec const*-fn (lambda _ (apply values xs))))
(define-syntax-rule (thunk body ...) (lambda () body ...))
(define-syntax-rule (thunk* body ...) (lambda _ body ...))
;; ((compose f g h) x) = (f (g (h x)))
(define (compose . fs)
(if (null? fs)
values
(let ([f (car fs)]
[rest (apply compose (cdr fs))])
;; gives the procedure a better name
(rec compose-fn
(lambda xs
(call-with-values
(lambda () (apply rest xs))
f))))))
(define (compose1 . fs)
(if (null? fs)
identity
(let ([f (car fs)]
[rest (apply compose1 (cdr fs))])
(rec compose1-fn
(lambda (x)
(f (rest x)))))))
(define (partial f . xs)
(rec partial-fn
(lambda as
(apply f (append xs as)))))
;; maybe nouns would be preferable? eh why not be consistent with racket
(define (negate f)
(rec negate-fn (lambda xs (not (apply f xs)))))
(define conjoin
(case-lambda
[() (const #t)]
[(f) f]
[(f . fs)
(let ([rest (apply conjoin fs)])
(rec conjoin-fn
(lambda xs
(and (apply f xs) (apply rest xs)))))]))
;; could be (negate conjoin) but i like good proc names
(define disjoin
(case-lambda
[() (const #f)]
[(f) f]
[(f . fs)
(let ([rest (apply disjoin fs)])
(rec disjoin-fn
(lambda xs
(or (apply f xs) (apply rest xs)))))]))
)