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.
- data/.gitignore +1 -0
- data/BSDL +22 -0
- data/COPYING +52 -0
- data/ChangeLog.rdoc +204 -0
- data/Gemfile +2 -0
- data/INSTALL +11 -0
- data/NEWS.rdoc +164 -0
- data/README.md +54 -0
- data/Rakefile +13 -0
- data/bindata.gemspec +31 -0
- data/doc/manual.haml +407 -0
- data/doc/manual.md +1649 -0
- data/examples/NBT.txt +149 -0
- data/examples/gzip.rb +161 -0
- data/examples/ip_address.rb +22 -0
- data/examples/list.rb +124 -0
- data/examples/nbt.rb +178 -0
- data/lib/bindata.rb +33 -0
- data/lib/bindata/alignment.rb +83 -0
- data/lib/bindata/array.rb +335 -0
- data/lib/bindata/base.rb +388 -0
- data/lib/bindata/base_primitive.rb +214 -0
- data/lib/bindata/bits.rb +87 -0
- data/lib/bindata/choice.rb +216 -0
- data/lib/bindata/count_bytes_remaining.rb +35 -0
- data/lib/bindata/deprecated.rb +50 -0
- data/lib/bindata/dsl.rb +312 -0
- data/lib/bindata/float.rb +80 -0
- data/lib/bindata/int.rb +184 -0
- data/lib/bindata/io.rb +274 -0
- data/lib/bindata/lazy.rb +105 -0
- data/lib/bindata/offset.rb +91 -0
- data/lib/bindata/params.rb +135 -0
- data/lib/bindata/primitive.rb +135 -0
- data/lib/bindata/record.rb +110 -0
- data/lib/bindata/registry.rb +92 -0
- data/lib/bindata/rest.rb +35 -0
- data/lib/bindata/sanitize.rb +290 -0
- data/lib/bindata/skip.rb +48 -0
- data/lib/bindata/string.rb +145 -0
- data/lib/bindata/stringz.rb +96 -0
- data/lib/bindata/struct.rb +388 -0
- data/lib/bindata/trace.rb +94 -0
- data/lib/bindata/version.rb +3 -0
- data/setup.rb +1585 -0
- data/spec/alignment_spec.rb +61 -0
- data/spec/array_spec.rb +331 -0
- data/spec/base_primitive_spec.rb +238 -0
- data/spec/base_spec.rb +376 -0
- data/spec/bits_spec.rb +163 -0
- data/spec/choice_spec.rb +263 -0
- data/spec/count_bytes_remaining_spec.rb +38 -0
- data/spec/deprecated_spec.rb +31 -0
- data/spec/example.rb +21 -0
- data/spec/float_spec.rb +37 -0
- data/spec/int_spec.rb +216 -0
- data/spec/io_spec.rb +352 -0
- data/spec/lazy_spec.rb +217 -0
- data/spec/primitive_spec.rb +202 -0
- data/spec/record_spec.rb +530 -0
- data/spec/registry_spec.rb +108 -0
- data/spec/rest_spec.rb +26 -0
- data/spec/skip_spec.rb +27 -0
- data/spec/spec_common.rb +58 -0
- data/spec/string_spec.rb +300 -0
- data/spec/stringz_spec.rb +118 -0
- data/spec/struct_spec.rb +350 -0
- data/spec/system_spec.rb +380 -0
- data/tasks/manual.rake +36 -0
- data/tasks/rspec.rake +17 -0
- metadata +208 -0
data/lib/bindata/bits.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'bindata/base_primitive'
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# Defines a number of classes that contain a bit based integer.
|
5
|
+
# The integer is defined by endian and number of bits.
|
6
|
+
|
7
|
+
module BitField #:nodoc: all
|
8
|
+
class << self
|
9
|
+
def define_class(nbits, endian)
|
10
|
+
name = "Bit#{nbits}"
|
11
|
+
name << "le" if endian == :little
|
12
|
+
unless BinData.const_defined?(name)
|
13
|
+
BinData.module_eval <<-END
|
14
|
+
class #{name} < BinData::BasePrimitive
|
15
|
+
BitField.define_methods(self, #{nbits}, :#{endian})
|
16
|
+
end
|
17
|
+
END
|
18
|
+
end
|
19
|
+
|
20
|
+
BinData.const_get(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def define_methods(bit_class, nbits, endian)
|
24
|
+
bit_class.module_eval <<-END
|
25
|
+
def assign(val)
|
26
|
+
#{create_clamp_code(nbits)}
|
27
|
+
super(val)
|
28
|
+
end
|
29
|
+
|
30
|
+
def do_write(io)
|
31
|
+
io.writebits(_value, #{nbits}, :#{endian})
|
32
|
+
end
|
33
|
+
|
34
|
+
def do_num_bytes
|
35
|
+
#{nbits / 8.0}
|
36
|
+
end
|
37
|
+
|
38
|
+
#---------------
|
39
|
+
private
|
40
|
+
|
41
|
+
def read_and_return_value(io)
|
42
|
+
io.readbits(#{nbits}, :#{endian})
|
43
|
+
end
|
44
|
+
|
45
|
+
def sensible_default
|
46
|
+
0
|
47
|
+
end
|
48
|
+
END
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_clamp_code(nbits)
|
52
|
+
min = 0
|
53
|
+
max = (1 << nbits) - 1
|
54
|
+
clamp = "(val < #{min}) ? #{min} : (val > #{max}) ? #{max} : val"
|
55
|
+
|
56
|
+
if nbits == 1
|
57
|
+
# allow single bits to be used as booleans
|
58
|
+
clamp = "(val == true) ? 1 : (not val) ? 0 : #{clamp}"
|
59
|
+
end
|
60
|
+
|
61
|
+
"val = #{clamp}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create classes on demand
|
67
|
+
class << self
|
68
|
+
alias_method :const_missing_without_bits, :const_missing
|
69
|
+
def const_missing_with_bits(name)
|
70
|
+
name = name.to_s
|
71
|
+
mappings = {
|
72
|
+
/^Bit(\d+)$/ => :big,
|
73
|
+
/^Bit(\d+)le$/ => :little
|
74
|
+
}
|
75
|
+
|
76
|
+
mappings.each_pair do |regex, endian|
|
77
|
+
if regex =~ name
|
78
|
+
nbits = $1.to_i
|
79
|
+
return BitField.define_class(nbits, endian)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
const_missing_without_bits(name)
|
84
|
+
end
|
85
|
+
alias_method :const_missing, :const_missing_with_bits
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
require 'bindata/dsl'
|
3
|
+
|
4
|
+
module BinData
|
5
|
+
# A Choice is a collection of data objects of which only one is active
|
6
|
+
# at any particular time. Method calls will be delegated to the active
|
7
|
+
# choice.
|
8
|
+
#
|
9
|
+
# require 'bindata'
|
10
|
+
#
|
11
|
+
# type1 = [:string, {:value => "Type1"}]
|
12
|
+
# type2 = [:string, {:value => "Type2"}]
|
13
|
+
#
|
14
|
+
# choices = {5 => type1, 17 => type2}
|
15
|
+
# a = BinData::Choice.new(:choices => choices, :selection => 5)
|
16
|
+
# a # => "Type1"
|
17
|
+
#
|
18
|
+
# choices = [ type1, type2 ]
|
19
|
+
# a = BinData::Choice.new(:choices => choices, :selection => 1)
|
20
|
+
# a # => "Type2"
|
21
|
+
#
|
22
|
+
# choices = [ nil, nil, nil, type1, nil, type2 ]
|
23
|
+
# a = BinData::Choice.new(:choices => choices, :selection => 3)
|
24
|
+
# a # => "Type1"
|
25
|
+
#
|
26
|
+
#
|
27
|
+
# Chooser = Struct.new(:choice)
|
28
|
+
# mychoice = Chooser.new
|
29
|
+
# mychoice.choice = 'big'
|
30
|
+
#
|
31
|
+
# choices = {'big' => :uint16be, 'little' => :uint16le}
|
32
|
+
# a = BinData::Choice.new(:choices => choices, :copy_on_change => true,
|
33
|
+
# :selection => lambda { mychoice.choice })
|
34
|
+
# a.assign(256)
|
35
|
+
# a.to_binary_s #=> "\001\000"
|
36
|
+
#
|
37
|
+
# mychoice.choice = 'little'
|
38
|
+
# a.to_binary_s #=> "\000\001"
|
39
|
+
#
|
40
|
+
# == Parameters
|
41
|
+
#
|
42
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
43
|
+
# an object. These params are:
|
44
|
+
#
|
45
|
+
# <tt>:choices</tt>:: Either an array or a hash specifying the possible
|
46
|
+
# data objects. The format of the
|
47
|
+
# array/hash.values is a list of symbols
|
48
|
+
# representing the data object type. If a choice
|
49
|
+
# is to have params passed to it, then it should
|
50
|
+
# be provided as [type_symbol, hash_params]. An
|
51
|
+
# implementation constraint is that the hash may
|
52
|
+
# not contain symbols as keys, with the exception
|
53
|
+
# of :default. :default is to be used when then
|
54
|
+
# :selection does not exist in the :choices hash.
|
55
|
+
# <tt>:selection</tt>:: An index/key into the :choices array/hash which
|
56
|
+
# specifies the currently active choice.
|
57
|
+
# <tt>:copy_on_change</tt>:: If set to true, copy the value of the previous
|
58
|
+
# selection to the current selection whenever the
|
59
|
+
# selection changes. Default is false.
|
60
|
+
class Choice < BinData::Base
|
61
|
+
include DSLMixin
|
62
|
+
|
63
|
+
dsl_parser :choice
|
64
|
+
|
65
|
+
mandatory_parameters :choices, :selection
|
66
|
+
optional_parameter :copy_on_change
|
67
|
+
|
68
|
+
class << self
|
69
|
+
|
70
|
+
def sanitize_parameters!(params) #:nodoc:
|
71
|
+
params.merge!(dsl_params)
|
72
|
+
|
73
|
+
if params.needs_sanitizing?(:choices)
|
74
|
+
choices = choices_as_hash(params[:choices])
|
75
|
+
ensure_valid_keys(choices)
|
76
|
+
params[:choices] = params.create_sanitized_choices(choices)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#-------------
|
81
|
+
private
|
82
|
+
|
83
|
+
def choices_as_hash(choices)
|
84
|
+
if choices.respond_to?(:to_ary)
|
85
|
+
key_array_by_index(choices.to_ary)
|
86
|
+
else
|
87
|
+
choices
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def key_array_by_index(array)
|
92
|
+
result = {}
|
93
|
+
array.each_with_index do |el, i|
|
94
|
+
result[i] = el unless el.nil?
|
95
|
+
end
|
96
|
+
result
|
97
|
+
end
|
98
|
+
|
99
|
+
def ensure_valid_keys(choices)
|
100
|
+
if choices.has_key?(nil)
|
101
|
+
raise ArgumentError, ":choices hash may not have nil key"
|
102
|
+
end
|
103
|
+
if choices.keys.detect { |key| key.is_a?(Symbol) and key != :default }
|
104
|
+
raise ArgumentError, ":choices hash may not have symbols for keys"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def initialize_shared_instance
|
110
|
+
if eval_parameter(:copy_on_change) == true
|
111
|
+
class << self
|
112
|
+
alias_method :hook_after_current_choice, :copy_previous_value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize_instance
|
118
|
+
@choices = {}
|
119
|
+
@last_selection = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# A convenience method that returns the current selection.
|
123
|
+
def selection
|
124
|
+
eval_parameter(:selection)
|
125
|
+
end
|
126
|
+
|
127
|
+
def clear #:nodoc:
|
128
|
+
initialize_instance
|
129
|
+
end
|
130
|
+
|
131
|
+
def clear? #:nodoc:
|
132
|
+
current_choice.clear?
|
133
|
+
end
|
134
|
+
|
135
|
+
def assign(val)
|
136
|
+
current_choice.assign(val)
|
137
|
+
end
|
138
|
+
|
139
|
+
def snapshot
|
140
|
+
current_choice.snapshot
|
141
|
+
end
|
142
|
+
|
143
|
+
def respond_to?(symbol, include_private = false) #:nodoc:
|
144
|
+
current_choice.respond_to?(symbol, include_private) || super
|
145
|
+
end
|
146
|
+
|
147
|
+
def safe_respond_to?(symbol, include_private = false) #:nodoc:
|
148
|
+
orig_respond_to?(symbol, include_private)
|
149
|
+
end
|
150
|
+
|
151
|
+
def method_missing(symbol, *args, &block) #:nodoc:
|
152
|
+
current_choice.__send__(symbol, *args, &block)
|
153
|
+
end
|
154
|
+
|
155
|
+
def do_read(io) #:nodoc:
|
156
|
+
hook_before_do_read
|
157
|
+
current_choice.do_read(io)
|
158
|
+
end
|
159
|
+
|
160
|
+
def do_write(io) #:nodoc:
|
161
|
+
current_choice.do_write(io)
|
162
|
+
end
|
163
|
+
|
164
|
+
def do_num_bytes #:nodoc:
|
165
|
+
current_choice.do_num_bytes
|
166
|
+
end
|
167
|
+
|
168
|
+
#---------------
|
169
|
+
private
|
170
|
+
|
171
|
+
def hook_before_do_read; end
|
172
|
+
def hook_after_current_choice(*args); end
|
173
|
+
|
174
|
+
def current_choice
|
175
|
+
selection = eval_parameter(:selection)
|
176
|
+
if selection.nil?
|
177
|
+
raise IndexError, ":selection returned nil for #{debug_name}"
|
178
|
+
end
|
179
|
+
|
180
|
+
obj = get_or_instantiate_choice(selection)
|
181
|
+
hook_after_current_choice(selection, obj)
|
182
|
+
|
183
|
+
obj
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_or_instantiate_choice(selection)
|
187
|
+
@choices[selection] ||= instantiate_choice(selection)
|
188
|
+
end
|
189
|
+
|
190
|
+
def instantiate_choice(selection)
|
191
|
+
prototype = get_parameter(:choices)[selection]
|
192
|
+
if prototype.nil?
|
193
|
+
raise IndexError, "selection '#{selection}' does not exist in :choices for #{debug_name}"
|
194
|
+
end
|
195
|
+
prototype.instantiate(nil, self)
|
196
|
+
end
|
197
|
+
|
198
|
+
def copy_previous_value(selection, obj)
|
199
|
+
prev = get_previous_choice(selection)
|
200
|
+
obj.assign(prev) unless prev.nil?
|
201
|
+
remember_current_selection(selection)
|
202
|
+
end
|
203
|
+
|
204
|
+
def get_previous_choice(selection)
|
205
|
+
if selection != @last_selection and @last_selection != nil
|
206
|
+
@choices[@last_selection]
|
207
|
+
else
|
208
|
+
nil
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def remember_current_selection(selection)
|
213
|
+
@last_selection = selection
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "bindata/base_primitive"
|
2
|
+
|
3
|
+
module BinData
|
4
|
+
# Counts the number of bytes remaining in the input stream from the current
|
5
|
+
# position to the end of the stream. This only makes sense for seekable
|
6
|
+
# streams.
|
7
|
+
#
|
8
|
+
# require 'bindata'
|
9
|
+
#
|
10
|
+
# class A < BinData::Record
|
11
|
+
# count_bytes_remaining :bytes_remaining
|
12
|
+
# string :all_data, :read_length => :bytes_remaining
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# obj = A.read("abcdefghij")
|
16
|
+
# obj.all_data #=> "abcdefghij"
|
17
|
+
#
|
18
|
+
class CountBytesRemaining < BinData::BasePrimitive
|
19
|
+
|
20
|
+
#---------------
|
21
|
+
private
|
22
|
+
|
23
|
+
def value_to_binary_string(val)
|
24
|
+
""
|
25
|
+
end
|
26
|
+
|
27
|
+
def read_and_return_value(io)
|
28
|
+
io.num_bytes_remaining
|
29
|
+
end
|
30
|
+
|
31
|
+
def sensible_default
|
32
|
+
0
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Implement Kernel#instance_exec for Ruby 1.8.6 and below
|
2
|
+
unless Object.respond_to? :instance_exec
|
3
|
+
module Kernel
|
4
|
+
# Taken from http://eigenclass.org/hiki/instance_exec
|
5
|
+
def instance_exec(*args, &block)
|
6
|
+
mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
|
7
|
+
Object.class_eval{ define_method(mname, &block) }
|
8
|
+
begin
|
9
|
+
ret = send(mname, *args)
|
10
|
+
ensure
|
11
|
+
Object.class_eval{ undef_method(mname) } rescue nil
|
12
|
+
end
|
13
|
+
ret
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module BinData
|
19
|
+
class Base
|
20
|
+
|
21
|
+
# Don't override initialize. If you are defining a new kind of datatype
|
22
|
+
# (list, array, choice etc) then put your initialization code in
|
23
|
+
# #initialize_instance. This is because BinData objects can be initialized
|
24
|
+
# as prototypes and your initialization code may not be called.
|
25
|
+
#
|
26
|
+
# If you're subclassing BinData::Record, you are definitely doing the wrong
|
27
|
+
# thing. Read the documentation on how to use BinData.
|
28
|
+
# http://bindata.rubyforge.org/manual.html#records
|
29
|
+
alias_method :initialize_without_warning, :initialize
|
30
|
+
def initialize_with_warning(*args)
|
31
|
+
owner = method(:initialize).owner
|
32
|
+
if owner != BinData::Base
|
33
|
+
msg = "Don't override #initialize on #{owner}."
|
34
|
+
if %w(BinData::Base BinData::BasePrimitive).include? self.class.superclass.name
|
35
|
+
msg += "\nrename #initialize to #initialize_instance."
|
36
|
+
end
|
37
|
+
fail msg
|
38
|
+
end
|
39
|
+
initialize_without_warning(*args)
|
40
|
+
end
|
41
|
+
alias_method :initialize, :initialize_with_warning
|
42
|
+
|
43
|
+
def initialize_instance(*args)
|
44
|
+
unless args.empty?
|
45
|
+
fail "#{caller[0]} remove the call to super in #initialize_instance"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/bindata/dsl.rb
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
module BinData
|
2
|
+
module DSLMixin
|
3
|
+
def self.included(base) #:nodoc:
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def dsl_parser(parser_type = nil)
|
10
|
+
unless defined? @dsl_parser
|
11
|
+
parser_type = superclass.dsl_parser.parser_type if parser_type.nil?
|
12
|
+
@dsl_parser = DSLParser.new(self, parser_type)
|
13
|
+
end
|
14
|
+
@dsl_parser
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(symbol, *args, &block) #:nodoc:
|
18
|
+
dsl_parser.__send__(symbol, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Assert object is not an array or string.
|
22
|
+
def to_ary; nil; end
|
23
|
+
def to_str; nil; end
|
24
|
+
end
|
25
|
+
|
26
|
+
# A DSLParser parses and accumulates field definitions of the form
|
27
|
+
#
|
28
|
+
# type name, params
|
29
|
+
#
|
30
|
+
# where:
|
31
|
+
# * +type+ is the under_scored name of a registered type
|
32
|
+
# * +name+ is the (possible optional) name of the field
|
33
|
+
# * +params+ is a hash containing any parameters
|
34
|
+
#
|
35
|
+
class DSLParser
|
36
|
+
def initialize(the_class, parser_type)
|
37
|
+
@the_class = the_class
|
38
|
+
@parser_type = parser_type
|
39
|
+
@endian = parent_attribute(:endian, nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :parser_type
|
43
|
+
|
44
|
+
def endian(endian = nil)
|
45
|
+
if endian.nil?
|
46
|
+
@endian
|
47
|
+
elsif endian == :big or endian == :little
|
48
|
+
@endian = endian
|
49
|
+
else
|
50
|
+
dsl_raise ArgumentError, "unknown value for endian '#{endian}'"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def hide(*args)
|
55
|
+
if option?(:hidden_fields)
|
56
|
+
hidden = args.collect do |name|
|
57
|
+
unless Symbol === name
|
58
|
+
warn "Hidden field '#{name}' should be provided as a symbol. Using strings is deprecated"
|
59
|
+
end
|
60
|
+
name.to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
unless defined? @hide
|
64
|
+
@hide = parent_attribute(:hide, []).dup
|
65
|
+
end
|
66
|
+
|
67
|
+
@hide.concat(hidden.compact)
|
68
|
+
@hide
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def fields
|
73
|
+
unless defined? @fields
|
74
|
+
fields = parent_attribute(:fields, nil)
|
75
|
+
@fields = SanitizedFields.new(endian)
|
76
|
+
@fields.copy_fields(fields) if fields
|
77
|
+
end
|
78
|
+
|
79
|
+
@fields
|
80
|
+
end
|
81
|
+
|
82
|
+
def dsl_params
|
83
|
+
case @parser_type
|
84
|
+
when :struct
|
85
|
+
to_struct_params
|
86
|
+
when :array
|
87
|
+
to_array_params
|
88
|
+
when :choice
|
89
|
+
to_choice_params
|
90
|
+
when :primitive
|
91
|
+
to_struct_params
|
92
|
+
else
|
93
|
+
raise "unknown parser type #{@parser_type}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def method_missing(symbol, *args, &block) #:nodoc:
|
98
|
+
type = symbol
|
99
|
+
name = name_from_field_declaration(args)
|
100
|
+
params = params_from_field_declaration(type, args, &block)
|
101
|
+
|
102
|
+
append_field(type, name, params)
|
103
|
+
end
|
104
|
+
|
105
|
+
#-------------
|
106
|
+
private
|
107
|
+
|
108
|
+
def option?(opt)
|
109
|
+
options.include?(opt)
|
110
|
+
end
|
111
|
+
|
112
|
+
def options
|
113
|
+
case @parser_type
|
114
|
+
when :struct
|
115
|
+
[:multiple_fields, :optional_fieldnames, :hidden_fields]
|
116
|
+
when :array
|
117
|
+
[:multiple_fields, :optional_fieldnames]
|
118
|
+
when :choice
|
119
|
+
[:multiple_fields, :all_or_none_fieldnames, :fieldnames_are_values]
|
120
|
+
when :primitive
|
121
|
+
[:multiple_fields, :optional_fieldnames]
|
122
|
+
else
|
123
|
+
raise "unknown parser type #{parser_type}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def parent_attribute(attr, default = nil)
|
128
|
+
parent = @the_class.superclass.respond_to?(:dsl_parser) ? @the_class.superclass.dsl_parser : nil
|
129
|
+
if parent and parent.respond_to?(attr)
|
130
|
+
parent.send(attr)
|
131
|
+
else
|
132
|
+
default
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def name_from_field_declaration(args)
|
137
|
+
name, params = args
|
138
|
+
if name == "" or name.is_a?(Hash)
|
139
|
+
nil
|
140
|
+
else
|
141
|
+
name
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def params_from_field_declaration(type, args, &block)
|
146
|
+
params = params_from_args(args)
|
147
|
+
|
148
|
+
if block_given?
|
149
|
+
params.merge(params_from_block(type, &block))
|
150
|
+
else
|
151
|
+
params
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def params_from_args(args)
|
156
|
+
name, params = args
|
157
|
+
params = name if name.is_a?(Hash)
|
158
|
+
|
159
|
+
params || {}
|
160
|
+
end
|
161
|
+
|
162
|
+
def params_from_block(type, &block)
|
163
|
+
bindata_classes = {
|
164
|
+
:array => BinData::Array,
|
165
|
+
:choice => BinData::Choice,
|
166
|
+
:struct => BinData::Struct
|
167
|
+
}
|
168
|
+
|
169
|
+
if bindata_classes.include?(type)
|
170
|
+
parser = DSLParser.new(bindata_classes[type], type)
|
171
|
+
parser.endian(endian)
|
172
|
+
parser.instance_eval(&block)
|
173
|
+
|
174
|
+
parser.dsl_params
|
175
|
+
else
|
176
|
+
{}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def append_field(type, name, params)
|
181
|
+
ensure_valid_field(name)
|
182
|
+
|
183
|
+
fields.add_field(type, name, params)
|
184
|
+
rescue ArgumentError => err
|
185
|
+
dsl_raise ArgumentError, err.message
|
186
|
+
rescue UnRegisteredTypeError => err
|
187
|
+
dsl_raise TypeError, "unknown type '#{err.message}'"
|
188
|
+
end
|
189
|
+
|
190
|
+
def ensure_valid_field(field_name)
|
191
|
+
if too_many_fields?
|
192
|
+
dsl_raise SyntaxError, "attempting to wrap more than one type"
|
193
|
+
end
|
194
|
+
|
195
|
+
if must_not_have_a_name_failed?(field_name)
|
196
|
+
dsl_raise SyntaxError, "field must not have a name"
|
197
|
+
end
|
198
|
+
|
199
|
+
if all_or_none_names_failed?(field_name)
|
200
|
+
dsl_raise SyntaxError, "fields must either all have names, or none must have names"
|
201
|
+
end
|
202
|
+
|
203
|
+
if must_have_a_name_failed?(field_name)
|
204
|
+
dsl_raise SyntaxError, "field must have a name"
|
205
|
+
end
|
206
|
+
|
207
|
+
ensure_valid_name(field_name)
|
208
|
+
end
|
209
|
+
|
210
|
+
def ensure_valid_name(name)
|
211
|
+
if name and not option?(:fieldnames_are_values)
|
212
|
+
if malformed_name?(name)
|
213
|
+
dsl_raise NameError.new("", name), "field '#{name}' is an illegal fieldname"
|
214
|
+
end
|
215
|
+
|
216
|
+
if duplicate_name?(name)
|
217
|
+
dsl_raise SyntaxError, "duplicate field '#{name}'"
|
218
|
+
end
|
219
|
+
|
220
|
+
if name_shadows_method?(name)
|
221
|
+
dsl_raise NameError.new("", name), "field '#{name}' shadows an existing method"
|
222
|
+
end
|
223
|
+
|
224
|
+
if name_is_reserved?(name)
|
225
|
+
dsl_raise NameError.new("", name), "field '#{name}' is a reserved name"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def too_many_fields?
|
231
|
+
option?(:only_one_field) and not fields.empty?
|
232
|
+
end
|
233
|
+
|
234
|
+
def must_not_have_a_name_failed?(name)
|
235
|
+
option?(:no_fieldnames) and name != nil
|
236
|
+
end
|
237
|
+
|
238
|
+
def must_have_a_name_failed?(name)
|
239
|
+
option?(:mandatory_fieldnames) and name.nil?
|
240
|
+
end
|
241
|
+
|
242
|
+
def all_or_none_names_failed?(name)
|
243
|
+
if option?(:all_or_none_fieldnames) and not fields.empty?
|
244
|
+
all_names_blank = fields.all_field_names_blank?
|
245
|
+
no_names_blank = fields.no_field_names_blank?
|
246
|
+
|
247
|
+
(name != nil and all_names_blank) or (name == nil and no_names_blank)
|
248
|
+
else
|
249
|
+
false
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def malformed_name?(name)
|
254
|
+
/^[a-z_]\w*$/ !~ name.to_s
|
255
|
+
end
|
256
|
+
|
257
|
+
def duplicate_name?(name)
|
258
|
+
fields.has_field_name?(name)
|
259
|
+
end
|
260
|
+
|
261
|
+
def name_shadows_method?(name)
|
262
|
+
@the_class.method_defined?(name)
|
263
|
+
end
|
264
|
+
|
265
|
+
def name_is_reserved?(name)
|
266
|
+
BinData::Struct::RESERVED.include?(name.to_sym)
|
267
|
+
end
|
268
|
+
|
269
|
+
def dsl_raise(exception, message)
|
270
|
+
backtrace = caller
|
271
|
+
backtrace.shift while %r{bindata/dsl.rb} =~ backtrace.first
|
272
|
+
|
273
|
+
raise exception, message + " in #{@the_class}", backtrace
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_array_params
|
277
|
+
case fields.length
|
278
|
+
when 0
|
279
|
+
{}
|
280
|
+
when 1
|
281
|
+
{:type => fields[0].prototype}
|
282
|
+
else
|
283
|
+
{:type => [:struct, to_struct_params]}
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def to_choice_params
|
288
|
+
if fields.length == 0
|
289
|
+
{}
|
290
|
+
elsif fields.all_field_names_blank?
|
291
|
+
{:choices => fields.collect { |f| f.prototype }}
|
292
|
+
else
|
293
|
+
choices = {}
|
294
|
+
fields.each { |f| choices[f.name] = f.prototype }
|
295
|
+
{:choices => choices}
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def to_struct_params
|
300
|
+
result = {:fields => fields}
|
301
|
+
if not endian.nil?
|
302
|
+
result[:endian] = endian
|
303
|
+
end
|
304
|
+
if option?(:hidden_fields) and not hide.empty?
|
305
|
+
result[:hide] = hide
|
306
|
+
end
|
307
|
+
|
308
|
+
result
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|