Class: CAStruct

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/carray/struct.rb,
lib/carray/struct.rb,
lib/carray/struct.rb

Overview

The data class for fixed length carray are required to satisfy only five conditions.

  • constant data_class::DATA_SIZE -> integer
  • constant data_class::MEMBER_TABLE -> hash
  • constant data_class::MEMBERS -> array (MEMBER_TABLE.keys as usual)
  • method data_class.decode(data) -> new data_class object
  • method data_class#encode() -> string

The implementation of other properties (cf. initialization, instance, methods ...) are left free.

CAStruct and CAUnion are examples of such data class.

option = { :pack => 1, # nil for alignment, int for pack(n) :size => 1024 # user defined size (with padding) }

CArray.struct(option) { |s|

# numeric types

int8 :a, :b, :c

float32 :f1, :f2 float :f5, :f6

float64 :d1, :d2 double :d5, :d6

# fixed length or string

fixlen :str1, :str2, :bytes => 3 char_p :str3, :str4, :bytes => 3

# array type array :ary1, :ary2, :type => CArray.int(3)

# struct type struct(:st1, :st2) { uint8 :a, :b, :c } struct :st3, :st4, :type => CArray.struct { uint8 :a, :b, :c }

# union type union(:un1, :un2) { uint8 :a; int16 :b; float32 :c } union :un3, :un4, :type => CArray.union { uint8 :a, :b, :c }

# anonymous

int8_t nil, nil, nil fixlen nil, :bytes=>3 ### padding

# low level definition member CA_INT8, :x0 member :int8, :mem0, :mem1 member "int8", :mem0, :mem1 member :uint8, nil ### anonymous member CArray.int(3), :ary3 member struct{ int8 :a, :b, :c }, :st5, :st6 member union{ int8 :a; int16 :b; float :c }, :st5, :st6

}

Direct Known Subclasses

CAUnion

Defined Under Namespace

Classes: Builder, DecodeError, DefinitionError, Error, Field

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*values) ⇒ CAStruct

Returns a new instance of CAStruct.

Allocates a new struct record. Members are set from the trailing positional values in declaration order, or from a single Hash argument keyed by member name.

Parameters:

Raises:

  • (ArgumentError)

    on unknown Hash keys or an excess of positional values.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/carray/struct.rb', line 291

def initialize (*argv)
  @data = CScalar.new(self.class)
  mems = members
  if argv.size == 1 and argv.first.is_a?(Hash)
    # Validate keys up-front so the caller gets one clear error
    # listing all unknown keys, rather than a NoMethodError from the
    # first one and silent partial-init for the rest.
    known   = self.class::MEMBER_TABLE
    unknown = argv.first.keys.reject { |k| known.key?(k.to_s) }
    unless unknown.empty?
      raise ArgumentError,
            format("unknown member(s) for %s: %s (known: %s)",
                   self.class.inspect,
                   unknown.map(&:inspect).join(", "),
                   mems.map(&:inspect).join(", "))
    end
    argv.first.each do |k, v|
      self[k] = v
    end
  elsif argv.size <= mems.size
    argv.each_with_index do |v, i|
      self[mems[i]] = v
    end
  else
    raise ArgumentError,
          format("too many arguments for %s.new (<%i> for <%i>)",
                 self.class.inspect, argv.size, mems.size)
  end
end

Class Method Details

.[](*values) ⇒ CAStruct

Returns a new struct record whose members are set from values in declaration order. Missing values leave the corresponding member at its default; extra values raise.

Parameters:

Returns:

Raises:

  • (ArgumentError)

    when too many values are given.



199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/carray/struct.rb', line 199

def [] (*argv)
  if argv.size > self::MEMBERS.size
    raise ArgumentError,
          format("too many arguments for %s.[] (<%i> for <%i>)",
                 inspect, argv.size, self::MEMBERS.size)
  end
  obj = new()
  members.each do |name|
    break if argv.empty?
    obj[name] = argv.shift
  end
  return obj
end

.decode(data) ⇒ CAStruct

Returns a new struct record built from the binary data (String or CArray).

Parameters:

Returns:



271
272
273
# File 'lib/carray/struct.rb', line 271

def decode (data)                        ### required element as data class
  return new.decode(data)
end

.field_info(name) ⇒ CAStruct::Field?

Returns the Field for name, or nil when the member is unknown.

Parameters:

  • name (Symbol, String)

Returns:



249
250
251
252
# File 'lib/carray/struct.rb', line 249

def field_info (name)
  key = name.to_s
  fields.find { |f| f.name == key }
end

.fieldsArray<CAStruct::Field>

Returns the struct's members as Field objects in declaration order. Cached on the class.

Returns:



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/carray/struct.rb', line 224

def fields
  @__fields__ ||= self::MEMBERS.map { |name|
    offset, type, opts = *self::MEMBER_TABLE[name]
    opts ||= {}
    if type == :bitfield
      CAStruct::Field.new(name:       name,
                          offset:     offset,
                          type:       :bitfield,
                          bits:       opts[:bits],
                          bit_offset: opts[:bit_offset])
    else
      CAStruct::Field.new(name:   name,
                          offset: offset,
                          type:   type,
                          bytes:  opts[:bytes],
                          endian: opts[:endian])
    end
  }.freeze
end

.inspectString

Returns the struct's name, or "AnonStruct" when it was defined anonymously.

Returns:

  • (String)

    the struct's name, or "AnonStruct" when it was defined anonymously.



188
189
190
# File 'lib/carray/struct.rb', line 188

def inspect
  return name.nil? ? "AnonStruct" : name
end

.membersArray<String>

Returns the member name list in declaration order.

Returns:



216
217
218
# File 'lib/carray/struct.rb', line 216

def members
  return self::MEMBERS
end

.offset_of(name) ⇒ Integer?

Returns the offset of member name: bytes for regular members, bits for bit members. nil when the member is unknown.

Parameters:

  • name (Symbol, String)

Returns:

  • (Integer, nil)


260
261
262
263
264
# File 'lib/carray/struct.rb', line 260

def offset_of (name)
  f = field_info(name)
  return nil unless f
  f.bitfield? ? f.bit_offset : f.offset
end

.sizeInteger

Returns the byte size of the struct's layout.

Returns:

  • (Integer)


278
279
280
# File 'lib/carray/struct.rb', line 278

def size
  return self::DATA_SIZE
end

.specString

Renders the struct's layout as the definition source that would rebuild it — member names, types, offsets and any nested struct.

Returns:

  • (String)


556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
# File 'lib/carray/struct.rb', line 556

def self.spec
  output = ""
  table  = self::MEMBER_TABLE
  stlist = []
  if self.name.nil?
    if self <= CAUnion
      prefix = "union"
    else
      prefix = "struct"
    end
    output << sprintf("%s_%i = ", 
                      prefix, [object_id].pack("V").unpack("V").first)
  else
    output << sprintf("%s = ", self.name)
  end
  if self < CAUnion
    output << sprintf("CArray.union(:size=>%i) {\n", self::DATA_SIZE)
  else
    output << sprintf("CArray.struct(:size=>%i) {\n", self::DATA_SIZE)
  end
  members.each do |member|
    offset, type, option = *table[member]
    case type
    when Class
      if type < CAStruct
        stlist << type
        if type.name.nil?
          if type <= CAUnion
            prefix = "union"
          else
            prefix = "struct"
          end
          output << sprintf("  member %s_%i, :%s, :offset=>%i\n", 
                            prefix,
                            [type.object_id].pack("V").unpack("V").first,
                            member, offset)
        else
          output << sprintf("  member %s, :%s, :offset=>%i\n", 
                            type.name, member, offset)
        end
      else
        raise "unknown type"
      end
    when CArray
      output << sprintf("  member %s, :%s, :offset=>%i\n", 
                        type.spec, member, offset)
    when :fixlen
      output << sprintf("  member :fixlen, :%s, :bytes=>%i, :offset=>%i\n", 
                        member, option[:bytes], offset)
    else
      output << sprintf("  member :%s, :%s, :offset=>%i\n", 
                        type, member, offset)
    end
  end
  output << sprintf("}\n")
  if stlist.empty?
    return output
  else
    stlist.uniq!
    preface = ""
    stlist.each do |st|
      preface << st.spec
    end
    return preface + output
  end
end

Instance Method Details

#==(other) ⇒ Boolean

Value comparison against another record of the same class. Unlike eql?, which compares the raw bytes, this compares the decoded members.

Returns:

  • (Boolean)


438
439
440
441
442
443
444
445
# File 'lib/carray/struct.rb', line 438

def == (other)
  case other
  when self.class
    return @data == other.__data__
  else
    return false
  end
end

#[](name) ⇒ Object

Member access (record[:name] / record["name"] / record[i]). Step 3 (3.0): the per-type case was replaced by a class-level DISPATCH_TABLE built once at struct-definition time. Each known member has a frozen [reader_proc, writer_proc] pair that closes over its offset / type / opts, so the per-call work is one Hash lookup + one Proc#call. Unknown names fall back to send(name) so subclasses can define computed members via plain Ruby methods.

Returns the value of member name (Integer indexes the member list). Unknown names fall through to send(name).

Parameters:

  • name (Symbol, String, Integer)

Returns:

  • (Object)


341
342
343
344
345
346
347
348
349
350
351
# File 'lib/carray/struct.rb', line 341

def [] (name)
  if name.kind_of?(Integer)
    name = members[name]
  end
  pair = self.class::DISPATCH_TABLE[name.to_s]
  if pair
    pair[0].call(@data)
  else
    send(name)
  end
end

#[]=(name, val) ⇒ Object

Sets member name to val. Unknown names fall through to send("#{name}=", val).

Parameters:

  • name (Symbol, String, Integer)
  • val (Object)

Returns:

  • (Object)

    val.



359
360
361
362
363
364
365
366
367
368
369
# File 'lib/carray/struct.rb', line 359

def []= (name, val)
  if name.kind_of?(Integer)
    name = members[name]
  end
  pair = self.class::DISPATCH_TABLE[name.to_s]
  if pair
    pair[1].call(@data, val)
  else
    send(name.to_s + "=", val)
  end
end

#decode(data) ⇒ self

Loads data's binary representation into self. A String is loaded directly; a CArray is copied through dump_binary (no aliasing).

Parameters:

Returns:

  • (self)

Raises:



467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/carray/struct.rb', line 467

def decode (data)
  case data
  when String
    @data.load_binary(data)
  when CArray
    @data.load_binary(data.dump_binary)
  else
    raise CAStruct::DecodeError,
          format("unknown data to decode: %s", data.class)
  end
  return self
end

#each({ |value| ... }) {|value| ... } ⇒ self

Yields each member value in declaration order.

Yield Parameters:

  • value (Object)

Returns:

  • (self)


375
376
377
378
379
# File 'lib/carray/struct.rb', line 375

def each
  members.each do |name|
    yield(self[name])
  end
end

#each_pair({ |name, value| ... }) {|name, value| ... } ⇒ self

Yields each member as a (name, value) pair in declaration order.

Yield Parameters:

  • name (Symbol)
  • value (Object)

Returns:

  • (self)


387
388
389
390
391
# File 'lib/carray/struct.rb', line 387

def each_pair
  members.each do |name|
    yield(name.intern, self[name])
  end
end

#encodeString

Returns the binary representation of self matching this struct's layout.

Returns:

  • (String)


484
485
486
# File 'lib/carray/struct.rb', line 484

def encode                          ### required element as data class
  return @data.dump_binary
end

#eql?(other) ⇒ Boolean

Byte-level identity. Two CAStruct instances are eql? iff they are of exactly the same class and their binary representations match. Lets struct records work as Hash keys / Set members.

Returns:

  • (Boolean)


450
451
452
# File 'lib/carray/struct.rb', line 450

def eql? (other)
  other.is_a?(self.class) && encode == other.encode
end

#hashInteger

Returns a hash consistent with #eql? (byte-level identity), so records work as Hash keys and Set members.

Returns:

  • (Integer)

    a hash consistent with #eql? (byte-level identity), so records work as Hash keys and Set members.



456
457
458
# File 'lib/carray/struct.rb', line 456

def hash
  encode.hash
end

#inspectString

Returns the class name followed by every member and its value.

Returns:

  • (String)

    the class name followed by every member and its value.



427
428
429
430
431
432
433
# File 'lib/carray/struct.rb', line 427

def inspect
  table = {}
  members.each do |key|
    table[key] = self[key]
  end
  return ["<", self.class.inspect, " ", table.inspect[1..-2], ">"].join
end

#lengthInteger Also known as: size

Returns the number of members.

Returns:

  • (Integer)


396
397
398
# File 'lib/carray/struct.rb', line 396

def length
  return self.class::MEMBERS.length
end

#membersArray<String>

Returns the member name list in declaration order.

Returns:



405
406
407
# File 'lib/carray/struct.rb', line 405

def members
  return self.class::MEMBERS
end

#specString

Returns the layout of this record's class; see spec.

Returns:

  • (String)

    the layout of this record's class; see spec.



624
625
626
# File 'lib/carray/struct.rb', line 624

def spec
  return self.class.spec
end

#swap_bytesCAStruct

Returns a fresh byte-swapped copy of self.

Returns:



499
500
501
# File 'lib/carray/struct.rb', line 499

def swap_bytes
  return self.class.decode(@data.swap_bytes.dump_binary)
end

#swap_bytes!self

Byte-swaps self in place (endian flip).

Returns:

  • (self)


491
492
493
494
# File 'lib/carray/struct.rb', line 491

def swap_bytes!
  @data[] = @data.swap_bytes
  return self
end

#to_ptrFiddle::Pointer

Returns a Fiddle::Pointer to the backing storage.

Returns:

  • (Fiddle::Pointer)


506
507
508
# File 'lib/carray/struct.rb', line 506

def to_ptr
  return @data.to_ptr
end

#valuesArray<Object> Also known as: to_a

Returns the member values in declaration order.

Returns:



412
413
414
# File 'lib/carray/struct.rb', line 412

def values
  return members.map{|name| self[name] }
end

#values_at(*names) ⇒ Array<Object>

Returns the values of the named members in the given order.

Parameters:

  • names (Array<Symbol, String, Integer>)

Returns:



422
423
424
# File 'lib/carray/struct.rb', line 422

def values_at (*names)
  return names.map{|name| self[name] }
end