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.

Files changed (47) hide show
  1. data/ChangeLog +18 -0
  2. data/NEWS +59 -0
  3. data/README +22 -23
  4. data/TODO +18 -12
  5. data/examples/gzip.rb +4 -4
  6. data/lib/bindata.rb +4 -3
  7. data/lib/bindata/array.rb +202 -132
  8. data/lib/bindata/base.rb +147 -166
  9. data/lib/bindata/{single.rb → base_primitive.rb} +82 -56
  10. data/lib/bindata/bits.rb +31 -770
  11. data/lib/bindata/choice.rb +157 -82
  12. data/lib/bindata/float.rb +25 -27
  13. data/lib/bindata/int.rb +144 -177
  14. data/lib/bindata/io.rb +59 -49
  15. data/lib/bindata/lazy.rb +80 -50
  16. data/lib/bindata/params.rb +134 -26
  17. data/lib/bindata/{single_value.rb → primitive.rb} +71 -64
  18. data/lib/bindata/{multi_value.rb → record.rb} +52 -70
  19. data/lib/bindata/registry.rb +49 -17
  20. data/lib/bindata/rest.rb +6 -10
  21. data/lib/bindata/sanitize.rb +55 -70
  22. data/lib/bindata/string.rb +60 -42
  23. data/lib/bindata/stringz.rb +34 -35
  24. data/lib/bindata/struct.rb +197 -152
  25. data/lib/bindata/trace.rb +35 -0
  26. data/spec/array_spec.rb +128 -112
  27. data/spec/{single_spec.rb → base_primitive_spec.rb} +102 -61
  28. data/spec/base_spec.rb +190 -185
  29. data/spec/bits_spec.rb +126 -98
  30. data/spec/choice_spec.rb +89 -98
  31. data/spec/example.rb +19 -0
  32. data/spec/float_spec.rb +28 -44
  33. data/spec/int_spec.rb +217 -127
  34. data/spec/io_spec.rb +41 -24
  35. data/spec/lazy_spec.rb +95 -49
  36. data/spec/primitive_spec.rb +191 -0
  37. data/spec/{multi_value_spec.rb → record_spec.rb} +124 -89
  38. data/spec/registry_spec.rb +53 -12
  39. data/spec/rest_spec.rb +2 -3
  40. data/spec/sanitize_spec.rb +47 -73
  41. data/spec/spec_common.rb +13 -1
  42. data/spec/string_spec.rb +34 -23
  43. data/spec/stringz_spec.rb +10 -18
  44. data/spec/struct_spec.rb +91 -63
  45. data/spec/system_spec.rb +291 -0
  46. metadata +12 -8
  47. 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::MultiValue
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::MultiValue
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::MultiValue
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. You may specify a name as nil, but this is described
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::MultiValue
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::MultiValue
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::MultiValue
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::MultiValue
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::MultiValue
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::MultiValue
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::MultiValue or
259
- BinData::SingleValue. Ocassionally it may be useful to subclass
260
- BinData::Single. Subclassing other classes may have unexpected results
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::MultiValue
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::Single object so we can use :initial_value etc. Here's an
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::MultiValue
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.to_s #=> "\004ruby\007freebsd"
280
+ f.to_binary_s #=> "\004ruby\007freebsd"
282
281
 
283
- We create this type of custom string by inheriting from BinData::SingleValue
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::SingleValue
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 single value then inherit from
295
- BinData::SingleValue, otherwise inherit from BinData::MultiValue.
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, 2008 Dion Mendel
300
+ Copyright (c) 2007 - 2009 Dion Mendel
data/TODO CHANGED
@@ -1,20 +1,26 @@
1
- * Think how offset_of should work.
1
+ * Write a Rakefile
2
2
 
3
- + perhaps #offset_of and #abs_offset_of methods?
4
- + needed for struct and array
3
+ * Registry should auto create integers and bits. (e.g. 24bit int, 191bit int)
5
4
 
6
- * Add ability to determine name of a data object.
7
- e.g. obj.a[4].c
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
- * Refactor test cases.
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::MultiValue
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::MultiValue
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::MultiValue
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/multi_value'
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.9.3"
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
- # These are the parameters used by this class.
62
- bindata_mandatory_parameter :type
63
- bindata_optional_parameters :initial_length, :read_until
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
- # Ensures that +params+ is of the form expected by #initialize.
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
- if params.has_key?(:read_length)
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.lookup_klass(type)
81
- sanitized_params = sanitizer.sanitize_params(klass, el_params)
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
- klass, el_params = no_eval_param(:type)
88
+ el_class, el_params = get_parameter(:type)
94
89
 
95
90
  @element_list = nil
96
- @element_klass = klass
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 fields are clear.
96
+ # is not given, then returns whether all elements are clear.
102
97
  def clear?(index = nil)
103
- if @element_list.nil?
104
- true
105
- elsif index.nil?
106
- elements.each { |f| return false if not f.clear? }
107
- true
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
- (index < elements.length) ? elements[index].clear? : true
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 @element_list.nil?
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 whether this data object contains a single value. Single
127
- # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
128
- def single_value?
129
- return false
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
- # To be called after calling #do_read.
133
- def done_read
134
- elements.each { |f| f.done_read }
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
- # Appends a new element to the end of the array. If the array contains
138
- # single_values then the +value+ may be provided to the call.
139
- # Returns the appended object, or value in the case of single_values.
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 index(obj)
148
- # TODO handle single values
149
- elements.index(obj)
150
+ def unshift(*args)
151
+ insert(0, *args)
152
+ self
150
153
  end
151
154
 
152
- # Pushes the given object(s) on to the end of this array.
153
- # This expression returns the array itself, so several appends may
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
- # Returns the element at +index+. If the element is a single_value
169
- # then the value of the element is returned instead.
170
- def [](*args)
171
- if args.length == 1 and ::Integer === args[0]
172
- # extend array automatically
173
- while args[0] >= elements.length
174
- append_new_element
175
- end
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
- data = elements[*args]
179
- if args.length > 1 or ::Range === args[0]
180
- data.collect { |el| (el && el.single_value?) ? el.value : el }
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
- (data && data.single_value?) ? data.value : data
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
- # Sets the element at +index+. If the element is a single_value
188
- # then the value of the element is set instead.
189
- def []=(index, value)
190
- # extend array automatically
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
- obj = elements[index]
196
- unless obj.single_value?
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
- # Iterate over each element in the array. If the elements are
204
- # single_values then the values of the elements are iterated instead.
205
- def each
206
- elements.each do |el|
207
- yield(el.single_value? ? el.value : el)
208
- end
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 elements.empty?
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
- snapshot
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
- # Reads the values for all fields in this object from +io+.
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 has_param?(:initial_length)
299
+ if has_parameter?(:initial_length)
259
300
  elements.each { |f| f.do_read(io) }
260
- elsif has_param?(:read_until)
261
- if no_eval_param(:read_until) == :eof
262
- @element_list = nil
263
- loop do
264
- element = append_new_element
265
- begin
266
- element.do_read(io)
267
- rescue
268
- @element_list.pop
269
- break
270
- end
271
- end
272
- else
273
- @element_list = nil
274
- loop do
275
- element = append_new_element
276
- element.do_read(io)
277
- variables = { :index => self.length - 1, :element => self.last,
278
- :array => self }
279
- finished = eval_param(:read_until, variables)
280
- break if finished
281
- end
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
- # Writes the values for all fields in this object to +io+.
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
- (elements.inject(0) { |sum, f| sum + f.do_num_bytes }).ceil
297
- else
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
- # Returns a snapshot of the data in this array.
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 has_param?(:initial_length)
313
- # create the desired number of instances
314
- eval_param(:initial_length).times do
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
- # ensure @element_list is initialised
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