struct → dict

main
Matthew Butterick 5 years ago
parent 077b227f03
commit 3d15b505f4

@ -25,9 +25,9 @@ https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee
mheq)
(define (parse-fields port mheq fields-arg)
(define fields (if (x:struct? fields-arg) (get-field fields fields-arg) fields-arg))
(define fields (if (x:dict? fields-arg) (get-field fields fields-arg) fields-arg))
(unless (assocs? fields)
(raise-argument-error 'x:struct-parse-fields "assocs" fields))
(raise-argument-error 'x:dict-parse-fields "assocs" fields))
(for ([(key type) (in-dict fields)])
(define val (match type
[(? procedure? proc) (proc mheq)]
@ -37,7 +37,7 @@ https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee
(hash-set! mheq x:current-offset-key (- (pos port) (hash-ref mheq x:start-offset-key))))
mheq)
(define x:struct%
(define x:dict%
(class x:base%
(super-new)
(init-field [(@fields fields)])
@ -54,10 +54,10 @@ https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee
(define/augride (x:encode field-data port [parent-arg #f])
(unless (dict? field-data)
(raise-result-error 'x:struct-encode "dict" field-data))
(raise-result-error 'x:dict-encode "dict" field-data))
;; check keys, because `size` also relies on keys being valid
(unless (andmap (λ (field-key) (memq field-key (dict-keys field-data))) (dict-keys @fields))
(raise-argument-error 'x:struct-encode
(raise-argument-error 'x:dict-encode
(format "dict that contains superset of xstruct keys: ~a"
(dict-keys @fields)) (dict-keys field-data)))
(define parent (mhasheq x:pointers-key null
@ -82,22 +82,22 @@ https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee
(define pointers-size (if include-pointers (dict-ref parent x:pointer-size-key) 0))
(+ fields-size pointers-size))))
(define (x:struct? x) (is-a? x x:struct%))
(define (x:dict? x) (is-a? x x:dict%))
(define/contract (x:struct #:pre-encode [pre-proc #f]
(define/contract (x:dict #:pre-encode [pre-proc #f]
#:post-decode [post-proc #f]
#:base-class [base-class x:struct%]
#:base-class [base-class x:dict%]
. dicts)
(()
(#:pre-encode (or/c (any/c . -> . any/c) #false)
#:post-decode (or/c (any/c . -> . any/c) #false)
#:base-class (λ (c) (subclass? c x:struct%)))
#:base-class (λ (c) (subclass? c x:dict%)))
#:rest (listof any/c)
. ->* .
x:struct?)
x:dict?)
(define args (flatten dicts))
(unless (even? (length args))
(raise-argument-error 'x:struct "equal number of keys and values" dicts))
(raise-argument-error 'x:dict "equal number of keys and values" dicts))
(define fields (for/list ([kv (in-slice 2 args)])
(unless (symbol? (car kv))
(raise-argument-error '+xstruct "symbol" (car kv)))
@ -107,7 +107,7 @@ https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee
(module+ test
(require rackunit "number.rkt" "base.rkt")
(define (random-pick xs) (list-ref xs (random (length xs))))
(check-exn exn:fail:contract? (λ () (x:struct 42)))
(check-exn exn:fail:contract? (λ () (x:dict 42)))
(for ([i (in-range 20)])
;; make random structs and make sure we can round trip
(define field-types
@ -116,8 +116,13 @@ https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee
(define size-num-types
(for/sum ([num-type (in-list field-types)])
(size num-type)))
(define xs (x:struct (for/list ([num-type (in-list field-types)])
(define xs (x:dict (for/list ([num-type (in-list field-types)])
(cons (gensym) num-type))))
(define bs (apply bytes (for/list ([i (in-range size-num-types)])
(random 256))))
(check-equal? (encode xs (decode xs bs) #f) bs)))
(check-equal? (encode xs (decode xs bs) #f) bs)))
;; bw compat
(define x:struct% x:dict%)
(define x:struct? x:dict?)
(define x:struct x:dict)

@ -6,6 +6,7 @@
(r+p "bitfield.rkt"
"bytes.rkt"
"dict.rkt"
"enum.rkt"
"base.rkt"
"list.rkt"
@ -15,8 +16,7 @@
"reserved.rkt"
"string.rkt"
"stream.rkt"
"struct.rkt"
"symbol.rkt"
"vector.rkt"
"versioned-struct.rkt"
"versioned-dict.rkt"
"util.rkt")

@ -21,7 +21,7 @@ OK, just a few of you, in the back. You're free to go.
As for everyone else: Xenomorph eases the pain of working with binary formats. Instead of laboriously counting bytes —
@itemlist[#:style 'ordered
@item{You describe a binary format declaratively by using smaller ingredients — e.g., integers, strings, lists, pointers, structs, and perhaps other nested encodings. This is known as a @deftech{xenomorphic object}.}
@item{You describe a binary format declaratively by using smaller ingredients — e.g., integers, strings, lists, pointers, dicts, and perhaps other nested encodings. This is known as a @deftech{xenomorphic object}.}
@item{This xenomorphic object can then be used as a binary encoder, allowing you to convert Racket values to binary and write them out to a file.}
@ -823,13 +823,13 @@ Generate an instance of @racket[x:vector%] (or a subclass of @racket[x:vector%])
}
@subsection{Structs}
@subsection{Dicts}
@defmodule[xenomorph/struct]
@defmodule[xenomorph/dict]
@defclass[x:struct% x:base% ()]{
Base class for struct formats. Use @racket[x:struct] to conveniently instantiate new struct formats.
@defclass[x:dict% x:base% ()]{
Base class for struct formats. Use @racket[x:dict] to conveniently instantiate new struct formats.
@defconstructor[
([fields dict?])]{
@ -859,22 +859,22 @@ Take the keys and values in @racket[kvs] and encode them as a @tech{byte string}
}
@defproc[
(x:struct?
(x:dict?
[x any/c])
boolean?]{
Whether @racket[x] is an object of type @racket[x:struct%].
Whether @racket[x] is an object of type @racket[x:dict%].
}
@defproc[
(x:struct
(x:dict
[#: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:struct%)) x:struct%]
[#:base-class base-class (λ (c) (subclass? c x:dict%)) x:dict%]
[dict (listof (pairof symbol? xenomorphic?))] ...
[key symbol?] [val-type xenomorphic?] ... ...
)
x:struct?]{
Generate an instance of @racket[x:struct%] (or a subclass of @racket[x:struct%]) with certain optional attributes.
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}.
@ -885,9 +885,9 @@ The rest arguments determine the keys and value types of the struct. These argum
@subsection{Versioned structs}
@subsection{Versioned dicts}
@defmodule[xenomorph/versioned-struct]
@defmodule[xenomorph/versioned-dict]
@subsection{Pointers}

@ -2,7 +2,7 @@
(require rackunit racket/dict
racket/class
"../base.rkt"
"../struct.rkt"
"../dict.rkt"
"../string.rkt"
"../pointer.rkt"
"../number.rkt"
@ -18,21 +18,21 @@ https://github.com/mbutterick/restructure/blob/master/test/Struct.coffee
"struct: decode into an object"
(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")])
(check-equal?
(decode (x:struct 'name (x:string #:length uint8) 'age uint8))
(decode (x:dict 'name (x:string #:length uint8) 'age uint8))
(mhasheq 'name "roxyb" 'age 21))))
(test-case
"struct: decode nested struct into an object"
(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15\x05roxyb\x15")])
(check-equal?
(decode (x:struct 'name (x:string #:length uint8) 'age uint8
'nested (x:struct 'name (x:string #:length uint8) 'age uint8)))
(decode (x:dict 'name (x:string #:length uint8) 'age uint8
'nested (x:dict 'name (x:string #:length uint8) 'age uint8)))
(mhasheq 'name "roxyb" 'age 21 'nested (mhasheq 'name "roxyb" 'age 21)))))
(test-case
"struct: decode with process hook"
(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")])
(define struct (x:struct #:post-decode (λ (o) (hash-set! o 'canDrink (>= (hash-ref o 'age) 21)) o)
(define struct (x:dict #:post-decode (λ (o) (hash-set! o 'canDrink (>= (hash-ref o 'age) 21)) o)
'name (x:string #:length uint8) 'age uint8))
(check-equal? (decode struct)
(mhasheq 'name "roxyb" 'age 32 'canDrink #t))))
@ -40,40 +40,40 @@ https://github.com/mbutterick/restructure/blob/master/test/Struct.coffee
(test-case
"struct: decode supports function keys"
(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")])
(define struct (x:struct 'name (x:string #:length uint8) 'age uint8 'canDrink (λ (o) (>= (hash-ref o 'age) 21))))
(define struct (x:dict 'name (x:string #:length uint8) 'age uint8 'canDrink (λ (o) (>= (hash-ref o 'age) 21))))
(check-equal? (decode struct)
(mhasheq 'name "roxyb" 'age 32 'canDrink #t))))
(test-case
"struct: compute the correct size"
(check-equal? (size (x:struct 'name (x:string #:length uint8) 'age uint8)
(check-equal? (size (x:dict 'name (x:string #:length uint8) 'age uint8)
(hasheq 'name "roxyb" 'age 32)) 7))
(test-case
"struct: compute the correct size with pointers"
(check-equal? (size (x:struct 'name (x:string #:length uint8)
(check-equal? (size (x:dict 'name (x:string #:length uint8)
'age uint8
'ptr (x:pointer #:type (x:string #:length uint8)))
(mhash 'name "roxyb" 'age 21 'ptr "hello")) 14))
(test-case
"struct: get the correct size when no value is given"
(check-equal? (size (x:struct 'name (x:string 4) 'age uint8)) 5))
(check-equal? (size (x:dict 'name (x:string 4) 'age uint8)) 5))
(test-case
"struct: throw when getting non-fixed length size and no value is given"
(check-exn exn:fail:contract? (λ () (size (x:struct 'name (x:string #:length uint8) 'age uint8)))))
(check-exn exn:fail:contract? (λ () (size (x:dict 'name (x:string #:length uint8) 'age uint8)))))
(test-case
"struct: encode objects to buffers"
(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")])
(check-equal? (decode (x:struct 'name (x:string #:length uint8) 'age uint8))
(check-equal? (decode (x:dict 'name (x:string #:length uint8) 'age uint8))
(mhasheq 'name "roxyb" 'age 21))))
(test-case
"struct: support pre-encode hook"
(parameterize ([current-output-port (open-output-bytes)])
(define struct (x:struct #:pre-encode (λ (val)
(define struct (x:dict #:pre-encode (λ (val)
(hash-set! val 'nameLength (string-length (hash-ref val 'name))) val)
'nameLength uint8
'name (x:string 'nameLength)
@ -84,7 +84,7 @@ https://github.com/mbutterick/restructure/blob/master/test/Struct.coffee
(test-case
"struct: encode pointer data after structure"
(parameterize ([current-output-port (open-output-bytes)])
(define struct (x:struct 'name (x:string #:length uint8)
(define struct (x:dict 'name (x:string #:length uint8)
'age uint8
'ptr (x:pointer #:type (x:string #:length uint8))))
(encode struct (hasheq 'name "roxyb" 'age 21 'ptr "hello"))

@ -2,7 +2,7 @@
(require rackunit
racket/class
"../list.rkt"
"../struct.rkt"
"../dict.rkt"
"../number.rkt"
"../pointer.rkt"
"../base.rkt"
@ -21,7 +21,7 @@ https://github.com/mbutterick/restructure/blob/master/test/Array.coffee
(test-case
"list: decode nested"
(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))])
(check-equal? (decode (x:list #:type (x:struct 'foo uint8) #:length 4))
(check-equal? (decode (x:list #:type (x:dict 'foo uint8) #:length 4))
(list (mhasheq 'foo 1)
(mhasheq 'foo 2)
(mhasheq 'foo 3)

@ -2,6 +2,7 @@
(require "bitfield-test.rkt"
"bytes-test.rkt"
"dict-test.rkt"
"enum-test.rkt"
"list-test.rkt"
"number-test.rkt"
@ -11,6 +12,5 @@
"stream-test.rkt"
"string-test.rkt"
"symbol-test.rkt"
"struct-test.rkt"
"vector-test.rkt"
"versioned-struct-test.rkt")
"versioned-dict-test.rkt")

@ -5,7 +5,7 @@
"../base.rkt"
"../pointer.rkt"
"../number.rkt"
"../struct.rkt"
"../dict.rkt"
"../base.rkt"
racket/promise
sugar/unstable/dict)
@ -54,7 +54,7 @@ https://github.com/mbutterick/restructure/blob/master/test/Pointer.coffee
(test-case
"pointer: decode should support decoding pointers lazily"
(parameterize ([current-input-port (open-input-bytes (bytes 1 53))])
(define res (decode (x:struct 'ptr (x:pointer #:lazy #t))))
(define res (decode (x:dict 'ptr (x:pointer #:lazy #t))))
(check-true (promise? (hash-ref res 'ptr)))
(check-equal? (force (hash-ref res 'ptr)) 53)))

@ -3,7 +3,7 @@
racket/class
racket/vector
"../vector.rkt"
"../struct.rkt"
"../dict.rkt"
"../number.rkt"
"../pointer.rkt"
"../base.rkt"
@ -22,7 +22,7 @@ https://github.com/mbutterick/restructure/blob/master/test/Array.coffee
(test-case
"vector: decode nested"
(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))])
(check-equal? (decode (x:vector #:type (x:struct 'foo uint8) #:length 4))
(check-equal? (decode (x:vector #:type (x:dict 'foo uint8) #:length 4))
(vector (mhasheq 'foo 1)
(mhasheq 'foo 2)
(mhasheq 'foo 3)

@ -6,9 +6,9 @@
"../number.rkt"
"../string.rkt"
"../pointer.rkt"
"../struct.rkt"
"../dict.rkt"
"../base.rkt"
"../versioned-struct.rkt")
"../versioned-dict.rkt")
#|
approximates
@ -16,12 +16,12 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
|#
(test-case
"versioned struct: decode should get version from number type"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: decode should get version from number type"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))])
(parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")])
@ -30,41 +30,41 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
(check-equal? (decode vstruct) (mhasheq 'name "roxyb 🤘" 'age 21 x:version-key 1 'gender 0)))))
(test-case
"versioned struct: decode should get version from number type, nested"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: decode should get version from number type, nested"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8
'nested (x:struct 'foo uint8))
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
'nested (x:dict 'foo uint8))
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8
'nested (x:struct 'foo uint8))))])
'nested (x:dict 'foo uint8))))])
(parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15\x2a")])
(check-equal? (decode vstruct) (mhasheq 'name "roxyb" 'age 21 'nested (mhasheq 'foo 42) x:version-key 0)))
(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x0aroxyb 🤘\x15\x00\x2a"))])
(check-equal? (decode vstruct) (mhasheq 'name "roxyb 🤘" 'age 21 x:version-key 1 'gender 0 'nested (mhasheq 'foo 42))))))
(test-case
"versioned struct: decode should throw for unknown version"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: decode should throw for unknown version"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))])
(parameterize ([current-input-port (open-input-bytes #"\x05\x05roxyb\x15")])
(check-exn exn:fail:contract? (λ () (decode vstruct))))))
(test-case
"versioned struct: decode should support common header block"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: decode should support common header block"
(let ([vstruct (x:versioned-dict uint8
(dictify
'header (dictify 'age uint8
'alive uint8)
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii))
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'gender uint8)))])
(parameterize ([current-input-port (open-input-bytes #"\x00\x15\x01\x05roxyb")])
(check-equal? (decode vstruct) (mhasheq 'name "roxyb"
@ -79,12 +79,12 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
'gender 0)))))
(test-case
"versioned struct: decode should support parent version key"
(let ([vstruct (x:versioned-struct x:version-key
"versioned dict: decode should support parent version key"
(let ([vstruct (x:versioned-dict x:version-key
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))])
(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")])
@ -95,12 +95,12 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
(mhasheq 'name "roxyb 🤘" 'age 21 x:version-key 1 'gender 0)))))
(test-case
"versioned struct: decode should support sub versioned structs"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: decode should support sub versioned dicts"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:versioned-struct uint8
1 (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string uint8))
1 (dictify 'name (x:string uint8)
@ -116,13 +116,13 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
(mhasheq 'name "ice cream" 'isDessert 1 x:version-key 1)))))
(test-case
"versioned struct: decode should support process hook"
(let ([vstruct (x:versioned-struct #:post-decode (λ (val) (hash-set! val 'processed "true") val)
"versioned dict: decode should support process hook"
(let ([vstruct (x:versioned-dict #:post-decode (λ (val) (hash-set! val 'processed "true") val)
uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))])
(parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")])
@ -130,12 +130,12 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
(mhasheq 'name "roxyb" 'processed "true" 'age 21 x:version-key 0)))))
(test-case
"versioned struct: size should compute the correct size"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: size should compute the correct size"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))])
(check-equal? (size vstruct (mhasheq 'name "roxyb"
@ -147,35 +147,35 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
x:version-key 1)) 14)))
(test-case
"versioned struct: size should throw for unknown version"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: size should throw for unknown version"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))])
(check-exn exn:fail:contract? (λ () (size vstruct (mhasheq 'name "roxyb" 'age 21 x:version-key 5))))))
(test-case
"versioned struct: size should support common header block"
(let ([struct (x:versioned-struct uint8
"versioned dict: size should support common header block"
(let ([struct (x:versioned-dict uint8
(dictify
'header (dictify 'age uint8
'alive uint8)
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii))
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'gender uint8)))])
(check-equal? (size struct (mhasheq 'name "roxyb" 'age 21 'alive 1 x:version-key 0)) 9)
(check-equal? (size struct (mhasheq 'name "roxyb 🤘" 'gender 0 'age 21 'alive 1 x:version-key 1)) 15)))
(test-case
"versioned struct: size should compute the correct size with pointers"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: size should compute the correct size with pointers"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'ptr (x:pointer #:offset-type uint8
#:type (x:string uint8)))))])
@ -185,23 +185,23 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
'ptr "hello")) 15)))
(test-case
"versioned struct: size should throw if no value is given"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: size should throw if no value is given"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))])
(check-exn exn:fail:contract? (λ () (size vstruct)))))
(test-case
"versioned struct: encode should encode objects to buffers"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: encode should encode objects to buffers"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))]
[op (open-output-bytes)])
@ -210,25 +210,25 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
(check-equal? (get-output-bytes op) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00"))))
(test-case
"versioned struct: encode should throw for unknown version"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: encode should throw for unknown version"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))]
[op (open-output-bytes)])
(check-exn exn:fail:contract? (λ () (encode vstruct op (mhasheq 'name "roxyb" 'age 21 x:version-key 5))))))
(test-case
"versioned struct: encode should support common header block"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: encode should support common header block"
(let ([vstruct (x:versioned-dict uint8
(dictify
'header (dictify 'age uint8
'alive uint8)
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii))
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'gender uint8)))]
[op (open-output-bytes)])
(encode vstruct (mhasheq 'name "roxyb" 'age 21 'alive 1 x:version-key 0) op)
@ -236,12 +236,12 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
(check-equal? (get-output-bytes op) (string->bytes/utf-8 "\x00\x15\x01\x05roxyb\x01\x15\x01\x0aroxyb 🤘\x00"))))
(test-case
"versioned struct: encode should encode pointer data after structure"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: encode should encode pointer data after structure"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'ptr (x:pointer #:offset-type uint8
#:type (x:string uint8)))))]
@ -251,12 +251,12 @@ https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffe
(check-equal? (get-output-bytes op) (string->bytes/utf-8 "\x01\x05roxyb\x15\x09\x05hello"))))
#;(test-case
"versioned struct: encode should support preEncode hook"
(let ([vstruct (x:versioned-struct uint8
"versioned dict: encode should support preEncode hook"
(let ([vstruct (x:versioned-dict uint8
(dictify
0 (dictify 'name (x:string #:length uint8 #:encoding 'ascii)
'age uint8)
1 (x:struct 'name (x:string #:length uint8 #:encoding 'utf8)
1 (x:dict 'name (x:string #:length uint8 #:encoding 'utf8)
'age uint8
'gender uint8)))]
[op (open-output-bytes)])

@ -1,5 +1,5 @@
#lang debug racket/base
(require "base.rkt" "struct.rkt"
(require "base.rkt" "dict.rkt"
racket/dict
racket/match
racket/class
@ -11,25 +11,25 @@ approximates
https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee
|#
(define x:versioned-struct%
(class x:struct%
(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))
(raise-argument-error 'x:versioned-struct "integer, procedure, symbol, or xenomorphic" @type))
(unless (and (dict? @versions) (andmap (λ (v) (or (dict? v) (x:struct? v))) (dict-values @versions)))
(raise-argument-error 'x:versioned-struct "dict of dicts or structish" @versions))
(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))
(define (select-field-set val)
(define version-key
(or (dict-ref val x:version-key #f)
(raise-argument-error 'x:versioned-struct-encode "value for version key" x:version-key)))
(raise-argument-error 'x:versioned-dict-encode "value for version key" x:version-key)))
(define field-object
(or (dict-ref @versions version-key #f)
(raise-argument-error 'x:versioned-struct-encode (format "valid field version: ~v" (dict-keys @versions)) version-key)))
(if (x:struct? field-object) (get-field fields field-object) field-object))
(raise-argument-error 'x:versioned-dict-encode (format "valid field version: ~v" (dict-keys @versions)) version-key)))
(if (x:dict? field-object) (get-field fields field-object) field-object))
(define/override (x:decode port parent [length 0])
(define res (setup-private-fields port parent length))
@ -38,7 +38,7 @@ https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee
[(? symbol? key) #:when parent (dict-ref parent key)]
[(? procedure? proc) #:when parent (proc parent)]
[(or (? symbol?) (? procedure?))
(raise-argument-error 'x:versioned-struct-decode "valid parent" parent)]
(raise-argument-error 'x:versioned-dict-decode "valid parent" parent)]
[_ (send @type x:decode port parent)]))
(dict-set! res x:version-key which-version)
@ -47,16 +47,16 @@ https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee
[header-val (parse-fields port res header-val)])
(match (dict-ref @versions which-version #f)
[#false (raise-argument-error 'x:versioned-struct-decode
[#false (raise-argument-error 'x:versioned-dict-decode
(format "valid field version: ~v" (dict-keys @versions)) which-version)]
[(? x:versioned-struct? vs) (send vs x:decode port parent)]
[(? x:versioned-dict? vs) (send vs x:decode port parent)]
[field-object (parse-fields port res field-object)]))
(define/override (pre-encode val) val)
(define/override (x:encode field-data port [parent-arg #f])
(unless (dict? field-data)
(raise-argument-error 'x:versioned-struct-encode "dict" field-data))
(raise-argument-error 'x:versioned-dict-encode "dict" field-data))
(define parent (mhasheq x:pointers-key null
x:start-offset-key (pos port)
x:parent-key parent-arg
@ -70,7 +70,7 @@ https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee
(define fields (select-field-set field-data))
(unless (andmap (λ (key) (member key (dict-keys field-data))) (dict-keys fields))
(raise-argument-error 'x:versioned-struct-encode (format "hash that contains superset of xversioned-struct keys: ~a" (dict-keys fields)) (dict-keys field-data)))
(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))
(let loop ([i 0])
@ -82,7 +82,7 @@ https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee
(define/override (x:size [val-arg #f] [parent-arg #f] [include-pointers #t])
(unless val-arg
(raise-argument-error 'x:versioned-struct-size "value" val-arg))
(raise-argument-error 'x:versioned-dict-size "value" val-arg))
(define val (pre-encode val-arg))
(define parent (mhasheq x:parent-key parent-arg
x:val-key val
@ -101,15 +101,20 @@ https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee
(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-struct? x) (is-a? x x:versioned-struct%))
(define (x:versioned-dict? x) (is-a? x x:versioned-dict%))
(define (x:versioned-struct type
(define (x:versioned-dict type
[versions (dictify)]
#:pre-encode [pre-proc #f]
#:post-decode [post-proc #f]
#:base-class [base-class x:versioned-struct%])
#:base-class [base-class x:versioned-dict%])
(new (generate-subclass base-class pre-proc post-proc)
[type type]
[versions versions]
[fields #f]))
;; bw compat
(define x:versioned-struct% x:versioned-dict%)
(define x:versioned-struct? x:versioned-dict?)
(define x:versioned-struct x:versioned-dict)
Loading…
Cancel
Save