Merge pull request #788 from luoliwoshang/llcppsymg/classname
llcppsymg:refine class fetch,test case,-v
This commit is contained in:
119
chore/_xtool/llcppsymg/_cmptest/clangutils_test/clangutils.go
Normal file
119
chore/_xtool/llcppsymg/_cmptest/clangutils_test/clangutils.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/goplus/llgo/c"
|
||||
"github.com/goplus/llgo/c/clang"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/clangutils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
TestClangUtil()
|
||||
}
|
||||
|
||||
func TestClangUtil() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
content string
|
||||
isTemp bool
|
||||
isCpp bool
|
||||
}{
|
||||
{
|
||||
name: "C Header File",
|
||||
content: `
|
||||
int test_function(int a, int b);
|
||||
void another_function(void);
|
||||
`,
|
||||
isTemp: false,
|
||||
isCpp: false,
|
||||
},
|
||||
{
|
||||
name: "C++ Temp File",
|
||||
content: `
|
||||
class TestClass {
|
||||
public:
|
||||
void test_method();
|
||||
static int static_method(float f);
|
||||
};
|
||||
|
||||
namespace TestNamespace {
|
||||
void namespaced_function();
|
||||
}
|
||||
`,
|
||||
isTemp: true,
|
||||
isCpp: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Printf("=== Test Case: %s ===\n", tc.name)
|
||||
|
||||
var filePath string
|
||||
var tempFile *os.File
|
||||
if tc.isTemp {
|
||||
filePath = tc.content
|
||||
} else {
|
||||
var err error
|
||||
tempFile, err = os.CreateTemp("", "test_*.h")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create temporary file: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = tempFile.Write([]byte(tc.content))
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to write to temporary file: %v\n", err)
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name())
|
||||
continue
|
||||
}
|
||||
tempFile.Close()
|
||||
filePath = tempFile.Name()
|
||||
}
|
||||
|
||||
config := &clangutils.Config{
|
||||
File: filePath,
|
||||
Temp: tc.isTemp,
|
||||
IsCpp: tc.isCpp,
|
||||
}
|
||||
index, unit, err := clangutils.CreateTranslationUnit(config)
|
||||
if err != nil {
|
||||
fmt.Printf("CreateTranslationUnit failed: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("CreateTranslationUnit succeeded")
|
||||
|
||||
cursor := unit.Cursor()
|
||||
|
||||
clangutils.VisitChildren(cursor, func(cursor, parent clang.Cursor) clang.ChildVisitResult {
|
||||
switch cursor.Kind {
|
||||
case clang.CursorFunctionDecl, clang.CursorCXXMethod:
|
||||
funcName := cursor.String()
|
||||
fmt.Printf("Function/Method: %s\n", c.GoString(funcName.CStr()))
|
||||
parts := clangutils.BuildScopingParts(cursor)
|
||||
fmt.Printf("Scoping parts: %v\n", parts)
|
||||
funcName.Dispose()
|
||||
case clang.CursorClassDecl:
|
||||
className := cursor.String()
|
||||
fmt.Printf("Class: %s\n", c.GoString(className.CStr()))
|
||||
className.Dispose()
|
||||
case clang.CursorNamespace:
|
||||
namespaceName := cursor.String()
|
||||
fmt.Printf("Namespace: %s\n", c.GoString(namespaceName.CStr()))
|
||||
namespaceName.Dispose()
|
||||
}
|
||||
return clang.ChildVisit_Recurse
|
||||
})
|
||||
index.Dispose()
|
||||
unit.Dispose()
|
||||
|
||||
if !tc.isTemp && tempFile != nil {
|
||||
os.Remove(tempFile.Name())
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
23
chore/_xtool/llcppsymg/_cmptest/clangutils_test/llgo.expect
Normal file
23
chore/_xtool/llcppsymg/_cmptest/clangutils_test/llgo.expect
Normal file
@@ -0,0 +1,23 @@
|
||||
#stdout
|
||||
=== Test Case: C Header File ===
|
||||
CreateTranslationUnit succeeded
|
||||
Function/Method: test_function
|
||||
Scoping parts: [test_function]
|
||||
Function/Method: another_function
|
||||
Scoping parts: [another_function]
|
||||
|
||||
=== Test Case: C++ Temp File ===
|
||||
CreateTranslationUnit succeeded
|
||||
Class: TestClass
|
||||
Function/Method: test_method
|
||||
Scoping parts: [TestClass test_method]
|
||||
Function/Method: static_method
|
||||
Scoping parts: [TestClass static_method]
|
||||
Namespace: TestNamespace
|
||||
Function/Method: namespaced_function
|
||||
Scoping parts: [TestNamespace namespaced_function]
|
||||
|
||||
|
||||
#stderr
|
||||
|
||||
#exit 0
|
||||
332
chore/_xtool/llcppsymg/_cmptest/config_test/config.go
Normal file
332
chore/_xtool/llcppsymg/_cmptest/config_test/config.go
Normal file
@@ -0,0 +1,332 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config/cfgparse"
|
||||
)
|
||||
|
||||
func main() {
|
||||
TestGetConf()
|
||||
TestParseLibs()
|
||||
TestGenDylibPaths()
|
||||
TestParseCFlags()
|
||||
TestGenHeaderFilePath()
|
||||
}
|
||||
|
||||
func TestGetConf() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
name: "SQLite configuration",
|
||||
input: `{
|
||||
"name": "sqlite",
|
||||
"cflags": "-I/opt/homebrew/opt/sqlite/include",
|
||||
"include": ["sqlite3.h"],
|
||||
"libs": "-L/opt/homebrew/opt/sqlite/lib -lsqlite3",
|
||||
"trimPrefixes": ["sqlite3_"],
|
||||
"cplusplus": false
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "Lua configuration",
|
||||
input: `{
|
||||
"name": "lua",
|
||||
"cflags": "-I/opt/homebrew/include/lua",
|
||||
"include": ["lua.h"],
|
||||
"libs": "-L/opt/homebrew/lib -llua -lm",
|
||||
"trimPrefixes": ["lua_", "lua_"],
|
||||
"cplusplus": false
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON",
|
||||
input: `{invalid json}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Printf("=== Test case: %s ===\n", tc.name)
|
||||
result, err := config.GetConf([]byte(tc.input))
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err.Error())
|
||||
} else {
|
||||
fmt.Println("Name:", result.Config.Name)
|
||||
fmt.Println("CFlags:", result.Config.CFlags)
|
||||
fmt.Println("Libs:", result.Config.Libs)
|
||||
fmt.Println("Include:", strings.Join(result.Config.Include, ", "))
|
||||
fmt.Println("TrimPrefixes:", strings.Join(result.Config.TrimPrefixes, ", "))
|
||||
fmt.Println("Cplusplus:", result.Config.Cplusplus)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseLibs() {
|
||||
fmt.Println("=== Test ParseLibs ===")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
name: "Lua library",
|
||||
input: "-L/opt/homebrew/lib -llua -lm",
|
||||
},
|
||||
{
|
||||
name: "SQLite library",
|
||||
input: "-L/opt/homebrew/opt/sqlite/lib -lsqlite3",
|
||||
},
|
||||
{
|
||||
name: "INIReader library",
|
||||
input: "-L/opt/homebrew/Cellar/inih/58/lib -lINIReader",
|
||||
},
|
||||
{
|
||||
name: "Multiple library paths",
|
||||
input: "-L/opt/homebrew/lib -L/usr/lib -llua",
|
||||
},
|
||||
{
|
||||
name: "No valid library",
|
||||
input: "-L/opt/homebrew/lib",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Printf("Test case: %s\n", tc.name)
|
||||
fmt.Printf("Input: %s\n", tc.input)
|
||||
|
||||
conf := cfgparse.ParseLibs(tc.input)
|
||||
|
||||
fmt.Println("Paths:", conf.Paths)
|
||||
fmt.Println("Names:", conf.Names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenDylibPaths() {
|
||||
fmt.Println("=== Test GenDylibPaths ===")
|
||||
|
||||
tempDir := os.TempDir()
|
||||
tempDefaultPath := filepath.Join(tempDir, "symblib")
|
||||
affix := ".dylib"
|
||||
if runtime.GOOS == "linux" {
|
||||
affix = ".so"
|
||||
}
|
||||
err := os.MkdirAll(tempDefaultPath, 0755)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create temp default path: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
dylib1 := filepath.Join(tempDir, "libsymb1"+affix)
|
||||
dylib2 := filepath.Join(tempDir, "libsymb2"+affix)
|
||||
defaultDylib3 := filepath.Join(tempDefaultPath, "libsymb3"+affix)
|
||||
|
||||
os.Create(dylib1)
|
||||
os.Create(dylib2)
|
||||
os.Create(defaultDylib3)
|
||||
defer os.Remove(dylib1)
|
||||
defer os.Remove(dylib2)
|
||||
defer os.Remove(defaultDylib3)
|
||||
defer os.Remove(tempDefaultPath)
|
||||
|
||||
testCase := []struct {
|
||||
name string
|
||||
conf *cfgparse.Libs
|
||||
defaultPaths []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "existing dylib",
|
||||
conf: &cfgparse.Libs{
|
||||
Names: []string{"symb1"},
|
||||
Paths: []string{tempDir},
|
||||
},
|
||||
defaultPaths: []string{},
|
||||
want: []string{dylib1},
|
||||
},
|
||||
{
|
||||
name: "existing dylibs",
|
||||
conf: &cfgparse.Libs{
|
||||
Names: []string{"symb1", "symb2"},
|
||||
Paths: []string{tempDir},
|
||||
},
|
||||
defaultPaths: []string{},
|
||||
want: []string{dylib1, dylib2},
|
||||
},
|
||||
{
|
||||
name: "existint default paths",
|
||||
conf: &cfgparse.Libs{
|
||||
Names: []string{"symb1", "symb3"},
|
||||
Paths: []string{tempDir},
|
||||
},
|
||||
defaultPaths: []string{tempDefaultPath},
|
||||
want: []string{dylib1, defaultDylib3},
|
||||
},
|
||||
{
|
||||
name: "existint default paths & not found",
|
||||
conf: &cfgparse.Libs{
|
||||
Names: []string{"symb1", "symb3", "math"},
|
||||
Paths: []string{tempDir},
|
||||
},
|
||||
defaultPaths: []string{tempDefaultPath},
|
||||
want: []string{dylib1, defaultDylib3},
|
||||
},
|
||||
{
|
||||
name: "no existing dylib",
|
||||
conf: &cfgparse.Libs{
|
||||
Names: []string{"notexist"},
|
||||
Paths: []string{tempDir},
|
||||
},
|
||||
want: []string{},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCase {
|
||||
fmt.Printf("Test case: %s\n", tc.name)
|
||||
paths, notFounds, err := tc.conf.GenDylibPaths(tc.defaultPaths)
|
||||
|
||||
if len(notFounds) > 0 {
|
||||
fmt.Println("notFounds", notFounds)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
found := false
|
||||
for _, wantPath := range tc.want {
|
||||
if path == wantPath {
|
||||
found = true
|
||||
fileName := filepath.Base(path)
|
||||
if runtime.GOOS == "linux" {
|
||||
fileName = strings.TrimSuffix(fileName, ".so")
|
||||
} else {
|
||||
fileName = strings.TrimSuffix(fileName, ".dylib")
|
||||
}
|
||||
fmt.Printf("Path %s is in the expected paths\n", fileName)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
fmt.Printf("Path %s is not in the expected paths\n", path)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCFlags() {
|
||||
fmt.Println("=== Test ParseCFlags ===")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
name: "Single include path",
|
||||
input: "-I/usr/include",
|
||||
},
|
||||
{
|
||||
name: "Multiple include paths",
|
||||
input: "-I/usr/include -I/opt/homebrew/include",
|
||||
},
|
||||
{
|
||||
name: "Include paths mixed with other flags",
|
||||
input: "-I/usr/include -DDEBUG -I/opt/local/include -Wall",
|
||||
},
|
||||
{
|
||||
name: "Empty input",
|
||||
input: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Printf("Test case: %s\n", tc.name)
|
||||
fmt.Printf("Input: %s\n", tc.input)
|
||||
|
||||
conf := cfgparse.ParseCFlags(tc.input)
|
||||
|
||||
fmt.Println("Paths:", conf.Paths)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenHeaderFilePath() {
|
||||
fmt.Println("=== Test GenHeaderFilePath ===")
|
||||
|
||||
tempDir := os.TempDir()
|
||||
temDir2 := filepath.Join(tempDir, "include")
|
||||
tempFile1 := filepath.Join(tempDir, "test1.h")
|
||||
tempFile2 := filepath.Join(tempDir, "test2.h")
|
||||
tempFile3 := filepath.Join(temDir2, "test3.h")
|
||||
os.MkdirAll(temDir2, 0755)
|
||||
os.Create(tempFile1)
|
||||
os.Create(tempFile2)
|
||||
os.Create(tempFile3)
|
||||
defer os.Remove(tempFile1)
|
||||
defer os.Remove(tempFile2)
|
||||
defer os.Remove(tempFile3)
|
||||
defer os.Remove(temDir2)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
cflags string
|
||||
files []string
|
||||
}{
|
||||
{
|
||||
name: "Valid files",
|
||||
cflags: "-I" + tempDir,
|
||||
files: []string{"test1.h", "test2.h"},
|
||||
},
|
||||
{
|
||||
name: "Mixed existing and non-existing files",
|
||||
cflags: "-I" + tempDir,
|
||||
files: []string{"test1.h", "nonexistent.h"},
|
||||
},
|
||||
{
|
||||
name: "Multiple include paths",
|
||||
cflags: "-I" + tempDir + " -I" + temDir2,
|
||||
files: []string{"test1.h", "test2.h", "test3.h"},
|
||||
},
|
||||
{
|
||||
name: "No existing files",
|
||||
cflags: "-I" + tempDir,
|
||||
files: []string{"nonexistent1.h", "nonexistent2.h"},
|
||||
},
|
||||
{
|
||||
name: "Empty file list",
|
||||
cflags: "-I/usr/include",
|
||||
files: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Printf("Test case: %s\n", tc.name)
|
||||
fmt.Printf("Input files: %v\n", tc.files)
|
||||
|
||||
cflag := cfgparse.ParseCFlags(tc.cflags)
|
||||
result, notFounds, err := cflag.GenHeaderFilePaths(tc.files)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
}
|
||||
if len(notFounds) > 0 {
|
||||
fmt.Println("notFounds", notFounds)
|
||||
}
|
||||
if result != nil {
|
||||
relativeResult := make([]string, len(result))
|
||||
for i, path := range result {
|
||||
relativeResult[i] = filepath.Base(path)
|
||||
}
|
||||
fmt.Printf("Output: %v\n", relativeResult)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
97
chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect
Normal file
97
chore/_xtool/llcppsymg/_cmptest/config_test/llgo.expect
Normal file
@@ -0,0 +1,97 @@
|
||||
#stdout
|
||||
=== Test case: SQLite configuration ===
|
||||
Name: sqlite
|
||||
CFlags: -I/opt/homebrew/opt/sqlite/include
|
||||
Libs: -L/opt/homebrew/opt/sqlite/lib -lsqlite3
|
||||
Include: sqlite3.h
|
||||
TrimPrefixes: sqlite3_
|
||||
Cplusplus: false
|
||||
|
||||
=== Test case: Lua configuration ===
|
||||
Name: lua
|
||||
CFlags: -I/opt/homebrew/include/lua
|
||||
Libs: -L/opt/homebrew/lib -llua -lm
|
||||
Include: lua.h
|
||||
TrimPrefixes: lua_, lua_
|
||||
Cplusplus: false
|
||||
|
||||
=== Test case: Invalid JSON ===
|
||||
Error: failed to parse config
|
||||
|
||||
=== Test ParseLibs ===
|
||||
Test case: Lua library
|
||||
Input: -L/opt/homebrew/lib -llua -lm
|
||||
Paths: [/opt/homebrew/lib]
|
||||
Names: [lua m]
|
||||
Test case: SQLite library
|
||||
Input: -L/opt/homebrew/opt/sqlite/lib -lsqlite3
|
||||
Paths: [/opt/homebrew/opt/sqlite/lib]
|
||||
Names: [sqlite3]
|
||||
Test case: INIReader library
|
||||
Input: -L/opt/homebrew/Cellar/inih/58/lib -lINIReader
|
||||
Paths: [/opt/homebrew/Cellar/inih/58/lib]
|
||||
Names: [INIReader]
|
||||
Test case: Multiple library paths
|
||||
Input: -L/opt/homebrew/lib -L/usr/lib -llua
|
||||
Paths: [/opt/homebrew/lib /usr/lib]
|
||||
Names: [lua]
|
||||
Test case: No valid library
|
||||
Input: -L/opt/homebrew/lib
|
||||
Paths: [/opt/homebrew/lib]
|
||||
Names: []
|
||||
=== Test GenDylibPaths ===
|
||||
Test case: existing dylib
|
||||
Path libsymb1 is in the expected paths
|
||||
Test case: existing dylibs
|
||||
Path libsymb1 is in the expected paths
|
||||
Path libsymb2 is in the expected paths
|
||||
Test case: existint default paths
|
||||
Path libsymb1 is in the expected paths
|
||||
Path libsymb3 is in the expected paths
|
||||
Test case: existint default paths & not found
|
||||
notFounds [math]
|
||||
Path libsymb1 is in the expected paths
|
||||
Path libsymb3 is in the expected paths
|
||||
Test case: no existing dylib
|
||||
notFounds [notexist]
|
||||
Error: failed to find any libraries
|
||||
=== Test ParseCFlags ===
|
||||
Test case: Single include path
|
||||
Input: -I/usr/include
|
||||
Paths: [/usr/include]
|
||||
Test case: Multiple include paths
|
||||
Input: -I/usr/include -I/opt/homebrew/include
|
||||
Paths: [/usr/include /opt/homebrew/include]
|
||||
Test case: Include paths mixed with other flags
|
||||
Input: -I/usr/include -DDEBUG -I/opt/local/include -Wall
|
||||
Paths: [/usr/include /opt/local/include]
|
||||
Test case: Empty input
|
||||
Input:
|
||||
Paths: []
|
||||
=== Test GenHeaderFilePath ===
|
||||
Test case: Valid files
|
||||
Input files: [test1.h test2.h]
|
||||
Output: [test1.h test2.h]
|
||||
|
||||
Test case: Mixed existing and non-existing files
|
||||
Input files: [test1.h nonexistent.h]
|
||||
notFounds [nonexistent.h]
|
||||
Output: [test1.h]
|
||||
|
||||
Test case: Multiple include paths
|
||||
Input files: [test1.h test2.h test3.h]
|
||||
Output: [test1.h test2.h test3.h]
|
||||
|
||||
Test case: No existing files
|
||||
Input files: [nonexistent1.h nonexistent2.h]
|
||||
Error: failed to find any header files
|
||||
notFounds [nonexistent1.h nonexistent2.h]
|
||||
|
||||
Test case: Empty file list
|
||||
Input files: []
|
||||
Error: failed to find any header files
|
||||
|
||||
|
||||
#stderr
|
||||
|
||||
#exit 0
|
||||
44
chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect
Normal file
44
chore/_xtool/llcppsymg/_cmptest/parse_test/llgo.expect
Normal file
@@ -0,0 +1,44 @@
|
||||
#stdout
|
||||
=== Test NewSymbolProcessor ===
|
||||
Before: No prefixes After: Prefixes: [lua_ luaL_]
|
||||
|
||||
=== Test RemovePrefix ===
|
||||
Before: lua_closethread After: closethread
|
||||
Before: luaL_checknumber After: checknumber
|
||||
|
||||
=== Test ToGoName ===
|
||||
Before: lua_closethread After: Closethread
|
||||
Before: luaL_checknumber After: Checknumber
|
||||
Before: sqlite3_close_v2 After: CloseV2
|
||||
Before: sqlite3_callback After: Callback
|
||||
Before: GetReal After: GetReal
|
||||
Before: GetBoolean After: GetBoolean
|
||||
Before: INIReader After: Reader
|
||||
|
||||
=== Test GenMethodName ===
|
||||
Before: Class: INIReader, Name: INIReader After: (*INIReader).Init
|
||||
Before: Class: INIReader, Name: INIReader After: (*INIReader).Dispose
|
||||
Before: Class: INIReader, Name: HasValue After: (*INIReader).HasValue
|
||||
|
||||
=== Test AddSuffix ===
|
||||
Before: Class: INIReader, Method: INIReader After: (*Reader).Init
|
||||
Before: Class: INIReader, Method: INIReader After: (*Reader).Init__1
|
||||
Before: Class: INIReader, Method: ParseError After: (*Reader).ParseError
|
||||
Before: Class: INIReader, Method: HasValue After: (*Reader).HasValue
|
||||
|
||||
=== Test Case: C++ Class with Methods ===
|
||||
Parsed Symbols:
|
||||
Symbol Map GoName: (*Reader).Init__1, ProtoName In HeaderFile: INIReader::INIReader(const char *, int), MangledName: _ZN9INIReaderC1EPKci
|
||||
Symbol Map GoName: (*Reader).Init, ProtoName In HeaderFile: INIReader::INIReader(const int &), MangledName: _ZN9INIReaderC1ERKi
|
||||
Symbol Map GoName: (*Reader).Dispose, ProtoName In HeaderFile: INIReader::~INIReader(), MangledName: _ZN9INIReaderD1Ev
|
||||
Symbol Map GoName: (*Reader).ParseError, ProtoName In HeaderFile: INIReader::ParseError(), MangledName: _ZNK9INIReader10ParseErrorEv
|
||||
|
||||
=== Test Case: C Functions ===
|
||||
Parsed Symbols:
|
||||
Symbol Map GoName: Compare, ProtoName In HeaderFile: lua_compare(lua_State *, int, int, int), MangledName: lua_compare
|
||||
Symbol Map GoName: Rawequal, ProtoName In HeaderFile: lua_rawequal(lua_State *, int, int), MangledName: lua_rawequal
|
||||
|
||||
|
||||
#stderr
|
||||
|
||||
#exit 0
|
||||
165
chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go
Normal file
165
chore/_xtool/llcppsymg/_cmptest/parse_test/parse.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||
)
|
||||
|
||||
func main() {
|
||||
TestNewSymbolProcessor()
|
||||
TestRemovePrefix()
|
||||
TestToGoName()
|
||||
TestGenMethodName()
|
||||
TestAddSuffix()
|
||||
TestParseHeaderFile()
|
||||
}
|
||||
|
||||
func TestNewSymbolProcessor() {
|
||||
fmt.Println("=== Test NewSymbolProcessor ===")
|
||||
process := parse.NewSymbolProcessor([]string{"lua_", "luaL_"})
|
||||
fmt.Printf("Before: No prefixes After: Prefixes: %v\n", process.Prefixes)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func TestRemovePrefix() {
|
||||
fmt.Println("=== Test RemovePrefix ===")
|
||||
process := parse.NewSymbolProcessor([]string{"lua_", "luaL_"})
|
||||
|
||||
testCases := []string{"lua_closethread", "luaL_checknumber"}
|
||||
|
||||
for _, input := range testCases {
|
||||
result := process.TrimPrefixes(input)
|
||||
fmt.Printf("Before: %s After: %s\n", input, result)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func TestToGoName() {
|
||||
fmt.Println("=== Test ToGoName ===")
|
||||
process1 := parse.NewSymbolProcessor([]string{"lua_", "luaL_"})
|
||||
process2 := parse.NewSymbolProcessor([]string{"sqlite3_", "sqlite3_"})
|
||||
process3 := parse.NewSymbolProcessor([]string{"INI"})
|
||||
|
||||
testCases := []struct {
|
||||
processor *parse.SymbolProcessor
|
||||
input string
|
||||
}{
|
||||
{process1, "lua_closethread"},
|
||||
{process1, "luaL_checknumber"},
|
||||
{process2, "sqlite3_close_v2"},
|
||||
{process2, "sqlite3_callback"},
|
||||
{process3, "GetReal"},
|
||||
{process3, "GetBoolean"},
|
||||
{process3, "INIReader"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
result := tc.processor.ToGoName(tc.input)
|
||||
fmt.Printf("Before: %s After: %s\n", tc.input, result)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func TestGenMethodName() {
|
||||
fmt.Println("=== Test GenMethodName ===")
|
||||
process := &parse.SymbolProcessor{}
|
||||
|
||||
testCases := []struct {
|
||||
class string
|
||||
name string
|
||||
isDestructor bool
|
||||
}{
|
||||
{"INIReader", "INIReader", false},
|
||||
{"INIReader", "INIReader", true},
|
||||
{"INIReader", "HasValue", false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
input := fmt.Sprintf("Class: %s, Name: %s", tc.class, tc.name)
|
||||
result := process.GenMethodName(tc.class, tc.name, tc.isDestructor)
|
||||
fmt.Printf("Before: %s After: %s\n", input, result)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func TestAddSuffix() {
|
||||
fmt.Println("=== Test AddSuffix ===")
|
||||
process := parse.NewSymbolProcessor([]string{"INI"})
|
||||
methods := []string{
|
||||
"INIReader",
|
||||
"INIReader",
|
||||
"ParseError",
|
||||
"HasValue",
|
||||
}
|
||||
for _, method := range methods {
|
||||
goName := process.ToGoName(method)
|
||||
className := process.ToGoName("INIReader")
|
||||
methodName := process.GenMethodName(className, goName, false)
|
||||
finalName := process.AddSuffix(methodName)
|
||||
input := fmt.Sprintf("Class: INIReader, Method: %s", method)
|
||||
fmt.Printf("Before: %s After: %s\n", input, finalName)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func TestParseHeaderFile() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
content string
|
||||
isCpp bool
|
||||
prefixes []string
|
||||
}{
|
||||
{
|
||||
name: "C++ Class with Methods",
|
||||
content: `
|
||||
class INIReader {
|
||||
public:
|
||||
INIReader(const std::string &filename);
|
||||
INIReader(const char *buffer, size_t buffer_size);
|
||||
~INIReader();
|
||||
int ParseError() const;
|
||||
private:
|
||||
static std::string MakeKey(const std::string §ion, const std::string &name);
|
||||
};
|
||||
`,
|
||||
isCpp: true,
|
||||
prefixes: []string{"INI"},
|
||||
},
|
||||
{
|
||||
name: "C Functions",
|
||||
content: `
|
||||
typedef struct lua_State lua_State;
|
||||
int(lua_rawequal)(lua_State *L, int idx1, int idx2);
|
||||
int(lua_compare)(lua_State *L, int idx1, int idx2, int op);
|
||||
`,
|
||||
isCpp: false,
|
||||
prefixes: []string{"lua_"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Printf("=== Test Case: %s ===\n", tc.name)
|
||||
|
||||
symbolMap, err := parse.ParseHeaderFile([]string{tc.content}, tc.prefixes, tc.isCpp, true)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("Parsed Symbols:")
|
||||
|
||||
var keys []string
|
||||
for key := range symbolMap {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
info := symbolMap[key]
|
||||
fmt.Printf("Symbol Map GoName: %s, ProtoName In HeaderFile: %s, MangledName: %s\n", info.GoName, info.ProtoName, key)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
46
chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect
Normal file
46
chore/_xtool/llcppsymg/_cmptest/symbol_test/llgo.expect
Normal file
@@ -0,0 +1,46 @@
|
||||
#stdout
|
||||
=== Test GetCommonSymbols ===
|
||||
|
||||
Test Case: Lua symbols
|
||||
Common Symbols (4):
|
||||
Mangle: lua_absindex, CPP: lua_absindex(lua_State *, int), Go: Absindex
|
||||
Mangle: lua_arith, CPP: lua_arith(lua_State *, int), Go: Arith
|
||||
Mangle: lua_atpanic, CPP: lua_atpanic(lua_State *, lua_CFunction), Go: Atpanic
|
||||
Mangle: lua_callk, CPP: lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction), Go: Callk
|
||||
|
||||
Test Case: INIReader and Std library symbols
|
||||
Common Symbols (3):
|
||||
Mangle: _ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x, CPP: INIReader::GetInteger64(const std::string &, const std::string &, int64_t), Go: (*Reader).GetInteger64
|
||||
Mangle: _ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d, CPP: INIReader::GetReal(const std::string &, const std::string &, double), Go: (*Reader).GetReal
|
||||
Mangle: _ZNK9INIReader10ParseErrorEv, CPP: INIReader::ParseError(), Go: (*Reader).ParseError
|
||||
|
||||
=== Test ReadExistingSymbolTable ===
|
||||
Symbols read from the file:
|
||||
Symbol Map GoName: (*Reader).Init__1, ProtoName In HeaderFile: INIReader::INIReader(const char *, size_t), MangledName: _ZN9INIReaderC1EPKcm
|
||||
Symbol Map GoName: (*Reader).GetBoolean, ProtoName In HeaderFile: INIReader::GetBoolean(const std::string &, const std::string &, bool), MangledName: _ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b
|
||||
Symbol Map GoName: (*Reader).ParseError, ProtoName In HeaderFile: INIReader::ParseError(), MangledName: _ZNK9INIReader10ParseErrorEv
|
||||
Havent existed symb file
|
||||
|
||||
=== Test GenSymbolTableData ===
|
||||
[{
|
||||
"mangle": "lua_absindex",
|
||||
"c++": "lua_absindex(lua_State *, int)",
|
||||
"go": "Absindex"
|
||||
}, {
|
||||
"mangle": "lua_arith",
|
||||
"c++": "lua_arith(lua_State *, int)",
|
||||
"go": "Arith"
|
||||
}, {
|
||||
"mangle": "lua_atpanic",
|
||||
"c++": "lua_atpanic(lua_State *, lua_CFunction)",
|
||||
"go": "Atpanic"
|
||||
}, {
|
||||
"mangle": "lua_callk",
|
||||
"c++": "lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction)",
|
||||
"go": "ModifiedCallk"
|
||||
}]
|
||||
|
||||
|
||||
#stderr
|
||||
|
||||
#exit 0
|
||||
152
chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go
Normal file
152
chore/_xtool/llcppsymg/_cmptest/symbol_test/symbol.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol"
|
||||
"github.com/goplus/llgo/chore/llcppg/types"
|
||||
"github.com/goplus/llgo/xtool/nm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
TestGetCommonSymbols()
|
||||
TestReadExistingSymbolTable()
|
||||
TestGenSymbolTableData()
|
||||
}
|
||||
|
||||
func TestGetCommonSymbols() {
|
||||
fmt.Println("=== Test GetCommonSymbols ===")
|
||||
testCases := []struct {
|
||||
name string
|
||||
dylibSymbols []*nm.Symbol
|
||||
headerSymbols map[string]*parse.SymbolInfo
|
||||
}{
|
||||
{
|
||||
name: "Lua symbols",
|
||||
dylibSymbols: []*nm.Symbol{
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_absindex", false)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_arith", false)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_atpanic", false)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_callk", false)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_lib_nonexistent", false)},
|
||||
},
|
||||
headerSymbols: map[string]*parse.SymbolInfo{
|
||||
"lua_absindex": {ProtoName: "lua_absindex(lua_State *, int)", GoName: "Absindex"},
|
||||
"lua_arith": {ProtoName: "lua_arith(lua_State *, int)", GoName: "Arith"},
|
||||
"lua_atpanic": {ProtoName: "lua_atpanic(lua_State *, lua_CFunction)", GoName: "Atpanic"},
|
||||
"lua_callk": {ProtoName: "lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction)", GoName: "Callk"},
|
||||
"lua_header_nonexistent": {ProtoName: "lua_header_nonexistent()", GoName: "HeaderNonexistent"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "INIReader and Std library symbols",
|
||||
dylibSymbols: []*nm.Symbol{
|
||||
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x", true)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d", true)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader10ParseErrorEv", true)},
|
||||
},
|
||||
headerSymbols: map[string]*parse.SymbolInfo{
|
||||
"_ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x": {GoName: "(*Reader).GetInteger64", ProtoName: "INIReader::GetInteger64(const std::string &, const std::string &, int64_t)"},
|
||||
"_ZNK9INIReader13GetUnsigned64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_y": {GoName: "(*Reader).GetUnsigned64", ProtoName: "INIReader::GetUnsigned64(const std::string &, const std::string &, uint64_t)"},
|
||||
"_ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d": {GoName: "(*Reader).GetReal", ProtoName: "INIReader::GetReal(const std::string &, const std::string &, double)"},
|
||||
"_ZNK9INIReader10ParseErrorEv": {GoName: "(*Reader).ParseError", ProtoName: "INIReader::ParseError()"},
|
||||
"_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b": {GoName: "(*Reader).GetBoolean", ProtoName: "INIReader::GetBoolean(const std::string &, const std::string &, bool)"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Printf("\nTest Case: %s\n", tc.name)
|
||||
commonSymbols := symbol.GetCommonSymbols(tc.dylibSymbols, tc.headerSymbols)
|
||||
fmt.Printf("Common Symbols (%d):\n", len(commonSymbols))
|
||||
for _, sym := range commonSymbols {
|
||||
fmt.Printf("Mangle: %s, CPP: %s, Go: %s\n", sym.Mangle, sym.CPP, sym.Go)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
func TestReadExistingSymbolTable() {
|
||||
fmt.Println("=== Test ReadExistingSymbolTable ===")
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "llcppg.symb.json")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create temp file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
testData := `[
|
||||
{
|
||||
"mangle": "_ZN9INIReaderC1EPKcm",
|
||||
"c++": "INIReader::INIReader(const char *, size_t)",
|
||||
"go": "(*Reader).Init__1"
|
||||
},
|
||||
{
|
||||
"mangle": "_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b",
|
||||
"c++": "INIReader::GetBoolean(const std::string &, const std::string &, bool)",
|
||||
"go": "(*Reader).GetBoolean"
|
||||
},
|
||||
{
|
||||
"mangle": "_ZNK9INIReader10ParseErrorEv",
|
||||
"c++": "INIReader::ParseError()",
|
||||
"go": "(*Reader).ParseError"
|
||||
}
|
||||
]`
|
||||
if _, err := tmpFile.Write([]byte(testData)); err != nil {
|
||||
fmt.Printf("Failed to write test data: %v\n", err)
|
||||
return
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
symbols, exist := symbol.ReadExistingSymbolTable(tmpFile.Name())
|
||||
if !exist {
|
||||
fmt.Printf("ReadExistingSymbolTable failed")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Symbols read from the file:")
|
||||
var keys []string
|
||||
for key := range symbols {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
info := symbols[key]
|
||||
fmt.Printf("Symbol Map GoName: %s, ProtoName In HeaderFile: %s, MangledName: %s\n",
|
||||
info.Go, info.CPP, key)
|
||||
}
|
||||
|
||||
_, exist = symbol.ReadExistingSymbolTable("other.json")
|
||||
if !exist {
|
||||
fmt.Println("Havent existed symb file")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
func TestGenSymbolTableData() {
|
||||
fmt.Println("=== Test GenSymbolTableData ===")
|
||||
|
||||
commonSymbols := []*types.SymbolInfo{
|
||||
{Mangle: "lua_absindex", CPP: "lua_absindex(lua_State *, int)", Go: "Absindex"},
|
||||
{Mangle: "lua_arith", CPP: "lua_arith(lua_State *, int)", Go: "Arith"},
|
||||
{Mangle: "lua_atpanic", CPP: "lua_atpanic(lua_State *, lua_CFunction)", Go: "Atpanic"},
|
||||
{Mangle: "lua_callk", CPP: "lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction)", Go: "Callk"},
|
||||
}
|
||||
|
||||
existingSymbols := map[string]types.SymbolInfo{
|
||||
"lua_absindex": {Mangle: "lua_absindex", CPP: "lua_absindex(lua_State *, int)", Go: "Absindex"},
|
||||
"lua_arith": {Mangle: "lua_arith", CPP: "lua_arith(lua_State *, int)", Go: "Arith"},
|
||||
"lua_callk": {Mangle: "lua_callk", CPP: "lua_callk(lua_State *, int, int, lua_KContext, lua_KFunction)", Go: "ModifiedCallk"},
|
||||
}
|
||||
|
||||
data, err := symbol.GenSymbolTableData(commonSymbols, existingSymbols)
|
||||
if err != nil {
|
||||
fmt.Printf("Error generating symbol table data: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
fmt.Println()
|
||||
}
|
||||
45
chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect
Normal file
45
chore/_xtool/llcppsymg/_cmptest/symg_test/llgo.expect
Normal file
@@ -0,0 +1,45 @@
|
||||
#stdout
|
||||
=== Test Case: inireader ===
|
||||
[{
|
||||
"mangle": "_ZN9INIReaderC1EPKc",
|
||||
"c++": "INIReader::INIReader(const char *)",
|
||||
"go": "(*Reader).Init"
|
||||
}, {
|
||||
"mangle": "_ZN9INIReaderC1EPKcl",
|
||||
"c++": "INIReader::INIReader(const char *, long)",
|
||||
"go": "(*Reader).Init__1"
|
||||
}, {
|
||||
"mangle": "_ZN9INIReaderD1Ev",
|
||||
"c++": "INIReader::~INIReader()",
|
||||
"go": "(*Reader).Dispose"
|
||||
}, {
|
||||
"mangle": "_ZNK9INIReader10ParseErrorEv",
|
||||
"c++": "INIReader::ParseError()",
|
||||
"go": "(*Reader).ModifyedParseError"
|
||||
}, {
|
||||
"mangle": "_ZNK9INIReader3GetEPKcS1_S1_",
|
||||
"c++": "INIReader::Get(const char *, const char *, const char *)",
|
||||
"go": "(*Reader).Get"
|
||||
}]
|
||||
=== Test Case: lua ===
|
||||
[{
|
||||
"mangle": "lua_error",
|
||||
"c++": "lua_error(lua_State *)",
|
||||
"go": "Error"
|
||||
}, {
|
||||
"mangle": "lua_next",
|
||||
"c++": "lua_next(lua_State *, int)",
|
||||
"go": "Next"
|
||||
}, {
|
||||
"mangle": "lua_concat",
|
||||
"c++": "lua_concat(lua_State *, int)",
|
||||
"go": "Concat"
|
||||
}, {
|
||||
"mangle": "lua_stringtonumber",
|
||||
"c++": "lua_stringtonumber(lua_State *, const char *)",
|
||||
"go": "Stringtonumber"
|
||||
}]
|
||||
|
||||
#stderr
|
||||
|
||||
#exit 0
|
||||
117
chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go
Normal file
117
chore/_xtool/llcppsymg/_cmptest/symg_test/symg.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol"
|
||||
"github.com/goplus/llgo/xtool/nm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
TestParseHeaderFile()
|
||||
}
|
||||
func TestParseHeaderFile() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
content string
|
||||
isCpp bool
|
||||
prefixes []string
|
||||
dylibSymbols []*nm.Symbol
|
||||
symbFileContent string
|
||||
}{
|
||||
{
|
||||
name: "inireader",
|
||||
content: `
|
||||
#define INI_API __attribute__((visibility("default")))
|
||||
class INIReader {
|
||||
public:
|
||||
__attribute__((visibility("default"))) explicit INIReader(const char *filename);
|
||||
INI_API explicit INIReader(const char *buffer, long buffer_size);
|
||||
~INIReader();
|
||||
INI_API int ParseError() const;
|
||||
INI_API const char * Get(const char *section, const char *name,
|
||||
const char *default_value) const;
|
||||
private:
|
||||
static const char * MakeKey(const char *section, const char *name);
|
||||
};
|
||||
`,
|
||||
isCpp: true,
|
||||
prefixes: []string{"INI"},
|
||||
dylibSymbols: []*nm.Symbol{
|
||||
{Name: symbol.AddSymbolPrefixUnder("ZN9INIReaderC1EPKc", true)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("ZN9INIReaderC1EPKcl", true)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("ZN9INIReaderD1Ev", true)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader10ParseErrorEv", true)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("ZNK9INIReader3GetEPKcS1_S1_", true)},
|
||||
},
|
||||
symbFileContent: `
|
||||
[{
|
||||
"mangle": "_ZN9INIReaderC1EPKc",
|
||||
"c++": "INIReader::INIReader(const char *)",
|
||||
"go": "(*Reader).Init"
|
||||
}, {
|
||||
"mangle": "_ZN9INIReaderC1EPKcl",
|
||||
"c++": "INIReader::INIReader(const char *, long)",
|
||||
"go": "(*Reader).Init__1"
|
||||
}, {
|
||||
"mangle": "_ZN9INIReaderD1Ev",
|
||||
"c++": "INIReader::~INIReader()",
|
||||
"go": "(*Reader).Dispose"
|
||||
}, {
|
||||
"mangle": "_ZNK9INIReader10ParseErrorEv",
|
||||
"c++": "INIReader::ParseError()",
|
||||
"go": "(*Reader).ModifyedParseError"
|
||||
}]`,
|
||||
},
|
||||
{
|
||||
name: "lua",
|
||||
content: `
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
LUA_API int(lua_error)(lua_State *L);
|
||||
|
||||
LUA_API int(lua_next)(lua_State *L, int idx);
|
||||
|
||||
LUA_API void(lua_concat)(lua_State *L, int n);
|
||||
LUA_API void(lua_len)(lua_State *L, int idx);
|
||||
|
||||
LUA_API long unsigned int(lua_stringtonumber)(lua_State *L, const char *s);
|
||||
|
||||
LUA_API void(lua_setallocf)(lua_State *L, lua_Alloc f, void *ud);
|
||||
|
||||
LUA_API void(lua_toclose)(lua_State *L, int idx);
|
||||
LUA_API void(lua_closeslot)(lua_State *L, int idx);
|
||||
`,
|
||||
isCpp: false,
|
||||
prefixes: []string{"lua_"},
|
||||
dylibSymbols: []*nm.Symbol{
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_error", false)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_next", false)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_concat", false)},
|
||||
{Name: symbol.AddSymbolPrefixUnder("lua_stringtonumber", false)},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Printf("=== Test Case: %s ===\n", tc.name)
|
||||
headerSymbolMap, err := parse.ParseHeaderFile([]string{tc.content}, tc.prefixes, tc.isCpp, true)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
tmpFile, err := os.CreateTemp("", "llcppg.symb.json")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create temp file: %v\n", err)
|
||||
return
|
||||
}
|
||||
tmpFile.Write([]byte(tc.symbFileContent))
|
||||
symbolData, err := symbol.GenerateAndUpdateSymbolTable(tc.dylibSymbols, headerSymbolMap, tmpFile.Name())
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
fmt.Println(string(symbolData))
|
||||
os.Remove(tmpFile.Name())
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ type Config struct {
|
||||
Index *clang.Index
|
||||
}
|
||||
|
||||
type Visitor func(cursor, parent clang.Cursor) clang.ChildVisitResult
|
||||
|
||||
const TEMP_FILE = "temp.h"
|
||||
|
||||
func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit, error) {
|
||||
// default use the c/c++ standard of clang; c:gnu17 c++:gnu++17
|
||||
// https://clang.llvm.org/docs/CommandGuide/clang.html
|
||||
@@ -42,7 +46,7 @@ func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit
|
||||
if config.Temp {
|
||||
content := c.AllocaCStr(config.File)
|
||||
tempFile := &clang.UnsavedFile{
|
||||
Filename: c.Str("temp.h"),
|
||||
Filename: c.Str(TEMP_FILE),
|
||||
Contents: content,
|
||||
Length: c.Ulong(c.Strlen(content)),
|
||||
}
|
||||
@@ -71,6 +75,11 @@ func CreateTranslationUnit(config *Config) (*clang.Index, *clang.TranslationUnit
|
||||
return index, unit, nil
|
||||
}
|
||||
|
||||
func GetLocation(loc clang.SourceLocation) (file clang.File, line c.Uint, column c.Uint, offset c.Uint) {
|
||||
loc.SpellingLocation(&file, &line, &column, &offset)
|
||||
return
|
||||
}
|
||||
|
||||
// Traverse up the semantic parents
|
||||
func BuildScopingParts(cursor clang.Cursor) []string {
|
||||
var parts []string
|
||||
@@ -83,3 +92,10 @@ func BuildScopingParts(cursor clang.Cursor) []string {
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
109
chore/_xtool/llcppsymg/config/cfgparse/parse.go
Normal file
109
chore/_xtool/llcppsymg/config/cfgparse/parse.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package cfgparse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Note: This package is not placed under the 'config' package because 'config'
|
||||
// depends on 'cjson'. The parsing of Libs and cflags is intended to be usable
|
||||
// by both llgo and go, without introducing additional dependencies.
|
||||
|
||||
type Libs struct {
|
||||
Paths []string // Dylib Path
|
||||
Names []string
|
||||
}
|
||||
|
||||
type CFlags struct {
|
||||
Paths []string // Include Path
|
||||
}
|
||||
|
||||
func ParseLibs(libs string) *Libs {
|
||||
parts := strings.Fields(libs)
|
||||
lbs := &Libs{}
|
||||
for _, part := range parts {
|
||||
if strings.HasPrefix(part, "-L") {
|
||||
lbs.Paths = append(lbs.Paths, part[2:])
|
||||
} else if strings.HasPrefix(part, "-l") {
|
||||
lbs.Names = append(lbs.Names, part[2:])
|
||||
}
|
||||
}
|
||||
return lbs
|
||||
}
|
||||
|
||||
// searches for each library name in the provided paths and default paths,
|
||||
// appending the appropriate file extension (.dylib for macOS, .so for Linux).
|
||||
//
|
||||
// Example: For "-L/opt/homebrew/lib -llua -lm":
|
||||
// - It will search for liblua.dylib (on macOS) or liblua.so (on Linux)
|
||||
// - System libs like -lm are ignored and included in notFound
|
||||
//
|
||||
// So error is returned if no libraries found at all.
|
||||
func (l *Libs) GenDylibPaths(defaultPaths []string) ([]string, []string, error) {
|
||||
var foundPaths []string
|
||||
var notFound []string
|
||||
affix := ".dylib"
|
||||
if runtime.GOOS == "linux" {
|
||||
affix = ".so"
|
||||
}
|
||||
searchPaths := append(l.Paths, defaultPaths...)
|
||||
for _, name := range l.Names {
|
||||
var foundPath string
|
||||
for _, path := range searchPaths {
|
||||
dylibPath := filepath.Join(path, "lib"+name+affix)
|
||||
if _, err := os.Stat(dylibPath); err == nil {
|
||||
foundPath = dylibPath
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundPath != "" {
|
||||
foundPaths = append(foundPaths, foundPath)
|
||||
} else {
|
||||
notFound = append(notFound, name)
|
||||
}
|
||||
}
|
||||
if len(foundPaths) == 0 {
|
||||
return nil, notFound, fmt.Errorf("failed to find any libraries")
|
||||
}
|
||||
return foundPaths, notFound, nil
|
||||
}
|
||||
|
||||
func ParseCFlags(cflags string) *CFlags {
|
||||
parts := strings.Fields(cflags)
|
||||
cf := &CFlags{}
|
||||
for _, part := range parts {
|
||||
if strings.HasPrefix(part, "-I") {
|
||||
cf.Paths = append(cf.Paths, part[2:])
|
||||
}
|
||||
}
|
||||
return cf
|
||||
}
|
||||
|
||||
func (cf *CFlags) GenHeaderFilePaths(files []string) ([]string, []string, error) {
|
||||
var foundPaths []string
|
||||
var notFound []string
|
||||
|
||||
for _, file := range files {
|
||||
var found bool
|
||||
for _, path := range cf.Paths {
|
||||
fullPath := filepath.Join(path, file)
|
||||
if _, err := os.Stat(fullPath); err == nil {
|
||||
foundPaths = append(foundPaths, fullPath)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
notFound = append(notFound, file)
|
||||
}
|
||||
}
|
||||
|
||||
if len(foundPaths) == 0 {
|
||||
return nil, notFound, fmt.Errorf("failed to find any header files")
|
||||
}
|
||||
|
||||
return foundPaths, notFound, nil
|
||||
}
|
||||
@@ -17,55 +17,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/goplus/llgo/c"
|
||||
"github.com/goplus/llgo/c/cjson"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/args"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config/cfgparse"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||
"github.com/goplus/llgo/chore/llcppg/types"
|
||||
"github.com/goplus/llgo/xtool/nm"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/symbol"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfgFile := "llcppg.cfg"
|
||||
if len(os.Args) > 1 {
|
||||
cfgFile = os.Args[1]
|
||||
}
|
||||
symbFile := "llcppg.symb.json"
|
||||
|
||||
ags, _ := args.ParseArgs(os.Args[1:], nil)
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
if cfgFile == "-" {
|
||||
if ags.UseStdin {
|
||||
data, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
data, err = os.ReadFile(cfgFile)
|
||||
data, err = os.ReadFile(ags.CfgFile)
|
||||
}
|
||||
check(err)
|
||||
|
||||
check(err)
|
||||
conf, err := config.GetConf(data)
|
||||
check(err)
|
||||
defer conf.Delete()
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile)
|
||||
if ags.Verbose {
|
||||
symbol.SetDebug(symbol.DbgFlagAll)
|
||||
if ags.UseStdin {
|
||||
fmt.Println("Config From Stdin")
|
||||
} else {
|
||||
fmt.Println("Config From File", ags.CfgFile)
|
||||
}
|
||||
fmt.Println("Name:", conf.Name)
|
||||
fmt.Println("CFlags:", conf.CFlags)
|
||||
fmt.Println("Libs:", conf.Libs)
|
||||
fmt.Println("Include:", conf.Include)
|
||||
fmt.Println("TrimPrefixes:", conf.TrimPrefixes)
|
||||
fmt.Println("Cplusplus:", conf.Cplusplus)
|
||||
}
|
||||
symbols, err := parseDylibSymbols(conf.Libs)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Failed to parse config file:", ags.CfgFile)
|
||||
}
|
||||
symbols, err := symbol.ParseDylibSymbols(conf.Libs)
|
||||
check(err)
|
||||
|
||||
filepaths := genHeaderFilePath(conf.CFlags, conf.Include)
|
||||
headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus)
|
||||
cflag := cfgparse.ParseCFlags(conf.CFlags)
|
||||
filepaths, notFounds, err := cflag.GenHeaderFilePaths(conf.Include)
|
||||
check(err)
|
||||
|
||||
symbolInfo := getCommonSymbols(symbols, headerInfos, conf.TrimPrefixes)
|
||||
if ags.Verbose {
|
||||
fmt.Println("header file paths", filepaths)
|
||||
if len(notFounds) > 0 {
|
||||
fmt.Println("not found header files", notFounds)
|
||||
}
|
||||
}
|
||||
|
||||
err = genSymbolTableFile(symbolInfo)
|
||||
headerInfos, err := parse.ParseHeaderFile(filepaths, conf.TrimPrefixes, conf.Cplusplus, false)
|
||||
check(err)
|
||||
|
||||
symbolData, err := symbol.GenerateAndUpdateSymbolTable(symbols, headerInfos, symbFile)
|
||||
check(err)
|
||||
|
||||
err = os.WriteFile(symbFile, symbolData, 0644)
|
||||
check(err)
|
||||
}
|
||||
|
||||
@@ -74,141 +93,3 @@ func check(err error) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseDylibSymbols(lib string) ([]*nm.Symbol, error) {
|
||||
dylibPath, err := genDylibPath(lib)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to generate dylib path")
|
||||
}
|
||||
|
||||
files, err := nm.New("").List(dylibPath)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to list symbols in dylib")
|
||||
}
|
||||
|
||||
var symbols []*nm.Symbol
|
||||
for _, file := range files {
|
||||
symbols = append(symbols, file.Symbols...)
|
||||
}
|
||||
|
||||
return symbols, nil
|
||||
}
|
||||
|
||||
func genDylibPath(lib string) (string, error) {
|
||||
output := lib
|
||||
libPath := ""
|
||||
libName := ""
|
||||
for _, part := range strings.Fields(string(output)) {
|
||||
if strings.HasPrefix(part, "-L") {
|
||||
libPath = part[2:]
|
||||
} else if strings.HasPrefix(part, "-l") {
|
||||
libName = part[2:]
|
||||
}
|
||||
}
|
||||
|
||||
if libPath == "" || libName == "" {
|
||||
return "", fmt.Errorf("failed to parse pkg-config output: %s", output)
|
||||
}
|
||||
|
||||
dylibPath := filepath.Join(libPath, "lib"+libName+".dylib")
|
||||
return dylibPath, nil
|
||||
}
|
||||
|
||||
func genHeaderFilePath(cflags string, files []string) []string {
|
||||
prefixPath := cflags
|
||||
prefixPath = strings.TrimPrefix(prefixPath, "-I")
|
||||
var includePaths []string
|
||||
for _, file := range files {
|
||||
includePaths = append(includePaths, filepath.Join(prefixPath, "/"+file))
|
||||
}
|
||||
return includePaths
|
||||
}
|
||||
|
||||
func getCommonSymbols(dylibSymbols []*nm.Symbol, symbolMap map[string]*parse.SymbolInfo, prefix []string) []*types.SymbolInfo {
|
||||
var commonSymbols []*types.SymbolInfo
|
||||
for _, dylibSym := range dylibSymbols {
|
||||
symName := strings.TrimPrefix(dylibSym.Name, "_")
|
||||
if symInfo, ok := symbolMap[symName]; ok {
|
||||
symbolInfo := &types.SymbolInfo{
|
||||
Mangle: symName,
|
||||
CPP: symInfo.ProtoName,
|
||||
Go: symInfo.GoName,
|
||||
}
|
||||
commonSymbols = append(commonSymbols, symbolInfo)
|
||||
}
|
||||
}
|
||||
return commonSymbols
|
||||
}
|
||||
|
||||
func genSymbolTableFile(symbolInfos []*types.SymbolInfo) error {
|
||||
fileName := "llcppg.symb.json"
|
||||
existingSymbols, err := readExistingSymbolTable(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range symbolInfos {
|
||||
if existingSymbol, exists := existingSymbols[symbolInfos[i].Mangle]; exists {
|
||||
symbolInfos[i].Go = existingSymbol.Go
|
||||
}
|
||||
}
|
||||
|
||||
root := cjson.Array()
|
||||
defer root.Delete()
|
||||
|
||||
for _, symbol := range symbolInfos {
|
||||
item := cjson.Object()
|
||||
item.SetItem(c.Str("mangle"), cjson.String(c.AllocaCStr(symbol.Mangle)))
|
||||
item.SetItem(c.Str("c++"), cjson.String(c.AllocaCStr(symbol.CPP)))
|
||||
item.SetItem(c.Str("go"), cjson.String(c.AllocaCStr(symbol.Go)))
|
||||
root.AddItem(item)
|
||||
}
|
||||
|
||||
cStr := root.Print()
|
||||
if cStr == nil {
|
||||
return errors.New("symbol table is empty")
|
||||
}
|
||||
defer c.Free(unsafe.Pointer(cStr))
|
||||
|
||||
data := unsafe.Slice((*byte)(unsafe.Pointer(cStr)), c.Strlen(cStr))
|
||||
|
||||
if err := os.WriteFile(fileName, data, 0644); err != nil {
|
||||
return errors.New("failed to write symbol table file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readExistingSymbolTable(fileName string) (map[string]types.SymbolInfo, error) {
|
||||
existingSymbols := make(map[string]types.SymbolInfo)
|
||||
|
||||
if _, err := os.Stat(fileName); err != nil {
|
||||
return existingSymbols, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to read symbol table file")
|
||||
}
|
||||
|
||||
parsedJSON := cjson.ParseBytes(data)
|
||||
if parsedJSON == nil {
|
||||
return nil, errors.New("failed to parse JSON")
|
||||
}
|
||||
|
||||
arraySize := parsedJSON.GetArraySize()
|
||||
|
||||
for i := 0; i < int(arraySize); i++ {
|
||||
item := parsedJSON.GetArrayItem(c.Int(i))
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
symbol := types.SymbolInfo{
|
||||
Mangle: config.GetStringItem(item, "mangle", ""),
|
||||
CPP: config.GetStringItem(item, "c++", ""),
|
||||
Go: config.GetStringItem(item, "go", ""),
|
||||
}
|
||||
existingSymbols[symbol.Mangle] = symbol
|
||||
}
|
||||
|
||||
return existingSymbols, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package parse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -15,37 +16,27 @@ type SymbolInfo struct {
|
||||
ProtoName string
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
namespaceName string
|
||||
className string
|
||||
prefixes []string
|
||||
symbolMap map[string]*SymbolInfo
|
||||
currentFile string
|
||||
nameCounts map[string]int
|
||||
type SymbolProcessor struct {
|
||||
Prefixes []string
|
||||
SymbolMap map[string]*SymbolInfo
|
||||
CurrentFile string
|
||||
NameCounts map[string]int
|
||||
}
|
||||
|
||||
func newContext(prefixes []string) *Context {
|
||||
return &Context{
|
||||
prefixes: prefixes,
|
||||
symbolMap: make(map[string]*SymbolInfo),
|
||||
nameCounts: make(map[string]int),
|
||||
func NewSymbolProcessor(Prefixes []string) *SymbolProcessor {
|
||||
return &SymbolProcessor{
|
||||
Prefixes: Prefixes,
|
||||
SymbolMap: make(map[string]*SymbolInfo),
|
||||
NameCounts: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) setNamespaceName(name string) {
|
||||
c.namespaceName = name
|
||||
func (p *SymbolProcessor) setCurrentFile(filename string) {
|
||||
p.CurrentFile = filename
|
||||
}
|
||||
|
||||
func (c *Context) setClassName(name string) {
|
||||
c.className = name
|
||||
}
|
||||
|
||||
func (c *Context) setCurrentFile(filename string) {
|
||||
c.currentFile = filename
|
||||
}
|
||||
|
||||
func (c *Context) removePrefix(str string) string {
|
||||
for _, prefix := range c.prefixes {
|
||||
func (p *SymbolProcessor) TrimPrefixes(str string) string {
|
||||
for _, prefix := range p.Prefixes {
|
||||
if strings.HasPrefix(str, prefix) {
|
||||
return strings.TrimPrefix(str, prefix)
|
||||
}
|
||||
@@ -57,10 +48,10 @@ func toTitle(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||
return strings.ToUpper(s[:1]) + (s[1:])
|
||||
}
|
||||
|
||||
func toCamel(originName string) string {
|
||||
func toUpperCamelCase(originName string) string {
|
||||
if originName == "" {
|
||||
return ""
|
||||
}
|
||||
@@ -74,37 +65,44 @@ func toCamel(originName string) string {
|
||||
|
||||
// 1. remove prefix from config
|
||||
// 2. convert to camel case
|
||||
func (c *Context) toGoName(name string) string {
|
||||
name = c.removePrefix(name)
|
||||
return toCamel(name)
|
||||
func (p *SymbolProcessor) ToGoName(name string) string {
|
||||
return toUpperCamelCase(p.TrimPrefixes(name))
|
||||
}
|
||||
|
||||
func (c *Context) genGoName(name string) string {
|
||||
class := c.toGoName(c.className)
|
||||
name = c.toGoName(name)
|
||||
|
||||
var baseName string
|
||||
if class == "" {
|
||||
baseName = name
|
||||
} else {
|
||||
baseName = c.genMethodName(class, name)
|
||||
}
|
||||
|
||||
return c.addSuffix(baseName)
|
||||
}
|
||||
|
||||
func (c *Context) genMethodName(class, name string) string {
|
||||
func (p *SymbolProcessor) GenMethodName(class, name string, isDestructor bool) string {
|
||||
prefix := "(*" + class + ")."
|
||||
if isDestructor {
|
||||
return prefix + "Dispose"
|
||||
}
|
||||
if class == name {
|
||||
return prefix + "Init"
|
||||
}
|
||||
if name == "~"+class {
|
||||
return prefix + "Dispose"
|
||||
}
|
||||
return prefix + name
|
||||
}
|
||||
|
||||
func (p *Context) genProtoName(cursor clang.Cursor) string {
|
||||
func (p *SymbolProcessor) genGoName(cursor clang.Cursor) string {
|
||||
funcName := cursor.String()
|
||||
defer funcName.Dispose()
|
||||
|
||||
originName := c.GoString(funcName.CStr())
|
||||
isDestructor := cursor.Kind == clang.CursorDestructor
|
||||
var convertedName string
|
||||
if isDestructor {
|
||||
convertedName = p.ToGoName(originName[1:])
|
||||
} else {
|
||||
convertedName = p.ToGoName(originName)
|
||||
}
|
||||
|
||||
if parent := cursor.SemanticParent(); parent.Kind == clang.CursorClassDecl {
|
||||
parentName := parent.String()
|
||||
defer parentName.Dispose()
|
||||
class := p.ToGoName(c.GoString(parentName.CStr()))
|
||||
return p.AddSuffix(p.GenMethodName(class, convertedName, isDestructor))
|
||||
}
|
||||
return p.AddSuffix(convertedName)
|
||||
}
|
||||
|
||||
func (p *SymbolProcessor) genProtoName(cursor clang.Cursor) string {
|
||||
displayName := cursor.DisplayName()
|
||||
defer displayName.Dispose()
|
||||
|
||||
@@ -120,95 +118,73 @@ func (p *Context) genProtoName(cursor clang.Cursor) string {
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (c *Context) addSuffix(name string) string {
|
||||
c.nameCounts[name]++
|
||||
count := c.nameCounts[name]
|
||||
if count > 1 {
|
||||
func (p *SymbolProcessor) AddSuffix(name string) string {
|
||||
p.NameCounts[name]++
|
||||
if count := p.NameCounts[name]; count > 1 {
|
||||
return name + "__" + strconv.Itoa(count-1)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
var context = newContext([]string{})
|
||||
|
||||
func collectFuncInfo(cursor clang.Cursor) {
|
||||
cursorStr := cursor.String()
|
||||
func (p *SymbolProcessor) collectFuncInfo(cursor clang.Cursor) {
|
||||
symbol := cursor.Mangling()
|
||||
|
||||
name := c.GoString(cursorStr.CStr())
|
||||
symbolName := c.GoString(symbol.CStr())
|
||||
if len(symbolName) >= 1 && symbolName[0] == '_' {
|
||||
symbolName = symbolName[1:]
|
||||
}
|
||||
defer symbol.Dispose()
|
||||
defer cursorStr.Dispose()
|
||||
|
||||
context.symbolMap[symbolName] = &SymbolInfo{
|
||||
GoName: context.genGoName(name),
|
||||
ProtoName: context.genProtoName(cursor),
|
||||
// On Linux, C++ symbols typically have one leading underscore
|
||||
// On macOS, C++ symbols may have two leading underscores
|
||||
// For consistency, we remove the first leading underscore on macOS
|
||||
symbolName := c.GoString(symbol.CStr())
|
||||
if runtime.GOOS == "darwin" {
|
||||
symbolName = strings.TrimPrefix(symbolName, "_")
|
||||
}
|
||||
p.SymbolMap[symbolName] = &SymbolInfo{
|
||||
GoName: p.genGoName(cursor),
|
||||
ProtoName: p.genProtoName(cursor),
|
||||
}
|
||||
}
|
||||
|
||||
func visit(cursor, parent clang.Cursor, clientData c.Pointer) clang.ChildVisitResult {
|
||||
func (p *SymbolProcessor) visitTop(cursor, parent clang.Cursor) clang.ChildVisitResult {
|
||||
switch cursor.Kind {
|
||||
case clang.CursorNamespace, clang.CursorClassDecl:
|
||||
nameStr := cursor.String()
|
||||
defer nameStr.Dispose()
|
||||
|
||||
name := c.GoString(nameStr.CStr())
|
||||
if cursor.Kind == clang.CursorNamespace {
|
||||
context.setNamespaceName(name)
|
||||
} else {
|
||||
context.setClassName(name)
|
||||
}
|
||||
|
||||
clang.VisitChildren(cursor, visit, nil)
|
||||
|
||||
if cursor.Kind == clang.CursorNamespace {
|
||||
context.setNamespaceName("")
|
||||
} else {
|
||||
context.setClassName("")
|
||||
}
|
||||
|
||||
clangutils.VisitChildren(cursor, p.visitTop)
|
||||
case clang.CursorCXXMethod, clang.CursorFunctionDecl, clang.CursorConstructor, clang.CursorDestructor:
|
||||
loc := cursor.Location()
|
||||
var file clang.File
|
||||
var line, column c.Uint
|
||||
|
||||
loc.SpellingLocation(&file, &line, &column, nil)
|
||||
file, _, _, _ := clangutils.GetLocation(loc)
|
||||
filename := file.FileName()
|
||||
defer filename.Dispose()
|
||||
|
||||
isCurrentFile := c.Strcmp(filename.CStr(), c.AllocaCStr(context.currentFile)) == 0
|
||||
isCurrentFile := c.Strcmp(filename.CStr(), c.AllocaCStr(p.CurrentFile)) == 0
|
||||
isPublicMethod := (cursor.CXXAccessSpecifier() == clang.CXXPublic) && cursor.Kind == clang.CursorCXXMethod || cursor.Kind == clang.CursorConstructor || cursor.Kind == clang.CursorDestructor
|
||||
|
||||
if isCurrentFile && (cursor.Kind == clang.CursorFunctionDecl || isPublicMethod) {
|
||||
collectFuncInfo(cursor)
|
||||
p.collectFuncInfo(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
return clang.ChildVisit_Continue
|
||||
}
|
||||
|
||||
func ParseHeaderFile(filepaths []string, prefixes []string, isCpp bool) (map[string]*SymbolInfo, error) {
|
||||
context = newContext(prefixes)
|
||||
func ParseHeaderFile(files []string, Prefixes []string, isCpp bool, isTemp bool) (map[string]*SymbolInfo, error) {
|
||||
processer := NewSymbolProcessor(Prefixes)
|
||||
index := clang.CreateIndex(0, 0)
|
||||
for _, filename := range filepaths {
|
||||
for _, file := range files {
|
||||
_, unit, err := clangutils.CreateTranslationUnit(&clangutils.Config{
|
||||
File: filename,
|
||||
Temp: false,
|
||||
File: file,
|
||||
Temp: isTemp,
|
||||
IsCpp: isCpp,
|
||||
Index: index,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.New("Unable to parse translation unit for file " + filename)
|
||||
return nil, errors.New("Unable to parse translation unit for file " + file)
|
||||
}
|
||||
|
||||
cursor := unit.Cursor()
|
||||
context.setCurrentFile(filename)
|
||||
clang.VisitChildren(cursor, visit, nil)
|
||||
if isTemp {
|
||||
processer.setCurrentFile(clangutils.TEMP_FILE)
|
||||
} else {
|
||||
processer.setCurrentFile(file)
|
||||
}
|
||||
clangutils.VisitChildren(cursor, processer.visitTop)
|
||||
unit.Dispose()
|
||||
}
|
||||
index.Dispose()
|
||||
return context.symbolMap, nil
|
||||
return processer.SymbolMap, nil
|
||||
}
|
||||
|
||||
283
chore/_xtool/llcppsymg/symbol/symbol.go
Normal file
283
chore/_xtool/llcppsymg/symbol/symbol.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package symbol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/goplus/llgo/c"
|
||||
"github.com/goplus/llgo/c/cjson"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config/cfgparse"
|
||||
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
|
||||
"github.com/goplus/llgo/chore/llcppg/types"
|
||||
"github.com/goplus/llgo/xtool/nm"
|
||||
)
|
||||
|
||||
type dbgFlags = int
|
||||
|
||||
const (
|
||||
DbgSymbol dbgFlags = 1 << iota
|
||||
DbgFlagAll = DbgSymbol
|
||||
)
|
||||
|
||||
var (
|
||||
debugSymbol bool
|
||||
)
|
||||
|
||||
func SetDebug(dbgFlags dbgFlags) {
|
||||
debugSymbol = (dbgFlags & DbgSymbol) != 0
|
||||
}
|
||||
|
||||
// ParseDylibSymbols parses symbols from dynamic libraries specified in the lib string.
|
||||
// It handles multiple libraries (e.g., -L/opt/homebrew/lib -llua -lm) and returns
|
||||
// symbols if at least one library is successfully parsed. Errors from inaccessible
|
||||
// libraries (like standard libs) are logged as warnings.
|
||||
//
|
||||
// Returns symbols and nil error if any symbols are found, or nil and error if none found.
|
||||
func ParseDylibSymbols(lib string) ([]*nm.Symbol, error) {
|
||||
if debugSymbol {
|
||||
fmt.Println("ParseDylibSymbols:from", lib)
|
||||
}
|
||||
sysPaths := getSysLibPaths()
|
||||
lbs := cfgparse.ParseLibs(lib)
|
||||
if debugSymbol {
|
||||
fmt.Println("ParseDylibSymbols:LibConfig Parse To")
|
||||
fmt.Println("libs.Names: ", lbs.Names)
|
||||
fmt.Println("libs.Paths: ", lbs.Paths)
|
||||
}
|
||||
dylibPaths, notFounds, err := lbs.GenDylibPaths(sysPaths)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate some dylib paths: %v", err)
|
||||
}
|
||||
|
||||
if debugSymbol {
|
||||
fmt.Println("ParseDylibSymbols:dylibPaths", dylibPaths)
|
||||
if len(notFounds) > 0 {
|
||||
fmt.Println("ParseDylibSymbols:not found libname", notFounds)
|
||||
} else {
|
||||
fmt.Println("ParseDylibSymbols:every library is found")
|
||||
}
|
||||
}
|
||||
|
||||
var symbols []*nm.Symbol
|
||||
var parseErrors []string
|
||||
|
||||
for _, dylibPath := range dylibPaths {
|
||||
if _, err := os.Stat(dylibPath); err != nil {
|
||||
if debugSymbol {
|
||||
fmt.Printf("ParseDylibSymbols:Failed to access dylib %s: %v\n", dylibPath, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
files, err := nm.New("").List(dylibPath)
|
||||
if err != nil {
|
||||
parseErrors = append(parseErrors, fmt.Sprintf("ParseDylibSymbols:Failed to list symbols in dylib %s: %v", dylibPath, err))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
symbols = append(symbols, file.Symbols...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(symbols) > 0 {
|
||||
if debugSymbol {
|
||||
if len(parseErrors) > 0 {
|
||||
fmt.Printf("ParseDylibSymbols:Some libraries could not be parsed: %v\n", parseErrors)
|
||||
}
|
||||
fmt.Println("ParseDylibSymbols:", len(symbols), "symbols")
|
||||
}
|
||||
return symbols, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no symbols found in any dylib. Errors: %v", parseErrors)
|
||||
}
|
||||
|
||||
func getSysLibPaths() []string {
|
||||
var paths []string
|
||||
if runtime.GOOS == "linux" {
|
||||
if debugSymbol {
|
||||
fmt.Println("getSysLibPaths:find sys lib path from linux")
|
||||
}
|
||||
paths = []string{
|
||||
"/usr/lib",
|
||||
"/usr/local/lib",
|
||||
}
|
||||
paths = append(paths, getPath("/etc/ld.so.conf")...)
|
||||
if debugSymbol && len(paths) == 0 {
|
||||
fmt.Println("getSysLibPaths:/etc/ld.so.conf havent find any path")
|
||||
}
|
||||
confd := "/etc/ld.so.conf.d"
|
||||
dir, err := os.Stat(confd)
|
||||
if err != nil || !dir.IsDir() {
|
||||
if debugSymbol {
|
||||
fmt.Println("getSysLibPaths:/etc/ld.so.conf.d not found or not dir")
|
||||
}
|
||||
return paths
|
||||
}
|
||||
// todo(zzy) : wait llgo os.ReadDir support
|
||||
// files, err := os.ReadDir(confd)
|
||||
// if err == nil {
|
||||
// for _, file := range files {
|
||||
// filepath := filepath.Join(confd, file.Name())
|
||||
// paths = append(paths, getPath(filepath)...)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func getPath(file string) []string {
|
||||
if debugSymbol {
|
||||
fmt.Println("getPath:from", file)
|
||||
}
|
||||
var paths []string
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return paths
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" && !strings.HasPrefix(line, "#") {
|
||||
if file, err := os.Stat(line); err == nil && file.IsDir() {
|
||||
paths = append(paths, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
// finds the intersection of symbols from the dynamic library's symbol table and the symbols parsed from header files.
|
||||
// It returns a list of symbols that can be externally linked.
|
||||
func GetCommonSymbols(dylibSymbols []*nm.Symbol, headerSymbols map[string]*parse.SymbolInfo) []*types.SymbolInfo {
|
||||
var commonSymbols []*types.SymbolInfo
|
||||
for _, dylibSym := range dylibSymbols {
|
||||
symName := dylibSym.Name
|
||||
if runtime.GOOS == "darwin" {
|
||||
symName = strings.TrimPrefix(symName, "_")
|
||||
}
|
||||
if symInfo, ok := headerSymbols[symName]; ok {
|
||||
symbolInfo := &types.SymbolInfo{
|
||||
Mangle: symName,
|
||||
CPP: symInfo.ProtoName,
|
||||
Go: symInfo.GoName,
|
||||
}
|
||||
commonSymbols = append(commonSymbols, symbolInfo)
|
||||
}
|
||||
}
|
||||
return commonSymbols
|
||||
}
|
||||
|
||||
func ReadExistingSymbolTable(fileName string) (map[string]types.SymbolInfo, bool) {
|
||||
if _, err := os.Stat(fileName); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
parsedJSON := cjson.ParseBytes(data)
|
||||
if parsedJSON == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
existingSymbols := make(map[string]types.SymbolInfo)
|
||||
arraySize := parsedJSON.GetArraySize()
|
||||
|
||||
for i := 0; i < int(arraySize); i++ {
|
||||
item := parsedJSON.GetArrayItem(c.Int(i))
|
||||
symbol := types.SymbolInfo{
|
||||
Mangle: config.GetStringItem(item, "mangle", ""),
|
||||
CPP: config.GetStringItem(item, "c++", ""),
|
||||
Go: config.GetStringItem(item, "go", ""),
|
||||
}
|
||||
existingSymbols[symbol.Mangle] = symbol
|
||||
}
|
||||
|
||||
return existingSymbols, true
|
||||
}
|
||||
|
||||
func GenSymbolTableData(commonSymbols []*types.SymbolInfo, existingSymbols map[string]types.SymbolInfo) ([]byte, error) {
|
||||
if len(existingSymbols) > 0 {
|
||||
if debugSymbol {
|
||||
fmt.Println("GenSymbolTableData:generate symbol table with exist symbol table")
|
||||
}
|
||||
for i := range commonSymbols {
|
||||
if existingSymbol, exists := existingSymbols[commonSymbols[i].Mangle]; exists && commonSymbols[i].Go != existingSymbol.Go {
|
||||
if debugSymbol {
|
||||
fmt.Println("symbol", commonSymbols[i].Mangle, "already exist, use exist symbol", existingSymbol.Go)
|
||||
}
|
||||
commonSymbols[i].Go = existingSymbol.Go
|
||||
} else {
|
||||
if debugSymbol {
|
||||
fmt.Println("new symbol", commonSymbols[i].Mangle, "-", commonSymbols[i].CPP, "-", commonSymbols[i].Go)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if debugSymbol {
|
||||
fmt.Println("GenSymbolTableData:generate symbol table without symbol table")
|
||||
for _, symbol := range commonSymbols {
|
||||
fmt.Println("new symbol", symbol.Mangle, "-", symbol.CPP, "-", symbol.Go)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root := cjson.Array()
|
||||
defer root.Delete()
|
||||
|
||||
for _, symbol := range commonSymbols {
|
||||
item := cjson.Object()
|
||||
item.SetItem(c.Str("mangle"), cjson.String(c.AllocaCStr(symbol.Mangle)))
|
||||
item.SetItem(c.Str("c++"), cjson.String(c.AllocaCStr(symbol.CPP)))
|
||||
item.SetItem(c.Str("go"), cjson.String(c.AllocaCStr(symbol.Go)))
|
||||
root.AddItem(item)
|
||||
}
|
||||
|
||||
cStr := root.Print()
|
||||
if cStr == nil {
|
||||
return nil, errors.New("symbol table is empty")
|
||||
}
|
||||
defer c.Free(unsafe.Pointer(cStr))
|
||||
result := []byte(c.GoString(cStr))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GenerateAndUpdateSymbolTable(symbols []*nm.Symbol, headerInfos map[string]*parse.SymbolInfo, symbFile string) ([]byte, error) {
|
||||
commonSymbols := GetCommonSymbols(symbols, headerInfos)
|
||||
if debugSymbol {
|
||||
fmt.Println("GenerateAndUpdateSymbolTable:", len(commonSymbols), "common symbols")
|
||||
}
|
||||
|
||||
existSymbols, exist := ReadExistingSymbolTable(symbFile)
|
||||
if exist && debugSymbol {
|
||||
fmt.Println("GenerateAndUpdateSymbolTable:current path have exist symbol table", symbFile)
|
||||
}
|
||||
|
||||
symbolData, err := GenSymbolTableData(commonSymbols, existSymbols)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return symbolData, nil
|
||||
}
|
||||
|
||||
// For mutiple os test,the nm output's symbol name is different.
|
||||
func AddSymbolPrefixUnder(name string, isCpp bool) string {
|
||||
prefix := ""
|
||||
if runtime.GOOS == "darwin" {
|
||||
prefix = prefix + "_"
|
||||
}
|
||||
if isCpp {
|
||||
prefix = prefix + "_"
|
||||
}
|
||||
return prefix + name
|
||||
}
|
||||
Reference in New Issue
Block a user