miqt/cmd/genbindings/intermediate.go
Jacek Sieka 187c0a02ec Move go name mangling to emitgo
This change helps keep rules for each language separate by moving `go`
rules to `emitgo` while the C bindings stay closer to the original Qt
naming (that already is mostly C-safe).

Although it doesn't practically matter for go, it makes it slightly
easier to reuse the generated code in other languages which have
different keywords and naming conventions.

The cabi generator also gains a few helpers to help keep names
consistent across files which hopefully aids reading the generator code
- it did for me at least;)

The rule that converts under_score to CamelCase is left for another day
since moving it turns out to be more invasive due to name collision
handling - when underscores are kept, there are fewer name conflicts
which ends up causing name changes in  the public go api when done
naively.
2025-02-01 13:44:32 +13:00

574 lines
16 KiB
Go

package main
import (
"log"
"regexp"
"strings"
)
type CppParameter struct {
ParameterName string
ParameterType string
Const bool
Pointer bool
PointerCount int
ByRef bool
Optional bool
QtCppOriginalType *CppParameter // If we rewrote QStringList->QList<String>, this field contains the original QStringList. Otherwise, it's blank
}
func (p CppParameter) String() string {
return "Param(" + ifv(p.Const, "const ", "") + p.ParameterType +
ifv(p.Pointer, strings.Repeat("*", p.PointerCount), "") + ifv(p.ByRef, "&", "") +
ifv(p.Optional, "?", "") + " " + p.ParameterName + ")"
}
func (p *CppParameter) ApplyTypedef(matchedUnderlyingType CppParameter) {
if p.QtCppOriginalType == nil {
tmp := *p // Copy
p.QtCppOriginalType = &tmp // Overwrite once only, at the earliest base type
}
p.ParameterType = matchedUnderlyingType.ParameterType
// If this was a pointer to a typedef'd type, or a typedef of a pointer type, we need to preserve that
p.Const = p.Const || matchedUnderlyingType.Const
p.Pointer = p.Pointer || matchedUnderlyingType.Pointer
p.PointerCount += matchedUnderlyingType.PointerCount
p.ByRef = p.ByRef || matchedUnderlyingType.ByRef
p.Optional = p.Optional || matchedUnderlyingType.Optional
}
func (p *CppParameter) PointerTo() CppParameter {
ret := *p // Copy
ret.Pointer = true
ret.PointerCount++
return ret
}
func (p *CppParameter) ConstCast(isConst bool) CppParameter {
ret := *p // Copy
ret.Const = isConst
return ret
}
func (p *CppParameter) GetQtCppType() *CppParameter {
if p.QtCppOriginalType != nil {
return p.QtCppOriginalType
}
return p
}
func (p CppParameter) QFlagsOf() (CppParameter, bool) {
if strings.HasPrefix(p.ParameterType, `QFlags<`) {
ret := parseSingleTypeString(p.ParameterType[7 : len(p.ParameterType)-1])
ret.ParameterName = p.ParameterName + "_qf"
return ret, true
}
if under := p.QtCppOriginalType; under != nil {
if strings.HasPrefix(under.ParameterType, `QFlags<`) {
ret := parseSingleTypeString(under.ParameterType[7 : len(under.ParameterType)-1])
ret.ParameterName = under.ParameterName + "_qf"
return ret, true
}
}
return CppParameter{}, false
}
func (p CppParameter) IsFlagType() bool {
_, ok := p.QFlagsOf()
return ok
}
func (p CppParameter) QtClassType() bool {
// QtClassType returns false for our customized container types (QList,
// QMap, QSet, etc)
// Maybe if it's an inner class
if _, ok := KnownClassnames[p.ParameterType]; ok {
return true
}
if p.ParameterType == "Scintilla::Internal::Point" {
return true
}
if p.ParameterType == "QString" || p.ParameterType == "QByteArray" {
return true
}
return false
}
func (p CppParameter) IsKnownEnum() bool {
_, ok := KnownEnums[p.ParameterType]
return ok
}
func (p CppParameter) QListOf() (CppParameter, bool) {
if strings.HasPrefix(p.ParameterType, "QList<") && strings.HasSuffix(p.ParameterType, `>`) {
ret := parseSingleTypeString(p.ParameterType[6 : len(p.ParameterType)-1])
ret.ParameterName = p.ParameterName + "_lv"
return ret, true
}
if strings.HasPrefix(p.ParameterType, "QVector<") && strings.HasSuffix(p.ParameterType, `>`) {
ret := parseSingleTypeString(p.ParameterType[8 : len(p.ParameterType)-1])
ret.ParameterName = p.ParameterName + "_vv"
return ret, true
}
return CppParameter{}, false
}
func (p CppParameter) QMapOf() (CppParameter, CppParameter, bool) {
// n.b. Need to block QMap<k,v>::const_terator
if strings.HasPrefix(p.ParameterType, `QMap<`) && strings.HasSuffix(p.ParameterType, `>`) {
interior := tokenizeMultipleParameters(p.ParameterType[5 : len(p.ParameterType)-1])
if len(interior) != 2 {
panic("QMap<> has unexpected number of template arguments")
}
first := parseSingleTypeString(interior[0])
first.ParameterName = p.ParameterName + "_mapkey"
second := parseSingleTypeString(interior[1])
second.ParameterName = p.ParameterName + "_mapval"
return first, second, true
}
if strings.HasPrefix(p.ParameterType, `QHash<`) && strings.HasSuffix(p.ParameterType, `>`) {
interior := tokenizeMultipleParameters(p.ParameterType[6 : len(p.ParameterType)-1])
if len(interior) != 2 {
panic("QHash<> has unexpected number of template arguments")
}
first := parseSingleTypeString(interior[0])
first.ParameterName = p.ParameterName + "_hashkey"
second := parseSingleTypeString(interior[1])
second.ParameterName = p.ParameterName + "_hashval"
return first, second, true
}
return CppParameter{}, CppParameter{}, false
}
func (p CppParameter) QPairOf() (CppParameter, CppParameter, bool) {
if strings.HasPrefix(p.ParameterType, `QPair<`) && strings.HasSuffix(p.ParameterType, `>`) {
interior := tokenizeMultipleParameters(p.ParameterType[6 : len(p.ParameterType)-1])
if len(interior) != 2 {
panic("QPair<> has unexpected number of template arguments")
}
first := parseSingleTypeString(interior[0])
first.ParameterName = p.ParameterName + "_first"
second := parseSingleTypeString(interior[1])
second.ParameterName = p.ParameterName + "_second"
return first, second, true
}
return CppParameter{}, CppParameter{}, false
}
func (p CppParameter) QSetOf() (CppParameter, bool) {
if strings.HasPrefix(p.ParameterType, `QSet<`) {
ret := parseSingleTypeString(p.ParameterType[5 : len(p.ParameterType)-1])
ret.ParameterName = p.ParameterName + "_sv"
return ret, true
}
return CppParameter{}, false
}
func (p CppParameter) QMultiMapOf() bool {
if strings.HasPrefix(p.ParameterType, `QMultiMap<`) ||
strings.HasPrefix(p.ParameterType, `QMultiHash<`) {
return true
}
return false
}
func (p CppParameter) IntType() bool {
if p.IsKnownEnum() {
return true
}
switch p.ParameterType {
case "int", "unsigned int", "uint",
"short", "unsigned short", "ushort", "qint16", "quint16", "uint16_t", "int16_t",
"qint8", "quint8",
"unsigned char", "signed char", "uchar",
"long", "unsigned long", "ulong", "qint32", "quint32", "int32_t", "uint32_t",
"longlong", "ulonglong", "qlonglong", "qulonglong", "qint64", "quint64", "int64_t", "uint64_t", "long long", "unsigned long long",
"qintptr", "quintptr", "uintptr_t", "intptr_t",
"qsizetype", "size_t",
"QIntegerForSizeof<void *>::Unsigned",
"QIntegerForSizeof<void *>::Signed",
"QIntegerForSizeof<std::size_t>::Signed",
"qptrdiff", "ptrdiff_t",
"double", "float", "qreal":
return true
case "char":
// Only count char as an integer type with cast assertions if it's
// not possibly a char* string in disguise
// (However, unsigned chars are always like ints)
return !p.Pointer
default:
return false
}
}
func (p CppParameter) Void() bool {
return p.ParameterType == "void" && !p.Pointer
}
type CppProperty struct {
PropertyName string
PropertyType string
Visibility string
}
type CppMethod struct {
MethodName string // C++ method name, unless OverrideMethodName is set, in which case a nice alternative name
OverrideMethodName string // C++ method name, present only if we changed the target
ReturnType CppParameter // Name not used
Parameters []CppParameter
IsStatic bool
IsSignal bool
IsConst bool
IsVirtual bool
IsPureVirtual bool // Virtual method was declared with = 0 i.e. there is no base method here to call
IsProtected bool // If true, we can't call this method but we may still be able to overload it
HiddenParams []CppParameter // Populated if there is an overload with more parameters
// Special quirks
LinuxOnly bool
BecomesNonConstInVersion *string // "6,7"
}
func (m CppMethod) CppCallTarget() string {
if m.OverrideMethodName != "" {
return m.OverrideMethodName
}
return m.MethodName
}
func (m *CppMethod) Rename(newName string) {
if m.OverrideMethodName == "" {
m.OverrideMethodName = m.MethodName
} else {
// If it was already set, we're already a level of overload resolution deep - preserve it
}
m.MethodName = newName
}
func IsArgcArgv(params []CppParameter, pos int) bool {
// Check if the arguments starting at position=pos are the argc/argv pattern.
// QApplication/QGuiApplication constructors are the only expected example of this.
return (len(params) > pos+1 &&
params[pos].ParameterName == "argc" &&
params[pos].ParameterType == "int" &&
params[pos].ByRef &&
params[pos+1].ParameterName == "argv" &&
params[pos+1].ParameterType == "char") &&
params[pos+1].Pointer &&
params[pos+1].PointerCount == 2
}
func IsReceiverMethod(params []CppParameter, pos int) bool {
// Check if the arguments starting at position=pos are the receiver/member pattern.
// QMenu->addAction is the main example of this
return (len(params) > pos+1 &&
params[pos].ParameterName == "receiver" &&
params[pos].ParameterType == "QObject" &&
params[pos].Pointer &&
params[pos+1].ParameterName == "member" &&
params[pos+1].ParameterType == "char" &&
params[pos+1].Pointer)
}
func (nm CppMethod) IsReceiverMethod() bool {
// Returns true if any of the parameters use the receiever-method pattern
for i := 0; i < len(nm.Parameters); i++ {
if IsReceiverMethod(nm.Parameters, i) {
return true
}
}
return false
}
func (nm CppMethod) SafeMethodName() string {
// Strip redundant Qt prefix, we know these are all Qt functions
tmp := strings.TrimPrefix(nm.MethodName, "qt_")
// Operator-overload methods have names not representable in binding
// languages. Replace more specific cases first
replacer := strings.NewReplacer(
// `operator ` with a trailing space only occurs in conversion operators
// Add a fake _ here, but it will be replaced with camelcase in the regex below
`operator `, `To `,
`::`, `__`, // e.g. `operator QCborError::Code`
`==`, `Equal`,
`!=`, `NotEqual`,
`>=`, `GreaterOrEqual`,
`<=`, `LesserOrEqual`,
`=`, `Assign`,
`<<`, `ShiftLeft`, // Qt classes use it more for stream functions e.g. in QDataStream
`>>`, `ShiftRight`,
`>`, `Greater`,
`<`, `Lesser`,
`+`, `Plus`,
`-`, `Minus`,
`*`, `Multiply`,
`/`, `Divide`,
`%`, `Modulo`,
`&&`, `LogicalAnd`,
`||`, `LogicalOr`,
`!`, `Not`,
`&`, `BitwiseAnd`,
`|`, `BitwiseOr`,
`~`, `BitwiseXor`,
`^`, `BitwiseNot`,
`->`, `PointerDereference`,
`[]`, `Subscript`,
`()`, `Call`,
)
tmp = replacer.Replace(tmp)
// Replace spaces (e.g. `operator long long` with CamelCase
tmp = regexp.MustCompile(` ([a-zA-Z])`).ReplaceAllStringFunc(tmp, func(match string) string { return strings.ToUpper(match[1:]) })
// Also replace any underscore_case with CamelCase
// Only catch lowercase letters in this one, not uppercase, as it causes a
// lot of churn for Scintilla
tmp = regexp.MustCompile(`_([a-z])`).ReplaceAllStringFunc(tmp, func(match string) string { return strings.ToUpper(match[1:]) })
return tmp
}
func (nm CppMethod) IsReadonlyOperator() bool {
targ := nm.CppCallTarget()
switch targ {
case "operator==", "operator!=",
"operator<", "operator<=", "operator>", "operator>=":
return true
}
return false
}
type CppEnumEntry struct {
EntryName string
EntryValue string
}
type CppEnum struct {
EnumName string
UnderlyingType CppParameter
Entries []CppEnumEntry
}
func (e CppEnum) ShortEnumName() string {
// Strip back one single :: pair from the generated variable name
if nameParts := strings.Split(e.EnumName, `::`); len(nameParts) > 1 {
nameParts = nameParts[0 : len(nameParts)-1]
return strings.Join(nameParts, `::`)
}
// No change
return e.EnumName
}
type CppClass struct {
ClassName string
Abstract bool
Ctors []CppMethod // only use the parameters
DirectInherits []string // other class names. This only includes direct inheritance - use AllInherits() to find recursive inheritance
Methods []CppMethod
Props []CppProperty
CanDelete bool
ChildTypedefs []CppTypedef
ChildClassdefs []CppClass
ChildEnums []CppEnum
PrivateMethods []string
}
// Virtual checks if the class has any virtual methods. This requires global
// state knowledge as virtual methods might have been inherited.
// C++ constructors cannot be virtual.
func (c *CppClass) VirtualMethods() []CppMethod {
var ret []CppMethod
var retNames = make(map[string]struct{}, 0) // if name is present, a child class found it first
var block = slice_to_set(c.PrivateMethods)
if len(c.Ctors) == 0 {
// This class can't be constructed
// Therefore there's no way to get a derived version of it for subclassing
// (Unless we add custom constructors, but that seems like there would be
// no in-Qt use for such a thing)
// Pretend that this class is non-virtual
return nil
}
if !AllowVirtualForClass(c.ClassName) {
return nil
}
for _, m := range c.Methods {
if !m.IsVirtual {
continue
}
if m.IsSignal {
continue
}
if !AllowVirtual(m) {
continue
}
ret = append(ret, m)
retNames[m.CppCallTarget()] = struct{}{}
}
for _, privMethod := range c.PrivateMethods {
block[privMethod] = struct{}{}
}
// Go will automatically allow virtual overrides for the base type because
// the parent struct is nested, but the resulting functions will not work
// because the C ABI dynamic_cast<> will fail for the base type.
// Scan all inherited classes
for _, cinfo := range c.AllInheritsClassInfo() {
// If a base class is permanently unprojectable, the child classes
// should be too
if !AllowVirtualForClass(cinfo.Class.ClassName) {
return nil
}
for _, m := range cinfo.Class.Methods {
if !m.IsVirtual {
continue
}
if m.IsSignal {
continue
}
if !AllowVirtual(m) {
continue
}
if _, ok := retNames[m.CppCallTarget()]; ok {
continue // Already found in a child class
}
// It's possible that a child class marked a parent method as private
// (e.g. Qt 5 QAbstractTableModel marks parent() as private)
// But then we find the protected version further down
// Use a blocklist to prevent exposing any deeper methods in the call chain
if _, ok := block[m.MethodName]; ok {
continue // Marked as private in a child class
}
// The class info we loaded has not had all typedefs applied to it
// m is copied by value. Mutate it
applyTypedefs_Method(&m)
// Same with astTransformBlocklist
if err := blocklist_MethodAllowed(&m); err != nil {
log.Printf("Blocking method %q(%v): %s", m.MethodName, m.Parameters, err)
continue
}
ret = append(ret, m)
retNames[m.CppCallTarget()] = struct{}{}
}
// Append this parent's private-virtuals to blocklist so that we
// do not consider them for grandparent classes
for _, privMethod := range cinfo.Class.PrivateMethods {
block[privMethod] = struct{}{}
}
}
return ret
}
// AllInheritsClassInfo recursively finds and lists all the parent classes of this class.
func (c *CppClass) AllInheritsClassInfo() []lookupResultClass {
var ret []lookupResultClass
// FIXME prevent duplicates arising from diamond inheritance
for _, baseClassInfo := range c.DirectInheritClassInfo() {
ret = append(ret, baseClassInfo)
recurseInfo := baseClassInfo.Class.AllInheritsClassInfo()
for _, childClass := range recurseInfo {
ret = append(ret, childClass)
}
}
return ret
}
// DirectInheritClassInfo looks up the CppClass for each entry in DirectInherits.
func (c *CppClass) DirectInheritClassInfo() []lookupResultClass {
var ret []lookupResultClass
for _, inh := range c.DirectInherits {
cinfo, ok := KnownClassnames[inh]
if !ok {
if !AllowInheritedParent(inh) {
// OK, allow this one to slip through
continue
} else {
panic("Class " + c.ClassName + " inherits from unknown class " + inh)
}
}
ret = append(ret, cinfo)
}
return ret
}
type CppTypedef struct {
Alias string
UnderlyingType CppParameter
}
type CppParsedHeader struct {
Filename string
Typedefs []CppTypedef
Enums []CppEnum
Classes []CppClass
}
func (c CppParsedHeader) Empty() bool {
// If there are only typedefs, that still counts as empty since typedefs
// are fully resolved inside genbindings, not exposed in MIQT classes
return len(c.Enums) == 0 && len(c.Classes) == 0
}
func (c *CppParsedHeader) AddContentFrom(other *CppParsedHeader) {
c.Classes = append(c.Classes, other.Classes...)
c.Enums = append(c.Enums, other.Enums...)
c.Typedefs = append(c.Typedefs, other.Typedefs...)
}