module Gogyou # # 構造体の構成情報などを保持するクラスです。 # # 構造体の大きさや各フィールドの名前、バイト位置、型などを管理します。 # class Model < ::Struct.new(:bytesize, # total bytesize in bytes :bytealign, # byte alignment :fields) # array of field BasicStruct = superclass FIELDNAME_PATTERN = /\A[A-Za-z_][0-9A-Za-z_]*\Z/ undef :bytesize=, :bytealign=, :fields= # # call-seq: # initialize(bytesize, bytealign, fields, ...) # # [bytesize] # This is total model size in bytes. # # [bytealign] # This is model alignment size in bytes. # # [fields ...] # These are one or more field instance. # def initialize(bytesize, bytealign, field1, *fields) if fields.empty? && field1.kind_of?(::Array) super else super bytesize.to_i, bytealign.to_i, [field1, *fields] end end def aset(buffer, offset, value) raise NotImplementedError end def aref(buffer, offset) raise NotImplementedError end def extensible? fields.any? { |f| f.extensible? } end def to_s "#{self.class}[bytesize=#{bytesize.inspect}, bytealign=#{bytealign.inspect}, fields=#{fields.inspect}]" end alias inspect to_s def pretty_print(q) #bytesize, bytealign, fields q.group(1, "#{self.class}[") do #q.breakable q.text "bytesize=" q.pp bytesize q.text ", " #q.breakable q.text "bytealign=" q.pp bytealign q.text ", " #q.breakable(" ") q.text "fields=" q.breakable q.pp fields end q.text "]" end # 構造体の各メンバの情報を保持する class Field < ::Struct.new(:offset, # field offset in model :name, # field name :vector, # 要素数。任意数配列の場合は 0。配列でないならば nil。 :type, # type specification (or model object) of this field :flags) # LSB-0+8: packed size exponent (not aligned) / LSB-16: const BasicStruct = superclass CONST_BITMASK = 1 << 16 PACKSIZE_BITMASK = 0xff PACKSIZE_NOTDEFINE = 20 # = (1 << 20) = 1 MiB # 事実上の無限 def initialize(offset, name, vector, type, flags = 0 | PACKSIZE_NOTDEFINE) super(offset, name, vector, type, flags) end def extensible? if vector vector[-1] == 0 ? true : false else type.extensible? end end def bytesize s = type.bytesize vector ? vector.inject(&:*) * s : s end def const? ((flags & CONST_BITMASK) == CONST_BITMASK) ? true : false end def packed? ((flags & PACKSIZE_BITMASK) == PACKSIZE_NOTDEFINE) ? false : true end def set_const self.flags |= CONST_BITMASK self end def set_packsize(pack_exponent) pack_exponent = PACKSIZE_NOTDEFINE if pack_exponent > PACKSIZE_NOTDEFINE self.flags = flags.setbit(0, 8, pack_exponent) self end def packsize 1 << flags.getbit(0, 8) end def strflags set = [const? ? "const" : nil, packed? ? "packed(#{packsize})" : nil] set.compact! return nil if set.empty? set.join(",") end def strflags_with_paren set = strflags set ? "(#{set})" : "" end def to_s "#{self.class}[offset=#{offset.inspect}, name=#{name.inspect}, vector=#{vector.inspect}, type=#{type.inspect}, flags=0x#{flags.to_s(16)}#{strflags_with_paren}]" end alias inspect to_s def pretty_print(q) q.group(1, "#{self.class}[") do #q.breakable q.text "offset=" q.pp offset q.text ", " #q.breakable q.text "name=" q.pp name q.text ", " #q.breakable q.text "vector=" q.pp vector q.text ", " #q.breakable q.text "flags=0x%02x%s" % [flags, strflags_with_paren] q.text "," q.breakable(" ") q.text "type=" q.pp type end q.text "]" end end def self.struct(typemap, packexp = Field::PACKSIZE_NOTDEFINE, &block) define_container(typemap, packexp, Model::Struct, &block) end def self.union(typemap, packexp = Field::PACKSIZE_NOTDEFINE, &block) define_container(typemap, packexp, Model::Union, &block) end def self.typedef(typemap, type, aliasname, *elements) raise ArgumentError, "informal aliasname (#{aliasname.inspect})" unless aliasname =~ FIELDNAME_PATTERN aliasname = aliasname.intern case type when Symbol, String type0 = type type = typemap[type.intern] raise ArgumentError, "type not defined (#{type0})" unless type else # 型情報子を用いる方法 raise ArgumentError, "type is not typeinfo (#{type.inspect})" unless Model.check_typeinfo(type) end unless elements.empty? # 配列型 # TODO: Accessor::Array を構築するのではなく、Model::Array インスタンスを生成するようにする type = Accessor.define_subarray(Model::Field[0, nil, elements, type, 0]) end typemap[aliasname] = type nil end def self.define_container(typemap, packexp, model_type, &block) creator = model_type::Creator.new(typemap, 0, []) proxy = model_type::Creator::Proxy.new(creator, packexp) proxy.instance_exec(&block) model = creator.to_model model end def self.check_typeinfo(obj) if obj.kind_of?(Model) || (obj.kind_of?(Module) && obj < Accessor) || (obj.respond_to?(:bytesize) && obj.respond_to?(:bytealign) && obj.respond_to?(:extensible?) && obj.respond_to?(:aset) && obj.respond_to?(:aref)) true else false end end BasicCreator = ::Struct.new(:typemap, :offset, :fields) class BasicCreator def maxalign(fields = self.fields) fields.map { |f| f.packed? ? f.packsize : f.type.bytealign }.max end def maxsize(fields = self.fields) fields.map { |f| f.bytesize }.max end def flatten_field(fields = self.fields) #pp fields fields2 = [] fields.each do |f| #p f #p f.class if f.name fields2 << f else raise "BUG? : field.type is not a Model (%p)" % f.type unless f.type.kind_of?(Model) fs = flatten_field(f.type.fields) fs.each { |ff| ff.offset += f.offset; ff.set_const if f.const? } fields2.concat fs end end fields2 end # # call-seq: # struct type, name, *vector # struct proc, name, *vector # struct { ... } # # 最初と二番目の呼び出し方法は、既存の (typedef していない) 型情報を用いる、または構造体をその場で定義するために利用できます。 # # 三番目の呼び出し方法は、無名構造体を定義するために利用できます。 # # === example (型情報を用いる) # # Type1 = struct { # struct UserType, :a, :b, 2, 3, 4 # } # # === example (構造体をその場で定義して、構造体へのアクセッサを定義する) # # Type2 = struct { # struct -> { # int :x, y, z # }, :a, :b, 2, 3, 4 # } # # === example (無名構造体) # # Type3 = struct { # struct { # int :a, :b, 2, 3, 4 # } # } # def struct(args, packexp, &block) define_container(args, packexp, block, Model.method(:struct)) end # # call-seq: # union type, name, *vector # union proc, name, *vector # union { ... } # # 共用体を定義します。 # # 呼び出し方は struct と変わりません。 # # ただ、union type, ... の場合は、struct type, ... と同じ結果となります。 # これは type がどのような構造になっているのかを gogyou が管理も把握もしないためです。 # この記述ができる唯一の理由は、人間が見てわかりやすくすることを意図しています # (ただし、ミスリードを誘う手口にも利用されてしまうのが最大の欠点です)。 # def union(args, packexp, &block) define_container(args, packexp, block, Model.method(:union)) end def define_container(args, packexp, anonymblock, container) if anonymblock raise ArgumentError, "given block and arguments" unless args.empty? model = container.(typemap.dup, packexp, &anonymblock) raise "BUG - object is not a Model (#{model.class})" unless model.kind_of?(Model) #p model: model, superclass: model.superclass self.offset = offset.align_ceil(model.bytealign) unless kind_of?(Model::Union::Creator) fields << f = Field[offset, nil, nil, model, packexp] self.offset += model.bytesize unless kind_of?(Model::Union::Creator) [f] else type = args.shift type = container.(typemap.dup, packexp, &type) if type.kind_of?(::Proc) addfield!(type, packexp, args) end end def typedef(args) raise NotImplementedError end def const(fields) fields.each { |f| f.set_const } end # # call-seq: # packed { ... } -> nil # packed(bytealign) { ... } -> nil # # ブロック内部のフィールドのバイトアライメントを調節します。 # # packed を直接の入れ子にして呼び出すことは出来ません。struct や union を挟んで呼び出すことは出来ます。 # # 引数無しで呼び出した場合は、bytealign に 1 を与えて呼び出すものと同義となります。 # # [bytealign] # 1 以上で2の冪乗となる整数値を指定します。 # # nil を与えた場合、上位階層で指定したパックサイズを無効化して本来のバイトアライメントに配置するようにします。 # def packed(bytealign = 1) raise "This method is defined for documentaion. Real implemented is in Gogyou::Model::BasicCreator::Proxy#initialize" yield nil end # # フィールド名の解析 # def parse!(args) raise ArgumentError, "nothing argument" if args.empty? name = nil vector = nil while arg = args.shift case arg when Symbol, String yield(name, vector) if name raise ArgumentError, "informal field name (#{arg.to_s})" unless arg =~ FIELDNAME_PATTERN name = arg.intern vector = nil when Integer raise ArgumentError, "first argument is field name only (#{arg})" unless name raise ArgumentError, "given negative number (#{arg})" unless arg >= 0 vector ||= [] vector << arg.to_i if vector[-1] == 0 yield(name, vector) unless args.empty? raise ArgumentError, "given fields after extensible vector" end return nil end else raise ArgumentError, "given any object (#{arg.inspect})" end end yield(name, vector) nil end def addfield(type, packexp, args) typeobj = typemap[type.intern] unless typeobj raise NoMethodError, "typename or method is missing (#{type})" end addfield!(typeobj, packexp, args) end def addfield!(typeobj, packexp, args) #p typeobj # check extensible field >>> creator.fields[-1].vector[-1] if (x = fields[-1]) && (x = x.vector) && x[-1] == 0 raise ArgumentError, "not given fields after extensible vector" end typesize = typeobj.bytesize typealign = [typeobj.bytealign, 1 << packexp].min tmpfields = [] parse!(args) do |name, vect| self.offset = offset.align_ceil(typealign) unless kind_of?(Model::Union::Creator) fields << f = Field[offset, name, vect, typeobj, 0 | packexp] tmpfields << f unless kind_of?(Model::Union::Creator) elements = vect ? vect.inject(1, &:*) : 1 self.offset += typesize * elements end end tmpfields end class Proxy < Object # :nodoc: all #class Proxy < BasicObject def initialize(creator, packexp = Field::PACKSIZE_NOTDEFINE) #singleton_class = (class << proxy; self; end) singleton_class.class_eval do latest_fields = nil #define_method(:method_missing, ->(type, *args) { latest_fields = creator.addfield(type, args); nil }) creator.typemap.each_key do |t| define_method(t, ->(*args) { latest_fields = creator.addfield(t, packexp, args); nil }) end define_method(:struct, ->(*args, &block) { latest_fields = creator.struct(args, packexp, &block); nil }) define_method(:union, ->(*args, &block) { latest_fields = creator.union(args, packexp, &block); nil }) define_method(:const, ->(dummy_fields) { creator.const(latest_fields); latest_fields = nil; nil }) define_method(:typedef, ->(*args, &block) { creator.typedef(args, &block) }) packexp0 = nil define_method(:packed, ->(bytealign = 1, &block) { raise "wrong nested ``packed''" if packexp0 if bytealign.nil? exp = Field::PACKSIZE_NOTDEFINE else exp = Math.log(bytealign, 2) # exp が Nan Infinity -Infinity の場合は例外が発生するので、それに対する処置も行う unless ((exp = exp.to_i) rescue nil) && (1 << exp) == bytealign raise ArgumentError, "shall be given power of two (but #{bytealign})" end end begin packexp0 = packexp packexp = exp self.instance_exec(&block) ensure (packexp, packexp0) = packexp0, nil end nil }) if creator.respond_to?(:bytealign) define_method(:bytealign, ->(bytesize, &block) { creator.bytealign(bytesize, &block); nil }) end if creator.respond_to?(:padding) define_method(:padding, ->(bytesize, &block) { creator.padding(bytesize, &block); nil }) end end end end end class Struct < Model class Creator < Model::BasicCreator def bytealign(bytesize) raise NotImplementedError end def padding(bytesize) raise NotImplementedError end def to_model Model::Struct.new(offset.align_ceil(maxalign), maxalign, flatten_field) end end def aset(buffer, offset, value) raise NotImplementedError end def aref(buffer, offset) v = Accessor::TemporaryStruct.new(buffer, offset, self) v.infect_from(self, buffer) unless v.frozen? v.freeze if frozen? || buffer.frozen? v end def create_accessor Accessor::Struct.define(self) end end class Union < Model class Creator < Model::BasicCreator def to_model Model::Union.new(maxsize.align_ceil(maxalign), maxalign, flatten_field) end end def aset(buffer, offset, value) raise NotImplementedError end def aref(buffer, offset) v = Accessor::TemporaryUnion.new(buffer, offset, self) v.infect_from(self, buffer) unless v.frozen? v.freeze if frozen? || buffer.frozen? v end def create_accessor Accessor::Union.define(self) end end # # C の配列を模造するクラス。 # class Array < Model def extensible? fields[-1] == 0 ? true : false end def aset(buffer, offset, value) raise NotImplementedError end def aref(buffer, offset) raise NotImplementedError accessor = Accessor::Array[buffer, offset, self] accessor.instance_eval do field = fields[0] type = field.type elements = field.vector[-1] define_singleton_method(:check_index, ->(i) { i = i.to_i raise IndexError unless i >= 0 && (elements.nil? || i < elements) i }) define_singleton_method(:[], ->(i) { v = type.aref(buffer__GOGYOU__, offset__GOGYOU__ + type.bytesize * check_index(i)) v.infect_from(self, buffer) unless v.frozen? v.freeze if frozen? || buffer.frozen? || field.const? v }) define_singleton_method(:[]=, ->(i, v) { type.aset(buffer__GOGYOU__, offset__GOGYOU__ + type.bytesize * check_index(i), v) }) end accessor end end end end