jbangert-bindata 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|