bindata 1.1.0 → 1.2.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 +7 -0
  2. data/README +32 -1167
  3. data/lib/bindata.rb +3 -3
  4. data/lib/bindata/array.rb +5 -6
  5. data/lib/bindata/base.rb +40 -58
  6. data/lib/bindata/base_primitive.rb +7 -11
  7. data/lib/bindata/bits.rb +47 -44
  8. data/lib/bindata/choice.rb +7 -11
  9. data/lib/bindata/deprecated.rb +17 -2
  10. data/lib/bindata/dsl.rb +332 -0
  11. data/lib/bindata/float.rb +48 -50
  12. data/lib/bindata/int.rb +66 -88
  13. data/lib/bindata/params.rb +112 -59
  14. data/lib/bindata/primitive.rb +8 -88
  15. data/lib/bindata/record.rb +11 -99
  16. data/lib/bindata/registry.rb +16 -3
  17. data/lib/bindata/rest.rb +1 -1
  18. data/lib/bindata/sanitize.rb +71 -53
  19. data/lib/bindata/skip.rb +2 -1
  20. data/lib/bindata/string.rb +3 -3
  21. data/lib/bindata/stringz.rb +1 -1
  22. data/lib/bindata/struct.rb +21 -20
  23. data/lib/bindata/trace.rb +8 -0
  24. data/lib/bindata/wrapper.rb +13 -69
  25. data/manual.haml +2 -2
  26. data/spec/array_spec.rb +1 -1
  27. data/spec/base_primitive_spec.rb +4 -4
  28. data/spec/base_spec.rb +19 -6
  29. data/spec/bits_spec.rb +5 -1
  30. data/spec/choice_spec.rb +13 -2
  31. data/spec/deprecated_spec.rb +31 -0
  32. data/spec/example.rb +5 -1
  33. data/spec/io_spec.rb +2 -4
  34. data/spec/lazy_spec.rb +10 -5
  35. data/spec/primitive_spec.rb +13 -5
  36. data/spec/record_spec.rb +149 -45
  37. data/spec/registry_spec.rb +18 -6
  38. data/spec/spec_common.rb +31 -6
  39. data/spec/string_spec.rb +0 -1
  40. data/spec/stringz_spec.rb +4 -4
  41. data/spec/struct_spec.rb +2 -2
  42. data/spec/system_spec.rb +26 -19
  43. data/spec/wrapper_spec.rb +52 -4
  44. data/tasks/manual.rake +1 -1
  45. data/tasks/pkg.rake +13 -0
  46. metadata +121 -46
  47. data/TODO +0 -3
@@ -9,16 +9,14 @@ module BinData
9
9
  def define_class(nbits, endian, signed)
10
10
  name = class_name(nbits, endian, signed)
11
11
  unless BinData.const_defined?(name)
12
- int_type = (signed == :signed) ? 'int' : 'uint'
13
- creation_method = "create_#{int_type}_methods"
14
-
15
12
  BinData.module_eval <<-END
16
13
  class #{name} < BinData::BasePrimitive
17
- register(self.name, self)
18
- Int.#{creation_method}(self, #{nbits}, :#{endian.to_s})
14
+ register_self
15
+ Int.define_methods(self, #{nbits}, :#{endian}, :#{signed})
19
16
  end
20
17
  END
21
18
  end
19
+
22
20
  BinData.const_get(name)
23
21
  end
24
22
 
@@ -29,37 +27,51 @@ module BinData
29
27
  "#{base}#{nbits}#{endian_str}"
30
28
  end
31
29
 
32
- def create_uint_methods(int_class, nbits, endian)
30
+ def define_methods(int_class, nbits, endian, signed)
33
31
  raise "nbits must be divisible by 8" unless (nbits % 8).zero?
34
32
 
35
- min = 0
36
- max = (1 << nbits) - 1
33
+ int_class.module_eval <<-END
34
+ #---------------
35
+ private
37
36
 
38
- clamp = create_clamp_code(min, max)
39
- read = create_read_code(nbits, endian)
40
- to_binary_s = create_to_binary_s_code(nbits, endian)
37
+ def _assign(val)
38
+ #{create_clamp_code(nbits, signed)}
39
+ super(val)
40
+ end
41
41
 
42
- define_methods(int_class, nbits / 8, clamp, read, to_binary_s)
43
- end
42
+ def _do_num_bytes
43
+ #{nbits / 8}
44
+ end
44
45
 
45
- def create_int_methods(int_class, nbits, endian)
46
- raise "nbits must be divisible by 8" unless (nbits % 8).zero?
46
+ def sensible_default
47
+ 0
48
+ end
47
49
 
48
- max = (1 << (nbits - 1)) - 1
49
- min = -(max + 1)
50
+ def value_to_binary_string(val)
51
+ #{create_clamp_code(nbits, signed)}
52
+ #{create_int2uint_code(nbits) if signed == :signed}
53
+ #{create_to_binary_s_code(nbits, endian)}
54
+ end
50
55
 
51
- clamp = create_clamp_code(min, max)
52
- read = create_read_code(nbits, endian)
53
- to_binary_s = create_to_binary_s_code(nbits, endian)
56
+ def read_and_return_value(io)
57
+ val = #{create_read_code(nbits, endian)}
58
+ #{create_uint2int_code(nbits) if signed == :signed}
59
+ end
60
+ END
61
+ end
54
62
 
55
- int2uint = create_int2uint_code(nbits)
56
- uint2int = create_uint2int_code(nbits)
63
+ #-------------
64
+ private
57
65
 
58
- define_methods(int_class, nbits / 8, clamp, read, to_binary_s,
59
- int2uint, uint2int)
60
- end
66
+ def create_clamp_code(nbits, signed)
67
+ if signed == :signed
68
+ max = (1 << (nbits - 1)) - 1
69
+ min = -(max + 1)
70
+ else
71
+ min = 0
72
+ max = (1 << nbits) - 1
73
+ end
61
74
 
62
- def create_clamp_code(min, max)
63
75
  "val = (val < #{min}) ? #{min} : (val > #{max}) ? #{max} : val"
64
76
  end
65
77
 
@@ -75,31 +87,22 @@ module BinData
75
87
  end
76
88
 
77
89
  def create_read_code(nbits, endian)
78
- # determine "word" size and unpack directive
79
- if (nbits % 32).zero?
80
- bytes_per_word = 4
81
- d = (endian == :big) ? 'N' : 'V'
82
- elsif (nbits % 16).zero?
83
- bytes_per_word = 2
84
- d = (endian == :big) ? 'n' : 'v'
85
- else
86
- bytes_per_word = 1
87
- d = 'C'
88
- end
89
-
90
- bits_per_word = bytes_per_word * 8
90
+ bits_per_word = bytes_per_word(nbits) * 8
91
91
  nwords = nbits / bits_per_word
92
92
  nbytes = nbits / 8
93
93
 
94
94
  idx = (0 ... nwords).to_a
95
95
  idx.reverse! if (endian == :big)
96
96
 
97
- unpack_str = "a = io.readbytes(#{nbytes}).unpack('#{d * nwords}')"
98
-
99
97
  parts = (0 ... nwords).collect do |i|
100
- i.zero? ? "a.at(#{idx[i]})" :
101
- "(a.at(#{idx[i]}) << #{bits_per_word * i})"
98
+ if i.zero?
99
+ "a.at(#{idx[i]})"
100
+ else
101
+ "(a.at(#{idx[i]}) << #{bits_per_word * i})"
102
+ end
102
103
  end
104
+
105
+ unpack_str = "a = io.readbytes(#{nbytes}).unpack('#{pack_directive(nbits, endian)}')"
103
106
  assemble_str = parts.join(" + ")
104
107
 
105
108
  "(#{unpack_str}; #{assemble_str})"
@@ -109,19 +112,7 @@ module BinData
109
112
  # special case 8bit integers for speed
110
113
  return "val.chr" if nbits == 8
111
114
 
112
- # determine "word" size and pack directive
113
- if (nbits % 32).zero?
114
- bytes_per_word = 4
115
- d = (endian == :big) ? 'N' : 'V'
116
- elsif (nbits % 16).zero?
117
- bytes_per_word = 2
118
- d = (endian == :big) ? 'n' : 'v'
119
- else
120
- bytes_per_word = 1
121
- d = 'C'
122
- end
123
-
124
- bits_per_word = bytes_per_word * 8
115
+ bits_per_word = bytes_per_word(nbits) * 8
125
116
  nwords = nbits / bits_per_word
126
117
  mask = (1 << bits_per_word) - 1
127
118
 
@@ -133,39 +124,26 @@ module BinData
133
124
  parts = (0 ... nwords).collect { |i| "#{vals[i]} & #{mask}" }
134
125
  array_str = "[" + parts.join(", ") + "]"
135
126
 
136
- "#{array_str}.pack('#{d * nwords}')"
127
+ "#{array_str}.pack('#{pack_directive(nbits, endian)}')"
137
128
  end
138
129
 
139
- def define_methods(int_class, nbytes, clamp, read, to_binary_s,
140
- int2uint = nil, uint2int = nil)
141
- int_class.module_eval <<-END
142
- #---------------
143
- private
144
-
145
- def _assign(val)
146
- #{clamp}
147
- super(val)
148
- end
149
-
150
- def _do_num_bytes
151
- #{nbytes}
152
- end
130
+ def bytes_per_word(nbits)
131
+ (nbits % 32).zero? ? 4 : (nbits % 16).zero? ? 2 : 1
132
+ end
153
133
 
154
- def sensible_default
155
- 0
156
- end
134
+ def pack_directive(nbits, endian)
135
+ bits_per_word = bytes_per_word(nbits) * 8
136
+ nwords = nbits / bits_per_word
157
137
 
158
- def value_to_binary_string(val)
159
- #{clamp}
160
- #{int2uint unless int2uint.nil?}
161
- #{to_binary_s}
162
- end
138
+ if (nbits % 32).zero?
139
+ d = (endian == :big) ? 'N' : 'V'
140
+ elsif (nbits % 16).zero?
141
+ d = (endian == :big) ? 'n' : 'v'
142
+ else
143
+ d = 'C'
144
+ end
163
145
 
164
- def read_and_return_value(io)
165
- val = #{read}
166
- #{uint2int unless uint2int.nil?}
167
- end
168
- END
146
+ d * nwords
169
147
  end
170
148
  end
171
149
  end
@@ -173,14 +151,14 @@ module BinData
173
151
 
174
152
  # Unsigned 1 byte integer.
175
153
  class Uint8 < BinData::BasePrimitive
176
- register(self.name, self)
177
- Int.create_uint_methods(self, 8, :little)
154
+ register_self
155
+ Int.define_methods(self, 8, :little, :unsigned)
178
156
  end
179
157
 
180
158
  # Signed 1 byte integer.
181
159
  class Int8 < BinData::BasePrimitive
182
- register(self.name, self)
183
- Int.create_int_methods(self, 8, :little)
160
+ register_self
161
+ Int.define_methods(self, 8, :little, :signed)
184
162
  end
185
163
 
186
164
  # Create classes on demand
@@ -1,82 +1,135 @@
1
1
  require 'bindata/lazy'
2
2
 
3
3
  module BinData
4
- # BinData objects accept parameters when initializing. AcceptedParameters
5
- # allow a BinData class to declaratively identify accepted parameters as
6
- # mandatory, optional, default or mutually exclusive.
7
- class AcceptedParameters
8
-
9
- def self.invalid_parameter_names
10
- unless defined? @invalid_names
11
- all_names = LazyEvaluator.instance_methods(true) + Kernel.methods
12
- all_names.collect! { |name| name.to_s }
13
- allowed_names = ["type"]
14
- @invalid_names = all_names - allowed_names
15
- end
16
- @invalid_names
4
+ module AcceptedParametersMixin
5
+ def self.included(base) #:nodoc:
6
+ base.extend ClassMethods
17
7
  end
18
8
 
19
- def initialize(ancestor_params = nil)
20
- @mandatory = ancestor_params ? ancestor_params.mandatory : []
21
- @optional = ancestor_params ? ancestor_params.optional : []
22
- @default = ancestor_params ? ancestor_params.default : Hash.new
23
- @mutually_exclusive = ancestor_params ?
24
- ancestor_params.mutually_exclusive : []
25
- end
9
+ # Class methods to mix in to BinData::Base
10
+ module ClassMethods
11
+ # Mandatory parameters must be present when instantiating a data object.
12
+ def mandatory_parameters(*args)
13
+ accepted_parameters.mandatory(*args)
14
+ end
26
15
 
27
- def mandatory(*args)
28
- if not args.empty?
29
- ensure_valid_names(args)
30
- @mandatory.concat(args.collect { |arg| arg.to_sym })
31
- @mandatory.uniq!
16
+ # Optional parameters may be present when instantiating a data object.
17
+ def optional_parameters(*args)
18
+ accepted_parameters.optional(*args)
32
19
  end
33
- @mandatory.dup
34
- end
35
20
 
36
- def optional(*args)
37
- if not args.empty?
38
- ensure_valid_names(args)
39
- @optional.concat(args.collect { |arg| arg.to_sym })
40
- @optional.uniq!
21
+ # Default parameters can be overridden when instantiating a data object.
22
+ def default_parameters(*args)
23
+ accepted_parameters.default(*args)
41
24
  end
42
- @optional.dup
43
- end
44
25
 
45
- def default(args = {})
46
- if not args.empty?
47
- ensure_valid_names(args.keys)
48
- args.each_pair do |param, value|
49
- @default[param.to_sym] = value
26
+ # Mutually exclusive parameters may not all be present when
27
+ # instantiating a data object.
28
+ def mutually_exclusive_parameters(*args)
29
+ accepted_parameters.mutually_exclusive(*args)
30
+ end
31
+
32
+ alias_method :mandatory_parameter, :mandatory_parameters
33
+ alias_method :optional_parameter, :optional_parameters
34
+ alias_method :default_parameter, :default_parameters
35
+
36
+ def accepted_parameters #:nodoc:
37
+ unless defined? @accepted_parameters
38
+ ancestor_params = superclass.respond_to?(:accepted_parameters) ?
39
+ superclass.accepted_parameters : nil
40
+ @accepted_parameters = AcceptedParameters.new(ancestor_params)
50
41
  end
42
+ @accepted_parameters
51
43
  end
52
- @default.dup
53
44
  end
54
45
 
55
- def mutually_exclusive(*args)
56
- arg1, arg2 = args
57
- if arg1 != nil && arg2 != nil
58
- @mutually_exclusive.push([arg1.to_sym, arg2.to_sym])
59
- @mutually_exclusive.uniq!
46
+ # BinData objects accept parameters when initializing. AcceptedParameters
47
+ # allow a BinData class to declaratively identify accepted parameters as
48
+ # mandatory, optional, default or mutually exclusive.
49
+ class AcceptedParameters
50
+
51
+ def initialize(ancestor_parameters = nil)
52
+ if ancestor_parameters
53
+ @mandatory = ancestor_parameters.mandatory.dup
54
+ @optional = ancestor_parameters.optional.dup
55
+ @default = ancestor_parameters.default.dup
56
+ @mutually_exclusive = ancestor_parameters.mutually_exclusive.dup
57
+ else
58
+ @mandatory = []
59
+ @optional = []
60
+ @default = Hash.new
61
+ @mutually_exclusive = []
62
+ end
60
63
  end
61
- @mutually_exclusive.dup
62
- end
63
64
 
64
- def all
65
- (@mandatory + @optional + @default.keys).uniq
66
- end
65
+ def mandatory(*args)
66
+ if not args.empty?
67
+ @mandatory.concat(to_syms(args))
68
+ @mandatory.uniq!
69
+ end
70
+ @mandatory
71
+ end
67
72
 
68
- #---------------
69
- private
73
+ def optional(*args)
74
+ if not args.empty?
75
+ @optional.concat(to_syms(args))
76
+ @optional.uniq!
77
+ end
78
+ @optional
79
+ end
70
80
 
71
- def ensure_valid_names(names)
72
- invalid_names = self.class.invalid_parameter_names
73
- names.each do |name|
74
- name = name.to_s
75
- if invalid_names.include?(name)
76
- raise NameError.new("Rename parameter '#{name}' " +
77
- "as it shadows an existing method.", name)
81
+ def default(args = {})
82
+ if not args.empty?
83
+ to_syms(args.keys) # call for side effect of validating names
84
+ args.each_pair do |param, value|
85
+ @default[param.to_sym] = value
86
+ end
78
87
  end
88
+ @default
89
+ end
90
+
91
+ def mutually_exclusive(*args)
92
+ arg1, arg2 = args
93
+ if arg1 != nil && arg2 != nil
94
+ @mutually_exclusive.push([arg1.to_sym, arg2.to_sym])
95
+ @mutually_exclusive.uniq!
96
+ end
97
+ @mutually_exclusive
98
+ end
99
+
100
+ def all
101
+ (@mandatory + @optional + @default.keys).uniq
102
+ end
103
+
104
+ #---------------
105
+ private
106
+
107
+ def to_syms(args)
108
+ syms = args.collect { |el| el.to_sym }
109
+ ensure_valid_names(syms)
110
+ syms
111
+ end
112
+
113
+ def ensure_valid_names(names)
114
+ invalid_names = self.class.invalid_parameter_names
115
+ names.each do |name|
116
+ if invalid_names.include?(name)
117
+ raise NameError.new("Rename parameter '#{name}' " +
118
+ "as it shadows an existing method.", name)
119
+ end
120
+ end
121
+ end
122
+
123
+ def self.invalid_parameter_names
124
+ unless defined? @invalid_names
125
+ all_names = LazyEvaluator.instance_methods(true) + Kernel.methods
126
+ allowed_names = ["type", :type] # ruby 1.8 vs 1.9
127
+ invalid_names = (all_names - allowed_names).uniq
128
+ @invalid_names = Hash[*invalid_names.collect { |key| [key.to_sym, true] }.flatten]
129
+ end
130
+ @invalid_names
79
131
  end
80
132
  end
81
133
  end
82
134
  end
135
+
@@ -1,4 +1,5 @@
1
1
  require 'bindata/base_primitive'
2
+ require 'bindata/dsl'
2
3
  require 'bindata/struct'
3
4
 
4
5
  module BinData
@@ -59,98 +60,21 @@ module BinData
59
60
  # Primitive objects accept all the parameters that BinData::BasePrimitive do.
60
61
  #
61
62
  class Primitive < BasePrimitive
63
+ include DSLMixin
62
64
 
63
- class << self
64
-
65
- def inherited(subclass) #:nodoc:
66
- # Register the names of all subclasses of this class.
67
- register(subclass.name, subclass)
68
- end
69
-
70
- def endian(endian = nil)
71
- @endian ||= default_endian
72
- if [:little, :big].include?(endian)
73
- @endian = endian
74
- elsif endian != nil
75
- raise ArgumentError,
76
- "unknown value for endian '#{endian}' in #{self}", caller(1)
77
- end
78
- @endian
79
- end
80
-
81
- def fields #:nodoc:
82
- @fields ||= default_fields
83
- end
84
-
85
- def method_missing(symbol, *args) #:nodoc:
86
- name, params = args
87
-
88
- if name.is_a?(Hash)
89
- params = name
90
- name = nil
91
- end
92
-
93
- type = symbol
94
- name = name.to_s
95
- params ||= {}
96
-
97
- append_field(type, name, params)
98
- end
65
+ register_subclasses
66
+ dsl_parser :multiple_fields, :optional_fieldnames, :sanitize_fields
99
67
 
68
+ class << self
100
69
  def sanitize_parameters!(params, sanitizer) #:nodoc:
101
- struct_params = {}
102
- struct_params[:fields] = fields
103
- struct_params[:endian] = endian unless endian.nil?
104
-
105
- params[:struct_params] = struct_params
106
- end
107
-
108
- #-------------
109
- private
110
-
111
- def parent_primitive
112
- ancestors[1..-1].find { |cls|
113
- cls.ancestors[1..-1].include?(BinData::Primitive)
114
- }
115
- end
116
-
117
- def default_endian
118
- prim = parent_primitive
119
- prim ? prim.endian : nil
120
- end
121
-
122
- def default_fields
123
- prim = parent_primitive
124
- if prim
125
- Sanitizer.new.clone_sanitized_fields(prim.fields)
126
- else
127
- Sanitizer.new.create_sanitized_fields
128
- end
129
- end
130
-
131
- def append_field(type, name, params)
132
- ensure_valid_name(name)
133
-
134
- fields.add_field(type, name, params, endian)
135
- rescue UnknownTypeError => err
136
- raise TypeError, "unknown type '#{err.message}' for #{self}", caller(2)
137
- end
138
-
139
- def ensure_valid_name(name)
140
- if fields.field_names.include?(name)
141
- raise SyntaxError, "duplicate field '#{name}' in #{self}", caller(3)
142
- end
143
- if self.instance_methods.collect { |meth| meth.to_s }.include?(name)
144
- raise NameError.new("", name),
145
- "field '#{name}' shadows an existing method in #{self}", caller(3)
146
- end
70
+ params[:struct_params] = sanitizer.create_sanitized_params(to_struct_params, BinData::Struct)
147
71
  end
148
72
  end
149
73
 
150
74
  mandatory_parameter :struct_params
151
75
 
152
- def initialize(params = {}, parent = nil)
153
- super(params, parent)
76
+ def initialize(parameters = {}, parent = nil)
77
+ super
154
78
 
155
79
  @struct = BinData::Struct.new(get_parameter(:struct_params), self)
156
80
  end
@@ -163,10 +87,6 @@ module BinData
163
87
  debug_name + "-internal-"
164
88
  end
165
89
 
166
- def offset_of(child) #:nodoc:
167
- @struct.offset_of(child)
168
- end
169
-
170
90
  #---------------
171
91
  private
172
92