Skip to content

Commit

Permalink
ZigZag class and docs added
Browse files Browse the repository at this point in the history
  • Loading branch information
arekbulski committed Feb 7, 2021
1 parent cf3c5e0 commit 7e672c4
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 1 deletion.
1 change: 1 addition & 0 deletions construct/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
'VarInt',
'version',
'version_string',
'ZigZag',
]
__all__ += ["Int%s%s%s" % (n,us,bln) for n in (8,16,24,32,64) for us in "us" for bln in "bln"]
__all__ += ["Float%s%s" % (n,bln) for n in (16,32,64) for bln in "bln"]
41 changes: 40 additions & 1 deletion construct/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1409,7 +1409,7 @@ def Int24sn():
@singleton
class VarInt(Construct):
r"""
VarInt encoded integer. Each 7 bits of the number are encoded in one byte of the stream, where leftmost bit (MSB) is unset when byte is terminal. Scheme is defined at Google site related to `Protocol Buffers <https://developers.google.com/protocol-buffers/docs/encoding>`_.
VarInt encoded unsigned integer. Each 7 bits of the number are encoded in one byte of the stream, where leftmost bit (MSB) is unset when byte is terminal. Scheme is defined at Google site related to `Protocol Buffers <https://developers.google.com/protocol-buffers/docs/encoding>`_.
Can only encode non-negative numbers.
Expand Down Expand Up @@ -1454,6 +1454,45 @@ def _emitprimitivetype(self, ksy, bitwise):
return "vlq_base128_le"


@singleton
class ZigZag(Construct):
r"""
ZigZag encoded signed integer. This is a variation of VarInt encoded that also can encode negative numbers. Scheme is defined at Google site related to `Protocol Buffers <https://developers.google.com/protocol-buffers/docs/encoding>`_.
Can encode negative numbers.
Parses into an integer. Builds from an integer. Size is undefined.
:raises StreamError: requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes
:raises IntegerError: given a negative value, or not an integer
Example::
>>> ZigZag.build(-3)
b'\x05'
>>> ZigZag.build(3)
b'\x06'
"""

def _parse(self, stream, context, path):
x = VarInt._parse(stream, context, path)
if x & 1 == 0:
x /= 2
else:
x = -(x//2+1)
return x

def _build(self, obj, stream, context, path):
if not isinstance(obj, integertypes):
raise IntegerError("value is not an integer", path=path)
if obj >= 0:
x = 2*obj
else:
x = 2*abs(obj)-1
VarInt._build(x, stream, context, path)
return obj


#===============================================================================
# strings
#===============================================================================
Expand Down
7 changes: 7 additions & 0 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ Integers can also be variable-length encoded for compactness. Google invented a
>>> VarInt.build(1234567890)
b'\xd2\x85\xd8\xcc\x04'

Signed integers can also be variable-lenght encoded using an encoding similar to VarInt. Also from Google:

>>> ZigZag.build(-3)
b'\x05'
>>> ZigZag.build(3)
b'\x06'

Long integers (or those of particularly odd sizes) can be encoded using a `BytesInteger`. Here is a 128-bit integer.

>>> BytesInteger(16).build(255)
Expand Down
1 change: 1 addition & 0 deletions docs/api/numerics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Core API: Integers and Floats
.. autofunction:: construct.BytesInteger
.. autofunction:: construct.BitsInteger
.. autofunction:: construct.VarInt
.. autofunction:: construct.ZigZag
2 changes: 2 additions & 0 deletions docs/transition210.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ build_file() opens a file for both reading and writing
BytesInteger BitsInteger can take lambda for swapped parameter

cloudpickle is now supported and tested for

ZigZag signed integer encoding from Protocol Buffers added
10 changes: 10 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ def test_varint_issue_705():
d = Struct('namelen' / VarInt, 'name' / Bytes(this.namelen))
d.build(Container(namelen = 400, name = bytes(400)))

def test_zigzag():
d = ZigZag
assert d.parse(b"\x05") == -3
assert d.parse(b"\x06") == 3
assert d.build(-3) == b"\x05"
assert d.build(3) == b"\x06"
assert raises(d.parse, b"") == StreamError
assert raises(d.build, None) == IntegerError
assert raises(d.sizeof) == SizeofError

def test_paddedstring():
common(PaddedString(10, "utf8"), b"hello\x00\x00\x00\x00\x00", u"hello", 10)

Expand Down

0 comments on commit 7e672c4

Please sign in to comment.