Steps toward efficiently-indexed routing tables
This commit is contained in:
parent
418132dad7
commit
1352f0d473
|
@ -0,0 +1,348 @@
|
|||
#lang racket/base
|
||||
|
||||
(require racket/set)
|
||||
(require racket/match)
|
||||
(require (only-in racket/port call-with-output-string))
|
||||
(require (only-in racket/class object?))
|
||||
|
||||
(require rackunit)
|
||||
|
||||
(provide )
|
||||
|
||||
(define-syntax-rule (define-singleton-struct singleton-name struct-name print-representation)
|
||||
(begin
|
||||
(struct struct-name ()
|
||||
#:transparent
|
||||
#:property prop:custom-write
|
||||
(lambda (v port mode) (display print-representation port)))
|
||||
(define singleton-name (struct-name))))
|
||||
|
||||
;; Unicode angle brackets: 〈, 〉
|
||||
|
||||
;; A Sigma is, roughly, a token in a value being matched. It is one of:
|
||||
;; - a struct-type, signifying the start of a struct.
|
||||
;; - start-of-pair, signifying the start of a pair.
|
||||
;; - start-of-vector, signifying the start of a vector.
|
||||
;; - end-of-sequence, signifying the notional close-paren at the end of a compound.
|
||||
;; - any other value, representing itself.
|
||||
(define-singleton-struct SOP start-of-pair "<pair")
|
||||
(define-singleton-struct SOV start-of-vector "<vector")
|
||||
(define-singleton-struct EOS end-of-sequence ">")
|
||||
|
||||
;; A Pattern is an atom, the special wildcard value, or a Racket
|
||||
;; compound (struct, pair, or vector) containing Patterns.
|
||||
(define-singleton-struct ? wildcard "★") ;; alternative printing: ¿
|
||||
|
||||
;; A Matcher is either
|
||||
;; - a Set of Any, representing a successful match (if the end of the input has been reached)
|
||||
;; - a Hashtable mapping (Sigma or wildcard) to Matcher
|
||||
;; - a (wildcard-sequence Matcher)
|
||||
;; If, in a hashtable matcher, a wild key is present, it is intended
|
||||
;; to catch all and ONLY those keys not otherwise present in the
|
||||
;; table.
|
||||
(struct wildcard-sequence (matcher) #:transparent)
|
||||
|
||||
(define (rnull) (hash))
|
||||
|
||||
(define (rempty? r)
|
||||
(and (hash? r)
|
||||
(zero? (hash-count r))))
|
||||
|
||||
(define (rvalue v) (set v))
|
||||
|
||||
(define (rseq e r) (if (rempty? r) r (hash e r)))
|
||||
(define (rwild r) (rseq ? r))
|
||||
(define (rwildseq r) (if (rempty? r) r (wildcard-sequence r)))
|
||||
|
||||
;; Any -> Boolean
|
||||
;; Racket objects are structures, so we reject them explicitly for
|
||||
;; now, leaving them opaque to unification.
|
||||
(define (non-object-struct? x)
|
||||
(and (struct? x)
|
||||
(not (object? x))))
|
||||
|
||||
(define (pattern->matcher v p)
|
||||
(let walk ((p p) (acc (rseq EOS (rvalue v))))
|
||||
(match p
|
||||
[(== ?) (rwild acc)]
|
||||
[(cons p1 p2) (rseq SOP (walk p1 (walk p2 (rseq EOS acc))))]
|
||||
[(vector ps ...) (rseq SOV (foldr walk (rseq 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))))
|
||||
(rseq t (foldr walk (rseq EOS acc) fs))]
|
||||
[other (rseq other acc)])))
|
||||
|
||||
(module+ test
|
||||
(define (E . vs) (hash EOS (apply set vs)))
|
||||
(check-equal? (pattern->matcher 'A 123) (hash 123 (E 'A)))
|
||||
(check-equal? (pattern->matcher 'A (cons 1 2)) (hash SOP (hash 1 (hash 2 (hash EOS (E 'A))))))
|
||||
(check-equal? (pattern->matcher 'A (cons ? 2)) (hash SOP (hash ? (hash 2 (hash EOS (E 'A))))))
|
||||
(check-equal? (pattern->matcher 'A SOP) (hash struct:start-of-pair (hash EOS (E 'A))))
|
||||
(check-equal? (pattern->matcher 'A ?) (hash ? (E 'A)))
|
||||
)
|
||||
|
||||
(define (rlookup r key)
|
||||
(hash-ref r key (lambda () #f)))
|
||||
|
||||
(define (rupdate r key k)
|
||||
(if k
|
||||
(hash-set r key k)
|
||||
(hash-remove r key)))
|
||||
|
||||
(define (key-open? k)
|
||||
(or (eq? k SOP)
|
||||
(eq? k SOV)
|
||||
(struct-type? k)))
|
||||
|
||||
(define (key-close? k)
|
||||
(eq? k EOS))
|
||||
|
||||
(define (key-normal? k)
|
||||
(not (or (key-open? k)
|
||||
(key-close? k))))
|
||||
|
||||
(define ror
|
||||
(let ()
|
||||
(define (merge o1 o2)
|
||||
(match* (o1 o2)
|
||||
[(#f #f) #f]
|
||||
[(#f r) r]
|
||||
[(r #f) r]
|
||||
[(r1 r2) (walk r1 r2)]))
|
||||
(define (walk-wildseq wsr r) (walk (walk (rwild (rwildseq wsr)) (rseq EOS wsr)) r))
|
||||
(define (walk re1 re2)
|
||||
(match* (re1 re2)
|
||||
[((wildcard-sequence r1) (wildcard-sequence r2)) (wildcard-sequence (walk r1 r2))]
|
||||
[((wildcard-sequence r1) r2) (walk-wildseq r1 r2)]
|
||||
[(r1 (wildcard-sequence r2)) (walk-wildseq r2 r1)]
|
||||
[((? set? v1) (? set? v2)) (set-union v1 v2)]
|
||||
[((? hash? h1) (? hash? h2))
|
||||
(if (< (hash-count h2) (hash-count h1))
|
||||
(walk h2 h1)
|
||||
(let ((w (merge (rlookup h1 ?) (rlookup h2 ?))))
|
||||
(if w
|
||||
(let ((keys (set-remove (set-union (hash-keys h1) (hash-keys h2)) ?)))
|
||||
(for/fold [(acc (hash ? w))] [(key keys)]
|
||||
(define k (merge (rlookup h1 key) (rlookup h2 key)))
|
||||
(rupdate acc
|
||||
key
|
||||
(cond
|
||||
[(key-open? key) (merge (rwildseq w) k)]
|
||||
[(key-close? key) (if (wildcard-sequence? w)
|
||||
(merge (wildcard-sequence-matcher w) k)
|
||||
k)]
|
||||
[else (merge w k)]))))
|
||||
(for/fold [(acc h2)] [((key k1) (in-hash h1))]
|
||||
(define k (merge k1 (rlookup h2 key)))
|
||||
(rupdate acc key k)))))]))
|
||||
walk))
|
||||
|
||||
(define (match-value r v)
|
||||
(let walk ((vs (list v)) (stack '(())) (r r))
|
||||
(define (walk-wild vs stack)
|
||||
(match (rlookup r ?)
|
||||
[#f (set)]
|
||||
[k (walk vs stack k)]))
|
||||
(match r
|
||||
[(wildcard-sequence k)
|
||||
(match stack
|
||||
['() (set)]
|
||||
[(cons rest stack1) (walk rest stack1 k)])]
|
||||
[(? set?)
|
||||
(if (and (null? vs)
|
||||
(null? stack))
|
||||
r
|
||||
(set))]
|
||||
[(? hash?)
|
||||
(match vs
|
||||
['()
|
||||
(match stack
|
||||
['() (set)]
|
||||
[(cons rest stack1)
|
||||
(match (rlookup r EOS)
|
||||
[#f (set)]
|
||||
[k (walk rest stack1 k)])])]
|
||||
[(cons (== ?) rest)
|
||||
(error 'match-value "Cannot match wildcard as a value")]
|
||||
[(cons (cons v1 v2) rest)
|
||||
(match (rlookup r SOP)
|
||||
[#f (walk-wild rest stack)]
|
||||
[k (walk (list v1 v2) (cons rest stack) k)])]
|
||||
[(cons (vector vv ...) rest)
|
||||
(match (rlookup r SOV)
|
||||
[#f (walk-wild rest stack)]
|
||||
[k (walk vv (cons rest stack) k)])]
|
||||
[(cons (? non-object-struct? s) rest)
|
||||
(define-values (t skipped?) (struct-info s))
|
||||
(when skipped? (error 'match-value "Cannot reflect on struct instance ~v" s))
|
||||
(define fs (cdr (vector->list (struct->vector s))))
|
||||
(match (rlookup r t)
|
||||
[#f (walk-wild rest stack)]
|
||||
[k (walk fs (cons rest stack) k)])]
|
||||
[(cons v rest)
|
||||
(match (rlookup r v)
|
||||
[#f (walk-wild rest stack)]
|
||||
[k (walk rest stack k)])])])))
|
||||
|
||||
(module+ test
|
||||
(define (pretty-print-matcher m [port (current-output-port)])
|
||||
(define (d x) (display x port))
|
||||
(define (walk i m)
|
||||
(match m
|
||||
[(wildcard-sequence k)
|
||||
(d "...>")
|
||||
(walk (+ i 4) k)]
|
||||
[(? set? vs)
|
||||
(d "{")
|
||||
(for ((v vs)) (d v))
|
||||
(d "}")]
|
||||
[(? hash? h)
|
||||
(for/fold [(need-sep? #f)] [((key k) (in-hash h))]
|
||||
(when need-sep?
|
||||
(newline port)
|
||||
(d (make-string i #\space)))
|
||||
(d " ")
|
||||
(define keystr (call-with-output-string (lambda (p) (display key p))))
|
||||
(d keystr)
|
||||
(walk (+ i 1 (string-length keystr)) k)
|
||||
#t)]))
|
||||
(newline port)
|
||||
(walk 0 m)
|
||||
(newline port)
|
||||
(flush-output port)
|
||||
m)
|
||||
|
||||
(define (check-matches matcher . tests)
|
||||
(let walk ((tests tests))
|
||||
(match tests
|
||||
['() (void)]
|
||||
[(list* message expectedstr rest)
|
||||
(define actualset (match-value matcher message))
|
||||
(printf "~v ==> ~v\n" message actualset)
|
||||
(check-equal? actualset
|
||||
(apply set (map (lambda (c) (string->symbol (string c)))
|
||||
(string->list expectedstr))))
|
||||
(walk rest)])))
|
||||
|
||||
(void (pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list (list ?) 'x))
|
||||
(pattern->matcher 'B (list (list ?) 'y)))))
|
||||
|
||||
(void (pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list (list 'a 'b) 'x))
|
||||
(pattern->matcher 'B (list (list 'c 'd) 'y)))))
|
||||
|
||||
(void (pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list (list 'a 'b) 'x))
|
||||
(pattern->matcher 'B (list (list ? ?) 'y)))))
|
||||
|
||||
(check-matches
|
||||
(pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list (list 'a 'b) 'x))
|
||||
(pattern->matcher 'B (list (list ? ?) 'x))))
|
||||
(list 'z 'x) ""
|
||||
(list (list 'z 'z) 'x) "B"
|
||||
(list (list 'z (list 'z)) 'x) "B"
|
||||
(list (list 'a 'b) 'x) "AB")
|
||||
|
||||
(check-matches
|
||||
(pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list (list 'a 'b) 'x))
|
||||
(pattern->matcher 'B (list (list ?) 'y))))
|
||||
(list 'z 'y) ""
|
||||
(list (list 'z 'z) 'y) ""
|
||||
(list (list 'z 'z) 'x) ""
|
||||
(list (list 'a 'b) 'x) "A")
|
||||
|
||||
(check-matches
|
||||
(pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list (list 'a 'b) 'x))
|
||||
(pattern->matcher 'B (list ? 'y))))
|
||||
(list 'z 'y) "B"
|
||||
(list (list 'z 'z) 'y) "B"
|
||||
(list (list 'a 'b) 'x) "A")
|
||||
|
||||
(check-matches
|
||||
(pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list 'a 'b))
|
||||
(pattern->matcher 'B (list 'c 'd))))
|
||||
(list 'a 'b) "A"
|
||||
(list 'c 'd) "B"
|
||||
(list 'a 'd) ""
|
||||
(list 'c 'b) "")
|
||||
|
||||
(void (pretty-print-matcher (ror (pattern->matcher 'A (list (list 'a 'b) 'x))
|
||||
;; Note: this is a largely nonsense matcher,
|
||||
;; since it expects no input at all
|
||||
(rseq EOS (rvalue 'B)))))
|
||||
|
||||
(check-matches
|
||||
(pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list (list 'a 'b) 'x))
|
||||
(pattern->matcher 'B ?)))
|
||||
(list (list 'a 'b) 'x) "AB"
|
||||
'p "B"
|
||||
(list 'p) "B")
|
||||
|
||||
(check-matches
|
||||
(pretty-print-matcher
|
||||
(ror (pattern->matcher 'A (list 'a ?))
|
||||
(pattern->matcher 'B (list 'a (list 'b)))))
|
||||
|
||||
(list 'a (list 'b)) "AB"
|
||||
(list 'a (list 'b 'b)) "A"
|
||||
(list 'a (list 'c 'c)) "A"
|
||||
(list 'a (list 'c)) "A"
|
||||
(list 'a (list (list))) "A"
|
||||
(list 'a (list)) "A"
|
||||
(list 'a 'x) "A")
|
||||
|
||||
(check-matches
|
||||
(pretty-print-matcher
|
||||
(ror (ror (pattern->matcher 'A (list 'a ?))
|
||||
(pattern->matcher 'A (list 'q ?)))
|
||||
(pattern->matcher 'B (list 'a (list 'b)))))
|
||||
(list 'a (list 'b)) "AB"
|
||||
(list 'q (list 'b)) "A"
|
||||
(list 'a 'x) "A"
|
||||
(list 'q 'x) "A"
|
||||
(list 'a (list)) "A"
|
||||
(list 'q (list)) "A"
|
||||
(list 'z (list)) "")
|
||||
|
||||
(define (bigdemo)
|
||||
(define ps
|
||||
(for/list ((c (in-string "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")))
|
||||
(define csym (string->symbol (string c)))
|
||||
(pattern->matcher csym (list csym ?))))
|
||||
(ror (foldr ror (rnull) ps)
|
||||
(pattern->matcher '+ (list 'Z (list ? '- ?)))))
|
||||
|
||||
(void (pretty-print-matcher (bigdemo)))
|
||||
(check-matches
|
||||
(bigdemo)
|
||||
(list 'a '-) "a"
|
||||
(list 'Z '-) "Z"
|
||||
(list '? '-) ""
|
||||
(list 'a (list '- '- '-)) "a"
|
||||
(list 'a (list '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '-)) "a"
|
||||
(list 'Z) ""
|
||||
(list 'Z 'x) "Z"
|
||||
(list 'Z (list)) "Z"
|
||||
(list 'Z (list '-)) "Z"
|
||||
(list 'Z (list '-)) "Z"
|
||||
(list 'Z (list '- '-)) "Z"
|
||||
(list 'Z (list '- '- '-)) "Z+"
|
||||
(list 'Z (list '- '- '- '-)) "Z"
|
||||
(list 'Z (list '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '- '-)) "Z"
|
||||
(list 'Z '((()) - -)) "Z+"
|
||||
(list '? (list '- '- '-)) "")
|
||||
|
||||
(check-matches (pretty-print-matcher (pattern->matcher 'A (list* 'a 'b ?)))
|
||||
(list 'a 'b 'c 'd 'e 'f) "A"
|
||||
(list 'b 'c 'd 'e 'f 'a) ""
|
||||
3 "")
|
||||
|
||||
)
|
Loading…
Reference in New Issue