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-15 07:51:13 +00:00
func parseHeader ( topLevel [ ] interface { } ) ( * 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" :
// Must have a name
nodename , ok := node [ "name" ] . ( string )
if ! ok {
return nil , errors . New ( "node has no name" )
}
fmt . Printf ( "-> %q name=%q\n" , kind , nodename )
2024-08-07 06:50:39 +00:00
2024-08-15 07:51:13 +00:00
// 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
}
}
2024-08-10 00:53:26 +00:00
}
2024-08-06 01:03:23 +00:00
2024-08-10 00:53:26 +00:00
// Process the inner class definition
obj , err := processClassType ( node , nodename )
if err != nil {
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-10 01:21:30 +00:00
case "ClassTemplateDecl" , "ClassTemplateSpecializationDecl" , "ClassTemplatePartialSpecializationDecl" :
// ignore
case "FileScopeAsmDecl" :
// ignore
case "NamespaceDecl" :
// ignore
2024-08-10 02:04:52 +00:00
case "FunctionDecl" :
// TODO
2024-08-14 06:34:46 +00:00
case "FunctionTemplateDecl" :
// TODO
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-14 06:26:42 +00:00
case "TypedefDecl" :
// Must have a name
nodename , ok := node [ "name" ] . ( string )
if ! ok {
return nil , errors . New ( "node has no name" )
}
if typ , ok := node [ "type" ] . ( map [ string ] interface { } ) ; ok {
if qualType , ok := typ [ "qualType" ] . ( string ) ; ok {
ret . Typedefs = append ( ret . Typedefs , CppTypedef {
Name : nodename ,
Typedef : qualType ,
} )
}
}
2024-08-10 02:04:52 +00:00
case "CXXMethodDecl" :
// A C++ class method implementation directly in the header
// Skip over these
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-10 00:53:26 +00:00
func processClassType ( node map [ string ] interface { } , className string ) ( CppClass , error ) {
2024-08-07 06:56:14 +00:00
var ret CppClass
ret . ClassName = className
2024-08-06 01:03:23 +00:00
2024-08-10 00:53:26 +00:00
inner , _ := node [ "inner" ] . ( [ ] interface { } ) // Cannot fail, the parent call already checked that `inner` was present
// 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
// 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-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-08 05:51:10 +00:00
case "CXXConstructorDecl" :
2024-08-14 06:39:04 +00:00
if ! visibility {
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`
if explicitlyDeleted , ok := node [ "explicitlyDeleted" ] . ( bool ) ; ok && explicitlyDeleted {
continue
}
2024-08-10 00:52:45 +00:00
var mm CppMethod
err := parseMethod ( node , & mm )
if err != nil {
if errors . Is ( err , ErrTooComplex ) {
log . Printf ( "Skipping method %q with complex type" , mm . MethodName )
continue nextMethod
}
// Real error
return CppClass { } , err
}
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
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-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`
if explicitlyDeleted , ok := node [ "explicitlyDeleted" ] . ( bool ) ; ok && explicitlyDeleted {
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-09 23:45:48 +00:00
if strings . HasPrefix ( methodName , "qt_" ) { // Strip redundant Qt prefix, we know these are all Qt functions
mm . OverrideMethodName = methodName
mm . MethodName = methodName [ 3 : ]
}
2024-08-06 02:29:12 +00:00
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-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-06 02:29:12 +00:00
fmt . 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
var ErrTooComplex error = errors . New ( "Type declaration is too complex to parse" )
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
}
}
}
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??
fmt . Printf ( "==> NOT IMPLEMENTED CXXMethodDecl->%q\n" , methodObj [ "kind" ] )
}
}
}
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-10 00:52:45 +00:00
return nil
}
2024-08-08 05:51:10 +00:00
// parseTypeString converts a string like
// - `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 ) {
if strings . Contains ( typeString , ` :: ` ) {
return CppParameter { } , nil , ErrTooComplex
}
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 {
return CppParameter { } , nil , fmt . Errorf ( "Type string %q missing brackets" )
}
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()
}
if returnType . QMapOf ( ) {
return CppParameter { } , nil , ErrTooComplex // e.g. QVariant constructor
}
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
params := strings . Split ( inner , ` , ` )
ret := make ( [ ] CppParameter , 0 , len ( params ) )
for _ , p := range params {
insert := parseSingleTypeString ( p )
2024-08-15 07:53:01 +00:00
if insert . QMapOf ( ) {
return CppParameter { } , nil , ErrTooComplex // Example???
}
if insert . ParameterType == "QList<QVariant>" {
return CppParameter { } , nil , ErrTooComplex // e.g. QVariant constructor - this has a deleted copy-constructor so we can't get it over the CABI boundary by value
}
if insert . ParameterType == "void **" {
return CppParameter { } , nil , ErrTooComplex // e.g. qobjectdefs.h QMetaObject->Activate()
}
if insert . ParameterType == "void" && insert . Pointer {
return CppParameter { } , nil , ErrTooComplex // e.g. qobjectdefs.h QMetaObject->InvokeOnGadget()
}
if insert . ParameterType == "char *&" {
return CppParameter { } , nil , ErrTooComplex // e.g. QDataStream.operator<<()
}
if insert . ParameterType == "qfloat16" {
return CppParameter { } , nil , ErrTooComplex // e.g. QDataStream - there is no such half-float type in C or Go
}
2024-08-08 05:51:10 +00:00
if insert . ParameterType != "" {
ret = append ( ret , insert )
}
}
return returnType , ret , nil
}
func parseSingleTypeString ( p string ) CppParameter {
tokens := strings . Split ( strings . TrimSpace ( p ) , " " )
insert := CppParameter { }
for _ , tok := range tokens {
2024-08-15 07:53:01 +00:00
if tok == "" {
continue // extra space
} 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
} else if tok == "*" {
insert . Pointer = true
2024-08-11 04:54:07 +00:00
} else if tok == "qreal" {
// Transform typedef
insert . ParameterType += " double"
2024-08-11 04:54:23 +00:00
} else if tok == "WId" {
// Transform typedef
insert . ParameterType += " uintptr_t"
2024-08-15 07:50:30 +00:00
} else if tok == "QStringList" {
insert . ParameterType += " QList<QString>"
} else if len ( tok ) > 4 && strings . HasSuffix ( tok , "List" ) {
// Typedef e.g. QObjectList
// QObjectList is a pointer, but QStringList is a whole custom class
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
}