diff --git a/minimart/route.rkt b/minimart/route.rkt index c32bd91..5b3855c 100644 --- a/minimart/route.rkt +++ b/minimart/route.rkt @@ -9,6 +9,8 @@ (provide ? wildcard? + ?! + capture? pattern->matcher matcher? ;; expensive; see implementation matcher-empty @@ -18,7 +20,10 @@ matcher-erase-path matcher-match-value matcher-match-matcher - matcher-relabel) + matcher-relabel + compile-projection + matcher-project + matcher->finite-set) (define-syntax-rule (define-singleton-struct singleton-name struct-name print-representation) (begin @@ -44,6 +49,9 @@ ;; compound (struct, pair, or vector) containing Patterns. (define-singleton-struct ? wildcard "★") ;; alternative printing: ¿ +;; When projecting a matcher, the capturing wildcard can be used. +(define-singleton-struct ?! capture "‽") + ;; A Matcher is either ;; - #f, indicating no further matches possible ;; - a Set of Any, representing a successful match (if the end of the input has been reached) @@ -355,6 +363,166 @@ [(wildcard-sequence m1) (wildcard-sequence (walk m1))] [(? hash?) (for/hash [((k v) (in-hash m))] (values k (walk v)))]))) +(define (compile-projection p) + ;; Extremely similar to pattern->matcher. Besides use of conses + ;; rather than chained hashtables, the only interesting difference + ;; is how ?! is treated. + (let walk ((p p) (acc (cons EOS '()))) + (match p + [(== ?!) (cons ?! acc)] + [(== ?) (cons ? acc)] + [(cons p1 p2) (cons SOP (walk p1 (walk p2 (cons EOS acc))))] + [(vector ps ...) (cons SOV (foldr walk (cons EOS acc) ps))] + [(? non-object-struct?) + (define-values (t skipped?) (struct-info p)) + (when skipped? (error 'pattern->matcher "Cannot reflect on struct instance ~v" p)) + (define fs (cdr (vector->list (struct->vector p)))) + (cons t (foldr walk (cons EOS acc) fs))] + ;; TODO: consider options for treating hash tables as compounds rather than (useless) atoms + [(? hash?) (error 'pattern->matcher "Cannot match on hash tables at present")] + [other (cons other acc)]))) + +;; Matcher × CompiledProjection → Matcher +;; The result matches a vector of length equal to the number of captures. +(define matcher-project + ;; TODO: skip-nested, capture-nested, and the ? and ?! cases in + ;; walk-out all share a suspicious amount of code. Refactor it away. + (let () + (define (skip-nested m k) + (match m + [(wildcard-sequence mk) (k mk)] + [(? hash?) + (for/fold [(acc (skip-nested (rlookup m ?) k))] [((key mk) (in-hash m))] + (if (eq? key ?) + acc + (matcher-union acc (cond + [(key-open? key) (skip-nested mk (lambda (mk) (skip-nested mk k)))] + [(key-close? key) (k mk)] + [else (skip-nested mk k)]))))] + [_ (matcher-empty)])) + + (define (capture-nested m k) + (match m + [(wildcard-sequence mk) (rwildseq (k mk))] + [(? hash?) + (for/fold [(acc (rwild (capture-nested (rlookup m ?) k)))] [((key mk) (in-hash m))] + (if (eq? key ?) + acc + (cond + [(key-open? key) + (rupdate acc key (capture-nested mk (lambda (mk) (capture-nested mk k))))] + [(key-close? key) (rupdate acc key (k mk))] + [else (rupdate acc key (capture-nested mk k))])))] + [_ (matcher-empty)])) + + (define (walk-out m spec) + (match spec + ['() + (match m + [(? set?) (rseq EOS (rseq EOS (set #t)))] + ;; ^ the #t yields a matcher that does not preserve map values. + [_ (matcher-empty)])] + + [(cons (== ?) k) + (match m + [(wildcard-sequence _) (walk-out m k)] + [(? hash?) + (for/fold [(acc (walk-out (rlookup m ?) k))] [((key mk) (in-hash m))] + (if (eq? key ?) + acc + (matcher-union acc (cond + [(key-open? key) (skip-nested mk (lambda (mk) (walk-out mk k)))] + [(key-close? key) #f] + [else (walk-out mk k)]))))] + [_ (matcher-empty)])] + + [(cons (== ?!) k) + (match m + [(wildcard-sequence _) (rwild (walk-out m k))] + [(? hash?) + (for/fold [(acc (rwild (walk-out (rlookup m ?) k)))] [((key mk) (in-hash m))] + (if (eq? key ?) + acc + (cond + [(key-open? key) + (rupdate acc key (capture-nested mk (lambda (mk) (walk-out mk k))))] + [(key-close? key) acc] + [else (rupdate acc key (walk-out mk k))])))] + [_ (matcher-empty)])] + + [(cons sigma k) + (match m + [(wildcard-sequence mk) + (if (key-close? sigma) + (walk-out mk k) + (walk-out m k))] + [(? hash?) + (matcher-union (walk-out (rlookup m sigma) k) + (walk-out (rlookup m ?) k))] + [_ (matcher-empty)])])) + + (lambda (m spec) + (rseq SOV (walk-out m spec))))) + +;; Matcher → (Option (Setof Value)) +;; Multiplies out unions. Returns #f if any dimension of m is infinite. +(define matcher->finite-set + (let () + ;; Matcher (Value Matcher -> (Setof Value)) -> (Option (Setof Value)) + ;; Calls k with each possible atomic value at this matcher + ;; position, and accumulates the results. + (define (walk m k) + (match m + [(wildcard-sequence _) #f] + [(? hash?) + (and (not (hash-has-key? m ?)) + (for/fold [(acc (set))] [((key mk) (in-hash m))] + (maybe-union + acc + (cond + [(key-open? key) + (walk-seq mk (lambda (vss vsk) + (for/fold [(acc (set))] [(vs (in-set vss))] + (maybe-union acc + (k (transform-seqs vs key) vsk)))))] + [(key-close? key) + (error 'matcher->finite-set "Internal error: unexpected key-close")] + [else + (k key mk)]))))] + [_ (k (set) #f)])) + + ;; Matcher (Value Matcher -> (Setof (Listof Value))) -> (Option (Setof (Listof Value))) + ;; Calls k with each possible sequence of atomic values at this + ;; matcher position, and accumulates the results. + (define (walk-seq m k) + (match m + [(wildcard-sequence _) #f] + [(? hash?) + (and (not (hash-has-key? m ?)) + (for/fold [(acc (set))] [((key mk) (in-hash m))] + (maybe-union acc (cond + [(key-close? key) (k (set '()) mk)] + [else (walk (rseq key mk) + (lambda (v vk) + (walk-seq vk (lambda (vss vsk) + (k (for/set [(vs (in-set vss))] + (cons v vs)) + vsk)))))]))))] + [_ (k (set) #f)])) + + ;; (Listof Value) Sigma -> Value + (define (transform-seqs vs opener) + (cond + [(eq? opener SOP) (apply cons vs)] + [(eq? opener SOV) (list->vector vs)] + [(struct-type? opener) (apply (struct-type-make-constructor opener) vs)])) + + ;; (Option (Setof A)) (Option (Setof A)) -> (Option (Setof A)) + (define (maybe-union s1 s2) (and s1 s2 (set-union s1 s2))) + + (lambda (m) + (walk m (lambda (v k) (set v)))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (module+ test @@ -687,3 +855,58 @@ (pattern->matcher 'C (list 'c ?)) (pattern->matcher 'd (list 'd ?)))))) (matcher-match-matcher abc (matcher-relabel bcd (lambda (old) (set #t)))))) + +(module+ test + (check-equal? (compile-projection (list 'a 'b)) + (list SOP 'a SOP 'b '() EOS EOS EOS)) + (check-equal? (compile-projection (list 'a ?!)) + (list SOP 'a SOP ?! '() EOS EOS EOS)) + + (check-equal? (matcher-project (matcher-union (pattern->matcher 'A (list 'a 'a)) + (pattern->matcher 'B (list 'a 'b))) + (compile-projection (list 'a ?!))) + (matcher-union (pattern->matcher #t (vector 'a)) + (pattern->matcher #t (vector 'b)))) + + (check-equal? (matcher-project (matcher-union (pattern->matcher 'A (list 'a 'a)) + (pattern->matcher 'B (list 'a (vector 'b 'c 'd)))) + (compile-projection (list 'a ?!))) + (matcher-union (pattern->matcher #t (vector 'a)) + (pattern->matcher #t (vector (vector 'b 'c 'd))))) + + (check-equal? (matcher-project (matcher-union (pattern->matcher 'A (list 'a 'a)) + (pattern->matcher 'B (list 'a (vector 'b ? 'd)))) + (compile-projection (list 'a ?!))) + (matcher-union (pattern->matcher #t (vector 'a)) + (pattern->matcher #t (vector (vector 'b ? 'd))))) + + (check-equal? (matcher->finite-set + (matcher-project (matcher-union (pattern->matcher 'A (list 'a 'a)) + (pattern->matcher 'B (list 'a 'b))) + (compile-projection (list 'a ?!)))) + (set '#(a) '#(b))) + + (check-equal? (matcher->finite-set + (matcher-project (matcher-union (pattern->matcher 'A (list 'a 'a)) + (pattern->matcher 'B (list 'a (vector 'b 'c 'd)))) + (compile-projection (list 'a ?!)))) + (set '#(a) '#(#(b c d)))) + + (check-equal? (matcher->finite-set + (matcher-project (matcher-union (pattern->matcher 'A (list 'a 'a)) + (pattern->matcher 'B (list 'a (vector 'b ? 'd)))) + (compile-projection (list 'a ?!)))) + #f) + + (check-equal? (matcher-project (matcher-union (pattern->matcher 'A (cons 1 2)) + (pattern->matcher 'B (cons 3 4))) + (compile-projection (cons ?! ?!))) + (matcher-union (pattern->matcher #t (vector 1 2)) + (pattern->matcher #t (vector 3 4)))) + + (check-equal? (matcher->finite-set + (matcher-project (matcher-union (pattern->matcher 'A (cons 1 2)) + (pattern->matcher 'B (cons 3 4))) + (compile-projection (cons ?! ?!)))) + (set '#(1 2) '#(3 4))) + ) \ No newline at end of file