From 35a73b4cdee6d416ec8125cd0da9315d24fcfcf4 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Tue, 14 May 2024 15:34:53 +0800 Subject: [PATCH 1/4] llpyg: todo --- chore/_xtool/pydump/pydump.go | 1 + chore/llpyg/llpyg.go | 41 +++++++++++++++++++++++++ py/module.go | 11 +++++++ py/object.go | 17 +++++++++++ py/type.go | 57 +++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 chore/llpyg/llpyg.go create mode 100644 py/type.go diff --git a/chore/_xtool/pydump/pydump.go b/chore/_xtool/pydump/pydump.go index c9926893..4d97f167 100644 --- a/chore/_xtool/pydump/pydump.go +++ b/chore/_xtool/pydump/pydump.go @@ -43,6 +43,7 @@ func main() { val := mod.GetAttr(key) doc := val.GetAttrString(c.Str("__doc__")) sym := cjson.Object() + sym.SetItem(c.Str("type"), cjson.String(val.Type().Name().CStr())) sym.SetItem(c.Str("name"), cjson.String(key.CStr())) sym.SetItem(c.Str("doc"), cjson.String(doc.CStr())) if val.Callable() != 0 && false { diff --git a/chore/llpyg/llpyg.go b/chore/llpyg/llpyg.go new file mode 100644 index 00000000..70590860 --- /dev/null +++ b/chore/llpyg/llpyg.go @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + "os" + "os/exec" +) + +func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "Usage: llpyg [destDir]") + return + } + pyLib := os.Args[1] + destDir := "." + if len(os.Args) > 2 { + destDir = os.Args[2] + } + + pydump := exec.Command("pydump", pyLib) + pydump.Stdout = os.Stdout + pydump.Run() + + _ = destDir +} diff --git a/py/module.go b/py/module.go index 163d0555..7f7381bc 100644 --- a/py/module.go +++ b/py/module.go @@ -25,6 +25,17 @@ import ( // https://docs.python.org/3/c-api/import.html // https://docs.python.org/3/c-api/module.html +// llgo:type C +type ModuleDefBase struct { + Unused [8]byte // TODO(xsw) +} + +// llgo:type C +type ModuleDef struct { + Base ModuleDefBase + // TODO(xsw) +} + // Return the module object corresponding to a module name. The name argument // may be of the form package.module. First check the modules dictionary if // there’s one there, and if not, create a new one and insert it in the modules diff --git a/py/object.go b/py/object.go index bfa1e04b..923aa79f 100644 --- a/py/object.go +++ b/py/object.go @@ -22,6 +22,8 @@ import ( "github.com/goplus/llgo/c" ) +// https://docs.python.org/3/c-api/object.html + // Object represents a Python object. type Object struct { Unused [8]byte @@ -30,6 +32,9 @@ type Object struct { // llgo:link (*Object).DecRef C.Py_DecRef func (o *Object) DecRef() {} +// llgo:link (*Object).Type C.PyObject_Type +func (o *Object) Type() *TypeObject { return nil } + // Compute a string representation of object o. Returns the string representation on // success, nil on failure. This is the equivalent of the Python expression str(o). // Called by the str() built-in function and, therefore, by the print() function. @@ -37,6 +42,18 @@ func (o *Object) DecRef() {} // llgo:link (*Object).Str C.PyObject_Str func (o *Object) Str() *Object { return nil } +// Returns 1 if the object o is considered to be true, and 0 otherwise. This is equivalent +// to the Python expression not not o. On failure, return -1. +// +// llgo:link (*Object) IsTrue() C.PyObject_IsTrue +func (o *Object) IsTrue() c.Int { return -1 } + +// Returns 0 if the object o is considered to be true, and 1 otherwise. This is equivalent +// to the Python expression not o. On failure, return -1. +// +// llgo:link (*Object) NotTrue() C.PyObject_Not +func (o *Object) NotTrue() c.Int { return -1 } + // ----------------------------------------------------------------------------- // Retrieve an attribute named attrName from object o. Returns the attribute value on success, diff --git a/py/type.go b/py/type.go new file mode 100644 index 00000000..cf6cc4e5 --- /dev/null +++ b/py/type.go @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package py + +import ( + _ "unsafe" +) + +// https://docs.python.org/3/c-api/type.html + +type TypeObject struct { + Object +} + +// Return the type’s name. Equivalent to getting the type’s __name__ attribute. +// +// llgo:link (*TypeObject).Name C.PyType_GetName +func (t *TypeObject) Name() *Object { return nil } + +// Return the tp_flags member of type. This function is primarily meant for use +// with Py_LIMITED_API; the individual flag bits are guaranteed to be stable across +// Python releases, but access to tp_flags itself is not part of the limited API. +// +// llgo:link (*TypeObject).Flags C.PyType_GetFlags +func (t *TypeObject) Flags() uint32 { return 0 } + +// Return the module object associated with the given type when the type was created +// using PyType_FromModuleAndSpec(). +// +// If no module is associated with the given type, sets TypeError and returns nil. +// +// This function is usually used to get the module in which a method is defined. Note +// that in such a method, Py_TYPE(self).Module() may not return the intended result. +// Py_TYPE(self) may be a subclass of the intended class, and subclasses are not +// necessarily defined in the same module as their superclass. See PyCMethod to get +// the class that defines the method. See ModuleByDef() for cases when PyCMethod +// cannot be used. +// +// llgo:link (*TypeObject).Module C.PyType_GetModule +func (t *TypeObject) Module() *Object { return nil } + +// llgo:link (*TypeObject).ModuleByDef C.PyType_GetModuleByDef +func (t *TypeObject) ModuleByDef(def *ModuleDef) *Object { return nil } From 172a268e772f0cc114800dcc982f695ff74944bc Mon Sep 17 00:00:00 2001 From: xushiwei Date: Tue, 14 May 2024 17:22:36 +0800 Subject: [PATCH 2/4] llpyg: ready to test --- chore/_xtool/pydump/pydump.go | 2 +- chore/llpyg/llpyg.go | 117 ++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/chore/_xtool/pydump/pydump.go b/chore/_xtool/pydump/pydump.go index 4d97f167..f5912b29 100644 --- a/chore/_xtool/pydump/pydump.go +++ b/chore/_xtool/pydump/pydump.go @@ -46,7 +46,7 @@ func main() { sym.SetItem(c.Str("type"), cjson.String(val.Type().Name().CStr())) sym.SetItem(c.Str("name"), cjson.String(key.CStr())) sym.SetItem(c.Str("doc"), cjson.String(doc.CStr())) - if val.Callable() != 0 && false { + if val.Callable() != 0 { sig := inspect.Signature(val) sym.SetItem(c.Str("sig"), cjson.String(sig.Str().CStr())) } diff --git a/chore/llpyg/llpyg.go b/chore/llpyg/llpyg.go index 70590860..c486dc17 100644 --- a/chore/llpyg/llpyg.go +++ b/chore/llpyg/llpyg.go @@ -17,25 +17,128 @@ package main import ( + "bytes" + "encoding/json" "fmt" + "go/ast" + "go/types" + "log" "os" "os/exec" + "strings" + + "github.com/goplus/gogen" ) +type symbol struct { + Name string `json:"name"` + Type string `json:"type"` + Doc string `json:"doc"` + Sig string `json:"sig"` +} + +type module struct { + Name string `json:"name"` + Items []*symbol `json:"items"` +} + func main() { if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "Usage: llpyg [destDir]") + fmt.Fprintln(os.Stderr, "Usage: llpyg ") return } pyLib := os.Args[1] - destDir := "." - if len(os.Args) > 2 { - destDir = os.Args[2] - } + var out bytes.Buffer pydump := exec.Command("pydump", pyLib) - pydump.Stdout = os.Stdout + pydump.Stdout = &out pydump.Run() - _ = destDir + var mod module + json.Unmarshal(out.Bytes(), &mod) + + pkg := gogen.NewPackage("", mod.Name, nil) + pkg.Import("unsafe").MarkForceUsed(pkg) // import _ "unsafe" + py := pkg.Import("github.com/goplus/llgo/py") // import "github.com/goplus/llgo/py" + + obj := py.Ref("Object").(*types.TypeName).Type().(*types.Named) + objPtr := types.NewPointer(obj) + ret := types.NewTuple(pkg.NewParam(0, "", objPtr)) + + ctx := &context{pkg, obj, objPtr, ret, py} + for _, sym := range mod.Items { + switch sym.Type { + case "builtin_function_or_method", "function": + ctx.genFunc(pkg, sym) + case "str", "float", "bool", "type", "dict", "list", "module", "int", "set": // skip + default: + t := sym.Type + if len(t) > 0 && (t[0] >= 'a' && t[0] <= 'z') { + log.Panicln("unsupport type:", sym.Type) + } + } + } + pkg.WriteTo(os.Stdout) +} + +type context struct { + pkg *gogen.Package + obj *types.Named + objPtr *types.Pointer + ret *types.Tuple + py gogen.PkgRef +} + +func (ctx *context) genFunc(pkg *gogen.Package, sym *symbol) { + name := sym.Name + if len(name) == 0 || name[0] == '_' { + return + } + params, variadic, skip := ctx.genParams(pkg, sym.Sig) + if skip { + // TODO(xsw): don't skip any func + log.Println("skip func:", name, sym.Sig) + return + } + if c := name[0]; c >= 'a' && c <= 'z' { + name = string(c+'A'-'a') + name[1:] + } + fn := pkg.NewFunc(nil, name, params, ctx.ret, variadic) + fn.SetComments(pkg, ctx.genComment(sym.Doc)) + fn.BodyStart(pkg).End() +} + +func (ctx *context) genParams(pkg *gogen.Package, sig string) (*types.Tuple, bool, bool) { + if sig == "" { + return nil, false, true + } + sig = strings.TrimSuffix(strings.TrimPrefix(sig, "("), ")") + parts := strings.Split(sig, ",") + n := len(parts) + if last := strings.TrimSpace(parts[n-1]); last == "/" { + n-- + } + objPtr := ctx.objPtr + list := make([]*types.Var, n) + for i := 0; i < n; i++ { + part := strings.TrimSpace(parts[i]) + if strings.HasPrefix(part, "*") { + if len(part) > 1 && part[1] == '*' || i != n-1 { + return nil, false, true + } + list[i] = pkg.NewParam(0, part[1:], types.NewSlice(objPtr)) + return types.NewTuple(list...), true, false + } + list[i] = pkg.NewParam(0, part, objPtr) + } + return types.NewTuple(list...), false, false +} + +func (ctx *context) genComment(doc string) *ast.CommentGroup { + lines := strings.Split(doc, "\n") + list := make([]*ast.Comment, len(lines)) + for i, line := range lines { + list[i] = &ast.Comment{Text: "// " + line} + } + return &ast.CommentGroup{List: list} } From 091fee61e075624e6b106b75c3915e9d5e99cd31 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Tue, 14 May 2024 18:16:32 +0800 Subject: [PATCH 3/4] llpyg: pkg.NewFuncDecl --- chore/llpyg/llpyg.go | 25 +++++++++++++++++++------ py/module.go | 2 ++ py/type.go | 7 +++---- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/chore/llpyg/llpyg.go b/chore/llpyg/llpyg.go index c486dc17..0fb85473 100644 --- a/chore/llpyg/llpyg.go +++ b/chore/llpyg/llpyg.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "go/ast" + "go/token" "go/types" "log" "os" @@ -103,9 +104,13 @@ func (ctx *context) genFunc(pkg *gogen.Package, sym *symbol) { if c := name[0]; c >= 'a' && c <= 'z' { name = string(c+'A'-'a') + name[1:] } - fn := pkg.NewFunc(nil, name, params, ctx.ret, variadic) - fn.SetComments(pkg, ctx.genComment(sym.Doc)) - fn.BodyStart(pkg).End() + sig := types.NewSignatureType(nil, nil, nil, params, ctx.ret, variadic) + fn := pkg.NewFuncDecl(token.NoPos, name, sig) + list := ctx.genDoc(sym.Doc) + list = append(list, emptyCommentLine) + list = append(list, ctx.genLinkname(name, sym)) + fn.SetComments(pkg, &ast.CommentGroup{List: list}) + // fn.BodyStart(pkg).End() } func (ctx *context) genParams(pkg *gogen.Package, sig string) (*types.Tuple, bool, bool) { @@ -134,11 +139,19 @@ func (ctx *context) genParams(pkg *gogen.Package, sig string) (*types.Tuple, boo return types.NewTuple(list...), false, false } -func (ctx *context) genComment(doc string) *ast.CommentGroup { +func (ctx *context) genLinkname(name string, sym *symbol) *ast.Comment { + return &ast.Comment{Text: "//go:linkname " + name + " py." + sym.Name} +} + +func (ctx *context) genDoc(doc string) []*ast.Comment { lines := strings.Split(doc, "\n") - list := make([]*ast.Comment, len(lines)) + list := make([]*ast.Comment, len(lines), len(lines)+2) for i, line := range lines { list[i] = &ast.Comment{Text: "// " + line} } - return &ast.CommentGroup{List: list} + return list } + +var ( + emptyCommentLine = &ast.Comment{Text: "//"} +) diff --git a/py/module.go b/py/module.go index 7f7381bc..7166fd9d 100644 --- a/py/module.go +++ b/py/module.go @@ -25,6 +25,7 @@ import ( // https://docs.python.org/3/c-api/import.html // https://docs.python.org/3/c-api/module.html +/* // llgo:type C type ModuleDefBase struct { Unused [8]byte // TODO(xsw) @@ -35,6 +36,7 @@ type ModuleDef struct { Base ModuleDefBase // TODO(xsw) } +*/ // Return the module object corresponding to a module name. The name argument // may be of the form package.module. First check the modules dictionary if diff --git a/py/type.go b/py/type.go index cf6cc4e5..a78cb1c6 100644 --- a/py/type.go +++ b/py/type.go @@ -22,9 +22,8 @@ import ( // https://docs.python.org/3/c-api/type.html -type TypeObject struct { - Object -} +// TypeObject represents the Python type object. +type TypeObject = Object // Return the type’s name. Equivalent to getting the type’s __name__ attribute. // @@ -54,4 +53,4 @@ func (t *TypeObject) Flags() uint32 { return 0 } func (t *TypeObject) Module() *Object { return nil } // llgo:link (*TypeObject).ModuleByDef C.PyType_GetModuleByDef -func (t *TypeObject) ModuleByDef(def *ModuleDef) *Object { return nil } +// func (t *TypeObject) ModuleByDef(def *ModuleDef) *Object { return nil } From c88418422012248bd804aa2ad1ff7a1adf3295f7 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Tue, 14 May 2024 18:19:44 +0800 Subject: [PATCH 4/4] py llgen --- py/llgo_autogen.lla | Bin 1635 -> 1710 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/py/llgo_autogen.lla b/py/llgo_autogen.lla index 61233d79e365f891bfc65b3946aec8df95f0cc57..2d02e0b3e0de3b12b24620cf79da93d6b6b8691c 100644 GIT binary patch delta 416 zcmaFNvyQhuz?+#xgn@y9gCTd)x`=lVckEDR1oBuI82A}v7;b@(h6<{MwYKY)nEgbPP*%T*g)jy{mZ!^}}(2Z9A!`^TRsKG9 zUmMEAvovGjhY3-?_uFbQrkJ{SlrcR$SngQ$G>L8Tc^Rv-(vM@mO}WD@wc3P#*0uJM z&bRyP>i~PJYK~#h5c$lg)(f3o9^2 F7ywN^wj%%l delta 341 zcmZ3-`x8R0|VHAr4!G0F&Xl-zpv~vShM5MgEJK&Ec|ibHk>yo z$$2A|WL$EqHTC1&-v18bDXi7mlWX?(mo&~#syDuTTxz;SnO*JEyj>GxpDuZB6LQ9H z)xUI}<|jKAmc5?YbaP^vIR9?$hO3WXU16VpFUIVmSXZIW+{8D*M<&c<)XA+?_#>A8 z(cb5y-NE>be*aus_9KC7J;ahPnm^_IwZyZtZ7=r?jV0YyYgY)}EIDFf#eU}B9qAn^ z=EXI?bT;%Wr?79`6OhF#Gs(p&ZP|>MqHnA3ufG3A)IRocZ|0JcUy?TtGj7gdoWjC~ e8a$Kv*fc;PG1-#MicxWL37ZMqN>*UNF#rGr#f``S