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=
def initialize(*args)
case
when args.size < 3
raise ArgumentError, "wrong argument size (#{args.size} for 3+)"
when args.size < 4 && args[2].kind_of?(::Array)
super
else
super(args[0], args[1], args.slice(2 .. -1))
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) # 0x01: const / 0x02: packed (not aligned)
BasicStruct = superclass
FLAG_CONST = 0x01
FLAG_PACKED = 0x02
def initialize(offset, name, vector, type, flags = 0)
super(offset, name, vector, type, flags)
end
def extensible?
if vector
vector[-1] == 0 ? true : false
else
type.extensible?
end
end
def const?
((flags & FLAG_CONST) == FLAG_CONST) ? true : false
end
def packed?
((flags & FLAG_PACKED) == FLAG_PACKED) ? true : false
end
def mark_const
self.flags |= FLAG_CONST
self
end
def mark_packed
self.flags |= FLAGS_PACKED
self
end
def strflags
set = [const? ? "const" : nil, packed? ? "packed" : 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, &block)
define_container(typemap, Model::Struct, &block)
end
def self.union(typemap, &block)
define_container(typemap, 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, model_type, &block)
creator = model_type::Creator.new(typemap, 0, [])
proxy = model_type::Creator::Proxy.new(creator)
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.type.bytealign }.max
end
def maxsize(fields = self.fields)
fields.map { |f| s = f.type.bytesize; f.vector ? f.vector.inject(&:*) * s : s }.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.mark_const if f.const? }
fields2.concat fs
end
end
fields2
end
# :nodoc: all
class Proxy < Object
#class Proxy < BasicObject
def initialize(creator)
#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, args); nil })
end
define_method(:struct, ->(*args, &block) { latest_fields = creator.struct(args, &block); nil })
define_method(:union, ->(*args, &block) { latest_fields = creator.union(args, &block); nil })
define_method(:const, ->(dummy_fields) { creator.const(latest_fields); latest_fields = nil; nil })
define_method(:typedef, ->(*args, &block) { creator.typedef(args, &block) })
if creator.respond_to?(:bytealign)
define_method(:bytealign, ->(bytesize, &block) { creator.bytealign(bytesize, &block) })
end
if creator.respond_to?(:padding)
define_method(:padding, ->(bytesize, &block) { creator.padding(bytesize, &block) })
end
end
end
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, &block)
define_container(args, 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, &block)
define_container(args, block, Model.method(:union))
end
def define_container(args, anonymblock, container)
if anonymblock
raise ArgumentError, "given block and arguments" unless args.empty?
model = container.(typemap.dup, &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, 0]
self.offset += model.bytesize unless kind_of?(Model::Union::Creator)
[f]
else
type = args.shift
type = container.(typemap.dup, &type) if type.kind_of?(::Proc)
addfield!(type, args)
end
end
def typedef(args)
raise NotImplementedError
end
def const(fields)
fields.each { |f| f.mark_const }
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, args)
typeobj = typemap[type.intern]
unless typeobj
raise NoMethodError, "typename or method is missing (#{type})"
end
addfield!(typeobj, args)
end
def addfield!(typeobj, 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
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]
tmpfields << f
unless kind_of?(Model::Union::Creator)
elements = vect ? vect.inject(1, &:*) : 1
self.offset += typesize * elements
end
end
tmpfields
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