diff --git a/chore/_xtool/llcppsigfetch/llcppsigfetch.go b/chore/_xtool/llcppsigfetch/llcppsigfetch.go index 61b12cf8..dfbea922 100644 --- a/chore/_xtool/llcppsigfetch/llcppsigfetch.go +++ b/chore/_xtool/llcppsigfetch/llcppsigfetch.go @@ -27,34 +27,42 @@ import ( "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/cjson" "github.com/goplus/llgo/chore/_xtool/llcppsigfetch/parse" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/clangutils" "github.com/goplus/llgo/chore/_xtool/llcppsymg/config" ) func main() { - if len(os.Args) == 1 { - // run with default config file - runFromConfig() - return - } - - if os.Args[1] == "--extract" { - runExtract() - } else if os.Args[1] == "--help" || os.Args[1] == "-h" { - printUsage() - } else { - runFromConfig() + cfgFile := "" + outputToFile := false + for i := 1; i < len(os.Args); i++ { + arg := os.Args[i] + if arg == "--extract" { + runExtract() + return + } else if arg == "--help" || arg == "-h" { + printUsage() + return + } else if strings.HasPrefix(arg, "-out=") { + outputToFile = parseBoolArg(arg, "out", false) + } else if cfgFile == "" && !strings.HasPrefix(arg, "-") { + cfgFile = arg + } } + runFromConfig(cfgFile, outputToFile) } func printUsage() { fmt.Println("Usage:") - fmt.Println(" llcppsigfetch []") + fmt.Println(" llcppsigfetch [] [-out=]") fmt.Println(" OR") - fmt.Println(" llcppsigfetch --extract [args...]") + fmt.Println(" llcppsigfetch --extract [-out=] [-temp=] [-cpp=] [args...]") fmt.Println("") fmt.Println("Options:") fmt.Println(" []: Path to the configuration file (use '-' for stdin)") fmt.Println(" If not provided, uses default 'llcppg.cfg'") + fmt.Println(" -out=: Optional. Set to 'true' to output results to a file,") + fmt.Println(" 'false' (default) to output to stdout") + fmt.Println(" This option can be used with both modes") fmt.Println("") fmt.Println(" --extract: Extract information from a single file") fmt.Println(" : Path to the file to process, or file content if -temp=true") @@ -71,10 +79,9 @@ func printUsage() { fmt.Println("Note: The two usage modes are mutually exclusive. Use either [] OR --extract, not both.") } -func runFromConfig() { - cfgFile := "llcppg.cfg" - if len(os.Args) > 1 { - cfgFile = os.Args[1] +func runFromConfig(cfgFile string, outputToFile bool) { + if cfgFile == "" { + cfgFile = "llcppg.cfg" } var data []byte @@ -100,7 +107,7 @@ func runFromConfig() { err = context.ProcessFiles(files) check(err) - outputInfo(context) + outputInfo(context, outputToFile) } func runExtract() { @@ -110,13 +117,14 @@ func runExtract() { os.Exit(1) } - cfg := &parse.Config{ + cfg := &clangutils.Config{ File: os.Args[2], Args: []string{}, IsCpp: true, Temp: false, } + outputToFile := false for i := 3; i < len(os.Args); i++ { arg := os.Args[i] switch { @@ -128,6 +136,10 @@ func runExtract() { cfg.IsCpp = parseBoolArg(arg, "cpp", true) os.Args = append(os.Args[:i], os.Args[i+1:]...) i-- + case strings.HasPrefix(arg, "-out="): + outputToFile = parseBoolArg(arg, "out", false) + os.Args = append(os.Args[:i], os.Args[i+1:]...) + i-- default: cfg.Args = append(cfg.Args, arg) } @@ -139,7 +151,7 @@ func runExtract() { check(err) result := converter.MarshalOutputASTFiles() cstr := result.Print() - c.Printf(cstr) + outputResult(cstr, outputToFile) cjson.FreeCStr(cstr) result.Delete() converter.Dispose() @@ -151,6 +163,20 @@ func check(err error) { } } +func outputResult(result *c.Char, outputToFile bool) { + if outputToFile { + outputFile := "llcppg.sigfetch.json" + err := os.WriteFile(outputFile, []byte(c.GoString(result)), 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Error writing to output file: %v\n", err) + os.Exit(1) + } + fmt.Printf("Results saved to %s\n", outputFile) + } else { + c.Printf(result) + } +} + func getHeaderFiles(cflags string, files []string) []string { prefix := cflags prefix = strings.TrimPrefix(prefix, "-I") @@ -161,12 +187,12 @@ func getHeaderFiles(cflags string, files []string) []string { return paths } -func outputInfo(context *parse.Context) { +func outputInfo(context *parse.Context, outputToFile bool) { info := context.Output() str := info.Print() defer cjson.FreeCStr(str) defer info.Delete() - c.Printf(str) + outputResult(str, outputToFile) } func parseBoolArg(arg, name string, defaultValue bool) bool { diff --git a/chore/_xtool/llcppsigfetch/parse/cvt.go b/chore/_xtool/llcppsigfetch/parse/cvt.go index 310adc6c..0cf35879 100644 --- a/chore/_xtool/llcppsigfetch/parse/cvt.go +++ b/chore/_xtool/llcppsigfetch/parse/cvt.go @@ -1,7 +1,6 @@ package parse import ( - "errors" "fmt" "os" "strings" @@ -10,6 +9,7 @@ import ( "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" ) @@ -63,8 +63,8 @@ type Config struct { IsCpp bool } -func NewConverter(config *Config) (*Converter, error) { - index, unit, err := CreateTranslationUnit(config) +func NewConverter(config *clangutils.Config) (*Converter, error) { + index, unit, err := clangutils.CreateTranslationUnit(config) if err != nil { return nil, err } @@ -78,56 +78,6 @@ func NewConverter(config *Config) (*Converter, error) { }, nil } -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 - defaultArgs := []string{"-x", "c"} - if config.IsCpp { - defaultArgs = []string{"-x", "c++"} - } - allArgs := append(defaultArgs, config.Args...) - - cArgs := make([]*c.Char, len(allArgs)) - for i, arg := range allArgs { - cArgs[i] = c.AllocaCStr(arg) - } - - index := clang.CreateIndex(0, 0) - - var unit *clang.TranslationUnit - - if config.Temp { - content := c.AllocaCStr(config.File) - tempFile := &clang.UnsavedFile{ - Filename: c.Str("temp.h"), - Contents: content, - Length: c.Ulong(c.Strlen(content)), - } - - unit = index.ParseTranslationUnit( - tempFile.Filename, - unsafe.SliceData(cArgs), c.Int(len(cArgs)), - tempFile, 1, - clang.DetailedPreprocessingRecord, - ) - - } else { - cFile := c.AllocaCStr(config.File) - unit = index.ParseTranslationUnit( - cFile, - unsafe.SliceData(cArgs), c.Int(len(cArgs)), - nil, 0, - clang.DetailedPreprocessingRecord, - ) - } - - if unit == nil { - return nil, nil, errors.New("failed to parse translation unit") - } - - return index, unit, nil -} - func (ct *Converter) Dispose() { ct.index.Dispose() ct.unit.Dispose() @@ -259,14 +209,13 @@ 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}) + commentGroup.List = append(commentGroup.List, &ast.Comment{Text: line + "\n"}) } return commentGroup } // visit top decls (struct,class,function,enum & macro,include) -func visitTop(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult { - ct := (*Converter)(clientData) +func (ct *Converter) visitTop(cursor, parent clang.Cursor) clang.ChildVisitResult { ct.UpdateLoc(cursor) curFile := ct.GetCurFile() @@ -300,7 +249,7 @@ func visitTop(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.Chil case clang.CursorTypedefDecl: curFile.Decls = append(curFile.Decls, ct.ProcessTypeDefDecl(cursor)) case clang.CursorNamespace: - clang.VisitChildren(cursor, visitTop, c.Pointer(ct)) + VisitChildren(cursor, ct.visitTop) } return clang.ChildVisit_Continue } @@ -308,10 +257,19 @@ func visitTop(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.Chil func (ct *Converter) Convert() ([]*FileEntry, error) { cursor := ct.unit.Cursor() // visit top decls (struct,class,function & macro,include) - clang.VisitChildren(cursor, visitTop, c.Pointer(ct)) + 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 { if t.Kind >= clang.TypeFirstBuiltin && t.Kind <= clang.TypeLastBuiltin { return ct.ProcessBuiltinType(t) @@ -519,38 +477,30 @@ func (ct *Converter) ProcessMethodAttributes(cursor clang.Cursor, fn *ast.FuncDe overridden.DisposeOverriddenCursors() } -type visitEnumContext struct { - enum *[]*ast.EnumItem - converter *Converter -} - -func visitEnum(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult { - ctx := (*visitEnumContext)(clientData) - if cursor.Kind == clang.CursorEnumConstantDecl { - name := cursor.String() - 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)) - defer name.Dispose() - enum := &ast.EnumItem{ - Name: &ast.Ident{Name: c.GoString(name.CStr())}, - Value: &ast.BasicLit{ - Kind: ast.IntLit, - Value: c.GoString(val), - }, - } - *ctx.enum = append(*ctx.enum, enum) - } - return clang.ChildVisit_Continue -} - func (ct *Converter) ProcessEnumType(cursor clang.Cursor) *ast.EnumType { items := make([]*ast.EnumItem, 0) - ctx := &visitEnumContext{ - enum: &items, - converter: ct, - } - clang.VisitChildren(cursor, visitEnum, c.Pointer(ctx)) + + 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, } @@ -587,6 +537,8 @@ func (ct *Converter) ProcessInclude(cursor clang.Cursor) *ast.Include { return &ast.Include{Path: c.GoString(name.CStr())} } +// 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 @@ -851,23 +803,10 @@ func (ct *Converter) ProcessBuiltinType(t clang.Type) *ast.BuiltinType { // 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 := ct.BuildScopingParts(cursor) + parts := clangutils.BuildScopingParts(cursor) return buildScopingFromParts(parts) } -func (ct *Converter) BuildScopingParts(cursor clang.Cursor) []string { - var parts []string - // Traverse up the semantic parents - for cursor.IsNull() != 1 && cursor.Kind != clang.CursorTranslationUnit { - name := cursor.String() - qualified := c.GoString(name.CStr()) - parts = append([]string{qualified}, parts...) - cursor = cursor.SemanticParent() - name.Dispose() - } - return parts -} - func (ct *Converter) MarshalASTFiles() *cjson.JSON { return MarshalASTFiles(ct.Files) } diff --git a/chore/_xtool/llcppsigfetch/parse/cvt_test/cvt.go b/chore/_xtool/llcppsigfetch/parse/cvt_test/cvt.go index a72e16b0..f45a4f6a 100644 --- a/chore/_xtool/llcppsigfetch/parse/cvt_test/cvt.go +++ b/chore/_xtool/llcppsigfetch/parse/cvt_test/cvt.go @@ -2,17 +2,17 @@ package cvttest import ( "fmt" - "unsafe" "github.com/goplus/llgo/c" "github.com/goplus/llgo/c/cjson" "github.com/goplus/llgo/c/clang" "github.com/goplus/llgo/chore/_xtool/llcppsigfetch/parse" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/clangutils" ) func RunTest(testName string, testCases []string) { for i, content := range testCases { - converter, err := parse.NewConverter(&parse.Config{ + converter, err := parse.NewConverter(&clangutils.Config{ File: content, Temp: true, IsCpp: true, @@ -63,7 +63,7 @@ type GetTypeOptions struct { // e.g. index.Dispose(), unit.Dispose() func GetType(option *GetTypeOptions) (clang.Type, *clang.Index, *clang.TranslationUnit) { code := fmt.Sprintf("%s placeholder;", option.TypeCode) - index, unit, err := parse.CreateTranslationUnit(&parse.Config{ + index, unit, err := clangutils.CreateTranslationUnit(&clangutils.Config{ File: code, Temp: true, Args: option.Args, @@ -73,22 +73,13 @@ func GetType(option *GetTypeOptions) (clang.Type, *clang.Index, *clang.Translati panic(err) } cursor := unit.Cursor() - visitType := &typeVisitData{typ: &clang.Type{}, expectTypeKind: option.ExpectTypeKind} - - clang.VisitChildren(cursor, typeVisit, unsafe.Pointer(visitType)) - return *visitType.typ, index, unit -} - -type typeVisitData struct { - typ *clang.Type - expectTypeKind clang.TypeKind -} - -func typeVisit(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult { - visitData := (*typeVisitData)(clientData) - if cursor.Kind == clang.CursorVarDecl && (visitData.expectTypeKind == clang.TypeInvalid || cursor.Type().Kind == visitData.expectTypeKind) { - *visitData.typ = cursor.Type() - return clang.ChildVisit_Break - } - return clang.ChildVisit_Continue + var typ clang.Type + parse.VisitChildren(cursor, func(child, parent clang.Cursor) clang.ChildVisitResult { + if child.Kind == clang.CursorVarDecl && (option.ExpectTypeKind == clang.TypeInvalid || option.ExpectTypeKind == child.Type().Kind) { + typ = child.Type() + return clang.ChildVisit_Break + } + return clang.ChildVisit_Continue + }) + return typ, index, unit } diff --git a/chore/_xtool/llcppsigfetch/parse/cvt_test/decl_test/comment_test/llgo.expect b/chore/_xtool/llcppsigfetch/parse/cvt_test/decl_test/comment_test/llgo.expect index 0a08f88d..6c769412 100644 --- a/chore/_xtool/llcppsigfetch/parse/cvt_test/decl_test/comment_test/llgo.expect +++ b/chore/_xtool/llcppsigfetch/parse/cvt_test/decl_test/comment_test/llgo.expect @@ -99,7 +99,7 @@ TestDoc Case 3: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/// doc" + "Text": "/// doc\n" }] }, "Parent": null, @@ -148,7 +148,7 @@ TestDoc Case 4: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/** doc */" + "Text": "/** doc */\n" }] }, "Parent": null, @@ -197,7 +197,7 @@ TestDoc Case 5: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/*! doc */" + "Text": "/*! doc */\n" }] }, "Parent": null, @@ -246,10 +246,10 @@ TestDoc Case 6: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/// doc 1" + "Text": "/// doc 1\n" }, { "_Type": "Comment", - "Text": "/// doc 2" + "Text": "/// doc 2\n" }] }, "Parent": null, @@ -298,10 +298,10 @@ TestDoc Case 7: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/*! doc 1 */" + "Text": "/*! doc 1 */\n" }, { "_Type": "Comment", - "Text": "/*! doc 2 */" + "Text": "/*! doc 2 */\n" }] }, "Parent": null, @@ -350,10 +350,10 @@ TestDoc Case 8: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/** doc 1 */" + "Text": "/** doc 1 */\n" }, { "_Type": "Comment", - "Text": "/** doc 1 */" + "Text": "/** doc 1 */\n" }] }, "Parent": null, @@ -402,16 +402,16 @@ TestDoc Case 9: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/**" + "Text": "/**\n" }, { "_Type": "Comment", - "Text": " * doc 1" + "Text": " * doc 1\n" }, { "_Type": "Comment", - "Text": " * doc 2" + "Text": " * doc 2\n" }, { "_Type": "Comment", - "Text": " */" + "Text": " */\n" }] }, "Parent": null, @@ -478,7 +478,7 @@ TestDoc Case 10: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/// doc" + "Text": "/// doc\n" }] }, "Comment": null, @@ -500,7 +500,7 @@ TestDoc Case 10: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "///< comment" + "Text": "///< comment\n" }] }, "IsStatic": false, @@ -521,7 +521,7 @@ TestDoc Case 10: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/*!< comment */" + "Text": "/*!< comment */\n" }] }, "IsStatic": false, @@ -572,13 +572,13 @@ TestDoc Case 11: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/** " + "Text": "/** \n" }, { "_Type": "Comment", - "Text": " * static field doc" + "Text": " * static field doc\n" }, { "_Type": "Comment", - "Text": " */" + "Text": " */\n" }] }, "Comment": null, @@ -600,7 +600,7 @@ TestDoc Case 11: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/*!< static field comment */" + "Text": "/*!< static field comment */\n" }] }, "IsStatic": true, @@ -620,13 +620,13 @@ TestDoc Case 11: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/** " + "Text": "/** \n" }, { "_Type": "Comment", - "Text": " * field doc" + "Text": " * field doc\n" }, { "_Type": "Comment", - "Text": " */" + "Text": " */\n" }] }, "Comment": null, @@ -648,7 +648,7 @@ TestDoc Case 11: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "///< field comment" + "Text": "///< field comment\n" }] }, "IsStatic": false, @@ -669,7 +669,7 @@ TestDoc Case 11: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/*!< protected field comment */" + "Text": "/*!< protected field comment */\n" }] }, "IsStatic": false, @@ -690,13 +690,13 @@ TestDoc Case 11: "_Type": "CommentGroup", "List": [{ "_Type": "Comment", - "Text": "/** " + "Text": "/** \n" }, { "_Type": "Comment", - "Text": " * method doc" + "Text": " * method doc\n" }, { "_Type": "Comment", - "Text": " */" + "Text": " */\n" }] }, "Parent": { diff --git a/chore/_xtool/llcppsigfetch/parse/parse.go b/chore/_xtool/llcppsigfetch/parse/parse.go index 9a1c3978..a267d4fc 100644 --- a/chore/_xtool/llcppsigfetch/parse/parse.go +++ b/chore/_xtool/llcppsigfetch/parse/parse.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/goplus/llgo/c/cjson" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/clangutils" ) type Context struct { @@ -49,7 +50,7 @@ func (p *Context) processFile(path string) error { } func (p *Context) parseFile(path string) ([]*FileEntry, error) { - converter, err := NewConverter(&Config{ + converter, err := NewConverter(&clangutils.Config{ File: path, Temp: false, IsCpp: p.IsCpp,