# -*- coding: binary -*- # A Vector is, like a BitStruct, a String. It retains all of the String # methods, except for #[], #[]=, and #each. These methods operate on entries # instead of chars. Other methods, including #length and #slice, are unchanged. # Hence a Vector can be used directly with sockets, binary files, etc. # # Note that Vector is not a subclass of BitStruct. It cannot be used in # a #nest declaration in a BitStruct. Instead, use the #vector declaration. # See BitStruct::VectorField. # # Different instances of the same Vector class may have different lengths, and # a single instance can change its length. The length should always be a # multiple of the struct size. class BitStruct::Vector < String include Enumerable @default_options = {} @struct_class = nil class << self def inherited cl cl.instance_eval do @struct_class = nil end end # Called as a class method with a single argument in a user-defined # subclass to specify a particular BitStruct class to use for each entry, # instead of generating an anonymous class. Called without arguments to # access the struct class, generating an anonymous one if needed. # The struct_class inherits from the struct_class of the parent Vector # class. def struct_class cl = nil if cl if @struct_class warn "changing struct_class in #{self} to #{cl}" end @struct_class = cl @struct_class.default_options default_options else unless @struct_class @struct_class = self == BitStruct::Vector ? BitStruct : Class.new(superclass.struct_class) @struct_class.default_options default_options end end @struct_class end def method_missing(*a, &block) # :nodoc: struct_class.send(*a, &block) end alias :orig_respond_to? :respond_to? def respond_to?(*m) # :nodoc: orig_respond_to?(*m) || struct_class.respond_to?(*m) end # Get or set the hash of default options for the class, which apply to all # fields in the entries. If +h+ is provided, update the default options # with that hash. Default options are inherited. # # This is especially useful with the :endian => val option. def default_options h = nil @default_options ||= superclass.default_options.dup if h @default_options.merge! h if @struct_class @struct_class.default_options h end end @default_options end def describe(*args) fmt = args[0] || BitStruct.describe_format if block_given? struct_class.describe(*args){|desc| yield desc} yield ["..."]*5 else struct_class.describe(*args) + [fmt % (["..."]*5)] end end end # Convenience method for instances. Returns the BitStruct class that # describes each entry. def struct_class self.class.struct_class end # Convenience method for instances. Returns the string length in bytes of # each entry in the vector. def struct_class_length self.class.struct_class.round_byte_length end # +arg+ can be an integer (number of entries) or a string # (binary data, such as another Vector of the same size). def initialize arg # :yields: instance case arg when Integer super(struct_class.initial_value * arg) else begin super arg rescue NameError raise ArgumentError, "must be string or integer: #{arg.inspect}" end end yield self if block_given? end # Get the +i+-th entry. Returns a *copy* of the entry. If you want to # use this copy to modify the entry, you must modify the copy and then # use #[]= to replace the entry with the copy. def [](i) sc = self.class.struct_class entry_length = sc.round_byte_length unless (0...(length / entry_length)).include? i raise ArgumentError, "index out of range: #{i}" end sc.new slice(entry_length * i, entry_length) end alias _old_replace_substr []= # Set the +i+-th entry to +val+. def []=(i,val) entry_length = struct_class_length unless (0...(length / entry_length)).include? i raise ArgumentError, "index out of range: #{i}" end unless val.length == entry_length raise ArgumentError, "wrong entry length: #{val.length} != #{entry_length}" end _old_replace_substr(entry_length * i, entry_length, val) end ## TODO: [i..j] etc. # Iterate over entries. def each entry_length = struct_class_length (length / entry_length).times do |i| yield self[i] end end def inspect(opts = BitStruct::DEFAULT_INSPECT_OPTS) if opts[:include_class] opts = opts.dup opts[:include_class] = false s = self.class.inspect + ": " else s = "" end s << entries.map{|entry| entry.inspect(opts)}.join(opts[:separator]) lb, rb = opts[:brackets] [lb, s, rb].join end def inspect_detailed inspect(BitStruct::DETAILED_INSPECT_OPTS) end end