Ruby でネストした構造体文字列をネストした配列として unpack する
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 関連エントリー
- Ruby で IO#ioctl の引数に構造体 (struct) へのポインタを渡したいとき ioctl に構造体のポインタを渡して値を返してもらうような場合があると思います。 このような場合、文字列つくってそのまま渡せば struc...
- ruby-serialport で任意のボーレートを設定するには (ただし Linux に限る) ruby serialport は、このあたりで設定できるボーレートを列挙していて、これら以外のボーレートを設定できないようです (unkn...
- スマートメータから瞬間消費電力を読むRubyのコード スマートメータのBルートサービスで Wi-SUN モジュールを使って瞬間消費電力を読み出す | tech - 氾濫原 にひき続き Wi-SU...
- Ruby の pack テンプレート文字列からそのデータサイズを求める pack のテンプレート文字列から、それを使ってパックした結果のサイズを求めたいということはありませんか。つまりやりたいことは sizeof...
- NiZ Keyboard PLUM からキーマップや打鍵回数を読み出す NiZ のアプリケーションは Windows 向けしかない。プロトコルが気になったので、とりあえず打鍵回数を読み出すところをまでをやってみた...