From af42fdcf99bb8b2ada0680e7cdef0becc8a5f92d Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Thu, 29 Jun 2017 18:47:06 -0700 Subject: [PATCH] pointer in progress --- pitfall/restructure/pointer-test.rkt | 361 +++++++++++++++++++++++++-- pitfall/restructure/pointer.rkt | 107 +++++--- 2 files changed, 405 insertions(+), 63 deletions(-) diff --git a/pitfall/restructure/pointer-test.rkt b/pitfall/restructure/pointer-test.rkt index 566aa779..dd874cf0 100644 --- a/pitfall/restructure/pointer-test.rkt +++ b/pitfall/restructure/pointer-test.rkt @@ -6,43 +6,358 @@ approximates https://github.com/mbutterick/restructure/blob/master/test/Pointer.coffee |# -;; it 'should handle null pointers', -> -(let ([stream (+DecodeStream (bytes 0))] + +;describe 'Pointer', -> +; describe 'decode', -> +; it 'should handle null pointers', -> +; stream = new DecodeStream new Buffer [0] +; pointer = new Pointer uint8, uint8 +; should.not.exist pointer.decode(stream, _startOffset: 50) + + +(let ([stream (+DecodeStream (+Buffer '(0)))] [pointer (+Pointer uint8 uint8)]) - (check-exn exn:fail? (λ () (send pointer decode stream (mhash '_startOffset 50))))) + (check-false (send pointer decode stream (mhash '_startOffset 50)))) + +; +; it 'should use local offsets from start of parent by default', -> +; stream = new DecodeStream new Buffer [1, 53] +; pointer = new Pointer uint8, uint8 +; pointer.decode(stream, _startOffset: 0).should.equal 53 + -;; it 'should use local offsets from start of parent by default', -> -(let ([stream (+DecodeStream (bytes 1 53))] +(let ([stream (+DecodeStream (+Buffer '(1 53)))] [pointer (+Pointer uint8 uint8)]) (check-equal? (send pointer decode stream (mhash '_startOffset 0)) 53)) -;; it 'should support immediate offsets', -> -(let ([stream (+DecodeStream (bytes 1 53))] - [pointer (+Pointer uint8 uint8 'immediate)]) + +; +; it 'should support immediate offsets', -> +; stream = new DecodeStream new Buffer [1, 53] +; pointer = new Pointer uint8, uint8, type: 'immediate' +; pointer.decode(stream).should.equal 53 + + +(let ([stream (+DecodeStream (+Buffer '(1 53)))] + [pointer (+Pointer uint8 uint8 (mhash 'type 'immediate))]) (check-equal? (send pointer decode stream) 53)) -;; it 'should support offsets relative to the parent', -> -(let ([stream (+DecodeStream (bytes 0 0 1 53))] - [pointer (+Pointer uint8 uint8 'parent)]) +; +; it 'should support offsets relative to the parent', -> +; stream = new DecodeStream new Buffer [0, 0, 1, 53] +; stream.pos = 2 +; pointer = new Pointer uint8, uint8, type: 'parent' +; pointer.decode(stream, parent: _startOffset: 2).should.equal 53 + + +(let ([stream (+DecodeStream (+Buffer '(0 0 1 53)))] + [pointer (+Pointer uint8 uint8 (mhash 'type 'parent))]) (send stream pos 2) (check-equal? (send pointer decode stream (mhash 'parent (mhash '_startOffset 2))) 53)) -;; it 'should support global offsets', -> -(let ([stream (+DecodeStream (bytes 1 2 4 0 0 0 53))] - [pointer (+Pointer uint8 uint8 'global)]) + +; +; it 'should support global offsets', -> +; stream = new DecodeStream new Buffer [1, 2, 4, 0, 0, 0, 53] +; pointer = new Pointer uint8, uint8, type: 'global' +; stream.pos = 2 +; pointer.decode(stream, parent: parent: _startOffset: 2).should.equal 53 + + +(let ([stream (+DecodeStream (+Buffer '(1 2 4 0 0 0 53)))] + [pointer (+Pointer uint8 uint8 (mhash 'type 'global))]) (send stream pos 2) (check-equal? (send pointer decode stream (mhash 'parent (mhash 'parent (mhash '_startOffset 2)))) 53)) -;; todo -#| - it 'should support offsets relative to a property on the parent', -> - stream = new DecodeStream new Buffer [1, 0, 0, 0, 0, 53] - pointer = new Pointer uint8, uint8, relativeTo: 'parent.ptr' - pointer.decode(stream, _startOffset: 0, parent: ptr: 4).should.equal 53 -|# -;; it 'should support returning pointer if there is no decode type', -> -(let ([stream (+DecodeStream (bytes 4))] +; it 'should support offsets relative to a property on the parent', -> +; stream = new DecodeStream new Buffer [1, 0, 0, 0, 0, 53] +; pointer = new Pointer uint8, uint8, relativeTo: 'parent.ptr' +; pointer.decode(stream, _startOffset: 0, parent: ptr: 4).should.equal 53 + +(let ([stream (+DecodeStream (+Buffer '(1 0 0 0 0 53)))] + [pointer (+Pointer uint8 uint8 (mhash 'relativeTo (λ (ctx) (· ctx parent ptr))))]) + (check-equal? (send pointer decode stream (mhash '_startOffset 0 'parent (mhash 'ptr 4))) 53)) + + +; +; it 'should support returning pointer if there is no decode type', -> +; stream = new DecodeStream new Buffer [4] +; pointer = new Pointer uint8, 'void' +; pointer.decode(stream, _startOffset: 0).should.equal 4 + +(let ([stream (+DecodeStream (+Buffer '(4)))] [pointer (+Pointer uint8 'void)]) (check-equal? (send pointer decode stream (mhash '_startOffset 0)) 4)) + +; todo: skipped lazy pointers for now +; it 'should support decoding pointers lazily', -> +; stream = new DecodeStream new Buffer [1, 53] +; struct = new Struct +; ptr: new Pointer uint8, uint8, lazy: yes +; +; res = struct.decode(stream) +; Object.getOwnPropertyDescriptor(res, 'ptr').get.should.be.a('function') +; Object.getOwnPropertyDescriptor(res, 'ptr').enumerable.should.equal(true) +; res.ptr.should.equal 53 +; + + + +; describe 'size', -> +; it 'should add to local pointerSize', -> +; pointer = new Pointer uint8, uint8 +; ctx = pointerSize: 0 +; pointer.size(10, ctx).should.equal 1 +; ctx.pointerSize.should.equal 1 + +(let ([pointer (+Pointer uint8 uint8)] + [ctx (mhash 'pointerSize 0)]) + (check-equal? (send pointer size 10 ctx) 1) + (check-equal? (ref ctx 'pointerSize) 1)) + + +; +; it 'should add to immediate pointerSize', -> +; pointer = new Pointer uint8, uint8, type: 'immediate' +; ctx = pointerSize: 0 +; pointer.size(10, ctx).should.equal 1 +; ctx.pointerSize.should.equal 1 + +(let ([pointer (+Pointer uint8 uint8 (mhash 'type 'immediate))] + [ctx (mhash 'pointerSize 0)]) + (check-equal? (send pointer size 10 ctx) 1) + (check-equal? (ref ctx 'pointerSize) 1)) + +; +; it 'should add to parent pointerSize', -> +; pointer = new Pointer uint8, uint8, type: 'parent' +; ctx = parent: pointerSize: 0 +; pointer.size(10, ctx).should.equal 1 +; ctx.parent.pointerSize.should.equal 1 + +(let ([pointer (+Pointer uint8 uint8 (mhash 'type 'parent))] + [ctx (mhash 'parent (mhash 'pointerSize 0))]) + (check-equal? (send pointer size 10 ctx) 1) + (check-equal? (ref* ctx 'parent 'pointerSize) 1)) + +; +; it 'should add to global pointerSize', -> +; pointer = new Pointer uint8, uint8, type: 'global' +; ctx = parent: parent: parent: pointerSize: 0 +; pointer.size(10, ctx).should.equal 1 +; ctx.parent.parent.parent.pointerSize.should.equal 1 + +(let ([pointer (+Pointer uint8 uint8 (mhash 'type 'global))] + [ctx (mhash 'parent (mhash 'parent (mhash 'parent (mhash 'pointerSize 0))))]) + (check-equal? (send pointer size 10 ctx) 1) + (check-equal? (ref* ctx 'parent 'parent 'parent 'pointerSize) 1)) + +; todo: void pointer +; it 'should handle void pointers', -> +; pointer = new Pointer uint8, 'void' +; ctx = pointerSize: 0 +; pointer.size(new VoidPointer(uint8, 50), ctx).should.equal 1 +; ctx.pointerSize.should.equal 1 +; +; it 'should throw if no type and not a void pointer', -> +; pointer = new Pointer uint8, 'void' +; ctx = pointerSize: 0 +; should.throw -> +; pointer.size(30, ctx).should.equal 1 +; + + + +; it 'should return a fixed size without a value', -> +; pointer = new Pointer uint8, uint8 +; pointer.size().should.equal 1 + +(let ([pointer (+Pointer uint8 uint8)]) + (check-equal? (send pointer size) 1)) + +; +; describe 'encode', -> +; it 'should handle null pointers', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer [0] +; done() +; +; ptr = new Pointer uint8, uint8 +; ctx = +; pointerSize: 0, +; startOffset: 0, +; pointerOffset: 0, +; pointers: [] +; +; ptr.encode(stream, null, ctx) +; ctx.pointerSize.should.equal 0 +; +; stream.end() +; +; it 'should handle local offsets', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer [1] +; done() +; +; ptr = new Pointer uint8, uint8 +; ctx = +; pointerSize: 0, +; startOffset: 0, +; pointerOffset: 1, +; pointers: [] +; +; ptr.encode(stream, 10, ctx) +; ctx.pointerOffset.should.equal 2 +; ctx.pointers.should.deep.equal [ +; { type: uint8, val: 10, parent: ctx } +; ] +; +; stream.end() +; +; it 'should handle immediate offsets', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer [0] +; done() +; +; ptr = new Pointer uint8, uint8, type: 'immediate' +; ctx = +; pointerSize: 0, +; startOffset: 0, +; pointerOffset: 1, +; pointers: [] +; +; ptr.encode(stream, 10, ctx) +; ctx.pointerOffset.should.equal 2 +; ctx.pointers.should.deep.equal [ +; { type: uint8, val: 10, parent: ctx } +; ] +; +; stream.end() +; +; it 'should handle immediate offsets', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer [0] +; done() +; +; ptr = new Pointer uint8, uint8, type: 'immediate' +; ctx = +; pointerSize: 0, +; startOffset: 0, +; pointerOffset: 1, +; pointers: [] +; +; ptr.encode(stream, 10, ctx) +; ctx.pointerOffset.should.equal 2 +; ctx.pointers.should.deep.equal [ +; { type: uint8, val: 10, parent: ctx } +; ] +; +; stream.end() +; +; it 'should handle offsets relative to parent', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer [2] +; done() +; +; ptr = new Pointer uint8, uint8, type: 'parent' +; ctx = +; parent: +; pointerSize: 0, +; startOffset: 3, +; pointerOffset: 5, +; pointers: [] +; +; ptr.encode(stream, 10, ctx) +; ctx.parent.pointerOffset.should.equal 6 +; ctx.parent.pointers.should.deep.equal [ +; { type: uint8, val: 10, parent: ctx } +; ] +; +; stream.end() +; +; it 'should handle global offsets', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer [5] +; done() +; +; ptr = new Pointer uint8, uint8, type: 'global' +; ctx = +; parent: +; parent: +; parent: +; pointerSize: 0, +; startOffset: 3, +; pointerOffset: 5, +; pointers: [] +; +; ptr.encode(stream, 10, ctx) +; ctx.parent.parent.parent.pointerOffset.should.equal 6 +; ctx.parent.parent.parent.pointers.should.deep.equal [ +; { type: uint8, val: 10, parent: ctx } +; ] +; +; stream.end() +; +; it 'should support offsets relative to a property on the parent', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer [6] +; done() +; +; ptr = new Pointer uint8, uint8, relativeTo: 'ptr' +; ctx = +; pointerSize: 0, +; startOffset: 0, +; pointerOffset: 10, +; pointers: [] +; val: +; ptr: 4 +; +; ptr.encode(stream, 10, ctx) +; ctx.pointerOffset.should.equal 11 +; ctx.pointers.should.deep.equal [ +; { type: uint8, val: 10, parent: ctx } +; ] +; +; stream.end() +; +; it 'should support void pointers', (done) -> +; stream = new EncodeStream +; stream.pipe concat (buf) -> +; buf.should.deep.equal new Buffer [1] +; done() +; +; ptr = new Pointer uint8, 'void' +; ctx = +; pointerSize: 0, +; startOffset: 0, +; pointerOffset: 1, +; pointers: [] +; +; ptr.encode(stream, new VoidPointer(uint8, 55), ctx) +; ctx.pointerOffset.should.equal 2 +; ctx.pointers.should.deep.equal [ +; { type: uint8, val: 55, parent: ctx } +; ] +; +; stream.end() +; +; it 'should throw if not a void pointer instance', -> +; stream = new EncodeStream +; ptr = new Pointer uint8, 'void' +; ctx = +; pointerSize: 0, +; startOffset: 0, +; pointerOffset: 1, +; pointers: [] +; +; should.throw -> +; ptr.encode(stream, 44, ctx) diff --git a/pitfall/restructure/pointer.rkt b/pitfall/restructure/pointer.rkt index bb07b1c1..954c6658 100644 --- a/pitfall/restructure/pointer.rkt +++ b/pitfall/restructure/pointer.rkt @@ -6,55 +6,82 @@ approximates https://github.com/mbutterick/restructure/blob/master/src/Pointer.coffee |# -(define-subclass object% (Pointer offsetType type [scope 'local]) +(define-subclass object% (Pointer offsetType type [options (mhash)]) (when (eq? type 'void) (set! type #f)) - - (and (symbol? scope) (caseq scope - [(local parent immediate global) 'yay] - [else (raise-argument-error 'Pointer "local or parent or immediate" scope)])) + (hash-ref! options 'type 'local) + (hash-ref! options 'allowNull #t) + (hash-ref! options 'nullValue 0) + (hash-ref! options 'lazy #f) + (define relativeToGetter (ref options 'relativeTo)) ; change this to a simple lambda (define/public (decode stream [ctx #f]) (define offset (send offsetType decode stream ctx)) - (report scope 'pointer-scope) - (define relative (caseq scope - [(local) #;(when (and (· ctx res _startOffset) (· ctx _startOffset) - (not (= (· ctx res _startOffset) (· ctx _startOffset)))) - (report* ctx (· ctx res _startOffset) (· ctx _startOffset)) - (error 'bazongas)) - (· ctx res _startOffset)] - [(parent) (· ctx res parent res _startOffset)] - [(immediate) (- (· stream pos) (send offsetType size))] - [(global) - (let loop ([c ctx]) - (cond - [(· c parent) => loop] - [(· c _startOffset)] - [else 0]))])) - (report* this (· this _startOffset) - (and (· this res) (· this res _startOffset)) - ctx - (and (· ctx res) (· ctx res _startOffset))) - #;(when (and ctx (· ctx res _startOffset) (= (· ctx res _startOffset) 1012)) (error 'stop)) - (report* offset relative) - (define ptr (+ offset relative)) - (report* ptr) - (report (send offsetType size) 'size) + (cond - [type (define orig-pos (send stream pos)) - (send stream pos ptr) - (define val (send type decode stream ctx)) - (send stream pos orig-pos) - val] - [else ptr])) + ;; handle NULL pointers + [(and (eq? offset (ref options 'nullValue)) (ref options 'allowNull)) #f] + [else + (define relative (caseq (ref options 'type) + [(local) (ref ctx '_startOffset)] + [(immediate) (- (· stream pos) (send offsetType size))] + [(parent) (ref* ctx 'parent '_startOffset)] + [else (let loop ([ctx ctx]) + (cond + [(· ctx parent) => loop] + [(ref ctx '_startOffset)] + [else 0]))])) + + (when (ref options 'relativeTo) + ; relativeToGetter only defined if 'relativeTo key exists, so this is safe + (increment! relative (relativeToGetter ctx))) + + (define ptr (+ offset relative)) + + (cond + [type (define val #f) + (define (decodeValue) + (cond + [val] + [else (define pos (· stream pos)) + (send stream pos ptr) + (define val (send type decode stream ctx)) + (send stream pos pos) + val])) + + ;; skip lazy pointer chores + + (decodeValue)] + [else ptr])])) (define/public (size [val #f] [ctx #f]) - (error 'Pointer-size-not-done) - (report* this offsetType type (send type size))) - + (define parent ctx) + (caseq (ref options 'type) + [(local immediate) (void)] + [(parent) (set! ctx (ref ctx 'parent))] + [else ; global + (set! ctx (let loop ([ctx ctx]) + (cond + [(ref ctx 'parent) => loop] + [else ctx])))]) + + (define type_ type) + (unless type_ + ; todo: uncomment when VoidPointer class is ready + #;(unless (VoidPointer? val) + (raise-argument-error 'Pointer:size "VoidPointer" val)) + + (set! type (ref val 'type)) + (set! val (ref val 'value))) + + (when (and val ctx) + (ref-set! ctx 'pointerSize (+ (ref ctx 'pointerSize) (send type size val parent)))) + + (send offsetType size)) + - (define/public (encode stream val) - (error 'Pointer-encode-not-done)) + #;(define/public (encode stream val) + (error 'Pointer-encode-not-done))