This repository has been archived on 2022-08-10. You can view files and clone it, but cannot push or open issues or pull requests.
chez-openbsd/s/enum.ss
2022-07-29 15:12:07 +02:00

299 lines
12 KiB
Scheme

;;; enum.ss
;;; Copyright 1984-2017 Cisco Systems, Inc.
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;;
;;; http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.
;; NOTES:
;; This implementation assume the universe is small
;; and the algorithms used by this implementation may be
;; up to linear in the universe
;;
;; This code is a good candidate for partial-static-structure optimization
;; Right now the define-enumeration macro is doing optimizations
;; that could be automatically performed by PSS if PSS worked on enums
;;
;; The R6RS standard is unclear whether the function returned by enum-set-indexer
;; should throw an error if its argument is not a symbol. We have chosen to
;; not include that check, but if the standard is updated, this may need to be changed.
(let ()
;;;;;;;;
#| Low-level enum-set definition and operations
The structure is as follows:
-------------------------------------------------------------------------------
The following records are created once:
enum-base-rtd:
+-----------------+--------------------+--------------------------------+-----+
| rtd:#!base-rtd | parent:#!base-rtd | fields:(index->sym sym->index) | ... |
+-----------------+--------------------+--------------------------------+-----+
enum-parent-rtd:
+-----------------+--------------------+--------------------------------+-----+
| rtd:#!base-rtd | parent: #f | fields:(members) | ... |
+-----------------+--------------------+--------------------------------+-----+
-------------------------------------------------------------------------------
The following record is created per enum-type and it stored the mappings
between symbols and their corresponding bits in the bit mask:
this-enum-rtd:
+-------------------+------------------------+-----------+-----
| rtd:enum-base-rtd | parent:enum-parent-rtd | fields:() | ...
+-------------------+------------------------+-----------+-----
----+------------+------------+
...| index->sym | sym->index |
----+------------+------------+
-------------------------------------------------------------------------------
The following record is created per enum-set:
an-enum-set:
+-------------------+--------------------------------+
| rtd:this-enum-rtd | members: 17 (integer bit mask) |
+-------------------+--------------------------------+
|#
(define enum-base-rtd
(make-record-type ; not sealed, not opaque
#!base-rtd ; undocumented #!base-rtd
'#{enum b9s78zmm79qs7j22-a} ; make enum-base-rtd type nongenerative
'((immutable sym->index) ; static (per enumeration type) fields
(immutable index->sym))))
(define enum-parent-rtd ; not sealed, not opaque, nongenerative
(make-record-type
'#{enum-parent dwwi4y1kribh7mif58yoxe-0}
'((immutable members))))
(define get-sym->index (csv7:record-field-accessor enum-base-rtd 'sym->index))
(define get-index->sym (csv7:record-field-accessor enum-base-rtd 'index->sym))
(define get-members (csv7:record-field-accessor enum-parent-rtd 'members))
(define members-universe -1) ;; All bits set
;;;;;;;;
;; Make a new enum-set using the rtd and the new set of members
(define (make-enum-set enum-set-rtd members)
#;((record-constructor enum-set-rtd) members)
; breaking the abstraction to avoid significant efficiency hit
($record enum-set-rtd members))
;; Perform type check for enum-set and return its RTD
(define (enum-set-rtd who enum-set)
(or (and (record? enum-set)
(let ([rtd (record-rtd enum-set)])
(and (eq? (record-rtd rtd) enum-base-rtd)
rtd)))
($oops who "~s is not an enumeration" enum-set)))
(define (assert-symbol-list who symbol-list)
(unless (and (list? symbol-list)
(for-all symbol? symbol-list))
($oops who "~s is not a list of symbols" symbol-list)))
(define (assert-symbol who symbol)
(unless (symbol? symbol)
($oops who "~s is not a symbol" symbol)))
(define rtd&list->enum-set
(lambda (who rtd symbol-list)
(let ([sym->index (get-sym->index rtd)])
(let loop ([members 0]
[symbol-list symbol-list])
(if (null? symbol-list)
(make-enum-set rtd members)
(let ([index (symbol-hashtable-ref sym->index (car symbol-list) #f)])
(if (not index)
(if who
($oops who "universe does not include specified symbol ~s"
(car symbol-list))
(loop members (cdr symbol-list)))
(loop (logbit1 index members) (cdr symbol-list)))))))))
(define $enum-set->list
(lambda (who enum-set)
(let ([rtd (enum-set-rtd who enum-set)])
(let ([index->sym (get-index->sym rtd)]
[members (get-members enum-set)])
(let loop ([i (fx1- (vector-length index->sym))]
[lst '()])
(if (fx< i 0)
lst
(loop (fx1- i)
(if (logbit? i members)
(cons (vector-ref index->sym i) lst)
lst))))))))
(record-writer enum-parent-rtd (lambda (x p wr) (display "#<enum-set>" p)))
;;;;;;;;
;; Constructor
(let ()
;; Takes lst and assigns indexes to each element of lst
;; lst :: symbol-list
;; index :: fixnum
;; symbol->index :: hashtable from symbols to fixnum
;; rev-lst :: symbol-list (stored in reverse order)
;;
;; Result :: (values fixnum (vector of symbols))
(define (make-symbol->index lst index symbol->index rev-lst)
(cond
[(null? lst)
(let ([index->symbol (make-vector index)])
(let loop ([i (fx1- index)]
[rev-lst rev-lst])
(unless (null? rev-lst) ;; or (< i 0)
(vector-set! index->symbol i (car rev-lst))
(loop (fx1- i) (cdr rev-lst))))
(values index index->symbol))]
[(symbol-hashtable-contains? symbol->index (car lst))
(make-symbol->index (cdr lst) index symbol->index rev-lst)]
[else
(symbol-hashtable-set! symbol->index (car lst) index)
(make-symbol->index (cdr lst) (fx1+ index) symbol->index (cons (car lst) rev-lst))]))
(set! make-enumeration
(lambda (symbol-list)
(assert-symbol-list 'make-enumeration symbol-list)
(let ([sym->index (make-hashtable symbol-hash eq?)])
(let-values ([(index index->sym) (make-symbol->index symbol-list 0 sym->index '())])
(let ([this-enum-rtd
($make-record-type
enum-base-rtd enum-parent-rtd "enum-type"
'() ; no fields to add
#t ; sealed
#f ; not opaque
sym->index
index->sym)])
(make-enum-set this-enum-rtd members-universe)))))))
;;;;;;;;;
;; Misc functions
(set! $enum-set-members get-members)
(set! enum-set-universe
(lambda (enum-set)
(make-enum-set (enum-set-rtd 'enum-set-universe enum-set) -1)))
(set! enum-set-indexer
(lambda (enum-set)
(let ([sym->index (get-sym->index (enum-set-rtd 'enum-set-indexer enum-set))])
(lambda (x)
(assert-symbol 'enum-set-indexer x)
(symbol-hashtable-ref sym->index x #f)))))
(set! enum-set-constructor
(lambda (enum-set)
(let ([rtd (enum-set-rtd 'enum-set-constructor enum-set)])
(lambda (symbol-list)
(assert-symbol-list 'enum-set-constructor symbol-list)
(rtd&list->enum-set 'enum-set-constructor rtd symbol-list)))))
(set! enum-set->list
(lambda (enum-set)
($enum-set->list 'enum-set->list enum-set)))
;;;;;;;;;
;; Predicates
(set! enum-set?
(lambda (enum-set)
(and (record? enum-set)
(let ([rtd (record-rtd enum-set)])
(eq? (record-rtd rtd) enum-base-rtd)))))
(let ()
(define (enum-set-subset-aux? enum-set1 enum-set2 rtd1 rtd2)
(let ([index->sym1 (get-index->sym rtd1)]
[members1 (get-members enum-set1)]
[sym->index2 (get-sym->index rtd2)]
[members2 (get-members enum-set2)])
(let loop ([index1 0])
(or (fx= index1 (vector-length index->sym1))
(let ([index2 (symbol-hashtable-ref
sym->index2
(vector-ref index->sym1 index1) #f)])
(and index2
(or (not (logbit? index1 members1))
(logbit? index2 members2))
(loop (fx1+ index1))))))))
(set! enum-set-member?
(lambda (symbol enum-set)
(assert-symbol 'enum-set-member? symbol)
(let ([sym->index (get-sym->index
(enum-set-rtd 'enum-set-member? enum-set))])
(let ([index (symbol-hashtable-ref sym->index symbol #f)])
(and index
(logbit? index (get-members enum-set)))))))
(set! enum-set-subset?
(lambda (enum-set1 enum-set2)
(let ([rtd1 (enum-set-rtd 'enum-set-subset? enum-set1)]
[rtd2 (enum-set-rtd 'enum-set-subset? enum-set2)])
(if (eq? rtd1 rtd2)
(let ([members2 (get-members enum-set2)])
(= members2 (logor (get-members enum-set1) members2)))
(enum-set-subset-aux? enum-set1 enum-set2 rtd1 rtd2)))))
(set! enum-set=?
(lambda (enum-set1 enum-set2)
(let ([rtd1 (enum-set-rtd 'enum-set=? enum-set1)]
[rtd2 (enum-set-rtd 'enum-set=? enum-set2)])
(if (eq? rtd1 rtd2)
(= (get-members enum-set1) (get-members enum-set2))
(and (enum-set-subset-aux? enum-set1 enum-set2 rtd1 rtd2)
(enum-set-subset-aux? enum-set2 enum-set1 rtd2 rtd1))))))
)
;;;;;;;;
;; Set-like functions
(let ()
(define-syntax enum-bin-op
(syntax-rules ()
[(_ name (members1 members2) members-expr)
(set! name
(lambda (enum-set1 enum-set2)
(let ([rtd1 (enum-set-rtd 'name enum-set1)]
[rtd2 (enum-set-rtd 'name enum-set2)])
(unless (eq? rtd1 rtd2)
($oops 'name "~s and ~s have different enumeration types"
enum-set1 enum-set2))
(make-enum-set rtd1 (let ([members1 (get-members enum-set1)]
[members2 (get-members enum-set2)])
members-expr)))))]))
(enum-bin-op enum-set-union (members1 members2) (logor members1 members2))
(enum-bin-op enum-set-intersection (members1 members2) (logand members1 members2))
(enum-bin-op enum-set-difference (members1 members2) (logand members1 (lognot members2)))
)
;;;;;;;;
;; Other functions
(set! enum-set-complement
(lambda (enum-set)
(let ([rtd (enum-set-rtd 'enum-set-complement enum-set)])
(make-enum-set rtd (lognot (get-members enum-set))))))
(set! enum-set-projection
(lambda (enum-set1 enum-set2)
(rtd&list->enum-set #f
(enum-set-rtd 'enum-set-projection enum-set2)
($enum-set->list 'enum-set-projection enum-set1))))
)