genbindings: further work on output generation

This commit is contained in:
mappu 2024-08-07 18:51:51 +12:00
parent 0514866d23
commit 3a802d0f5a
6 changed files with 233 additions and 64 deletions

View File

@ -96,6 +96,9 @@ nextMethod:
panic("unexpected access visibility '" + access + "'") panic("unexpected access visibility '" + access + "'")
} }
case "CXXConstructorDecl", "":
// panic("TODO")
case "CXXMethodDecl": case "CXXMethodDecl":
if !visibility { if !visibility {
continue // Skip private/protected 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{ mm.parameters = append(mm.parameters, nativeParameter{
name: parmName, name: parmName,
typ: parmType, typ: parmType,

107
cmd/genbindings/emitcabi.go Normal file
View File

@ -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 <stdbool.h>
#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
}

View File

@ -1,18 +1,11 @@
package main package main
import ( import (
"fmt" "go/format"
"log"
"strings" "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 { func emitParametersGo(params []nativeParameter) string {
tmp := make([]string, 0, len(params)) tmp := make([]string, 0, len(params))
for _, p := range params { for _, p := range params {
@ -21,66 +14,75 @@ func emitParametersGo(params []nativeParameter) string {
return strings.Join(tmp, ", ") 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) { func emitGo(src *parsedHeader) (string, error) {
ret := strings.Builder{} 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 { 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
} }

View File

@ -1,5 +1,9 @@
package main package main
import (
"strings"
)
type nativeParameter struct { type nativeParameter struct {
name string name string
typ string typ string
@ -17,6 +21,40 @@ type nativeMethod struct {
parameters []nativeParameter 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 { type nativeClass struct {
className string className string
ctors []nativeMethod // only use the parameters ctors []nativeMethod // only use the parameters

View File

@ -67,7 +67,7 @@ func main() {
panic(err) panic(err)
} }
bindingCppSrc, err := emitBindingCpp(parsed) bindingCppSrc, err := emitBindingCpp(parsed, filepath.Base(*inputHeader))
if err != nil { if err != nil {
panic(err) panic(err)
} }

13
cmd/genbindings/util.go Normal file
View File

@ -0,0 +1,13 @@
package main
import (
"fmt"
)
func maybeSuffix(counter int) string {
if counter == 0 {
return ""
}
return fmt.Sprintf("%d", counter+1)
}