2024-08-06 01:03:23 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2024-08-06 02:29:12 +00:00
|
|
|
"log"
|
|
|
|
"strings"
|
2024-08-06 01:03:23 +00:00
|
|
|
)
|
|
|
|
|
2024-08-27 07:12:08 +00:00
|
|
|
func parseHeader(topLevel []interface{}, addNamePrefix string) (*CppParsedHeader, error) {
|
2024-08-06 01:03:23 +00:00
|
|
|
|
2024-08-07 06:56:14 +00:00
|
|
|
var ret CppParsedHeader
|
2024-08-06 01:03:23 +00:00
|
|
|
|
2024-08-15 07:51:13 +00:00
|
|
|
for _, node := range topLevel {
|
2024-08-06 01:03:23 +00:00
|
|
|
|
|
|
|
node, ok := node.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("inner[] element not an object")
|
|
|
|
}
|
|
|
|
|
|
|
|
kind, ok := node["kind"].(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("node has no kind")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch kind {
|
|
|
|
|
|
|
|
case "CXXRecordDecl":
|
2024-08-17 02:09:51 +00:00
|
|
|
|
2024-08-10 00:53:26 +00:00
|
|
|
// Process the inner class definition
|
2024-08-27 07:12:08 +00:00
|
|
|
obj, err := processClassType(node, addNamePrefix)
|
2024-08-10 00:53:26 +00:00
|
|
|
if err != nil {
|
2024-08-25 07:08:28 +00:00
|
|
|
if errors.Is(err, ErrNoContent) {
|
|
|
|
log.Printf("-> Skipping (%v)\n", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// A real error (shouldn't happen)
|
2024-08-10 00:53:26 +00:00
|
|
|
panic(err)
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|
|
|
|
|
2024-08-10 00:53:26 +00:00
|
|
|
ret.Classes = append(ret.Classes, obj)
|
|
|
|
|
2024-08-06 01:03:23 +00:00
|
|
|
case "StaticAssertDecl":
|
|
|
|
// ignore
|
|
|
|
|
2024-08-19 07:14:10 +00:00
|
|
|
case "ClassTemplateDecl",
|
|
|
|
"ClassTemplateSpecializationDecl",
|
|
|
|
"ClassTemplatePartialSpecializationDecl",
|
|
|
|
"FunctionTemplateDecl",
|
|
|
|
"TypeAliasTemplateDecl", // e.g. qendian.h
|
|
|
|
"VarTemplateDecl": // e.g. qglobal.h
|
|
|
|
// Template stuff probably can't be supported in the binding since
|
|
|
|
// we would need to link a concrete instantiation for each type in
|
|
|
|
// the CABI
|
|
|
|
// Ignore this node
|
2024-08-10 01:21:30 +00:00
|
|
|
|
|
|
|
case "FileScopeAsmDecl":
|
|
|
|
// ignore
|
|
|
|
|
|
|
|
case "NamespaceDecl":
|
2024-08-27 07:12:08 +00:00
|
|
|
// Parse everything inside the namespace with prefix, as if it is
|
|
|
|
// a whole separate file
|
|
|
|
// Then copy the parsed elements back into our own file
|
|
|
|
namespace, ok := node["name"].(string)
|
|
|
|
if !ok {
|
|
|
|
panic("NamespaceDecl missing name")
|
|
|
|
}
|
|
|
|
|
|
|
|
namespaceInner, ok := node["inner"].([]interface{})
|
|
|
|
if !ok {
|
|
|
|
// A namespace declaration with no inner content means that, for
|
|
|
|
// the rest of this whole file, we are in this namespace
|
|
|
|
// Update our own `addNamePrefix` accordingly
|
|
|
|
addNamePrefix += namespace + "::"
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
contents, err := parseHeader(namespaceInner, namespace+"::")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.AddContentFrom(contents)
|
|
|
|
}
|
2024-08-10 01:21:30 +00:00
|
|
|
|
2024-08-10 02:04:52 +00:00
|
|
|
case "FunctionDecl":
|
|
|
|
// TODO
|
|
|
|
|
2024-08-19 07:14:10 +00:00
|
|
|
case "EnumDecl":
|
|
|
|
// TODO e.g. qmetatype.h
|
|
|
|
|
|
|
|
case "VarDecl":
|
|
|
|
// TODO e.g. qmath.h
|
|
|
|
// We could probably generate setter/getter for this in the CABI
|
2024-08-14 06:34:46 +00:00
|
|
|
|
|
|
|
case "CXXConstructorDecl":
|
|
|
|
// TODO (why is this at the top level? e.g qobject.h)
|
|
|
|
|
|
|
|
case "CXXDestructorDecl":
|
|
|
|
// ignore
|
|
|
|
|
|
|
|
case "CXXConversionDecl":
|
|
|
|
// TODO (e.g. qbytearray.h)
|
|
|
|
|
2024-08-19 07:14:10 +00:00
|
|
|
case "LinkageSpecDecl":
|
|
|
|
// TODO e.g. qfuturewatcher.h
|
|
|
|
// Probably can't be supported in the Go binding
|
|
|
|
|
2024-08-27 07:12:08 +00:00
|
|
|
case "TypeAliasDecl", // qglobal.h
|
|
|
|
"UsingDirectiveDecl", // qtextstream.h
|
|
|
|
"UsingDecl", // qglobal.h
|
|
|
|
"UsingShadowDecl": // global.h
|
|
|
|
// TODO e.g.
|
2024-08-19 07:14:10 +00:00
|
|
|
// Should be treated like a typedef
|
|
|
|
|
2024-08-14 06:26:42 +00:00
|
|
|
case "TypedefDecl":
|
2024-08-27 07:12:08 +00:00
|
|
|
td, err := processTypedef(node, addNamePrefix)
|
2024-08-26 10:45:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("processTypedef: %w", err)
|
2024-08-14 06:26:42 +00:00
|
|
|
}
|
2024-08-26 10:45:52 +00:00
|
|
|
ret.Typedefs = append(ret.Typedefs, td)
|
2024-08-14 06:26:42 +00:00
|
|
|
|
2024-08-10 02:04:52 +00:00
|
|
|
case "CXXMethodDecl":
|
|
|
|
// A C++ class method implementation directly in the header
|
|
|
|
// Skip over these
|
|
|
|
|
2024-08-27 07:12:08 +00:00
|
|
|
case "FullComment":
|
|
|
|
// Safe to skip
|
|
|
|
|
2024-08-06 01:03:23 +00:00
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("missing handling for clang ast node type %q", kind)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ret, nil // done
|
|
|
|
}
|
|
|
|
|
2024-08-26 10:45:52 +00:00
|
|
|
func processTypedef(node map[string]interface{}, addNamePrefix string) (CppTypedef, error) {
|
|
|
|
// Must have a name
|
|
|
|
nodename, ok := node["name"].(string)
|
|
|
|
if !ok {
|
|
|
|
return CppTypedef{}, errors.New("node has no name")
|
|
|
|
}
|
|
|
|
|
|
|
|
if typ, ok := node["type"].(map[string]interface{}); ok {
|
|
|
|
if qualType, ok := typ["qualType"].(string); ok {
|
|
|
|
return CppTypedef{
|
|
|
|
Alias: addNamePrefix + nodename,
|
|
|
|
UnderlyingType: parseSingleTypeString(qualType),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return CppTypedef{}, errors.New("processTypedef: ???")
|
|
|
|
}
|
|
|
|
|
2024-08-25 07:08:28 +00:00
|
|
|
func processClassType(node map[string]interface{}, addNamePrefix string) (CppClass, error) {
|
2024-08-07 06:56:14 +00:00
|
|
|
var ret CppClass
|
2024-08-20 08:10:57 +00:00
|
|
|
ret.CanDelete = true
|
2024-08-06 01:03:23 +00:00
|
|
|
|
2024-08-25 07:08:28 +00:00
|
|
|
// Must have a name
|
|
|
|
nodename, ok := node["name"].(string)
|
|
|
|
if !ok {
|
|
|
|
// This can happen for some nested class definitions e.g. qbytearraymatcher.h::Data
|
|
|
|
return CppClass{}, ErrNoContent // errors.New("node has no name")
|
|
|
|
}
|
|
|
|
nodename = addNamePrefix + nodename
|
|
|
|
ret.ClassName = nodename
|
|
|
|
|
|
|
|
log.Printf("-> Processing class %q...\n", nodename)
|
|
|
|
|
|
|
|
// Also skip over any custom exceptions
|
|
|
|
if !AllowClass(nodename) {
|
|
|
|
return CppClass{}, ErrNoContent
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip over forward class declarations
|
|
|
|
// This is determined in two ways:
|
|
|
|
// 1. If the class has no inner nodes
|
|
|
|
inner, ok := node["inner"].([]interface{})
|
|
|
|
if !ok {
|
|
|
|
return CppClass{}, ErrNoContent
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. If this class has only one `inner` entry that's a VisibilityAttr
|
|
|
|
if len(inner) == 1 {
|
|
|
|
if node, ok := inner[0].(map[string]interface{}); ok {
|
|
|
|
if kind, ok := node["kind"].(string); ok && kind == "VisibilityAttr" {
|
|
|
|
return CppClass{}, ErrNoContent
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-10 00:53:26 +00:00
|
|
|
|
|
|
|
// Check if this was 'struct' (default visible) or 'class' (default invisible)
|
|
|
|
visibility := true
|
|
|
|
if tagUsed, ok := node["tagUsed"].(string); ok && tagUsed == "class" {
|
|
|
|
visibility = false
|
|
|
|
}
|
2024-08-10 00:53:48 +00:00
|
|
|
|
|
|
|
// Check if this is an abstract class
|
|
|
|
if definitionData, ok := node["definitionData"].(map[string]interface{}); ok {
|
|
|
|
if isAbstract, ok := definitionData["isAbstract"].(bool); ok && isAbstract {
|
|
|
|
ret.Abstract = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-10 00:54:26 +00:00
|
|
|
// Check if this (publicly) inherits another class
|
|
|
|
if bases, ok := node["bases"].([]interface{}); ok {
|
|
|
|
for _, base := range bases {
|
|
|
|
base, ok := base.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
access, ok := base["access"].(string)
|
|
|
|
if !(ok && access == "public") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if typ, ok := base["type"].(map[string]interface{}); ok {
|
|
|
|
if qualType, ok := typ["qualType"].(string); ok {
|
|
|
|
ret.Inherits = append(ret.Inherits, qualType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-10 00:53:48 +00:00
|
|
|
|
2024-08-18 03:56:48 +00:00
|
|
|
isSignal := false
|
|
|
|
|
2024-08-10 00:53:48 +00:00
|
|
|
// Parse all methods
|
|
|
|
|
2024-08-06 02:29:12 +00:00
|
|
|
nextMethod:
|
2024-08-06 01:03:23 +00:00
|
|
|
for _, node := range inner {
|
|
|
|
node, ok := node.(map[string]interface{})
|
|
|
|
if !ok {
|
2024-08-07 06:56:14 +00:00
|
|
|
return CppClass{}, errors.New("inner[] element not an object")
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|
|
|
|
|
2024-08-15 07:51:13 +00:00
|
|
|
kind, ok := node["kind"].(string)
|
2024-08-06 01:03:23 +00:00
|
|
|
if !ok {
|
|
|
|
panic("inner element has no kind")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch kind {
|
2024-08-07 06:50:39 +00:00
|
|
|
case "AccessSpecDecl":
|
|
|
|
// Swap between visible/invisible
|
|
|
|
access, ok := node["access"].(string)
|
|
|
|
if !ok {
|
|
|
|
panic("AccessSpecDecl missing `access` field")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch access {
|
|
|
|
case "public":
|
|
|
|
visibility = true
|
|
|
|
case "private", "protected":
|
|
|
|
visibility = false
|
|
|
|
default:
|
|
|
|
panic("unexpected access visibility '" + access + "'")
|
|
|
|
}
|
|
|
|
|
2024-08-18 03:56:48 +00:00
|
|
|
// Clang sees Q_SIGNALS/signals as being a macro for `public`
|
|
|
|
// If this AccessSpecDecl was imported from a macro, assume it's signals
|
|
|
|
isSignal = false
|
|
|
|
if loc, ok := node["loc"].(map[string]interface{}); ok {
|
|
|
|
if _, ok := loc["expansionLoc"].(map[string]interface{}); ok {
|
|
|
|
isSignal = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-08 05:51:10 +00:00
|
|
|
case "FriendDecl":
|
|
|
|
// Safe to ignore
|
|
|
|
|
2024-08-10 00:53:26 +00:00
|
|
|
case "VisibilityAttr":
|
|
|
|
// These seem to have no useful content
|
|
|
|
|
2024-08-25 07:08:28 +00:00
|
|
|
case "CXXRecordDecl":
|
|
|
|
// Child class type definition e.g. QAbstractEventDispatcher::TimerInfo
|
|
|
|
// Parse as a whole child class
|
|
|
|
|
|
|
|
if !visibility {
|
|
|
|
continue // Skip private/protected
|
|
|
|
}
|
|
|
|
|
|
|
|
child, err := processClassType(node, nodename+"::")
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, ErrNoContent) {
|
2024-08-28 05:59:56 +00:00
|
|
|
log.Printf("-> Skipping inner class because: %v", err)
|
2024-08-25 07:08:28 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
panic(err) // A real problem
|
|
|
|
}
|
|
|
|
|
|
|
|
ret.ChildClassdefs = append(ret.ChildClassdefs, child)
|
|
|
|
|
2024-08-26 10:45:52 +00:00
|
|
|
case "TypedefDecl":
|
|
|
|
// Child class typedef
|
|
|
|
td, err := processTypedef(node, nodename+"::")
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("processTypedef: %w", err)) // A real problem
|
|
|
|
}
|
|
|
|
ret.ChildTypedefs = append(ret.ChildTypedefs, td)
|
|
|
|
|
2024-08-08 05:51:10 +00:00
|
|
|
case "CXXConstructorDecl":
|
2024-08-25 00:48:19 +00:00
|
|
|
|
|
|
|
if isImplicit, ok := node["isImplicit"].(bool); ok && isImplicit {
|
|
|
|
// This is an implicit ctor. Therefore the class is constructable
|
|
|
|
// even if we're currently in a `private:` block.
|
|
|
|
|
|
|
|
} else if !visibility {
|
2024-08-14 06:39:04 +00:00
|
|
|
continue // Skip private/protected
|
|
|
|
}
|
2024-08-10 00:52:45 +00:00
|
|
|
|
2024-08-10 00:53:48 +00:00
|
|
|
if ret.Abstract {
|
|
|
|
continue // The bindings can't construct an abstract class
|
|
|
|
}
|
|
|
|
|
2024-08-10 00:54:00 +00:00
|
|
|
// Check if this is `= delete`
|
2024-08-20 08:10:57 +00:00
|
|
|
if isExplicitlyDeleted(node) {
|
2024-08-10 00:54:00 +00:00
|
|
|
continue
|
|
|
|
}
|
2024-08-10 00:52:45 +00:00
|
|
|
|
|
|
|
var mm CppMethod
|
|
|
|
err := parseMethod(node, &mm)
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, ErrTooComplex) {
|
2024-08-20 08:19:53 +00:00
|
|
|
log.Printf("Skipping ctor with complex type")
|
2024-08-10 00:52:45 +00:00
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
|
|
|
// Real error
|
|
|
|
return CppClass{}, err
|
|
|
|
}
|
|
|
|
|
2024-08-23 23:46:26 +00:00
|
|
|
// Always set IsStatic for constructors, since they can be called without
|
|
|
|
// an existing class instance
|
|
|
|
mm.IsStatic = true
|
|
|
|
|
2024-08-10 00:54:14 +00:00
|
|
|
// Some QFoo constructors take a QFooPrivate
|
|
|
|
for _, p := range mm.Parameters {
|
|
|
|
if strings.Contains(p.ParameterType, "Private") {
|
|
|
|
log.Printf("Skipping constructor taking Private type")
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
}
|
2024-08-10 00:52:45 +00:00
|
|
|
|
2024-08-20 08:19:15 +00:00
|
|
|
if ret.ClassName == "QDebug" && len(mm.Parameters) == 1 && mm.Parameters[0].ParameterType == "QString" && mm.Parameters[0].Pointer {
|
|
|
|
log.Printf("Skipping ctor taking QString pointer")
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
2024-08-22 09:56:25 +00:00
|
|
|
if ret.ClassName == "QXmlStreamWriter" && len(mm.Parameters) == 1 && mm.Parameters[0].ParameterType == "QString" && mm.Parameters[0].Pointer {
|
|
|
|
log.Printf("Skipping ctor taking QString pointer") // qxmlstream.h 4th constructor overload
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
2024-08-10 00:52:45 +00:00
|
|
|
ret.Ctors = append(ret.Ctors, mm)
|
2024-08-08 05:51:10 +00:00
|
|
|
|
|
|
|
case "CXXDestructorDecl":
|
2024-08-10 00:52:45 +00:00
|
|
|
// We don't need to expose destructors in the binding beyond offering
|
|
|
|
// a regular delete function
|
2024-08-20 08:10:57 +00:00
|
|
|
// However if this destructor is private or deleted, we should
|
|
|
|
// not bind it
|
|
|
|
|
2024-08-25 00:48:19 +00:00
|
|
|
if isImplicit, ok := node["isImplicit"].(bool); ok && isImplicit {
|
|
|
|
// This is an implicit dtor. Therefore the class is deleteable
|
|
|
|
// even if we're currently in a `private:` block.
|
|
|
|
ret.CanDelete = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-08-20 08:10:57 +00:00
|
|
|
if !visibility {
|
|
|
|
ret.CanDelete = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if this is `= delete`
|
|
|
|
if isExplicitlyDeleted(node) {
|
|
|
|
ret.CanDelete = false
|
|
|
|
continue
|
|
|
|
}
|
2024-08-07 06:51:51 +00:00
|
|
|
|
2024-08-06 02:29:12 +00:00
|
|
|
case "CXXMethodDecl":
|
2024-08-07 06:50:39 +00:00
|
|
|
if !visibility {
|
|
|
|
continue // Skip private/protected
|
|
|
|
}
|
|
|
|
|
2024-08-10 00:54:00 +00:00
|
|
|
// Check if this is `= delete`
|
2024-08-20 08:10:57 +00:00
|
|
|
if isExplicitlyDeleted(node) {
|
2024-08-10 00:54:00 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-08-06 02:29:12 +00:00
|
|
|
// Method
|
|
|
|
methodName, ok := node["name"].(string)
|
|
|
|
if !ok {
|
2024-08-07 06:56:14 +00:00
|
|
|
return CppClass{}, errors.New("method has no name")
|
2024-08-06 02:29:12 +00:00
|
|
|
}
|
|
|
|
|
2024-08-07 06:56:14 +00:00
|
|
|
var mm CppMethod
|
|
|
|
mm.MethodName = methodName
|
2024-08-06 02:29:12 +00:00
|
|
|
|
2024-08-18 05:47:51 +00:00
|
|
|
if strings.Contains(methodName, `QGADGET`) {
|
|
|
|
log.Printf("Skipping method %q with weird QGADGET behaviour\n", mm.MethodName)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-08-10 00:52:45 +00:00
|
|
|
err := parseMethod(node, &mm)
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, ErrTooComplex) {
|
|
|
|
log.Printf("Skipping method %q with complex type", mm.MethodName)
|
|
|
|
continue nextMethod
|
2024-08-06 02:29:12 +00:00
|
|
|
}
|
|
|
|
|
2024-08-10 00:52:45 +00:00
|
|
|
// Real error
|
|
|
|
return CppClass{}, err
|
2024-08-06 02:29:12 +00:00
|
|
|
}
|
|
|
|
|
2024-08-18 07:02:43 +00:00
|
|
|
for _, p := range mm.Parameters {
|
|
|
|
if strings.HasSuffix(p.ParameterType, "Private") {
|
|
|
|
log.Printf("Skipping method %q taking Private type", mm.MethodName)
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if strings.HasSuffix(mm.ReturnType.ParameterType, "Private") {
|
|
|
|
log.Printf("Skipping method %q returning Private type", mm.MethodName)
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
2024-08-18 03:56:48 +00:00
|
|
|
mm.IsSignal = isSignal && !mm.IsStatic && mm.MethodName != `metaObject`
|
|
|
|
|
2024-08-18 03:57:40 +00:00
|
|
|
if mm.IsReceiverMethod() {
|
|
|
|
log.Printf("Skipping method %q using non-projectable receiver pattern parameters", mm.MethodName)
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
2024-08-20 08:19:15 +00:00
|
|
|
if ret.ClassName == "QFile" && mm.MethodName == "moveToTrash" && len(mm.Parameters) == 2 && mm.Parameters[1].ParameterType == "QString" && mm.Parameters[1].Pointer {
|
|
|
|
// @ref https://doc.qt.io/qt-6/qfile.html#moveToTrash-1
|
|
|
|
log.Printf("Skipping method %q using complex return type by pointer argument", mm.MethodName) // TODO support this
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
2024-08-22 07:20:34 +00:00
|
|
|
if ret.ClassName == "QLockFile" && mm.MethodName == "getLockInfo" && len(mm.Parameters) == 3 && mm.Parameters[1].ParameterType == "QString" && mm.Parameters[1].Pointer {
|
|
|
|
log.Printf("Skipping method %q using complex return type by pointer argument", mm.MethodName) // TODO support this
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
|
|
|
if ret.ClassName == "QTextDecoder" && mm.MethodName == "toUnicode" && len(mm.Parameters) == 3 && mm.Parameters[0].ParameterType == "QString" && mm.Parameters[0].Pointer {
|
|
|
|
log.Printf("Skipping method %q using complex return type by pointer argument", mm.MethodName) // TODO support this
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
2024-08-22 09:56:25 +00:00
|
|
|
if ret.ClassName == "QTextStream" && mm.MethodName == "readLineInto" && len(mm.Parameters) > 0 && mm.Parameters[0].ParameterType == "QString" && mm.Parameters[0].Pointer {
|
|
|
|
log.Printf("Skipping method %q using complex return type by pointer argument", mm.MethodName) // TODO support this
|
|
|
|
continue nextMethod
|
|
|
|
}
|
|
|
|
|
2024-08-07 06:56:14 +00:00
|
|
|
ret.Methods = append(ret.Methods, mm)
|
2024-08-06 02:29:12 +00:00
|
|
|
|
2024-08-06 01:03:23 +00:00
|
|
|
default:
|
2024-08-18 03:55:23 +00:00
|
|
|
log.Printf("==> NOT IMPLEMENTED %q\n", kind)
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil // done
|
|
|
|
}
|
2024-08-08 05:51:10 +00:00
|
|
|
|
2024-08-20 08:10:57 +00:00
|
|
|
// isExplicitlyDeleted checks if this node is marked `= delete`.
|
|
|
|
func isExplicitlyDeleted(node map[string]interface{}) bool {
|
|
|
|
|
|
|
|
if explicitlyDeleted, ok := node["explicitlyDeleted"].(bool); ok && explicitlyDeleted {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if explicitlyDefaulted, ok := node["explicitlyDefaulted"].(string); ok && explicitlyDefaulted == "deleted" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-08-25 07:08:28 +00:00
|
|
|
var (
|
|
|
|
ErrTooComplex = errors.New("Type declaration is too complex to parse")
|
|
|
|
ErrNoContent = errors.New("There's no content to include")
|
|
|
|
)
|
2024-08-08 05:51:10 +00:00
|
|
|
|
2024-08-10 00:52:45 +00:00
|
|
|
func parseMethod(node map[string]interface{}, mm *CppMethod) error {
|
|
|
|
|
|
|
|
if typobj, ok := node["type"].(map[string]interface{}); ok {
|
|
|
|
if qualType, ok := typobj["qualType"].(string); ok {
|
|
|
|
// The qualType is the whole type of the method, including its parameter types
|
|
|
|
// If anything here is too complicated, skip the whole method
|
|
|
|
|
|
|
|
var err error = nil
|
|
|
|
mm.ReturnType, mm.Parameters, err = parseTypeString(qualType)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-18 03:24:04 +00:00
|
|
|
if storageClass, ok := node["storageClass"].(string); ok && storageClass == "static" {
|
|
|
|
mm.IsStatic = true
|
|
|
|
}
|
|
|
|
|
2024-08-10 00:52:45 +00:00
|
|
|
if methodInner, ok := node["inner"].([]interface{}); ok {
|
|
|
|
paramCounter := 0
|
|
|
|
for _, methodObj := range methodInner {
|
|
|
|
methodObj, ok := methodObj.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return errors.New("inner[] element not an object")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch methodObj["kind"] {
|
|
|
|
case "ParmVarDecl":
|
|
|
|
// Parameter variable
|
|
|
|
parmName, _ := methodObj["name"].(string) // n.b. may be unnamed
|
|
|
|
if parmName == "" {
|
|
|
|
|
|
|
|
// Generate a default parameter name
|
|
|
|
// Super nice autogen names if this is a Q_PROPERTY setter:
|
|
|
|
if len(mm.Parameters) == 1 && strings.HasPrefix(mm.MethodName, "set") {
|
|
|
|
parmName = strings.ToLower(string(mm.MethodName[3])) + mm.MethodName[4:]
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Otherwise - default
|
|
|
|
parmName = fmt.Sprintf("param%d", paramCounter+1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Block reserved Go words, replace with generic parameters
|
2024-08-15 07:49:38 +00:00
|
|
|
if goReservedWord(parmName) {
|
2024-08-10 00:52:45 +00:00
|
|
|
parmName += "Val"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the name for the existing nth parameter
|
|
|
|
mm.Parameters[paramCounter].ParameterName = parmName
|
|
|
|
|
|
|
|
// If this parameter has any internal AST nodes of its
|
|
|
|
// own, assume it means it's an optional parameter
|
|
|
|
if _, ok := methodObj["inner"]; ok {
|
|
|
|
mm.Parameters[paramCounter].Optional = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next
|
|
|
|
paramCounter++
|
|
|
|
|
|
|
|
default:
|
|
|
|
// Something else inside a declaration??
|
2024-08-18 03:55:23 +00:00
|
|
|
log.Printf("==> NOT IMPLEMENTED CXXMethodDecl->%q\n", methodObj["kind"])
|
2024-08-10 00:52:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-15 07:53:01 +00:00
|
|
|
// Fixups
|
|
|
|
// QDataStream.operator<< return a reference to QDataStream, but have a private
|
|
|
|
// copy constructor
|
|
|
|
// The bindings don't know it's the same object. This is just a trick for a more
|
|
|
|
// ergonomic C++ API but has no real effect
|
|
|
|
// Wipe out
|
|
|
|
if (mm.MethodName == "operator<<" || mm.MethodName == "operator>>" || mm.MethodName == "writeBytes") && mm.ReturnType.ParameterType == "QDataStream" && mm.ReturnType.ByRef {
|
|
|
|
mm.ReturnType = CppParameter{ParameterType: "void"}
|
|
|
|
}
|
|
|
|
|
2024-08-25 00:48:33 +00:00
|
|
|
// Change operator= (assign) to always return void. By default it returns *self which
|
|
|
|
// is a trick for more ergnonomic C++ that has no real effect
|
2024-08-25 03:32:16 +00:00
|
|
|
if mm.MethodName == "operator=" ||
|
|
|
|
mm.MethodName == "operator&=" || // qbitarray.h
|
|
|
|
mm.MethodName == "operator|=" || // qbitarray.h
|
|
|
|
mm.MethodName == "operator^=" { // qbitarray.h
|
2024-08-25 00:48:33 +00:00
|
|
|
mm.ReturnType = CppParameter{ParameterType: "void"}
|
|
|
|
}
|
|
|
|
|
2024-08-10 00:52:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-26 10:49:33 +00:00
|
|
|
// parseTypeString converts a function/method type string such as
|
2024-08-08 05:51:10 +00:00
|
|
|
// - `QString (const char *, const char *, int)`
|
|
|
|
// - `void (const QKeySequence \u0026)`
|
|
|
|
// into its (A) return type and (B) separate parameter types.
|
|
|
|
// These clang strings never contain the parameter's name, so the names here are
|
|
|
|
// not filled in.
|
|
|
|
func parseTypeString(typeString string) (CppParameter, []CppParameter, error) {
|
|
|
|
|
2024-08-10 02:04:52 +00:00
|
|
|
if strings.Contains(typeString, `&&`) { // TODO Rvalue references
|
|
|
|
return CppParameter{}, nil, ErrTooComplex
|
|
|
|
}
|
|
|
|
|
2024-08-08 05:51:10 +00:00
|
|
|
// Cut to exterior-most (, ) pair
|
|
|
|
opos := strings.Index(typeString, `(`)
|
|
|
|
epos := strings.LastIndex(typeString, `)`)
|
|
|
|
|
|
|
|
if opos == -1 || epos == -1 {
|
2024-08-18 07:01:49 +00:00
|
|
|
return CppParameter{}, nil, fmt.Errorf("Type string %q missing brackets", typeString)
|
2024-08-08 05:51:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
returnType := parseSingleTypeString(strings.TrimSpace(typeString[0:opos]))
|
|
|
|
|
2024-08-15 07:53:01 +00:00
|
|
|
// Skip functions that return ints-by-reference since the ergonomics don't
|
|
|
|
// go through the binding
|
|
|
|
if returnType.IntType() && returnType.ByRef {
|
|
|
|
return CppParameter{}, nil, ErrTooComplex // e.g. QSize::rheight()
|
|
|
|
}
|
|
|
|
|
2024-08-08 05:51:10 +00:00
|
|
|
inner := typeString[opos+1 : epos]
|
|
|
|
|
|
|
|
// Should be no more brackets
|
|
|
|
if strings.ContainsAny(inner, `()`) {
|
|
|
|
return CppParameter{}, nil, ErrTooComplex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parameters are separated by commas and nesting can not be possible
|
2024-08-19 07:11:19 +00:00
|
|
|
params := tokenizeMultipleParameters(inner) // strings.Split(inner, `,`)
|
2024-08-08 05:51:10 +00:00
|
|
|
|
|
|
|
ret := make([]CppParameter, 0, len(params))
|
|
|
|
for _, p := range params {
|
|
|
|
|
|
|
|
insert := parseSingleTypeString(p)
|
|
|
|
|
|
|
|
if insert.ParameterType != "" {
|
|
|
|
ret = append(ret, insert)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return returnType, ret, nil
|
|
|
|
}
|
|
|
|
|
2024-08-19 07:11:19 +00:00
|
|
|
func tokenizeMultipleParameters(p string) []string {
|
|
|
|
// Tokenize into top-level strings
|
|
|
|
templateDepth := 0
|
|
|
|
tokens := []string{}
|
|
|
|
wip := ""
|
|
|
|
p = strings.TrimSpace(p)
|
|
|
|
for _, c := range p {
|
|
|
|
if c == '<' {
|
|
|
|
wip += string(c)
|
|
|
|
templateDepth++
|
|
|
|
} else if c == '>' {
|
|
|
|
wip += string(c)
|
|
|
|
templateDepth--
|
|
|
|
} else if c == ',' && templateDepth == 0 {
|
|
|
|
tokens = append(tokens, wip)
|
|
|
|
wip = ""
|
|
|
|
} else {
|
|
|
|
wip += string(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tokens = append(tokens, wip)
|
|
|
|
return tokens
|
|
|
|
}
|
|
|
|
|
2024-08-28 05:59:21 +00:00
|
|
|
func tokenizeSingleParameter(p string) []string {
|
|
|
|
// Tokenize into top-level strings
|
|
|
|
templateDepth := 0
|
|
|
|
tokens := []string{}
|
|
|
|
wip := ""
|
|
|
|
p = strings.TrimSpace(p)
|
|
|
|
for _, c := range p {
|
|
|
|
if c == '<' || c == '(' {
|
|
|
|
wip += string(c)
|
|
|
|
templateDepth++
|
|
|
|
} else if c == '>' || c == ')' {
|
|
|
|
wip += string(c)
|
|
|
|
templateDepth--
|
|
|
|
} else if (c == '*' || c == '&') && templateDepth == 0 {
|
|
|
|
if len(wip) > 0 {
|
|
|
|
tokens = append(tokens, wip)
|
|
|
|
}
|
|
|
|
tokens = append(tokens, string(c))
|
|
|
|
wip = ""
|
|
|
|
} else if c == ' ' && templateDepth == 0 {
|
|
|
|
if len(wip) > 0 {
|
|
|
|
tokens = append(tokens, wip)
|
|
|
|
}
|
|
|
|
wip = ""
|
|
|
|
} else {
|
|
|
|
wip += string(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(wip) > 0 {
|
|
|
|
tokens = append(tokens, wip)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tokens
|
|
|
|
}
|
2024-08-08 05:51:10 +00:00
|
|
|
func parseSingleTypeString(p string) CppParameter {
|
|
|
|
|
2024-08-28 05:59:21 +00:00
|
|
|
tokens := tokenizeSingleParameter(p) // strings.Split(strings.TrimSpace(p), " ")
|
2024-08-08 05:51:10 +00:00
|
|
|
insert := CppParameter{}
|
|
|
|
for _, tok := range tokens {
|
2024-08-19 07:42:34 +00:00
|
|
|
|
2024-08-15 07:53:01 +00:00
|
|
|
if tok == "" {
|
|
|
|
continue // extra space
|
2024-08-19 07:42:34 +00:00
|
|
|
|
2024-08-15 07:53:01 +00:00
|
|
|
} else if tok == "const" || tok == "*const" {
|
|
|
|
// *const happens for QPixmap, clang reports `const char *const *` which
|
|
|
|
// isn't even valid syntax
|
2024-08-08 05:51:10 +00:00
|
|
|
insert.Const = true
|
2024-08-15 07:53:01 +00:00
|
|
|
|
2024-08-08 05:51:10 +00:00
|
|
|
} else if tok == "&" { // U+0026
|
|
|
|
insert.ByRef = true
|
2024-08-19 07:42:34 +00:00
|
|
|
|
2024-08-26 10:51:21 +00:00
|
|
|
} else if tok == "signed" {
|
|
|
|
// continue
|
|
|
|
|
2024-08-08 05:51:10 +00:00
|
|
|
} else if tok == "*" {
|
|
|
|
insert.Pointer = true
|
2024-08-19 07:42:34 +00:00
|
|
|
|
|
|
|
} else if len(tok) > 4 && strings.HasSuffix(tok, "List") {
|
|
|
|
// Classes ending in --List are usually better represented as a QList
|
|
|
|
// type directly, so that the binding uses proper Go slices
|
2024-08-15 07:50:30 +00:00
|
|
|
// Typedef e.g. QObjectList
|
2024-08-20 08:16:13 +00:00
|
|
|
insert.TypeAlias = tok
|
2024-08-19 07:42:34 +00:00
|
|
|
switch tok {
|
2024-08-20 08:19:15 +00:00
|
|
|
case "QStringList", "QModelIndexList", "QVariantList", "QFileInfoList":
|
2024-08-19 07:42:34 +00:00
|
|
|
// These types are defined as a QList of values
|
2024-08-20 08:19:15 +00:00
|
|
|
insert.ParameterType += " QList<" + tok[0:len(tok)-4] + ">"
|
2024-08-19 07:42:34 +00:00
|
|
|
case "QTextList":
|
|
|
|
// This is really a custom class, preserve as-is
|
|
|
|
insert.ParameterType += " " + tok
|
|
|
|
default:
|
|
|
|
// These types are defined as a QList of pointers.
|
|
|
|
// QList<QFoo*>. This is the most common case
|
|
|
|
insert.ParameterType += " QList<" + tok[0:len(tok)-4] + " *>"
|
|
|
|
}
|
|
|
|
|
2024-08-08 05:51:10 +00:00
|
|
|
} else {
|
|
|
|
// Valid part of the type name
|
|
|
|
insert.ParameterType += " " + tok
|
|
|
|
}
|
|
|
|
}
|
|
|
|
insert.ParameterType = strings.TrimSpace(insert.ParameterType)
|
|
|
|
|
|
|
|
return insert
|
|
|
|
}
|