cffi: Support named type + Pass 'seq.go' test.

* Pass 'named.go' test.
* Support to generate named type includes slice and array types.
* Add doc.go for bind package.
* Add comments.
* Support __iadd__ for Go slice types.
* Add a 'seq.go' test for the CFFI engine.
This commit is contained in:
Dong-hee Na 2017-07-13 11:03:40 +00:00
parent 3b8a754b6c
commit c2676d7f14
8 changed files with 512 additions and 14 deletions

View File

@ -6,6 +6,9 @@
from __future__ import print_function
import named
import sys
_PY2 = sys.version_info[0] == 2
### test docs
print("doc(named): %r" % (named.__doc__,))
@ -96,13 +99,14 @@ try:
print("arr = named.Array(range(10))")
arr = named.Array(range(10))
print("arr = %s" % (arr,))
except Exception, err:
print("caught: %s" % (err,))
except Exception as err:
print("caught: %s" % (str(err),))
pass
print("arr = named.Array(xrange(2))")
arr = named.Array(xrange(2))
print("arr = %s" % (arr,))
if _PY2:
print("arr = named.Array(xrange(2))")
arr = named.Array(xrange(2))
print("arr = %s" % (arr,))
print("s = named.Slice()")
s = named.Slice()
@ -116,7 +120,7 @@ print("s = named.Slice(range(10))")
s = named.Slice(range(10))
print("s = %s" % (s,))
print("s = named.Slice(xrange(10))")
s = named.Slice(xrange(10))
print("s = %s" % (s,))
if _PY2:
print("s = named.Slice(xrange(10))")
s = named.Slice(xrange(10))
print("s = %s" % (s,))

6
bind/doc.go Normal file
View File

@ -0,0 +1,6 @@
// Copyright 2017 The go-python Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package bind provides tools to generate bindings to use Go from Python.
package bind

View File

@ -16,6 +16,7 @@ const (
// """)
// discuss: https://github.com/go-python/gopy/pull/93#discussion_r119652220
cffiPreamble = `"""%[1]s"""
import collections
import os
import sys
import cffi as _cffi_backend
@ -56,6 +57,7 @@ extern void cgopy_incref(void* p0);
extern void cgopy_decref(void* p0);
extern void cgo_pkg_%[2]s_init();
`
cffiHelperPreamble = `""")
@ -230,6 +232,17 @@ func (g *cffiGen) genWrappedPy() {
n := g.pkg.pkg.Name()
g.wrapper.Printf(cffiHelperPreamble, n)
g.wrapper.Indent()
// first, process slices, arrays
names := g.pkg.syms.names()
for _, n := range names {
sym := g.pkg.syms.sym(n)
if !sym.isType() {
continue
}
g.genTypeConverter(sym)
}
for _, s := range g.pkg.structs {
g.genStructConversion(s)
}
@ -240,6 +253,14 @@ func (g *cffiGen) genWrappedPy() {
g.wrapper.Printf("# make sure Cgo is loaded and initialized\n")
g.wrapper.Printf("_cffi_helper.lib.cgo_pkg_%[1]s_init()\n", n)
for _, n := range names {
sym := g.pkg.syms.sym(n)
if !sym.isType() {
continue
}
g.genType(sym)
}
for _, s := range g.pkg.structs {
g.genStruct(s)
}

View File

@ -5,18 +5,22 @@
package bind
import (
"fmt"
"go/types"
"strconv"
"strings"
)
// genCdefStruct generates C definitions for a Go struct.
func (g *cffiGen) genCdefStruct(s Struct) {
g.wrapper.Printf("// A type definition of the %[1]s.%[2]s for wrapping.\n", s.Package().Name(), s.sym.cgoname)
g.wrapper.Printf("typedef void* %s;\n", s.sym.cgoname)
g.wrapper.Printf("extern void* cgo_func_%s_new();\n", s.sym.id)
}
// genCdefType generates C definitions for a Go type.
func (g *cffiGen) genCdefType(sym *symbol) {
g.wrapper.Printf("// C definitions for Go type %[1]s.%[2]s.\n", g.pkg.pkg.Name(), sym.id)
if !sym.isType() {
return
}
@ -29,14 +33,64 @@ func (g *cffiGen) genCdefType(sym *symbol) {
return
}
g.genTypeCdefInit(sym)
if sym.isNamed() {
typ := sym.GoType().(*types.Named)
for imeth := 0; imeth < typ.NumMethods(); imeth++ {
m := typ.Method(imeth)
if !m.Exported() {
continue
}
mname := types.ObjectString(m, nil)
msym := g.pkg.syms.sym(mname)
if msym == nil {
panic(fmt.Errorf(
"gopy: could not find symbol for [%[1]T] (%#[1]v) (%[2]s)",
m.Type(),
m.Name()+" || "+m.FullName(),
))
}
g.genTypeCdefFunc(sym, msym)
}
}
g.genCdefTypeTPStr(sym)
g.wrapper.Printf("\n")
}
// genTypeCdefInit generates cgo_func_XXXX_new() for a Go type.
func (g *cffiGen) genTypeCdefInit(sym *symbol) {
if sym.isBasic() {
btyp := g.pkg.syms.symtype(sym.GoType().Underlying())
g.wrapper.Printf("typedef %s %s;\n\n", btyp.cgoname, sym.cgoname)
g.wrapper.Printf("typedef %[1]s %[2]s;\n", btyp.cgoname, sym.cgoname)
g.wrapper.Printf("extern %[1]s cgo_func_%[2]s_new();\n", btyp.cgoname, sym.id)
} else {
g.wrapper.Printf("typedef void* %s;\n\n", sym.cgoname)
g.wrapper.Printf("typedef void* %s;\n", sym.cgoname)
g.wrapper.Printf("extern void* cgo_func_%s_new();\n", sym.id)
}
switch {
case sym.isBasic():
case sym.isArray():
typ := sym.GoType().Underlying().(*types.Array)
elemType := g.pkg.syms.symtype(typ.Elem())
g.wrapper.Printf("extern void cgo_func_%[1]s_ass_item(void* p0, GoInt p1, %[2]s p2);\n", sym.id, elemType.cgoname)
case sym.isSlice():
typ := sym.GoType().Underlying().(*types.Slice)
elemType := g.pkg.syms.symtype(typ.Elem())
g.wrapper.Printf("extern void cgo_func_%[1]s_append(void* p0, %[2]s p1);\n", sym.id, elemType.cgoname)
case sym.isMap():
case sym.isSignature():
case sym.isInterface():
default:
panic(fmt.Errorf(
"gopy: cdef for %s not handled",
sym.gofmt(),
))
}
}
// genCdefFunc generates a C definition for a Go function.
func (g *cffiGen) genCdefFunc(f Func) {
var params []string
var retParams []string
@ -68,6 +122,51 @@ func (g *cffiGen) genCdefFunc(f Func) {
g.wrapper.Printf("extern %[1]s cgo_func_%[2]s(%[3]s);\n", cdef_ret, f.id, paramString)
}
// genTypeCdefFunc generates a C definition for a Go type's method.
func (g *cffiGen) genTypeCdefFunc(sym *symbol, fsym *symbol) {
if !sym.isType() {
return
}
if sym.isStruct() {
return
}
if sym.isBasic() && !sym.isNamed() {
return
}
isMethod := (sym != nil)
sig := fsym.GoType().Underlying().(*types.Signature)
args := sig.Params()
res := sig.Results()
var cdef_ret string
var params []string
switch res.Len() {
case 0:
cdef_ret = "void"
case 1:
ret := res.At(0)
cdef_ret = g.pkg.syms.symtype(ret.Type()).cgoname
}
if isMethod {
params = append(params, sym.cgoname+" p0")
}
for i := 0; i < args.Len(); i++ {
arg := args.At(i)
index := i
if isMethod {
index++
}
paramVar := g.pkg.syms.symtype(arg.Type()).cgoname + " " + "p" + strconv.Itoa(index)
params = append(params, paramVar)
}
paramString := strings.Join(params, ", ")
g.wrapper.Printf("extern %[1]s cgo_func_%[2]s(%[3]s);\n", cdef_ret, fsym.id, paramString)
}
// genCdefMethod generates a C definition for a Go Struct's method.
func (g *cffiGen) genCdefMethod(f Func) {
var retParams []string
var cdef_ret string
@ -99,6 +198,7 @@ func (g *cffiGen) genCdefMethod(f Func) {
g.wrapper.Printf("extern %[1]s cgo_func_%[2]s(%[3]s);\n", cdef_ret, f.id, paramString)
}
// genCdefStructMemberGetter generates C definitions of Getter/Setter for a Go struct.
func (g *cffiGen) genCdefStructMemberGetter(s Struct, i int, f types.Object) {
pkg := s.Package()
ft := f.Type()
@ -116,6 +216,7 @@ func (g *cffiGen) genCdefStructMemberGetter(s Struct, i int, f types.Object) {
}
}
// genCdefStructMemberSetter generates C defintion of Setter for a Go struct.
func (g *cffiGen) genCdefStructMemberSetter(s Struct, i int, f types.Object) {
pkg := s.Package()
ft := f.Type()
@ -126,6 +227,12 @@ func (g *cffiGen) genCdefStructMemberSetter(s Struct, i int, f types.Object) {
g.wrapper.Printf("extern void cgo_func_%[1]s_setter_%[2]d(void* p0, %[3]s p1);\n", s.sym.id, i+1, cdef_value)
}
// genCdefStructTPStr generates C definitions of str method for a Go struct.
func (g *cffiGen) genCdefStructTPStr(s Struct) {
g.wrapper.Printf("extern GoString cgo_func_%[1]s_str(void* p0);\n", s.sym.id)
}
// genCdefTypeTPStr generates C definitions of str method for a Go type.
func (g *cffiGen) genCdefTypeTPStr(sym *symbol) {
g.wrapper.Printf("extern GoString cgo_func_%[1]s_str(%[2]s p0);\n", sym.id, sym.cgoname)
}

View File

@ -6,6 +6,7 @@ package bind
import (
"fmt"
"go/types"
"strings"
)
@ -21,7 +22,7 @@ func (g *cffiGen) genMethod(s Struct, m Func) {
g.wrapper.Printf(`
# pythonization of: %[1]s.%[2]s
def %[2]s(%[3]s):
"""%[4]s"""
""%[4]q""
`,
g.pkg.pkg.Name(),
m.GoName(),
@ -117,7 +118,7 @@ func (g *cffiGen) genFunc(o Func) {
g.wrapper.Printf(`
# pythonization of: %[1]s.%[2]s
def %[2]s(%[3]s):
"""%[4]s"""
""%[4]q""
`,
g.pkg.pkg.Name(),
o.GoName(),
@ -253,3 +254,76 @@ func (g *cffiGen) genFuncBody(f Func) {
panic(fmt.Errorf("gopy: Not yet implemeted for multiple return."))
}
}
func (g *cffiGen) genTypeFunc(sym *symbol, fsym *symbol) {
isMethod := (sym != nil)
sig := fsym.GoType().Underlying().(*types.Signature)
args := sig.Params()
nargs := 0
var funcArgs []string
if isMethod {
funcArgs = append(funcArgs, "self")
}
if args != nil {
nargs = args.Len()
for i := 0; i < nargs; i++ {
arg := args.At(i)
if args == nil {
panic(fmt.Errorf(
"gopy: could not find symbol for %q",
arg.String(),
))
}
funcArgs = append(funcArgs, arg.Name())
}
}
g.wrapper.Printf(
`def %[1]s(%[2]s):
""%[3]q""
`,
fsym.goname,
strings.Join(funcArgs, ", "),
fsym.doc,
)
g.wrapper.Indent()
g.genTypeFuncBody(sym, fsym)
g.wrapper.Outdent()
}
func (g *cffiGen) genTypeFuncBody(sym *symbol, fsym *symbol) {
isMethod := (sym != nil)
sig := fsym.GoType().Underlying().(*types.Signature)
args := sig.Params()
res := sig.Results()
nargs := 0
nres := 0
var funcArgs []string
if isMethod {
funcArgs = append(funcArgs, "self.cgopy")
}
if args != nil {
nargs = args.Len()
for i := 0; i < nargs; i++ {
arg := args.At(i)
funcArgs = append(funcArgs, arg.Name())
}
}
if res != nil {
nres = res.Len()
}
if nres > 0 {
g.wrapper.Printf("res = ")
g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s(%[2]s)\n", fsym.id, strings.Join(funcArgs, ", "))
ret := res.At(0)
sret := g.pkg.syms.symtype(ret.Type())
g.wrapper.Printf("return _cffi_helper.cffi_%[1]s(res)\n", sret.c2py)
} else {
g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s(%[2]s)\n", fsym.id, strings.Join(funcArgs, ", "))
}
}

View File

@ -14,7 +14,7 @@ func (g *cffiGen) genStruct(s Struct) {
g.wrapper.Printf(`
# Python type for struct %[1]s.%[2]s
class %[2]s(object):
"""%[3]s"""
""%[3]q""
`,
pkgname,
s.GoName(),

198
bind/gencffi_type.go Normal file
View File

@ -0,0 +1,198 @@
// Copyright 2017 The go-python Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package bind
import (
"fmt"
"go/types"
)
// genType generates a Python class wrapping the given Go type.
func (g *cffiGen) genType(sym *symbol) {
if !sym.isType() {
return
}
if sym.isStruct() {
return
}
if sym.isBasic() && !sym.isNamed() {
return
}
pkgname := g.pkg.pkg.Name()
g.wrapper.Printf(`
# Python type for %[1]s.%[2]s
class %[2]s(object):
""%[3]q""
`, pkgname,
sym.goname,
sym.doc,
)
g.wrapper.Indent()
g.genTypeInit(sym)
g.genTypeMethod(sym)
g.genTypeTPStr(sym)
if sym.isSlice() {
g.genTypeIAdd(sym)
}
g.wrapper.Outdent()
}
// genTypeIAdd generates Type __iadd__.
func (g *cffiGen) genTypeIAdd(sym *symbol) {
g.wrapper.Printf("def __iadd__(self, value):\n")
g.wrapper.Indent()
switch {
case sym.isSlice():
g.wrapper.Printf("if not isinstance(value, collections.Iterable):\n")
g.wrapper.Indent()
g.wrapper.Printf("raise TypeError('%[1]s.__iadd__ takes a iterable type as argument')\n", sym.goname)
g.wrapper.Outdent()
typ := sym.GoType().Underlying().(*types.Slice)
esym := g.pkg.syms.symtype(typ.Elem())
g.wrapper.Printf("for elt in value:\n")
g.wrapper.Indent()
g.wrapper.Printf("pyitem = _cffi_helper.cffi_%[1]s(elt)\n", esym.py2c)
g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s_append(self.cgopy, pyitem)\n", sym.id)
g.wrapper.Outdent()
default:
panic(fmt.Errorf(
"gopy: __iadd__ for %s not handled",
sym.gofmt(),
))
}
g.wrapper.Printf("return self\n")
g.wrapper.Outdent()
g.wrapper.Printf("\n")
}
// genTypeInit generates Type __init__.
func (g *cffiGen) genTypeInit(sym *symbol) {
nargs := 1
g.wrapper.Printf("def __init__(self, *args, **kwargs):\n")
g.wrapper.Indent()
g.wrapper.Printf("nkwds = len(kwargs)\n")
g.wrapper.Printf("nargs = len(args)\n")
g.wrapper.Printf("if nkwds + nargs > %[1]d:\n", nargs)
g.wrapper.Indent()
g.wrapper.Printf("raise TypeError('%s.__init__ takes at most %d argument(s)')\n",
sym.goname,
nargs,
)
g.wrapper.Outdent()
g.wrapper.Printf("self.cgopy = _cffi_helper.lib.cgo_func_%[1]s_new()\n", sym.id)
switch {
case sym.isBasic():
bsym := g.pkg.syms.symtype(sym.GoType().Underlying())
g.wrapper.Printf("if len(args) == 1:\n")
g.wrapper.Indent()
g.wrapper.Printf("self.cgopy = _cffi_helper.cffi_%[1]s(args[0])\n",
bsym.py2c,
)
g.wrapper.Outdent()
case sym.isArray():
g.wrapper.Printf("if args:\n")
g.wrapper.Indent()
g.wrapper.Printf("if not isinstance(args[0], collections.Sequence):\n")
g.wrapper.Indent()
g.wrapper.Printf("raise TypeError('%[1]s.__init__ takes a sequence as argument')\n", sym.goname)
g.wrapper.Outdent()
typ := sym.GoType().Underlying().(*types.Array)
esym := g.pkg.syms.symtype(typ.Elem())
g.wrapper.Printf("if len(args[0]) > %[1]d:\n", typ.Len())
g.wrapper.Indent()
g.wrapper.Printf("raise ValueError('%[1]s.__init__ takes a sequence of size at most %[2]d')\n",
sym.goname,
typ.Len(),
)
g.wrapper.Outdent()
g.wrapper.Printf("for idx, elt in enumerate(args[0]):\n")
g.wrapper.Indent()
g.wrapper.Printf("pyitem = _cffi_helper.cffi_%[1]s(elt)\n", esym.py2c)
g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s_ass_item(self.cgopy, idx, pyitem)\n", sym.id)
g.wrapper.Outdent()
g.wrapper.Outdent()
case sym.isSlice():
g.wrapper.Printf("if args:\n")
g.wrapper.Indent()
g.wrapper.Printf("if not isinstance(args[0], collections.Iterable):\n")
g.wrapper.Indent()
g.wrapper.Printf("raise TypeError('%[1]s.__init__ takes a sequence as argument')\n", sym.goname)
g.wrapper.Outdent()
typ := sym.GoType().Underlying().(*types.Slice)
esym := g.pkg.syms.symtype(typ.Elem())
g.wrapper.Printf("for elt in args[0]:\n")
g.wrapper.Indent()
g.wrapper.Printf("pyitem = _cffi_helper.cffi_%[1]s(elt)\n", esym.py2c)
g.wrapper.Printf("_cffi_helper.lib.cgo_func_%[1]s_append(self.cgopy, pyitem)\n", sym.id)
g.wrapper.Outdent()
g.wrapper.Outdent()
case sym.isMap():
case sym.isSignature():
//TODO(corona10)
case sym.isInterface():
//TODO(corona10)
default:
panic(fmt.Errorf(
"gopy: init for %s not handled",
sym.gofmt(),
))
}
g.wrapper.Outdent()
g.wrapper.Printf("\n")
}
// genTypeConverter generates a Type converter between C and Python.
func (g *cffiGen) genTypeConverter(sym *symbol) {
g.wrapper.Printf("# converters for %s - %s\n", sym.id, sym.goname)
g.wrapper.Printf("@staticmethod\n")
g.wrapper.Printf("def cffi_cgopy_cnv_py2c_%[1]s(o):\n", sym.id)
g.wrapper.Indent()
g.wrapper.Printf("raise NotImplementedError\n")
g.wrapper.Outdent()
g.wrapper.Printf("\n")
g.wrapper.Printf("@staticmethod\n")
g.wrapper.Printf("def cffi_cgopy_cnv_c2py_%[1]s(c):\n", sym.id)
g.wrapper.Indent()
g.wrapper.Printf("raise NotImplementedError\n")
g.wrapper.Outdent()
g.wrapper.Printf("\n")
}
// genTypeMethod generates Type methods.
func (g *cffiGen) genTypeMethod(sym *symbol) {
g.wrapper.Printf("# methods for %s\n", sym.gofmt())
if sym.isNamed() {
typ := sym.GoType().(*types.Named)
for imeth := 0; imeth < typ.NumMethods(); imeth++ {
m := typ.Method(imeth)
if !m.Exported() {
continue
}
mname := types.ObjectString(m, nil)
msym := g.pkg.syms.sym(mname)
if msym == nil {
panic(fmt.Errorf(
"gopy: could not find symbol for [%[1]T] (%#[1]v) (%[2]s)",
m.Type(),
m.Name()+" || "+m.FullName(),
))
}
g.genTypeFunc(sym, msym)
}
}
g.wrapper.Printf("\n")
}
// genTypeTPStr generates Type __str__ method.
func (g *cffiGen) genTypeTPStr(sym *symbol) {
g.wrapper.Printf("def __str__(self):\n")
g.wrapper.Indent()
g.wrapper.Printf("cret = _cffi_helper.lib.cgo_func_%[1]s_str(self.cgopy)\n", sym.id)
g.wrapper.Printf("ret = _cffi_helper.cffi_cgopy_cnv_c2py_string(cret)\n")
g.wrapper.Printf("return ret\n")
g.wrapper.Outdent()
g.wrapper.Printf("\n")
}

View File

@ -499,6 +499,72 @@ s = named.Slice(range(10))
s = named.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s = named.Slice(xrange(10))
s = named.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
`),
})
testPkgWithCFFI(t, pkg{
path: "_examples/named",
want: []byte(`doc(named): 'package named tests various aspects of named types.\n'
doc(named.Float): ''
doc(named.Float.Value): 'Value() float\n\nValue returns a float32 value\n'
v = named.Float()
v = 0
v.Value() = 0.0
x = named.X()
x = 0
x.Value() = 0.0
x = named.XX()
x = 0
x.Value() = 0.0
x = named.XXX()
x = 0
x.Value() = 0.0
x = named.XXXX()
x = 0
x.Value() = 0.0
v = named.Float(42)
v = 42
v.Value() = 42.0
v = named.Float(42.0)
v = 42
v.Value() = 42.0
x = named.X(42)
x = 42
x.Value() = 42.0
x = named.XX(42)
x = 42
x.Value() = 42.0
x = named.XXX(42)
x = 42
x.Value() = 42.0
x = named.XXXX(42)
x = 42
x.Value() = 42.0
x = named.XXXX(42.0)
x = 42
x.Value() = 42.0
s = named.Str()
s = ""
s.Value() = ''
s = named.Str('string')
s = "string"
s.Value() = 'string'
arr = named.Array()
arr = named.Array{0, 0}
arr = named.Array([1,2])
arr = named.Array{1, 2}
arr = named.Array(range(10))
caught: Array.__init__ takes a sequence of size at most 2
arr = named.Array(xrange(2))
arr = named.Array{0, 1}
s = named.Slice()
s = named.Slice(nil)
s = named.Slice([1,2])
s = named.Slice{1, 2}
s = named.Slice(range(10))
s = named.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s = named.Slice(xrange(10))
s = named.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
`),
})
}
@ -663,6 +729,28 @@ s += [1,2]
s = seqs.Slice{1, 2}
s += [10,20]
s = seqs.Slice{1, 2, 10, 20}
`),
})
testPkgWithCFFI(t, pkg{
path: "_examples/seqs",
want: []byte(`doc(seqs): 'package seqs tests various aspects of sequence types.\n'
arr = seqs.Array(xrange(2))
arr = seqs.Array{0, 1, 0, 0, 0, 0, 0, 0, 0, 0}
s = seqs.Slice()
s = seqs.Slice(nil)
s = seqs.Slice([1,2])
s = seqs.Slice{1, 2}
s = seqs.Slice(range(10))
s = seqs.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s = seqs.Slice(xrange(10))
s = seqs.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s = seqs.Slice()
s = seqs.Slice(nil)
s += [1,2]
s = seqs.Slice{1, 2}
s += [10,20]
s = seqs.Slice{1, 2, 10, 20}
`),
})
}