Class: CAStruct
- Inherits:
-
Object
- Object
- CAStruct
- 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
Defined Under Namespace
Classes: Builder, DecodeError, DefinitionError, Error, Field
Class Method Summary collapse
-
.[](*values) ⇒ CAStruct
Returns a new struct record whose members are set from
valuesin declaration order. -
.decode(data) ⇒ CAStruct
Returns a new struct record built from the binary
data(String or CArray). -
.field_info(name) ⇒ CAStruct::Field?
Returns the Field for
name, ornilwhen the member is unknown. -
.fields ⇒ Array<CAStruct::Field>
Returns the struct's members as Field objects in declaration order.
-
.inspect ⇒ String
The struct's name, or
"AnonStruct"when it was defined anonymously. -
.members ⇒ Array<String>
Returns the member name list in declaration order.
-
.offset_of(name) ⇒ Integer?
Returns the offset of member
name: bytes for regular members, bits for bit members. -
.size ⇒ Integer
Returns the byte size of the struct's layout.
-
.spec ⇒ String
Renders the struct's layout as the definition source that would rebuild it — member names, types, offsets and any nested struct.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Value comparison against another record of the same class.
-
#[](name) ⇒ Object
Member access (
record[:name]/record["name"]/record[i]). -
#[]=(name, val) ⇒ Object
Sets member
nametoval. -
#decode(data) ⇒ self
Loads
data's binary representation intoself. -
#each({ |value| ... }) {|value| ... } ⇒ self
Yields each member value in declaration order.
-
#each_pair({ |name, value| ... }) {|name, value| ... } ⇒ self
Yields each member as a
(name, value)pair in declaration order. -
#encode ⇒ String
Returns the binary representation of
selfmatching this struct's layout. -
#eql?(other) ⇒ Boolean
Byte-level identity.
-
#hash ⇒ Integer
A hash consistent with #eql? (byte-level identity), so records work as Hash keys and Set members.
-
#initialize(*values) ⇒ CAStruct
constructor
Allocates a new struct record.
-
#inspect ⇒ String
The class name followed by every member and its value.
-
#length ⇒ Integer
(also: #size)
Returns the number of members.
-
#members ⇒ Array<String>
Returns the member name list in declaration order.
-
#spec ⇒ String
The layout of this record's class; see CAStruct.spec.
-
#swap_bytes ⇒ CAStruct
Returns a fresh byte-swapped copy of
self. -
#swap_bytes! ⇒ self
Byte-swaps
selfin place (endian flip). -
#to_ptr ⇒ Fiddle::Pointer
Returns a
Fiddle::Pointerto the backing storage. -
#values ⇒ Array<Object>
(also: #to_a)
Returns the member values in declaration order.
-
#values_at(*names) ⇒ Array<Object>
Returns the values of the named members in the given order.
Constructor Details
#initialize(*values) ⇒ CAStruct
Returns a new instance of CAStruct.
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
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
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?
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 |
.fields ⇒ Array<CAStruct::Field>
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 |
.inspect ⇒ String
Returns 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 |
.members ⇒ Array<String>
216 217 218 |
# File 'lib/carray/struct.rb', line 216 def members return self::MEMBERS end |
.offset_of(name) ⇒ Integer?
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 |
.size ⇒ Integer
278 279 280 |
# File 'lib/carray/struct.rb', line 278 def size return self::DATA_SIZE end |
.spec ⇒ String
Renders the struct's layout as the definition source that would rebuild it — member names, types, offsets and any nested struct.
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.
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.
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
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
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
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
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 |
#encode ⇒ 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.
450 451 452 |
# File 'lib/carray/struct.rb', line 450 def eql? (other) other.is_a?(self.class) && encode == other.encode end |
#hash ⇒ Integer
Returns 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 |
#inspect ⇒ String
Returns 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 |
#length ⇒ Integer Also known as: size
396 397 398 |
# File 'lib/carray/struct.rb', line 396 def length return self.class::MEMBERS.length end |
#members ⇒ Array<String>
405 406 407 |
# File 'lib/carray/struct.rb', line 405 def members return self.class::MEMBERS end |
#spec ⇒ String
Returns 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_bytes ⇒ CAStruct
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
491 492 493 494 |
# File 'lib/carray/struct.rb', line 491 def swap_bytes! @data[] = @data.swap_bytes return self end |
#to_ptr ⇒ Fiddle::Pointer
506 507 508 |
# File 'lib/carray/struct.rb', line 506 def to_ptr return @data.to_ptr end |
#values ⇒ Array<Object> Also known as: to_a
412 413 414 |
# File 'lib/carray/struct.rb', line 412 def values return members.map{|name| self[name] } end |
#values_at(*names) ⇒ Array<Object>
422 423 424 |
# File 'lib/carray/struct.rb', line 422 def values_at (*names) return names.map{|name| self[name] } end |