From 697577b7f4f3f11c7c1d20ae8ffce811df1c8b10 Mon Sep 17 00:00:00 2001 From: "Randall C. O'Reilly" Date: Thu, 28 Feb 2019 12:19:14 -0700 Subject: [PATCH] python callback fully implemented -- one last thing is dealing with return values properly.. --- _examples/funcs/funcs.go | 11 +++- _examples/funcs/test.py | 20 +++++-- bind/gen.go | 14 +++-- bind/gen_varconst.go | 6 ++ bind/stdtypes.go | 2 +- bind/symbols.go | 124 +++++++++++++++++++++++++++------------ 6 files changed, 128 insertions(+), 49 deletions(-) diff --git a/_examples/funcs/funcs.go b/_examples/funcs/funcs.go index 7407dc3..dabbf59 100644 --- a/_examples/funcs/funcs.go +++ b/_examples/funcs/funcs.go @@ -8,13 +8,20 @@ import "fmt" type FunStruct struct { FieldI int + FieldS string } -func (fs *FunStruct) CallBack(arg1 int, fun func(a1 int)) { - fun(arg1) +func (fs *FunStruct) CallBack(arg1 int, fun func(afs *FunStruct, a1 int, s1 string)) { + fun(fs, arg1, fs.FieldS) +} + +func (fs *FunStruct) CallBackIf(arg1 int, fun func(afs *FunStruct, a1 int, if1 interface{})) { + fun(fs, arg1, fs.FieldS) } func (fs *FunStruct) OtherMeth(arg1 int, args string) { + fs.FieldI = arg1 + fs.FieldS = args fmt.Printf("arg1: %d args: %s\n", arg1, args) } diff --git a/_examples/funcs/test.py b/_examples/funcs/test.py index 3222f52..f5bdb28 100644 --- a/_examples/funcs/test.py +++ b/_examples/funcs/test.py @@ -7,23 +7,33 @@ from __future__ import print_function from funcs import go, funcs -def cbfun(val): - print("in python cbfun: val", val) +def cbfun(afs, ival, sval): + tfs = funcs.FunStruct(handle=afs) + print("in python cbfun: FieldI: ", tfs.FieldI, " FieldS: ", tfs.FieldS, " ival: ", ival, " sval: ", sval) + +def cbfunif(afs, ival, ifval): + tfs = funcs.FunStruct(handle=afs) + print("in python cbfunif: FieldI: ", tfs.FieldI, " FieldS: ", tfs.FieldS, " ival: ", ival, " ifval: ", ifval) class MyClass(object): def __init__(self, *args, **kwargs): self.misc = 2 - def ClassFun(self, val): - print("in python class fun: val", val) + def ClassFun(self, afs, ival, sval): + tfs = funcs.FunStruct(handle=afs) + print("in python class fun: FieldI: ", tfs.FieldI, " FieldS: ", tfs.FieldS, " ival: ", ival, " sval: ", sval) fs = funcs.FunStruct() +fs.FieldS = "str field" +fs.FieldI = 42 fs.CallBack(22, cbfun) +fs.CallBackIf(22, cbfunif) + cls = MyClass() -fs.CallBack(22, cls.ClassFun) +fs.CallBack(32, cls.ClassFun) # print("funcs.GetF1()...") # f1 = funcs.GetF1() diff --git a/bind/gen.go b/bind/gen.go index b1da40b..68538a9 100644 --- a/bind/gen.go +++ b/bind/gen.go @@ -44,14 +44,20 @@ package main // btw, static inline is trick for avoiding need for extra .c file // the following are used for build value -- switch on reflect.Kind // or the types equivalent -static inline PyObject* py_build_int64(int64_t val) { +static inline PyObject* gopy_build_bool(uint8_t val) { + return Py_BuildValue("b", val); +} +static inline PyObject* gopy_build_int64(int64_t val) { return Py_BuildValue("k", val); } -static inline PyObject* py_build_float64(double val) { +static inline PyObject* gopy_build_uint64(uint64_t val) { + return Py_BuildValue("K", val); +} +static inline PyObject* gopy_build_float64(double val) { return Py_BuildValue("d", val); } -static inline PyObject* py_build_string(const char* val) { - return Py_BuildValue("d", val); +static inline PyObject* gopy_build_string(const char* val) { + return Py_BuildValue("s", val); } */ import "C" diff --git a/bind/gen_varconst.go b/bind/gen_varconst.go index 43802bb..6b098b2 100644 --- a/bind/gen_varconst.go +++ b/bind/gen_varconst.go @@ -12,6 +12,9 @@ func (g *pyGen) genConst(c *Const) { if isPyCompatVar(c.sym) != nil { return } + if c.sym.isSignature() { + return + } g.genConstValue(c) } @@ -19,6 +22,9 @@ func (g *pyGen) genVar(v *Var) { if isPyCompatVar(v.sym) != nil { return } + if v.sym.isSignature() { + return + } g.genVarGetter(v) if !v.sym.isArray() { g.genVarSetter(v) diff --git a/bind/stdtypes.go b/bind/stdtypes.go index 200dc87..4153745 100644 --- a/bind/stdtypes.go +++ b/bind/stdtypes.go @@ -320,7 +320,7 @@ func stdBasicTypes() map[string]*symbol { go2py: "C.CString", py2go: "C.GoString", zval: `""`, - pyfmt: "O&", + pyfmt: "s", }, "rune": { // FIXME(sbinet) py2/py3 diff --git a/bind/symbols.go b/bind/symbols.go index a9b3989..563ddfa 100644 --- a/bind/symbols.go +++ b/bind/symbols.go @@ -100,9 +100,6 @@ func isPyCompatVar(v *symbol) error { if v == nil { return fmt.Errorf("gopy: var symbol not found") } - // if v.isSignature() { - // return fmt.Errorf("gopy: var is function signature") - // } if v.isPointer() && v.isBasic() { return fmt.Errorf("gopy: var is pointer to basic type") } @@ -120,9 +117,7 @@ func isPyCompatVar(v *symbol) error { // isPyCompatType checks if type is compatible with python func isPyCompatType(typ types.Type) error { - // if _, isSig := typ.(*types.Signature); isSig { - // return fmt.Errorf("gopy: type is function signature") - // } + typ = typ.Underlying() if ptyp, isPtr := typ.(*types.Pointer); isPtr { if _, isBasic := ptyp.Elem().(*types.Basic); isBasic { return fmt.Errorf("gopy: type is pointer to basic type") @@ -146,6 +141,9 @@ func isPyCompatField(f *types.Var) (error, *symbol) { return fmt.Errorf("gopy: field not exported or is embedded"), nil } ftyp := current.symtype(f.Type()) + if _, isSig := f.Type().Underlying().(*types.Signature); isSig { + return fmt.Errorf("gopy: type is function signature"), nil + } return isPyCompatVar(ftyp), ftyp } @@ -189,6 +187,9 @@ func isPyCompatFunc(sig *types.Signature) (err error, ret types.Type, haserr, ha if err = isPyCompatType(ret); err != nil { return } + if _, isSig := ret.Underlying().(*types.Signature); isSig { + return + } } args := sig.Params() @@ -199,7 +200,7 @@ func isPyCompatFunc(sig *types.Signature) (err error, ret types.Type, haserr, ha if err = isPyCompatType(argt); err != nil { return } - if _, isSig := argt.(*types.Signature); isSig { + if _, isSig := argt.Underlying().(*types.Signature); isSig { if !hasfun { hasfun = true } else { @@ -554,6 +555,7 @@ func (sym *symtab) addSymbol(obj types.Object) error { return nil } +// processTuple ensures that all types in a tuple are in sym table func (sym *symtab) processTuple(tuple *types.Tuple) error { if tuple == nil { return nil @@ -572,6 +574,55 @@ func (sym *symtab) processTuple(tuple *types.Tuple) error { return nil } +// buildTuple returns a string of Go code that builds a PyTuple +// for the given tuple (e.g., function args). +// varnm is the name of the local variable for the built tuple +func (sym *symtab) buildTuple(tuple *types.Tuple, varnm string) (string, error) { + sz := tuple.Len() + if sz == 0 { + return "", fmt.Errorf("buildTuple: no elements") + } + bstr := fmt.Sprintf("%s := C.PyTuple_New(%d); ", varnm, sz) + for i := 0; i < sz; i++ { + v := tuple.At(i) + typ := v.Type() + anm := pySafeArg(v.Name(), i) + vsym := sym.symtype(typ) + if vsym == nil { + err := sym.addType(v, typ) + if err != nil { + return "", err + } + vsym = sym.symtype(typ) + if vsym == nil { + return "", fmt.Errorf("buildTuple: type still not found: %s", typ.String()) + } + } + bt, isb := typ.Underlying().(*types.Basic) + switch { + case vsym.hasHandle(): // note: assuming int64 handles + bstr += fmt.Sprintf("C.PyTuple_SetItem(%s, %d, C.gopy_build_int64(C.int64_t(%s(%s)%s))); ", varnm, i, vsym.go2py, anm, vsym.go2pyParenEx) + case isb: + bk := bt.Kind() + switch { + case bk >= types.Int && bk <= types.Int64: + bstr += fmt.Sprintf("C.PyTuple_SetItem(%s, %d, C.gopy_build_int64(C.int64_t(%s))); ", varnm, i, anm) + case bk >= types.Uint && bk <= types.Uintptr: + bstr += fmt.Sprintf("C.PyTuple_SetItem(%s, %d, C.gopy_build_uint64(C.uint64_t(%s))); ", varnm, i, anm) + case bk >= types.Float32 && bk <= types.Float64: + bstr += fmt.Sprintf("C.PyTuple_SetItem(%s, %d, C.gopy_build_float64(C.double(%s))); ", varnm, i, anm) + case bk == types.String: + bstr += fmt.Sprintf("C.PyTuple_SetItem(%s, %d, C.gopy_build_string(C.CString(%s))); ", varnm, i, anm) + case bk == types.Bool: + bstr += fmt.Sprintf("C.PyTuple_SetItem(%s, %d, C.gopy_build_bool(C.uint8_t(boolGoToPy(%s)))); ", varnm, i, anm) + } + default: + return "", fmt.Errorf("buildTuple: type not handled: %s", typ.String()) + } + } + return bstr, nil +} + // typeNamePkg gets the goname and package for a given types.Type -- deals with // naming for types in other packages, and adds those packages to imports paths. // Falls back on sym.pkg if no other package info avail. @@ -879,44 +930,43 @@ func (sym *symtab) addStructType(pkg *types.Package, obj types.Object, t types.T } func (sym *symtab) addSignatureType(pkg *types.Package, obj types.Object, t types.Type, kind symkind, id, n string) error { - fn := sym.fullTypeString(t) + fn := sym.fullTypeString(t.Underlying()) kind |= skSignature - // if (kind & skNamed) == 0 { - // id = hash(id) - // } - py2g := fmt.Sprintf("%s { ", fn) + py2g := fmt.Sprintf("%s { ", n) - sig := t.(*types.Signature) + sig := t.Underlying().(*types.Signature) args := sig.Params() + rets := sig.Results() + if rets.Len() > 0 { // todo 1 + return fmt.Errorf("multiple return values not supported") + } + retstr := "" + if rets.Len() == 1 { + retstr = "_fcret := " + } nargs := args.Len() - // todo: this va_list thing is when you're in a var arg function - // inside of c -- not for building dynamically - // need to use the ... var args version in C -- need to put - // all this arg building code into c.. :( if nargs > 0 { - py2g += fmt.Sprintf("_fcargs := C.PyTuple_New(%d); ", nargs) - for i := 0; i < nargs; i++ { - arg := args.At(i) - argt := arg.Type() - anm := pySafeArg(arg.Name(), i) - asym := sym.symtype(argt) - if asym == nil { - err := sym.addType(arg, argt) - if err != nil { - return err - } - asym = sym.symtype(argt) - if asym == nil { - return fmt.Errorf("type still not found: %s", argt.String()) - } - } - // todo: switch on type - py2g += fmt.Sprintf("C.PyTuple_SetItem(_fcargs, %d, C.py_build_int64(C.int64_t(%s))); ", i, anm) + bstr, err := sym.buildTuple(args, "_fcargs") + if err != nil { + return err } - py2g += fmt.Sprintf("C.PyObject_CallObject(_fun_arg, _fcargs) ") + py2g += bstr + retstr + py2g += fmt.Sprintf("C.PyObject_CallObject(_fun_arg, _fcargs); ") } else { - py2g += "C.PyObject_CallObject(_fun_arg, nil) " + py2g += retstr + "C.PyObject_CallObject(_fun_arg, nil); " + } + if rets.Len() == 1 { + ret := rets.At(0) + rsym := sym.symtype(ret.Type()) + if rsym == nil { + return fmt.Errorf("return type not supported: %s", n) + } + if rsym.py2go != "" { + py2g += fmt.Sprintf("return %s(_fcret)%s", rsym.py2go, rsym.py2goParenEx) + } else { + py2g += "return _fcret" + } } py2g += "}"