genbindings: initial commit

This commit is contained in:
mappu 2024-08-06 13:03:23 +12:00
parent c6c8a4dbb7
commit f54814588e
6 changed files with 280 additions and 0 deletions

2
.gitignore vendored
View File

@ -2,4 +2,6 @@
qtbindingexperiment
cmd/bindings_test/direct
cmd/bindings_test/testapp
cmd/genbindings/genbindings

View File

@ -0,0 +1,85 @@
package main
import (
"errors"
"fmt"
)
func parseHeader(inner []interface{}) (*parsedHeader, error) {
var ret parsedHeader
/*
jb, err := json.MarshalIndent(inner, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(jb))
*/
fmt.Printf("package miqt\n\n")
for _, node := range inner {
node, ok := node.(map[string]interface{})
if !ok {
return nil, errors.New("inner[] element not an object")
}
kind, ok := node["kind"].(string)
if !ok {
return nil, errors.New("node has no kind")
}
switch kind {
case "CXXRecordDecl":
// Must have a name
nodename, ok := node["name"].(string)
if !ok {
return nil, errors.New("node has no name")
}
fmt.Printf("-> %q name=%q\n", kind, nodename)
if classInner, ok := node["inner"].([]interface{}); ok {
obj, err := processType(classInner)
if err != nil {
panic(err)
}
ret.classes = append(ret.classes, obj)
}
case "StaticAssertDecl":
// ignore
default:
return nil, fmt.Errorf("missing handling for clang ast node type %q", kind)
}
}
return &ret, nil // done
}
func processType(inner []interface{}) (nativeClass, error) {
var ret nativeClass
for _, node := range inner {
node, ok := node.(map[string]interface{})
if !ok {
return nativeClass{}, errors.New("inner[] element not an object")
}
kind, ok := node["kind"]
if !ok {
panic("inner element has no kind")
}
switch kind {
default:
fmt.Printf("==> %q\n", kind)
}
}
return ret, nil // done
}

View File

@ -0,0 +1,119 @@
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os/exec"
"sync"
)
func clangExec(ctx context.Context, clangBin, inputHeader string, cflags []string) ([]interface{}, error) {
clangArgs := []string{`-x`, `c++`}
clangArgs = append(clangArgs, cflags...)
clangArgs = append(clangArgs, `-Xclang`, `-ast-dump=json`, `-fsyntax-only`, inputHeader)
cmd := exec.CommandContext(ctx, clangBin, clangArgs...)
pr, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("StdoutPipe: %w", err)
}
err = cmd.Start()
if err != nil {
return nil, fmt.Errorf("Start: %w", err)
}
var wg sync.WaitGroup
var inner []interface{}
wg.Add(1)
go func() {
defer wg.Done()
inner__, err := clangStripUpToFile(pr, inputHeader)
if err != nil {
panic(err)
}
inner = inner__
}()
err = cmd.Wait()
if err != nil {
panic(err)
}
wg.Wait()
return inner, nil
}
// clangStripUpToFile strips all AST nodes from the clang output until we find
// one that really originated in the source file.
// This cleans out everything in the translation unit that came from an
// #included file.
// @ref https://stackoverflow.com/a/71128654
func clangStripUpToFile(stdout io.Reader, inputFilePath string) ([]interface{}, error) {
var obj = map[string]interface{}{}
err := json.NewDecoder(stdout).Decode(&obj)
if err != nil {
return nil, err
}
inner, ok := obj["inner"].([]interface{})
if !ok {
return nil, errors.New("no inner")
}
var markerPosition int = -1
for i, entry := range inner {
entry, ok := entry.(map[string]interface{})
if !ok {
return nil, errors.New("entry is not a map")
}
if _, ok := entry["isImplicit"]; ok {
continue
}
var match_filename = ""
if loc, ok := entry["loc"].(map[string]interface{}); ok {
if includedFrom, ok := loc["includedFrom"].(map[string]interface{}); ok {
if filename, ok := includedFrom["file"].(string); ok {
match_filename = filename
}
}
if match_filename == "" {
if expansionloc, ok := loc["expansionLoc"].(map[string]interface{}); ok {
if filename, ok := expansionloc["file"].(string); ok {
match_filename = filename
}
}
}
} else {
return nil, errors.New("no loc")
}
if match_filename == inputFilePath {
// Found the marker position
markerPosition = i
break
}
}
log.Printf("found first instance of same file at inner entry %d/%d", markerPosition, len(inner))
inner = inner[markerPosition:]
return inner, nil
}

View File

@ -0,0 +1,9 @@
package main
import (
"errors"
)
func emitGo(src *parsedHeader) (string, error) {
return "", errors.New("TODO")
}

View File

@ -0,0 +1,24 @@
package main
type nativeProperty struct {
propertyName string
propertyType string
visibility string
}
type nativeMethod struct {
methodName string
returnType string
parameters []string
}
type nativeClass struct {
className string
extends []string
methods []nativeMethod
props []nativeProperty
}
type parsedHeader struct {
classes []nativeClass
}

41
cmd/genbindings/main.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"strings"
)
func main() {
ctx := context.Background()
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`)")
flag.Parse()
// Parse the file
astInner, err := clangExec(ctx, *clang, *inputHeader, strings.Fields(*cflags))
if err != nil {
panic(err)
}
// Convert it to our intermediate format
parsed, err := parseHeader(astInner)
if err != nil {
panic(err)
}
// Emit Go code from the intermediate format
goSrc, err := emitGo(parsed)
if err != nil {
panic(err)
}
fmt.Println(goSrc)
log.Printf("Processing %q completed", inputHeader)
}