class BinData::Array

to any lambda evaluated as a parameter of that data object.
Each data object in an array has the variable index made available
as much data from the stream as possible.
is the symbol :eof, then the array will read
this parameter. If the value of this parameter
are made available to any lambda assigned to
The variables index, element and array
read an array until a sentinel value is found.
condition is true. This is typically used to
:read_until
While reading, elements are read until this
:initial_length
The initial length of the array.
[type_symbol, hash_params].
passed to it, then it should be provided as
array elements. If the type is to have params
:type

The symbol representing the data type of the
an object. These params are:
Parameters may be provided at initialisation to control the behaviour of
== Parameters
obj.snapshot #=> [3, 4, 5, 6, 7, 8, 9]
obj.read(data)
obj = BinData::Array.new(:type => :int8, :read_until => :eof)
obj.snapshot #=> [3, 4, 5, 6, 7]
obj.read(data)
:read_until => lambda { array + array[index - 1] == 13 })
obj = BinData::Array.new(:type => :int8,
obj.snapshot #=> [3, 4, 5, 6]
obj.read(data)
:read_until => lambda { element >= 6 })
obj = BinData::Array.new(:type => :int8,
obj.snapshot #=> [3, 4]
obj.read(data)
:read_until => lambda { index == 1 })
obj = BinData::Array.new(:type => :int8,
obj.snapshot #=> [3, 4, 5, 6, 7, 8]
obj.read(data)
obj = BinData::Array.new(:type => :int8, :initial_length => 6)
data = “x03x04x05x06x07x08x09”
require ‘bindata’
An Array is a list of data objects of the same type.

def [](*args)

then the value of the element is returned instead.
Returns the element at +index+. If the element is a single_value
def [](*args)
  if args.length == 1 and ::Integer === args[0]
    # extend array automatically
    while args[0] >= elements.length
      append_new_element
    end
  end
  data = elements[*args]
  if args.length > 1 or ::Range === args[0]
    data.collect { |el| (el && el.single_value?) ? el.value : el }
  else
    (data && data.single_value?) ? data.value : data
  end
end

def []=(index, value)

then the value of the element is set instead.
Sets the element at +index+. If the element is a single_value
def []=(index, value)
  # extend array automatically
  while index >= elements.length
    append_new_element
  end
  obj = elements[index]
  unless obj.single_value?
    # TODO: allow setting objects, not just values
    raise NoMethodError, "undefined method `[]=' for #{self}", caller
  end
  obj.value = value
end

def _do_num_bytes(index)

to write all fields.
+index+. If +index+, then returns the number of bytes required
Returns the number of bytes it will take to write the element at
def _do_num_bytes(index)
  if index.nil?
    (elements.inject(0) { |sum, f| sum + f.do_num_bytes }).ceil
  else
    elements[index].do_num_bytes
  end
end

def _do_read(io)

Reads the values for all fields in this object from +io+.
def _do_read(io)
  if has_param?(:initial_length)
    elements.each { |f| f.do_read(io) }
  elsif has_param?(:read_until)
    if no_eval_param(:read_until) == :eof
      @element_list = nil
      loop do
        element = append_new_element
        begin
          element.do_read(io)
        rescue
          @element_list.pop
          break
        end
      end
    else
      @element_list = nil
      loop do
        element = append_new_element
        element.do_read(io)
        variables = { :index => self.length - 1, :element => self.last,
                      :array => self }
        finished = eval_param(:read_until, variables)
        break if finished
      end
    end
  end
end

def _do_write(io)

Writes the values for all fields in this object to +io+.
def _do_write(io)
  elements.each { |f| f.do_write(io) }
end

def _snapshot

Returns a snapshot of the data in this array.
def _snapshot
  elements.collect { |e| e.snapshot }
end

def append(value = nil)

Returns the appended object, or value in the case of single_values.
single_values then the +value+ may be provided to the call.
Appends a new element to the end of the array. If the array contains
def append(value = nil)
  # TODO: deprecate #append as it can be replaced with #push
  append_new_element
  self[-1] = value unless value.nil?
  self.last
end

def append_new_element

Returns the newly created element
Creates a new element and appends it to the end of @element_list.
def append_new_element
  # ensure @element_list is initialised
  elements()
  element = @element_klass.new(@element_params, self)
  @element_list << element
  element
end

def clear(index = nil)

object.
the internal state of the array is reset to that of a newly created
Clears the element at position +index+. If +index+ is not given, then
def clear(index = nil)
  if @element_list.nil?
    # do nothing as the array is already clear
  elsif index.nil?
    @element_list = nil
  elsif index < elements.length
    elements[index].clear
  end
end

def clear?(index = nil)

is not given, then returns whether all fields are clear.
Returns if the element at position +index+ is clear?. If +index+
def clear?(index = nil)
  if @element_list.nil?
    true
  elsif index.nil?
    elements.each { |f| return false if not f.clear? }
    true
  else
    (index < elements.length) ? elements[index].clear? : true
  end
end

def done_read

To be called after calling #do_read.
def done_read
  elements.each { |f| f.done_read }
end

def each

single_values then the values of the elements are iterated instead.
Iterate over each element in the array. If the elements are
def each
  elements.each do |el|
    yield(el.single_value? ? el.value : el)
  end
end

def elements

will be instantiated on the first call to this method.
Returns the list of all elements in the array. The elements
def elements
  if @element_list.nil?
    @element_list = []
    if has_param?(:initial_length)
      # create the desired number of instances
      eval_param(:initial_length).times do
        append_new_element
      end
    end
  end
  @element_list
end

def empty?

Returns true if self array contains no elements.
def empty?
  length.zero?
end

def first(n = nil)

form returns an empty array.
If the array is empty, the first form returns nil, and the second
Returns the first element, or the first +n+ elements, of the array.
def first(n = nil)
  if n.nil? and elements.empty?
    # explicitly return nil as arrays grow automatically
    nil
  elsif n.nil?
    self[0]
  else
    self[0, n]
  end
end

def index(obj)

def index(obj)
  # TODO handle single values
  elements.index(obj)
end

def initialize(params = {}, parent = nil)

Creates a new Array
def initialize(params = {}, parent = nil)
  super(params, parent)
  klass, el_params = no_eval_param(:type)
  @element_list    = nil
  @element_klass   = klass
  @element_params  = el_params
end

def last(n = nil)

form returns an empty array.
If the array is empty, the first form returns nil, and the second
Returns the last element, or the last +n+ elements, of the array.
def last(n = nil)
  if n.nil?
    self[-1]
  else
    n = length if n > length
    self[-n, n]
  end
end

def length

The number of elements in this array.
def length
  elements.length
end

def push(*args)

be chained together.
This expression returns the array itself, so several appends may
Pushes the given object(s) on to the end of this array.
def push(*args)
  args.each do |arg|
    if @element_klass == arg.class
      # TODO: need to modify arg.env to add_variable(:index) and
      # to link arg.env to self.env
      elements.push(arg)
    else
      append(arg)
    end
  end
  self
end

def sanitize_parameters!(sanitizer, params)

Ensures that +params+ is of the form expected by #initialize.
def sanitize_parameters!(sanitizer, params)
  unless params.has_key?(:initial_length) or params.has_key?(:read_until)
    # ensure one of :initial_length and :read_until exists
    params[:initial_length] = 0
  end
  if params.has_key?(:read_length)
    warn ":read_length is not used with arrays.  You probably want to change this to :initial_length"
  end
  if params.has_key?(:type)
    type, el_params = params[:type]
    klass = sanitizer.lookup_klass(type)
    sanitized_params = sanitizer.sanitize_params(klass, el_params)
    params[:type] = [klass, sanitized_params]
  end
  super(sanitizer, params)
end

def single_value?

value data objects respond to #value and #value=.
Returns whether this data object contains a single value. Single
def single_value?
  return false
end

def to_ary

Allow this object to be used in array context.
def to_ary
  snapshot
end