diff --git a/pitfall/restructure/array.rkt b/pitfall/restructure/array.rkt index f7ab9669..5c79033e 100644 --- a/pitfall/restructure/array.rkt +++ b/pitfall/restructure/array.rkt @@ -1,79 +1,3 @@ -#lang restructure/racket -(require "number.rkt" "utils.rkt" "stream.rkt") -(provide (all-defined-out)) +#lang reader (submod "private/racket.rkt" reader) -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Array.coffee -|# - -(define-subclass Streamcoder (ArrayT type [len #f] [length-type 'count]) - - (define/augride (decode stream [parent #f]) - (define ctx (if (NumberT? len) - (mhasheq 'parent parent - '_startOffset (· stream pos) - '_currentOffset 0 - '_length len) - parent)) - - (define decoded-len (resolve-length len stream parent)) - (cond - [(or (not decoded-len) (eq? length-type 'bytes)) - (define end-pos (cond - ;; decoded-len is byte length - [decoded-len (+ (· stream pos) 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 (· stream length_)])) - (for/list ([i (in-naturals)] - #:break (= (· stream pos) end-pos)) - (send type decode stream ctx))] - ;; we have decoded-len, which is treated as count of items - [else (for/list ([i (in-range decoded-len)]) - (send type decode stream ctx))])) - - - (define/override (size [val #f] [ctx #f]) - (when val (unless (countable? val) - (raise-argument-error 'Array:size "list or 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 stream array [parent #f]) - (when array (unless (countable? array) - (raise-argument-error 'Array:encode "list or countable" array))) - - (define (encode-items ctx) - (for ([item (in-list (countable->list array))]) - (send type encode stream item ctx))) - - (cond - [(NumberT? len) (define ctx (mhash 'pointers null - 'startOffset (· stream pos) - 'parent parent)) - (ref-set! ctx 'pointerOffset (+ (· stream pos) (size array ctx))) - (send len encode stream (length array)) ; encode length at front - (encode-items ctx) - (for ([ptr (in-list (· ctx pointers))]) ; encode pointer data at end - (send (· ptr type) encode stream (· ptr val)))] - [else (encode-items parent)]))) - -(define-values (Array Array? +Array) (values ArrayT ArrayT? +ArrayT)) - -(test-module - (define stream (+DecodeStream #"ABCDEFG")) - (define A (+Array uint16be 3)) - (check-equal? (send A decode stream) '(16706 17220 17734)) - (check-equal? (send A encode #f '(16706 17220 17734)) #"ABCDEF") - (check-equal? (send (+Array uint16be) size '(1 2 3)) 6) - (check-equal? (send (+Array doublebe) size '(1 2 3 4 5)) 40)) +(r+p "private/array.rkt") \ No newline at end of file diff --git a/pitfall/restructure/bitfield.rkt b/pitfall/restructure/bitfield.rkt index 9ade64a9..53b7ccc2 100644 --- a/pitfall/restructure/bitfield.rkt +++ b/pitfall/restructure/bitfield.rkt @@ -1,45 +1,3 @@ -#lang restructure/racket -(require "stream.rkt") -(provide (all-defined-out)) +#lang reader (submod "private/racket.rkt" reader) -#| -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/override (size . _) (send type size)) - - (define/augment (encode stream flag-hash [ctx #f]) - (define bitfield-integer (for/sum ([(flag i) (in-indexed flags)] - #:when (and flag (ref flag-hash flag))) - (arithmetic-shift 1 i))) - (send type encode stream bitfield-integer))) - - -(test-module - (require "number.rkt" "stream.rkt") - (define bfer (+Bitfield uint16be '(bold italic underline #f shadow condensed extended))) - (define bf (send bfer decode (+DecodeStream #"\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)) - - (define os (+EncodeStream)) - (send bfer encode os bf) - (check-equal? (send os dump) #"\0\25")) \ No newline at end of file +(r+p "private/bitfield.rkt") \ No newline at end of file diff --git a/pitfall/restructure/buffer.rkt b/pitfall/restructure/buffer.rkt index e79093ad..c3358fed 100644 --- a/pitfall/restructure/buffer.rkt +++ b/pitfall/restructure/buffer.rkt @@ -1,53 +1,3 @@ -#lang restructure/racket -(require "number.rkt" "utils.rkt") -(provide (all-defined-out)) +#lang reader (submod "private/racket.rkt" reader) -#| -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 RestructureBase (RBuffer [len #xffff]) - - (define/override (decode stream [parent #f]) - (define decoded-len (resolve-length len stream parent)) - (send stream readBuffer decoded-len)) - - (define/override (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/override (encode stream buf [parent #f]) - (unless (bytes? buf) - (raise-argument-error 'Buffer:encode "bytes" buf)) - (when (NumberT? len) - (send len encode stream (length buf))) - (send stream writeBuffer buf))) - -(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 +(r+p "private/buffer.rkt") \ No newline at end of file diff --git a/pitfall/restructure/enum.rkt b/pitfall/restructure/enum.rkt index 48ff1487..351b3c4e 100644 --- a/pitfall/restructure/enum.rkt +++ b/pitfall/restructure/enum.rkt @@ -1,23 +1,3 @@ -#lang restructure/racket -(require "stream.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Enum.coffee -|# - -(define-subclass Streamcoder (Enum type [options empty]) - - (define/augment (decode stream . _) - (define index (send type decode stream)) - (or (list-ref options index) index)) - - (define/override (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))) +#lang reader (submod "private/racket.rkt" reader) +(r+p "private/enum.rkt") \ No newline at end of file diff --git a/pitfall/restructure/files.rkt b/pitfall/restructure/files.rkt new file mode 100644 index 00000000..168c65fc --- /dev/null +++ b/pitfall/restructure/files.rkt @@ -0,0 +1 @@ +#lang br diff --git a/pitfall/restructure/lazy-array.rkt b/pitfall/restructure/lazy-array.rkt index 0b7d93e7..3650b716 100644 --- a/pitfall/restructure/lazy-array.rkt +++ b/pitfall/restructure/lazy-array.rkt @@ -1,74 +1,3 @@ -#lang restructure/racket -(require "utils.rkt" "array.rkt" "number.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/LazyArray.coffee -|# - -(define-subclass object% (InnerLazyArray type [len #f] [stream #f] [ctx #f]) - (unless stream (raise-argument-error 'LazyArray "stream" stream)) - (define starting-pos (· stream pos)) - (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" len) index) - (void)) - (ref! item-cache index (λ () - (define orig-pos (· stream pos)) - (send stream pos (+ starting-pos (* (send type size #f ctx) index))) - (define new-item (send type decode stream ctx)) - (send stream pos 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 stream [parent #f]) - (define pos (· stream pos)) ; ! placement matters. `resolve-length` will change `pos` - (define decoded-len (resolve-length len stream parent)) - (let ([parent (if (NumberT? len) - (mhasheq 'parent parent - '_startOffset pos - '_currentOffset 0 - '_length len) - parent)]) - (define res (+InnerLazyArray type decoded-len stream parent)) - (send stream pos (+ (· stream pos) (* 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 stream val [ctx #f]) - (super encode stream (if (InnerLazyArray? val) - (send val to-list) - val) ctx))) - -(test-module - (require "stream.rkt") - (define bstr #"ABCD1234") - (define ds (+DecodeStream bstr)) - (define la (+LazyArray uint8 4)) - (define ila (send la decode ds)) - (check-equal? (send ds pos) 4) - (check-equal? (send ila get 1) 66) - (check-equal? (send ila get 3) 68) - (check-equal? (send ds pos) 4) - (check-equal? (send ila to-list) '(65 66 67 68)) - - (define la2 (+LazyArray int16be (λ (t) 4))) - (define es (+EncodeStream)) - (send la2 encode es '(1 2 3 4)) - (check-equal? (send es dump) #"\0\1\0\2\0\3\0\4") - (check-equal? (send (send la2 decode (+DecodeStream #"\0\1\0\2\0\3\0\4")) to-list) '(1 2 3 4))) +#lang reader (submod "private/racket.rkt" reader) +(r+p "private/lazy-array.rkt") \ No newline at end of file diff --git a/pitfall/restructure/main.rkt b/pitfall/restructure/main.rkt index 0cb6a8b9..a7fadebf 100644 --- a/pitfall/restructure/main.rkt +++ b/pitfall/restructure/main.rkt @@ -1,7 +1,6 @@ -#lang restructure/racket +#lang reader (submod "private/racket.rkt" reader) -(r+p "base.rkt" - "array.rkt" +(r+p "array.rkt" "bitfield.rkt" "buffer.rkt" "enum.rkt" diff --git a/pitfall/restructure/number.rkt b/pitfall/restructure/number.rkt index fc3c0258..8053d6c4 100644 --- a/pitfall/restructure/number.rkt +++ b/pitfall/restructure/number.rkt @@ -1,188 +1,3 @@ -#lang restructure/racket -(require "stream.rkt" "sizes.rkt" (for-syntax "sizes.rkt" racket/match)) -(provide (all-defined-out)) +#lang reader (submod "private/racket.rkt" reader) -#| -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 Streamcoder (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/override (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 stream . args) - (define bstr (send stream readBuffer _size)) - (define bs ((if (eq? endian system-endian) identity reverse) (bytes->list bstr))) - (define unsigned-int (for/sum ([(b i) (in-indexed bs)]) - (arithmetic-shift b (* 8 i)))) - (post-decode unsigned-int)) - - (define/public (post-decode unsigned-int) - (if _signed? (unsigned->signed unsigned-int bits) unsigned-int)) - - (define/public (pre-encode val-in) - (exact-if-possible val-in)) - - (define/augment (encode stream val-in [parent #f]) - (define val (pre-encode val-in)) - (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 empty] [n val]) - ([i (in-range _size)]) - (values (cons (bitwise-and n #xff) bs) (arithmetic-shift n -8)))) - (define bstr (apply bytes ((if (eq? endian 'be) identity reverse) bs))) - (send stream write bstr))) - -(define-values (NumberT NumberT? +NumberT) (values Integer Integer? +Integer)) -(define-values (Number Number? +Number) (values Integer Integer? +Integer)) - -(define-subclass Streamcoder (Float _size [endian system-endian]) - (define byte-size (/ _size 8)) - - (define/augment (decode stream . args) ; convert int to float - (define bs (send stream readBuffer byte-size)) - (floating-point-bytes->real bs (eq? endian 'be))) - - (define/augment (encode stream val-in [parent #f]) ; convert float to int - (define bs (real->floating-point-bytes val-in byte-size (eq? endian 'be))) - (send stream write bs)) - - (define/override (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? (λ () (send uint8 encode (+EncodeStream) 256))) - (check-not-exn (λ () (send uint8 encode (+EncodeStream) 255))) - (check-exn exn:fail:contract? (λ () (send int8 encode (+EncodeStream) 256))) - (check-exn exn:fail:contract? (λ () (send int8 encode (+EncodeStream) 255))) - (check-not-exn (λ () (send int8 encode (+EncodeStream) 127))) - (check-not-exn (λ () (send int8 encode (+EncodeStream) -128))) - (check-exn exn:fail:contract? (λ () (send int8 encode (+EncodeStream) -129))) - (check-exn exn:fail:contract? (λ () (send uint16 encode (+EncodeStream) (add1 #xffff)))) - (check-not-exn (λ () (send uint16 encode (+EncodeStream) #xffff))) - - (let ([o (+Integer 'uint16 'le)] - [ip (+DecodeStream (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 - (send o encode op 513) - (check-equal? (get-output-bytes op) (bytes 1 2)) - (send o encode op 1027) - (check-equal? (get-output-bytes op) (bytes 1 2 3 4))) - - (let ([o (+Integer 'uint16 'be)] - [ip (+DecodeStream (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 - (send o encode op 258) - (check-equal? (get-output-bytes op) (bytes 1 2)) - (send o encode op 772) - (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-macro (make-int-types) - (with-pattern ([((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 ...) (suffix-id #'(ID ...) #:context caller-stx)]) - #'(begin (define-instance ID (make-object Integer 'BASE 'ENDIAN)) ...))) - -(make-int-types) - -(test-module - (check-equal? (send uint8 size) 1) - (check-equal? (send uint16 size) 2) - (check-equal? (send uint32 size) 4) - (check-equal? (send double size) 8) - - (define bs (send fixed16be encode #f 123.45)) - (check-equal? bs #"{s") - (check-equal? (ceiling (* (send fixed16be decode bs) 100)) 12345.0) - - (check-equal? (send int8 decode (bytes 127)) 127) - (check-equal? (send int8 decode (bytes 255)) -1) - - (check-equal? (send int8 encode #f -1) (bytes 255)) - (check-equal? (send int8 encode #f 127) (bytes 127))) +(r+p "private/number.rkt") \ No newline at end of file diff --git a/pitfall/restructure/optional.rkt b/pitfall/restructure/optional.rkt index c76d7867..66b76cc1 100644 --- a/pitfall/restructure/optional.rkt +++ b/pitfall/restructure/optional.rkt @@ -1,29 +1,3 @@ -#lang restructure/racket -(require "stream.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Optional.coffee -|# - -(define-subclass Streamcoder (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/override (size [val #f] [parent #f]) - (if (resolve-condition parent) - (send type size val parent) - 0)) - - (define/augment (encode stream val parent) - (when (resolve-condition parent) - (send type encode stream val parent)))) +#lang reader (submod "private/racket.rkt" reader) +(r+p "private/optional.rkt") \ No newline at end of file diff --git a/pitfall/restructure/pointer.rkt b/pitfall/restructure/pointer.rkt index b65e90fe..f4dae86a 100644 --- a/pitfall/restructure/pointer.rkt +++ b/pitfall/restructure/pointer.rkt @@ -1,98 +1,3 @@ -#lang restructure/racket -(require racket/undefined) -(provide (all-defined-out)) +#lang reader (submod "private/racket.rkt" reader) -#| -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 object% (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/public (decode stream [ctx #f]) - (define offset (send offset-type decode stream ctx)) - (cond - [(and allow-null (= offset null-value)) #f] ; handle null pointers - [else - (define relative (+ (caseq pointer-style - [(local) (· ctx _startOffset)] - [(immediate) (- (· stream pos) (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 (· stream pos)) - (send stream pos ptr) - (set! val (send type decode stream ctx)) - (send stream pos orig-pos) - val])) - (if lazy - (LazyThunk decode-value) - (decode-value))] - [else ptr])])) - - - (define/public (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/public (encode stream val [ctx #f]) - (if (not val) - (send offset-type encode stream 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) (+ (· stream pos) (send offset-type size val parent))] - [(global) 0]) - (relative-getter-or-0 (· parent val)))]) - - (send offset-type encode stream (- (· 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)) +(r+p "private/pointer.rkt") \ No newline at end of file diff --git a/pitfall/restructure/private/array.rkt b/pitfall/restructure/private/array.rkt new file mode 100644 index 00000000..2c0d4788 --- /dev/null +++ b/pitfall/restructure/private/array.rkt @@ -0,0 +1,79 @@ +#lang reader (submod "racket.rkt" reader) +(require "number.rkt" "utils.rkt" "stream.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Array.coffee +|# + +(define-subclass Streamcoder (ArrayT type [len #f] [length-type 'count]) + + (define/augride (decode stream [parent #f]) + (define ctx (if (NumberT? len) + (mhasheq 'parent parent + '_startOffset (· stream pos) + '_currentOffset 0 + '_length len) + parent)) + + (define decoded-len (resolve-length len stream parent)) + (cond + [(or (not decoded-len) (eq? length-type 'bytes)) + (define end-pos (cond + ;; decoded-len is byte length + [decoded-len (+ (· stream pos) 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 (· stream length_)])) + (for/list ([i (in-naturals)] + #:break (= (· stream pos) end-pos)) + (send type decode stream ctx))] + ;; we have decoded-len, which is treated as count of items + [else (for/list ([i (in-range decoded-len)]) + (send type decode stream ctx))])) + + + (define/override (size [val #f] [ctx #f]) + (when val (unless (countable? val) + (raise-argument-error 'Array:size "list or 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 stream array [parent #f]) + (when array (unless (countable? array) + (raise-argument-error 'Array:encode "list or countable" array))) + + (define (encode-items ctx) + (for ([item (in-list (countable->list array))]) + (send type encode stream item ctx))) + + (cond + [(NumberT? len) (define ctx (mhash 'pointers null + 'startOffset (· stream pos) + 'parent parent)) + (ref-set! ctx 'pointerOffset (+ (· stream pos) (size array ctx))) + (send len encode stream (length array)) ; encode length at front + (encode-items ctx) + (for ([ptr (in-list (· ctx pointers))]) ; encode pointer data at end + (send (· ptr type) encode stream (· ptr val)))] + [else (encode-items parent)]))) + +(define-values (Array Array? +Array) (values ArrayT ArrayT? +ArrayT)) + +(test-module + (define stream (+DecodeStream #"ABCDEFG")) + (define A (+Array uint16be 3)) + (check-equal? (send A decode stream) '(16706 17220 17734)) + (check-equal? (send A encode #f '(16706 17220 17734)) #"ABCDEF") + (check-equal? (send (+Array uint16be) size '(1 2 3)) 6) + (check-equal? (send (+Array doublebe) size '(1 2 3 4 5)) 40)) diff --git a/pitfall/restructure/base.rkt b/pitfall/restructure/private/base.rkt similarity index 100% rename from pitfall/restructure/base.rkt rename to pitfall/restructure/private/base.rkt diff --git a/pitfall/restructure/private/bitfield.rkt b/pitfall/restructure/private/bitfield.rkt new file mode 100644 index 00000000..aa0dd35e --- /dev/null +++ b/pitfall/restructure/private/bitfield.rkt @@ -0,0 +1,45 @@ +#lang reader (submod "racket.rkt" reader) +(require "stream.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/override (size . _) (send type size)) + + (define/augment (encode stream flag-hash [ctx #f]) + (define bitfield-integer (for/sum ([(flag i) (in-indexed flags)] + #:when (and flag (ref flag-hash flag))) + (arithmetic-shift 1 i))) + (send type encode stream bitfield-integer))) + + +(test-module + (require "number.rkt" "stream.rkt") + (define bfer (+Bitfield uint16be '(bold italic underline #f shadow condensed extended))) + (define bf (send bfer decode (+DecodeStream #"\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)) + + (define os (+EncodeStream)) + (send bfer encode os bf) + (check-equal? (send os dump) #"\0\25")) \ No newline at end of file diff --git a/pitfall/restructure/private/buffer.rkt b/pitfall/restructure/private/buffer.rkt new file mode 100644 index 00000000..05cb619f --- /dev/null +++ b/pitfall/restructure/private/buffer.rkt @@ -0,0 +1,53 @@ +#lang reader (submod "racket.rkt" reader) +(require "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 RestructureBase (RBuffer [len #xffff]) + + (define/override (decode stream [parent #f]) + (define decoded-len (resolve-length len stream parent)) + (send stream readBuffer decoded-len)) + + (define/override (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/override (encode stream buf [parent #f]) + (unless (bytes? buf) + (raise-argument-error 'Buffer:encode "bytes" buf)) + (when (NumberT? len) + (send len encode stream (length buf))) + (send stream writeBuffer buf))) + +(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/pitfall/restructure/private/enum.rkt b/pitfall/restructure/private/enum.rkt new file mode 100644 index 00000000..ceebeace --- /dev/null +++ b/pitfall/restructure/private/enum.rkt @@ -0,0 +1,23 @@ +#lang reader (submod "racket.rkt" reader) +(require "stream.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Enum.coffee +|# + +(define-subclass Streamcoder (Enum type [options empty]) + + (define/augment (decode stream . _) + (define index (send type decode stream)) + (or (list-ref options index) index)) + + (define/override (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/pitfall/restructure/generic.rkt b/pitfall/restructure/private/generic.rkt similarity index 100% rename from pitfall/restructure/generic.rkt rename to pitfall/restructure/private/generic.rkt diff --git a/pitfall/restructure/helper.rkt b/pitfall/restructure/private/helper.rkt similarity index 100% rename from pitfall/restructure/helper.rkt rename to pitfall/restructure/private/helper.rkt diff --git a/pitfall/restructure/private/lazy-array.rkt b/pitfall/restructure/private/lazy-array.rkt new file mode 100644 index 00000000..66157089 --- /dev/null +++ b/pitfall/restructure/private/lazy-array.rkt @@ -0,0 +1,74 @@ +#lang reader (submod "racket.rkt" reader) +(require "utils.rkt" "array.rkt" "number.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/LazyArray.coffee +|# + +(define-subclass object% (InnerLazyArray type [len #f] [stream #f] [ctx #f]) + (unless stream (raise-argument-error 'LazyArray "stream" stream)) + (define starting-pos (· stream pos)) + (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" len) index) + (void)) + (ref! item-cache index (λ () + (define orig-pos (· stream pos)) + (send stream pos (+ starting-pos (* (send type size #f ctx) index))) + (define new-item (send type decode stream ctx)) + (send stream pos 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 stream [parent #f]) + (define pos (· stream pos)) ; ! placement matters. `resolve-length` will change `pos` + (define decoded-len (resolve-length len stream parent)) + (let ([parent (if (NumberT? len) + (mhasheq 'parent parent + '_startOffset pos + '_currentOffset 0 + '_length len) + parent)]) + (define res (+InnerLazyArray type decoded-len stream parent)) + (send stream pos (+ (· stream pos) (* 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 stream val [ctx #f]) + (super encode stream (if (InnerLazyArray? val) + (send val to-list) + val) ctx))) + +(test-module + (require "stream.rkt") + (define bstr #"ABCD1234") + (define ds (+DecodeStream bstr)) + (define la (+LazyArray uint8 4)) + (define ila (send la decode ds)) + (check-equal? (send ds pos) 4) + (check-equal? (send ila get 1) 66) + (check-equal? (send ila get 3) 68) + (check-equal? (send ds pos) 4) + (check-equal? (send ila to-list) '(65 66 67 68)) + + (define la2 (+LazyArray int16be (λ (t) 4))) + (define es (+EncodeStream)) + (send la2 encode es '(1 2 3 4)) + (check-equal? (send es dump) #"\0\1\0\2\0\3\0\4") + (check-equal? (send (send la2 decode (+DecodeStream #"\0\1\0\2\0\3\0\4")) to-list) '(1 2 3 4))) + diff --git a/pitfall/restructure/private/number.rkt b/pitfall/restructure/private/number.rkt new file mode 100644 index 00000000..7066c92d --- /dev/null +++ b/pitfall/restructure/private/number.rkt @@ -0,0 +1,188 @@ +#lang reader (submod "racket.rkt" reader) +(require "stream.rkt" "sizes.rkt" (for-syntax "sizes.rkt" racket/match)) +(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 Streamcoder (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/override (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 stream . args) + (define bstr (send stream readBuffer _size)) + (define bs ((if (eq? endian system-endian) identity reverse) (bytes->list bstr))) + (define unsigned-int (for/sum ([(b i) (in-indexed bs)]) + (arithmetic-shift b (* 8 i)))) + (post-decode unsigned-int)) + + (define/public (post-decode unsigned-int) + (if _signed? (unsigned->signed unsigned-int bits) unsigned-int)) + + (define/public (pre-encode val-in) + (exact-if-possible val-in)) + + (define/augment (encode stream val-in [parent #f]) + (define val (pre-encode val-in)) + (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 empty] [n val]) + ([i (in-range _size)]) + (values (cons (bitwise-and n #xff) bs) (arithmetic-shift n -8)))) + (define bstr (apply bytes ((if (eq? endian 'be) identity reverse) bs))) + (send stream write bstr))) + +(define-values (NumberT NumberT? +NumberT) (values Integer Integer? +Integer)) +(define-values (Number Number? +Number) (values Integer Integer? +Integer)) + +(define-subclass Streamcoder (Float _size [endian system-endian]) + (define byte-size (/ _size 8)) + + (define/augment (decode stream . args) ; convert int to float + (define bs (send stream readBuffer byte-size)) + (floating-point-bytes->real bs (eq? endian 'be))) + + (define/augment (encode stream val-in [parent #f]) ; convert float to int + (define bs (real->floating-point-bytes val-in byte-size (eq? endian 'be))) + (send stream write bs)) + + (define/override (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? (λ () (send uint8 encode (+EncodeStream) 256))) + (check-not-exn (λ () (send uint8 encode (+EncodeStream) 255))) + (check-exn exn:fail:contract? (λ () (send int8 encode (+EncodeStream) 256))) + (check-exn exn:fail:contract? (λ () (send int8 encode (+EncodeStream) 255))) + (check-not-exn (λ () (send int8 encode (+EncodeStream) 127))) + (check-not-exn (λ () (send int8 encode (+EncodeStream) -128))) + (check-exn exn:fail:contract? (λ () (send int8 encode (+EncodeStream) -129))) + (check-exn exn:fail:contract? (λ () (send uint16 encode (+EncodeStream) (add1 #xffff)))) + (check-not-exn (λ () (send uint16 encode (+EncodeStream) #xffff))) + + (let ([o (+Integer 'uint16 'le)] + [ip (+DecodeStream (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 + (send o encode op 513) + (check-equal? (get-output-bytes op) (bytes 1 2)) + (send o encode op 1027) + (check-equal? (get-output-bytes op) (bytes 1 2 3 4))) + + (let ([o (+Integer 'uint16 'be)] + [ip (+DecodeStream (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 + (send o encode op 258) + (check-equal? (get-output-bytes op) (bytes 1 2)) + (send o encode op 772) + (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-macro (make-int-types) + (with-pattern ([((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 ...) (suffix-id #'(ID ...) #:context caller-stx)]) + #'(begin (define-instance ID (make-object Integer 'BASE 'ENDIAN)) ...))) + +(make-int-types) + +(test-module + (check-equal? (send uint8 size) 1) + (check-equal? (send uint16 size) 2) + (check-equal? (send uint32 size) 4) + (check-equal? (send double size) 8) + + (define bs (send fixed16be encode #f 123.45)) + (check-equal? bs #"{s") + (check-equal? (ceiling (* (send fixed16be decode bs) 100)) 12345.0) + + (check-equal? (send int8 decode (bytes 127)) 127) + (check-equal? (send int8 decode (bytes 255)) -1) + + (check-equal? (send int8 encode #f -1) (bytes 255)) + (check-equal? (send int8 encode #f 127) (bytes 127))) diff --git a/pitfall/restructure/private/optional.rkt b/pitfall/restructure/private/optional.rkt new file mode 100644 index 00000000..a1b59e6f --- /dev/null +++ b/pitfall/restructure/private/optional.rkt @@ -0,0 +1,29 @@ +#lang reader (submod "racket.rkt" reader) +(require "stream.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Optional.coffee +|# + +(define-subclass Streamcoder (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/override (size [val #f] [parent #f]) + (if (resolve-condition parent) + (send type size val parent) + 0)) + + (define/augment (encode stream val parent) + (when (resolve-condition parent) + (send type encode stream val parent)))) + diff --git a/pitfall/restructure/private/pointer.rkt b/pitfall/restructure/private/pointer.rkt new file mode 100644 index 00000000..703ecac2 --- /dev/null +++ b/pitfall/restructure/private/pointer.rkt @@ -0,0 +1,98 @@ +#lang reader (submod "racket.rkt" reader) +(require racket/undefined) +(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 object% (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/public (decode stream [ctx #f]) + (define offset (send offset-type decode stream ctx)) + (cond + [(and allow-null (= offset null-value)) #f] ; handle null pointers + [else + (define relative (+ (caseq pointer-style + [(local) (· ctx _startOffset)] + [(immediate) (- (· stream pos) (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 (· stream pos)) + (send stream pos ptr) + (set! val (send type decode stream ctx)) + (send stream pos orig-pos) + val])) + (if lazy + (LazyThunk decode-value) + (decode-value))] + [else ptr])])) + + + (define/public (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/public (encode stream val [ctx #f]) + (if (not val) + (send offset-type encode stream 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) (+ (· stream pos) (send offset-type size val parent))] + [(global) 0]) + (relative-getter-or-0 (· parent val)))]) + + (send offset-type encode stream (- (· 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/pitfall/restructure/racket.rkt b/pitfall/restructure/private/racket.rkt similarity index 82% rename from pitfall/restructure/racket.rkt rename to pitfall/restructure/private/racket.rkt index 2afb8c7a..e8a2ee81 100644 --- a/pitfall/restructure/racket.rkt +++ b/pitfall/restructure/private/racket.rkt @@ -24,7 +24,4 @@ (module reader syntax/module-reader - #:language 'restructure/racket - #:read @-read - #:read-syntax @-read-syntax - (require (prefix-in @- scribble/reader))) \ No newline at end of file + #:language 'restructure/private/racket) \ No newline at end of file diff --git a/pitfall/restructure/private/reserved.rkt b/pitfall/restructure/private/reserved.rkt new file mode 100644 index 00000000..4efae96c --- /dev/null +++ b/pitfall/restructure/private/reserved.rkt @@ -0,0 +1,21 @@ +#lang reader (submod "racket.rkt" reader) +(require "stream.rkt" "utils.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/Reserved.coffee +|# + +(define-subclass Streamcoder (Reserved type [count 1]) + + (define/augment (decode stream parent) + (send stream pos (+ (· stream pos) (size #f parent))) + (void)) + + (define/override (size [val #f] [parent #f]) + (* (send type size) (resolve-length count #f parent))) + + (define/augment (encode stream val [parent #f]) + (send stream fill 0 (size val parent)))) + diff --git a/pitfall/restructure/sizes.rkt b/pitfall/restructure/private/sizes.rkt similarity index 97% rename from pitfall/restructure/sizes.rkt rename to pitfall/restructure/private/sizes.rkt index 01963bda..82a72012 100644 --- a/pitfall/restructure/sizes.rkt +++ b/pitfall/restructure/private/sizes.rkt @@ -1,4 +1,4 @@ -#lang restructure/racket +#lang reader (submod "racket.rkt" reader) (provide type-sizes get-type-size) (define-values (int-keys byte-values) (for*/lists (int-keys byte-values) diff --git a/pitfall/restructure/private/stream.rkt b/pitfall/restructure/private/stream.rkt new file mode 100644 index 00000000..d653a216 --- /dev/null +++ b/pitfall/restructure/private/stream.rkt @@ -0,0 +1,215 @@ +#lang reader (submod "racket.rkt" reader) +(require racket/private/generic-methods) +(provide (all-defined-out)) + +;; helper class +(define-subclass object% (PortWrapper _port) + (unless (port? _port) + (raise-argument-error 'PortWrapper:constructor "port" _port)) + (define/public (pos [where #f]) + (when where (file-position _port where)) + (file-position _port)) + (define/public (dump) (void))) + +(test-module + (check-not-exn (λ () (make-object PortWrapper (open-input-bytes #"Foo")))) + (check-not-exn (λ () (make-object PortWrapper (open-output-bytes)))) + (check-exn exn:fail? (λ () (make-object PortWrapper -42)))) + +#| approximates +https://github.com/mbutterick/restructure/blob/master/src/EncodeStream.coffee +|# + +;; basically just a wrapper for a Racket output port +(define-subclass* PortWrapper (EncodeStream [maybe-output-port (open-output-bytes)]) + + (unless (output-port? maybe-output-port) + (raise-argument-error 'EncodeStream:constructor "output port" maybe-output-port)) + + (super-make-object maybe-output-port) + (inherit-field _port) + + (define/override-final (dump) (get-output-bytes _port)) + + (define/public-final (write val) + (unless (bytes? val) + (raise-argument-error 'EncodeStream:write "bytes" val)) + (write-bytes val _port) + (void)) + + (define/public-final (writeBuffer buffer) + (write buffer)) + + (define/public-final (writeUInt8 int) + (write (bytes int))) + + (define/public (writeString string [encoding 'ascii]) + ;; todo: handle encodings correctly. + ;; right now just utf8 and ascii are correct + (caseq encoding + [(utf16le ucs2 utf8 ascii) (writeBuffer (string->bytes/utf-8 string)) + (when (eq? encoding 'utf16le) + (error 'swap-bytes-unimplemented))] + [else (error 'unsupported-string-encoding)])) + + (define/public (fill val len) + (write (make-bytes len val)))) + +(test-module + (define es (+EncodeStream)) + (check-true (EncodeStream? es)) + (send es write #"AB") + (check-equal? (· es pos) 2) + (send es write #"C") + (check-equal? (· es pos) 3) + (send es write #"D") + (check-equal? (· es pos) 4) + (check-exn exn:fail? (λ () (send es write -42))) + (check-exn exn:fail? (λ () (send es write 1))) + (define op (open-output-bytes)) + (define es2 (+EncodeStream op)) + (send es2 write #"FOOBAR") + (check-equal? (send es2 dump) #"FOOBAR") + (check-equal? (send es2 dump) #"FOOBAR") ; dump can repeat + (check-equal? (get-output-bytes op) #"FOOBAR") + (define es3 (+EncodeStream)) + (send es3 fill 0 10) + (check-equal? (send es3 dump) (make-bytes 10 0))) + + +#| approximates +https://github.com/mbutterick/restructure/blob/master/src/DecodeStream.coffee +|# + +;; basically just a wrapper for a Racket port +;; but needs to start with a buffer so length can be found + +(require "sizes.rkt") +(define-macro (define-reader ID) + #'(define/public (ID) + (define bs (*ref type-sizes (string->symbol (string-downcase (string-replace (symbol->string 'ID) "read" ""))))) + (readBuffer bs))) + +(define countable<%> + (interface* () + ([(generic-property gen:countable) + (generic-method-table gen:countable + (define (length o) (get-field length_ o)))]))) + +(define DecodeStreamT + (class* PortWrapper + (countable<%>) + (init-field [buffer #""]) + (unless (bytes? buffer) ; corresponds to a Node Buffer, not a restructure BufferT object + (raise-argument-error 'DecodeStream:constructor "bytes" buffer)) + (super-make-object (open-input-bytes buffer)) + (inherit-field _port) + + (field [_pos 0] + [length_ (length buffer)]) + + (define/override (pos [where #f]) + (when where + (set! _pos (super pos where))) + _pos) + + (define/public (count-nonzero-chars) + ;; helper function for String + ;; counts nonzero chars from current position + (length (car (regexp-match-peek "[^\u0]*" _port)))) + + (public [-length length]) + (define (-length) length_) + + (define/public (readString length__ [encoding 'ascii]) + (define proc (caseq encoding + [(utf16le) (error 'bah)] + [(ucs2) (error 'bleh)] + [(utf8) bytes->string/utf-8] + [(ascii) bytes->string/latin-1] + [else identity])) + (define start (pos)) + (define stop (+ start length__)) + (proc (subbytes buffer start (pos stop)))) + + (define/public-final (readBuffer count) + (unless (index? count) + (raise-argument-error 'DecodeStream:read "positive integer" count)) + (define bytes-remaining (- length_ (pos))) + (when (> count bytes-remaining) + (raise-argument-error 'DecodeStream:read (format "byte count not more than bytes remaining = ~a" bytes-remaining) count)) + (increment-field! _pos this count) ; don't use `pos` method here because `read-bytes` will increment the port position + (define bs (read-bytes count _port)) + (unless (= _pos (file-position _port)) (raise-result-error 'DecodeStream "positions askew" (list _pos (file-position _port)))) + bs) + + (define/public (read count) (readBuffer count)) + + (define/public (readUInt8) (bytes-ref (readBuffer 1) 0)) + (define/public (readUInt16BE) (+ (arithmetic-shift (readUInt8) 8) (readUInt8))) + (define/public (readInt16BE) (unsigned->signed (readUInt16BE) 16)) + (define/public (readUInt16LE) (+ (readUInt8) (arithmetic-shift (readUInt8) 8))) + (define/public (readUInt24BE) (+ (arithmetic-shift (readUInt16BE) 8) (readUInt8))) + (define/public (readUInt24LE) (+ (readUInt16LE) (arithmetic-shift (readUInt8) 16))) + (define/public (readInt24BE) (unsigned->signed (readUInt24BE) 24)) + (define/public (readInt24LE) (unsigned->signed (readUInt24LE) 24)) + + (define/override-final (dump) + (define current-position (port-position _port)) + (set-port-position! _port 0) + (define bs (port->bytes _port)) + (set-port-position! _port current-position) + bs))) + +(define-subclass DecodeStreamT (DecodeStream)) + +(test-module + (define ds (+DecodeStream #"ABCD")) + (check-true (DecodeStream? ds)) + (check-equal? (length ds) 4) + (check-equal? (send ds dump) #"ABCD") + (check-equal? (send ds dump) #"ABCD") ; dump can repeat + (check-equal? (send ds readUInt16BE) 16706) + (check-equal? (send ds dump) #"ABCD") + (check-equal? (· ds pos) 2) + (check-equal? (send ds readUInt8) 67) + (check-equal? (· ds pos) 3) + (check-equal? (send ds readUInt8) 68) + (check-equal? (· ds pos) 4) + (check-exn exn:fail? (λ () (send ds read -42))) + (check-exn exn:fail? (λ () (send ds read 1)))) + + +;; Streamcoder is a helper class that checks / converts stream arguments before decode / encode +;; not a subclass of DecodeStream or EncodeStream, however. +(define-subclass RestructureBase (Streamcoder) + (define/overment (decode x [parent #f]) + (when parent (unless (indexable? parent) + (raise-argument-error 'Streamcoder:decode "hash or indexable" x))) + (define stream (if (bytes? x) (+DecodeStream x) x)) + (unless (DecodeStream? stream) + (raise-argument-error 'Streamcoder:decode "bytes or DecodeStream" x)) + (inner (void) decode stream parent)) + + (define/overment (encode x [val #f] [parent #f]) + (define stream (cond + [(output-port? x) (+EncodeStream x)] + [(not x) (+EncodeStream)] + [else x])) + (unless (EncodeStream? stream) + (raise-argument-error 'Streamcoder:encode "output port or EncodeStream" x)) + (inner (void) encode stream val parent) + (when (not x) (send stream dump)))) + +(test-module + (define-subclass Streamcoder (Dummy) + (define/augment (decode stream . args) "foo") + (define/augment (encode stream val parent) "bar") + (define/override (size) 42)) + + (define d (+Dummy)) + (check-true (Dummy? d)) + (check-exn exn:fail:contract? (λ () (send d decode 42))) + (check-not-exn (λ () (send d decode #"foo"))) + (check-exn exn:fail:contract? (λ () (send d encode 42 21))) + (check-not-exn (λ () (send d encode (open-output-bytes) 42)))) \ No newline at end of file diff --git a/pitfall/restructure/private/string.rkt b/pitfall/restructure/private/string.rkt new file mode 100644 index 00000000..18724b4b --- /dev/null +++ b/pitfall/restructure/private/string.rkt @@ -0,0 +1,61 @@ +#lang reader (submod "racket.rkt" reader) +(require "number.rkt" "utils.rkt" "stream.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbutterick/restructure/blob/master/src/String.coffee +|# + +(define (byte-length val encoding) + (define encoder + (caseq encoding + [(ascii utf8) string->bytes/utf-8])) + (bytes-length (encoder (format "~a" val)))) + +(define-subclass Streamcoder (StringT [len #f] [encoding 'ascii]) + + (define/augment (decode stream [parent #f]) + (let ([len (or (resolve-length len stream parent) (send stream count-nonzero-chars))] + [encoding (if (procedure? encoding) + (or (encoding parent) 'ascii) + encoding)] + [adjustment (if (and (not len) (< (· stream pos) (· stream length))) 1 0)]) + (define string (send stream readString len encoding)) + (send stream pos (+ (· stream pos) adjustment)) + string)) + + + (define/augment (encode stream val [parent #f]) + (let* ([val (format "~a" val)] + [encoding (if (procedure? encoding) + (or (encoding (and parent (· parent val)) 'ascii)) + encoding)]) + (when (NumberT? len) + (send len encode stream (byte-length val encoding))) + (send stream writeString val encoding) + (when (not len) (send stream writeUInt8 #x00)))) ; null terminated when no len + + + (define/override (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)) + +(test-module + (require "stream.rkt") + (define stream (+DecodeStream #"\2BCDEF")) + (define S (+String uint8 'utf8)) + (check-equal? (send S decode stream) "BC") + (check-equal? (send S encode #f "Mike") #"\4Mike") + (check-equal? (send (+String) size "foobar") 7)) ; null terminated when no len \ No newline at end of file diff --git a/pitfall/restructure/private/struct.rkt b/pitfall/restructure/private/struct.rkt new file mode 100644 index 00000000..0999f656 --- /dev/null +++ b/pitfall/restructure/private/struct.rkt @@ -0,0 +1,139 @@ +#lang reader (submod "racket.rkt" reader) +(require racket/dict "stream.rkt" racket/private/generic-methods racket/struct) +(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))))] + [(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 (get-field kv o) port)))]))) + +(define StructDictRes (class* RestructureBase (dictable<%>) + (super-make-object) + (field [kv (mhasheq)] + [pvt (mhasheq)]) + (public [_kv kv]) + (define (_kv) kv))) + + +(define-subclass Streamcoder (Struct [fields (dictify)]) + (field [[_process process] (λ (res stream ctx) res)] + [[_preEncode preEncode] void]) ; store as field so it can be mutated from outside + + (define/overment (process res stream [ctx #f]) + (let* ([res (_process res stream ctx)] + [res (inner res process res stream ctx)]) + (unless (dict? res) (raise-result-error 'Struct:process "dict" res)) + res)) + + (define/override (preEncode . args) (apply _preEncode args)) + + (unless ((disjoin assocs? 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* ([res (_setup stream parent len)] + [res (_parse-fields stream res fields)] + [res (process res stream)]) + res)) + + (define/public-final (_setup stream parent len) + (define res (make-object StructDictRes)) ; not mere hash + (dict-set*! res 'parent parent + '_startOffset (· stream pos) + '_currentOffset 0 + '_length len) + res) + + (define/public-final (_parse-fields stream res fields) + (unless (assocs? fields) + (raise-argument-error '_parse-fields "assocs" fields)) + (for/fold ([res res]) + ([(key type) (in-dict fields)]) + (define val (if (procedure? type) + (type res) + (send type decode stream res))) + (unless (void? val) + (ref-set! res key val)) + (ref-set! res '_currentOffset (- (· stream pos) (· res _startOffset))) + res)) + + + (define/override (size [val (mhash)] [parent #f] [include-pointers #t]) + (define ctx (mhasheq 'parent parent + 'val val + 'pointerSize 0)) + (+ (for/sum ([(key type) (in-dict fields)] + #:when val) + (send type size (ref val key) ctx)) + (if include-pointers (· ctx pointerSize) 0))) + + (define/augride (encode stream val [parent #f]) + (unless (dict? val) + (raise-argument-error 'Struct:encode "dict" val)) + + (send this preEncode val stream) ; preEncode goes first, because it might bring input dict into compliance + (define ctx (mhash 'pointers empty + 'startOffset (· stream pos) + 'parent parent + 'val val + 'pointerSize 0)) + (ref-set! ctx 'pointerOffset (+ (· stream pos) (size val ctx #f))) + + (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))) + (for ([(key type) (in-dict fields)]) + (send type encode stream (ref val key) ctx)) + (for ([ptr (in-list (· ctx pointers))]) + (send (· ptr type) encode stream (· 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/pitfall/restructure/utils.rkt b/pitfall/restructure/private/utils.rkt similarity index 89% rename from pitfall/restructure/utils.rkt rename to pitfall/restructure/private/utils.rkt index a6f1fad5..2d880233 100644 --- a/pitfall/restructure/utils.rkt +++ b/pitfall/restructure/private/utils.rkt @@ -1,4 +1,4 @@ -#lang restructure/racket +#lang reader (submod "racket.rkt" reader) (provide (all-defined-out) (rename-out [resolveLength resolve-length])) (require "number.rkt") diff --git a/pitfall/restructure/private/versioned-struct.rkt b/pitfall/restructure/private/versioned-struct.rkt new file mode 100644 index 00000000..3842560a --- /dev/null +++ b/pitfall/restructure/private/versioned-struct.rkt @@ -0,0 +1,155 @@ +#lang reader (submod "racket.rkt" reader) +(require racket/dict "struct.rkt") +(provide (all-defined-out)) + +#| +approximates +https://github.com/mbuttrackerick/restructure/blob/master/src/VersionedStruct.coffee +|# + + +(define-subclass Struct (VersionedStruct type [versions (dictify)]) + + (unless ((disjoin integer? procedure? RestructureBase? symbol?) 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 process) + (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) + (process res stream) + res])) + + (define/public-final (force-version! version) + (set! forced-version version)) + + (define/override (encode stream val [parent #f]) + (unless (hash? val) + (raise-argument-error 'Struct:encode "hash" val)) + + (send this preEncode val stream) ; preEncode goes first, because it might bring input hash into compliance + + (define ctx (mhash 'pointers empty + 'startOffset (· stream pos) + 'parent parent + 'val val + 'pointerSize 0)) + + (ref-set! ctx 'pointerOffset (+ (· stream pos) (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 (mhash)] [parent #f] [includePointers #t]) + (unless (or val forced-version) + (error 'VersionedStruct-cannot-compute-size)) + + (define ctx (mhash 'parent parent + 'val val + 'pointerSize 0)) + + (define size 0) + (when (not (or (key? type) (procedure? type))) + (increment! size (send type size (or forced-version (ref val 'version)) ctx))) + + (when (ref versions 'header) + (increment! size + (for/sum ([(key type) (in-dict (ref versions 'header))]) + (send type size (ref val key) ctx)))) + + (define fields (or (ref versions (or forced-version (ref val 'version))) (raise-argument-error 'VersionedStruct:encode "valid version key" version))) + + (increment! size + (for/sum ([(key type) (in-dict fields)]) + (send type size (ref val key) ctx))) + + (when includePointers + (increment! size (ref ctx 'pointerSize))) + + size)) + +#;(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/pitfall/restructure/reserved.rkt b/pitfall/restructure/reserved.rkt index adb8e165..23afbc43 100644 --- a/pitfall/restructure/reserved.rkt +++ b/pitfall/restructure/reserved.rkt @@ -1,21 +1,3 @@ -#lang restructure/racket -(require "stream.rkt" "utils.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/Reserved.coffee -|# - -(define-subclass Streamcoder (Reserved type [count 1]) - - (define/augment (decode stream parent) - (send stream pos (+ (· stream pos) (size #f parent))) - (void)) - - (define/override (size [val #f] [parent #f]) - (* (send type size) (resolve-length count #f parent))) - - (define/augment (encode stream val [parent #f]) - (send stream fill 0 (size val parent)))) +#lang reader (submod "private/racket.rkt" reader) +(r+p "private/reserved.rkt") \ No newline at end of file diff --git a/pitfall/restructure/stream.rkt b/pitfall/restructure/stream.rkt index 3be98bec..85dccc2b 100644 --- a/pitfall/restructure/stream.rkt +++ b/pitfall/restructure/stream.rkt @@ -1,215 +1,3 @@ -#lang restructure/racket -(require racket/private/generic-methods) -(provide (all-defined-out)) +#lang reader (submod "private/racket.rkt" reader) -;; helper class -(define-subclass object% (PortWrapper _port) - (unless (port? _port) - (raise-argument-error 'PortWrapper:constructor "port" _port)) - (define/public (pos [where #f]) - (when where (file-position _port where)) - (file-position _port)) - (define/public (dump) (void))) - -(test-module - (check-not-exn (λ () (make-object PortWrapper (open-input-bytes #"Foo")))) - (check-not-exn (λ () (make-object PortWrapper (open-output-bytes)))) - (check-exn exn:fail? (λ () (make-object PortWrapper -42)))) - -#| approximates -https://github.com/mbutterick/restructure/blob/master/src/EncodeStream.coffee -|# - -;; basically just a wrapper for a Racket output port -(define-subclass* PortWrapper (EncodeStream [maybe-output-port (open-output-bytes)]) - - (unless (output-port? maybe-output-port) - (raise-argument-error 'EncodeStream:constructor "output port" maybe-output-port)) - - (super-make-object maybe-output-port) - (inherit-field _port) - - (define/override-final (dump) (get-output-bytes _port)) - - (define/public-final (write val) - (unless (bytes? val) - (raise-argument-error 'EncodeStream:write "bytes" val)) - (write-bytes val _port) - (void)) - - (define/public-final (writeBuffer buffer) - (write buffer)) - - (define/public-final (writeUInt8 int) - (write (bytes int))) - - (define/public (writeString string [encoding 'ascii]) - ;; todo: handle encodings correctly. - ;; right now just utf8 and ascii are correct - (caseq encoding - [(utf16le ucs2 utf8 ascii) (writeBuffer (string->bytes/utf-8 string)) - (when (eq? encoding 'utf16le) - (error 'swap-bytes-unimplemented))] - [else (error 'unsupported-string-encoding)])) - - (define/public (fill val len) - (write (make-bytes len val)))) - -(test-module - (define es (+EncodeStream)) - (check-true (EncodeStream? es)) - (send es write #"AB") - (check-equal? (· es pos) 2) - (send es write #"C") - (check-equal? (· es pos) 3) - (send es write #"D") - (check-equal? (· es pos) 4) - (check-exn exn:fail? (λ () (send es write -42))) - (check-exn exn:fail? (λ () (send es write 1))) - (define op (open-output-bytes)) - (define es2 (+EncodeStream op)) - (send es2 write #"FOOBAR") - (check-equal? (send es2 dump) #"FOOBAR") - (check-equal? (send es2 dump) #"FOOBAR") ; dump can repeat - (check-equal? (get-output-bytes op) #"FOOBAR") - (define es3 (+EncodeStream)) - (send es3 fill 0 10) - (check-equal? (send es3 dump) (make-bytes 10 0))) - - -#| approximates -https://github.com/mbutterick/restructure/blob/master/src/DecodeStream.coffee -|# - -;; basically just a wrapper for a Racket port -;; but needs to start with a buffer so length can be found - -(require "sizes.rkt") -(define-macro (define-reader ID) - #'(define/public (ID) - (define bs (*ref type-sizes (string->symbol (string-downcase (string-replace (symbol->string 'ID) "read" ""))))) - (readBuffer bs))) - -(define countable<%> - (interface* () - ([(generic-property gen:countable) - (generic-method-table gen:countable - (define (length o) (get-field length_ o)))]))) - -(define DecodeStreamT - (class* PortWrapper - (countable<%>) - (init-field [buffer #""]) - (unless (bytes? buffer) ; corresponds to a Node Buffer, not a restructure BufferT object - (raise-argument-error 'DecodeStream:constructor "bytes" buffer)) - (super-make-object (open-input-bytes buffer)) - (inherit-field _port) - - (field [_pos 0] - [length_ (length buffer)]) - - (define/override (pos [where #f]) - (when where - (set! _pos (super pos where))) - _pos) - - (define/public (count-nonzero-chars) - ;; helper function for String - ;; counts nonzero chars from current position - (length (car (regexp-match-peek "[^\u0]*" _port)))) - - (public [-length length]) - (define (-length) length_) - - (define/public (readString length__ [encoding 'ascii]) - (define proc (caseq encoding - [(utf16le) (error 'bah)] - [(ucs2) (error 'bleh)] - [(utf8) bytes->string/utf-8] - [(ascii) bytes->string/latin-1] - [else identity])) - (define start (pos)) - (define stop (+ start length__)) - (proc (subbytes buffer start (pos stop)))) - - (define/public-final (readBuffer count) - (unless (index? count) - (raise-argument-error 'DecodeStream:read "positive integer" count)) - (define bytes-remaining (- length_ (pos))) - (when (> count bytes-remaining) - (raise-argument-error 'DecodeStream:read (format "byte count not more than bytes remaining = ~a" bytes-remaining) count)) - (increment-field! _pos this count) ; don't use `pos` method here because `read-bytes` will increment the port position - (define bs (read-bytes count _port)) - (unless (= _pos (file-position _port)) (raise-result-error 'DecodeStream "positions askew" (list _pos (file-position _port)))) - bs) - - (define/public (read count) (readBuffer count)) - - (define/public (readUInt8) (bytes-ref (readBuffer 1) 0)) - (define/public (readUInt16BE) (+ (arithmetic-shift (readUInt8) 8) (readUInt8))) - (define/public (readInt16BE) (unsigned->signed (readUInt16BE) 16)) - (define/public (readUInt16LE) (+ (readUInt8) (arithmetic-shift (readUInt8) 8))) - (define/public (readUInt24BE) (+ (arithmetic-shift (readUInt16BE) 8) (readUInt8))) - (define/public (readUInt24LE) (+ (readUInt16LE) (arithmetic-shift (readUInt8) 16))) - (define/public (readInt24BE) (unsigned->signed (readUInt24BE) 24)) - (define/public (readInt24LE) (unsigned->signed (readUInt24LE) 24)) - - (define/override-final (dump) - (define current-position (port-position _port)) - (set-port-position! _port 0) - (define bs (port->bytes _port)) - (set-port-position! _port current-position) - bs))) - -(define-subclass DecodeStreamT (DecodeStream)) - -(test-module - (define ds (+DecodeStream #"ABCD")) - (check-true (DecodeStream? ds)) - (check-equal? (length ds) 4) - (check-equal? (send ds dump) #"ABCD") - (check-equal? (send ds dump) #"ABCD") ; dump can repeat - (check-equal? (send ds readUInt16BE) 16706) - (check-equal? (send ds dump) #"ABCD") - (check-equal? (· ds pos) 2) - (check-equal? (send ds readUInt8) 67) - (check-equal? (· ds pos) 3) - (check-equal? (send ds readUInt8) 68) - (check-equal? (· ds pos) 4) - (check-exn exn:fail? (λ () (send ds read -42))) - (check-exn exn:fail? (λ () (send ds read 1)))) - - -;; Streamcoder is a helper class that checks / converts stream arguments before decode / encode -;; not a subclass of DecodeStream or EncodeStream, however. -(define-subclass RestructureBase (Streamcoder) - (define/overment (decode x [parent #f]) - (when parent (unless (indexable? parent) - (raise-argument-error 'Streamcoder:decode "hash or indexable" x))) - (define stream (if (bytes? x) (+DecodeStream x) x)) - (unless (DecodeStream? stream) - (raise-argument-error 'Streamcoder:decode "bytes or DecodeStream" x)) - (inner (void) decode stream parent)) - - (define/overment (encode x [val #f] [parent #f]) - (define stream (cond - [(output-port? x) (+EncodeStream x)] - [(not x) (+EncodeStream)] - [else x])) - (unless (EncodeStream? stream) - (raise-argument-error 'Streamcoder:encode "output port or EncodeStream" x)) - (inner (void) encode stream val parent) - (when (not x) (send stream dump)))) - -(test-module - (define-subclass Streamcoder (Dummy) - (define/augment (decode stream . args) "foo") - (define/augment (encode stream val parent) "bar") - (define/override (size) 42)) - - (define d (+Dummy)) - (check-true (Dummy? d)) - (check-exn exn:fail:contract? (λ () (send d decode 42))) - (check-not-exn (λ () (send d decode #"foo"))) - (check-exn exn:fail:contract? (λ () (send d encode 42 21))) - (check-not-exn (λ () (send d encode (open-output-bytes) 42)))) \ No newline at end of file +(r+p "private/stream.rkt") \ No newline at end of file diff --git a/pitfall/restructure/string.rkt b/pitfall/restructure/string.rkt index d293801c..8e5d4334 100644 --- a/pitfall/restructure/string.rkt +++ b/pitfall/restructure/string.rkt @@ -1,61 +1,3 @@ -#lang restructure/racket -(require "number.rkt" "utils.rkt" "stream.rkt") -(provide (all-defined-out)) +#lang reader (submod "private/racket.rkt" reader) -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/String.coffee -|# - -(define (byte-length val encoding) - (define encoder - (caseq encoding - [(ascii utf8) string->bytes/utf-8])) - (bytes-length (encoder (format "~a" val)))) - -(define-subclass Streamcoder (StringT [len #f] [encoding 'ascii]) - - (define/augment (decode stream [parent #f]) - (let ([len (or (resolve-length len stream parent) (send stream count-nonzero-chars))] - [encoding (if (procedure? encoding) - (or (encoding parent) 'ascii) - encoding)] - [adjustment (if (and (not len) (< (· stream pos) (· stream length))) 1 0)]) - (define string (send stream readString len encoding)) - (send stream pos (+ (· stream pos) adjustment)) - string)) - - - (define/augment (encode stream val [parent #f]) - (let* ([val (format "~a" val)] - [encoding (if (procedure? encoding) - (or (encoding (and parent (· parent val)) 'ascii)) - encoding)]) - (when (NumberT? len) - (send len encode stream (byte-length val encoding))) - (send stream writeString val encoding) - (when (not len) (send stream writeUInt8 #x00)))) ; null terminated when no len - - - (define/override (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)) - -(test-module - (require "stream.rkt") - (define stream (+DecodeStream #"\2BCDEF")) - (define S (+String uint8 'utf8)) - (check-equal? (send S decode stream) "BC") - (check-equal? (send S encode #f "Mike") #"\4Mike") - (check-equal? (send (+String) size "foobar") 7)) ; null terminated when no len \ No newline at end of file +(r+p "private/string.rkt") \ No newline at end of file diff --git a/pitfall/restructure/struct.rkt b/pitfall/restructure/struct.rkt index 1f77acca..c9546f0a 100644 --- a/pitfall/restructure/struct.rkt +++ b/pitfall/restructure/struct.rkt @@ -1,139 +1,3 @@ -#lang restructure/racket -(require racket/dict "stream.rkt" racket/private/generic-methods racket/struct) -(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))))] - [(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 (get-field kv o) port)))]))) - -(define StructDictRes (class* RestructureBase (dictable<%>) - (super-make-object) - (field [kv (mhasheq)] - [pvt (mhasheq)]) - (public [_kv kv]) - (define (_kv) kv))) - - -(define-subclass Streamcoder (Struct [fields (dictify)]) - (field [[_process process] (λ (res stream ctx) res)] - [[_preEncode preEncode] void]) ; store as field so it can be mutated from outside - - (define/overment (process res stream [ctx #f]) - (let* ([res (_process res stream ctx)] - [res (inner res process res stream ctx)]) - (unless (dict? res) (raise-result-error 'Struct:process "dict" res)) - res)) - - (define/override (preEncode . args) (apply _preEncode args)) - - (unless ((disjoin assocs? 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* ([res (_setup stream parent len)] - [res (_parse-fields stream res fields)] - [res (process res stream)]) - res)) - - (define/public-final (_setup stream parent len) - (define res (make-object StructDictRes)) ; not mere hash - (dict-set*! res 'parent parent - '_startOffset (· stream pos) - '_currentOffset 0 - '_length len) - res) - - (define/public-final (_parse-fields stream res fields) - (unless (assocs? fields) - (raise-argument-error '_parse-fields "assocs" fields)) - (for/fold ([res res]) - ([(key type) (in-dict fields)]) - (define val (if (procedure? type) - (type res) - (send type decode stream res))) - (unless (void? val) - (ref-set! res key val)) - (ref-set! res '_currentOffset (- (· stream pos) (· res _startOffset))) - res)) - - - (define/override (size [val (mhash)] [parent #f] [include-pointers #t]) - (define ctx (mhasheq 'parent parent - 'val val - 'pointerSize 0)) - (+ (for/sum ([(key type) (in-dict fields)] - #:when val) - (send type size (ref val key) ctx)) - (if include-pointers (· ctx pointerSize) 0))) - - (define/augride (encode stream val [parent #f]) - (unless (dict? val) - (raise-argument-error 'Struct:encode "dict" val)) - - (send this preEncode val stream) ; preEncode goes first, because it might bring input dict into compliance - (define ctx (mhash 'pointers empty - 'startOffset (· stream pos) - 'parent parent - 'val val - 'pointerSize 0)) - (ref-set! ctx 'pointerOffset (+ (· stream pos) (size val ctx #f))) - - (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))) - (for ([(key type) (in-dict fields)]) - (send type encode stream (ref val key) ctx)) - (for ([ptr (in-list (· ctx pointers))]) - (send (· ptr type) encode stream (· 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))) - - +#lang reader (submod "private/racket.rkt" reader) +(r+p "private/struct.rkt") \ No newline at end of file diff --git a/pitfall/restructure/test/array-test.rkt b/pitfall/restructure/test/array-test.rkt index 87c952d0..44fc7c3c 100644 --- a/pitfall/restructure/test/array-test.rkt +++ b/pitfall/restructure/test/array-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| diff --git a/pitfall/restructure/test/bitfield-test.rkt b/pitfall/restructure/test/bitfield-test.rkt index 31fd5e72..79e098d7 100644 --- a/pitfall/restructure/test/bitfield-test.rkt +++ b/pitfall/restructure/test/bitfield-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) (require racket/match) #| diff --git a/pitfall/restructure/test/buffer-test.rkt b/pitfall/restructure/test/buffer-test.rkt index 2a420fc2..e20dab0c 100644 --- a/pitfall/restructure/test/buffer-test.rkt +++ b/pitfall/restructure/test/buffer-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| diff --git a/pitfall/restructure/test/enum-test.rkt b/pitfall/restructure/test/enum-test.rkt index cbb825b5..b74048f2 100644 --- a/pitfall/restructure/test/enum-test.rkt +++ b/pitfall/restructure/test/enum-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| approximates diff --git a/pitfall/restructure/test/lazy-array-test.rkt b/pitfall/restructure/test/lazy-array-test.rkt index 506a6ba2..0ddb7b48 100644 --- a/pitfall/restructure/test/lazy-array-test.rkt +++ b/pitfall/restructure/test/lazy-array-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| approximates diff --git a/pitfall/restructure/test/main.rkt b/pitfall/restructure/test/main.rkt index 1ae6ea14..d9dba3e8 100644 --- a/pitfall/restructure/test/main.rkt +++ b/pitfall/restructure/test/main.rkt @@ -1,4 +1,4 @@ -#lang restructure/racket +#lang reader (submod "racket.rkt" reader) (require "array-test.rkt" "bitfield-test.rkt" diff --git a/pitfall/restructure/test/number-test.rkt b/pitfall/restructure/test/number-test.rkt index 2857c86d..65f06b87 100644 --- a/pitfall/restructure/test/number-test.rkt +++ b/pitfall/restructure/test/number-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| diff --git a/pitfall/restructure/test/optional-test.rkt b/pitfall/restructure/test/optional-test.rkt index eb350dba..d0138690 100644 --- a/pitfall/restructure/test/optional-test.rkt +++ b/pitfall/restructure/test/optional-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| approximates diff --git a/pitfall/restructure/test/pointer-test.rkt b/pitfall/restructure/test/pointer-test.rkt index ab0f1a2c..b2689268 100644 --- a/pitfall/restructure/test/pointer-test.rkt +++ b/pitfall/restructure/test/pointer-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| approximates diff --git a/pitfall/restructure/test/racket.rkt b/pitfall/restructure/test/racket.rkt index ebdb51e8..7efec920 100644 --- a/pitfall/restructure/test/racket.rkt +++ b/pitfall/restructure/test/racket.rkt @@ -1,6 +1,6 @@ #lang racket/base -(require rackunit restructure restructure/racket) -(provide (all-from-out rackunit restructure restructure/racket)) +(require rackunit restructure "../private/racket.rkt") +(provide (all-from-out rackunit restructure "../private/racket.rkt")) (module reader syntax/module-reader #:language 'restructure/test/racket) \ No newline at end of file diff --git a/pitfall/restructure/test/reserved-test.rkt b/pitfall/restructure/test/reserved-test.rkt index 900a5ace..37d0028e 100644 --- a/pitfall/restructure/test/reserved-test.rkt +++ b/pitfall/restructure/test/reserved-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| approximates diff --git a/pitfall/restructure/test/stream-test.rkt b/pitfall/restructure/test/stream-test.rkt index 42738047..74e8adaa 100644 --- a/pitfall/restructure/test/stream-test.rkt +++ b/pitfall/restructure/test/stream-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| diff --git a/pitfall/restructure/test/string-test.rkt b/pitfall/restructure/test/string-test.rkt index 013140ca..a12efc0f 100644 --- a/pitfall/restructure/test/string-test.rkt +++ b/pitfall/restructure/test/string-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| diff --git a/pitfall/restructure/test/struct-test.rkt b/pitfall/restructure/test/struct-test.rkt index 070bae16..f4d0eef5 100644 --- a/pitfall/restructure/test/struct-test.rkt +++ b/pitfall/restructure/test/struct-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| approximates diff --git a/pitfall/restructure/test/test.rkt b/pitfall/restructure/test/test.rkt index 018318fe..e139ccf9 100644 --- a/pitfall/restructure/test/test.rkt +++ b/pitfall/restructure/test/test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) (define Person (make-object Struct diff --git a/pitfall/restructure/test/versioned-struct-test.rkt b/pitfall/restructure/test/versioned-struct-test.rkt index ba31b4f8..41792390 100644 --- a/pitfall/restructure/test/versioned-struct-test.rkt +++ b/pitfall/restructure/test/versioned-struct-test.rkt @@ -1,4 +1,4 @@ -#lang restructure/test/racket +#lang reader (submod "racket.rkt" reader) #| approximates diff --git a/pitfall/restructure/versioned-struct.rkt b/pitfall/restructure/versioned-struct.rkt index 05ca5244..bce8d99f 100644 --- a/pitfall/restructure/versioned-struct.rkt +++ b/pitfall/restructure/versioned-struct.rkt @@ -1,155 +1,3 @@ -#lang restructure/racket -(require racket/dict "struct.rkt") -(provide (all-defined-out)) - -#| -approximates -https://github.com/mbutterick/restructure/blob/master/src/VersionedStruct.coffee -|# - - -(define-subclass Struct (VersionedStruct type [versions (dictify)]) - - (unless ((disjoin integer? procedure? RestructureBase? symbol?) 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 process) - (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) - (process res stream) - res])) - - (define/public-final (force-version! version) - (set! forced-version version)) - - (define/override (encode stream val [parent #f]) - (unless (hash? val) - (raise-argument-error 'Struct:encode "hash" val)) - - (send this preEncode val stream) ; preEncode goes first, because it might bring input hash into compliance - - (define ctx (mhash 'pointers empty - 'startOffset (· stream pos) - 'parent parent - 'val val - 'pointerSize 0)) - - (ref-set! ctx 'pointerOffset (+ (· stream pos) (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 (mhash)] [parent #f] [includePointers #t]) - (unless (or val forced-version) - (error 'VersionedStruct-cannot-compute-size)) - - (define ctx (mhash 'parent parent - 'val val - 'pointerSize 0)) - - (define size 0) - (when (not (or (key? type) (procedure? type))) - (increment! size (send type size (or forced-version (ref val 'version)) ctx))) - - (when (ref versions 'header) - (increment! size - (for/sum ([(key type) (in-dict (ref versions 'header))]) - (send type size (ref val key) ctx)))) - - (define fields (or (ref versions (or forced-version (ref val 'version))) (raise-argument-error 'VersionedStruct:encode "valid version key" version))) - - (increment! size - (for/sum ([(key type) (in-dict fields)]) - (send type size (ref val key) ctx))) - - (when includePointers - (increment! size (ref ctx 'pointerSize))) - - size)) - -#;(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) -|# - ) - +#lang reader (submod "private/racket.rkt" reader) +(r+p "private/versioned-struct.rkt") \ No newline at end of file