genbindings: wip

This commit is contained in:
mappu 2024-08-06 14:29:12 +12:00
parent 559aca01b2
commit 7319683a3f
5 changed files with 189 additions and 11 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
# scratch files
*.json
gen_*
# binaries
miqt

View File

@ -3,6 +3,8 @@ package main
import (
"errors"
"fmt"
"log"
"strings"
)
func parseHeader(inner []interface{}) (*parsedHeader, error) {
@ -42,7 +44,7 @@ func parseHeader(inner []interface{}) (*parsedHeader, error) {
fmt.Printf("-> %q name=%q\n", kind, nodename)
if classInner, ok := node["inner"].([]interface{}); ok {
obj, err := processType(classInner)
obj, err := processType(classInner, nodename)
if err != nil {
panic(err)
}
@ -61,9 +63,11 @@ func parseHeader(inner []interface{}) (*parsedHeader, error) {
return &ret, nil // done
}
func processType(inner []interface{}) (nativeClass, error) {
func processType(inner []interface{}, className string) (nativeClass, error) {
var ret nativeClass
ret.className = className
nextMethod:
for _, node := range inner {
node, ok := node.(map[string]interface{})
if !ok {
@ -76,8 +80,69 @@ func processType(inner []interface{}) (nativeClass, error) {
}
switch kind {
case "CXXMethodDecl":
// Method
methodName, ok := node["name"].(string)
if !ok {
return nativeClass{}, errors.New("method has no name")
}
var mm nativeMethod
mm.methodName = methodName
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
if strings.Contains(qualType, `::`) {
log.Printf("Skipping method %q with complex type %q", mm.methodName, qualType)
continue nextMethod
}
// We only want up to the first ( character
mm.returnType, _, _ = strings.Cut(qualType, `(`)
mm.returnType = strings.TrimSpace(mm.returnType)
}
}
if methodInner, ok := node["inner"].([]interface{}); ok {
for _, methodObj := range methodInner {
methodObj, ok := methodObj.(map[string]interface{})
if !ok {
return nativeClass{}, 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 == "" {
parmName = fmt.Sprintf("param%d", len(mm.parameters)+1)
}
var parmType string
if typobj, ok := node["type"].(map[string]interface{}); ok {
if qualType, ok := typobj["qualType"].(string); ok {
parmType = qualType
}
}
mm.parameters = append(mm.parameters, nativeParameter{
name: parmName,
typ: parmType,
})
default:
// Something else inside a declaration??
fmt.Printf("==> NOT IMPLEMENTED CXXMethodDecl->%q\n", kind)
}
}
}
ret.methods = append(ret.methods, mm)
default:
fmt.Printf("==> %q\n", kind)
fmt.Printf("==> NOT IMPLEMENTED %q\n", kind)
}
}

View File

@ -1,9 +1,86 @@
package main
import (
"errors"
"fmt"
"strings"
)
func emitGo(src *parsedHeader) (string, error) {
return "", errors.New("TODO")
func emitParametersCpp(params []nativeParameter) string {
tmp := make([]string, 0, len(params))
for _, p := range params {
tmp = append(tmp, p.name+" "+p.typ)
}
return strings.Join(tmp, ", ")
}
func emitParametersGo(params []nativeParameter) string {
tmp := make([]string, 0, len(params))
for _, p := range params {
tmp = append(tmp, p.typ+" "+p.name)
}
return strings.Join(tmp, ", ")
}
func emitBindingHeader(src *parsedHeader, filename string) (string, error) {
ret := strings.Builder{}
includeGuard := strings.ToUpper(strings.Replace(filename, `.`, `_`, -1)) + "_H"
ret.WriteString(`#ifndef ` + includeGuard + `
#define ` + includeGuard + `
#ifdef __cplusplus
extern "C" {
#endif
`)
for _, c := range src.classes {
ret.WriteString(`typedef void* P` + c.className + ";\n\n")
for i, ctor := range c.ctors {
suffix := ""
if i > 0 {
suffix = fmt.Sprintf("%d", i+1)
}
// TODO fixup parameters
ret.WriteString(fmt.Sprintf("P%s %s_new%s(%s);\n", c.className, suffix, emitParametersCpp(ctor.parameters)))
}
for _, m := range c.methods {
ret.WriteString(fmt.Sprintf("%s %s_%s(%s);\n", m.returnType, c.className, m.methodName, emitParametersCpp(m.parameters)))
}
ret.WriteString("\n")
}
ret.WriteString(`
#ifdef __cplusplus
} /* extern C */
#endif
#endif
`)
return ret.String(), nil
}
func emitBindingCpp(src *parsedHeader) (string, error) {
ret := strings.Builder{}
return ret.String(), nil
}
func emitGo(src *parsedHeader) (string, error) {
ret := strings.Builder{}
ret.WriteString("package miqt\n\n")
// CGO block
// Pure-Go block
for _, c := range src.classes {
_ = c
}
return ret.String(), nil
}

View File

@ -1,5 +1,10 @@
package main
type nativeParameter struct {
name string
typ string
}
type nativeProperty struct {
propertyName string
propertyType string
@ -9,11 +14,12 @@ type nativeProperty struct {
type nativeMethod struct {
methodName string
returnType string
parameters []string
parameters []nativeParameter
}
type nativeClass struct {
className string
ctors []nativeMethod // only use the parameters
extends []string
methods []nativeMethod
props []nativeProperty

View File

@ -3,8 +3,9 @@ package main
import (
"context"
"flag"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"strings"
)
@ -14,6 +15,7 @@ func main() {
clang := flag.String("clang", "clang", "Custom path to clang")
inputHeader := flag.String("inputHeader", `/usr/include/x86_64-linux-gnu/qt5/QtWidgets/qpushbutton.h`, "Input file")
cflags := flag.String("cflags", `-DQT_WIDGETS_LIB -I/usr/include/x86_64-linux-gnu/qt5/QtWidgets -I/usr/include/x86_64-linux-gnu/qt5 -I/usr/include/x86_64-linux-gnu/qt5/QtCore -DQT_GUI_LIB -I/usr/include/x86_64-linux-gnu/qt5/QtGui -DQT_CORE_LIB`, "Cflags to pass to clang (e.g. `pkg-config --cflags Qt5Widgets`)")
outDir := flag.String("outdir", "..", "Output directory for generated gen_** files")
flag.Parse()
@ -29,13 +31,40 @@ func main() {
panic(err)
}
// Emit Go code from the intermediate format
// Emit 3 code files from the intermediate format
outputName := filepath.Join(*outDir, "gen_"+strings.TrimSuffix(filepath.Base(*inputHeader), `.h`))
goSrc, err := emitGo(parsed)
if err != nil {
panic(err)
}
fmt.Println(goSrc)
err = ioutil.WriteFile(outputName+".go", []byte(goSrc), 0644)
if err != nil {
panic(err)
}
log.Printf("Processing %q completed", inputHeader)
bindingCppSrc, err := emitBindingCpp(parsed)
if err != nil {
panic(err)
}
err = ioutil.WriteFile(outputName+".cpp", []byte(bindingCppSrc), 0644)
if err != nil {
panic(err)
}
bindingHSrc, err := emitBindingHeader(parsed, filepath.Base(*inputHeader))
if err != nil {
panic(err)
}
err = ioutil.WriteFile(outputName+".h", []byte(bindingHSrc), 0644)
if err != nil {
panic(err)
}
// Done
log.Printf("Processing %q completed", *inputHeader)
}