Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions _xtool/pydump/class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package main

import (
"fmt"
"strings"
"github.com/goplus/lib/c"
"github.com/goplus/lib/py"
"github.com/goplus/lib/py/inspect"
"github.com/goplus/llpyg/symbol"
)

func parseClass(class *py.Object, sym *symbol.Symbol) (*symbol.Class, error) {
cls := &symbol.Class{
Name: sym.Name,
Doc: sym.Doc,
}
// bases
bases, err := parseBases(class, sym.Name)
if err != nil {
return nil, err
}
cls.Bases = bases
// methods, properties, etc.
cls, err = parseClassDict(class, cls)
if err != nil {
return nil, err
}
return cls, nil
}

// get class parents
func parseBases(class *py.Object, name string) ([]*symbol.Base, error) {
basesObj := class.GetAttrString(c.Str("__bases__")) // tuple
if basesObj == nil {
return nil, fmt.Errorf("can't get __bases__ from %s", name)
}
bases := make([]*symbol.Base, 0)
for i, n := 0, basesObj.TupleLen(); i < n; i++ {
baseObj := basesObj.TupleItem(i)
base := &symbol.Base{
Name: c.GoString(baseObj.GetAttrString(c.Str("__name__")).CStr()),
Module: c.GoString(baseObj.GetAttrString(c.Str("__module__")).CStr()),
}
bases = append(bases, base)
}
return bases, nil
}

func parseClassDict(class *py.Object, cls *symbol.Class) (*symbol.Class, error) {
items, err := getRealDictItems(class, cls.Name)
if err != nil {
return nil, err
}
for i, n := 0, items.ListLen(); i < n; i++ {
item := items.ListItem(i)
name := c.GoString(item.TupleItem(0).CStr())
val := item.TupleItem(1)
typeName := c.GoString(val.Type().TypeName().CStr())
Comment thread
toaction marked this conversation as resolved.
typeName = strings.TrimSpace(typeName)
// init method
if name == "__init__" {
sym := &symbol.Symbol{
Name: name,
Type: typeName,
}
sig, err := getSignature(val, sym, true)
if err != nil {
return nil, err
}
sym.Sig = sig
cls.InitMethod = sym
continue
}
// instance method
if inspect.Isfunction(val).IsTrue() == 1 {
sym, err := parseMethod(val, name, typeName, true)
if err != nil {
return nil, err
}
cls.InstanceMethods = append(cls.InstanceMethods, sym)
continue
}
// hard-code
switch typeName {
case "classmethod":
val = val.GetAttrString(c.Str("__func__"))
if val == nil {
return nil, fmt.Errorf("can't get __func__ of %s", name)
}
sym, err := parseMethod(val, name, typeName, true)
if err != nil {
return nil, err
}
cls.ClassMethods = append(cls.ClassMethods, sym)
case "staticmethod":
val = val.GetAttrString(c.Str("__func__"))
if val == nil {
return nil, fmt.Errorf("can't get __func__ of %s", name)
}
sym, err := parseMethod(val, name, typeName, false)
if err != nil {
return nil, err
}
cls.StaticMethods = append(cls.StaticMethods, sym)
case "property":
property, err := parseProperty(val, name)
if err != nil {
return nil, err
}
cls.Properties = append(cls.Properties, property)
default:
// TODO: others
}
}
return cls, nil
}

func parseMethod(val *py.Object, name string, typeName string, skipFirst bool) (*symbol.Symbol, error) {
sym := &symbol.Symbol{
Name: name,
Type: typeName,
}
doc := val.GetAttrString(c.Str("__doc__"))
if doc != nil && doc.IsTrue() == 1 {
sym.Doc = c.GoString(doc.Str().CStr())
}
sig, err := getSignature(val, sym, skipFirst)
if err != nil {
return nil, err
}
sym.Sig = sig
return sym, nil
}

func parseProperty(val *py.Object, name string) (*symbol.Property, error) {
property := &symbol.Property{
Name: name,
}
getter := val.GetAttrString(c.Str("fget"))
if getter != nil {
// (self) -> value
property.Getter = "()"
Comment thread
toaction marked this conversation as resolved.
}
setter := val.GetAttrString(c.Str("fset"))
if setter != nil {
sym := &symbol.Symbol{
Name: name,
Type: "property",
}
// (self, value) -> None
sig, err := getSignature(setter, sym, true)
if err != nil {
return nil, err
}
property.Setter = sig
}
return property, nil
}

func getRealDictItems(class *py.Object, name string) (*py.Object, error) {
dict := class.GetAttrString(c.Str("__dict__"))
if dict == nil {
return nil, fmt.Errorf("can't get __dict__ of %s", name)
}
dictTypeName := c.GoString(dict.Type().TypeName().CStr())
if dictTypeName != "mappingproxy" {
return nil, fmt.Errorf("__dict__ of %s is not a mappingproxy", name)
}
realDict := dict.CallMethod(c.Str("copy"), nil)
if realDict == nil {
return nil, fmt.Errorf("failed to copy real dict of %s", name)
}
return realDict.DictItems(), nil
}
62 changes: 41 additions & 21 deletions _xtool/pydump/pydump.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,39 @@ func extractSignatureFromDoc(doc, funcName string) string {
return strings.Join(fields, " ")
}

func getSignature(val *py.Object, sym *symbol.Symbol) string {
// function, method, class, or implement __call__
func getSignature(val *py.Object, sym *symbol.Symbol, skipFirst bool) (string, error) {
// which has __call__
if val.Callable() == 0 {
return ""
return "", fmt.Errorf("%s is not callable", sym.Name)
}
// get signature from inspect
sigFromInspect := inspect.Signature(val)
if sigFromInspect != nil {
sig := c.GoString(sigFromInspect.Str().CStr())
if sig != "(*args, **kwargs)" {
return sig
// use inspect
sigObj := inspect.Signature(val)
if sigObj != nil {
sig := c.GoString(sigObj.Str().CStr())
if skipFirst {
sig = removeFirstParam(sig)
}
return sig, nil
}
// get signature from doc
sigFromDoc := extractSignatureFromDoc(sym.Doc, sym.Name)
if sigFromDoc != "" {
return sigFromDoc
// parse doc
sig := extractSignatureFromDoc(sym.Doc, sym.Name)
if sig != "" {
return sig, nil
}
// Paradigms
if pyFuncTypes[sym.Type] {
return "(*args, **kwargs)"
return "(*args, **kwargs)", nil
}
return "", fmt.Errorf("failed to get signature of %s", sym.Name)
}

// (self, ...) -> (...)
func removeFirstParam(sig string) string {
idx := strings.Index(sig, ",")
if idx == -1 {
return "()"
}
return ""
return "(" + strings.TrimSpace(sig[idx+1:])
Comment thread
toaction marked this conversation as resolved.
}

// moduleName: Python module name
Expand All @@ -70,11 +80,8 @@ func pydump(moduleName string) (*symbol.Module, error) {
if mod == nil {
return nil, fmt.Errorf("failed to import module %s", moduleName)
}
// get dict, python list Object
// get object dict
keys := mod.ModuleGetDict().DictKeys()
if keys == nil {
return nil, fmt.Errorf("failed to get dict keys of %s", moduleName)
}
// create module instance
modInstance := &symbol.Module{
Name: moduleName,
Expand All @@ -96,10 +103,23 @@ func pydump(moduleName string) (*symbol.Module, error) {
}
// functions
if pyFuncTypes[sym.Type] {
sym.Sig = getSignature(val, sym)
sig, err := getSignature(val, sym, false)
if err != nil {
return nil, err
}
sym.Sig = sig
modInstance.Functions = append(modInstance.Functions, sym)
continue
}
// classes
if inspect.Isclass(val).IsTrue() == 1 {
cls, err := parseClass(val, sym)
if err != nil {
return nil, err
}
modInstance.Classes = append(modInstance.Classes, cls)
continue
}
// TODO: variables, classes, etc.
}
return modInstance, nil
}
Expand Down
28 changes: 27 additions & 1 deletion symbol/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,31 @@ type Symbol struct {
type Module struct {
Name string `json:"name"` // python module name
Functions []*Symbol `json:"functions"` // package functions
// TODO: variables, classes, etc.
Classes []*Class `json:"classes"`
}

// base class
type Base struct {
Name string `json:"name"`
Module string `json:"module"`
}

// @property
type Property struct {
Name string `json:"name"`
Getter string `json:"getter"`
Setter string `json:"setter"`
}

// Python class
type Class struct {
Name string `json:"name"`
Doc string `json:"doc"`
Bases []*Base `json:"bases"`
InitMethod *Symbol `json:"initMethod"`
InstanceMethods []*Symbol `json:"instanceMethods"` // include override special methods
ClassMethods []*Symbol `json:"classMethods"`
StaticMethods []*Symbol `json:"staticMethods"`
Properties []*Property `json:"properties"`
// TODO: attributes
}
Loading