diff --git a/chore/_xtool/pydump/pydump.go b/chore/_xtool/pydump/pydump.go index c9926893..f5912b29 100644 --- a/chore/_xtool/pydump/pydump.go +++ b/chore/_xtool/pydump/pydump.go @@ -43,9 +43,10 @@ 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 { + 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 new file mode 100644 index 00000000..0fb85473 --- /dev/null +++ b/chore/llpyg/llpyg.go @@ -0,0 +1,157 @@ +/* + * 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 ( + "bytes" + "encoding/json" + "fmt" + "go/ast" + "go/token" + "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 ") + return + } + pyLib := os.Args[1] + + var out bytes.Buffer + pydump := exec.Command("pydump", pyLib) + pydump.Stdout = &out + pydump.Run() + + 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:] + } + 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) { + 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) 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), len(lines)+2) + for i, line := range lines { + list[i] = &ast.Comment{Text: "// " + line} + } + return list +} + +var ( + emptyCommentLine = &ast.Comment{Text: "//"} +) diff --git a/py/llgo_autogen.lla b/py/llgo_autogen.lla index 61233d79..2d02e0b3 100644 Binary files a/py/llgo_autogen.lla and b/py/llgo_autogen.lla differ diff --git a/py/module.go b/py/module.go index 163d0555..7166fd9d 100644 --- a/py/module.go +++ b/py/module.go @@ -25,6 +25,19 @@ 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..a78cb1c6 --- /dev/null +++ b/py/type.go @@ -0,0 +1,56 @@ +/* + * 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 + +// TypeObject represents the Python type object. +type TypeObject = 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 }