mirror of
https://github.com/mappu/miqt.git
synced 2024-12-22 00:48:38 +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
|
||||
cmd/bindings_test/direct
|
||||
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