require'bindata/framework'require'bindata/io'require'bindata/lazy'require'bindata/name'require'bindata/params'require'bindata/registry'require'bindata/sanitize'moduleBinData# This is the abstract base class for all data objects.classBaseextendAcceptedParametersPluginincludeFrameworkincludeRegisterNamePluginclass<<self# Instantiates this class and reads from +io+, returning the newly# created data object. +args+ will be used when instantiating.defread(io,*args,&block)obj=self.new(*args)obj.read(io,&block)objend# The arg processor for this class.defarg_processor(name=nil)@arg_processor||=nilifname@arg_processor="#{name}_arg_processor".gsub(/(?:^|_)(.)/){$1.upcase}.to_symelsif@arg_processor.is_a?Symbol@arg_processor=BinData.const_get(@arg_processor).newelsif@arg_processor.nil?@arg_processor=superclass.arg_processorelse@arg_processorendend# The name of this class as used by Records, Arrays etc.defbindata_nameRegisteredClasses.underscore_name(name)end# Call this method if this class is abstract and not to be used.defunregister_selfRegisteredClasses.unregister(name)end# Registers all subclasses of this class for usedefregister_subclasses#:nodoc:singleton_class.send(:undef_method,:inherited)define_singleton_method(:inherited)do|subclass|RegisteredClasses.register(subclass.name,subclass)register_subclassesendendprivate:unregister_self,:register_subclassesend# 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.#definitialize(*args)value,@params,@parent=extract_args(args)initialize_shared_instanceinitialize_instanceassign(value)ifvalueendattr_accessor:parentprotected: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.defnew(value=nil,parent=nil)obj=cloneobj.parent=parentifparentobj.initialize_instanceobj.assign(value)ifvalueobjend# 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.defeval_parameter(key,overrides=nil)value=get_parameter(key)ifvalue.is_a?(Symbol)||value.respond_to?(:arity)lazy_evaluator.lazy_eval(value,overrides)elsevalueendend# Returns a lazy evaluator for this object.deflazy_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.defget_parameter(key)@params[key]end# Returns whether +key+ exists in the +parameters+ hash.defhas_parameter?(key)@params.has_parameter?(key)end# Resets the internal state to that of a newly created object.defclearinitialize_instanceend# Reads data into this data object.defread(io,&block)io=BinData::IO::Read.new(io)unlessBinData::IO::Read===iostart_readdocleardo_read(io)endblock.call(self)ifblock_given?selfend# Writes the value for this data object to +io+.defwrite(io,&block)io=BinData::IO::Write.new(io)unlessBinData::IO::Write===iodo_write(io)io.flushblock.call(self)ifblock_given?selfend# Returns the number of bytes it will take to write this data object.defnum_bytesdo_num_bytes.ceilend# Returns the string representation of this data object.defto_binary_s(&block)io=BinData::IO.create_string_iowrite(io,&block)io.rewindio.readend# Returns the hexadecimal string representation of this data object.defto_hex(&block)to_binary_s(&block).unpack('H*')[0]end# Return a human readable representation of this data object.definspectsnapshot.inspectend# Return a string representing this data object.defto_ssnapshot.to_send# Work with Ruby's pretty-printer library.defpretty_print(pp)#:nodoc:pp.pp(snapshot)end# Override and delegate =~ as it is defined in Object.def=~(other)snapshot=~otherend# Returns a user friendly name of this object for debugging purposes.defdebug_nameif@parent@parent.debug_name_of(self)else"obj"endend# Returns the offset (in bytes) of this object with respect to its most# distant ancestor.defabs_offsetif@parent@parent.abs_offset+@parent.offset_of(self)else0endend# Returns the offset (in bytes) of this object with respect to its parent.defrel_offsetif@parent@parent.offset_of(self)else0endenddef==(other)#:nodoc:# double dispatchother==snapshotend# A version of +respond_to?+ used by the lazy evaluator. It doesn't# reinvoke the evaluator so as to avoid infinite evaluation loops.defsafe_respond_to?(symbol,include_private=false)#:nodoc:base_respond_to?(symbol,include_private)endaliasbase_respond_to?respond_to?#---------------privatedefextract_args(args)self.class.arg_processor.extract_args(self.class,args)enddefstart_readtop_level_set(:in_read,true)yieldensuretop_level_set(:in_read,false)end# Is this object tree currently being read? Used by BasePrimitive.defreading?top_level_get(:in_read)enddeftop_level_set(sym,value)top_level.instance_variable_set("@tl_#{sym}",value)enddeftop_level_get(sym)tl=top_leveltl.instance_variable_defined?("@tl_#{sym}")&&tl.instance_variable_get("@tl_#{sym}")enddeftop_levelifparent.nil?tl=selfelsetl=parenttl=tl.parentwhiletl.parentendtlenddefbinary_string(str)str.to_s.dup.force_encoding(Encoding::BINARY)endend# 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.classBaseArgProcessor@@empty_hash=Hash.new.freeze# Takes the arguments passed to BinData::Base.new and# extracts [value, sanitized_parameters, parent].defextract_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.defseparate_args(_obj_class,obj_args)args=obj_args.dupvalue=parameters=parent=nilifargs.length>1&&args.last.is_a?(BinData::Base)parent=args.popendifargs.length>0&&args.last.is_a?(Hash)parameters=args.popendifargs.length>0value=args.popendparameters||=@@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.defsanitize_parameters!(obj_class,obj_params)endendend