module Gogyou # # 構造体データを構造体として参照させるためのクラスです。 # # このクラスのサブクラスをさらに派生させたクラスが、実際の役目を負います。 # # === クラス関係図 # # リストの入れ子関係は、クラスの親子関係を表します。 # # * Accessor - 基本クラス # * Accessor::BasicStruct - 構造体の基本クラス # * Accessor::Struct - 特定の Model に対する構造体クラス # * Accessor::TemporaryStruct - 汎用的な型情報に対する構造体クラス # * Accessor::BasicUnion - 共用体の基本クラス # * Accessor::Union - 特定の Model に対する共用体クラス # * Accessor::TemporaryUnion - 汎用的な型情報に対する共用体クラス # * Accessor::BasicArray - 配列型の基本クラス # * Accessor::Array - 特定の Model に対する配列型クラス # * Accessor::TemporaryArray - 汎用的な型情報に対する配列型クラス # class Accessor attr_reader :buffer__GOGYOU__ attr_reader :offset__GOGYOU__ alias buffer buffer__GOGYOU__ alias offset offset__GOGYOU__ def initialize(buffer = String.alloc(self.class::BYTESIZE), offset = 0) buffer = String.alloc(buffer.to_i) if buffer.kind_of?(Integer) @buffer__GOGYOU__ = buffer @offset__GOGYOU__ = offset.to_i end def initialize_copy(obj) super(obj) unless obj.frozen? @buffer__GOGYOU__ = obj.buffer__GOGYOU__.dup end end # # バイナリデータとして取り出します。 # def to_s @buffer__GOGYOU__.byteslice(@offset__GOGYOU__, self.class::BYTESIZE) end def to_buffer @buffer__GOGYOU__ end def to_ptr @buffer__GOGYOU__.to_ptr end # # call-seq: # slide() -> new_accessor or nil # slide(bytesize) -> new_accessor or nil # # 自身のデータ領域を指定バイト数ずらした参照体を返します。 # def slide(bytesize = 0) offset = @offset__GOGYOU__ + self.class::BYTESIZE + bytesize self.class.new(@buffer__GOGYOU__, offset) end # # call-seq: # slide!() -> self or nil # slide!(bytesize) -> self or nil # # 詳細は slide を参照して下さい。 # def slide!(bytesize = 0) offset = @offset__GOGYOU__ + self.class::BYTESIZE + bytesize @offset__GOGYOU__ = offset self end def bytesize self.class::BYTESIZE end def elementsize nil end def size elementsize end # # call-seq: # validate? -> true or false # # validation for buffer window. # def validate? if @offset__GOGYOU__ + bytesize > @buffer__GOGYOU__.bytesize false else true end end def inspect text = "#<#{self.class}" bufsize = @buffer__GOGYOU__.bytesize self.class::MODEL.fields.each_with_index do |f, i| if @offset__GOGYOU__ + f.offset + f.bytesize > bufsize text << "#{i > 0 ? "," : ""} #{f.name}=N/A" else text << "#{i > 0 ? "," : ""} #{f.name}=#{__send__(f.name).inspect}" end end text << ">" end def pretty_print(q) bufsize = @buffer__GOGYOU__.bytesize q.group(2, "#<#{self.class}") do self.class::MODEL.fields.each_with_index do |f, i| q.text "," if i > 0 q.breakable " " if @offset__GOGYOU__ + f.offset + f.bytesize > bufsize q.text "#{f.name}=N/A" else q.group(1, "#{f.name}=") do q.breakable "" q.pp __send__(f.name) end end end q.text ">" end end def self.bind(buffer, offset = 0) new(buffer, offset) end def self.model self::MODEL end def self.define(model) klass = ::Class.new(self) klass.class_eval do const_set(:MODEL, model) const_set(:BYTESIZE, model.bytesize) const_set(:BYTEALIGN, model.bytealign) const_set(:EXTENSIBLE, model.extensible?) end define_accessors(klass, model) klass end def self.define_subarray(field) #sub-array のためのクラスを生成する (多次元配列であれば、それぞれの次元に対して作成) #sub-array クラスの MODEL 定数は、Gogyou::Model::Array のインスタンス fsize = field.vector.inject(&:*) * field.type.bytesize falign = field.type.bytealign felements = field.vector[-1] raise "BUG: negative element bytesize - #{field.inspect}" unless felements >= 0 felements = nil if felements == 0 fvect = field.vector.slice(0 ... -1) fvect = nil if fvect.empty? subarray = Accessor::Array.define(Model::Array[fsize, falign, [Model::Field[nil, felements, fvect, field.type]]]) subarray.name # すでに名前が定義されてる場合はこれで固定される Accessor.const_set("UserArray_%08X" % subarray.__id__, subarray) subarray end def self.define_accessors(accessorclass, model) accessorclass.class_eval do namecheck = {} fieldsize = model.fields.size define_method(:size__GOGYOU__, -> { fieldsize }) alias_method(:elementsize, :size__GOGYOU__) alias_method(:size, :size__GOGYOU__) model.fields.each do |field| name = field.name name = name.intern raise NameError, "already exist field name - #{name}" if namecheck[name] namecheck[name] = true if field.vector subarray = define_subarray(field) type = subarray else subarray = nil type = field.type end define_method(field.name, -> { v = type.aref(@buffer__GOGYOU__, @offset__GOGYOU__ + field.offset) v.infect_from(self, buffer) unless v.frozen? v.freeze if frozen? || buffer.frozen? || field.const? v }) define_method("#{field.name}=", ->(value) { raise TypeError, "immutable object (#<%s:0x%08X>.%s)" % [self.class, __id__, field.name], caller(2) if frozen? raise TypeError, "immutable field (#<%s:0x%08X>.%s)" % [self.class, __id__, field.name], caller(2) if field.const? type.aset(@buffer__GOGYOU__, @offset__GOGYOU__ + field.offset, value) }) end end end # # 型情報オブジェクトとしてのメソッドです。 # def self.aref(buffer, offset) new(buffer, offset) end # # 型情報オブジェクトとしてのメソッドです。 # def self.aset(buffer, offset, data) raise NotImplementedError, "IMPLEMENT ME in sub class!" end # # 型情報オブジェクトとしてのメソッドです。 # def self.bytesize self::BYTESIZE end # # 型情報オブジェクトとしてのメソッドです。 # def self.bytealign self::BYTEALIGN end # # 型情報オブジェクトとしてのメソッドです。 # def self.extensible? self::EXTENSIBLE end class BasicStruct < Accessor end class BasicUnion < BasicStruct end class BasicArray < Accessor end class Struct < BasicStruct def self.aref(buffer, offset) new(buffer, offset) end def self.aset(buffer, offset, data) self::MODEL.aset(buffer, offset, data) end end class Union < BasicUnion end class Array < BasicArray def self.elements self::ELEMENTS end def self.define(model) klass = ::Class.new(self) klass.class_eval do field = model.fields[0] const_set(:MODEL, model) const_set(:BYTESIZE, model.bytesize) const_set(:BYTEALIGN, model.bytealign) const_set(:EXTENSIBLE, model.bytesize == 0 || model.extensible?) const_set(:ELEMENTS, elements = field.name) vector = field.vector if vector type = define_subarray(field) else type = field.type end if type.kind_of?(::Module) # すでに名前が定義されてる場合はこれで固定される type.name end const_set(:SUBTYPE, type) bytesize = type.bytesize if model.bytesize == 0 define_method(:check_index, ->(index) { index = index.to_i unless index >= 0 && index < self.elementsize raise IndexError, "out of element size (index #{index} for 0 ... #{self.elementsize})", caller(2) end index }) define_method(:<<, ->(value) { raise TypeError, "immutable object (#<%s:0x%08X>)" % [self.class, __id__], caller(2) if frozen? voff = (@buffer__GOGYOU__.bytesize - @offset__GOGYOU__).align_floor(type.bytesize) expandsize = @offset__GOGYOU__ + voff + type.bytesize @buffer__GOGYOU__.resize(expandsize) type.aset(@buffer__GOGYOU__, @offset__GOGYOU__ + voff, value) self }) define_method(:elementsize, -> { (@buffer__GOGYOU__.bytesize - @offset__GOGYOU__).unit_floor(type.bytesize) }) alias_method(:size, :elementsize) define_method(:bytesize, -> { (@buffer__GOGYOU__.bytesize - @offset__GOGYOU__).align_floor(type.bytesize) }) else eval <<-EOS def check_index(index) index = index.to_i unless index >= 0 && (#{elements.nil?} || index < #{elements}) raise IndexError, "out of element size (index \#{index} for 0 ... #{elements})", caller end index end def elementsize #{elements} end alias size elementsize def bytesize #{type.bytesize * elements} end EOS end define_method(:to_s, -> { @buffer__GOGYOU__.byteslice(@offset__GOGYOU__, self.bytesize) }) define_method(:[], ->(index) { v = type.aref(@buffer__GOGYOU__, @offset__GOGYOU__ + check_index(index) * bytesize) v.infect_from(self, buffer) unless v.frozen? v.freeze if frozen? || buffer.frozen? || field.const? v }) define_method(:[]=, ->(index, value) { raise TypeError, "immutable object (#<%s:0x%08X>)" % [self.class, __id__, index], caller(2) if frozen? || field.const? type.aset(@buffer__GOGYOU__, @offset__GOGYOU__ + check_index(index) * bytesize, value) }) end klass end def self.aset(buffer, offset, value) case value when ::String raise ArgumentError, "buffer size too small" unless value.bytesize <= self::BYTESIZE buffer.setbinary(offset, value, 0, self::BYTESIZE) when ::Array raise NotImplementedError when self::SUBTYPE raise NotImplementedError else raise ArgumentError end value end def bytesize return super unless self.class.extensible? self.class::BYTESIZE * @buffer__GOGYOU__.bytesize.unit_floor(self.class::SUBTYPE) end def inspect text = "[" elementsize.times.with_index do |n, i| text << (i > 0 ? ", " : "") << __send__(:[], n).inspect end text << "]" end def pretty_print(q) q.group(1, "[") do elementsize.times.with_index do |n, i| if i > 0 q.text "," q.breakable " " end q.pp __send__(:[], n) end q.text "]" end end end module TemporaryMixin attr_reader :model__GOGYOU__ def initialize(buffer, offset, model) super(buffer, offset) @model__GOGYOU__ = model self.class.define_accessors(singleton_class, model) end def inspect text = "{" model__GOGYOU__.fields.each_with_index do |f, i| text << "#{i > 0 ? ", " : ""}#{f.name}=#{__send__(f.name).inspect}" end text << "}" end def pretty_print(q) q.group(1, "{") do model__GOGYOU__.fields.each_with_index do |f, i| if i > 0 q.text "," q.breakable " " end q.group(1, "#{f.name}=") do q.breakable "" q.pp __send__(f.name) end end q.text "}" end end end class TemporaryStruct < BasicStruct include TemporaryMixin end class TemporaryUnion < BasicUnion include TemporaryMixin end class TemporaryArray < BasicArray include TemporaryMixin end end end