/* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "fmt" "io" "os" "path/filepath" "strconv" "strings" "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/args" "github.com/goplus/llgo/chore/_xtool/llcppsymg/clangutils" "github.com/goplus/llgo/chore/_xtool/llcppsymg/config" ) func main() { ags, remainArgs := args.ParseArgs(os.Args[1:], map[string]bool{ "--extract": true, }) if ags.Help { printUsage() return } if ags.Verbose { parse.SetDebug(parse.DbgFlagAll) } extract := false out := false var extractFile string isTemp := false isCpp := true otherArgs := []string{} for i := 0; i < len(remainArgs); i++ { arg := remainArgs[i] switch { case arg == "--extract": extract = true if i+1 < len(remainArgs) && !strings.HasPrefix(remainArgs[i+1], "-") { extractFile = remainArgs[i+1] i++ } else { fmt.Fprintln(os.Stderr, "Error: --extract requires a valid file argument") printUsage() os.Exit(1) } case strings.HasPrefix(arg, "-out="): out = parseBoolArg(arg, "out", false) case strings.HasPrefix(arg, "-temp="): isTemp = parseBoolArg(arg, "temp", false) case strings.HasPrefix(arg, "-cpp="): isCpp = parseBoolArg(arg, "cpp", true) default: otherArgs = append(otherArgs, arg) } } if extract { if ags.Verbose { fmt.Fprintln(os.Stderr, "runExtract: extractFile:", extractFile) fmt.Fprintln(os.Stderr, "isTemp:", isTemp) fmt.Fprintln(os.Stderr, "isCpp:", isCpp) fmt.Fprintln(os.Stderr, "out:", out) fmt.Fprintln(os.Stderr, "otherArgs:", otherArgs) } runExtract(extractFile, isTemp, isCpp, out, otherArgs, ags.Verbose) } else { if ags.Verbose { fmt.Fprintln(os.Stderr, "runFromConfig: config file:", ags.CfgFile) fmt.Fprintln(os.Stderr, "use stdin:", ags.UseStdin) fmt.Fprintln(os.Stderr, "output to file:", out) } runFromConfig(ags.CfgFile, ags.UseStdin, out, ags.Verbose) } } func printUsage() { fmt.Println("Usage:") fmt.Println(" llcppsigfetch [] [-out=]") fmt.Println(" OR") 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") fmt.Println(" -temp=: Optional. Set to 'true' if contains file content,") fmt.Println(" 'false' (default) if it's a file path") fmt.Println(" -cpp=: Optional. Set to 'true' if the language is C++ (default: true)") fmt.Println(" If not present, is a file path") fmt.Println(" [args]: Optional additional arguments") fmt.Println(" Default for C++: -x c++") fmt.Println(" Default for C: -x c") fmt.Println("") fmt.Println(" --help, -h: Show this help message") fmt.Println("") fmt.Println("Note: The two usage modes are mutually exclusive. Use either [] OR --extract, not both.") } func runFromConfig(cfgFile string, useStdin bool, outputToFile bool, verbose bool) { var data []byte var err error if useStdin { data, err = io.ReadAll(os.Stdin) } else { data, err = os.ReadFile(cfgFile) } if verbose { if useStdin { fmt.Fprintln(os.Stderr, "runFromConfig: read from stdin") } else { fmt.Fprintln(os.Stderr, "runFromConfig: read from file", cfgFile) } } 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) os.Exit(1) } //todo(zzy): reuse the llcppsymg's cflags parse cflag := ParseCFlags(conf.CFlags) files, notFounds, err := cflag.GenHeaderFilePaths(conf.Include) check(err) if verbose { fmt.Fprintln(os.Stderr, "runFromConfig: header file paths", files) if len(notFounds) > 0 { fmt.Fprintln(os.Stderr, "runFromConfig: not found header files", notFounds) } } context := parse.NewContext(conf.Cplusplus) err = context.ProcessFiles(files) check(err) outputInfo(context, outputToFile) } func runExtract(file string, isTemp bool, isCpp bool, outToFile bool, otherArgs []string, verbose bool) { cfg := &clangutils.Config{ File: file, Args: otherArgs, IsCpp: isCpp, Temp: isTemp, } converter, err := parse.NewConverter(cfg) check(err) _, err = converter.Convert() check(err) result := converter.MarshalOutputASTFiles() cstr := result.Print() outputResult(cstr, outToFile) cjson.FreeCStr(cstr) result.Delete() converter.Dispose() } func check(err error) { if err != nil { panic(err) } } 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.Fprintf(os.Stderr, "Results saved to %s\n", outputFile) } else { c.Printf(result) } } // todo(zzy): reuse the llcppsymg's cflags parse https://github.com/goplus/llgo/pull/788 type CFlags struct { Paths []string // Include Path } 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 } func outputInfo(context *parse.Context, outputToFile bool) { info := context.Output() str := info.Print() defer cjson.FreeCStr(str) defer info.Delete() outputResult(str, outputToFile) } func parseBoolArg(arg, name string, defaultValue bool) bool { parts := strings.SplitN(arg, "=", 2) if len(parts) != 2 { fmt.Fprintf(os.Stderr, "Warning: Invalid -%s= argument, defaulting to %v\n", name, defaultValue) return defaultValue } value, err := strconv.ParseBool(parts[1]) if err != nil { fmt.Fprintf(os.Stderr, "Warning: Invalid -%s= value '%s', defaulting to %v\n", name, parts[1], defaultValue) return defaultValue } return value }