require 'bindata/base'
module BinData
# An Array is a list of data objects of the same type.
#
# require 'bindata'
# require 'stringio'
#
# a = BinData::Array.new(:type => :int8, :initial_length => 5)
# io = StringIO.new("\x03\x04\x05\x06\x07")
# a.read(io)
# a.snapshot #=> [3, 4, 5, 6, 7]
#
# == Parameters
#
# Parameters may be provided at initialisation to control the behaviour of
# an object. These params are:
#
# :type:: The symbol representing the data type of the
# array elements. If the type is to have params
# passed to it, then it should be provided as
# [type_symbol, hash_params].
# :initial_length:: The initial length of the array.
class Array < Base
include Enumerable
# Register this class
register(self.name, self)
# These are the parameters used by this class.
mandatory_parameters :type, :initial_length
# Creates a new Array
def initialize(params = {}, env = nil)
super(params, env)
type, el_params = param(:type)
klass = self.class.lookup(type)
raise TypeError, "unknown type '#{type}' for #{self}" if klass.nil?
@element_list = nil
@element_klass = klass
@element_params = el_params || {}
# TODO: how to increase the size of the array?
end
# Clears the element at position +index+. If +index+ is not given, then
# the internal state of the array is reset to that of a newly created
# object.
def clear(index = nil)
if @element_list.nil?
# do nothing as the array is already clear
elsif index.nil?
@element_list = nil
else
elements[index].clear
end
end
# Returns if the element at position +index+ is clear?. If +index+
# is not given, then returns whether all fields are clear.
def clear?(index = nil)
if @element_list.nil?
true
elsif index.nil?
elements.each { |f| return false if not f.clear? }
true
else
elements[index].clear?
end
end
# Reads the values for all fields in this object from +io+.
def _do_read(io)
elements.each { |f| f.do_read(io) }
end
# To be called after calling #do_read.
def done_read
elements.each { |f| f.done_read }
end
# Writes the values for all fields in this object to +io+.
def _write(io)
elements.each { |f| f.write(io) }
end
# Returns the number of bytes it will take to write the element at
# +index+. If +index+, then returns the number of bytes required
# to write all fields.
def _num_bytes(index)
if index.nil?
elements.inject(0) { |sum, f| sum + f.num_bytes }
else
elements[index].num_bytes
end
end
# Returns a snapshot of the data in this array.
def snapshot
elements.collect { |e| e.snapshot }
end
# An array has no fields.
def field_names
[]
end
# Returns the element at +index+. If the element is a single_value
# then the value of the element is returned instead.
def [](index)
obj = elements[index]
obj.single_value? ? obj.value : obj
end
# Sets the element at +index+. If the element is a single_value
# then the value of the element is set instead.
def []=(index, value)
obj = elements[index]
unless obj.single_value?
raise NoMethodError, "undefined method `[]=' for #{self}", caller
end
obj.value = value
end
# Iterate over each element in the array. If the elements are
# single_values then the values of the elements are iterated instead.
def each
elements.each do |el|
yield(el.single_value? ? el.value : el)
end
end
# The number of elements in this array.
def length
elements.length
end
alias_method :size, :length
#---------------
private
# Returns the list of all elements in the array. The elements
# will be instantiated on the first call to this method.
def elements
if @element_list.nil?
@element_list = []
# create the desired number of instances
eval_param(:initial_length).times do |i|
env = create_env
env.index = i
@element_list << @element_klass.new(@element_params, env)
end
end
@element_list
end
end
end