617 lines
14 KiB
Go
617 lines
14 KiB
Go
package wrap
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitlab.wow.st/gmp/nswrap/ast"
|
|
"gitlab.wow.st/gmp/nswrap/types"
|
|
)
|
|
|
|
var (
|
|
Debug = false
|
|
)
|
|
|
|
type Wrapper struct {
|
|
Package string
|
|
Interfaces map[string]*Interface
|
|
Functions map[string]*Method
|
|
|
|
cCode strings.Builder // put cGo code here
|
|
goTypes strings.Builder // put Go type declarations here
|
|
goCode strings.Builder // put Go code here
|
|
goHelpers strings.Builder // put Go helper functions here
|
|
Processed map[string]bool
|
|
VaArgs int
|
|
}
|
|
|
|
func NewWrapper(debug bool) *Wrapper {
|
|
Debug = debug
|
|
if Debug { fmt.Println("// Debug mode") }
|
|
ret := &Wrapper{
|
|
Interfaces: map[string]*Interface{},
|
|
Functions: map[string]*Method{},
|
|
Processed: map[string]bool{},
|
|
VaArgs: 16,
|
|
}
|
|
ret.cCode.WriteString(`/*
|
|
#cgo CFLAGS: -x objective-c
|
|
#cgo LDFLAGS: -framework Foundation
|
|
`)
|
|
ret.goTypes.WriteString(`
|
|
type Id struct { }
|
|
func (o *Id) Ptr() unsafe.Pointer { return unsafe.Pointer(o) }
|
|
`)
|
|
return ret
|
|
}
|
|
|
|
func (w *Wrapper) Import(ss []string) {
|
|
for _,s := range ss {
|
|
w.cCode.WriteString(`
|
|
#import "` + s + `"
|
|
`)
|
|
}
|
|
}
|
|
|
|
func (w *Wrapper) SysImport(ss []string) {
|
|
for _,s := range ss {
|
|
w.cCode.WriteString("\n#import <" + s + ">\n")
|
|
}
|
|
}
|
|
|
|
func (w *Wrapper) Pragma(ss []string) {
|
|
for _,s := range ss {
|
|
w.cCode.WriteString("\n#pragma " + s + "\n")
|
|
}
|
|
}
|
|
|
|
type Property struct {
|
|
Name, Attr string
|
|
Type *types.Type
|
|
}
|
|
|
|
type Parameter struct {
|
|
Pname, Vname string
|
|
Type *types.Type
|
|
}
|
|
|
|
type Method struct {
|
|
Name, Class string
|
|
Type *types.Type
|
|
ClassMethod bool
|
|
Parameters []*Parameter
|
|
}
|
|
|
|
//isVoid() returns true if the method has no return value.
|
|
func (m Method) isVoid() bool {
|
|
return m.Type.CType() == "void"
|
|
}
|
|
|
|
//hasFunctionParam() returns true if a method has a function as a parameter.
|
|
func (m Method) hasFunctionParam() bool {
|
|
for _,p := range m.Parameters {
|
|
if p.Type.IsFunction() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (w Wrapper) cparamlist(m *Method) (string,string) {
|
|
ns := make([]string,0)
|
|
ret := make([]string,0)
|
|
if !m.ClassMethod {
|
|
ret = append(ret,"void* o")
|
|
}
|
|
for _,p := range m.Parameters {
|
|
var tp string
|
|
if p.Type.IsPointer() || p.Type.Variadic {
|
|
tp = "void*"
|
|
} else {
|
|
tp = p.Type.CType()
|
|
}
|
|
ns = append(ns,p.Vname)
|
|
ret = append(ret,fmt.Sprintf("%s %s",tp,p.Vname))
|
|
}
|
|
return strings.Join(ns,", "),strings.Join(ret,", ")
|
|
}
|
|
|
|
func (w Wrapper) objcparamlist(m *Method) string {
|
|
if len(m.Parameters) == 0 {
|
|
return m.Name
|
|
}
|
|
first := true
|
|
ret := []string{}
|
|
for _,p := range m.Parameters {
|
|
if first && !p.Type.Variadic {
|
|
ret = append(ret,m.Name + ":" + p.Vname)
|
|
first = false
|
|
} else {
|
|
if p.Type.Variadic {
|
|
str := []string{m.Name + ":arr[0]"}
|
|
for i := 1; i < w.VaArgs; i++ {
|
|
str = append(str,"arr["+strconv.Itoa(i)+"]")
|
|
}
|
|
str = append(str,"nil")
|
|
ret = append(ret, strings.Join(str,", "))
|
|
} else {
|
|
ret = append(ret, p.Pname + ":" + p.Vname)
|
|
}
|
|
}
|
|
}
|
|
return strings.Join(ret," ")
|
|
}
|
|
|
|
//goreserved is a map telling whether a word is a go reserved word that is not
|
|
//also a C/Objective-C reserved word.
|
|
var goreserved map[string]bool = map[string]bool{
|
|
"range": true,
|
|
}
|
|
|
|
func (w *Wrapper) gpntp(m *Method) ([]string,[]*types.Type,string) {
|
|
w.processType(m.Type)
|
|
ns := []string{}
|
|
tps := []*types.Type{}
|
|
if !m.ClassMethod {
|
|
ns = append(ns,"o")
|
|
tps = append(tps,types.NewTypeFromString(m.Class + "*",""))
|
|
}
|
|
for _,p := range m.Parameters {
|
|
gname := p.Vname
|
|
if goreserved[gname] {
|
|
gname = gname + "_"
|
|
}
|
|
ns = append(ns,gname)
|
|
tps = append(tps,p.Type)
|
|
}
|
|
w.processTypes(tps)
|
|
ret := []string{}
|
|
i := 0
|
|
if !m.ClassMethod { i = 1 }
|
|
for ; i < len(ns); i++ {
|
|
gt := tps[i].GoType()
|
|
if gt == "*Void" {
|
|
gt = "unsafe.Pointer"
|
|
}
|
|
if tps[i].Variadic {
|
|
gt = "..." + gt
|
|
ns[i] = ns[i] + "s"
|
|
}
|
|
ret = append(ret,ns[i] + " " + gt)
|
|
}
|
|
return ns, tps, strings.Join(ret,", ")
|
|
}
|
|
|
|
|
|
type Interface struct {
|
|
Name string
|
|
Properties map[string]*Property
|
|
Methods map[string]*Method
|
|
}
|
|
|
|
func (w *Wrapper) AddInterface(n *ast.ObjCInterfaceDecl) {
|
|
//fmt.Printf("ast.ObjCInterfaceDecl: %s\n",n.Name)
|
|
w.add(n.Name, n.Children())
|
|
}
|
|
|
|
func (w *Wrapper) AddCategory(n *ast.ObjCCategoryDecl) {
|
|
ns := n.Children()
|
|
if len(ns) > 0 {
|
|
switch x := ns[0].(type) {
|
|
case *ast.ObjCInterface:
|
|
w.add(x.Name, ns[1:])
|
|
return
|
|
}
|
|
}
|
|
fmt.Printf("Not adding methods for %s: interface name not found in first child node of category defclaration\n",n.Name)
|
|
}
|
|
|
|
func (w *Wrapper) AddFunction(n *ast.FunctionDecl) {
|
|
//treat functions as class methods with no class
|
|
tp := types.NewTypeFromString(n.Type,"")
|
|
m := &Method{
|
|
Name: n.Name,
|
|
Type: tp.ReturnType(),
|
|
Class: "",
|
|
ClassMethod: true,
|
|
Parameters: []*Parameter{},
|
|
}
|
|
f := tp.Node.Children[len(tp.Node.Children)-1] // Function node
|
|
if f.Kind != "Function" {
|
|
//fmt.Printf("AddFunction(%s): not a function -- Node type is %s\n%s",n.Name,f.Kind,tp.String())
|
|
return
|
|
}
|
|
//fmt.Printf("FunctionDecl: %s (%s) %s\n",n.Type,m.Type.CType(),n.Name)
|
|
i := 0
|
|
for _,c := range n.Children() {
|
|
switch x := c.(type) {
|
|
case *ast.ParmVarDecl:
|
|
p := &Parameter{
|
|
Vname: x.Name,
|
|
Type: types.NewTypeFromString(x.Type,""),
|
|
}
|
|
m.Parameters = append(m.Parameters,p)
|
|
i++
|
|
//fmt.Printf(" %s\n",p.Type.CType())
|
|
}
|
|
}
|
|
if i > 0 && len(f.Children) > i {
|
|
if e := f.Children[i]; len(e.Children) > 0 {
|
|
//fmt.Println(" Next parameter: ",e.Children[0].String())
|
|
m.Parameters[i-1].Type.Variadic = true
|
|
}
|
|
}
|
|
w.Functions[n.Name] = m
|
|
}
|
|
|
|
func (w *Wrapper) add(name string, ns []ast.Node) {
|
|
var i *Interface
|
|
var ok bool
|
|
if i,ok = w.Interfaces[name]; !ok {
|
|
i = &Interface{
|
|
Name: name,
|
|
Properties: map[string]*Property{},
|
|
Methods: map[string]*Method{},
|
|
}
|
|
}
|
|
tp := types.NewTypeFromString(name,name)
|
|
types.Wrap(tp.GoType())
|
|
var avail bool
|
|
for _,c := range ns {
|
|
switch x := c.(type) {
|
|
case *ast.ObjCPropertyDecl:
|
|
//fmt.Printf("ObjCPropertyDecl: %s\n",x.Name)
|
|
p := &Property{
|
|
Name: x.Name,
|
|
Type: types.NewTypeFromString(x.Type,name),
|
|
}
|
|
//_,avail = w.GetParms(x,name) // TODO
|
|
//if avail {
|
|
i.Properties[p.Name] = p
|
|
//}
|
|
case *ast.ObjCMethodDecl:
|
|
//fmt.Printf("ObjCMethodDecl: %s (%s) %s\n",x.Type,name,x.Name)
|
|
m := &Method{
|
|
Name: x.Name,
|
|
Type: types.NewTypeFromString(x.Type,name),
|
|
Class: name,
|
|
ClassMethod: x.ClassMethod,
|
|
}
|
|
//fmt.Println(m.Type.Node.String())
|
|
m.Parameters, avail = w.GetParms(x,name)
|
|
if avail {
|
|
i.Methods[m.Name] = m
|
|
}
|
|
case *ast.ObjCProtocol:
|
|
//fmt.Printf("ast.ObjCProtocol: %s\n",x.Name)
|
|
case *ast.ObjCInterface:
|
|
if x.Super {
|
|
//fmt.Printf("ast.ObjCInterface: %s inherits from %s\n",name,x.Name)
|
|
types.SetSuper(name,x.Name)
|
|
}
|
|
case *ast.ObjCTypeParamDecl:
|
|
//fmt.Printf("ObjCTypeParamDecl: %s = %s\n",x.Name,x.Type)
|
|
types.SetTypeParam(name,x.Name,x.Type)
|
|
case *ast.Unknown:
|
|
//fmt.Printf("(*ast.Unkonwn %s: %s)\n",x.Name,x.Content)
|
|
default:
|
|
//fmt.Println(reflect.TypeOf(x))
|
|
}
|
|
}
|
|
//Add class methods from super class
|
|
var supmethods func(*Interface,string)
|
|
supmethods = func(i *Interface, s string) {
|
|
if sup,ok := w.Interfaces[s]; !ok {
|
|
return
|
|
} else {
|
|
for _,m := range sup.Methods {
|
|
if !m.ClassMethod {
|
|
continue
|
|
}
|
|
m2 := &Method{
|
|
Name: m.Name,
|
|
Class: i.Name,
|
|
Type: m.Type.CloneToClass(i.Name),
|
|
ClassMethod: true,
|
|
Parameters: []*Parameter{},
|
|
}
|
|
for _,p := range m.Parameters {
|
|
m2.Parameters = append(m2.Parameters,
|
|
&Parameter{
|
|
Pname: p.Pname,
|
|
Vname: p.Vname,
|
|
Type: p.Type.CloneToClass(i.Name),
|
|
})
|
|
}
|
|
i.Methods[m.Name] = m2
|
|
}
|
|
}
|
|
supmethods(i,types.Super(s))
|
|
}
|
|
supmethods(i,types.Super(i.Name))
|
|
//fmt.Println("Add interface ",i.Name)
|
|
w.Interfaces[i.Name] = i
|
|
}
|
|
|
|
type AvailAttr struct {
|
|
OS, Version string
|
|
Deprecated bool
|
|
}
|
|
|
|
//GetParms returns the parameters of a method declaration and a bool
|
|
//indicating whether the given method is available on MacOS and not
|
|
//deprecated.
|
|
func (w *Wrapper) GetParms(n ast.Node,class string) ([]*Parameter,bool) {
|
|
ret := make([]*Parameter,0)
|
|
avail := make([]AvailAttr,0)
|
|
var parms []string
|
|
switch x := n.(type) {
|
|
case *ast.ObjCMethodDecl:
|
|
parms = x.Parameters
|
|
case *ast.FunctionDecl:
|
|
default:
|
|
panic("GetParms called with wrong node type")
|
|
}
|
|
j := 0
|
|
for _,c := range n.Children() {
|
|
switch x := c.(type) {
|
|
case *ast.ParmVarDecl:
|
|
p := &Parameter{
|
|
Vname: x.Name,
|
|
Type: types.NewTypeFromString(x.Type,class),
|
|
}
|
|
if parms != nil {
|
|
p.Pname = parms[j]
|
|
}
|
|
ret = append(ret,p)
|
|
j++
|
|
case *ast.Variadic:
|
|
ret[j-1].Type.Variadic = true
|
|
case *ast.AvailabilityAttr:
|
|
avail = append(avail,
|
|
AvailAttr{
|
|
OS: x.OS,
|
|
Version: x.Version,
|
|
Deprecated: x.Unknown1 != "0",
|
|
})
|
|
//fmt.Println("AvailAttr ",avail,x)
|
|
case *ast.UnavailableAttr:
|
|
avail = append(avail,
|
|
AvailAttr{ OS: "macos", Deprecated: true })
|
|
case *ast.Unknown:
|
|
if Debug { fmt.Printf("GetParms(): ast.Unknown: %s\n",x.Name) }
|
|
}
|
|
}
|
|
// check that the method is available for this OS and not deprecated
|
|
a := func() bool {
|
|
if len(avail) == 0 {
|
|
return true
|
|
}
|
|
for _,x := range avail {
|
|
if x.OS == "macos" && x.Deprecated == false {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}()
|
|
if !a {
|
|
return nil, false
|
|
}
|
|
// check that we found the right number of parameters
|
|
//if len(ret) != len(n.Parameters) {
|
|
// fmt.Printf("Error in method declaration %s: Wrong number of ParmVarDecl children: %d parameters but %d ParmVarDecl children\n",n.Name,len(n.Parameters),len(ret))
|
|
//}
|
|
return ret, true
|
|
}
|
|
|
|
func (w *Wrapper) processTypes(tps []*types.Type) {
|
|
switch len(tps) {
|
|
case 0:
|
|
return
|
|
case 1:
|
|
w.processType(tps[0])
|
|
default:
|
|
for _,tp := range tps {
|
|
w.processType(tp)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
func (w *Wrapper) processType(tp *types.Type) {
|
|
bt := tp.BaseType()
|
|
gt := bt.GoType()
|
|
if gt == "" {
|
|
return
|
|
}
|
|
if gt[0] == '*' {
|
|
w.processType(bt.PointsTo())
|
|
return
|
|
}
|
|
if w.Processed[gt] { return }
|
|
w.Processed[gt] = true
|
|
if gt == "Char" {
|
|
w.CharHelpers()
|
|
}
|
|
if bt.IsFunction() {
|
|
return
|
|
}
|
|
super := types.Super(gt)
|
|
if super != "" {
|
|
types.Wrap(super)
|
|
w.processType(types.NewTypeFromString(super,""))
|
|
}
|
|
w.goTypes.WriteString(bt.GoTypeDecl())
|
|
}
|
|
|
|
func (w *Wrapper) CharHelpers() {
|
|
w.goHelpers.WriteString(`
|
|
func CharFromString(s string) *Char {
|
|
return (*Char)(unsafe.Pointer(C.CString(s)))
|
|
}
|
|
|
|
func CharFromBytes(b []byte) *Char {
|
|
return (*Char)(unsafe.Pointer(C.CString(string(b))))
|
|
}
|
|
|
|
func (c *Char) String() string {
|
|
return C.GoString((*C.char)(c))
|
|
}
|
|
`)
|
|
}
|
|
|
|
func (w *Wrapper) ProcessMethod(m *Method) {
|
|
w._processMethod(m,false)
|
|
}
|
|
|
|
func (w *Wrapper) ProcessFunction(m *Method) {
|
|
w._processMethod(m,true)
|
|
}
|
|
|
|
func (w *Wrapper) _processMethod(m *Method,fun bool) {
|
|
if Debug {
|
|
fmt.Printf(" method: %s (%s)\n", m.Name, m.Type)
|
|
}
|
|
if m.Type.IsFunction() || m.hasFunctionParam() {
|
|
return
|
|
}
|
|
gname := strings.Title(m.Name)
|
|
if !m.ClassMethod {
|
|
gname = "(o *" + m.Class + ") " + gname
|
|
} else {
|
|
gname = m.Class + gname
|
|
}
|
|
cname := m.Name
|
|
if m.Class != "" {
|
|
cname = m.Class + "_" + cname
|
|
}
|
|
|
|
cmtype := m.Type.CTypeAttrib()
|
|
ns,tps,gplist := w.gpntp(m)
|
|
grtype := m.Type.GoType()
|
|
if grtype == "Void" {
|
|
grtype = ""
|
|
}
|
|
w.goCode.WriteString(fmt.Sprintf(`
|
|
//%s
|
|
func %s(%s) %s {
|
|
`,m.Type.CType(),gname,gplist,grtype))
|
|
lparm := len(tps)-1
|
|
if len(tps) > 0 && tps[lparm].Variadic {
|
|
vn := ns[lparm]
|
|
vn = vn[:len(vn)-1]
|
|
ns[lparm] = vn
|
|
w.goCode.WriteString(fmt.Sprintf(
|
|
` var %s [%d]unsafe.Pointer
|
|
for i,o := range %ss {
|
|
%s[i] = o.Ptr()
|
|
}
|
|
`,vn,w.VaArgs,vn,vn))
|
|
}
|
|
w.goCode.WriteString(` ` +
|
|
types.GoToC(cname,ns,m.Type,tps) + "\n}\n\n")
|
|
|
|
cret := ""
|
|
if !m.isVoid() {
|
|
cret = "return "
|
|
}
|
|
var cobj string
|
|
if m.ClassMethod {
|
|
cobj = m.Class
|
|
} else {
|
|
cobj = "(id)o"
|
|
}
|
|
cns,cntps := w.cparamlist(m)
|
|
_ = cns
|
|
if fun {
|
|
return
|
|
}
|
|
w.cCode.WriteString(fmt.Sprintf(`
|
|
%s
|
|
%s(%s) {
|
|
`, cmtype, cname, cntps))
|
|
if len(tps) > 0 && tps[lparm].Variadic {
|
|
w.cCode.WriteString(fmt.Sprintf(
|
|
` %s* arr = %s;
|
|
`, tps[lparm].CType(), ns[lparm]))
|
|
}
|
|
if fun {
|
|
w.cCode.WriteString(fmt.Sprintf(` %s%s(%s);
|
|
}`, cret, m.Name, cns))
|
|
} else {
|
|
w.cCode.WriteString(fmt.Sprintf(` %s[%s %s];
|
|
}`, cret, cobj, w.objcparamlist(m)))
|
|
}
|
|
}
|
|
|
|
func (w *Wrapper) Wrap(toproc []string) {
|
|
if w.Package == "" { w.Package = "ns" }
|
|
err := os.MkdirAll(w.Package,0755)
|
|
if err != nil {
|
|
fmt.Printf("Error creating directory '%s'\n%s\n",w.Package,err)
|
|
os.Exit(-1)
|
|
}
|
|
of,err := os.Create(path.Join(w.Package,"main.go"))
|
|
if err != nil {
|
|
fmt.Printf("Error opening file %s\n%s\n",path.Join(w.Package,"main.go"),err)
|
|
os.Exit(-1)
|
|
}
|
|
fmt.Printf("Writing output to %s\n",path.Join(w.Package,"main.go"))
|
|
pInterfaces := map[string]*Interface{}
|
|
for _,iface := range toproc {
|
|
pInterfaces[iface] = w.Interfaces[iface]
|
|
}
|
|
//FIXME: sort pInterfaces
|
|
for _,i := range pInterfaces {
|
|
fmt.Printf("Interface %s: %d properties, %d methods\n",
|
|
i.Name, len(i.Properties), len(i.Methods))
|
|
|
|
w.goCode.WriteString(fmt.Sprintf(`
|
|
func New%s() *%s {
|
|
return (*%s)(unsafe.Pointer(C.New%s()))
|
|
}
|
|
`,i.Name,i.Name,i.Name,i.Name))
|
|
|
|
w.cCode.WriteString(fmt.Sprintf(`
|
|
%s*
|
|
New%s() {
|
|
return [%s alloc];
|
|
}
|
|
`, i.Name, i.Name, i.Name))
|
|
|
|
//FIXME: sort properties
|
|
for _,p := range i.Properties {
|
|
if Debug {
|
|
fmt.Printf(" property: %s (%s)\n", p.Name, p.Type.CType())
|
|
}
|
|
}
|
|
//FIXME: sort methods
|
|
for _,m := range i.Methods {
|
|
w.ProcessMethod(m)
|
|
|
|
}
|
|
}
|
|
for _,m := range w.Functions {
|
|
//fmt.Printf("Processing function %s %s\n",m.Type.CType(),m.Name)
|
|
w.ProcessFunction(m)
|
|
}
|
|
fmt.Printf("%d functions\n", len(w.Functions))
|
|
of.WriteString("package " + w.Package + "\n\n")
|
|
of.WriteString(w.cCode.String())
|
|
of.WriteString(`
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"unsafe"
|
|
)
|
|
`)
|
|
of.WriteString(w.goTypes.String())
|
|
of.WriteString(w.goHelpers.String())
|
|
of.WriteString(w.goCode.String())
|
|
of.Close()
|
|
}
|