klayout/scripts/mkqtdecl_common/produce.rb

3282 lines
92 KiB
Ruby
Executable File

#!/usr/bin/env ruby
#
# Copyright (C) 2006-2024 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
$:.push(File.dirname($0))
require 'oj'
require 'cpp_classes.rb'
require 'reader_ext.rb'
input_file = "all.db"
conf_file = "mkqtdecl.conf"
cls_list = nil
excl_list = {}
modn = "Qt"
$gen_dir = "generated"
while ARGV.size > 0
o = ARGV.shift
if o == "-i"
input_file = ARGV.shift
elsif o == "-c"
cls_list = ARGV.shift
elsif o == "-x"
excl_file = ARGV.shift
File.open(excl_file, "r") do |file|
file.each_line { |l| excl_list[l.chop] = true }
end
elsif o == "-s"
conf_file = ARGV.shift
elsif o == "-m"
modn = ARGV.shift
else
raise("Invalid option #{o} - usage is 'produce.rb -s mkqtdecl.conf -i all.db -c QWidget,QSizePolicy'")
end
end
# Objects of this type require special treatment when passing values between GSI and Qt
# (a converter is instantiated)
SpecialClasses = [ "QChar", "WId", "Q_PID" ]
# These typedefs are maintained to provide some abstraction
MaintainTypedefs = [ "Q_PID", "WId", "HANDLE", "Qt::HANDLE",
"qulonglong", "qlonglong",
"qint8", "qint16", "qint32", "qint64",
"quint8", "quint16", "quint32", "quint64",
"quintptr" ]
# The substitutions for signal/slot signatures to maintain the signature
SignalSubstitutions = {
"QList<QModelIndex>" => "QModelIndexList"
}
class CPPDeclaration
def is_const?
self.type.func.cv == :const
end
def ref
self.type.func.ref
end
def hash_str
# TODO: this is a hack for making the hash values unique:
$unique ||= {}
# (weak) backward compatibility for hash computation
func = self.type.func
nmax = func.max_args
hk = (self.is_const? ? "c" : "")
if self.ref
if self.ref == "&"
hk += "r"
elsif self.ref == "&&"
hk += "rr"
end
end
if nmax > 0
args = nmax.times.collect { |ia| func.args[ia].anonymous_type.to_s }
sig = args.join(",")
hk += args.collect { |a| a.split(/\s+/) }.inspect.sum.to_s
hku = 0
hk_basic = hk
while ($unique[self.type.name + "-" + hk] ||= sig) != sig
hku += 1
hk = hk_basic + "u" + hku.to_s
end
else
hk += "0"
end
hk
end
def call_sig
func = self.type.func
nmax = func.max_args
args = nmax.times.collect { |ia| func.args[ia].anonymous_type.to_s }.join(", ")
res = "(" + args + ")"
if self.is_const?
res += " const"
end
if self.ref
res += " " + self.ref
end
res
end
def sig(cls)
self.type.name_substituted_type(CPPQualifiedId::new(false, [ cls, self.type.func.func_name ])).to_s
end
def raw_sig(cls)
# backward compatibility for signature computation
s = self.type.name_substituted_type(CPPQualifiedId::new(false, [ cls, self.type.func.func_name ])).to_s
if self.ref
s = s.sub(/\s+&+$/, "")
end
if self.is_const?
s = s.sub(/\s+const$/, "")
end
s
end
end
class CPPNamespace
def collect_enum_decls(map, &filter)
self.members.each do |bd|
if bd.is_a?(CPPEnumDeclaration)
bd.enum && filter.call(bd) && (map[bd.enum.name] ||= bd)
end
end
end
end
class CPPModule
def collect_enum_decls(map, &filter)
self.decls.each do |bd|
if bd.is_a?(CPPEnumDeclaration)
bd.enum && filter.call(bd) && (map[bd.enum.name] ||= bd)
end
end
end
end
class CPPEnum
def resolve_typedefs(scope)
end
def rescope(prev_scope, other_scope, idpath)
end
def each_qid(&block)
end
end
class CPPStruct
def resolve_typedefs(scope)
end
def rescope(prev_scope, other_scope, idpath)
end
def each_qid(&block)
end
def is_qobject?(conf)
if self.id.to_s == "QObject"
return true
end
self.each_base_class(conf) do |bc|
if bc.struct.is_qobject?(conf)
return true
end
end
return false
end
def needs_adaptor(conf)
cls = self.id.to_s
if conf.is_final_class?(cls)
return false
end
all_methods = {}
self.collect_all_methods(all_methods, conf)
# If there is a private destructor, we cannot create an adaptor
dtor = self.get_dtor
if dtor && dtor.visibility == :private
return false
end
# we generate an adaptor if there is a virtual destructor
# Note: an adaptor can only be generated if the class has a vtable since
# it will get a vtable through QtObjectBase and because of this a pure
# cast to the base class will fail (base class = without vtable, derived class
# = with vtable). But that cast is the basis of the void * representation of
# the objects.
needs_adaptor = dtor && dtor.virtual
all_methods.each do |mn,m|
m.each do |bd|
if bd.virtual && conf.target_name(cls, bd, mn)
needs_adaptor = true
elsif bd.virtual && bd.type.init == "0"
# an abstract method was dismissed -> cannot generate an adaptor
return false
end
end
end
# QObject will always require an adaptor to produce event emitters
return is_qobject?(conf) || needs_adaptor
end
def collect_used_enums(map, conf)
cls = self.id.to_s
# collect used enums from inner classes
(self.body_decl || []).each do |bd|
decl = nil
if bd.is_a?(CPPStructDeclaration) && bd.visibility == :public && bd.struct.body_decl && bd.myself != "" && !conf.is_class_dropped?(cls, bd.myself)
bd.struct.collect_used_enums(map, conf)
end
end
# collect used enums from base classes
(self.base_classes || []).each do |bc|
bc_obj = self.parent.resolve_qid(bc.class_id)
if bc_obj.is_a?(CPPStructDeclaration) && bc_obj.visibility == :public && bc_obj.struct.body_decl && bc_obj.myself != "" && !conf.is_class_dropped?(cls, bc_obj.myself)
bc_obj.struct.collect_used_enums(map, conf)
end
end
methods = {}
self.collect_all_methods(methods, conf)
methods[cls] = self.collect_ctors
needs_adaptor = self.needs_adaptor(conf)
methods.each do |mn,m|
m.each do |bd|
vis = bd.visibility
if vis == :public || (vis == :protected && needs_adaptor)
# don't consider dropped methods
conf.target_name(cls, bd, mn) || next
bd.type.each_qid do |qid|
obj = self.parent.resolve_qid(qid)
if obj.is_a?(CPPEnumDeclaration) && obj.visibility == :public
map[obj.object_id] = obj
end
end
end
end
end
end
def collect_enum_decls(map, &filter)
(self.body_decl || []).each do |bd|
if bd.is_a?(CPPEnumDeclaration) && bd.visibility == :public
bd.enum && filter.call(bd) && (map[bd.enum.name] ||= bd)
end
end
end
def collect_methods(map, weak = false)
mmap = {}
(self.body_decl || []).each do |bd|
decl = nil
if bd.is_a?(CPPDeclaration) && ! bd.template_decl
vis = bd.visibility
decl = bd
elsif bd.is_a?(CPPUsingSpec)
# resolve using specs
vis = bd.visibility
tbd = self.parent.resolve_qid(bd.qid)
if tbd && tbd.is_a?(CPPDeclaration) && ! tbd.template_decl
decl = tbd
end
end
if decl
func = decl.type.func
if func && decl.type.concrete != nil
mn = func.func_name
cls = self.id.to_s
decl_new = decl.dup
decl_new.visibility = vis
decl_new.type.resolve_typedefs(self.parent)
decl_new.type.rescope(self.parent, self.global_scope, false)
(mmap[mn] ||= []) << decl_new
end
end
end
# take non-duplicates (by call signature) for the map
# weak ones do not redefine methods
mmap.each do |mn,decls|
seen = {}
decls.each do |d|
s = d.call_sig
if !seen[s]
seen[s] = true
if !weak || !map[mn]
(map[mn] ||= []) << d
end
end
end
end
end
def get_dtor
(self.body_decl || []).each do |bd|
if bd.is_a?(CPPDeclaration)
func = bd.type.func
if func
mn = func.func_name
cls = self.id.to_s
if mn == "~" + cls
if ! bd.virtual
# inherit the virtual flag from the base classes
(self.base_classes || []).each do |bc|
bc_obj = self.parent.resolve_qid(bc.class_id)
if bc_obj.is_a?(CPPStructDeclaration)
bc_dtor = bc_obj.struct.get_dtor
if bc_dtor && bc_dtor.virtual
bd.virtual = true
return bd
end
end
end
end
return bd
end
end
end
end
nil
end
def collect_ctors
ctors = []
(self.body_decl || []).each do |bd|
if bd.is_a?(CPPDeclaration) && ! bd.template_decl
# only public ctors are considered - currently protected ones are not used
bd.visibility == :public || next
func = bd.type.func
if func
mn = func.func_name
cls = self.id.to_s
if mn == cls
bd_new = bd.dup
bd_new.type.resolve_typedefs(self.parent)
bd_new.type.rescope(self.parent, self.global_scope, false)
ctors << bd_new
end
end
end
end
ctors
end
def collect_all_methods(map, conf)
self.collect_methods(map)
(self.base_classes || []).select { |b| conf.imported?(self.id.to_s, b.class_id.to_s) }.each do |bc|
bc_obj = self.parent.resolve_qid(bc.class_id)
if bc_obj.is_a?(CPPStructDeclaration)
base_map = {}
bc_obj.struct.collect_all_methods(base_map, conf)
# derived classes override base class declarations
base_map.each do |mn, d|
# virtual is inherited to the derived class
map[mn] ||= d
d.each do |bd_base|
if bd_base.virtual
sig = bd_base.call_sig
map[mn].each do |bd|
bd.call_sig == sig && (bd.virtual = true)
end
end
end
end
elsif bc_obj
puts("Warning: #{bc.class_id.to_s} is not a base class in #{self.id.to_s}")
else
puts("Cannot find base class: #{bc.class_id.to_s} of #{self.parent.myself} - declaration ignored")
end
end
end
def each_base_class(conf, &block)
(self.base_classes || []).select { |b| conf.imported?(self.id.to_s, b.class_id.to_s) }.each do |bc|
bc_obj = self.parent.resolve_qid(bc.class_id)
if bc_obj.is_a?(CPPStructDeclaration)
block.call(bc_obj)
bc_obj.struct.each_base_class(conf, &block)
end
end
end
end
class CPPEllipsis
def resolve_typedefs(scope)
end
def rescope(prev_scope, other_scope, idpath)
end
def each_qid(&block)
end
end
class CPPPOD
def resolve_typedefs(scope)
end
def rescope(prev_scope, other_scope, idpath)
end
def each_qid(&block)
end
end
class CPPAnonymousId
def resolve_typedefs(scope)
end
def rescope(prev_scope, other_scope, idpath)
end
def each_qid(&block)
end
end
class CPPOuterType
def resolve_typedefs(scope)
self.inner.resolve_typedefs(scope)
end
def rescope(prev_scope, other_scope, idpath)
self.inner.rescope(prev_scope, other_scope, idpath)
end
def each_qid(&block)
self.inner.each_qid(&block)
end
end
class CPPFunc
def func_name
self.inner.is_a?(CPPQualifiedId) && self.inner.parts[-1].to_s
end
def min_args
n = 0
self.args.each do |a|
if a.is_a?(CPPType) && !a.init
n += 1
else
break
end
end
n
end
def max_args
n = 0
self.args.each do |a|
if a.is_a?(CPPType)
n += 1
else
break
end
end
n
end
def resolve_typedefs(scope)
self.args && self.args.each { |a| a.resolve_typedefs(scope) }
end
def rescope(prev_scope, other_scope, idpath)
self.inner && self.inner.rescope(prev_scope, other_scope, idpath)
self.args && self.args.each { |a| a.rescope(prev_scope, other_scope, false) }
end
def each_qid(&block)
self.inner && self.inner.each_qid(&block)
self.args && self.args.each { |a| a.each_qid(&block) }
end
end
class CPPConst
def resolve_typedefs(scope)
end
def rescope(prev_scope, other_scope, idpath)
end
def each_qid(&block)
end
end
class CPPTemplateArgs
def resolve_typedefs(scope)
self.args && self.args.each { |a| a.resolve_typedefs(scope) }
end
def rescope(prev_scope, other_scope, idpath)
self.args && self.args.each { |a| a.rescope(prev_scope, other_scope, false) }
end
def each_qid(&block)
self.args && self.args.each { |a| a.each_qid(&block) }
end
end
class CPPQualifiedId
def resolve_typedefs(scope)
self.parts.each do |p|
p.template_args && p.template_args.resolve_typedefs(scope)
end
end
def each_qid(&block)
block.call(self)
self.parts.each do |p|
p.template_args && p.template_args.each_qid(&block)
end
end
def rescope(prev_scope, other_scope, idpath)
# don't rescope identifiers
idpath && return
self.parts.each do |p|
p.template_args && p.template_args.rescope(prev_scope, other_scope, false)
end
if !self.global && prev_scope != other_scope
parents = {}
o = other_scope
while o
parents[o.parent] = true
o = o.parent
end
obj = prev_scope.resolve_qid(self)
if obj
self.parts = [ self.parts[-1] ]
s = obj.parent
while s && !parents[s]
s.myself && self.parts.unshift(CPPId::new(s.myself, nil))
s = s.parent
end
parents[s] || raise("Unable to rescope from #{obj.parent.myself || '::'} to #{other_scope.myself || '::'}")
end
end
end
end
class CPPType
def each_qid(&block)
self.inner && self.inner.each_qid(&block)
self.concrete && self.concrete.each_qid(&block)
end
# @brief Returns the CPPQualifiedId inside self.concrete or nil if there is none
def concrete_qid
it = self.concrete
while it.respond_to?(:inner)
it = it.inner
end
it.is_a?(CPPQualifiedId) && it
end
# @brief Replaces the CPPQualifiedId inside self.concrete
def concrete_qid=(qid)
it = self.concrete
if it.is_a?(CPPQualifiedId)
self.concrete = qid
else
itt = nil
while it.respond_to?(:inner)
itt = it
it = it.inner
end
itt && (itt.inner = qid)
end
end
def resolve_typedefs(scope)
while self.concrete_qid
qid = self.concrete_qid
# keep some typedefs since they provide some abstraction
if MaintainTypedefs.index(qid.to_s)
break
end
obj = scope.resolve_qid(qid)
if obj && obj.is_a?(CPPTypedef)
new_type = obj.type.dup
it = new_type
while it.respond_to?(:inner)
if it.inner.is_a?(CPPAnonymousId) || it.inner.is_a?(CPPQualifiedId)
it.inner = self.inner
break
else
it = it.inner
end
end
self.inner = new_type.inner
self.concrete_qid = new_type.concrete
self.rescope(obj.parent, scope, false)
else
# resolve template arguments
qid.resolve_typedefs(scope)
break
end
end
self.inner.resolve_typedefs(scope)
end
def rescope(prev_scope, other_scope, idpath)
self.inner && self.inner.rescope(prev_scope, other_scope, true)
self.concrete && self.concrete.rescope(prev_scope, other_scope, false)
end
def is_enum(decl_obj)
if self.concrete_qid
obj = decl_obj.resolve_qid(self.concrete_qid)
# replace protected enum's by unsigned int (TODO: is that necessary?)
return obj.is_a?(CPPEnumDeclaration) && obj.visibility == :protected
end
false
end
def is_special(decl_obj)
if self.concrete_qid
obj = decl_obj.resolve_qid(self.concrete_qid)
if obj.is_a?(CPPEnumDeclaration)
return true
elsif obj.is_a?(CPPTypedef) || obj.is_a?(CPPStructDeclaration)
name = obj.myself
return SpecialClasses.index(name) != nil
end
end
false
end
def access_qt_arg(decl_obj, expr = nil)
expr ||= self.name
expr || raise("access_qt called with nil expression")
if self.is_enum(decl_obj)
# enums currently use unsigned int for the target name
return "#{self.anonymous_type.to_s}(" + expr + ")"
elsif self.is_special(decl_obj)
ta = self.anonymous_type.to_s
if ta =~ /^const (.*) &&$/
ta = $1
acc = ".cmove()"
elsif ta =~ /^(.*) &&$/
ta = $1
acc = ".move()"
elsif ta =~ /^const (.*) &$/
ta = $1
acc = ".cref()"
elsif ta =~ /^(.*) &$/
ta = $1
acc = ".ref()"
elsif ta =~ /^const (.*) \*$/
ta = $1
acc = ".cptr()"
elsif ta =~ /^(.*) \*$/
ta = $1
acc = ".ptr()"
else
ta = ta.sub(/^const /, "")
acc = ".cref()"
end
ta = ta.sub(/>$/, "> ")
return "qt_gsi::QtToCppAdaptor<#{ta}>(" + expr + ")#{acc}"
else
return expr
end
end
def access_qt_return(decl_obj, expr = nil)
expr ||= self.name
expr || raise("access_qt called with nil expression")
if self.is_enum(decl_obj)
# enums currently use unsigned int for the target name
return "#{self.anonymous_type.to_s}(" + expr + ")"
elsif self.is_special(decl_obj)
ta = self.anonymous_type.to_s
acc = ""
racc = ""
# Handle references and pointers in returns as copies - we loose the ability to write,
# but can at least read them.
if ta =~ /^const (.*) &$/ || ta =~ /^(.*) &$/
ta = $1
elsif ta =~ /^const (.*) &&$/ || ta =~ /^(.*) &&$/
ta = $1
elsif ta =~ /^const (.*) \*$/ || ta =~ /^(.*) \*$/
ta = $1
acc = "*"
else
# TODO: this is not required but for backward compatibility - questionable!
racc = ".cref()"
end
ta = ta.sub(/^const /, "")
ta = ta.sub(/>$/, "> ")
return "qt_gsi::QtToCppAdaptor<#{ta}>(" + acc + expr + ")#{racc}"
else
return expr
end
end
def access_gsi_arg(decl_obj, expr = nil)
if self.is_enum(decl_obj)
# enums currently use unsigned int for the target name
return "(unsigned int)(" + (expr || self.name) + ")"
elsif self.is_special(decl_obj)
ta = self.anonymous_type.to_s
if ta =~ /^const (.*) &$/ || ta =~ /^(.*) &$/
acc = ""
ta = $1
elsif ta =~ /^const (.*) \*$/ || ta =~ /^(.*) \*$/
acc = "*"
ta = $1
else
ta = ta.sub(/^const /, "")
# TODO: should be, but not backward compatible: acc = ".cref()"
acc = ""
end
ta = ta.sub(/>$/, "> ")
if expr
# Used to derivce init values dynamically - need to store the value on the heap since the
# read adaptor's lifetime is very limited and references are kept.
return "qt_gsi::CppToQtReadAdaptor<#{ta}>(%HEAP%, " + acc + expr + ")"
else
return "qt_gsi::CppToQtAdaptor<#{ta}>(" + acc + self.name + ")"
end
else
return expr || self.name
end
end
def access_gsi_return(decl_obj, expr = nil)
expr ||= self.name
expr || raise("access_gsi called with nil expression")
if self.is_enum(decl_obj)
# enums currently use unsigned int for the target name
return "(unsigned int)(" + expr + ")"
elsif self.is_special(decl_obj)
ta = self.anonymous_type.to_s
acc = ""
# Handle references and pointers in returns as copies - we loose the ability to write,
# but can at least read them.
if ta =~ /^const (.*) &$/ || ta =~ /^(.*) &$/
ta = $1
elsif ta =~ /^const (.*) \*$/ || ta =~ /^(.*) \*$/
ta = $1
acc = "*"
end
ta = ta.sub(/^const /, "")
ta = ta.sub(/>$/, "> ")
return "qt_gsi::CppToQtAdaptor<#{ta}>(" + acc + expr + ")"
else
return expr
end
end
def gsi_decl_return(decl_obj)
if self.is_enum(decl_obj)
# enums currently use unsigned int for the target name
return "unsigned int" + (self.name ? " " + self.name : "")
elsif self.is_special(decl_obj)
ta = (self.name ? self.anonymous_type.to_s : self.to_s)
# return values of references or pointers are converted to plain copies - we cannot
# convert them on the fly - a temporary object would not be sufficient
if ta =~ /^const (.*) &$/ || ta =~ /^(.*) &$/ || ta =~ /^const (.*) \*$/ || ta =~ /^(.*) \*$/
ta = $1
end
ta = ta.sub(/^const /, "")
ta = ta.sub(/>$/, "> ")
return "qt_gsi::Converter<#{ta}>::target_type" + (self.name ? " " + self.name : "")
else
return self.to_s
end
end
def gsi_decl_arg(decl_obj)
if self.is_enum(decl_obj)
# enums currently use unsigned int for the target name
return "unsigned int" + (self.name ? " " + self.name : "")
elsif self.is_special(decl_obj)
ta = (self.name ? self.anonymous_type.to_s : self.to_s)
if ta =~ /^const (.*) &$/
ta = $1
const = "const "
acc = " &"
elsif ta =~ /^(.*) &$/
ta = $1
const = ""
acc = " &"
elsif ta =~ /^const (.*) \*$/
ta = $1
const = "const "
acc = " *"
elsif ta =~ /^(.*) \*$/
ta = $1
const = ""
acc = " *"
else
ta = ta.sub(/^const /, "")
acc = " &"
const = "const "
end
ta = ta.sub(/>$/, "> ")
return "#{const}qt_gsi::Converter<#{ta}>::target_type#{acc}" + (self.name ? " " + self.name : "")
else
return self.to_s
end
end
end
class Configurator
def initialize
@include = {}
@no_copy_ctor = {}
@no_default_ctor = {}
@final_classes = {}
@dropped_methods = {}
@dropped_classes = {}
@dropped_enums = {}
@included_enums = {}
@renamed_methods = {}
@kept_args = {}
@owner_args = {}
@return_new = {}
@aliased_methods = {}
@dropped_enum_consts = {}
@renamed_enum_consts = {}
@aliased_enum_consts = {}
@events = {}
@property_readers = {}
@property_writers = {}
@no_imports = {}
@native_impl = {}
end
def include(cls, files)
@include[cls] ||= []
@include[cls] += files
end
def no_copy_ctor(cls)
@no_copy_ctor[cls] = true
end
def has_copy_ctor?(cls)
!@no_copy_ctor[cls]
end
def no_default_ctor(cls)
@no_default_ctor[cls] = true
end
def has_default_ctor?(cls)
!@no_default_ctor[cls]
end
def final_class(cls)
@final_classes[cls] = true
end
def is_final_class?(cls)
@final_classes[cls]
end
def includes(cls)
@include[cls]
end
def no_imports(cls, base = :every_base)
@no_imports[cls] ||= {}
@no_imports[cls][base] = true
end
def imported?(cls, base)
!(@no_imports[cls] && (@no_imports[cls][:every_base] || @no_imports[cls][base]))
end
def drop_enum_const(cls, sig)
@dropped_enum_consts[cls] ||= []
@dropped_enum_consts[cls] << sig
end
def rename_enum_const(cls, sig, to)
@renamed_enum_consts[cls] ||= []
@renamed_enum_consts[cls] << [ sig, to ]
end
def def_enum_const_alias(cls, sig, to)
@aliased_enum_consts[cls] ||= []
@aliased_enum_consts[cls] << [ sig, to ]
end
def is_enum_const_dropped?(cls, sig)
dm = (@dropped_enum_consts[:all_classes] || []) + (@dropped_enum_consts[cls] || [])
dm.find { |d| sig =~ d } != nil
end
def drop_class(cls, inner_cls = :whole_class)
@dropped_classes[cls] ||= []
@dropped_classes[cls] << inner_cls
end
def drop_method(cls, sig)
@dropped_methods[cls] ||= []
@dropped_methods[cls] << sig
end
def return_new(cls, sig)
@return_new[cls] ||= []
@return_new[cls] << sig
end
def owner_arg(cls, sig, nth)
@owner_args[cls] ||= []
@owner_args[cls] << [ sig, nth ]
end
def keep_arg(cls, sig, nth)
@kept_args[cls] ||= []
@kept_args[cls] << [ sig, nth ]
end
def rename(cls, sig, to)
@renamed_methods[cls] ||= []
@renamed_methods[cls] << [ sig, to ]
end
def def_alias(cls, sig, to)
@aliased_methods[cls] ||= []
@aliased_methods[cls] << [ sig, to ]
end
def is_dropped?(cls, sig)
dm = (@dropped_methods[:all_classes] || []) + (@dropped_methods[cls] || [])
dm.find { |d| sig =~ d } != nil
end
def is_class_dropped?(cls, sig = :whole_class)
if cls != :all_classes && cls !~ /^Q\w+$/
# don't consider classes which are not plain Qt classes (i.e. templates, internal ones etc.)
return true
elsif cls != :all_classes && cls =~ /^QPrivateSignal$/
# drop QPrivateSignal because that's just a marker
return true
else
dc = (@dropped_classes[:all_classes] || []) + (@dropped_classes[cls] || [])
if sig != :whole_class
return dc.find { |d| d == :whole_class || sig =~ d } != nil
else
return dc.find { |d| d == :whole_class || sig == d } != nil
end
end
end
# @brief Produce an enum target name
def target_name_for_enum_const(cls, sig, mid, mm = nil, decl = nil)
if is_enum_const_dropped?(cls, sig)
return nil
else
name = mid
rn = (@renamed_enum_consts[:all_classes] || []) + (@renamed_enum_consts[cls] || [])
rt = rn.find { |d| sig =~ d[0] }
if rt
name = rt[1]
end
al = (@aliased_enum_consts[:all_classes] || []) + (@aliased_enum_consts[cls] || [])
at = al.select { |d| sig =~ d[0] }
if !at.empty?
name += "|" + at.collect { |a| a[1] }.join("|")
end
name
end
end
# @brief Gets a list of owner arguments which will keep the object is non-null
def owner_args(bd)
cls = bd.parent.myself || "::"
kn = (@owner_args[:all_classes] || []) + (@owner_args[cls] || [])
if !kn.empty?
sig = bd.sig(bd.parent.myself || "")
return kn.select { |d| sig =~ d[0] }.collect { |d| d[1] }
else
return []
end
end
# @brief Gets a list of arguments which need to be kept
def kept_args(bd)
cls = bd.parent.myself || "::"
kn = (@kept_args[:all_classes] || []) + (@kept_args[cls] || [])
if !kn.empty?
sig = bd.sig(bd.parent.myself || "")
return kn.select { |d| sig =~ d[0] }.collect { |d| d[1] }
else
return []
end
end
# @brief Gets a value indicating whether a method returns a new object
# The lifetime of an object returned by such a method is managed by the
# script client
def returns_new(bd)
cls = bd.parent.myself || "::"
rn = (@return_new[:all_classes] || []) + (@return_new[cls] || [])
if !rn.empty?
sig = bd.sig(bd.parent.myself || "")
return rn.find { |d| sig =~ d } != nil
else
return false
end
end
# @brief Produce the target name for a method
# Produces the target name string or nil if the method is supposed
# to be dropped.
def target_name(cls, bd, mid, mm = nil, decl = nil)
sig = bd.sig(cls)
# we also test for the parent of bd which may be different from cls if
# the method is imported from a base class or through "using"
cls2 = bd.parent.myself || ""
sig2 = bd.sig(cls2)
# the drop test includes the static attribute so we can distinguish between
# static and non-static methods
if bd.storage_class == :static
sig = "static " + sig
sig2 = "static " + sig2
end
if is_dropped?(cls, sig) || is_dropped?(cls2, sig2)
return nil
end
# strip operator
name = mid.sub(/^operator\s*/, "")
# replace assignment operator
if name == "="
# drop assignment if the class does not have a copy ctor ("no copy semantics")
# (required because otherwise operator= is exposed for adaptor classes)
if !has_copy_ctor?(cls)
return nil
end
name = "assign"
end
rn = (@renamed_methods[:all_classes] || []) + (@renamed_methods[cls] || []) + (@renamed_methods[cls2] || [])
rt = rn.find { |d| sig =~ d[0] }
if rt
name = rt[1]
end
al = (@aliased_methods[:all_classes] || []) + (@aliased_methods[cls] || []) + (@aliased_methods[cls2] || [])
at = al.select { |d| sig =~ d[0] }
if !at.empty?
name += "|" + at.collect { |a| a[1] }.uniq.join("|")
elsif mm && decl && decl.type.func
rn = property_reader_name(cls, sig) || property_reader_name(cls2, sig2)
wn = property_writer_name(cls, sig) || property_writer_name(cls2, sig2)
if rn && (decl.type.func.args || []).size == 0
# property readers become
# isX -> isX?|:x
# x -> :x
# the colon hints that this has to be a getter (for Python)
if name =~ /^is([A-Z])(.*)/
name += "?"
end
if name == rn
name = ":" + rn
else
name += "|:" + rn
end
elsif wn && (decl.type.func.args || []).size == 1
# property writers become setX -> setX|x=
if name == wn
name = wn + "="
else
name += "|" + wn + "="
end
elsif name =~ /^is([A-Z])(.*)/
# implicit alias: isX -> isX?
name += "?"
end
end
name
end
# @brief Returns a string for building identifers from a method id
def mid2str(mid)
mid.gsub(/\(\)/, "_func_").
gsub(/\[\]/, "_index_").
gsub(/\//, "_slash_").
gsub(/\*/, "_star_").
gsub(/\^/, "_acute_").
gsub(/=/, "_eq_").
gsub(/-/, "_minus_").
gsub(/~/, "_tilde_").
gsub(/\+/, "_plus_").
gsub(/</, "_lt_").
gsub(/>/, "_gt_").
gsub(/!/, "_excl_").
gsub(/&/, "_amp_").
gsub(/\|/, "_pipe_").
gsub(/\s+/, "")
end
def include_enum(cls, sig)
@included_enums[cls] ||= []
@included_enums[cls] << sig
end
def is_enum_included?(cls, sig)
dm = (@included_enums[:all_classes] || []) + (@included_enums[cls] || [])
dm.find { |d| sig =~ d } != nil
end
def drop_enum(cls, sig)
@dropped_enums[cls] ||= []
@dropped_enums[cls] << sig
end
def is_enum_dropped?(cls, sig)
dm = (@dropped_enums[:all_classes] || []) + (@dropped_enums[cls] || [])
dm.find { |d| sig =~ d } != nil
end
def add_native_impl(cls, code, decl)
@native_impl[cls] ||= []
@native_impl[cls] << [ code, decl ]
end
def native_impl(cls)
@native_impl[cls]
end
def property_writer(cls, sig, name)
@property_writers[cls] ||= []
@property_writers[cls] << [ sig, name ]
end
def property_writer_name(cls, sig)
pm = (@property_writers[:all_classes] || []) + (@property_writers[cls] || [])
p = pm.find { |d| sig =~ d[0] }
p && p[1]
end
def property_reader(cls, sig, name)
@property_readers[cls] ||= []
@property_readers[cls] << [ sig, name ]
end
def property_reader_name(cls, sig)
pm = (@property_readers[:all_classes] || []) + (@property_readers[cls] || [])
p = pm.find { |d| sig =~ d[0] }
p && p[1]
end
def event(cls, sig, args)
@events[cls] ||= []
@events[cls] << [ sig, args ]
end
# @brief Returns the event arguments or nil if the method is not an event
def event_args(cls, sig)
dm = (@events[:all_classes] || []) + (@events[cls] || [])
e = dm.find { |d| sig =~ d[0] }
e && e[1]
end
def load(filename)
# with Ruby 1.9
# found = ([ File.dirname($0) ] + $:).each do |d|
# ap = File.absolute_path(filename, d)
# File.readable?(ap) && ap
# end
# found = found.select { |f| f }
# found.is_empty? && raise("Cannot load file #{filename} - file not found or not readable")
# File.open(found[0], "r") do |file|
# self.instance_eval(file.read, found[0])
# end
# with Ruby 1.8
File.open(filename, "r") do |file|
self.instance_eval(file.read, filename)
end
end
end
class BindingProducer
attr_accessor :modn, :root
# @brief Read the input file (JSON)
#
# This method will set up the binding producer for generating
# the class declarations
def read(input_file)
@source_files = nil
@classes = nil
@ext_decls = []
File.open(input_file, "r") do |file|
json = file.read
@root = Oj.load(json)
puts "Reading done."
# replace default visibility by the real one
@root.set_visibility
# collect the children and create the lookup tables. Also establish
# the parent relationships
@root.set_parent
# move cross-scoped items such as "class A::B { ... };" to the right place
@root.inject_scoped
# and regenerate the lookup tables plus new parent relationships
@root.set_parent
@used_enums = {}
end
end
# @brief Gets a list of objects to produce in the root namespace
def prod_list(conf)
pl = []
@root.ids.each do |cls|
# only take plain (non-template) classes which are not dropped
if !conf.is_class_dropped?(cls)
pl << @root.id2obj(cls)
end
end
return pl
end
# @brief Collects the used enum declarations
def collect_used_enums(conf, decl_obj)
if decl_obj.is_a?(CPPStructDeclaration)
decl_obj.struct.collect_used_enums(@used_enums, conf)
end
end
# @brief Substitute words from the context of a declaration
def rescope_expr(expr, decl_obj)
expr.gsub(/(::)?\w+/) do |s|
if s[0] == ":"
s
else
res = decl_obj.resolve_qid(CPPQualifiedId::new(false, [ CPPId::new(s, nil) ]))
(res && res.parent && res.parent.myself) ? (res.parent.myself + "::" + s) : s
end
end
end
# @brief Produce the main declaration file for the given cls inside the given module
def produce_cpp(conf, cls)
qid = CPPQualifiedId::new(false, [ CPPId::new(cls, nil) ])
decl_obj = @root.resolve_qid(qid)
decl_obj || raise("Class not found: #{cls}")
produce_cpp_from_decl(conf, decl_obj)
end
def produce_cpp_from_decl(conf, decl_obj)
if !decl_obj.is_a?(CPPStructDeclaration) && !decl_obj.is_a?(CPPNamespace) && !decl_obj.is_a?(CPPEnumDeclaration)
return
end
index = 0
cont = true
while cont
(cls, clsn) = make_cls_names(decl_obj)
@classes ||= []
@classes << clsn
if index > 0
ofile_name = "gsiDecl#{clsn}_#{index}.cc"
else
ofile_name = "gsiDecl#{clsn}.cc"
end
ofile_path = $gen_dir + "/" + ofile_name
File.open(ofile_path, "w") do |ofile|
@source_files ||= []
@source_files << ofile_name
ofile.puts(<<"END");
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file #{ofile_name}
*
* DO NOT EDIT THIS FILE.
* This file has been created automatically
*/
END
if decl_obj.is_a?(CPPStructDeclaration)
cont = produce_class(conf, decl_obj, ofile, index)
elsif decl_obj.is_a?(CPPNamespace)
cont = produce_namespace(conf, decl_obj, ofile, index)
elsif decl_obj.is_a?(CPPEnumDeclaration)
cont = produce_enum(conf, decl_obj, ofile, index)
end
puts("#{ofile_name} written.")
index += 1
end
end
end
def is_enum_used?(bd)
@used_enums[bd.object_id]
end
def make_child_cls_name(s)
s[0].upcase + s[1..-1]
end
def make_cls_names(decl_obj)
cls = decl_obj.myself
clsn = make_child_cls_name(decl_obj.myself)
o = decl_obj
while o.parent && o.parent.myself
o = o.parent
cls = o.myself + "::" + cls
clsn = make_child_cls_name(o.myself) + "_" + clsn
end
return [ cls, clsn ]
end
def make_cls_name(cls)
return cls.split("::").collect { |s| make_child_cls_name(s) }.join("_")
end
def produce_class_include(conf, decl_obj, ofile)
( cls, clsn ) = make_cls_names(decl_obj)
if !conf.includes(cls)
if cls =~ /^(.*?)::/
ofile.puts("#include <#{$1}>")
else
ofile.puts("#include <#{cls}>")
end
# derive dependencies where possible
if decl_obj.is_a?(CPPStructDeclaration)
struct = decl_obj.struct
methods = {}
struct.collect_all_methods(methods, conf)
methods[cls] = struct.collect_ctors
needs_adaptor = struct.needs_adaptor(conf)
used_classes = {}
methods.each do |mn,m|
m.each do |bd|
vis = bd.visibility
is_signal = conf.event_args(cls, bd.sig(cls))
if vis == :public || (vis == :protected && needs_adaptor) || is_signal
# don't consider dropped methods
conf.target_name(cls, bd, mn) || next
bd.type.each_qid do |qid|
obj = decl_obj.parent.resolve_qid(qid)
if obj.is_a?(CPPStructDeclaration)
used_classes[obj.object_id] = obj
end
end
end
end
end
used_classes.values.map { |uc| uc.myself || uc.myself_weak }.select { |uc| !conf.is_class_dropped?(uc) && uc != cls }.sort.each do |uc|
ofile.puts("#include <#{uc}>")
end
end
else
conf.includes(cls).each do |f|
ofile.puts("#include #{f}")
end
end
ofile.puts("#include \"gsiQt.h\"")
ofile.puts("#include \"gsi#{modn}Common.h\"")
end
def produce_enum(conf, decl_obj, ofile, index)
( cls, clsn ) = make_cls_names(decl_obj)
produce_class_include(conf, decl_obj, ofile)
ofile.puts("#include <memory>")
ofile.puts("")
ofile.puts("// -----------------------------------------------------------------------")
ofile.puts("// enum #{cls}")
ofile.puts("")
# emit enum wrapper classes (top level, hence container class is nil)
produce_enum_wrapper_class(ofile, conf, nil, cls, decl_obj)
return false
end
def produce_namespace(conf, decl_obj, ofile, index)
( cls, clsn ) = make_cls_names(decl_obj)
enum_decls_by_name = {}
decl_obj.collect_enum_decls(enum_decls_by_name) { true }
produce_class_include(conf, decl_obj, ofile)
ofile.puts("#include <memory>")
ofile.puts("")
ofile.puts("// -----------------------------------------------------------------------")
ofile.puts("// namespace #{cls}")
ofile.puts("")
ofile.puts("class #{cls}_Namespace { };")
ofile.puts("")
if index == 0
ofile.puts("namespace gsi")
ofile.puts("{")
ofile.puts("gsi::Class<#{cls}_Namespace> decl_#{cls}_Namespace (\"#{modn}\", \"#{clsn}\",")
ofile.puts(" gsi::Methods(),")
ofile.puts(" \"@qt\\n@brief This class represents the #{cls} namespace\");")
ofile.puts("}")
ofile.puts("")
end
# emit enum wrapper classes
enums_per_file = 20
en_names = enum_decls_by_name.keys.sort
en_names.each_with_index do |en,en_index|
if en_index < (index + 1) * enums_per_file && en_index >= index * enums_per_file
ed = enum_decls_by_name[en]
produce_enum_wrapper_class(ofile, conf, cls, en, ed)
end
end
# produce more files if required
return en_names.size > (index + 1) * enums_per_file
end
def produce_enum_wrapper_class(ofile, conf, cls, en, ed)
clsn = cls && make_cls_name(cls)
# emit enum wrapper classes
ofile.puts("")
ofile.puts("// Implementation of the enum wrapper class for #{cls}::#{en}")
ofile.puts("namespace qt_gsi")
ofile.puts("{")
ofile.puts("")
if cls
ofile.puts("static gsi::Enum<#{cls}::#{en}> decl_#{clsn}_#{en}_Enum (\"#{modn}\", \"#{clsn}_#{en}\",")
else
ofile.puts("static gsi::Enum<#{en}> decl_#{en}_Enum (\"#{modn}\", \"#{en}\",")
end
edecl = []
ec = ed.enum.specs.collect { |s| s.name }
ec.each_with_index do |ei,i|
ei_name = conf.target_name_for_enum_const(cls ? cls : "::", "#{en}::#{ei}", ei)
if ! ei_name
# enum dropped
next
end
if cls
if ed.enum.is_class
edecl << " gsi::enum_const (\"#{ei_name}\", #{cls}::#{en}::#{ei}, \"@brief Enum constant #{cls}::#{en}::#{ei}\")"
else
edecl << " gsi::enum_const (\"#{ei_name}\", #{cls}::#{ei}, \"@brief Enum constant #{cls}::#{ei}\")"
end
else
if ed.enum.is_class
edecl << " gsi::enum_const (\"#{ei_name}\", #{en}::#{ei}, \"@brief Enum constant #{en}::#{ei}\")"
else
edecl << " gsi::enum_const (\"#{ei_name}\", #{ei}, \"@brief Enum constant #{ei}\")"
end
end
end
ofile.puts(" " + edecl.join(" +\n ") + ",\n")
if cls
ofile.puts(" \"@qt\\n@brief This class represents the #{cls}::#{en} enum\");")
else
ofile.puts(" \"@qt\\n@brief This class represents the #{en} enum\");")
end
ofile.puts("")
if cls
ofile.puts("static gsi::QFlagsClass<#{cls}::#{en} > decl_#{clsn}_#{en}_Enums (\"#{modn}\", \"#{clsn}_QFlags_#{en}\",")
ofile.puts(" \"@qt\\n@brief This class represents the QFlags<#{cls}::#{en}> flag set\");")
ofile.puts("")
else
ofile.puts("static gsi::QFlagsClass<#{en} > decl_#{en}_Enums (\"#{modn}\", \"QFlags_#{en}\",")
ofile.puts(" \"@qt\\n@brief This class represents the QFlags<#{en}> flag set\");")
ofile.puts("")
end
if cls
# inject the declarations into the parent namespace or class
pdecl_obj = ed.parent
pcls = pdecl_obj.myself
o = pdecl_obj
while o.parent && o.parent.myself
o = o.parent
pcls = o.myself + "::" + pcls
end
pname = pcls
if pdecl_obj.is_a?(CPPNamespace)
pname = pcls + "_Namespace"
end
if ! ed.enum.is_class
ofile.puts("// Inject the declarations into the parent")
ofile.puts("static gsi::ClassExt<#{pname}> inject_#{clsn}_#{en}_Enum_in_parent (decl_#{clsn}_#{en}_Enum.defs ());")
end
ofile.puts("static gsi::ClassExt<#{pname}> decl_#{clsn}_#{en}_Enum_as_child (decl_#{clsn}_#{en}_Enum, \"#{en}\");")
ofile.puts("static gsi::ClassExt<#{pname}> decl_#{clsn}_#{en}_Enums_as_child (decl_#{clsn}_#{en}_Enums, \"QFlags_#{en}\");")
ofile.puts("")
end
ofile.puts("}")
ofile.puts("")
end
def produce_keep_self(ofile, alist, obj, owner_args = [])
owner_args.each do |a|
ofile.puts(" if (#{alist[a]}) {");
ofile.puts(" qt_gsi::qt_keep (#{obj});")
ofile.puts(" } else {");
ofile.puts(" qt_gsi::qt_release (#{obj});")
ofile.puts(" }");
end
end
def produce_arg_read(ofile, decl_obj, func, alist, kept_args = [])
n_args = func.max_args
n_min_args = func.min_args
has_heap = false
n_args.times do |ia|
t = func.args[ia]
argname = alist[ia]
at = t.anonymous_type
ta = at.gsi_decl_arg(decl_obj)
nt = t.renamed_type(argname)
tn = nt.gsi_decl_arg(decl_obj)
if !has_heap
ofile.puts(" tl::Heap heap;")
has_heap = true
end
if ia < n_min_args || !t.init
ofile.puts(" #{tn} = gsi::arg_reader<#{ta} >() (args, heap);")
else
init = rescope_expr(t.init.to_s, decl_obj)
init_expr = at.access_gsi_arg(decl_obj, init)
if init_expr =~ /%HEAP%/
init_expr = init_expr.gsub("%HEAP%", "heap")
end
ofile.puts(" #{tn} = args ? gsi::arg_reader<#{ta} >() (args, heap) : gsi::arg_maker<#{ta} >() (#{init_expr}, heap);")
end
if kept_args.index(ia)
ofile.puts(" qt_gsi::qt_keep (#{argname});")
end
end
end
def produce_arg_init(ofile, decl_obj, func)
n_min_args = func.min_args
func.max_args.times do |ia|
t = func.args[ia]
argname = t.name || "arg#{ia + 1}"
at = t.anonymous_type
ta = at.gsi_decl_arg(decl_obj);
if ia < n_min_args || !t.init
ofile.puts(" static gsi::ArgSpecBase argspec_#{ia} (\"#{argname}\");")
else
init = rescope_expr(t.init.to_s, decl_obj)
istr = init.gsub(/"/, "\\\"")
ofile.puts(" static gsi::ArgSpecBase argspec_#{ia} (\"#{argname}\", true, \"#{istr}\");")
end
ofile.puts(" decl->add_arg<#{ta} > (argspec_#{ia});")
end
end
def produce_class(conf, decl_obj, ofile, index)
if index > 0
raise "Internal error: no splitting of class definitions yet."
end
struct = decl_obj.struct
(cls, clsn) = make_cls_names(decl_obj)
cclsn = make_child_cls_name(decl_obj.myself)
# produce public subclasses
(struct.body_decl || []).each do |bd|
if bd.is_a?(CPPStructDeclaration) && bd.visibility == :public && bd.struct.body_decl && bd.myself != "" && !conf.is_class_dropped?(cls, bd.myself)
produce_cpp_from_decl(conf, bd)
end
end
base_classes = (struct.base_classes || []).select { |b| conf.imported?(cls, b.class_id.to_s) }
base_cls = base_classes[0] && base_classes[0].class_id.to_s
base_clsn = base_cls && make_cls_name(base_cls)
# as we only support single base classes (a tribute to Ruby), we treat all other base classes as
# mixins
mixin_base_classes = base_classes[1..] || []
methods_by_name = {}
all_methods_by_name = {}
enum_decls_by_name = {}
bc_methods_by_name = {}
base_classes.each do |bc|
bc_decl_obj = decl_obj.resolve_qid(bc.class_id)
bc_decl_obj && bc_decl_obj.respond_to?(:struct) && bc_decl_obj.struct.collect_all_methods(bc_methods_by_name, conf)
end
mmap = {}
struct.collect_all_methods(all_methods_by_name, conf)
struct.collect_methods(methods_by_name)
struct.collect_enum_decls(enum_decls_by_name) { |bd| self.is_enum_used?(bd) || conf.is_enum_included?(cls, bd.myself) }
# if one method is abstract, omit ctors for example
is_abstract = all_methods_by_name.values.find do |m|
m.find { |bd| bd.virtual && bd.type.init == "0" } != nil
end
# gets the operator= if there is one
eq_op = (all_methods_by_name["operator="] || [])[0]
# collect used enums in order to generate forward converter declarations
# ("used" implies argument types and defined enums)
used_ed = {}
enum_decls_by_name.each do |en,ed|
used_ed[ed.object_id] = ed
end
struct.collect_used_enums(used_ed, conf)
used_enums_by_name = {}
used_ed.each do |id,ed|
id = CPPQualifiedId::new(false, [ CPPId::new(ed.enum.name, nil) ])
id.rescope(ed.parent, ed.global_scope, false)
used_enums_by_name[id.to_s] = ed
end
# needs_adaptor is true if there is any virtual method which can potentially
# be reimplemented in script
needs_adaptor = struct.needs_adaptor(conf)
# is_qobject is true, if the class is derived from QObject
is_qobject = struct.is_qobject?(conf)
# provide constructors only if we really can create an object
# (we can't, if the class is abstract and there is no adaptor)
if !is_abstract || needs_adaptor
# collect ctors
ctors = struct.collect_ctors
# create a default ctor if there is no ctor at all and we can have one
if ctors.empty? && conf.has_default_ctor?(cls)
func = CPPFunc::new(CPPQualifiedId::new(false, [ CPPId::new(decl_obj.myself, nil) ]), [], nil, nil)
type = CPPType::new(nil, func, nil)
def_ctor = CPPDeclaration::new(type, nil, :public, nil, false, false)
def_ctor.parent = decl_obj
ctors << def_ctor
end
else
ctors = []
end
global_operators = []
# collect global operators with the given class as the first argument
@root.decls.each do |bd|
if bd.is_a?(CPPDeclaration) && bd.type.func && bd.type.name =~ /^operator/
op_func = bd.type.func
if op_func.args.size >= 1
a1 = op_func.args[0].anonymous_type.to_s
if a1 =~ /^(const\s+)?#{cls}(\s*&)?$/
global_operators << bd
end
end
end
end
native_impl = conf.native_impl(cls)
has_metaobject = ((struct.body_decl || []).find { |bd| bd.is_a?(CPPDeclaration) && bd.type.name == "metaObject" } != nil)
has_metaobject = has_metaobject && !conf.is_dropped?(cls, cls + "::staticMetaObject")
mdecl = []
mdecl_ctors = []
produce_class_include(conf, decl_obj, ofile)
ofile.puts("#include <memory>")
ofile.puts("")
ofile.puts("// -----------------------------------------------------------------------")
ofile.puts("// #{struct.kind.to_s} #{cls}")
if has_metaobject
ofile.puts("")
ofile.puts("// get static meta object")
ofile.puts("")
ofile.puts("static void _init_smo (qt_gsi::GenericStaticMethod *decl)")
ofile.puts("{")
ofile.puts(" decl->set_return<const QMetaObject &> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_smo (const qt_gsi::GenericStaticMethod *, gsi::SerialArgs &, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" ret.write<const QMetaObject &> (#{cls}::staticMetaObject);")
ofile.puts("}")
ofile.puts("")
mdecl << "new qt_gsi::GenericStaticMethod (\"staticMetaObject\", \"@brief Obtains the static MetaObject for this class.\", &_init_smo, &_call_smo);"
end
native_impl && native_impl.each { |n| n[0] && ofile.puts(n[0]) }
if ! needs_adaptor
# expose ctors here (with virtual functions, the adaptor will expose the ctors)
ctors.each do |bd|
bd.visibility == :public || next
func = bd.type.func
hk = bd.hash_str
sig = bd.sig(cls)
rsig = bd.raw_sig(cls)
mn = decl_obj.myself # ctor!
mn_name = conf.target_name(cls, bd, mn)
if ! mn_name
# method dropped
next
elsif mn_name == mn
mn_name = "new"
end
n_args = func.max_args
n_min_args = func.min_args
ant = n_args.times.collect { |ia| func.args[ia].anonymous_type }
alist = n_args.times.collect { |ia| "arg#{ia + 1}" }
qt_alist = n_args.times.collect { |ia| func.args[ia].renamed_type(alist[ia]).access_qt_arg(decl_obj) }
ofile.puts("")
ofile.puts("// Constructor #{rsig}")
ofile.puts("")
ofile.puts("")
ofile.puts("static void _init_ctor_#{clsn}_#{hk} (qt_gsi::GenericStaticMethod *decl)")
ofile.puts("{")
produce_arg_init(ofile, decl_obj, func)
ofile.puts(" decl->set_return_new<#{cls}> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_ctor_#{clsn}_#{hk} (const qt_gsi::GenericStaticMethod * /*decl*/, gsi::SerialArgs &args, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" __SUPPRESS_UNUSED_WARNING(args);")
produce_arg_read(ofile, decl_obj, func, alist, conf.kept_args(bd))
if conf.owner_args(bd).size > 0
ofile.puts(" #{cls} *obj = new #{cls} (#{qt_alist.join(', ')});")
conf.owner_args(bd).each do |a|
ofile.puts(" if (#{alist[a]}) {");
ofile.puts(" qt_gsi::qt_keep (obj);")
ofile.puts(" }");
end
ofile.puts(" ret.write<#{cls} *> (obj);")
else
ofile.puts(" ret.write<#{cls} *> (new #{cls} (#{qt_alist.join(', ')}));")
end
ofile.puts("}")
ofile.puts("")
mdecl_ctors << "new qt_gsi::GenericStaticMethod (\"#{mn_name}\", \"@brief Constructor #{rsig}\\nThis method creates an object of class #{cls}.\", &_init_ctor_#{clsn}_#{hk}, &_call_ctor_#{clsn}_#{hk});"
end
end
# produce public, non-static methods
methods_by_name.keys.sort.each do |mid|
methods_by_name[mid].each do |bd|
if bd.visibility != :public || bd.storage_class == :static
next
end
mn = conf.mid2str(mid)
mn_name = conf.target_name(cls, bd, mid, all_methods_by_name, bd)
if ! mn_name
# method dropped
next
end
func = bd.type.func
const = bd.is_const?
hk = bd.hash_str
sig = bd.sig(cls)
rsig = bd.raw_sig(cls)
if conf.event_args(cls, sig) && bd.type.return_type.is_void?
# don't produce bindings for signals (which are public in Qt5)
next
end
is_reimp = nil
if bc_methods_by_name[mid]
call_sig = bd.call_sig
bc_methods_by_name[mid].each do |bd_base|
if bd_base.call_sig == call_sig
bd_base.virtual && (is_reimp = bd_base)
break
end
end
end
rt = bd.type.return_type
n_args = func.max_args
n_min_args = func.min_args
ant = n_args.times.collect { |ia| func.args[ia].anonymous_type }
alist = n_args.times.collect { |ia| "arg#{ia + 1}" }
qt_alist = n_args.times.collect { |ia| func.args[ia].renamed_type(alist[ia]).access_qt_arg(decl_obj) }
ofile.puts("")
ofile.puts("// #{rsig}")
ofile.puts("")
ofile.puts("")
ofile.puts("static void _init_f_#{mn}_#{hk} (qt_gsi::GenericMethod *decl)")
ofile.puts("{")
produce_arg_init(ofile, decl_obj, func)
rpf = (conf.returns_new(bd) ? "_new" : "")
ofile.puts(" decl->set_return#{rpf}<#{rt.gsi_decl_return(decl_obj)} > ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_f_#{mn}_#{hk} (const qt_gsi::GenericMethod * /*decl*/, void *cls, gsi::SerialArgs &args, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" __SUPPRESS_UNUSED_WARNING(args);")
produce_arg_read(ofile, decl_obj, func, alist, conf.kept_args(bd))
produce_keep_self(ofile, alist, "(#{cls} *)cls", conf.owner_args(bd))
if !rt.is_void?
ofile.puts(" ret.write<#{rt.gsi_decl_return(decl_obj)} > ((#{rt.gsi_decl_return(decl_obj)})" + rt.access_gsi_return(decl_obj, "((#{cls} *)cls)->#{mid} (#{qt_alist.join(', ')})") + ");")
else
ofile.puts(" __SUPPRESS_UNUSED_WARNING(ret);")
ofile.puts(" ((#{cls} *)cls)->#{mid} (#{qt_alist.join(', ')});")
end
ofile.puts("}")
ofile.puts("")
mdecl << "new qt_gsi::GenericMethod (\"#{mn_name}\", \"@brief Method #{rsig}\\n" + (is_reimp ? "This is a reimplementation of #{is_reimp.parent.myself}::#{mid}" : "") + "\", #{const.to_s}, &_init_f_#{mn}_#{hk}, &_call_f_#{mn}_#{hk});"
end
end
# handle events
if is_qobject
all_methods_by_name.keys.sort.each do |mid|
all_methods_by_name[mid].each do |bd|
if bd.visibility == :private || bd.storage_class == :static || !bd.type.return_type.is_void?
next
end
mn = conf.mid2str(mid)
mn_name = conf.target_name(cls, bd, mid, all_methods_by_name, bd)
if ! mn_name
# method dropped
next
end
if conf.event_args(cls, bd.sig(cls))
# strip QPrivateSignal argument if present
bd_short = bd.dup
func = bd_short.type.func
if func.args.size > 0 && func.args[-1].anonymous_type.to_s =~ /QPrivateSignal/
func.args.pop
end
sig = bd_short.sig(cls)
rsig = bd_short.raw_sig(cls)
hk = bd_short.hash_str
n_args = func.max_args
argnames = n_args.times.collect { |ia| (func.args[ia].name || "arg#{ia + 1}") }
ant = n_args.times.collect { |ia| func.args[ia].anonymous_type }
ren_args = n_args.times.collect { |ia| func.args[ia].renamed_type(argnames[ia]) }
gsi_args = ant.collect { |a| a.gsi_decl_arg(decl_obj) }
callargs = ren_args.collect { |a| a.access_gsi_arg(decl_obj) }
event_al = gsi_args.join(", ")
al = ant.collect { |a| a.to_s }.join(", ")
aln = ren_args.collect { |a| a.to_s }.join(", ")
rsig_wo_void = rsig.sub(/^void /, "")
al_subst = al
SignalSubstitutions.each do |t,s|
al_subst = al_subst.gsub(t, s)
end
argspecs = argnames.collect { |a| "gsi::arg(\"#{a}\"), " }.join("")
if gsi_args.empty?
mdecl << "gsi::qt_signal (\"#{mid}(#{al_subst})\", \"#{mn_name}\", \"@brief Signal declaration for #{rsig_wo_void}\\nYou can bind a procedure to this signal.\");"
else
mdecl << "gsi::qt_signal<#{event_al} > (\"#{mid}(#{al_subst})\", \"#{mn_name}\", #{argspecs}\"@brief Signal declaration for #{rsig_wo_void}\\nYou can bind a procedure to this signal.\");"
end
end
end
end
end
# produce public, static methods
methods_by_name.keys.sort.each do |mid|
methods_by_name[mid].each do |bd|
if bd.visibility != :public || bd.storage_class != :static
next
end
func = bd.type.func
const = bd.is_const?
hk = bd.hash_str
sig = bd.sig(cls)
rsig = bd.raw_sig(cls)
mn = conf.mid2str(mid)
mn_name = conf.target_name(cls, bd, mid, all_methods_by_name, bd)
if ! mn_name
# method dropped
next
end
rt = bd.type.return_type
n_args = func.max_args
n_min_args = func.min_args
ant = n_args.times.collect { |ia| func.args[ia].anonymous_type }
alist = n_args.times.collect { |ia| "arg#{ia + 1}" }
qt_alist = n_args.times.collect { |ia| func.args[ia].renamed_type(alist[ia]).access_qt_arg(decl_obj) }
ofile.puts("")
ofile.puts("// static #{rsig}")
ofile.puts("")
ofile.puts("")
ofile.puts("static void _init_f_#{mn}_#{hk} (qt_gsi::GenericStaticMethod *decl)")
ofile.puts("{")
produce_arg_init(ofile, decl_obj, func)
rpf = (conf.returns_new(bd) ? "_new" : "")
ofile.puts(" decl->set_return#{rpf}<#{rt.gsi_decl_return(decl_obj)} > ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_f_#{mn}_#{hk} (const qt_gsi::GenericStaticMethod * /*decl*/, gsi::SerialArgs &args, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" __SUPPRESS_UNUSED_WARNING(args);")
produce_arg_read(ofile, decl_obj, func, alist, conf.kept_args(bd))
if !rt.is_void?
ofile.puts(" ret.write<#{rt.gsi_decl_return(decl_obj)} > ((#{rt.gsi_decl_return(decl_obj)})" + rt.access_gsi_return(decl_obj, "#{cls}::#{mid} (#{qt_alist.join(', ')})") + ");")
else
ofile.puts(" __SUPPRESS_UNUSED_WARNING(ret);")
ofile.puts(" #{cls}::#{mid} (#{qt_alist.join(', ')});")
end
ofile.puts("}")
ofile.puts("")
mdecl << "new qt_gsi::GenericStaticMethod (\"#{mn_name}\", \"@brief Static method #{rsig}\\nThis method is static and can be called without an instance.\", &_init_f_#{mn}_#{hk}, &_call_f_#{mn}_#{hk});"
end
end
# produce global operators
seen_sig = {}
global_operators.each do |bd|
func = bd.type.func
mid = bd.type.name
const = bd.is_const?
hk = bd.hash_str
sig = bd.sig("")
rsig = bd.raw_sig("")
# operators may be present twice with the same signature
# (here: same hash key)
hash_sig = mid + "-" + hk
seen_sig[hash_sig] && next
seen_sig[hash_sig] = true
mn = conf.mid2str(mid)
mn_name = conf.target_name("", bd, mid)
if ! mn_name
# operator dropped
next
end
rt = bd.type.return_type
# modify first argument (reference, value -> pointer)
func = func.dup
it = func.args[0].inner
if it.is_a?(CPPReference)
func.args[0].inner = CPPPointer::new(it.inner)
else
func.args[0].inner = CPPPointer::new(it)
end
n_args = func.max_args
argnames = n_args.times.collect { |ia| (func.args[ia].name || "arg#{ia + 1}") }
argnames[0] = "_self"
qt_alist = n_args.times.collect { |ia| func.args[ia].renamed_type(argnames[ia]).access_qt_arg(decl_obj) }
rnt = n_args.times.collect { |ia| func.args[ia].renamed_type(argnames[ia]) }
args = rnt.collect { |t| t.gsi_decl_arg(decl_obj) }.join(", ")
ofile.puts("")
ofile.puts("// #{rsig}")
ofile.puts("static #{rt.gsi_decl_return(decl_obj)} op_#{clsn}_#{mn}_#{hk}(#{args}) {")
if !rt.is_void?
ofile.puts(" return " + rt.access_gsi_return(decl_obj, "::#{mid}(*#{qt_alist.join(', ')});"))
else
ofile.puts(" ::#{mid}(*#{qt_alist.join(', ')});")
end
ofile.puts("}")
argspecs = argnames[1..-1].collect { |a| "gsi::arg (\"#{a}\"), " }.join("")
mdecl << "gsi::method_ext(\"#{mn_name}\", &::op_#{clsn}_#{mn}_#{hk}, #{argspecs}\"@brief Operator #{rsig}\\nThis is the mapping of the global operator to the instance method.\");"
end
mdecl_bcc = []
if base_classes.size > 1
ofile.puts("")
base_classes.each do |bc|
bc_name = bc.class_id.to_s
ofile.puts("// base class cast for #{bc_name}")
ofile.puts("")
ofile.puts("static void _init_f_#{clsn}_as_#{bc_name} (qt_gsi::GenericMethod *decl)")
ofile.puts("{")
ofile.puts(" decl->set_return<#{bc_name} *> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_f_#{clsn}_as_#{bc_name} (const qt_gsi::GenericMethod *, void *cls, gsi::SerialArgs &, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" ret.write<#{bc_name} *> ((#{bc_name} *)(#{cls} *)cls);")
ofile.puts("}")
ofile.puts("")
mdecl_bcc << "new qt_gsi::GenericMethod (\"as#{bc_name}\", \"@brief Delivers the base class interface #{bc_name} of #{cls}\\nClass #{cls} is derived from multiple base classes. This method delivers the #{bc_name} base class aspect.\", false, &_init_f_#{clsn}_as_#{bc_name}, &_call_f_#{clsn}_as_#{bc_name});"
ofile.puts("static void _init_f_#{clsn}_as_const_#{bc_name} (qt_gsi::GenericMethod *decl)")
ofile.puts("{")
ofile.puts(" decl->set_return<const #{bc_name} *> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_f_#{clsn}_as_const_#{bc_name} (const qt_gsi::GenericMethod *, void *cls, gsi::SerialArgs &, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" ret.write<const #{bc_name} *> ((const #{bc_name} *)(const #{cls} *)cls);")
ofile.puts("}")
ofile.puts("")
mdecl_bcc << "new qt_gsi::GenericMethod (\"asConst#{bc_name}\", \"@brief Delivers the base class interface #{bc_name} of #{cls}\\nClass #{cls} is derived from multiple base classes. This method delivers the #{bc_name} base class aspect.\\n\\nUse this version if you have a const reference.\", true, &_init_f_#{clsn}_as_const_#{bc_name}, &_call_f_#{clsn}_as_const_#{bc_name});"
end
end
mdecl = mdecl_ctors + mdecl + mdecl_bcc
if ! needs_adaptor
ofile.puts("")
end
ofile.puts("")
ofile.puts("namespace gsi")
ofile.puts("{")
ofile.puts("")
ofile.puts("static gsi::Methods methods_#{clsn} () {")
ofile.puts(" gsi::Methods methods;")
mdecl.each do |s|
ofile.puts(" methods += #{s}")
end
ofile.puts(" return methods;")
ofile.puts("}")
ofile.puts("")
if is_qobject
decl_type = "qt_gsi::QtNativeClass<#{cls}>"
else
decl_type = "gsi::Class<#{cls}>"
end
if base_cls
ofile.puts("gsi::Class<#{base_cls}> &qtdecl_#{base_clsn} ();")
ofile.puts("")
ofile.puts("#{decl_type} decl_#{clsn} (" + "qtdecl_#{base_clsn} (), \"#{modn}\", \"#{clsn}" + (needs_adaptor ? "_Native" : "") + "\",")
else
ofile.puts("#{decl_type} decl_#{clsn} (\"#{modn}\", \"#{clsn}" + (needs_adaptor ? "_Native" : "") + "\",")
end
if native_impl
native_impl.each { |n| n[1] && ofile.puts(n[1] + "+") }
end
ofile.puts(" methods_#{clsn} (),")
is_child_class = (decl_obj.parent && decl_obj.parent.myself)
if needs_adaptor
ofile.puts(" \"@hide\\n@alias #{clsn}\");")
else
ofile.puts(" \"@qt\\n@brief Binding of #{cls}\");")
ofile.puts("")
if is_child_class
# Produce the child class declaration if applicable
pdecl_obj = decl_obj.parent
pcls = pdecl_obj.myself
o = pdecl_obj
while o.parent && o.parent.myself
o = o.parent
pcls = o.myself + "::" + pcls
end
ofile.puts("gsi::ClassExt<#{pcls}> decl_#{clsn}_as_child (decl_#{clsn}, \"#{cclsn}\");")
end
end
if !is_child_class
# forward decl
@ext_decls << "#{struct.kind.to_s} #{cls};\n\n"
# only for top-level classes external declarations are produced currently
@ext_decls << "namespace gsi { GSI_#{modn.upcase}_PUBLIC gsi::Class<#{cls}> &qtdecl_#{clsn} (); }\n\n"
end
# Produce the mixin base classes
if ! mixin_base_classes.empty?
ofile.puts("")
ofile.puts("// Additional base classes")
ofile.puts("")
mixin_base_classes.each do |bc|
bc_name = bc.class_id.to_s
ofile.puts("gsi::Class<#{bc_name}> &qtdecl_#{bc_name} ();")
end
ofile.puts("")
end
mixin_base_classes.each do |bc|
bc_name = bc.class_id.to_s
ofile.puts("gsi::ClassExt<#{cls}> base_class_#{bc_name}_in_#{clsn} (qtdecl_#{bc_name} ());")
end
ofile.puts("")
ofile.puts("GSI_#{modn.upcase}_PUBLIC gsi::Class<#{cls}> &qtdecl_#{clsn} () { return decl_#{clsn}; }")
ofile.puts("")
ofile.puts("}")
ofile.puts("")
if needs_adaptor
# need to produce an adaptor
native_impl = conf.native_impl(clsn + "_Adaptor")
ofile.puts("")
ofile.puts("class #{clsn}_Adaptor : public #{cls}, public qt_gsi::QtObjectBase")
ofile.puts("{")
ofile.puts("public:")
native_impl && native_impl.each { |n| n[0] && ofile.puts(n[0]) }
ofile.puts("")
ofile.puts(" virtual ~#{clsn}_Adaptor();")
# expose ctors here
ctors.each do |bd|
bd.visibility == :public || next
func = bd.type.func
hk = bd.hash_str
sig = bd.sig(cls)
rsig = bd.raw_sig(cls)
mn = decl_obj.myself # ctor!
mn_name = conf.target_name(cls, bd, mn)
if ! mn_name
# method dropped
next
elsif mn_name == mn
mn_name = "new"
end
rt = bd.type.return_type
i_var = 0
# TODO: provide initializer instead of multiple implementations?
(func.min_args .. func.max_args).each do |n_args|
argnames = n_args.times.collect { |ia| (func.args[ia].name || "arg#{ia + 1}") }
rnt = n_args.times.collect { |ia| func.args[ia].renamed_type(argnames[ia]) }
args = rnt.collect { |t| t.to_s }.join(", ")
ofile.puts("")
ofile.puts(" // [adaptor ctor] #{rsig}")
ofile.puts(" #{clsn}_Adaptor(#{args}) : #{cls}(#{argnames.join(', ')})")
ofile.puts(" {")
ofile.puts(" qt_gsi::QtObjectBase::init (this);")
ofile.puts(" }")
i_var += 1
end
end
# expose all protected, non-virtual methods so we can call them from an implementation
all_methods_by_name.keys.sort.each do |mid|
all_methods_by_name[mid].each do |bd|
bd.virtual && next
bd.visibility == :protected || next
func = bd.type.func
hk = bd.hash_str
sig = bd.sig(cls)
rsig = bd.raw_sig(cls)
const = bd.is_const?
# exclude events
conf.event_args(cls, sig) && next
mn = conf.mid2str(mid)
mn_name = conf.target_name(cls, bd, mid)
if ! mn_name
# method dropped
next
end
rt = bd.type.return_type
n_args = func.max_args
argnames = n_args.times.collect { |ia| (func.args[ia].name || "arg#{ia + 1}") }
rnt = n_args.times.collect { |ia| func.args[ia].renamed_type(argnames[ia]) }
args = rnt.collect { |t| t.gsi_decl_arg(decl_obj) }.join(", ")
argexpr = rnt.collect { |t| t.access_qt_arg(decl_obj) }.join(", ")
ofile.puts("")
ofile.puts(" // [expose] #{rsig}")
ofile.puts(" " + (bd.storage_class == :static ? "static " : "") + "#{rt.gsi_decl_return(decl_obj)} fp_#{clsn}_#{mn}_#{hk} (#{args}) " + (const ? "const " : "") + "{")
if rt.is_void?
ofile.puts(" #{cls}::#{mid}(#{argexpr});")
else
ofile.puts(" return " + rt.access_gsi_return(decl_obj, "#{cls}::#{mid}(#{argexpr})") + ";")
end
ofile.puts(" }")
end
end
callbacks = []
# expose implementation hooks for a virtual methods
# first public, then protected for backward compatibility
[ :public, :protected ].each do |vis|
all_methods_by_name.keys.sort.each do |mid|
all_methods_by_name[mid].each do |bd|
bd.visibility == vis || next
func = bd.type.func
hk = bd.hash_str
sig = bd.sig(cls)
rsig = bd.raw_sig(cls)
const = bd.is_const?
rt = bd.type.return_type
mn = conf.mid2str(mid)
mn_name = conf.target_name(cls, bd, mid)
if ! mn_name
# method dropped
next
end
if is_qobject && conf.event_args(cls, sig) && bd.storage_class != :static && rt.is_void?
# if the last argument is a QPrivateSignal we cannot emit
is_private = false
bd_short = bd.dup
func = bd_short.type.func
if func.args.size > 0 && func.args[-1].anonymous_type.to_s =~ /QPrivateSignal/
func.args.pop
is_private = true
end
sig = bd_short.sig(cls)
rsig = bd_short.raw_sig(cls)
# for events produce an emitter function
i_var = func.max_args - func.min_args # for backward compatibility
n_args = func.max_args
argnames = n_args.times.collect { |ia| (func.args[ia].name || "arg#{ia + 1}") }.collect { |a| a == mn ? "_" + a : a }
rnt = n_args.times.collect { |ia| func.args[ia].renamed_type(argnames[ia]) }
raw_args = rnt.collect { |t| t.to_s }.join(", ")
call_args = argnames.join(", ")
ofile.puts("")
ofile.puts(" // [emitter impl] #{rsig}")
ofile.puts(" #{rt.to_s} emitter_#{clsn}_#{mn}_#{hk}(#{raw_args})")
ofile.puts(" {")
if is_private
argnames.each do |a|
ofile.puts(" __SUPPRESS_UNUSED_WARNING (#{a});")
end
ofile.puts(" throw tl::Exception (\"Can't emit private signal '#{sig}'\");")
else
ofile.puts(" emit #{cls}::#{mid}(#{call_args});")
end
ofile.puts(" }")
elsif bd.virtual
# for virtual functions produce a callback and a virtual reimplementation
abstract = (bd.virtual && bd.type.init == "0")
i_var = func.max_args - func.min_args # for backward compatibility
n_args = func.max_args
argnames = n_args.times.collect { |ia| (func.args[ia].name || "arg#{ia + 1}") }.collect { |a| a == mn ? "_" + a : a }
rnt = n_args.times.collect { |ia| func.args[ia].renamed_type(argnames[ia]) }
args = rnt.collect { |t| t.gsi_decl_arg(decl_obj) }.join(", ")
argexpr = rnt.collect { |t| t.access_qt_arg(decl_obj) }.join(", ")
argexprr = rnt.collect { |t| ", " + t.access_gsi_arg(decl_obj) }.join("")
ta = [ "#{clsn}_Adaptor" ]
if !rt.is_void?
ta << rt.gsi_decl_return(decl_obj)
end
ta += rnt.collect { |t| t.anonymous_type.gsi_decl_arg(decl_obj) }
ta_str = ta.join(", ")
if ta_str =~ />$/
ta_str += " "
end
raw_args = rnt.collect { |t| t.to_s }.join(", ")
call_args = argnames.join(", ")
ofile.puts("")
ofile.puts(" // [adaptor impl] #{rsig}")
ofile.puts(" #{rt.gsi_decl_return(decl_obj)} cbs_#{mn}_#{hk}_#{i_var}(#{args})" + (const ? " const" : ""))
ofile.puts(" {")
if abstract
argnames.each do |a|
ofile.puts(" __SUPPRESS_UNUSED_WARNING (#{a});")
end
ofile.puts(" throw qt_gsi::AbstractMethodCalledException(\"#{mn}\");")
elsif rt.is_void?
ofile.puts(" #{cls}::#{mid}(#{argexpr});")
else
ofile.puts(" return " + rt.access_gsi_return(decl_obj, "#{cls}::#{mid}(#{argexpr})") + ";")
end
ofile.puts(" }")
ofile.puts("")
ofile.puts(" virtual #{rt.to_s} #{mid}(#{raw_args})" + (const ? " const" : ""))
ofile.puts(" {")
if rt.is_void?
ofile.puts(" if (cb_#{mn}_#{hk}_#{i_var}.can_issue()) {");
ofile.puts(" cb_#{mn}_#{hk}_#{i_var}.issue<#{ta_str}>(&#{clsn}_Adaptor::cbs_#{mn}_#{hk}_#{i_var}#{argexprr});");
ofile.puts(" } else {");
if abstract
ofile.puts(" throw qt_gsi::AbstractMethodCalledException(\"#{mn}\");")
else
ofile.puts(" #{cls}::#{mid}(#{call_args});");
end
ofile.puts(" }");
else
ofile.puts(" if (cb_#{mn}_#{hk}_#{i_var}.can_issue()) {");
ofile.puts(" return " + rt.access_qt_return(decl_obj, "cb_#{mn}_#{hk}_#{i_var}.issue<#{ta_str}>(&#{clsn}_Adaptor::cbs_#{mn}_#{hk}_#{i_var}#{argexprr})") + ";");
ofile.puts(" } else {");
if abstract
ofile.puts(" throw qt_gsi::AbstractMethodCalledException(\"#{mn}\");")
else
ofile.puts(" return #{cls}::#{mid}(#{call_args});");
end
ofile.puts(" }");
end
ofile.puts(" }")
callbacks << "gsi::Callback cb_#{mn}_#{hk}_#{i_var};";
end
end
end
end
ofile.puts("")
ofile.puts(" " + callbacks.join("\n ") + "\n")
ofile.puts("};")
ofile.puts("")
ofile.puts("#{clsn}_Adaptor::~#{clsn}_Adaptor() { }");
mdecl = []
ctors.each do |bd|
bd.visibility == :public || next
func = bd.type.func
hk = bd.hash_str
sig = bd.sig(cls)
rsig = bd.raw_sig(cls)
mn = decl_obj.myself # ctor!
mn_name = conf.target_name(cls, bd, mn)
if ! mn_name
# method dropped
next
elsif mn_name == mn
mn_name = "new"
end
n_args = func.max_args
n_min_args = func.min_args
alist = n_args.times.collect { |ia| "arg#{ia + 1}" }
qt_alist = n_args.times.collect { |ia| func.args[ia].renamed_type(alist[ia]).access_qt_arg(decl_obj) }
ofile.puts("")
ofile.puts("// Constructor #{rsig} (adaptor class)")
ofile.puts("")
ofile.puts("static void _init_ctor_#{clsn}_Adaptor_#{hk} (qt_gsi::GenericStaticMethod *decl)")
ofile.puts("{")
produce_arg_init(ofile, decl_obj, func)
ofile.puts(" decl->set_return_new<#{clsn}_Adaptor> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_ctor_#{clsn}_Adaptor_#{hk} (const qt_gsi::GenericStaticMethod * /*decl*/, gsi::SerialArgs &args, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" __SUPPRESS_UNUSED_WARNING(args);")
produce_arg_read(ofile, decl_obj, func, alist, conf.kept_args(bd))
if conf.owner_args(bd).size > 0
ofile.puts(" #{clsn}_Adaptor *obj = new #{clsn}_Adaptor (#{qt_alist.join(', ')});")
produce_keep_self(ofile, alist, "obj", conf.owner_args(bd))
ofile.puts(" ret.write<#{clsn}_Adaptor *> (obj);")
else
ofile.puts(" ret.write<#{clsn}_Adaptor *> (new #{clsn}_Adaptor (#{qt_alist.join(', ')}));")
end
ofile.puts("}")
ofile.puts("")
mdecl << "new qt_gsi::GenericStaticMethod (\"#{mn_name}\", \"@brief Constructor #{rsig}\\nThis method creates an object of class #{cls}.\", &_init_ctor_#{clsn}_Adaptor_#{hk}, &_call_ctor_#{clsn}_Adaptor_#{hk});"
end
all_methods_by_name.keys.sort.each do |mid|
all_methods_by_name[mid].each do |bd|
bd.visibility == :public || bd.visibility == :protected || next
func = bd.type.func
hk = bd.hash_str
sig = bd.sig(cls)
rsig = bd.raw_sig(cls)
const = bd.is_const?
rt = bd.type.return_type
mn = conf.mid2str(mid)
mn_name = conf.target_name(cls, bd, mid)
if ! mn_name
# method dropped
next
end
if is_qobject && conf.event_args(cls, sig) && bd.storage_class != :static && rt.is_void?
# for events produce the emitter
bd_short = bd.dup
func = bd_short.type.func
if func.args.size > 0 && func.args[-1].anonymous_type.to_s =~ /QPrivateSignal/
func.args.pop
end
sig = bd_short.sig(cls)
rsig = bd_short.raw_sig(cls)
n_args = func.max_args
n_min_args = func.min_args
alist = n_args.times.collect { |ia| "arg#{ia + 1}" }
ifc_obj = "GenericMethod"
ofile.puts("")
ofile.puts("// emitter #{rsig}")
ofile.puts("")
ofile.puts("static void _init_emitter_#{mn}_#{hk} (qt_gsi::#{ifc_obj} *decl)")
ofile.puts("{")
produce_arg_init(ofile, decl_obj, func)
rpf = (conf.returns_new(bd_short) ? "_new" : "")
ofile.puts(" decl->set_return#{rpf}<#{rt.gsi_decl_return(decl_obj)} > ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_emitter_#{mn}_#{hk} (const qt_gsi::#{ifc_obj} * /*decl*/, void *cls, gsi::SerialArgs &args, gsi::SerialArgs & /*ret*/) ")
ofile.puts("{")
ofile.puts(" __SUPPRESS_UNUSED_WARNING(args);")
produce_arg_read(ofile, decl_obj, func, alist, conf.kept_args(bd_short))
ofile.puts(" ((#{clsn}_Adaptor *)cls)->emitter_#{clsn}_#{mn}_#{hk} (#{alist.join(', ')});")
ofile.puts("}")
ofile.puts("")
const_flag = ""
if bd_short.storage_class != :static
const_flag = ", " + const.to_s
end
mdecl << "new qt_gsi::#{ifc_obj} (\"emit_#{mn_name}\", \"@brief Emitter for signal #{rsig}\\nCall this method to emit this signal.\"#{const_flag}, &_init_emitter_#{mn}_#{hk}, &_call_emitter_#{mn}_#{hk});"
elsif !bd.virtual && bd.visibility == :protected
# expose all protected, non-virtual methods and the signals so we can call them from an implementation
n_args = func.max_args
n_min_args = func.min_args
alist = n_args.times.collect { |ia| "arg#{ia + 1}" }
ifc_obj = bd.storage_class == :static ? "GenericStaticMethod" : "GenericMethod"
ofile.puts("")
ofile.puts("// exposed #{rsig}")
ofile.puts("")
ofile.puts("static void _init_fp_#{mn}_#{hk} (qt_gsi::#{ifc_obj} *decl)")
ofile.puts("{")
produce_arg_init(ofile, decl_obj, func)
rpf = (conf.returns_new(bd) ? "_new" : "")
ofile.puts(" decl->set_return#{rpf}<#{rt.gsi_decl_return(decl_obj)} > ();")
ofile.puts("}")
ofile.puts("")
if bd.storage_class == :static
ofile.puts("static void _call_fp_#{mn}_#{hk} (const qt_gsi::#{ifc_obj} * /*decl*/, gsi::SerialArgs &args, gsi::SerialArgs &ret) ")
else
ofile.puts("static void _call_fp_#{mn}_#{hk} (const qt_gsi::#{ifc_obj} * /*decl*/, void *cls, gsi::SerialArgs &args, gsi::SerialArgs &ret) ")
end
ofile.puts("{")
ofile.puts(" __SUPPRESS_UNUSED_WARNING(args);")
produce_arg_read(ofile, decl_obj, func, alist, conf.kept_args(bd))
if bd.storage_class == :static
if !rt.is_void?
ofile.puts(" ret.write<#{rt.gsi_decl_return(decl_obj)} > ((#{rt.gsi_decl_return(decl_obj)})#{cls}_Adaptor::fp_#{clsn}_#{mn}_#{hk} (#{alist.join(', ')}));")
else
ofile.puts(" __SUPPRESS_UNUSED_WARNING(ret);")
ofile.puts(" #{cls}_Adaptor::fp_#{clsn}_#{mn}_#{hk} (#{alist.join(', ')});")
end
else
if !rt.is_void?
ofile.puts(" ret.write<#{rt.gsi_decl_return(decl_obj)} > ((#{rt.gsi_decl_return(decl_obj)})((#{clsn}_Adaptor *)cls)->fp_#{clsn}_#{mn}_#{hk} (#{alist.join(', ')}));")
else
ofile.puts(" __SUPPRESS_UNUSED_WARNING(ret);")
ofile.puts(" ((#{clsn}_Adaptor *)cls)->fp_#{clsn}_#{mn}_#{hk} (#{alist.join(', ')});")
end
end
ofile.puts("}")
ofile.puts("")
const_flag = ""
if bd.storage_class != :static
const_flag = ", " + const.to_s
end
mdecl << "new qt_gsi::#{ifc_obj} (\"*#{mn_name}\", \"@brief Method #{rsig}\\nThis method is protected and can only be called from inside a derived class.\"#{const_flag}, &_init_fp_#{mn}_#{hk}, &_call_fp_#{mn}_#{hk});"
elsif bd.virtual
# produce call wrappers for the virtual methods
pp = (bd.visibility == :protected ? "*" : "")
abstract = bd.type.init == "0"
i_var = func.max_args - func.min_args # backward compatibility
n_args = func.max_args
ant = n_args.times.collect { |ia| func.args[ia].anonymous_type }
alist = n_args.times.collect { |ia| "arg#{ia + 1}" }
ofile.puts("")
ofile.puts("// #{rsig}")
ofile.puts("")
ofile.puts("static void _init_cbs_#{mn}_#{hk}_#{i_var} (qt_gsi::GenericMethod *decl)")
ofile.puts("{")
ant.each_with_index do |at,ia|
ta = at.gsi_decl_arg(decl_obj);
argname = func.args[ia].name || "arg#{ia + 1}"
ofile.puts(" static gsi::ArgSpecBase argspec_#{ia} (\"#{argname}\");")
ofile.puts(" decl->add_arg<#{ta} > (argspec_#{ia});")
end
ofile.puts(" decl->set_return<#{rt.gsi_decl_return(decl_obj)} > ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_cbs_#{mn}_#{hk}_#{i_var} (const qt_gsi::GenericMethod * /*decl*/, void *cls, gsi::SerialArgs &args, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" __SUPPRESS_UNUSED_WARNING(args);")
if !ant.empty?
ofile.puts(" tl::Heap heap;")
end
ant.each_with_index do |at,ia|
ofile.puts(" #{at.renamed_type(alist[ia]).gsi_decl_arg(decl_obj)} = args.read<#{at.gsi_decl_arg(decl_obj)} > (heap);")
end
if !rt.is_void?
ofile.puts(" ret.write<#{rt.gsi_decl_return(decl_obj)} > ((#{rt.gsi_decl_return(decl_obj)})((#{clsn}_Adaptor *)cls)->cbs_#{mn}_#{hk}_#{i_var} (#{alist.join(', ')}));")
else
ofile.puts(" __SUPPRESS_UNUSED_WARNING(ret);")
ofile.puts(" ((#{clsn}_Adaptor *)cls)->cbs_#{mn}_#{hk}_#{i_var} (#{alist.join(', ')});")
end
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _set_callback_cbs_#{mn}_#{hk}_#{i_var} (void *cls, const gsi::Callback &cb)")
ofile.puts("{")
ofile.puts(" ((#{clsn}_Adaptor *)cls)->cb_#{mn}_#{hk}_#{i_var} = cb;")
ofile.puts("}")
ofile.puts("")
mdecl << "new qt_gsi::GenericMethod (\"#{pp}#{mn_name}\", \"@brief Virtual method #{rsig}\\nThis method can be reimplemented in a derived class.\", #{const.to_s}, &_init_cbs_#{mn}_#{hk}_#{i_var}, &_call_cbs_#{mn}_#{hk}_#{i_var});"
mdecl << "new qt_gsi::GenericMethod (\"#{pp}#{mn_name}\", \"@hide\", #{const.to_s}, &_init_cbs_#{mn}_#{hk}_#{i_var}, &_call_cbs_#{mn}_#{hk}_#{i_var}, &_set_callback_cbs_#{mn}_#{hk}_#{i_var});"
end
end
end
# produce the main declaration
ofile.puts("")
ofile.puts("namespace gsi")
ofile.puts("{")
ofile.puts("")
ofile.puts("gsi::Class<#{cls}> &qtdecl_#{clsn} ();")
ofile.puts("")
ofile.puts("static gsi::Methods methods_#{clsn}_Adaptor () {")
ofile.puts(" gsi::Methods methods;")
mdecl.each do |s|
ofile.puts(" methods += #{s}")
end
ofile.puts(" return methods;")
ofile.puts("}")
ofile.puts("")
ofile.puts("gsi::Class<#{clsn}_Adaptor> decl_#{clsn}_Adaptor (qtdecl_#{clsn} (), \"#{modn}\", \"#{clsn}\",")
if native_impl
native_impl.each { |n| n[1] && ofile.puts(n[1] + "+") }
end
ofile.puts(" methods_#{clsn}_Adaptor (),")
ofile.puts(" \"@qt\\n@brief Binding of #{cls}\");")
ofile.puts("")
if decl_obj.parent && decl_obj.parent.myself
# Produce the child class declaration if applicable
pdecl_obj = decl_obj.parent
pcls = pdecl_obj.myself
o = pdecl_obj
while o.parent && o.parent.myself
o = o.parent
pcls = o.myself + "::" + pcls
end
ofile.puts("gsi::ClassExt<#{pcls}> decl_#{clsn}_as_child (decl_#{clsn}_Adaptor, \"#{cclsn}\");")
end
ofile.puts("}")
ofile.puts("")
end
# emit enum wrapper classes
enum_decls_by_name.keys.sort.each do |en|
ed = enum_decls_by_name[en]
produce_enum_wrapper_class(ofile, conf, cls, en, ed)
end
# don't continue
return false
end
def produce_externals
externals_name = "gsiQtExternals.h"
externals_path = $gen_dir + "/" + externals_name
File.open(externals_path, "w") do |extfile|
extfile.puts(<<"END");
/*
KLayout Layout Viewer
Copyright (C) 2006-2024 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
END
extfile.puts("/*")
extfile.puts(" External declarations for for Qt bindings")
extfile.puts("")
extfile.puts(" DO NOT EDIT THIS FILE. ")
extfile.puts(" This file has been created automatically")
extfile.puts("*/")
extfile.puts("")
extfile.puts("#if !defined(HDR_gsi#{modn}Externals)")
extfile.puts("#define HDR_gsi#{modn}Externals")
extfile.puts("")
extfile.puts("#include \"gsiClass.h\"")
extfile.puts("#include \"gsi#{modn}Common.h\"")
extfile.puts("")
@ext_decls.each do |ed|
extfile.puts(ed)
end
extfile.puts("")
extfile.puts("#define QT_EXTERNAL_BASE(X) gsi::qtdecl_##X(),")
extfile.puts("")
extfile.puts("#endif")
extfile.puts("")
end
end
def produce_common_header
src_name = "gsi#{modn}Common.h"
src_path = $gen_dir + "/" + src_name
File.open(src_path, "w") do |src|
src.puts("/**")
src.puts(" * Common header for Qt binding definition library")
src.puts(" *")
src.puts(" * DO NOT EDIT THIS FILE. ")
src.puts(" * This file has been created automatically")
src.puts(" */")
src.puts("")
src.puts("#include \"tlDefs.h\"")
src.puts("")
src.puts("#if !defined(HDR_gsi#{modn}Common_h)")
src.puts("# define HDR_gsi#{modn}Common_h")
src.puts("")
src.puts("# ifdef MAKE_GSI_#{modn.upcase}_LIBRARY")
src.puts("# define GSI_#{modn.upcase}_PUBLIC DEF_INSIDE_PUBLIC")
src.puts("# define GSI_#{modn.upcase}_PUBLIC_TEMPLATE DEF_INSIDE_PUBLIC_TEMPLATE")
src.puts("# define GSI_#{modn.upcase}_LOCAL DEF_INSIDE_LOCAL")
src.puts("# else")
src.puts("# define GSI_#{modn.upcase}_PUBLIC DEF_OUTSIDE_PUBLIC")
src.puts("# define GSI_#{modn.upcase}_PUBLIC_TEMPLATE DEF_OUTSIDE_PUBLIC_TEMPLATE")
src.puts("# define GSI_#{modn.upcase}_LOCAL DEF_OUTSIDE_LOCAL")
src.puts("# endif")
src.puts("")
src.puts("#define FORCE_LINK_GSI_#{modn.upcase} GSI_#{modn.upcase}_PUBLIC int _force_link_gsi#{modn}_f (); int _force_link_gsi#{modn} = _force_link_gsi#{modn}_f ();")
src.puts("")
src.puts("#endif")
puts("#{src_name} written.")
end
end
def produce_main_source
src_name = "gsi#{modn}Main.cc"
src_path = $gen_dir + "/" + src_name
File.open(src_path, "w") do |src|
src.puts("/**")
src.puts(" * Main source file for Qt binding definition library")
src.puts(" *")
src.puts(" * DO NOT EDIT THIS FILE. ")
src.puts(" * This file has been created automatically")
src.puts(" */")
src.puts("")
src.puts("#include \"gsi#{modn}Common.h\"")
src.puts("")
src.puts("GSI_#{modn.upcase}_PUBLIC int _force_link_gsi#{modn}_f () { return 0; }")
src.puts("")
puts("#{src_name} written.")
end
end
def produce_pri
makefile_name = modn + ".pri"
makefile_path = $gen_dir + "/" + makefile_name
File.open(makefile_path, "w") do |makefile|
makefile.puts("#")
makefile.puts("# Partial QMAKE project file for Qt bindings")
makefile.puts("#")
makefile.puts("# DO NOT EDIT THIS FILE. ")
makefile.puts("# This file has been created automatically")
makefile.puts("#")
makefile.puts("")
makefile.puts("SOURCES += \\")
makefile.puts(" gsi#{modn}Main.cc \\")
if @source_files
makefile.puts(@source_files.collect { |s| " $$PWD/" + s }.join(" \\\n"))
end
makefile.puts("")
makefile.puts("HEADERS += gsi#{modn}Common.h")
makefile.puts("")
puts("#{makefile_name} written.")
end
end
def produce_class_list
File.open("class_list.txt", "w") do |list|
(@classes || []).each do |c|
list.puts(c)
end
end
end
end
# ---------------------------------------------------------------------
conf = Configurator::new
File.open(conf_file, "r") do |file|
conf.instance_eval(file.read, conf_file)
end
bp = BindingProducer::new
bp.modn = modn
bp.read(input_file)
puts("Collecting used enums ..")
l = bp.prod_list(conf)
l.each_with_index do |decl_obj,i|
decl_obj.myself && puts("#{decl_obj.myself}: #{i+1}/#{l.size}")
bp.collect_used_enums(conf, decl_obj)
end
puts("Producing ..")
if cls_list
cls_list.split(",").each do |cs|
bp.produce_cpp(conf, cs)
end
else
bp.prod_list(conf).each do |decl_obj|
if decl_obj.myself && !excl_list[decl_obj.myself]
bp.produce_cpp_from_decl(conf, decl_obj)
end
end
end
puts("Producing class list")
bp.produce_class_list
puts("Producing main source file ..")
bp.produce_main_source
puts("Producing common header file ..")
bp.produce_common_header
puts("Producing .pri file ..")
bp.produce_pri
puts("Producing external declarations ..")
bp.produce_externals