]> Sergey Matveev's repositories - btrtrc.git/commitdiff
bencode: Implement unbuffered scanner used by NewDecoder
authorMatt Joiner <anacrolix@gmail.com>
Sun, 5 Nov 2017 04:38:09 +0000 (15:38 +1100)
committerMatt Joiner <anacrolix@gmail.com>
Sun, 5 Nov 2017 04:39:36 +0000 (15:39 +1100)
Minimizes unused reads into the input Reader stream

bencode/api.go
bencode/decode_test.go
bencode/scanner.go [new file with mode: 0644]

index b5f026c5ce58a65390b5817038fb0d856b6d031a..1b77f588b58865cd592657d7d6206f2ef063fa83 100644 (file)
@@ -129,7 +129,7 @@ func Unmarshal(data []byte, v interface{}) error {
 }
 
 func NewDecoder(r io.Reader) *Decoder {
-       return &Decoder{r: bufio.NewReader(r)}
+       return &Decoder{r: &scanner{r: r}}
 }
 
 func NewEncoder(w io.Writer) *Encoder {
index 1db0c7e4c85a5cbcc51ad75a7d2400b9515b5900..572740b8c3611158d8b196814b1133846a386f6b 100644 (file)
@@ -72,16 +72,28 @@ func TestDecoderConsecutive(t *testing.T) {
 
 func TestDecoderConsecutiveDicts(t *testing.T) {
        bb := bytes.NewBufferString("d4:herp4:derped3:wat1:ke17:oh baby a triple!")
+
        d := NewDecoder(bb)
+       assert.EqualValues(t, "d4:herp4:derped3:wat1:ke17:oh baby a triple!", bb.Bytes())
+       assert.EqualValues(t, 0, d.offset)
+
        var m map[string]interface{}
+
        require.NoError(t, d.Decode(&m))
        assert.Len(t, m, 1)
        assert.Equal(t, "derp", m["herp"])
+       assert.Equal(t, "d3:wat1:ke17:oh baby a triple!", bb.String())
+       assert.EqualValues(t, 14, d.offset)
+
        require.NoError(t, d.Decode(&m))
        assert.Equal(t, "k", m["wat"])
+       assert.Equal(t, "17:oh baby a triple!", bb.String())
+       assert.EqualValues(t, 24, d.offset)
+
        var s string
        require.NoError(t, d.Decode(&s))
        assert.Equal(t, "oh baby a triple!", s)
+       assert.EqualValues(t, 44, d.offset)
 }
 
 func check_error(t *testing.T, err error) {
diff --git a/bencode/scanner.go b/bencode/scanner.go
new file mode 100644 (file)
index 0000000..eaf22dd
--- /dev/null
@@ -0,0 +1,41 @@
+package bencode
+
+import (
+       "errors"
+       "io"
+)
+
+// Implements io.ByteScanner over io.Reader, for use in Decoder, to ensure
+// that as little as the undecoded input Reader is consumed as possible.
+type scanner struct {
+       r      io.Reader
+       b      [1]byte // Buffer for ReadByte
+       unread bool    // True if b has been unread, and so should be returned next
+}
+
+func (me *scanner) Read(b []byte) (int, error) {
+       return me.r.Read(b)
+}
+
+func (me *scanner) ReadByte() (byte, error) {
+       if me.unread {
+               me.unread = false
+               return me.b[0], nil
+       }
+       n, err := me.r.Read(me.b[:])
+       if err != nil {
+               return me.b[0], err
+       }
+       if n != 1 {
+               panic(n)
+       }
+       return me.b[0], err
+}
+
+func (me *scanner) UnreadByte() error {
+       if me.unread {
+               return errors.New("byte already unread")
+       }
+       me.unread = true
+       return nil
+}