genbindings: add qmap/qhash support

This commit is contained in:
mappu 2024-11-04 20:59:03 +13:00
parent 69011a5acb
commit a009008525
5 changed files with 189 additions and 8 deletions

View File

@ -82,6 +82,8 @@ func AllowHeader(fullpath string) bool {
"qbytearray.h", // QByteArray does not exist in this binding
"qlist.h", // QList does not exist in this binding
"qvector.h", // QVector does not exist in this binding
"qhash.h", // QHash does not exist in this binding
"qmap.h", // QMap does not exist in this binding
"qtcoreexports.h", // Nothing bindable here and has Q_CORE_EXPORT definition issues
"q20algorithm.h", // Qt 6 unstable header
"q20functional.h", // Qt 6 unstable header
@ -216,9 +218,6 @@ func AllowMethod(className string, mm CppMethod) error {
// Any type not permitted by AllowClass is also not permitted by this method.
func AllowType(p CppParameter, isReturnType bool) error {
if p.QMapOf() {
return ErrTooComplex // Example???
}
if p.QPairOf() {
return ErrTooComplex // e.g. QGradientStop
}
@ -238,6 +237,15 @@ func AllowType(p CppParameter, isReturnType bool) error {
return ErrTooComplex
}
}
if kType, vType, ok := p.QMapOf(); ok {
if err := AllowType(kType, isReturnType); err != nil {
return err
}
if err := AllowType(vType, isReturnType); err != nil {
return err
}
}
if !AllowClass(p.ParameterType) {
return ErrTooComplex // This whole class type has been blocked, not only as a parameter/return type
}
@ -294,6 +302,12 @@ func AllowType(p CppParameter, isReturnType bool) error {
if strings.Contains(p.ParameterType, `Iterator::value_type`) {
return ErrTooComplex // e.g. qcbormap
}
if strings.Contains(p.ParameterType, `>::iterator`) ||
strings.Contains(p.ParameterType, `>::const_iterator`) {
// qresultstore.h tries to create a
// NewQtPrivate__ResultIteratorBase2(_mapIterator QMap<int, ResultItem>__const_iterator)
return ErrTooComplex
}
if strings.Contains(p.ParameterType, `::QPrivate`) {
return ErrTooComplex // e.g. QAbstractItemModel::QPrivateSignal
}

View File

@ -20,6 +20,9 @@ func (p CppParameter) RenderTypeCabi() string {
} else if _, ok := p.QSetOf(); ok {
return "struct miqt_array"
} else if _, _, ok := p.QMapOf(); ok {
return "struct miqt_map"
} else if (p.Pointer || p.ByRef) && p.QtClassType() {
return cabiClassName(p.ParameterType) + "*"
@ -231,6 +234,32 @@ func emitCABI2CppForwarding(p CppParameter, indent string) (preamble string, for
preamble += indent + "}\n"
return preamble, nameprefix + "_QList"
} else if kType, vType, ok := p.QMapOf(); ok {
preamble += indent + p.GetQtCppType().ParameterType + " " + nameprefix + "_QMap;\n"
// This container may be a QMap or a QHash
// QHash supports .reserve(), but QMap doesn't
if strings.HasPrefix(p.ParameterType, "QHash<") {
preamble += indent + nameprefix + "_QMap.reserve(" + p.ParameterName + ".len);\n"
}
preamble += indent + kType.RenderTypeCabi() + "* " + nameprefix + "_karr = static_cast<" + kType.RenderTypeCabi() + "*>(" + p.ParameterName + ".keys);\n"
preamble += indent + vType.RenderTypeCabi() + "* " + nameprefix + "_varr = static_cast<" + vType.RenderTypeCabi() + "*>(" + p.ParameterName + ".values);\n"
preamble += indent + "for(size_t i = 0; i < " + p.ParameterName + ".len; ++i) {\n"
kType.ParameterName = nameprefix + "_karr[i]"
addPreK, addFwdK := emitCABI2CppForwarding(kType, indent+"\t")
preamble += addPreK
vType.ParameterName = nameprefix + "_varr[i]"
addPreV, addFwdV := emitCABI2CppForwarding(vType, indent+"\t")
preamble += addPreV
preamble += indent + "\t" + nameprefix + "_QMap[" + addFwdK + "] = " + addFwdV + ";\n"
preamble += indent + "}\n"
return preamble, nameprefix + "_QMap"
} else if p.IsFlagType() || p.IntType() || p.IsKnownEnum() {
castSrc := p.ParameterName
castType := p.RenderTypeQtCpp()
@ -386,6 +415,30 @@ func emitAssignCppToCabi(assignExpression string, p CppParameter, rvalue string)
afterCall += indent + assignExpression + "" + namePrefix + "_out;\n"
} else if kType, vType, ok := p.QMapOf(); ok {
// QMap<K,V>
shouldReturn = p.RenderTypeQtCpp() + " " + namePrefix + "_ret = "
afterCall += indent + "// Convert QMap<> from C++ memory to manually-managed C memory\n"
afterCall += indent + "" + kType.RenderTypeCabi() + "* " + namePrefix + "_karr = static_cast<" + kType.RenderTypeCabi() + "*>(malloc(sizeof(" + kType.RenderTypeCabi() + ") * " + namePrefix + "_ret.size()));\n"
afterCall += indent + "" + vType.RenderTypeCabi() + "* " + namePrefix + "_varr = static_cast<" + vType.RenderTypeCabi() + "*>(malloc(sizeof(" + vType.RenderTypeCabi() + ") * " + namePrefix + "_ret.size()));\n"
afterCall += indent + "int " + namePrefix + "_ctr = 0;\n"
afterCall += indent + "for (auto " + namePrefix + "_itr = " + namePrefix + "_ret.keyValueBegin(); " + namePrefix + "_itr != " + namePrefix + "_ret.keyValueEnd(); ++" + namePrefix + "_itr) {\n"
afterCall += emitAssignCppToCabi(indent+"\t"+namePrefix+"_karr["+namePrefix+"_ctr] = ", kType, namePrefix+"_itr->first")
afterCall += emitAssignCppToCabi(indent+"\t"+namePrefix+"_varr["+namePrefix+"_ctr] = ", vType, namePrefix+"_itr->second")
afterCall += indent + "\t" + namePrefix + "_ctr++;\n"
afterCall += indent + "}\n"
afterCall += indent + "struct miqt_map " + namePrefix + "_out;\n"
afterCall += indent + "" + namePrefix + "_out.len = " + namePrefix + "_ret.size();\n"
afterCall += indent + "" + namePrefix + "_out.keys = static_cast<void*>(" + namePrefix + "_karr);\n"
afterCall += indent + "" + namePrefix + "_out.values = static_cast<void*>(" + namePrefix + "_varr);\n"
afterCall += indent + assignExpression + "" + namePrefix + "_out;\n"
} else if p.QtClassType() && p.ByRef {
// It's a pointer in disguise, just needs one cast
shouldReturn = p.RenderTypeQtCpp() + " " + namePrefix + "_ret = "
@ -439,6 +492,15 @@ func getReferencedTypes(src *CppParsedHeader) []string {
foundTypes[t.ParameterType] = struct{}{}
}
}
if kType, vType, ok := p.QMapOf(); ok {
foundTypes["QMap"] = struct{}{} // FIXME or QHash?
if kType.QtClassType() {
foundTypes[kType.ParameterType] = struct{}{}
}
if vType.QtClassType() {
foundTypes[vType.ParameterType] = struct{}{}
}
}
}
for _, c := range src.Classes {
@ -490,6 +552,15 @@ func cabiClassName(className string) string {
return strings.Replace(className, `::`, `__`, -1)
}
func cabiPreventStructDeclaration(className string) bool {
switch className {
case "QList", "QString", "QSet", "QMap", "QHash":
return true // These types are reprojected
default:
return false
}
}
func emitBindingHeader(src *CppParsedHeader, filename string, packageName string) (string, error) {
ret := strings.Builder{}
@ -523,7 +594,7 @@ extern "C" {
ret.WriteString("#ifdef __cplusplus\n")
for _, ft := range foundTypesList {
if ft == "QList" || ft == "QString" { // These types are reprojected
if cabiPreventStructDeclaration(ft) {
continue
}
@ -545,7 +616,7 @@ extern "C" {
ret.WriteString("#else\n")
for _, ft := range foundTypesList {
if ft == "QList" || ft == "QString" { // These types are reprojected
if cabiPreventStructDeclaration(ft) {
continue
}
ret.WriteString(`typedef struct ` + cabiClassName(ft) + " " + cabiClassName(ft) + ";\n")

View File

@ -39,6 +39,10 @@ func (p CppParameter) RenderTypeGo(gfs *goFileState) string {
return "map[" + t.RenderTypeGo(gfs) + "]struct{}"
}
if t1, t2, ok := p.QMapOf(); ok {
return "map[" + t1.RenderTypeGo(gfs) + "]" + t2.RenderTypeGo(gfs)
}
if p.ParameterType == "void" && p.Pointer {
return "unsafe.Pointer"
}
@ -161,6 +165,10 @@ func (p CppParameter) parameterTypeCgo() string {
return "C.struct_miqt_array"
}
if _, _, ok := p.QMapOf(); ok {
return "C.struct_miqt_map"
}
tmp := strings.Replace(p.RenderTypeCabi(), `*`, "", -1)
if strings.HasPrefix(tmp, "const ") && tmp != "const char" { // Special typedef to make this work for const char* signal parameters
@ -320,6 +328,39 @@ func (gfs *goFileState) emitParameterGo2CABIForwarding(p CppParameter) (preamble
} else if _, ok := p.QSetOf(); ok {
panic("QSet<> arguments are not yet implemented") // n.b. doesn't seem to exist in QtCore/QtGui/QtWidgets at all
} else if kType, vType, ok := p.QMapOf(); ok {
// QMap<T>
gfs.imports["unsafe"] = struct{}{}
preamble += nameprefix + "_Keys_CArray := (*[0xffff]" + kType.parameterTypeCgo() + ")(C.malloc(C.size_t(" + kType.mallocSizeCgoExpression() + " * len(" + p.ParameterName + "))))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_Keys_CArray))\n"
preamble += nameprefix + "_Values_CArray := (*[0xffff]" + vType.parameterTypeCgo() + ")(C.malloc(C.size_t(" + vType.mallocSizeCgoExpression() + " * len(" + p.ParameterName + "))))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_Values_CArray))\n"
preamble += nameprefix + "_ctr := 0\n"
preamble += "for " + nameprefix + "_k, " + nameprefix + "_v := range " + p.ParameterName + "{\n"
kType.ParameterName = nameprefix + "_k"
addPreamble, innerRvalue := gfs.emitParameterGo2CABIForwarding(kType)
preamble += addPreamble
preamble += nameprefix + "_Keys_CArray[" + nameprefix + "_ctr] = " + innerRvalue + "\n"
vType.ParameterName = nameprefix + "_v"
addPreamble, innerRvalue = gfs.emitParameterGo2CABIForwarding(vType)
preamble += addPreamble
preamble += nameprefix + "_Values_CArray[" + nameprefix + "_ctr] = " + innerRvalue + "\n"
preamble += nameprefix + "_ctr++\n"
preamble += "}\n"
preamble += p.ParameterName + "_mm := C.struct_miqt_map{\nlen: C.size_t(len(" + p.ParameterName + ")),\nkeys: unsafe.Pointer(" + nameprefix + "_Keys_CArray),\nvalues: unsafe.Pointer(" + nameprefix + "_Values_CArray),\n}\n"
rvalue = p.ParameterName + "_mm"
} else if p.Pointer && p.ParameterType == "char" {
// Single char* argument
gfs.imports["unsafe"] = struct{}{}
@ -420,6 +461,7 @@ func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue
afterword += gfs.emitCabiToGo(namePrefix+"_ret[i] = ", t, namePrefix+"_outCast[i]")
afterword += "}\n"
afterword += assignExpr + " " + namePrefix + "_ret\n"
return shouldReturn + " " + rvalue + "\n" + afterword
@ -440,6 +482,24 @@ func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue
afterword += assignExpr + " " + namePrefix + "_ret\n"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if kType, vType, ok := rt.QMapOf(); ok {
gfs.imports["unsafe"] = struct{}{}
shouldReturn = "var " + namePrefix + "_mm C.struct_miqt_map = "
afterword += namePrefix + "_ret := make(map[" + kType.RenderTypeGo(gfs) + "]" + vType.RenderTypeGo(gfs) + ", int(" + namePrefix + "_mm.len))\n"
afterword += namePrefix + "_Keys := (*[0xffff]" + kType.parameterTypeCgo() + ")(unsafe.Pointer(" + namePrefix + "_mm.keys))\n"
afterword += namePrefix + "_Values := (*[0xffff]" + vType.parameterTypeCgo() + ")(unsafe.Pointer(" + namePrefix + "_mm.values))\n"
afterword += "for i := 0; i < int(" + namePrefix + "_mm.len); i++ {\n"
afterword += gfs.emitCabiToGo(namePrefix+"_entry_Key := ", kType, namePrefix+"_Keys[i]") + "\n"
afterword += gfs.emitCabiToGo(namePrefix+"_entry_Value := ", vType, namePrefix+"_Values[i]") + "\n"
afterword += namePrefix + "_ret[" + namePrefix + "_entry_Key] = " + namePrefix + "_entry_Value\n"
afterword += "}\n"
afterword += assignExpr + " " + namePrefix + "_ret\n"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if rt.QtClassType() {
// Construct our Go type based on this inner CABI type
shouldReturn = "" + namePrefix + "_ret := "

View File

@ -105,6 +105,9 @@ func (p CppParameter) IsFlagType() bool {
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
@ -142,9 +145,36 @@ func (p CppParameter) QListOf() (CppParameter, bool) {
return CppParameter{}, false
}
func (p CppParameter) QMapOf() bool {
return strings.HasPrefix(p.ParameterType, `QMap<`) ||
strings.HasPrefix(p.ParameterType, `QHash<`) // TODO support this
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() bool {

View File

@ -18,6 +18,12 @@ struct miqt_array {
void* data;
};
struct miqt_map {
size_t len;
void* keys;
void* values;
};
struct miqt_string* miqt_strdup(const char* src, size_t len);
typedef const char const_char;