Skip to content

Commit 103fc39

Browse files
in-code-i-trustslozier
authored andcommitted
Add support for struct.iter_unpack (#729)
* Add support for struct.iter_unpack * Removed dependency on n/N formats from iter_unpack test. * Hide the .NET members from Python layer. * Marked appropriate fields as readonly. * Added validation for given buffer length.
1 parent 65881f0 commit 103fc39

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

Src/IronPython.Modules/_struct.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,15 @@ public void pack_into(CodeContext/*!*/ context, [NotNull]ByteArray/*!*/ buffer,
373373
return unpack_from(context, buffer.ToByteArray(), offset);
374374
}
375375

376+
[Documentation("iteratively unpack the current format from the specified array.")]
377+
public PythonUnpackIterator iter_unpack(CodeContext/*!*/ context, [BytesConversion][NotNull]IList<byte>/*!*/ buffer, int offset = 0) {
378+
return new PythonUnpackIterator(this, context, buffer, offset);
379+
}
380+
381+
[Documentation("iteratively unpack the current format from the specified array.")]
382+
public PythonUnpackIterator iter_unpack(CodeContext/*!*/ context, [NotNull]ArrayModule.array/*!*/ buffer, int offset = 0) {
383+
return new PythonUnpackIterator(this, context, buffer, offset);
384+
}
376385

377386
[Documentation("gets the number of bytes that the serialized string will occupy or are required to deserialize the data")]
378387
public int size {
@@ -582,6 +591,88 @@ internal static Struct Create(string/*!*/ format) {
582591
#endregion
583592
}
584593

594+
[PythonType("unpack_iterator"), Documentation("Represents an iterator returned by _struct.iter_unpack()")]
595+
public class PythonUnpackIterator : System.Collections.IEnumerator, System.Collections.IEnumerable {
596+
private object _iter_current;
597+
private int _next_offset;
598+
599+
private readonly CodeContext _context;
600+
private readonly IList<byte> _buffer;
601+
private readonly int _start_offset;
602+
private readonly Struct _owner;
603+
604+
private PythonUnpackIterator() { }
605+
606+
internal PythonUnpackIterator(Struct/*!*/ owner, CodeContext/*!*/ context, IList<byte>/*!*/ buffer, int offset) {
607+
_context = context;
608+
_buffer = buffer;
609+
_start_offset = offset;
610+
_owner = owner;
611+
612+
Reset();
613+
ValidateBufferLength();
614+
}
615+
616+
internal PythonUnpackIterator(Struct/*!*/ owner, CodeContext/*!*/ context, ArrayModule.array/*!*/ buffer, int offset) {
617+
_context = context;
618+
_buffer = buffer.ToByteArray();
619+
_start_offset = offset;
620+
_owner = owner;
621+
622+
Reset();
623+
ValidateBufferLength();
624+
}
625+
626+
private void ValidateBufferLength() {
627+
if (_buffer.Count - _start_offset < _owner.size) {
628+
throw Error(_context, $"iterative unpacking requires a buffer of a multiple of {_owner.size} bytes");
629+
}
630+
}
631+
632+
#region IEnumerable
633+
[PythonHidden]
634+
public System.Collections.IEnumerator GetEnumerator() {
635+
return this;
636+
}
637+
#endregion
638+
639+
#region IEnumerator
640+
[PythonHidden]
641+
public object Current => _iter_current;
642+
643+
[PythonHidden]
644+
public bool MoveNext() {
645+
if (_buffer.Count - _next_offset < _owner.size) {
646+
return false;
647+
}
648+
649+
_iter_current = _owner.unpack_from(_context, _buffer, _next_offset);
650+
_next_offset += _owner.size;
651+
return true;
652+
}
653+
654+
[PythonHidden]
655+
public void Reset() {
656+
_iter_current = null;
657+
_next_offset = _start_offset;
658+
}
659+
#endregion
660+
661+
public object __iter__() {
662+
return this;
663+
}
664+
665+
public object __next__() {
666+
if (!MoveNext()) {
667+
throw PythonOps.StopIteration();
668+
}
669+
return Current;
670+
}
671+
672+
public int __length_hint__() {
673+
return (_buffer.Count - _next_offset) / _owner.size;
674+
}
675+
}
585676
#endregion
586677

587678
#region Compiled Format
@@ -738,6 +829,15 @@ public static void pack_into(CodeContext/*!*/ context, object fmt, [NotNull]Byte
738829
return GetStructFromCache(context, fmt).unpack_from(context, buffer, offset);
739830
}
740831

832+
[Documentation("Iteratively unpack the buffer, containing packed C structure data, according to\nfmt, starting at offset. Requires len(buffer[offset:]) >= calcsize(fmt).")]
833+
public static PythonUnpackIterator/*!*/ iter_unpack(CodeContext/*!*/ context, object fmt, [BytesConversion][NotNull]IList<byte>/*!*/ buffer, int offset = 0) {
834+
return GetStructFromCache(context, fmt).iter_unpack(context, buffer, offset);
835+
}
836+
837+
[Documentation("Iteratively unpack the buffer, containing packed C structure data, according to\nfmt, starting at offset. Requires len(buffer[offset:]) >= calcsize(fmt).")]
838+
public static PythonUnpackIterator/*!*/ iter_unpack(CodeContext/*!*/ context, object fmt, [NotNull]ArrayModule.array/*!*/ buffer, int offset = 0) {
839+
return GetStructFromCache(context, fmt).iter_unpack(context, buffer, offset);
840+
}
741841
#endregion
742842

743843
#region Write Helpers

Tests/test_struct.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,22 @@ def assertStructError(func, *args, **kwargs):
7979
assertStructError(struct.pack, format, 0)
8080
assertStructError(struct.unpack, format, b"")
8181

82+
def test_iter_unpack(self):
83+
import operator
84+
85+
packed = struct.pack('hlhlhl', 1, 2, 3, 4, 5, 6)
86+
it = struct.iter_unpack('hl', packed)
87+
88+
self.assertEqual(operator.length_hint(it), 3)
89+
self.assertEqual(it.__next__(), (1, 2))
90+
self.assertEqual(operator.length_hint(it), 2)
91+
self.assertEqual(it.__next__(), (3, 4))
92+
self.assertEqual(operator.length_hint(it), 1)
93+
self.assertEqual(it.__next__(), (5, 6))
94+
self.assertEqual(operator.length_hint(it), 0)
95+
self.assertRaises(StopIteration, next, it)
96+
97+
# struct.error: iterative unpacking requires a buffer of a multiple of {N} bytes
98+
self.assertRaises(struct.error, struct.iter_unpack, "h", b"\0")
99+
82100
run_test(__name__)

0 commit comments

Comments
 (0)