Files
llgo/chore/_xtool/llcppsigfetch/parse/cvt.go

1072 lines
31 KiB
Go

package parse
import (
"fmt"
"os"
"strings"
"unsafe"
"github.com/goplus/llgo/c"
"github.com/goplus/llgo/c/cjson"
"github.com/goplus/llgo/c/clang"
"github.com/goplus/llgo/chore/_xtool/llcppsymg/clangutils"
"github.com/goplus/llgo/chore/llcppg/ast"
"github.com/goplus/llgo/chore/llcppg/token"
)
type FileEntry struct {
Path string
Doc *ast.File
}
type Converter struct {
Files []*FileEntry
FileOrder []string // todo(zzy): more efficient struct
curLoc ast.Location
index *clang.Index
unit *clang.TranslationUnit
typeDecls map[string]ast.Decl // cursorUsr -> ast.Decl
// anonyTypeMap stores mappings for unexpected named declarations in typedefs
// that actually represent anonymous types.
//
// Key: The USR (Unified Symbol Resolution) of the declaration cursor.
// Value: The generated name for the anonymous type.
//
// This map is necessary due to a limitation in libclang where anonymous
// structs, unions, or enums within typedefs are incorrectly reported as
// named declarations. We use this map to keep track of these cases and
// generate appropriate names for them.
//
// Additionally, for all nodes referencing these anonymous types, their
// name references are updated to use the corresponding anonyname from
// this map. This ensures consistent naming across the entire AST for
// these anonymous types.
//
// Example:
// typedef struct { int x; } MyStruct;
anonyTypeMap map[string]bool // cursorUsr
indent int // for verbose debug
}
var tagMap = map[string]ast.Tag{
"struct": ast.Struct,
"union": ast.Union,
"enum": ast.Enum,
"class": ast.Class,
}
type Config struct {
File string
Temp bool
Args []string
IsCpp bool
}
func NewConverter(config *clangutils.Config) (*Converter, error) {
if debugParse {
fmt.Fprintln(os.Stderr, "NewConverter: config")
fmt.Fprintln(os.Stderr, "config.File", config.File)
fmt.Fprintln(os.Stderr, "config.Args", config.Args)
fmt.Fprintln(os.Stderr, "config.IsCpp", config.IsCpp)
fmt.Fprintln(os.Stderr, "config.Temp", config.Temp)
}
index, unit, err := clangutils.CreateTranslationUnit(config)
if err != nil {
return nil, err
}
return &Converter{
Files: make([]*FileEntry, 0),
index: index,
unit: unit,
anonyTypeMap: make(map[string]bool),
typeDecls: make(map[string]ast.Decl),
}, nil
}
func (ct *Converter) Dispose() {
ct.logln("Dispose")
ct.index.Dispose()
ct.unit.Dispose()
}
func (ct *Converter) GetTokens(cursor clang.Cursor) []*ast.Token {
ran := cursor.Extent()
var numTokens c.Uint
var tokens *clang.Token
ct.unit.Tokenize(ran, &tokens, &numTokens)
defer ct.unit.DisposeTokens(tokens, numTokens)
tokensSlice := unsafe.Slice(tokens, int(numTokens))
result := make([]*ast.Token, 0, int(numTokens))
for _, tok := range tokensSlice {
tokStr := ct.unit.Token(tok)
result = append(result, &ast.Token{
Token: toToken(tok),
Lit: c.GoString(tokStr.CStr()),
})
tokStr.Dispose()
}
return result
}
func (ct *Converter) logBase() string {
return strings.Repeat(" ", ct.indent)
}
func (ct *Converter) incIndent() {
ct.indent++
}
func (ct *Converter) decIndent() {
if ct.indent > 0 {
ct.indent--
}
}
func (ct *Converter) logf(format string, args ...interface{}) {
if debugParse {
fmt.Fprintf(os.Stderr, ct.logBase()+format, args...)
}
}
func (ct *Converter) logln(args ...interface{}) {
if debugParse {
if len(args) > 0 {
firstArg := fmt.Sprintf("%s%v", ct.logBase(), args[0])
fmt.Fprintln(os.Stderr, append([]interface{}{firstArg}, args[1:]...)...)
} else {
fmt.Fprintln(os.Stderr, ct.logBase())
}
}
}
func (ct *Converter) UpdateLoc(cursor clang.Cursor) {
loc := cursor.Location()
var file clang.File
loc.SpellingLocation(&file, nil, nil, nil)
filePath := toStr(file.FileName())
if filePath == "" {
//todo(zzy): For some built-in macros, there is no file.
ct.curLoc = ast.Location{File: ""}
return
}
ct.curLoc = ast.Location{File: filePath}
}
func (ct *Converter) GetCurFile() *ast.File {
if ct.curLoc.File == "" {
ct.logln("GetCurFile: NO FILE")
return nil
}
// todo(zzy): more efficient
for i, entry := range ct.Files {
if entry.Path == ct.curLoc.File {
ct.logln("GetCurFile: found", ct.curLoc.File)
return ct.Files[i].Doc
}
}
ct.logln("GetCurFile: Create New ast.File", ct.curLoc.File)
newDoc := &ast.File{}
ct.Files = append(ct.Files, &FileEntry{Path: ct.curLoc.File, Doc: newDoc})
return newDoc
}
func (ct *Converter) SetAnonyType(cursor clang.Cursor) {
usr := toStr(cursor.USR())
ct.anonyTypeMap[usr] = true
}
func (ct *Converter) GetAnonyType(cursor clang.Cursor) (bool, bool) {
usr := toStr(cursor.USR())
isAnony, ok := ct.anonyTypeMap[usr]
return isAnony, ok
}
func (ct *Converter) SetTypeDecl(cursor clang.Cursor, decl ast.Decl) {
usr := toStr(cursor.USR())
ct.typeDecls[usr] = decl
}
func (ct *Converter) GetTypeDecl(cursor clang.Cursor) (ast.Decl, bool) {
usr := toStr(cursor.USR())
decl, ok := ct.typeDecls[usr]
return decl, ok
}
func (ct *Converter) CreateDeclBase(cursor clang.Cursor) ast.DeclBase {
base := ast.DeclBase{
Loc: &ct.curLoc,
Parent: ct.BuildScopingExpr(cursor.SemanticParent()),
}
commentGroup, isDoc := ct.ParseCommentGroup(cursor)
if isDoc {
base.Doc = commentGroup
}
return base
}
// extracts and parses comments associated with a given Clang cursor,
// distinguishing between documentation comments and line comments.
// It uses libclang to parse only Doxygen-style comments.
// Reference for Doxygen documentation blocks: https://www.doxygen.nl/manual/docblocks.html
// The function determines whether a comment is a documentation comment or a line comment by
// comparing the range of the comment node with the range of the declaration node in the AST.
// Note: In cases where both documentation comments and line comments conceptually exist,
// only the line comment will be preserved.
func (ct *Converter) ParseCommentGroup(cursor clang.Cursor) (comentGroup *ast.CommentGroup, isDoc bool) {
rawComment := toStr(cursor.RawCommentText())
commentGroup := &ast.CommentGroup{}
if rawComment != "" {
commentRange := cursor.CommentRange()
cursorRange := cursor.Extent()
isDoc := getOffset(commentRange.RangeStart()) < getOffset(cursorRange.RangeStart())
commentGroup = ct.ParseComment(rawComment)
if len(commentGroup.List) > 0 {
return commentGroup, isDoc
}
}
return nil, false
}
func (ct *Converter) ParseComment(rawComment string) *ast.CommentGroup {
lines := strings.Split(rawComment, "\n")
commentGroup := &ast.CommentGroup{}
for _, line := range lines {
commentGroup.List = append(commentGroup.List, &ast.Comment{Text: line + "\n"})
}
return commentGroup
}
// visit top decls (struct,class,function,enum & macro,include)
func (ct *Converter) visitTop(cursor, parent clang.Cursor) clang.ChildVisitResult {
ct.incIndent()
defer ct.decIndent()
ct.UpdateLoc(cursor)
curFile := ct.GetCurFile()
name := toStr(cursor.String())
ct.logf("visitTop: Cursor: %s\n", name)
if curFile == nil {
return clang.ChildVisit_Continue
}
switch cursor.Kind {
case clang.CursorInclusionDirective:
include := ct.ProcessInclude(cursor)
curFile.Includes = append(curFile.Includes, include)
ct.logln("visitTop: ProcessInclude END ", include.Path)
case clang.CursorMacroDefinition:
macro := ct.ProcessMacro(cursor)
curFile.Macros = append(curFile.Macros, macro)
ct.logln("visitTop: ProcessMacro END ", macro.Name, "Tokens Length:", len(macro.Tokens))
case clang.CursorEnumDecl:
enum := ct.ProcessEnumDecl(cursor)
curFile.Decls = append(curFile.Decls, enum)
ct.logf("visitTop: ProcessEnumDecl END")
if enum.Name != nil {
ct.logln(enum.Name.Name)
} else {
ct.logln("ANONY")
}
case clang.CursorClassDecl:
classDecl := ct.ProcessClassDecl(cursor)
curFile.Decls = append(curFile.Decls, classDecl)
// class havent anonymous situation
ct.logln("visitTop: ProcessClassDecl END", classDecl.Name.Name)
case clang.CursorStructDecl:
structDecl := ct.ProcessStructDecl(cursor)
curFile.Decls = append(curFile.Decls, structDecl)
ct.logf("visitTop: ProcessStructDecl END")
if structDecl.Name != nil {
ct.logln(structDecl.Name.Name)
} else {
ct.logln("ANONY")
}
case clang.CursorUnionDecl:
unionDecl := ct.ProcessUnionDecl(cursor)
curFile.Decls = append(curFile.Decls, unionDecl)
ct.logf("visitTop: ProcessUnionDecl END")
if unionDecl.Name != nil {
ct.logln(unionDecl.Name.Name)
} else {
ct.logln("ANONY")
}
case clang.CursorFunctionDecl, clang.CursorCXXMethod, clang.CursorConstructor, clang.CursorDestructor:
// Handle functions and class methods (including out-of-class method)
// Example: void MyClass::myMethod() { ... } out-of-class method
funcDecl := ct.ProcessFuncDecl(cursor)
curFile.Decls = append(curFile.Decls, funcDecl)
ct.logln("visitTop: ProcessFuncDecl END", funcDecl.Name.Name, funcDecl.MangledName, "isStatic:", funcDecl.IsStatic, "isInline:", funcDecl.IsInline)
case clang.CursorTypedefDecl:
typedefDecl := ct.ProcessTypeDefDecl(cursor)
curFile.Decls = append(curFile.Decls, typedefDecl)
ct.logln("visitTop: ProcessTypeDefDecl END", typedefDecl.Name.Name)
case clang.CursorNamespace:
VisitChildren(cursor, ct.visitTop)
}
return clang.ChildVisit_Continue
}
func (ct *Converter) Convert() ([]*FileEntry, error) {
cursor := ct.unit.Cursor()
// visit top decls (struct,class,function & macro,include)
VisitChildren(cursor, ct.visitTop)
return ct.Files, nil
}
type Visitor func(cursor, parent clang.Cursor) clang.ChildVisitResult
func VisitChildren(cursor clang.Cursor, fn Visitor) c.Uint {
return clang.VisitChildren(cursor, func(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult {
cfn := *(*Visitor)(clientData)
return cfn(cursor, parent)
}, unsafe.Pointer(&fn))
}
func (ct *Converter) ProcessType(t clang.Type) ast.Expr {
ct.incIndent()
defer ct.decIndent()
typeName, typeKind := getTypeDesc(t)
ct.logln("ProcessType: TypeName:", typeName, "TypeKind:", typeKind)
if t.Kind >= clang.TypeFirstBuiltin && t.Kind <= clang.TypeLastBuiltin {
return ct.ProcessBuiltinType(t)
}
if t.Kind == clang.TypeElaborated {
return ct.ProcessElaboratedType(t)
}
if t.Kind == clang.TypeTypedef {
return ct.ProcessTypeDefType(t)
}
var expr ast.Expr
switch t.Kind {
case clang.TypePointer:
name, kind := getTypeDesc(t.PointeeType())
ct.logln("ProcessType: PointerType Pointee TypeName:", name, "TypeKind:", kind)
expr = &ast.PointerType{X: ct.ProcessType(t.PointeeType())}
case clang.TypeLValueReference:
name, kind := getTypeDesc(t.NonReferenceType())
ct.logln("ProcessType: LvalueRefType NonReference TypeName:", name, "TypeKind:", kind)
expr = &ast.LvalueRefType{X: ct.ProcessType(t.NonReferenceType())}
case clang.TypeRValueReference:
name, kind := getTypeDesc(t.NonReferenceType())
ct.logln("ProcessType: RvalueRefType NonReference TypeName:", name, "TypeKind:", kind)
expr = &ast.RvalueRefType{X: ct.ProcessType(t.NonReferenceType())}
case clang.TypeFunctionProto, clang.TypeFunctionNoProto:
// treating TypeFunctionNoProto as a general function without parameters
// function type will only collect return type, params will be collected in ProcessFuncDecl
name, kind := getTypeDesc(t)
ct.logln("ProcessType: FunctionType TypeName:", name, "TypeKind:", kind)
expr = ct.ProcessFunctionType(t)
case clang.TypeConstantArray, clang.TypeIncompleteArray, clang.TypeVariableArray, clang.TypeDependentSizedArray:
if t.Kind == clang.TypeConstantArray {
len := (*c.Char)(c.Malloc(unsafe.Sizeof(c.Char(0)) * 20))
c.Sprintf(len, c.Str("%lld"), t.ArraySize())
defer c.Free(unsafe.Pointer(len))
expr = &ast.ArrayType{
Elt: ct.ProcessType(t.ArrayElementType()),
Len: &ast.BasicLit{Kind: ast.IntLit, Value: c.GoString(len)},
}
} else if t.Kind == clang.TypeIncompleteArray {
// incomplete array havent len expr
expr = &ast.ArrayType{
Elt: ct.ProcessType(t.ArrayElementType()),
}
}
default:
name, kind := getTypeDesc(t)
ct.logln("ProcessType: Unknown Type TypeName:", name, "TypeKind:", kind)
}
return expr
}
// For function types, we can only obtain the parameter types, but not the parameter names.
// This is because we cannot reverse-lookup the corresponding declaration node from a function type.
// Note: For function declarations, parameter names are collected in the ProcessFuncDecl method.
func (ct *Converter) ProcessFunctionType(t clang.Type) *ast.FuncType {
ct.incIndent()
defer ct.decIndent()
typeName, typeKind := getTypeDesc(t)
ct.logln("ProcessFunctionType: TypeName:", typeName, "TypeKind:", typeKind)
// Note: Attempting to get the type declaration for a function type will result in CursorNoDeclFound
// cursor := t.TypeDeclaration()
// This would return CursorNoDeclFound
resType := t.ResultType()
name, kind := getTypeDesc(resType)
ct.logln("ProcessFunctionType: ResultType TypeName:", name, "TypeKind:", kind)
ret := ct.ProcessType(resType)
params := &ast.FieldList{}
numArgs := t.NumArgTypes()
for i := 0; i < int(numArgs); i++ {
argType := t.ArgType(c.Uint(i))
params.List = append(params.List, &ast.Field{
Type: ct.ProcessType(argType),
})
}
if t.IsFunctionTypeVariadic() != 0 {
params.List = append(params.List, &ast.Field{
Type: &ast.Variadic{},
})
}
return &ast.FuncType{
Ret: ret,
Params: params,
}
}
func (ct *Converter) ProcessTypeDefDecl(cursor clang.Cursor) *ast.TypedefDecl {
ct.incIndent()
defer ct.decIndent()
name, kind := getCursorDesc(cursor)
ct.logln("ProcessTypeDefDecl: CursorName:", name, "CursorKind:", kind, "CursorTypeKind:", toStr(cursor.Type().Kind.String()))
typ := ct.ProcessUnderlyingType(cursor)
decl := &ast.TypedefDecl{
DeclBase: ct.CreateDeclBase(cursor),
Name: &ast.Ident{Name: name},
Type: typ,
}
ct.SetTypeDecl(cursor, decl)
return decl
}
func (ct *Converter) ProcessUnderlyingType(cursor clang.Cursor) ast.Expr {
underlyingTyp := cursor.TypedefDeclUnderlyingType()
if underlyingTyp.Kind != clang.TypeElaborated {
ct.logln("ProcessUnderlyingType: not elaborated")
return ct.ProcessType(underlyingTyp)
}
referTypeCursor := underlyingTyp.TypeDeclaration()
// If the type decl for the reference already exists in anonyTypeMap
// then the refer has been processed in ProcessElaboratedType
if _, ok := ct.GetAnonyType(referTypeCursor); !ok && isCursorChildOf(referTypeCursor, cursor) {
// Handle unexpected named structures generated from anonymous RecordTypes in Typedefs
// In this case, libclang incorrectly reports an anonymous struct as a named struct
sourceCode := ct.GetTokens(referTypeCursor)
if isAnonymousStructure(sourceCode) {
ct.logln("ProcessUnderlyingType: is anonymous structure")
ct.SetAnonyType(referTypeCursor)
typ, isValidType := ct.GetTypeDecl(referTypeCursor)
if isValidType {
// There will be no anonymous classes,here will execute enum,union,struct
// according to a normal anonymous decl
switch declType := typ.(type) {
case *ast.EnumTypeDecl:
ct.logln("ProcessUnderlyingType: is actually anonymous enum,remove name")
declType.Name = nil
case *ast.TypeDecl:
if declType.Type.Tag != ast.Class {
ct.logln("ProcessUnderlyingType: is actually anonymous struct,remove name")
declType.Name = nil
} else {
// Unreachable: There should be no anonymous classes in this context
fmt.Fprintln(os.Stderr, "unexpect typedef anonymous class %s", declType.Name.Name)
}
}
} else {
// Unreachable:When referencing an anonymous node, its collection must have been completed beforehand
fmt.Fprintln(os.Stderr, "anonymous node not collected before reference")
}
}
}
return ct.ProcessElaboratedType(underlyingTyp)
}
// converts functions, methods, constructors, destructors (including out-of-class decl) to ast.FuncDecl nodes.
func (ct *Converter) ProcessFuncDecl(cursor clang.Cursor) *ast.FuncDecl {
ct.incIndent()
defer ct.decIndent()
name, kind := getCursorDesc(cursor)
mangledName := toStr(cursor.Mangling())
ct.logln("ProcessFuncDecl: CursorName:", name, "CursorKind:", kind)
// function type will only collect return type
// ProcessType can't get the field names,will collect in follows
funcType, ok := ct.ProcessType(cursor.Type()).(*ast.FuncType)
if !ok {
ct.logln("ProcessFuncDecl: failed to process function type")
return nil
}
ct.logln("ProcessFuncDecl: ProcessFieldList")
params := ct.ProcessFieldList(cursor)
funcType.Params = params
mangledName = strings.TrimLeft(mangledName, "_")
funcDecl := &ast.FuncDecl{
DeclBase: ct.CreateDeclBase(cursor),
Name: &ast.Ident{Name: name},
Type: funcType,
MangledName: mangledName,
}
if cursor.IsFunctionInlined() != 0 {
funcDecl.IsInline = true
}
if isMethod(cursor) {
ct.logln("ProcessFuncDecl: is method, ProcessMethodAttributes")
ct.ProcessMethodAttributes(cursor, funcDecl)
} else {
if cursor.StorageClass() == clang.SCStatic {
funcDecl.IsStatic = true
}
}
ct.SetTypeDecl(cursor, funcDecl)
return funcDecl
}
// get Methods Attributes
func (ct *Converter) ProcessMethodAttributes(cursor clang.Cursor, fn *ast.FuncDecl) {
if parent := cursor.SemanticParent(); parent.Equal(cursor.LexicalParent()) != 1 {
fn.DeclBase.Parent = ct.BuildScopingExpr(cursor.SemanticParent())
}
switch cursor.Kind {
case clang.CursorDestructor:
fn.IsDestructor = true
case clang.CursorConstructor:
fn.IsConstructor = true
if cursor.IsExplicit() != 0 {
fn.IsExplicit = true
}
}
if cursor.IsStatic() != 0 {
fn.IsStatic = true
}
if cursor.IsVirtual() != 0 || cursor.IsPureVirtual() != 0 {
fn.IsVirtual = true
}
if cursor.IsConst() != 0 {
fn.IsConst = true
}
var numOverridden c.Uint
var overridden *clang.Cursor
cursor.OverriddenCursors(&overridden, &numOverridden)
if numOverridden > 0 {
fn.IsOverride = true
}
overridden.DisposeOverriddenCursors()
}
func (ct *Converter) ProcessEnumType(cursor clang.Cursor) *ast.EnumType {
items := make([]*ast.EnumItem, 0)
VisitChildren(cursor, func(cursor, parent clang.Cursor) clang.ChildVisitResult {
if cursor.Kind == clang.CursorEnumConstantDecl {
name := cursor.String()
defer name.Dispose()
val := (*c.Char)(c.Malloc(unsafe.Sizeof(c.Char(0)) * 20))
c.Sprintf(val, c.Str("%lld"), cursor.EnumConstantDeclValue())
defer c.Free(unsafe.Pointer(val))
enum := &ast.EnumItem{
Name: &ast.Ident{Name: c.GoString(name.CStr())},
Value: &ast.BasicLit{
Kind: ast.IntLit,
Value: c.GoString(val),
},
}
items = append(items, enum)
}
return clang.ChildVisit_Continue
})
return &ast.EnumType{
Items: items,
}
}
func (ct *Converter) ProcessEnumDecl(cursor clang.Cursor) *ast.EnumTypeDecl {
name := toStr(cursor.String())
decl := &ast.EnumTypeDecl{
DeclBase: ct.CreateDeclBase(cursor),
Name: &ast.Ident{Name: name},
Type: ct.ProcessEnumType(cursor),
}
ct.SetTypeDecl(cursor, decl)
return decl
}
// current only collect macro which defined in file
func (ct *Converter) ProcessMacro(cursor clang.Cursor) *ast.Macro {
name := toStr(cursor.String())
macro := &ast.Macro{
Name: name,
Tokens: ct.GetTokens(cursor),
}
return macro
}
func (ct *Converter) ProcessInclude(cursor clang.Cursor) *ast.Include {
name := toStr(cursor.String())
return &ast.Include{Path: name}
}
// todo(zzy): after https://github.com/goplus/llgo/issues/804 has be resolved
// Change the following code to use the closure
type visitFieldContext struct {
params *ast.FieldList
converter *Converter
}
func (p *visitFieldContext) createBaseField(cursor clang.Cursor) *ast.Field {
p.converter.incIndent()
defer p.converter.decIndent()
fieldName := toStr(cursor.String())
typ := cursor.Type()
typeName, typeKind := getTypeDesc(typ)
p.converter.logf("createBaseField: ProcessType %s TypeKind: %s", typeName, typeKind)
field := &ast.Field{
Type: p.converter.ProcessType(typ),
}
commentGroup, isDoc := p.converter.ParseCommentGroup(cursor)
if commentGroup != nil {
if isDoc {
field.Doc = commentGroup
} else {
field.Comment = commentGroup
}
}
if fieldName != "" {
field.Names = []*ast.Ident{{Name: fieldName}}
}
return field
}
func visitFieldList(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult {
ctx := (*visitFieldContext)(clientData)
switch cursor.Kind {
case clang.CursorParmDecl, clang.CursorFieldDecl:
// In C language, parameter lists do not have similar parameter grouping in Go.
// func foo(a, b int)
// For follows struct, it will also parse to two FieldDecl
// struct A {
// int a, b;
// };
if cursor.Kind == clang.CursorFieldDecl {
ctx.converter.logln("visitFieldList: CursorFieldDecl")
} else {
ctx.converter.logln("visitFieldList: CursorParmDecl")
}
field := ctx.createBaseField(cursor)
if cursor.Kind == clang.CursorFieldDecl {
field.Access = ast.AccessSpecifier(cursor.CXXAccessSpecifier())
}
ctx.params.List = append(ctx.params.List, field)
case clang.CursorVarDecl:
if cursor.StorageClass() == clang.SCStatic {
// static member variable
field := ctx.createBaseField(cursor)
field.Access = ast.AccessSpecifier(cursor.CXXAccessSpecifier())
field.IsStatic = true
ctx.params.List = append(ctx.params.List, field)
}
}
return clang.ChildVisit_Continue
}
// For Record Type(struct,union ...) & Func 's FieldList
func (ct *Converter) ProcessFieldList(cursor clang.Cursor) *ast.FieldList {
ct.incIndent()
defer ct.decIndent()
params := &ast.FieldList{}
ctx := &visitFieldContext{
params: params,
converter: ct,
}
ct.logln("ProcessFieldList: VisitChildren")
clang.VisitChildren(cursor, visitFieldList, c.Pointer(ctx))
if (cursor.Kind == clang.CursorFunctionDecl || isMethod(cursor)) && cursor.IsVariadic() != 0 {
params.List = append(params.List, &ast.Field{
Type: &ast.Variadic{},
})
}
return params
}
type visitMethodsContext struct {
methods *[]*ast.FuncDecl
converter *Converter
}
func visitMethods(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult {
ctx := (*visitMethodsContext)(clientData)
if isMethod(cursor) && cursor.CXXAccessSpecifier() == clang.CXXPublic {
method := ctx.converter.ProcessFuncDecl(cursor)
if method != nil {
*ctx.methods = append(*ctx.methods, method)
}
}
return clang.ChildVisit_Continue
}
// Note:Public Method is considered
func (ct *Converter) ProcessMethods(cursor clang.Cursor) []*ast.FuncDecl {
methods := make([]*ast.FuncDecl, 0)
ctx := &visitMethodsContext{
methods: &methods,
converter: ct,
}
clang.VisitChildren(cursor, visitMethods, c.Pointer(ctx))
return methods
}
func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) *ast.TypeDecl {
ct.incIndent()
defer ct.decIndent()
cursorName, cursorKind := getCursorDesc(cursor)
ct.logln("ProcessRecordDecl: CursorName:", cursorName, "CursorKind:", cursorKind)
anony := cursor.IsAnonymousRecordDecl()
var name *ast.Ident
if anony == 0 {
name = &ast.Ident{Name: cursorName}
ct.logln("ProcessRecordDecl: has name", cursorName)
} else {
ct.logln("ProcessRecordDecl: is anonymous")
}
decl := &ast.TypeDecl{
DeclBase: ct.CreateDeclBase(cursor),
Name: name,
Type: ct.ProcessRecordType(cursor),
}
ct.SetTypeDecl(cursor, decl)
return decl
}
func (ct *Converter) ProcessStructDecl(cursor clang.Cursor) *ast.TypeDecl {
return ct.ProcessRecordDecl(cursor)
}
func (ct *Converter) ProcessUnionDecl(cursor clang.Cursor) *ast.TypeDecl {
return ct.ProcessRecordDecl(cursor)
}
func (ct *Converter) ProcessClassDecl(cursor clang.Cursor) *ast.TypeDecl {
cursorName, cursorKind := getCursorDesc(cursor)
ct.logln("ProcessClassDecl: CursorName:", cursorName, "CursorKind:", cursorKind)
// Pushing class scope before processing its type and popping after
base := ct.CreateDeclBase(cursor)
typ := ct.ProcessRecordType(cursor)
decl := &ast.TypeDecl{
DeclBase: base,
Name: &ast.Ident{Name: c.GoString(cursor.String().CStr())},
Type: typ,
}
ct.SetTypeDecl(cursor, decl)
return decl
}
func (ct *Converter) ProcessRecordType(cursor clang.Cursor) *ast.RecordType {
ct.incIndent()
defer ct.decIndent()
cursorName, cursorKind := getCursorDesc(cursor)
ct.logln("ProcessRecordType: CursorName:", cursorName, "CursorKind:", cursorKind)
tag := toTag(cursor.Kind)
ct.logln("ProcessRecordType: toTag", tag)
ct.logln("ProcessRecordType: ProcessFieldList")
fields := ct.ProcessFieldList(cursor)
ct.logln("ProcessRecordType: ProcessMethods")
methods := ct.ProcessMethods(cursor)
return &ast.RecordType{
Tag: tag,
Fields: fields,
Methods: methods,
}
}
// process ElaboratedType Reference
//
// 1. Named elaborated type references:
// - Examples: struct MyStruct, union MyUnion, class MyClass, enum MyEnum
// - Handling: Constructed as TagExpr or ScopingExpr references
//
// 2. Anonymous elaborated type references:
// - Examples: struct { int x; int y; }, union { int a; float b; }
// - Handling: Retrieve their corresponding concrete types
func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr {
ct.incIndent()
defer ct.decIndent()
typeName, typeKind := getTypeDesc(t)
ct.logln("ProcessElaboratedType: TypeName:", typeName, "TypeKind:", typeKind)
decl := t.TypeDeclaration()
isAnony, ok := ct.GetAnonyType(decl)
if decl.IsAnonymous() != 0 || isAnony && ok {
// anonymous type refer (except anonymous RecordType&EnumType in TypedefDecl)
if decl.Kind == clang.CursorEnumDecl {
return ct.ProcessEnumType(decl)
}
return ct.ProcessRecordType(decl)
}
// for elaborated type, it could have a tag description
// like struct A, union B, class C, enum D
parts := strings.SplitN(typeName, " ", 2)
if len(parts) == 2 {
if tagValue, ok := tagMap[parts[0]]; ok {
return &ast.TagExpr{
Tag: tagValue,
Name: ct.BuildScopingExpr(decl),
}
}
}
return ct.BuildScopingExpr(decl)
}
func (ct *Converter) ProcessTypeDefType(t clang.Type) ast.Expr {
cursor := t.TypeDeclaration()
ct.logln("ProcessTypeDefType: Typedef TypeDeclaration", toStr(cursor.String()), toStr(t.String()))
if name := toStr(cursor.String()); name != "" {
return &ast.Ident{Name: name}
}
ct.logln("ProcessTypeDefType: typedef type have no name")
return nil
}
func (ct *Converter) ProcessBuiltinType(t clang.Type) *ast.BuiltinType {
ct.incIndent()
defer ct.decIndent()
typeName, typeKind := getTypeDesc(t)
ct.logln("ProcessBuiltinType: TypeName:", typeName, "TypeKind:", typeKind)
kind := ast.Void
var flags ast.TypeFlag
switch t.Kind {
case clang.TypeVoid:
kind = ast.Void
case clang.TypeBool:
kind = ast.Bool
case clang.TypeCharU, clang.TypeUChar, clang.TypeCharS, clang.TypeSChar:
kind = ast.Char
case clang.TypeChar16:
kind = ast.Char16
case clang.TypeChar32:
kind = ast.Char32
case clang.TypeWChar:
kind = ast.WChar
case clang.TypeShort, clang.TypeUShort:
kind = ast.Int
flags |= ast.Short
case clang.TypeInt, clang.TypeUInt:
kind = ast.Int
case clang.TypeLong, clang.TypeULong:
kind = ast.Int
flags |= ast.Long
case clang.TypeLongLong, clang.TypeULongLong:
kind = ast.Int
flags |= ast.LongLong
case clang.TypeInt128, clang.TypeUInt128:
kind = ast.Int128
case clang.TypeFloat:
kind = ast.Float
case clang.TypeHalf, clang.TypeFloat16:
kind = ast.Float16
case clang.TypeDouble:
kind = ast.Float
flags |= ast.Double
case clang.TypeLongDouble:
kind = ast.Float
flags |= ast.Long | ast.Double
case clang.TypeFloat128:
kind = ast.Float128
case clang.TypeComplex:
kind = ast.Complex
complexKind := t.ElementType().Kind
if complexKind == clang.TypeLongDouble {
flags |= ast.Long | ast.Double
} else if complexKind == clang.TypeDouble {
flags |= ast.Double
}
// float complfex flag is not set
default:
// like IBM128,NullPtr,Accum
kindStr := toStr(t.Kind.String())
fmt.Fprintln(os.Stderr, "todo: unknown builtin type:", kindStr)
}
if IsExplicitSigned(t) {
flags |= ast.Signed
} else if IsExplicitUnsigned(t) {
flags |= ast.Unsigned
}
return &ast.BuiltinType{
Kind: kind,
Flags: flags,
}
}
// Constructs a complete scoping expression by traversing the semantic parents, starting from the given clang.Cursor
// For anonymous decl of typedef references, use their anonymous name
func (ct *Converter) BuildScopingExpr(cursor clang.Cursor) ast.Expr {
parts := clangutils.BuildScopingParts(cursor)
return buildScopingFromParts(parts)
}
func (ct *Converter) MarshalASTFiles() *cjson.JSON {
return MarshalASTFiles(ct.Files)
}
func (ct *Converter) MarshalOutputASTFiles() *cjson.JSON {
return MarshalOutputASTFiles(ct.Files)
}
func IsExplicitSigned(t clang.Type) bool {
return t.Kind == clang.TypeCharS || t.Kind == clang.TypeSChar
}
func IsExplicitUnsigned(t clang.Type) bool {
return t.Kind == clang.TypeCharU || t.Kind == clang.TypeUChar ||
t.Kind == clang.TypeUShort || t.Kind == clang.TypeUInt ||
t.Kind == clang.TypeULong || t.Kind == clang.TypeULongLong ||
t.Kind == clang.TypeUInt128
}
func toTag(kind clang.CursorKind) ast.Tag {
switch kind {
case clang.CursorStructDecl:
return ast.Struct
case clang.CursorUnionDecl:
return ast.Union
case clang.CursorClassDecl:
return ast.Class
default:
panic(fmt.Sprintf("Unexpected cursor kind in toTag: %v", kind))
}
}
func toToken(tok clang.Token) token.Token {
if tok.Kind() < clang.Punctuation || tok.Kind() > clang.Comment {
return token.ILLEGAL
} else {
return token.Token(tok.Kind() + 1)
}
}
func isMethod(cursor clang.Cursor) bool {
return cursor.Kind == clang.CursorCXXMethod || cursor.Kind == clang.CursorConstructor || cursor.Kind == clang.CursorDestructor
}
func buildScopingFromParts(parts []string) ast.Expr {
if len(parts) == 0 {
return nil
}
var expr ast.Expr = &ast.Ident{Name: parts[0]}
for _, part := range parts[1:] {
expr = &ast.ScopingExpr{
Parent: expr,
X: &ast.Ident{Name: part},
}
}
return expr
}
// isCursorChildOf checks if the child cursor is contained within the parent cursor.
// This function is necessary because libclang doesn't correctly report the lexical
// or semantic parent for anonymous structs inside typedefs. By comparing source ranges,
// we can determine if one cursor is nested inside another.
func isCursorChildOf(child, parent clang.Cursor) bool {
return isRangeChildOf(child.Extent(), parent.Extent())
}
func isRangeChildOf(childRange, parentRange clang.SourceRange) bool {
return getOffset(childRange.RangeStart()) >= getOffset(parentRange.RangeStart()) &&
getOffset(childRange.RangeEnd()) <= getOffset(parentRange.RangeEnd())
}
func getOffset(location clang.SourceLocation) c.Uint {
var offset c.Uint
location.SpellingLocation(nil, nil, nil, &offset)
return offset
}
// checks if the source code represents an actual anonymous structure
func isAnonymousStructure(sourceCode []*ast.Token) bool {
_, isValidTag := tagMap[sourceCode[0].Lit]
return sourceCode[0].Token == token.KEYWORD &&
isValidTag &&
sourceCode[1].Token == token.PUNCT &&
sourceCode[1].Lit == "{"
}
func toStr(clangStr clang.String) (str string) {
defer clangStr.Dispose()
if clangStr.CStr() != nil {
str = c.GoString(clangStr.CStr())
}
return
}
func getTypeDesc(t clang.Type) (name string, kind string) {
name = toStr(t.String())
kind = toStr(t.Kind.String())
return
}
func getCursorDesc(cursor clang.Cursor) (name string, kind string) {
name = toStr(cursor.String())
kind = toStr(cursor.Kind.String())
return
}