Class: CTypes::Array

Inherits:
Object
  • Object
show all
Includes:
Type
Defined in:
lib/ctypes/array.rb

Overview

Examples:

array of unsigned 32-bit integers

t = CTypes::Array.new(type: CTypes::Helpers.uint32)
t.unpack("\xaa\xaa\xaa\xaa\xbb\xbb\xbb\xbb")
                                  # => [0xaaaaaaaa, 0xbbbbbbbb]
t.pack([0,0xffffffff])            # => "\0\0\0\0\xff\xff\xff\xff"

array of signed 8-bit integers

t = CTypes::Array.new(type: CTypes::Helpers.int8)
t.unpack("\x01\x02\x03\x04")      # => [1, 2, 3, 4]
t.pack([1, 2, 3, 4])              # => "\x01\x02\x03\x04"

fixed-size array of 8-bit integers

t = CTypes::Array.new(type: CTypes::Helpers.int8, size: 2)
t.unpack("\x01\x02\x03\x04")      # => [1, 2]
t.pack([1, 2])                    # => "\x01\x02"

terminated array of 8-bit integers

t = CTypes::Array.new(type: CTypes::Helpers.int8, terminator: -1)
t.unpack("\x01\xff\x03\x04")      # => [1]
t.pack([1])                       # => "\x01\xff"

array of structures

include CTypes::Helpers
s = struct do
  attribute :type, uint8
  attribute :value, uint8
end
t = array(s)
t.unpack("\x01\x02\x03\x04")      # => [ { .type = 1, value = 2 },
                                  #      { .type = 3, value = 4 } ]
t.pack([{type: 1, value: 2}, {type: 3, value: 4}])
                                  # => "\x01\x02\x03\x04"

Instance Attribute Summary collapse

Attributes included from Type

#dry_type, #endian

Instance Method Summary collapse

Methods included from Type

#default_endian, #default_value, #fixed_size?, #pread, #read, #unpack, #unpack_all, #with_endian, #without_endian

Constructor Details

#initialize(type:, size: nil, terminator: nil) ⇒ Array

declare a new Array type

Parameters:

  • type (CTypes::Type)

    type contained within the array

  • size (Integer) (defaults to: nil)

    number of elements in the array; nil means greedy unpack

  • terminator (defaults to: nil)

    array value that denotes the end of the array; the value will not be appended in 'unpack` results, but will be appended during `pack`

Raises:



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/ctypes/array.rb', line 54

def initialize(type:, size: nil, terminator: nil)
  raise Error, "cannot use terminator with fixed size array" if
    size && terminator
  raise Error, "cannot make an Array of variable-length Unions" if
    type.is_a?(Class) && type < Union && !type.fixed_size?

  @type = type
  @size = size
  if terminator
    @terminator = terminator
    @term_packed = @type.pack(terminator)
    @term_unpacked = @type.unpack(@term_packed)
  end

  @dry_type = Dry::Types["coercible.array"].of(type.dry_type)
  @dry_type = if size
    @dry_type.constrained(size:)
      .default { ::Array.new(size, type.dry_type[]) }
  else
    @dry_type.default([].freeze)
  end
end

Instance Attribute Details

#terminatorObject (readonly)

Returns the value of attribute terminator.



76
77
78
# File 'lib/ctypes/array.rb', line 76

def terminator
  @terminator
end

#typeObject (readonly)

Returns the value of attribute type.



76
77
78
# File 'lib/ctypes/array.rb', line 76

def type
  @type
end

Instance Method Details

#==(other) ⇒ Object



173
174
175
176
177
178
# File 'lib/ctypes/array.rb', line 173

def ==(other)
  return false unless other.is_a?(Array)
  other.type == @type &&
    other.size == size &&
    other.terminator == terminator
end

#export_type(q) ⇒ Object

:nodoc:



158
159
160
161
162
163
164
165
# File 'lib/ctypes/array.rb', line 158

def export_type(q) # :nodoc:
  q << "array("
  q << @type
  q << ", #{@size}" if @size
  q << ", terminator: #{@terminator}" if @terminator
  q << ")"
  q << ".with_endian(%p)" % [@endian] if @endian
end

#greedy?Boolean

check if this Array is greedy

Returns:

  • (Boolean)


132
133
134
# File 'lib/ctypes/array.rb', line 132

def greedy?
  !@size && !@terminator
end

#pack(value, endian: default_endian, validate: true) ⇒ ::String

pack a ruby array into a binary string

Parameters:

  • value (::Array)

    array value to pack

  • endian (Symbol) (defaults to: default_endian)

    optional endian override

  • validate (Boolean) (defaults to: true)

    set to false to disable value validation

Returns:

  • (::String)

    binary string



83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ctypes/array.rb', line 83

def pack(value, endian: default_endian, validate: true)
  value = @dry_type[value] if validate
  out = value.inject(::String.new) do |o, v|
    o << @type.pack(v, endian: @type.endian || endian)
  end
  out << @term_packed if @term_packed
  out
rescue Dry::Types::ConstraintError
  raise unless @size && @size > value.size

  # value is short some elements; fill them in and retry
  value += ::Array.new(@size - value.size, @type.default_value)
  retry
end

#pretty_print(q) ⇒ Object

:nodoc:



143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ctypes/array.rb', line 143

def pretty_print(q) # :nodoc:
  q.group(1, "array(", ")") do
    q.pp(@type)
    if @size
      q.comma_breakable
      q.text(@size.to_s)
    end
    if @terminator
      q.comma_breakable
      q.text("terminator: #{@terminator}")
    end
  end
end

#sizeObject

return the size of the array if one is defined



137
138
139
140
141
# File 'lib/ctypes/array.rb', line 137

def size
  s = @size ? @size * @type.size : 0
  s += @term_packed.size if @term_packed
  s
end

#type_nameObject



167
168
169
170
171
# File 'lib/ctypes/array.rb', line 167

def type_name
  @size ?
    "%s[%s]" % [@type.type_name, @size] :
    "%s[]" % [@type.type_name]
end

#unpack_one(buf, endian: default_endian) ⇒ Array(Object, ::String)

unpack an instance of an array from the beginning of the supplied binary string

Parameters:

  • buf (::String)

    binary string

  • endian (Symbol) (defaults to: default_endian)

    optional endian override

Returns:

  • (Array(Object, ::String))

    unpacked Array, unused bytes fron buf



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/ctypes/array.rb', line 103

def unpack_one(buf, endian: default_endian)
  rest = buf
  if @size
    value = @size.times.map do |i|
      o, rest = @type.unpack_one(rest, endian: @type.endian || endian)
      o or raise missing_bytes_error(input: value,
        need: @size * @type.size)
    end
  else
    # handle variable-length array; both greedy and terminated
    value = []
    loop do
      if rest.empty?
        if @term_packed
          raise TerminatorNotFoundError,
            "terminator not found in: %p" % buf
        end
        break
      end

      v, rest = @type.unpack_one(rest, endian: @type.endian || endian)
      break if v === @term_unpacked
      value << v
    end
  end
  [value, rest]
end