bindata 0.9.3 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bindata might be problematic. Click here for more details.
- data/ChangeLog +18 -0
- data/NEWS +59 -0
- data/README +22 -23
- data/TODO +18 -12
- data/examples/gzip.rb +4 -4
- data/lib/bindata.rb +4 -3
- data/lib/bindata/array.rb +202 -132
- data/lib/bindata/base.rb +147 -166
- data/lib/bindata/{single.rb → base_primitive.rb} +82 -56
- data/lib/bindata/bits.rb +31 -770
- data/lib/bindata/choice.rb +157 -82
- data/lib/bindata/float.rb +25 -27
- data/lib/bindata/int.rb +144 -177
- data/lib/bindata/io.rb +59 -49
- data/lib/bindata/lazy.rb +80 -50
- data/lib/bindata/params.rb +134 -26
- data/lib/bindata/{single_value.rb → primitive.rb} +71 -64
- data/lib/bindata/{multi_value.rb → record.rb} +52 -70
- data/lib/bindata/registry.rb +49 -17
- data/lib/bindata/rest.rb +6 -10
- data/lib/bindata/sanitize.rb +55 -70
- data/lib/bindata/string.rb +60 -42
- data/lib/bindata/stringz.rb +34 -35
- data/lib/bindata/struct.rb +197 -152
- data/lib/bindata/trace.rb +35 -0
- data/spec/array_spec.rb +128 -112
- data/spec/{single_spec.rb → base_primitive_spec.rb} +102 -61
- data/spec/base_spec.rb +190 -185
- data/spec/bits_spec.rb +126 -98
- data/spec/choice_spec.rb +89 -98
- data/spec/example.rb +19 -0
- data/spec/float_spec.rb +28 -44
- data/spec/int_spec.rb +217 -127
- data/spec/io_spec.rb +41 -24
- data/spec/lazy_spec.rb +95 -49
- data/spec/primitive_spec.rb +191 -0
- data/spec/{multi_value_spec.rb → record_spec.rb} +124 -89
- data/spec/registry_spec.rb +53 -12
- data/spec/rest_spec.rb +2 -3
- data/spec/sanitize_spec.rb +47 -73
- data/spec/spec_common.rb +13 -1
- data/spec/string_spec.rb +34 -23
- data/spec/stringz_spec.rb +10 -18
- data/spec/struct_spec.rb +91 -63
- data/spec/system_spec.rb +291 -0
- metadata +12 -8
- data/spec/single_value_spec.rb +0 -131
data/ChangeLog
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
= BinData Changelog
|
2
2
|
|
3
|
+
== Version
|
4
|
+
|
5
|
+
* Arbitrary byte sized integers are now supported (e.g. 24bit, 808bit).
|
6
|
+
* Renamed String :trim_value parameter to :trim_padding.
|
7
|
+
* BinData::Array now behaves more like Ruby's Array.
|
8
|
+
* Added debug_name
|
9
|
+
* Added ability to trace reading
|
10
|
+
* Primitives now behave as their value. Calling #value is no longer needed.
|
11
|
+
* Renamed #to_s -> #to_binary_s to avoid confusion with ruby's #to_s.
|
12
|
+
* Added #assign as the generic way to assign values to objects.
|
13
|
+
* Added :copy_on_change parameter to Choice.
|
14
|
+
* Implement #offset for all objects.
|
15
|
+
* Renamed Single -> BasePrimitive.
|
16
|
+
* Renamed SingleValue -> Primitive.
|
17
|
+
* Renamed MultiValue -> Record.
|
18
|
+
* The :onlyif parameter now only applies to fields inside Structs.
|
19
|
+
* LazyEvaluator can now supply arguments when invoking methods
|
20
|
+
|
3
21
|
== Version 0.9.3 (2008-12-03)
|
4
22
|
|
5
23
|
* Arrays can now :read_until => :eof
|
data/NEWS
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
= 0.10.0
|
2
|
+
|
3
|
+
There are several new features in this release. The major ones are:
|
4
|
+
|
5
|
+
* Debugging declarations is now easier with the ability to trace values when
|
6
|
+
reading.
|
7
|
+
|
8
|
+
BinData::trace_reading(STDERR) do
|
9
|
+
obj_not_quite_working_correctly.read(io)
|
10
|
+
end
|
11
|
+
|
12
|
+
Will result in a debugging trace written to STDERR.
|
13
|
+
|
14
|
+
* Support for arbitrary sized integers and bit fields.
|
15
|
+
|
16
|
+
* Struct/Array field/element access now returns a BinData object, rather than
|
17
|
+
the underlying Fixnum or String. The BinData object will behave like it's
|
18
|
+
underlying primitive so existing code should continue to work. This allows
|
19
|
+
for an improved syntax in your declarations.
|
20
|
+
|
21
|
+
If your code requires access to the underlying primitive, you can access it
|
22
|
+
with the #value method.
|
23
|
+
|
24
|
+
There are several deprecations and one backwards incompatible change in this
|
25
|
+
release. Turn on $VERBOSE mode (-w command line switch) to see warnings
|
26
|
+
regarding these deprecations.
|
27
|
+
|
28
|
+
== IMPORTANT - Ruby does not warn you about this change!
|
29
|
+
|
30
|
+
* The #to_s method for getting the binary string representation of a data
|
31
|
+
object has been renamed to #to_binary_s. If you use this method you must
|
32
|
+
manually change your code or you will get strange results.
|
33
|
+
|
34
|
+
== Deprecations. Ruby will warn you of these when in $VERBOSE mode.
|
35
|
+
|
36
|
+
Code using these deprecated features will still work in this release but will
|
37
|
+
fail to work in some future release. Change your code now.
|
38
|
+
|
39
|
+
* BinData::SingleValue has been renamed to BinData::Primitive.
|
40
|
+
|
41
|
+
* BinData::MultiValue has been renamed to BinData::Record.
|
42
|
+
|
43
|
+
* String :trim_value parameter has been renamed to :trim_padding.
|
44
|
+
|
45
|
+
* Registry.instance should be replaced with RegisteredClasses.
|
46
|
+
|
47
|
+
* struct.offset_of("field") should be replaced with struct.field.offset.
|
48
|
+
|
49
|
+
* struct.clear("field") should be replaced with struct.field.clear.
|
50
|
+
|
51
|
+
* struct.clear?("field") should be replaced with struct.field.clear?.
|
52
|
+
|
53
|
+
* struct.num_bytes("field") should be replaced with struct.field.num_bytes.
|
54
|
+
|
55
|
+
* array.clear(n) should be replaced with array[n].clear.
|
56
|
+
|
57
|
+
* array.clear?(n) should be replaced with array[n].clear?.
|
58
|
+
|
59
|
+
* array.num_bytes(n) should be replaced with array[n].num_bytes.
|
data/README
CHANGED
@@ -15,7 +15,7 @@ Do you ever find yourself writing code like this?
|
|
15
15
|
It's ugly, violates DRY and feels like you're writing Perl, not Ruby.
|
16
16
|
There is a better way.
|
17
17
|
|
18
|
-
class Rectangle < BinData::
|
18
|
+
class Rectangle < BinData::Record
|
19
19
|
uint16le :len
|
20
20
|
string :name, :read_length => :len
|
21
21
|
uint32le :width
|
@@ -36,7 +36,7 @@ download[http://rubyforge.org/frs/?group_id=3252] page.
|
|
36
36
|
|
37
37
|
BinData declarations are easy to read. Here's an example.
|
38
38
|
|
39
|
-
class MyFancyFormat < BinData::
|
39
|
+
class MyFancyFormat < BinData::Record
|
40
40
|
stringz :comment
|
41
41
|
uint8 :count, :check_value => lambda { (value % 2) == 0 }
|
42
42
|
array :some_ints, :type => :int32be, :initial_length => :count
|
@@ -62,7 +62,7 @@ the writing code, have a go at the reading code.
|
|
62
62
|
The general format of a BinData declaration is a class containing one or more
|
63
63
|
fields.
|
64
64
|
|
65
|
-
class MyName < BinData::
|
65
|
+
class MyName < BinData::Record
|
66
66
|
type field_name, :param1 => "foo", :param2 => bar, ...
|
67
67
|
...
|
68
68
|
end
|
@@ -72,8 +72,7 @@ or a user defined type. For user defined types, convert the class name
|
|
72
72
|
from CamelCase to lowercase underscore_style.
|
73
73
|
|
74
74
|
*field_name* is the name by which you can access the data. Use either a
|
75
|
-
String or a Symbol.
|
76
|
-
later in the tutorial.
|
75
|
+
String or a Symbol.
|
77
76
|
|
78
77
|
Each field may have *parameters* for how to process the data. The
|
79
78
|
parameters are passed as a Hash using Symbols for keys.
|
@@ -100,7 +99,7 @@ the string contains the string's length.
|
|
100
99
|
|
101
100
|
Here's how we'd implement the same example with BinData.
|
102
101
|
|
103
|
-
class PascalString < BinData::
|
102
|
+
class PascalString < BinData::Record
|
104
103
|
uint8 :len, :value => lambda { data.length }
|
105
104
|
string :data, :read_length => :len
|
106
105
|
end
|
@@ -120,7 +119,7 @@ Here's how we'd implement the same example with BinData.
|
|
120
119
|
This syntax needs explaining. Let's simplify by examining reading and
|
121
120
|
writing separately.
|
122
121
|
|
123
|
-
class PascalStringReader < BinData::
|
122
|
+
class PascalStringReader < BinData::Record
|
124
123
|
uint8 :len
|
125
124
|
string :data, :read_length => :len
|
126
125
|
end
|
@@ -132,7 +131,7 @@ This states that when reading the string, the initial length of the string
|
|
132
131
|
Note that <tt>:read_length => :len</tt> is syntactic sugar for
|
133
132
|
<tt>:read_length => lambda { len }</tt>, but more on that later.
|
134
133
|
|
135
|
-
class PascalStringWriter < BinData::
|
134
|
+
class PascalStringWriter < BinData::Record
|
136
135
|
uint8 :len, :value => lambda { data.length }
|
137
136
|
string :data
|
138
137
|
end
|
@@ -194,7 +193,7 @@ BinData::Rest:: Consumes the rest of the input stream.
|
|
194
193
|
|
195
194
|
== Parameters
|
196
195
|
|
197
|
-
class PascalStringWriter < BinData::
|
196
|
+
class PascalStringWriter < BinData::Record
|
198
197
|
uint8 :len, :value => lambda { data.length }
|
199
198
|
string :data
|
200
199
|
end
|
@@ -229,7 +228,7 @@ produced is independent of architecture. Explicitly specifying the
|
|
229
228
|
endianess of each numeric type can become tedious, so the following
|
230
229
|
shortcut is provided.
|
231
230
|
|
232
|
-
class A < BinData::
|
231
|
+
class A < BinData::Record
|
233
232
|
endian :little
|
234
233
|
|
235
234
|
uint16 :a
|
@@ -241,7 +240,7 @@ shortcut is provided.
|
|
241
240
|
|
242
241
|
is equivalent to:
|
243
242
|
|
244
|
-
class A < BinData::
|
243
|
+
class A < BinData::Record
|
245
244
|
uint16le :a
|
246
245
|
uint32le :b
|
247
246
|
double_le :c
|
@@ -255,35 +254,35 @@ cascade to nested types, as illustrated with the array in the above example.
|
|
255
254
|
|
256
255
|
== Creating custom types
|
257
256
|
|
258
|
-
Custom types should be created by subclassing BinData::
|
259
|
-
BinData::
|
260
|
-
BinData::
|
257
|
+
Custom types should be created by subclassing BinData::Record or
|
258
|
+
BinData::Primitive. Ocassionally it may be useful to subclass
|
259
|
+
BinData::BasePrimitive. Subclassing other classes may have unexpected results
|
261
260
|
and is unsupported.
|
262
261
|
|
263
262
|
Let us revisit the Pascal String example.
|
264
263
|
|
265
|
-
class PascalString < BinData::
|
264
|
+
class PascalString < BinData::Record
|
266
265
|
uint8 :len, :value => lambda { data.length }
|
267
266
|
string :data, :read_length => :len
|
268
267
|
end
|
269
268
|
|
270
269
|
We'd like to make PascalString a custom type that behaves like a
|
271
|
-
BinData::
|
270
|
+
BinData::BasePrimitive object so we can use :initial_value etc. Here's an
|
272
271
|
example usage of what we'd like:
|
273
272
|
|
274
|
-
class Favourites < BinData::
|
273
|
+
class Favourites < BinData::Record
|
275
274
|
pascal_string :language, :initial_value => "ruby"
|
276
275
|
pascal_string :os, :initial_value => "unix"
|
277
276
|
end
|
278
277
|
|
279
278
|
f = Favourites.new
|
280
279
|
f.os = "freebsd"
|
281
|
-
f.
|
280
|
+
f.to_binary_s #=> "\004ruby\007freebsd"
|
282
281
|
|
283
|
-
We create this type of custom string by inheriting from BinData::
|
282
|
+
We create this type of custom string by inheriting from BinData::Primitive
|
284
283
|
and implementing the #get and #set methods.
|
285
284
|
|
286
|
-
class PascalString < BinData::
|
285
|
+
class PascalString < BinData::Primitive
|
287
286
|
uint8 :len, :value => lambda { data.length }
|
288
287
|
string :data, :read_length => :len
|
289
288
|
|
@@ -291,11 +290,11 @@ and implementing the #get and #set methods.
|
|
291
290
|
def set(v) self.data = v; end
|
292
291
|
end
|
293
292
|
|
294
|
-
If the type we are creating represents a
|
295
|
-
BinData::
|
293
|
+
If the type we are creating represents a primitive value then inherit from
|
294
|
+
BinData::Primitive, otherwise inherit from BinData::Record.
|
296
295
|
|
297
296
|
== License
|
298
297
|
|
299
298
|
BinData is released under the same license as Ruby.
|
300
299
|
|
301
|
-
Copyright (c) 2007
|
300
|
+
Copyright (c) 2007 - 2009 Dion Mendel
|
data/TODO
CHANGED
@@ -1,20 +1,26 @@
|
|
1
|
-
*
|
1
|
+
* Write a Rakefile
|
2
2
|
|
3
|
-
|
4
|
-
+ needed for struct and array
|
3
|
+
* Registry should auto create integers and bits. (e.g. 24bit int, 191bit int)
|
5
4
|
|
6
|
-
*
|
7
|
-
|
5
|
+
* Improve speed of Sanitizer for recursive records
|
6
|
+
rework it so that sanitizing is done during definition (before initialising).
|
7
|
+
|
8
|
+
* Write a detailed tutorial (probably as a web page).
|
9
|
+
|
10
|
+
* Need more examples.
|
11
|
+
|
12
|
+
* Need wrapper to be able to define new wrapped classes - with parameters
|
13
|
+
|
14
|
+
-----------------------------------------------------------------------------
|
15
|
+
define_wrapped_class("MyInt", :uint32be, :initial_value => 3) #=> MyInt
|
16
|
+
define_wrapped_class("ByteArray", :array, :type => :uint8) #=> ByteArray
|
17
|
+
define_wrapped_class("MyInt", :uint32be, {:initial_value => :mult}, [], [], {:mult => 3}) #=> MyInt
|
18
|
+
# mandatory, optional, default
|
19
|
+
-----------------------------------------------------------------------------
|
8
20
|
|
9
|
-
This will be used when throwing exceptions to aid debugging.
|
10
21
|
|
11
|
-
* Using the above names, add tracing capability when reading.
|
12
22
|
|
13
|
-
* Clean up Array to make it as close to ruby Array as possible.
|
14
23
|
|
15
|
-
* Need more examples.
|
16
24
|
|
17
|
-
|
18
|
-
add more comprehensive integration tests to serve as examples
|
25
|
+
refactor snapshot -> _snapshot et al
|
19
26
|
|
20
|
-
* Need better documentation.
|
data/examples/gzip.rb
CHANGED
@@ -9,14 +9,14 @@ class Gzip
|
|
9
9
|
# Known compression methods
|
10
10
|
DEFLATE = 8
|
11
11
|
|
12
|
-
class Extra < BinData::
|
12
|
+
class Extra < BinData::Record
|
13
13
|
endian :little
|
14
14
|
|
15
15
|
uint16 :len, :length => lambda { data.length }
|
16
16
|
string :data, :read_length => :len
|
17
17
|
end
|
18
18
|
|
19
|
-
class Header < BinData::
|
19
|
+
class Header < BinData::Record
|
20
20
|
endian :little
|
21
21
|
|
22
22
|
uint16 :ident, :value => 0x8b1f, :check_value => 0x8b1f
|
@@ -45,7 +45,7 @@ class Gzip
|
|
45
45
|
uint16 :crc16, :onlyif => lambda { fcrc16.nonzero? }
|
46
46
|
end
|
47
47
|
|
48
|
-
class Footer < BinData::
|
48
|
+
class Footer < BinData::Record
|
49
49
|
endian :little
|
50
50
|
|
51
51
|
uint32 :crc32
|
@@ -64,7 +64,7 @@ class Gzip
|
|
64
64
|
def_delegators :@footer, :crc32, :uncompressed_size
|
65
65
|
|
66
66
|
def mtime
|
67
|
-
Time.at(@header.mtime)
|
67
|
+
Time.at(@header.mtime.snapshot)
|
68
68
|
end
|
69
69
|
|
70
70
|
def mtime=(tm)
|
data/lib/bindata.rb
CHANGED
@@ -6,17 +6,18 @@ require 'bindata/bits'
|
|
6
6
|
require 'bindata/choice'
|
7
7
|
require 'bindata/float'
|
8
8
|
require 'bindata/int'
|
9
|
-
require 'bindata/
|
9
|
+
require 'bindata/primitive'
|
10
|
+
require 'bindata/record'
|
10
11
|
require 'bindata/rest'
|
11
|
-
require 'bindata/single_value'
|
12
12
|
require 'bindata/string'
|
13
13
|
require 'bindata/stringz'
|
14
14
|
require 'bindata/struct'
|
15
|
+
require 'bindata/trace'
|
15
16
|
|
16
17
|
# = BinData
|
17
18
|
#
|
18
19
|
# A declarative way to read and write structured binary data.
|
19
20
|
#
|
20
21
|
module BinData
|
21
|
-
VERSION = "0.
|
22
|
+
VERSION = "0.10.0"
|
22
23
|
end
|
data/lib/bindata/array.rb
CHANGED
@@ -55,30 +55,26 @@ module BinData
|
|
55
55
|
class Array < BinData::Base
|
56
56
|
include Enumerable
|
57
57
|
|
58
|
-
# Register this class
|
59
58
|
register(self.name, self)
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
bindata_mutually_exclusive_parameters :initial_length, :read_until
|
60
|
+
mandatory_parameter :type
|
61
|
+
optional_parameters :initial_length, :read_until
|
62
|
+
mutually_exclusive_parameters :initial_length, :read_until
|
65
63
|
|
66
64
|
class << self
|
67
|
-
|
65
|
+
|
68
66
|
def sanitize_parameters!(sanitizer, params)
|
69
67
|
unless params.has_key?(:initial_length) or params.has_key?(:read_until)
|
70
68
|
# ensure one of :initial_length and :read_until exists
|
71
69
|
params[:initial_length] = 0
|
72
70
|
end
|
73
71
|
|
74
|
-
|
75
|
-
warn ":read_length is not used with arrays. You probably want to change this to :initial_length"
|
76
|
-
end
|
72
|
+
warn_replacement_parameter(params, :read_length, :initial_length)
|
77
73
|
|
78
74
|
if params.has_key?(:type)
|
79
75
|
type, el_params = params[:type]
|
80
|
-
klass = sanitizer.
|
81
|
-
sanitized_params = sanitizer.
|
76
|
+
klass = sanitizer.lookup_class(type)
|
77
|
+
sanitized_params = sanitizer.sanitized_params(klass, el_params)
|
82
78
|
params[:type] = [klass, sanitized_params]
|
83
79
|
end
|
84
80
|
|
@@ -86,27 +82,26 @@ module BinData
|
|
86
82
|
end
|
87
83
|
end
|
88
84
|
|
89
|
-
# Creates a new Array
|
90
85
|
def initialize(params = {}, parent = nil)
|
91
86
|
super(params, parent)
|
92
87
|
|
93
|
-
|
88
|
+
el_class, el_params = get_parameter(:type)
|
94
89
|
|
95
90
|
@element_list = nil
|
96
|
-
@
|
91
|
+
@element_class = el_class
|
97
92
|
@element_params = el_params
|
98
93
|
end
|
99
94
|
|
100
95
|
# Returns if the element at position +index+ is clear?. If +index+
|
101
|
-
# is not given, then returns whether all
|
96
|
+
# is not given, then returns whether all elements are clear.
|
102
97
|
def clear?(index = nil)
|
103
|
-
if
|
104
|
-
true
|
105
|
-
elsif index.
|
106
|
-
|
107
|
-
|
98
|
+
if index.nil?
|
99
|
+
@element_list.nil? or elements.inject(true) { |all_clear, f| all_clear and f.clear? }
|
100
|
+
elsif index < elements.length
|
101
|
+
warn "'obj.clear?(n)' is deprecated. Replacing with 'obj[n].clear?'"
|
102
|
+
elements[index].clear?
|
108
103
|
else
|
109
|
-
|
104
|
+
true
|
110
105
|
end
|
111
106
|
end
|
112
107
|
|
@@ -114,105 +109,116 @@ module BinData
|
|
114
109
|
# the internal state of the array is reset to that of a newly created
|
115
110
|
# object.
|
116
111
|
def clear(index = nil)
|
117
|
-
if
|
118
|
-
# do nothing as the array is already clear
|
119
|
-
elsif index.nil?
|
112
|
+
if index.nil?
|
120
113
|
@element_list = nil
|
121
114
|
elsif index < elements.length
|
115
|
+
warn "'obj.clear(n)' is deprecated. Replacing with 'obj[n].clear'"
|
122
116
|
elements[index].clear
|
123
117
|
end
|
124
118
|
end
|
125
119
|
|
126
|
-
# Returns
|
127
|
-
#
|
128
|
-
|
129
|
-
|
120
|
+
# Returns the first index of +obj+ in self.
|
121
|
+
#
|
122
|
+
# a = BinData::String.new; a.value = "a"
|
123
|
+
# b = BinData::String.new; b.value = "b"
|
124
|
+
# c = BinData::String.new; c.value = "c"
|
125
|
+
#
|
126
|
+
# arr = BinData::Array.new(:type => :string)
|
127
|
+
# arr.push(a, b, c)
|
128
|
+
#
|
129
|
+
# arr.find_index("b") #=> 1
|
130
|
+
# arr.find_index(c) #=> 2
|
131
|
+
#
|
132
|
+
def find_index(obj)
|
133
|
+
elements.find_index(obj)
|
130
134
|
end
|
135
|
+
alias_method :index, :find_index
|
131
136
|
|
132
|
-
#
|
133
|
-
|
134
|
-
|
137
|
+
# Returns the first index of +obj+ in self.
|
138
|
+
#
|
139
|
+
# Uses equal? for the comparator.
|
140
|
+
def find_index_of(obj)
|
141
|
+
elements.find_index { |el| el.equal?(obj) }
|
135
142
|
end
|
136
143
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
def append(value = nil)
|
141
|
-
# TODO: deprecate #append as it can be replaced with #push
|
142
|
-
append_new_element
|
143
|
-
self[-1] = value unless value.nil?
|
144
|
-
self.last
|
144
|
+
def push(*args)
|
145
|
+
insert(-1, *args)
|
146
|
+
self
|
145
147
|
end
|
148
|
+
alias_method :<<, :push
|
146
149
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
150
|
+
def unshift(*args)
|
151
|
+
insert(0, *args)
|
152
|
+
self
|
150
153
|
end
|
151
154
|
|
152
|
-
|
153
|
-
|
154
|
-
# be chained together.
|
155
|
-
def push(*args)
|
156
|
-
args.each do |arg|
|
157
|
-
if @element_klass == arg.class
|
158
|
-
# TODO: need to modify arg.env to add_variable(:index) and
|
159
|
-
# to link arg.env to self.env
|
160
|
-
elements.push(arg)
|
161
|
-
else
|
162
|
-
append(arg)
|
163
|
-
end
|
164
|
-
end
|
155
|
+
def concat(array)
|
156
|
+
insert(-1, *array.to_ary)
|
165
157
|
self
|
166
158
|
end
|
167
159
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
160
|
+
def insert(index, *objs)
|
161
|
+
extend_array(index - 1)
|
162
|
+
elements.insert(index, *to_storage_formats(objs))
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
def append(value = nil)
|
167
|
+
warn "#append is deprecated, use push or slice instead"
|
168
|
+
if value.nil?
|
169
|
+
slice(length)
|
170
|
+
else
|
171
|
+
push(value)
|
176
172
|
end
|
173
|
+
self.last
|
174
|
+
end
|
177
175
|
|
178
|
-
|
179
|
-
|
180
|
-
|
176
|
+
# Returns the element at +index+.
|
177
|
+
def [](arg1, arg2 = nil)
|
178
|
+
if arg1.respond_to?(:to_int) and arg2.nil?
|
179
|
+
slice_index(arg1.to_int)
|
180
|
+
elsif arg1.respond_to?(:to_int) and arg2.respond_to?(:to_int)
|
181
|
+
slice_start_length(arg1.to_int, arg2.to_int)
|
182
|
+
elsif arg1.is_a?(Range) and arg2.nil?
|
183
|
+
slice_range(arg1)
|
181
184
|
else
|
182
|
-
|
185
|
+
raise TypeError, "can't convert #{arg1} into Integer" unless arg1.respond_to?(:to_int)
|
186
|
+
raise TypeError, "can't convert #{arg2} into Integer" unless arg2.respond_to?(:to_int)
|
183
187
|
end
|
184
188
|
end
|
185
189
|
alias_method :slice, :[]
|
186
190
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
while index >= elements.length
|
192
|
-
append_new_element
|
193
|
-
end
|
191
|
+
def slice_index(index)
|
192
|
+
extend_array(index)
|
193
|
+
at(index)
|
194
|
+
end
|
194
195
|
|
195
|
-
|
196
|
-
|
197
|
-
# TODO: allow setting objects, not just values
|
198
|
-
raise NoMethodError, "undefined method `[]=' for #{self}", caller
|
199
|
-
end
|
200
|
-
obj.value = value
|
196
|
+
def slice_start_length(start, length)
|
197
|
+
elements[start, length]
|
201
198
|
end
|
202
199
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
200
|
+
def slice_range(range)
|
201
|
+
elements[range]
|
202
|
+
end
|
203
|
+
private :slice_index, :slice_start_length, :slice_range
|
204
|
+
|
205
|
+
# Returns the element at +index+. Unlike +slice+, if +index+ is out
|
206
|
+
# of range the array will not be automatically extended.
|
207
|
+
def at(index)
|
208
|
+
elements[index]
|
209
|
+
end
|
210
|
+
|
211
|
+
# Sets the element at +index+.
|
212
|
+
def []=(index, value)
|
213
|
+
extend_array(index)
|
214
|
+
elements[index].assign(value)
|
209
215
|
end
|
210
216
|
|
211
217
|
# Returns the first element, or the first +n+ elements, of the array.
|
212
218
|
# If the array is empty, the first form returns nil, and the second
|
213
219
|
# form returns an empty array.
|
214
220
|
def first(n = nil)
|
215
|
-
if n.nil? and
|
221
|
+
if n.nil? and empty?
|
216
222
|
# explicitly return nil as arrays grow automatically
|
217
223
|
nil
|
218
224
|
elsif n.nil?
|
@@ -247,87 +253,151 @@ module BinData
|
|
247
253
|
|
248
254
|
# Allow this object to be used in array context.
|
249
255
|
def to_ary
|
250
|
-
|
256
|
+
collect { |el| el }
|
257
|
+
end
|
258
|
+
|
259
|
+
# Iterate over each element in the array.
|
260
|
+
def each
|
261
|
+
elements.each { |el| yield el }
|
262
|
+
end
|
263
|
+
|
264
|
+
def debug_name_of(child)
|
265
|
+
index = find_index_of(child)
|
266
|
+
"#{debug_name}[#{index}]"
|
267
|
+
end
|
268
|
+
|
269
|
+
def offset_of(child)
|
270
|
+
index = find_index_of(child)
|
271
|
+
sum = sum_num_bytes_below_index(index)
|
272
|
+
|
273
|
+
child_offset = (::Integer === child.do_num_bytes) ? sum.ceil : sum.floor
|
274
|
+
|
275
|
+
offset + child_offset
|
251
276
|
end
|
252
277
|
|
253
278
|
#---------------
|
254
279
|
private
|
255
280
|
|
256
|
-
|
281
|
+
def extend_array(max_index)
|
282
|
+
max_length = max_index + 1
|
283
|
+
while elements.length < max_length
|
284
|
+
append_new_element
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def to_storage_formats(els)
|
289
|
+
els.collect { |el| to_storage_format(el) }
|
290
|
+
end
|
291
|
+
|
292
|
+
def to_storage_format(obj)
|
293
|
+
element = new_element
|
294
|
+
element.assign(obj)
|
295
|
+
element
|
296
|
+
end
|
297
|
+
|
257
298
|
def _do_read(io)
|
258
|
-
if
|
299
|
+
if has_parameter?(:initial_length)
|
259
300
|
elements.each { |f| f.do_read(io) }
|
260
|
-
elsif
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
301
|
+
elsif has_parameter?(:read_until)
|
302
|
+
read_until(io)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def read_until(io)
|
307
|
+
if get_parameter(:read_until) == :eof
|
308
|
+
read_until_eof(io)
|
309
|
+
else
|
310
|
+
read_until_condition(io)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def read_until_eof(io)
|
315
|
+
finished = false
|
316
|
+
while not finished
|
317
|
+
element = append_new_element
|
318
|
+
begin
|
319
|
+
element.do_read(io)
|
320
|
+
rescue
|
321
|
+
elements.pop
|
322
|
+
finished = true
|
282
323
|
end
|
283
324
|
end
|
284
325
|
end
|
285
326
|
|
286
|
-
|
327
|
+
def read_until_condition(io)
|
328
|
+
finished = false
|
329
|
+
while not finished
|
330
|
+
element = append_new_element
|
331
|
+
element.do_read(io)
|
332
|
+
variables = { :index => self.length - 1, :element => self.last,
|
333
|
+
:array => self }
|
334
|
+
finished = eval_parameter(:read_until, variables)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def _done_read
|
339
|
+
elements.each { |f| f.done_read }
|
340
|
+
end
|
341
|
+
|
287
342
|
def _do_write(io)
|
288
343
|
elements.each { |f| f.do_write(io) }
|
289
344
|
end
|
290
345
|
|
291
|
-
# Returns the number of bytes it will take to write the element at
|
292
|
-
# +index+. If +index+, then returns the number of bytes required
|
293
|
-
# to write all fields.
|
294
346
|
def _do_num_bytes(index)
|
295
347
|
if index.nil?
|
296
|
-
|
297
|
-
|
348
|
+
sum_num_bytes_for_all_elements.ceil
|
349
|
+
elsif index < elements.length
|
350
|
+
warn "'obj.num_bytes(n)' is deprecated. Replacing with 'obj[n].num_bytes'"
|
298
351
|
elements[index].do_num_bytes
|
352
|
+
else
|
353
|
+
0
|
299
354
|
end
|
300
355
|
end
|
301
356
|
|
302
|
-
|
357
|
+
def _assign(array)
|
358
|
+
raise ArgumentError, "can't set a nil value for #{debug_name}" if array.nil?
|
359
|
+
|
360
|
+
@element_list = to_storage_formats(array.to_ary)
|
361
|
+
end
|
362
|
+
|
303
363
|
def _snapshot
|
304
364
|
elements.collect { |e| e.snapshot }
|
305
365
|
end
|
306
366
|
|
307
|
-
# Returns the list of all elements in the array. The elements
|
308
|
-
# will be instantiated on the first call to this method.
|
309
367
|
def elements
|
310
368
|
if @element_list.nil?
|
311
369
|
@element_list = []
|
312
|
-
if
|
313
|
-
|
314
|
-
|
315
|
-
append_new_element
|
370
|
+
if has_parameter?(:initial_length)
|
371
|
+
eval_parameter(:initial_length).times do
|
372
|
+
@element_list << new_element
|
316
373
|
end
|
317
374
|
end
|
318
375
|
end
|
319
376
|
@element_list
|
320
377
|
end
|
321
378
|
|
322
|
-
# Creates a new element and appends it to the end of @element_list.
|
323
|
-
# Returns the newly created element
|
324
379
|
def append_new_element
|
325
|
-
|
326
|
-
elements
|
327
|
-
|
328
|
-
element = @element_klass.new(@element_params, self)
|
329
|
-
@element_list << element
|
380
|
+
element = new_element
|
381
|
+
elements << element
|
330
382
|
element
|
331
383
|
end
|
384
|
+
|
385
|
+
def new_element
|
386
|
+
@element_class.new(@element_params, self)
|
387
|
+
end
|
388
|
+
|
389
|
+
def sum_num_bytes_for_all_elements
|
390
|
+
sum_num_bytes_below_index(length)
|
391
|
+
end
|
392
|
+
|
393
|
+
def sum_num_bytes_below_index(index)
|
394
|
+
sum = 0
|
395
|
+
(0...index).each do |i|
|
396
|
+
nbytes = elements[i].do_num_bytes
|
397
|
+
sum = ((::Integer === nbytes) ? sum.ceil : sum) + nbytes
|
398
|
+
end
|
399
|
+
|
400
|
+
sum
|
401
|
+
end
|
332
402
|
end
|
333
403
|
end
|