Skip to content
This repository has been archived by the owner on Jun 30, 2022. It is now read-only.

Commit

Permalink
struct → dict
Browse files Browse the repository at this point in the history
  • Loading branch information
mbutterick committed Apr 29, 2019
1 parent ea1741b commit d597a65
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 123 deletions.
33 changes: 19 additions & 14 deletions xenomorph/struct.rkt → xenomorph/dict.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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)])
Expand All @@ -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
Expand All @@ -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)))
Expand All @@ -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
Expand All @@ -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)
4 changes: 2 additions & 2 deletions xenomorph/main.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

(r+p "bitfield.rkt"
"bytes.rkt"
"dict.rkt"
"enum.rkt"
"base.rkt"
"list.rkt"
Expand All @@ -15,8 +16,7 @@
"reserved.rkt"
"string.rkt"
"stream.rkt"
"struct.rkt"
"symbol.rkt"
"vector.rkt"
"versioned-struct.rkt"
"versioned-dict.rkt"
"util.rkt")
26 changes: 13 additions & 13 deletions xenomorph/scribblings/xenomorph.scrbl
Original file line number Diff line number Diff line change
Expand Up @@ -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.}

Expand Down Expand Up @@ -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?])]{
Expand Down Expand Up @@ -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}.
Expand All @@ -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}
Expand Down
26 changes: 13 additions & 13 deletions xenomorph/test/struct-test.rkt → xenomorph/test/dict-test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
(require rackunit racket/dict
racket/class
"../base.rkt"
"../struct.rkt"
"../dict.rkt"
"../string.rkt"
"../pointer.rkt"
"../number.rkt"
Expand All @@ -18,62 +18,62 @@ 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))))

(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)
Expand All @@ -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"))
Expand Down
4 changes: 2 additions & 2 deletions xenomorph/test/list-test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
(require rackunit
racket/class
"../list.rkt"
"../struct.rkt"
"../dict.rkt"
"../number.rkt"
"../pointer.rkt"
"../base.rkt"
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions xenomorph/test/main.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

(require "bitfield-test.rkt"
"bytes-test.rkt"
"dict-test.rkt"
"enum-test.rkt"
"list-test.rkt"
"number-test.rkt"
Expand All @@ -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")
4 changes: 2 additions & 2 deletions xenomorph/test/pointer-test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"../base.rkt"
"../pointer.rkt"
"../number.rkt"
"../struct.rkt"
"../dict.rkt"
"../base.rkt"
racket/promise
sugar/unstable/dict)
Expand Down Expand Up @@ -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)))

Expand Down
4 changes: 2 additions & 2 deletions xenomorph/test/vector-test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
racket/class
racket/vector
"../vector.rkt"
"../struct.rkt"
"../dict.rkt"
"../number.rkt"
"../pointer.rkt"
"../base.rkt"
Expand All @@ -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)
Expand Down
Loading

0 comments on commit d597a65

Please sign in to comment.