diff --git a/.gitignore b/.gitignore index 5ec7b15..810e53c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # scratch files *.json +gen_* # binaries miqt diff --git a/cmd/genbindings/clang2il.go b/cmd/genbindings/clang2il.go index e432458..c647a3f 100644 --- a/cmd/genbindings/clang2il.go +++ b/cmd/genbindings/clang2il.go @@ -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) } } diff --git a/cmd/genbindings/emitgo.go b/cmd/genbindings/emitgo.go index cbff4c0..ae2c661 100644 --- a/cmd/genbindings/emitgo.go +++ b/cmd/genbindings/emitgo.go @@ -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 } diff --git a/cmd/genbindings/intermediate.go b/cmd/genbindings/intermediate.go index d6735ac..914344f 100644 --- a/cmd/genbindings/intermediate.go +++ b/cmd/genbindings/intermediate.go @@ -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 diff --git a/cmd/genbindings/main.go b/cmd/genbindings/main.go index 836c65d..1e8200a 100644 --- a/cmd/genbindings/main.go +++ b/cmd/genbindings/main.go @@ -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) }