github.com/goplus/llgo@v0.8.3/chore/llpyg/llpyg.go (about) 1 /* 2 * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "go/ast" 24 "go/token" 25 "go/types" 26 "log" 27 "os" 28 "os/exec" 29 "strings" 30 31 "github.com/goplus/gogen" 32 "github.com/goplus/llgo/chore/llpyg/pysig" 33 "github.com/goplus/llgo/ssa" 34 ) 35 36 type symbol struct { 37 Name string `json:"name"` 38 Type string `json:"type"` 39 Doc string `json:"doc"` 40 Sig string `json:"sig"` 41 URL string `json:"url"` 42 } 43 44 type module struct { 45 Name string `json:"name"` 46 Items []*symbol `json:"items"` 47 } 48 49 func pydump(pyLib string) (mod module) { 50 var out bytes.Buffer 51 cmd := exec.Command("pydump", pyLib) 52 cmd.Stdout = &out 53 cmd.Stderr = os.Stderr 54 cmd.Run() 55 56 json.Unmarshal(out.Bytes(), &mod) 57 return 58 } 59 60 func pysigfetch(pyLib string, names []string) (mod module) { 61 var out bytes.Buffer 62 cmd := exec.Command("pysigfetch", pyLib, "-") 63 cmd.Stdin = strings.NewReader(strings.Join(names, " ")) 64 cmd.Stdout = &out 65 cmd.Stderr = os.Stderr 66 cmd.Run() 67 68 json.Unmarshal(out.Bytes(), &mod) 69 return 70 } 71 72 func main() { 73 if len(os.Args) < 2 { 74 fmt.Fprintln(os.Stderr, "Usage: llpyg <pythonLibPath>") 75 return 76 } 77 pyLib := os.Args[1] 78 79 mod := pydump(pyLib) 80 if mod.Name != pyLib { 81 log.Printf("import module %s failed\n", pyLib) 82 os.Exit(1) 83 } 84 pkg := gogen.NewPackage("", pyLib, nil) 85 pkg.Import("unsafe").MarkForceUsed(pkg) // import _ "unsafe" 86 py := pkg.Import("github.com/goplus/llgo/py") // import "github.com/goplus/llgo/py" 87 88 f := func(cb *gogen.CodeBuilder) int { 89 cb.Val("py." + mod.Name) 90 return 1 91 } 92 defs := pkg.NewConstDefs(pkg.Types.Scope()) 93 defs.New(f, 0, 0, nil, "LLGoPackage") 94 95 obj := py.Ref("Object").(*types.TypeName).Type().(*types.Named) 96 objPtr := types.NewPointer(obj) 97 ret := types.NewTuple(pkg.NewParam(0, "", objPtr)) 98 99 ctx := &context{pkg, obj, objPtr, ret, nil, py} 100 ctx.genMod(pkg, &mod) 101 skips := ctx.skips 102 if n := len(skips); n > 0 { 103 log.Printf("==> There are %d signatures not found, fetch from doc site\n", n) 104 mod = pysigfetch(pyLib, skips) 105 ctx.skips = skips[:0] 106 ctx.genMod(pkg, &mod) 107 if len(mod.Items) > 0 { 108 skips = ctx.skips 109 } 110 if n := len(skips); n > 0 { 111 log.Printf("==> Skip %d symbols:\n%v\n", n, skips) 112 } 113 } 114 115 pkg.WriteTo(os.Stdout) 116 } 117 118 type context struct { 119 pkg *gogen.Package 120 obj *types.Named 121 objPtr *types.Pointer 122 ret *types.Tuple 123 skips []string 124 py gogen.PkgRef 125 } 126 127 func (ctx *context) genMod(pkg *gogen.Package, mod *module) { 128 for _, sym := range mod.Items { 129 switch sym.Type { 130 case "builtin_function_or_method", "function", "ufunc", "method-wrapper": 131 ctx.genFunc(pkg, sym) 132 case "str", "float", "bool", "type", "dict", "tuple", "list", "object", "module", 133 "int", "set", "frozenset", "flags", "bool_", "pybind11_type", "layout", 134 "memory_format", "qscheme", "dtype", "tensortype": // skip 135 case "": // pysigfetch: page not found 136 ctx.skips = append(ctx.skips, sym.Name) 137 default: 138 t := sym.Type 139 if len(t) > 0 && (t[0] >= 'a' && t[0] <= 'z') && !strings.HasSuffix(t, "_info") { 140 log.Panicln("unsupport type:", sym.Type) 141 } 142 } 143 } 144 } 145 146 func (ctx *context) genFunc(pkg *gogen.Package, sym *symbol) { 147 name, symSig := sym.Name, sym.Sig 148 if len(name) == 0 || name[0] == '_' { 149 return 150 } 151 if symSig == "<NULL>" { 152 ctx.skips = append(ctx.skips, name) 153 return 154 } 155 params, variadic := ctx.genParams(pkg, symSig) 156 name = genName(name, -1) 157 sig := types.NewSignatureType(nil, nil, nil, params, ctx.ret, variadic) 158 fn := pkg.NewFuncDecl(token.NoPos, name, sig) 159 list := ctx.genDoc(sym.Doc) 160 if sym.URL != "" { 161 if len(list) > 0 { 162 list = append(list, emptyCommentLine) 163 } 164 list = append(list, genSee(sym.URL)) 165 } 166 if len(list) > 0 { 167 list = append(list, emptyCommentLine) 168 } 169 list = append(list, ctx.genLinkname(name, sym)) 170 fn.SetComments(pkg, &ast.CommentGroup{List: list}) 171 // fn.BodyStart(pkg).End() 172 } 173 174 func (ctx *context) genParams(pkg *gogen.Package, sig string) (*types.Tuple, bool) { 175 args := pysig.Parse(sig) 176 if len(args) == 0 { 177 return nil, false 178 } 179 n := len(args) 180 objPtr := ctx.objPtr 181 list := make([]*types.Var, 0, n) 182 for i := 0; i < n; i++ { 183 name := args[i].Name 184 if name == "/" { 185 continue 186 } 187 if name == "*" || name == "\\*" { 188 break 189 } 190 if strings.HasPrefix(name, "*") { 191 if name[1] != '*' { 192 list = append(list, ssa.VArg()) 193 return types.NewTuple(list...), true 194 } 195 return types.NewTuple(list...), false 196 } 197 list = append(list, pkg.NewParam(0, genName(name, 0), objPtr)) 198 } 199 return types.NewTuple(list...), false 200 } 201 202 func genName(name string, idxDontTitle int) string { 203 parts := strings.Split(name, "_") 204 for i, part := range parts { 205 if i != idxDontTitle && part != "" { 206 if c := part[0]; c >= 'a' && c <= 'z' { 207 part = string(c+'A'-'a') + part[1:] 208 } 209 parts[i] = part 210 } 211 } 212 name = strings.Join(parts, "") 213 switch name { 214 case "default", "func", "var", "": 215 name += "_" 216 } 217 return name 218 } 219 220 func (ctx *context) genLinkname(name string, sym *symbol) *ast.Comment { 221 return &ast.Comment{Text: "//go:linkname " + name + " py." + sym.Name} 222 } 223 224 func (ctx *context) genDoc(doc string) []*ast.Comment { 225 if doc == "" { 226 return make([]*ast.Comment, 0, 4) 227 } 228 lines := strings.Split(doc, "\n") 229 list := make([]*ast.Comment, len(lines), len(lines)+4) 230 for i, line := range lines { 231 list[i] = &ast.Comment{Text: "// " + line} 232 } 233 return list 234 } 235 236 func genSee(url string) *ast.Comment { 237 return &ast.Comment{Text: "// See " + url} 238 } 239 240 var ( 241 emptyCommentLine = &ast.Comment{Text: "//"} 242 )