2011年10月3日月曜日

IO#unpack!

String#unpack!の続き。 普通はStringが処理できれば問題ないんだけど、時にはIO(と言うかファイル)を直接パースしたい時がある。特にファイルだとサイズが巨大で、事前に全部読み込んでおくのが現実的でないこともあるし。
ここで問題になるのが、pack文字列の中には読んでみないとデータ長が確定しない場合があること。なので、事前にpack文字列をパースして、判るならば読み込み長を事前に計算する実装にしてみた。以下ソース。
class IO
def unpack!(ptn)
ptn2 = ptn.dup
until ptn2.empty?
len += case ptn2
when /\*/
len = nil
break
when /^[aAcCx](\d+)/
$1 ? $1.to_i : 1
when /^[sSnv](\d+)/
($1 ? $1.to_i : 1) * 2
when /^[iIlLNVfegPp](\d+)/
($1 ? $1.to_i : 1) * 4
when /^[qQdEG](\d+)/
($1 ? $1.to_i : 1) * 8
else
len = nil
break
end
ptn.gsub(/^#{$&}/,'')
end
pos = self.tell if len.nil?
ret = read(len).unpack(ptn)
if len.nil?
pos += ret.pack(ptn).size
self.pos = pos
ret
end
end
end
# use like this
open(ARGV[0]) do |f|
p f.unpack!('csi')
p f.tell
end
view raw gistfile1.rb hosted with ❤ by GitHub
長さがわからなったら、packして実際のデータ長を求め直してからseekするという富豪設計。ちなみにSTDINとかのtellやseekがエラーになるストリームでは使えないので注意。

本来unpackで消費するバイト数が取得できたり、unpack時に1バイト取ってくる動作をカスタマイズできたりすれば、こんなゴマカシは必要ないわけで。この辺はRubyの入出力周り、とくにIOが安易にunixのファイルに準拠した仕様になっている弊害だと思う。

0 件のコメント:

コメントを投稿