diff --git a/xenomorph/xenomorph/array.rkt b/xenomorph/xenomorph/array.rkt index 2fd7d9d6..bc013def 100644 --- a/xenomorph/xenomorph/array.rkt +++ b/xenomorph/xenomorph/array.rkt @@ -1,12 +1,5 @@ -#lang racket/base -(require racket/class - sugar/unstable/class - sugar/unstable/dict - sugar/unstable/js - "private/generic.rkt" - "private/helper.rkt" - "number.rkt" - "utils.rkt") +#lang debug racket/base +(require racket/dict racket/sequence "helper.rkt" "number.rkt" "util.rkt" sugar/unstable/dict) (provide (all-defined-out)) #| @@ -14,81 +7,96 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Array.coffee |# -(define-subclass xenomorph-base% (ArrayT type [len #f] [length-type 'count]) - - (define/augride (decode port [parent #f]) - (define ctx (if (NumberT? len) - (mhasheq 'parent parent - '_startOffset (pos port) - '_currentOffset 0 - '_length len) - parent)) - - (define decoded-len (resolve-length len port parent)) +(define/post-decode (xarray-decode xa [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) + (define new-parent (if (xint? (xarray-base-len xa)) + (mhasheq 'parent parent + '_startOffset (pos port) + '_currentOffset 0 + '_length (xarray-base-len xa)) + parent)) + (define decoded-len (resolve-length (xarray-base-len xa) #:parent parent)) (cond - [(or (not decoded-len) (eq? length-type 'bytes)) + [(or (not decoded-len) (eq? (xarray-length-type xa) 'bytes)) (define end-pos (cond ;; decoded-len is byte length [decoded-len (+ (pos port) decoded-len)] ;; no decoded-len, but parent has length - [(and parent (not (zero? (· parent _length)))) (+ (· parent _startOffset) (· parent _length))] + [(and parent (not (zero? (dict-ref parent '_length)))) (+ (dict-ref parent '_startOffset) (dict-ref parent '_length))] ;; no decoded-len or parent, so consume whole stream [else +inf.0])) (for/list ([i (in-naturals)] - #:break (or (eof-object? (peek-byte port)) (= (pos port) end-pos))) - (send type decode port ctx))] + #:break (or (eof-object? (peek-byte)) (= (pos port) end-pos))) + (decode (xarray-base-type xa) #:parent new-parent))] ;; we have decoded-len, which is treated as count of items [else (for/list ([i (in-range decoded-len)]) - (send type decode port ctx))])) - + (decode (xarray-base-type xa) #:parent new-parent))]))) - (define/augride (size [val #f] [ctx #f]) - (when val (unless (countable? val) - (raise-argument-error 'Array:size "countable" val))) +(define/pre-encode (xarray-encode xa array [port-arg (current-output-port)] #:parent [parent #f]) + (unless (sequence? array) + (raise-argument-error 'xarray-encode "sequence" array)) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) + (define (encode-items parent) + ;; todo: should array with fixed length stop encoding after it reaches max? + ;; cf. xstring, which rejects input that is too big for fixed length. + (let* (#;[items (sequence->list array)] + #;[item-count (length items)] + #;[max-items (if (number? (xarray-len xa)) (xarray-len xa) item-count)]) + (for ([item array]) + (encode (xarray-base-type xa) item #:parent parent)))) (cond - [val (let-values ([(ctx len-size) (if (NumberT? len) - (values (mhasheq 'parent ctx) (send len size)) - (values ctx 0))]) - (+ len-size (for/sum ([item (in-list (countable->list val))]) - (send type size item ctx))))] - [else (let ([item-count (resolve-length len #f ctx)] - [item-size (send type size #f ctx)]) - (* item-size item-count))])) - + [(xint? (xarray-base-len xa)) + (let ([parent (mhash 'pointers null + 'startOffset (pos port) + 'parent parent)]) + (dict-set! parent 'pointerOffset (+ (pos port) (size xa array #:parent parent))) + (encode (xarray-base-len xa) (length array)) ; encode length at front + (encode-items parent) + (for ([ptr (in-list (dict-ref parent 'pointers))]) ; encode pointer data at end + (encode (dict-ref ptr 'type) (dict-ref ptr 'val))))] + [else (encode-items parent)]) + (unless port-arg (get-output-bytes port)))) - (define/augride (encode port array [parent #f]) - (when array (unless (countable? array) - (raise-argument-error 'Array:encode "list or countable" array))) +(define/finalize-size (xarray-size xa [val #f] #:parent [parent #f]) + (when val (unless (sequence? val) + (raise-argument-error 'xarray-size "sequence" val))) + (cond + [val (define-values (new-parent len-size) (if (xint? (xarray-base-len xa)) + (values (mhasheq 'parent parent) (size (xarray-base-len xa))) + (values parent 0))) + (define items-size (for/sum ([item val]) + (size (xarray-base-type xa) item #:parent new-parent))) + (+ items-size len-size)] + [else (define item-count (resolve-length (xarray-base-len xa) #f #:parent parent)) + (define item-size (size (xarray-base-type xa) #f #:parent parent)) + (* item-size item-count)])) - (define (encode-items ctx) - (let* ([items (countable->list array)] - [item-count (length items)] - [max-items (if (number? len) len item-count)]) - (for ([item (in-list items)]) - (send type encode port item ctx)))) +(struct xarray-base xbase (type len) #:transparent) +(struct xarray xarray-base (length-type) #:transparent + #:methods gen:xenomorphic + [(define decode xarray-decode) + (define encode xarray-encode) + (define size xarray-size)]) - (cond - [(NumberT? len) (define ctx (mhash 'pointers null - 'startOffset (pos port) - 'parent parent)) - (ref-set! ctx 'pointerOffset (+ (pos port) (size array ctx))) - (send len encode port (length array)) ; encode length at front - (encode-items ctx) - (for ([ptr (in-list (· ctx pointers))]) ; encode pointer data at end - (send (· ptr type) encode port (· ptr val)))] - [else (encode-items parent)]))) - -(define-syntax-rule (define-procedures (NEW ...) (OLD ...)) - (define-values (NEW ...) - (values (if (procedure? OLD) - (procedure-rename OLD 'NEW) - OLD) ...))) - -(define-procedures (Array Array? +Array) (ArrayT ArrayT? +ArrayT)) -(define-procedures (array% array? array) (ArrayT ArrayT? +ArrayT)) +(define (+xarray [type-arg #f] [len-arg #f] [length-type-arg 'count] + #:type [type-kwarg #f] #:length [len-kwarg #f] #:count-bytes [count-bytes? #f]) + (define type (or type-arg type-kwarg)) + (define len (or len-arg len-kwarg)) + (define length-type (if count-bytes? 'bytes length-type-arg)) + (unless (xenomorphic? type) + (raise-argument-error '+xarray "xenomorphic type" type)) + (unless (length-resolvable? len) + (raise-argument-error '+xarray "length-resolvable?" len)) + (unless (memq length-type '(bytes count)) + (raise-argument-error '+xarray "'bytes or 'count" length-type)) + (xarray type len length-type)) + -(test-module - (check-equal? (decode (+Array uint16be 3) #"ABCDEF") '(16706 17220 17734)) - (check-equal? (encode (+Array uint16be 3) '(16706 17220 17734) #f) #"ABCDEF") - (check-equal? (size (+Array uint16be) '(1 2 3)) 6) - (check-equal? (size (+Array doublebe) '(1 2 3 4 5)) 40)) +(module+ test + (require rackunit) + (check-equal? (decode (+xarray uint16be 3) #"ABCDEF") '(16706 17220 17734)) + (check-equal? (encode (+xarray uint16be 3) '(16706 17220 17734) #f) #"ABCDEF") + (check-equal? (size (+xarray uint16be) '(1 2 3)) 6) + (check-equal? (size (+xarray doublebe) '(1 2 3 4 5)) 40)) diff --git a/xenomorph/xenomorph/bitfield.rkt b/xenomorph/xenomorph/bitfield.rkt index 78c2b267..25b8ef94 100644 --- a/xenomorph/xenomorph/bitfield.rkt +++ b/xenomorph/xenomorph/bitfield.rkt @@ -1,10 +1,5 @@ #lang racket/base -(require racket/class - racket/list - sugar/unstable/class - sugar/unstable/dict - "private/generic.rkt" - "private/helper.rkt") +(require "helper.rkt" racket/dict sugar/unstable/dict) (provide (all-defined-out)) #| @@ -12,38 +7,51 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Bitfield.coffee |# -(define-subclass Streamcoder (Bitfield type [flags empty]) - (unless (andmap (λ (f) (or (key? f) (not f))) flags) - (raise-argument-error 'Bitfield "list of keys" flags)) - - (define/augment (decode stream . _) +(define/post-decode (xbitfield-decode xb [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) (define flag-hash (mhasheq)) - (for* ([val (in-value (send type decode stream))] - [(flag i) (in-indexed flags)] - #:when flag) + (define val (decode (xbitfield-type xb))) + (for ([(flag i) (in-indexed (xbitfield-flags xb))] + #:when flag) (hash-set! flag-hash flag (bitwise-bit-set? val i))) - flag-hash) + flag-hash)) - (define/augment (size . _) (send type size)) +(define/pre-encode (xbitfield-encode xb flag-hash [port-arg (current-output-port)] #:parent [parent #f]) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) + (define bit-int (for/sum ([(flag i) (in-indexed (xbitfield-flags xb))] + #:when (and flag (dict-ref flag-hash flag #f))) + (arithmetic-shift 1 i))) + (encode (xbitfield-type xb) bit-int) + (unless port-arg (get-output-bytes port)))) - (define/augment (encode port flag-hash [ctx #f]) - (define bit-int (for/sum ([(flag i) (in-indexed flags)] - #:when (and flag (ref flag-hash flag))) - (arithmetic-shift 1 i))) - (send type encode port bit-int)) +(define (xbitfield-size xb [val #f] #:parent [parent #f]) + (size (xbitfield-type xb))) - (define/override (get-class-name) 'Bitfield)) +(struct xbitfield xbase (type flags) #:transparent + #:methods gen:xenomorphic + [(define decode xbitfield-decode) + (define encode xbitfield-encode) + (define size xbitfield-size)]) +(define (+xbitfield [type-arg #f] [flag-arg #f] + #:type [type-kwarg #f] #:flags [flag-kwarg #f]) + (define type (or type-arg type-kwarg)) + (define flags (or flag-arg flag-kwarg null)) + (unless (andmap (λ (f) (or (symbol? f) (not f))) flags) + (raise-argument-error '+xbitfield "list of symbols" flags)) + (xbitfield type flags)) -(test-module - (require "number.rkt") - (define bfer (+Bitfield uint16be '(bold italic underline #f shadow condensed extended))) - (define bf (send bfer decode #"\0\25")) - (check-equal? (length (ref-keys bf)) 6) ; omits #f flag - (check-true (ref bf 'bold)) - (check-true (ref bf 'underline)) - (check-true (ref bf 'shadow)) - (check-false (ref bf 'italic)) - (check-false (ref bf 'condensed)) - (check-false (ref bf 'extended)) - (check-equal? (encode bfer bf #f) #"\0\25")) \ No newline at end of file +(module+ test + (require rackunit "number.rkt") + (define bfer (+xbitfield uint16be '(bold italic underline #f shadow condensed extended))) + (define bf (decode bfer #"\0\25")) + (check-equal? (length (dict-keys bf)) 6) ; omits #f flag + (check-true (dict-ref bf 'bold)) + (check-true (dict-ref bf 'underline)) + (check-true (dict-ref bf 'shadow)) + (check-false (dict-ref bf 'italic)) + (check-false (dict-ref bf 'condensed)) + (check-false (dict-ref bf 'extended)) + (check-equal? (encode bfer bf #f) #"\0\25")) \ No newline at end of file diff --git a/xenomorph/xenomorph/buffer.rkt b/xenomorph/xenomorph/buffer.rkt index bb4ebc1e..c3a7c47b 100644 --- a/xenomorph/xenomorph/buffer.rkt +++ b/xenomorph/xenomorph/buffer.rkt @@ -1,10 +1,5 @@ #lang racket/base -(require racket/class - sugar/unstable/class - "private/generic.rkt" - "private/helper.rkt" - "number.rkt" - "utils.rkt") +(require "helper.rkt" "util.rkt" "number.rkt") (provide (all-defined-out)) #| @@ -12,49 +7,38 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Buffer.coffee |# -#| -A Buffer is a container object for any data object that supports random access -A Node Buffer object is basically a byte string. -First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object. -A Restructure RBuffer object is separate. -|# - -(define (+Buffer xs [type #f]) - ((if (string? xs) - string->bytes/utf-8 - list->bytes) xs)) - -(define-subclass xenomorph-base% (RBuffer [len #xffff]) - - (define/augment (decode port [parent #f]) - (define decoded-len (resolve-length len port parent)) - (read-bytes decoded-len port)) +(define/post-decode (xbuffer-decode xb [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) + (define decoded-len (resolve-length (xbuffer-len xb) #:parent parent)) + (read-bytes decoded-len))) - (define/augment (size [val #f] [parent #f]) - (when val (unless (bytes? val) - (raise-argument-error 'Buffer:size "bytes" val))) - (if val - (bytes-length val) - (resolve-length len val parent))) - - (define/augment (encode port buf [parent #f]) +(define/pre-encode (xbuffer-encode xb buf [port-arg (current-output-port)] #:parent [parent #f]) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) (unless (bytes? buf) - (raise-argument-error 'Buffer:encode "bytes" buf)) - (define op (or port (open-output-bytes))) - (when (NumberT? len) - (send len encode op (length buf))) - (write-bytes buf op) - (unless port (get-output-bytes op)))) - -(define-subclass RBuffer (BufferT)) - - -#;(test-module - (require "stream.rkt") - (define stream (+DecodeStream #"\2BCDEF")) - (define S (+String uint8 'utf8)) - (check-equal? (send S decode stream) "BC") - (define os (+EncodeStream)) - (send S encode os "Mike") - (check-equal? (send os dump) #"\4Mike") - (check-equal? (send (+String) size "foobar") 6)) \ No newline at end of file + (raise-argument-error 'xbuffer-encode "bytes" buf)) + (when (xint? (xbuffer-len xb)) + (encode (xbuffer-len xb) (bytes-length buf))) + (write-bytes buf) + (unless port-arg (get-output-bytes port)))) + +(define/finalize-size (xbuffer-size xb [val #f] #:parent [parent #f]) + (when val (unless (bytes? val) + (raise-argument-error 'xbuffer-size "bytes" val))) + (if (bytes? val) + (bytes-length val) + (resolve-length (xbuffer-len xb) val #:parent parent))) + +(struct xbuffer xbase (len) #:transparent + #:methods gen:xenomorphic + [(define decode xbuffer-decode) + (define encode xbuffer-encode) + (define size xbuffer-size)]) + +(define (+xbuffer [len-arg #f] + #:length [len-kwarg #f]) + (define len (or len-arg len-kwarg #xffff)) + (unless (length-resolvable? len) + (raise-argument-error '+xbuffer "resolvable length" len)) + (xbuffer len)) \ No newline at end of file diff --git a/xenomorph/xenomorph/enum.rkt b/xenomorph/xenomorph/enum.rkt index 8913d6d0..c8394638 100644 --- a/xenomorph/xenomorph/enum.rkt +++ b/xenomorph/xenomorph/enum.rkt @@ -1,8 +1,5 @@ #lang racket/base -(require racket/class - racket/list - sugar/unstable/class - "private/helper.rkt") +(require "helper.rkt" racket/list) (provide (all-defined-out)) #| @@ -10,17 +7,37 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Enum.coffee |# -(define-subclass xenomorph-base% (Enum type [options empty]) +(define/post-decode (xenum-decode xe [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) + (define index (decode (xenum-type xe))) + (or (list-ref (xenum-options xe) index) index))) - (define/augment (decode stream . _) - (define index (send type decode stream)) - (or (list-ref options index) index)) +(define (xenum-size xe [val #f] #:parent [parent #f]) + (size (xenum-type xe))) - (define/augment (size . _) (send type size)) +(define/pre-encode (xenum-encode xe val [port-arg (current-output-port)] #:parent [parent #f]) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) + (define index (index-of (xenum-options xe) val)) + (unless index + (raise-argument-error 'xenum-encode "valid option" val)) + (encode (xenum-type xe) index) + (unless port-arg (get-output-bytes port)))) - (define/augment (encode stream val [ctx #f]) - (define index (index-of options val)) - (unless index - (raise-argument-error 'Enum:encode "valid option" val)) - (send type encode stream index))) +(struct xenum xbase (type options) #:transparent + #:methods gen:xenomorphic + [(define decode xenum-decode) + (define encode xenum-encode) + (define size xenum-size)]) +(define (+xenum [type-arg #f] [values-arg #f] + #:type [type-kwarg #f] + #:values [values-kwarg #f]) + (define type (or type-arg type-kwarg)) + (unless (xenomorphic? type) + (raise-argument-error '+xenum "xenomorphic type" type)) + (define values (or values-arg values-kwarg)) + (unless (list? values) + (raise-argument-error '+xenum "list of values" values)) + (xenum type values)) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/helper.rkt b/xenomorph/xenomorph/helper.rkt similarity index 83% rename from xenomorph/xenomorph/redo/helper.rkt rename to xenomorph/xenomorph/helper.rkt index 12d0026a..3d5d47ee 100644 --- a/xenomorph/xenomorph/redo/helper.rkt +++ b/xenomorph/xenomorph/helper.rkt @@ -10,12 +10,20 @@ [(input-port? arg) arg] [else (raise-argument-error '->input-port "byte string or input port" arg)])) +(define private-keys '(parent _startOffset _currentOffset _length)) +(define (dump-mutable x) + (define h (make-hasheq)) + (for ([(k v) (in-dict (dump x))]) + (hash-set! h k v)) + h) + (define (dump x) (cond [(input-port? x) (port->bytes x)] [(output-port? x) (get-output-bytes x)] - [(dict? x) (for/list ([(k v) (in-dict x)]) - (cons (dump k) (dump v)))] + [(dict? x) (for/hasheq ([(k v) (in-dict x)] + #:unless (memq k private-keys)) + (values k v))] [(list? x) (map dump x)] [else x])) diff --git a/xenomorph/xenomorph/lazy-array.rkt b/xenomorph/xenomorph/lazy-array.rkt index e8cd6528..11808a01 100644 --- a/xenomorph/xenomorph/lazy-array.rkt +++ b/xenomorph/xenomorph/lazy-array.rkt @@ -1,83 +1,69 @@ #lang racket/base -(require racket/class - sugar/unstable/class - sugar/unstable/dict - "private/generic.rkt" - "private/helper.rkt" - "utils.rkt" - "array.rkt" - "number.rkt") +(require "helper.rkt" "util.rkt" "number.rkt" "array.rkt" racket/stream racket/dict sugar/unstable/dict) (provide (all-defined-out)) #| approximates https://github.com/mbutterick/restructure/blob/master/src/LazyArray.coffee |# - -(define (get o i) (send o get i)) -(define (LazyArray->list o) (send o to-list)) - -(define-subclass object% (InnerLazyArray type [len #f] [port-in #f] [ctx #f]) - (field ([port port] (cond - [(bytes? port-in) (open-input-bytes port-in)] - [(port? port-in) port-in] - [else (raise-argument-error 'LazyArray "port" port)]))) - (define starting-pos (pos port)) - (define item-cache (mhasheqv)) ; integer-keyed hash, rather than list - - - (define/public-final (get index) - (unless (<= 0 index (sub1 len)) - (raise-argument-error 'LazyArray:get (format "index in range 0 to ~a" (sub1 len)) index)) - (ref! item-cache index (λ () - (define orig-pos (pos port)) - (pos port (+ starting-pos (* (send type size #f ctx) index))) - (define new-item (send type decode port ctx)) - (pos port orig-pos) - new-item))) - - (define/public-final (to-list) - (for/list ([i (in-range len)]) - (get i)))) - - -(define-subclass ArrayT (LazyArray) - (inherit-field len type) - (define/override (decode port [parent #f]) +(define (xlazy-array-decode xla [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) (define starting-pos (pos port)) ; ! placement matters. `resolve-length` will change `pos` - (define decoded-len (resolve-length len port parent)) - (let ([parent (if (NumberT? len) + (define decoded-len (resolve-length (xarray-base-len xla) #:parent parent)) + (let ([parent (if (xint? (xarray-base-len xla)) (mhasheq 'parent parent '_startOffset starting-pos '_currentOffset 0 - '_length len) + '_length (xarray-base-len xla)) parent)]) - (define res (+InnerLazyArray type decoded-len port parent)) - (pos port (+ (pos port) (* decoded-len (send type size #f parent)))) - res)) + (define starting-pos (pos port)) + (define type (xarray-base-type xla)) + (begin0 + (for/stream ([index (in-range decoded-len)]) + (define orig-pos (pos port)) + (pos port (+ starting-pos (* (size type #f #:parent parent) index))) + ;; use explicit `port` arg below because this evaluation is delayed + (begin0 + (post-decode xla (decode type port #:parent parent)) + (pos port orig-pos))) + (pos port (+ (pos port) (* decoded-len (size (xarray-base-type xla) #f #:parent parent)))))))) + +(define (xlazy-array-encode xla val [port-arg (current-output-port)] #:parent [parent #f]) + (xarray-encode xla (if (stream? val) (stream->list val) val) port-arg #:parent parent)) - (define/override (size [val #f] [ctx #f]) - (super size (if (InnerLazyArray? val) - (send val to-list) - val) ctx)) +(define (xlazy-array-size xla [val #f] #:parent [parent #f]) + (xarray-size xla (if (stream? val) (stream->list val) val) #:parent parent)) - (define/override (encode port val [ctx #f]) - (super encode port (if (InnerLazyArray? val) - (send val to-list) - val) ctx))) +;; xarray-base holds type and len fields +(struct xlazy-array xarray-base () #:transparent + #:methods gen:xenomorphic + [(define decode xlazy-array-decode) + (define encode xlazy-array-encode) + (define size xlazy-array-size)]) -(test-module - (define bstr #"ABCD1234") - (define ds (open-input-bytes bstr)) - (define la (+LazyArray uint8 4)) - (define ila (decode la ds)) - (check-equal? (pos ds) 4) - (check-equal? (get ila 1) 66) - (check-equal? (get ila 3) 68) - (check-equal? (pos ds) 4) - (check-equal? (LazyArray->list ila) '(65 66 67 68)) - (define la2 (+LazyArray int16be (λ (t) 4))) - (check-equal? (encode la2 '(1 2 3 4) #f) #"\0\1\0\2\0\3\0\4") - (check-equal? (send (decode la2 (open-input-bytes #"\0\1\0\2\0\3\0\4")) to-list) '(1 2 3 4))) +(define (+xlazy-array [type-arg #f] [len-arg #f] + #:type [type-kwarg #f] #:length [len-kwarg #f]) + (define type (or type-arg type-kwarg)) + (define len (or len-arg len-kwarg)) + (unless (xenomorphic? type) + (raise-argument-error '+xarray "xenomorphic type" type)) + (unless (length-resolvable? len) + (raise-argument-error '+xarray "length-resolvable?" len)) + (xlazy-array type len)) +(module+ test + (require rackunit "number.rkt") + (define bstr #"ABCD1234") + (define ds (open-input-bytes bstr)) + (define la (+xlazy-array uint8 4)) + (define ila (decode la ds)) + (check-equal? (pos ds) 4) + (check-equal? (stream-ref ila 1) 66) + (check-equal? (stream-ref ila 3) 68) + (check-equal? (pos ds) 4) + (check-equal? (stream->list ila) '(65 66 67 68)) + (define la2 (+xlazy-array int16be (λ (t) 4))) + (check-equal? (encode la2 '(1 2 3 4) #f) #"\0\1\0\2\0\3\0\4") + (check-equal? (stream->list (decode la2 (open-input-bytes #"\0\1\0\2\0\3\0\4"))) '(1 2 3 4))) \ No newline at end of file diff --git a/xenomorph/xenomorph/main.rkt b/xenomorph/xenomorph/main.rkt index 00e28a92..9f227b19 100644 --- a/xenomorph/xenomorph/main.rkt +++ b/xenomorph/xenomorph/main.rkt @@ -1,13 +1,14 @@ #lang racket/base +(require racket/require) (define-syntax-rule (r+p ID ...) (begin (require ID ...) (provide (all-from-out ID ...)))) (r+p "array.rkt" - "base.rkt" "bitfield.rkt" "buffer.rkt" "enum.rkt" + "helper.rkt" "lazy-array.rkt" "number.rkt" "optional.rkt" diff --git a/xenomorph/xenomorph/number.rkt b/xenomorph/xenomorph/number.rkt index b555c926..4163df30 100644 --- a/xenomorph/xenomorph/number.rkt +++ b/xenomorph/xenomorph/number.rkt @@ -1,12 +1,5 @@ -#lang racket/base -(require (for-syntax racket/base - racket/syntax - "sizes.rkt" - racket/match) - racket/class - sugar/unstable/class - "private/helper.rkt" - "sizes.rkt") +#lang debug racket/base +(require "helper.rkt") (provide (all-defined-out)) #| @@ -14,184 +7,229 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Number.coffee |# -(define (ends-with-8? type) - (define str (symbol->string type)) - (equal? (substring str (sub1 (string-length str))) "8")) +(define (unsigned->signed uint bits) + (define most-significant-bit-mask (arithmetic-shift 1 (sub1 bits))) + (- (bitwise-xor uint most-significant-bit-mask) most-significant-bit-mask)) -(define (signed-type? type) - (not (equal? "u" (substring (symbol->string type) 0 1)))) +(define (signed->unsigned sint bits) + (bitwise-and sint (arithmetic-shift 1 bits))) -(test-module - (check-false (signed-type? 'uint16)) - (check-true (signed-type? 'int16))) +(define (reverse-bytes bstr) + (apply bytes + (for/list ([b (in-bytes bstr (sub1 (bytes-length bstr)) -1 -1)]) + b))) (define (exact-if-possible x) (if (integer? x) (inexact->exact x) x)) + (define system-endian (if (system-big-endian?) 'be 'le)) -(define-subclass xenomorph-base% (Integer [type 'uint16] [endian system-endian]) - (getter-field [number-type (string->symbol (format "~a~a" type (if (ends-with-8? type) "" endian)))]) - (define _signed? (signed-type? type)) - - ;; `get-type-size` will raise error if number-type is invalid: use this as check of input - ;; size of a number doesn't change, so we can stash it as `_size` - (define _size (with-handlers ([exn:fail:contract? - (λ (exn) - (raise-argument-error 'Integer "valid type and endian" (format "~v ~v" type endian)))]) - (get-type-size number-type))) - - (define bits (* _size 8)) - - (define/augment (size . args) _size) - - (define-values (bound-min bound-max) - ;; if a signed integer has n bits, it can contain a number - ;; between - (expt 2 (sub1 n)) and (sub1 (expt 2 (sub1 n)). - (let* ([signed-max (sub1 (arithmetic-shift 1 (sub1 bits)))] - [signed-min (sub1 (- signed-max))] - [delta (if _signed? 0 signed-min)]) - (values (- signed-min delta) (- signed-max delta)))) - - (define/augment (decode port [parent #f]) - (define bstr (read-bytes _size port)) - (define bs ((if (eq? endian system-endian) values reverse) (bytes->list bstr))) - (define unsigned-int (for/sum ([(b i) (in-indexed bs)]) - (arithmetic-shift b (* 8 i)))) - unsigned-int) - - (define/override (post-decode unsigned-val . _) - (if _signed? (unsigned->signed unsigned-val bits) unsigned-val)) - - (define/override (pre-encode val . _) - (exact-if-possible val)) - - (define/augment (encode port val [parent #f]) - (unless (<= bound-min val bound-max) - (raise-argument-error 'Integer:encode (format "value within range of ~a ~a-byte int (~a to ~a)" (if _signed? "signed" "unsigned") _size bound-min bound-max) val)) - (define-values (bs _) (for/fold ([bs null] [n val]) - ([i (in-range _size)]) - (values (cons (bitwise-and n #xff) bs) (arithmetic-shift n -8)))) - (apply bytes ((if (eq? endian 'be) values reverse) bs)))) - -(define-values (NumberT NumberT? +NumberT) (values Integer Integer? +Integer)) -(define-values (Number Number? +Number) (values Integer Integer? +Integer)) - - -(define-subclass xenomorph-base% (Float _size [endian system-endian]) - (define byte-size (/ _size 8)) - - (define/augment (decode port [parent #f]) ; convert int to float - (define bs (read-bytes byte-size port)) - (floating-point-bytes->real bs (eq? endian 'be))) - - (define/augment (encode port val [parent #f]) ; convert float to int - (define bs (real->floating-point-bytes val byte-size (eq? endian 'be))) - bs) - - (define/augment (size . args) byte-size)) - - -(define-instance float (make-object Float 32)) -(define-instance floatbe (make-object Float 32 'be)) -(define-instance floatle (make-object Float 32 'le)) - -(define-instance double (make-object Float 64)) -(define-instance doublebe (make-object Float 64 'be)) -(define-instance doublele (make-object Float 64 'le)) - - -(define-subclass* Integer (Fixed size [fixed-endian system-endian] [fracBits (floor (/ size 2))]) - (super-make-object (string->symbol (format "int~a" size)) fixed-endian) - (define _point (arithmetic-shift 1 fracBits)) - - (define/override (post-decode int . _) - (exact-if-possible (/ int _point 1.0))) - - (define/override (pre-encode fixed . _) - (exact-if-possible (floor (* fixed _point))))) - -(define-instance fixed16 (make-object Fixed 16)) -(define-instance fixed16be (make-object Fixed 16 'be)) -(define-instance fixed16le (make-object Fixed 16 'le)) -(define-instance fixed32 (make-object Fixed 32)) -(define-instance fixed32be (make-object Fixed 32 'be)) -(define-instance fixed32le (make-object Fixed 32 'le)) - - -(test-module - (check-exn exn:fail:contract? (λ () (+Integer 'not-a-valid-type))) - (check-exn exn:fail:contract? (λ () (encode uint8 256 #f))) - (check-not-exn (λ () (encode uint8 255 #f))) - (check-exn exn:fail:contract? (λ () (encode int8 256 #f))) - (check-exn exn:fail:contract? (λ () (encode int8 255 #f))) - (check-not-exn (λ () (encode int8 127 #f))) - (check-not-exn (λ () (encode int8 -128 #f ))) - (check-exn exn:fail:contract? (λ () (encode int8 -129 #f))) - (check-exn exn:fail:contract? (λ () (encode uint16 (add1 #xffff) #f))) - (check-not-exn (λ () (encode uint16 #xffff #f))) - - (let ([o (+Integer 'uint16 'le)] - [ip (open-input-bytes (bytes 1 2 3 4))] - [op (open-output-bytes)]) - (check-equal? (send o decode ip) 513) ;; 1000 0000 0100 0000 - (check-equal? (send o decode ip) 1027) ;; 1100 0000 0010 0000 - (encode o 513 op) - (check-equal? (get-output-bytes op) (bytes 1 2)) - (encode o 1027 op) - (check-equal? (get-output-bytes op) (bytes 1 2 3 4))) - - (let ([o (+Integer 'uint16 'be)] - [ip (open-input-bytes (bytes 1 2 3 4))] - [op (open-output-bytes)]) - (check-equal? (send o decode ip) 258) ;; 0100 0000 1000 0000 - (check-equal? (send o decode ip) 772) ;; 0010 0000 1100 0000 - (encode o 258 op) - (check-equal? (get-output-bytes op) (bytes 1 2)) - (encode o 772 op) - (check-equal? (get-output-bytes op) (bytes 1 2 3 4)))) - - -(test-module - (check-equal? (send (+Integer 'uint8) size) 1) - (check-equal? (send (+Integer) size) 2) - (check-equal? (send (+Integer 'uint32) size) 4) - (check-equal? (send (+Integer 'double) size) 8) - - (check-equal? (send (+Number 'uint8) size) 1) - (check-equal? (send (+Number) size) 2) - (check-equal? (send (+Number 'uint32) size) 4) - (check-equal? (send (+Number 'double) size) 8)) - -;; use keys of type-sizes hash to generate corresponding number definitions -(define-syntax (make-int-types stx) - (syntax-case stx () - [(_) (with-syntax* ([((ID BASE ENDIAN) ...) (for*/list ([k (in-hash-keys type-sizes)] - [kstr (in-value (format "~a" k))] - #:unless (regexp-match #rx"^(float|double)" kstr)) - (match-define (list* prefix suffix _) - (regexp-split #rx"(?=[bl]e|$)" kstr)) - (map string->symbol - (list (string-downcase kstr) - prefix - (if (positive? (string-length suffix)) - suffix - (if (system-big-endian?) "be" "le")))))] - [(ID ...) (map (λ (s) (datum->syntax stx (syntax->datum s))) (syntax->list #'(ID ...)))]) - #'(begin (define-instance ID (make-object Integer 'BASE 'ENDIAN)) ...))])) - -(make-int-types) - -(test-module - (check-equal? (size uint8) 1) - (check-equal? (size uint16) 2) - (check-equal? (size uint32) 4) - (check-equal? (size double) 8) - - (define bs (encode fixed16be 123.45 #f)) - (check-equal? bs #"{s") - (check-equal? (ceiling (* (decode fixed16be bs) 100)) 12345.0) - - (check-equal? (decode int8 (bytes 127)) 127) - (check-equal? (decode int8 (bytes 255)) -1) - - (check-equal? (encode int8 -1 #f) (bytes 255)) - (check-equal? (encode int8 127 #f) (bytes 127))) +(define/pre-encode (xint-encode i val [port-arg (current-output-port)] #:parent [parent #f]) + (unless (xint? i) + (raise-argument-error 'encode "xint instance" i)) + (define-values (bound-min bound-max) (bounds i)) + (unless (<= bound-min val bound-max) + (raise-argument-error 'encode (format "value that fits within ~a ~a-byte int (~a to ~a)" (if (xint-signed i) "signed" "unsigned") (xint-size i) bound-min bound-max) val)) + (unless (or (not port-arg) (output-port? port-arg)) + (raise-argument-error 'encode "output port or #f" port-arg)) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) + (define bs (for/fold ([bs null] + [val (exact-if-possible val)] + #:result bs) + ([i (in-range (xint-size i))]) + (values (cons (bitwise-and val #xff) bs) (arithmetic-shift val -8)))) + (define res (apply bytes ((if (eq? (xint-endian i) 'be) values reverse) bs))) + (if port-arg (write-bytes res) res))) + +(define/post-decode (xint-decode i [port-arg (current-input-port)] #:parent [parent #f]) + (unless (xint? i) + (raise-argument-error 'decode "xint instance" i)) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) + (define bstr (read-bytes (xint-size i))) + (define bs ((if (eq? (xint-endian i) system-endian) + values + reverse-bytes) bstr)) + (define uint (for/sum ([b (in-bytes bs)] + [i (in-naturals)]) + (arithmetic-shift b (* 8 i)))) + (if (xint-signed i) (unsigned->signed uint (bits i)) uint))) + +(struct xnumber xbase () #:transparent) + +(struct xint xnumber (size signed endian) #:transparent + #:methods gen:xenomorphic + [(define decode xint-decode) + (define encode xint-encode) + (define size (λ (i [item #f] #:parent [parent #f]) (xint-size i)))]) + +(define (+xint [size 2] #:signed [signed #true] #:endian [endian system-endian]) + (unless (exact-positive-integer? size) + (raise-argument-error '+xint "exact positive integer" size)) + (unless (memq endian '(le be)) + (raise-argument-error '+xint "'le or 'be" endian)) + (xint size signed endian)) + +(define (type-tag i) + (string->symbol + (string-append (if (xint-signed i) "" "u") + "int" + (number->string (bits i)) + (if (> (xint-size i) 1) (symbol->string (xint-endian i)) "")))) + +(define (bits i) (* (xint-size i) 8)) + +(define (bounds i) + (unless (xint? i) + (raise-argument-error 'bounds "integer instance" i)) + ;; if a signed integer has n bits, it can contain a number + ;; between - (expt 2 (sub1 n)) and (sub1 (expt 2 (sub1 n)). + (let* ([signed-max (sub1 (arithmetic-shift 1 (sub1 (bits i))))] + [signed-min (sub1 (- signed-max))] + [delta (if (xint-signed i) 0 signed-min)]) + (values (- signed-min delta) (- signed-max delta)))) + +(define int8 (+xint 1)) +(define int16 (+xint 2)) +(define int24 (+xint 3)) +(define int32 (+xint 4)) +(define uint8 (+xint 1 #:signed #f)) +(define uint16 (+xint 2 #:signed #f)) +(define uint24 (+xint 3 #:signed #f)) +(define uint32 (+xint 4 #:signed #f)) +(define int8be (+xint 1 #:endian 'be)) +(define int16be (+xint 2 #:endian 'be)) +(define int24be (+xint 3 #:endian 'be)) +(define int32be (+xint 4 #:endian 'be)) +(define uint8be (+xint 1 #:signed #f #:endian 'be)) +(define uint16be (+xint 2 #:signed #f #:endian 'be)) +(define uint24be (+xint 3 #:signed #f #:endian 'be)) +(define uint32be (+xint 4 #:signed #f #:endian 'be)) +(define int8le (+xint 1 #:endian 'le)) +(define int16le (+xint 2 #:endian 'le)) +(define int24le (+xint 3 #:endian 'le)) +(define int32le (+xint 4 #:endian 'le)) +(define uint8le (+xint 1 #:signed #f #:endian 'le)) +(define uint16le (+xint 2 #:signed #f #:endian 'le)) +(define uint24le (+xint 3 #:signed #f #:endian 'le)) +(define uint32le (+xint 4 #:signed #f #:endian 'le)) + +(module+ test + (require rackunit) + (check-exn exn:fail:contract? (λ () (+xint 'not-a-valid-type))) + (check-exn exn:fail:contract? (λ () (encode uint8 256 #f))) + (check-not-exn (λ () (encode uint8 255 #f))) + (check-exn exn:fail:contract? (λ () (encode int8 256 #f))) + (check-exn exn:fail:contract? (λ () (encode int8 255 #f))) + (check-not-exn (λ () (encode int8 127 #f))) + (check-not-exn (λ () (encode int8 -128 #f))) + (check-exn exn:fail:contract? (λ () (encode int8 -129 #f))) + (check-exn exn:fail:contract? (λ () (encode uint16 (add1 #xffff) #f))) + (check-not-exn (λ () (encode uint16 #xffff #f))) + + (let ([i (+xint 2 #:signed #f #:endian 'le)] + [ip (open-input-bytes (bytes 1 2 3 4))] + [op (open-output-bytes)]) + (check-equal? (decode i ip) 513) ;; 1000 0000 0100 0000 + (check-equal? (decode i ip) 1027) ;; 1100 0000 0010 0000 + (encode i 513 op) + (check-equal? (get-output-bytes op) (bytes 1 2)) + (encode i 1027 op) + (check-equal? (get-output-bytes op) (bytes 1 2 3 4))) + + (let ([i (+xint 2 #:signed #f #:endian 'be)] + [ip (open-input-bytes (bytes 1 2 3 4))] + [op (open-output-bytes)]) + (check-equal? (decode i ip) 258) ;; 0100 0000 1000 0000 + (check-equal? (decode i ip) 772) ;; 0010 0000 1100 0000 + (encode i 258 op) + (check-equal? (get-output-bytes op) (bytes 1 2)) + (encode i 772 op) + (check-equal? (get-output-bytes op) (bytes 1 2 3 4))) + + (check-equal? (size (+xint 1)) 1) + (check-equal? (size (+xint)) 2) + (check-equal? (size (+xint 4)) 4) + (check-equal? (size (+xint 8)) 8) + + (check-equal? (decode int8 (bytes 127)) 127) + (check-equal? (decode int8 (bytes 255)) -1) + (check-equal? (encode int8 -1 #f) (bytes 255)) + (check-equal? (encode int8 127 #f) (bytes 127))) + +(define/post-decode (xfloat-decode xf [port-arg (current-input-port)] #:parent [parent #f]) + (unless (xfloat? xf) + (raise-argument-error 'decode "xfloat instance" xf)) + (define bs (read-bytes (xfloat-size xf) (->input-port port-arg))) + (floating-point-bytes->real bs (eq? (xfloat-endian xf) 'be))) + +(define/pre-encode (xfloat-encode xf val [port (current-output-port)] #:parent [parent #f]) + (unless (xfloat? xf) + (raise-argument-error 'encode "xfloat instance" xf)) + (unless (or (not port) (output-port? port)) + (raise-argument-error 'encode "output port or #f" port)) + (define res (real->floating-point-bytes val (xfloat-size xf) (eq? (xfloat-endian xf) 'be))) + (if port (write-bytes res port) res)) + +(struct xfloat xnumber (size endian) #:transparent + #:methods gen:xenomorphic + [(define decode xfloat-decode) + (define encode xfloat-encode) + (define size (λ (i [item #f] #:parent [parent #f]) (xfloat-size i)))]) + +(define (+xfloat [size 4] #:endian [endian system-endian]) + (unless (exact-positive-integer? size) + (raise-argument-error '+xfloat "exact positive integer" size)) + (unless (memq endian '(le be)) + (raise-argument-error '+xfloat "'le or 'be" endian)) + (xfloat size endian)) + +(define float (+xfloat 4)) +(define floatbe (+xfloat 4 #:endian 'be)) +(define floatle (+xfloat 4 #:endian 'le)) + +(define double (+xfloat 8)) +(define doublebe (+xfloat 8 #:endian 'be)) +(define doublele (+xfloat 8 #:endian 'le)) + +(define/post-decode (xfixed-decode xf [port-arg (current-input-port)] #:parent [parent #f]) + (unless (xfixed? xf) + (raise-argument-error 'decode "xfixed instance" xf)) + (define int (xint-decode xf port-arg)) + (exact-if-possible (/ int (fixed-shift xf) 1.0))) + +(define/pre-encode (xfixed-encode xf val [port (current-output-port)] #:parent [parent #f]) + (unless (xfixed? xf) + (raise-argument-error 'encode "xfixed instance" xf)) + (define int (exact-if-possible (floor (* val (fixed-shift xf))))) + (xint-encode xf int port)) + +(struct xfixed xint (fracbits) #:transparent + #:methods gen:xenomorphic + [(define decode xfixed-decode) + (define encode xfixed-encode) + (define size (λ (i [item #f] #:parent [parent #f]) (xint-size i)))]) + +(define (+xfixed [size 2] #:signed [signed #true] #:endian [endian system-endian] [fracbits (/ (* size 8) 2)]) + (unless (exact-positive-integer? size) + (raise-argument-error '+xfixed "exact positive integer" size)) + (unless (exact-positive-integer? fracbits) + (raise-argument-error '+xfixed "exact positive integer" fracbits)) + (unless (memq endian '(le be)) + (raise-argument-error '+xfixed "'le or 'be" endian)) + (xfixed size signed endian fracbits)) + +(define (fixed-shift xf) + (arithmetic-shift 1 (xfixed-fracbits xf))) + +(define fixed16 (+xfixed 2)) +(define fixed16be (+xfixed 2 #:endian 'be)) +(define fixed16le (+xfixed 2 #:endian 'le)) +(define fixed32 (+xfixed 4)) +(define fixed32be (+xfixed 4 #:endian 'be)) +(define fixed32le (+xfixed 4 #:endian 'le)) + +(module+ test + (define bs (encode fixed16be 123.45 #f)) + (check-equal? bs #"{s") + (check-equal? (ceiling (* (decode fixed16be bs) 100)) 12345.0)) diff --git a/xenomorph/xenomorph/optional.rkt b/xenomorph/xenomorph/optional.rkt index 80b2cf6d..7480a1d4 100644 --- a/xenomorph/xenomorph/optional.rkt +++ b/xenomorph/xenomorph/optional.rkt @@ -1,7 +1,5 @@ -#lang racket/base -(require racket/class - sugar/unstable/class - "private/helper.rkt") +#lang debug racket/base +(require "helper.rkt") (provide (all-defined-out)) #| @@ -9,22 +7,53 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Optional.coffee |# -(define-subclass xenomorph-base% (Optional type [condition #t]) +(define (resolve-condition xo parent) + (define maybe-proc (xoptional-condition xo)) + (if (procedure? maybe-proc) + (maybe-proc parent) + maybe-proc)) - (define (resolve-condition parent) - (if (procedure? condition) - (condition parent) - condition)) - - (define/augment (decode stream parent) - (when (resolve-condition parent) - (send type decode stream parent))) +(define/post-decode (xoptional-decode xo [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) + (when (resolve-condition xo parent) + (decode (xoptional-type xo) #:parent parent)))) - (define/augment (size val parent) - (when (resolve-condition parent) - (send type size val parent))) +(define/pre-encode (xoptional-encode xo val [port-arg (current-output-port)] #:parent [parent #f]) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) + (when (resolve-condition xo parent) + (encode (xoptional-type xo) val #:parent parent)) + (unless port-arg (get-output-bytes port)))) - (define/augment (encode stream val parent) - (when (resolve-condition parent) - (send type encode stream val parent)))) +(define/finalize-size (xoptional-size xo [val #f] #:parent [parent #f]) + (when (resolve-condition xo parent) + (size (xoptional-type xo) val #:parent parent))) +(struct xoptional xbase (type condition) #:transparent + #:methods gen:xenomorphic + [(define decode xoptional-decode) + (define encode xoptional-encode) + (define size xoptional-size)]) + +#;(define (+xoptional [type-arg #f] [cond-arg #f] + #:type [type-kwarg #f] + #:condition [cond-kwarg #f]) + (define type (or type-arg type-kwarg)) + (unless (xenomorphic? type) + (raise-argument-error '+xoptional"xenomorphic type" type)) + (define condition (or cond-arg cond-kwarg)) + (xoptional type condition)) + +(define no-val (gensym)) +(define (+xoptional [type-arg #f] [cond-arg no-val] + #:type [type-kwarg #f] + #:condition [cond-kwarg no-val]) + (define type (or type-arg type-kwarg)) + (unless (xenomorphic? type) + (raise-argument-error '+xoptional"xenomorphic type" type)) + (define condition (cond + [(and (eq? cond-arg no-val) (eq? cond-kwarg no-val)) #true] + [(not (eq? cond-arg no-val)) cond-arg] + [(not (eq? cond-kwarg no-val)) cond-kwarg])) + (xoptional type condition)) diff --git a/xenomorph/xenomorph/pointer.rkt b/xenomorph/xenomorph/pointer.rkt index ddb41362..a0060bf7 100644 --- a/xenomorph/xenomorph/pointer.rkt +++ b/xenomorph/xenomorph/pointer.rkt @@ -1,11 +1,9 @@ -#lang racket/base -(require racket/class - sugar/unstable/class - sugar/unstable/case - sugar/unstable/dict - sugar/unstable/js - "private/generic.rkt" - "private/helper.rkt") +#lang debug racket/base +(require "helper.rkt" + "number.rkt" + racket/dict + racket/promise + sugar/unstable/dict) (provide (all-defined-out)) #| @@ -13,95 +11,119 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Pointer.coffee |# -(define (resolve-void-pointer type val) - (cond - [type (values type val)] - [(VoidPointer? val) (values (· val type) (· val value))] - [else (raise-argument-error 'Pointer:size "VoidPointer" val)])) - -(define (find-top-ctx ctx) +(define (find-top-parent parent) (cond - [(· ctx parent) => find-top-ctx] - [else ctx])) - -(define-subclass xenomorph-base% (Pointer offset-type type-in [options (mhasheq)]) - (field [type (and (not (eq? type-in 'void)) type-in)]) - (define pointer-style (or (· options type) 'local)) - (define allow-null (or (· options allowNull) #t)) - (define null-value (or (· options nullValue) 0)) - (define lazy (· options lazy)) - (define relative-getter-or-0 (or (· options relativeTo) (λ (ctx) 0))) ; changed this to a simple lambda + [(dict-ref parent 'parent #f) => find-top-parent] + [else parent])) - (define/augment (decode port [ctx #f]) - (define offset (send offset-type decode port ctx)) +(define/post-decode (xpointer-decode xp [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) + (define offset (decode (xpointer-offset-type xp) #:parent parent)) (cond - [(and allow-null (= offset null-value)) #f] ; handle null pointers + [(and allow-null (= offset (null-value xp))) #f] ; handle null pointers [else - (define relative (+ (caseq pointer-style - [(local) (· ctx _startOffset)] - [(immediate) (- (pos port) (send offset-type size))] - [(parent) (· ctx parent _startOffset)] - [(global) (or (· (find-top-ctx ctx) _startOffset) 0)] - [else (error 'unknown-pointer-style)]) - (relative-getter-or-0 ctx))) + (define relative (+ (case (pointer-style xp) + [(local) (dict-ref parent '_startOffset)] + [(immediate) (- (pos port) (size (xpointer-offset-type xp)))] + [(parent) (dict-ref (dict-ref parent 'parent) '_startOffset)] + [(global) (or (dict-ref (find-top-parent parent) '_startOffset) 0)] + [else (error 'unknown-pointer-style)]) + ((relative-getter-or-0 xp) parent))) (define ptr (+ offset relative)) (cond - [type (define val (void)) - (define (decode-value) - (cond - [(not (void? val)) val] - [else - (define orig-pos (pos port)) - (pos port ptr) - (set! val (send type decode port ctx)) - (pos port orig-pos) - val])) - (if lazy - (LazyThunk decode-value) - (decode-value))] - [else ptr])])) + [(xpointer-type xp) + (define val (void)) + (define (decode-value) + (cond + [(not (void? val)) val] + [else + (define orig-pos (pos port)) + (pos port ptr) + (set! val (decode (xpointer-type xp) #:parent parent)) + (pos port orig-pos) + val])) + (if (pointer-lazy? xp) + (delay (decode-value)) + (decode-value))] + [else ptr])]))) - - (define/augment (size [val #f] [ctx #f]) - (let*-values ([(parent) ctx] - [(ctx) (caseq pointer-style - [(local immediate) ctx] - [(parent) (· ctx parent)] - [(global) (find-top-ctx ctx)] - [else (error 'unknown-pointer-style)])] - [(type val) (resolve-void-pointer type val)]) - (when (and val ctx) - (ref-set! ctx 'pointerSize (and (· ctx pointerSize) - (+ (· ctx pointerSize) (send type size val parent))))) - (send offset-type size))) - +(define (resolve-void-pointer type val) + (cond + [type (values type val)] + [(xvoid-pointer? val) (values (xvoid-pointer-type val) (xvoid-pointer-value val))] + [else (raise-argument-error 'Pointer:size "VoidPointer" val)])) - (define/augment (encode port val [ctx #f]) - (unless ctx - ;; todo: furnish default pointer context? adapt from Struct? - (raise-argument-error 'Pointer:encode "valid pointer context" ctx)) +(define/pre-encode (xpointer-encode xp val [port-arg (current-output-port)] #:parent [parent #f]) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (unless parent ; todo: furnish default pointer context? adapt from Struct? + (raise-argument-error 'xpointer-encode "valid pointer context" parent)) + (parameterize ([current-output-port port]) (if (not val) - (send offset-type encode port null-value) - (let* ([parent ctx] - [ctx (caseq pointer-style - [(local immediate) ctx] - [(parent) (· ctx parent)] - [(global) (find-top-ctx ctx)] - [else (error 'unknown-pointer-style)])] - [relative (+ (caseq pointer-style - [(local parent) (· ctx startOffset)] - [(immediate) (+ (pos port) (send offset-type size val parent))] - [(global) 0]) - (relative-getter-or-0 (· parent val)))]) + (encode (xpointer-offset-type xp) (null-value xp) port) + (let* ([new-parent (case (pointer-style xp) + [(local immediate) parent] + [(parent) (dict-ref parent 'parent)] + [(global) (find-top-parent parent)] + [else (error 'unknown-pointer-style)])] + [relative (+ (case (pointer-style xp) + [(local parent) (dict-ref new-parent 'startOffset)] + [(immediate) (+ (pos port) (size (xpointer-offset-type xp) val #:parent parent))] + [(global) 0]) + ((relative-getter-or-0 xp) (dict-ref parent 'val #f)))]) + (encode (xpointer-offset-type xp) (- (dict-ref new-parent 'pointerOffset) relative)) + (let-values ([(type val) (resolve-void-pointer (xpointer-type xp) val)]) + (dict-set! new-parent 'pointers (append (dict-ref new-parent 'pointers) + (list (mhasheq 'type type + 'val val + 'parent parent)))) + (dict-set! new-parent 'pointerOffset (+ (dict-ref new-parent 'pointerOffset) (size type val #:parent parent))))))) + (unless port-arg (get-output-bytes port))) + +(define (xpointer-size xp [val #f] #:parent [parent #f]) + (let*-values ([(parent) (case (pointer-style xp) + [(local immediate) parent] + [(parent) (dict-ref parent 'parent)] + [(global) (find-top-parent parent)] + [else (error 'unknown-pointer-style)])] + [(type val) (resolve-void-pointer (xpointer-type xp) val)]) + (when (and val parent) + (dict-set! parent 'pointerSize (and (dict-ref parent 'pointerSize #f) + (+ (dict-ref parent 'pointerSize) (size type val #:parent parent))))) + (size (xpointer-offset-type xp)))) + +(struct xpointer xbase (offset-type type options) #:transparent + #:methods gen:xenomorphic + [(define decode xpointer-decode) + (define encode xpointer-encode) + (define size xpointer-size)]) - (send offset-type encode port (- (· ctx pointerOffset) relative)) - - (let-values ([(type val) (resolve-void-pointer type val)]) - (ref-set! ctx 'pointers (append (· ctx pointers) (list (mhasheq 'type type - 'val val - 'parent parent)))) - (ref-set! ctx 'pointerOffset (+ (· ctx pointerOffset) (send type size val parent)))))))) +(define (+xpointer [offset-arg #f] [type-arg #f] + #:offset-type [offset-kwarg #f] + #:type [type-kwarg #f] + #:style [style 'local] + #:relative-to [relative-to #f] + #:lazy [lazy? #f] + #:allow-null [allow-null? #t] + #:null [null-value 0]) + (define valid-pointer-styles '(local immediate parent global)) + (unless (memq style valid-pointer-styles) + (raise-argument-error '+xpointer (format "~v" valid-pointer-styles) style)) + (define options (mhasheq 'style style + 'relativeTo relative-to + 'lazy lazy? + 'allowNull allow-null? + 'nullValue null-value)) + (define offset-type (or offset-arg offset-kwarg uint8)) + (define type-in (or type-arg type-kwarg uint8)) + (xpointer offset-type (case type-in [(void) #f][else type-in]) options)) +(define (pointer-style xp) (dict-ref (xpointer-options xp) 'style)) +(define (allow-null xp) (dict-ref (xpointer-options xp) 'allowNull)) +(define (null-value xp) (dict-ref (xpointer-options xp) 'nullValue)) +(define (pointer-lazy? xp) (dict-ref (xpointer-options xp) 'lazy)) +(define (relative-getter-or-0 xp) (or (dict-ref (xpointer-options xp) 'relativeTo #f) (λ (parent) 0))) ; changed this to a simple lambda ;; A pointer whose type is determined at decode time -(define-subclass object% (VoidPointer type value)) +(struct xvoid-pointer (type value) #:transparent) +(define +xvoid-pointer xvoid-pointer) diff --git a/xenomorph/xenomorph/redo.rkt b/xenomorph/xenomorph/redo.rkt deleted file mode 100644 index ce1b950b..00000000 --- a/xenomorph/xenomorph/redo.rkt +++ /dev/null @@ -1,19 +0,0 @@ -#lang racket/base -(require racket/require) - -(define-syntax-rule (r+p ID ...) - (begin (require ID ...) (provide (all-from-out ID ...)))) - -(r+p "redo/array.rkt" - "redo/bitfield.rkt" - "redo/buffer.rkt" - "redo/enum.rkt" - "redo/helper.rkt" - "redo/lazy-array.rkt" - "redo/number.rkt" - "redo/optional.rkt" - "redo/pointer.rkt" - "redo/reserved.rkt" - "redo/string.rkt" - "redo/struct.rkt" - "redo/versioned-struct.rkt") diff --git a/xenomorph/xenomorph/redo/array.rkt b/xenomorph/xenomorph/redo/array.rkt deleted file mode 100644 index bc013def..00000000 --- a/xenomorph/xenomorph/redo/array.rkt +++ /dev/null @@ -1,102 +0,0 @@ -#lang debug racket/base -(require racket/dict racket/sequence "helper.rkt" "number.rkt" "util.rkt" sugar/unstable/dict) -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Array.coffee -|# - -(define/post-decode (xarray-decode xa [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (define new-parent (if (xint? (xarray-base-len xa)) - (mhasheq 'parent parent - '_startOffset (pos port) - '_currentOffset 0 - '_length (xarray-base-len xa)) - parent)) - (define decoded-len (resolve-length (xarray-base-len xa) #:parent parent)) - (cond - [(or (not decoded-len) (eq? (xarray-length-type xa) 'bytes)) - (define end-pos (cond - ;; decoded-len is byte length - [decoded-len (+ (pos port) decoded-len)] - ;; no decoded-len, but parent has length - [(and parent (not (zero? (dict-ref parent '_length)))) (+ (dict-ref parent '_startOffset) (dict-ref parent '_length))] - ;; no decoded-len or parent, so consume whole stream - [else +inf.0])) - (for/list ([i (in-naturals)] - #:break (or (eof-object? (peek-byte)) (= (pos port) end-pos))) - (decode (xarray-base-type xa) #:parent new-parent))] - ;; we have decoded-len, which is treated as count of items - [else (for/list ([i (in-range decoded-len)]) - (decode (xarray-base-type xa) #:parent new-parent))]))) - -(define/pre-encode (xarray-encode xa array [port-arg (current-output-port)] #:parent [parent #f]) - (unless (sequence? array) - (raise-argument-error 'xarray-encode "sequence" array)) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - (define (encode-items parent) - ;; todo: should array with fixed length stop encoding after it reaches max? - ;; cf. xstring, which rejects input that is too big for fixed length. - (let* (#;[items (sequence->list array)] - #;[item-count (length items)] - #;[max-items (if (number? (xarray-len xa)) (xarray-len xa) item-count)]) - (for ([item array]) - (encode (xarray-base-type xa) item #:parent parent)))) - (cond - [(xint? (xarray-base-len xa)) - (let ([parent (mhash 'pointers null - 'startOffset (pos port) - 'parent parent)]) - (dict-set! parent 'pointerOffset (+ (pos port) (size xa array #:parent parent))) - (encode (xarray-base-len xa) (length array)) ; encode length at front - (encode-items parent) - (for ([ptr (in-list (dict-ref parent 'pointers))]) ; encode pointer data at end - (encode (dict-ref ptr 'type) (dict-ref ptr 'val))))] - [else (encode-items parent)]) - (unless port-arg (get-output-bytes port)))) - -(define/finalize-size (xarray-size xa [val #f] #:parent [parent #f]) - (when val (unless (sequence? val) - (raise-argument-error 'xarray-size "sequence" val))) - (cond - [val (define-values (new-parent len-size) (if (xint? (xarray-base-len xa)) - (values (mhasheq 'parent parent) (size (xarray-base-len xa))) - (values parent 0))) - (define items-size (for/sum ([item val]) - (size (xarray-base-type xa) item #:parent new-parent))) - (+ items-size len-size)] - [else (define item-count (resolve-length (xarray-base-len xa) #f #:parent parent)) - (define item-size (size (xarray-base-type xa) #f #:parent parent)) - (* item-size item-count)])) - -(struct xarray-base xbase (type len) #:transparent) -(struct xarray xarray-base (length-type) #:transparent - #:methods gen:xenomorphic - [(define decode xarray-decode) - (define encode xarray-encode) - (define size xarray-size)]) - -(define (+xarray [type-arg #f] [len-arg #f] [length-type-arg 'count] - #:type [type-kwarg #f] #:length [len-kwarg #f] #:count-bytes [count-bytes? #f]) - (define type (or type-arg type-kwarg)) - (define len (or len-arg len-kwarg)) - (define length-type (if count-bytes? 'bytes length-type-arg)) - (unless (xenomorphic? type) - (raise-argument-error '+xarray "xenomorphic type" type)) - (unless (length-resolvable? len) - (raise-argument-error '+xarray "length-resolvable?" len)) - (unless (memq length-type '(bytes count)) - (raise-argument-error '+xarray "'bytes or 'count" length-type)) - (xarray type len length-type)) - - -(module+ test - (require rackunit) - (check-equal? (decode (+xarray uint16be 3) #"ABCDEF") '(16706 17220 17734)) - (check-equal? (encode (+xarray uint16be 3) '(16706 17220 17734) #f) #"ABCDEF") - (check-equal? (size (+xarray uint16be) '(1 2 3)) 6) - (check-equal? (size (+xarray doublebe) '(1 2 3 4 5)) 40)) diff --git a/xenomorph/xenomorph/redo/bitfield.rkt b/xenomorph/xenomorph/redo/bitfield.rkt deleted file mode 100644 index 25b8ef94..00000000 --- a/xenomorph/xenomorph/redo/bitfield.rkt +++ /dev/null @@ -1,57 +0,0 @@ -#lang racket/base -(require "helper.rkt" racket/dict sugar/unstable/dict) -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Bitfield.coffee -|# - -(define/post-decode (xbitfield-decode xb [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (define flag-hash (mhasheq)) - (define val (decode (xbitfield-type xb))) - (for ([(flag i) (in-indexed (xbitfield-flags xb))] - #:when flag) - (hash-set! flag-hash flag (bitwise-bit-set? val i))) - flag-hash)) - -(define/pre-encode (xbitfield-encode xb flag-hash [port-arg (current-output-port)] #:parent [parent #f]) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - (define bit-int (for/sum ([(flag i) (in-indexed (xbitfield-flags xb))] - #:when (and flag (dict-ref flag-hash flag #f))) - (arithmetic-shift 1 i))) - (encode (xbitfield-type xb) bit-int) - (unless port-arg (get-output-bytes port)))) - -(define (xbitfield-size xb [val #f] #:parent [parent #f]) - (size (xbitfield-type xb))) - -(struct xbitfield xbase (type flags) #:transparent - #:methods gen:xenomorphic - [(define decode xbitfield-decode) - (define encode xbitfield-encode) - (define size xbitfield-size)]) - -(define (+xbitfield [type-arg #f] [flag-arg #f] - #:type [type-kwarg #f] #:flags [flag-kwarg #f]) - (define type (or type-arg type-kwarg)) - (define flags (or flag-arg flag-kwarg null)) - (unless (andmap (λ (f) (or (symbol? f) (not f))) flags) - (raise-argument-error '+xbitfield "list of symbols" flags)) - (xbitfield type flags)) - -(module+ test - (require rackunit "number.rkt") - (define bfer (+xbitfield uint16be '(bold italic underline #f shadow condensed extended))) - (define bf (decode bfer #"\0\25")) - (check-equal? (length (dict-keys bf)) 6) ; omits #f flag - (check-true (dict-ref bf 'bold)) - (check-true (dict-ref bf 'underline)) - (check-true (dict-ref bf 'shadow)) - (check-false (dict-ref bf 'italic)) - (check-false (dict-ref bf 'condensed)) - (check-false (dict-ref bf 'extended)) - (check-equal? (encode bfer bf #f) #"\0\25")) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/buffer.rkt b/xenomorph/xenomorph/redo/buffer.rkt deleted file mode 100644 index c3a7c47b..00000000 --- a/xenomorph/xenomorph/redo/buffer.rkt +++ /dev/null @@ -1,44 +0,0 @@ -#lang racket/base -(require "helper.rkt" "util.rkt" "number.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Buffer.coffee -|# - -(define/post-decode (xbuffer-decode xb [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (define decoded-len (resolve-length (xbuffer-len xb) #:parent parent)) - (read-bytes decoded-len))) - -(define/pre-encode (xbuffer-encode xb buf [port-arg (current-output-port)] #:parent [parent #f]) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - (unless (bytes? buf) - (raise-argument-error 'xbuffer-encode "bytes" buf)) - (when (xint? (xbuffer-len xb)) - (encode (xbuffer-len xb) (bytes-length buf))) - (write-bytes buf) - (unless port-arg (get-output-bytes port)))) - -(define/finalize-size (xbuffer-size xb [val #f] #:parent [parent #f]) - (when val (unless (bytes? val) - (raise-argument-error 'xbuffer-size "bytes" val))) - (if (bytes? val) - (bytes-length val) - (resolve-length (xbuffer-len xb) val #:parent parent))) - -(struct xbuffer xbase (len) #:transparent - #:methods gen:xenomorphic - [(define decode xbuffer-decode) - (define encode xbuffer-encode) - (define size xbuffer-size)]) - -(define (+xbuffer [len-arg #f] - #:length [len-kwarg #f]) - (define len (or len-arg len-kwarg #xffff)) - (unless (length-resolvable? len) - (raise-argument-error '+xbuffer "resolvable length" len)) - (xbuffer len)) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/enum.rkt b/xenomorph/xenomorph/redo/enum.rkt deleted file mode 100644 index c8394638..00000000 --- a/xenomorph/xenomorph/redo/enum.rkt +++ /dev/null @@ -1,43 +0,0 @@ -#lang racket/base -(require "helper.rkt" racket/list) -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Enum.coffee -|# - -(define/post-decode (xenum-decode xe [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (define index (decode (xenum-type xe))) - (or (list-ref (xenum-options xe) index) index))) - -(define (xenum-size xe [val #f] #:parent [parent #f]) - (size (xenum-type xe))) - -(define/pre-encode (xenum-encode xe val [port-arg (current-output-port)] #:parent [parent #f]) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - (define index (index-of (xenum-options xe) val)) - (unless index - (raise-argument-error 'xenum-encode "valid option" val)) - (encode (xenum-type xe) index) - (unless port-arg (get-output-bytes port)))) - -(struct xenum xbase (type options) #:transparent - #:methods gen:xenomorphic - [(define decode xenum-decode) - (define encode xenum-encode) - (define size xenum-size)]) - -(define (+xenum [type-arg #f] [values-arg #f] - #:type [type-kwarg #f] - #:values [values-kwarg #f]) - (define type (or type-arg type-kwarg)) - (unless (xenomorphic? type) - (raise-argument-error '+xenum "xenomorphic type" type)) - (define values (or values-arg values-kwarg)) - (unless (list? values) - (raise-argument-error '+xenum "list of values" values)) - (xenum type values)) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/lazy-array.rkt b/xenomorph/xenomorph/redo/lazy-array.rkt deleted file mode 100644 index 11808a01..00000000 --- a/xenomorph/xenomorph/redo/lazy-array.rkt +++ /dev/null @@ -1,69 +0,0 @@ -#lang racket/base -(require "helper.rkt" "util.rkt" "number.rkt" "array.rkt" racket/stream racket/dict sugar/unstable/dict) -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/LazyArray.coffee -|# - -(define (xlazy-array-decode xla [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (define starting-pos (pos port)) ; ! placement matters. `resolve-length` will change `pos` - (define decoded-len (resolve-length (xarray-base-len xla) #:parent parent)) - (let ([parent (if (xint? (xarray-base-len xla)) - (mhasheq 'parent parent - '_startOffset starting-pos - '_currentOffset 0 - '_length (xarray-base-len xla)) - parent)]) - (define starting-pos (pos port)) - (define type (xarray-base-type xla)) - (begin0 - (for/stream ([index (in-range decoded-len)]) - (define orig-pos (pos port)) - (pos port (+ starting-pos (* (size type #f #:parent parent) index))) - ;; use explicit `port` arg below because this evaluation is delayed - (begin0 - (post-decode xla (decode type port #:parent parent)) - (pos port orig-pos))) - (pos port (+ (pos port) (* decoded-len (size (xarray-base-type xla) #f #:parent parent)))))))) - -(define (xlazy-array-encode xla val [port-arg (current-output-port)] #:parent [parent #f]) - (xarray-encode xla (if (stream? val) (stream->list val) val) port-arg #:parent parent)) - -(define (xlazy-array-size xla [val #f] #:parent [parent #f]) - (xarray-size xla (if (stream? val) (stream->list val) val) #:parent parent)) - -;; xarray-base holds type and len fields -(struct xlazy-array xarray-base () #:transparent - #:methods gen:xenomorphic - [(define decode xlazy-array-decode) - (define encode xlazy-array-encode) - (define size xlazy-array-size)]) - -(define (+xlazy-array [type-arg #f] [len-arg #f] - #:type [type-kwarg #f] #:length [len-kwarg #f]) - (define type (or type-arg type-kwarg)) - (define len (or len-arg len-kwarg)) - (unless (xenomorphic? type) - (raise-argument-error '+xarray "xenomorphic type" type)) - (unless (length-resolvable? len) - (raise-argument-error '+xarray "length-resolvable?" len)) - (xlazy-array type len)) - -(module+ test - (require rackunit "number.rkt") - (define bstr #"ABCD1234") - (define ds (open-input-bytes bstr)) - (define la (+xlazy-array uint8 4)) - (define ila (decode la ds)) - (check-equal? (pos ds) 4) - (check-equal? (stream-ref ila 1) 66) - (check-equal? (stream-ref ila 3) 68) - (check-equal? (pos ds) 4) - (check-equal? (stream->list ila) '(65 66 67 68)) - (define la2 (+xlazy-array int16be (λ (t) 4))) - (check-equal? (encode la2 '(1 2 3 4) #f) #"\0\1\0\2\0\3\0\4") - (check-equal? (stream->list (decode la2 (open-input-bytes #"\0\1\0\2\0\3\0\4"))) '(1 2 3 4))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/number.rkt b/xenomorph/xenomorph/redo/number.rkt deleted file mode 100644 index 4163df30..00000000 --- a/xenomorph/xenomorph/redo/number.rkt +++ /dev/null @@ -1,235 +0,0 @@ -#lang debug racket/base -(require "helper.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Number.coffee -|# - -(define (unsigned->signed uint bits) - (define most-significant-bit-mask (arithmetic-shift 1 (sub1 bits))) - (- (bitwise-xor uint most-significant-bit-mask) most-significant-bit-mask)) - -(define (signed->unsigned sint bits) - (bitwise-and sint (arithmetic-shift 1 bits))) - -(define (reverse-bytes bstr) - (apply bytes - (for/list ([b (in-bytes bstr (sub1 (bytes-length bstr)) -1 -1)]) - b))) - -(define (exact-if-possible x) (if (integer? x) (inexact->exact x) x)) - -(define system-endian (if (system-big-endian?) 'be 'le)) - -(define/pre-encode (xint-encode i val [port-arg (current-output-port)] #:parent [parent #f]) - (unless (xint? i) - (raise-argument-error 'encode "xint instance" i)) - (define-values (bound-min bound-max) (bounds i)) - (unless (<= bound-min val bound-max) - (raise-argument-error 'encode (format "value that fits within ~a ~a-byte int (~a to ~a)" (if (xint-signed i) "signed" "unsigned") (xint-size i) bound-min bound-max) val)) - (unless (or (not port-arg) (output-port? port-arg)) - (raise-argument-error 'encode "output port or #f" port-arg)) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - (define bs (for/fold ([bs null] - [val (exact-if-possible val)] - #:result bs) - ([i (in-range (xint-size i))]) - (values (cons (bitwise-and val #xff) bs) (arithmetic-shift val -8)))) - (define res (apply bytes ((if (eq? (xint-endian i) 'be) values reverse) bs))) - (if port-arg (write-bytes res) res))) - -(define/post-decode (xint-decode i [port-arg (current-input-port)] #:parent [parent #f]) - (unless (xint? i) - (raise-argument-error 'decode "xint instance" i)) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (define bstr (read-bytes (xint-size i))) - (define bs ((if (eq? (xint-endian i) system-endian) - values - reverse-bytes) bstr)) - (define uint (for/sum ([b (in-bytes bs)] - [i (in-naturals)]) - (arithmetic-shift b (* 8 i)))) - (if (xint-signed i) (unsigned->signed uint (bits i)) uint))) - -(struct xnumber xbase () #:transparent) - -(struct xint xnumber (size signed endian) #:transparent - #:methods gen:xenomorphic - [(define decode xint-decode) - (define encode xint-encode) - (define size (λ (i [item #f] #:parent [parent #f]) (xint-size i)))]) - -(define (+xint [size 2] #:signed [signed #true] #:endian [endian system-endian]) - (unless (exact-positive-integer? size) - (raise-argument-error '+xint "exact positive integer" size)) - (unless (memq endian '(le be)) - (raise-argument-error '+xint "'le or 'be" endian)) - (xint size signed endian)) - -(define (type-tag i) - (string->symbol - (string-append (if (xint-signed i) "" "u") - "int" - (number->string (bits i)) - (if (> (xint-size i) 1) (symbol->string (xint-endian i)) "")))) - -(define (bits i) (* (xint-size i) 8)) - -(define (bounds i) - (unless (xint? i) - (raise-argument-error 'bounds "integer instance" i)) - ;; if a signed integer has n bits, it can contain a number - ;; between - (expt 2 (sub1 n)) and (sub1 (expt 2 (sub1 n)). - (let* ([signed-max (sub1 (arithmetic-shift 1 (sub1 (bits i))))] - [signed-min (sub1 (- signed-max))] - [delta (if (xint-signed i) 0 signed-min)]) - (values (- signed-min delta) (- signed-max delta)))) - -(define int8 (+xint 1)) -(define int16 (+xint 2)) -(define int24 (+xint 3)) -(define int32 (+xint 4)) -(define uint8 (+xint 1 #:signed #f)) -(define uint16 (+xint 2 #:signed #f)) -(define uint24 (+xint 3 #:signed #f)) -(define uint32 (+xint 4 #:signed #f)) -(define int8be (+xint 1 #:endian 'be)) -(define int16be (+xint 2 #:endian 'be)) -(define int24be (+xint 3 #:endian 'be)) -(define int32be (+xint 4 #:endian 'be)) -(define uint8be (+xint 1 #:signed #f #:endian 'be)) -(define uint16be (+xint 2 #:signed #f #:endian 'be)) -(define uint24be (+xint 3 #:signed #f #:endian 'be)) -(define uint32be (+xint 4 #:signed #f #:endian 'be)) -(define int8le (+xint 1 #:endian 'le)) -(define int16le (+xint 2 #:endian 'le)) -(define int24le (+xint 3 #:endian 'le)) -(define int32le (+xint 4 #:endian 'le)) -(define uint8le (+xint 1 #:signed #f #:endian 'le)) -(define uint16le (+xint 2 #:signed #f #:endian 'le)) -(define uint24le (+xint 3 #:signed #f #:endian 'le)) -(define uint32le (+xint 4 #:signed #f #:endian 'le)) - -(module+ test - (require rackunit) - (check-exn exn:fail:contract? (λ () (+xint 'not-a-valid-type))) - (check-exn exn:fail:contract? (λ () (encode uint8 256 #f))) - (check-not-exn (λ () (encode uint8 255 #f))) - (check-exn exn:fail:contract? (λ () (encode int8 256 #f))) - (check-exn exn:fail:contract? (λ () (encode int8 255 #f))) - (check-not-exn (λ () (encode int8 127 #f))) - (check-not-exn (λ () (encode int8 -128 #f))) - (check-exn exn:fail:contract? (λ () (encode int8 -129 #f))) - (check-exn exn:fail:contract? (λ () (encode uint16 (add1 #xffff) #f))) - (check-not-exn (λ () (encode uint16 #xffff #f))) - - (let ([i (+xint 2 #:signed #f #:endian 'le)] - [ip (open-input-bytes (bytes 1 2 3 4))] - [op (open-output-bytes)]) - (check-equal? (decode i ip) 513) ;; 1000 0000 0100 0000 - (check-equal? (decode i ip) 1027) ;; 1100 0000 0010 0000 - (encode i 513 op) - (check-equal? (get-output-bytes op) (bytes 1 2)) - (encode i 1027 op) - (check-equal? (get-output-bytes op) (bytes 1 2 3 4))) - - (let ([i (+xint 2 #:signed #f #:endian 'be)] - [ip (open-input-bytes (bytes 1 2 3 4))] - [op (open-output-bytes)]) - (check-equal? (decode i ip) 258) ;; 0100 0000 1000 0000 - (check-equal? (decode i ip) 772) ;; 0010 0000 1100 0000 - (encode i 258 op) - (check-equal? (get-output-bytes op) (bytes 1 2)) - (encode i 772 op) - (check-equal? (get-output-bytes op) (bytes 1 2 3 4))) - - (check-equal? (size (+xint 1)) 1) - (check-equal? (size (+xint)) 2) - (check-equal? (size (+xint 4)) 4) - (check-equal? (size (+xint 8)) 8) - - (check-equal? (decode int8 (bytes 127)) 127) - (check-equal? (decode int8 (bytes 255)) -1) - (check-equal? (encode int8 -1 #f) (bytes 255)) - (check-equal? (encode int8 127 #f) (bytes 127))) - -(define/post-decode (xfloat-decode xf [port-arg (current-input-port)] #:parent [parent #f]) - (unless (xfloat? xf) - (raise-argument-error 'decode "xfloat instance" xf)) - (define bs (read-bytes (xfloat-size xf) (->input-port port-arg))) - (floating-point-bytes->real bs (eq? (xfloat-endian xf) 'be))) - -(define/pre-encode (xfloat-encode xf val [port (current-output-port)] #:parent [parent #f]) - (unless (xfloat? xf) - (raise-argument-error 'encode "xfloat instance" xf)) - (unless (or (not port) (output-port? port)) - (raise-argument-error 'encode "output port or #f" port)) - (define res (real->floating-point-bytes val (xfloat-size xf) (eq? (xfloat-endian xf) 'be))) - (if port (write-bytes res port) res)) - -(struct xfloat xnumber (size endian) #:transparent - #:methods gen:xenomorphic - [(define decode xfloat-decode) - (define encode xfloat-encode) - (define size (λ (i [item #f] #:parent [parent #f]) (xfloat-size i)))]) - -(define (+xfloat [size 4] #:endian [endian system-endian]) - (unless (exact-positive-integer? size) - (raise-argument-error '+xfloat "exact positive integer" size)) - (unless (memq endian '(le be)) - (raise-argument-error '+xfloat "'le or 'be" endian)) - (xfloat size endian)) - -(define float (+xfloat 4)) -(define floatbe (+xfloat 4 #:endian 'be)) -(define floatle (+xfloat 4 #:endian 'le)) - -(define double (+xfloat 8)) -(define doublebe (+xfloat 8 #:endian 'be)) -(define doublele (+xfloat 8 #:endian 'le)) - -(define/post-decode (xfixed-decode xf [port-arg (current-input-port)] #:parent [parent #f]) - (unless (xfixed? xf) - (raise-argument-error 'decode "xfixed instance" xf)) - (define int (xint-decode xf port-arg)) - (exact-if-possible (/ int (fixed-shift xf) 1.0))) - -(define/pre-encode (xfixed-encode xf val [port (current-output-port)] #:parent [parent #f]) - (unless (xfixed? xf) - (raise-argument-error 'encode "xfixed instance" xf)) - (define int (exact-if-possible (floor (* val (fixed-shift xf))))) - (xint-encode xf int port)) - -(struct xfixed xint (fracbits) #:transparent - #:methods gen:xenomorphic - [(define decode xfixed-decode) - (define encode xfixed-encode) - (define size (λ (i [item #f] #:parent [parent #f]) (xint-size i)))]) - -(define (+xfixed [size 2] #:signed [signed #true] #:endian [endian system-endian] [fracbits (/ (* size 8) 2)]) - (unless (exact-positive-integer? size) - (raise-argument-error '+xfixed "exact positive integer" size)) - (unless (exact-positive-integer? fracbits) - (raise-argument-error '+xfixed "exact positive integer" fracbits)) - (unless (memq endian '(le be)) - (raise-argument-error '+xfixed "'le or 'be" endian)) - (xfixed size signed endian fracbits)) - -(define (fixed-shift xf) - (arithmetic-shift 1 (xfixed-fracbits xf))) - -(define fixed16 (+xfixed 2)) -(define fixed16be (+xfixed 2 #:endian 'be)) -(define fixed16le (+xfixed 2 #:endian 'le)) -(define fixed32 (+xfixed 4)) -(define fixed32be (+xfixed 4 #:endian 'be)) -(define fixed32le (+xfixed 4 #:endian 'le)) - -(module+ test - (define bs (encode fixed16be 123.45 #f)) - (check-equal? bs #"{s") - (check-equal? (ceiling (* (decode fixed16be bs) 100)) 12345.0)) diff --git a/xenomorph/xenomorph/redo/optional.rkt b/xenomorph/xenomorph/redo/optional.rkt deleted file mode 100644 index 7480a1d4..00000000 --- a/xenomorph/xenomorph/redo/optional.rkt +++ /dev/null @@ -1,59 +0,0 @@ -#lang debug racket/base -(require "helper.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Optional.coffee -|# - -(define (resolve-condition xo parent) - (define maybe-proc (xoptional-condition xo)) - (if (procedure? maybe-proc) - (maybe-proc parent) - maybe-proc)) - -(define/post-decode (xoptional-decode xo [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (when (resolve-condition xo parent) - (decode (xoptional-type xo) #:parent parent)))) - -(define/pre-encode (xoptional-encode xo val [port-arg (current-output-port)] #:parent [parent #f]) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - (when (resolve-condition xo parent) - (encode (xoptional-type xo) val #:parent parent)) - (unless port-arg (get-output-bytes port)))) - -(define/finalize-size (xoptional-size xo [val #f] #:parent [parent #f]) - (when (resolve-condition xo parent) - (size (xoptional-type xo) val #:parent parent))) - -(struct xoptional xbase (type condition) #:transparent - #:methods gen:xenomorphic - [(define decode xoptional-decode) - (define encode xoptional-encode) - (define size xoptional-size)]) - -#;(define (+xoptional [type-arg #f] [cond-arg #f] - #:type [type-kwarg #f] - #:condition [cond-kwarg #f]) - (define type (or type-arg type-kwarg)) - (unless (xenomorphic? type) - (raise-argument-error '+xoptional"xenomorphic type" type)) - (define condition (or cond-arg cond-kwarg)) - (xoptional type condition)) - -(define no-val (gensym)) -(define (+xoptional [type-arg #f] [cond-arg no-val] - #:type [type-kwarg #f] - #:condition [cond-kwarg no-val]) - (define type (or type-arg type-kwarg)) - (unless (xenomorphic? type) - (raise-argument-error '+xoptional"xenomorphic type" type)) - (define condition (cond - [(and (eq? cond-arg no-val) (eq? cond-kwarg no-val)) #true] - [(not (eq? cond-arg no-val)) cond-arg] - [(not (eq? cond-kwarg no-val)) cond-kwarg])) - (xoptional type condition)) diff --git a/xenomorph/xenomorph/redo/pointer.rkt b/xenomorph/xenomorph/redo/pointer.rkt deleted file mode 100644 index a0060bf7..00000000 --- a/xenomorph/xenomorph/redo/pointer.rkt +++ /dev/null @@ -1,129 +0,0 @@ -#lang debug racket/base -(require "helper.rkt" - "number.rkt" - racket/dict - racket/promise - sugar/unstable/dict) -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Pointer.coffee -|# - -(define (find-top-parent parent) - (cond - [(dict-ref parent 'parent #f) => find-top-parent] - [else parent])) - -(define/post-decode (xpointer-decode xp [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (define offset (decode (xpointer-offset-type xp) #:parent parent)) - (cond - [(and allow-null (= offset (null-value xp))) #f] ; handle null pointers - [else - (define relative (+ (case (pointer-style xp) - [(local) (dict-ref parent '_startOffset)] - [(immediate) (- (pos port) (size (xpointer-offset-type xp)))] - [(parent) (dict-ref (dict-ref parent 'parent) '_startOffset)] - [(global) (or (dict-ref (find-top-parent parent) '_startOffset) 0)] - [else (error 'unknown-pointer-style)]) - ((relative-getter-or-0 xp) parent))) - (define ptr (+ offset relative)) - (cond - [(xpointer-type xp) - (define val (void)) - (define (decode-value) - (cond - [(not (void? val)) val] - [else - (define orig-pos (pos port)) - (pos port ptr) - (set! val (decode (xpointer-type xp) #:parent parent)) - (pos port orig-pos) - val])) - (if (pointer-lazy? xp) - (delay (decode-value)) - (decode-value))] - [else ptr])]))) - -(define (resolve-void-pointer type val) - (cond - [type (values type val)] - [(xvoid-pointer? val) (values (xvoid-pointer-type val) (xvoid-pointer-value val))] - [else (raise-argument-error 'Pointer:size "VoidPointer" val)])) - -(define/pre-encode (xpointer-encode xp val [port-arg (current-output-port)] #:parent [parent #f]) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (unless parent ; todo: furnish default pointer context? adapt from Struct? - (raise-argument-error 'xpointer-encode "valid pointer context" parent)) - (parameterize ([current-output-port port]) - (if (not val) - (encode (xpointer-offset-type xp) (null-value xp) port) - (let* ([new-parent (case (pointer-style xp) - [(local immediate) parent] - [(parent) (dict-ref parent 'parent)] - [(global) (find-top-parent parent)] - [else (error 'unknown-pointer-style)])] - [relative (+ (case (pointer-style xp) - [(local parent) (dict-ref new-parent 'startOffset)] - [(immediate) (+ (pos port) (size (xpointer-offset-type xp) val #:parent parent))] - [(global) 0]) - ((relative-getter-or-0 xp) (dict-ref parent 'val #f)))]) - (encode (xpointer-offset-type xp) (- (dict-ref new-parent 'pointerOffset) relative)) - (let-values ([(type val) (resolve-void-pointer (xpointer-type xp) val)]) - (dict-set! new-parent 'pointers (append (dict-ref new-parent 'pointers) - (list (mhasheq 'type type - 'val val - 'parent parent)))) - (dict-set! new-parent 'pointerOffset (+ (dict-ref new-parent 'pointerOffset) (size type val #:parent parent))))))) - (unless port-arg (get-output-bytes port))) - -(define (xpointer-size xp [val #f] #:parent [parent #f]) - (let*-values ([(parent) (case (pointer-style xp) - [(local immediate) parent] - [(parent) (dict-ref parent 'parent)] - [(global) (find-top-parent parent)] - [else (error 'unknown-pointer-style)])] - [(type val) (resolve-void-pointer (xpointer-type xp) val)]) - (when (and val parent) - (dict-set! parent 'pointerSize (and (dict-ref parent 'pointerSize #f) - (+ (dict-ref parent 'pointerSize) (size type val #:parent parent))))) - (size (xpointer-offset-type xp)))) - -(struct xpointer xbase (offset-type type options) #:transparent - #:methods gen:xenomorphic - [(define decode xpointer-decode) - (define encode xpointer-encode) - (define size xpointer-size)]) - -(define (+xpointer [offset-arg #f] [type-arg #f] - #:offset-type [offset-kwarg #f] - #:type [type-kwarg #f] - #:style [style 'local] - #:relative-to [relative-to #f] - #:lazy [lazy? #f] - #:allow-null [allow-null? #t] - #:null [null-value 0]) - (define valid-pointer-styles '(local immediate parent global)) - (unless (memq style valid-pointer-styles) - (raise-argument-error '+xpointer (format "~v" valid-pointer-styles) style)) - (define options (mhasheq 'style style - 'relativeTo relative-to - 'lazy lazy? - 'allowNull allow-null? - 'nullValue null-value)) - (define offset-type (or offset-arg offset-kwarg uint8)) - (define type-in (or type-arg type-kwarg uint8)) - (xpointer offset-type (case type-in [(void) #f][else type-in]) options)) - -(define (pointer-style xp) (dict-ref (xpointer-options xp) 'style)) -(define (allow-null xp) (dict-ref (xpointer-options xp) 'allowNull)) -(define (null-value xp) (dict-ref (xpointer-options xp) 'nullValue)) -(define (pointer-lazy? xp) (dict-ref (xpointer-options xp) 'lazy)) -(define (relative-getter-or-0 xp) (or (dict-ref (xpointer-options xp) 'relativeTo #f) (λ (parent) 0))) ; changed this to a simple lambda - -;; A pointer whose type is determined at decode time -(struct xvoid-pointer (type value) #:transparent) -(define +xvoid-pointer xvoid-pointer) diff --git a/xenomorph/xenomorph/redo/reserved.rkt b/xenomorph/xenomorph/redo/reserved.rkt deleted file mode 100644 index 9947b160..00000000 --- a/xenomorph/xenomorph/redo/reserved.rkt +++ /dev/null @@ -1,32 +0,0 @@ -#lang racket/base -(require "helper.rkt" "util.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Reserved.coffee -|# - -(define/post-decode (xreserved-decode xo [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (pos port (+ (pos port) (size xo #f #:parent parent))) - (void)) - -(define/pre-encode (xreserved-encode xo val [port-arg (current-output-port)] #:parent [parent #f]) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (write-bytes (make-bytes (size xo val #:parent parent) 0) port) - (unless port-arg (get-output-bytes port))) - -(define/finalize-size (xreserved-size xo [val #f] #:parent [parent #f]) - (define item-size (size (xreserved-type xo))) - (define count (resolve-length (xreserved-count xo) #f #:parent parent)) - (* item-size count)) - -(struct xreserved xbase (type count) #:transparent - #:methods gen:xenomorphic - [(define decode xreserved-decode) - (define encode xreserved-encode) - (define size xreserved-size)]) - -(define (+xreserved type [count 1]) - (xreserved type count)) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/string.rkt b/xenomorph/xenomorph/redo/string.rkt deleted file mode 100644 index f2cabbea..00000000 --- a/xenomorph/xenomorph/redo/string.rkt +++ /dev/null @@ -1,134 +0,0 @@ -#lang debug racket/base -(require racket/dict "helper.rkt" "util.rkt" "number.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/String.coffee -|# - -(define (read-encoded-string len [encoding 'ascii]) - (define proc (case encoding - [(utf16le) (error 'bah)] - [(ucs2) (error 'bleh)] - [(utf8) bytes->string/utf-8] - [(ascii) bytes->string/latin-1] - [else values])) - (proc (read-bytes len))) - -(define (write-encoded-string string [encoding 'ascii]) - ;; todo: handle encodings correctly. - ;; right now just utf8 and ascii are correct - (define proc (case encoding - [(ucs2 utf8 ascii) string->bytes/utf-8] - [(utf16le) (error 'swap-bytes-unimplemented)] - [else (error 'unsupported-string-encoding)])) - (write-bytes (proc string))) - -(define (count-nonzero-chars port) - ;; helper function for String - ;; counts nonzero chars from current position - (bytes-length (car (regexp-match-peek "[^\u0]*" port)))) - -(define (bytes-left-in-port? port) - (not (eof-object? (peek-byte port)))) - -(define (byte-length val encoding) - (define encoder - (case encoding - [(ascii utf8) string->bytes/utf-8])) - (bytes-length (encoder (format "~a" val)))) - -(define/post-decode (xstring-decode xs [port-arg (current-input-port)] #:parent [parent #f]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - (let ([len (or (resolve-length (xstring-len xs) #:parent parent) (count-nonzero-chars port))] - [encoding (if (procedure? (xstring-encoding xs)) - (or ((xstring-encoding xs) parent) 'ascii) - (xstring-encoding xs))] - [adjustment (if (and (not (xstring-len xs)) (bytes-left-in-port? port)) 1 0)]) - (define string (read-encoded-string len encoding)) - (pos port (+ (pos port) adjustment)) - string))) - -(define/pre-encode (xstring-encode xs val [port-arg (current-output-port)] #:parent [parent #f]) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - (let* ([val (format "~a" val)] - [encoding (if (procedure? (xstring-encoding xs)) - (or ((xstring-encoding xs) (and parent (dict-ref parent val)) 'ascii)) - (xstring-encoding xs))]) - (define encoded-length (byte-length val encoding)) - (when (and (exact-nonnegative-integer? (xstring-len xs)) (> encoded-length (xstring-len xs))) - (raise-argument-error 'xstring-encode (format "string no longer than ~a" (xstring-len xs)) val)) - (when (xint? (xstring-len xs)) - (encode (xstring-len xs) encoded-length)) - (write-encoded-string val encoding) - (when (not (xstring-len xs)) (write-byte #x00)) ; null terminated when no len - (unless port-arg (get-output-bytes port))))) - -(define/finalize-size (xstring-size xs [val #f] #:parent [parent #f]) - (cond - [val (define encoding (if (procedure? (xstring-encoding xs)) - (or ((xstring-encoding xs) (and parent (dict-ref parent val)) 'ascii)) - (xstring-encoding xs))) - (define string-size (byte-length val (if (eq? encoding 'utf16be) 'utf16le encoding))) - (define strlen-size (cond - [(not (xstring-len xs)) 1] - [(xint? (xstring-len xs)) (size (xstring-len xs))] - [else 0])) - (+ string-size strlen-size)] - [else (resolve-length (xstring-len xs) #f #:parent parent)])) - -(struct xstring xbase (len encoding) #:transparent - #:methods gen:xenomorphic - [(define decode xstring-decode) - (define encode xstring-encode) - (define size xstring-size)]) - -(define supported-encodings '(ascii utf8)) -(define (+xstring [len-arg #f] [enc-arg #f] - #:length [len-kwarg #f] #:encoding [enc-kwarg #f]) - (define len (or len-arg len-kwarg)) - (define encoding (or enc-arg enc-kwarg 'ascii)) - (unless (length-resolvable? len) - (raise-argument-error '+xarray "length-resolvable?" len)) - (unless (or (procedure? encoding) (memq encoding supported-encodings)) - (raise-argument-error '+xarray (format "procedure or member of ~v" supported-encodings) encoding)) - (xstring len encoding)) - -(define (xsymbol-decode xs [port-arg (current-input-port)] #:parent [parent #f]) - (string->symbol (xstring-decode xs port-arg #:parent parent))) - -(define (xsymbol-encode xs val [port (current-output-port)] #:parent [parent #f]) - (unless (xsymbol? xs) - (raise-argument-error 'encode "xsymbol instance" xs)) - (unless (or (string? val) (symbol? val)) - (raise-argument-error 'xsymbol-encode "symbol or string" val)) - (xstring-encode xs (if (symbol? val) val (string->symbol val)) port #:parent parent)) - -(struct xsymbol xstring () #:transparent - #:methods gen:xenomorphic - [(define decode xsymbol-decode) - (define encode xsymbol-encode) - (define size xstring-size)]) - -(define (+xsymbol [len-arg #f] [enc-arg #f] - #:length [len-kwarg #f] #:encoding [enc-kwarg #f]) - (define len (or len-arg len-kwarg)) - (define encoding (or enc-arg enc-kwarg 'ascii)) - (xsymbol len encoding)) - -(module+ test - (require rackunit) - (define S-fixed (+xstring 4 'utf8)) - (check-equal? (encode S-fixed "Mike" #f) #"Mike") - (check-exn exn:fail? (λ () (encode S-fixed "Mikes" #f))) ; too long for fixed string - (define S (+xstring uint8 'utf8)) - (check-equal? (decode S #"\2BCDEF") "BC") - (check-equal? (encode S "Mike" #f) #"\4Mike") - (check-equal? (size (+xstring) "foobar") 7) ; null terminated when no len - (check-equal? (decode (+xsymbol 4) #"Mike") 'Mike) - (check-equal? (encode (+xsymbol 4) 'Mike #f) #"Mike") - (check-equal? (encode (+xsymbol 4) "Mike" #f) #"Mike") - (check-exn exn:fail:contract? (λ () (encode (+xsymbol 4) 42 #f)))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/struct.rkt b/xenomorph/xenomorph/redo/struct.rkt deleted file mode 100644 index 0457409f..00000000 --- a/xenomorph/xenomorph/redo/struct.rkt +++ /dev/null @@ -1,156 +0,0 @@ -#lang debug racket/base -(require (prefix-in d: racket/dict) - racket/promise - racket/sequence - racket/list - "helper.rkt" - "number.rkt" - sugar/unstable/dict) -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee -|# - -(define private-keys '(parent _startOffset _currentOffset _length)) - -(define (choose-dict d k) - (if (memq k private-keys) - (struct-dict-res-_pvt d) - (struct-dict-res-_kv d))) - -(struct struct-dict-res (_kv _pvt) #:transparent - #:methods d:gen:dict - [(define (dict-set! d k v) (d:dict-set! (choose-dict d k) k v)) - (define (dict-ref d k [thunk #f]) - (define res (d:dict-ref (choose-dict d k) k thunk)) - (force res)) - (define (dict-remove! d k) (d:dict-remove! (choose-dict d k) k)) - ;; public keys only - (define (dict-keys d) (d:dict-keys (struct-dict-res-_kv d))) - (define (dict-iterate-first d) (and (pair? (dict-keys d)) 0)) - (define (dict-iterate-next d i) (and (< (add1 i) (length (dict-keys d))) (add1 i))) - (define (dict-iterate-key d i) (list-ref (dict-keys d) i)) - (define (dict-iterate-value d i) (dict-ref d (dict-iterate-key d i)))]) - -(define (+struct-dict-res [_kv (mhasheq)] [_pvt (mhasheq)]) - (struct-dict-res _kv _pvt)) - -(define (_setup port parent len) - (define sdr (+struct-dict-res)) ; not mere hash - (d:dict-set*! sdr 'parent parent - '_startOffset (pos port) - '_currentOffset 0 - '_length len) - sdr) - -(define (_parse-fields port sdr fields) - (unless (assocs? fields) - (raise-argument-error '_parse-fields "assocs" fields)) - (for/fold ([sdr sdr]) - ([(key type) (d:in-dict fields)]) - (define val (if (procedure? type) - (type sdr) - (decode type port #:parent sdr))) - (unless (void? val) - (d:dict-set! sdr key val)) - (d:dict-set! sdr '_currentOffset (- (pos port) (d:dict-ref sdr '_startOffset))) - sdr)) - -(define (sdr-to-hash sdr) - (for/hasheq ([(k v) (d:in-dict sdr)] - #:unless (memq k private-keys)) - (values k v))) - -(define-syntax-rule (decode/hash . ARGS) - (sdr-to-hash (decode . ARGS))) - -(define (xstruct-decode xs [port-arg (current-input-port)] #:parent [parent #f] [len 0]) - (define port (->input-port port-arg)) - (parameterize ([current-input-port port]) - ;; _setup and _parse-fields are separate to cooperate with VersionedStruct - (define res - (post-decode xs - (let* ([sdr (_setup port parent len)] ; returns StructDictRes - [sdr (_parse-fields port sdr (xstruct-fields xs))]) - sdr))) - (unless (d:dict? res) - (raise-result-error 'xstruct-decode "dict" res)) - res)) - -(define/finalize-size (xstruct-size xs [val #f] #:parent [parent-arg #f] [include-pointers #t]) - (define parent (mhasheq 'parent parent-arg - 'val val - 'pointerSize 0)) - (define fields-size (for/sum ([(key type) (d:in-dict (xstruct-fields xs))] - #:when (xenomorphic? type)) - (size type (and val (d:dict-ref val key)) #:parent parent))) - (define pointers-size (if include-pointers (d:dict-ref parent 'pointerSize) 0)) - (+ fields-size pointers-size)) - -(define (xstruct-encode xs val-arg [port-arg (current-output-port)] #:parent [parent-arg #f]) - (unless (d:dict? val-arg) - (raise-argument-error 'xstruct-encode "dict" val-arg)) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - ;; check keys first, since `size` also relies on keys being valid - (define val (let* ([val (pre-encode xs val-arg)] - #;[val (inner res pre-encode val . args)]) - (unless (d:dict? val) (raise-result-error 'xstruct-encode "dict" val)) - val)) - (unless (andmap (λ (key) (memq key (d:dict-keys val))) (d:dict-keys (xstruct-fields xs))) - (raise-argument-error 'xstruct-encode - (format "dict that contains superset of Struct keys: ~a" (d:dict-keys (xstruct-fields xs))) (d:dict-keys val))) - - (define parent (mhash 'pointers empty - 'startOffset (pos port) - 'parent parent-arg - 'val val - 'pointerSize 0)) - - ; deliberately use `xstruct-size` instead of `size` to use extra arg - (d:dict-set! parent 'pointerOffset (+ (pos port) (xstruct-size xs val #:parent parent #f))) - - (for ([(key type) (d:in-dict (xstruct-fields xs))]) - (encode type (d:dict-ref val key) #:parent parent)) - (for ([ptr (in-list (d:dict-ref parent 'pointers))]) - (encode (d:dict-ref ptr 'type) (d:dict-ref ptr 'val) #:parent (d:dict-ref ptr 'parent))) - (unless port-arg (get-output-bytes port)))) - -(struct structish xbase () #:transparent) -(struct xstruct structish (fields) #:transparent #:mutable - #:methods gen:xenomorphic - [(define decode xstruct-decode) - (define encode xstruct-encode) - (define size xstruct-size)]) - -(define (+xstruct . dicts) - (define args (flatten dicts)) - (unless (even? (length args)) - (raise-argument-error '+xstruct "equal keys and values" dicts)) - (define fields (for/list ([kv (in-slice 2 args)]) - (unless (symbol? (car kv)) - (raise-argument-error '+xstruct "symbol" (car kv))) - (apply cons kv))) - (unless (d:dict? fields) - (raise-argument-error '+xstruct "dict" fields)) - (xstruct fields)) - -(module+ test - (require rackunit "number.rkt") - (define (random-pick xs) (list-ref xs (random (length xs)))) - (check-exn exn:fail:contract? (λ () (+xstruct 42))) - (for ([i (in-range 20)]) - ;; make random structs and make sure we can round trip - (define field-types - (for/list ([i (in-range 40)]) - (random-pick (list uint8 uint16be uint16le uint32be uint32le double)))) - (define size-num-types - (for/sum ([num-type (in-list field-types)]) - (size num-type))) - (define xs (+xstruct (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))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/array-test.rkt b/xenomorph/xenomorph/redo/test/array-test.rkt deleted file mode 100644 index b4a52db5..00000000 --- a/xenomorph/xenomorph/redo/test/array-test.rkt +++ /dev/null @@ -1,106 +0,0 @@ -#lang racket/base -(require rackunit - "../helper.rkt" - "../array.rkt" - "../number.rkt" - "../pointer.rkt" - sugar/unstable/dict) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Array.coffee -|# - -(test-case - "decode fixed length" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint8 4)) '(1 2 3 4)))) - -(test-case - "decode with post-decode" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define xa (+xarray uint8 4)) - (set-post-decode! xa (λ (val . _) (map (λ (x) (* 2 x)) val))) - (check-equal? (decode xa) '(2 4 6 8)))) - -(test-case - "decode fixed number of bytes" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint16be 4 'bytes)) '(258 772)))) - -(test-case - "decode length from parent key" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint8 'len) #:parent (mhash 'len 4)) '(1 2 3 4)))) - -(test-case - "decode byte count from parent key" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint16be 'len 'bytes) #:parent (mhash 'len 4)) '(258 772)))) - -(test-case - "decode length as number before array" - (parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint8 uint8)) '(1 2 3 4)))) - -(test-case - "decode byte count as number before array" - (parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint16be uint8 'bytes)) '(258 772)))) - -(test-case - "decode length from function" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint8 (λ _ 4))) '(1 2 3 4)))) - -(test-case - "decode byte count from function" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint16be (λ _ 4) 'bytes)) '(258 772)))) - -(test-case - "decode to the end of parent if no length given" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint8) #:parent (mhash '_length 4 '_startOffset 0)) '(1 2 3 4)))) - -(test-case - "decode to the end of the stream if parent exists, but its length is 0" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+xarray uint8) #:parent (mhash '_length 0 '_startOffset 0)) '(1 2 3 4 5)))) - -(test-case - "decode to the end of the stream if no parent and length is given" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4))]) - (check-equal? (decode (+xarray uint8)) '(1 2 3 4 )))) - -(test-case - "use array length" - (check-equal? (size (+xarray uint8 10) '(1 2 3 4)) 4)) - -(test-case - "add size of length field before string" - (check-equal? (size (+xarray uint8 uint8) '(1 2 3 4)) 5)) - -(test-case - "use defined length if no value given" - (check-equal? (size (+xarray uint8 10)) 10)) - -(test-case - "encode using array length" - (check-equal? (encode (+xarray uint8 10) '(1 2 3 4) #f) (bytes 1 2 3 4))) - -(test-case - "encode with pre-encode" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define xa (+xarray uint8 4)) - (set-pre-encode! xa (λ (val . _) (map (λ (x) (* 2 x)) val))) - (check-equal? (encode xa '(1 2 3 4) #f) (bytes 2 4 6 8)))) - -(test-case - "encode length as number before array" - (check-equal? (encode (+xarray uint8 uint8) '(1 2 3 4) #f) (bytes 4 1 2 3 4))) - -(test-case - "add pointers after array if length is encoded at start" - (check-equal? (encode (+xarray (+xpointer #:offset-type uint8 - #:type uint8) uint8) '(1 2 3 4) #f) (bytes 4 5 6 7 8 1 2 3 4))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/bitfield-test.rkt b/xenomorph/xenomorph/redo/test/bitfield-test.rkt deleted file mode 100644 index 883285c3..00000000 --- a/xenomorph/xenomorph/redo/test/bitfield-test.rkt +++ /dev/null @@ -1,76 +0,0 @@ -#lang racket/base -(require rackunit - racket/match - racket/list - sugar/unstable/dict - "../helper.rkt" - "../number.rkt" - "../bitfield.rkt") - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Bitfield.coffee -|# - -(define bitfield (+xbitfield uint8 '(Jack Kack Lack Mack Nack Oack Pack Quack))) -(match-define (list JACK KACK LACK MACK NACK OACK PACK QUACK) - (map (λ (x) (arithmetic-shift 1 x)) (range 8))) - -(test-case - "bitfield should have the right size" - (check-equal? (size bitfield) 1)) - -(test-case - "bitfield should decode" - (parameterize ([current-input-port (open-input-bytes (bytes (bitwise-ior JACK MACK PACK NACK QUACK)))]) - (check-equal? (decode bitfield) (mhasheq 'Quack #t - 'Nack #t - 'Lack #f - 'Oack #f - 'Pack #t - 'Mack #t - 'Jack #t - 'Kack #f)))) - -(test-case - "bitfield should decode with post-decode" - (parameterize ([current-input-port (open-input-bytes (bytes (bitwise-ior JACK MACK PACK NACK QUACK)))]) - (set-post-decode! bitfield (λ (fh . _) (hash-set! fh 'foo 42) fh)) - (check-equal? (decode bitfield) (mhasheq 'Quack #t - 'Nack #t - 'Lack #f - 'Oack #f - 'Pack #t - 'Mack #t - 'Jack #t - 'Kack #f - 'foo 42)))) - -(test-case - "bitfield should encode" - (check-equal? (encode bitfield (mhasheq 'Quack #t - 'Nack #t - 'Lack #f - 'Oack #f - 'Pack #t - 'Mack #t - 'Jack #t - 'Kack #f) #f) - (bytes (bitwise-ior JACK MACK PACK NACK QUACK)))) - -(test-case - "bitfield should encode with pre-encode" - (set-pre-encode! bitfield (λ (fh . _) - (hash-set! fh 'Jack #f) - (hash-set! fh 'Mack #f) - (hash-set! fh 'Pack #f) - fh)) - (check-equal? (encode bitfield (mhasheq 'Quack #t - 'Nack #t - 'Lack #f - 'Oack #f - 'Pack #t - 'Mack #t - 'Jack #t - 'Kack #f) #f) - (bytes (bitwise-ior NACK QUACK)))) diff --git a/xenomorph/xenomorph/redo/test/buffer-test.rkt b/xenomorph/xenomorph/redo/test/buffer-test.rkt deleted file mode 100644 index d62a5266..00000000 --- a/xenomorph/xenomorph/redo/test/buffer-test.rkt +++ /dev/null @@ -1,64 +0,0 @@ -#lang racket/base -(require rackunit - sugar/unstable/dict - "../buffer.rkt" - "../number.rkt" - "../helper.rkt") - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Buffer.coffee -|# - -(test-case - "buffer should decode" - (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) - (define buf (+xbuffer #:length 2)) - (check-equal? (decode buf) (bytes #xab #xff)) - (check-equal? (decode buf) (bytes #x1f #xb6)))) - -(test-case - "buffer should error on invalid length" - (check-exn exn:fail:contract? (λ () (+xbuffer #:length #true)))) - -(test-case - "buffer should decode with post-decode" - (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) - (define buf (+xbuffer #:length 2)) - (set-post-decode! buf (λ (bs) (bytes 1 2))) - (check-equal? (decode buf) (bytes 1 2)) - (check-equal? (decode buf) (bytes 1 2)))) - -(test-case - "buffer should decode with parent key length" - (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) - (define buf (+xbuffer #:length 'len)) - (check-equal? (decode buf #:parent (hash 'len 3)) (bytes #xab #xff #x1f)) - (check-equal? (decode buf #:parent (hash 'len 1)) (bytes #xb6)))) - -(test-case - "size should return size" - (check-equal? (size (+xbuffer #:length 2) (bytes #xab #xff)) 2)) - -(test-case - "size should use defined length if no value given" - (check-equal? (size (+xbuffer #:length 10)) 10)) - -(test-case - "encode should encode" - (let ([buf (+xbuffer 2)]) - (check-equal? (bytes-append - (encode buf (bytes #xab #xff) #f) - (encode buf (bytes #x1f #xb6) #f)) (bytes #xab #xff #x1f #xb6)))) - -(test-case - "encode should encode with pre-encode" - (let ([buf (+xbuffer 2)]) - (set-pre-encode! buf (λ (bs) (bytes 1 2))) - (check-equal? (bytes-append - (encode buf (bytes #xab #xff) #f) - (encode buf (bytes #x1f #xb6) #f)) (bytes 1 2 1 2)))) - -(test-case - "encode should encode length before buffer" - (check-equal? (encode (+xbuffer #:length uint8) (bytes #xab #xff) #f) (bytes 2 #xab #xff))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/enum-test.rkt b/xenomorph/xenomorph/redo/test/enum-test.rkt deleted file mode 100644 index 7fd73d22..00000000 --- a/xenomorph/xenomorph/redo/test/enum-test.rkt +++ /dev/null @@ -1,64 +0,0 @@ -#lang racket/base -(require rackunit - sugar/unstable/dict - "../helper.rkt" - "../number.rkt" - "../enum.rkt") - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Enum.coffee -|# - -(define e (+xenum #:type uint8 - #:values '("foo" "bar" "baz"))) - -(test-case - "should error with invalid type" - (check-exn exn:fail:contract? (λ () (+xenum 42)))) - -(test-case - "should error with invalid values" - (check-exn exn:fail:contract? (λ () (+xenum #:values 42)))) - -(test-case - "should have the right size" - (check-equal? (size e) 1)) - -(test-case - "decode should decode" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 0))]) - (check-equal? (decode e) "bar") - (check-equal? (decode e) "baz") - (check-equal? (decode e) "foo"))) - -(test-case - "decode should decode with post-decode" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 0))]) - (set-post-decode! e (λ (val) "foobar")) - (check-equal? (decode e) "foobar") - (check-equal? (decode e) "foobar") - (check-equal? (decode e) "foobar"))) - -(test-case - "encode should encode" - (parameterize ([current-output-port (open-output-bytes)]) - (encode e "bar") - (encode e "baz") - (encode e "foo") - (check-equal? (dump (current-output-port)) (bytes 1 2 0)))) - -(test-case - "encode should encode with pre-encode" - (parameterize ([current-output-port (open-output-bytes)]) - (set-pre-encode! e (λ (val) "foo")) - (encode e "bar") - (encode e "baz") - (encode e "foo") - (check-equal? (dump (current-output-port)) (bytes 0 0 0)))) - -(test-case - "should throw on unknown option" - (set-pre-encode! e values) - (set-post-decode! e values) - (check-exn exn:fail:contract? (λ () (encode e "unknown" (open-output-bytes))))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/lazy-array-test.rkt b/xenomorph/xenomorph/redo/test/lazy-array-test.rkt deleted file mode 100644 index e1d76c0d..00000000 --- a/xenomorph/xenomorph/redo/test/lazy-array-test.rkt +++ /dev/null @@ -1,76 +0,0 @@ -#lang racket/base -(require rackunit - racket/dict - racket/stream - "../array.rkt" - "../helper.rkt" - "../number.rkt" - "../lazy-array.rkt") - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/LazyArray.coffee -|# - -(test-case - "decode should decode items lazily" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define xla (+xlazy-array uint8 4)) - (define arr (decode xla)) - (check-false (xarray? arr)) - (check-equal? (stream-length arr) 4) - (check-equal? (pos (current-input-port)) 4) - (check-equal? (stream-ref arr 0) 1) - (check-equal? (stream-ref arr 1) 2) - (check-equal? (stream-ref arr 2) 3) - (check-equal? (stream-ref arr 3) 4))) - -(test-case - "decode should decode items lazily with post-decode" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define xla (+xlazy-array uint8 4)) - (set-post-decode! xla (λ (val) (* 2 val))) - (define arr (decode xla)) - (check-false (xarray? arr)) - (check-equal? (stream-length arr) 4) - (check-equal? (pos (current-input-port)) 4) - (check-equal? (stream-ref arr 0) 2) - (check-equal? (stream-ref arr 1) 4) - (check-equal? (stream-ref arr 2) 6) - (check-equal? (stream-ref arr 3) 8))) - -(test-case - "should be able to convert to an array" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define xla (+xlazy-array uint8 4)) - (define arr (decode xla)) - (check-equal? (stream->list arr) '(1 2 3 4)))) - -(test-case - "decode should decode length as number before array" - (parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) - (define xla (+xlazy-array uint8 uint8)) - (define arr (decode xla)) - (check-equal? (stream->list arr) '(1 2 3 4)))) - -(test-case - "size should work with xlazy-arrays" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define xla (+xlazy-array uint8 4)) - (define arr (decode xla)) - (check-equal? (size xla arr) 4))) - -(test-case - "encode should work with xlazy-arrays" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define xla (+xlazy-array uint8 4)) - (define arr (decode xla)) - (check-equal? (encode xla arr #f) (bytes 1 2 3 4)))) - -(test-case - "encode should work with xlazy-arrays with pre-encode" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define xla (+xlazy-array uint8 4)) - (set-pre-encode! xla (λ (vals) (map (λ (val) (* 2 val)) vals))) - (define arr (decode xla)) - (check-equal? (encode xla arr #f) (bytes 2 4 6 8)))) diff --git a/xenomorph/xenomorph/redo/test/number-test.rkt b/xenomorph/xenomorph/redo/test/number-test.rkt deleted file mode 100644 index a10cccfb..00000000 --- a/xenomorph/xenomorph/redo/test/number-test.rkt +++ /dev/null @@ -1,209 +0,0 @@ -#lang racket/base -(require rackunit "../number.rkt" "../helper.rkt") - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Number.coffee -|# - -(test-case - "uint8: decode, size, encode" - (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff))]) - (check-equal? (decode uint8) #xab) - (check-equal? (decode uint8) #xff)) - (check-equal? (size uint8) 1) - (let ([port (open-output-bytes)]) - (encode uint8 #xab port) - (encode uint8 #xff port) - (check-equal? (dump port) (bytes #xab #xff)))) - -(test-case - "uint8: decode with post-decode, size, encode with pre-encode" - (define myuint8 (+xint 1 #:signed #f)) - (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff))]) - (set-post-decode! myuint8 (λ (b) #xdeadbeef)) - (check-equal? (decode myuint8) #xdeadbeef) - (check-equal? (decode myuint8) #xdeadbeef)) - (check-equal? (size myuint8) 1) - (let ([port (open-output-bytes)]) - (set-pre-encode! myuint8 (λ (b) #xcc)) - (encode myuint8 #xab port) - (encode myuint8 #xff port) - (check-equal? (dump port) (bytes #xcc #xcc)))) - -(test-case - "uint16 is the same endianness as the platform" - (check-equal? (decode uint16 (bytes 0 1)) - (decode (if (system-big-endian?) uint16be uint16le) (bytes 0 1)))) - -(test-case - "uint16be: decode, size, encode" - (check-equal? (decode uint16be (open-input-bytes (bytes #xab #xff))) #xabff) - (check-equal? (size uint16be) 2) - (check-equal? (encode uint16be #xabff #f) (bytes #xab #xff))) - -(test-case - "uint16le: decode, size, encode" - (check-equal? (decode uint16le (open-input-bytes (bytes #xff #xab))) #xabff) - (check-equal? (size uint16le) 2) - (check-equal? (encode uint16le #xabff #f) (bytes #xff #xab))) - -(test-case - "uint24 is the same endianness as the platform" - (check-equal? (decode uint24 (bytes 0 1 2)) - (decode (if (system-big-endian?) uint24be uint24le) (bytes 0 1 2)))) -(test-case - "uint24be: decode, size, encode" - (check-equal? (decode uint24be (open-input-bytes (bytes #xff #xab #x24))) #xffab24) - (check-equal? (size uint24be) 3) - (check-equal? (encode uint24be #xffab24 #f) (bytes #xff #xab #x24))) - -(test-case - "uint24le: decode, size, encode" - (check-equal? (decode uint24le (open-input-bytes (bytes #x24 #xab #xff))) #xffab24) - (check-equal? (size uint24le) 3) - (check-equal? (encode uint24le #xffab24 #f) (bytes #x24 #xab #xff))) - -(test-case - "uint32 is the same endianness as the platform" - (check-equal? (decode uint32 (bytes 0 1 2 3)) - (decode (if (system-big-endian?) uint32be uint32le) (bytes 0 1 2 3)))) -(test-case - "uint32be: decode, size, encode" - (check-equal? (decode uint32be (open-input-bytes (bytes #xff #xab #x24 #xbf))) #xffab24bf) - (check-equal? (size uint32be) 4) - (check-equal? (encode uint32be #xffab24bf #f) (bytes #xff #xab #x24 #xbf))) - -(test-case - "uint32le: decode, size, encode" - (check-equal? (decode uint32le (open-input-bytes (bytes #xbf #x24 #xab #xff))) #xffab24bf) - (check-equal? (size uint32le) 4) - (check-equal? (encode uint32le #xffab24bf #f) (bytes #xbf #x24 #xab #xff))) - -(test-case - "int8: decode, size, encode" - (let ([port (open-input-bytes (bytes #x7f #xff))]) - (check-equal? (decode int8 port) 127) - (check-equal? (decode int8 port) -1)) - (check-equal? (size int8) 1) - (let ([port (open-output-bytes)]) - (encode int8 127 port) - (encode int8 -1 port) - (check-equal? (dump port) (bytes #x7f #xff)))) - -(test-case - "int32 is the same endianness as the platform" - (check-equal? (decode int16 (bytes 0 1)) - (decode (if (system-big-endian?) int16be int16le) (bytes 0 1)))) -(test-case - "int16be: decode, size, encode" - (let ([port (open-input-bytes (bytes #xff #xab))]) - (check-equal? (decode int16be port) -85)) - (check-equal? (size int16be) 2) - (let ([port (open-output-bytes)]) - (encode int16be -85 port) - (check-equal? (dump port) (bytes #xff #xab)))) - -(test-case - "int16le: decode, size, encode" - (check-equal? (decode int16le (open-input-bytes (bytes #xab #xff))) -85) - (check-equal? (size int16le) 2) - (check-equal? (encode int16le -85 #f) (bytes #xab #xff))) - -(test-case - "int24 is the same endianness as the platform" - (check-equal? (decode int24 (bytes 0 1 2)) - (decode (if (system-big-endian?) int24be int24le) (bytes 0 1 2)))) -(test-case - "int24be: decode, size, encode" - (check-equal? (decode int24be (open-input-bytes (bytes #xff #xab #x24))) -21724) - (check-equal? (size int24be) 3) - (check-equal? (encode int24be -21724 #f) (bytes #xff #xab #x24))) - -(test-case - "int24le: decode, size, encode" - (check-equal? (decode int24le (open-input-bytes (bytes #x24 #xab #xff))) -21724) - (check-equal? (size int24le) 3) - (check-equal? (encode int24le -21724 #f) (bytes #x24 #xab #xff))) -(test-case - "int32 is the same endianness as the platform" - (check-equal? (decode int32 (bytes 0 1 2 3)) - (decode (if (system-big-endian?) int32be int32le) (bytes 0 1 2 3)))) - -(test-case - "int32be: decode, size, encode" - (check-equal? (decode int32be (open-input-bytes (bytes #xff #xab #x24 #xbf))) -5561153) - (check-equal? (size int32be) 4) - (check-equal? (encode int32be -5561153 #f) (bytes #xff #xab #x24 #xbf))) - -(test-case - "int32le: decode, size, encode" - (check-equal? (decode int32le (open-input-bytes (bytes #xbf #x24 #xab #xff))) -5561153) - (check-equal? (size int32le) 4) - (check-equal? (encode int32le -5561153 #f) (bytes #xbf #x24 #xab #xff))) - -(test-case - "float is the same endianness as the platform" - (check-equal? (decode float (bytes 0 1 2 3)) - (decode (if (system-big-endian?) floatbe floatle) (bytes 0 1 2 3)))) -(test-case - "floatbe: decode, size, encode" - (check-= (decode floatbe (open-input-bytes (bytes #x43 #x7a #x8c #xcd))) 250.55 0.01) - (check-equal? (size floatbe) 4) - (check-equal? (encode floatbe 250.55 #f) (bytes #x43 #x7a #x8c #xcd))) - -(test-case - "floatle: decode, size, encode" - (check-= (decode floatle (open-input-bytes (bytes #xcd #x8c #x7a #x43))) 250.55 0.01) - (check-equal? (size floatle) 4) - (check-equal? (encode floatle 250.55 #f) (bytes #xcd #x8c #x7a #x43))) - -(test-case - "double is the same endianness as the platform" - (check-equal? (decode double (bytes 0 1 2 3 4 5 6 7)) - (decode (if (system-big-endian?) doublebe doublele) (bytes 0 1 2 3 4 5 6 7)))) -(test-case - "doublebe: decode, size, encode" - (check-equal? (decode doublebe (open-input-bytes (bytes #x40 #x93 #x4a #x3d #x70 #xa3 #xd7 #x0a))) 1234.56) - (check-equal? (size doublebe) 8) - (check-equal? (encode doublebe 1234.56 #f) (bytes #x40 #x93 #x4a #x3d #x70 #xa3 #xd7 #x0a))) - -(test-case - "doublele: decode, size, encode" - (check-equal? (decode doublele (open-input-bytes (bytes #x0a #xd7 #xa3 #x70 #x3d #x4a #x93 #x40))) 1234.56) - (check-equal? (size doublele) 8) - (check-equal? (encode doublele 1234.56 #f) (bytes #x0a #xd7 #xa3 #x70 #x3d #x4a #x93 #x40))) - -(test-case - "fixed16 is the same endianness as the platform" - (check-equal? (decode fixed16 (bytes 0 1)) - (decode (if (system-big-endian?) fixed16be fixed16le) (bytes 0 1)))) - -(test-case - "fixed16be: decode, size, encode" - (check-= (decode fixed16be (open-input-bytes (bytes #x19 #x57))) 25.34 0.01) - (check-equal? (size fixed16be) 2) - (check-equal? (encode fixed16be 25.34 #f) (bytes #x19 #x57))) - -(test-case - "fixed16le: decode, size, encode" - (check-= (decode fixed16le (open-input-bytes (bytes #x57 #x19))) 25.34 0.01) - (check-equal? (size fixed16le) 2) - (check-equal? (encode fixed16le 25.34 #f) (bytes #x57 #x19))) - -(test-case - "fixed32 is the same endianness as the platform" - (check-equal? (decode fixed32 (bytes 0 1 2 3)) - (decode (if (system-big-endian?) fixed32be fixed32le) (bytes 0 1 2 3)))) - -(test-case - "fixed32be: decode, size, encode" - (check-= (decode fixed32be (open-input-bytes (bytes #x00 #xfa #x8c #xcc))) 250.55 0.01) - (check-equal? (size fixed32be) 4) - (check-equal? (encode fixed32be 250.55 #f) (bytes #x00 #xfa #x8c #xcc))) - -(test-case - "fixed32le: decode, size, encode" - (check-= (decode fixed32le (open-input-bytes (bytes #xcc #x8c #xfa #x00))) 250.55 0.01) - (check-equal? (size fixed32le) 4) - (check-equal? (encode fixed32le 250.55 #f) (bytes #xcc #x8c #xfa #x00))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/optional-test.rkt b/xenomorph/xenomorph/redo/test/optional-test.rkt deleted file mode 100644 index e3749772..00000000 --- a/xenomorph/xenomorph/redo/test/optional-test.rkt +++ /dev/null @@ -1,116 +0,0 @@ -#lang racket/base -(require rackunit - "../helper.rkt" - "../number.rkt" - "../optional.rkt") - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Optional.coffee -|# - -(test-case - "decode should not decode when condition is falsy" - (parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+xoptional #:type uint8 #:condition #f)) - (check-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 0))) - -(test-case - "decode with post-decode" - (parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+xoptional #:type uint8 #:condition #f)) - (set-post-decode! optional (λ (val) 42)) - (check-equal? (decode optional) 42) - (check-equal? (pos (current-input-port)) 0))) - -(test-case - "decode should not decode when condition is a function and falsy" - (parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+xoptional #:type uint8 #:condition (λ _ #f))) - (check-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 0))) - -(test-case - "decode should decode when condition is omitted" - (parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+xoptional #:type uint8)) - (check-not-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 1))) - -(test-case - "decode should decode when condition is truthy" - (parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+xoptional #:type uint8 #:condition #t)) - (check-not-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 1))) - -(test-case - "decode should decode when condition is a function and truthy" - (parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+xoptional #:type uint8 #:condition (λ _ #t))) - (check-not-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 1))) - -(test-case - "size" - (check-equal? (size (+xoptional #:type uint8 #:condition #f)) 0)) - -(test-case - "size should return 0 when condition is a function and falsy" - (check-equal? (size (+xoptional #:type uint8 #:condition (λ _ #f))) 0)) - -(test-case - "size should return given type size when condition is omitted" - (check-equal? (size (+xoptional #:type uint8)) 1)) - -(test-case - "size should return given type size when condition is truthy" - (check-equal? (size (+xoptional #:type uint8 #:condition #t)) 1)) - -(test-case - "size should return given type size when condition is a function and truthy" - (check-equal? (size (+xoptional #:type uint8 #:condition (λ _ #t))) 1)) - -(test-case - "encode should not encode when condition is falsy" - (parameterize ([current-output-port (open-output-bytes)]) - (define optional (+xoptional #:type uint8 #:condition #f)) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes)))) - -(test-case - "encode with pre-encode" - (parameterize ([current-output-port (open-output-bytes)]) - (define optional (+xoptional #:type uint8)) - (set-pre-encode! optional (λ (val) 42)) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes 42)))) - -(test-case - "encode should not encode when condition is a function and falsy" - (parameterize ([current-output-port (open-output-bytes)]) - (define optional (+xoptional #:type uint8 #:condition (λ _ #f))) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes)))) - -(test-case - "encode should encode when condition is omitted" - (parameterize ([current-output-port (open-output-bytes)]) - (define optional (+xoptional #:type uint8)) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes 128)))) - -(test-case - "encode should encode when condition is truthy" - (parameterize ([current-output-port (open-output-bytes)]) - (define optional (+xoptional #:type uint8 #:condition #t)) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes 128)))) - -(test-case - "encode should encode when condition is a function and truthy" - (parameterize ([current-output-port (open-output-bytes)]) - (define optional (+xoptional #:type uint8 #:condition (λ _ #t))) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes 128)))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/pointer-test.rkt b/xenomorph/xenomorph/redo/test/pointer-test.rkt deleted file mode 100644 index 1178b8f8..00000000 --- a/xenomorph/xenomorph/redo/test/pointer-test.rkt +++ /dev/null @@ -1,211 +0,0 @@ -#lang debug racket/base -(require rackunit - racket/dict - "../helper.rkt" - "../pointer.rkt" - "../number.rkt" - "../struct.rkt" - racket/promise - sugar/unstable/dict) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Pointer.coffee -|# - -(test-case - "decode should handle null pointers" - (parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (check-false (decode (+xpointer) #:parent (mhash '_startOffset 50))))) - -(test-case - "decode should use local offsets from start of parent by default" - (parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) - (check-equal? (decode (+xpointer) #:parent (mhash '_startOffset 0)) 53))) - -(test-case - "decode should support immediate offsets" - (parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) - (check-equal? (decode (+xpointer #:style 'immediate)) 53))) - -(test-case - "decode should support offsets relative to the parent" - (parameterize ([current-input-port (open-input-bytes (bytes 0 0 1 53))]) - (pos (current-input-port) 2) - (check-equal? (decode (+xpointer #:style 'parent) - #:parent (mhash 'parent (mhash '_startOffset 2))) 53))) - -(test-case - "decode should support global offsets" - (parameterize ([current-input-port (open-input-bytes (bytes 1 2 4 0 0 0 53))]) - (pos (current-input-port) 2) - (check-equal? (decode (+xpointer #:style 'global) - #:parent (mhash 'parent (mhash 'parent (mhash '_startOffset 2)))) - 53))) - -(test-case - "decode should support offsets relative to a property on the parent" - (parameterize ([current-input-port (open-input-bytes (bytes 1 0 0 0 0 53))]) - (check-equal? (decode (+xpointer #:relative-to (λ (parent) (dict-ref (dict-ref parent 'parent) 'ptr))) - #:parent (mhash '_startOffset 0 'parent (mhash 'ptr 4))) - 53))) - -(test-case - "decode should support returning pointer if there is no decode type" - (parameterize ([current-input-port (open-input-bytes (bytes 4))]) - (check-equal? (decode (+xpointer uint8 'void) - #:parent (mhash '_startOffset 0)) 4))) - -(test-case - "decode should support decoding pointers lazily" - (parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) - (define res (decode (+xstruct (dictify 'ptr (+xpointer #:lazy #t))))) - (check-true (promise? (dict-ref (struct-dict-res-_kv res) 'ptr))) - (check-equal? (dict-ref res 'ptr) 53))) - -(test-case - "size" - (let ([parent (mhash 'pointerSize 0)]) - (check-equal? (size (+xpointer) 10 #:parent parent) 1) - (check-equal? (dict-ref parent 'pointerSize) 1))) - -(test-case - "size should add to immediate pointerSize" - (let ([parent (mhash 'pointerSize 0)]) - (check-equal? (size (+xpointer #:style 'immediate) 10 #:parent parent) 1) - (check-equal? (dict-ref parent 'pointerSize) 1))) - -(test-case - "size should add to parent pointerSize" - (let ([parent (mhash 'parent (mhash 'pointerSize 0))]) - (check-equal? (size (+xpointer #:style 'parent) 10 #:parent parent) 1) - (check-equal? (dict-ref (dict-ref parent 'parent) 'pointerSize) 1))) - -(test-case - "size should add to global pointerSize" - (let ([parent (mhash 'parent (mhash 'parent (mhash 'parent (mhash 'pointerSize 0))))]) - (check-equal? (size (+xpointer #:style 'global) 10 #:parent parent) 1) - (check-equal? (dict-ref (dict-ref (dict-ref (dict-ref parent 'parent) 'parent) 'parent) 'pointerSize) 1))) - -(test-case - "size should handle void pointers" - (let ([parent (mhash 'pointerSize 0)]) - (check-equal? (size (+xpointer uint8 'void) (+xvoid-pointer uint8 50) #:parent parent) 1) - (check-equal? (dict-ref parent 'pointerSize) 1))) - -(test-case - "size should throw if no type and not a void pointer" - (let ([parent (mhash 'pointerSize 0)]) - (check-exn exn:fail:contract? (λ () (size (+xpointer uint8 'void) 30 #:parent parent))))) - -(test-case - "size should return a fixed size without a value" - (check-equal? (size (+xpointer)) 1)) - -(test-case - "encode should handle null pointers" - (parameterize ([current-output-port (open-output-bytes)]) - (define parent (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 0 - 'pointers null)) - (encode (+xpointer) #f #:parent parent) - (check-equal? (dict-ref parent 'pointerSize) 0) - (check-equal? (dump (current-output-port)) (bytes 0)))) - -(test-case - "encode should handle local offsets" - (parameterize ([current-output-port (open-output-bytes)]) - (define parent (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 1 - 'pointers null)) - (encode (+xpointer) 10 #:parent parent) - (check-equal? (dict-ref parent 'pointerOffset) 2) - (check-equal? (dict-ref parent 'pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent parent))) - (check-equal? (dump (current-output-port)) (bytes 1)))) - -(test-case - "encode should handle immediate offsets" - (parameterize ([current-output-port (open-output-bytes)]) - (define parent (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 1 - 'pointers null)) - (encode (+xpointer #:style 'immediate) 10 #:parent parent) - (check-equal? (dict-ref parent 'pointerOffset) 2) - (check-equal? (dict-ref parent 'pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent parent))) - (check-equal? (dump (current-output-port)) (bytes 0)))) - -(test-case - "encode should handle offsets relative to parent" - (parameterize ([current-output-port (open-output-bytes)]) - (define parent (mhash 'parent (mhash 'pointerSize 0 - 'startOffset 3 - 'pointerOffset 5 - 'pointers null))) - (encode (+xpointer #:style 'parent) 10 #:parent parent) - (check-equal? (dict-ref (dict-ref parent 'parent) 'pointerOffset) 6) - (check-equal? (dict-ref (dict-ref parent 'parent) 'pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent parent))) - (check-equal? (dump (current-output-port)) (bytes 2)))) - -(test-case - "encode should handle global offsets" - (parameterize ([current-output-port (open-output-bytes)]) - (define parent (mhash 'parent - (mhash 'parent - (mhash 'parent (mhash 'pointerSize 0 - 'startOffset 3 - 'pointerOffset 5 - 'pointers null))))) - (encode (+xpointer #:style 'global) 10 #:parent parent) - (check-equal? (dict-ref (dict-ref (dict-ref (dict-ref parent 'parent) 'parent) 'parent) 'pointerOffset) 6) - (check-equal? (dict-ref (dict-ref (dict-ref (dict-ref parent 'parent) 'parent) 'parent) 'pointers) - (list (mhasheq 'type uint8 - 'val 10 - 'parent parent))) - (check-equal? (dump (current-output-port)) (bytes 5)))) - -(test-case - "encode should support offsets relative to a property on the parent" - (parameterize ([current-output-port (open-output-bytes)]) - (define parent (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 10 - 'pointers null - 'val (mhash 'ptr 4))) - (encode (+xpointer #:relative-to (λ (parent) (dict-ref parent 'ptr))) 10 #:parent parent) - (check-equal? (dict-ref parent 'pointerOffset) 11) - (check-equal? (dict-ref parent 'pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent parent))) - (check-equal? (dump (current-output-port)) (bytes 6)))) - -(test-case - "encode should support void pointers" - (parameterize ([current-output-port (open-output-bytes)]) - (define parent (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 1 - 'pointers null)) - (encode (+xpointer uint8 'void) (+xvoid-pointer uint8 55) #:parent parent) - (check-equal? (dict-ref parent 'pointerOffset) 2) - (check-equal? (dict-ref parent 'pointers) (list (mhasheq 'type uint8 - 'val 55 - 'parent parent))) - (check-equal? (dump (current-output-port)) (bytes 1)))) - -(test-case - "encode should throw if not a void pointer instance" - (parameterize ([current-output-port (open-output-bytes)]) - (define parent (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 1 - 'pointers null)) - (check-exn exn:fail:contract? (λ () (encode (+xpointer uint8 'void) 44 #:parent parent))))) diff --git a/xenomorph/xenomorph/redo/test/reserved-test.rkt b/xenomorph/xenomorph/redo/test/reserved-test.rkt deleted file mode 100644 index a6f833ea..00000000 --- a/xenomorph/xenomorph/redo/test/reserved-test.rkt +++ /dev/null @@ -1,48 +0,0 @@ -#lang racket/base -(require rackunit - "../number.rkt" - "../helper.rkt" - "../reserved.rkt") - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Reserved.coffee -|# - -(test-case - "size should have a default count of 1" - (check-equal? (size (+xreserved uint8)) 1)) - -(test-case - "size should allow custom counts and types" - (check-equal? (size (+xreserved uint16be 10)) 20)) - -(test-case - "should decode" - (parameterize ([current-input-port (open-input-bytes (bytes 0 0))]) - (define reserved (+xreserved uint16be)) - (check-equal? (decode reserved) (void)) - (check-equal? (pos (current-input-port)) 2))) - -(test-case - "should decode with post-decode" - (parameterize ([current-input-port (open-input-bytes (bytes 0 0))]) - (define reserved (+xreserved uint16be)) - (set-post-decode! reserved (λ (val) 42)) - (check-equal? (decode reserved) 42) - (check-equal? (pos (current-input-port)) 2))) - -(test-case - "should encode" - (parameterize ([current-output-port (open-output-bytes)]) - (define reserved (+xreserved uint16be)) - (encode reserved #f) - (check-equal? (dump (current-output-port)) (bytes 0 0)))) - -(test-case - "should encode with pre-encode" - (parameterize ([current-output-port (open-output-bytes)]) - (define reserved (+xreserved uint32be)) - (set-pre-encode! reserved (λ (val) 42)) - (encode reserved #f) - (check-equal? (dump (current-output-port)) (bytes 0 0 0 0)))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/string-test.rkt b/xenomorph/xenomorph/redo/test/string-test.rkt deleted file mode 100644 index e6a0b485..00000000 --- a/xenomorph/xenomorph/redo/test/string-test.rkt +++ /dev/null @@ -1,124 +0,0 @@ -#lang racket/base -(require rackunit - "../helper.rkt" - "../string.rkt" - "../number.rkt" - sugar/unstable/dict) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/String.coffee -|# - -(test-case - "decode fixed length" - (parameterize ([current-input-port (open-input-bytes #"testing")]) - (check-equal? (decode (+xstring 7)) "testing"))) - -(test-case - "decode fixed length with post-decode" - (parameterize ([current-input-port (open-input-bytes #"testing")]) - (define xs (+xstring 7)) - (set-post-decode! xs (λ (val) "ring a ding")) - (check-equal? (decode xs) "ring a ding"))) - -(test-case - "decode length from parent key" - (parameterize ([current-input-port (open-input-bytes #"testing")]) - (check-equal? (decode (+xstring 'len) #:parent (mhash 'len 7)) "testing"))) - -(test-case - "decode length as number before string" - (parameterize ([current-input-port (open-input-bytes #"\x07testing")]) - (check-equal? (decode (+xstring uint8) #:parent (mhash 'len 7)) "testing"))) - -(test-case - "decode utf8" - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) - (check-equal? (decode (+xstring 4 'utf8)) "🍻"))) - -(test-case - "decode encoding computed from function" - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) - (check-equal? (decode (+xstring 4 (λ _ 'utf8))) "🍻"))) - -(test-case - "decode null-terminated string and read past terminator" - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻\x00"))]) - (check-equal? (decode (+xstring #f 'utf8)) "🍻") - (check-equal? (pos (current-input-port)) 5))) - -(test-case - "decode remainder of buffer when null-byte missing" - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) - (check-equal? (decode (+xstring #f 'utf8)) "🍻"))) - -(test-case - "size should use string length" - (check-equal? (size (+xstring 7) "testing") 7)) - -(test-case - "size should use correct encoding" - (check-equal? (size (+xstring 10 'utf8) "🍻") 4)) - -(test-case - "size should use encoding from function" - (check-equal? (size (+xstring 10 (λ _ 'utf8)) "🍻") 4)) - -(test-case - "should add size of length field before string" - (check-equal? (size (+xstring uint8 'utf8) "🍻") 5)) - -; todo: it "should work with utf16be encoding" - -(test-case - "size should take null-byte into account" - (check-equal? (size (+xstring #f 'utf8) "🍻") 5)) - -(test-case - "size should use defined length if no value given" - (check-equal? (size (+xstring 10)) 10)) - -(test-case - "encode using string length" - (parameterize ([current-output-port (open-output-bytes)]) - (encode (+xstring 7) "testing") - (check-equal? (dump (current-output-port)) #"testing"))) - -(test-case - "encode using string length and pre-encode" - (parameterize ([current-output-port (open-output-bytes)]) - (define xs (+xstring 7)) - (set-pre-encode! xs (compose1 list->string reverse string->list)) - (encode xs "testing") - (check-equal? (dump (current-output-port)) #"gnitset"))) - -(test-case - "encode length as number before string" - (parameterize ([current-output-port (open-output-bytes)]) - (encode (+xstring uint8) "testing") - (check-equal? (dump (current-output-port)) #"\x07testing"))) - -(test-case - "encode length as number before string utf8" - (parameterize ([current-output-port (open-output-bytes)]) - (encode (+xstring uint8 'utf8) "testing 😜") - (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "\x0ctesting 😜")))) - -(test-case - "encode utf8" - (parameterize ([current-output-port (open-output-bytes)]) - (encode (+xstring 4 'utf8) "🍻" ) - (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻")))) - -(test-case - "encode encoding computed from function" - (parameterize ([current-output-port (open-output-bytes)]) - (encode (+xstring 4 (λ _ 'utf8)) "🍻") - (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻")))) - -(test-case - "encode null-terminated string" - (parameterize ([current-output-port (open-output-bytes)]) - (encode (+xstring #f 'utf8) "🍻" ) - (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻\x00")))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/struct-test.rkt b/xenomorph/xenomorph/redo/test/struct-test.rkt deleted file mode 100644 index 22d6c2e8..00000000 --- a/xenomorph/xenomorph/redo/test/struct-test.rkt +++ /dev/null @@ -1,80 +0,0 @@ -#lang debug racket/base -(require rackunit racket/dict - "../helper.rkt" - "../struct.rkt" - "../string.rkt" - "../pointer.rkt" - "../number.rkt" - sugar/unstable/dict) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/Struct.coffee -|# - -(test-case - "decode into an object" - (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) - (check-equal? - (decode/hash (+xstruct 'name (+xstring #:length uint8) 'age uint8)) - (hasheq 'name "roxyb" 'age 21)))) - -(test-case - "decode with process hook" - (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")]) - (define struct (+xstruct 'name (+xstring #:length uint8) 'age uint8)) - (set-post-decode! struct (λ (o . _) (dict-set! o 'canDrink (>= (dict-ref o 'age) 21)) o)) - (check-equal? (decode/hash struct) - (hasheq 'name "roxyb" 'age 32 'canDrink #t)))) - -(test-case - "decode supports function keys" - (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")]) - (define struct (+xstruct 'name (+xstring #:length uint8) 'age uint8 'canDrink (λ (o) (>= (dict-ref o 'age) 21)))) - (check-equal? (decode/hash struct) - (hasheq 'name "roxyb" 'age 32 'canDrink #t)))) - -(test-case - "compute the correct size" - (check-equal? (size (+xstruct 'name (+xstring #:length uint8) 'age uint8) - (hasheq 'name "roxyb" 'age 32)) 7)) - -(test-case - "compute the correct size with pointers" - (check-equal? (size (+xstruct 'name (+xstring #:length uint8) - 'age uint8 - 'ptr (+xpointer #:type (+xstring #:length uint8))) - (mhash 'name "roxyb" 'age 21 'ptr "hello")) 14)) - -(test-case - "get the correct size when no value is given" - (check-equal? (size (+xstruct 'name (+xstring 4) 'age uint8)) 5)) - -(test-case - "throw when getting non-fixed length size and no value is given" - (check-exn exn:fail:contract? (λ () (size (+xstruct 'name (+xstring #:length uint8) 'age uint8))))) - -(test-case - "encode objects to buffers" - (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) - (check-equal? (decode/hash (+xstruct 'name (+xstring #:length uint8) 'age uint8)) - (hasheq 'name "roxyb" 'age 21)))) - -(test-case - "support pre-encode hook" - (parameterize ([current-output-port (open-output-bytes)]) - (define struct (+xstruct 'nameLength uint8 - 'name (+xstring 'nameLength) - 'age uint8)) - (set-pre-encode! struct (λ (val) (dict-set! val 'nameLength (string-length (dict-ref val 'name))) val)) - (encode struct (mhasheq 'name "roxyb" 'age 21)) - (check-equal? (dump (current-output-port)) #"\x05roxyb\x15"))) - -(test-case - "encode pointer data after structure" - (parameterize ([current-output-port (open-output-bytes)]) - (define struct (+xstruct 'name (+xstring #:length uint8) - 'age uint8 - 'ptr (+xpointer #:type (+xstring #:length uint8)))) - (encode struct (hasheq 'name "roxyb" 'age 21 'ptr "hello")) - (check-equal? (dump (current-output-port)) #"\x05roxyb\x15\x08\x05hello"))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/versioned-struct-test.rkt b/xenomorph/xenomorph/redo/test/versioned-struct-test.rkt deleted file mode 100644 index 61de216b..00000000 --- a/xenomorph/xenomorph/redo/test/versioned-struct-test.rkt +++ /dev/null @@ -1,248 +0,0 @@ -#lang debug racket/base -(require rackunit - racket/dict - sugar/unstable/dict - "../helper.rkt" - "../number.rkt" - "../string.rkt" - "../pointer.rkt" - "../versioned-struct.rkt") - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffee -|# - -(test-case - "decode should get version from number type" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'gender uint8)))]) - (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) - (check-equal? (decode/hash vstruct) (hasheq 'name "roxyb" 'age 21 'version 0))) - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x0aroxyb 🤘\x15\x00"))]) - (check-equal? (decode/hash vstruct) (hasheq 'name "roxyb 🤘" 'age 21 'version 1 'gender 0))))) - -(test-case - "decode should throw for unknown version" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #: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 - "decode should support common header block" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 'header (dictify 'age uint8 - 'alive uint8) - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii)) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'gender uint8)))]) - (parameterize ([current-input-port (open-input-bytes #"\x00\x15\x01\x05roxyb")]) - (check-equal? (decode/hash vstruct) (hasheq 'name "roxyb" - 'age 21 - 'alive 1 - 'version 0))) - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x15\x01\x0aroxyb 🤘\x00"))]) - (check-equal? (decode/hash vstruct) (hasheq 'name "roxyb 🤘" - 'age 21 - 'version 1 - 'alive 1 - 'gender 0))))) - -(test-case - "decode should support parent version key" - (let ([vstruct (+xversioned-struct 'version - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'gender uint8)))]) - (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) - (check-equal? (decode/hash vstruct #:parent (mhash 'version 0)) - (hasheq 'name "roxyb" 'age 21 'version 0))) - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x0aroxyb 🤘\x15\x00"))]) - (check-equal? (decode/hash vstruct #:parent (mhash 'version 1)) - (hasheq 'name "roxyb 🤘" 'age 21 'version 1 'gender 0))))) - -(test-case - "decode should support sub versioned structs" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring uint8)) - 1 (dictify 'name (+xstring uint8) - 'isDessert uint8)))))]) - (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) - (check-equal? (decode/hash vstruct #:parent (mhash 'version 0)) - (hasheq 'name "roxyb" 'age 21 'version 0))) - (parameterize ([current-input-port (open-input-bytes #"\x01\x00\x05pasta")]) - (check-equal? (decode/hash vstruct #:parent (mhash 'version 0)) - (hasheq 'name "pasta" 'version 0))) - (parameterize ([current-input-port (open-input-bytes #"\x01\x01\x09ice cream\x01")]) - (check-equal? (decode/hash vstruct #:parent (mhash 'version 0)) - (hasheq 'name "ice cream" 'isDessert 1 'version 1))))) - -(test-case - "decode should support process hook" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'gender uint8)))]) - (set-post-decode! vstruct (λ (val) (dict-set! val 'processed "true") val)) - (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) - (check-equal? (decode/hash vstruct) - (hasheq 'name "roxyb" 'processed "true" 'age 21 'version 0))))) - -(test-case - "size should compute the correct size" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'gender uint8)))]) - (check-equal? (size vstruct (mhasheq 'name "roxyb" - 'age 21 - 'version 0)) 8) - (check-equal? (size vstruct (mhasheq 'name "roxyb 🤘" - 'gender 0 - 'age 21 - 'version 1)) 14))) - -(test-case - "size should throw for unknown version" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'gender uint8)))]) - (check-exn exn:fail:contract? (λ () (size vstruct (mhasheq 'name "roxyb" 'age 21 'version 5)))))) - -(test-case - "size should support common header block" - (let ([struct (+xversioned-struct uint8 - (dictify - 'header (dictify 'age uint8 - 'alive uint8) - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii)) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'gender uint8)))]) - (check-equal? (size struct (mhasheq 'name "roxyb" 'age 21 'alive 1 'version 0)) 9) - (check-equal? (size struct (mhasheq 'name "roxyb 🤘" 'gender 0 'age 21 'alive 1 'version 1)) 15))) - -(test-case - "size should compute the correct size with pointers" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'ptr (+xpointer #:offset-type uint8 - #:type (+xstring uint8)))))]) - (check-equal? (size vstruct (mhasheq 'name "roxyb" - 'age 21 - 'version 1 - 'ptr "hello")) 15))) - -(test-case - "size should throw if no value is given" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'gender uint8)))]) - (check-exn exn:fail:contract? (λ () (size vstruct))))) - -(test-case - "encode should encode objects to buffers" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'gender uint8)))] - [op (open-output-bytes)]) - (encode vstruct (mhasheq 'name "roxyb" 'age 21 'version 0) op) - (encode vstruct (mhasheq 'name "roxyb 🤘" 'age 21 'gender 0 'version 1) op) - (check-equal? (dump op) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00")))) - -(test-case - "encode should throw for unknown version" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #: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 'version 5)))))) - -(test-case - "encode should support common header block" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 'header (dictify 'age uint8 - 'alive uint8) - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii)) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'gender uint8)))] - [op (open-output-bytes)]) - (encode vstruct (mhasheq 'name "roxyb" 'age 21 'alive 1 'version 0) op) - (encode vstruct (mhasheq 'name "roxyb 🤘" 'gender 0 'age 21 'alive 1 'version 1) op) - (check-equal? (dump op) (string->bytes/utf-8 "\x00\x15\x01\x05roxyb\x01\x15\x01\x0aroxyb 🤘\x00")))) - -(test-case - "encode should encode pointer data after structure" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'ptr (+xpointer #:offset-type uint8 - #:type (+xstring uint8)))))] - [op (open-output-bytes)]) - (encode vstruct (mhasheq 'version 1 'name "roxyb" 'age 21 'ptr "hello") op) - - (check-equal? (dump op) (string->bytes/utf-8 "\x01\x05roxyb\x15\x09\x05hello")))) - -(test-case - "encode should support preEncode hook" - (let ([vstruct (+xversioned-struct uint8 - (dictify - 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) - 'age uint8) - 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) - 'age uint8 - 'gender uint8)))] - [stream (open-output-bytes)]) - (set-pre-encode! vstruct (λ (val) (dict-set! val 'version (if (dict-ref val 'gender #f) 1 0)) val)) - (encode vstruct (mhasheq 'name "roxyb" 'age 21 'version 0) stream) - (encode vstruct (mhasheq 'name "roxyb 🤘" 'age 21 'gender 0) stream) - (check-equal? (dump stream) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00")))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/versioned-struct.rkt b/xenomorph/xenomorph/redo/versioned-struct.rkt deleted file mode 100644 index 7ed56e59..00000000 --- a/xenomorph/xenomorph/redo/versioned-struct.rkt +++ /dev/null @@ -1,108 +0,0 @@ -#lang racket/base -(require "helper.rkt" "struct.rkt" - racket/dict - sugar/unstable/dict) -(provide (all-defined-out) decode/hash) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee -|# - -(define/post-decode (xversioned-struct-decode xvs [port-arg (current-input-port)] #:parent [parent #f] [length 0]) - (define port (->input-port port-arg)) - (define res (_setup port parent length)) - - (dict-set! res 'version - (cond - [(integer? (xversioned-struct-type xvs)) (xversioned-struct-type xvs)] - #;[forced-version] ; for testing purposes: pass an explicit version - [(or (symbol? (xversioned-struct-type xvs)) (procedure? (xversioned-struct-type xvs))) - (unless parent - (raise-argument-error 'xversioned-struct-decode "valid parent" parent)) - ((xversioned-struct-version-getter xvs) parent)] - [else (decode (xversioned-struct-type xvs) port)])) - - (when (dict-ref (xversioned-struct-versions xvs) 'header #f) - (_parse-fields port res (dict-ref (xversioned-struct-versions xvs) 'header))) - - (define fields (or (dict-ref (xversioned-struct-versions xvs) (dict-ref res 'version #f) #f) - (raise-argument-error 'xversioned-struct-decode "valid version key" (cons version (xversioned-struct-versions xvs))))) - - (cond - [(xversioned-struct? fields) (decode fields port #:parent parent)] - [else (_parse-fields port res fields) - res])) - -(define/finalize-size (xversioned-struct-size xvs [val #f] #:parent [parent-arg #f] [include-pointers #t]) - (unless val - (raise-argument-error 'xversioned-struct-size "value" val)) - (define parent (mhash 'parent parent-arg 'val val 'pointerSize 0)) - (define version-size - (if (not (or (symbol? (xversioned-struct-type xvs)) (procedure? (xversioned-struct-type xvs)))) - (size (xversioned-struct-type xvs) (dict-ref val 'version) #:parent parent) - 0)) - (define header-size - (for/sum ([(key type) (in-dict (or (dict-ref (xversioned-struct-versions xvs) 'header #f) null))]) - (size type (and val (dict-ref val key)) #:parent parent))) - (define fields-size - (let ([fields (or (dict-ref (xversioned-struct-versions xvs) (dict-ref val 'version)) - (raise-argument-error 'xversioned-struct-size "valid version key" version))]) - (for/sum ([(key type) (in-dict fields)]) - (size type (and val (dict-ref val key)) #:parent parent)))) - (define pointer-size (if include-pointers (dict-ref parent 'pointerSize) 0)) - (+ version-size header-size fields-size pointer-size)) - -(define/pre-encode (xversioned-struct-encode xvs val [port-arg (current-output-port)] #:parent [parent-arg #f]) - (define port (if (output-port? port-arg) port-arg (open-output-bytes))) - (parameterize ([current-output-port port]) - (unless (dict? val) - (raise-argument-error 'xversioned-struct-encode "dict" val)) - - (define parent (mhash 'pointers null - 'startOffset (pos port) - 'parent parent-arg - 'val val - 'pointerSize 0)) - (dict-set! parent 'pointerOffset (+ (pos port) (xversioned-struct-size xvs val #:parent parent #f))) - - (when (not (or (symbol? (xversioned-struct-type xvs)) (procedure? (xversioned-struct-type xvs)))) - (encode (xversioned-struct-type xvs) (dict-ref val 'version #f))) - - (when (dict-ref (xversioned-struct-versions xvs) 'header #f) - (for ([(key type) (in-dict (dict-ref (xversioned-struct-versions xvs) 'header))]) - (encode type (dict-ref val key) #:parent parent))) - - (define fields (or (dict-ref (xversioned-struct-versions xvs) (dict-ref val 'version #f)) - (raise-argument-error 'xversioned-struct-encode "valid version key" version))) - - (unless (andmap (λ (key) (member key (dict-keys val))) (dict-keys fields)) - (raise-argument-error 'xversioned-struct-encode (format "hash that contains superset of Struct keys: ~a" (dict-keys fields)) (hash-keys val))) - - (for ([(key type) (in-dict fields)]) - (encode type (dict-ref val key) #:parent parent)) - (for ([ptr (in-list (dict-ref parent 'pointers))]) - (encode (dict-ref ptr 'type) (dict-ref ptr 'val) #:parent (dict-ref ptr 'parent))) - - (unless port-arg (get-output-bytes port)))) - -(struct xversioned-struct structish (type versions version-getter version-setter) #:transparent #:mutable - #:methods gen:xenomorphic - [(define decode xversioned-struct-decode) - (define encode xversioned-struct-encode) - (define size xversioned-struct-size)]) - -(define (+xversioned-struct type [versions (dictify)]) - (unless (for/or ([proc (list integer? procedure? xenomorphic? symbol?)]) - (proc type)) - (raise-argument-error '+xversioned-struct "integer, procedure, symbol, or xenomorphic" type)) - (unless (and (dict? versions) (andmap (λ (v) (or (dict? v) (structish? v))) (dict-values versions))) - (raise-argument-error '+xversioned-struct "dict of dicts or structish" versions)) - (define version-getter (cond - [(procedure? type) type] - [(symbol? type) (λ (parent) (dict-ref parent type))])) - (define version-setter (cond - [(procedure? type) type] - [(symbol? type) (λ (parent version) (dict-set! parent type version))])) - (xversioned-struct type versions version-getter version-setter)) - diff --git a/xenomorph/xenomorph/reserved.rkt b/xenomorph/xenomorph/reserved.rkt index b0649366..9947b160 100644 --- a/xenomorph/xenomorph/reserved.rkt +++ b/xenomorph/xenomorph/reserved.rkt @@ -1,8 +1,5 @@ #lang racket/base -(require racket/class - sugar/unstable/class - "private/helper.rkt" - "utils.rkt") +(require "helper.rkt" "util.rkt") (provide (all-defined-out)) #| @@ -10,15 +7,26 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Reserved.coffee |# -(define-subclass xenomorph-base% (Reserved type [count 1]) +(define/post-decode (xreserved-decode xo [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (pos port (+ (pos port) (size xo #f #:parent parent))) + (void)) - (define/augment (decode port parent) - (pos port (+ (pos port) (size #f parent))) - (void)) +(define/pre-encode (xreserved-encode xo val [port-arg (current-output-port)] #:parent [parent #f]) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (write-bytes (make-bytes (size xo val #:parent parent) 0) port) + (unless port-arg (get-output-bytes port))) - (define/augment (size [val #f] [parent #f]) - (* (send type size) (resolve-length count #f parent))) +(define/finalize-size (xreserved-size xo [val #f] #:parent [parent #f]) + (define item-size (size (xreserved-type xo))) + (define count (resolve-length (xreserved-count xo) #f #:parent parent)) + (* item-size count)) - (define/augment (encode port val [parent #f]) - (make-bytes (size val parent) 0))) +(struct xreserved xbase (type count) #:transparent + #:methods gen:xenomorphic + [(define decode xreserved-decode) + (define encode xreserved-encode) + (define size xreserved-size)]) +(define (+xreserved type [count 1]) + (xreserved type count)) \ No newline at end of file diff --git a/xenomorph/xenomorph/string.rkt b/xenomorph/xenomorph/string.rkt index cecabfee..f2cabbea 100644 --- a/xenomorph/xenomorph/string.rkt +++ b/xenomorph/xenomorph/string.rkt @@ -1,12 +1,5 @@ -#lang racket/base -(require racket/class - sugar/unstable/class - sugar/unstable/case - sugar/unstable/js - "private/generic.rkt" - "private/helper.rkt" - "number.rkt" - "utils.rkt") +#lang debug racket/base +(require racket/dict "helper.rkt" "util.rkt" "number.rkt") (provide (all-defined-out)) #| @@ -14,99 +7,128 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/String.coffee |# -(define (read-encoded-string port len [encoding 'ascii]) - (define proc (caseq encoding - [(utf16le) (error 'bah)] - [(ucs2) (error 'bleh)] - [(utf8) bytes->string/utf-8] - [(ascii) bytes->string/latin-1] - [else values])) - (proc (read-bytes len port))) +(define (read-encoded-string len [encoding 'ascii]) + (define proc (case encoding + [(utf16le) (error 'bah)] + [(ucs2) (error 'bleh)] + [(utf8) bytes->string/utf-8] + [(ascii) bytes->string/latin-1] + [else values])) + (proc (read-bytes len))) -(define (write-encoded-string port string [encoding 'ascii]) +(define (write-encoded-string string [encoding 'ascii]) ;; todo: handle encodings correctly. ;; right now just utf8 and ascii are correct - (define proc (caseq encoding - [(ucs2 utf8 ascii) string->bytes/utf-8] - [(utf16le) (error 'swap-bytes-unimplemented)] - [else (error 'unsupported-string-encoding)])) - (write-bytes (proc string) port)) + (define proc (case encoding + [(ucs2 utf8 ascii) string->bytes/utf-8] + [(utf16le) (error 'swap-bytes-unimplemented)] + [else (error 'unsupported-string-encoding)])) + (write-bytes (proc string))) (define (count-nonzero-chars port) ;; helper function for String ;; counts nonzero chars from current position - (length (car (regexp-match-peek "[^\u0]*" port)))) + (bytes-length (car (regexp-match-peek "[^\u0]*" port)))) + +(define (bytes-left-in-port? port) + (not (eof-object? (peek-byte port)))) (define (byte-length val encoding) (define encoder - (caseq encoding - [(ascii utf8) string->bytes/utf-8])) + (case encoding + [(ascii utf8) string->bytes/utf-8])) (bytes-length (encoder (format "~a" val)))) -(define (bytes-left-in-port? port) - (not (eof-object? (peek-byte port)))) - -(define-subclass xenomorph-base% (StringT [len #f] [encoding 'ascii]) - - (define/augment (decode port [parent #f]) - (let ([len (or (resolve-length len port parent) (count-nonzero-chars port))] - [encoding (if (procedure? encoding) - (or (encoding parent) 'ascii) - encoding)] - [adjustment (if (and (not len) (bytes-left-in-port? port)) 1 0)]) - (define string (read-encoded-string port len encoding)) +(define/post-decode (xstring-decode xs [port-arg (current-input-port)] #:parent [parent #f]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) + (let ([len (or (resolve-length (xstring-len xs) #:parent parent) (count-nonzero-chars port))] + [encoding (if (procedure? (xstring-encoding xs)) + (or ((xstring-encoding xs) parent) 'ascii) + (xstring-encoding xs))] + [adjustment (if (and (not (xstring-len xs)) (bytes-left-in-port? port)) 1 0)]) + (define string (read-encoded-string len encoding)) (pos port (+ (pos port) adjustment)) - string)) - + string))) - (define/augment (encode port val [parent #f]) +(define/pre-encode (xstring-encode xs val [port-arg (current-output-port)] #:parent [parent #f]) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) (let* ([val (format "~a" val)] - [encoding (if (procedure? encoding) - (or (encoding (and parent (· parent val)) 'ascii)) - encoding)]) + [encoding (if (procedure? (xstring-encoding xs)) + (or ((xstring-encoding xs) (and parent (dict-ref parent val)) 'ascii)) + (xstring-encoding xs))]) (define encoded-length (byte-length val encoding)) - (when (and (exact-nonnegative-integer? len) (> encoded-length len)) - (raise-argument-error 'String:encode (format "string no longer than ~a" len) val)) - (when (NumberT? len) - (send len encode port encoded-length)) - (write-encoded-string port val encoding) - (when (not len) (write-byte #x00 port)))) ; null terminated when no len - - - (define/augment (size [val #f] [parent #f]) - (if (not val) - (resolve-length len #f parent) - (let* ([encoding (if (procedure? encoding) - (or (encoding (and parent (· parent val)) 'ascii)) - encoding)] - [encoding (if (eq? encoding 'utf16be) 'utf16le encoding)]) - (+ (byte-length val encoding) (cond - [(not len) 1] - [(NumberT? len) (send len size)] - [else 0])))))) - - -(define-values (String? +String) (values StringT? +StringT)) - -(define-subclass StringT (Symbol) - (define/override (post-decode string-val . _) - (string->symbol string-val)) - - (define/override (pre-encode sym-val . _) - (unless (or (string? sym-val) (symbol? sym-val)) - (raise-argument-error 'Symbol "symbol or string" sym-val)) - (if (symbol? sym-val) sym-val (string->symbol sym-val)))) - - -(test-module - (define S-fixed (+String 4 'utf8)) - (check-equal? (encode S-fixed "Mike" #f) #"Mike") - (check-exn exn:fail? (λ () (encode S-fixed "Mikes" #f))) ; too long for fixed string - (define S (+String uint8 'utf8)) - (check-equal? (decode S #"\2BCDEF") "BC") - (check-equal? (encode S "Mike" #f) #"\4Mike") - (check-equal? (size (+String) "foobar") 7) ; null terminated when no len - (check-equal? (decode (+Symbol 4) #"Mike") 'Mike) - (check-equal? (encode (+Symbol 4) 'Mike #f) #"Mike") - (check-equal? (encode (+Symbol 4) "Mike" #f) #"Mike") - (check-exn exn:fail:contract? (λ () (encode (+Symbol 4) 42 #f)))) \ No newline at end of file + (when (and (exact-nonnegative-integer? (xstring-len xs)) (> encoded-length (xstring-len xs))) + (raise-argument-error 'xstring-encode (format "string no longer than ~a" (xstring-len xs)) val)) + (when (xint? (xstring-len xs)) + (encode (xstring-len xs) encoded-length)) + (write-encoded-string val encoding) + (when (not (xstring-len xs)) (write-byte #x00)) ; null terminated when no len + (unless port-arg (get-output-bytes port))))) + +(define/finalize-size (xstring-size xs [val #f] #:parent [parent #f]) + (cond + [val (define encoding (if (procedure? (xstring-encoding xs)) + (or ((xstring-encoding xs) (and parent (dict-ref parent val)) 'ascii)) + (xstring-encoding xs))) + (define string-size (byte-length val (if (eq? encoding 'utf16be) 'utf16le encoding))) + (define strlen-size (cond + [(not (xstring-len xs)) 1] + [(xint? (xstring-len xs)) (size (xstring-len xs))] + [else 0])) + (+ string-size strlen-size)] + [else (resolve-length (xstring-len xs) #f #:parent parent)])) + +(struct xstring xbase (len encoding) #:transparent + #:methods gen:xenomorphic + [(define decode xstring-decode) + (define encode xstring-encode) + (define size xstring-size)]) + +(define supported-encodings '(ascii utf8)) +(define (+xstring [len-arg #f] [enc-arg #f] + #:length [len-kwarg #f] #:encoding [enc-kwarg #f]) + (define len (or len-arg len-kwarg)) + (define encoding (or enc-arg enc-kwarg 'ascii)) + (unless (length-resolvable? len) + (raise-argument-error '+xarray "length-resolvable?" len)) + (unless (or (procedure? encoding) (memq encoding supported-encodings)) + (raise-argument-error '+xarray (format "procedure or member of ~v" supported-encodings) encoding)) + (xstring len encoding)) + +(define (xsymbol-decode xs [port-arg (current-input-port)] #:parent [parent #f]) + (string->symbol (xstring-decode xs port-arg #:parent parent))) + +(define (xsymbol-encode xs val [port (current-output-port)] #:parent [parent #f]) + (unless (xsymbol? xs) + (raise-argument-error 'encode "xsymbol instance" xs)) + (unless (or (string? val) (symbol? val)) + (raise-argument-error 'xsymbol-encode "symbol or string" val)) + (xstring-encode xs (if (symbol? val) val (string->symbol val)) port #:parent parent)) + +(struct xsymbol xstring () #:transparent + #:methods gen:xenomorphic + [(define decode xsymbol-decode) + (define encode xsymbol-encode) + (define size xstring-size)]) + +(define (+xsymbol [len-arg #f] [enc-arg #f] + #:length [len-kwarg #f] #:encoding [enc-kwarg #f]) + (define len (or len-arg len-kwarg)) + (define encoding (or enc-arg enc-kwarg 'ascii)) + (xsymbol len encoding)) + +(module+ test + (require rackunit) + (define S-fixed (+xstring 4 'utf8)) + (check-equal? (encode S-fixed "Mike" #f) #"Mike") + (check-exn exn:fail? (λ () (encode S-fixed "Mikes" #f))) ; too long for fixed string + (define S (+xstring uint8 'utf8)) + (check-equal? (decode S #"\2BCDEF") "BC") + (check-equal? (encode S "Mike" #f) #"\4Mike") + (check-equal? (size (+xstring) "foobar") 7) ; null terminated when no len + (check-equal? (decode (+xsymbol 4) #"Mike") 'Mike) + (check-equal? (encode (+xsymbol 4) 'Mike #f) #"Mike") + (check-equal? (encode (+xsymbol 4) "Mike" #f) #"Mike") + (check-exn exn:fail:contract? (λ () (encode (+xsymbol 4) 42 #f)))) \ No newline at end of file diff --git a/xenomorph/xenomorph/struct.rkt b/xenomorph/xenomorph/struct.rkt index a7efdcba..2cfe11d5 100644 --- a/xenomorph/xenomorph/struct.rkt +++ b/xenomorph/xenomorph/struct.rkt @@ -1,15 +1,12 @@ -#lang racket/base -(require racket/list - sugar/unstable/class - sugar/unstable/dict - sugar/unstable/js - racket/class - "private/helper.rkt" - "private/generic.rkt" - racket/dict - racket/private/generic-methods) -(provide (all-defined-out) ref* ref*-set! (all-from-out racket/dict)) -(require (prefix-in d: racket/dict)) +#lang debug racket/base +(require (prefix-in d: racket/dict) + racket/promise + racket/sequence + racket/list + "helper.rkt" + "number.rkt" + sugar/unstable/dict) +(provide (all-defined-out)) #| approximates @@ -17,145 +14,137 @@ https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee |# -(define private-keys '(parent _startOffset _currentOffset _length)) - (define (choose-dict d k) (if (memq k private-keys) - (get-field _pvt d) - (get-field _kv d))) - -(define dictable<%> - (interface* () - ([(generic-property gen:dict) - (generic-method-table gen:dict - (define (dict-set! d k v) (d:dict-set! (choose-dict d k) k v)) - (define (dict-ref d k [thunk #f]) - (define res (d:dict-ref (choose-dict d k) k thunk)) - (if (LazyThunk? res) ((LazyThunk-proc res)) res)) - (define (dict-remove! d k) (d:dict-remove! (choose-dict d k) k)) - ;; public keys only - (define (dict-keys d) (d:dict-keys (get-field _kv d))) - (define (dict-iterate-first d) (and (pair? (dict-keys d)) 0)) - (define (dict-iterate-next d i) (and (< (add1 i) (length (dict-keys d))) (add1 i))) - (define (dict-iterate-key d i) (list-ref (dict-keys d) i)) - (define (dict-iterate-value d i) (dict-ref d (dict-iterate-key d i))))] - [(generic-property gen:custom-write) - (generic-method-table gen:custom-write - (define (write-proc o port mode) - (define proc (case mode - [(#t) write] - [(#f) display] - [else (λ (p port) (print p port mode))])) - (proc (dump o) port)))]))) - -(define-subclass*/interfaces xenomorph-base% (dictable<%>) - (StructDictRes) - (super-make-object) - (field [_kv (mhasheq)] - [_pvt (mhasheq)]) - - (define/override (dump) - ;; convert to immutable for display & debug - (for/hasheq ([(k v) (in-hash _kv)]) - (values k v))) - - (define/public (to-hash) _kv)) - - -(define-subclass xenomorph-base% (Struct [fields (dictify)]) - (field [[_post-decode post-decode] (λ (val port ctx) val)] - [[_pre-encode pre-encode] (λ (val port) val)]) ; store as field so it can be mutated from outside - - (define/overment (post-decode res . args) - (let* ([res (apply _post-decode res args)] - [res (inner res post-decode res . args)]) - (unless (dict? res) (raise-result-error 'Struct:post-decode "dict" res)) - res)) - - (define/overment (pre-encode res . args) - (let* ([res (apply _pre-encode res args)] - [res (inner res pre-encode res . args)]) - (unless (dict? res) (raise-result-error 'Struct:pre-encode "dict" res)) - res)) - - (unless (or (assocs? fields) (Struct? fields)) ; should be Versioned Struct but whatever - (raise-argument-error 'Struct "assocs or Versioned Struct" fields)) - - (define/augride (decode stream [parent #f] [len 0]) - ;; _setup and _parse-fields are separate to cooperate with VersionedStruct - (let* ([sdr (_setup stream parent len)] ; returns StructDictRes - [sdr (_parse-fields stream sdr fields)]) - sdr)) - - (define/public-final (_setup port parent len) - (define sdr (make-object StructDictRes)) ; not mere hash - (dict-set*! sdr 'parent parent + (struct-dict-res-_pvt d) + (struct-dict-res-_kv d))) + +(struct struct-dict-res (_kv _pvt) #:transparent + #:methods d:gen:dict + [(define (dict-set! d k v) (d:dict-set! (choose-dict d k) k v)) + (define (dict-ref d k [thunk #f]) + (define res (d:dict-ref (choose-dict d k) k thunk)) + (force res)) + (define (dict-remove! d k) (d:dict-remove! (choose-dict d k) k)) + ;; public keys only + (define (dict-keys d) (d:dict-keys (struct-dict-res-_kv d))) + (define (dict-iterate-first d) (and (pair? (dict-keys d)) 0)) + (define (dict-iterate-next d i) (and (< (add1 i) (length (dict-keys d))) (add1 i))) + (define (dict-iterate-key d i) (list-ref (dict-keys d) i)) + (define (dict-iterate-value d i) (dict-ref d (dict-iterate-key d i)))]) + +(define (+struct-dict-res [_kv (mhasheq)] [_pvt (mhasheq)]) + (struct-dict-res _kv _pvt)) + +(define (_setup port parent len) + (define sdr (+struct-dict-res)) ; not mere hash + (d:dict-set*! sdr 'parent parent '_startOffset (pos port) '_currentOffset 0 '_length len) - sdr) - - (define/public-final (_parse-fields port sdr fields) - (unless (assocs? fields) - (raise-argument-error '_parse-fields "assocs" fields)) - (for/fold ([sdr sdr]) - ([(key type) (in-dict fields)]) - (define val (if (procedure? type) - (type sdr) - (send type decode port sdr))) - (unless (void? val) - (dict-set! sdr key val)) - (dict-set! sdr '_currentOffset (- (pos port) (· sdr _startOffset))) - sdr)) - - - (define/augride (size [val #f] [parent #f] [include-pointers #t]) - (define ctx (mhasheq 'parent parent - 'val val - 'pointerSize 0)) - (+ (for/sum ([(key type) (in-dict fields)] - #:when (object? type)) - (send type size (and val (ref val key)) ctx)) - (if include-pointers (· ctx pointerSize) 0))) - - (define/augride (encode port val [parent #f]) - (unless (dict? val) - (raise-argument-error 'Struct:encode "dict" val)) - + sdr) + +(define (_parse-fields port sdr fields) + (unless (assocs? fields) + (raise-argument-error '_parse-fields "assocs" fields)) + (for/fold ([sdr sdr]) + ([(key type) (d:in-dict fields)]) + (define val (if (procedure? type) + (type sdr) + (decode type port #:parent sdr))) + (unless (void? val) + (d:dict-set! sdr key val)) + (d:dict-set! sdr '_currentOffset (- (pos port) (d:dict-ref sdr '_startOffset))) + sdr)) + +(define-syntax-rule (decode/hash . ARGS) + (dump (decode . ARGS))) + +(define (xstruct-decode xs [port-arg (current-input-port)] #:parent [parent #f] [len 0]) + (define port (->input-port port-arg)) + (parameterize ([current-input-port port]) + ;; _setup and _parse-fields are separate to cooperate with VersionedStruct + (define res + (post-decode xs + (let* ([sdr (_setup port parent len)] ; returns StructDictRes + [sdr (_parse-fields port sdr (xstruct-fields xs))]) + sdr))) + (unless (d:dict? res) + (raise-result-error 'xstruct-decode "dict" res)) + res)) + +(define/finalize-size (xstruct-size xs [val #f] #:parent [parent-arg #f] [include-pointers #t]) + (define parent (mhasheq 'parent parent-arg + 'val val + 'pointerSize 0)) + (define fields-size (for/sum ([(key type) (d:in-dict (xstruct-fields xs))] + #:when (xenomorphic? type)) + (size type (and val (d:dict-ref val key)) #:parent parent))) + (define pointers-size (if include-pointers (d:dict-ref parent 'pointerSize) 0)) + (+ fields-size pointers-size)) + +(define (xstruct-encode xs val-arg [port-arg (current-output-port)] #:parent [parent-arg #f]) + (unless (d:dict? val-arg) + (raise-argument-error 'xstruct-encode "dict" val-arg)) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) ;; check keys first, since `size` also relies on keys being valid - (unless (andmap (λ (key) (memq key (dict-keys val))) (dict-keys fields)) - (raise-argument-error 'Struct:encode - (format "dict that contains superset of Struct keys: ~a" (dict-keys fields)) (dict-keys val))) - - (define ctx (mhash 'pointers empty - 'startOffset (pos port) - 'parent parent - 'val val - 'pointerSize 0)) - (ref-set! ctx 'pointerOffset (+ (pos port) (size val ctx #f))) - - (for ([(key type) (in-dict fields)]) - (send type encode port (ref val key) ctx)) - (for ([ptr (in-list (· ctx pointers))]) - (send (· ptr type) encode port (· ptr val) (· ptr parent))))) - - -(test-module - (require "number.rkt") - (define (random-pick xs) (list-ref xs (random (length xs)))) - (check-exn exn:fail:contract? (λ () (+Struct 42))) - - ;; make random structs and make sure we can round trip - (for ([i (in-range 20)]) - (define field-types (for/list ([i (in-range 40)]) - (random-pick (list uint8 uint16be uint16le uint32be uint32le double)))) - (define size-num-types (for/sum ([num-type (in-list field-types)]) - (send num-type size))) - (define s (+Struct (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? (send s encode #f (send s decode bs)) bs))) - - - + (define val (let* ([val (pre-encode xs val-arg)] + #;[val (inner res pre-encode val . args)]) + (unless (d:dict? val) (raise-result-error 'xstruct-encode "dict" val)) + val)) + (unless (andmap (λ (key) (memq key (d:dict-keys val))) (d:dict-keys (xstruct-fields xs))) + (raise-argument-error 'xstruct-encode + (format "dict that contains superset of Struct keys: ~a" (d:dict-keys (xstruct-fields xs))) (d:dict-keys val))) + + (define parent (mhash 'pointers empty + 'startOffset (pos port) + 'parent parent-arg + 'val val + 'pointerSize 0)) + + ; deliberately use `xstruct-size` instead of `size` to use extra arg + (d:dict-set! parent 'pointerOffset (+ (pos port) (xstruct-size xs val #:parent parent #f))) + + (for ([(key type) (d:in-dict (xstruct-fields xs))]) + (encode type (d:dict-ref val key) #:parent parent)) + (for ([ptr (in-list (d:dict-ref parent 'pointers))]) + (encode (d:dict-ref ptr 'type) (d:dict-ref ptr 'val) #:parent (d:dict-ref ptr 'parent))) + (unless port-arg (get-output-bytes port)))) + +(struct structish xbase () #:transparent) +(struct xstruct structish (fields) #:transparent #:mutable + #:methods gen:xenomorphic + [(define decode xstruct-decode) + (define encode xstruct-encode) + (define size xstruct-size)]) + +(define (+xstruct . dicts) + (define args (flatten dicts)) + (unless (even? (length args)) + (raise-argument-error '+xstruct "equal keys and values" dicts)) + (define fields (for/list ([kv (in-slice 2 args)]) + (unless (symbol? (car kv)) + (raise-argument-error '+xstruct "symbol" (car kv))) + (apply cons kv))) + (unless (d:dict? fields) + (raise-argument-error '+xstruct "dict" fields)) + (xstruct fields)) + +(module+ test + (require rackunit "number.rkt") + (define (random-pick xs) (list-ref xs (random (length xs)))) + (check-exn exn:fail:contract? (λ () (+xstruct 42))) + (for ([i (in-range 20)]) + ;; make random structs and make sure we can round trip + (define field-types + (for/list ([i (in-range 40)]) + (random-pick (list uint8 uint16be uint16le uint32be uint32le double)))) + (define size-num-types + (for/sum ([num-type (in-list field-types)]) + (size num-type))) + (define xs (+xstruct (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))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/array-test.rkt b/xenomorph/xenomorph/test/array-test.rkt index 9907499f..b4a52db5 100644 --- a/xenomorph/xenomorph/test/array-test.rkt +++ b/xenomorph/xenomorph/test/array-test.rkt @@ -1,6 +1,9 @@ #lang racket/base (require rackunit - xenomorph + "../helper.rkt" + "../array.rkt" + "../number.rkt" + "../pointer.rkt" sugar/unstable/dict) #| @@ -8,84 +11,96 @@ approximates https://github.com/mbutterick/restructure/blob/master/test/Array.coffee |# -;describe 'Array', -> -; describe 'decode', -> -; it 'should decode fixed length', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint8 4)) '(1 2 3 4))) - - -; it 'should decode fixed amount of bytes', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint16be 4 'bytes)) '(258 772))) - - -; it 'should decode length from parent key', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint8 'len) #:parent (mhash 'len 4)) '(1 2 3 4))) - - -; it 'should decode amount of bytes from parent key', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint16be 'len 'bytes) #:parent (mhash 'len 4)) '(258 772))) - - -; it 'should decode length as number before array', -> -(parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint8 uint8)) '(1 2 3 4))) - - -; it 'should decode amount of bytes as number before array', -> -(parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint16be uint8 'bytes)) '(258 772))) - - -; it 'should decode length from function', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint8 (λ _ 4))) '(1 2 3 4))) - - -; it 'should decode amount of bytes from function', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint16be (λ _ 4) 'bytes)) '(258 772))) - - -; it 'should decode to the end of the parent if no length is given', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint8) #:parent (mhash '_length 4 '_startOffset 0)) '(1 2 3 4))) - - -; decode to the end of the stream if parent exists, but its length is 0 -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (check-equal? (decode (+ArrayT uint8) #:parent (mhash '_length 0 '_startOffset 0)) '(1 2 3 4 5))) - - -; it 'should decode to the end of the stream if no parent and length is given', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4))]) - (check-equal? (decode (+ArrayT uint8)) '(1 2 3 4 ))) - - -; describe 'size', -> -; it 'should use array length', -> -(check-equal? (size (+ArrayT uint8 10) '(1 2 3 4)) 4) - - -; it 'should add size of length field before string', -> -(check-equal? (size (+ArrayT uint8 uint8) '(1 2 3 4)) 5) - - -; it 'should use defined length if no value given', -> -(check-equal? (size (+ArrayT uint8 10)) 10) - - -; describe 'encode', -> -; it 'should encode using array length', (done) -> -(check-equal? (encode (+ArrayT uint8 10) '(1 2 3 4) #f) (bytes 1 2 3 4)) - - -; it 'should encode length as number before array', (done) -> -(check-equal? (encode (+ArrayT uint8 uint8) '(1 2 3 4) #f) (bytes 4 1 2 3 4)) - - -; it 'should add pointers after array if length is encoded at start', (done) -> -(check-equal? (encode (+ArrayT (+Pointer uint8 uint8) uint8) '(1 2 3 4) #f) (bytes 4 5 6 7 8 1 2 3 4)) \ No newline at end of file +(test-case + "decode fixed length" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint8 4)) '(1 2 3 4)))) + +(test-case + "decode with post-decode" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define xa (+xarray uint8 4)) + (set-post-decode! xa (λ (val . _) (map (λ (x) (* 2 x)) val))) + (check-equal? (decode xa) '(2 4 6 8)))) + +(test-case + "decode fixed number of bytes" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint16be 4 'bytes)) '(258 772)))) + +(test-case + "decode length from parent key" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint8 'len) #:parent (mhash 'len 4)) '(1 2 3 4)))) + +(test-case + "decode byte count from parent key" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint16be 'len 'bytes) #:parent (mhash 'len 4)) '(258 772)))) + +(test-case + "decode length as number before array" + (parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint8 uint8)) '(1 2 3 4)))) + +(test-case + "decode byte count as number before array" + (parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint16be uint8 'bytes)) '(258 772)))) + +(test-case + "decode length from function" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint8 (λ _ 4))) '(1 2 3 4)))) + +(test-case + "decode byte count from function" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint16be (λ _ 4) 'bytes)) '(258 772)))) + +(test-case + "decode to the end of parent if no length given" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint8) #:parent (mhash '_length 4 '_startOffset 0)) '(1 2 3 4)))) + +(test-case + "decode to the end of the stream if parent exists, but its length is 0" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+xarray uint8) #:parent (mhash '_length 0 '_startOffset 0)) '(1 2 3 4 5)))) + +(test-case + "decode to the end of the stream if no parent and length is given" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4))]) + (check-equal? (decode (+xarray uint8)) '(1 2 3 4 )))) + +(test-case + "use array length" + (check-equal? (size (+xarray uint8 10) '(1 2 3 4)) 4)) + +(test-case + "add size of length field before string" + (check-equal? (size (+xarray uint8 uint8) '(1 2 3 4)) 5)) + +(test-case + "use defined length if no value given" + (check-equal? (size (+xarray uint8 10)) 10)) + +(test-case + "encode using array length" + (check-equal? (encode (+xarray uint8 10) '(1 2 3 4) #f) (bytes 1 2 3 4))) + +(test-case + "encode with pre-encode" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define xa (+xarray uint8 4)) + (set-pre-encode! xa (λ (val . _) (map (λ (x) (* 2 x)) val))) + (check-equal? (encode xa '(1 2 3 4) #f) (bytes 2 4 6 8)))) + +(test-case + "encode length as number before array" + (check-equal? (encode (+xarray uint8 uint8) '(1 2 3 4) #f) (bytes 4 1 2 3 4))) + +(test-case + "add pointers after array if length is encoded at start" + (check-equal? (encode (+xarray (+xpointer #:offset-type uint8 + #:type uint8) uint8) '(1 2 3 4) #f) (bytes 4 5 6 7 8 1 2 3 4))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/bitfield-test.rkt b/xenomorph/xenomorph/test/bitfield-test.rkt index 8dba2c75..883285c3 100644 --- a/xenomorph/xenomorph/test/bitfield-test.rkt +++ b/xenomorph/xenomorph/test/bitfield-test.rkt @@ -1,51 +1,76 @@ #lang racket/base (require rackunit - xenomorph - sugar/unstable/dict + racket/match racket/list - racket/match) + sugar/unstable/dict + "../helper.rkt" + "../number.rkt" + "../bitfield.rkt") #| approximates https://github.com/mbutterick/restructure/blob/master/test/Bitfield.coffee |# -;describe 'Bitfield', -> -; bitfield = new Bitfield uint8, ['Jack', 'Kack', 'Lack', 'Mack', 'Nack', 'Oack', 'Pack', 'Quack'] -; JACK = 1 << 0 -; KACK = 1 << 1 -; LACK = 1 << 2 -; MACK = 1 << 3 -; NACK = 1 << 4 -; OACK = 1 << 5 -; PACK = 1 << 6 -; QUACK = 1 << 7 - -(define bitfield (+Bitfield uint8 '(Jack Kack Lack Mack Nack Oack Pack Quack))) +(define bitfield (+xbitfield uint8 '(Jack Kack Lack Mack Nack Oack Pack Quack))) (match-define (list JACK KACK LACK MACK NACK OACK PACK QUACK) (map (λ (x) (arithmetic-shift 1 x)) (range 8))) -; it 'should have the right size', -> -(check-equal? (size bitfield) 1) +(test-case + "bitfield should have the right size" + (check-equal? (size bitfield) 1)) + +(test-case + "bitfield should decode" + (parameterize ([current-input-port (open-input-bytes (bytes (bitwise-ior JACK MACK PACK NACK QUACK)))]) + (check-equal? (decode bitfield) (mhasheq 'Quack #t + 'Nack #t + 'Lack #f + 'Oack #f + 'Pack #t + 'Mack #t + 'Jack #t + 'Kack #f)))) + +(test-case + "bitfield should decode with post-decode" + (parameterize ([current-input-port (open-input-bytes (bytes (bitwise-ior JACK MACK PACK NACK QUACK)))]) + (set-post-decode! bitfield (λ (fh . _) (hash-set! fh 'foo 42) fh)) + (check-equal? (decode bitfield) (mhasheq 'Quack #t + 'Nack #t + 'Lack #f + 'Oack #f + 'Pack #t + 'Mack #t + 'Jack #t + 'Kack #f + 'foo 42)))) -; it 'should decode', -> -(parameterize ([current-input-port (open-input-bytes (bytes (bitwise-ior JACK MACK PACK NACK QUACK)))]) - (check-equal? (decode bitfield) (mhasheq 'Quack #t - 'Nack #t - 'Lack #f - 'Oack #f - 'Pack #t - 'Mack #t - 'Jack #t - 'Kack #f))) +(test-case + "bitfield should encode" + (check-equal? (encode bitfield (mhasheq 'Quack #t + 'Nack #t + 'Lack #f + 'Oack #f + 'Pack #t + 'Mack #t + 'Jack #t + 'Kack #f) #f) + (bytes (bitwise-ior JACK MACK PACK NACK QUACK)))) -; it 'should encode', (done) -> -(check-equal? (encode bitfield (mhasheq 'Quack #t - 'Nack #t - 'Lack #f - 'Oack #f - 'Pack #t - 'Mack #t - 'Jack #t - 'Kack #f) #f) - (bytes (bitwise-ior JACK MACK PACK NACK QUACK))) +(test-case + "bitfield should encode with pre-encode" + (set-pre-encode! bitfield (λ (fh . _) + (hash-set! fh 'Jack #f) + (hash-set! fh 'Mack #f) + (hash-set! fh 'Pack #f) + fh)) + (check-equal? (encode bitfield (mhasheq 'Quack #t + 'Nack #t + 'Lack #f + 'Oack #f + 'Pack #t + 'Mack #t + 'Jack #t + 'Kack #f) #f) + (bytes (bitwise-ior NACK QUACK)))) diff --git a/xenomorph/xenomorph/test/buffer-test.rkt b/xenomorph/xenomorph/test/buffer-test.rkt index f3d19d68..d62a5266 100644 --- a/xenomorph/xenomorph/test/buffer-test.rkt +++ b/xenomorph/xenomorph/test/buffer-test.rkt @@ -1,46 +1,64 @@ #lang racket/base (require rackunit - xenomorph - sugar/unstable/dict) - + sugar/unstable/dict + "../buffer.rkt" + "../number.rkt" + "../helper.rkt") #| approximates https://github.com/mbutterick/restructure/blob/master/test/Buffer.coffee |# -;describe 'Buffer', -> -; describe 'decode', -> -; it 'should decode', -> -(parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) - (define buf (+BufferT 2)) - (check-equal? (decode buf) (bytes #xab #xff)) - (check-equal? (decode buf) (bytes #x1f #xb6))) - - -; it 'should decode with parent key length', -> -(parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) - (define buf (+BufferT 'len)) - (check-equal? (decode buf #:parent (hash 'len 3)) (bytes #xab #xff #x1f)) - (check-equal? (decode buf #:parent (hash 'len 1)) (bytes #xb6))) +(test-case + "buffer should decode" + (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) + (define buf (+xbuffer #:length 2)) + (check-equal? (decode buf) (bytes #xab #xff)) + (check-equal? (decode buf) (bytes #x1f #xb6)))) +(test-case + "buffer should error on invalid length" + (check-exn exn:fail:contract? (λ () (+xbuffer #:length #true)))) -; describe 'size', -> -; it 'should return size', -> -(check-equal? (size (+BufferT 2) (bytes #xab #xff)) 2) +(test-case + "buffer should decode with post-decode" + (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) + (define buf (+xbuffer #:length 2)) + (set-post-decode! buf (λ (bs) (bytes 1 2))) + (check-equal? (decode buf) (bytes 1 2)) + (check-equal? (decode buf) (bytes 1 2)))) +(test-case + "buffer should decode with parent key length" + (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) + (define buf (+xbuffer #:length 'len)) + (check-equal? (decode buf #:parent (hash 'len 3)) (bytes #xab #xff #x1f)) + (check-equal? (decode buf #:parent (hash 'len 1)) (bytes #xb6)))) -; it 'should use defined length if no value given', ->x -(check-equal? (size (+BufferT 10)) 10) +(test-case + "size should return size" + (check-equal? (size (+xbuffer #:length 2) (bytes #xab #xff)) 2)) +(test-case + "size should use defined length if no value given" + (check-equal? (size (+xbuffer #:length 10)) 10)) -; describe 'encode', -> -; it 'should encode', (done) -> -(let ([buf (+BufferT 2)]) - (check-equal? (bytes-append - (encode buf (bytes #xab #xff) #f) - (encode buf (bytes #x1f #xb6) #f)) (bytes #xab #xff #x1f #xb6))) +(test-case + "encode should encode" + (let ([buf (+xbuffer 2)]) + (check-equal? (bytes-append + (encode buf (bytes #xab #xff) #f) + (encode buf (bytes #x1f #xb6) #f)) (bytes #xab #xff #x1f #xb6)))) +(test-case + "encode should encode with pre-encode" + (let ([buf (+xbuffer 2)]) + (set-pre-encode! buf (λ (bs) (bytes 1 2))) + (check-equal? (bytes-append + (encode buf (bytes #xab #xff) #f) + (encode buf (bytes #x1f #xb6) #f)) (bytes 1 2 1 2)))) -; it 'should encode length before buffer', (done) -> -(check-equal? (encode (+BufferT uint8) (bytes #xab #xff) #f) (bytes 2 #xab #xff)) \ No newline at end of file +(test-case + "encode should encode length before buffer" + (check-equal? (encode (+xbuffer #:length uint8) (bytes #xab #xff) #f) (bytes 2 #xab #xff))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/enum-test.rkt b/xenomorph/xenomorph/test/enum-test.rkt index 5ca766c6..7fd73d22 100644 --- a/xenomorph/xenomorph/test/enum-test.rkt +++ b/xenomorph/xenomorph/test/enum-test.rkt @@ -1,37 +1,64 @@ #lang racket/base (require rackunit - xenomorph - sugar/unstable/dict) + sugar/unstable/dict + "../helper.rkt" + "../number.rkt" + "../enum.rkt") #| approximates https://github.com/mbutterick/restructure/blob/master/test/Enum.coffee |# -;describe 'Enum', -> -; e = new Enum uint8, ['foo', 'bar', 'baz'] -; it 'should have the right size', -> -; e.size().should.equal 1 +(define e (+xenum #:type uint8 + #:values '("foo" "bar" "baz"))) -(define e (+Enum uint8 '("foo" "bar" "baz"))) -(check-equal? (size e) 1) +(test-case + "should error with invalid type" + (check-exn exn:fail:contract? (λ () (+xenum 42)))) +(test-case + "should error with invalid values" + (check-exn exn:fail:contract? (λ () (+xenum #:values 42)))) -; it 'should decode', -> -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 0))]) - (check-equal? (decode e) "bar") - (check-equal? (decode e) "baz") - (check-equal? (decode e) "foo")) +(test-case + "should have the right size" + (check-equal? (size e) 1)) +(test-case + "decode should decode" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 0))]) + (check-equal? (decode e) "bar") + (check-equal? (decode e) "baz") + (check-equal? (decode e) "foo"))) -; it 'should encode', (done) -> -(parameterize ([current-output-port (open-output-bytes)]) - (encode e "bar") - (encode e "baz") - (encode e "foo") - (check-equal? (dump (current-output-port)) (bytes 1 2 0))) +(test-case + "decode should decode with post-decode" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 0))]) + (set-post-decode! e (λ (val) "foobar")) + (check-equal? (decode e) "foobar") + (check-equal? (decode e) "foobar") + (check-equal? (decode e) "foobar"))) +(test-case + "encode should encode" + (parameterize ([current-output-port (open-output-bytes)]) + (encode e "bar") + (encode e "baz") + (encode e "foo") + (check-equal? (dump (current-output-port)) (bytes 1 2 0)))) -; it 'should throw on unknown option', -> +(test-case + "encode should encode with pre-encode" + (parameterize ([current-output-port (open-output-bytes)]) + (set-pre-encode! e (λ (val) "foo")) + (encode e "bar") + (encode e "baz") + (encode e "foo") + (check-equal? (dump (current-output-port)) (bytes 0 0 0)))) -(check-exn exn:fail:contract? (λ () (encode e "unknown" (open-output-bytes)))) \ No newline at end of file +(test-case + "should throw on unknown option" + (set-pre-encode! e values) + (set-post-decode! e values) + (check-exn exn:fail:contract? (λ () (encode e "unknown" (open-output-bytes))))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/lazy-array-test.rkt b/xenomorph/xenomorph/test/lazy-array-test.rkt index 73eeb6a9..e1d76c0d 100644 --- a/xenomorph/xenomorph/test/lazy-array-test.rkt +++ b/xenomorph/xenomorph/test/lazy-array-test.rkt @@ -1,62 +1,76 @@ #lang racket/base (require rackunit - xenomorph - sugar/unstable/dict - "../private/generic.rkt") + racket/dict + racket/stream + "../array.rkt" + "../helper.rkt" + "../number.rkt" + "../lazy-array.rkt") #| approximates https://github.com/mbutterick/restructure/blob/master/test/LazyArray.coffee |# -;describe 'LazyArray', -> -; describe 'decode', -> -; it 'should decode items lazily', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define array (+LazyArray uint8 4)) - (define arr (decode array)) - (check-false (Array? arr)) - (check-equal? (ref arr 'len) 4) - (check-equal? (pos (current-input-port)) 4) - (check-equal? (get arr 0) 1) - (check-equal? (get arr 1) 2) - (check-equal? (get arr 2) 3) - (check-equal? (get arr 3) 4)) - -; it 'should be able to convert to an array', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define array (+LazyArray uint8 4)) - (define arr (decode array)) - (check-equal? (LazyArray->list arr) '(1 2 3 4))) - - -; it 'should have an inspect method', -> -; [skipped] - - -; it 'should decode length as number before array', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) - (define array (+LazyArray uint8 uint8)) - (define arr (decode array)) - (check-equal? (LazyArray->list arr) '(1 2 3 4))) - -; -; describe 'size', -> -; it 'should work with LazyArrays', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define array (+LazyArray uint8 4)) - (define arr (decode array)) - (check-equal? (size array arr) 4)) - - -; describe 'encode', -> -; it 'should work with LazyArrays', (done) -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) - (define array (+LazyArray uint8 4)) - (define arr (decode array)) - (check-equal? (encode array arr #f) (bytes 1 2 3 4))) \ No newline at end of file +(test-case + "decode should decode items lazily" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define xla (+xlazy-array uint8 4)) + (define arr (decode xla)) + (check-false (xarray? arr)) + (check-equal? (stream-length arr) 4) + (check-equal? (pos (current-input-port)) 4) + (check-equal? (stream-ref arr 0) 1) + (check-equal? (stream-ref arr 1) 2) + (check-equal? (stream-ref arr 2) 3) + (check-equal? (stream-ref arr 3) 4))) + +(test-case + "decode should decode items lazily with post-decode" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define xla (+xlazy-array uint8 4)) + (set-post-decode! xla (λ (val) (* 2 val))) + (define arr (decode xla)) + (check-false (xarray? arr)) + (check-equal? (stream-length arr) 4) + (check-equal? (pos (current-input-port)) 4) + (check-equal? (stream-ref arr 0) 2) + (check-equal? (stream-ref arr 1) 4) + (check-equal? (stream-ref arr 2) 6) + (check-equal? (stream-ref arr 3) 8))) + +(test-case + "should be able to convert to an array" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define xla (+xlazy-array uint8 4)) + (define arr (decode xla)) + (check-equal? (stream->list arr) '(1 2 3 4)))) + +(test-case + "decode should decode length as number before array" + (parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) + (define xla (+xlazy-array uint8 uint8)) + (define arr (decode xla)) + (check-equal? (stream->list arr) '(1 2 3 4)))) + +(test-case + "size should work with xlazy-arrays" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define xla (+xlazy-array uint8 4)) + (define arr (decode xla)) + (check-equal? (size xla arr) 4))) + +(test-case + "encode should work with xlazy-arrays" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define xla (+xlazy-array uint8 4)) + (define arr (decode xla)) + (check-equal? (encode xla arr #f) (bytes 1 2 3 4)))) + +(test-case + "encode should work with xlazy-arrays with pre-encode" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define xla (+xlazy-array uint8 4)) + (set-pre-encode! xla (λ (vals) (map (λ (val) (* 2 val)) vals))) + (define arr (decode xla)) + (check-equal? (encode xla arr #f) (bytes 2 4 6 8)))) diff --git a/xenomorph/xenomorph/test/number-test.rkt b/xenomorph/xenomorph/test/number-test.rkt index 0ac7eb4c..a10cccfb 100644 --- a/xenomorph/xenomorph/test/number-test.rkt +++ b/xenomorph/xenomorph/test/number-test.rkt @@ -1,339 +1,209 @@ #lang racket/base -(require rackunit - xenomorph - racket/class - sugar/unstable/dict) - +(require rackunit "../number.rkt" "../helper.rkt") #| approximates https://github.com/mbutterick/restructure/blob/master/test/Number.coffee |# -;describe 'Number', -> -; describe 'uint8', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> -(parameterize ([current-input-port (open-input-bytes (bytes #xab #xff))]) - (check-equal? (decode uint8) #xab) - (check-equal? (decode uint8) #xff)) - -(check-equal? (size uint8) 1) - -(let ([port (open-output-bytes)]) - (encode uint8 #xab port) - (encode uint8 #xff port) - (check-equal? (dump port) (bytes #xab #xff))) - - -; describe 'uint16', -> -; it 'is an alias for uint16be', -> -; modified test: `uint16` is the same endianness as the platform -(check-equal? (decode uint16 (bytes 0 1)) (send (if (system-big-endian?) - uint16be - uint16le) decode (bytes 0 1))) - -; describe 'uint16be', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode uint16be (open-input-bytes (bytes #xab #xff))) #xabff) -(check-equal? (size uint16be) 2) -(check-equal? (encode uint16be #xabff #f) (bytes #xab #xff)) - -; -; describe 'uint16le', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode uint16le (open-input-bytes (bytes #xff #xab))) #xabff) -(check-equal? (size uint16le) 2) -(check-equal? (encode uint16le #xabff #f) (bytes #xff #xab)) - -; -; describe 'uint24', -> -; it 'is an alias for uint24be', -> -;; modified test: `uint24` is the same endianness as the platform -(check-equal? (decode uint24 (bytes 0 1 2)) (send (if (system-big-endian?) - uint24be - uint24le) decode (bytes 0 1 2))) - -; -; describe 'uint24be', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode uint24be (open-input-bytes (bytes #xff #xab #x24))) #xffab24) -(check-equal? (size uint24be) 3) -(check-equal? (encode uint24be #xffab24 #f) (bytes #xff #xab #x24)) - -; -; describe 'uint24le', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode uint24le (open-input-bytes (bytes #x24 #xab #xff))) #xffab24) -(check-equal? (size uint24le) 3) -(check-equal? (encode uint24le #xffab24 #f) (bytes #x24 #xab #xff)) - -; -; describe 'uint32', -> -; it 'is an alias for uint32be', -> -;; modified test: `uint32` is the same endianness as the platform -(check-equal? (decode uint32 (bytes 0 1 2 3)) (send (if (system-big-endian?) - uint32be - uint32le) decode (bytes 0 1 2 3))) - -; -; describe 'uint32be', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode uint32be (open-input-bytes (bytes #xff #xab #x24 #xbf))) #xffab24bf) -(check-equal? (size uint32be) 4) -(check-equal? (encode uint32be #xffab24bf #f) (bytes #xff #xab #x24 #xbf)) - -; -; describe 'uint32le', -> -; it 'should decode', -> -; it 'should encode', (done) -> - -(check-equal? (decode uint32le (open-input-bytes (bytes #xbf #x24 #xab #xff))) #xffab24bf) -(check-equal? (size uint32le) 4) -(check-equal? (encode uint32le #xffab24bf #f) (bytes #xbf #x24 #xab #xff)) - - -; -; describe 'int8', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(let ([port (open-input-bytes (bytes #x7f #xff))]) - (check-equal? (decode int8 port) 127) - (check-equal? (decode int8 port) -1)) - -(check-equal? (size int8) 1) - -(let ([port (open-output-bytes)]) - (encode int8 127 port) - (encode int8 -1 port) - (check-equal? (dump port) (bytes #x7f #xff))) - - -; -; describe 'int16', -> -; it 'is an alias for int16be', -> -; int16.should.equal int16be - -;; modified test: `int16` is the same endianness as the platform -(check-equal? (decode int16 (bytes 0 1)) (send (if (system-big-endian?) - int16be - int16le) decode (bytes 0 1))) - - -; -; describe 'int16be', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(let ([port (open-input-bytes (bytes #xff #xab))]) - (check-equal? (decode int16be port) -85)) - -(check-equal? (size int16be) 2) - -(let ([port (open-output-bytes)]) - (encode int16be -85 port) - (check-equal? (dump port) (bytes #xff #xab))) - - -; describe 'int16le', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode int16le (open-input-bytes (bytes #xab #xff))) -85) -(check-equal? (size int16le) 2) -(check-equal? (encode int16le -85 #f) (bytes #xab #xff)) - - -; -; describe 'int24', -> -; it 'is an alias for int24be', -> -; int24.should.equal int24be - -;; modified test: `int24` is the same endianness as the platform -(check-equal? (decode int24 (bytes 0 1 2)) (send (if (system-big-endian?) - int24be - int24le) decode (bytes 0 1 2))) - - -; -; describe 'int24be', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode int24be (open-input-bytes (bytes #xff #xab #x24))) -21724) -(check-equal? (size int24be) 3) -(check-equal? (encode int24be -21724 #f) (bytes #xff #xab #x24)) - -; -; describe 'int24le', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode int24le (open-input-bytes (bytes #x24 #xab #xff))) -21724) -(check-equal? (size int24le) 3) -(check-equal? (encode int24le -21724 #f) (bytes #x24 #xab #xff)) - - - -; describe 'int32', -> -; it 'is an alias for int32be', -> -; modified test: `int32` is the same endianness as the platform -(check-equal? (decode int32 (bytes 0 1 2 3)) (send (if (system-big-endian?) - int32be - int32le) decode (bytes 0 1 2 3))) - - - -; -; describe 'int32be', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode int32be (open-input-bytes (bytes #xff #xab #x24 #xbf))) -5561153) -(check-equal? (size int32be) 4) -(check-equal? (encode int32be -5561153 #f) (bytes #xff #xab #x24 #xbf)) - -; -; describe 'int32le', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode int32le (open-input-bytes (bytes #xbf #x24 #xab #xff))) -5561153) -(check-equal? (size int32le) 4) -(check-equal? (encode int32le -5561153 #f) (bytes #xbf #x24 #xab #xff)) - -; -; describe 'float', -> -; it 'is an alias for floatbe', -> -; modified test: `float` is the same endianness as the platform -(check-equal? (decode float (bytes 0 1 2 3)) (send (if (system-big-endian?) - floatbe - floatle) decode (bytes 0 1 2 3))) - -; -; describe 'floatbe', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-= (decode floatbe (open-input-bytes (bytes #x43 #x7a #x8c #xcd))) 250.55 0.01) -(check-equal? (size floatbe) 4) -(check-equal? (encode floatbe 250.55 #f) (bytes #x43 #x7a #x8c #xcd)) - -; -; describe 'floatle', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-= (decode floatle (open-input-bytes (bytes #xcd #x8c #x7a #x43))) 250.55 0.01) -(check-equal? (size floatle) 4) -(check-equal? (encode floatle 250.55 #f) (bytes #xcd #x8c #x7a #x43)) - -; -; describe 'double', -> -; it 'is an alias for doublebe', -> -; modified test: `double` is the same endianness as the platform -(check-equal? (decode double (bytes 0 1 2 3 4 5 6 7)) (send (if (system-big-endian?) - doublebe - doublele) decode (bytes 0 1 2 3 4 5 6 7))) - -; -; describe 'doublebe', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode doublebe (open-input-bytes (bytes #x40 #x93 #x4a #x3d #x70 #xa3 #xd7 #x0a))) 1234.56) -(check-equal? (size doublebe) 8) -(check-equal? (encode doublebe 1234.56 #f) (bytes #x40 #x93 #x4a #x3d #x70 #xa3 #xd7 #x0a)) - -; -; describe 'doublele', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-equal? (decode doublele (open-input-bytes (bytes #x0a #xd7 #xa3 #x70 #x3d #x4a #x93 #x40))) 1234.56) -(check-equal? (size doublele) 8) -(check-equal? (encode doublele 1234.56 #f) (bytes #x0a #xd7 #xa3 #x70 #x3d #x4a #x93 #x40)) - -; -; describe 'fixed16', -> -; it 'is an alias for fixed16be', -> -; modified test: `fixed16` is the same endianness as the platform -(check-equal? (decode fixed16 (bytes 0 1)) (send (if (system-big-endian?) - fixed16be - fixed16le) decode (bytes 0 1))) - -; -; describe 'fixed16be', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-= (decode fixed16be (open-input-bytes (bytes #x19 #x57))) 25.34 0.01) -(check-equal? (size fixed16be) 2) -(check-equal? (encode fixed16be 25.34 #f) (bytes #x19 #x57)) - -; -; describe 'fixed16le', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-= (decode fixed16le (open-input-bytes (bytes #x57 #x19))) 25.34 0.01) -(check-equal? (size fixed16le) 2) -(check-equal? (encode fixed16le 25.34 #f) (bytes #x57 #x19)) - -; -; describe 'fixed32', -> -; it 'is an alias for fixed32be', -> -; modified test: `fixed32` is the same endianness as the platform - -(check-equal? (decode fixed32 (bytes 0 1 2 3)) (send (if (system-big-endian?) - fixed32be - fixed32le) decode (bytes 0 1 2 3))) - -; -; describe 'fixed32be', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> -(check-= (decode fixed32be (open-input-bytes (bytes #x00 #xfa #x8c #xcc))) 250.55 0.01) -(check-equal? (size fixed32be) 4) -(check-equal? (encode fixed32be 250.55 #f) (bytes #x00 #xfa #x8c #xcc)) - -; -; describe 'fixed32le', -> -; it 'should decode', -> -; it 'should have a size', -> -; it 'should encode', (done) -> - -(check-= (decode fixed32le (open-input-bytes (bytes #xcc #x8c #xfa #x00))) 250.55 0.01) -(check-equal? (size fixed32le) 4) -(check-equal? (encode fixed32le 250.55 #f) (bytes #xcc #x8c #xfa #x00)) \ No newline at end of file +(test-case + "uint8: decode, size, encode" + (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff))]) + (check-equal? (decode uint8) #xab) + (check-equal? (decode uint8) #xff)) + (check-equal? (size uint8) 1) + (let ([port (open-output-bytes)]) + (encode uint8 #xab port) + (encode uint8 #xff port) + (check-equal? (dump port) (bytes #xab #xff)))) + +(test-case + "uint8: decode with post-decode, size, encode with pre-encode" + (define myuint8 (+xint 1 #:signed #f)) + (parameterize ([current-input-port (open-input-bytes (bytes #xab #xff))]) + (set-post-decode! myuint8 (λ (b) #xdeadbeef)) + (check-equal? (decode myuint8) #xdeadbeef) + (check-equal? (decode myuint8) #xdeadbeef)) + (check-equal? (size myuint8) 1) + (let ([port (open-output-bytes)]) + (set-pre-encode! myuint8 (λ (b) #xcc)) + (encode myuint8 #xab port) + (encode myuint8 #xff port) + (check-equal? (dump port) (bytes #xcc #xcc)))) + +(test-case + "uint16 is the same endianness as the platform" + (check-equal? (decode uint16 (bytes 0 1)) + (decode (if (system-big-endian?) uint16be uint16le) (bytes 0 1)))) + +(test-case + "uint16be: decode, size, encode" + (check-equal? (decode uint16be (open-input-bytes (bytes #xab #xff))) #xabff) + (check-equal? (size uint16be) 2) + (check-equal? (encode uint16be #xabff #f) (bytes #xab #xff))) + +(test-case + "uint16le: decode, size, encode" + (check-equal? (decode uint16le (open-input-bytes (bytes #xff #xab))) #xabff) + (check-equal? (size uint16le) 2) + (check-equal? (encode uint16le #xabff #f) (bytes #xff #xab))) + +(test-case + "uint24 is the same endianness as the platform" + (check-equal? (decode uint24 (bytes 0 1 2)) + (decode (if (system-big-endian?) uint24be uint24le) (bytes 0 1 2)))) +(test-case + "uint24be: decode, size, encode" + (check-equal? (decode uint24be (open-input-bytes (bytes #xff #xab #x24))) #xffab24) + (check-equal? (size uint24be) 3) + (check-equal? (encode uint24be #xffab24 #f) (bytes #xff #xab #x24))) + +(test-case + "uint24le: decode, size, encode" + (check-equal? (decode uint24le (open-input-bytes (bytes #x24 #xab #xff))) #xffab24) + (check-equal? (size uint24le) 3) + (check-equal? (encode uint24le #xffab24 #f) (bytes #x24 #xab #xff))) + +(test-case + "uint32 is the same endianness as the platform" + (check-equal? (decode uint32 (bytes 0 1 2 3)) + (decode (if (system-big-endian?) uint32be uint32le) (bytes 0 1 2 3)))) +(test-case + "uint32be: decode, size, encode" + (check-equal? (decode uint32be (open-input-bytes (bytes #xff #xab #x24 #xbf))) #xffab24bf) + (check-equal? (size uint32be) 4) + (check-equal? (encode uint32be #xffab24bf #f) (bytes #xff #xab #x24 #xbf))) + +(test-case + "uint32le: decode, size, encode" + (check-equal? (decode uint32le (open-input-bytes (bytes #xbf #x24 #xab #xff))) #xffab24bf) + (check-equal? (size uint32le) 4) + (check-equal? (encode uint32le #xffab24bf #f) (bytes #xbf #x24 #xab #xff))) + +(test-case + "int8: decode, size, encode" + (let ([port (open-input-bytes (bytes #x7f #xff))]) + (check-equal? (decode int8 port) 127) + (check-equal? (decode int8 port) -1)) + (check-equal? (size int8) 1) + (let ([port (open-output-bytes)]) + (encode int8 127 port) + (encode int8 -1 port) + (check-equal? (dump port) (bytes #x7f #xff)))) + +(test-case + "int32 is the same endianness as the platform" + (check-equal? (decode int16 (bytes 0 1)) + (decode (if (system-big-endian?) int16be int16le) (bytes 0 1)))) +(test-case + "int16be: decode, size, encode" + (let ([port (open-input-bytes (bytes #xff #xab))]) + (check-equal? (decode int16be port) -85)) + (check-equal? (size int16be) 2) + (let ([port (open-output-bytes)]) + (encode int16be -85 port) + (check-equal? (dump port) (bytes #xff #xab)))) + +(test-case + "int16le: decode, size, encode" + (check-equal? (decode int16le (open-input-bytes (bytes #xab #xff))) -85) + (check-equal? (size int16le) 2) + (check-equal? (encode int16le -85 #f) (bytes #xab #xff))) + +(test-case + "int24 is the same endianness as the platform" + (check-equal? (decode int24 (bytes 0 1 2)) + (decode (if (system-big-endian?) int24be int24le) (bytes 0 1 2)))) +(test-case + "int24be: decode, size, encode" + (check-equal? (decode int24be (open-input-bytes (bytes #xff #xab #x24))) -21724) + (check-equal? (size int24be) 3) + (check-equal? (encode int24be -21724 #f) (bytes #xff #xab #x24))) + +(test-case + "int24le: decode, size, encode" + (check-equal? (decode int24le (open-input-bytes (bytes #x24 #xab #xff))) -21724) + (check-equal? (size int24le) 3) + (check-equal? (encode int24le -21724 #f) (bytes #x24 #xab #xff))) +(test-case + "int32 is the same endianness as the platform" + (check-equal? (decode int32 (bytes 0 1 2 3)) + (decode (if (system-big-endian?) int32be int32le) (bytes 0 1 2 3)))) + +(test-case + "int32be: decode, size, encode" + (check-equal? (decode int32be (open-input-bytes (bytes #xff #xab #x24 #xbf))) -5561153) + (check-equal? (size int32be) 4) + (check-equal? (encode int32be -5561153 #f) (bytes #xff #xab #x24 #xbf))) + +(test-case + "int32le: decode, size, encode" + (check-equal? (decode int32le (open-input-bytes (bytes #xbf #x24 #xab #xff))) -5561153) + (check-equal? (size int32le) 4) + (check-equal? (encode int32le -5561153 #f) (bytes #xbf #x24 #xab #xff))) + +(test-case + "float is the same endianness as the platform" + (check-equal? (decode float (bytes 0 1 2 3)) + (decode (if (system-big-endian?) floatbe floatle) (bytes 0 1 2 3)))) +(test-case + "floatbe: decode, size, encode" + (check-= (decode floatbe (open-input-bytes (bytes #x43 #x7a #x8c #xcd))) 250.55 0.01) + (check-equal? (size floatbe) 4) + (check-equal? (encode floatbe 250.55 #f) (bytes #x43 #x7a #x8c #xcd))) + +(test-case + "floatle: decode, size, encode" + (check-= (decode floatle (open-input-bytes (bytes #xcd #x8c #x7a #x43))) 250.55 0.01) + (check-equal? (size floatle) 4) + (check-equal? (encode floatle 250.55 #f) (bytes #xcd #x8c #x7a #x43))) + +(test-case + "double is the same endianness as the platform" + (check-equal? (decode double (bytes 0 1 2 3 4 5 6 7)) + (decode (if (system-big-endian?) doublebe doublele) (bytes 0 1 2 3 4 5 6 7)))) +(test-case + "doublebe: decode, size, encode" + (check-equal? (decode doublebe (open-input-bytes (bytes #x40 #x93 #x4a #x3d #x70 #xa3 #xd7 #x0a))) 1234.56) + (check-equal? (size doublebe) 8) + (check-equal? (encode doublebe 1234.56 #f) (bytes #x40 #x93 #x4a #x3d #x70 #xa3 #xd7 #x0a))) + +(test-case + "doublele: decode, size, encode" + (check-equal? (decode doublele (open-input-bytes (bytes #x0a #xd7 #xa3 #x70 #x3d #x4a #x93 #x40))) 1234.56) + (check-equal? (size doublele) 8) + (check-equal? (encode doublele 1234.56 #f) (bytes #x0a #xd7 #xa3 #x70 #x3d #x4a #x93 #x40))) + +(test-case + "fixed16 is the same endianness as the platform" + (check-equal? (decode fixed16 (bytes 0 1)) + (decode (if (system-big-endian?) fixed16be fixed16le) (bytes 0 1)))) + +(test-case + "fixed16be: decode, size, encode" + (check-= (decode fixed16be (open-input-bytes (bytes #x19 #x57))) 25.34 0.01) + (check-equal? (size fixed16be) 2) + (check-equal? (encode fixed16be 25.34 #f) (bytes #x19 #x57))) + +(test-case + "fixed16le: decode, size, encode" + (check-= (decode fixed16le (open-input-bytes (bytes #x57 #x19))) 25.34 0.01) + (check-equal? (size fixed16le) 2) + (check-equal? (encode fixed16le 25.34 #f) (bytes #x57 #x19))) + +(test-case + "fixed32 is the same endianness as the platform" + (check-equal? (decode fixed32 (bytes 0 1 2 3)) + (decode (if (system-big-endian?) fixed32be fixed32le) (bytes 0 1 2 3)))) + +(test-case + "fixed32be: decode, size, encode" + (check-= (decode fixed32be (open-input-bytes (bytes #x00 #xfa #x8c #xcc))) 250.55 0.01) + (check-equal? (size fixed32be) 4) + (check-equal? (encode fixed32be 250.55 #f) (bytes #x00 #xfa #x8c #xcc))) + +(test-case + "fixed32le: decode, size, encode" + (check-= (decode fixed32le (open-input-bytes (bytes #xcc #x8c #xfa #x00))) 250.55 0.01) + (check-equal? (size fixed32le) 4) + (check-equal? (encode fixed32le 250.55 #f) (bytes #xcc #x8c #xfa #x00))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/optional-test.rkt b/xenomorph/xenomorph/test/optional-test.rkt index d5e87301..e3749772 100644 --- a/xenomorph/xenomorph/test/optional-test.rkt +++ b/xenomorph/xenomorph/test/optional-test.rkt @@ -1,116 +1,116 @@ #lang racket/base (require rackunit - xenomorph - sugar/unstable/dict) + "../helper.rkt" + "../number.rkt" + "../optional.rkt") #| approximates https://github.com/mbutterick/restructure/blob/master/test/Optional.coffee |# -;describe 'Optional', -> -; describe 'decode', -> -; it 'should not decode when condition is falsy', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+Optional uint8 #f)) - (check-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 0)) - -; it 'should not decode when condition is a function and falsy', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+Optional uint8 (λ _ #f))) - (check-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 0)) - - -; it 'should decode when condition is omitted', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+Optional uint8)) - (check-not-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 1)) - -; -; it 'should decode when condition is truthy', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+Optional uint8 #t)) - (check-not-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 1)) - - -; it 'should decode when condition is a function and truthy', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (define optional (+Optional uint8 (λ _ #t))) - (check-not-equal? (decode optional) (void)) - (check-equal? (pos (current-input-port)) 1)) - - -; describe 'size', -> - -(check-equal? (size (+Optional uint8 #f)) 0) - -; -; it 'should return 0 when condition is a function and falsy', -> - -(check-equal? (size (+Optional uint8 (λ _ #f))) 0) - - -; it 'should return given type size when condition is omitted', -> - -(check-equal? (size (+Optional uint8)) 1) - - -; it 'should return given type size when condition is truthy', -> - -(check-equal? (size (+Optional uint8 #t)) 1) - - -; it 'should return given type size when condition is a function and truthy', -> - -(check-equal? (size (+Optional uint8 (λ _ #t))) 1) - - -; describe 'encode', -> -; it 'should not encode when condition is falsy', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define optional (+Optional uint8 #f)) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes))) - - -; it 'should not encode when condition is a function and falsy', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define optional (+Optional uint8 (λ _ #f))) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes))) - - -; -; it 'should encode when condition is omitted', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define optional (+Optional uint8)) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes 128))) - - -; it 'should encode when condition is truthy', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define optional (+Optional uint8 #t)) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes 128))) - - -; it 'should encode when condition is a function and truthy', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define optional (+Optional uint8 (λ _ #t))) - (encode optional 128) - (check-equal? (dump (current-output-port)) (bytes 128))) \ No newline at end of file +(test-case + "decode should not decode when condition is falsy" + (parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+xoptional #:type uint8 #:condition #f)) + (check-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 0))) + +(test-case + "decode with post-decode" + (parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+xoptional #:type uint8 #:condition #f)) + (set-post-decode! optional (λ (val) 42)) + (check-equal? (decode optional) 42) + (check-equal? (pos (current-input-port)) 0))) + +(test-case + "decode should not decode when condition is a function and falsy" + (parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+xoptional #:type uint8 #:condition (λ _ #f))) + (check-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 0))) + +(test-case + "decode should decode when condition is omitted" + (parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+xoptional #:type uint8)) + (check-not-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 1))) + +(test-case + "decode should decode when condition is truthy" + (parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+xoptional #:type uint8 #:condition #t)) + (check-not-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 1))) + +(test-case + "decode should decode when condition is a function and truthy" + (parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+xoptional #:type uint8 #:condition (λ _ #t))) + (check-not-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 1))) + +(test-case + "size" + (check-equal? (size (+xoptional #:type uint8 #:condition #f)) 0)) + +(test-case + "size should return 0 when condition is a function and falsy" + (check-equal? (size (+xoptional #:type uint8 #:condition (λ _ #f))) 0)) + +(test-case + "size should return given type size when condition is omitted" + (check-equal? (size (+xoptional #:type uint8)) 1)) + +(test-case + "size should return given type size when condition is truthy" + (check-equal? (size (+xoptional #:type uint8 #:condition #t)) 1)) + +(test-case + "size should return given type size when condition is a function and truthy" + (check-equal? (size (+xoptional #:type uint8 #:condition (λ _ #t))) 1)) + +(test-case + "encode should not encode when condition is falsy" + (parameterize ([current-output-port (open-output-bytes)]) + (define optional (+xoptional #:type uint8 #:condition #f)) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes)))) + +(test-case + "encode with pre-encode" + (parameterize ([current-output-port (open-output-bytes)]) + (define optional (+xoptional #:type uint8)) + (set-pre-encode! optional (λ (val) 42)) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes 42)))) + +(test-case + "encode should not encode when condition is a function and falsy" + (parameterize ([current-output-port (open-output-bytes)]) + (define optional (+xoptional #:type uint8 #:condition (λ _ #f))) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes)))) + +(test-case + "encode should encode when condition is omitted" + (parameterize ([current-output-port (open-output-bytes)]) + (define optional (+xoptional #:type uint8)) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes 128)))) + +(test-case + "encode should encode when condition is truthy" + (parameterize ([current-output-port (open-output-bytes)]) + (define optional (+xoptional #:type uint8 #:condition #t)) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes 128)))) + +(test-case + "encode should encode when condition is a function and truthy" + (parameterize ([current-output-port (open-output-bytes)]) + (define optional (+xoptional #:type uint8 #:condition (λ _ #t))) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes 128)))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/pointer-test.rkt b/xenomorph/xenomorph/test/pointer-test.rkt index 71093e08..1178b8f8 100644 --- a/xenomorph/xenomorph/test/pointer-test.rkt +++ b/xenomorph/xenomorph/test/pointer-test.rkt @@ -1,239 +1,211 @@ -#lang racket/base +#lang debug racket/base (require rackunit - xenomorph - sugar/unstable/js - sugar/unstable/dict - racket/class - "../private/helper.rkt") + racket/dict + "../helper.rkt" + "../pointer.rkt" + "../number.rkt" + "../struct.rkt" + racket/promise + sugar/unstable/dict) #| approximates https://github.com/mbutterick/restructure/blob/master/test/Pointer.coffee |# - -;describe 'Pointer', -> -; describe 'decode', -> -; it 'should handle null pointers', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 0))]) - (check-false (decode (+Pointer uint8 uint8) #:parent (mhash '_startOffset 50)))) - - -; it 'should use local offsets from start of parent by default', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) - (check-equal? (decode (+Pointer uint8 uint8) #:parent (mhash '_startOffset 0)) 53)) - - -; it 'should support immediate offsets', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) - (check-equal? (decode (+Pointer uint8 uint8 (mhash 'type 'immediate))) 53)) - - -; it 'should support offsets relative to the parent', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 0 0 1 53))]) - (pos (current-input-port) 2) - (check-equal? (decode (+Pointer uint8 uint8 (mhash 'type 'parent)) - #:parent (mhash 'parent (mhash '_startOffset 2))) 53)) - - -; it 'should support global offsets', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 2 4 0 0 0 53))]) - (pos (current-input-port) 2) - (check-equal? (decode (+Pointer uint8 uint8 (mhash 'type 'global)) - #:parent (mhash 'parent (mhash 'parent (mhash '_startOffset 2)))) - 53)) - - -; it 'should support offsets relative to a property on the parent', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 0 0 0 0 53))]) - (check-equal? (decode (+Pointer uint8 uint8 (mhash 'relativeTo (λ (ctx) (· ctx parent ptr)))) - #:parent (mhash '_startOffset 0 'parent (mhash 'ptr 4))) - 53)) - - -; it 'should support returning pointer if there is no decode type', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 4))]) - (check-equal? (decode (+Pointer uint8 'void) - #:parent (mhash '_startOffset 0)) 4)) - - -; it 'should support decoding pointers lazily', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) - (define res (decode (+Struct (dictify 'ptr (+Pointer uint8 uint8 (mhasheq 'lazy #t)))))) - (check-true (LazyThunk? (hash-ref (get-field _kv res) 'ptr))) - (check-equal? (· res ptr) 53)) - - - -; describe 'size', -> - -(let ([ctx (mhash 'pointerSize 0)]) - (check-equal? (size (+Pointer uint8 uint8) 10 ctx) 1) - (check-equal? (· ctx pointerSize) 1)) - - - -; it 'should add to immediate pointerSize', -> - -(let ([ctx (mhash 'pointerSize 0)]) - (check-equal? (size (+Pointer uint8 uint8 (mhash 'type 'immediate)) 10 ctx) 1) - (check-equal? (· ctx pointerSize) 1)) - - -; it 'should add to parent pointerSize', -> - -(let ([ctx (mhash 'parent (mhash 'pointerSize 0))]) - (check-equal? (size (+Pointer uint8 uint8 (mhash 'type 'parent)) 10 ctx) 1) - (check-equal? (· ctx parent pointerSize) 1)) - - - -; it 'should add to global pointerSize', -> - -(let ([ctx (mhash 'parent (mhash 'parent (mhash 'parent (mhash 'pointerSize 0))))]) - (check-equal? (size (+Pointer uint8 uint8 (mhash 'type 'global)) 10 ctx) 1) - (check-equal? (· ctx parent parent parent pointerSize) 1)) - - - -; it 'should handle void pointers', -> - -(let ([ctx (mhash 'pointerSize 0)]) - (check-equal? (size (+Pointer uint8 'void) (+VoidPointer uint8 50) ctx) 1) - (check-equal? (· ctx pointerSize) 1)) - - -; it 'should throw if no type and not a void pointer', -> - -(let ([ctx (mhash 'pointerSize 0)]) - (check-exn exn:fail:contract? (λ () (size (+Pointer uint8 'void) 30 ctx)))) - - -; it 'should return a fixed size without a value', -> - -(check-equal? (size (+Pointer uint8 uint8)) 1) - - -; describe 'encode', -> -; it 'should handle null pointers', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define ctx (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 0 - 'pointers null)) - (encode (+Pointer uint8 uint8) #f #:parent ctx) - (check-equal? (· ctx pointerSize) 0) - (check-equal? (dump (current-output-port)) (bytes 0))) - - -; it 'should handle local offsets', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define ctx (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 1 - 'pointers null)) - (encode (+Pointer uint8 uint8) 10 #:parent ctx) - (check-equal? (· ctx pointerOffset) 2) - (check-equal? (· ctx pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent ctx))) - (check-equal? (dump (current-output-port)) (bytes 1))) - - -; it 'should handle immediate offsets', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define ctx (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 1 - 'pointers null)) - (encode (+Pointer uint8 uint8 (mhash 'type 'immediate)) 10 #:parent ctx) - (check-equal? (· ctx pointerOffset) 2) - (check-equal? (· ctx pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent ctx))) - (check-equal? (dump (current-output-port)) (bytes 0))) - - -; it 'should handle offsets relative to parent', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define ctx (mhash 'parent (mhash 'pointerSize 0 - 'startOffset 3 - 'pointerOffset 5 - 'pointers null))) - (encode (+Pointer uint8 uint8 (mhash 'type 'parent)) 10 #:parent ctx) - (check-equal? (· ctx parent pointerOffset) 6) - (check-equal? (· ctx parent pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent ctx))) - (check-equal? (dump (current-output-port)) (bytes 2))) - - - -; it 'should handle global offsets', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define ctx (mhash 'parent - (mhash 'parent - (mhash 'parent (mhash 'pointerSize 0 - 'startOffset 3 - 'pointerOffset 5 - 'pointers null))))) - (encode (+Pointer uint8 uint8 (mhash 'type 'global)) 10 #:parent ctx) - (check-equal? (· ctx parent parent parent pointerOffset) 6) - (check-equal? (· ctx parent parent parent pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent ctx))) - (check-equal? (dump (current-output-port)) (bytes 5))) - - -; it 'should support offsets relative to a property on the parent', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define ctx (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 10 - 'pointers null - 'val (mhash 'ptr 4))) - (encode (+Pointer uint8 uint8 (mhash 'relativeTo (λ (ctx) (· ctx ptr)))) 10 #:parent ctx) - (check-equal? (· ctx pointerOffset) 11) - (check-equal? (· ctx pointers) (list (mhasheq 'type uint8 - 'val 10 - 'parent ctx))) - (check-equal? (dump (current-output-port)) (bytes 6))) - - -; it 'should support void pointers', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define ctx (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 1 - 'pointers null)) - (encode (+Pointer uint8 'void) (+VoidPointer uint8 55) #:parent ctx) - (check-equal? (· ctx pointerOffset) 2) - (check-equal? (· ctx pointers) (list (mhasheq 'type uint8 - 'val 55 - 'parent ctx))) - (check-equal? (dump (current-output-port)) (bytes 1))) - - -; it 'should throw if not a void pointer instance', -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define ctx (mhash 'pointerSize 0 - 'startOffset 0 - 'pointerOffset 1 - 'pointers null)) - (check-exn exn:fail:contract? (λ () (encode (+Pointer uint8 'void) 44 #:parent ctx)))) +(test-case + "decode should handle null pointers" + (parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (check-false (decode (+xpointer) #:parent (mhash '_startOffset 50))))) + +(test-case + "decode should use local offsets from start of parent by default" + (parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) + (check-equal? (decode (+xpointer) #:parent (mhash '_startOffset 0)) 53))) + +(test-case + "decode should support immediate offsets" + (parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) + (check-equal? (decode (+xpointer #:style 'immediate)) 53))) + +(test-case + "decode should support offsets relative to the parent" + (parameterize ([current-input-port (open-input-bytes (bytes 0 0 1 53))]) + (pos (current-input-port) 2) + (check-equal? (decode (+xpointer #:style 'parent) + #:parent (mhash 'parent (mhash '_startOffset 2))) 53))) + +(test-case + "decode should support global offsets" + (parameterize ([current-input-port (open-input-bytes (bytes 1 2 4 0 0 0 53))]) + (pos (current-input-port) 2) + (check-equal? (decode (+xpointer #:style 'global) + #:parent (mhash 'parent (mhash 'parent (mhash '_startOffset 2)))) + 53))) + +(test-case + "decode should support offsets relative to a property on the parent" + (parameterize ([current-input-port (open-input-bytes (bytes 1 0 0 0 0 53))]) + (check-equal? (decode (+xpointer #:relative-to (λ (parent) (dict-ref (dict-ref parent 'parent) 'ptr))) + #:parent (mhash '_startOffset 0 'parent (mhash 'ptr 4))) + 53))) + +(test-case + "decode should support returning pointer if there is no decode type" + (parameterize ([current-input-port (open-input-bytes (bytes 4))]) + (check-equal? (decode (+xpointer uint8 'void) + #:parent (mhash '_startOffset 0)) 4))) + +(test-case + "decode should support decoding pointers lazily" + (parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) + (define res (decode (+xstruct (dictify 'ptr (+xpointer #:lazy #t))))) + (check-true (promise? (dict-ref (struct-dict-res-_kv res) 'ptr))) + (check-equal? (dict-ref res 'ptr) 53))) + +(test-case + "size" + (let ([parent (mhash 'pointerSize 0)]) + (check-equal? (size (+xpointer) 10 #:parent parent) 1) + (check-equal? (dict-ref parent 'pointerSize) 1))) + +(test-case + "size should add to immediate pointerSize" + (let ([parent (mhash 'pointerSize 0)]) + (check-equal? (size (+xpointer #:style 'immediate) 10 #:parent parent) 1) + (check-equal? (dict-ref parent 'pointerSize) 1))) + +(test-case + "size should add to parent pointerSize" + (let ([parent (mhash 'parent (mhash 'pointerSize 0))]) + (check-equal? (size (+xpointer #:style 'parent) 10 #:parent parent) 1) + (check-equal? (dict-ref (dict-ref parent 'parent) 'pointerSize) 1))) + +(test-case + "size should add to global pointerSize" + (let ([parent (mhash 'parent (mhash 'parent (mhash 'parent (mhash 'pointerSize 0))))]) + (check-equal? (size (+xpointer #:style 'global) 10 #:parent parent) 1) + (check-equal? (dict-ref (dict-ref (dict-ref (dict-ref parent 'parent) 'parent) 'parent) 'pointerSize) 1))) + +(test-case + "size should handle void pointers" + (let ([parent (mhash 'pointerSize 0)]) + (check-equal? (size (+xpointer uint8 'void) (+xvoid-pointer uint8 50) #:parent parent) 1) + (check-equal? (dict-ref parent 'pointerSize) 1))) + +(test-case + "size should throw if no type and not a void pointer" + (let ([parent (mhash 'pointerSize 0)]) + (check-exn exn:fail:contract? (λ () (size (+xpointer uint8 'void) 30 #:parent parent))))) + +(test-case + "size should return a fixed size without a value" + (check-equal? (size (+xpointer)) 1)) + +(test-case + "encode should handle null pointers" + (parameterize ([current-output-port (open-output-bytes)]) + (define parent (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 0 + 'pointers null)) + (encode (+xpointer) #f #:parent parent) + (check-equal? (dict-ref parent 'pointerSize) 0) + (check-equal? (dump (current-output-port)) (bytes 0)))) + +(test-case + "encode should handle local offsets" + (parameterize ([current-output-port (open-output-bytes)]) + (define parent (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 1 + 'pointers null)) + (encode (+xpointer) 10 #:parent parent) + (check-equal? (dict-ref parent 'pointerOffset) 2) + (check-equal? (dict-ref parent 'pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent parent))) + (check-equal? (dump (current-output-port)) (bytes 1)))) + +(test-case + "encode should handle immediate offsets" + (parameterize ([current-output-port (open-output-bytes)]) + (define parent (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 1 + 'pointers null)) + (encode (+xpointer #:style 'immediate) 10 #:parent parent) + (check-equal? (dict-ref parent 'pointerOffset) 2) + (check-equal? (dict-ref parent 'pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent parent))) + (check-equal? (dump (current-output-port)) (bytes 0)))) + +(test-case + "encode should handle offsets relative to parent" + (parameterize ([current-output-port (open-output-bytes)]) + (define parent (mhash 'parent (mhash 'pointerSize 0 + 'startOffset 3 + 'pointerOffset 5 + 'pointers null))) + (encode (+xpointer #:style 'parent) 10 #:parent parent) + (check-equal? (dict-ref (dict-ref parent 'parent) 'pointerOffset) 6) + (check-equal? (dict-ref (dict-ref parent 'parent) 'pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent parent))) + (check-equal? (dump (current-output-port)) (bytes 2)))) + +(test-case + "encode should handle global offsets" + (parameterize ([current-output-port (open-output-bytes)]) + (define parent (mhash 'parent + (mhash 'parent + (mhash 'parent (mhash 'pointerSize 0 + 'startOffset 3 + 'pointerOffset 5 + 'pointers null))))) + (encode (+xpointer #:style 'global) 10 #:parent parent) + (check-equal? (dict-ref (dict-ref (dict-ref (dict-ref parent 'parent) 'parent) 'parent) 'pointerOffset) 6) + (check-equal? (dict-ref (dict-ref (dict-ref (dict-ref parent 'parent) 'parent) 'parent) 'pointers) + (list (mhasheq 'type uint8 + 'val 10 + 'parent parent))) + (check-equal? (dump (current-output-port)) (bytes 5)))) + +(test-case + "encode should support offsets relative to a property on the parent" + (parameterize ([current-output-port (open-output-bytes)]) + (define parent (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 10 + 'pointers null + 'val (mhash 'ptr 4))) + (encode (+xpointer #:relative-to (λ (parent) (dict-ref parent 'ptr))) 10 #:parent parent) + (check-equal? (dict-ref parent 'pointerOffset) 11) + (check-equal? (dict-ref parent 'pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent parent))) + (check-equal? (dump (current-output-port)) (bytes 6)))) + +(test-case + "encode should support void pointers" + (parameterize ([current-output-port (open-output-bytes)]) + (define parent (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 1 + 'pointers null)) + (encode (+xpointer uint8 'void) (+xvoid-pointer uint8 55) #:parent parent) + (check-equal? (dict-ref parent 'pointerOffset) 2) + (check-equal? (dict-ref parent 'pointers) (list (mhasheq 'type uint8 + 'val 55 + 'parent parent))) + (check-equal? (dump (current-output-port)) (bytes 1)))) + +(test-case + "encode should throw if not a void pointer instance" + (parameterize ([current-output-port (open-output-bytes)]) + (define parent (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 1 + 'pointers null)) + (check-exn exn:fail:contract? (λ () (encode (+xpointer uint8 'void) 44 #:parent parent))))) diff --git a/xenomorph/xenomorph/test/reserved-test.rkt b/xenomorph/xenomorph/test/reserved-test.rkt index 40dbead8..a6f833ea 100644 --- a/xenomorph/xenomorph/test/reserved-test.rkt +++ b/xenomorph/xenomorph/test/reserved-test.rkt @@ -1,35 +1,48 @@ #lang racket/base (require rackunit - xenomorph - sugar/unstable/dict) + "../number.rkt" + "../helper.rkt" + "../reserved.rkt") #| approximates https://github.com/mbutterick/restructure/blob/master/test/Reserved.coffee |# -;describe 'Reserved', -> -; it 'should have a default count of 1', -> - -(check-equal? (size (+Reserved uint8)) 1) - - -; it 'should allow custom counts and types', -> - -(check-equal? (size (+Reserved uint16be 10)) 20) - - -; it 'should decode', -> - -(parameterize ([current-input-port (open-input-bytes (bytes 0 0))]) - (define reserved (+Reserved uint16be)) - (check-equal? (decode reserved) (void)) - (check-equal? (pos (current-input-port)) 2)) - - -; it 'should encode', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define reserved (+Reserved uint16be)) - (encode reserved #f) - (check-equal? (dump (current-output-port)) (bytes 0 0))) \ No newline at end of file +(test-case + "size should have a default count of 1" + (check-equal? (size (+xreserved uint8)) 1)) + +(test-case + "size should allow custom counts and types" + (check-equal? (size (+xreserved uint16be 10)) 20)) + +(test-case + "should decode" + (parameterize ([current-input-port (open-input-bytes (bytes 0 0))]) + (define reserved (+xreserved uint16be)) + (check-equal? (decode reserved) (void)) + (check-equal? (pos (current-input-port)) 2))) + +(test-case + "should decode with post-decode" + (parameterize ([current-input-port (open-input-bytes (bytes 0 0))]) + (define reserved (+xreserved uint16be)) + (set-post-decode! reserved (λ (val) 42)) + (check-equal? (decode reserved) 42) + (check-equal? (pos (current-input-port)) 2))) + +(test-case + "should encode" + (parameterize ([current-output-port (open-output-bytes)]) + (define reserved (+xreserved uint16be)) + (encode reserved #f) + (check-equal? (dump (current-output-port)) (bytes 0 0)))) + +(test-case + "should encode with pre-encode" + (parameterize ([current-output-port (open-output-bytes)]) + (define reserved (+xreserved uint32be)) + (set-pre-encode! reserved (λ (val) 42)) + (encode reserved #f) + (check-equal? (dump (current-output-port)) (bytes 0 0 0 0)))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/string-test.rkt b/xenomorph/xenomorph/test/string-test.rkt index 786e594e..e6a0b485 100644 --- a/xenomorph/xenomorph/test/string-test.rkt +++ b/xenomorph/xenomorph/test/string-test.rkt @@ -1,131 +1,124 @@ #lang racket/base (require rackunit - xenomorph + "../helper.rkt" + "../string.rkt" + "../number.rkt" sugar/unstable/dict) - #| approximates https://github.com/mbutterick/restructure/blob/master/test/String.coffee |# -;describe 'String', -> -; describe 'decode', -> -; it 'should decode fixed length', -> - -(parameterize ([current-input-port (open-input-bytes #"testing")]) - (check-equal? (decode (+StringT 7)) "testing")) - - -; it 'should decode length from parent key', -> - -(parameterize ([current-input-port (open-input-bytes #"testing")]) - (check-equal? (decode (+StringT 'len) #:parent (mhash 'len 7)) "testing")) - - -; it 'should decode length as number before string', -> - -(parameterize ([current-input-port (open-input-bytes #"\x07testing")]) - (check-equal? (decode (+StringT uint8) #:parent (mhash 'len 7)) "testing")) - - -;; it 'should decode utf8', -> - -(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) - (check-equal? (decode (+StringT 4 'utf8)) "🍻")) - -;; it 'should decode encoding computed from function', -> - -(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) - (check-equal? (decode (+StringT 4 (λ _ 'utf8))) "🍻")) - - -; it 'should decode null-terminated string and read past terminator', -> - -(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻\x00"))]) - (check-equal? (decode (+StringT #f 'utf8)) "🍻") - (check-equal? (pos (current-input-port)) 5)) - - -; it 'should decode remainder of buffer when null-byte missing', -> - -(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) - (check-equal? (decode (+StringT #f 'utf8)) "🍻")) - - -; describe 'size', -> -; it 'should use string length', -> - -(check-equal? (size (+StringT 7) "testing") 7) - - -; it 'should use correct encoding', -> - -(check-equal? (size (+StringT 10 'utf8) "🍻") 4) - - -; it 'should use encoding from function', -> - -(check-equal? (size (+StringT 10 (λ _ 'utf8)) "🍻") 4) - - -; it 'should add size of length field before string', -> - -(check-equal? (size (+StringT uint8 'utf8) "🍻") 5) - - -; todo -; it 'should work with utf16be encoding', -> - - -; it 'should take null-byte into account', -> - -(check-equal? (size (+StringT #f 'utf8) "🍻") 5) - - -; it 'should use defined length if no value given', -> - -(check-equal? (size (+StringT 10)) 10) - -; -; describe 'encode', -> -; it 'should encode using string length', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (encode (+StringT 7) "testing") - (check-equal? (dump (current-output-port)) #"testing")) - - -; it 'should encode length as number before string', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (encode (+StringT uint8) "testing") - (check-equal? (dump (current-output-port)) #"\x07testing")) - - -; it 'should encode length as number before string utf8', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (encode (+StringT uint8 'utf8) "testing 😜") - (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "\x0ctesting 😜"))) - - -; it 'should encode utf8', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (encode (+StringT 4 'utf8) "🍻" ) - (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻"))) - - -; it 'should encode encoding computed from function', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (encode (+StringT 4 (λ _ 'utf8)) "🍻") - (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻"))) - - -; it 'should encode null-terminated string', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (encode (+StringT #f 'utf8) "🍻" ) - (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻\x00"))) \ No newline at end of file +(test-case + "decode fixed length" + (parameterize ([current-input-port (open-input-bytes #"testing")]) + (check-equal? (decode (+xstring 7)) "testing"))) + +(test-case + "decode fixed length with post-decode" + (parameterize ([current-input-port (open-input-bytes #"testing")]) + (define xs (+xstring 7)) + (set-post-decode! xs (λ (val) "ring a ding")) + (check-equal? (decode xs) "ring a ding"))) + +(test-case + "decode length from parent key" + (parameterize ([current-input-port (open-input-bytes #"testing")]) + (check-equal? (decode (+xstring 'len) #:parent (mhash 'len 7)) "testing"))) + +(test-case + "decode length as number before string" + (parameterize ([current-input-port (open-input-bytes #"\x07testing")]) + (check-equal? (decode (+xstring uint8) #:parent (mhash 'len 7)) "testing"))) + +(test-case + "decode utf8" + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) + (check-equal? (decode (+xstring 4 'utf8)) "🍻"))) + +(test-case + "decode encoding computed from function" + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) + (check-equal? (decode (+xstring 4 (λ _ 'utf8))) "🍻"))) + +(test-case + "decode null-terminated string and read past terminator" + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻\x00"))]) + (check-equal? (decode (+xstring #f 'utf8)) "🍻") + (check-equal? (pos (current-input-port)) 5))) + +(test-case + "decode remainder of buffer when null-byte missing" + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) + (check-equal? (decode (+xstring #f 'utf8)) "🍻"))) + +(test-case + "size should use string length" + (check-equal? (size (+xstring 7) "testing") 7)) + +(test-case + "size should use correct encoding" + (check-equal? (size (+xstring 10 'utf8) "🍻") 4)) + +(test-case + "size should use encoding from function" + (check-equal? (size (+xstring 10 (λ _ 'utf8)) "🍻") 4)) + +(test-case + "should add size of length field before string" + (check-equal? (size (+xstring uint8 'utf8) "🍻") 5)) + +; todo: it "should work with utf16be encoding" + +(test-case + "size should take null-byte into account" + (check-equal? (size (+xstring #f 'utf8) "🍻") 5)) + +(test-case + "size should use defined length if no value given" + (check-equal? (size (+xstring 10)) 10)) + +(test-case + "encode using string length" + (parameterize ([current-output-port (open-output-bytes)]) + (encode (+xstring 7) "testing") + (check-equal? (dump (current-output-port)) #"testing"))) + +(test-case + "encode using string length and pre-encode" + (parameterize ([current-output-port (open-output-bytes)]) + (define xs (+xstring 7)) + (set-pre-encode! xs (compose1 list->string reverse string->list)) + (encode xs "testing") + (check-equal? (dump (current-output-port)) #"gnitset"))) + +(test-case + "encode length as number before string" + (parameterize ([current-output-port (open-output-bytes)]) + (encode (+xstring uint8) "testing") + (check-equal? (dump (current-output-port)) #"\x07testing"))) + +(test-case + "encode length as number before string utf8" + (parameterize ([current-output-port (open-output-bytes)]) + (encode (+xstring uint8 'utf8) "testing 😜") + (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "\x0ctesting 😜")))) + +(test-case + "encode utf8" + (parameterize ([current-output-port (open-output-bytes)]) + (encode (+xstring 4 'utf8) "🍻" ) + (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻")))) + +(test-case + "encode encoding computed from function" + (parameterize ([current-output-port (open-output-bytes)]) + (encode (+xstring 4 (λ _ 'utf8)) "🍻") + (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻")))) + +(test-case + "encode null-terminated string" + (parameterize ([current-output-port (open-output-bytes)]) + (encode (+xstring #f 'utf8) "🍻" ) + (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻\x00")))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/struct-test.rkt b/xenomorph/xenomorph/test/struct-test.rkt index 587428b9..22d6c2e8 100644 --- a/xenomorph/xenomorph/test/struct-test.rkt +++ b/xenomorph/xenomorph/test/struct-test.rkt @@ -1,126 +1,80 @@ -#lang racket/base -(require rackunit - xenomorph - racket/class - sugar/unstable/dict - sugar/unstable/js - "../private/generic.rkt") +#lang debug racket/base +(require rackunit racket/dict + "../helper.rkt" + "../struct.rkt" + "../string.rkt" + "../pointer.rkt" + "../number.rkt" + sugar/unstable/dict) #| approximates https://github.com/mbutterick/restructure/blob/master/test/Struct.coffee |# - -;describe 'Struct', -> -; describe 'decode', -> -; it 'should decode into an object', -> - -(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) - (check-equal? - (dump (decode (+Struct (dictify 'name (+StringT uint8) - 'age uint8)))) - (hasheq 'name "roxyb" 'age 21))) - - - -; it 'should support process hook', -> - -(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")]) - (define struct (+Struct (dictify 'name (+StringT uint8) - 'age uint8))) - (set-field! post-decode struct (λ (o . _) (ref-set! o 'canDrink (>= (· o age) 21)) o)) - (check-equal? (dump (decode struct)) - (hasheq 'name "roxyb" 'age 32 'canDrink #t))) - - - -; it 'should support function keys', -> - -(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")]) - (define struct (+Struct (dictify 'name (+StringT uint8) - 'age uint8 - 'canDrink (λ (o) (>= (ref o 'age) 21))))) - (check-equal? (dump (decode struct)) - (hasheq 'name "roxyb" 'age 32 'canDrink #t))) - - - - -; -; describe 'size', -> -; it 'should compute the correct size', -> - -(check-equal? (size (+Struct (dictify - 'name (+StringT uint8) - 'age uint8)) - (hasheq 'name "roxyb" 'age 32)) 7) - - - -; it 'should compute the correct size with pointers', -> - -(check-equal? (size (+Struct (dictify - 'name (+StringT uint8) - 'age uint8 - 'ptr (+Pointer uint8 (+StringT uint8)))) - (mhash 'name "roxyb" 'age 21 'ptr "hello")) 14) - - -; it 'should get the correct size when no value is given', -> - -(check-equal? (size (+Struct (dictify - 'name (+StringT 4) - 'age uint8))) 5) - -; it 'should throw when getting non-fixed length size and no value is given', -> - -(check-exn exn:fail:contract? (λ () (size (+Struct (dictify 'name (+StringT uint8) - 'age uint8))))) - - - -; -; describe 'encode', -> -; it 'should encode objects to buffers', (done) -> -; stream = new EncodeStream -; stream.pipe concat (buf) -> -; buf.should.deep.equal new Buffer '\x05roxyb\x15' -; done() -; -; struct = new Struct -; name: new StringT uint8 -; age: uint8 -; -; struct.encode stream, -; name: 'roxyb' -; age: 21 -; -; stream.end() - -(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) - (check-equal? (dump (decode (+Struct (dictify 'name (+StringT uint8) - 'age uint8)))) - (hasheq 'name "roxyb" 'age 21))) - - -; it 'should support preEncode hook', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define struct (+Struct (dictify 'nameLength uint8 - 'name (+StringT 'nameLength) - 'age uint8))) - (set-field! pre-encode struct (λ (val port) (ref-set! val 'nameLength (length (ref val 'name))) val)) - (encode struct (mhasheq 'name "roxyb" 'age 21)) - (check-equal? (dump (current-output-port)) #"\x05roxyb\x15")) - - -; it 'should encode pointer data after structure', (done) -> - -(parameterize ([current-output-port (open-output-bytes)]) - (define struct (+Struct (dictify 'name (+StringT uint8) - 'age uint8 - 'ptr (+Pointer uint8 (+StringT uint8))))) - (encode struct (hasheq 'name "roxyb" 'age 21 'ptr "hello")) - (check-equal? (dump (current-output-port)) #"\x05roxyb\x15\x08\x05hello")) - +(test-case + "decode into an object" + (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) + (check-equal? + (decode/hash (+xstruct 'name (+xstring #:length uint8) 'age uint8)) + (hasheq 'name "roxyb" 'age 21)))) + +(test-case + "decode with process hook" + (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")]) + (define struct (+xstruct 'name (+xstring #:length uint8) 'age uint8)) + (set-post-decode! struct (λ (o . _) (dict-set! o 'canDrink (>= (dict-ref o 'age) 21)) o)) + (check-equal? (decode/hash struct) + (hasheq 'name "roxyb" 'age 32 'canDrink #t)))) + +(test-case + "decode supports function keys" + (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")]) + (define struct (+xstruct 'name (+xstring #:length uint8) 'age uint8 'canDrink (λ (o) (>= (dict-ref o 'age) 21)))) + (check-equal? (decode/hash struct) + (hasheq 'name "roxyb" 'age 32 'canDrink #t)))) + +(test-case + "compute the correct size" + (check-equal? (size (+xstruct 'name (+xstring #:length uint8) 'age uint8) + (hasheq 'name "roxyb" 'age 32)) 7)) + +(test-case + "compute the correct size with pointers" + (check-equal? (size (+xstruct 'name (+xstring #:length uint8) + 'age uint8 + 'ptr (+xpointer #:type (+xstring #:length uint8))) + (mhash 'name "roxyb" 'age 21 'ptr "hello")) 14)) + +(test-case + "get the correct size when no value is given" + (check-equal? (size (+xstruct 'name (+xstring 4) 'age uint8)) 5)) + +(test-case + "throw when getting non-fixed length size and no value is given" + (check-exn exn:fail:contract? (λ () (size (+xstruct 'name (+xstring #:length uint8) 'age uint8))))) + +(test-case + "encode objects to buffers" + (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) + (check-equal? (decode/hash (+xstruct 'name (+xstring #:length uint8) 'age uint8)) + (hasheq 'name "roxyb" 'age 21)))) + +(test-case + "support pre-encode hook" + (parameterize ([current-output-port (open-output-bytes)]) + (define struct (+xstruct 'nameLength uint8 + 'name (+xstring 'nameLength) + 'age uint8)) + (set-pre-encode! struct (λ (val) (dict-set! val 'nameLength (string-length (dict-ref val 'name))) val)) + (encode struct (mhasheq 'name "roxyb" 'age 21)) + (check-equal? (dump (current-output-port)) #"\x05roxyb\x15"))) + +(test-case + "encode pointer data after structure" + (parameterize ([current-output-port (open-output-bytes)]) + (define struct (+xstruct 'name (+xstring #:length uint8) + 'age uint8 + 'ptr (+xpointer #:type (+xstring #:length uint8)))) + (encode struct (hasheq 'name "roxyb" 'age 21 'ptr "hello")) + (check-equal? (dump (current-output-port)) #"\x05roxyb\x15\x08\x05hello"))) \ No newline at end of file diff --git a/xenomorph/xenomorph/test/versioned-struct-test.rkt b/xenomorph/xenomorph/test/versioned-struct-test.rkt index f8e13351..61de216b 100644 --- a/xenomorph/xenomorph/test/versioned-struct-test.rkt +++ b/xenomorph/xenomorph/test/versioned-struct-test.rkt @@ -1,344 +1,248 @@ -#lang racket/base +#lang debug racket/base (require rackunit - xenomorph - racket/class - "../private/generic.rkt" - sugar/unstable/dict) + racket/dict + sugar/unstable/dict + "../helper.rkt" + "../number.rkt" + "../string.rkt" + "../pointer.rkt" + "../versioned-struct.rkt") #| approximates https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffee |# -;describe 'VersionedStruct', -> -; describe 'decode', -> -; it 'should get version from number type', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))]) - - (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) - (check-equal? (dump (decode struct)) (hasheq 'name "roxyb" - 'age 21 - 'version 0))) - - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x0aroxyb 🤘\x15\x00"))]) - (check-equal? (dump (decode struct)) (hasheq 'name "roxyb 🤘" - 'age 21 - 'version 1 - 'gender 0)))) - - -; it 'should throw for unknown version', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))]) - - (parameterize ([current-input-port (open-input-bytes #"\x05\x05roxyb\x15")]) - (check-exn exn:fail:contract? (λ () (decode struct))))) - - -; -; it 'should support common header block', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 'header (dictify 'age uint8 - 'alive uint8) - 0 (dictify 'name (+StringT uint8 'ascii)) - 1 (dictify 'name (+StringT uint8 'utf8) - 'gender uint8)))]) - - (parameterize ([current-input-port (open-input-bytes #"\x00\x15\x01\x05roxyb")]) - (check-equal? (dump (decode struct)) (hasheq 'name "roxyb" +(test-case + "decode should get version from number type" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'gender uint8)))]) + (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) + (check-equal? (decode/hash vstruct) (hasheq 'name "roxyb" 'age 21 'version 0))) + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x0aroxyb 🤘\x15\x00"))]) + (check-equal? (decode/hash vstruct) (hasheq 'name "roxyb 🤘" 'age 21 'version 1 'gender 0))))) + +(test-case + "decode should throw for unknown version" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #: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 + "decode should support common header block" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 'header (dictify 'age uint8 + 'alive uint8) + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii)) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'gender uint8)))]) + (parameterize ([current-input-port (open-input-bytes #"\x00\x15\x01\x05roxyb")]) + (check-equal? (decode/hash vstruct) (hasheq 'name "roxyb" 'age 21 'alive 1 'version 0))) - - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x15\x01\x0aroxyb 🤘\x00"))]) - (check-equal? (dump (decode struct)) (hasheq 'name "roxyb 🤘" + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x15\x01\x0aroxyb 🤘\x00"))]) + (check-equal? (decode/hash vstruct) (hasheq 'name "roxyb 🤘" 'age 21 'version 1 'alive 1 - 'gender 0)))) - - -; it 'should support parent version key', -> - -(let ([struct (+VersionedStruct 'version - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))]) - - (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) - (check-equal? (dump (decode struct #:parent (mhash 'version 0))) (hasheq 'name "roxyb" - 'age 21 - 'version 0))) - - (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x0aroxyb 🤘\x15\x00"))]) - (check-equal? (dump (decode struct #:parent (mhash 'version 1))) (hasheq 'name "roxyb 🤘" - 'age 21 - 'version 1 - 'gender 0)))) - - - -; -; it 'should support sub versioned structs', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8)) - 1 (dictify 'name (+StringT uint8) - 'isDessert uint8)))))]) - - (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) - (check-equal? (dump (decode struct #:parent (mhash 'version 0))) (hasheq 'name "roxyb" - 'age 21 - 'version 0))) - - (parameterize ([current-input-port (open-input-bytes #"\x01\x00\x05pasta")]) - (check-equal? (dump (decode struct #:parent (mhash 'version 0))) (hasheq 'name "pasta" - 'version 0))) - - (parameterize ([current-input-port (open-input-bytes #"\x01\x01\x09ice cream\x01")]) - (check-equal? (dump (decode struct #:parent (mhash 'version 0))) (hasheq 'name "ice cream" - 'isDessert 1 - 'version 1)))) - - -; -; it 'should support process hook', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))]) - (set-field! post-decode struct (λ (o stream ctx) (ref-set! o 'processed "true") o)) - (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) - (check-equal? (dump (decode struct)) (hasheq 'name "roxyb" - 'processed "true" - 'age 21 - 'version 0)))) - - -; -; describe 'size', -> -; it 'should compute the correct size', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))]) - - (check-equal? (size struct (mhasheq 'name "roxyb" - 'age 21 - 'version 0)) 8) - - (check-equal? (size struct (mhasheq 'name "roxyb 🤘" - 'gender 0 - 'age 21 - 'version 1)) 14)) - - - - -; -; it 'should throw for unknown version', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))]) - - (check-exn exn:fail:contract? (λ () (size struct (mhasheq 'name "roxyb" - 'age 21 - 'version 5))))) - - -; -; it 'should support common header block', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 'header (dictify 'age uint8 - 'alive uint8) - 0 (dictify 'name (+StringT uint8 'ascii)) - 1 (dictify 'name (+StringT uint8 'utf8) - 'gender uint8)))]) - - (check-equal? (size struct (mhasheq 'name "roxyb" - 'age 21 - 'alive 1 - 'version 0)) 9) - - (check-equal? (size struct (mhasheq 'name "roxyb 🤘" - 'gender 0 - 'age 21 - 'alive 1 - 'version 1)) 15)) - - - -; it 'should compute the correct size with pointers', -> - - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'ptr (+Pointer uint8 (+StringT uint8)))))]) - - (check-equal? (size struct (mhasheq 'name "roxyb" - 'age 21 - 'version 1 - 'ptr "hello")) 15)) - - -; -; it 'should throw if no value is given', -> - - - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))]) - - (check-exn exn:fail:contract? (λ () (size struct)))) - - - -; describe 'encode', -> -; it 'should encode objects to buffers', (done) -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))] - [port (open-output-bytes)]) - (encode struct (mhasheq 'name "roxyb" - 'age 21 - 'version 0) port) - (encode struct (mhasheq 'name "roxyb 🤘" - 'age 21 - 'gender 0 - 'version 1) port) - (check-equal? (dump port) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00"))) - - -; -; it 'should throw for unknown version', -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))] - [port (open-output-bytes)]) - (check-exn exn:fail:contract? (λ () (encode struct port (mhasheq 'name "roxyb" - 'age 21 - 'version 5))))) - - - -; it 'should support common header block', (done) -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 'header (dictify 'age uint8 - 'alive uint8) - 0 (dictify 'name (+StringT uint8 'ascii)) - 1 (dictify 'name (+StringT uint8 'utf8) - 'gender uint8)))] - [stream (open-output-bytes)]) - - (encode struct (mhasheq 'name "roxyb" - 'age 21 - 'alive 1 - 'version 0) stream) - - (encode struct (mhasheq 'name "roxyb 🤘" - 'gender 0 - 'age 21 - 'alive 1 - 'version 1) stream) - - (check-equal? (dump stream) (string->bytes/utf-8 "\x00\x15\x01\x05roxyb\x01\x15\x01\x0aroxyb 🤘\x00"))) - - - -; it 'should encode pointer data after structure', (done) -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'ptr (+Pointer uint8 (+StringT uint8)))))] - [stream (open-output-bytes)]) - (encode struct (mhasheq 'version 1 - 'name "roxyb" - 'age 21 - 'ptr "hello") stream) - - (check-equal? (dump stream) (string->bytes/utf-8 "\x01\x05roxyb\x15\x09\x05hello"))) - - - - -; it 'should support preEncode hook', (done) -> - -(let ([struct (+VersionedStruct uint8 - (dictify - 0 (dictify 'name (+StringT uint8 'ascii) - 'age uint8) - 1 (dictify 'name (+StringT uint8 'utf8) - 'age uint8 - 'gender uint8)))] - [stream (open-output-bytes)]) - (set-field! pre-encode struct (λ (val port) (ref-set! val 'version (if (ref val 'gender) 1 0)) val)) - (encode struct (mhasheq 'name "roxyb" - 'age 21 - 'version 0) stream) - (encode struct (mhasheq 'name "roxyb 🤘" - 'age 21 - 'gender 0) stream) - (check-equal? (dump stream) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00"))) + 'gender 0))))) + +(test-case + "decode should support parent version key" + (let ([vstruct (+xversioned-struct 'version + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'gender uint8)))]) + (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) + (check-equal? (decode/hash vstruct #:parent (mhash 'version 0)) + (hasheq 'name "roxyb" 'age 21 'version 0))) + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x0aroxyb 🤘\x15\x00"))]) + (check-equal? (decode/hash vstruct #:parent (mhash 'version 1)) + (hasheq 'name "roxyb 🤘" 'age 21 'version 1 'gender 0))))) + +(test-case + "decode should support sub versioned structs" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring uint8)) + 1 (dictify 'name (+xstring uint8) + 'isDessert uint8)))))]) + (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) + (check-equal? (decode/hash vstruct #:parent (mhash 'version 0)) + (hasheq 'name "roxyb" 'age 21 'version 0))) + (parameterize ([current-input-port (open-input-bytes #"\x01\x00\x05pasta")]) + (check-equal? (decode/hash vstruct #:parent (mhash 'version 0)) + (hasheq 'name "pasta" 'version 0))) + (parameterize ([current-input-port (open-input-bytes #"\x01\x01\x09ice cream\x01")]) + (check-equal? (decode/hash vstruct #:parent (mhash 'version 0)) + (hasheq 'name "ice cream" 'isDessert 1 'version 1))))) + +(test-case + "decode should support process hook" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'gender uint8)))]) + (set-post-decode! vstruct (λ (val) (dict-set! val 'processed "true") val)) + (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) + (check-equal? (decode/hash vstruct) + (hasheq 'name "roxyb" 'processed "true" 'age 21 'version 0))))) + +(test-case + "size should compute the correct size" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'gender uint8)))]) + (check-equal? (size vstruct (mhasheq 'name "roxyb" + 'age 21 + 'version 0)) 8) + (check-equal? (size vstruct (mhasheq 'name "roxyb 🤘" + 'gender 0 + 'age 21 + 'version 1)) 14))) + +(test-case + "size should throw for unknown version" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'gender uint8)))]) + (check-exn exn:fail:contract? (λ () (size vstruct (mhasheq 'name "roxyb" 'age 21 'version 5)))))) + +(test-case + "size should support common header block" + (let ([struct (+xversioned-struct uint8 + (dictify + 'header (dictify 'age uint8 + 'alive uint8) + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii)) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'gender uint8)))]) + (check-equal? (size struct (mhasheq 'name "roxyb" 'age 21 'alive 1 'version 0)) 9) + (check-equal? (size struct (mhasheq 'name "roxyb 🤘" 'gender 0 'age 21 'alive 1 'version 1)) 15))) + +(test-case + "size should compute the correct size with pointers" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'ptr (+xpointer #:offset-type uint8 + #:type (+xstring uint8)))))]) + (check-equal? (size vstruct (mhasheq 'name "roxyb" + 'age 21 + 'version 1 + 'ptr "hello")) 15))) + +(test-case + "size should throw if no value is given" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'gender uint8)))]) + (check-exn exn:fail:contract? (λ () (size vstruct))))) + +(test-case + "encode should encode objects to buffers" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'gender uint8)))] + [op (open-output-bytes)]) + (encode vstruct (mhasheq 'name "roxyb" 'age 21 'version 0) op) + (encode vstruct (mhasheq 'name "roxyb 🤘" 'age 21 'gender 0 'version 1) op) + (check-equal? (dump op) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00")))) + +(test-case + "encode should throw for unknown version" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #: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 'version 5)))))) + +(test-case + "encode should support common header block" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 'header (dictify 'age uint8 + 'alive uint8) + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii)) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'gender uint8)))] + [op (open-output-bytes)]) + (encode vstruct (mhasheq 'name "roxyb" 'age 21 'alive 1 'version 0) op) + (encode vstruct (mhasheq 'name "roxyb 🤘" 'gender 0 'age 21 'alive 1 'version 1) op) + (check-equal? (dump op) (string->bytes/utf-8 "\x00\x15\x01\x05roxyb\x01\x15\x01\x0aroxyb 🤘\x00")))) + +(test-case + "encode should encode pointer data after structure" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'ptr (+xpointer #:offset-type uint8 + #:type (+xstring uint8)))))] + [op (open-output-bytes)]) + (encode vstruct (mhasheq 'version 1 'name "roxyb" 'age 21 'ptr "hello") op) + + (check-equal? (dump op) (string->bytes/utf-8 "\x01\x05roxyb\x15\x09\x05hello")))) + +(test-case + "encode should support preEncode hook" + (let ([vstruct (+xversioned-struct uint8 + (dictify + 0 (dictify 'name (+xstring #:length uint8 #:encoding 'ascii) + 'age uint8) + 1 (dictify 'name (+xstring #:length uint8 #:encoding 'utf8) + 'age uint8 + 'gender uint8)))] + [stream (open-output-bytes)]) + (set-pre-encode! vstruct (λ (val) (dict-set! val 'version (if (dict-ref val 'gender #f) 1 0)) val)) + (encode vstruct (mhasheq 'name "roxyb" 'age 21 'version 0) stream) + (encode vstruct (mhasheq 'name "roxyb 🤘" 'age 21 'gender 0) stream) + (check-equal? (dump stream) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00")))) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/array.rkt b/xenomorph/xenomorph/undo/array.rkt new file mode 100644 index 00000000..2fd7d9d6 --- /dev/null +++ b/xenomorph/xenomorph/undo/array.rkt @@ -0,0 +1,94 @@ +#lang racket/base +(require racket/class + sugar/unstable/class + sugar/unstable/dict + sugar/unstable/js + "private/generic.rkt" + "private/helper.rkt" + "number.rkt" + "utils.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Array.coffee +|# + +(define-subclass xenomorph-base% (ArrayT type [len #f] [length-type 'count]) + + (define/augride (decode port [parent #f]) + (define ctx (if (NumberT? len) + (mhasheq 'parent parent + '_startOffset (pos port) + '_currentOffset 0 + '_length len) + parent)) + + (define decoded-len (resolve-length len port parent)) + (cond + [(or (not decoded-len) (eq? length-type 'bytes)) + (define end-pos (cond + ;; decoded-len is byte length + [decoded-len (+ (pos port) decoded-len)] + ;; no decoded-len, but parent has length + [(and parent (not (zero? (· parent _length)))) (+ (· parent _startOffset) (· parent _length))] + ;; no decoded-len or parent, so consume whole stream + [else +inf.0])) + (for/list ([i (in-naturals)] + #:break (or (eof-object? (peek-byte port)) (= (pos port) end-pos))) + (send type decode port ctx))] + ;; we have decoded-len, which is treated as count of items + [else (for/list ([i (in-range decoded-len)]) + (send type decode port ctx))])) + + + (define/augride (size [val #f] [ctx #f]) + (when val (unless (countable? val) + (raise-argument-error 'Array:size "countable" val))) + (cond + [val (let-values ([(ctx len-size) (if (NumberT? len) + (values (mhasheq 'parent ctx) (send len size)) + (values ctx 0))]) + (+ len-size (for/sum ([item (in-list (countable->list val))]) + (send type size item ctx))))] + [else (let ([item-count (resolve-length len #f ctx)] + [item-size (send type size #f ctx)]) + (* item-size item-count))])) + + + (define/augride (encode port array [parent #f]) + (when array (unless (countable? array) + (raise-argument-error 'Array:encode "list or countable" array))) + + (define (encode-items ctx) + (let* ([items (countable->list array)] + [item-count (length items)] + [max-items (if (number? len) len item-count)]) + (for ([item (in-list items)]) + (send type encode port item ctx)))) + + (cond + [(NumberT? len) (define ctx (mhash 'pointers null + 'startOffset (pos port) + 'parent parent)) + (ref-set! ctx 'pointerOffset (+ (pos port) (size array ctx))) + (send len encode port (length array)) ; encode length at front + (encode-items ctx) + (for ([ptr (in-list (· ctx pointers))]) ; encode pointer data at end + (send (· ptr type) encode port (· ptr val)))] + [else (encode-items parent)]))) + +(define-syntax-rule (define-procedures (NEW ...) (OLD ...)) + (define-values (NEW ...) + (values (if (procedure? OLD) + (procedure-rename OLD 'NEW) + OLD) ...))) + +(define-procedures (Array Array? +Array) (ArrayT ArrayT? +ArrayT)) +(define-procedures (array% array? array) (ArrayT ArrayT? +ArrayT)) + +(test-module + (check-equal? (decode (+Array uint16be 3) #"ABCDEF") '(16706 17220 17734)) + (check-equal? (encode (+Array uint16be 3) '(16706 17220 17734) #f) #"ABCDEF") + (check-equal? (size (+Array uint16be) '(1 2 3)) 6) + (check-equal? (size (+Array doublebe) '(1 2 3 4 5)) 40)) diff --git a/xenomorph/xenomorph/base.rkt b/xenomorph/xenomorph/undo/base.rkt similarity index 100% rename from xenomorph/xenomorph/base.rkt rename to xenomorph/xenomorph/undo/base.rkt diff --git a/xenomorph/xenomorph/undo/bitfield.rkt b/xenomorph/xenomorph/undo/bitfield.rkt new file mode 100644 index 00000000..78c2b267 --- /dev/null +++ b/xenomorph/xenomorph/undo/bitfield.rkt @@ -0,0 +1,49 @@ +#lang racket/base +(require racket/class + racket/list + sugar/unstable/class + sugar/unstable/dict + "private/generic.rkt" + "private/helper.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Bitfield.coffee +|# + +(define-subclass Streamcoder (Bitfield type [flags empty]) + (unless (andmap (λ (f) (or (key? f) (not f))) flags) + (raise-argument-error 'Bitfield "list of keys" flags)) + + (define/augment (decode stream . _) + (define flag-hash (mhasheq)) + (for* ([val (in-value (send type decode stream))] + [(flag i) (in-indexed flags)] + #:when flag) + (hash-set! flag-hash flag (bitwise-bit-set? val i))) + flag-hash) + + (define/augment (size . _) (send type size)) + + (define/augment (encode port flag-hash [ctx #f]) + (define bit-int (for/sum ([(flag i) (in-indexed flags)] + #:when (and flag (ref flag-hash flag))) + (arithmetic-shift 1 i))) + (send type encode port bit-int)) + + (define/override (get-class-name) 'Bitfield)) + + +(test-module + (require "number.rkt") + (define bfer (+Bitfield uint16be '(bold italic underline #f shadow condensed extended))) + (define bf (send bfer decode #"\0\25")) + (check-equal? (length (ref-keys bf)) 6) ; omits #f flag + (check-true (ref bf 'bold)) + (check-true (ref bf 'underline)) + (check-true (ref bf 'shadow)) + (check-false (ref bf 'italic)) + (check-false (ref bf 'condensed)) + (check-false (ref bf 'extended)) + (check-equal? (encode bfer bf #f) #"\0\25")) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/buffer.rkt b/xenomorph/xenomorph/undo/buffer.rkt new file mode 100644 index 00000000..bb4ebc1e --- /dev/null +++ b/xenomorph/xenomorph/undo/buffer.rkt @@ -0,0 +1,60 @@ +#lang racket/base +(require racket/class + sugar/unstable/class + "private/generic.rkt" + "private/helper.rkt" + "number.rkt" + "utils.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Buffer.coffee +|# + +#| +A Buffer is a container object for any data object that supports random access +A Node Buffer object is basically a byte string. +First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object. +A Restructure RBuffer object is separate. +|# + +(define (+Buffer xs [type #f]) + ((if (string? xs) + string->bytes/utf-8 + list->bytes) xs)) + +(define-subclass xenomorph-base% (RBuffer [len #xffff]) + + (define/augment (decode port [parent #f]) + (define decoded-len (resolve-length len port parent)) + (read-bytes decoded-len port)) + + (define/augment (size [val #f] [parent #f]) + (when val (unless (bytes? val) + (raise-argument-error 'Buffer:size "bytes" val))) + (if val + (bytes-length val) + (resolve-length len val parent))) + + (define/augment (encode port buf [parent #f]) + (unless (bytes? buf) + (raise-argument-error 'Buffer:encode "bytes" buf)) + (define op (or port (open-output-bytes))) + (when (NumberT? len) + (send len encode op (length buf))) + (write-bytes buf op) + (unless port (get-output-bytes op)))) + +(define-subclass RBuffer (BufferT)) + + +#;(test-module + (require "stream.rkt") + (define stream (+DecodeStream #"\2BCDEF")) + (define S (+String uint8 'utf8)) + (check-equal? (send S decode stream) "BC") + (define os (+EncodeStream)) + (send S encode os "Mike") + (check-equal? (send os dump) #"\4Mike") + (check-equal? (send (+String) size "foobar") 6)) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/enum.rkt b/xenomorph/xenomorph/undo/enum.rkt new file mode 100644 index 00000000..8913d6d0 --- /dev/null +++ b/xenomorph/xenomorph/undo/enum.rkt @@ -0,0 +1,26 @@ +#lang racket/base +(require racket/class + racket/list + sugar/unstable/class + "private/helper.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Enum.coffee +|# + +(define-subclass xenomorph-base% (Enum type [options empty]) + + (define/augment (decode stream . _) + (define index (send type decode stream)) + (or (list-ref options index) index)) + + (define/augment (size . _) (send type size)) + + (define/augment (encode stream val [ctx #f]) + (define index (index-of options val)) + (unless index + (raise-argument-error 'Enum:encode "valid option" val)) + (send type encode stream index))) + diff --git a/xenomorph/xenomorph/info.rkt b/xenomorph/xenomorph/undo/info.rkt similarity index 100% rename from xenomorph/xenomorph/info.rkt rename to xenomorph/xenomorph/undo/info.rkt diff --git a/xenomorph/xenomorph/undo/lazy-array.rkt b/xenomorph/xenomorph/undo/lazy-array.rkt new file mode 100644 index 00000000..e8cd6528 --- /dev/null +++ b/xenomorph/xenomorph/undo/lazy-array.rkt @@ -0,0 +1,83 @@ +#lang racket/base +(require racket/class + sugar/unstable/class + sugar/unstable/dict + "private/generic.rkt" + "private/helper.rkt" + "utils.rkt" + "array.rkt" + "number.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/LazyArray.coffee +|# + +(define (get o i) (send o get i)) +(define (LazyArray->list o) (send o to-list)) + +(define-subclass object% (InnerLazyArray type [len #f] [port-in #f] [ctx #f]) + (field ([port port] (cond + [(bytes? port-in) (open-input-bytes port-in)] + [(port? port-in) port-in] + [else (raise-argument-error 'LazyArray "port" port)]))) + (define starting-pos (pos port)) + (define item-cache (mhasheqv)) ; integer-keyed hash, rather than list + + + (define/public-final (get index) + (unless (<= 0 index (sub1 len)) + (raise-argument-error 'LazyArray:get (format "index in range 0 to ~a" (sub1 len)) index)) + (ref! item-cache index (λ () + (define orig-pos (pos port)) + (pos port (+ starting-pos (* (send type size #f ctx) index))) + (define new-item (send type decode port ctx)) + (pos port orig-pos) + new-item))) + + (define/public-final (to-list) + (for/list ([i (in-range len)]) + (get i)))) + + +(define-subclass ArrayT (LazyArray) + (inherit-field len type) + + (define/override (decode port [parent #f]) + (define starting-pos (pos port)) ; ! placement matters. `resolve-length` will change `pos` + (define decoded-len (resolve-length len port parent)) + (let ([parent (if (NumberT? len) + (mhasheq 'parent parent + '_startOffset starting-pos + '_currentOffset 0 + '_length len) + parent)]) + (define res (+InnerLazyArray type decoded-len port parent)) + (pos port (+ (pos port) (* decoded-len (send type size #f parent)))) + res)) + + (define/override (size [val #f] [ctx #f]) + (super size (if (InnerLazyArray? val) + (send val to-list) + val) ctx)) + + (define/override (encode port val [ctx #f]) + (super encode port (if (InnerLazyArray? val) + (send val to-list) + val) ctx))) + +(test-module + (define bstr #"ABCD1234") + (define ds (open-input-bytes bstr)) + (define la (+LazyArray uint8 4)) + (define ila (decode la ds)) + (check-equal? (pos ds) 4) + (check-equal? (get ila 1) 66) + (check-equal? (get ila 3) 68) + (check-equal? (pos ds) 4) + (check-equal? (LazyArray->list ila) '(65 66 67 68)) + (define la2 (+LazyArray int16be (λ (t) 4))) + (check-equal? (encode la2 '(1 2 3 4) #f) #"\0\1\0\2\0\3\0\4") + (check-equal? (send (decode la2 (open-input-bytes #"\0\1\0\2\0\3\0\4")) to-list) '(1 2 3 4))) + diff --git a/xenomorph/xenomorph/undo/main.rkt b/xenomorph/xenomorph/undo/main.rkt new file mode 100644 index 00000000..00e28a92 --- /dev/null +++ b/xenomorph/xenomorph/undo/main.rkt @@ -0,0 +1,18 @@ +#lang racket/base + +(define-syntax-rule (r+p ID ...) + (begin (require ID ...) (provide (all-from-out ID ...)))) + +(r+p "array.rkt" + "base.rkt" + "bitfield.rkt" + "buffer.rkt" + "enum.rkt" + "lazy-array.rkt" + "number.rkt" + "optional.rkt" + "pointer.rkt" + "reserved.rkt" + "string.rkt" + "struct.rkt" + "versioned-struct.rkt") diff --git a/xenomorph/xenomorph/undo/number.rkt b/xenomorph/xenomorph/undo/number.rkt new file mode 100644 index 00000000..b555c926 --- /dev/null +++ b/xenomorph/xenomorph/undo/number.rkt @@ -0,0 +1,197 @@ +#lang racket/base +(require (for-syntax racket/base + racket/syntax + "sizes.rkt" + racket/match) + racket/class + sugar/unstable/class + "private/helper.rkt" + "sizes.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Number.coffee +|# + +(define (ends-with-8? type) + (define str (symbol->string type)) + (equal? (substring str (sub1 (string-length str))) "8")) + +(define (signed-type? type) + (not (equal? "u" (substring (symbol->string type) 0 1)))) + +(test-module + (check-false (signed-type? 'uint16)) + (check-true (signed-type? 'int16))) + +(define (exact-if-possible x) (if (integer? x) (inexact->exact x) x)) +(define system-endian (if (system-big-endian?) 'be 'le)) + +(define-subclass xenomorph-base% (Integer [type 'uint16] [endian system-endian]) + (getter-field [number-type (string->symbol (format "~a~a" type (if (ends-with-8? type) "" endian)))]) + (define _signed? (signed-type? type)) + + ;; `get-type-size` will raise error if number-type is invalid: use this as check of input + ;; size of a number doesn't change, so we can stash it as `_size` + (define _size (with-handlers ([exn:fail:contract? + (λ (exn) + (raise-argument-error 'Integer "valid type and endian" (format "~v ~v" type endian)))]) + (get-type-size number-type))) + + (define bits (* _size 8)) + + (define/augment (size . args) _size) + + (define-values (bound-min bound-max) + ;; if a signed integer has n bits, it can contain a number + ;; between - (expt 2 (sub1 n)) and (sub1 (expt 2 (sub1 n)). + (let* ([signed-max (sub1 (arithmetic-shift 1 (sub1 bits)))] + [signed-min (sub1 (- signed-max))] + [delta (if _signed? 0 signed-min)]) + (values (- signed-min delta) (- signed-max delta)))) + + (define/augment (decode port [parent #f]) + (define bstr (read-bytes _size port)) + (define bs ((if (eq? endian system-endian) values reverse) (bytes->list bstr))) + (define unsigned-int (for/sum ([(b i) (in-indexed bs)]) + (arithmetic-shift b (* 8 i)))) + unsigned-int) + + (define/override (post-decode unsigned-val . _) + (if _signed? (unsigned->signed unsigned-val bits) unsigned-val)) + + (define/override (pre-encode val . _) + (exact-if-possible val)) + + (define/augment (encode port val [parent #f]) + (unless (<= bound-min val bound-max) + (raise-argument-error 'Integer:encode (format "value within range of ~a ~a-byte int (~a to ~a)" (if _signed? "signed" "unsigned") _size bound-min bound-max) val)) + (define-values (bs _) (for/fold ([bs null] [n val]) + ([i (in-range _size)]) + (values (cons (bitwise-and n #xff) bs) (arithmetic-shift n -8)))) + (apply bytes ((if (eq? endian 'be) values reverse) bs)))) + +(define-values (NumberT NumberT? +NumberT) (values Integer Integer? +Integer)) +(define-values (Number Number? +Number) (values Integer Integer? +Integer)) + + +(define-subclass xenomorph-base% (Float _size [endian system-endian]) + (define byte-size (/ _size 8)) + + (define/augment (decode port [parent #f]) ; convert int to float + (define bs (read-bytes byte-size port)) + (floating-point-bytes->real bs (eq? endian 'be))) + + (define/augment (encode port val [parent #f]) ; convert float to int + (define bs (real->floating-point-bytes val byte-size (eq? endian 'be))) + bs) + + (define/augment (size . args) byte-size)) + + +(define-instance float (make-object Float 32)) +(define-instance floatbe (make-object Float 32 'be)) +(define-instance floatle (make-object Float 32 'le)) + +(define-instance double (make-object Float 64)) +(define-instance doublebe (make-object Float 64 'be)) +(define-instance doublele (make-object Float 64 'le)) + + +(define-subclass* Integer (Fixed size [fixed-endian system-endian] [fracBits (floor (/ size 2))]) + (super-make-object (string->symbol (format "int~a" size)) fixed-endian) + (define _point (arithmetic-shift 1 fracBits)) + + (define/override (post-decode int . _) + (exact-if-possible (/ int _point 1.0))) + + (define/override (pre-encode fixed . _) + (exact-if-possible (floor (* fixed _point))))) + +(define-instance fixed16 (make-object Fixed 16)) +(define-instance fixed16be (make-object Fixed 16 'be)) +(define-instance fixed16le (make-object Fixed 16 'le)) +(define-instance fixed32 (make-object Fixed 32)) +(define-instance fixed32be (make-object Fixed 32 'be)) +(define-instance fixed32le (make-object Fixed 32 'le)) + + +(test-module + (check-exn exn:fail:contract? (λ () (+Integer 'not-a-valid-type))) + (check-exn exn:fail:contract? (λ () (encode uint8 256 #f))) + (check-not-exn (λ () (encode uint8 255 #f))) + (check-exn exn:fail:contract? (λ () (encode int8 256 #f))) + (check-exn exn:fail:contract? (λ () (encode int8 255 #f))) + (check-not-exn (λ () (encode int8 127 #f))) + (check-not-exn (λ () (encode int8 -128 #f ))) + (check-exn exn:fail:contract? (λ () (encode int8 -129 #f))) + (check-exn exn:fail:contract? (λ () (encode uint16 (add1 #xffff) #f))) + (check-not-exn (λ () (encode uint16 #xffff #f))) + + (let ([o (+Integer 'uint16 'le)] + [ip (open-input-bytes (bytes 1 2 3 4))] + [op (open-output-bytes)]) + (check-equal? (send o decode ip) 513) ;; 1000 0000 0100 0000 + (check-equal? (send o decode ip) 1027) ;; 1100 0000 0010 0000 + (encode o 513 op) + (check-equal? (get-output-bytes op) (bytes 1 2)) + (encode o 1027 op) + (check-equal? (get-output-bytes op) (bytes 1 2 3 4))) + + (let ([o (+Integer 'uint16 'be)] + [ip (open-input-bytes (bytes 1 2 3 4))] + [op (open-output-bytes)]) + (check-equal? (send o decode ip) 258) ;; 0100 0000 1000 0000 + (check-equal? (send o decode ip) 772) ;; 0010 0000 1100 0000 + (encode o 258 op) + (check-equal? (get-output-bytes op) (bytes 1 2)) + (encode o 772 op) + (check-equal? (get-output-bytes op) (bytes 1 2 3 4)))) + + +(test-module + (check-equal? (send (+Integer 'uint8) size) 1) + (check-equal? (send (+Integer) size) 2) + (check-equal? (send (+Integer 'uint32) size) 4) + (check-equal? (send (+Integer 'double) size) 8) + + (check-equal? (send (+Number 'uint8) size) 1) + (check-equal? (send (+Number) size) 2) + (check-equal? (send (+Number 'uint32) size) 4) + (check-equal? (send (+Number 'double) size) 8)) + +;; use keys of type-sizes hash to generate corresponding number definitions +(define-syntax (make-int-types stx) + (syntax-case stx () + [(_) (with-syntax* ([((ID BASE ENDIAN) ...) (for*/list ([k (in-hash-keys type-sizes)] + [kstr (in-value (format "~a" k))] + #:unless (regexp-match #rx"^(float|double)" kstr)) + (match-define (list* prefix suffix _) + (regexp-split #rx"(?=[bl]e|$)" kstr)) + (map string->symbol + (list (string-downcase kstr) + prefix + (if (positive? (string-length suffix)) + suffix + (if (system-big-endian?) "be" "le")))))] + [(ID ...) (map (λ (s) (datum->syntax stx (syntax->datum s))) (syntax->list #'(ID ...)))]) + #'(begin (define-instance ID (make-object Integer 'BASE 'ENDIAN)) ...))])) + +(make-int-types) + +(test-module + (check-equal? (size uint8) 1) + (check-equal? (size uint16) 2) + (check-equal? (size uint32) 4) + (check-equal? (size double) 8) + + (define bs (encode fixed16be 123.45 #f)) + (check-equal? bs #"{s") + (check-equal? (ceiling (* (decode fixed16be bs) 100)) 12345.0) + + (check-equal? (decode int8 (bytes 127)) 127) + (check-equal? (decode int8 (bytes 255)) -1) + + (check-equal? (encode int8 -1 #f) (bytes 255)) + (check-equal? (encode int8 127 #f) (bytes 127))) diff --git a/xenomorph/xenomorph/undo/optional.rkt b/xenomorph/xenomorph/undo/optional.rkt new file mode 100644 index 00000000..80b2cf6d --- /dev/null +++ b/xenomorph/xenomorph/undo/optional.rkt @@ -0,0 +1,30 @@ +#lang racket/base +(require racket/class + sugar/unstable/class + "private/helper.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Optional.coffee +|# + +(define-subclass xenomorph-base% (Optional type [condition #t]) + + (define (resolve-condition parent) + (if (procedure? condition) + (condition parent) + condition)) + + (define/augment (decode stream parent) + (when (resolve-condition parent) + (send type decode stream parent))) + + (define/augment (size val parent) + (when (resolve-condition parent) + (send type size val parent))) + + (define/augment (encode stream val parent) + (when (resolve-condition parent) + (send type encode stream val parent)))) + diff --git a/xenomorph/xenomorph/undo/pointer.rkt b/xenomorph/xenomorph/undo/pointer.rkt new file mode 100644 index 00000000..ddb41362 --- /dev/null +++ b/xenomorph/xenomorph/undo/pointer.rkt @@ -0,0 +1,107 @@ +#lang racket/base +(require racket/class + sugar/unstable/class + sugar/unstable/case + sugar/unstable/dict + sugar/unstable/js + "private/generic.rkt" + "private/helper.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Pointer.coffee +|# + +(define (resolve-void-pointer type val) + (cond + [type (values type val)] + [(VoidPointer? val) (values (· val type) (· val value))] + [else (raise-argument-error 'Pointer:size "VoidPointer" val)])) + +(define (find-top-ctx ctx) + (cond + [(· ctx parent) => find-top-ctx] + [else ctx])) + +(define-subclass xenomorph-base% (Pointer offset-type type-in [options (mhasheq)]) + (field [type (and (not (eq? type-in 'void)) type-in)]) + (define pointer-style (or (· options type) 'local)) + (define allow-null (or (· options allowNull) #t)) + (define null-value (or (· options nullValue) 0)) + (define lazy (· options lazy)) + (define relative-getter-or-0 (or (· options relativeTo) (λ (ctx) 0))) ; changed this to a simple lambda + + (define/augment (decode port [ctx #f]) + (define offset (send offset-type decode port ctx)) + (cond + [(and allow-null (= offset null-value)) #f] ; handle null pointers + [else + (define relative (+ (caseq pointer-style + [(local) (· ctx _startOffset)] + [(immediate) (- (pos port) (send offset-type size))] + [(parent) (· ctx parent _startOffset)] + [(global) (or (· (find-top-ctx ctx) _startOffset) 0)] + [else (error 'unknown-pointer-style)]) + (relative-getter-or-0 ctx))) + (define ptr (+ offset relative)) + (cond + [type (define val (void)) + (define (decode-value) + (cond + [(not (void? val)) val] + [else + (define orig-pos (pos port)) + (pos port ptr) + (set! val (send type decode port ctx)) + (pos port orig-pos) + val])) + (if lazy + (LazyThunk decode-value) + (decode-value))] + [else ptr])])) + + + (define/augment (size [val #f] [ctx #f]) + (let*-values ([(parent) ctx] + [(ctx) (caseq pointer-style + [(local immediate) ctx] + [(parent) (· ctx parent)] + [(global) (find-top-ctx ctx)] + [else (error 'unknown-pointer-style)])] + [(type val) (resolve-void-pointer type val)]) + (when (and val ctx) + (ref-set! ctx 'pointerSize (and (· ctx pointerSize) + (+ (· ctx pointerSize) (send type size val parent))))) + (send offset-type size))) + + + (define/augment (encode port val [ctx #f]) + (unless ctx + ;; todo: furnish default pointer context? adapt from Struct? + (raise-argument-error 'Pointer:encode "valid pointer context" ctx)) + (if (not val) + (send offset-type encode port null-value) + (let* ([parent ctx] + [ctx (caseq pointer-style + [(local immediate) ctx] + [(parent) (· ctx parent)] + [(global) (find-top-ctx ctx)] + [else (error 'unknown-pointer-style)])] + [relative (+ (caseq pointer-style + [(local parent) (· ctx startOffset)] + [(immediate) (+ (pos port) (send offset-type size val parent))] + [(global) 0]) + (relative-getter-or-0 (· parent val)))]) + + (send offset-type encode port (- (· ctx pointerOffset) relative)) + + (let-values ([(type val) (resolve-void-pointer type val)]) + (ref-set! ctx 'pointers (append (· ctx pointers) (list (mhasheq 'type type + 'val val + 'parent parent)))) + (ref-set! ctx 'pointerOffset (+ (· ctx pointerOffset) (send type size val parent)))))))) + + +;; A pointer whose type is determined at decode time +(define-subclass object% (VoidPointer type value)) diff --git a/xenomorph/xenomorph/private/generic.rkt b/xenomorph/xenomorph/undo/private/generic.rkt similarity index 100% rename from xenomorph/xenomorph/private/generic.rkt rename to xenomorph/xenomorph/undo/private/generic.rkt diff --git a/xenomorph/xenomorph/private/helper.rkt b/xenomorph/xenomorph/undo/private/helper.rkt similarity index 100% rename from xenomorph/xenomorph/private/helper.rkt rename to xenomorph/xenomorph/undo/private/helper.rkt diff --git a/xenomorph/xenomorph/undo/reserved.rkt b/xenomorph/xenomorph/undo/reserved.rkt new file mode 100644 index 00000000..b0649366 --- /dev/null +++ b/xenomorph/xenomorph/undo/reserved.rkt @@ -0,0 +1,24 @@ +#lang racket/base +(require racket/class + sugar/unstable/class + "private/helper.rkt" + "utils.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Reserved.coffee +|# + +(define-subclass xenomorph-base% (Reserved type [count 1]) + + (define/augment (decode port parent) + (pos port (+ (pos port) (size #f parent))) + (void)) + + (define/augment (size [val #f] [parent #f]) + (* (send type size) (resolve-length count #f parent))) + + (define/augment (encode port val [parent #f]) + (make-bytes (size val parent) 0))) + diff --git a/xenomorph/xenomorph/sizes.rkt b/xenomorph/xenomorph/undo/sizes.rkt similarity index 100% rename from xenomorph/xenomorph/sizes.rkt rename to xenomorph/xenomorph/undo/sizes.rkt diff --git a/xenomorph/xenomorph/undo/string.rkt b/xenomorph/xenomorph/undo/string.rkt new file mode 100644 index 00000000..cecabfee --- /dev/null +++ b/xenomorph/xenomorph/undo/string.rkt @@ -0,0 +1,112 @@ +#lang racket/base +(require racket/class + sugar/unstable/class + sugar/unstable/case + sugar/unstable/js + "private/generic.rkt" + "private/helper.rkt" + "number.rkt" + "utils.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/String.coffee +|# + +(define (read-encoded-string port len [encoding 'ascii]) + (define proc (caseq encoding + [(utf16le) (error 'bah)] + [(ucs2) (error 'bleh)] + [(utf8) bytes->string/utf-8] + [(ascii) bytes->string/latin-1] + [else values])) + (proc (read-bytes len port))) + +(define (write-encoded-string port string [encoding 'ascii]) + ;; todo: handle encodings correctly. + ;; right now just utf8 and ascii are correct + (define proc (caseq encoding + [(ucs2 utf8 ascii) string->bytes/utf-8] + [(utf16le) (error 'swap-bytes-unimplemented)] + [else (error 'unsupported-string-encoding)])) + (write-bytes (proc string) port)) + +(define (count-nonzero-chars port) + ;; helper function for String + ;; counts nonzero chars from current position + (length (car (regexp-match-peek "[^\u0]*" port)))) + +(define (byte-length val encoding) + (define encoder + (caseq encoding + [(ascii utf8) string->bytes/utf-8])) + (bytes-length (encoder (format "~a" val)))) + +(define (bytes-left-in-port? port) + (not (eof-object? (peek-byte port)))) + +(define-subclass xenomorph-base% (StringT [len #f] [encoding 'ascii]) + + (define/augment (decode port [parent #f]) + (let ([len (or (resolve-length len port parent) (count-nonzero-chars port))] + [encoding (if (procedure? encoding) + (or (encoding parent) 'ascii) + encoding)] + [adjustment (if (and (not len) (bytes-left-in-port? port)) 1 0)]) + (define string (read-encoded-string port len encoding)) + (pos port (+ (pos port) adjustment)) + string)) + + + (define/augment (encode port val [parent #f]) + (let* ([val (format "~a" val)] + [encoding (if (procedure? encoding) + (or (encoding (and parent (· parent val)) 'ascii)) + encoding)]) + (define encoded-length (byte-length val encoding)) + (when (and (exact-nonnegative-integer? len) (> encoded-length len)) + (raise-argument-error 'String:encode (format "string no longer than ~a" len) val)) + (when (NumberT? len) + (send len encode port encoded-length)) + (write-encoded-string port val encoding) + (when (not len) (write-byte #x00 port)))) ; null terminated when no len + + + (define/augment (size [val #f] [parent #f]) + (if (not val) + (resolve-length len #f parent) + (let* ([encoding (if (procedure? encoding) + (or (encoding (and parent (· parent val)) 'ascii)) + encoding)] + [encoding (if (eq? encoding 'utf16be) 'utf16le encoding)]) + (+ (byte-length val encoding) (cond + [(not len) 1] + [(NumberT? len) (send len size)] + [else 0])))))) + + +(define-values (String? +String) (values StringT? +StringT)) + +(define-subclass StringT (Symbol) + (define/override (post-decode string-val . _) + (string->symbol string-val)) + + (define/override (pre-encode sym-val . _) + (unless (or (string? sym-val) (symbol? sym-val)) + (raise-argument-error 'Symbol "symbol or string" sym-val)) + (if (symbol? sym-val) sym-val (string->symbol sym-val)))) + + +(test-module + (define S-fixed (+String 4 'utf8)) + (check-equal? (encode S-fixed "Mike" #f) #"Mike") + (check-exn exn:fail? (λ () (encode S-fixed "Mikes" #f))) ; too long for fixed string + (define S (+String uint8 'utf8)) + (check-equal? (decode S #"\2BCDEF") "BC") + (check-equal? (encode S "Mike" #f) #"\4Mike") + (check-equal? (size (+String) "foobar") 7) ; null terminated when no len + (check-equal? (decode (+Symbol 4) #"Mike") 'Mike) + (check-equal? (encode (+Symbol 4) 'Mike #f) #"Mike") + (check-equal? (encode (+Symbol 4) "Mike" #f) #"Mike") + (check-exn exn:fail:contract? (λ () (encode (+Symbol 4) 42 #f)))) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/struct.rkt b/xenomorph/xenomorph/undo/struct.rkt new file mode 100644 index 00000000..a7efdcba --- /dev/null +++ b/xenomorph/xenomorph/undo/struct.rkt @@ -0,0 +1,161 @@ +#lang racket/base +(require racket/list + sugar/unstable/class + sugar/unstable/dict + sugar/unstable/js + racket/class + "private/helper.rkt" + "private/generic.rkt" + racket/dict + racket/private/generic-methods) +(provide (all-defined-out) ref* ref*-set! (all-from-out racket/dict)) +(require (prefix-in d: racket/dict)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Struct.coffee +|# + + +(define private-keys '(parent _startOffset _currentOffset _length)) + +(define (choose-dict d k) + (if (memq k private-keys) + (get-field _pvt d) + (get-field _kv d))) + +(define dictable<%> + (interface* () + ([(generic-property gen:dict) + (generic-method-table gen:dict + (define (dict-set! d k v) (d:dict-set! (choose-dict d k) k v)) + (define (dict-ref d k [thunk #f]) + (define res (d:dict-ref (choose-dict d k) k thunk)) + (if (LazyThunk? res) ((LazyThunk-proc res)) res)) + (define (dict-remove! d k) (d:dict-remove! (choose-dict d k) k)) + ;; public keys only + (define (dict-keys d) (d:dict-keys (get-field _kv d))) + (define (dict-iterate-first d) (and (pair? (dict-keys d)) 0)) + (define (dict-iterate-next d i) (and (< (add1 i) (length (dict-keys d))) (add1 i))) + (define (dict-iterate-key d i) (list-ref (dict-keys d) i)) + (define (dict-iterate-value d i) (dict-ref d (dict-iterate-key d i))))] + [(generic-property gen:custom-write) + (generic-method-table gen:custom-write + (define (write-proc o port mode) + (define proc (case mode + [(#t) write] + [(#f) display] + [else (λ (p port) (print p port mode))])) + (proc (dump o) port)))]))) + +(define-subclass*/interfaces xenomorph-base% (dictable<%>) + (StructDictRes) + (super-make-object) + (field [_kv (mhasheq)] + [_pvt (mhasheq)]) + + (define/override (dump) + ;; convert to immutable for display & debug + (for/hasheq ([(k v) (in-hash _kv)]) + (values k v))) + + (define/public (to-hash) _kv)) + + +(define-subclass xenomorph-base% (Struct [fields (dictify)]) + (field [[_post-decode post-decode] (λ (val port ctx) val)] + [[_pre-encode pre-encode] (λ (val port) val)]) ; store as field so it can be mutated from outside + + (define/overment (post-decode res . args) + (let* ([res (apply _post-decode res args)] + [res (inner res post-decode res . args)]) + (unless (dict? res) (raise-result-error 'Struct:post-decode "dict" res)) + res)) + + (define/overment (pre-encode res . args) + (let* ([res (apply _pre-encode res args)] + [res (inner res pre-encode res . args)]) + (unless (dict? res) (raise-result-error 'Struct:pre-encode "dict" res)) + res)) + + (unless (or (assocs? fields) (Struct? fields)) ; should be Versioned Struct but whatever + (raise-argument-error 'Struct "assocs or Versioned Struct" fields)) + + (define/augride (decode stream [parent #f] [len 0]) + ;; _setup and _parse-fields are separate to cooperate with VersionedStruct + (let* ([sdr (_setup stream parent len)] ; returns StructDictRes + [sdr (_parse-fields stream sdr fields)]) + sdr)) + + (define/public-final (_setup port parent len) + (define sdr (make-object StructDictRes)) ; not mere hash + (dict-set*! sdr 'parent parent + '_startOffset (pos port) + '_currentOffset 0 + '_length len) + sdr) + + (define/public-final (_parse-fields port sdr fields) + (unless (assocs? fields) + (raise-argument-error '_parse-fields "assocs" fields)) + (for/fold ([sdr sdr]) + ([(key type) (in-dict fields)]) + (define val (if (procedure? type) + (type sdr) + (send type decode port sdr))) + (unless (void? val) + (dict-set! sdr key val)) + (dict-set! sdr '_currentOffset (- (pos port) (· sdr _startOffset))) + sdr)) + + + (define/augride (size [val #f] [parent #f] [include-pointers #t]) + (define ctx (mhasheq 'parent parent + 'val val + 'pointerSize 0)) + (+ (for/sum ([(key type) (in-dict fields)] + #:when (object? type)) + (send type size (and val (ref val key)) ctx)) + (if include-pointers (· ctx pointerSize) 0))) + + (define/augride (encode port val [parent #f]) + (unless (dict? val) + (raise-argument-error 'Struct:encode "dict" val)) + + ;; check keys first, since `size` also relies on keys being valid + (unless (andmap (λ (key) (memq key (dict-keys val))) (dict-keys fields)) + (raise-argument-error 'Struct:encode + (format "dict that contains superset of Struct keys: ~a" (dict-keys fields)) (dict-keys val))) + + (define ctx (mhash 'pointers empty + 'startOffset (pos port) + 'parent parent + 'val val + 'pointerSize 0)) + (ref-set! ctx 'pointerOffset (+ (pos port) (size val ctx #f))) + + (for ([(key type) (in-dict fields)]) + (send type encode port (ref val key) ctx)) + (for ([ptr (in-list (· ctx pointers))]) + (send (· ptr type) encode port (· ptr val) (· ptr parent))))) + + +(test-module + (require "number.rkt") + (define (random-pick xs) (list-ref xs (random (length xs)))) + (check-exn exn:fail:contract? (λ () (+Struct 42))) + + ;; make random structs and make sure we can round trip + (for ([i (in-range 20)]) + (define field-types (for/list ([i (in-range 40)]) + (random-pick (list uint8 uint16be uint16le uint32be uint32le double)))) + (define size-num-types (for/sum ([num-type (in-list field-types)]) + (send num-type size))) + (define s (+Struct (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? (send s encode #f (send s decode bs)) bs))) + + + diff --git a/xenomorph/xenomorph/undo/test/array-test.rkt b/xenomorph/xenomorph/undo/test/array-test.rkt new file mode 100644 index 00000000..9907499f --- /dev/null +++ b/xenomorph/xenomorph/undo/test/array-test.rkt @@ -0,0 +1,91 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/dict) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Array.coffee +|# + +;describe 'Array', -> +; describe 'decode', -> +; it 'should decode fixed length', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint8 4)) '(1 2 3 4))) + + +; it 'should decode fixed amount of bytes', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint16be 4 'bytes)) '(258 772))) + + +; it 'should decode length from parent key', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint8 'len) #:parent (mhash 'len 4)) '(1 2 3 4))) + + +; it 'should decode amount of bytes from parent key', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint16be 'len 'bytes) #:parent (mhash 'len 4)) '(258 772))) + + +; it 'should decode length as number before array', -> +(parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint8 uint8)) '(1 2 3 4))) + + +; it 'should decode amount of bytes as number before array', -> +(parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint16be uint8 'bytes)) '(258 772))) + + +; it 'should decode length from function', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint8 (λ _ 4))) '(1 2 3 4))) + + +; it 'should decode amount of bytes from function', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint16be (λ _ 4) 'bytes)) '(258 772))) + + +; it 'should decode to the end of the parent if no length is given', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint8) #:parent (mhash '_length 4 '_startOffset 0)) '(1 2 3 4))) + + +; decode to the end of the stream if parent exists, but its length is 0 +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (check-equal? (decode (+ArrayT uint8) #:parent (mhash '_length 0 '_startOffset 0)) '(1 2 3 4 5))) + + +; it 'should decode to the end of the stream if no parent and length is given', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4))]) + (check-equal? (decode (+ArrayT uint8)) '(1 2 3 4 ))) + + +; describe 'size', -> +; it 'should use array length', -> +(check-equal? (size (+ArrayT uint8 10) '(1 2 3 4)) 4) + + +; it 'should add size of length field before string', -> +(check-equal? (size (+ArrayT uint8 uint8) '(1 2 3 4)) 5) + + +; it 'should use defined length if no value given', -> +(check-equal? (size (+ArrayT uint8 10)) 10) + + +; describe 'encode', -> +; it 'should encode using array length', (done) -> +(check-equal? (encode (+ArrayT uint8 10) '(1 2 3 4) #f) (bytes 1 2 3 4)) + + +; it 'should encode length as number before array', (done) -> +(check-equal? (encode (+ArrayT uint8 uint8) '(1 2 3 4) #f) (bytes 4 1 2 3 4)) + + +; it 'should add pointers after array if length is encoded at start', (done) -> +(check-equal? (encode (+ArrayT (+Pointer uint8 uint8) uint8) '(1 2 3 4) #f) (bytes 4 5 6 7 8 1 2 3 4)) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/test/bitfield-test.rkt b/xenomorph/xenomorph/undo/test/bitfield-test.rkt new file mode 100644 index 00000000..8dba2c75 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/bitfield-test.rkt @@ -0,0 +1,51 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/dict + racket/list + racket/match) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Bitfield.coffee +|# + +;describe 'Bitfield', -> +; bitfield = new Bitfield uint8, ['Jack', 'Kack', 'Lack', 'Mack', 'Nack', 'Oack', 'Pack', 'Quack'] +; JACK = 1 << 0 +; KACK = 1 << 1 +; LACK = 1 << 2 +; MACK = 1 << 3 +; NACK = 1 << 4 +; OACK = 1 << 5 +; PACK = 1 << 6 +; QUACK = 1 << 7 + +(define bitfield (+Bitfield uint8 '(Jack Kack Lack Mack Nack Oack Pack Quack))) +(match-define (list JACK KACK LACK MACK NACK OACK PACK QUACK) + (map (λ (x) (arithmetic-shift 1 x)) (range 8))) + +; it 'should have the right size', -> +(check-equal? (size bitfield) 1) + +; it 'should decode', -> +(parameterize ([current-input-port (open-input-bytes (bytes (bitwise-ior JACK MACK PACK NACK QUACK)))]) + (check-equal? (decode bitfield) (mhasheq 'Quack #t + 'Nack #t + 'Lack #f + 'Oack #f + 'Pack #t + 'Mack #t + 'Jack #t + 'Kack #f))) + +; it 'should encode', (done) -> +(check-equal? (encode bitfield (mhasheq 'Quack #t + 'Nack #t + 'Lack #f + 'Oack #f + 'Pack #t + 'Mack #t + 'Jack #t + 'Kack #f) #f) + (bytes (bitwise-ior JACK MACK PACK NACK QUACK))) diff --git a/xenomorph/xenomorph/undo/test/buffer-test.rkt b/xenomorph/xenomorph/undo/test/buffer-test.rkt new file mode 100644 index 00000000..f3d19d68 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/buffer-test.rkt @@ -0,0 +1,46 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/dict) + + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Buffer.coffee +|# + +;describe 'Buffer', -> +; describe 'decode', -> +; it 'should decode', -> +(parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) + (define buf (+BufferT 2)) + (check-equal? (decode buf) (bytes #xab #xff)) + (check-equal? (decode buf) (bytes #x1f #xb6))) + + +; it 'should decode with parent key length', -> +(parameterize ([current-input-port (open-input-bytes (bytes #xab #xff #x1f #xb6))]) + (define buf (+BufferT 'len)) + (check-equal? (decode buf #:parent (hash 'len 3)) (bytes #xab #xff #x1f)) + (check-equal? (decode buf #:parent (hash 'len 1)) (bytes #xb6))) + + +; describe 'size', -> +; it 'should return size', -> +(check-equal? (size (+BufferT 2) (bytes #xab #xff)) 2) + + +; it 'should use defined length if no value given', ->x +(check-equal? (size (+BufferT 10)) 10) + + +; describe 'encode', -> +; it 'should encode', (done) -> +(let ([buf (+BufferT 2)]) + (check-equal? (bytes-append + (encode buf (bytes #xab #xff) #f) + (encode buf (bytes #x1f #xb6) #f)) (bytes #xab #xff #x1f #xb6))) + + +; it 'should encode length before buffer', (done) -> +(check-equal? (encode (+BufferT uint8) (bytes #xab #xff) #f) (bytes 2 #xab #xff)) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/test/enum-test.rkt b/xenomorph/xenomorph/undo/test/enum-test.rkt new file mode 100644 index 00000000..5ca766c6 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/enum-test.rkt @@ -0,0 +1,37 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/dict) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Enum.coffee +|# + +;describe 'Enum', -> +; e = new Enum uint8, ['foo', 'bar', 'baz'] +; it 'should have the right size', -> +; e.size().should.equal 1 + +(define e (+Enum uint8 '("foo" "bar" "baz"))) +(check-equal? (size e) 1) + + +; it 'should decode', -> +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 0))]) + (check-equal? (decode e) "bar") + (check-equal? (decode e) "baz") + (check-equal? (decode e) "foo")) + + +; it 'should encode', (done) -> +(parameterize ([current-output-port (open-output-bytes)]) + (encode e "bar") + (encode e "baz") + (encode e "foo") + (check-equal? (dump (current-output-port)) (bytes 1 2 0))) + + +; it 'should throw on unknown option', -> + +(check-exn exn:fail:contract? (λ () (encode e "unknown" (open-output-bytes)))) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/test/lazy-array-test.rkt b/xenomorph/xenomorph/undo/test/lazy-array-test.rkt new file mode 100644 index 00000000..73eeb6a9 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/lazy-array-test.rkt @@ -0,0 +1,62 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/dict + "../private/generic.rkt") + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/LazyArray.coffee +|# + +;describe 'LazyArray', -> +; describe 'decode', -> +; it 'should decode items lazily', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define array (+LazyArray uint8 4)) + (define arr (decode array)) + (check-false (Array? arr)) + (check-equal? (ref arr 'len) 4) + (check-equal? (pos (current-input-port)) 4) + (check-equal? (get arr 0) 1) + (check-equal? (get arr 1) 2) + (check-equal? (get arr 2) 3) + (check-equal? (get arr 3) 4)) + +; it 'should be able to convert to an array', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define array (+LazyArray uint8 4)) + (define arr (decode array)) + (check-equal? (LazyArray->list arr) '(1 2 3 4))) + + +; it 'should have an inspect method', -> +; [skipped] + + +; it 'should decode length as number before array', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 4 1 2 3 4 5))]) + (define array (+LazyArray uint8 uint8)) + (define arr (decode array)) + (check-equal? (LazyArray->list arr) '(1 2 3 4))) + +; +; describe 'size', -> +; it 'should work with LazyArrays', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define array (+LazyArray uint8 4)) + (define arr (decode array)) + (check-equal? (size array arr) 4)) + + +; describe 'encode', -> +; it 'should work with LazyArrays', (done) -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 3 4 5))]) + (define array (+LazyArray uint8 4)) + (define arr (decode array)) + (check-equal? (encode array arr #f) (bytes 1 2 3 4))) \ No newline at end of file diff --git a/xenomorph/xenomorph/redo/test/main.rkt b/xenomorph/xenomorph/undo/test/main.rkt similarity index 100% rename from xenomorph/xenomorph/redo/test/main.rkt rename to xenomorph/xenomorph/undo/test/main.rkt diff --git a/xenomorph/xenomorph/undo/test/number-test.rkt b/xenomorph/xenomorph/undo/test/number-test.rkt new file mode 100644 index 00000000..0ac7eb4c --- /dev/null +++ b/xenomorph/xenomorph/undo/test/number-test.rkt @@ -0,0 +1,339 @@ +#lang racket/base +(require rackunit + xenomorph + racket/class + sugar/unstable/dict) + + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Number.coffee +|# + +;describe 'Number', -> +; describe 'uint8', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> +(parameterize ([current-input-port (open-input-bytes (bytes #xab #xff))]) + (check-equal? (decode uint8) #xab) + (check-equal? (decode uint8) #xff)) + +(check-equal? (size uint8) 1) + +(let ([port (open-output-bytes)]) + (encode uint8 #xab port) + (encode uint8 #xff port) + (check-equal? (dump port) (bytes #xab #xff))) + + +; describe 'uint16', -> +; it 'is an alias for uint16be', -> +; modified test: `uint16` is the same endianness as the platform +(check-equal? (decode uint16 (bytes 0 1)) (send (if (system-big-endian?) + uint16be + uint16le) decode (bytes 0 1))) + +; describe 'uint16be', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode uint16be (open-input-bytes (bytes #xab #xff))) #xabff) +(check-equal? (size uint16be) 2) +(check-equal? (encode uint16be #xabff #f) (bytes #xab #xff)) + +; +; describe 'uint16le', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode uint16le (open-input-bytes (bytes #xff #xab))) #xabff) +(check-equal? (size uint16le) 2) +(check-equal? (encode uint16le #xabff #f) (bytes #xff #xab)) + +; +; describe 'uint24', -> +; it 'is an alias for uint24be', -> +;; modified test: `uint24` is the same endianness as the platform +(check-equal? (decode uint24 (bytes 0 1 2)) (send (if (system-big-endian?) + uint24be + uint24le) decode (bytes 0 1 2))) + +; +; describe 'uint24be', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode uint24be (open-input-bytes (bytes #xff #xab #x24))) #xffab24) +(check-equal? (size uint24be) 3) +(check-equal? (encode uint24be #xffab24 #f) (bytes #xff #xab #x24)) + +; +; describe 'uint24le', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode uint24le (open-input-bytes (bytes #x24 #xab #xff))) #xffab24) +(check-equal? (size uint24le) 3) +(check-equal? (encode uint24le #xffab24 #f) (bytes #x24 #xab #xff)) + +; +; describe 'uint32', -> +; it 'is an alias for uint32be', -> +;; modified test: `uint32` is the same endianness as the platform +(check-equal? (decode uint32 (bytes 0 1 2 3)) (send (if (system-big-endian?) + uint32be + uint32le) decode (bytes 0 1 2 3))) + +; +; describe 'uint32be', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode uint32be (open-input-bytes (bytes #xff #xab #x24 #xbf))) #xffab24bf) +(check-equal? (size uint32be) 4) +(check-equal? (encode uint32be #xffab24bf #f) (bytes #xff #xab #x24 #xbf)) + +; +; describe 'uint32le', -> +; it 'should decode', -> +; it 'should encode', (done) -> + +(check-equal? (decode uint32le (open-input-bytes (bytes #xbf #x24 #xab #xff))) #xffab24bf) +(check-equal? (size uint32le) 4) +(check-equal? (encode uint32le #xffab24bf #f) (bytes #xbf #x24 #xab #xff)) + + +; +; describe 'int8', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(let ([port (open-input-bytes (bytes #x7f #xff))]) + (check-equal? (decode int8 port) 127) + (check-equal? (decode int8 port) -1)) + +(check-equal? (size int8) 1) + +(let ([port (open-output-bytes)]) + (encode int8 127 port) + (encode int8 -1 port) + (check-equal? (dump port) (bytes #x7f #xff))) + + +; +; describe 'int16', -> +; it 'is an alias for int16be', -> +; int16.should.equal int16be + +;; modified test: `int16` is the same endianness as the platform +(check-equal? (decode int16 (bytes 0 1)) (send (if (system-big-endian?) + int16be + int16le) decode (bytes 0 1))) + + +; +; describe 'int16be', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(let ([port (open-input-bytes (bytes #xff #xab))]) + (check-equal? (decode int16be port) -85)) + +(check-equal? (size int16be) 2) + +(let ([port (open-output-bytes)]) + (encode int16be -85 port) + (check-equal? (dump port) (bytes #xff #xab))) + + +; describe 'int16le', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode int16le (open-input-bytes (bytes #xab #xff))) -85) +(check-equal? (size int16le) 2) +(check-equal? (encode int16le -85 #f) (bytes #xab #xff)) + + +; +; describe 'int24', -> +; it 'is an alias for int24be', -> +; int24.should.equal int24be + +;; modified test: `int24` is the same endianness as the platform +(check-equal? (decode int24 (bytes 0 1 2)) (send (if (system-big-endian?) + int24be + int24le) decode (bytes 0 1 2))) + + +; +; describe 'int24be', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode int24be (open-input-bytes (bytes #xff #xab #x24))) -21724) +(check-equal? (size int24be) 3) +(check-equal? (encode int24be -21724 #f) (bytes #xff #xab #x24)) + +; +; describe 'int24le', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode int24le (open-input-bytes (bytes #x24 #xab #xff))) -21724) +(check-equal? (size int24le) 3) +(check-equal? (encode int24le -21724 #f) (bytes #x24 #xab #xff)) + + + +; describe 'int32', -> +; it 'is an alias for int32be', -> +; modified test: `int32` is the same endianness as the platform +(check-equal? (decode int32 (bytes 0 1 2 3)) (send (if (system-big-endian?) + int32be + int32le) decode (bytes 0 1 2 3))) + + + +; +; describe 'int32be', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode int32be (open-input-bytes (bytes #xff #xab #x24 #xbf))) -5561153) +(check-equal? (size int32be) 4) +(check-equal? (encode int32be -5561153 #f) (bytes #xff #xab #x24 #xbf)) + +; +; describe 'int32le', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode int32le (open-input-bytes (bytes #xbf #x24 #xab #xff))) -5561153) +(check-equal? (size int32le) 4) +(check-equal? (encode int32le -5561153 #f) (bytes #xbf #x24 #xab #xff)) + +; +; describe 'float', -> +; it 'is an alias for floatbe', -> +; modified test: `float` is the same endianness as the platform +(check-equal? (decode float (bytes 0 1 2 3)) (send (if (system-big-endian?) + floatbe + floatle) decode (bytes 0 1 2 3))) + +; +; describe 'floatbe', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-= (decode floatbe (open-input-bytes (bytes #x43 #x7a #x8c #xcd))) 250.55 0.01) +(check-equal? (size floatbe) 4) +(check-equal? (encode floatbe 250.55 #f) (bytes #x43 #x7a #x8c #xcd)) + +; +; describe 'floatle', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-= (decode floatle (open-input-bytes (bytes #xcd #x8c #x7a #x43))) 250.55 0.01) +(check-equal? (size floatle) 4) +(check-equal? (encode floatle 250.55 #f) (bytes #xcd #x8c #x7a #x43)) + +; +; describe 'double', -> +; it 'is an alias for doublebe', -> +; modified test: `double` is the same endianness as the platform +(check-equal? (decode double (bytes 0 1 2 3 4 5 6 7)) (send (if (system-big-endian?) + doublebe + doublele) decode (bytes 0 1 2 3 4 5 6 7))) + +; +; describe 'doublebe', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode doublebe (open-input-bytes (bytes #x40 #x93 #x4a #x3d #x70 #xa3 #xd7 #x0a))) 1234.56) +(check-equal? (size doublebe) 8) +(check-equal? (encode doublebe 1234.56 #f) (bytes #x40 #x93 #x4a #x3d #x70 #xa3 #xd7 #x0a)) + +; +; describe 'doublele', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-equal? (decode doublele (open-input-bytes (bytes #x0a #xd7 #xa3 #x70 #x3d #x4a #x93 #x40))) 1234.56) +(check-equal? (size doublele) 8) +(check-equal? (encode doublele 1234.56 #f) (bytes #x0a #xd7 #xa3 #x70 #x3d #x4a #x93 #x40)) + +; +; describe 'fixed16', -> +; it 'is an alias for fixed16be', -> +; modified test: `fixed16` is the same endianness as the platform +(check-equal? (decode fixed16 (bytes 0 1)) (send (if (system-big-endian?) + fixed16be + fixed16le) decode (bytes 0 1))) + +; +; describe 'fixed16be', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-= (decode fixed16be (open-input-bytes (bytes #x19 #x57))) 25.34 0.01) +(check-equal? (size fixed16be) 2) +(check-equal? (encode fixed16be 25.34 #f) (bytes #x19 #x57)) + +; +; describe 'fixed16le', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-= (decode fixed16le (open-input-bytes (bytes #x57 #x19))) 25.34 0.01) +(check-equal? (size fixed16le) 2) +(check-equal? (encode fixed16le 25.34 #f) (bytes #x57 #x19)) + +; +; describe 'fixed32', -> +; it 'is an alias for fixed32be', -> +; modified test: `fixed32` is the same endianness as the platform + +(check-equal? (decode fixed32 (bytes 0 1 2 3)) (send (if (system-big-endian?) + fixed32be + fixed32le) decode (bytes 0 1 2 3))) + +; +; describe 'fixed32be', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> +(check-= (decode fixed32be (open-input-bytes (bytes #x00 #xfa #x8c #xcc))) 250.55 0.01) +(check-equal? (size fixed32be) 4) +(check-equal? (encode fixed32be 250.55 #f) (bytes #x00 #xfa #x8c #xcc)) + +; +; describe 'fixed32le', -> +; it 'should decode', -> +; it 'should have a size', -> +; it 'should encode', (done) -> + +(check-= (decode fixed32le (open-input-bytes (bytes #xcc #x8c #xfa #x00))) 250.55 0.01) +(check-equal? (size fixed32le) 4) +(check-equal? (encode fixed32le 250.55 #f) (bytes #xcc #x8c #xfa #x00)) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/test/optional-test.rkt b/xenomorph/xenomorph/undo/test/optional-test.rkt new file mode 100644 index 00000000..d5e87301 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/optional-test.rkt @@ -0,0 +1,116 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/dict) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Optional.coffee +|# + +;describe 'Optional', -> +; describe 'decode', -> +; it 'should not decode when condition is falsy', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+Optional uint8 #f)) + (check-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 0)) + +; it 'should not decode when condition is a function and falsy', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+Optional uint8 (λ _ #f))) + (check-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 0)) + + +; it 'should decode when condition is omitted', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+Optional uint8)) + (check-not-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 1)) + +; +; it 'should decode when condition is truthy', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+Optional uint8 #t)) + (check-not-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 1)) + + +; it 'should decode when condition is a function and truthy', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (define optional (+Optional uint8 (λ _ #t))) + (check-not-equal? (decode optional) (void)) + (check-equal? (pos (current-input-port)) 1)) + + +; describe 'size', -> + +(check-equal? (size (+Optional uint8 #f)) 0) + +; +; it 'should return 0 when condition is a function and falsy', -> + +(check-equal? (size (+Optional uint8 (λ _ #f))) 0) + + +; it 'should return given type size when condition is omitted', -> + +(check-equal? (size (+Optional uint8)) 1) + + +; it 'should return given type size when condition is truthy', -> + +(check-equal? (size (+Optional uint8 #t)) 1) + + +; it 'should return given type size when condition is a function and truthy', -> + +(check-equal? (size (+Optional uint8 (λ _ #t))) 1) + + +; describe 'encode', -> +; it 'should not encode when condition is falsy', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define optional (+Optional uint8 #f)) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes))) + + +; it 'should not encode when condition is a function and falsy', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define optional (+Optional uint8 (λ _ #f))) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes))) + + +; +; it 'should encode when condition is omitted', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define optional (+Optional uint8)) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes 128))) + + +; it 'should encode when condition is truthy', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define optional (+Optional uint8 #t)) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes 128))) + + +; it 'should encode when condition is a function and truthy', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define optional (+Optional uint8 (λ _ #t))) + (encode optional 128) + (check-equal? (dump (current-output-port)) (bytes 128))) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/test/pointer-test.rkt b/xenomorph/xenomorph/undo/test/pointer-test.rkt new file mode 100644 index 00000000..71093e08 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/pointer-test.rkt @@ -0,0 +1,239 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/js + sugar/unstable/dict + racket/class + "../private/helper.rkt") + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Pointer.coffee +|# + + +;describe 'Pointer', -> +; describe 'decode', -> +; it 'should handle null pointers', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 0))]) + (check-false (decode (+Pointer uint8 uint8) #:parent (mhash '_startOffset 50)))) + + +; it 'should use local offsets from start of parent by default', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) + (check-equal? (decode (+Pointer uint8 uint8) #:parent (mhash '_startOffset 0)) 53)) + + +; it 'should support immediate offsets', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) + (check-equal? (decode (+Pointer uint8 uint8 (mhash 'type 'immediate))) 53)) + + +; it 'should support offsets relative to the parent', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 0 0 1 53))]) + (pos (current-input-port) 2) + (check-equal? (decode (+Pointer uint8 uint8 (mhash 'type 'parent)) + #:parent (mhash 'parent (mhash '_startOffset 2))) 53)) + + +; it 'should support global offsets', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 2 4 0 0 0 53))]) + (pos (current-input-port) 2) + (check-equal? (decode (+Pointer uint8 uint8 (mhash 'type 'global)) + #:parent (mhash 'parent (mhash 'parent (mhash '_startOffset 2)))) + 53)) + + +; it 'should support offsets relative to a property on the parent', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 0 0 0 0 53))]) + (check-equal? (decode (+Pointer uint8 uint8 (mhash 'relativeTo (λ (ctx) (· ctx parent ptr)))) + #:parent (mhash '_startOffset 0 'parent (mhash 'ptr 4))) + 53)) + + +; it 'should support returning pointer if there is no decode type', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 4))]) + (check-equal? (decode (+Pointer uint8 'void) + #:parent (mhash '_startOffset 0)) 4)) + + +; it 'should support decoding pointers lazily', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 1 53))]) + (define res (decode (+Struct (dictify 'ptr (+Pointer uint8 uint8 (mhasheq 'lazy #t)))))) + (check-true (LazyThunk? (hash-ref (get-field _kv res) 'ptr))) + (check-equal? (· res ptr) 53)) + + + +; describe 'size', -> + +(let ([ctx (mhash 'pointerSize 0)]) + (check-equal? (size (+Pointer uint8 uint8) 10 ctx) 1) + (check-equal? (· ctx pointerSize) 1)) + + + +; it 'should add to immediate pointerSize', -> + +(let ([ctx (mhash 'pointerSize 0)]) + (check-equal? (size (+Pointer uint8 uint8 (mhash 'type 'immediate)) 10 ctx) 1) + (check-equal? (· ctx pointerSize) 1)) + + +; it 'should add to parent pointerSize', -> + +(let ([ctx (mhash 'parent (mhash 'pointerSize 0))]) + (check-equal? (size (+Pointer uint8 uint8 (mhash 'type 'parent)) 10 ctx) 1) + (check-equal? (· ctx parent pointerSize) 1)) + + + +; it 'should add to global pointerSize', -> + +(let ([ctx (mhash 'parent (mhash 'parent (mhash 'parent (mhash 'pointerSize 0))))]) + (check-equal? (size (+Pointer uint8 uint8 (mhash 'type 'global)) 10 ctx) 1) + (check-equal? (· ctx parent parent parent pointerSize) 1)) + + + +; it 'should handle void pointers', -> + +(let ([ctx (mhash 'pointerSize 0)]) + (check-equal? (size (+Pointer uint8 'void) (+VoidPointer uint8 50) ctx) 1) + (check-equal? (· ctx pointerSize) 1)) + + +; it 'should throw if no type and not a void pointer', -> + +(let ([ctx (mhash 'pointerSize 0)]) + (check-exn exn:fail:contract? (λ () (size (+Pointer uint8 'void) 30 ctx)))) + + +; it 'should return a fixed size without a value', -> + +(check-equal? (size (+Pointer uint8 uint8)) 1) + + +; describe 'encode', -> +; it 'should handle null pointers', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define ctx (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 0 + 'pointers null)) + (encode (+Pointer uint8 uint8) #f #:parent ctx) + (check-equal? (· ctx pointerSize) 0) + (check-equal? (dump (current-output-port)) (bytes 0))) + + +; it 'should handle local offsets', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define ctx (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 1 + 'pointers null)) + (encode (+Pointer uint8 uint8) 10 #:parent ctx) + (check-equal? (· ctx pointerOffset) 2) + (check-equal? (· ctx pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent ctx))) + (check-equal? (dump (current-output-port)) (bytes 1))) + + +; it 'should handle immediate offsets', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define ctx (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 1 + 'pointers null)) + (encode (+Pointer uint8 uint8 (mhash 'type 'immediate)) 10 #:parent ctx) + (check-equal? (· ctx pointerOffset) 2) + (check-equal? (· ctx pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent ctx))) + (check-equal? (dump (current-output-port)) (bytes 0))) + + +; it 'should handle offsets relative to parent', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define ctx (mhash 'parent (mhash 'pointerSize 0 + 'startOffset 3 + 'pointerOffset 5 + 'pointers null))) + (encode (+Pointer uint8 uint8 (mhash 'type 'parent)) 10 #:parent ctx) + (check-equal? (· ctx parent pointerOffset) 6) + (check-equal? (· ctx parent pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent ctx))) + (check-equal? (dump (current-output-port)) (bytes 2))) + + + +; it 'should handle global offsets', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define ctx (mhash 'parent + (mhash 'parent + (mhash 'parent (mhash 'pointerSize 0 + 'startOffset 3 + 'pointerOffset 5 + 'pointers null))))) + (encode (+Pointer uint8 uint8 (mhash 'type 'global)) 10 #:parent ctx) + (check-equal? (· ctx parent parent parent pointerOffset) 6) + (check-equal? (· ctx parent parent parent pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent ctx))) + (check-equal? (dump (current-output-port)) (bytes 5))) + + +; it 'should support offsets relative to a property on the parent', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define ctx (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 10 + 'pointers null + 'val (mhash 'ptr 4))) + (encode (+Pointer uint8 uint8 (mhash 'relativeTo (λ (ctx) (· ctx ptr)))) 10 #:parent ctx) + (check-equal? (· ctx pointerOffset) 11) + (check-equal? (· ctx pointers) (list (mhasheq 'type uint8 + 'val 10 + 'parent ctx))) + (check-equal? (dump (current-output-port)) (bytes 6))) + + +; it 'should support void pointers', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define ctx (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 1 + 'pointers null)) + (encode (+Pointer uint8 'void) (+VoidPointer uint8 55) #:parent ctx) + (check-equal? (· ctx pointerOffset) 2) + (check-equal? (· ctx pointers) (list (mhasheq 'type uint8 + 'val 55 + 'parent ctx))) + (check-equal? (dump (current-output-port)) (bytes 1))) + + +; it 'should throw if not a void pointer instance', -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define ctx (mhash 'pointerSize 0 + 'startOffset 0 + 'pointerOffset 1 + 'pointers null)) + (check-exn exn:fail:contract? (λ () (encode (+Pointer uint8 'void) 44 #:parent ctx)))) diff --git a/xenomorph/xenomorph/undo/test/reserved-test.rkt b/xenomorph/xenomorph/undo/test/reserved-test.rkt new file mode 100644 index 00000000..40dbead8 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/reserved-test.rkt @@ -0,0 +1,35 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/dict) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Reserved.coffee +|# + +;describe 'Reserved', -> +; it 'should have a default count of 1', -> + +(check-equal? (size (+Reserved uint8)) 1) + + +; it 'should allow custom counts and types', -> + +(check-equal? (size (+Reserved uint16be 10)) 20) + + +; it 'should decode', -> + +(parameterize ([current-input-port (open-input-bytes (bytes 0 0))]) + (define reserved (+Reserved uint16be)) + (check-equal? (decode reserved) (void)) + (check-equal? (pos (current-input-port)) 2)) + + +; it 'should encode', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define reserved (+Reserved uint16be)) + (encode reserved #f) + (check-equal? (dump (current-output-port)) (bytes 0 0))) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/test/string-test.rkt b/xenomorph/xenomorph/undo/test/string-test.rkt new file mode 100644 index 00000000..786e594e --- /dev/null +++ b/xenomorph/xenomorph/undo/test/string-test.rkt @@ -0,0 +1,131 @@ +#lang racket/base +(require rackunit + xenomorph + sugar/unstable/dict) + + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/String.coffee +|# + +;describe 'String', -> +; describe 'decode', -> +; it 'should decode fixed length', -> + +(parameterize ([current-input-port (open-input-bytes #"testing")]) + (check-equal? (decode (+StringT 7)) "testing")) + + +; it 'should decode length from parent key', -> + +(parameterize ([current-input-port (open-input-bytes #"testing")]) + (check-equal? (decode (+StringT 'len) #:parent (mhash 'len 7)) "testing")) + + +; it 'should decode length as number before string', -> + +(parameterize ([current-input-port (open-input-bytes #"\x07testing")]) + (check-equal? (decode (+StringT uint8) #:parent (mhash 'len 7)) "testing")) + + +;; it 'should decode utf8', -> + +(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) + (check-equal? (decode (+StringT 4 'utf8)) "🍻")) + +;; it 'should decode encoding computed from function', -> + +(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) + (check-equal? (decode (+StringT 4 (λ _ 'utf8))) "🍻")) + + +; it 'should decode null-terminated string and read past terminator', -> + +(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻\x00"))]) + (check-equal? (decode (+StringT #f 'utf8)) "🍻") + (check-equal? (pos (current-input-port)) 5)) + + +; it 'should decode remainder of buffer when null-byte missing', -> + +(parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "🍻"))]) + (check-equal? (decode (+StringT #f 'utf8)) "🍻")) + + +; describe 'size', -> +; it 'should use string length', -> + +(check-equal? (size (+StringT 7) "testing") 7) + + +; it 'should use correct encoding', -> + +(check-equal? (size (+StringT 10 'utf8) "🍻") 4) + + +; it 'should use encoding from function', -> + +(check-equal? (size (+StringT 10 (λ _ 'utf8)) "🍻") 4) + + +; it 'should add size of length field before string', -> + +(check-equal? (size (+StringT uint8 'utf8) "🍻") 5) + + +; todo +; it 'should work with utf16be encoding', -> + + +; it 'should take null-byte into account', -> + +(check-equal? (size (+StringT #f 'utf8) "🍻") 5) + + +; it 'should use defined length if no value given', -> + +(check-equal? (size (+StringT 10)) 10) + +; +; describe 'encode', -> +; it 'should encode using string length', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (encode (+StringT 7) "testing") + (check-equal? (dump (current-output-port)) #"testing")) + + +; it 'should encode length as number before string', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (encode (+StringT uint8) "testing") + (check-equal? (dump (current-output-port)) #"\x07testing")) + + +; it 'should encode length as number before string utf8', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (encode (+StringT uint8 'utf8) "testing 😜") + (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "\x0ctesting 😜"))) + + +; it 'should encode utf8', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (encode (+StringT 4 'utf8) "🍻" ) + (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻"))) + + +; it 'should encode encoding computed from function', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (encode (+StringT 4 (λ _ 'utf8)) "🍻") + (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻"))) + + +; it 'should encode null-terminated string', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (encode (+StringT #f 'utf8) "🍻" ) + (check-equal? (dump (current-output-port)) (string->bytes/utf-8 "🍻\x00"))) \ No newline at end of file diff --git a/xenomorph/xenomorph/undo/test/struct-test.rkt b/xenomorph/xenomorph/undo/test/struct-test.rkt new file mode 100644 index 00000000..587428b9 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/struct-test.rkt @@ -0,0 +1,126 @@ +#lang racket/base +(require rackunit + xenomorph + racket/class + sugar/unstable/dict + sugar/unstable/js + "../private/generic.rkt") + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/Struct.coffee +|# + + +;describe 'Struct', -> +; describe 'decode', -> +; it 'should decode into an object', -> + +(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) + (check-equal? + (dump (decode (+Struct (dictify 'name (+StringT uint8) + 'age uint8)))) + (hasheq 'name "roxyb" 'age 21))) + + + +; it 'should support process hook', -> + +(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")]) + (define struct (+Struct (dictify 'name (+StringT uint8) + 'age uint8))) + (set-field! post-decode struct (λ (o . _) (ref-set! o 'canDrink (>= (· o age) 21)) o)) + (check-equal? (dump (decode struct)) + (hasheq 'name "roxyb" 'age 32 'canDrink #t))) + + + +; it 'should support function keys', -> + +(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x20")]) + (define struct (+Struct (dictify 'name (+StringT uint8) + 'age uint8 + 'canDrink (λ (o) (>= (ref o 'age) 21))))) + (check-equal? (dump (decode struct)) + (hasheq 'name "roxyb" 'age 32 'canDrink #t))) + + + + +; +; describe 'size', -> +; it 'should compute the correct size', -> + +(check-equal? (size (+Struct (dictify + 'name (+StringT uint8) + 'age uint8)) + (hasheq 'name "roxyb" 'age 32)) 7) + + + +; it 'should compute the correct size with pointers', -> + +(check-equal? (size (+Struct (dictify + 'name (+StringT uint8) + 'age uint8 + 'ptr (+Pointer uint8 (+StringT uint8)))) + (mhash 'name "roxyb" 'age 21 'ptr "hello")) 14) + + +; it 'should get the correct size when no value is given', -> + +(check-equal? (size (+Struct (dictify + 'name (+StringT 4) + 'age uint8))) 5) + +; it 'should throw when getting non-fixed length size and no value is given', -> + +(check-exn exn:fail:contract? (λ () (size (+Struct (dictify 'name (+StringT uint8) + 'age uint8))))) + + + +; +; describe 'encode', -> +; it 'should encode objects to buffers', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer '\x05roxyb\x15' +; done() +; +; struct = new Struct +; name: new StringT uint8 +; age: uint8 +; +; struct.encode stream, +; name: 'roxyb' +; age: 21 +; +; stream.end() + +(parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) + (check-equal? (dump (decode (+Struct (dictify 'name (+StringT uint8) + 'age uint8)))) + (hasheq 'name "roxyb" 'age 21))) + + +; it 'should support preEncode hook', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define struct (+Struct (dictify 'nameLength uint8 + 'name (+StringT 'nameLength) + 'age uint8))) + (set-field! pre-encode struct (λ (val port) (ref-set! val 'nameLength (length (ref val 'name))) val)) + (encode struct (mhasheq 'name "roxyb" 'age 21)) + (check-equal? (dump (current-output-port)) #"\x05roxyb\x15")) + + +; it 'should encode pointer data after structure', (done) -> + +(parameterize ([current-output-port (open-output-bytes)]) + (define struct (+Struct (dictify 'name (+StringT uint8) + 'age uint8 + 'ptr (+Pointer uint8 (+StringT uint8))))) + (encode struct (hasheq 'name "roxyb" 'age 21 'ptr "hello")) + (check-equal? (dump (current-output-port)) #"\x05roxyb\x15\x08\x05hello")) + diff --git a/xenomorph/xenomorph/test/test.rkt b/xenomorph/xenomorph/undo/test/test.rkt similarity index 100% rename from xenomorph/xenomorph/test/test.rkt rename to xenomorph/xenomorph/undo/test/test.rkt diff --git a/xenomorph/xenomorph/undo/test/versioned-struct-test.rkt b/xenomorph/xenomorph/undo/test/versioned-struct-test.rkt new file mode 100644 index 00000000..f8e13351 --- /dev/null +++ b/xenomorph/xenomorph/undo/test/versioned-struct-test.rkt @@ -0,0 +1,344 @@ +#lang racket/base +(require rackunit + xenomorph + racket/class + "../private/generic.rkt" + sugar/unstable/dict) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/test/VersionedStruct.coffee +|# + +;describe 'VersionedStruct', -> +; describe 'decode', -> +; it 'should get version from number type', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))]) + + (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) + (check-equal? (dump (decode struct)) (hasheq 'name "roxyb" + 'age 21 + 'version 0))) + + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x0aroxyb 🤘\x15\x00"))]) + (check-equal? (dump (decode struct)) (hasheq 'name "roxyb 🤘" + 'age 21 + 'version 1 + 'gender 0)))) + + +; it 'should throw for unknown version', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))]) + + (parameterize ([current-input-port (open-input-bytes #"\x05\x05roxyb\x15")]) + (check-exn exn:fail:contract? (λ () (decode struct))))) + + +; +; it 'should support common header block', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 'header (dictify 'age uint8 + 'alive uint8) + 0 (dictify 'name (+StringT uint8 'ascii)) + 1 (dictify 'name (+StringT uint8 'utf8) + 'gender uint8)))]) + + (parameterize ([current-input-port (open-input-bytes #"\x00\x15\x01\x05roxyb")]) + (check-equal? (dump (decode struct)) (hasheq 'name "roxyb" + 'age 21 + 'alive 1 + 'version 0))) + + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x01\x15\x01\x0aroxyb 🤘\x00"))]) + (check-equal? (dump (decode struct)) (hasheq 'name "roxyb 🤘" + 'age 21 + 'version 1 + 'alive 1 + 'gender 0)))) + + +; it 'should support parent version key', -> + +(let ([struct (+VersionedStruct 'version + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))]) + + (parameterize ([current-input-port (open-input-bytes #"\x05roxyb\x15")]) + (check-equal? (dump (decode struct #:parent (mhash 'version 0))) (hasheq 'name "roxyb" + 'age 21 + 'version 0))) + + (parameterize ([current-input-port (open-input-bytes (string->bytes/utf-8 "\x0aroxyb 🤘\x15\x00"))]) + (check-equal? (dump (decode struct #:parent (mhash 'version 1))) (hasheq 'name "roxyb 🤘" + 'age 21 + 'version 1 + 'gender 0)))) + + + +; +; it 'should support sub versioned structs', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8)) + 1 (dictify 'name (+StringT uint8) + 'isDessert uint8)))))]) + + (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) + (check-equal? (dump (decode struct #:parent (mhash 'version 0))) (hasheq 'name "roxyb" + 'age 21 + 'version 0))) + + (parameterize ([current-input-port (open-input-bytes #"\x01\x00\x05pasta")]) + (check-equal? (dump (decode struct #:parent (mhash 'version 0))) (hasheq 'name "pasta" + 'version 0))) + + (parameterize ([current-input-port (open-input-bytes #"\x01\x01\x09ice cream\x01")]) + (check-equal? (dump (decode struct #:parent (mhash 'version 0))) (hasheq 'name "ice cream" + 'isDessert 1 + 'version 1)))) + + +; +; it 'should support process hook', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))]) + (set-field! post-decode struct (λ (o stream ctx) (ref-set! o 'processed "true") o)) + (parameterize ([current-input-port (open-input-bytes #"\x00\x05roxyb\x15")]) + (check-equal? (dump (decode struct)) (hasheq 'name "roxyb" + 'processed "true" + 'age 21 + 'version 0)))) + + +; +; describe 'size', -> +; it 'should compute the correct size', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))]) + + (check-equal? (size struct (mhasheq 'name "roxyb" + 'age 21 + 'version 0)) 8) + + (check-equal? (size struct (mhasheq 'name "roxyb 🤘" + 'gender 0 + 'age 21 + 'version 1)) 14)) + + + + +; +; it 'should throw for unknown version', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))]) + + (check-exn exn:fail:contract? (λ () (size struct (mhasheq 'name "roxyb" + 'age 21 + 'version 5))))) + + +; +; it 'should support common header block', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 'header (dictify 'age uint8 + 'alive uint8) + 0 (dictify 'name (+StringT uint8 'ascii)) + 1 (dictify 'name (+StringT uint8 'utf8) + 'gender uint8)))]) + + (check-equal? (size struct (mhasheq 'name "roxyb" + 'age 21 + 'alive 1 + 'version 0)) 9) + + (check-equal? (size struct (mhasheq 'name "roxyb 🤘" + 'gender 0 + 'age 21 + 'alive 1 + 'version 1)) 15)) + + + +; it 'should compute the correct size with pointers', -> + + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'ptr (+Pointer uint8 (+StringT uint8)))))]) + + (check-equal? (size struct (mhasheq 'name "roxyb" + 'age 21 + 'version 1 + 'ptr "hello")) 15)) + + +; +; it 'should throw if no value is given', -> + + + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))]) + + (check-exn exn:fail:contract? (λ () (size struct)))) + + + +; describe 'encode', -> +; it 'should encode objects to buffers', (done) -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))] + [port (open-output-bytes)]) + (encode struct (mhasheq 'name "roxyb" + 'age 21 + 'version 0) port) + (encode struct (mhasheq 'name "roxyb 🤘" + 'age 21 + 'gender 0 + 'version 1) port) + (check-equal? (dump port) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00"))) + + +; +; it 'should throw for unknown version', -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))] + [port (open-output-bytes)]) + (check-exn exn:fail:contract? (λ () (encode struct port (mhasheq 'name "roxyb" + 'age 21 + 'version 5))))) + + + +; it 'should support common header block', (done) -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 'header (dictify 'age uint8 + 'alive uint8) + 0 (dictify 'name (+StringT uint8 'ascii)) + 1 (dictify 'name (+StringT uint8 'utf8) + 'gender uint8)))] + [stream (open-output-bytes)]) + + (encode struct (mhasheq 'name "roxyb" + 'age 21 + 'alive 1 + 'version 0) stream) + + (encode struct (mhasheq 'name "roxyb 🤘" + 'gender 0 + 'age 21 + 'alive 1 + 'version 1) stream) + + (check-equal? (dump stream) (string->bytes/utf-8 "\x00\x15\x01\x05roxyb\x01\x15\x01\x0aroxyb 🤘\x00"))) + + + +; it 'should encode pointer data after structure', (done) -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'ptr (+Pointer uint8 (+StringT uint8)))))] + [stream (open-output-bytes)]) + (encode struct (mhasheq 'version 1 + 'name "roxyb" + 'age 21 + 'ptr "hello") stream) + + (check-equal? (dump stream) (string->bytes/utf-8 "\x01\x05roxyb\x15\x09\x05hello"))) + + + + +; it 'should support preEncode hook', (done) -> + +(let ([struct (+VersionedStruct uint8 + (dictify + 0 (dictify 'name (+StringT uint8 'ascii) + 'age uint8) + 1 (dictify 'name (+StringT uint8 'utf8) + 'age uint8 + 'gender uint8)))] + [stream (open-output-bytes)]) + (set-field! pre-encode struct (λ (val port) (ref-set! val 'version (if (ref val 'gender) 1 0)) val)) + (encode struct (mhasheq 'name "roxyb" + 'age 21 + 'version 0) stream) + (encode struct (mhasheq 'name "roxyb 🤘" + 'age 21 + 'gender 0) stream) + (check-equal? (dump stream) (string->bytes/utf-8 "\x00\x05roxyb\x15\x01\x0aroxyb 🤘\x15\x00"))) diff --git a/xenomorph/xenomorph/utils.rkt b/xenomorph/xenomorph/undo/utils.rkt similarity index 100% rename from xenomorph/xenomorph/utils.rkt rename to xenomorph/xenomorph/undo/utils.rkt diff --git a/xenomorph/xenomorph/undo/versioned-struct.rkt b/xenomorph/xenomorph/undo/versioned-struct.rkt new file mode 100644 index 00000000..e36924d6 --- /dev/null +++ b/xenomorph/xenomorph/undo/versioned-struct.rkt @@ -0,0 +1,155 @@ +#lang racket/base +(require racket/class + racket/list + sugar/unstable/class + sugar/unstable/dict + sugar/unstable/js + racket/dict + "struct.rkt" + "private/generic.rkt" + "private/helper.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbuttrackerick/restructure/blob/master/src/VersionedStruct.coffee +|# + + +(define-subclass Struct (VersionedStruct type [versions (dictify)]) + + (unless (for/or ([proc (list integer? procedure? xenomorph-base%? symbol?)]) + (proc type)) + (raise-argument-error 'VersionedStruct "integer, function, symbol, or Restructure object" type)) + (unless (and (dict? versions) (andmap (λ (val) (or (dict? val) (Struct? val))) (map cdr versions))) + (raise-argument-error 'VersionedStruct "dict of dicts or Structs" versions)) + + (inherit _setup _parse-fields post-decode) + (inherit-field fields) + (field [forced-version #f] + [versionGetter void] + [versionSetter void]) + + (when (or (key? type) (procedure? type)) + (set-field! versionGetter this (if (procedure? type) + type + (λ (parent) (ref parent type)))) + (set-field! versionSetter this (if (procedure? type) + type + (λ (parent version) (ref-set! parent type version))))) + + (define/override (decode stream [parent #f] [length 0]) + (define res (_setup stream parent length)) + + (ref-set! res 'version + (cond + [forced-version] ; for testing purposes: pass an explicit version + [(or (key? type) (procedure? type)) + (unless parent + (raise-argument-error 'VersionedStruct:decode "valid parent" parent)) + (versionGetter parent)] + [else (send type decode stream)])) + + (when (ref versions 'header) + (_parse-fields stream res (ref versions 'header))) + + (define fields (or (ref versions (ref res 'version)) (raise-argument-error 'VersionedStruct:decode "valid version key" (cons version (· this versions))))) + + + (cond + [(VersionedStruct? fields) (send fields decode stream parent)] + [else + (_parse-fields stream res fields) + res])) + + (define/public-final (force-version! version) + (set! forced-version version)) + + (define/override (encode stream val [parent #f]) + (unless (hash? val) + (raise-argument-error 'VersionedStruct:encode "hash" val)) + + (define ctx (mhash 'pointers empty + 'startOffset (pos stream) + 'parent parent + 'val val + 'pointerSize 0)) + + (ref-set! ctx 'pointerOffset (+ (pos stream) (size val ctx #f))) + + (when (not (or (key? type) (procedure? type))) + (send type encode stream (or forced-version (· val version)))) + + (when (ref versions 'header) + (for ([(key type) (in-dict (ref versions 'header))]) + (send type encode stream (ref val key) ctx))) + + (define fields (or (ref versions (or forced-version (· val version))) (raise-argument-error 'VersionedStruct:encode "valid version key" version))) + + (unless (andmap (λ (key) (member key (ref-keys val))) (ref-keys fields)) + (raise-argument-error 'VersionedStruct:encode (format "hash that contains superset of Struct keys: ~a" (dict-keys fields)) (hash-keys val))) + + (for ([(key type) (in-dict fields)]) + (send type encode stream (ref val key) ctx)) + + (for ([ptr (in-list (ref ctx 'pointers))]) + (send (ref ptr 'type) encode stream (ref ptr 'val) (ref ptr 'parent)))) + + + (define/override (size [val #f] [parent #f] [includePointers #t]) + (unless (or val forced-version) + (raise-argument-error 'VersionedStruct:size "value" val)) + + (define ctx (mhash 'parent parent + 'val val + 'pointerSize 0)) + + (+ (if (not (or (key? type) (procedure? type))) + (send type size (or forced-version (ref val 'version)) ctx) + 0) + + (for/sum ([(key type) (in-dict (or (ref versions 'header) empty))]) + (send type size (and val (ref val key)) ctx)) + + (let ([fields (or (ref versions (or forced-version (ref val 'version))) + (raise-argument-error 'VersionedStruct:encode "valid version key" version))]) + (for/sum ([(key type) (in-dict fields)]) + (send type size (and val (ref val key)) ctx))) + + (if includePointers (ref ctx 'pointerSize) 0)))) + +#;(test-module + (require "number.rkt") + (define (random-pick xs) (list-ref xs (random (length xs)))) + (check-exn exn:fail:contract? (λ () (+VersionedStruct 42 42))) + + ;; make random versioned structs and make sure we can round trip + #;(for ([i (in-range 1)]) + (define field-types (for/list ([i (in-range 1)]) + (random-pick (list uint8 uint16be uint16le uint32be uint32le double)))) + (define num-versions 20) + (define which-struct (random num-versions)) + (define struct-versions (for/list ([v (in-range num-versions)]) + (cons v (for/list ([num-type (in-list field-types)]) + (cons (gensym) num-type))))) + (define vs (+VersionedStruct which-struct struct-versions)) + (define struct-size (for/sum ([num-type (in-list (map cdr (ref struct-versions which-struct)))]) + (send num-type size))) + (define bs (apply bytes (for/list ([i (in-range struct-size)]) + (random 256)))) + (check-equal? (send vs encode #f (send vs decode bs)) bs)) + + (define s (+Struct (dictify 'a uint8 'b uint8 'c uint8))) + (check-equal? (send s size) 3) + (define vs (+VersionedStruct uint8 (dictify 1 (dictify 'd s) 2 (dictify 'e s 'f s)))) + (send vs force-version! 1) + (check-equal? (send vs size) 6) + #| + (define s2 (+Struct (dictify 'a vs))) + (check-equal? (send s2 size) 6) + (define vs2 (+VersionedStruct (λ (p) 2) (dictify 1 vs 2 vs))) + (check-equal? (send vs2 size) 6) +|# + ) + + diff --git a/xenomorph/xenomorph/redo/util.rkt b/xenomorph/xenomorph/util.rkt similarity index 100% rename from xenomorph/xenomorph/redo/util.rkt rename to xenomorph/xenomorph/util.rkt diff --git a/xenomorph/xenomorph/versioned-struct.rkt b/xenomorph/xenomorph/versioned-struct.rkt index e36924d6..7ed56e59 100644 --- a/xenomorph/xenomorph/versioned-struct.rkt +++ b/xenomorph/xenomorph/versioned-struct.rkt @@ -1,155 +1,108 @@ #lang racket/base -(require racket/class - racket/list - sugar/unstable/class - sugar/unstable/dict - sugar/unstable/js +(require "helper.rkt" "struct.rkt" racket/dict - "struct.rkt" - "private/generic.rkt" - "private/helper.rkt") -(provide (all-defined-out)) + sugar/unstable/dict) +(provide (all-defined-out) decode/hash) #| approximates -https://github.com/mbuttrackerick/restructure/blob/master/src/VersionedStruct.coffee +https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee |# - -(define-subclass Struct (VersionedStruct type [versions (dictify)]) - - (unless (for/or ([proc (list integer? procedure? xenomorph-base%? symbol?)]) - (proc type)) - (raise-argument-error 'VersionedStruct "integer, function, symbol, or Restructure object" type)) - (unless (and (dict? versions) (andmap (λ (val) (or (dict? val) (Struct? val))) (map cdr versions))) - (raise-argument-error 'VersionedStruct "dict of dicts or Structs" versions)) - - (inherit _setup _parse-fields post-decode) - (inherit-field fields) - (field [forced-version #f] - [versionGetter void] - [versionSetter void]) - - (when (or (key? type) (procedure? type)) - (set-field! versionGetter this (if (procedure? type) - type - (λ (parent) (ref parent type)))) - (set-field! versionSetter this (if (procedure? type) - type - (λ (parent version) (ref-set! parent type version))))) - - (define/override (decode stream [parent #f] [length 0]) - (define res (_setup stream parent length)) - - (ref-set! res 'version - (cond - [forced-version] ; for testing purposes: pass an explicit version - [(or (key? type) (procedure? type)) - (unless parent - (raise-argument-error 'VersionedStruct:decode "valid parent" parent)) - (versionGetter parent)] - [else (send type decode stream)])) - - (when (ref versions 'header) - (_parse-fields stream res (ref versions 'header))) +(define/post-decode (xversioned-struct-decode xvs [port-arg (current-input-port)] #:parent [parent #f] [length 0]) + (define port (->input-port port-arg)) + (define res (_setup port parent length)) + + (dict-set! res 'version + (cond + [(integer? (xversioned-struct-type xvs)) (xversioned-struct-type xvs)] + #;[forced-version] ; for testing purposes: pass an explicit version + [(or (symbol? (xversioned-struct-type xvs)) (procedure? (xversioned-struct-type xvs))) + (unless parent + (raise-argument-error 'xversioned-struct-decode "valid parent" parent)) + ((xversioned-struct-version-getter xvs) parent)] + [else (decode (xversioned-struct-type xvs) port)])) + + (when (dict-ref (xversioned-struct-versions xvs) 'header #f) + (_parse-fields port res (dict-ref (xversioned-struct-versions xvs) 'header))) - (define fields (or (ref versions (ref res 'version)) (raise-argument-error 'VersionedStruct:decode "valid version key" (cons version (· this versions))))) - + (define fields (or (dict-ref (xversioned-struct-versions xvs) (dict-ref res 'version #f) #f) + (raise-argument-error 'xversioned-struct-decode "valid version key" (cons version (xversioned-struct-versions xvs))))) - (cond - [(VersionedStruct? fields) (send fields decode stream parent)] - [else - (_parse-fields stream res fields) - res])) - - (define/public-final (force-version! version) - (set! forced-version version)) - - (define/override (encode stream val [parent #f]) - (unless (hash? val) - (raise-argument-error 'VersionedStruct:encode "hash" val)) - - (define ctx (mhash 'pointers empty - 'startOffset (pos stream) - 'parent parent - 'val val - 'pointerSize 0)) - - (ref-set! ctx 'pointerOffset (+ (pos stream) (size val ctx #f))) - - (when (not (or (key? type) (procedure? type))) - (send type encode stream (or forced-version (· val version)))) - - (when (ref versions 'header) - (for ([(key type) (in-dict (ref versions 'header))]) - (send type encode stream (ref val key) ctx))) - - (define fields (or (ref versions (or forced-version (· val version))) (raise-argument-error 'VersionedStruct:encode "valid version key" version))) - - (unless (andmap (λ (key) (member key (ref-keys val))) (ref-keys fields)) - (raise-argument-error 'VersionedStruct:encode (format "hash that contains superset of Struct keys: ~a" (dict-keys fields)) (hash-keys val))) - - (for ([(key type) (in-dict fields)]) - (send type encode stream (ref val key) ctx)) - - (for ([ptr (in-list (ref ctx 'pointers))]) - (send (ref ptr 'type) encode stream (ref ptr 'val) (ref ptr 'parent)))) - + (cond + [(xversioned-struct? fields) (decode fields port #:parent parent)] + [else (_parse-fields port res fields) + res])) + +(define/finalize-size (xversioned-struct-size xvs [val #f] #:parent [parent-arg #f] [include-pointers #t]) + (unless val + (raise-argument-error 'xversioned-struct-size "value" val)) + (define parent (mhash 'parent parent-arg 'val val 'pointerSize 0)) + (define version-size + (if (not (or (symbol? (xversioned-struct-type xvs)) (procedure? (xversioned-struct-type xvs)))) + (size (xversioned-struct-type xvs) (dict-ref val 'version) #:parent parent) + 0)) + (define header-size + (for/sum ([(key type) (in-dict (or (dict-ref (xversioned-struct-versions xvs) 'header #f) null))]) + (size type (and val (dict-ref val key)) #:parent parent))) + (define fields-size + (let ([fields (or (dict-ref (xversioned-struct-versions xvs) (dict-ref val 'version)) + (raise-argument-error 'xversioned-struct-size "valid version key" version))]) + (for/sum ([(key type) (in-dict fields)]) + (size type (and val (dict-ref val key)) #:parent parent)))) + (define pointer-size (if include-pointers (dict-ref parent 'pointerSize) 0)) + (+ version-size header-size fields-size pointer-size)) + +(define/pre-encode (xversioned-struct-encode xvs val [port-arg (current-output-port)] #:parent [parent-arg #f]) + (define port (if (output-port? port-arg) port-arg (open-output-bytes))) + (parameterize ([current-output-port port]) + (unless (dict? val) + (raise-argument-error 'xversioned-struct-encode "dict" val)) + + (define parent (mhash 'pointers null + 'startOffset (pos port) + 'parent parent-arg + 'val val + 'pointerSize 0)) + (dict-set! parent 'pointerOffset (+ (pos port) (xversioned-struct-size xvs val #:parent parent #f))) + + (when (not (or (symbol? (xversioned-struct-type xvs)) (procedure? (xversioned-struct-type xvs)))) + (encode (xversioned-struct-type xvs) (dict-ref val 'version #f))) + + (when (dict-ref (xversioned-struct-versions xvs) 'header #f) + (for ([(key type) (in-dict (dict-ref (xversioned-struct-versions xvs) 'header))]) + (encode type (dict-ref val key) #:parent parent))) + + (define fields (or (dict-ref (xversioned-struct-versions xvs) (dict-ref val 'version #f)) + (raise-argument-error 'xversioned-struct-encode "valid version key" version))) + + (unless (andmap (λ (key) (member key (dict-keys val))) (dict-keys fields)) + (raise-argument-error 'xversioned-struct-encode (format "hash that contains superset of Struct keys: ~a" (dict-keys fields)) (hash-keys val))) + + (for ([(key type) (in-dict fields)]) + (encode type (dict-ref val key) #:parent parent)) + (for ([ptr (in-list (dict-ref parent 'pointers))]) + (encode (dict-ref ptr 'type) (dict-ref ptr 'val) #:parent (dict-ref ptr 'parent))) - (define/override (size [val #f] [parent #f] [includePointers #t]) - (unless (or val forced-version) - (raise-argument-error 'VersionedStruct:size "value" val)) - - (define ctx (mhash 'parent parent - 'val val - 'pointerSize 0)) + (unless port-arg (get-output-bytes port)))) - (+ (if (not (or (key? type) (procedure? type))) - (send type size (or forced-version (ref val 'version)) ctx) - 0) - - (for/sum ([(key type) (in-dict (or (ref versions 'header) empty))]) - (send type size (and val (ref val key)) ctx)) - - (let ([fields (or (ref versions (or forced-version (ref val 'version))) - (raise-argument-error 'VersionedStruct:encode "valid version key" version))]) - (for/sum ([(key type) (in-dict fields)]) - (send type size (and val (ref val key)) ctx))) - - (if includePointers (ref ctx 'pointerSize) 0)))) - -#;(test-module - (require "number.rkt") - (define (random-pick xs) (list-ref xs (random (length xs)))) - (check-exn exn:fail:contract? (λ () (+VersionedStruct 42 42))) - - ;; make random versioned structs and make sure we can round trip - #;(for ([i (in-range 1)]) - (define field-types (for/list ([i (in-range 1)]) - (random-pick (list uint8 uint16be uint16le uint32be uint32le double)))) - (define num-versions 20) - (define which-struct (random num-versions)) - (define struct-versions (for/list ([v (in-range num-versions)]) - (cons v (for/list ([num-type (in-list field-types)]) - (cons (gensym) num-type))))) - (define vs (+VersionedStruct which-struct struct-versions)) - (define struct-size (for/sum ([num-type (in-list (map cdr (ref struct-versions which-struct)))]) - (send num-type size))) - (define bs (apply bytes (for/list ([i (in-range struct-size)]) - (random 256)))) - (check-equal? (send vs encode #f (send vs decode bs)) bs)) - - (define s (+Struct (dictify 'a uint8 'b uint8 'c uint8))) - (check-equal? (send s size) 3) - (define vs (+VersionedStruct uint8 (dictify 1 (dictify 'd s) 2 (dictify 'e s 'f s)))) - (send vs force-version! 1) - (check-equal? (send vs size) 6) - #| - (define s2 (+Struct (dictify 'a vs))) - (check-equal? (send s2 size) 6) - (define vs2 (+VersionedStruct (λ (p) 2) (dictify 1 vs 2 vs))) - (check-equal? (send vs2 size) 6) -|# - ) +(struct xversioned-struct structish (type versions version-getter version-setter) #:transparent #:mutable + #:methods gen:xenomorphic + [(define decode xversioned-struct-decode) + (define encode xversioned-struct-encode) + (define size xversioned-struct-size)]) +(define (+xversioned-struct type [versions (dictify)]) + (unless (for/or ([proc (list integer? procedure? xenomorphic? symbol?)]) + (proc type)) + (raise-argument-error '+xversioned-struct "integer, procedure, symbol, or xenomorphic" type)) + (unless (and (dict? versions) (andmap (λ (v) (or (dict? v) (structish? v))) (dict-values versions))) + (raise-argument-error '+xversioned-struct "dict of dicts or structish" versions)) + (define version-getter (cond + [(procedure? type) type] + [(symbol? type) (λ (parent) (dict-ref parent type))])) + (define version-setter (cond + [(procedure? type) type] + [(symbol? type) (λ (parent version) (dict-set! parent type version))])) + (xversioned-struct type versions version-getter version-setter))