jbangert-bindata 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. data/.gitignore +1 -0
  2. data/BSDL +22 -0
  3. data/COPYING +52 -0
  4. data/ChangeLog.rdoc +204 -0
  5. data/Gemfile +2 -0
  6. data/INSTALL +11 -0
  7. data/NEWS.rdoc +164 -0
  8. data/README.md +54 -0
  9. data/Rakefile +13 -0
  10. data/bindata.gemspec +31 -0
  11. data/doc/manual.haml +407 -0
  12. data/doc/manual.md +1649 -0
  13. data/examples/NBT.txt +149 -0
  14. data/examples/gzip.rb +161 -0
  15. data/examples/ip_address.rb +22 -0
  16. data/examples/list.rb +124 -0
  17. data/examples/nbt.rb +178 -0
  18. data/lib/bindata.rb +33 -0
  19. data/lib/bindata/alignment.rb +83 -0
  20. data/lib/bindata/array.rb +335 -0
  21. data/lib/bindata/base.rb +388 -0
  22. data/lib/bindata/base_primitive.rb +214 -0
  23. data/lib/bindata/bits.rb +87 -0
  24. data/lib/bindata/choice.rb +216 -0
  25. data/lib/bindata/count_bytes_remaining.rb +35 -0
  26. data/lib/bindata/deprecated.rb +50 -0
  27. data/lib/bindata/dsl.rb +312 -0
  28. data/lib/bindata/float.rb +80 -0
  29. data/lib/bindata/int.rb +184 -0
  30. data/lib/bindata/io.rb +274 -0
  31. data/lib/bindata/lazy.rb +105 -0
  32. data/lib/bindata/offset.rb +91 -0
  33. data/lib/bindata/params.rb +135 -0
  34. data/lib/bindata/primitive.rb +135 -0
  35. data/lib/bindata/record.rb +110 -0
  36. data/lib/bindata/registry.rb +92 -0
  37. data/lib/bindata/rest.rb +35 -0
  38. data/lib/bindata/sanitize.rb +290 -0
  39. data/lib/bindata/skip.rb +48 -0
  40. data/lib/bindata/string.rb +145 -0
  41. data/lib/bindata/stringz.rb +96 -0
  42. data/lib/bindata/struct.rb +388 -0
  43. data/lib/bindata/trace.rb +94 -0
  44. data/lib/bindata/version.rb +3 -0
  45. data/setup.rb +1585 -0
  46. data/spec/alignment_spec.rb +61 -0
  47. data/spec/array_spec.rb +331 -0
  48. data/spec/base_primitive_spec.rb +238 -0
  49. data/spec/base_spec.rb +376 -0
  50. data/spec/bits_spec.rb +163 -0
  51. data/spec/choice_spec.rb +263 -0
  52. data/spec/count_bytes_remaining_spec.rb +38 -0
  53. data/spec/deprecated_spec.rb +31 -0
  54. data/spec/example.rb +21 -0
  55. data/spec/float_spec.rb +37 -0
  56. data/spec/int_spec.rb +216 -0
  57. data/spec/io_spec.rb +352 -0
  58. data/spec/lazy_spec.rb +217 -0
  59. data/spec/primitive_spec.rb +202 -0
  60. data/spec/record_spec.rb +530 -0
  61. data/spec/registry_spec.rb +108 -0
  62. data/spec/rest_spec.rb +26 -0
  63. data/spec/skip_spec.rb +27 -0
  64. data/spec/spec_common.rb +58 -0
  65. data/spec/string_spec.rb +300 -0
  66. data/spec/stringz_spec.rb +118 -0
  67. data/spec/struct_spec.rb +350 -0
  68. data/spec/system_spec.rb +380 -0
  69. data/tasks/manual.rake +36 -0
  70. data/tasks/rspec.rake +17 -0
  71. metadata +208 -0
@@ -0,0 +1,178 @@
1
+ require 'bindata'
2
+
3
+ # An example reader for Minecraft's NBT format.
4
+ # http://www.minecraft.net/docs/NBT.txt
5
+ #
6
+ # This is an example of how to write a BinData
7
+ # declaration for a recursively defined file format.
8
+ module Nbt
9
+
10
+ TAG_NAMES = {
11
+ 0 => "End",
12
+ 1 => "Byte",
13
+ 2 => "Short",
14
+ 3 => "Int",
15
+ 4 => "Long",
16
+ 5 => "Float",
17
+ 6 => "Double",
18
+ 7 => "Byte_Array",
19
+ 8 => "String",
20
+ 9 => "List",
21
+ 10 => "Compound"
22
+ }
23
+
24
+ # NBT.txt line 25
25
+ class TagEnd < BinData::Primitive
26
+ def get; ""; end
27
+ def set(v); end
28
+
29
+ def to_formatted_s(indent = 0); to_s; end
30
+ end
31
+
32
+ # NBT.txt line 31
33
+ class TagByte < BinData::Int8
34
+ def to_formatted_s(indent = 0); to_s; end
35
+ end
36
+
37
+ # NBT.txt line 34
38
+ class TagShort < BinData::Int16be
39
+ def to_formatted_s(indent = 0); to_s; end
40
+ end
41
+
42
+ # NBT.txt line 37
43
+ class TagInt < BinData::Int32be
44
+ def to_formatted_s(indent = 0); to_s; end
45
+ end
46
+
47
+ # NBT.txt line 40
48
+ class TagLong < BinData::Int64be
49
+ def to_formatted_s(indent = 0); to_s; end
50
+ end
51
+
52
+ # NBT.txt line 43
53
+ class TagFloat < BinData::FloatBe
54
+ def to_formatted_s(indent = 0); to_s; end
55
+ end
56
+
57
+ # NBT.txt line 46
58
+ class TagDouble < BinData::DoubleBe
59
+ def to_formatted_s(indent = 0); to_s; end
60
+ end
61
+
62
+ # NBT.txt line 49
63
+ class TagByteArray < BinData::Record
64
+ int32be :len, :value => lambda { data.length }
65
+ string :data, :read_length => :len
66
+
67
+ def to_formatted_s(indent = 0)
68
+ "[#{len} bytes]"
69
+ end
70
+ end
71
+
72
+ # NBT.txt line 53
73
+ class TagString < BinData::Primitive
74
+ int16be :len, :value => lambda { data.length }
75
+ string :data, :read_length => :len
76
+
77
+ def get
78
+ self.data
79
+ end
80
+
81
+ def set(v)
82
+ self.data = v
83
+ end
84
+
85
+ def to_formatted_s(indent = 0); to_s; end
86
+ end
87
+
88
+ ## Payload is the most important class to understand.
89
+ ## This abstraction allows recursive formats.
90
+ ## eg. lists can contain lists can contain lists.
91
+
92
+ # Forward references used by Payload
93
+ class TagCompound < BinData::Record; end
94
+ class TagList < BinData::Record; end
95
+
96
+ # NBT.txt line 10
97
+ class Payload < BinData::Choice
98
+ tag_end 0
99
+ tag_byte 1
100
+ tag_short 2
101
+ tag_int 3
102
+ tag_long 4
103
+ tag_float 5
104
+ tag_double 6
105
+ tag_byte_array 7
106
+ tag_string 8
107
+ tag_list 9
108
+ tag_compound 10
109
+ end
110
+
111
+ # NBT.txt line 6, 27
112
+ class NamedTag < BinData::Record
113
+ int8 :tag_id
114
+ tag_string :name, :onlyif => :not_end_tag?
115
+ payload :payload, :onlyif => :not_end_tag?, :selection => :tag_id
116
+
117
+ def not_end_tag?
118
+ tag_id != 0
119
+ end
120
+
121
+ def to_formatted_s(indent = 0)
122
+ " " * indent +
123
+ "TAG_#{TAG_NAMES[tag_id]}(\"#{name}\"): " +
124
+ payload.to_formatted_s(indent) + "\n"
125
+ end
126
+ end
127
+
128
+ # NBT.txt line 57
129
+ class TagList < BinData::Record
130
+ int8 :tag_id
131
+ int32be :len, :value => lambda { data.length }
132
+ array :data, :initial_length => :len do
133
+ payload :selection => :tag_id
134
+ end
135
+
136
+ def to_formatted_s(indent = 0)
137
+ pre = " " * indent
138
+ tag_type = "TAG_#{TAG_NAMES[tag_id]}"
139
+
140
+ "#{len} entries of type #{tag_type}\n" +
141
+ pre + "{\n" +
142
+ data.collect { |el| " #{pre}#{tag_type}: #{el.to_formatted_s(indent + 1)}\n" }.join("") +
143
+ pre + "}"
144
+ end
145
+ end
146
+
147
+ # NBT.txt line 63
148
+ class TagCompound < BinData::Record
149
+ array :data, :read_until => lambda { element.tag_id == 0 } do
150
+ named_tag
151
+ end
152
+
153
+ def to_formatted_s(indent = 0)
154
+ pre = " " * indent
155
+ "#{data.length - 1} entries\n" +
156
+ pre + "{\n" +
157
+ data[0..-2].collect { |el| el.to_formatted_s(indent + 1) }.join("") +
158
+ pre + "}"
159
+ end
160
+ end
161
+
162
+ # NBT.txt line 3
163
+ class Nbt < NamedTag
164
+ def self.read(io)
165
+ require 'zlib'
166
+ super(Zlib::GzipReader.new(io))
167
+ end
168
+ end
169
+ end
170
+
171
+ if $0 == __FILE__
172
+ require 'stringio'
173
+
174
+ bigtest_nbt = StringIO.new "\037\213\b\000\000\000\000\000\000\003\355T\317O\032A\024~\302\002\313\226\202\261\304\020c\314\253\265\204\245\333\315B\021\211\261\210\026,\232\r\032\330\2501\206\270+\303\202.\273fw\260\361\324K{lz\353?\323#\177C\317\275\366\277\240\303/{i\317\275\3602\311\367\346\275o\346{o&y\002\004TrO,\016x\313\261M\215x\364\343pb>\b{\035\307\245\223\030\017\202G\335\356\204\002b\265\242\252\307xv\\W\313\250U\017\e\310\326\036j\225\206\206\r\255~X{\217\203\317\203O\203o\317\003\020n[\216>\276\2458Ld\375\020\352\332t\246\#@\334f.i\341\265\323\273s\372v\v)\333\v\340\357\350=\0368[\357\021\bV\365\336]\337\v@\340^\267\372d\267\004\000\214ALs\306\bUL\323 .}\244\300\310\302\020\263\272\336X\vS\243\356D\216E\0030\261'S\214L\361\351\024\243S\214\205\341\331\237\343\263\362D\201\245|3\335\330\273\307\252u\023_(\034\b\327.\321Y?\257\035\e`!Y\337\372\361\005\376\301\316\374\235\275\000\274\361@\311\370\205B@F\376\236\353\352\017\223:h\207`\273\35327\243(\n\216\273\365\320ic\312N\333\351\354\346\346+;\275%\276dI\t=\252\273\224\375\030~\350\322\016\332o\025L\261h>+\341\233\234\204\231\274\204\005\teY\026E\000\377/(\256/\362\302\262\244.\035 wZ;\271\214\312\347)\337QA\311\026\265\305m\241*\255,\3051\177\272z\222\216^\235_\370\022\005#\e\321\366\267w\252\315\225r\274\236\337X]K\227\256\222\027\271D\320\200\310\372>\277\263\334T\313\aun\243\266vY\222\223\251\334QP\231k\3145\346\032\377W#\bB\313\351\e\326x\302\354\376\374z\373}x\323\204\337\324\362\244\373\b\006\000\000"
175
+
176
+ nbt = Nbt::Nbt.read(bigtest_nbt)
177
+ puts nbt.to_formatted_s
178
+ end
@@ -0,0 +1,33 @@
1
+ # BinData -- Binary data manipulator.
2
+ # Copyright (c) 2007 - 2013 Dion Mendel.
3
+
4
+ require 'bindata/version'
5
+ require 'bindata/array'
6
+ require 'bindata/bits'
7
+ require 'bindata/choice'
8
+ require 'bindata/count_bytes_remaining'
9
+ require 'bindata/float'
10
+ require 'bindata/int'
11
+ require 'bindata/primitive'
12
+ require 'bindata/record'
13
+ require 'bindata/rest'
14
+ require 'bindata/skip'
15
+ require 'bindata/string'
16
+ require 'bindata/stringz'
17
+ require 'bindata/struct'
18
+ require 'bindata/trace'
19
+ require 'bindata/alignment'
20
+ require 'bindata/deprecated'
21
+
22
+ # = BinData
23
+ #
24
+ # A declarative way to read and write structured binary data.
25
+ #
26
+ # A full reference manual is available online at
27
+ # http://bindata.rubyforge.org/manual.html
28
+ #
29
+ # == License
30
+ #
31
+ # BinData is released under the same license as Ruby.
32
+ #
33
+ # Copyright (c) 2007 - 2013 Dion Mendel.
@@ -0,0 +1,83 @@
1
+ require 'bindata/base_primitive'
2
+
3
+ module BinData
4
+ # Resets the stream alignment to the next byte. This is
5
+ # only useful when using bit-based primitives.
6
+ #
7
+ # class MyRec < BinData::Record
8
+ # bit4 :a
9
+ # resume_byte_alignment
10
+ # bit4 :b
11
+ # end
12
+ #
13
+ # MyRec.read("\x12\x34") #=> {"a" => 1, "b" => 3}
14
+ #
15
+ class ResumeByteAlignment < BinData::Base
16
+ def clear; end
17
+ def clear?; true; end
18
+ def assign(val); end
19
+ def snapshot; nil; end
20
+ def do_num_bytes; 0; end
21
+
22
+ def do_read(io)
23
+ io.reset_read_bits
24
+ end
25
+
26
+ def do_write(io)
27
+ io.flushbits
28
+ end
29
+ end
30
+
31
+ # A monkey patch to force byte-aligned primitives to
32
+ # become bit-aligned. This allows them to be used at
33
+ # non byte based boundaries.
34
+ #
35
+ # class BitString < BinData::String
36
+ # bit_aligned
37
+ # end
38
+ #
39
+ # class MyRecord < BinData::Record
40
+ # bit4 :preamble
41
+ # bit_string :str, :length => 2
42
+ # end
43
+ #
44
+ module BitAligned
45
+ class BitAlignedIO
46
+ def initialize(io)
47
+ @io = io
48
+ end
49
+ def readbytes(n)
50
+ n.times.inject("") do |bytes, i|
51
+ bytes << @io.readbits(8, :big).chr
52
+ end
53
+ end
54
+ def method_missing(sym, *args, &block)
55
+ @io.send(sym, *args, &block)
56
+ end
57
+ end
58
+
59
+ def read_and_return_value(io)
60
+ super(BitAlignedIO.new(io))
61
+ end
62
+
63
+ def do_num_bytes
64
+ super.to_f
65
+ end
66
+
67
+ def do_write(io)
68
+ value_to_binary_string(_value).each_byte { |v| io.writebits(v, 8, :big) }
69
+ end
70
+ end
71
+
72
+ class BasePrimitive < BinData::Base
73
+ def self.bit_aligned
74
+ include BitAligned
75
+ end
76
+ end
77
+
78
+ class Primitive < BinData::BasePrimitive
79
+ def self.bit_aligned
80
+ fail "'bit_aligned' is not needed for BinData::Primitives"
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,335 @@
1
+ require 'bindata/base'
2
+ require 'bindata/dsl'
3
+
4
+ module BinData
5
+ # An Array is a list of data objects of the same type.
6
+ #
7
+ # require 'bindata'
8
+ #
9
+ # data = "\x03\x04\x05\x06\x07\x08\x09"
10
+ #
11
+ # obj = BinData::Array.new(:type => :int8, :initial_length => 6)
12
+ # obj.read(data) #=> [3, 4, 5, 6, 7, 8]
13
+ #
14
+ # obj = BinData::Array.new(:type => :int8,
15
+ # :read_until => lambda { index == 1 })
16
+ # obj.read(data) #=> [3, 4]
17
+ #
18
+ # obj = BinData::Array.new(:type => :int8,
19
+ # :read_until => lambda { element >= 6 })
20
+ # obj.read(data) #=> [3, 4, 5, 6]
21
+ #
22
+ # obj = BinData::Array.new(:type => :int8,
23
+ # :read_until => lambda { array[index] + array[index - 1] == 13 })
24
+ # obj.read(data) #=> [3, 4, 5, 6, 7]
25
+ #
26
+ # obj = BinData::Array.new(:type => :int8, :read_until => :eof)
27
+ # obj.read(data) #=> [3, 4, 5, 6, 7, 8, 9]
28
+ #
29
+ # == Parameters
30
+ #
31
+ # Parameters may be provided at initialisation to control the behaviour of
32
+ # an object. These params are:
33
+ #
34
+ # <tt>:type</tt>:: The symbol representing the data type of the
35
+ # array elements. If the type is to have params
36
+ # passed to it, then it should be provided as
37
+ # <tt>[type_symbol, hash_params]</tt>.
38
+ # <tt>:initial_length</tt>:: The initial length of the array.
39
+ # <tt>:read_until</tt>:: While reading, elements are read until this
40
+ # condition is true. This is typically used to
41
+ # read an array until a sentinel value is found.
42
+ # The variables +index+, +element+ and +array+
43
+ # are made available to any lambda assigned to
44
+ # this parameter. If the value of this parameter
45
+ # is the symbol :eof, then the array will read
46
+ # as much data from the stream as possible.
47
+ #
48
+ # Each data object in an array has the variable +index+ made available
49
+ # to any lambda evaluated as a parameter of that data object.
50
+ class Array < BinData::Base
51
+ include DSLMixin
52
+ include Enumerable
53
+
54
+ dsl_parser :array
55
+
56
+ mandatory_parameter :type
57
+ optional_parameters :initial_length, :read_until
58
+ mutually_exclusive_parameters :initial_length, :read_until
59
+
60
+ class << self
61
+
62
+ def sanitize_parameters!(params) #:nodoc:
63
+ unless params.has_parameter?(:initial_length) or
64
+ params.has_parameter?(:read_until)
65
+ # ensure one of :initial_length and :read_until exists
66
+ params[:initial_length] = 0
67
+ end
68
+
69
+ params.warn_replacement_parameter(:read_length, :initial_length)
70
+
71
+ params.merge!(dsl_params)
72
+
73
+ if params.needs_sanitizing?(:type)
74
+ el_type, el_params = params[:type]
75
+ params[:type] = params.create_sanitized_object_prototype(el_type, el_params)
76
+ end
77
+ end
78
+ end
79
+
80
+ def initialize_shared_instance
81
+ @element_prototype = get_parameter(:type)
82
+ end
83
+
84
+ def initialize_instance
85
+ @element_list = nil
86
+ end
87
+
88
+ def clear?
89
+ @element_list.nil? or elements.all? { |el| el.clear? }
90
+ end
91
+
92
+ def clear
93
+ initialize_instance
94
+ end
95
+
96
+ def assign(array)
97
+ raise ArgumentError, "can't set a nil value for #{debug_name}" if array.nil?
98
+
99
+ @element_list = to_storage_formats(array.to_ary)
100
+ end
101
+
102
+ def snapshot
103
+ elements.collect { |el| el.snapshot }
104
+ end
105
+
106
+ def find_index(obj)
107
+ elements.index(obj)
108
+ end
109
+ alias_method :index, :find_index
110
+
111
+ # Returns the first index of +obj+ in self.
112
+ #
113
+ # Uses equal? for the comparator.
114
+ def find_index_of(obj)
115
+ elements.index { |el| el.equal?(obj) }
116
+ end
117
+
118
+ def push(*args)
119
+ insert(-1, *args)
120
+ self
121
+ end
122
+ alias_method :<<, :push
123
+
124
+ def unshift(*args)
125
+ insert(0, *args)
126
+ self
127
+ end
128
+
129
+ def concat(array)
130
+ insert(-1, *array.to_ary)
131
+ self
132
+ end
133
+
134
+ def insert(index, *objs)
135
+ extend_array(index - 1)
136
+ elements.insert(index, *to_storage_formats(objs))
137
+ self
138
+ end
139
+
140
+ # Returns the element at +index+.
141
+ def [](arg1, arg2 = nil)
142
+ if arg1.respond_to?(:to_int) and arg2.nil?
143
+ slice_index(arg1.to_int)
144
+ elsif arg1.respond_to?(:to_int) and arg2.respond_to?(:to_int)
145
+ slice_start_length(arg1.to_int, arg2.to_int)
146
+ elsif arg1.is_a?(Range) and arg2.nil?
147
+ slice_range(arg1)
148
+ else
149
+ raise TypeError, "can't convert #{arg1} into Integer" unless arg1.respond_to?(:to_int)
150
+ raise TypeError, "can't convert #{arg2} into Integer" unless arg2.respond_to?(:to_int)
151
+ end
152
+ end
153
+ alias_method :slice, :[]
154
+
155
+ def slice_index(index)
156
+ extend_array(index)
157
+ at(index)
158
+ end
159
+
160
+ def slice_start_length(start, length)
161
+ elements[start, length]
162
+ end
163
+
164
+ def slice_range(range)
165
+ elements[range]
166
+ end
167
+ private :slice_index, :slice_start_length, :slice_range
168
+
169
+ # Returns the element at +index+. Unlike +slice+, if +index+ is out
170
+ # of range the array will not be automatically extended.
171
+ def at(index)
172
+ elements[index]
173
+ end
174
+
175
+ # Sets the element at +index+.
176
+ def []=(index, value)
177
+ extend_array(index)
178
+ elements[index].assign(value)
179
+ end
180
+
181
+ # Returns the first element, or the first +n+ elements, of the array.
182
+ # If the array is empty, the first form returns nil, and the second
183
+ # form returns an empty array.
184
+ def first(n = nil)
185
+ if n.nil? and empty?
186
+ # explicitly return nil as arrays grow automatically
187
+ nil
188
+ elsif n.nil?
189
+ self[0]
190
+ else
191
+ self[0, n]
192
+ end
193
+ end
194
+
195
+ # Returns the last element, or the last +n+ elements, of the array.
196
+ # If the array is empty, the first form returns nil, and the second
197
+ # form returns an empty array.
198
+ def last(n = nil)
199
+ if n.nil?
200
+ self[-1]
201
+ else
202
+ n = length if n > length
203
+ self[-n, n]
204
+ end
205
+ end
206
+
207
+ def length
208
+ elements.length
209
+ end
210
+ alias_method :size, :length
211
+
212
+ def empty?
213
+ length.zero?
214
+ end
215
+
216
+ # Allow this object to be used in array context.
217
+ def to_ary
218
+ collect { |el| el }
219
+ end
220
+
221
+ def each
222
+ elements.each { |el| yield el }
223
+ end
224
+
225
+ def debug_name_of(child) #:nodoc:
226
+ index = find_index_of(child)
227
+ "#{debug_name}[#{index}]"
228
+ end
229
+
230
+ def offset_of(child) #:nodoc:
231
+ index = find_index_of(child)
232
+ sum = sum_num_bytes_below_index(index)
233
+
234
+ child.do_num_bytes.is_a?(Integer) ? sum.ceil : sum.floor
235
+ end
236
+
237
+ def do_read(io) #:nodoc:
238
+ if has_parameter?(:initial_length)
239
+ elements.each { |el| el.do_read(io) }
240
+ elsif has_parameter?(:read_until)
241
+ read_until(io)
242
+ end
243
+ end
244
+
245
+ def do_write(io) #:nodoc:
246
+ elements.each { |el| el.do_write(io) }
247
+ end
248
+
249
+ def do_num_bytes #:nodoc:
250
+ sum_num_bytes_for_all_elements
251
+ end
252
+
253
+ #---------------
254
+ private
255
+
256
+ def extend_array(max_index)
257
+ max_length = max_index + 1
258
+ while elements.length < max_length
259
+ append_new_element
260
+ end
261
+ end
262
+
263
+ def to_storage_formats(els)
264
+ els.collect { |el| new_element(el) }
265
+ end
266
+
267
+ def read_until(io)
268
+ if get_parameter(:read_until) == :eof
269
+ read_until_eof(io)
270
+ else
271
+ read_until_condition(io)
272
+ end
273
+ end
274
+
275
+ def read_until_eof(io)
276
+ loop do
277
+ element = append_new_element
278
+ begin
279
+ element.do_read(io)
280
+ rescue EOFError, IOError
281
+ elements.pop
282
+ break
283
+ end
284
+ end
285
+ end
286
+
287
+ def read_until_condition(io)
288
+ loop do
289
+ element = append_new_element
290
+ element.do_read(io)
291
+ variables = { :index => self.length - 1, :element => self.last,
292
+ :array => self }
293
+ break if eval_parameter(:read_until, variables)
294
+ end
295
+ end
296
+
297
+ def elements
298
+ if @element_list.nil?
299
+ @element_list = []
300
+ if has_parameter?(:initial_length)
301
+ eval_parameter(:initial_length).times do
302
+ @element_list << new_element
303
+ end
304
+ end
305
+ end
306
+ @element_list
307
+ end
308
+
309
+ def append_new_element
310
+ element = new_element
311
+ elements << element
312
+ element
313
+ end
314
+
315
+ def new_element(value = nil)
316
+ @element_prototype.instantiate(value, self)
317
+ end
318
+
319
+ def sum_num_bytes_for_all_elements
320
+ sum_num_bytes_below_index(length)
321
+ end
322
+
323
+ def sum_num_bytes_below_index(index)
324
+ (0...index).inject(0) do |sum, i|
325
+ nbytes = elements[i].do_num_bytes
326
+
327
+ if nbytes.is_a?(Integer)
328
+ sum.ceil + nbytes
329
+ else
330
+ sum + nbytes
331
+ end
332
+ end
333
+ end
334
+ end
335
+ end