diff --git a/cmd/genbindings/clang2il.go b/cmd/genbindings/clang2il.go index 760f9cc..7aa12e4 100644 --- a/cmd/genbindings/clang2il.go +++ b/cmd/genbindings/clang2il.go @@ -96,6 +96,9 @@ nextMethod: panic("unexpected access visibility '" + access + "'") } + case "CXXConstructorDecl", "": + // panic("TODO") + case "CXXMethodDecl": if !visibility { continue // Skip private/protected @@ -147,6 +150,12 @@ nextMethod: } } + // TODO fixup parameters + // Reference -> pointer + // Remove const + // Remove extra () -- if there are more than expected, skip method with complex type + // If this parameter is optional, expand it into multiple function overloads + mm.parameters = append(mm.parameters, nativeParameter{ name: parmName, typ: parmType, diff --git a/cmd/genbindings/emitcabi.go b/cmd/genbindings/emitcabi.go new file mode 100644 index 0000000..9f18c08 --- /dev/null +++ b/cmd/genbindings/emitcabi.go @@ -0,0 +1,107 @@ +package main + +import ( + "fmt" + "strings" +) + +func emitParametersCpp(params []nativeParameter, selfType string) string { + tmp := make([]string, 0, len(params)+1) + + if selfType != "" { + tmp = append(tmp, "self "+selfType) + } + + for _, p := range params { + tmp = append(tmp, p.name+" "+p.typ) + } + return strings.Join(tmp, ", ") +} + +func emitParametersNames(params []nativeParameter, selfType string) string { + tmp := make([]string, 0, len(params)+1) + + if selfType != "" { + tmp = append(tmp, "self") + } + + for _, p := range params { + tmp = append(tmp, 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 + ` + +#include + +#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 { + ret.WriteString(fmt.Sprintf("P%s %s_new%s(%s);\n", c.className, maybeSuffix(i), emitParametersCpp(ctor.parameters, ""))) + } + + for _, m := range c.methods { + ret.WriteString(fmt.Sprintf("%s %s_%s(%s);\n", m.returnType, c.className, m.SafeMethodName(), emitParametersCpp(m.parameters, "P"+c.className))) + } + + ret.WriteString("\n") + } + + ret.WriteString(` + +#ifdef __cplusplus +} /* extern C */ +#endif + +#endif +`) + return ret.String(), nil +} + +func emitBindingCpp(src *parsedHeader, filename string) (string, error) { + ret := strings.Builder{} + + ret.WriteString(`#include "gen_` + filename + `" +#include "` + filename + `" + +`) + + for _, c := range src.classes { + + for i, ctor := range c.ctors { + ret.WriteString(fmt.Sprintf( + "P%s %s_new%s(%s) {\n\treturn new %s(%s);\n}\n\n", c.className, maybeSuffix(i), emitParametersCpp(ctor.parameters, ""), + c.className, emitParametersNames(ctor.parameters, ""), + )) + } + + for _, m := range c.methods { + // Need to take an extra 'self' parameter + + shouldReturn := "return " + if m.returnType == "void" { + shouldReturn = "" + } + + ret.WriteString(fmt.Sprintf("%s %s_%s(%s) {\n\t%sstatic_cast<%s*>(self)->%s(%s);\n}\n\n", m.returnType, c.className, m.SafeMethodName(), emitParametersCpp(m.parameters, "P"+c.className), + shouldReturn, c.className, m.methodName, emitParametersNames(m.parameters, c.className), + )) + } + } + + return ret.String(), nil +} diff --git a/cmd/genbindings/emitgo.go b/cmd/genbindings/emitgo.go index ae2c661..ab71838 100644 --- a/cmd/genbindings/emitgo.go +++ b/cmd/genbindings/emitgo.go @@ -1,18 +1,11 @@ package main import ( - "fmt" + "go/format" + "log" "strings" ) -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 { @@ -21,66 +14,75 @@ func emitParametersGo(params []nativeParameter) string { 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") + ret.WriteString(`package miqt - // CGO block +/* + +#cgo CFLAGS: -fPIC +#cgo pkg-config: Qt5Widgets +#include "binding.h" + +*/ +import "C" + +`) - // Pure-Go block for _, c := range src.classes { - _ = c + + ret.WriteString(` + type ` + c.className + ` struct { + h C.P` + c.className + ` + } + + func (this *` + c.className + `) cPointer() C.P` + c.className + ` { + if this == nil { + return nil + } + return this.h + } + + `) + + for i, ctor := range c.ctors { + ret.WriteString(` + // New` + c.className + maybeSuffix(i) + ` constructs a new ` + c.className + ` object. + func New` + c.className + maybeSuffix(i) + `(` + emitParametersGo(ctor.parameters) + `) { + ret := C.` + c.className + `_new` + maybeSuffix(i) + `(` + emitParametersNames(ctor.parameters, "") + `) + return &` + c.className + `{h: ret} + } + + `) + } + + for _, m := range c.methods { + // TODO for any known pointer type, call its cPointer() method instead of passing it directly + + shouldReturn := "return " + returnTypeDecl := m.returnType + if returnTypeDecl == "void" { + shouldReturn = "" + returnTypeDecl = "" + } + + ret.WriteString(` + func (this *` + c.className + `) ` + m.methodName + `(` + emitParametersGo(m.parameters) + `) ` + returnTypeDecl + ` { + ` + shouldReturn + ` C.` + c.className + `_` + m.SafeMethodName() + `(` + emitParametersNames(m.parameters, c.className) + `) + } + + `) + } + } - return ret.String(), nil + // Run gofmt over the result + formattedSrc, err := format.Source([]byte(ret.String())) + if err != nil { + log.Printf("gofmt failure: %v", err) + formattedSrc = []byte(ret.String()) + } + + return string(formattedSrc), nil } diff --git a/cmd/genbindings/intermediate.go b/cmd/genbindings/intermediate.go index 914344f..2bc54ed 100644 --- a/cmd/genbindings/intermediate.go +++ b/cmd/genbindings/intermediate.go @@ -1,5 +1,9 @@ package main +import ( + "strings" +) + type nativeParameter struct { name string typ string @@ -17,6 +21,40 @@ type nativeMethod struct { parameters []nativeParameter } +func (nm nativeMethod) SafeMethodName() string { + // Operator-overload methods have names not representable in binding + // languages. Replace more specific cases first + replacer := strings.NewReplacer( + + `==`, `Equal`, + `>=`, `GreaterOrEqual`, + `<=`, `LesserOrEqual`, + `=`, `Assign`, + `>`, `Greater`, + `<`, `Lesser`, + + `+`, `Plus`, + `-`, `Minus`, + `*`, `Multiply`, + `/`, `Divide`, + `%`, `Modulo`, + + `&&`, `LogicalAnd`, + `||`, `LogicalOr`, + `!`, `Not`, + `&`, `BitwiseAnd`, + `|`, `BitwiseOr`, + `~`, `BitwiseXor`, + `^`, `BitwiseNot`, + + `->`, `PointerDereference`, + `[]`, `Subscript`, + `()`, `Call`, + ) + + return replacer.Replace(nm.methodName) +} + type nativeClass struct { className string ctors []nativeMethod // only use the parameters diff --git a/cmd/genbindings/main.go b/cmd/genbindings/main.go index 1be3352..d3bc467 100644 --- a/cmd/genbindings/main.go +++ b/cmd/genbindings/main.go @@ -67,7 +67,7 @@ func main() { panic(err) } - bindingCppSrc, err := emitBindingCpp(parsed) + bindingCppSrc, err := emitBindingCpp(parsed, filepath.Base(*inputHeader)) if err != nil { panic(err) } diff --git a/cmd/genbindings/util.go b/cmd/genbindings/util.go new file mode 100644 index 0000000..0760ef6 --- /dev/null +++ b/cmd/genbindings/util.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" +) + +func maybeSuffix(counter int) string { + if counter == 0 { + return "" + } + + return fmt.Sprintf("%d", counter+1) +}