lib/bindata/base_primitive.rb
require 'bindata/base' module BinData # A BinData::BasePrimitive object is a container for a value that has a # particular binary representation. A value corresponds to a primitive type # such as as integer, float or string. Only one value can be contained by # this object. This value can be read from or written to an IO stream. # # require 'bindata' # # obj = BinData::Uint8.new(:initial_value => 42) # obj #=> 42 # obj.assign(5) # obj #=> 5 # obj.clear # obj #=> 42 # # obj = BinData::Uint8.new(:value => 42) # obj #=> 42 # obj.assign(5) # obj #=> 42 # # obj = BinData::Uint8.new(:check_value => 3) # obj.read("\005") #=> BinData::ValidityError: value is '5' but expected '3' # # obj = BinData::Uint8.new(:check_value => lambda { value < 5 }) # obj.read("\007") #=> BinData::ValidityError: value not as expected # # == Parameters # # Parameters may be provided at initialisation to control the behaviour of # an object. These params include those for BinData::Base as well as: # # [<tt>:initial_value</tt>] This is the initial value to use before one is # either #read or explicitly set with #value=. # [<tt>:value</tt>] The object will always have this value. # Calls to #value= are ignored when # using this param. While reading, #value # will return the value of the data read from the # IO, not the result of the <tt>:value</tt> param. # [<tt>:check_value</tt>] Raise an error unless the value read in meets # this criteria. The variable +value+ is made # available to any lambda assigned to this # parameter. A boolean return indicates success # or failure. Any other return is compared to # the value just read in. class BasePrimitive < BinData::Base unregister_self optional_parameters :initial_value, :value, :check_value mutually_exclusive_parameters :initial_value, :value def initialize_shared_instance if has_parameter?(:check_value) class << self alias_method :do_read_without_check_value, :do_read alias_method :do_read, :do_read_with_check_value end end if has_parameter?(:value) class << self alias_method :_value, :_value_with_value end end if has_parameter?(:initial_value) class << self alias_method :_value, :_value_with_initial_value end end end def initialize_instance @value = nil end def clear #:nodoc: @value = nil end def clear? #:nodoc: @value.nil? end def assign(val) raise ArgumentError, "can't set a nil value for #{debug_name}" if val.nil? unless has_parameter?(:value) raw_val = val.respond_to?(:snapshot) ? val.snapshot : val @value = begin raw_val.dup rescue TypeError # can't dup Fixnums raw_val end end end def snapshot _value end def value snapshot end alias_method :value=, :assign def respond_to?(symbol, include_private = false) #:nodoc: child = snapshot child.respond_to?(symbol, include_private) || super end def method_missing(symbol, *args, &block) #:nodoc: child = snapshot if child.respond_to?(symbol) child.__send__(symbol, *args, &block) else super end end def <=>(other) snapshot <=> other end def eql?(other) # double dispatch other.eql?(snapshot) end def hash snapshot.hash end def do_read(io) #:nodoc: @value = read_and_return_value(io) hook_after_do_read end def do_read_with_check_value(io) #:nodoc: do_read_without_check_value(io) check_value(snapshot) end def do_write(io) #:nodoc: io.writebytes(value_to_binary_string(_value)) end def do_num_bytes #:nodoc: value_to_binary_string(_value).length end #--------------- private def hook_after_do_read; end def check_value(current_value) expected = eval_parameter(:check_value, :value => current_value) if not expected raise ValidityError, "value '#{current_value}' not as expected for #{debug_name}" elsif current_value != expected and expected != true raise ValidityError, "value is '#{current_value}' but " + "expected '#{expected}' for #{debug_name}" end end # The unmodified value of this data object. Note that #snapshot calls this # method. This indirection is so that #snapshot can be overridden in # subclasses to modify the presentation value. # # Table of possible preconditions and expected outcome # 1. :value and !reading? -> :value # 2. :value and reading? -> @value # 3. :initial_value and clear? -> :initial_value # 4. :initial_value and !clear? -> @value # 5. clear? -> sensible_default # 6. !clear? -> @value def _value @value != nil ? @value : sensible_default() end def _value_with_value #:nodoc: if reading? @value else eval_parameter(:value) end end def _value_with_initial_value #:nodoc: @value != nil ? @value : eval_parameter(:initial_value) end ########################################################################### # To be implemented by subclasses # Return the string representation that +val+ will take when written. def value_to_binary_string(val) raise NotImplementedError end # Read a number of bytes from +io+ and return the value they represent. def read_and_return_value(io) raise NotImplementedError end # Return a sensible default for this data. def sensible_default raise NotImplementedError end # To be implemented by subclasses ########################################################################### end end