gogyou 0.1.240911.prototype → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,106 @@
1
+
2
+ require "rake/clean"
3
+
4
+ DOC = FileList["{README,LICENSE,CHANGELOG,Changelog}{,.ja}{,.txt,.rd,.rdoc,.md,.markdown}"] +
5
+ FileList["ext/**/{README,LICENSE,CHANGELOG,Changelog}{,.ja}{,.txt,.rd,.rdoc,.md,.markdown}"]
6
+ #EXT = FileList["ext/**/*.{h,hh,c,cc,cpp,cxx}"] +
7
+ # FileList["ext/externals/**/*"]
8
+ EXT = FileList["ext/**/*"]
9
+ BIN = FileList["bin/*"]
10
+ LIB = FileList["lib/**/*.rb"]
11
+ SPEC = FileList["spec/**/*"]
12
+ EXAMPLE = FileList["examples/**/*"]
13
+ RAKEFILE = [File.basename(__FILE__), "gemstub.rb"]
14
+ EXTRA = []
15
+
16
+ load "gemstub.rb"
17
+
18
+ EXTCONF = FileList["ext/extconf.rb"]
19
+ EXTCONF.reject! { |n| !File.file?(n) }
20
+ GEMSTUB.extensions += EXTCONF
21
+ GEMSTUB.executables += FileList["bin/*"].map { |n| File.basename n }
22
+
23
+ GEMFILE = "#{GEMSTUB.name}-#{GEMSTUB.version}.gem"
24
+ GEMSPEC = "#{GEMSTUB.name}.gemspec"
25
+
26
+ GEMSTUB.files += DOC + EXT + EXTCONF + BIN + LIB + SPEC + EXAMPLE + RAKEFILE + EXTRA
27
+ GEMSTUB.rdoc_options ||= %w(--charset UTF-8)
28
+ GEMSTUB.extra_rdoc_files += DOC + LIB + EXT.reject { |n| n.include?("/externals/") || !%w(.h .hh .c .cc .cpp .cxx).include?(File.extname(n)) }
29
+
30
+ CLEAN << GEMSPEC
31
+ CLOBBER << GEMFILE
32
+
33
+ task :default => :all
34
+
35
+ task :all => GEMFILE
36
+
37
+ task :rdoc => DOC + EXT + LIB do
38
+ sh *(%w(rdoc) + GEMSTUB.rdoc_options + DOC + EXT + LIB)
39
+ end
40
+
41
+ file GEMFILE => DOC + EXT + EXTCONF + BIN + LIB + SPEC + EXAMPLE + RAKEFILE + [GEMSPEC] do
42
+ sh "gem build #{GEMSPEC}"
43
+ end
44
+
45
+ file GEMSPEC => RAKEFILE do
46
+ File.write(GEMSPEC, GEMSTUB.to_ruby, mode: "wb")
47
+ end
48
+
49
+
50
+ RUBYSET ||= nil
51
+
52
+ if RUBYSET && !RUBYSET.empty? && !EXTCONF.empty?
53
+ RUBY_VERSIONS = RUBYSET.map do |ruby|
54
+ ver = `#{ruby} --disable gem -rrbconfig -e "puts RbConfig::CONFIG['ruby_version']"`.chomp
55
+ raise "failed ruby checking - ``#{ruby}''" unless $?.success?
56
+ [ver, ruby]
57
+ end
58
+ SOFILES_SET = RUBY_VERSIONS.map { |(ver, ruby)| ["lib/#{ver}/#{GEMSTUB.name}.so", ruby] }
59
+ SOFILES = SOFILES_SET.map { |(lib, ruby)| lib }
60
+ platforms = RUBYSET.map { |ruby| `#{ruby} -rubygems -e "puts Gem::Platform.local.to_s"`.chomp }
61
+ platforms.uniq!
62
+ platforms.compact!
63
+ unless platforms.size == 1
64
+ raise "wrong platforms (#{RUBYSET.inspect} => #{platforms.inspect})"
65
+ end
66
+
67
+ GEMSTUB_NATIVE = GEMSTUB.dup
68
+ GEMSTUB_NATIVE.files += SOFILES
69
+ GEMSTUB_NATIVE.platform = platforms[0]
70
+ GEMFILE_NATIVE = "#{GEMSTUB_NATIVE.name}-#{GEMSTUB_NATIVE.version}-#{GEMSTUB_NATIVE.platform}.gem"
71
+ GEMSPEC_NATIVE = "#{GEMSTUB_NATIVE.name}-#{GEMSTUB_NATIVE.platform}.gemspec"
72
+
73
+ task :all => [GEMFILE, :native]
74
+
75
+ task :native => GEMFILE_NATIVE
76
+
77
+ file GEMFILE_NATIVE => DOC + EXT + [EXTCONF] + BIN + LIB + SPEC + EXAMPLE + SOFILES + RAKEFILE + [GEMSPEC_NATIVE] do
78
+ sh "gem build #{GEMSPEC_NATIVE}"
79
+ end
80
+
81
+ file GEMSPEC_NATIVE => __FILE__ do
82
+ File.write(GEMSPEC_NATIVE, GEMSTUB_NATIVE.to_ruby, mode: "wb")
83
+ end
84
+
85
+ SOFILES_SET.each do |(soname, ruby)|
86
+ sodir = File.dirname(soname)
87
+ makefile = File.join(sodir, "Makefile")
88
+
89
+ CLEAN << GEMSPEC_NATIVE << sodir
90
+ CLOBBER << GEMFILE_NATIVE
91
+
92
+ directory sodir
93
+
94
+ file soname => [makefile] + EXT do
95
+ cd sodir do
96
+ sh "make"
97
+ end
98
+ end
99
+
100
+ file makefile => [sodir] + [EXTCONF] do
101
+ cd sodir do
102
+ sh "#{ruby} ../../#{EXTCONF} \"--ruby=#{ruby}\""
103
+ end
104
+ end
105
+ end
106
+ end
data/gemstub.rb ADDED
@@ -0,0 +1,40 @@
1
+
2
+ # "lib/gogyou/primitives.rb" がない場合は、読み込みを遅らせる。Gogyou::VERSION の参照も後回し。
3
+ hasprims = File.file?(File.join(File.dirname(__FILE__), "lib", "gogyou", "primitives.rb"))
4
+ require_relative "lib/gogyou" if hasprims
5
+
6
+ GEMSTUB = Gem::Specification.new do |s|
7
+ s.name = "gogyou"
8
+ s.version = Gogyou::VERSION if hasprims
9
+ s.summary = "binary data operation library with the C liked struct and union"
10
+ s.description = <<EOS
11
+ The gogyou is a library that provides auxiliary features of binary data operation for ruby.
12
+
13
+ The C-liked struct, union and multidimensional array definition are posible in ruby syntax.
14
+
15
+ * Usable nested struct.
16
+ * Usable nested union.
17
+ * Usable multidimensional array.
18
+ * Usable user definition types.
19
+ EOS
20
+ s.license = "2-clause BSD License"
21
+ s.author = "dearblue"
22
+ s.email = "dearblue@users.sourceforge.jp"
23
+ #s.author = "**PRIVATE**"
24
+ #s.email = "**PRIVATE**"
25
+ s.homepage = "http://sourceforge.jp/projects/rutsubo/"
26
+
27
+ s.required_ruby_version = ">= 2.0"
28
+ s.add_development_dependency "rspec", "~> 2.14"
29
+ s.add_development_dependency "rake", "~> 10.0"
30
+ end
31
+
32
+ LIB << "lib/gogyou/primitives.rb"
33
+ EXTRA << "mkprims.rb"
34
+ CLEAN << "lib/gogyou/primitives.rb"
35
+
36
+ file "lib/gogyou/primitives.rb" => "mkprims.rb" do
37
+ sh "ruby mkprims.rb"
38
+ require_relative "lib/gogyou"
39
+ GEMSTUB.version = Gogyou::VERSION
40
+ end
data/lib/gogyou.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  #vim: set fileencoding:utf-8
2
2
 
3
3
  # gogyou.rb
4
- # - AUTHOR: dearblue <dearblue@sourceforge.jp>
4
+ # - AUTHOR: dearblue <dearblue@users.sourceforge.jp>
5
5
  # - WEBSIZE: http://sourceforge.jp/projects/rutsubo/
6
6
  # - LICENSE: same as 2-clause BSD License
7
7
 
@@ -19,612 +19,283 @@
19
19
  # #
20
20
  #++
21
21
 
22
- # gogyou は ruby 向けに作成され、バイナリデータと構造体との変換を容易にするためのライブラリです。名前は春の七種の一つ『ごぎょう』からとりました。
23
22
  #
24
- # ruby に組み込まれている String#unpack / Array#pack は Struct クラスとの関係性はまったくありません。対応付けをするのも自力で書く必要があり、パックフォーマットと変数の関係性の直接的な結びつきがないため視認性に乏しいと言わざるをえません。
25
- #
26
- # gogyou ライブラリはこの負担を軽減することとともに、視認性の向上を目的に作成されました。
27
- #
28
- # ただし、rdoc の適用対象とは (やっぱり) なりません。必要であれば定数定義の前にコメントを入力するか、サブクラスを定義してそこで rdoc を記述するのがいいでしょう。
29
- #
30
- # == 基本
31
- #
32
- # gogyou ライブラリを用いることによって以下のように記述できます。
33
- #
34
- # require "gogyou"
35
- #
36
- # Sample = Gogyou.struct do
37
- # uint32_t :data1 # C の uint32_t (32ビット無符号整数値) を定義
38
- # int16_t :data2, 2 # C の int16_t (16ビット符号付き整数値) を要素数 2 で定義
39
- # uint64_t :data3 # C の uint64_t (64ビット無符号整数値) を定義
40
- # uint8_t :data4, 2 # C の uint8_t (8ビット無符号整数値) を要素数 2 で定義
41
- # char :data5, 2 # C の char (8ビット符号付き整数値) を要素数 2 で定義
42
- # ustring :data6, 4 # NULL 終端の UTF-8 文字列を定義 / 文字列長は 4 / 要素数 1
43
- # binary :data7, 8 # ASCII-8BIT 文字列を定義 / 文字列長は 8 / 要素数 1
44
- # end
45
- #
46
- # p Sample::PACK_FORMAT # => "I s2 Q C2 c2 Z4 a8" # 実際には空白は含まれません
47
- # p Sample::SIZE # => 32
48
- #
49
- # Sample クラスは Struct クラスのインスタンス (ruby から見ればクラスオブジェクト) で、PACK_FORMAT、SIZE の定数が定義され、.unpack / .pack メソッドが定義されます。
50
- #
51
- # つまり上記の例であれば、
52
- #
53
- # sample = Sample.unpack(data_sequence)
54
- # data_sequence = sample_data.pack
55
- #
56
- # というようなメソッド呼び出しができる Sample クラスが定義されることになります。
57
- #
58
- # == Gogyou.struct ブロック内は self コンテキストが切り替わる
59
- #
60
- # Gogyou.struct を呼び出すときに渡すブロックは、そのブロック内での self コンテキストが切り替わります。
61
- #
62
- # ブロックの外で呼び出せるメソッドがブロック内で呼び出せないもしくは意図しない結果になる場合は、このことが原因と見ると対策しやすいかもしれません。
63
- #
64
- # こんな (一見横暴な) 仕様になっているのは、ブロックパラメータの受け取りと型名を記述する際の変数の記述という煩わしさから開放するという目的を達成させるためです。
65
- #
66
- # 実装としては Gogyou.struct メソッド内で instance_eval にブロックをそのまま渡しています。
67
- #
68
- # == 各要素は境界上に配置される
69
- #
70
- # それぞれの要素のバイトオフセットは C 言語の構造体に倣って、各要素の境界上に配置されます。
71
- #
72
- # Sample = Gogyou.struct do
73
- # uint8_t :data1 # 0000h に配置。幅は1。
74
- # uint16_t :data2 # 0002h に配置。幅は2。
75
- # uint8_t :data3 # 0004h に配置。幅は1。
76
- # uint32_t :data3 # 0008h に配置。幅は4。
77
- # uint64_t :data3 # 0010h に配置。幅は8。
78
- # end
79
- #
80
- # p Sample::PACK_FORMAT # => "C x S C x3 L x4 Q"
81
- #
82
- # == 入れ子になった構造体
83
- #
84
- # 入れ子の構造体表記も可能です。入れ子の入れ子のさらに・・・みたいなやつも記述可能ですが、展開がその分遅くなります。
85
- #
86
- # Sample = Gogyou.struct do
87
- # struct :ref1 do # 入れ子の構造体。名無しクラス (Struct を祖に持つクラス) を返します。
88
- # uint32_t :data1 # ref1.data1 で参照できます。
89
- # uint32_t :data2
90
- # end
91
- #
92
- # int :data3
93
- # end
94
- #
95
- # p Sample::PACK_FORMAT # => "a8 i"
96
- #
97
- # == エンディアン指定
98
- #
99
- # エンディアン指定も可能です。サフィックスが『_t』で終わるやつは基本的に『_le』でリトルエンディアン、『_be』かサフィックスなしでビッグエンディアンになります。
100
- #
101
- # Sample = Gogyou.struct do
102
- # uint32_t :data1 # 動作環境に依存。
103
- # uint32 :data2 # 常にビッグエンディアン (ネットワークエンディアン) / パックフォーマットでは『N』と同等。
104
- # uint32_be :data3 # uint32 と同じ。
105
- # uint32_le :data4 # 常にリトルエンディアン (バックスエンディアン) / パックフォーマットでは『V』と同等。
106
- # end
107
- #
108
- # p Sample::PACK_FORMAT # => "L N N V" # 実際は環境によって変わってきます
109
- #
110
- # == 境界上に配置されない要素
111
- #
112
- # 型名の後ろに感嘆符『!』をつけるとその型の境界上に配置されるのを抑制します。GCC でいうところの "__attribute__((__packed__))" が付いている構造体です。
113
- #
114
- # Sample = Gogyou.struct do
115
- # uint8_t! :data1, 3 # 0000h に配置。幅は1。
116
- # uint16_t! :data2, 3 # 0003h に配置。幅は2。
117
- # uint32_t! :data3, 3 # 0009h に配置。幅は4。
118
- # end
119
- #
120
- # p Sample::PACK_FORMAT # => "C3 S3 L3"
121
- # p Sample::SIZE # => 21
122
- #
123
- # == 境界指定
124
- #
125
- # "alignment" (短縮して "align" も利用可) で、次の要素の配置が指定境界に来るようになります。ただし要素自体の境界配置が無効化さるわけではないため、そのことも含めて強制させるためには『!』を用いる必要があります。
126
- #
127
- # Sample = Gogyou.struct do
128
- # uint8_t :data1 # 0000h に配置。
129
- # alignment 16 # 穴埋め。次の要素は0010hに配置される。
130
- # uint32_t :data2 # 0010h に配置。
131
- # end
132
- #
133
- # p Sample::PACK_FORMAT # => "C x15 L"
134
- #
135
- # == 任意幅の穴埋め
136
- #
137
- # "padding" で、任意幅の穴埋めができます。
138
- #
139
- # Sample = Gogyou.struct do
140
- # uint8_t :data1 # 0000h に配置。
141
- # padding 8 # 穴埋め。次の要素は 0009h に配置される。
142
- # uint32_t :data2 # 4バイト境界上の 000Ch に配置。
143
- # end
144
- #
145
- # p Sample::PACK_FORMAT # => "C x8 x3 L"
146
- #
147
- # == 型の別名定義
148
- #
149
- # "typedef" で、C でいう型の別名が定義できます。
150
- #
151
- # Sample = Gogyou.struct do
152
- # typedef :uint, :HANDLE # 以降 HANDLE を使うと uint を指定したことになる
153
- #
154
- # uint8_t :data1, 3
155
- # HANDLE :data2
156
- # end
157
- #
158
- # p Sample::PACK_FORMAT # => "C3 x I"
159
- #
160
- # 入れ子定義内でも親で定義した typedef は有効です。
161
- #
162
- # Sample = Gogyou.struct do
163
- # typedef :uint, :HANDLE
164
- #
165
- # HANDLE :data1
166
- # struct :dataset do
167
- # HANDLE :data1 # この HANDLE は親スコープで typedef したものが継承されたものです。
168
- # HANDLE :data2
169
- # end
23
+ # gogyou は構造体や共用体、多次元配列 (もどき) を扱うためのライブラリです。
24
+ #
25
+ # 原始的な型情報は Gogyou::Primitives で定義してあり、struct や union メソッド内で利用できる型を次の表に示します:
26
+ #
27
+ # 符号あり 符号なし
28
+ # ---- ----
29
+ # 8ビット整数型 char uchar
30
+ # unsigned_char
31
+ # 16ビット整数型 short ushort
32
+ # unsigned_short
33
+ # 32ビット整数型 int uint
34
+ # unsigned_int
35
+ # 環境依存32/64ビット整数型 long ulong
36
+ # unsigned_long
37
+ # 64ビット整数型 longlong ulonglong
38
+ # long_long unsigned_long_long
39
+ # 32ビット浮動少数型 float
40
+ # 64ビット浮動少数型 double
41
+ # sizeof 表現型 ssize_t size_t
42
+ # ポインタ整数型 intptr_t uintptr_t
43
+ #
44
+ # *** ビット数環境非依存 ***
45
+ #
46
+ # バイトオーダー環境依存 バイトオーダー反転
47
+ # 符号あり 符号なし 符号あり 符号なし
48
+ # ---- ---- ---- ----
49
+ # 8ビット整数型 int8_t uint8_t // //
50
+ # 16ビット整数型 int16_t uint16_t int16_swap uint16_swap
51
+ # 24ビット整数型 int24_t uint24_t int24_swap uint24_swap
52
+ # 32ビット整数型 int32_t uint32_t int32_swap uint32_swap
53
+ # 48ビット整数型 int48_t uint48_t int48_swap uint48_swap
54
+ # 64ビット整数型 int64_t uint64_t int64_swap uint64_swap
55
+ #
56
+ # ビッグエンディアン リトルエンディアン
57
+ # 符号あり 符号なし 符号あり 符号なし
58
+ # ---- ---- ---- ----
59
+ # 16ビット整数型 int16_be uint16_be int16_le uint16_le
60
+ # 24ビット整数型 int24_be uint24_be int24_le uint24_le
61
+ # 32ビット整数型 int32_be uint32_be int32_le uint32_le
62
+ # 48ビット整数型 int48_be uint48_be int48_le uint48_le
63
+ # 64ビット整数型 int64_be uint64_be int64_le uint64_le
64
+ #
65
+ # ビッグエンディアン リトルエンディアン バイトオーダー反転
66
+ # 32ビット浮動少数型 float_be float_le float_swap
67
+ # 64ビット浮動少数型 double_be double_le double_swap
68
+ #
69
+ #
70
+ # ==== 利用者定義の型情報
71
+ #
72
+ # 型情報を利用者が定義して利用することが出来ます。
73
+ #
74
+ # 型情報オブジェクトは、次のメソッドを必要とします:
75
+ #
76
+ # * bytesize - 型のバイト数です。拡張要素を含んでいる場合は、最小となるバイト数です。
77
+ # * bytealign - 型のバイト位置境界です。uint32_t であれば、通常は 4バイトです。
78
+ # * aset(buffer, offset, value) - バッファに値を埋め込みます。
79
+ # * aref(buffer, offset) - バッファから値を取り出します。
80
+ # * extensible? - 型自身が拡張要素、または拡張要素が含まれているかの有無です。<tt>int a[0]</tt> のような可変個配列などの場合が当てはまります。
81
+ #
82
+ # 利用者定義の型情報は、struct / union / typedef メソッドの引数として与えることが出来ます。
83
+ #
84
+ #
85
+ # ==== example (ruby.h から struct RBasic と struct RString を模倣した場合)
86
+ #
87
+ # ポインタ型は実現できていないため、intptr_t で代用しています。
88
+ #
89
+ # module MyRuby
90
+ # extend Gogyou
91
+ #
92
+ # typedef :uintptr_t, :VALUE
93
+ #
94
+ # RBasic = struct {
95
+ # VALUE :flags
96
+ # VALUE :klass
97
+ # }
98
+ #
99
+ # RString = struct {
100
+ # RBasic :basic
101
+ # union -> {
102
+ # struct -> {
103
+ # long :len
104
+ # intptr_t :ptr
105
+ # union -> {
106
+ # long :capa
107
+ # VALUE :shared
108
+ # }, :aux
109
+ # }, :heap
110
+ # char :ary, RSTRING_EMBED_LEN_MAX + 1
111
+ # }, :as
112
+ # }
170
113
  # end
171
114
  #
172
- # また任意のクラスを指定することで外部クラスをブロック内で定義することができるようになります。
173
115
  #
174
- # Sample = Gogyou.struct do
175
- # typedef MD5 :MD5 # 以降 MD5 で MD5 クラスが展開と格納を行う
116
+ # ==== "gogyou" の処理の分類とクラスの役割
176
117
  #
177
- # MD5 :md5
178
- # end
179
- #
180
- # この MD5 クラス (モジュールでも可) は、pack / unpack メソッドを持ち、SIZE 定数 (バイト数。MD5 なので 16) を持つ必要があります。
181
- #
182
- # class MD5
183
- # SIZE = 16
184
- #
185
- # def self.pack(obj)
186
- # ...
187
- # # string オブジェクトを返さなければならない
188
- # # bytesize は SIZE と等しくなければならない
189
- # end
118
+ # * 原始的な型情報の管理と登録
119
+ # * Primitives - 原始的な型情報
120
+ # * Model::TYPEMAP (hash) - 構造体構築時に利用できる、型名の登録
121
+ # * 型情報オブジェクト - 型の情報を保持するオブジェクト
190
122
  #
191
- # def self.unpack(str)
192
- # # str は SIZE 定数で指定した分量のバイナリ文字列
193
- # ...
194
- # # 展開したオブジェクトを返す
195
- # end
196
- # end
123
+ # Primitives 内の定数として定義されているオブジェクトや、Accessor のサブクラスなどが当てはまります。
197
124
  #
198
- # == 型名一覧
125
+ # 利用者定義の任意のオブジェクト (クラスやモジュールも含まれる) も、利用できます。
199
126
  #
200
- # それぞれ正規表現に沿った見方をしてください。。。ちょーみづれー
127
+ # 利用者定義の型情報オブジェクトについては、README を参照してください。
128
+ # * 構造体構築
129
+ # * Model - 構造体・共用体の定義時にフィールド並びを管理するためのクラス
201
130
  #
202
- # - バイナリ列: binary
203
- # - UTF-8 文字列: ustring
204
- # - 整数 (環境依存 : ビット数、エンディアン): u?char, u?short!?, u?int!?, u?long!?, u?longlong!?
205
- # - 整数 (環境依存 : ビット数、エンディアン): s?size_t!?, u?intptr_t!?
206
- # - 整数 (環境依存 : エンディアン): u?int16_t!?, u?int32_t!?, u?int64_t!?
207
- # - 浮動少数 (環境依存 : ビット数、エンディアン): float!?, double!?
208
- # - 浮動少数 (環境非依存): float(_be|_le)!?, double(_be|_le)!?
209
- # - 整数 (環境非依存): u?int8(_t)?, u?int16(_be|_le)?!?, u?int32(_be|_le)?!?, u?int64(_be|_le)?!?
131
+ # 利用者が直接扱う必要はありません。
132
+ # * 構造体の実体の管理と参照・操作手段の提供
133
+ # * Accessor - 構造体・共用体・配列を定義したあとの各クラスの親クラス
210
134
  #
211
- # == 実行速度面では不利
135
+ # 次のインスタンスメソッドが定義されます。
212
136
  #
213
- # 構造体定義は結構重い処理 (数ミリ秒) になっています。しかし、アプリケーション初期化時において各構造体ごとに一度だけ処理されるため、気になることはあまりないと思います。
137
+ # * #size - フィールドの要素数。配列の場合はその要素数。
138
+ # * #bytesize - バイトサイズを返す。可変長配列を含んでいる場合は、現在の buffer と offset から計算された最大値を返す。
139
+ # * #\<field> / #\<field>= - 構造体・共用体のフィールドへの参照・代入メソッド。配列の場合は定義されない。
140
+ # * #[] / []= - 配列の要素への参照・代入メソッド。構造体・共用体の場合は定義されない。
214
141
  #
215
- # そしてやはり pack / unpack は自力で記述した場合と比べれば重くなりますが、記述の容易さ・可読性に免じて目を瞑ってください。
216
-
217
142
  module Gogyou
218
- Gogyou = self
219
-
220
- # このメソッドにブロックつきで呼び出すことによって、Struct の構築と、Array#pack および String#unpack を用いてデータの詰め込みと展開を行える機能を持ったクラスを構築することができます。
221
- #
222
- # 詳しい説明は Gogyou モジュールに記述してあります。
223
- def self.struct(&block)
224
- farm = StructFarm.new
225
- farm.instance_eval(&block)
226
- define_struct(farm)
143
+ Gogyou = self
144
+ VERSION = Gem::Version.new("0.2")
145
+
146
+ require_relative "gogyou/typespec"
147
+ require_relative "gogyou/mixin"
148
+ require_relative "gogyou/model"
149
+ require_relative "gogyou/primitives"
150
+ require_relative "gogyou/accessor"
151
+
152
+ #
153
+ # call-seq:
154
+ # struct { ... } -> accessor class
155
+ #
156
+ # 構造体を定義します。モジュールやクラス内で <tt>extend Gogyou</tt> しない(したくない)場合に利用することが出来ます。
157
+ #
158
+ # === example
159
+ #
160
+ # class MyClass
161
+ # Type1 = Gogyou.struct {
162
+ # ...
163
+ # }
164
+ # end
165
+ #
166
+ def self.struct(&block)
167
+ Model.struct(Model::TYPEMAP.dup, &block).create_accessor
168
+ end
169
+
170
+ def self.union(&block)
171
+ Model.union(Model::TYPEMAP.dup, &block).create_accessor
172
+ end
173
+
174
+ #
175
+ # call-seq:
176
+ # typeinfo(typename) -> typeinfo
177
+ # typeinfo(typeobj) -> typeinfo
178
+ #
179
+ # 型名に対する型情報子を取得します。
180
+ #
181
+ # 型情報子を渡した場合は、それをそのまま返り値とします。
182
+ #
183
+ # 型名が存在しないか、型情報子でない場合は nil を返します。
184
+ #
185
+ def self.typeinfo(type)
186
+ case type
187
+ when Symbol, String
188
+ return nil unless type =~ /\A[_A-Za-z][_0-9A-Za-z]*\Z/
189
+ Model::TYPEMAP[type.intern]
190
+ else
191
+ if Model.check_typeinfo(type)
192
+ type
193
+ else
194
+ nil
195
+ end
227
196
  end
228
-
229
- # Gogyou.struct によって生成したクラスが extend によって組み込むモジュールです。
230
- #
231
- # 利用者定義クラスのクラスメソッドとして利用されます。
232
- module ModuleUnpacker
233
- def unpack(str)
234
- ary = str.unpack(self::PACK_FORMAT)
235
- self::PROPERTIES.each_with_index do |(name, size, offset, pack, unpack), i|
236
- unless offset
237
- # メンバ変数の宣言のみ
238
- ary.insert(i, nil)
239
- next
240
- end
241
-
242
- if size && size > 1
243
- ary[i] = ary.slice(i, size)
244
- ary.slice!(i + 1, size - 1)
245
- end
246
-
247
- if unpack
248
- if size > 1
249
- a = ary[i]
250
- size.times do |j|
251
- a[j] = unpack.(a[j])
252
- end
253
- else
254
- ary[i] = unpack.(ary[i])
255
- end
256
- end
257
- end
258
- new(*ary)
259
- end
197
+ end
198
+
199
+ #
200
+ # 構造体 (もどき) を定義します。
201
+ #
202
+ # 入れ子の構造体や共用体を定義するのはもちろん、無名構造体に無名共用体、多次元配列を定義することが出来ます。
203
+ #
204
+ # <tt>extend Gogyou</tt> したモジュール・クラス内で定義された構造体(もどき)のクラスは自動的に型情報を取り込みます。
205
+ # サンプルコードの MyType3 の定義する際に使われる MyType1 と MyType2 に注目して下さい。
206
+ #
207
+ # === example
208
+ #
209
+ # class MyClass
210
+ # extend Gogyou
211
+ #
212
+ # MyType1 = struct { # struct MyType1 {
213
+ # uint32_t :a # uint32_t a;
214
+ # uint32_t :b # uint32_t b;
215
+ # uint32_t :c, 8, 4 # uint32_t c[8][4];
216
+ # } # };
217
+ #
218
+ # MyType2 = struct { # struct MyType2 {
219
+ # float :a, :b, :c, 8, 4 # float a, b, c[8][4];
220
+ # } # };
221
+ #
222
+ # MyType3 = union { # union MyType3 {
223
+ # MyType1 :a # MyType1 a;
224
+ # MyType2 :b # MyType2 b;
225
+ # } # };
226
+ # end
227
+ #
228
+ # t1 = MyClass::MyType1.new
229
+ # t2 = MyClass::MyType2.bind(String.alloc(MyClass::MyType2::BYTESIZE))
230
+ # t3 = MyClass::MyType3.bind(File.read("sample.bin", MyClass::MyType3::BYTESIZE, mode: "rb"))
231
+ #
232
+ def struct(&block)
233
+ Model.struct(update_typemap__GOGYOU__, &block).create_accessor
234
+ end
235
+
236
+ def union(&block)
237
+ Model.union(update_typemap__GOGYOU__, &block).create_accessor
238
+ end
239
+
240
+ #
241
+ # call-seq:
242
+ # typeinfo(typename) -> typeinfo
243
+ # typeinfo(typeobj) -> typeinfo
244
+ #
245
+ # 型名に対する型情報子を取得します。
246
+ #
247
+ # 型情報子を渡した場合は、それをそのまま返り値とします。
248
+ #
249
+ # 型名が存在しないか、型情報子でない場合は nil を返します。
250
+ #
251
+ def typeinfo(type)
252
+ case type
253
+ when Symbol, String
254
+ return nil unless type =~ /\A[_A-Za-z][_0-9A-Za-z]*\Z/
255
+ update_typemap__GOGYOU__[type.intern]
256
+ else
257
+ if Model.check_typeinfo(type)
258
+ type
259
+ else
260
+ nil
261
+ end
260
262
  end
261
-
262
- # Gogyou.struct によって生成したクラスが extend によって組み込むモジュールです。
263
- #
264
- # 利用者定義クラスのクラスメソッドとして利用されます。
265
- module ModulePacker
266
- def pack(obj)
267
- obj = obj.each
268
- ary = []
269
- self::PROPERTIES.each do |name, size, offset, pack, unpack|
270
- #p [name, size, offset, pack, unpack]
271
- unless offset
272
- # メンバ変数の宣言のみ
273
- obj.next
274
- next
275
- end
276
-
277
- if pack
278
- if size > 1
279
- obj.next.each do |a|
280
- ary << pack.call(a)
281
- end
282
- else
283
- ary << pack.call(obj.next)
284
- end
285
- else
286
- if size > 1
287
- ary.concat obj.next
288
- else
289
- ary << obj.next
290
- end
291
- end
292
- #p ary
293
- end
294
-
295
- ary.pack(self::PACK_FORMAT)
296
- end
297
- end
298
-
299
- # Gogyou.struct によって生成したクラスが include によって組み込むモジュールです。
300
- #
301
- # 利用者定義クラスのインスタンスメソッドとして利用されます。
302
- module Packer
303
- def pack
304
- self.class.pack(self)
305
- end
306
- end
307
-
308
- module Types
309
- SIZEOF_CHAR = [0].pack("C").bytesize
310
- SIZEOF_SHORT = [0].pack("S").bytesize
311
- SIZEOF_INT = [0].pack("I").bytesize
312
- SIZEOF_LONG = [0].pack("L").bytesize
313
- SIZEOF_LONGLONG = [0].pack("Q").bytesize
314
- SIZEOF_SIZE_T = [nil].pack("P").bytesize
315
- SIZEOF_FLOAT = [0].pack("F").bytesize
316
- SIZEOF_DOUBLE = [0].pack("D").bytesize
317
-
318
- FORMATOF_INT8_T = "c"
319
- FORMATOF_UINT8_T = "C"
320
-
321
- FORMATOF_INT16_T = "s"
322
- FORMATOF_INT16_BE = FORMATOF_INT16_T + ">"
323
- FORMATOF_INT16_LE = FORMATOF_INT16_T + "<"
324
- FORMATOF_UINT16_T = "S"
325
- FORMATOF_UINT16_BE = FORMATOF_UINT16_T + ">"
326
- FORMATOF_UINT16_LE = FORMATOF_UINT16_T + "<"
327
-
328
- case
329
- when SIZEOF_LONG == 4
330
- FORMATOF_INT32_T = "l"
331
- FORMATOF_UINT32_T = "L"
332
- when SIZEOF_INT == 4
333
- FORMATOF_INT32_T = "i"
334
- FORMATOF_UINT32_T = "I"
335
- else
336
- raise "can not be define int32_t type"
337
- end
338
-
339
- FORMATOF_INT32_BE = FORMATOF_INT32_T + ">"
340
- FORMATOF_INT32_LE = FORMATOF_INT32_T + "<"
341
- FORMATOF_UINT32_BE = FORMATOF_UINT32_T + ">"
342
- FORMATOF_UINT32_LE = FORMATOF_UINT32_T + "<"
343
-
344
- case
345
- when SIZEOF_LONG == 8
346
- FORMATOF_INT64_T = "l"
347
- FORMATOF_UINT64_T = "L"
348
- when SIZEOF_LONGLONG == 8
349
- FORMATOF_INT64_T = "q"
350
- FORMATOF_UINT64_T = "Q"
351
- else
352
- raise "can not be define int64_t type"
353
- end
354
-
355
- FORMATOF_INT64_BE = FORMATOF_INT64_T + ">"
356
- FORMATOF_INT64_LE = FORMATOF_INT64_T + "<"
357
- FORMATOF_UINT64_BE = FORMATOF_UINT64_T + ">"
358
- FORMATOF_UINT64_LE = FORMATOF_UINT64_T + "<"
359
-
360
- case
361
- when SIZEOF_SIZE_T == 4
362
- FORMATOF_SSIZE_T = FORMATOF_INT32_T
363
- FORMATOF_SIZE_T = FORMATOF_UINT32_T
364
- when SIZEOF_SIZE_T == 8
365
- FORMATOF_SSIZE_T = FORMATOF_INT64_T
366
- FORMATOF_SIZE_T = FORMATOF_UINT64_T
367
- else
368
- raise "can not be define size_t type"
369
- end
370
-
371
- FORMATOF_INTPTR_T = FORMATOF_SSIZE_T
372
- FORMATOF_UINTPTR_T = FORMATOF_SIZE_T
373
- SIZEOF_INTPTR_T = SIZEOF_UINTPTR_T = SIZEOF_SIZE_T
374
- end
375
-
376
- if false
377
- Types.constants.sort.each do |e|
378
- puts "#{e}: #{Types.const_get(e).inspect}."
379
- end
380
- raise "TEST BREAK!"
381
- end
382
-
383
- class Property < Struct.new(:name, :elementof, :offset, :pack, :unpack)
384
- BasicStruct = superclass
385
- end
386
-
387
- # 構造体クラスの定義作業に使われるクラスです。
388
- #
389
- # Gogyou.struct に渡すブロック内において self はこのクラスのインスタンスに切り替わるため、レシーバなしのメソッド呼び出しはこのクラスのインスタンスメソッドが呼ばれることになります。
390
- #
391
- # もしブロック外では問題ないのにブロック内で問題が出る場合、このことが原因になることがあります。
392
- class StructFarm < Struct.new(:packlist,
393
- :properties,
394
- :offset)
395
-
396
- # Gogyou::StructFarm の派生元クラスとなる Struct インスタントクラス。
397
- BasicStruct = superclass
398
-
399
- include Types
400
-
401
- def initialize
402
- super([], [], 0)
403
- end
404
-
405
- def initialize_clone(obj)
406
- self.packlist = []
407
- self.properties = []
408
- self.offset = 0
409
- end
410
-
411
- def variable?
412
- false
413
- end
414
-
415
- def union(name, size = 1, &block)
416
- raise NotImplementedError
417
- f = UnionFarm.new
418
- end
419
-
420
- def struct(name, elementof = 1, &block)
421
- f = clone
422
- f.instance_eval(&block)
423
- type = Gogyou.instance_eval { define_struct(f) }
424
-
425
- name = name.to_sym
426
- elementof = _convert_size(elementof)
427
- elementof.times { packlist << "a#{type::SIZE}" }
428
- properties << [name, elementof, offset, type.method(:pack), type.method(:unpack)]
429
- self.offset += type::SIZE * elementof
430
- type
431
- end
432
-
433
- def voidp(name, size = 1)
434
- define(name, "P", 1, size, SIZEOF_SIZE_T, nil)
435
- end
436
-
437
- def typedef(target, aliasname)
438
- case target
439
- when String, Symbol
440
- singleton_class.class_eval do
441
- alias_method aliasname, target
442
- alias_method :"#{aliasname}!", :"#{target}!" unless aliasname =~ /!$/ || target =~ /!$/
443
- end
444
- when Class
445
- sizeof = target.const_get(:SIZE)
446
- pack = target.method(:pack)
447
- unpack = target.method(:unpack)
448
- usertype(aliasname, sizeof, pack, unpack)
449
- else
450
- sizeof = target.size
451
- pack = target.method(:pack)
452
- unpack = target.method(:unpack)
453
- usertype(aliasname, sizeof, pack, unpack)
454
- end
455
- self
456
- end
457
-
458
- # [tipename (require)]
459
- # 型名
460
- # [sizeof (require)]
461
- # 1要素あたりのオクテット数
462
- # [pack (optional)]
463
- # 格納する場合に呼び出されるメソッド。不要であれば nil を指定可。
464
- # [unpack (optional)]
465
- # 展開する場合に呼び出されるメソッド。不要であれば nil を指定可。
466
- def usertype(typename, sizeof, pack = nil, unpack = nil)
467
- #raise(ArgumentError, "need block") unless reduce
468
- #typename = typename.to_sym
469
- #raise(ArgumentError, "wrong typename (#{typename})") if typename =~ /\s/m || typename !~ /^[_\w][_\w\d]*$/
470
- define_singleton_method(typename, &->(name, size = 1) {
471
- name = name.to_sym
472
- size = _convert_size(size)
473
- packlist.concat(["a#{sizeof}"] * size)
474
- properties << [name, size, offset, pack, unpack]
475
- self.offset += sizeof * size
476
- self
477
- })
478
- self
479
- end
480
-
481
- def padding(size)
482
- size = _convert_size(size)
483
- packlist << (size > 1 ? "x#{size}" : "x")
484
- self.offset += size
485
- self
486
- end
487
-
488
- def alignment(elementof)
489
- elementof = _convert_size(elementof)
490
- size = (elementof - offset % elementof) % elementof
491
- padding(size) if size > 0
492
- self
493
- end
494
-
495
- alias align alignment
496
-
497
- # call-seq:
498
- # exclude(name, ...)
499
- #
500
- # 追加するメンバ変数を定義します。これで追加されたメンバ変数は pack / unpack の対象外として扱われます。
501
- #
502
- # インスタンス変数の代わりに定義することを想定しています。
503
- def exclude(*names)
504
- names.each do |n|
505
- property = [n.to_sym, nil, nil]
506
- properties << property
507
- end
508
- self
509
- end
510
-
511
- # メンバ変数宣言の実体。int や long などの型指定時に呼び出されます。
512
- #
513
- # [name] アクセッサ名 (メンバ名)
514
- # [format] パックフォーマット (Array#pack や String#unpack を参照)
515
- # [elementof] 要素数
516
- # [sizeof] 1要素あたりのオクテット数
517
- # [elements] パックフォーマットの要素数が複数要素として展開される場合は nil を指定する
518
- # [pack] Array#pack の時に置き換える場合の前処理
519
- # [unpack] String#unpack の時に置き換える後処理
520
- def define(name, format, elementof, sizeof, elements = nil, packer = nil, unpacker = nil)
521
- name = name.to_sym
522
- elementof = _convert_size(elementof)
523
- packlist << "#{format}#{elementof > 1 ? elementof : nil}"
524
- property = [name, elements || elementof, offset]
525
- property << packer << unpacker if packer || unpacker
526
- properties << property
527
- self.offset += sizeof * elementof
528
- self
529
- end
530
-
531
- def unpack_ustring(str)
532
- str.force_encoding(Encoding::UTF_8)
533
- end
534
-
535
- def pack_ustring(str)
536
- str
537
- end
538
-
539
- [
540
- # 0: type name
541
- # 1: size of type
542
- # 2: number of multiple elements
543
- # 3: default number of element for pack format
544
- # 4.0: pack format (non-suffix)
545
- # 4.1: pack format ("_be" suffixed)
546
- # 4.2: pack format ("_le" suffixed)
547
- # 4.3: pack format ("_t" suffixed)
548
- # 5: pack method (optional)
549
- # 6: unpack method (optional)
550
- [:binary, 1, 1, false, ["a", nil, nil, nil]],
551
- [:ustring, 1, 1, false, ["Z", nil, nil, nil], :pack_ustring, :unpack_ustring],
552
- [:char, SIZEOF_CHAR, nil, true, ["c", nil, nil, nil]],
553
- [:uchar, SIZEOF_CHAR, nil, true, ["C", nil, nil, nil]],
554
- [:short, SIZEOF_SHORT, nil, true, ["s", nil, nil, nil]],
555
- [:ushort, SIZEOF_SHORT, nil, true, ["S", nil, nil, nil]],
556
- [:int, SIZEOF_INT, nil, true, ["i", nil, nil, nil]],
557
- [:uint, SIZEOF_INT, nil, true, ["I", nil, nil, nil]],
558
- [:long, SIZEOF_LONG, nil, true, ["l", nil, nil, nil]],
559
- [:ulong, SIZEOF_LONG, nil, true, ["L", nil, nil, nil]],
560
- [:longlong, SIZEOF_LONGLONG, nil, true, ["q", nil, nil, nil]],
561
- [:ulonglong, SIZEOF_LONGLONG, nil, true, ["Q", nil, nil, nil]],
562
- [:float, SIZEOF_FLOAT, nil, true, ["F", "g", "e", nil]],
563
- [:double, SIZEOF_DOUBLE, nil, true, ["D", "G", "E", nil]],
564
- [:size_t, SIZEOF_SIZE_T, nil, true, [FORMATOF_SIZE_T, nil, nil, nil]],
565
- [:ssize_t, SIZEOF_SIZE_T, nil, true, [FORMATOF_SSIZE_T, nil, nil, nil]],
566
- [:intptr_t, SIZEOF_INTPTR_T, nil, true, [FORMATOF_INTPTR_T, nil, nil, nil]],
567
- [:uintptr_t, SIZEOF_INTPTR_T, nil, true, [FORMATOF_UINTPTR_T, nil, nil, nil]],
568
- [:int8, 1, nil, true, [FORMATOF_INT8_T, nil, nil, FORMATOF_INT8_T]],
569
- [:uint8, 1, nil, true, [FORMATOF_UINT8_T, nil, nil, FORMATOF_UINT8_T]],
570
- [:int16, 2, nil, true, [FORMATOF_INT16_BE, FORMATOF_INT16_BE, FORMATOF_INT16_LE, FORMATOF_INT16_T]],
571
- [:uint16, 2, nil, true, [FORMATOF_UINT16_BE, FORMATOF_UINT16_BE, FORMATOF_UINT16_LE, FORMATOF_UINT16_T]],
572
- [:int32, 4, nil, true, [FORMATOF_INT32_BE, FORMATOF_INT32_BE, FORMATOF_INT32_LE, FORMATOF_INT32_T]],
573
- [:uint32, 4, nil, true, [FORMATOF_UINT32_BE, FORMATOF_UINT32_BE, FORMATOF_UINT32_LE, FORMATOF_UINT32_T]],
574
- [:int64, 8, nil, true, [FORMATOF_INT64_BE, FORMATOF_INT64_BE, FORMATOF_INT64_LE, FORMATOF_INT64_T]],
575
- [:uint64, 8, nil, true, [FORMATOF_UINT64_BE, FORMATOF_UINT64_BE, FORMATOF_UINT64_LE, FORMATOF_UINT64_T]],
576
- ].each do |name, sizeof, ismultielement, defaultelements, format, pack, unpack|
577
- default_elementnum = defaultelements ? " = 1" : ""
578
- ["", "_be", "_le", "_t"].zip(format).each do |suffix, f|
579
- next unless f
580
-
581
- if pack || unpack
582
- reduce = ""
583
- reduce << ", " << (pack ? "method(#{pack.to_sym.inspect})" : "nil")
584
- reduce << ", " << (unpack ? "method(#{unpack.to_sym.inspect})" : "nil")
585
- end
586
-
587
- class_eval(x = <<-EOS, "#{__FILE__}<#{name}#{suffix}>", __LINE__ + 1)
588
- def #{name}#{suffix}(name, elementnum#{default_elementnum})
589
- alignment #{sizeof}
590
- define(name, #{f.inspect}, elementnum, #{sizeof}, #{ismultielement.inspect}#{reduce})
591
- end
592
-
593
- def #{name}#{suffix}!(name, elementnum#{default_elementnum})
594
- define(name, #{f.inspect}, elementnum, #{sizeof}, #{ismultielement.inspect}#{reduce})
595
- end
596
- EOS
597
- #puts x.gsub!(/\s+/m, " ").strip
598
- end
599
- end
600
-
601
- #p instance_methods.sort - Object.methods
602
-
603
- def _convert_size(size)
604
- size = size.to_i
605
- raise ArgumentError, "size is must not zero or negative" unless size > 0
606
- size
607
- end
608
- end
609
-
610
- class << self
611
- private
612
- def define_struct(farm)
613
- syms = farm.properties.map { |e| e[0].to_sym }
614
- raise(ArgumentError, "not defined struct members") if syms.empty?
615
- type0 = ::Struct.new(*syms)
616
- type0.class_eval do
617
- const_set(:PACK_FORMAT, farm.packlist.join("").freeze)
618
- const_set(:SIZE, farm.offset)
619
- const_set(:VARIABLE, farm.variable?)
620
- const_set(:PROPERTIES, farm.properties.freeze)
621
- extend ModuleUnpacker
622
- extend ModulePacker
623
- include Packer
624
- end
625
- type = Class.new(type0)
626
- type.const_set(:BasicStruct, type0)
627
- type
628
- end
263
+ end
264
+
265
+ #
266
+ # call-seq:
267
+ # typedef type, aliasname -> self
268
+ # typedef type, aliasname, *elements -> self
269
+ #
270
+ # [type]
271
+ # This parameter can given a symbol or an object.
272
+ #
273
+ # シンボル (または文字列) を与える場合、すでに定義されている型名である必要があります。
274
+ #
275
+ # クラスオブジェクト (またはモジュールオブジェクト) を与える場合、`.aset` と `.aref` `.bytesize` `.bytealign` メソッドを持つ必要があります。
276
+ #
277
+ # [aliasname]
278
+ # 定義する型名としてのシンボル (または文字列) を与えます。
279
+ #
280
+ # [elements]
281
+ # 配列型の要素数を与えます。要素数は複数をとることが出来、最後の要素数として `0` を与えると任意個の要素数として定義されます。
282
+ #
283
+ def typedef(type, aliasname, *elements)
284
+ Model.typedef(update_typemap__GOGYOU__, type, aliasname, *elements)
285
+ end
286
+
287
+ private
288
+ def update_typemap__GOGYOU__(force = false)
289
+ typemap = @typemap__GOGYOU__ ||= Model::TYPEMAP.dup
290
+ constants.each do |n|
291
+ obj = const_get(n)
292
+ next unless Model.check_typeinfo(obj)
293
+ if force
294
+ typemap[n] = obj
295
+ else
296
+ typemap[n] ||= obj
297
+ end
629
298
  end
299
+ typemap
300
+ end
630
301
  end