mirror of
https://github.com/mappu/miqt.git
synced 2024-12-22 08:58:37 +00:00
genbindings: initial commit
This commit is contained in:
parent
c6c8a4dbb7
commit
f54814588e
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,6 @@
|
|||||||
qtbindingexperiment
|
qtbindingexperiment
|
||||||
cmd/bindings_test/direct
|
cmd/bindings_test/direct
|
||||||
cmd/bindings_test/testapp
|
cmd/bindings_test/testapp
|
||||||
|
cmd/genbindings/genbindings
|
||||||
|
|
||||||
|
|
||||||
|
85
cmd/genbindings/clang2il.go
Normal file
85
cmd/genbindings/clang2il.go
Normal 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
|
||||||
|
}
|
119
cmd/genbindings/clangexec.go
Normal file
119
cmd/genbindings/clangexec.go
Normal 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
|
||||||
|
}
|
9
cmd/genbindings/emitgo.go
Normal file
9
cmd/genbindings/emitgo.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func emitGo(src *parsedHeader) (string, error) {
|
||||||
|
return "", errors.New("TODO")
|
||||||
|
}
|
24
cmd/genbindings/intermediate.go
Normal file
24
cmd/genbindings/intermediate.go
Normal 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
41
cmd/genbindings/main.go
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user