Cの構造体とかだと、構造体の中に他の構造体ということは普通にあります。
こういった構造体の文字列を unpack すると、全部フラットな配列になってしまうので、Ruby レベルのオブジェクト構造として再構成しようと思うと面倒なことになります。
ということで、入れ子に対応した unpack というのを実装してみました。
概要
以下のような感じで {} (ブレース) で入れ子を表現するようにテンプレート文字列を拡張します。
struct_foo_t = %{
I!
I!
Z16
}
struct_bar_t = %{
I!
I!
{
#{struct_foo_t}
}
I!
} #=> "I!I!{I!I!Z16}I!"
original = [
0xffff,
0xfeff,
[
0x11,
0x22,
"foobar"
],
0x10,
]
packed = original.pack_deeply(struct_bar_t)
p packed
#=> "\xFF\xFF\x00\x00\xFF\xFE\x00\x00\x11\x00\x00\x00\"\x00\x00\x00foobar\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00"
unpacked = packed.unpack_deeply(struct_bar_t)
p unpacked
#=> [65535, 65279, [17, 34, "foobar"], 16]
p original == unpacked
#=> true
実装
先頭から unpack して入れ子を処理していくというイメージです。処理済みのバイト長が欲しいので、クソみたいですが一旦 unpack したものをもう一度 pack しています。
pack_deeply は単にフラットにして pack するだけなので簡単です。
class String
def unpack_deeply(template)
ret = []
tree = [ ret ]
n = 0
tmpl = ""
# unpack partial and add to result
unpack = lambda {
unpacked = self.slice(n..-1).unpack(tmpl)
# re-pack to get byte length
size = unpacked.pack(tmpl).size
n += size
tmpl = ""
tree.last.concat(unpacked)
}
template.each_char do |chr|
case chr
when "{"
unpack.call
ary = []
tree.last << ary
tree << ary
when "}"
unpack.call
tree.pop
else
tmpl << chr
end
end
unpack.call
ret
end
end
class Array
def pack_deeply(template)
flatten.pack(template.gsub(/[{}]/, ''))
end
end