bindata 0.9.2 → 0.9.3

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  = BinData Changelog
2
2
 
3
+ == Version 0.9.3 (2008-12-03)
4
+
5
+ * Arrays can now :read_until => :eof
6
+ * TCPSocket and UDPSocket can now be used as input streams (patch courtesy
7
+ of Peter Suschlik).
8
+ * Added 128 bit integers.
9
+ * Significant memory usage reduction.
10
+ * Added custom mandatory and default parameters for user defined MultiValues.
11
+
3
12
  == Version 0.9.2 (2008-07-18)
4
13
 
5
14
  * Added lazy instantiation to allow recursive definitions.
data/TODO CHANGED
@@ -1,3 +1,20 @@
1
1
  * Think how offset_of should work.
2
2
 
3
- * Add (Multi|Single)Value.custom_parameters
3
+ + perhaps #offset_of and #abs_offset_of methods?
4
+ + needed for struct and array
5
+
6
+ * Add ability to determine name of a data object.
7
+ e.g. obj.a[4].c
8
+
9
+ This will be used when throwing exceptions to aid debugging.
10
+
11
+ * Using the above names, add tracing capability when reading.
12
+
13
+ * Clean up Array to make it as close to ruby Array as possible.
14
+
15
+ * Need more examples.
16
+
17
+ * Refactor test cases.
18
+ add more comprehensive integration tests to serve as examples
19
+
20
+ * Need better documentation.
data/lib/bindata.rb CHANGED
@@ -18,5 +18,5 @@ require 'bindata/struct'
18
18
  # A declarative way to read and write structured binary data.
19
19
  #
20
20
  module BinData
21
- VERSION = "0.9.2"
21
+ VERSION = "0.9.3"
22
22
  end
data/lib/bindata/array.rb CHANGED
@@ -27,6 +27,10 @@ module BinData
27
27
  # obj.read(data)
28
28
  # obj.snapshot #=> [3, 4, 5, 6, 7]
29
29
  #
30
+ # obj = BinData::Array.new(:type => :int8, :read_until => :eof)
31
+ # obj.read(data)
32
+ # obj.snapshot #=> [3, 4, 5, 6, 7, 8, 9]
33
+ #
30
34
  # == Parameters
31
35
  #
32
36
  # Parameters may be provided at initialisation to control the behaviour of
@@ -42,7 +46,9 @@ module BinData
42
46
  # read an array until a sentinel value is found.
43
47
  # The variables +index+, +element+ and +array+
44
48
  # are made available to any lambda assigned to
45
- # this parameter.
49
+ # this parameter. If the value of this parameter
50
+ # is the symbol :eof, then the array will read
51
+ # as much data from the stream as possible.
46
52
  #
47
53
  # Each data object in an array has the variable +index+ made available
48
54
  # to any lambda evaluated as a parameter of that data object.
@@ -53,16 +59,13 @@ module BinData
53
59
  register(self.name, self)
54
60
 
55
61
  # These are the parameters used by this class.
56
- mandatory_parameter :type
57
- optional_parameters :initial_length, :read_until
58
- mutually_exclusive_parameters :initial_length, :read_until
62
+ bindata_mandatory_parameter :type
63
+ bindata_optional_parameters :initial_length, :read_until
64
+ bindata_mutually_exclusive_parameters :initial_length, :read_until
59
65
 
60
66
  class << self
61
- # Returns a sanitized +params+ that is of the form expected
62
- # by #initialize.
63
- def sanitize_parameters(sanitizer, params)
64
- params = params.dup
65
-
67
+ # Ensures that +params+ is of the form expected by #initialize.
68
+ def sanitize_parameters!(sanitizer, params)
66
69
  unless params.has_key?(:initial_length) or params.has_key?(:read_until)
67
70
  # ensure one of :initial_length and :read_until exists
68
71
  params[:initial_length] = 0
@@ -74,7 +77,9 @@ module BinData
74
77
 
75
78
  if params.has_key?(:type)
76
79
  type, el_params = params[:type]
77
- params[:type] = sanitizer.sanitize(type, el_params)
80
+ klass = sanitizer.lookup_klass(type)
81
+ sanitized_params = sanitizer.sanitize_params(klass, el_params)
82
+ params[:type] = [klass, sanitized_params]
78
83
  end
79
84
 
80
85
  super(sanitizer, params)
@@ -82,10 +87,10 @@ module BinData
82
87
  end
83
88
 
84
89
  # Creates a new Array
85
- def initialize(params = {}, env = nil)
86
- super(params, env)
90
+ def initialize(params = {}, parent = nil)
91
+ super(params, parent)
87
92
 
88
- klass, el_params = param(:type)
93
+ klass, el_params = no_eval_param(:type)
89
94
 
90
95
  @element_list = nil
91
96
  @element_klass = klass
@@ -139,6 +144,11 @@ module BinData
139
144
  self.last
140
145
  end
141
146
 
147
+ def index(obj)
148
+ # TODO handle single values
149
+ elements.index(obj)
150
+ end
151
+
142
152
  # Pushes the given object(s) on to the end of this array.
143
153
  # This expression returns the array itself, so several appends may
144
154
  # be chained together.
@@ -166,7 +176,7 @@ module BinData
166
176
  end
167
177
 
168
178
  data = elements[*args]
169
- if data.respond_to?(:each)
179
+ if args.length > 1 or ::Range === args[0]
170
180
  data.collect { |el| (el && el.single_value?) ? el.value : el }
171
181
  else
172
182
  (data && data.single_value?) ? data.value : data
@@ -202,13 +212,11 @@ module BinData
202
212
  # If the array is empty, the first form returns nil, and the second
203
213
  # form returns an empty array.
204
214
  def first(n = nil)
205
- if n.nil?
206
- if elements.empty?
207
- # explicitly return nil as arrays grow automatically
208
- nil
209
- else
210
- self[0]
211
- end
215
+ if n.nil? and elements.empty?
216
+ # explicitly return nil as arrays grow automatically
217
+ nil
218
+ elsif n.nil?
219
+ self[0]
212
220
  else
213
221
  self[0, n]
214
222
  end
@@ -249,15 +257,28 @@ module BinData
249
257
  def _do_read(io)
250
258
  if has_param?(:initial_length)
251
259
  elements.each { |f| f.do_read(io) }
252
- else # :read_until
253
- @element_list = nil
254
- loop do
255
- element = append_new_element
256
- element.do_read(io)
257
- variables = { :index => self.length - 1, :element => self.last,
258
- :array => self }
259
- finished = eval_param(:read_until, variables)
260
- break if finished
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
261
282
  end
262
283
  end
263
284
  end
@@ -304,9 +325,7 @@ module BinData
304
325
  # ensure @element_list is initialised
305
326
  elements()
306
327
 
307
- env = create_env
308
- env.add_variable(:index, @element_list.length)
309
- element = @element_klass.new(@element_params, env)
328
+ element = @element_klass.new(@element_params, self)
310
329
  @element_list << element
311
330
  element
312
331
  end
data/lib/bindata/base.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'bindata/io'
2
2
  require 'bindata/lazy'
3
+ require 'bindata/params'
3
4
  require 'bindata/registry'
4
5
  require 'bindata/sanitize'
5
6
  require 'stringio'
@@ -32,98 +33,43 @@ module BinData
32
33
  # <tt>:check_offset</tt>, except that it will
33
34
  # adjust the IO offset instead of raising an error.
34
35
  class Base
36
+
35
37
  class << self
36
- # Returns the mandatory parameters used by this class. Any given args
37
- # are appended to the parameters list. The parameters for a class will
38
- # include the parameters of its ancestors.
39
- def mandatory_parameters(*args)
40
- unless defined? @mandatory_parameters
41
- @mandatory_parameters = []
42
- ancestors[1..-1].each do |parent|
43
- if parent.respond_to?(:mandatory_parameters)
44
- pmp = parent.mandatory_parameters
45
- @mandatory_parameters.concat(pmp)
46
- end
47
- end
48
- end
49
- if not args.empty?
50
- args.each { |arg| @mandatory_parameters << arg.to_sym }
51
- @mandatory_parameters.uniq!
52
- end
53
- @mandatory_parameters
54
- end
55
- alias_method :mandatory_parameter, :mandatory_parameters
56
-
57
- # Returns the optional parameters used by this class. Any given args
58
- # are appended to the parameters list. The parameters for a class will
59
- # include the parameters of its ancestors.
60
- def optional_parameters(*args)
61
- unless defined? @optional_parameters
62
- @optional_parameters = []
63
- ancestors[1..-1].each do |parent|
64
- if parent.respond_to?(:optional_parameters)
65
- pop = parent.optional_parameters
66
- @optional_parameters.concat(pop)
67
- end
68
- end
69
- end
70
- if not args.empty?
71
- args.each { |arg| @optional_parameters << arg.to_sym }
72
- @optional_parameters.uniq!
73
- end
74
- @optional_parameters
38
+ extend Parameters
39
+
40
+ # Define methods for:
41
+ # bindata_mandatory_parameters
42
+ # bindata_optional_parameters
43
+ # bindata_default_parameters
44
+ # bindata_mutually_exclusive_parameters
45
+
46
+ define_x_parameters(:bindata_mandatory, []) do |array, args|
47
+ args.each { |arg| array << arg.to_sym }
48
+ array.uniq!
75
49
  end
76
- alias_method :optional_parameter, :optional_parameters
77
-
78
- # Returns the default parameters used by this class. Any given args
79
- # are appended to the parameters list. The parameters for a class will
80
- # include the parameters of its ancestors.
81
- def default_parameters(params = {})
82
- unless defined? @default_parameters
83
- @default_parameters = {}
84
- ancestors[1..-1].each do |parent|
85
- if parent.respond_to?(:default_parameters)
86
- pdp = parent.default_parameters
87
- @default_parameters = @default_parameters.merge(pdp)
88
- end
89
- end
90
- end
91
- if not params.empty?
92
- @default_parameters = @default_parameters.merge(params)
93
- end
94
- @default_parameters
50
+
51
+ define_x_parameters(:bindata_optional, []) do |array, args|
52
+ args.each { |arg| array << arg.to_sym }
53
+ array.uniq!
95
54
  end
96
- alias_method :default_parameter, :default_parameters
97
-
98
- # Returns the pairs of mutually exclusive parameters used by this class.
99
- # Any given args are appended to the parameters list. The parameters for
100
- # a class will include the parameters of its ancestors.
101
- def mutually_exclusive_parameters(*args)
102
- unless defined? @mutually_exclusive_parameters
103
- @mutually_exclusive_parameters = []
104
- ancestors[1..-1].each do |parent|
105
- if parent.respond_to?(:mutually_exclusive_parameters)
106
- pmep = parent.mutually_exclusive_parameters
107
- @mutually_exclusive_parameters.concat(pmep)
108
- end
109
- end
110
- end
111
- if not args.empty?
112
- @mutually_exclusive_parameters << [args[0].to_sym, args[1].to_sym]
113
- end
114
- @mutually_exclusive_parameters
55
+
56
+ define_x_parameters(:bindata_default, {}) do |hash, args|
57
+ params = args.length > 0 ? args[0] : {}
58
+ hash.merge!(params)
115
59
  end
116
60
 
117
- # Returns a list of parameters that are accepted by this object
118
- def accepted_parameters
119
- (mandatory_parameters + optional_parameters + default_parameters.keys).uniq
61
+ define_x_parameters(:bindata_mutually_exclusive, []) do |array, args|
62
+ array << [args[0].to_sym, args[1].to_sym]
120
63
  end
121
64
 
122
- # Returns a sanitized +params+ that is of the form expected
123
- # by #initialize.
124
- def sanitize_parameters(sanitizer, params, *args)
125
- params = params.dup
65
+ # Returns a list of internal parameters that are accepted by this object
66
+ def internal_parameters
67
+ (bindata_mandatory_parameters + bindata_optional_parameters +
68
+ bindata_default_parameters.keys).uniq
69
+ end
126
70
 
71
+ # Ensures that +params+ is of the form expected by #initialize.
72
+ def sanitize_parameters!(sanitizer, params)
127
73
  # replace :readwrite with :onlyif
128
74
  if params.has_key?(:readwrite)
129
75
  warn ":readwrite is deprecated. Replacing with :onlyif"
@@ -131,12 +77,12 @@ module BinData
131
77
  end
132
78
 
133
79
  # add default parameters
134
- default_parameters.each do |k,v|
80
+ bindata_default_parameters.each do |k,v|
135
81
  params[k] = v unless params.has_key?(k)
136
82
  end
137
83
 
138
84
  # ensure mandatory parameters exist
139
- mandatory_parameters.each do |prm|
85
+ bindata_mandatory_parameters.each do |prm|
140
86
  if not params.has_key?(prm)
141
87
  raise ArgumentError, "parameter ':#{prm}' must be specified " +
142
88
  "in #{self}"
@@ -144,14 +90,17 @@ module BinData
144
90
  end
145
91
 
146
92
  # ensure mutual exclusion
147
- mutually_exclusive_parameters.each do |param1, param2|
93
+ bindata_mutually_exclusive_parameters.each do |param1, param2|
148
94
  if params.has_key?(param1) and params.has_key?(param2)
149
95
  raise ArgumentError, "params #{param1} and #{param2} " +
150
96
  "are mutually exclusive"
151
97
  end
152
98
  end
99
+ end
153
100
 
154
- params
101
+ # Can this data object self reference itself?
102
+ def recursive?
103
+ false
155
104
  end
156
105
 
157
106
  # Instantiates this class and reads from +io+. For single value objects
@@ -171,26 +120,27 @@ module BinData
171
120
  end
172
121
 
173
122
  # Define the parameters we use in this class.
174
- optional_parameters :check_offset, :adjust_offset
175
- default_parameters :onlyif => true
176
- mutually_exclusive_parameters :check_offset, :adjust_offset
123
+ bindata_optional_parameters :check_offset, :adjust_offset
124
+ bindata_default_parameters :onlyif => true
125
+ bindata_mutually_exclusive_parameters :check_offset, :adjust_offset
177
126
 
178
127
  # Creates a new data object.
179
128
  #
180
129
  # +params+ is a hash containing symbol keys. Some params may
181
- # reference callable objects (methods or procs). +env+ is the
182
- # environment that these callable objects are evaluated in.
183
- def initialize(params = {}, env = nil)
184
- unless SanitizedParameters === params
185
- params = Sanitizer.sanitize(self, params)
186
- end
130
+ # reference callable objects (methods or procs). +parent+ is the
131
+ # parent data object (e.g. struct, array, choice) this object resides
132
+ # under.
133
+ def initialize(params = {}, parent = nil)
134
+ @params = Sanitizer.sanitize(self.class, params)
135
+ @parent = parent
136
+ end
187
137
 
188
- @params = params.accepted_parameters
138
+ # The parent data object.
139
+ attr_accessor :parent
189
140
 
190
- # set up the environment
191
- @env = env || LazyEvalEnv.new
192
- @env.params = params.extra_parameters
193
- @env.data_object = self
141
+ # Returns all the custom parameters supplied to this data object.
142
+ def parameters
143
+ @params.extra_parameters
194
144
  end
195
145
 
196
146
  # Reads data into this data object by calling #do_read then #done_read.
@@ -272,33 +222,33 @@ module BinData
272
222
  snapshot.inspect
273
223
  end
274
224
 
225
+ # Returns the object this object represents.
226
+ def obj
227
+ self
228
+ end
229
+
275
230
  #---------------
276
231
  private
277
232
 
278
- # Creates a new LazyEvalEnv for use by a child data object.
279
- def create_env
280
- LazyEvalEnv.new(@env)
281
- end
282
-
283
233
  # Returns the value of the evaluated parameter. +key+ references a
284
234
  # parameter from the +params+ hash used when creating the data object.
285
235
  # +values+ contains data that may be accessed when evaluating +key+.
286
236
  # Returns nil if +key+ does not refer to any parameter.
287
237
  def eval_param(key, values = nil)
288
- @env.lazy_eval(@params[key], values)
238
+ LazyEvaluator.eval(no_eval_param(key), self, values)
289
239
  end
290
240
 
291
241
  # Returns the parameter from the +params+ hash referenced by +key+.
292
242
  # Use this method if you are sure the parameter is not to be evaluated.
293
243
  # You most likely want #eval_param.
294
- def param(key)
295
- @params[key]
244
+ def no_eval_param(key)
245
+ @params.internal_parameters[key]
296
246
  end
297
247
 
298
248
  # Returns whether +key+ exists in the +params+ hash used when creating
299
249
  # this data object.
300
250
  def has_param?(key)
301
- @params.has_key?(key.to_sym)
251
+ @params.internal_parameters.has_key?(key)
302
252
  end
303
253
 
304
254
  # Checks that the current offset of +io+ is as expected. This should