diff --git a/xenomorph/xenomorph/scribblings/xenomorph.scrbl b/xenomorph/xenomorph/scribblings/xenomorph.scrbl index 30f3ccd5..45c2a6fd 100644 --- a/xenomorph/xenomorph/scribblings/xenomorph.scrbl +++ b/xenomorph/xenomorph/scribblings/xenomorph.scrbl @@ -1,6 +1,6 @@ #lang scribble/manual -@(require scribble/eval (for-label racket/base racket/class racket/file xenomorph)) +@(require scribble/eval (for-label racket/base racket/class racket/file racket/dict xenomorph)) @(define my-eval (make-base-eval)) @(my-eval `(require xenomorph)) @@ -456,7 +456,7 @@ Whether @racket[x] is an object of type @racket[x:fixed%]. [size-arg (or/c exact-positive-integer? #false) #false] [#:size size-kw exact-positive-integer? 2] [#:endian endian endian-value? system-endian] -[#:fracbits fracbits (or/c exact-positive-integer? #false) #false] +[#:fracbits fracbits (or/c exact-positive-integer? #false) (/ (* _size 8) 2)] [#:pre-encode pre-encode-proc (or/c (any/c . -> . any/c) #false) #false] [#:post-decode post-decode-proc (or/c (any/c . -> . any/c) #false) #false] [#:base-class base-class (λ (c) (subclass? c x:fixed%)) x:fixed%] @@ -464,7 +464,7 @@ Whether @racket[x] is an object of type @racket[x:fixed%]. x:int?]{ Generate an instance of @racket[x:fixed%] (or a subclass of @racket[x:fixed%]) with certain optional attributes. -@racket[size-arg] or @racket[size-kw] (whichever is provided, though @racket[size-arg] takes precedence) controls the encoded size. +@racket[size-arg] or @racket[size-kw] (whichever is provided, though @racket[size-arg] takes precedence) controls the encoded size. Defaults to @racket[2]. @racket[endian] controls the byte-ordering convention. @@ -546,7 +546,7 @@ Whether @racket[x] is an object of type @racket[x:string%]. [len-arg (or/c length-resolvable? #false) #false] [enc-arg (or/c procedure? supported-encoding? #false) #false] [#:length len-kw (or/c length-resolvable? #false) #false] -[#:encoding enc-kw (or/c procedure? supported-encoding? #false) #false] +[#:encoding enc-kw (or/c procedure? supported-encoding? #false) 'utf8] [#:pre-encode pre-encode-proc (or/c (any/c . -> . any/c) #false) #false] [#:post-decode post-decode-proc (or/c (any/c . -> . any/c) #false) #false] [#:base-class base-class (λ (c) (subclass? c x:string%)) x:string%] @@ -611,7 +611,7 @@ Whether @racket[x] is an object of type @racket[x:symbol%]. [len-arg (or/c length-resolvable? #false) #false] [enc-arg (or/c procedure? supported-encoding? #false) #false] [#:length len-kw (or/c length-resolvable? #false) #false] -[#:encoding enc-kw (or/c procedure? supported-encoding? #false) #false] +[#:encoding enc-kw (or/c procedure? supported-encoding? #false) 'utf8] [#:pre-encode pre-encode-proc (or/c (any/c . -> . any/c) #false) #false] [#:post-decode post-decode-proc (or/c (any/c . -> . any/c) #false) #false] [#:base-class base-class (λ (c) (subclass? c x:symbol%)) x:symbol%] @@ -827,13 +827,12 @@ Generate an instance of @racket[x:vector%] (or a subclass of @racket[x:vector%]) @defmodule[xenomorph/dict] - @defclass[x:dict% x:base% ()]{ -Base class for struct formats. Use @racket[x:dict] to conveniently instantiate new struct formats. +Base class for dict formats. Use @racket[x:dict] to conveniently instantiate new dict formats. @defconstructor[ ([fields dict?])]{ -Create class instance that represents a struct format with @racket[fields] as a dictionary holding the key–value pairs that define the struct format. Each key must be a @racket[symbol?] and each value must be a @racket[xenomorphic?] type. +Create class instance that represents a dict format with @racket[fields] as a dictionary holding the key–value pairs that define the dict format. Each key must be a @racket[symbol?] and each value must be a @racket[xenomorphic?] type. } @defmethod[ @@ -842,7 +841,7 @@ Create class instance that represents a struct format with @racket[fields] as a [input-port input-port?] [parent (or/c xenomorphic? #false)]) hash-eq?]{ -Returns a @tech{hash-eq?} whose keys are the same as the keys in @racket[_fields]. +Returns a @racket[hasheq] whose keys are the same as the keys in @racket[_fields]. } @@ -876,7 +875,7 @@ Whether @racket[x] is an object of type @racket[x:dict%]. x:dict?]{ Generate an instance of @racket[x:dict%] (or a subclass of @racket[x:dict%]) with certain optional attributes. -The rest arguments determine the keys and value types of the struct. These arguments can either be alternating keys and value-type arguments (similar to the calling pattern for @racket[hasheq]) or @tech{association lists}. +The rest arguments determine the keys and value types of the dict. These arguments can either be alternating keys and value-type arguments (similar to the calling pattern for @racket[hasheq]) or @tech{association lists}. @racket[pre-encode-proc] and @racket[post-decode-proc] control the pre-encoding and post-decodeing procedures, respectively. @@ -890,6 +889,81 @@ The rest arguments determine the keys and value types of the struct. These argum @defmodule[xenomorph/versioned-dict] +The versioned dict is a format derived from @racket[x:dict%] that contains multiple possible dict encodings. It also carries a version field to select among them. This version is stored with the encoded data, of course, so on decode, the correct version will be chosen. + + +@defproc[ +(version-type? +[x any/c]) +boolean?]{ +Whether @racket[x] can be used as the version type of a versioned dict. Valid types are @racket[integer?], @racket[procedure?], @racket[xenomorphic?], or @racket[symbol?]. +} + + +@defclass[x:versioned-dict% x:dict% ()]{ +Base class for versioned dict formats. Use @racket[x:versioned-dict] to conveniently instantiate new dict formats. + +@defconstructor[ +([type version-type?] +[versions dict?] +[fields #false])]{ +Create class instance that represents a versioned dict format with @racket[type] as the encoded type of the version value, and @racket[versions] as a dictionary holding the key–value pairs that define the versioned dict. Each key of @racket[versions] must be a value consistent with @racket[type], and each value must either be a @racket[dict?] or @racket[x:dict?]. +} + +@defmethod[ +#:mode extend +(x:decode +[input-port input-port?] +[parent (or/c xenomorphic? #false)]) +hash-eq?]{ +Returns a @racket[hasheq] whose keys are the same as the keys in @racket[_fields]. + +} + +@defmethod[ +#:mode extend +(x:encode +[kvs dict?] +[input-port input-port?] +[parent (or/c xenomorphic? #false)]) +bytes?]{ +Take the keys and values in @racket[kvs] and encode them as a @tech{byte string}. +} + +} + +@defproc[ +(x:versioned-dict? +[x any/c]) +boolean?]{ +Whether @racket[x] is an object of type @racket[x:versioned-dict%]. +} + +@defproc[ +(x:versioned-dict +[type-arg (or/c version-type? #false)] +[versions-arg (or/c dict? #false)] +[#:type type-kw (or/c version-type? #false)] +[#:versions versions-kw (or/c dict? #false)] +[#:pre-encode pre-encode-proc (or/c (any/c . -> . any/c) #false) #false] +[#:post-decode post-decode-proc (or/c (any/c . -> . any/c) #false) #false] +[#:base-class base-class (λ (c) (subclass? c x:versioned-dict%)) x:versioned-dict%] +) +x:versioned-dict?]{ +Generate an instance of @racket[x:versioned-dict%] (or a subclass of @racket[x:versioned-dict%]) with certain optional attributes. + +@racket[type-arg] or @racket[type-kw] (whichever is provided, though @racket[type-arg] takes precedence) determines the type of the version value that is used to select from among available dicts. + +@racket[versions-arg] or @racket[versions-kw] (whichever is provided, though @racket[versions-arg] takes precedence) is a dictionary holding the key–value pairs that define the versioned dict. Each key of @racket[versions] must be a value consistent with @racket[type], and each value must either be a @racket[dict?] or @racket[x:dict?]. + + + +@racket[pre-encode-proc] and @racket[post-decode-proc] control the pre-encoding and post-decodeing procedures, respectively. + +@racket[base-class] controls the class used for instantiation of the new object. +} + + @subsection{Pointers} @defmodule[xenomorph/pointer] diff --git a/xenomorph/xenomorph/versioned-dict.rkt b/xenomorph/xenomorph/versioned-dict.rkt index 22be1b55..3f741dfd 100644 --- a/xenomorph/xenomorph/versioned-dict.rkt +++ b/xenomorph/xenomorph/versioned-dict.rkt @@ -1,8 +1,10 @@ #lang debug racket/base -(require "base.rkt" "dict.rkt" +(require "base.rkt" + "dict.rkt" racket/dict racket/match racket/class + racket/contract sugar/unstable/dict) (provide (all-defined-out)) @@ -11,13 +13,16 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee |# +(define (version-type? x) + (for/or ([proc (list integer? procedure? xenomorphic? symbol?)]) + (proc x))) + (define x:versioned-dict% (class x:dict% (super-new) (init-field [(@type type)] [(@versions versions)]) - (unless (for/or ([proc (list integer? procedure? xenomorphic-type? symbol?)]) - (proc @type)) + (unless (version-type? @type) (raise-argument-error 'x:versioned-dict "integer, procedure, symbol, or xenomorphic" @type)) (unless (and (dict? @versions) (andmap (λ (v) (or (dict? v) (x:dict? v))) (dict-values @versions))) (raise-argument-error 'x:versioned-dict "dict of dicts or structish" @versions)) @@ -66,13 +71,13 @@ https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee (unless (or (symbol? @type) (procedure? @type)) (send @type x:encode (dict-ref field-data x:version-key #f) port parent)) (for ([(key type) (in-dict (dict-ref @versions 'header null))]) - (send type x:encode (dict-ref field-data key) port parent)) + (send type x:encode (dict-ref field-data key) port parent)) (define fields (select-field-set field-data)) (unless (andmap (λ (key) (member key (dict-keys field-data))) (dict-keys fields)) (raise-argument-error 'x:versioned-dict-encode (format "hash that contains superset of xversioned-dict keys: ~a" (dict-keys fields)) (dict-keys field-data))) (for ([(key type) (in-dict fields)]) - (send type x:encode (dict-ref field-data key) port parent)) + (send type x:encode (dict-ref field-data key) port parent)) (let loop ([i 0]) (when (< i (length (dict-ref parent x:pointers-key))) (define ptr (list-ref (dict-ref parent x:pointers-key) i)) @@ -94,20 +99,39 @@ https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee (define header-size (for/sum ([(key type) (in-dict (dict-ref @versions 'header null))]) - (send type x:size (and val (dict-ref val key)) parent))) + (send type x:size (and val (dict-ref val key)) parent))) (define fields-size (for/sum ([(key type) (in-dict (select-field-set val))]) - (send type x:size (and val (dict-ref val key)) parent))) + (send type x:size (and val (dict-ref val key)) parent))) (define pointer-size (if include-pointers (dict-ref parent x:pointer-size-key) 0)) (+ version-size header-size fields-size pointer-size)))) (define (x:versioned-dict? x) (is-a? x x:versioned-dict%)) -(define (x:versioned-dict type - [versions (dictify)] - #:pre-encode [pre-proc #f] - #:post-decode [post-proc #f] - #:base-class [base-class x:versioned-dict%]) +(define/contract (x:versioned-dict + [type-arg #false] + [versions-arg #false] + #:type [type-kw #false] + #:versions [versions-kw #false] + #:pre-encode [pre-proc #f] + #:post-decode [post-proc #f] + #:base-class [base-class x:versioned-dict%]) + (() + ((or/c version-type? #false) + (or/c dict? #false) + #:type (or/c version-type? #false) + #:versions (or/c dict? #false) + #:pre-encode (or/c (any/c . -> . any/c) #false) + #:post-decode (or/c (any/c . -> . any/c) #false) + #:base-class (λ (c) (subclass? c x:versioned-dict%))) + . ->* . + x:versioned-dict?) + (define type (or type-arg type-kw)) + (unless (version-type? type) + (raise-argument-error 'x:versioned-dict "version-type?" type)) + (define versions (or versions-arg versions-kw)) + (unless (dict? versions) + (raise-argument-error 'x:versioned-dict "dict" versions)) (new (generate-subclass base-class pre-proc post-proc) [type type] [versions versions]