genbindings: add support for enums

This commit is contained in:
mappu 2024-09-04 18:54:10 +12:00
parent 25e6ea1698
commit d62ec99cf1
5 changed files with 165 additions and 4 deletions

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"strconv"
"strings"
)
@ -88,7 +89,14 @@ func parseHeader(topLevel []interface{}, addNamePrefix string) (*CppParsedHeader
// TODO
case "EnumDecl":
// TODO e.g. qmetatype.h
// Child class enum
en, err := processEnum(node, addNamePrefix)
if err != nil {
panic(fmt.Errorf("processEnum: %w", err)) // A real problem
}
if len(en.Entries) > 0 { // e.g. qmetatype's version of QCborSimpleType (the real one is in qcborcommon)
ret.Enums = append(ret.Enums, en)
}
case "VarDecl":
// TODO e.g. qmath.h
@ -312,6 +320,21 @@ nextMethod:
}
ret.ChildTypedefs = append(ret.ChildTypedefs, td)
case "EnumDecl":
// Child class enum
if !visibility {
continue // Skip private/protected
}
en, err := processEnum(node, nodename+"::")
if err != nil {
panic(fmt.Errorf("processEnum: %w", err)) // A real problem
}
if len(en.Entries) > 0 { // e.g. qmetatype's version of QCborSimpleType (the real one is in qcborcommon)
ret.ChildEnums = append(ret.ChildEnums, en)
}
case "CXXConstructorDecl":
if isImplicit, ok := node["isImplicit"].(bool); ok && isImplicit {
@ -455,6 +478,108 @@ var (
ErrNoContent = errors.New("There's no content to include")
)
func processEnum(node map[string]interface{}, addNamePrefix string) (CppEnum, error) {
var ret CppEnum
ret.UnderlyingType = "int" // FIXME
nodename, ok := node["name"].(string)
if !ok {
// An unnamed enum is possible (e.g. qcalendar.h)
// It defines integer constants just in the current scope
ret.EnumName = addNamePrefix
} else {
ret.EnumName = addNamePrefix + nodename
}
inner, ok := node["inner"].([]interface{})
if !ok {
// An enum with no entries? We're done
return ret, nil
}
var lastImplicitValue int64 = -1
for _, entry := range inner {
entry, ok := entry.(map[string]interface{})
if !ok {
return ret, errors.New("bad inner type")
}
kind, ok := entry["kind"].(string)
if !ok || kind != "EnumConstantDecl" {
return ret, fmt.Errorf("unexpected kind %q", kind)
}
var cee CppEnumEntry
entryname, ok := entry["name"].(string)
if !ok {
return ret, errors.New("entry without name")
}
cee.EntryName = entryname
// Try to find the enum value
ei1, ok := entry["inner"].([]interface{})
if !ok || len(ei1) == 0 {
// Enum case without definition e.g. QCalendar::Gregorian
// This means one more than the last value
cee.EntryValue = fmt.Sprintf("%d", lastImplicitValue+1)
} else if len(ei1) == 1 {
ei1_0 := ei1[0].(map[string]interface{})
// Best case: .inner -> kind=ConstantExpr value=xx
// e.g. qabstractitemmodel
if ei1Kind, ok := ei1_0["kind"].(string); ok && ei1Kind == "ConstantExpr" {
log.Printf("Got ConstantExpr OK")
if ei1Value, ok := ei1_0["value"].(string); ok {
cee.EntryValue = ei1Value
goto afterParse
}
}
// Best case: .inner -> kind=ImplicitCastExpr .inner -> kind=ConstantExpr value=xx
// e.g. QCalendar (when there is a int typecast)
if ei1Kind, ok := ei1_0["kind"].(string); ok && ei1Kind == "ImplicitCastExpr" {
log.Printf("Got ImplicitCastExpr OK")
if ei2, ok := ei1_0["inner"].([]interface{}); ok && len(ei2) > 0 {
ei2_0 := ei2[0].(map[string]interface{})
if ei2Kind, ok := ei2_0["kind"].(string); ok && ei2Kind == "ConstantExpr" {
log.Printf("Got ConstantExpr OK")
if ei2Value, ok := ei2_0["value"].(string); ok {
cee.EntryValue = ei2Value
goto afterParse
}
}
}
}
}
afterParse:
if cee.EntryValue == "" {
return ret, fmt.Errorf("Complex enum %q entry %q", ret.EnumName, entryname)
}
var err error
if cee.EntryValue == "true" || cee.EntryValue == "false" {
ret.UnderlyingType = "bool"
} else {
lastImplicitValue, err = strconv.ParseInt(cee.EntryValue, 10, 64)
if err != nil {
return ret, fmt.Errorf("Enum %q entry %q has non-parseable value %q: %w", ret.EnumName, entryname, cee.EntryValue, err)
}
}
ret.Entries = append(ret.Entries, cee)
}
return ret, nil
}
func parseMethod(node map[string]interface{}, mm *CppMethod) error {
if typobj, ok := node["type"].(map[string]interface{}); ok {

View File

@ -379,6 +379,12 @@ func getReferencedTypes(src *CppParsedHeader) []string {
// 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 {
// Many types are defined in qnamespace.h under Qt::
// The Go implementation is always called qt.Foo, and these names don't
// collide with anything, so strip the redundant prefix
className = strings.TrimPrefix(className, `Qt::`)
// Must use __ to avoid subclass/method name collision e.g. QPagedPaintDevice::Margins
return strings.Replace(className, `::`, `__`, -1)
}

View File

@ -85,7 +85,10 @@ func (p CppParameter) RenderTypeGo() string {
ret += "int"
} else if strings.Contains(p.ParameterType, `::`) {
if p.IsEnum() {
if p.IsKnownEnum() {
ret += cabiClassName(p.ParameterType)
} else if p.IsEnum() {
ret += "uintptr"
} else {
// Inner class
@ -291,6 +294,22 @@ import "C"
imports: map[string]struct{}{},
}
for _, e := range src.Enums {
goEnumName := cabiClassName(e.EnumName)
ret.WriteString(`
type ` + goEnumName + ` ` + parseSingleTypeString(e.UnderlyingType).RenderTypeGo() + `
const (
`)
for _, ee := range e.Entries {
ret.WriteString(cabiClassName(goEnumName+"::"+ee.EntryName) + " " + goEnumName + " = " + ee.EntryValue + "\n")
}
ret.WriteString("\n)\n\n")
}
for _, c := range src.Classes {
goClassName := cabiClassName(c.ClassName)

View File

@ -8,11 +8,13 @@ import (
var (
KnownClassnames map[string]struct{} // Entries of the form QFoo::Bar if it is an inner class
KnownTypedefs map[string]CppTypedef
KnownEnums map[string]CppEnum
)
func init() {
KnownClassnames = make(map[string]struct{})
KnownTypedefs = make(map[string]CppTypedef)
KnownEnums = make(map[string]CppEnum)
// Seed well-known typedefs
@ -96,6 +98,11 @@ func (p CppParameter) QtClassType() bool {
return false
}
func (p CppParameter) IsKnownEnum() bool {
_, ok := KnownEnums[p.ParameterType]
return ok
}
func (p CppParameter) IsEnum() bool {
if strings.Contains(p.ParameterType, `::`) {
if _, ok := KnownClassnames[p.ParameterType]; ok {
@ -279,6 +286,7 @@ type CppEnumEntry struct {
type CppEnum struct {
EnumName string
UnderlyingType string
Entries []CppEnumEntry
}

View File

@ -144,6 +144,9 @@ func main() {
for _, td := range parsed.Typedefs {
KnownTypedefs[td.Alias] = td // copy
}
for _, en := range parsed.Enums {
KnownEnums[en.EnumName] = en // copy
}
processHeaders = append(processHeaders, parsed)
}