racket/collects/rnrs/enums-6.rkt
2010-04-27 16:50:15 -06:00

262 lines
9.2 KiB
Racket

#lang scheme/base
(require scheme/mpair
rnrs/arithmetic/bitwise-6
(for-syntax scheme/base))
(provide make-enumeration
enum-set-universe
enum-set-indexer
enum-set-constructor
enum-set->list
enum-set-member?
enum-set-subset?
enum-set=?
enum-set-union
enum-set-intersection
enum-set-difference
enum-set-complement
enum-set-projection
define-enumeration)
(define-struct universe (ht syms))
(define-struct enum-set (val uni))
(define (make-enumeration-universe enum)
(let ([bad (lambda ()
(raise-type-error
'make-enumeration
"list of symbols"
enum))])
(unless (mlist? enum) (bad))
(let ([enum (mlist->list enum)])
(unless (andmap symbol? enum) (bad))
(let ([ht (make-hasheq)])
(make-universe
ht
(for/list ([s (in-list enum)]
#:when (not (hash-ref ht s #f)))
(hash-set! ht s (arithmetic-shift 1 (hash-count ht)))
s))))))
(define (make-enumeration enum)
(let ([uni (make-enumeration-universe enum)])
(make-enum-set (sub1 (arithmetic-shift 1 (hash-count (universe-ht uni))))
uni)))
(define (enum-set-universe enum)
(unless (enum-set? enum)
(raise-type-error 'enum-set-universe
"enumeration set"
enum))
(let ([uni (enum-set-uni enum)])
(make-enum-set (sub1 (arithmetic-shift 1 (hash-count
(universe-ht uni))))
uni)))
(define (enum-set-indexer enum)
(unless (enum-set? enum)
(raise-type-error 'enum-set-indexer
"enumeration set"
enum))
(let ([ht (universe-ht (enum-set-uni enum))])
(lambda (sym)
(let ([v (hash-ref ht sym #f)])
(if v
(bitwise-first-bit-set v)
(if (symbol? sym)
#f
(error 'generated-enum-set-indexer
"not a symbol: ~e"
sym)))))))
(define (enum-set-constructor enum)
(unless (enum-set? enum)
(raise-type-error 'enum-set-constructor
"enumeration set"
enum))
(let* ([uni (enum-set-uni enum)]
[ht (universe-ht uni)])
(lambda (orig-syms)
(let loop ([syms orig-syms][val 0])
(cond
[(null? syms) (make-enum-set val uni)]
[(not (mpair? syms))
(raise-type-error 'make-enum-set
"list of symbols"
orig-syms)]
[(hash-ref ht (mcar syms) #f)
=> (lambda (n)
(loop (mcdr syms) (bitwise-ior val n)))]
[else
(error 'make-enum-set
(if (symbol? (mcar syms))
"symbol not in universe: ~e"
"not a symbol: ~e")
(mcar syms))])))))
(define (enum-set->list enum)
(unless (enum-set? enum)
(raise-type-error 'enum-set->list
"enumeration set"
enum))
(let ([v (enum-set-val enum)])
(list->mlist
(for/list ([sym (in-list (universe-syms (enum-set-uni enum)))]
[i (in-naturals)]
#:when (not (zero? (bitwise-and (arithmetic-shift 1 i) v))))
sym))))
(define (enum-set-member? sym enum)
(unless (symbol? sym)
(raise-type-error 'enum-set-member?
"symbol"
sym))
(unless (enum-set? enum)
(raise-type-error 'enum-set-member?
"enumeration set"
enum))
(let ([v (hash-ref (universe-ht (enum-set-uni enum)) sym #f)])
(and v
(not (zero? (bitwise-and v (enum-set-val enum)))))))
(define (check-2-enums who enum1 enum2)
(unless (and (enum-set? enum1)
(enum-set? enum2))
(raise-type-error who
"enumeration set"
(if (enum-set? enum1)
enum2
enum1))))
(define (enum-set-subset? enum1 enum2)
(check-2-enums 'enum-set-subset? enum1 enum2)
(if (eq? (enum-set-uni enum1) (enum-set-uni enum2))
(= (enum-set-val enum1)
(bitwise-and (enum-set-val enum1) (enum-set-val enum2)))
(let ([ht2 (universe-ht (enum-set-uni enum2))]
[v1 (enum-set-val enum1)]
[v2 (enum-set-val enum2)])
(for/fold ([sub? #t])
([(key1 val1) (in-hash (universe-ht (enum-set-uni enum1)))]
#:when sub?)
(let ([val2 (hash-ref ht2 key1 #f)])
(and val2
(or (zero? (bitwise-and v1 val1))
(not (zero? (bitwise-and v2 val2))))))))))
(define (enum-set=? enum1 enum2)
(check-2-enums 'enum-set=? enum1 enum2)
(if (eq? (enum-set-uni enum1) (enum-set-uni enum2))
(= (enum-set-val enum1) (enum-set-val enum2))
(and (enum-set-subset? enum1 enum2)
(enum-set-subset? enum2 enum1))))
(define (check-2-enums/same who enum1 enum2)
(check-2-enums who enum1 enum2)
(unless (eq? (enum-set-uni enum1)
(enum-set-uni enum2))
(error who
"enumeration sets are not the same enumeration type: ~e ~e"
enum1 enum2)))
(define (enum-set-union enum1 enum2)
(check-2-enums/same 'enum-set-union enum1 enum2)
(make-enum-set (bitwise-ior (enum-set-val enum1)
(enum-set-val enum2))
(enum-set-uni enum1)))
(define (enum-set-intersection enum1 enum2)
(check-2-enums/same 'enum-set-intersection enum1 enum2)
(make-enum-set (bitwise-and (enum-set-val enum1)
(enum-set-val enum2))
(enum-set-uni enum1)))
(define (enum-set-difference enum1 enum2)
(check-2-enums/same 'enum-set-intersection enum1 enum2)
(make-enum-set (- (enum-set-val enum1)
(bitwise-and (enum-set-val enum1)
(enum-set-val enum2)))
(enum-set-uni enum1)))
(define (enum-set-complement enum1)
(unless (enum-set? enum1)
(raise-type-error 'enum-set-complement
"enumeration set"
enum1))
(make-enum-set (bitwise-xor (sub1 (arithmetic-shift
1
(hash-count
(universe-ht (enum-set-uni enum1)))))
(enum-set-val enum1))
(enum-set-uni enum1)))
(define (enum-set-projection enum1 enum2)
(check-2-enums 'enum-set-projection enum1 enum2)
(let* ([uni2 (enum-set-uni enum2)]
[ht2 (universe-ht uni2)]
[v1 (enum-set-val enum1)]
[v2 (enum-set-val enum2)])
(make-enum-set
(for/fold ([val 0])
([(key1 val1) (in-hash (universe-ht (enum-set-uni enum1)))])
(if (zero? (bitwise-and v1 val1))
val
(let ([val2 (hash-ref ht2 key1 #f)])
(if val2
(bitwise-ior val val2)
val))))
uni2)))
(define-syntax (define-enumeration stx)
(syntax-case stx ()
[(_ type-name (sym ...) constructor)
(let ([syms (syntax->list #'(sym ...))]
[ht (make-hasheq)])
(unless (identifier? #'type-name)
(raise-syntax-error #f
"not an identifier for type name"
stx
#'type-name))
(for-each (lambda (sym)
(unless (identifier? sym)
(raise-syntax-error #f
"not an identifier (to be used as a symbol)"
stx
sym)))
syms)
(unless (identifier? #'constructor)
(raise-syntax-error #f
"not an identifier for type name"
stx
#'constructor))
(for ([s (in-list syms)])
(unless (hash-ref ht (syntax-e s) #f)
(hash-set! ht (syntax-e s)
(arithmetic-shift 1 (hash-count ht)))))
(with-syntax ([(val ...)
(map (lambda (s) (hash-ref ht (syntax-e s))) syms)])
#'(begin
(define enum-universe (make-enumeration-universe (mlist 'sym ...)))
(define-syntax (type-name stx)
(syntax-case* stx (sym ...) (lambda (a b) (eq? (syntax-e a) (syntax-e b)))
[(_ sym) #''sym]
...
[(_ other)
(identifier? #'other)
(raise-syntax-error #f "not in enumeration" stx #'other)]))
(define-syntax (bit-value stx)
(syntax-case* stx (sym ...) (lambda (a b) (eq? (syntax-e a) (syntax-e b)))
[(_ orig sym) #'val]
...
[(_ orig s)
(raise-syntax-error #f "not in enumeration" #'orig #'s)]))
(...
(define-syntax (constructor stx)
(syntax-case stx ()
[(_ s ...)
(andmap identifier? (syntax->list #'(s ...)))
(with-syntax ([orig stx])
#'(make-enum-set (bitwise-ior (bit-value orig s) ...)
enum-universe))]))))))]))