File: base.rb

package info (click to toggle)
ruby-bindata 2.4.14-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 600 kB
  • sloc: ruby: 8,566; makefile: 4
file content (335 lines) | stat: -rw-r--r-- 8,767 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
require 'bindata/framework'
require 'bindata/io'
require 'bindata/lazy'
require 'bindata/name'
require 'bindata/params'
require 'bindata/registry'
require 'bindata/sanitize'

module BinData
  # This is the abstract base class for all data objects.
  class Base
    extend AcceptedParametersPlugin
    include Framework
    include RegisterNamePlugin

    class << self
      # Instantiates this class and reads from +io+, returning the newly
      # created data object.  +args+ will be used when instantiating.
      def read(io, *args, &block)
        obj = self.new(*args)
        obj.read(io, &block)
        obj
      end

      # The arg processor for this class.
      def arg_processor(name = nil)
        @arg_processor ||= nil

        if name
          @arg_processor = "#{name}_arg_processor".gsub(/(?:^|_)(.)/) { $1.upcase }.to_sym
        elsif @arg_processor.is_a? Symbol
          @arg_processor = BinData.const_get(@arg_processor).new
        elsif @arg_processor.nil?
          @arg_processor = superclass.arg_processor
        else
          @arg_processor
        end
      end

      # The name of this class as used by Records, Arrays etc.
      def bindata_name
        RegisteredClasses.underscore_name(name)
      end

      # Call this method if this class is abstract and not to be used.
      def unregister_self
        RegisteredClasses.unregister(name)
      end

      # Registers all subclasses of this class for use
      def register_subclasses #:nodoc:
        singleton_class.send(:undef_method, :inherited)
        define_singleton_method(:inherited) do |subclass|
          RegisteredClasses.register(subclass.name, subclass)
          register_subclasses
        end
      end

      private :unregister_self, :register_subclasses
    end

    # Register all subclasses of this class.
    register_subclasses

    # Set the initial arg processor.
    arg_processor :base

    # Creates a new data object.
    #
    # Args are optional, but if present, must be in the following order.
    #
    # +value+ is a value that is +assign+ed immediately after initialization.
    #
    # +parameters+ is a hash containing symbol keys.  Some parameters may
    # reference callable objects (methods or procs).
    #
    # +parent+ is the parent data object (e.g. struct, array, choice) this
    # object resides under.
    #
    def initialize(*args)
      value, @params, @parent = extract_args(args)

      initialize_shared_instance
      initialize_instance
      assign(value) if value
    end

    attr_accessor :parent
    protected :parent=

    # Creates a new data object based on this instance.
    #
    # All parameters will be be duplicated.  Use this method
    # when creating multiple objects with the same parameters.
    def new(value = nil, parent = nil)
      obj = clone
      obj.parent = parent if parent
      obj.initialize_instance
      obj.assign(value) if value

      obj
    end

    # Returns the result of evaluating the parameter identified by +key+.
    #
    # +overrides+ is an optional +parameters+ like hash that allow the
    # parameters given at object construction to be overridden.
    #
    # Returns nil if +key+ does not refer to any parameter.
    def eval_parameter(key, overrides = nil)
      value = get_parameter(key)
      if value.is_a?(Symbol) || value.respond_to?(:arity)
        lazy_evaluator.lazy_eval(value, overrides)
      else
        value
      end
    end

    # Returns a lazy evaluator for this object.
    def lazy_evaluator #:nodoc:
      @lazy ||= LazyEvaluator.new(self)
    end

    # Returns the parameter referenced by +key+.
    # Use this method if you are sure the parameter is not to be evaluated.
    # You most likely want #eval_parameter.
    def get_parameter(key)
      @params[key]
    end

    # Returns whether +key+ exists in the +parameters+ hash.
    def has_parameter?(key)
      @params.has_parameter?(key)
    end

    # Resets the internal state to that of a newly created object.
    def clear
      initialize_instance
    end

    # Reads data into this data object.
    def read(io, &block)
      io = BinData::IO::Read.new(io) unless BinData::IO::Read === io

      start_read do
        clear
        do_read(io)
      end
      block.call(self) if block_given?

      self
    end

    # Writes the value for this data object to +io+.
    def write(io, &block)
      io = BinData::IO::Write.new(io) unless BinData::IO::Write === io

      do_write(io)
      io.flush

      block.call(self) if block_given?

      self
    end

    # Returns the number of bytes it will take to write this data object.
    def num_bytes
      do_num_bytes.ceil
    end

    # Returns the string representation of this data object.
    def to_binary_s(&block)
      io = BinData::IO.create_string_io
      write(io, &block)
      io.string
    end

    # Returns the hexadecimal string representation of this data object.
    def to_hex(&block)
      to_binary_s(&block).unpack('H*')[0]
    end

    # Return a human readable representation of this data object.
    def inspect
      snapshot.inspect
    end

    # Return a string representing this data object.
    def to_s
      snapshot.to_s
    end

    # Work with Ruby's pretty-printer library.
    def pretty_print(pp) #:nodoc:
      pp.pp(snapshot)
    end

    # Override and delegate =~ as it is defined in Object.
    def =~(other)
      snapshot =~ other
    end

    # Returns a user friendly name of this object for debugging purposes.
    def debug_name
      if @parent
        @parent.debug_name_of(self)
      else
        "obj"
      end
    end

    # Returns the offset (in bytes) of this object with respect to its most
    # distant ancestor.
    def abs_offset
      if @parent
        @parent.abs_offset + @parent.offset_of(self)
      else
        0
      end
    end

    # Returns the offset (in bytes) of this object with respect to its parent.
    def rel_offset
      if @parent
        @parent.offset_of(self)
      else
        0
      end
    end

    def ==(other) #:nodoc:
      # double dispatch
      other == snapshot
    end

    # A version of +respond_to?+ used by the lazy evaluator.  It doesn't
    # reinvoke the evaluator so as to avoid infinite evaluation loops.
    def safe_respond_to?(symbol, include_private = false) #:nodoc:
      base_respond_to?(symbol, include_private)
    end

    alias base_respond_to? respond_to?

    #---------------
    private

    def extract_args(args)
      self.class.arg_processor.extract_args(self.class, args)
    end

    def start_read
      top_level_set(:in_read, true)
      yield
    ensure
      top_level_set(:in_read, false)
    end

    # Is this object tree currently being read?  Used by BasePrimitive.
    def reading?
      top_level_get(:in_read)
    end

    def top_level_set(sym, value)
      top_level.instance_variable_set("@tl_#{sym}", value)
    end

    def top_level_get(sym)
      tl = top_level
      tl.instance_variable_defined?("@tl_#{sym}") &&
        tl.instance_variable_get("@tl_#{sym}")
    end

    def top_level
      if parent.nil?
        tl = self
      else
        tl = parent
        tl = tl.parent while tl.parent
      end

      tl
    end

    def binary_string(str)
      str.to_s.dup.force_encoding(Encoding::BINARY)
    end
  end

  # ArgProcessors process the arguments passed to BinData::Base.new into
  # the form required to initialise the BinData object.
  #
  # Any passed parameters are sanitized so the BinData object doesn't
  # need to perform error checking on the parameters.
  class BaseArgProcessor
    @@empty_hash = Hash.new.freeze

    # Takes the arguments passed to BinData::Base.new and
    # extracts [value, sanitized_parameters, parent].
    def extract_args(obj_class, obj_args)
      value, params, parent = separate_args(obj_class, obj_args)
      sanitized_params = SanitizedParameters.sanitize(params, obj_class)

      [value, sanitized_params, parent]
    end

    # Separates the arguments passed to BinData::Base.new into
    # [value, parameters, parent].  Called by #extract_args.
    def separate_args(_obj_class, obj_args)
      args = obj_args.dup
      value = parameters = parent = nil

      if args.length > 1 && args.last.is_a?(BinData::Base)
        parent = args.pop
      end

      if args.length > 0 && args.last.is_a?(Hash)
        parameters = args.pop
      end

      if args.length > 0
        value = args.pop
      end

      parameters ||= @@empty_hash

      [value, parameters, parent]
    end

    # Performs sanity checks on the given parameters.
    # This method converts the parameters to the form expected
    # by the data object.
    def sanitize_parameters!(obj_class, obj_params)
    end
  end
end