genbindings: initial work on inner class definitions

This commit is contained in:
mappu 2024-08-25 19:08:28 +12:00
parent b4df6d06f9
commit f8a9a3f36e
6 changed files with 166 additions and 67 deletions

View File

@ -26,39 +26,16 @@ func parseHeader(topLevel []interface{}) (*CppParsedHeader, error) {
switch kind {
case "CXXRecordDecl":
// Must have a name
nodename, ok := node["name"].(string)
if !ok {
return nil, errors.New("node has no name")
}
log.Printf("-> %q name=%q\n", kind, nodename)
// Skip over forward class declarations
// This is determined in two ways:
// 1. If the class has no inner nodes
nodeInner, ok := node["inner"].([]interface{})
if !ok {
continue
}
// 2. If this class has only one `inner` entry that's a VisibilityAttr
if len(nodeInner) == 1 {
if node, ok := nodeInner[0].(map[string]interface{}); ok {
if kind, ok := node["kind"].(string); ok && kind == "VisibilityAttr" {
continue
}
}
}
// Also skip over any custom exceptions
if !AllowClass(nodename) {
continue
}
// Process the inner class definition
obj, err := processClassType(node, nodename)
obj, err := processClassType(node, "")
if err != nil {
if errors.Is(err, ErrNoContent) {
log.Printf("-> Skipping (%v)\n", err)
continue
}
// A real error (shouldn't happen)
panic(err)
}
@ -143,12 +120,42 @@ func parseHeader(topLevel []interface{}) (*CppParsedHeader, error) {
return &ret, nil // done
}
func processClassType(node map[string]interface{}, className string) (CppClass, error) {
func processClassType(node map[string]interface{}, addNamePrefix string) (CppClass, error) {
var ret CppClass
ret.ClassName = className
ret.CanDelete = true
inner, _ := node["inner"].([]interface{}) // Cannot fail, the parent call already checked that `inner` was present
// 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
}
}
}
// Check if this was 'struct' (default visible) or 'class' (default invisible)
visibility := true
@ -232,6 +239,24 @@ nextMethod:
case "VisibilityAttr":
// These seem to have no useful content
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) {
continue
}
panic(err) // A real problem
}
ret.ChildClassdefs = append(ret.ChildClassdefs, child)
case "CXXConstructorDecl":
if isImplicit, ok := node["isImplicit"].(bool); ok && isImplicit {
@ -409,7 +434,10 @@ func isExplicitlyDeleted(node map[string]interface{}) bool {
return false
}
var ErrTooComplex error = errors.New("Type declaration is too complex to parse")
var (
ErrTooComplex = errors.New("Type declaration is too complex to parse")
ErrNoContent = errors.New("There's no content to include")
)
func parseMethod(node map[string]interface{}, mm *CppMethod) error {

View File

@ -350,6 +350,13 @@ func getReferencedTypes(src *CppParsedHeader) []string {
return foundTypesList
}
// cabiClassName returns the Go / CABI class name for a Qt C++ class.
// Normally this is the same, except for class types that are nested inside another class definition.
func cabiClassName(className string) string {
// Must use __ to avoid subclass/method name collision e.g. QPagedPaintDevice::Margins
return strings.Replace(className, `::`, `__`, -1)
}
func emitBindingHeader(src *CppParsedHeader, filename string) (string, error) {
ret := strings.Builder{}
@ -378,7 +385,26 @@ extern "C" {
if ft == "QList" || ft == "QString" { // These types are reprojected
continue
}
ret.WriteString(`class ` + ft + ";\n")
if strings.Contains(ft, `::`) {
// Forward declarations of inner classes are not yet supported in C++
// @ref https://stackoverflow.com/q/1021793
// ret.WriteString(`class ` + ft + ";\n")
//parent, child, _ := strings.Cut(ft, `::`)
//ret.WriteString(`namespace ` + parent + ` { class ` + child + "; }\n")
//ret.WriteString(`typedef ` + ft + " " + cabiClassName(ft) + ";\n")
ret.WriteString(`#if defined(WORKAROUND_INNER_CLASS_DEFINITION_` + cabiClassName(ft) + ")\n")
ret.WriteString(`typedef ` + ft + " " + cabiClassName(ft) + ";\n")
ret.WriteString("#else\n")
ret.WriteString(`class ` + cabiClassName(ft) + ";\n")
ret.WriteString("#endif\n")
} else {
ret.WriteString(`class ` + ft + ";\n")
}
}
ret.WriteString("#else\n")
@ -387,7 +413,7 @@ extern "C" {
if ft == "QList" || ft == "QString" { // These types are reprojected
continue
}
ret.WriteString(`typedef struct ` + ft + " " + ft + ";\n")
ret.WriteString(`typedef struct ` + cabiClassName(ft) + " " + cabiClassName(ft) + ";\n")
}
ret.WriteString("#endif\n")
@ -396,21 +422,23 @@ extern "C" {
for _, c := range src.Classes {
cClassName := cabiClassName(c.ClassName)
for i, ctor := range c.Ctors {
ret.WriteString(fmt.Sprintf("%s %s_new%s(%s);\n", c.ClassName+"*", c.ClassName, maybeSuffix(i), emitParametersCabi(ctor, "")))
ret.WriteString(fmt.Sprintf("%s %s_new%s(%s);\n", cClassName+"*", cClassName, maybeSuffix(i), emitParametersCabi(ctor, "")))
}
for _, m := range c.Methods {
ret.WriteString(fmt.Sprintf("%s %s_%s(%s);\n", emitReturnTypeCabi(m.ReturnType), c.ClassName, m.SafeMethodName(), emitParametersCabi(m, c.ClassName+"*")))
ret.WriteString(fmt.Sprintf("%s %s_%s(%s);\n", emitReturnTypeCabi(m.ReturnType), cClassName, m.SafeMethodName(), emitParametersCabi(m, cClassName+"*")))
if m.IsSignal && !m.HasHiddenParams {
ret.WriteString(fmt.Sprintf("%s %s_connect_%s(%s* self, void* slot);\n", emitReturnTypeCabi(m.ReturnType), c.ClassName, m.SafeMethodName(), c.ClassName))
ret.WriteString(fmt.Sprintf("%s %s_connect_%s(%s* self, void* slot);\n", emitReturnTypeCabi(m.ReturnType), cClassName, m.SafeMethodName(), cClassName))
}
}
// delete
if c.CanDelete {
ret.WriteString(fmt.Sprintf("void %s_Delete(%s* self);\n", c.ClassName, c.ClassName))
ret.WriteString(fmt.Sprintf("void %s_Delete(%s* self);\n", cClassName, cClassName))
}
ret.WriteString("\n")
@ -429,19 +457,24 @@ extern "C" {
func emitBindingCpp(src *CppParsedHeader, filename string) (string, error) {
ret := strings.Builder{}
ret.WriteString(`#include "gen_` + filename + `"
#include "` + filename + `"
`)
for _, ref := range getReferencedTypes(src) {
if !ImportHeaderForClass(ref) {
continue
}
if strings.Contains(ref, `::`) {
ret.WriteString(`#define WORKAROUND_INNER_CLASS_DEFINITION_` + cabiClassName(ref) + "\n")
continue
}
ret.WriteString(`#include <` + ref + ">\n")
}
ret.WriteString(`#include "gen_` + filename + `"
#include "` + filename + `"
`)
ret.WriteString(`
extern "C" {
@ -452,6 +485,8 @@ extern "C" {
for _, c := range src.Classes {
cClassName := cabiClassName(c.ClassName)
for i, ctor := range c.Ctors {
preamble, forwarding := emitParametersCABI2CppForwarding(ctor.Parameters)
ret.WriteString(fmt.Sprintf(
@ -460,7 +495,7 @@ extern "C" {
"\treturn new %s(%s);\n"+
"}\n"+
"\n",
c.ClassName, c.ClassName, maybeSuffix(i), emitParametersCabi(ctor, ""),
cClassName, cClassName, maybeSuffix(i), emitParametersCabi(ctor, ""),
preamble,
c.ClassName, forwarding,
))
@ -591,7 +626,7 @@ extern "C" {
"%s"+
"}\n"+
"\n",
emitReturnTypeCabi(m.ReturnType), c.ClassName, m.SafeMethodName(), emitParametersCabi(m, c.ClassName+"*"),
emitReturnTypeCabi(m.ReturnType), cClassName, m.SafeMethodName(), emitParametersCabi(m, cClassName+"*"),
preamble,
shouldReturn, callTarget, nativeMethodName, forwarding,
afterCall,
@ -601,7 +636,7 @@ extern "C" {
exactSignal := `static_cast<void (` + c.ClassName + `::*)(` + emitParameterTypesCpp(m) + `)>(&` + c.ClassName + `::` + nativeMethodName + `)`
ret.WriteString(
`void ` + c.ClassName + `_connect_` + m.SafeMethodName() + `(` + c.ClassName + `* self, void* slot) {` + "\n" +
`void ` + cClassName + `_connect_` + m.SafeMethodName() + `(` + cClassName + `* self, void* slot) {` + "\n" +
"\t" + c.ClassName + `::connect(self, ` + exactSignal + `, self, [=](` + emitParametersCpp(m) + `) {` + "\n" +
"\t\t" + `miqt_exec_callback(slot, 0, nullptr);` + "\n" +
"\t});\n" +
@ -618,7 +653,7 @@ extern "C" {
"\tdelete self;\n"+
"}\n"+
"\n",
c.ClassName, c.ClassName,
cClassName, cClassName,
))
}
}

View File

@ -287,9 +287,11 @@ import "C"
for _, c := range src.Classes {
goClassName := cabiClassName(c.ClassName)
ret.WriteString(`
type ` + c.ClassName + ` struct {
h *C.` + c.ClassName + `
type ` + goClassName + ` struct {
h *C.` + goClassName + `
`)
// Embed all inherited types to directly allow calling inherited methods
@ -300,7 +302,7 @@ import "C"
ret.WriteString(`
}
func (this *` + c.ClassName + `) cPointer() *C.` + c.ClassName + ` {
func (this *` + goClassName + `) cPointer() *C.` + goClassName + ` {
if this == nil {
return nil
}
@ -316,8 +318,8 @@ import "C"
}
ret.WriteString(`
func new` + c.ClassName + `(h *C.` + c.ClassName + `) *` + c.ClassName + ` {
return &` + c.ClassName + `{` + localInit + `}
func new` + goClassName + `(h *C.` + goClassName + `) *` + goClassName + ` {
return &` + goClassName + `{` + localInit + `}
}
`)
@ -328,8 +330,8 @@ import "C"
// that never happens in Go's type system.
gfs.imports["unsafe"] = struct{}{}
ret.WriteString(`
func new` + c.ClassName + `_U(h unsafe.Pointer) *` + c.ClassName + ` {
return new` + c.ClassName + `( (*C.` + c.ClassName + `)(h) )
func new` + goClassName + `_U(h unsafe.Pointer) *` + goClassName + ` {
return new` + goClassName + `( (*C.` + goClassName + `)(h) )
}
`)
@ -337,10 +339,10 @@ import "C"
for i, ctor := range c.Ctors {
preamble, forwarding := gfs.emitParametersGo2CABIForwarding(ctor)
ret.WriteString(`
// New` + c.ClassName + maybeSuffix(i) + ` constructs a new ` + c.ClassName + ` object.
func New` + c.ClassName + maybeSuffix(i) + `(` + emitParametersGo(ctor.Parameters) + `) *` + c.ClassName + ` {
` + preamble + ` ret := C.` + c.ClassName + `_new` + maybeSuffix(i) + `(` + forwarding + `)
return new` + c.ClassName + `(ret)
// New` + goClassName + maybeSuffix(i) + ` constructs a new ` + c.ClassName + ` object.
func New` + goClassName + maybeSuffix(i) + `(` + emitParametersGo(ctor.Parameters) + `) *` + goClassName + ` {
` + preamble + ` ret := C.` + goClassName + `_new` + maybeSuffix(i) + `(` + forwarding + `)
return new` + goClassName + `(ret)
}
`)
@ -462,15 +464,15 @@ import "C"
}
receiverAndMethod := `(this *` + c.ClassName + `) ` + m.SafeMethodName()
receiverAndMethod := `(this *` + goClassName + `) ` + m.SafeMethodName()
if m.IsStatic {
receiverAndMethod = c.ClassName + `_` + m.SafeMethodName()
receiverAndMethod = goClassName + `_` + m.SafeMethodName()
}
ret.WriteString(`
func ` + receiverAndMethod + `(` + emitParametersGo(m.Parameters) + `) ` + returnTypeDecl + ` {
` + preamble +
shouldReturn + ` C.` + c.ClassName + `_` + m.SafeMethodName() + `(` + forwarding + `)
shouldReturn + ` C.` + goClassName + `_` + m.SafeMethodName() + `(` + forwarding + `)
` + afterword + `}
`)
@ -479,12 +481,12 @@ import "C"
if m.IsSignal && !m.HasHiddenParams {
gfs.imports["unsafe"] = struct{}{}
gfs.imports["runtime/cgo"] = struct{}{}
ret.WriteString(`func (this *` + c.ClassName + `) On` + m.SafeMethodName() + `(slot func()) {
ret.WriteString(`func (this *` + goClassName + `) On` + m.SafeMethodName() + `(slot func()) {
var slotWrapper miqtCallbackFunc = func(argc C.int, args *C.void) {
slot()
}
C.` + c.ClassName + `_connect_` + m.SafeMethodName() + `(this.h, unsafe.Pointer(uintptr(cgo.NewHandle(slotWrapper))))
C.` + goClassName + `_connect_` + m.SafeMethodName() + `(this.h, unsafe.Pointer(uintptr(cgo.NewHandle(slotWrapper))))
}
`)
}
@ -492,8 +494,8 @@ import "C"
if c.CanDelete {
ret.WriteString(`
func (this *` + c.ClassName + `) Delete() {
C.` + c.ClassName + `_Delete(this.h)
func (this *` + goClassName + `) Delete() {
C.` + goClassName + `_Delete(this.h)
}
`)
}

View File

@ -184,6 +184,8 @@ type CppClass struct {
Methods []CppMethod
Props []CppProperty
CanDelete bool
ChildClassdefs []CppClass
}
type CppTypedef struct {

View File

@ -124,6 +124,7 @@ func main() {
}
// AST transforms on our IL
astTransformChildClasses(parsed) // must be first
astTransformOptional(parsed)
astTransformOverloads(parsed)

View File

@ -0,0 +1,31 @@
package main
// takeChildren recursively takes the children of the class.
func takeChildren(c *CppClass) []CppClass {
if len(c.ChildClassdefs) == 0 {
return []CppClass{}
}
var ret []CppClass
for _, child := range c.ChildClassdefs {
ret = append(ret, takeChildren(&child)...)
child.ChildClassdefs = nil
ret = append(ret, child)
}
c.ChildClassdefs = nil
return ret
}
// astTransformChildClasses expands all classes with child class definitions
// into new top-level entries within the header.
func astTransformChildClasses(parsed *CppParsedHeader) {
var taken []CppClass
for i, _ := range parsed.Classes {
taken = append(taken, takeChildren(&parsed.Classes[i])...)
}
parsed.Classes = append(parsed.Classes, taken...)
}