Skip to content

Commit 4cb3cd1

Browse files
committed
Enable packing and unpacking structures with specified gaps between fields
1 parent ce63b33 commit 4cb3cd1

File tree

2 files changed

+110
-4
lines changed

2 files changed

+110
-4
lines changed

src/StructIO.jl

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ end
7070

7171
@pure function packed_sizeof(T::DataType, ::Type{Packed})
7272
@assert fieldcount(T) != 0 && isbitstype(T)
73-
return sum(packed_sizeof, T.types)
73+
return sum(packed_sizeof, T.types) + sum(field_gaps(T))
7474
end
7575

7676
@pure function packed_sizeof(T::DataType)
@@ -103,7 +103,6 @@ Return the size (in bytes) of a field within `T` in memory
103103
end
104104
end
105105

106-
107106
"""
108107
@io <type definition>
109108
...
@@ -136,11 +135,20 @@ macro io(typ, annotations...)
136135
T = T.args[1]
137136
end
138137

138+
if alignment == :align_packed
139+
gap_values = strip_gap_nodes!(typ)
140+
end
141+
139142
ret = Expr(:toplevel, :(Base.@__doc__ $(typ)))
140143
strat = (alignment == :align_default ? StructIO.Default : StructIO.Packed)
141144
push!(ret.args, :($StructIO.packing_strategy(::Type{T}) where {T <: $T} = $strat))
145+
if strat == Packed
146+
push!(ret.args, :($StructIO.field_gaps(::Type{T}) where {T <: $T} = $gap_values))
147+
end
148+
142149
return esc(ret)
143150
end
151+
field_gaps(::Type{T}) where T = UInt[]
144152

145153
"""
146154
unsafe_unpack(io, T, target, endianness, ::Type{Default})
@@ -242,7 +250,7 @@ function unsafe_pack(io, source::Ref{T}, endianness, ::Type{Default}) where {T}
242250
end
243251

244252
# `Packed` packing strategy override for `unsafe_unpack`
245-
function unsafe_unpack(io, T, target, endianness, ::Type{Packed})
253+
function unsafe_unpack(io, T, target, endianness, ::Type{Packed}; gaps=field_gaps(T))
246254
# If this type cannot be subdivided, packing strategy means nothing, so
247255
# hand it off to the `Default` packing strategy method
248256
if fieldcount(T) == 0
@@ -255,12 +263,15 @@ function unsafe_unpack(io, T, target, endianness, ::Type{Packed})
255263
# Unpack this field into `target` at the appropriate offset
256264
fT = fieldtype(T, i)
257265
target_i = target_ptr + fieldoffset(T, i)
266+
isempty(gaps) || skip(io, gaps[i])
258267
unsafe_unpack(io, fT, target_i, endianness, Packed)
259268
end
260269
end
261270

262271
# `Packed` packing strategy override for `unsafe_pack`
263-
function unsafe_pack(io, source::Ref{T}, endianness, ::Type{Packed}) where {T}
272+
function unsafe_pack(
273+
io, source::Ref{T}, endianness, ::Type{Packed}; gaps=field_gaps(T)
274+
) where {T}
264275
# If this type cannot be subdivided, packing strategy means nothing, so
265276
# hand it off to the `Default` packing strategy method
266277
if fieldcount(T) == 0
@@ -272,6 +283,7 @@ function unsafe_pack(io, source::Ref{T}, endianness, ::Type{Packed}) where {T}
272283
# Unpack this field into `target` at the appropriate offset
273284
fT = fieldtype(T, i)
274285
f = Ref{fT}(getfield(source[], fieldname(T, i)))
286+
isempty(gaps) || skip(io, gaps[i])
275287
unsafe_pack(io, f, endianness, Packed)
276288
end
277289
end
@@ -311,4 +323,48 @@ function pack(io::IO, source::T, endianness::Symbol = :NativeEndian) where {T}
311323
return nothing
312324
end
313325

326+
function strip_gap_nodes!(strct_expr::Expr)
327+
@assert strct_expr.head == :struct
328+
strct_nodes = strct_expr.args[3].args
329+
K = length(strct_nodes)
330+
gap_locations = UInt[]
331+
# accumulator, in case several gap specifications occur in unbroken succession
332+
gap_value = zero(UInt)
333+
# gaps before structure fields
334+
field_gap_values = UInt[]
335+
for k in 1:K
336+
f = strct_nodes[k]
337+
!isa(f, LineNumberNode) || continue
338+
if f.head === :(::)
339+
# collect positions of gap nodes, when encountering a structure
340+
# field,
341+
if f.args[1] === :_
342+
if isa(f.args[2], Integer) && f.args[2] >= 0
343+
push!(gap_locations, k)
344+
gap_value += UInt(f.args[2])
345+
else
346+
error("incorrect gap specification: $f")
347+
end
348+
elseif isa(f.args[1], Symbol)
349+
350+
push!(field_gap_values, gap_value)
351+
gap_value = zero(UInt)
352+
else
353+
error("unsupported structure field specificaion: $f")
354+
end
355+
end
356+
end
357+
if all(iszero, field_gap_values)
358+
field_gap_values = UInt[]
359+
end
360+
# remove gap specifications from the expression tree
361+
for k in length(gap_locations):-1:1
362+
deleteat!(strct_nodes, gap_locations[k])
363+
end
364+
strct_expr.args[3].args = strct_nodes
365+
366+
return field_gap_values
367+
end
368+
369+
314370
end # module

test/runtests.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,32 @@ end
3232
y::T
3333
end align_packed
3434

35+
# Structs with gaps
36+
@io struct RawData
37+
A::UInt32
38+
dummy_1::UInt8
39+
dummy_2::UInt16
40+
B::UInt16
41+
dummy_3::UInt32
42+
C::UInt128
43+
dummy_4::UInt16
44+
dummy_5::UInt64
45+
D::UInt8
46+
end align_packed
47+
48+
@io struct GappedStruct
49+
A::UInt32
50+
_::1
51+
_::2
52+
B::UInt16
53+
_::4
54+
C::UInt128
55+
_::2
56+
_::8
57+
_D::UInt8
58+
end align_packed
59+
60+
3561
# Also test documenting a type
3662
"""
3763
This is a docstring
@@ -167,3 +193,27 @@ end
167193
@testset "Documentation" begin
168194
@test string(@doc ParametricType) == "This is a docstring\n"
169195
end
196+
197+
@testset "Struct with gaps" begin
198+
A,B,C,D = 1,2,3,4
199+
gapped_struct = GappedStruct(A, B, C, D)
200+
raw_data = RawData(UInt32(A),0xBE, 0xBEEF,UInt16(B),0xDEADBEEF,UInt128(C),
201+
0xBEEF, 0xDEADBEEFDEADBEEF, UInt8(D));
202+
unpacked_raw_data = RawData(UInt32(A),0x00, 0x0000,UInt16(B),0x00000000,UInt128(C),
203+
0x0000, 0x0000000000000000, UInt8(D));
204+
#
205+
@test packed_sizeof(RawData) == packed_sizeof(GappedStruct)
206+
for endian in [:LittleEndian, :BigEndian]
207+
buf = IOBuffer()
208+
pack(buf, raw_data, endian)
209+
seekstart(buf)
210+
@test unpack(buf, GappedStruct, endian) == gapped_struct
211+
#
212+
buf = IOBuffer(zeros(UInt8, packed_sizeof(GappedStruct)), read=true, write=true)
213+
pack(buf, gapped_struct, endian)
214+
seekstart(buf)
215+
@test unpack(buf, GappedStruct, endian) == gapped_struct
216+
seekstart(buf)
217+
@test unpack(buf, RawData, endian) == unpacked_raw_data
218+
end
219+
end

0 commit comments

Comments
 (0)