diff --git a/cmd/gogensig/cmp/cmp.go b/cmd/gogensig/cmp/cmp.go new file mode 100644 index 0000000..1232abb --- /dev/null +++ b/cmd/gogensig/cmp/cmp.go @@ -0,0 +1,79 @@ +package cmp + +import ( + "bufio" + "bytes" + "strings" + "unicode" + + "github.com/google/go-cmp/cmp" +) + +func skipSpace(data []byte, from int) int { + dataBuf := bytes.NewBuffer(data[from:]) + index := from + for { + rn, sz, err := dataBuf.ReadRune() + if err != nil { + break + } + if !unicode.IsSpace(rn) { + break + } + index = index + sz + } + return index +} + +func SplitLineIgnoreSpace(s string) []string { + buf := bytes.NewBufferString(s) + scan := bufio.NewScanner(buf) + results := make([]string, 0) + for scan.Scan() { + lineText := scan.Text() + lineTextBuf := bytes.NewBufferString(lineText) + lineTextScan := bufio.NewScanner(lineTextBuf) + lineTextScan.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.IndexAny(data, " \t"); i >= 0 { + ii := skipSpace(data, i+1) + return ii, data[0:i], nil + } + if atEOF { + return len(data), data, nil + } + return 0, nil, nil + }) + + strBuilder := strings.Builder{} + writeSpace := false + total := 0 + for lineTextScan.Scan() { + word := lineTextScan.Text() + if writeSpace { + strBuilder.WriteRune(' ') + } + n, err := strBuilder.WriteString(word) + if err != nil { + break + } + total += n + writeSpace = total > 0 + } + if total > 0 { + results = append(results, strBuilder.String()) + } + } + return results +} + +func EqualStringIgnoreSpace(s1 string, s2 string) (bool, string) { + arr1 := SplitLineIgnoreSpace(s1) + arr2 := SplitLineIgnoreSpace(s2) + if !cmp.Equal(arr1, arr2) { + return false, cmp.Diff(arr1, arr2) + } + return true, "" +} diff --git a/cmd/gogensig/config/_testinput/llcppg.pub b/cmd/gogensig/config/_testinput/llcppg.pub new file mode 100644 index 0000000..aed4739 --- /dev/null +++ b/cmd/gogensig/config/_testinput/llcppg.pub @@ -0,0 +1,4 @@ +file FILE +err Err + +stdio \ No newline at end of file diff --git a/cmd/gogensig/config/_testinput/llcppg.symb.json b/cmd/gogensig/config/_testinput/llcppg.symb.json new file mode 100644 index 0000000..3af1ffb --- /dev/null +++ b/cmd/gogensig/config/_testinput/llcppg.symb.json @@ -0,0 +1,61 @@ +[{ + "mangle": "_ZN9INIReader12ValueHandlerEPvPKcS2_S2_", + "c++": "INIReader::ValueHandler(void*, char const*, char const*, char const*)", + "go": "(*Reader).ValueHandler" + }, { + "mangle": "_ZN9INIReader7MakeKeyERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_", + "c++": "INIReader::MakeKey(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&)", + "go": "(*Reader).MakeKey" + }, { + "mangle": "_ZN9INIReaderC1EPKcm", + "c++": "INIReader::INIReader(char const*, unsigned long)", + "go": "(*Reader).Init__1" + }, { + "mangle": "_ZN9INIReaderC1ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE", + "c++": "INIReader::INIReader(std::__1::basic_string, std::__1::allocator> const&)", + "go": "(*Reader).Init" + }, { + "mangle": "_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b", + "c++": "INIReader::GetBoolean(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, bool) const", + "go": "(*Reader).GetBoolean" + }, { + "mangle": "_ZNK9INIReader10GetIntegerERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_l", + "c++": "INIReader::GetInteger(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, long) const", + "go": "(*Reader).GetInteger" + }, { + "mangle": "_ZNK9INIReader10HasSectionERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE", + "c++": "INIReader::HasSection(std::__1::basic_string, std::__1::allocator> const&) const", + "go": "(*Reader).HasSection" + }, { + "mangle": "_ZNK9INIReader10ParseErrorEv", + "c++": "INIReader::ParseError() const", + "go": "(*Reader).ParseError" + }, { + "mangle": "_ZNK9INIReader11GetUnsignedERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_m", + "c++": "INIReader::GetUnsigned(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, unsigned long) const", + "go": "(*Reader).GetUnsigned" + }, { + "mangle": "_ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x", + "c++": "INIReader::GetInteger64(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, long long) const", + "go": "(*Reader).GetInteger64" + }, { + "mangle": "_ZNK9INIReader13GetUnsigned64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_y", + "c++": "INIReader::GetUnsigned64(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, unsigned long long) const", + "go": "(*Reader).GetUnsigned64" + }, { + "mangle": "_ZNK9INIReader3GetERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_S8_", + "c++": "INIReader::Get(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&) const", + "go": "(*Reader).Get" + }, { + "mangle": "_ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d", + "c++": "INIReader::GetReal(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, double) const", + "go": "(*Reader).GetReal" + }, { + "mangle": "_ZNK9INIReader8HasValueERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_", + "c++": "INIReader::HasValue(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&) const", + "go": "(*Reader).HasValue" + }, { + "mangle": "_ZNK9INIReader9GetStringERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_S8_", + "c++": "INIReader::GetString(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&) const", + "go": "(*Reader).GetString" + }] \ No newline at end of file diff --git a/cmd/gogensig/config/_testinput/llcppg.symb.txt b/cmd/gogensig/config/_testinput/llcppg.symb.txt new file mode 100644 index 0000000..79569bd --- /dev/null +++ b/cmd/gogensig/config/_testinput/llcppg.symb.txt @@ -0,0 +1,61 @@ +[{ + : "_ZN9INIReader12ValueHandlerEPvPKcS2_S2_", + "c++": "INIReader::ValueHandler(void*, char const*, char const*, char const*)", + "go": "(*Reader).ValueHandler" + }, { + "mangle": "_ZN9INIReader7MakeKeyERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_", + "c++": "INIReader::MakeKey(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&)", + "go": "(*Reader).MakeKey" + }, { + "mangle": "_ZN9INIReaderC1EPKcm", + "c++": "INIReader::INIReader(char const*, unsigned long)", + "go": "(*Reader).Init__1" + }, { + "mangle": "_ZN9INIReaderC1ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE", + "c++": "INIReader::INIReader(std::__1::basic_string, std::__1::allocator> const&)", + "go": "(*Reader).Init" + }, { + "mangle": "_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b", + "c++": "INIReader::GetBoolean(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, bool) const", + "go": "(*Reader).GetBoolean" + }, { + "mangle": "_ZNK9INIReader10GetIntegerERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_l", + "c++": "INIReader::GetInteger(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, long) const", + "go": "(*Reader).GetInteger" + }, { + "mangle": "_ZNK9INIReader10HasSectionERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE", + "c++": "INIReader::HasSection(std::__1::basic_string, std::__1::allocator> const&) const", + "go": "(*Reader).HasSection" + }, { + "mangle": "_ZNK9INIReader10ParseErrorEv", + "c++": "INIReader::ParseError() const", + "go": "(*Reader).ParseError" + }, { + "mangle": "_ZNK9INIReader11GetUnsignedERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_m", + "c++": "INIReader::GetUnsigned(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, unsigned long) const", + "go": "(*Reader).GetUnsigned" + }, { + "mangle": "_ZNK9INIReader12GetInteger64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_x", + "c++": "INIReader::GetInteger64(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, long long) const", + "go": "(*Reader).GetInteger64" + }, { + "mangle": "_ZNK9INIReader13GetUnsigned64ERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_y", + "c++": "INIReader::GetUnsigned64(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, unsigned long long) const", + "go": "(*Reader).GetUnsigned64" + }, { + "mangle": "_ZNK9INIReader3GetERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_S8_", + "c++": "INIReader::Get(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&) const", + "go": "(*Reader).Get" + }, { + "mangle": "_ZNK9INIReader7GetRealERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_d", + "c++": "INIReader::GetReal(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, double) const", + "go": "(*Reader).GetReal" + }, { + "mangle": "_ZNK9INIReader8HasValueERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_", + "c++": "INIReader::HasValue(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&) const", + "go": "(*Reader).HasValue" + }, { + "mangle": "_ZNK9INIReader9GetStringERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_S8_", + "c++": "INIReader::GetString(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&) const", + "go": "(*Reader).GetString" + }] \ No newline at end of file diff --git a/cmd/gogensig/config/conf.go b/cmd/gogensig/config/conf.go new file mode 100644 index 0000000..ad55996 --- /dev/null +++ b/cmd/gogensig/config/conf.go @@ -0,0 +1,160 @@ +package config + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "github.com/goplus/llgo/chore/gogensig/unmarshal" + cppgtypes "github.com/goplus/llgo/chore/llcppg/types" +) + +// llcppg.cfg +func GetCppgCfgFromPath(filePath string) (*cppgtypes.Config, error) { + bytes, err := ReadFile(filePath) + if err != nil { + return nil, err + } + conf := &cppgtypes.Config{} + err = json.Unmarshal(bytes, &conf) + if err != nil { + return nil, err + } + return conf, nil +} + +// llcppg.pub +func GetPubFromPath(filePath string) (map[string]string, error) { + return ReadPubFile(filePath) +} + +func GetCppgSigfetchFromByte(data []byte) (unmarshal.FileSet, error) { + return unmarshal.UnmarshalFileSet(data) +} + +func SigfetchExtract(file string, isTemp bool, isCPP bool, dir string) ([]byte, error) { + args := []string{"--extract", file} + + if isTemp { + args = append(args, "-temp=true") + } + + if isCPP { + args = append(args, "-cpp=true") + } else { + args = append(args, "-cpp=false") + } + + return executeSigfetch(args, dir) +} + +func SigfetchConfig(configFile string, dir string) ([]byte, error) { + args := []string{configFile} + return executeSigfetch(args, dir) +} + +func executeSigfetch(args []string, dir string) ([]byte, error) { + cmd := exec.Command("llcppsigfetch", args...) + if dir != "" { + cmd.Dir = dir + } + + var out bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + return nil, fmt.Errorf("error running llcppsigfetch: %v\nStderr: %s\nArgs: %s", err, stderr.String(), strings.Join(args, " ")) + } + + return out.Bytes(), nil +} + +func ReadFile(filePath string) ([]byte, error) { + jsonFile, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer jsonFile.Close() + return io.ReadAll(jsonFile) +} + +func ReadPubFile(pubfile string) (ret map[string]string, err error) { + b, err := os.ReadFile(pubfile) + if err != nil { + if os.IsNotExist(err) { + return make(map[string]string), nil + } + return + } + + text := string(b) + lines := strings.Split(text, "\n") + ret = make(map[string]string, len(lines)) + for i, line := range lines { + flds := strings.Fields(line) + goName := "" + switch len(flds) { + case 1: + case 2: + goName = flds[1] + case 0: + continue + default: + err = fmt.Errorf("%s:%d: too many fields", pubfile, i+1) + return + } + ret[flds[0]] = goName + } + return +} + +func WritePubFile(file string, public map[string]string) (err error) { + if len(public) == 0 { + return + } + f, err := os.Create(file) + if err != nil { + return + } + defer f.Close() + ret := make([]string, 0, len(public)) + for name, goName := range public { + if goName == "" { + ret = append(ret, name) + } else { + ret = append(ret, name+" "+goName) + } + } + sort.Strings(ret) + _, err = f.WriteString(strings.Join(ret, "\n")) + return +} + +func RunCommand(dir, cmdName string, args ...string) error { + execCmd := exec.Command(cmdName, args...) + execCmd.Stdout = os.Stdout + execCmd.Stderr = os.Stderr + execCmd.Dir = dir + return execCmd.Run() +} + +func CreateJSONFile(filename string, data interface{}) (string, error) { + filePath := filepath.Join(os.TempDir(), filename) + file, err := os.Create(filePath) + if err != nil { + return "", err + } + defer file.Close() + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + return filePath, encoder.Encode(data) +} diff --git a/cmd/gogensig/config/config_test.go b/cmd/gogensig/config/config_test.go new file mode 100644 index 0000000..58afa9b --- /dev/null +++ b/cmd/gogensig/config/config_test.go @@ -0,0 +1,268 @@ +package config_test + +import ( + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/goplus/llgo/chore/gogensig/config" + cppgtypes "github.com/goplus/llgo/chore/llcppg/types" +) + +func TestLookupSymbolOK(t *testing.T) { + table, err := config.NewSymbolTable("./_testinput/llcppg.symb.json") + if err != nil { + t.Fatal(err) + } + entry, err := table.LookupSymbol("_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_b") + if err != nil { + t.Fatal(err) + } + const expectCppName = "INIReader::GetBoolean(std::__1::basic_string, std::__1::allocator> const&, std::__1::basic_string, std::__1::allocator> const&, bool) const" + const expectGoName = "(*Reader).GetBoolean" + if !cmp.Equal(string(entry.CppName), expectCppName) || + !cmp.Equal(string(entry.GoName), expectGoName) { + t.Fatalf("%s\n%s", cmp.Diff(entry.CppName, expectCppName), cmp.Diff(entry.GoName, expectGoName)) + } +} + +func TestLookupSymbolError(t *testing.T) { + _, err := config.NewSymbolTable("./_testinput/llcppg.symb.txt") + if err == nil { + t.Error("expect error") + } + table, err := config.NewSymbolTable("./_testinput/llcppg.symb.json") + if err != nil { + t.Fatal(err) + } + lookupSymbs := []string{ + "_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_bXXX", + "", + } + for _, lookupSymbol := range lookupSymbs { + _, err := table.LookupSymbol(lookupSymbol) + if err == nil { + t.Error("expect error") + } + } + nilTable, _ := config.NewSymbolTable("") + _, err = nilTable.LookupSymbol("_ZNK9INIReader10GetBooleanERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_bXXX") + if err == nil { + t.Error("expect error") + } +} + +func TestSigfetch(t *testing.T) { + testCases := []struct { + name string + input string + isTemp bool + isCpp bool + }{ + {name: "cpp sigfetch", input: `void fn();`, isTemp: true, isCpp: true}, + {name: "c sigfetch", input: `void fn();`, isTemp: true, isCpp: false}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + data, err := config.SigfetchExtract(tc.input, tc.isTemp, tc.isCpp, ".") + if err != nil { + t.Fatal(err) + } + _, err = config.GetCppgSigfetchFromByte(data) + if err != nil { + t.Fatal(err) + } + }) + } +} + +func TestSigfetchError(t *testing.T) { + oldPath := os.Getenv("PATH") + defer os.Setenv("PATH", oldPath) + + _, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current working directory: %v", err) + } + + tempDir, err := os.MkdirTemp("", "test") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + defer os.RemoveAll(tempDir) + + mockScript := filepath.Join(tempDir, "llcppsigfetch") + err = os.WriteFile(mockScript, []byte("#!/bin/bash\necho 'Simulated llcppsigfetch error' >&2\nexit 1"), 0755) + if err != nil { + t.Fatalf("Failed to create mock script: %v", err) + } + + os.Setenv("PATH", tempDir+string(os.PathListSeparator)+oldPath) + + _, err = config.SigfetchExtract("test.cpp", false, true, ".") + if err == nil { + t.Error("Expected error, got nil") + } + if !strings.Contains(err.Error(), "Simulated llcppsigfetch error") { + t.Errorf("Unexpected error message: %v", err) + } +} + +func TestGetCppgCfgFromPath(t *testing.T) { + // Create temporary directory + tempDir, err := os.MkdirTemp("", "config_test") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + defer os.RemoveAll(tempDir) + + // Prepare valid config file + validConfigPath := filepath.Join(tempDir, "valid_config.cfg") + validConfigContent := `{ + "name": "lua", + "cflags": "$(pkg-config --cflags lua5.4)", + "include": ["litelua.h"], + "libs": "$(pkg-config --libs lua5.4)", + "trimPrefixes": ["lua_"], + "cplusplus": false + }` + err = os.WriteFile(validConfigPath, []byte(validConfigContent), 0644) + if err != nil { + t.Fatalf("Failed to create valid config file: %v", err) + } + + t.Run("Successfully parse config file", func(t *testing.T) { + cfg, err := config.GetCppgCfgFromPath(validConfigPath) + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + if cfg == nil { + t.Fatal("Expected non-nil config") + } + + expectedConfig := &cppgtypes.Config{ + Name: "lua", + CFlags: "$(pkg-config --cflags lua5.4)", + Include: []string{"litelua.h"}, + Libs: "$(pkg-config --libs lua5.4)", + TrimPrefixes: []string{"lua_"}, + Cplusplus: false, + } + + if !reflect.DeepEqual(cfg, expectedConfig) { + t.Errorf("Parsed config does not match expected config.\nGot: %+v\nWant: %+v", cfg, expectedConfig) + } + }) + + t.Run("File not found", func(t *testing.T) { + _, err := config.GetCppgCfgFromPath(filepath.Join(tempDir, "nonexistent_file.cfg")) + if err == nil { + t.Error("Expected error for non-existent file, got nil") + } + }) + + t.Run("Invalid JSON", func(t *testing.T) { + invalidJSONPath := filepath.Join(tempDir, "invalid_config.cfg") + err := os.WriteFile(invalidJSONPath, []byte("{invalid json}"), 0644) + if err != nil { + t.Fatalf("Failed to create invalid JSON file: %v", err) + } + + _, err = config.GetCppgCfgFromPath(invalidJSONPath) + if err == nil { + t.Error("Expected error for invalid JSON, got nil") + } + }) +} + +func TestRunCommand(t *testing.T) { + err := config.RunCommand(".", "echo", "hello") + if err != nil { + t.Fatal(err) + } +} + +func TestGetPubFromPath(t *testing.T) { + pub, err := config.GetPubFromPath("./_testinput/llcppg.pub") + if err != nil { + t.Fatal(err) + } + if len(pub) != 3 { + t.Fatalf("expect 3 entries, got %d", len(pub)) + } + if pub["file"] != "FILE" || pub["err"] != "Err" || pub["stdio"] != "" { + t.Fatalf("expect file, err, stdio, got %v", pub) + } +} + +func TestGetPubFromPathError(t *testing.T) { + pub, err := config.GetPubFromPath("./_testinput/llcppg.txt") + if !(pub != nil && len(pub) == 0 && err == nil) { + t.Fatalf("expect empty map for llcppg.txt") + } + temp, err := os.CreateTemp("", "config_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(temp.Name()) + content := `a b c` + _, err = temp.WriteString(content) + if err != nil { + t.Fatal(err) + } + _, err = config.GetPubFromPath(temp.Name()) + if err == nil { + t.Fatalf("expect error, got nil") + } +} + +func TestWritePubFile(t *testing.T) { + pub := map[string]string{ + "file": "FILE", + "err": "Err", + "stdio": "", + } + tempDir, err := os.MkdirTemp("", "config_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + pubFile := filepath.Join(tempDir, "llcppg.pub") + err = config.WritePubFile(pubFile, pub) + if err != nil { + t.Fatal(err) + } + content, err := os.ReadFile(pubFile) + if err != nil { + t.Fatal(err) + } + expect := + `err Err +file FILE +stdio` + if string(content) != expect { + t.Fatalf("expect %s, got %s", expect, string(content)) + } + + notExistFilePath := filepath.Join(tempDir, "not_exit_dir", "not_exist_file.pub") + err = config.WritePubFile(notExistFilePath, pub) + if err == nil { + t.Fatalf("expect error, got nil") + } + if !os.IsNotExist(err) { + t.Fatalf("expect os.IsNotExist error, got %v", err) + } + + notExistFile := filepath.Join(tempDir, "not_exist_file.pub") + err = config.WritePubFile(notExistFile, make(map[string]string, 0)) + if err != nil { + t.Fatalf("expect no error, got %v", err) + } + _, err = os.Stat(notExistFile) + if err != nil && !os.IsNotExist(err) { + t.Fatalf("expect file %s, got error %v", notExistFile, err) + } +} diff --git a/cmd/gogensig/config/symb.go b/cmd/gogensig/config/symb.go new file mode 100644 index 0000000..2b2d1f1 --- /dev/null +++ b/cmd/gogensig/config/symb.go @@ -0,0 +1,59 @@ +package config + +import ( + "encoding/json" + "fmt" +) + +type MangleNameType = string + +type CppNameType = string + +type GoNameType = string + +type SymbolEntry struct { + MangleName MangleNameType `json:"mangle"` + CppName CppNameType `json:"c++"` + GoName GoNameType `json:"go"` +} + +type SymbolTable struct { + t map[MangleNameType]SymbolEntry +} + +// llcppg.symb.json +func NewSymbolTable(filePath string) (*SymbolTable, error) { + bytes, err := ReadFile(filePath) + if err != nil { + return nil, err + } + var symbs []SymbolEntry + err = json.Unmarshal(bytes, &symbs) + if err != nil { + return nil, err + } + return CreateSymbolTable(symbs), nil +} +func CreateSymbolTable(symbs []SymbolEntry) *SymbolTable { + symbolTable := &SymbolTable{ + t: make(map[MangleNameType]SymbolEntry), + } + for _, symb := range symbs { + symbolTable.t[symb.MangleName] = symb + } + return symbolTable +} + +func (t *SymbolTable) LookupSymbol(name MangleNameType) (*SymbolEntry, error) { + if t == nil || t.t == nil { + return nil, fmt.Errorf("symbol table not initialized") + } + if len(name) <= 0 { + return nil, fmt.Errorf("symbol not found") + } + symbol, ok := t.t[name] + if ok { + return &symbol, nil + } + return nil, fmt.Errorf("symbol not found") +} diff --git a/cmd/gogensig/convert/_comment_test.go b/cmd/gogensig/convert/_comment_test.go new file mode 100644 index 0000000..e42f586 --- /dev/null +++ b/cmd/gogensig/convert/_comment_test.go @@ -0,0 +1,120 @@ +package convert_test + +import ( + "testing" + + "github.com/goplus/llgo/chore/gogensig/cmptest" + "github.com/goplus/llgo/chore/gogensig/config" + cppgtypes "github.com/goplus/llgo/chore/llcppg/types" +) + +// TODO + +func TestCommentSlashStarStar(t *testing.T) { + cmptest.RunTest(t, "typeref", false, []config.SymbolEntry{ + { + MangleName: "ExecuteFoo", + CppName: "ExecuteFoo", + GoName: "CustomExecuteFoo", + }, + }, + &cppgtypes.Config{}, + ` +/** +Foo comment +*/ +struct Foo { int a; double b; bool c; }; +/** +ExecuteFoo comment +*/ +int ExecuteFoo(int a,Foo b); + `, ` +package typeref + +import "github.com/goplus/llgo/c" +/** +Foo comment +*/ +type Foo struct { + a c.Int + b float64 + c c.Int +} +/** +ExecuteFoo comment +*/ +//go:linkname CustomExecuteFoo C.ExecuteFoo +func CustomExecuteFoo(a c.Int, b Foo) c.Int + `) +} + +func TestCommentSlashStar(t *testing.T) { + cmptest.RunTest(t, "typeref", false, []symb.SymbolEntry{ + { + MangleName: "ExecuteFoo", + CppName: "ExecuteFoo", + GoName: "CustomExecuteFoo", + }, + }, + &cppgtypes.Config{}, + ` +/* +Foo comment +*/ +struct Foo { int a; double b; bool c; }; +/* +ExecuteFoo comment +*/ +int ExecuteFoo(int a,Foo b); + `, ` +package typeref + +import "github.com/goplus/llgo/c" +/* +Foo comment +*/ +type Foo struct { + a c.Int + b float64 + c c.Int +} +/* +ExecuteFoo comment +*/ +//go:linkname CustomExecuteFoo C.ExecuteFoo +func CustomExecuteFoo(a c.Int, b Foo) c.Int + `) +} + +func TestCommentSlashSlash(t *testing.T) { + cmptest.RunTest(t, "typeref", false, []symb.SymbolEntry{ + { + MangleName: "ExecuteFoo", + CppName: "ExecuteFoo", + GoName: "CustomExecuteFoo", + }, + }, + &cppgtypes.Config{}, + ` +// Foo comment +struct Foo { int a; double b; bool c; }; + +// ExecuteFoo comment +int ExecuteFoo(int a,Foo b); + `, ` +package typeref + +import "github.com/goplus/llgo/c" + +// Foo comment +type Foo struct { + a c.Int + b float64 + c c.Int +} + +// ExecuteFoo comment +//go:linkname CustomExecuteFoo C.ExecuteFoo +func CustomExecuteFoo(a c.Int, b Foo) c.Int + `) +} diff --git a/cmd/gogensig/convert/_testdata/_depcjson/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/_depcjson/conf/llcppg.cfg new file mode 100644 index 0000000..4861813 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/_depcjson/conf/llcppg.cfg @@ -0,0 +1,7 @@ +{ + "name": "depcjson", + "include": ["temp.h"], + "cflags" :"$(pkg-config --cflags libcjson)", + "cplusplus":false, + "deps": ["github.com/goplus/llgo/chore/gogensig/convert/testdata/cjson"] +} diff --git a/cmd/gogensig/convert/_testdata/_depcjson/conf/llcppg.symb.json b/cmd/gogensig/convert/_testdata/_depcjson/conf/llcppg.symb.json new file mode 100644 index 0000000..8642ee2 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/_depcjson/conf/llcppg.symb.json @@ -0,0 +1,18 @@ +[ + { + "mangle": "create_response", + "c++": "create_response", + "go": "CreateResponse" + }, + { + "mangle": "parse_client_request", + "c++": "parse_client_request", + "go": "ParseClientRequest" + }, + { + "mangle": "serialize_response", + "c++": "serialize_response", + "go": "SerializeResponse" + } + ] + \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/_depcjson/gogensig.expect b/cmd/gogensig/convert/_testdata/_depcjson/gogensig.expect new file mode 100644 index 0000000..6d110fa --- /dev/null +++ b/cmd/gogensig/convert/_testdata/_depcjson/gogensig.expect @@ -0,0 +1,13 @@ +===== temp.go ===== +package _depcjson + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/chore/gogensig/convert/testdata/cjson" + _ "unsafe" +) +//go:linkname CreateResponse C.create_response +func CreateResponse(status_code c.Int, message *int8) *cjson.CJSON +//go:linkname SerializeResponse C.serialize_response +func SerializeResponse(response *cjson.CJSON, buffer *int8, length c.Int, pretty_print cjson.CJSONBool) cjson.CJSONBool + diff --git a/cmd/gogensig/convert/_testdata/_depcjson/hfile/temp.h b/cmd/gogensig/convert/_testdata/_depcjson/hfile/temp.h new file mode 100644 index 0000000..767ed23 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/_depcjson/hfile/temp.h @@ -0,0 +1,10 @@ +#include +// This file is supposed to depend on cjson in its cflags, but for testing, +// we will simulate its API using libcjson instead. +// "cflags" :"$(pkg-config --cflags libcjson)" + +cJSON* create_response(int status_code, const char* message); + +cJSON_bool parse_client_request(const char* json_string, char* error_buffer, size_t buffer_size); + +cJSON_bool serialize_response(cJSON *response, char *buffer, const int length, const cJSON_bool pretty_print); diff --git a/cmd/gogensig/convert/_testdata/_systopkg/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/_systopkg/conf/llcppg.cfg new file mode 100644 index 0000000..7d9cbe7 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/_systopkg/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "avoidkeyword", + "include": ["temp.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/_systopkg/conf/llcppg.symb.json b/cmd/gogensig/convert/_testdata/_systopkg/conf/llcppg.symb.json new file mode 100644 index 0000000..af86ad3 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/_systopkg/conf/llcppg.symb.json @@ -0,0 +1,7 @@ +[ + { + "mangle": "funcc", + "c++": "funcc", + "go": "Funcc" + } +] diff --git a/cmd/gogensig/convert/_testdata/_systopkg/gogensig.expect b/cmd/gogensig/convert/_testdata/_systopkg/gogensig.expect new file mode 100644 index 0000000..f48620e --- /dev/null +++ b/cmd/gogensig/convert/_testdata/_systopkg/gogensig.expect @@ -0,0 +1,27 @@ +===== temp.go ===== +package _systopkg + +import _ "unsafe" + + + + + + + + + + + + + + + +===== llcppg.pub ===== +fenv Fenv +stddef Stddef +stdint Stdint +stdio Stdio +time Time +uchar Uchar +wchar Wchar \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/_systopkg/hfile/temp.h b/cmd/gogensig/convert/_testdata/_systopkg/hfile/temp.h new file mode 100644 index 0000000..d87d82f --- /dev/null +++ b/cmd/gogensig/convert/_testdata/_systopkg/hfile/temp.h @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include mac not supported +#include +#include +#include +#include + + +// https://en.cppreference.com/w/c/header +struct fenv{ + fenv_t t1; + fexcept_t t2; +}; + +struct stddef{ + size_t t1; + ptrdiff_t t2; + nullptr_t t3; + max_align_t t4; +}; + +struct stdint { + int8_t t1; + int16_t t2; + int32_t t3; + int64_t t4; + int_fast8_t t5; + int_fast16_t t6; + int_fast32_t t7; + int_fast64_t t8; + int_least8_t t9; + int_least16_t t10; + int_least32_t t11; + int_least64_t t12; + intmax_t t13; + intptr_t t14; + uint8_t t15; + uint16_t t16; + uint32_t t17; + uint64_t t18; + uint_fast8_t t19; + uint_fast16_t t20; + uint_fast32_t t21; + uint_fast64_t t22; + uint_least8_t t23; + uint_least16_t t24; + uint_least32_t t25; + uint_least64_t t26; + uintmax_t t27; + uintptr_t t28; +}; + +struct stdio +{ + FILE *t1; + fpos_t t2; +}; + +struct time{ + tm t1; + time_t t2; + clock_t t3; + timespec t4; +}; + +struct uchar{ + mbstate_t t1; + char16_t t2; + char32_t t3; +}; + +struct wchar{ + wint_t t2; + wctrans_t t3; + wctype_t t4; +}; \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/avoidkeyword/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/avoidkeyword/conf/llcppg.cfg new file mode 100644 index 0000000..7d9cbe7 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/avoidkeyword/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "avoidkeyword", + "include": ["temp.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/avoidkeyword/conf/llcppg.symb.json b/cmd/gogensig/convert/_testdata/avoidkeyword/conf/llcppg.symb.json new file mode 100644 index 0000000..605c844 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/avoidkeyword/conf/llcppg.symb.json @@ -0,0 +1,7 @@ +[ + { + "mangle": "lua_sethook", + "c++": "lua_sethook", + "go": "Sethook" + } +] diff --git a/cmd/gogensig/convert/_testdata/avoidkeyword/gogensig.expect b/cmd/gogensig/convert/_testdata/avoidkeyword/gogensig.expect new file mode 100644 index 0000000..951bbca --- /dev/null +++ b/cmd/gogensig/convert/_testdata/avoidkeyword/gogensig.expect @@ -0,0 +1,19 @@ +===== temp.go ===== +package avoidkeyword + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type LuaState struct { + Unused [8]uint8 +} +// llgo:type C +type LuaHook func(*LuaState) +//go:linkname Sethook C.lua_sethook +func Sethook(L *LuaState, func_ LuaHook, mask c.Int, count c.Int) + +===== llcppg.pub ===== +lua_Hook LuaHook +lua_State LuaState \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/avoidkeyword/hfile/temp.h b/cmd/gogensig/convert/_testdata/avoidkeyword/hfile/temp.h new file mode 100644 index 0000000..0ed688d --- /dev/null +++ b/cmd/gogensig/convert/_testdata/avoidkeyword/hfile/temp.h @@ -0,0 +1,3 @@ +typedef struct lua_State lua_State; +typedef void (*lua_Hook)(lua_State *L); +void(lua_sethook)(lua_State *L, lua_Hook func, int mask, int count); \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/cjsontestgen/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/cjsontestgen/conf/llcppg.cfg new file mode 100644 index 0000000..b67967e --- /dev/null +++ b/cmd/gogensig/convert/_testdata/cjsontestgen/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "cjsontestgen", + "include": ["cJSON.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/cjsontestgen/gogensig.expect b/cmd/gogensig/convert/_testdata/cjsontestgen/gogensig.expect new file mode 100644 index 0000000..dbbc357 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/cjsontestgen/gogensig.expect @@ -0,0 +1,29 @@ +===== cJSON.go ===== +package cjsontestgen + +import ( + "github.com/goplus/llgo/c" + "unsafe" +) + +type CJSON struct { + Next *CJSON + Prev *CJSON + Child *CJSON + Type c.Int + Valuestring *int8 + Valueint c.Int + Valuedouble float64 + String *int8 +} + +type CJSONHooks struct { + MallocFn func(c.SizeT) unsafe.Pointer + FreeFn func(unsafe.Pointer) +} +type CJSONBool c.Int + +===== llcppg.pub ===== +cJSON CJSON +cJSON_Hooks CJSONHooks +cJSON_bool CJSONBool \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/cjsontestgen/hfile/cJSON.h b/cmd/gogensig/convert/_testdata/cjsontestgen/hfile/cJSON.h new file mode 100644 index 0000000..48c7a95 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/cjsontestgen/hfile/cJSON.h @@ -0,0 +1,31 @@ +#include + +typedef struct cJSON { + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks { + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the + * hooks allow passing those functions directly. */ + void *( *malloc_fn)(size_t sz); + void(*free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/enum/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/enum/conf/llcppg.cfg new file mode 100644 index 0000000..65090d4 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/enum/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "enum", + "include": ["temp.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/enum/gogensig.expect b/cmd/gogensig/convert/_testdata/enum/gogensig.expect new file mode 100644 index 0000000..2f25775 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/enum/gogensig.expect @@ -0,0 +1,56 @@ +===== temp.go ===== +package enum + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +const ( + Enum1 c.Int = 0 + Enum2 c.Int = 1 +) + +type Spectrum c.Int + +const ( + SpectrumRed Spectrum = 0 + SpectrumOrange Spectrum = 1 + SpectrumYello Spectrum = 2 + SpectrumGreen Spectrum = 3 + SpectrumBlue Spectrum = 4 + SpectrumViolet Spectrum = 5 +) + +type Kids c.Int + +const ( + KidsNippy Kids = 0 + KidsSlats Kids = 1 + KidsSkippy Kids = 2 + KidsNina Kids = 3 + KidsLiz Kids = 4 +) + +type Levels c.Int + +const ( + LevelsLow Levels = 100 + LevelsMedium Levels = 500 + LevelsHigh Levels = 2000 +) + +type Feline c.Int + +const ( + FelineCat Feline = 0 + FelineLynx Feline = 10 + FelinePuma Feline = 11 + FelineTiger Feline = 12 +) + +===== llcppg.pub ===== +feline Feline +kids Kids +levels Levels +spectrum Spectrum \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/enum/hfile/temp.h b/cmd/gogensig/convert/_testdata/enum/hfile/temp.h new file mode 100644 index 0000000..0936217 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/enum/hfile/temp.h @@ -0,0 +1,8 @@ +enum { enum1, enum2 }; +enum spectrum { red, orange, yello, green, blue, violet }; + +enum kids { nippy, slats, skippy, nina, liz }; + +enum levels { low = 100, medium = 500, high = 2000 }; + +enum feline { cat, lynx = 10, puma, tiger }; diff --git a/cmd/gogensig/convert/_testdata/forwarddecl/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/forwarddecl/conf/llcppg.cfg new file mode 100644 index 0000000..0e8538b --- /dev/null +++ b/cmd/gogensig/convert/_testdata/forwarddecl/conf/llcppg.cfg @@ -0,0 +1,6 @@ +{ + "name": "forwarddecl", + "include": ["temp.h"], + "trimPrefixes": ["sqlite3_"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/forwarddecl/gogensig.expect b/cmd/gogensig/convert/_testdata/forwarddecl/gogensig.expect new file mode 100644 index 0000000..d8187e1 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/forwarddecl/gogensig.expect @@ -0,0 +1,37 @@ +===== temp.go ===== +package forwarddecl + +import ( + "github.com/goplus/llgo/c" + "unsafe" +) + +type PcachePage struct { + PBuf unsafe.Pointer + PExtra unsafe.Pointer +} + +type Pcache struct { + Unused [8]uint8 +} + +type PcacheMethods2 struct { + IVersion c.Int + PArg unsafe.Pointer + XInit func(unsafe.Pointer) c.Int + XShutdown func(unsafe.Pointer) + XCreate func(c.Int, c.Int, c.Int) *Pcache + XCachesize func(*Pcache, c.Int) + XPagecount func(*Pcache) c.Int + XFetch func(*Pcache, c.Uint, c.Int) *PcachePage + XUnpin func(*Pcache, *PcachePage, c.Int) + XRekey func(*Pcache, *PcachePage, c.Uint, c.Uint) + XTruncate func(*Pcache, c.Uint) + XDestroy func(*Pcache) + XShrink func(*Pcache) +} + +===== llcppg.pub ===== +sqlite3_pcache Pcache +sqlite3_pcache_methods2 PcacheMethods2 +sqlite3_pcache_page PcachePage \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/forwarddecl/hfile/temp.h b/cmd/gogensig/convert/_testdata/forwarddecl/hfile/temp.h new file mode 100644 index 0000000..63825fa --- /dev/null +++ b/cmd/gogensig/convert/_testdata/forwarddecl/hfile/temp.h @@ -0,0 +1,24 @@ +typedef struct sqlite3_pcache_page sqlite3_pcache_page; +struct sqlite3_pcache_page { + void *pBuf; + void *pExtra; +}; + +typedef struct sqlite3_pcache sqlite3_pcache; + +typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2; +struct sqlite3_pcache_methods2 { + int iVersion; + void *pArg; + int (*xInit)(void *); + void (*xShutdown)(void *); + sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable); + void (*xCachesize)(sqlite3_pcache *, int nCachesize); + int (*xPagecount)(sqlite3_pcache *); + sqlite3_pcache_page *(*xFetch)(sqlite3_pcache *, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache *, sqlite3_pcache_page *, int discard); + void (*xRekey)(sqlite3_pcache *, sqlite3_pcache_page *, unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache *, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache *); + void (*xShrink)(sqlite3_pcache *); +}; \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/keepcomment/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/keepcomment/conf/llcppg.cfg new file mode 100644 index 0000000..e9eddec --- /dev/null +++ b/cmd/gogensig/convert/_testdata/keepcomment/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "comment", + "include": ["temp.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/keepcomment/conf/llcppg.symb.json b/cmd/gogensig/convert/_testdata/keepcomment/conf/llcppg.symb.json new file mode 100644 index 0000000..f80f834 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/keepcomment/conf/llcppg.symb.json @@ -0,0 +1,7 @@ +[ + { + "mangle": "ExecuteFoo", + "c++": "ExecuteFoo", + "go": "CustomExecuteFoo" + } +] diff --git a/cmd/gogensig/convert/_testdata/keepcomment/gogensig.expect b/cmd/gogensig/convert/_testdata/keepcomment/gogensig.expect new file mode 100644 index 0000000..4253763 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/keepcomment/gogensig.expect @@ -0,0 +1,19 @@ +===== temp.go ===== +package keepcomment + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) +/// Foo comment +type Foo struct { + A c.Int + B float64 + C c.Int +} +/// ExecuteFoo comment +//go:linkname CustomExecuteFoo C.ExecuteFoo +func CustomExecuteFoo(a c.Int, b Foo) c.Int + +===== llcppg.pub ===== +Foo \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/keepcomment/hfile/temp.h b/cmd/gogensig/convert/_testdata/keepcomment/hfile/temp.h new file mode 100644 index 0000000..86cb80e --- /dev/null +++ b/cmd/gogensig/convert/_testdata/keepcomment/hfile/temp.h @@ -0,0 +1,9 @@ +/// Foo comment +struct Foo { + int a; + double b; + bool c; +}; + +/// ExecuteFoo comment +int ExecuteFoo(int a, Foo b); \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/lua/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/lua/conf/llcppg.cfg new file mode 100644 index 0000000..5302242 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/lua/conf/llcppg.cfg @@ -0,0 +1,7 @@ +{ + "name": "lua", + "include": ["lua.h","luaconf.h"], + "trimPrefixes": ["lua_","lua"], + "libs":"$(pkg-config --libs lua)", + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/lua/conf/llcppg.symb.json b/cmd/gogensig/convert/_testdata/lua/conf/llcppg.symb.json new file mode 100644 index 0000000..9c9e335 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/lua/conf/llcppg.symb.json @@ -0,0 +1,492 @@ +[ + { + "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": "Callk" + }, + { + "mangle": "lua_checkstack", + "c++": "lua_checkstack(lua_State *, int)", + "go": "Checkstack" + }, + { + "mangle": "lua_close", + "c++": "lua_close(lua_State *)", + "go": "Close" + }, + { + "mangle": "lua_closeslot", + "c++": "lua_closeslot(lua_State *, int)", + "go": "Closeslot" + }, + { + "mangle": "lua_closethread", + "c++": "lua_closethread(lua_State *, lua_State *)", + "go": "Closethread" + }, + { + "mangle": "lua_compare", + "c++": "lua_compare(lua_State *, int, int, int)", + "go": "Compare" + }, + { + "mangle": "lua_concat", + "c++": "lua_concat(lua_State *, int)", + "go": "Concat" + }, + { + "mangle": "lua_copy", + "c++": "lua_copy(lua_State *, int, int)", + "go": "Copy" + }, + { + "mangle": "lua_createtable", + "c++": "lua_createtable(lua_State *, int, int)", + "go": "Createtable" + }, + { + "mangle": "lua_dump", + "c++": "lua_dump(lua_State *, lua_Writer, void *, int)", + "go": "Dump" + }, + { + "mangle": "lua_error", + "c++": "lua_error(lua_State *)", + "go": "Error" + }, + { + "mangle": "lua_gc", + "c++": "lua_gc(lua_State *, int, ...)", + "go": "Gc" + }, + { + "mangle": "lua_getallocf", + "c++": "lua_getallocf(lua_State *, void **)", + "go": "Getallocf" + }, + { + "mangle": "lua_getfield", + "c++": "lua_getfield(lua_State *, int, const char *)", + "go": "Getfield" + }, + { + "mangle": "lua_getglobal", + "c++": "lua_getglobal(lua_State *, const char *)", + "go": "Getglobal" + }, + { + "mangle": "lua_gethook", + "c++": "lua_gethook(lua_State *)", + "go": "Gethook" + }, + { + "mangle": "lua_gethookcount", + "c++": "lua_gethookcount(lua_State *)", + "go": "Gethookcount" + }, + { + "mangle": "lua_gethookmask", + "c++": "lua_gethookmask(lua_State *)", + "go": "Gethookmask" + }, + { + "mangle": "lua_geti", + "c++": "lua_geti(lua_State *, int, lua_Integer)", + "go": "Geti" + }, + { + "mangle": "lua_getinfo", + "c++": "lua_getinfo(lua_State *, const char *, lua_Debug *)", + "go": "Getinfo" + }, + { + "mangle": "lua_getiuservalue", + "c++": "lua_getiuservalue(lua_State *, int, int)", + "go": "Getiuservalue" + }, + { + "mangle": "lua_getlocal", + "c++": "lua_getlocal(lua_State *, const lua_Debug *, int)", + "go": "Getlocal" + }, + { + "mangle": "lua_getmetatable", + "c++": "lua_getmetatable(lua_State *, int)", + "go": "Getmetatable" + }, + { + "mangle": "lua_getstack", + "c++": "lua_getstack(lua_State *, int, lua_Debug *)", + "go": "Getstack" + }, + { + "mangle": "lua_gettable", + "c++": "lua_gettable(lua_State *, int)", + "go": "Gettable" + }, + { + "mangle": "lua_gettop", + "c++": "lua_gettop(lua_State *)", + "go": "Gettop" + }, + { + "mangle": "lua_getupvalue", + "c++": "lua_getupvalue(lua_State *, int, int)", + "go": "Getupvalue" + }, + { + "mangle": "lua_iscfunction", + "c++": "lua_iscfunction(lua_State *, int)", + "go": "Iscfunction" + }, + { + "mangle": "lua_isinteger", + "c++": "lua_isinteger(lua_State *, int)", + "go": "Isinteger" + }, + { + "mangle": "lua_isnumber", + "c++": "lua_isnumber(lua_State *, int)", + "go": "Isnumber" + }, + { + "mangle": "lua_isstring", + "c++": "lua_isstring(lua_State *, int)", + "go": "Isstring" + }, + { + "mangle": "lua_isuserdata", + "c++": "lua_isuserdata(lua_State *, int)", + "go": "Isuserdata" + }, + { + "mangle": "lua_isyieldable", + "c++": "lua_isyieldable(lua_State *)", + "go": "Isyieldable" + }, + { + "mangle": "lua_len", + "c++": "lua_len(lua_State *, int)", + "go": "Len" + }, + { + "mangle": "lua_load", + "c++": "lua_load(lua_State *, lua_Reader, void *, const char *, const char *)", + "go": "Load" + }, + { + "mangle": "lua_newstate", + "c++": "lua_newstate(lua_Alloc, void *)", + "go": "Newstate" + }, + { + "mangle": "lua_newthread", + "c++": "lua_newthread(lua_State *)", + "go": "Newthread" + }, + { + "mangle": "lua_newuserdatauv", + "c++": "lua_newuserdatauv(lua_State *, size_t, int)", + "go": "Newuserdatauv" + }, + { + "mangle": "lua_next", + "c++": "lua_next(lua_State *, int)", + "go": "Next" + }, + { + "mangle": "lua_pcallk", + "c++": "lua_pcallk(lua_State *, int, int, int, lua_KContext, lua_KFunction)", + "go": "Pcallk" + }, + { + "mangle": "lua_pushboolean", + "c++": "lua_pushboolean(lua_State *, int)", + "go": "Pushboolean" + }, + { + "mangle": "lua_pushcclosure", + "c++": "lua_pushcclosure(lua_State *, lua_CFunction, int)", + "go": "Pushcclosure" + }, + { + "mangle": "lua_pushfstring", + "c++": "lua_pushfstring(lua_State *, const char *, ...)", + "go": "Pushfstring" + }, + { + "mangle": "lua_pushinteger", + "c++": "lua_pushinteger(lua_State *, lua_Integer)", + "go": "Pushinteger" + }, + { + "mangle": "lua_pushlightuserdata", + "c++": "lua_pushlightuserdata(lua_State *, void *)", + "go": "Pushlightuserdata" + }, + { + "mangle": "lua_pushlstring", + "c++": "lua_pushlstring(lua_State *, const char *, size_t)", + "go": "Pushlstring" + }, + { + "mangle": "lua_pushnil", + "c++": "lua_pushnil(lua_State *)", + "go": "Pushnil" + }, + { + "mangle": "lua_pushnumber", + "c++": "lua_pushnumber(lua_State *, lua_Number)", + "go": "Pushnumber" + }, + { + "mangle": "lua_pushstring", + "c++": "lua_pushstring(lua_State *, const char *)", + "go": "Pushstring" + }, + { + "mangle": "lua_pushthread", + "c++": "lua_pushthread(lua_State *)", + "go": "Pushthread" + }, + { + "mangle": "lua_pushvalue", + "c++": "lua_pushvalue(lua_State *, int)", + "go": "Pushvalue" + }, + { + "mangle": "lua_pushvfstring", + "c++": "lua_pushvfstring(lua_State *, const char *, int)", + "go": "Pushvfstring" + }, + { + "mangle": "lua_rawequal", + "c++": "lua_rawequal(lua_State *, int, int)", + "go": "Rawequal" + }, + { + "mangle": "lua_rawget", + "c++": "lua_rawget(lua_State *, int)", + "go": "Rawget" + }, + { + "mangle": "lua_rawgeti", + "c++": "lua_rawgeti(lua_State *, int, lua_Integer)", + "go": "Rawgeti" + }, + { + "mangle": "lua_rawgetp", + "c++": "lua_rawgetp(lua_State *, int, const void *)", + "go": "Rawgetp" + }, + { + "mangle": "lua_rawlen", + "c++": "lua_rawlen(lua_State *, int)", + "go": "Rawlen" + }, + { + "mangle": "lua_rawset", + "c++": "lua_rawset(lua_State *, int)", + "go": "Rawset" + }, + { + "mangle": "lua_rawseti", + "c++": "lua_rawseti(lua_State *, int, lua_Integer)", + "go": "Rawseti" + }, + { + "mangle": "lua_rawsetp", + "c++": "lua_rawsetp(lua_State *, int, const void *)", + "go": "Rawsetp" + }, + { + "mangle": "lua_resetthread", + "c++": "lua_resetthread(lua_State *)", + "go": "Resetthread" + }, + { + "mangle": "lua_resume", + "c++": "lua_resume(lua_State *, lua_State *, int, int *)", + "go": "Resume" + }, + { + "mangle": "lua_rotate", + "c++": "lua_rotate(lua_State *, int, int)", + "go": "Rotate" + }, + { + "mangle": "lua_setallocf", + "c++": "lua_setallocf(lua_State *, lua_Alloc, void *)", + "go": "Setallocf" + }, + { + "mangle": "lua_setcstacklimit", + "c++": "lua_setcstacklimit(lua_State *, unsigned int)", + "go": "Setcstacklimit" + }, + { + "mangle": "lua_setfield", + "c++": "lua_setfield(lua_State *, int, const char *)", + "go": "Setfield" + }, + { + "mangle": "lua_setglobal", + "c++": "lua_setglobal(lua_State *, const char *)", + "go": "Setglobal" + }, + { + "mangle": "lua_sethook", + "c++": "lua_sethook(lua_State *, lua_Hook, int, int)", + "go": "Sethook" + }, + { + "mangle": "lua_seti", + "c++": "lua_seti(lua_State *, int, lua_Integer)", + "go": "Seti" + }, + { + "mangle": "lua_setiuservalue", + "c++": "lua_setiuservalue(lua_State *, int, int)", + "go": "Setiuservalue" + }, + { + "mangle": "lua_setlocal", + "c++": "lua_setlocal(lua_State *, const lua_Debug *, int)", + "go": "Setlocal" + }, + { + "mangle": "lua_setmetatable", + "c++": "lua_setmetatable(lua_State *, int)", + "go": "Setmetatable" + }, + { + "mangle": "lua_settable", + "c++": "lua_settable(lua_State *, int)", + "go": "Settable" + }, + { + "mangle": "lua_settop", + "c++": "lua_settop(lua_State *, int)", + "go": "Settop" + }, + { + "mangle": "lua_setupvalue", + "c++": "lua_setupvalue(lua_State *, int, int)", + "go": "Setupvalue" + }, + { + "mangle": "lua_setwarnf", + "c++": "lua_setwarnf(lua_State *, lua_WarnFunction, void *)", + "go": "Setwarnf" + }, + { + "mangle": "lua_status", + "c++": "lua_status(lua_State *)", + "go": "Status" + }, + { + "mangle": "lua_stringtonumber", + "c++": "lua_stringtonumber(lua_State *, const char *)", + "go": "Stringtonumber" + }, + { + "mangle": "lua_toboolean", + "c++": "lua_toboolean(lua_State *, int)", + "go": "Toboolean" + }, + { + "mangle": "lua_tocfunction", + "c++": "lua_tocfunction(lua_State *, int)", + "go": "Tocfunction" + }, + { + "mangle": "lua_toclose", + "c++": "lua_toclose(lua_State *, int)", + "go": "Toclose" + }, + { + "mangle": "lua_tointegerx", + "c++": "lua_tointegerx(lua_State *, int, int *)", + "go": "Tointegerx" + }, + { + "mangle": "lua_tolstring", + "c++": "lua_tolstring(lua_State *, int, size_t *)", + "go": "Tolstring" + }, + { + "mangle": "lua_tonumberx", + "c++": "lua_tonumberx(lua_State *, int, int *)", + "go": "Tonumberx" + }, + { + "mangle": "lua_topointer", + "c++": "lua_topointer(lua_State *, int)", + "go": "Topointer" + }, + { + "mangle": "lua_tothread", + "c++": "lua_tothread(lua_State *, int)", + "go": "Tothread" + }, + { + "mangle": "lua_touserdata", + "c++": "lua_touserdata(lua_State *, int)", + "go": "Touserdata" + }, + { + "mangle": "lua_type", + "c++": "lua_type(lua_State *, int)", + "go": "Type" + }, + { + "mangle": "lua_typename", + "c++": "lua_typename(lua_State *, int)", + "go": "Typename" + }, + { + "mangle": "lua_upvalueid", + "c++": "lua_upvalueid(lua_State *, int, int)", + "go": "Upvalueid" + }, + { + "mangle": "lua_upvaluejoin", + "c++": "lua_upvaluejoin(lua_State *, int, int, int, int)", + "go": "Upvaluejoin" + }, + { + "mangle": "lua_version", + "c++": "lua_version(lua_State *)", + "go": "Version" + }, + { + "mangle": "lua_warning", + "c++": "lua_warning(lua_State *, const char *, int)", + "go": "Warning" + }, + { + "mangle": "lua_xmove", + "c++": "lua_xmove(lua_State *, lua_State *, int)", + "go": "Xmove" + }, + { + "mangle": "lua_yieldk", + "c++": "lua_yieldk(lua_State *, int, lua_KContext, lua_KFunction)", + "go": "Yieldk" + } +] diff --git a/cmd/gogensig/convert/_testdata/lua/gogensig.expect b/cmd/gogensig/convert/_testdata/lua/gogensig.expect new file mode 100644 index 0000000..983f8f4 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/lua/gogensig.expect @@ -0,0 +1,267 @@ +===== lua.go ===== +package lua + +import ( + "github.com/goplus/llgo/c" + "unsafe" +) + +type State struct { + Unused [8]uint8 +} +type Number float64 +type Integer c.LongLong +type Unsigned c.UlongLong +type KContext c.IntptrT +// llgo:type C +type CFunction func(*State) c.Int +// llgo:type C +type KFunction func(*State, c.Int, KContext) c.Int +// llgo:type C +type Reader func(*State, unsafe.Pointer, *c.SizeT) *int8 +// llgo:type C +type Writer func(*State, unsafe.Pointer, c.SizeT, unsafe.Pointer) c.Int +// llgo:type C +type Alloc func(unsafe.Pointer, unsafe.Pointer, c.SizeT, c.SizeT) unsafe.Pointer +// llgo:type C +type WarnFunction func(unsafe.Pointer, *int8, c.Int) + +type Debug struct { + Event c.Int + Name *int8 + Namewhat *int8 + What *int8 + Source *int8 + Srclen c.SizeT + Currentline c.Int + Linedefined c.Int + Lastlinedefined c.Int + Nups int8 + Nparams int8 + Isvararg int8 + Istailcall int8 + Ftransfer uint16 + Ntransfer uint16 + ShortSrc [60]int8 +} +// llgo:type C +type Hook func(*State, *Debug) +//go:linkname Newstate C.lua_newstate +func Newstate(f Alloc, ud unsafe.Pointer) *State +//go:linkname Close C.lua_close +func Close(L *State) +//go:linkname Newthread C.lua_newthread +func Newthread(L *State) *State +//go:linkname Closethread C.lua_closethread +func Closethread(L *State, from *State) c.Int +//go:linkname Resetthread C.lua_resetthread +func Resetthread(L *State) c.Int +//go:linkname Atpanic C.lua_atpanic +func Atpanic(L *State, panicf CFunction) CFunction +//go:linkname Version C.lua_version +func Version(L *State) Number +//go:linkname Absindex C.lua_absindex +func Absindex(L *State, idx c.Int) c.Int +//go:linkname Gettop C.lua_gettop +func Gettop(L *State) c.Int +//go:linkname Settop C.lua_settop +func Settop(L *State, idx c.Int) +//go:linkname Pushvalue C.lua_pushvalue +func Pushvalue(L *State, idx c.Int) +//go:linkname Rotate C.lua_rotate +func Rotate(L *State, idx c.Int, n c.Int) +//go:linkname Copy C.lua_copy +func Copy(L *State, fromidx c.Int, toidx c.Int) +//go:linkname Checkstack C.lua_checkstack +func Checkstack(L *State, n c.Int) c.Int +//go:linkname Xmove C.lua_xmove +func Xmove(from *State, to *State, n c.Int) +//go:linkname Isnumber C.lua_isnumber +func Isnumber(L *State, idx c.Int) c.Int +//go:linkname Isstring C.lua_isstring +func Isstring(L *State, idx c.Int) c.Int +//go:linkname Iscfunction C.lua_iscfunction +func Iscfunction(L *State, idx c.Int) c.Int +//go:linkname Isinteger C.lua_isinteger +func Isinteger(L *State, idx c.Int) c.Int +//go:linkname Isuserdata C.lua_isuserdata +func Isuserdata(L *State, idx c.Int) c.Int +//go:linkname Type C.lua_type +func Type(L *State, idx c.Int) c.Int +//go:linkname Typename C.lua_typename +func Typename(L *State, tp c.Int) *int8 +//go:linkname Tonumberx C.lua_tonumberx +func Tonumberx(L *State, idx c.Int, isnum *c.Int) Number +//go:linkname Tointegerx C.lua_tointegerx +func Tointegerx(L *State, idx c.Int, isnum *c.Int) Integer +//go:linkname Toboolean C.lua_toboolean +func Toboolean(L *State, idx c.Int) c.Int +//go:linkname Tolstring C.lua_tolstring +func Tolstring(L *State, idx c.Int, len *c.SizeT) *int8 +//go:linkname Rawlen C.lua_rawlen +func Rawlen(L *State, idx c.Int) Unsigned +//go:linkname Tocfunction C.lua_tocfunction +func Tocfunction(L *State, idx c.Int) CFunction +//go:linkname Touserdata C.lua_touserdata +func Touserdata(L *State, idx c.Int) unsafe.Pointer +//go:linkname Tothread C.lua_tothread +func Tothread(L *State, idx c.Int) *State +//go:linkname Topointer C.lua_topointer +func Topointer(L *State, idx c.Int) unsafe.Pointer +//go:linkname Arith C.lua_arith +func Arith(L *State, op c.Int) +//go:linkname Rawequal C.lua_rawequal +func Rawequal(L *State, idx1 c.Int, idx2 c.Int) c.Int +//go:linkname Compare C.lua_compare +func Compare(L *State, idx1 c.Int, idx2 c.Int, op c.Int) c.Int +//go:linkname Pushnil C.lua_pushnil +func Pushnil(L *State) +//go:linkname Pushnumber C.lua_pushnumber +func Pushnumber(L *State, n Number) +//go:linkname Pushinteger C.lua_pushinteger +func Pushinteger(L *State, n Integer) +//go:linkname Pushlstring C.lua_pushlstring +func Pushlstring(L *State, s *int8, len c.SizeT) *int8 +//go:linkname Pushstring C.lua_pushstring +func Pushstring(L *State, s *int8) *int8 +//go:linkname Pushfstring C.lua_pushfstring +func Pushfstring(L *State, fmt *int8, __llgo_va_list ...interface{}) *int8 +//go:linkname Pushcclosure C.lua_pushcclosure +func Pushcclosure(L *State, fn CFunction, n c.Int) +//go:linkname Pushboolean C.lua_pushboolean +func Pushboolean(L *State, b c.Int) +//go:linkname Pushlightuserdata C.lua_pushlightuserdata +func Pushlightuserdata(L *State, p unsafe.Pointer) +//go:linkname Pushthread C.lua_pushthread +func Pushthread(L *State) c.Int +//go:linkname Getglobal C.lua_getglobal +func Getglobal(L *State, name *int8) c.Int +//go:linkname Gettable C.lua_gettable +func Gettable(L *State, idx c.Int) c.Int +//go:linkname Getfield C.lua_getfield +func Getfield(L *State, idx c.Int, k *int8) c.Int +//go:linkname Geti C.lua_geti +func Geti(L *State, idx c.Int, n Integer) c.Int +//go:linkname Rawget C.lua_rawget +func Rawget(L *State, idx c.Int) c.Int +//go:linkname Rawgeti C.lua_rawgeti +func Rawgeti(L *State, idx c.Int, n Integer) c.Int +//go:linkname Rawgetp C.lua_rawgetp +func Rawgetp(L *State, idx c.Int, p unsafe.Pointer) c.Int +//go:linkname Createtable C.lua_createtable +func Createtable(L *State, narr c.Int, nrec c.Int) +//go:linkname Newuserdatauv C.lua_newuserdatauv +func Newuserdatauv(L *State, sz c.SizeT, nuvalue c.Int) unsafe.Pointer +//go:linkname Getmetatable C.lua_getmetatable +func Getmetatable(L *State, objindex c.Int) c.Int +//go:linkname Getiuservalue C.lua_getiuservalue +func Getiuservalue(L *State, idx c.Int, n c.Int) c.Int +//go:linkname Setglobal C.lua_setglobal +func Setglobal(L *State, name *int8) +//go:linkname Settable C.lua_settable +func Settable(L *State, idx c.Int) +//go:linkname Setfield C.lua_setfield +func Setfield(L *State, idx c.Int, k *int8) +//go:linkname Seti C.lua_seti +func Seti(L *State, idx c.Int, n Integer) +//go:linkname Rawset C.lua_rawset +func Rawset(L *State, idx c.Int) +//go:linkname Rawseti C.lua_rawseti +func Rawseti(L *State, idx c.Int, n Integer) +//go:linkname Rawsetp C.lua_rawsetp +func Rawsetp(L *State, idx c.Int, p unsafe.Pointer) +//go:linkname Setmetatable C.lua_setmetatable +func Setmetatable(L *State, objindex c.Int) c.Int +//go:linkname Setiuservalue C.lua_setiuservalue +func Setiuservalue(L *State, idx c.Int, n c.Int) c.Int +//go:linkname Callk C.lua_callk +func Callk(L *State, nargs c.Int, nresults c.Int, ctx KContext, k KFunction) +//go:linkname Pcallk C.lua_pcallk +func Pcallk(L *State, nargs c.Int, nresults c.Int, errfunc c.Int, ctx KContext, k KFunction) c.Int +//go:linkname Load C.lua_load +func Load(L *State, reader Reader, dt unsafe.Pointer, chunkname *int8, mode *int8) c.Int +//go:linkname Dump C.lua_dump +func Dump(L *State, writer Writer, data unsafe.Pointer, strip c.Int) c.Int +//go:linkname Yieldk C.lua_yieldk +func Yieldk(L *State, nresults c.Int, ctx KContext, k KFunction) c.Int +//go:linkname Resume C.lua_resume +func Resume(L *State, from *State, narg c.Int, nres *c.Int) c.Int +//go:linkname Status C.lua_status +func Status(L *State) c.Int +//go:linkname Isyieldable C.lua_isyieldable +func Isyieldable(L *State) c.Int +//go:linkname Setwarnf C.lua_setwarnf +func Setwarnf(L *State, f WarnFunction, ud unsafe.Pointer) +//go:linkname Warning C.lua_warning +func Warning(L *State, msg *int8, tocont c.Int) +//go:linkname Gc C.lua_gc +func Gc(L *State, what c.Int, __llgo_va_list ...interface{}) c.Int +//go:linkname Error C.lua_error +func Error(L *State) c.Int +//go:linkname Next C.lua_next +func Next(L *State, idx c.Int) c.Int +//go:linkname Concat C.lua_concat +func Concat(L *State, n c.Int) +//go:linkname Len C.lua_len +func Len(L *State, idx c.Int) +//go:linkname Stringtonumber C.lua_stringtonumber +func Stringtonumber(L *State, s *int8) c.SizeT +//go:linkname Getallocf C.lua_getallocf +func Getallocf(L *State, ud *unsafe.Pointer) Alloc +//go:linkname Setallocf C.lua_setallocf +func Setallocf(L *State, f Alloc, ud unsafe.Pointer) +//go:linkname Toclose C.lua_toclose +func Toclose(L *State, idx c.Int) +//go:linkname Closeslot C.lua_closeslot +func Closeslot(L *State, idx c.Int) +//go:linkname Getstack C.lua_getstack +func Getstack(L *State, level c.Int, ar *Debug) c.Int +//go:linkname Getinfo C.lua_getinfo +func Getinfo(L *State, what *int8, ar *Debug) c.Int +//go:linkname Getlocal C.lua_getlocal +func Getlocal(L *State, ar *Debug, n c.Int) *int8 +//go:linkname Setlocal C.lua_setlocal +func Setlocal(L *State, ar *Debug, n c.Int) *int8 +//go:linkname Getupvalue C.lua_getupvalue +func Getupvalue(L *State, funcindex c.Int, n c.Int) *int8 +//go:linkname Setupvalue C.lua_setupvalue +func Setupvalue(L *State, funcindex c.Int, n c.Int) *int8 +//go:linkname Upvalueid C.lua_upvalueid +func Upvalueid(L *State, fidx c.Int, n c.Int) unsafe.Pointer +//go:linkname Upvaluejoin C.lua_upvaluejoin +func Upvaluejoin(L *State, fidx1 c.Int, n1 c.Int, fidx2 c.Int, n2 c.Int) +//go:linkname Sethook C.lua_sethook +func Sethook(L *State, func_ Hook, mask c.Int, count c.Int) +//go:linkname Gethook C.lua_gethook +func Gethook(L *State) Hook +//go:linkname Gethookmask C.lua_gethookmask +func Gethookmask(L *State) c.Int +//go:linkname Gethookcount C.lua_gethookcount +func Gethookcount(L *State) c.Int +//go:linkname Setcstacklimit C.lua_setcstacklimit +func Setcstacklimit(L *State, limit c.Uint) c.Int + +===== lua_autogen_link.go ===== +package lua + +const LLGoPackage string = "link: $(pkg-config --libs lua);" + +===== luaconf.go ===== +package lua + +import _ "unsafe" + +===== llcppg.pub ===== +lua_Alloc Alloc +lua_CFunction CFunction +lua_Debug Debug +lua_Hook Hook +lua_Integer Integer +lua_KContext KContext +lua_KFunction KFunction +lua_Number Number +lua_Reader Reader +lua_State State +lua_Unsigned Unsigned +lua_WarnFunction WarnFunction +lua_Writer Writer \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/lua/hfile/lua.h b/cmd/gogensig/convert/_testdata/lua/hfile/lua.h new file mode 100644 index 0000000..0af344b --- /dev/null +++ b/cmd/gogensig/convert/_testdata/lua/hfile/lua.h @@ -0,0 +1,451 @@ +/* +** $Id: lua.h $ +** Lua - A Scripting Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + +#ifndef lua_h +#define lua_h + +#include +#include + +#include "luaconf.h" + +#define LUA_VERSION_MAJOR "5" +#define LUA_VERSION_MINOR "4" +#define LUA_VERSION_RELEASE "7" + +#define LUA_VERSION_NUM 504 +#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 7) + +#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2024 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" + +/* mark for precompiled code ('Lua') */ +#define LUA_SIGNATURE "\x1bLua" + +/* option for multiple returns in 'lua_pcall' and 'lua_call' */ +#define LUA_MULTRET (-1) + +/* +** Pseudo-indices +** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty +** space after that to help overflow detection) +*/ +#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) +#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) + +/* thread status */ +#define LUA_OK 0 +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRERR 5 + +typedef struct lua_State lua_State; + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + +#define LUA_NUMTYPES 9 + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + +/* predefined values in the registry */ +#define LUA_RIDX_MAINTHREAD 1 +#define LUA_RIDX_GLOBALS 2 +#define LUA_RIDX_LAST LUA_RIDX_GLOBALS + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + +/* unsigned integer type */ +typedef LUA_UNSIGNED lua_Unsigned; + +/* type for continuation-function contexts */ +typedef LUA_KCONTEXT lua_KContext; + +/* +** Type for C functions registered with Lua +*/ +typedef int (*lua_CFunction)(lua_State *L); + +/* +** Type for continuation functions +*/ +typedef int (*lua_KFunction)(lua_State *L, int status, lua_KContext ctx); + +/* +** Type for functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char *(*lua_Reader)(lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer)(lua_State *L, const void *p, size_t sz, void *ud); + +/* +** Type for memory-allocation functions +*/ +typedef void *(*lua_Alloc)(void *ud, void *ptr, size_t osize, size_t nsize); + +/* +** Type for warning functions +*/ +typedef void (*lua_WarnFunction)(void *ud, const char *msg, int tocont); + +/* +** Type used by the debug API to collect debug information +*/ +typedef struct lua_Debug lua_Debug; + +/* +** Functions to be called by the debugger in specific events +*/ +typedef void (*lua_Hook)(lua_State *L, lua_Debug *ar); + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + +/* +** RCS ident string +*/ +extern const char lua_ident[]; + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate)(lua_Alloc f, void *ud); +LUA_API void(lua_close)(lua_State *L); +LUA_API lua_State *(lua_newthread)(lua_State *L); +LUA_API int(lua_closethread)(lua_State *L, lua_State *from); +LUA_API int(lua_resetthread)(lua_State *L); /* Deprecated! */ + +LUA_API lua_CFunction(lua_atpanic)(lua_State *L, lua_CFunction panicf); + +LUA_API lua_Number(lua_version)(lua_State *L); + +/* +** basic stack manipulation +*/ +LUA_API int(lua_absindex)(lua_State *L, int idx); +LUA_API int(lua_gettop)(lua_State *L); +LUA_API void(lua_settop)(lua_State *L, int idx); +LUA_API void(lua_pushvalue)(lua_State *L, int idx); +LUA_API void(lua_rotate)(lua_State *L, int idx, int n); +LUA_API void(lua_copy)(lua_State *L, int fromidx, int toidx); +LUA_API int(lua_checkstack)(lua_State *L, int n); + +LUA_API void(lua_xmove)(lua_State *from, lua_State *to, int n); + +/* +** access functions (stack -> C) +*/ + +LUA_API int(lua_isnumber)(lua_State *L, int idx); +LUA_API int(lua_isstring)(lua_State *L, int idx); +LUA_API int(lua_iscfunction)(lua_State *L, int idx); +LUA_API int(lua_isinteger)(lua_State *L, int idx); +LUA_API int(lua_isuserdata)(lua_State *L, int idx); +LUA_API int(lua_type)(lua_State *L, int idx); +LUA_API const char *(lua_typename)(lua_State *L, int tp); + +LUA_API lua_Number(lua_tonumberx)(lua_State *L, int idx, int *isnum); +LUA_API lua_Integer(lua_tointegerx)(lua_State *L, int idx, int *isnum); +LUA_API int(lua_toboolean)(lua_State *L, int idx); +LUA_API const char *(lua_tolstring)(lua_State *L, int idx, size_t *len); +LUA_API lua_Unsigned(lua_rawlen)(lua_State *L, int idx); +LUA_API lua_CFunction(lua_tocfunction)(lua_State *L, int idx); +LUA_API void *(lua_touserdata)(lua_State *L, int idx); +LUA_API lua_State *(lua_tothread)(lua_State *L, int idx); +LUA_API const void *(lua_topointer)(lua_State *L, int idx); + +/* +** Comparison and arithmetic functions +*/ + +#define LUA_OPADD 0 /* ORDER TM, ORDER OP */ +#define LUA_OPSUB 1 +#define LUA_OPMUL 2 +#define LUA_OPMOD 3 +#define LUA_OPPOW 4 +#define LUA_OPDIV 5 +#define LUA_OPIDIV 6 +#define LUA_OPBAND 7 +#define LUA_OPBOR 8 +#define LUA_OPBXOR 9 +#define LUA_OPSHL 10 +#define LUA_OPSHR 11 +#define LUA_OPUNM 12 +#define LUA_OPBNOT 13 + +LUA_API void(lua_arith)(lua_State *L, int op); + +#define LUA_OPEQ 0 +#define LUA_OPLT 1 +#define LUA_OPLE 2 + +LUA_API int(lua_rawequal)(lua_State *L, int idx1, int idx2); +LUA_API int(lua_compare)(lua_State *L, int idx1, int idx2, int op); + +/* +** push functions (C -> stack) +*/ +LUA_API void(lua_pushnil)(lua_State *L); +LUA_API void(lua_pushnumber)(lua_State *L, lua_Number n); +LUA_API void(lua_pushinteger)(lua_State *L, lua_Integer n); +LUA_API const char *(lua_pushlstring)(lua_State *L, const char *s, size_t len); +LUA_API const char *(lua_pushstring)(lua_State *L, const char *s); +// todo(zzy):on macos undef va_list will be a int,it is mistake +// LUA_API const char *(lua_pushvfstring)(lua_State *L, const char *fmt, va_list argp); +LUA_API const char *(lua_pushfstring)(lua_State *L, const char *fmt, ...); +LUA_API void(lua_pushcclosure)(lua_State *L, lua_CFunction fn, int n); +LUA_API void(lua_pushboolean)(lua_State *L, int b); +LUA_API void(lua_pushlightuserdata)(lua_State *L, void *p); +LUA_API int(lua_pushthread)(lua_State *L); + +/* +** get functions (Lua -> stack) +*/ +LUA_API int(lua_getglobal)(lua_State *L, const char *name); +LUA_API int(lua_gettable)(lua_State *L, int idx); +LUA_API int(lua_getfield)(lua_State *L, int idx, const char *k); +LUA_API int(lua_geti)(lua_State *L, int idx, lua_Integer n); +LUA_API int(lua_rawget)(lua_State *L, int idx); +LUA_API int(lua_rawgeti)(lua_State *L, int idx, lua_Integer n); +LUA_API int(lua_rawgetp)(lua_State *L, int idx, const void *p); + +LUA_API void(lua_createtable)(lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdatauv)(lua_State *L, size_t sz, int nuvalue); +LUA_API int(lua_getmetatable)(lua_State *L, int objindex); +LUA_API int(lua_getiuservalue)(lua_State *L, int idx, int n); + +/* +** set functions (stack -> Lua) +*/ +LUA_API void(lua_setglobal)(lua_State *L, const char *name); +LUA_API void(lua_settable)(lua_State *L, int idx); +LUA_API void(lua_setfield)(lua_State *L, int idx, const char *k); +LUA_API void(lua_seti)(lua_State *L, int idx, lua_Integer n); +LUA_API void(lua_rawset)(lua_State *L, int idx); +LUA_API void(lua_rawseti)(lua_State *L, int idx, lua_Integer n); +LUA_API void(lua_rawsetp)(lua_State *L, int idx, const void *p); +LUA_API int(lua_setmetatable)(lua_State *L, int objindex); +LUA_API int(lua_setiuservalue)(lua_State *L, int idx, int n); + +/* +** 'load' and 'call' functions (load and run Lua code) +*/ +LUA_API void(lua_callk)(lua_State *L, int nargs, int nresults, lua_KContext ctx, lua_KFunction k); +#define lua_call(L, n, r) lua_callk(L, (n), (r), 0, NULL) + +LUA_API int(lua_pcallk)(lua_State *L, int nargs, int nresults, int errfunc, lua_KContext ctx, lua_KFunction k); +#define lua_pcall(L, n, r, f) lua_pcallk(L, (n), (r), (f), 0, NULL) + +LUA_API int(lua_load)(lua_State *L, lua_Reader reader, void *dt, const char *chunkname, const char *mode); + +LUA_API int(lua_dump)(lua_State *L, lua_Writer writer, void *data, int strip); + +/* +** coroutine functions +*/ +LUA_API int(lua_yieldk)(lua_State *L, int nresults, lua_KContext ctx, lua_KFunction k); +LUA_API int(lua_resume)(lua_State *L, lua_State *from, int narg, int *nres); +LUA_API int(lua_status)(lua_State *L); +LUA_API int(lua_isyieldable)(lua_State *L); + +#define lua_yield(L, n) lua_yieldk(L, (n), 0, NULL) + +/* +** Warning-related functions +*/ +LUA_API void(lua_setwarnf)(lua_State *L, lua_WarnFunction f, void *ud); +LUA_API void(lua_warning)(lua_State *L, const char *msg, int tocont); + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 +#define LUA_GCISRUNNING 9 +#define LUA_GCGEN 10 +#define LUA_GCINC 11 + +LUA_API int(lua_gc)(lua_State *L, int what, ...); + +/* +** miscellaneous functions +*/ + +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 size_t(lua_stringtonumber)(lua_State *L, const char *s); + +LUA_API lua_Alloc(lua_getallocf)(lua_State *L, void **ud); +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); + +/* +** {============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +#define lua_tonumber(L, i) lua_tonumberx(L, (i), NULL) +#define lua_tointeger(L, i) lua_tointegerx(L, (i), NULL) + +#define lua_pop(L, n) lua_settop(L, -(n) - 1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L, n, f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L, f) lua_pushcclosure(L, (f), 0) + +#define lua_isfunction(L, n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L, n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L, n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L, n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L, n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L, n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L, n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) lua_pushstring(L, "" s) + +#define lua_pushglobaltable(L) ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) + +#define lua_tostring(L, i) lua_tolstring(L, (i), NULL) + +#define lua_insert(L, idx) lua_rotate(L, (idx), 1) + +#define lua_remove(L, idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) + +#define lua_replace(L, idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) + +/* }============================================================== */ + +/* +** {============================================================== +** compatibility macros +** =============================================================== +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define lua_pushunsigned(L, n) lua_pushinteger(L, (lua_Integer)(n)) +#define lua_tounsignedx(L, i, is) ((lua_Unsigned)lua_tointegerx(L, i, is)) +#define lua_tounsigned(L, i) lua_tounsignedx(L, (i), NULL) + +#endif + +#define lua_newuserdata(L, s) lua_newuserdatauv(L, s, 1) +#define lua_getuservalue(L, idx) lua_getiuservalue(L, idx, 1) +#define lua_setuservalue(L, idx) lua_setiuservalue(L, idx, 1) + +#define LUA_NUMTAGS LUA_NUMTYPES + +/* }============================================================== */ + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILCALL 4 + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + +LUA_API int(lua_getstack)(lua_State *L, int level, lua_Debug *ar); +LUA_API int(lua_getinfo)(lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *(lua_getlocal)(lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_setlocal)(lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_getupvalue)(lua_State *L, int funcindex, int n); +LUA_API const char *(lua_setupvalue)(lua_State *L, int funcindex, int n); + +LUA_API void *(lua_upvalueid)(lua_State *L, int fidx, int n); +LUA_API void(lua_upvaluejoin)(lua_State *L, int fidx1, int n1, int fidx2, int n2); + +LUA_API void(lua_sethook)(lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook(lua_gethook)(lua_State *L); +LUA_API int(lua_gethookmask)(lua_State *L); +LUA_API int(lua_gethookcount)(lua_State *L); + +LUA_API int(lua_setcstacklimit)(lua_State *L, unsigned int limit); + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ + const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ + const char *source; /* (S) */ + size_t srclen; /* (S) */ + int currentline; /* (l) */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + unsigned char nups; /* (u) number of upvalues */ + unsigned char nparams; /* (u) number of parameters */ + char isvararg; /* (u) */ + char istailcall; /* (t) */ + unsigned short ftransfer; /* (r) index of first value transferred */ + unsigned short ntransfer; /* (r) number of transferred values */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + // struct CallInfo *i_ci; /* active function */ +}; + +/* }====================================================================== */ + +#endif diff --git a/cmd/gogensig/convert/_testdata/lua/hfile/luaconf.h b/cmd/gogensig/convert/_testdata/lua/hfile/luaconf.h new file mode 100644 index 0000000..89862e2 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/lua/hfile/luaconf.h @@ -0,0 +1,737 @@ +/* +** $Id: luaconf.h $ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + +#ifndef luaconf_h +#define luaconf_h + +#include +#include + +/* +** =================================================================== +** General Configuration File for Lua +** +** Some definitions here can be changed externally, through the compiler +** (e.g., with '-D' options): They are commented out or protected +** by '#if !defined' guards. However, several other definitions +** should be changed directly here, either because they affect the +** Lua ABI (by making the changes here, you ensure that all software +** connected to Lua, such as C libraries, will be compiled with the same +** configuration); or because they are seldom changed. +** +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + +/* +** {==================================================================== +** System Configuration: macros to adapt (if needed) Lua to some +** particular platform, for instance restricting it to C89. +** ===================================================================== +*/ + +/* +@@ LUA_USE_C89 controls the use of non-ISO-C89 features. +** Define it if you want Lua to avoid the use of a few C99 features +** or Windows-specific features on Windows. +*/ +/* #define LUA_USE_C89 */ + +/* +** By default, Lua on Windows use (some) specific Windows features +*/ +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_USE_WINDOWS /* enable goodies for regular Windows */ +#endif + +#if defined(LUA_USE_WINDOWS) +#define LUA_DL_DLL /* enable support for DLL */ +#define LUA_USE_C89 /* broadly, Windows is C89 */ +#endif + +#if defined(LUA_USE_LINUX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#endif + +#if defined(LUA_USE_MACOSX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ +#endif + +#if defined(LUA_USE_IOS) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN +#endif + +/* +@@ LUAI_IS32INT is true iff 'int' has (at least) 32 bits. +*/ +#define LUAI_IS32INT ((UINT_MAX >> 30) >= 3) + +/* }================================================================== */ + +/* +** {================================================================== +** Configuration for Number types. These options should not be +** set externally, because any other code connected to Lua must +** use the same configuration. +** =================================================================== +*/ + +/* +@@ LUA_INT_TYPE defines the type for Lua integers. +@@ LUA_FLOAT_TYPE defines the type for Lua floats. +** Lua should work fine with any mix of these options supported +** by your C compiler. The usual configurations are 64-bit integers +** and 'double' (the default), 32-bit integers and 'float' (for +** restricted platforms), and 'long'/'double' (for C compilers not +** compliant with C99, which may not have support for 'long long'). +*/ + +/* predefined options for LUA_INT_TYPE */ +#define LUA_INT_INT 1 +#define LUA_INT_LONG 2 +#define LUA_INT_LONGLONG 3 + +/* predefined options for LUA_FLOAT_TYPE */ +#define LUA_FLOAT_FLOAT 1 +#define LUA_FLOAT_DOUBLE 2 +#define LUA_FLOAT_LONGDOUBLE 3 + +/* Default configuration ('long long' and 'double', for 64-bit Lua) */ +#define LUA_INT_DEFAULT LUA_INT_LONGLONG +#define LUA_FLOAT_DEFAULT LUA_FLOAT_DOUBLE + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. +*/ +#define LUA_32BITS 0 + +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS 1 +#else +#define LUA_C89_NUMBERS 0 +#endif + +#if LUA_32BITS /* { */ +/* +** 32-bit integers and 'float' +*/ +#if LUAI_IS32INT /* use 'int' if big enough */ +#define LUA_INT_TYPE LUA_INT_INT +#else /* otherwise use 'long' */ +#define LUA_INT_TYPE LUA_INT_LONG +#endif +#define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT + +#elif LUA_C89_NUMBERS /* }{ */ +/* +** largest types available for C89 ('long' and 'double') +*/ +#define LUA_INT_TYPE LUA_INT_LONG +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE + +#else /* }{ */ +/* use defaults */ + +#define LUA_INT_TYPE LUA_INT_DEFAULT +#define LUA_FLOAT_TYPE LUA_FLOAT_DEFAULT + +#endif /* } */ + +/* }================================================================== */ + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ + +/* +** LUA_PATH_SEP is the character that separates templates in a path. +** LUA_PATH_MARK is the string that marks the substitution points in a +** template. +** LUA_EXEC_DIR in a Windows path is replaced by the executable's +** directory. +*/ +#define LUA_PATH_SEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXEC_DIR "!" + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +** Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +** C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ + +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#if defined(_WIN32) /* { */ +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" + +#if !defined(LUA_PATH_DEFAULT) +#define LUA_PATH_DEFAULT \ + LUA_LDIR "?.lua;" LUA_LDIR "?\\init.lua;" LUA_CDIR "?.lua;" LUA_CDIR "?\\init.lua;" LUA_SHRDIR "?.lua;" LUA_SHRDIR \ + "?\\init.lua;" \ + ".\\?.lua;" \ + ".\\?\\init.lua" +#endif + +#if !defined(LUA_CPATH_DEFAULT) +#define LUA_CPATH_DEFAULT \ + LUA_CDIR "?.dll;" LUA_CDIR "..\\lib\\lua\\" LUA_VDIR "\\?.dll;" LUA_CDIR "loadall.dll;" \ + ".\\?.dll" +#endif + +#else /* }{ */ + +#define LUA_ROOT "/opt/homebrew/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" + +#if !defined(LUA_PATH_DEFAULT) +#define LUA_PATH_DEFAULT \ + LUA_LDIR "?.lua;" LUA_LDIR "?/init.lua;" LUA_CDIR "?.lua;" LUA_CDIR "?/init.lua;" \ + "./?.lua;" \ + "./?/init.lua" +#endif + +#if !defined(LUA_CPATH_DEFAULT) +#define LUA_CPATH_DEFAULT \ + LUA_CDIR "?.so;" LUA_CDIR "loadall.so;" \ + "./?.so" +#endif + +#endif /* } */ + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if !defined(LUA_DIRSEP) + +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + +#endif + +/* +** LUA_IGMARK is a mark to ignore all after it when building the +** module name (e.g., used to build the luaopen_ function name). +** Typically, the suffix after the mark is the module version, +** as in "mod-v1.2.so". +*/ +#define LUA_IGMARK "-" + +/* }================================================================== */ + +/* +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== +*/ + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all auxiliary library functions. +@@ LUAMOD_API is a mark for all standard library opening functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) /* { */ + +#if defined(LUA_CORE) || defined(LUA_LIB) /* { */ +#define LUA_API __declspec(dllexport) +#else /* }{ */ +#define LUA_API __declspec(dllimport) +#endif /* } */ + +#else /* }{ */ + +#define LUA_API extern + +#endif /* } */ + +/* +** More often than not the libs go together with the core. +*/ +#define LUALIB_API LUA_API +#define LUAMOD_API LUA_API + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +** exported to outside modules. +@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables, +** none of which to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. Not all elf targets support +** this attribute. Unfortunately, gcc does not offer a way to check +** whether the target offers that support, and those without support +** give a warning about it. To avoid these warnings, change to the +** default definition. +*/ +#if defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 302) && defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("internal"))) extern +#else /* }{ */ +#define LUAI_FUNC extern +#endif /* } */ + +#define LUAI_DDEC(dec) LUAI_FUNC dec +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ + +/* +** {================================================================== +** Compatibility with previous versions +** =================================================================== +*/ + +/* +@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3. +** You can define it to get all options, or change specific options +** to fit your specific needs. +*/ +#if defined(LUA_COMPAT_5_3) /* { */ + +/* +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. +** (These functions were already officially removed in 5.3; +** nevertheless they are still available here.) +*/ +#define LUA_COMPAT_MATHLIB + +/* +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) +** (These macros were also officially removed in 5.3, but they are still +** available here.) +*/ +#define LUA_COMPAT_APIINTCASTS + +/* +@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod +** using '__lt'. +*/ +#define LUA_COMPAT_LT_LE + +/* +@@ The following macros supply trivial compatibility for some +** changes in the API. The macros themselves document how to +** change your code to avoid using them. +** (Once more, these macros were officially removed in 5.3, but they are +** still available here.) +*/ +#define lua_strlen(L, i) lua_rawlen(L, (i)) + +#define lua_objlen(L, i) lua_rawlen(L, (i)) + +#define lua_equal(L, idx1, idx2) lua_compare(L, (idx1), (idx2), LUA_OPEQ) +#define lua_lessthan(L, idx1, idx2) lua_compare(L, (idx1), (idx2), LUA_OPLT) + +#endif /* } */ + +/* }================================================================== */ + +/* +** {================================================================== +** Configuration for Numbers (low-level part). +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== +*/ + +/* +@@ LUAI_UACNUMBER is the result of a 'default argument promotion' +@@ over a floating number. +@@ l_floatatt(x) corrects float attribute 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeral to a number. +*/ + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s, sz, n) l_sprintf((s), sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)(n)) + +/* +@@ lua_numbertointeger converts a float number with an integral value +** to an integer, or returns 0 if float is not within the range of +** a lua_Integer. (The range comparisons are tricky because of +** rounding. The tests here assume a two-complement representation, +** where MININTEGER always has an exact representation as a float; +** MAXINTEGER may not have one, and therefore its conversion to float +** may have an ill-defined value.) +*/ +#define lua_numbertointeger(n, p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && (n) < -(LUA_NUMBER)(LUA_MININTEGER) && (*(p) = (LUA_INTEGER)(n), 1)) + +/* now the variable definitions */ + +#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ + +#define LUA_NUMBER float + +#define l_floatatt(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7g" + +#define l_mathop(op) op##f + +#define lua_str2number(s, p) strtof((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE /* }{ long double */ + +#define LUA_NUMBER long double + +#define l_floatatt(n) (LDBL_##n) + +#define LUAI_UACNUMBER long double + +#define LUA_NUMBER_FRMLEN "L" +#define LUA_NUMBER_FMT "%.19Lg" + +#define l_mathop(op) op##l + +#define lua_str2number(s, p) strtold((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE /* }{ double */ + +#define LUA_NUMBER double + +#define l_floatatt(n) (DBL_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.14g" + +#define l_mathop(op) op + +#define lua_str2number(s, p) strtod((s), (p)) + +#else /* }{ */ + +#error "numeric float type not defined" + +#endif /* } */ + +/* +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +@@ LUAI_UACINT is the result of a 'default argument promotion' +@@ over a LUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ LUA_MAXUNSIGNED is the maximum value for a LUA_UNSIGNED. +@@ lua_integer2str converts an integer to a string. +*/ + +/* The following definitions are good for most cases here */ + +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" + +#define LUAI_UACINT LUA_INTEGER + +#define lua_integer2str(s, sz, n) l_sprintf((s), sz, LUA_INTEGER_FMT, (LUAI_UACINT)(n)) + +/* +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) +*/ +#define LUA_UNSIGNED unsigned LUAI_UACINT + +/* now the variable definitions */ + +#if LUA_INT_TYPE == LUA_INT_INT /* { int */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#define LUA_MAXUNSIGNED UINT_MAX + +#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ + +#define LUA_INTEGER long +#define LUA_INTEGER_FRMLEN "l" + +#define LUA_MAXINTEGER LONG_MAX +#define LUA_MININTEGER LONG_MIN + +#define LUA_MAXUNSIGNED ULONG_MAX + +#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ + +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ +#if defined(LLONG_MAX) /* { */ +/* use ISO C99 stuff */ + +#define LUA_INTEGER long long +#define LUA_INTEGER_FRMLEN "ll" + +#define LUA_MAXINTEGER LLONG_MAX +#define LUA_MININTEGER LLONG_MIN + +#define LUA_MAXUNSIGNED ULLONG_MAX + +#elif defined(LUA_USE_WINDOWS) /* }{ */ +/* in Windows, can use specific Windows types */ + +#define LUA_INTEGER __int64 +#define LUA_INTEGER_FRMLEN "I64" + +#define LUA_MAXINTEGER _I64_MAX +#define LUA_MININTEGER _I64_MIN + +#define LUA_MAXUNSIGNED _UI64_MAX + +#else /* }{ */ + +#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ + or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)" + +#endif /* } */ + +#else /* }{ */ + +#error "numeric integer type not defined" + +#endif /* } */ + +/* }================================================================== */ + +/* +** {================================================================== +** Dependencies with C99 and other C details +** =================================================================== +*/ + +/* +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if !defined(LUA_USE_C89) +#define l_sprintf(s, sz, f, i) snprintf(s, sz, f, i) +#else +#define l_sprintf(s, sz, f, i) ((void)(sz), sprintf(s, f, i)) +#endif + +/* +@@ lua_strx2number converts a hexadecimal numeral to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s, p) lua_str2number(s, p) +#endif + +/* +@@ lua_pointer2str converts a pointer to a readable string in a +** non-specified way. +*/ +#define lua_pointer2str(buff, sz, p) l_sprintf(buff, sz, "%p", p) + +/* +@@ lua_number2strx converts a float to a hexadecimal numeral. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_number2strx(L, b, sz, f, n) ((void)L, l_sprintf(b, sz, f, (LUAI_UACNUMBER)(n))) +#endif + +/* +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) +*/ +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number) op /* no variant */ +#define lua_str2number(s, p) ((lua_Number)strtod((s), (p))) +#endif + +/* +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) +*/ +#define LUA_KCONTEXT ptrdiff_t + +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t +#endif +#endif + +/* +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include the header 'locale.h'.) +*/ +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + +/* +** macros to improve jump prediction, used mostly for error handling +** and debug facilities. (Some macros in the Lua API use these macros. +** Define LUA_NOBUILTIN if you do not want '__builtin_expect' in your +** code.) +*/ +#if !defined(luai_likely) + +#if defined(__GNUC__) && !defined(LUA_NOBUILTIN) +#define luai_likely(x) (__builtin_expect(((x) != 0), 1)) +#define luai_unlikely(x) (__builtin_expect(((x) != 0), 0)) +#else +#define luai_likely(x) (x) +#define luai_unlikely(x) (x) +#endif + +#endif + +#if defined(LUA_CORE) || defined(LUA_LIB) +/* shorter names for Lua's own use */ +#define l_likely(x) luai_likely(x) +#define l_unlikely(x) luai_unlikely(x) +#endif + +/* }================================================================== */ + +/* +** {================================================================== +** Language Variations +** ===================================================================== +*/ + +/* +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. +*/ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ + +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l, e) assert(e) +#endif + +/* }================================================================== */ + +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). +** ===================================================================== +*/ + +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +** (It must fit into max(size_t)/32 and max(int)/2.) +*/ +#if LUAI_IS32INT +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif + +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +** of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + +/* +@@ LUAL_BUFFERSIZE is the initial buffer size used by the lauxlib +** buffer system. +*/ +#define LUAL_BUFFERSIZE ((int)(16 * sizeof(void *) * sizeof(lua_Number))) + +/* +@@ LUAI_MAXALIGN defines fields that, when used in a union, ensure +** maximum alignment for the other items in that union. +*/ +#define LUAI_MAXALIGN \ + lua_Number n; \ + double u; \ + void *s; \ + lua_Integer i; \ + long l + +/* }================================================================== */ + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + +#endif diff --git a/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.cfg new file mode 100644 index 0000000..34eda90 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "pubfile", + "include": ["temp.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.pub b/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.pub new file mode 100644 index 0000000..ec41235 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.pub @@ -0,0 +1 @@ +data CustomData \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.symb.json b/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.symb.json new file mode 100644 index 0000000..e68d33a --- /dev/null +++ b/cmd/gogensig/convert/_testdata/pubfile/conf/llcppg.symb.json @@ -0,0 +1,7 @@ +[ + { + "mangle": "func", + "c++": "func", + "go": "Func" + } +] diff --git a/cmd/gogensig/convert/_testdata/pubfile/gogensig.expect b/cmd/gogensig/convert/_testdata/pubfile/gogensig.expect new file mode 100644 index 0000000..59da96a --- /dev/null +++ b/cmd/gogensig/convert/_testdata/pubfile/gogensig.expect @@ -0,0 +1,34 @@ +===== temp.go ===== +package pubfile + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type Point struct { + X c.Int + Y c.Int +} + +type Capital struct { + X c.Int + Y c.Int +} + +type CustomData struct { + Str [20]int8 +} +type UintT c.Uint +type Color c.Int + +const ColorRED Color = 0 +//go:linkname Func C.func +func Func(a c.Int, b c.Int) + +===== llcppg.pub ===== +Capital +color Color +data CustomData +point Point +uint_t UintT \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/pubfile/hfile/temp.h b/cmd/gogensig/convert/_testdata/pubfile/hfile/temp.h new file mode 100644 index 0000000..a3e4918 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/pubfile/hfile/temp.h @@ -0,0 +1,17 @@ +struct point { + int x; + int y; +}; +struct Capital { + int x; + int y; +}; +union data { + float f; + char str[20]; +}; +typedef unsigned int uint_t; +enum color { + RED = 0, +}; +void func(int a, int b); \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/selfref/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/selfref/conf/llcppg.cfg new file mode 100644 index 0000000..2d595e2 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/selfref/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "selfref", + "include": ["temp.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/selfref/gogensig.expect b/cmd/gogensig/convert/_testdata/selfref/gogensig.expect new file mode 100644 index 0000000..dbe5cd0 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/selfref/gogensig.expect @@ -0,0 +1,21 @@ +===== temp.go ===== +package selfref + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type CJSON struct { + Next *CJSON + Prev *CJSON + Child *CJSON + Type c.Int + Valuestring *int8 + Valueint c.Int + Valuedouble float64 + String *int8 +} + +===== llcppg.pub ===== +cJSON CJSON \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/selfref/hfile/temp.h b/cmd/gogensig/convert/_testdata/selfref/hfile/temp.h new file mode 100644 index 0000000..d458d17 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/selfref/hfile/temp.h @@ -0,0 +1,10 @@ +typedef struct cJSON { + struct cJSON *next; + struct cJSON *prev; + struct cJSON *child; + int type; + char *valuestring; + int valueint; + double valuedouble; + char *string; +} cJSON; \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/stdtype/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/stdtype/conf/llcppg.cfg new file mode 100644 index 0000000..34e5e30 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/stdtype/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "stdtype", + "include": ["temp.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/stdtype/conf/llcppg.symb.json b/cmd/gogensig/convert/_testdata/stdtype/conf/llcppg.symb.json new file mode 100644 index 0000000..cbb8fdb --- /dev/null +++ b/cmd/gogensig/convert/_testdata/stdtype/conf/llcppg.symb.json @@ -0,0 +1,7 @@ +[ + { + "mangle": "testStdType", + "c++": "testStdType", + "go": "TestStdType" + } +] diff --git a/cmd/gogensig/convert/_testdata/stdtype/gogensig.expect b/cmd/gogensig/convert/_testdata/stdtype/gogensig.expect new file mode 100644 index 0000000..61b5b90 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/stdtype/gogensig.expect @@ -0,0 +1,10 @@ +===== temp.go ===== +package stdtype + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) +//go:linkname TestStdType C.testStdType +func TestStdType(a c.SizeT, b c.PtrdiffT) + diff --git a/cmd/gogensig/convert/_testdata/stdtype/hfile/temp.h b/cmd/gogensig/convert/_testdata/stdtype/hfile/temp.h new file mode 100644 index 0000000..a556deb --- /dev/null +++ b/cmd/gogensig/convert/_testdata/stdtype/hfile/temp.h @@ -0,0 +1,3 @@ +#include + +void testStdType(size_t a, ptrdiff_t b); \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/union/conf/llcppg.cfg b/cmd/gogensig/convert/_testdata/union/conf/llcppg.cfg new file mode 100644 index 0000000..65090d4 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/union/conf/llcppg.cfg @@ -0,0 +1,5 @@ +{ + "name": "enum", + "include": ["temp.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/_testdata/union/gogensig.expect b/cmd/gogensig/convert/_testdata/union/gogensig.expect new file mode 100644 index 0000000..ade7ec6 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/union/gogensig.expect @@ -0,0 +1,16 @@ +===== temp.go ===== +package union + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type X__U struct { + B c.Long +} +type U X__U + +===== llcppg.pub ===== +__u X__U +u U \ No newline at end of file diff --git a/cmd/gogensig/convert/_testdata/union/hfile/temp.h b/cmd/gogensig/convert/_testdata/union/hfile/temp.h new file mode 100644 index 0000000..8181860 --- /dev/null +++ b/cmd/gogensig/convert/_testdata/union/hfile/temp.h @@ -0,0 +1,5 @@ +typedef union __u { + int a; + long b; + float c; +} u; \ No newline at end of file diff --git a/cmd/gogensig/convert/ast.go b/cmd/gogensig/convert/ast.go new file mode 100644 index 0000000..c663d7e --- /dev/null +++ b/cmd/gogensig/convert/ast.go @@ -0,0 +1,60 @@ +/* +This file is used to convert ast +from "github.com/goplus/llgo/chore/llcppg/ast" to "go/ast" +*/ +package convert + +import ( + "fmt" + goast "go/ast" + "go/token" + + "github.com/goplus/llgo/chore/llcppg/ast" +) + +type ConvertComment struct { + *goast.Comment +} + +func Comment(doc *ast.Comment) *ConvertComment { + return &ConvertComment{ + Comment: &goast.Comment{ + Slash: token.NoPos, Text: doc.Text, + }, + } +} + +type ConvertCommentGroup struct { + *goast.CommentGroup +} + +func CommentGroup(doc *ast.CommentGroup) *ConvertCommentGroup { + goDoc := &goast.CommentGroup{} + goDoc.List = make([]*goast.Comment, 0) + if doc != nil && doc.List != nil { + for _, comment := range doc.List { + goDoc.List = append(goDoc.List, Comment(comment).Comment) + } + } + return &ConvertCommentGroup{CommentGroup: goDoc} +} + +func (p *ConvertCommentGroup) AddComment(comment *goast.Comment) error { + if comment == nil || len(comment.Text) <= 0 { + return fmt.Errorf("%s", "add nil or empty comment") + } + p.CommentGroup.List = append(p.CommentGroup.List, + []*goast.Comment{ + comment, + }..., + ) + return nil +} + +func (p *ConvertCommentGroup) AddCommentGroup(doc *goast.CommentGroup) error { + if doc == nil || doc.List == nil || len(doc.List) <= 0 { + return fmt.Errorf("%s", "add nil or empty commentgroup") + } + p.CommentGroup.List = append(p.CommentGroup.List, doc.List...) + return nil +} diff --git a/cmd/gogensig/convert/ast_test.go b/cmd/gogensig/convert/ast_test.go new file mode 100644 index 0000000..daccc6a --- /dev/null +++ b/cmd/gogensig/convert/ast_test.go @@ -0,0 +1,63 @@ +package convert_test + +import ( + goast "go/ast" + "testing" + + "github.com/goplus/llgo/chore/gogensig/convert" + "github.com/goplus/llgo/chore/llcppg/ast" +) + +func TestConvertCommentGroupOK(t *testing.T) { + comment := &ast.Comment{Text: "Foo comment"} + commentGroup := &ast.CommentGroup{List: []*ast.Comment{comment}} + var goCommentNode goast.Node = convert.CommentGroup(commentGroup).CommentGroup + _, ok := goCommentNode.(*goast.CommentGroup) + if !ok { + t.Error("convert ast.CommentGroup to goast.CommentGroup fail") + } +} + +func TestAddCommentOK(t *testing.T) { + comment := &ast.Comment{Text: "Foo comment"} + commentGroup := &ast.CommentGroup{List: []*ast.Comment{comment}} + convertCommentGroup := convert.CommentGroup(commentGroup) + err := convertCommentGroup.AddComment(&goast.Comment{Text: "Good"}) + if err != nil { + t.Error(err) + } +} + +func TestAddCommentError(t *testing.T) { + comment := &ast.Comment{Text: "Foo comment"} + commentGroup := &ast.CommentGroup{List: []*ast.Comment{comment}} + convertCommentGroup := convert.CommentGroup(commentGroup) + err := convertCommentGroup.AddComment(nil) + if err == nil { + t.Error("expect a error") + } +} + +func TestAddCommentGroupOK(t *testing.T) { + comment := &ast.Comment{Text: "Foo comment"} + commentGroup := &ast.CommentGroup{List: []*ast.Comment{comment}} + convertCommentGroup := convert.CommentGroup(commentGroup) + err := convertCommentGroup.AddCommentGroup(&goast.CommentGroup{ + List: []*goast.Comment{{Text: "Good"}}, + }) + if err != nil { + t.Error(err) + } +} + +func TestAddCommentGroupError(t *testing.T) { + comment := &ast.Comment{Text: "Foo comment"} + commentGroup := &ast.CommentGroup{List: []*ast.Comment{comment}} + convertCommentGroup := convert.CommentGroup(commentGroup) + err := convertCommentGroup.AddCommentGroup(&goast.CommentGroup{ + List: []*goast.Comment{}, + }) + if err == nil { + t.Error("expect a error") + } +} diff --git a/cmd/gogensig/convert/basic/basic.go b/cmd/gogensig/convert/basic/basic.go new file mode 100644 index 0000000..23c804d --- /dev/null +++ b/cmd/gogensig/convert/basic/basic.go @@ -0,0 +1,40 @@ +package basic + +import ( + "github.com/goplus/llgo/chore/gogensig/convert" + "github.com/goplus/llgo/chore/gogensig/processor" + "github.com/goplus/llgo/chore/gogensig/unmarshal" + "github.com/goplus/llgo/chore/gogensig/visitor" +) + +// For a default full convert processing,for main logic +type Config struct { + convert.AstConvertConfig +} + +func ConvertProcesser(cfg *Config) (*processor.DocFileSetProcessor, *convert.Package, error) { + astConvert, err := convert.NewAstConvert(&convert.AstConvertConfig{ + PkgName: cfg.PkgName, + SymbFile: cfg.SymbFile, + CfgFile: cfg.CfgFile, + OutputDir: cfg.OutputDir, + PubFile: cfg.PubFile, + }) + if err != nil { + return nil, nil, err + } + docVisitors := []visitor.DocVisitor{astConvert} + visitorManager := processor.NewDocVisitorManager(docVisitors) + + return processor.NewDocFileSetProcessor(&processor.ProcesserConfig{ + Exec: func(file *unmarshal.FileEntry) error { + visitorManager.Visit(file.Doc, file.Path, file.IncPath, file.IsSys) + return nil + }, + DepIncs: astConvert.Pkg.AllDepIncs(), + Done: func() { + astConvert.WriteLinkFile() + astConvert.WritePubFile() + }, + }), astConvert.Pkg, nil +} diff --git a/cmd/gogensig/convert/builtin.go b/cmd/gogensig/convert/builtin.go new file mode 100644 index 0000000..21b2c03 --- /dev/null +++ b/cmd/gogensig/convert/builtin.go @@ -0,0 +1,85 @@ +package convert + +import ( + "fmt" + "go/types" + + "github.com/goplus/gogen" + "github.com/goplus/llgo/chore/llcppg/ast" +) + +type BuiltinTypeMap struct { + pkgMap map[string]gogen.PkgRef + builtinTypeMap map[ast.BuiltinType]types.Type + typeAliases map[string]typeAliasInfo +} + +type typeAliasInfo struct { + Type types.Type + HeaderFile string +} + +func NewBuiltinTypeMapWithPkgRefS(pkgs ...gogen.PkgRef) *BuiltinTypeMap { + builtinTypeMap := &BuiltinTypeMap{} + builtinTypeMap.pkgMap = make(map[string]gogen.PkgRef) + for _, pkg := range pkgs { + builtinTypeMap.pkgMap[pkg.Types.Name()] = pkg + builtinTypeMap.pkgMap[pkg.Path()] = pkg + } + builtinTypeMap.initBuiltinTypeMap() + return builtinTypeMap +} + +func NewBuiltinTypeMap(pkgPath, name string, conf *gogen.Config) *BuiltinTypeMap { + p := gogen.NewPackage(pkgPath, name, conf) + clib := p.Import("github.com/goplus/llgo/c") + builtinTypeMap := NewBuiltinTypeMapWithPkgRefS(clib, p.Unsafe()) + return builtinTypeMap +} + +func (p *BuiltinTypeMap) CType(typ string) types.Type { + clib, ok := p.pkgMap["c"] + if ok { + return clib.Ref(typ).Type() + } + return nil +} + +func (p *BuiltinTypeMap) IsVoidType(typ types.Type) bool { + voidType := p.builtinTypeMap[ast.BuiltinType{Kind: ast.Void}] + return typ == voidType +} + +func (p *BuiltinTypeMap) FindBuiltinType(builtinType ast.BuiltinType) (types.Type, error) { + t, ok := p.builtinTypeMap[builtinType] + if ok { + return t, nil + } + return nil, fmt.Errorf("%s", "not found in type map") +} + +func (p *BuiltinTypeMap) initBuiltinTypeMap() { + // todo(zzy): int128/uint128 half(float16),long double,float 128 + p.builtinTypeMap = map[ast.BuiltinType]types.Type{ + {Kind: ast.Void}: p.CType("Void"), // [0]byte + {Kind: ast.Bool}: types.Typ[types.Bool], // Bool + {Kind: ast.Char, Flags: ast.Signed}: p.CType("Char"), // Char_S + {Kind: ast.Char, Flags: ast.Unsigned}: p.CType("Char"), // Char_U + {Kind: ast.WChar}: types.Typ[types.Int16], // WChar + {Kind: ast.Char16}: types.Typ[types.Int16], // Char16 + {Kind: ast.Char32}: types.Typ[types.Int32], // Char32 + {Kind: ast.Int, Flags: ast.Short}: types.Typ[types.Int16], // Short + {Kind: ast.Int, Flags: ast.Short | ast.Unsigned}: types.Typ[types.Uint16], // UShort + {Kind: ast.Int}: p.CType("Int"), // Int + {Kind: ast.Int, Flags: ast.Unsigned}: p.CType("Uint"), // UInt + {Kind: ast.Int, Flags: ast.Long}: p.CType("Long"), // Long + {Kind: ast.Int, Flags: ast.Long | ast.Unsigned}: p.CType("Ulong"), // Ulong + {Kind: ast.Int, Flags: ast.LongLong}: p.CType("LongLong"), // LongLong + {Kind: ast.Int, Flags: ast.LongLong | ast.Unsigned}: p.CType("UlongLong"), // ULongLong + {Kind: ast.Float}: p.CType("Float"), // Float + {Kind: ast.Float, Flags: ast.Double}: p.CType("Double"), // Double + {Kind: ast.Float, Flags: ast.Double | ast.Long}: p.CType("Double"), // Long Double (same as double,need more precision) + {Kind: ast.Complex}: types.Typ[types.Complex64], // ComplexFloat + {Kind: ast.Complex, Flags: ast.Double}: types.Typ[types.Complex128], // ComplexDouble + } +} diff --git a/cmd/gogensig/convert/builtin_test.go b/cmd/gogensig/convert/builtin_test.go new file mode 100644 index 0000000..1f16a1b --- /dev/null +++ b/cmd/gogensig/convert/builtin_test.go @@ -0,0 +1,84 @@ +package convert_test + +import ( + "go/types" + "testing" + + "github.com/goplus/llgo/chore/gogensig/convert" + "github.com/goplus/llgo/chore/llcppg/ast" +) + +func TestBuiltinType(t *testing.T) { + typmap := convert.NewBuiltinTypeMap(".", "temp", nil) + testCases := []struct { + name string + input *ast.BuiltinType + expected string + wantErr bool + }{ + {"Void", &ast.BuiltinType{Kind: ast.Void}, "[0]byte", false}, + {"Bool", &ast.BuiltinType{Kind: ast.Bool}, "bool", false}, + {"Char_S", &ast.BuiltinType{Kind: ast.Char, Flags: ast.Signed}, "int8", false}, + {"Char_U", &ast.BuiltinType{Kind: ast.Char, Flags: ast.Unsigned}, "int8", false}, + {"WChar", &ast.BuiltinType{Kind: ast.WChar}, "int16", false}, + {"Char16", &ast.BuiltinType{Kind: ast.Char16}, "int16", false}, + {"Char32", &ast.BuiltinType{Kind: ast.Char32}, "int32", false}, + {"Short", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Short}, "int16", false}, + {"UShort", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Short | ast.Unsigned}, "uint16", false}, + {"Int", &ast.BuiltinType{Kind: ast.Int}, "github.com/goplus/llgo/c.Int", false}, + {"UInt", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Unsigned}, "github.com/goplus/llgo/c.Uint", false}, + {"Long", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long}, "github.com/goplus/llgo/c.Long", false}, + {"ULong", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long | ast.Unsigned}, "github.com/goplus/llgo/c.Ulong", false}, + {"LongLong", &ast.BuiltinType{Kind: ast.Int, Flags: ast.LongLong}, "github.com/goplus/llgo/c.LongLong", false}, + {"ULongLong", &ast.BuiltinType{Kind: ast.Int, Flags: ast.LongLong | ast.Unsigned}, "github.com/goplus/llgo/c.UlongLong", false}, + {"Float", &ast.BuiltinType{Kind: ast.Float}, "float32", false}, + {"Double", &ast.BuiltinType{Kind: ast.Float, Flags: ast.Double}, "float64", false}, + {"ComplexFloat", &ast.BuiltinType{Kind: ast.Complex}, "complex64", false}, + {"ComplexDouble", &ast.BuiltinType{Kind: ast.Complex, Flags: ast.Double}, "complex128", false}, + + {"Unsupported", &ast.BuiltinType{Kind: 1000}, "", true}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := typmap.FindBuiltinType(*tc.input) + if (err != nil) != tc.wantErr { + t.Errorf("FindBuiltinType() error = %v, wantErr %v", err, tc.wantErr) + return + } + + if tc.wantErr { + return + } + + if result != nil && result.String() != tc.expected { + t.Errorf("unexpected result:%s expected:%s", result.String(), tc.expected) + } + }) + } +} + +func TestIsVoidType(t *testing.T) { + typmap := convert.NewBuiltinTypeMap(".", "temp", nil) + if !typmap.IsVoidType(typmap.CType("Void")) { + t.Error("Expect return true") + } + if typmap.IsVoidType(types.Typ[types.Float32]) { + t.Error("Expect return false") + } +} + +func TestCType(t *testing.T) { + typmap := convert.NewBuiltinTypeMap(".", "temp", nil) + ptrType := typmap.CType("Pointer") + if ptrType == nil { + t.Error("Expect a non nil pointer type") + } +} + +func TestCTypeNotFound(t *testing.T) { + typmap := &convert.BuiltinTypeMap{} + ptrType := typmap.CType("Pointer") + if ptrType != nil { + t.Error("Expect a nil") + } +} diff --git a/cmd/gogensig/convert/comments.go b/cmd/gogensig/convert/comments.go new file mode 100644 index 0000000..0aa5df6 --- /dev/null +++ b/cmd/gogensig/convert/comments.go @@ -0,0 +1,23 @@ +package convert + +import ( + goast "go/ast" +) + +const ( + TYPEC = "// llgo:type C" +) + +func NewFuncDocComments(funcName string, goFuncName string) *goast.CommentGroup { + txt := "//go:linkname " + goFuncName + " " + "C." + funcName + comment := goast.Comment{Text: txt} + commentGroup := goast.CommentGroup{List: []*goast.Comment{&comment}} + return &commentGroup +} + +func NewTypecDocComments() *goast.CommentGroup { + return &goast.CommentGroup{ + List: []*goast.Comment{ + {Text: TYPEC}, + }} +} diff --git a/cmd/gogensig/convert/convert.go b/cmd/gogensig/convert/convert.go new file mode 100644 index 0000000..7ad573a --- /dev/null +++ b/cmd/gogensig/convert/convert.go @@ -0,0 +1,148 @@ +package convert + +import ( + "errors" + "log" + + cfg "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/gogensig/visitor" + "github.com/goplus/llgo/chore/llcppg/ast" + cppgtypes "github.com/goplus/llgo/chore/llcppg/types" +) + +type AstConvert struct { + *visitor.BaseDocVisitor + Pkg *Package + visitDone func(pkg *Package, incPath string) +} + +type AstConvertConfig struct { + PkgName string + SymbFile string // llcppg.symb.json + CfgFile string // llcppg.cfg + PubFile string // llcppg.pub + OutputDir string +} + +func NewAstConvert(config *AstConvertConfig) (*AstConvert, error) { + if config == nil { + return nil, errors.New("config is nil") + } + p := new(AstConvert) + p.BaseDocVisitor = visitor.NewBaseDocVisitor(p) + symbTable, err := cfg.NewSymbolTable(config.SymbFile) + if err != nil { + if debug { + log.Printf("Can't get llcppg.symb.json from %s Use empty table\n", config.SymbFile) + } + symbTable = cfg.CreateSymbolTable([]cfg.SymbolEntry{}) + } + + conf, err := cfg.GetCppgCfgFromPath(config.CfgFile) + if err != nil { + if debug { + log.Printf("Cant get llcppg.cfg from %s Use empty config\n", config.CfgFile) + } + conf = &cppgtypes.Config{} + } + + public, err := cfg.GetPubFromPath(config.PubFile) + if err != nil { + return nil, err + } + + pkg := NewPackage(&PackageConfig{ + PkgPath: ".", + Name: config.PkgName, + OutputDir: config.OutputDir, + SymbolTable: symbTable, + CppgConf: conf, + Public: public, + }) + p.Pkg = pkg + return p, nil +} + +func (p *AstConvert) SetVisitDone(fn func(pkg *Package, incPath string)) { + p.visitDone = fn +} + +func (p *AstConvert) WriteLinkFile() { + p.Pkg.WriteLinkFile() +} + +func (p *AstConvert) WritePubFile() { + p.Pkg.WritePubFile() +} + +func (p *AstConvert) VisitFuncDecl(funcDecl *ast.FuncDecl) { + err := p.Pkg.NewFuncDecl(funcDecl) + if err != nil { + log.Printf("NewFuncDecl %s Fail: %s\n", funcDecl.Name.Name, err.Error()) + } +} + +/* +//TODO +func (p *AstConvert) VisitClass(className *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) { + fmt.Printf("visit class %s\n", className.Name) + p.pkg.NewTypeDecl(typeDecl) +} + +func (p *AstConvert) VisitMethod(className *ast.Ident, method *ast.FuncDecl, typeDecl *ast.TypeDecl) { + fmt.Printf("visit method %s of %s\n", method.Name.Name, className.Name) +}*/ + +func (p *AstConvert) VisitStruct(structName *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) { + err := p.Pkg.NewTypeDecl(typeDecl) + if typeDecl.Name == nil { + log.Printf("NewTypeDecl anonymous struct skipped") + } + if err != nil { + if name := typeDecl.Name; name != nil { + log.Printf("NewTypeDecl %s Fail: %s\n", name.Name, err.Error()) + } + } +} + +func (p *AstConvert) VisitUnion(unionName *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) { + p.VisitStruct(unionName, fields, typeDecl) +} + +func (p *AstConvert) VisitEnumTypeDecl(enumTypeDecl *ast.EnumTypeDecl) { + err := p.Pkg.NewEnumTypeDecl(enumTypeDecl) + if err != nil { + if name := enumTypeDecl.Name; name != nil { + log.Printf("NewEnumTypeDecl %s Fail: %s\n", name.Name, err.Error()) + } else { + log.Printf("NewEnumTypeDecl anonymous Fail: %s\n", err.Error()) + } + } +} + +func (p *AstConvert) VisitTypedefDecl(typedefDecl *ast.TypedefDecl) { + err := p.Pkg.NewTypedefDecl(typedefDecl) + if err != nil { + log.Printf("NewTypedefDecl %s Fail: %s\n", typedefDecl.Name.Name, err.Error()) + } +} + +func (p *AstConvert) VisitStart(path string, incPath string, isSys bool) { + inPkgIncPath := false + incPaths := p.Pkg.GetIncPaths() + for _, includePath := range incPaths { + if includePath == path { + inPkgIncPath = true + break + } + } + p.Pkg.SetCurFile(path, incPath, true, inPkgIncPath, isSys) +} + +func (p *AstConvert) VisitDone(incPath string) { + if p.visitDone != nil { + p.visitDone(p.Pkg, incPath) + } else { + p.Pkg.Write(incPath) + } +} diff --git a/cmd/gogensig/convert/convert_test.go b/cmd/gogensig/convert/convert_test.go new file mode 100644 index 0000000..c659afd --- /dev/null +++ b/cmd/gogensig/convert/convert_test.go @@ -0,0 +1,384 @@ +package convert_test + +import ( + "fmt" + "os" + "path" + "path/filepath" + "sort" + "strings" + "testing" + + "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/gogensig/convert" + "github.com/goplus/llgo/chore/gogensig/convert/basic" + "github.com/goplus/llgo/chore/gogensig/unmarshal" + "github.com/goplus/llgo/chore/llcppg/ast" + "github.com/goplus/llgo/xtool/env" +) + +func init() { + convert.SetDebug(convert.DbgFlagAll) +} + +func TestFromTestdata(t *testing.T) { + testFromDir(t, "./_testdata", false) +} + +// test sys type in stdinclude to package +func TestSysToPkg(t *testing.T) { + name := "_systopkg" + dir, err := os.Getwd() + if err != nil { + t.Fatal("Getwd failed:", err) + } + testFrom(t, name, path.Join(dir, "_testdata", name), false, func(t *testing.T, pkg *convert.Package) { + typConv := pkg.GetTypeConv() + if typConv.SysTypeLoc == nil { + t.Fatal("sysTypeLoc is nil") + } + pkgIncTypes := make(map[string]map[string][]string) + + // full type in all std lib + for name, info := range typConv.SysTypeLoc { + targetPkg, isDefault := convert.IncPathToPkg(info.IncPath) + if isDefault { + targetPkg = "github.com/goplus/llgo/c [default]" + } + if pkgIncTypes[targetPkg] == nil { + pkgIncTypes[targetPkg] = make(map[string][]string, 0) + } + if pkgIncTypes[targetPkg][info.IncPath] == nil { + pkgIncTypes[targetPkg][info.IncPath] = make([]string, 0) + } + pkgIncTypes[targetPkg][info.IncPath] = append(pkgIncTypes[targetPkg][info.IncPath], name) + } + + for pkg, incTypes := range pkgIncTypes { + t.Logf("\x1b[1;32m %s \x1b[0m Package contains inc types:", pkg) + for incPath, types := range incTypes { + t.Logf("\x1b[1;33m - %s\x1b[0m (%s):", incPath, pkg) + sort.Strings(types) + t.Logf(" - %s", strings.Join(types, " ")) + } + } + + // check referd type in std lib + // Expected type to package mappings + expected := map[string]string{ + "mbstate_t": "github.com/goplus/llgo/c", + "wint_t": "github.com/goplus/llgo/c", + "ptrdiff_t": "github.com/goplus/llgo/c", + "int8_t": "github.com/goplus/llgo/c", + "max_align_t": "github.com/goplus/llgo/c", + "FILE": "github.com/goplus/llgo/c", + "tm": "github.com/goplus/llgo/c/time", + "time_t": "github.com/goplus/llgo/c/time", + "clock_t": "github.com/goplus/llgo/c/time", + "fenv_t": "github.com/goplus/llgo/c/math", + "size_t": "github.com/goplus/llgo/c", + } + + for name, exp := range expected { + if _, ok := typConv.SysTypePkg[name]; ok { + if typConv.SysTypePkg[name].PkgPath != exp { + t.Errorf("type [%s]: expected package [%s], got [%s] in header [%s]", name, exp, typConv.SysTypePkg[name].PkgPath, typConv.SysTypePkg[name].Header.IncPath) + } else { + t.Logf("refer type [%s] expected package [%s] from header [%s]", name, exp, typConv.SysTypePkg[name].Header.IncPath) + } + } else { + t.Logf("missing expected type %s (package: %s)", name, exp) + } + } + }) +} + +func TestDepPkg(t *testing.T) { + name := "_depcjson" + dir, err := os.Getwd() + if err != nil { + t.Fatal("Getwd failed:", err) + } + testFrom(t, name, path.Join(dir, "_testdata", name), false, nil) +} + +func testFromDir(t *testing.T, relDir string, gen bool) { + dir, err := os.Getwd() + if err != nil { + t.Fatal("Getwd failed:", err) + } + dir = path.Join(dir, relDir) + fis, err := os.ReadDir(dir) + if err != nil { + t.Fatal("ReadDir failed:", err) + } + for _, fi := range fis { + name := fi.Name() + if strings.HasPrefix(name, "_") { + continue + } + t.Run(name, func(t *testing.T) { + testFrom(t, name, dir+"/"+name, gen, nil) + }) + } +} + +func testFrom(t *testing.T, name, dir string, gen bool, validateFunc func(t *testing.T, pkg *convert.Package)) { + confPath := filepath.Join(dir, "conf") + cfgPath := filepath.Join(confPath, "llcppg.cfg") + symbPath := filepath.Join(confPath, "llcppg.symb.json") + pubPath := filepath.Join(confPath, "llcppg.pub") + expect := filepath.Join(dir, "gogensig.expect") + var expectContent []byte + if !gen { + var err error + expectContent, err = os.ReadFile(expect) + if err != nil { + t.Fatal(expectContent) + } + } + + cfg, err := config.GetCppgCfgFromPath(cfgPath) + if err != nil { + t.Fatal(err) + } + + if cfg.CFlags != "" { + cfg.CFlags = env.ExpandEnv(cfg.CFlags) + } + + cfg.CFlags += " -I" + filepath.Join(dir, "hfile") + + flagedCfgPath, err := config.CreateJSONFile("llcppg.cfg", cfg) + if err != nil { + t.Fatal(err) + } + tempDir, err := os.MkdirTemp("", "gogensig-test") + if err != nil { + t.Fatal("failed to create temp dir") + } + defer os.RemoveAll(tempDir) + + outputDir := filepath.Join(tempDir, name) + err = os.MkdirAll(outputDir, 0744) + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(outputDir) + + projectRoot, err := filepath.Abs("../../../") + if err != nil { + t.Fatal(err) + } + originalWd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer os.Chdir(originalWd) + os.Chdir(outputDir) + + config.RunCommand(outputDir, "go", "mod", "init", name) + config.RunCommand(outputDir, "go", "get", "github.com/goplus/llgo") + config.RunCommand(outputDir, "go", "mod", "edit", "-replace", "github.com/goplus/llgo="+projectRoot) + + p, pkg, err := basic.ConvertProcesser(&basic.Config{ + AstConvertConfig: convert.AstConvertConfig{ + PkgName: name, + SymbFile: symbPath, + CfgFile: flagedCfgPath, + OutputDir: outputDir, + PubFile: pubPath, + }, + }) + if err != nil { + t.Fatal(err) + } + + bytes, err := config.SigfetchConfig(flagedCfgPath, confPath) + if err != nil { + t.Fatal(err) + } + + inputdata, err := unmarshal.UnmarshalFileSet(bytes) + if err != nil { + t.Fatal(err) + } + + err = p.ProcessFileSet(inputdata) + if err != nil { + t.Fatal(err) + } + + var res strings.Builder + + outDir, err := os.ReadDir(outputDir) + if err != nil { + t.Fatal(err) + } + for _, fi := range outDir { + if strings.HasSuffix(fi.Name(), "go.mod") || strings.HasSuffix(fi.Name(), "go.sum") || strings.HasSuffix(fi.Name(), "llcppg.pub") { + continue + } else { + content, err := os.ReadFile(filepath.Join(outputDir, fi.Name())) + if err != nil { + t.Fatal(err) + } + res.WriteString(fmt.Sprintf("===== %s =====\n", fi.Name())) + res.Write(content) + res.WriteString("\n") + } + } + + pub, err := os.ReadFile(filepath.Join(outputDir, "llcppg.pub")) + if err == nil { + res.WriteString("===== llcppg.pub =====\n") + res.Write(pub) + } + + if gen { + if err := os.WriteFile(expect, []byte(res.String()), 0644); err != nil { + t.Fatal(err) + } + } else { + expect := string(expectContent) + got := res.String() + if strings.TrimSpace(expect) != strings.TrimSpace(got) { + t.Errorf("does not match expected.\nExpected:\n%s\nGot:\n%s", expect, got) + } + } + + if validateFunc != nil { + validateFunc(t, pkg) + } +} + +// ===========================error +func TestNewAstConvert(t *testing.T) { + _, err := convert.NewAstConvert(&convert.AstConvertConfig{ + PkgName: "test", + SymbFile: "", + CfgFile: "", + }) + if err != nil { + t.Fatal("NewAstConvert Fail") + } +} + +func TestNewAstConvertFail(t *testing.T) { + _, err := convert.NewAstConvert(nil) + if err == nil { + t.Fatal("no error") + } +} + +func TestVisitDone(t *testing.T) { + pkg, err := convert.NewAstConvert(&convert.AstConvertConfig{ + PkgName: "test", + SymbFile: "", + CfgFile: "", + }) + if err != nil { + t.Fatal("NewAstConvert Fail") + } + pkg.SetVisitDone(func(pkg *convert.Package, incPath string) { + if pkg.Name() != "test" { + t.Fatal("pkg name error") + } + if incPath != "test.h" { + t.Fatal("doc path error") + } + }) + pkg.VisitDone("test.h") +} + +func TestVisitFail(t *testing.T) { + converter, err := convert.NewAstConvert(&convert.AstConvertConfig{ + PkgName: "test", + SymbFile: "", + CfgFile: "", + }) + if err != nil { + t.Fatal("NewAstConvert Fail") + } + + // expect type + converter.VisitTypedefDecl(&ast.TypedefDecl{ + Name: &ast.Ident{Name: "NormalType"}, + Type: &ast.BuiltinType{Kind: ast.Int}, + }) + + // not appear in output,because expect error + converter.VisitTypedefDecl(&ast.TypedefDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: nil, + }) + + errRecordType := &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + {Type: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Double}}, + }, + }, + } + // error field type for struct + converter.VisitStruct(&ast.Ident{Name: "Foo"}, nil, &ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: errRecordType, + }) + + // error field type for anonymous struct + converter.VisitStruct(&ast.Ident{Name: "Foo"}, nil, &ast.TypeDecl{ + Name: nil, + Type: errRecordType, + }) + + converter.VisitEnumTypeDecl(&ast.EnumTypeDecl{ + Name: &ast.Ident{Name: "NormalType"}, + Type: &ast.EnumType{}, + }) + + // error enum item for anonymous enum + converter.VisitEnumTypeDecl(&ast.EnumTypeDecl{ + Name: nil, + Type: &ast.EnumType{ + Items: []*ast.EnumItem{ + {Name: &ast.Ident{Name: "Item1"}}, + }, + }, + }) + + converter.VisitFuncDecl(&ast.FuncDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + {Type: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Double}}, + }, + }, + }, + }) + // not appear in output + + buf, err := converter.Pkg.WriteDefaultFileToBuffer() + if err != nil { + t.Fatalf("WriteTo failed: %v", err) + } + + expectedOutput := + ` +package test + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type NormalType c.Int +` + if strings.TrimSpace(expectedOutput) != strings.TrimSpace(buf.String()) { + t.Errorf("does not match expected.\nExpected:\n%s\nGot:\n%s", expectedOutput, buf.String()) + } +} + +// = env.ExpandEnv(conf.CFlags) diff --git a/cmd/gogensig/convert/deps/deps.go b/cmd/gogensig/convert/deps/deps.go new file mode 100644 index 0000000..7acc989 --- /dev/null +++ b/cmd/gogensig/convert/deps/deps.go @@ -0,0 +1,70 @@ +package deps + +import ( + "errors" + "path/filepath" + + "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/mod/gopmod" +) + +func LoadDeps(dir string, deps []string) (pkgs []*CPackage, err error) { + mod, err := gopmod.Load(dir) + if err != nil { + return nil, err + } + return Imports(mod, deps) +} + +type Module = gopmod.Module + +func Imports(mod *Module, pkgPaths []string) (pkgs []*CPackage, err error) { + pkgs = make([]*CPackage, len(pkgPaths)) + for i, pkgPath := range pkgPaths { + pkgs[i], err = Import(mod, pkgPath) + if err != nil { + return nil, err + } + } + return +} + +type CPackage struct { + *gopmod.Package + Path string // package path + Dir string // absolue local path of the package + Pubs map[string]string + StdIncs []string // std include dirs +} + +func Import(mod *Module, pkgPath string) (p *CPackage, err error) { + if mod == nil { + return nil, errors.New("go.mod not found") + } + pkg, err := mod.Lookup(pkgPath) + if err != nil { + return nil, err + } + pkgDir, err := filepath.Abs(pkg.Dir) + if err != nil { + return nil, err + } + pubs, err := config.ReadPubFile(filepath.Join(pkgDir, "llcppg.pub")) + if err != nil { + return nil, err + } + pkgIncs, err := findStdIncs(pkgDir) + if err != nil { + return nil, err + } + return &CPackage{Package: pkg, Path: pkgPath, Dir: pkgDir, Pubs: pubs, StdIncs: pkgIncs}, nil +} + +func findStdIncs(pkgDir string) (incs []string, err error) { + file := filepath.Join(pkgDir, "llcppg.cfg") + cfg, err := config.GetCppgCfgFromPath(file) + if err != nil { + return nil, err + } + return cfg.Include, nil +} diff --git a/cmd/gogensig/convert/expr.go b/cmd/gogensig/convert/expr.go new file mode 100644 index 0000000..c18badb --- /dev/null +++ b/cmd/gogensig/convert/expr.go @@ -0,0 +1,51 @@ +package convert + +import ( + "fmt" + "strconv" + + "github.com/goplus/llgo/chore/llcppg/ast" +) + +type ConvertExpr struct { + e ast.Expr +} + +func Expr(e ast.Expr) *ConvertExpr { + return &ConvertExpr{e: e} +} + +func (p *ConvertExpr) ToInt() (int, error) { + v, ok := p.e.(*ast.BasicLit) + if ok && v.Kind == ast.IntLit { + return strconv.Atoi(v.Value) + } + return 0, fmt.Errorf("%v can't convert to int", p.e) +} + +func (p *ConvertExpr) ToFloat(bitSize int) (float64, error) { + v, ok := p.e.(*ast.BasicLit) + if ok && v.Kind == ast.FloatLit { + return strconv.ParseFloat(v.Value, bitSize) + } + return 0, fmt.Errorf("%v can't convert to float", v) +} + +func (p *ConvertExpr) ToString() (string, error) { + v, ok := p.e.(*ast.BasicLit) + if ok && v.Kind == ast.StringLit { + return v.Value, nil + } + return "", fmt.Errorf("%v can't convert to string", v) +} + +func (p *ConvertExpr) ToChar() (int8, error) { + v, ok := p.e.(*ast.BasicLit) + if ok && v.Kind == ast.CharLit { + iV, err := strconv.Atoi(v.Value) + if err == nil { + return int8(iV), nil + } + } + return 0, fmt.Errorf("%v can't convert to char", p.e) +} diff --git a/cmd/gogensig/convert/expr_test.go b/cmd/gogensig/convert/expr_test.go new file mode 100644 index 0000000..8b6c100 --- /dev/null +++ b/cmd/gogensig/convert/expr_test.go @@ -0,0 +1,125 @@ +package convert_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/goplus/llgo/chore/gogensig/convert" + "github.com/goplus/llgo/chore/llcppg/ast" +) + +func TestBasicLitFail(t *testing.T) { + t.Parallel() + type CaseType[T any] struct { + name string + expr ast.Expr + want T + } + type CaseTypeSlice[T any] []CaseType[T] + testCases := CaseTypeSlice[any]{ + { + name: "ToInt", + expr: &ast.TagExpr{Tag: ast.Class, Name: &ast.Ident{Name: "Foo"}}, + want: 123, + }, + { + name: "ToFloat", + expr: &ast.TagExpr{Tag: ast.Class, Name: &ast.Ident{Name: "Foo"}}, + want: 123.123, + }, + { + name: "ToString", + expr: &ast.TagExpr{Tag: ast.Class, Name: &ast.Ident{Name: "Foo"}}, + want: "abcd", + }, + { + name: "ToChar", + expr: &ast.TagExpr{Tag: ast.Class, Name: &ast.Ident{Name: "Foo"}}, + want: (int8)(98), + }, + } + + for _, tc := range testCases { + t.Run("convert "+tc.name, func(t *testing.T) { + if tc.name == "ToInt" { + _, err := convert.Expr(tc.expr).ToInt() + expectError(t, err) + } else if tc.name == "ToFloat" { + _, err := convert.Expr(tc.expr).ToFloat(64) + expectError(t, err) + } else if tc.name == "ToChar" { + _, err := convert.Expr(tc.expr).ToChar() + expectError(t, err) + } else if tc.name == "ToString" { + _, err := convert.Expr(tc.expr).ToString() + expectError(t, err) + } + }) + } +} + +func TestBasicLitOK(t *testing.T) { + t.Parallel() + type CaseType[T any] struct { + name string + expr ast.Expr + want T + } + type CaseTypeSlice[T any] []CaseType[T] + testCases := CaseTypeSlice[any]{ + { + name: "ToInt", + expr: &ast.BasicLit{Kind: ast.IntLit, Value: "123"}, + want: 123, + }, + { + name: "ToFloat", + expr: &ast.BasicLit{Kind: ast.FloatLit, Value: "123.123"}, + want: 123.123, + }, + { + name: "ToString", + expr: &ast.BasicLit{Kind: ast.StringLit, Value: "abcd"}, + want: "abcd", + }, + { + name: "ToChar", + expr: &ast.BasicLit{Kind: ast.CharLit, Value: "98"}, + want: (int8)(98), + }, + } + + for _, tc := range testCases { + t.Run("convert "+tc.name, func(t *testing.T) { + if tc.name == "ToInt" { + result, err := convert.Expr(tc.expr).ToInt() + checkResult(t, result, err, tc.want) + } else if tc.name == "ToFloat" { + result, err := convert.Expr(tc.expr).ToFloat(64) + checkResult(t, result, err, tc.want) + } else if tc.name == "ToChar" { + result, err := convert.Expr(tc.expr).ToChar() + checkResult(t, result, err, tc.want) + } else if tc.name == "ToString" { + result, err := convert.Expr(tc.expr).ToString() + checkResult(t, result, err, tc.want) + } + }) + } +} + +func expectError(t *testing.T, err error) { + if err == nil { + t.Error("expect error") + } +} + +func checkResult(t *testing.T, result any, err error, want any) { + t.Helper() + if err != nil { + t.Error(err) + } + if !cmp.Equal(result, want) { + t.Error(cmp.Diff(result, want)) + } +} diff --git a/cmd/gogensig/convert/headerfile.go b/cmd/gogensig/convert/headerfile.go new file mode 100644 index 0000000..65ae1e8 --- /dev/null +++ b/cmd/gogensig/convert/headerfile.go @@ -0,0 +1,49 @@ +package convert + +import ( + "fmt" + "log" + + "github.com/goplus/llgo/chore/gogensig/convert/names" +) + +type HeaderFile struct { + file string + incPath string + isHeaderFile bool + inCurPkg bool + isSys bool + sysIncPath string +} + +func (p *HeaderFile) setSysIncPath(isSys bool, incPath string) error { + if isSys { + if incPath == "" { + return fmt.Errorf("system header file %s has no include path", p.file) + } + p.sysIncPath = incPath + if debug { + log.Printf("%s is a system header file,include path: %s\n", p.file, incPath) + } + return nil + } + return nil +} + +func (p *HeaderFile) ToGoFileName() string { + var fileName string + if p.isHeaderFile { + // path to go filename + fileName = names.HeaderFileToGo(p.file) + } else { + // package name as the default file + fileName = p.file + ".go" + } + return fileName +} + +func NewHeaderFile(file string, incPath string, isHeaderFile bool, inCurPkg bool, isSys bool) (*HeaderFile, error) { + p := &HeaderFile{file: file, incPath: incPath, isHeaderFile: isHeaderFile, inCurPkg: inCurPkg, isSys: isSys} + err := p.setSysIncPath(isSys, incPath) + return p, err +} diff --git a/cmd/gogensig/convert/names/names.go b/cmd/gogensig/convert/names/names.go new file mode 100644 index 0000000..099dd1c --- /dev/null +++ b/cmd/gogensig/convert/names/names.go @@ -0,0 +1,56 @@ +package names + +import ( + "path/filepath" + "strings" +) + +func RemovePrefixedName(name string, trimPrefixes []string) string { + if len(trimPrefixes) == 0 { + return name + } + for _, prefix := range trimPrefixes { + if strings.HasPrefix(name, prefix) { + return strings.TrimPrefix(name, prefix) + } + } + return name +} + +func CPubName(name string) string { + if len(name) == 0 { + return name + } + toCamelCase := func(s string) string { + parts := strings.Split(s, "_") + for i := 0; i < len(parts); i++ { + if len(parts[i]) > 0 { + parts[i] = strings.ToUpper(parts[i][:1]) + parts[i][1:] + } + } + return strings.Join(parts, "") + } + if name[0] == '_' { + i := 0 + for i < len(name) && name[i] == '_' { + i++ + } + prefix := name[:i] + return "X" + prefix + toCamelCase(name[i:]) + } + return toCamelCase(name) +} + +// /path/to/foo.h -> foo.go +// /path/to/_intptr.h -> X_intptr.go +func HeaderFileToGo(incPath string) string { + _, fileName := filepath.Split(incPath) + ext := filepath.Ext(fileName) + if len(ext) > 0 { + fileName = strings.TrimSuffix(fileName, ext) + } + if strings.HasPrefix(fileName, "_") { + fileName = "X" + fileName + } + return fileName + ".go" +} diff --git a/cmd/gogensig/convert/package.go b/cmd/gogensig/convert/package.go new file mode 100644 index 0000000..8e3ebdc --- /dev/null +++ b/cmd/gogensig/convert/package.go @@ -0,0 +1,616 @@ +package convert + +import ( + "bytes" + "fmt" + "go/token" + "go/types" + "log" + "os" + "path/filepath" + "regexp" + + "github.com/goplus/gogen" + "github.com/goplus/llgo/chore/_xtool/llcppsymg/config/cfgparse" + cfg "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/gogensig/convert/deps" + "github.com/goplus/llgo/chore/gogensig/convert/names" + "github.com/goplus/llgo/chore/llcppg/ast" + cppgtypes "github.com/goplus/llgo/chore/llcppg/types" +) + +const ( + DbgFlagAll = 1 +) + +var ( + debug bool +) + +func SetDebug(flags int) { + if flags != 0 { + debug = true + } +} + +type Package struct { + name string // package name + p *gogen.Package // package writer + conf *PackageConfig // package config + cvt *TypeConv // package type convert + curFile *HeaderFile // current processing c header file. + incomplete map[string]*gogen.TypeDecl +} + +type PackageConfig struct { + PkgPath string + Name string + OutputDir string + SymbolTable *cfg.SymbolTable + GenConf *gogen.Config + CppgConf *cppgtypes.Config + Public map[string]string +} + +func (p *PackageConfig) GetGoName(name string, inCurPkg bool) string { + goName, ok := p.Public[name] + if ok { + return goName + } + if inCurPkg { + name = names.RemovePrefixedName(name, p.CppgConf.TrimPrefixes) + } + return names.CPubName(name) +} + +func (p *PackageConfig) GetIncPaths() ([]string, error) { + cflags := cfgparse.ParseCFlags(p.CppgConf.CFlags) + incPaths, _, err := cflags.GenHeaderFilePaths(p.CppgConf.Include) + return incPaths, err +} + +// When creating a new package for conversion, a Go file named after the package is generated by default. +// If SetCurFile is not called, all type conversions will be written to this default Go file. +func NewPackage(config *PackageConfig) *Package { + p := &Package{ + p: gogen.NewPackage(config.PkgPath, config.Name, config.GenConf), + name: config.Name, + conf: config, + incomplete: make(map[string]*gogen.TypeDecl), + } + clib := p.p.Import("github.com/goplus/llgo/c") + typeMap := NewBuiltinTypeMapWithPkgRefS(clib, p.p.Unsafe()) + p.cvt = NewConv(&TypeConfig{ + Types: p.p.Types, + TypeMap: typeMap, + SymbolTable: config.SymbolTable, + Package: p, + }) + p.SetCurFile(p.Name(), "", false, false, false) + return p +} + +// get current pkg's include file +func (p *Package) GetIncPaths() []string { + incPaths, err := p.conf.GetIncPaths() + if err != nil { + log.Println("failed to gen include paths: \n", err.Error()) + } + return incPaths +} + +func (p *Package) SetCurFile(file string, incPath string, isHeaderFile bool, inCurPkg bool, isSys bool) error { + curHeaderFile, err := NewHeaderFile(file, incPath, isHeaderFile, inCurPkg, isSys) + if err != nil { + return err + } + p.curFile = curHeaderFile + fileName := p.curFile.ToGoFileName() + if debug { + log.Printf("SetCurFile: %s File in Current Package: %v\n", fileName, inCurPkg) + } + if _, err := p.p.SetCurFile(fileName, true); err != nil { + return fmt.Errorf("fail to set current file %s\n%w", file, err) + } + p.p.Unsafe().MarkForceUsed(p.p) + return nil +} + +func (p *Package) GetGenPackage() *gogen.Package { + return p.p +} + +func (p *Package) GetOutputDir() string { + return p.conf.OutputDir +} + +func (p *Package) Name() string { + return p.name +} + +func (p *Package) GetTypeConv() *TypeConv { + return p.cvt +} + +// todo(zzy):refine logic +func (p *Package) linkLib(lib string) error { + if lib == "" { + return fmt.Errorf("empty lib name") + } + linkString := fmt.Sprintf("link: %s;", lib) + p.p.CB().NewConstStart(types.Typ[types.String], "LLGoPackage").Val(linkString).EndInit(1) + return nil +} + +func (p *Package) NewFuncDecl(funcDecl *ast.FuncDecl) error { + skip, anony, err := p.cvt.handleSysType(funcDecl.Name, funcDecl.Loc, p.curFile.sysIncPath) + if skip { + if debug { + log.Printf("NewFuncDecl: %v is a function of system header file\n", funcDecl.Name) + } + return err + } + if debug { + log.Printf("NewFuncDecl: %v\n", funcDecl.Name) + } + if anony { + return fmt.Errorf("anonymous function not supported") + } + + goFuncName, err := p.cvt.LookupSymbol(cfg.MangleNameType(funcDecl.MangledName)) + if err != nil { + // not gen the function not in the symbolmap + return err + } + if obj := p.p.Types.Scope().Lookup(goFuncName); obj != nil { + return fmt.Errorf("function %s already defined", goFuncName) + } + sig, err := p.cvt.ToSignature(funcDecl.Type) + if err != nil { + return err + } + decl := p.p.NewFuncDecl(token.NoPos, string(goFuncName), sig) + doc := CommentGroup(funcDecl.Doc) + doc.AddCommentGroup(NewFuncDocComments(funcDecl.Name.Name, string(goFuncName))) + decl.SetComments(p.p, doc.CommentGroup) + return nil +} + +// NewTypeDecl converts C/C++ type declarations to Go. +// Besides regular type declarations, it also supports: +// - Forward declarations: Pre-registers incomplete types for later definition +// - Self-referential types: Handles types that reference themselves (like linked lists) +func (p *Package) NewTypeDecl(typeDecl *ast.TypeDecl) error { + skip, anony, err := p.cvt.handleSysType(typeDecl.Name, typeDecl.Loc, p.curFile.sysIncPath) + if skip { + if debug { + log.Printf("NewTypeDecl: %s type of system header\n", typeDecl.Name) + } + return err + } + if debug { + log.Printf("NewTypeDecl: %v\n", typeDecl.Name) + } + if anony { + if debug { + log.Println("NewTypeDecl:Skip a anonymous type") + } + return nil + } + + // every type name should be public + name, changed, err := p.DeclName(typeDecl.Name.Name, true) + if err != nil { + return err + } + + decl := p.handleTypeDecl(name, typeDecl, changed) + + if !p.cvt.inComplete(typeDecl.Type) { + if err := p.handleCompleteType(decl, typeDecl.Type, name); err != nil { + return err + } + } + return nil +} + +// handleTypeDecl creates a new type declaration or retrieves existing one +func (p *Package) handleTypeDecl(name string, typeDecl *ast.TypeDecl, changed bool) *gogen.TypeDecl { + var decl *gogen.TypeDecl + if !p.cvt.inComplete(typeDecl.Type) { + if existDecl, exists := p.incomplete[name]; exists { + decl = existDecl + } else { + decl = p.emptyTypeDecl(name, typeDecl.Doc) + } + } else { + decl = p.emptyTypeDecl(name, typeDecl.Doc) + p.incomplete[name] = decl + } + + if changed { + substObj(p.p.Types, p.p.Types.Scope(), typeDecl.Name.Name, decl.Type().Obj()) + } + return decl +} + +func (p *Package) handleCompleteType(decl *gogen.TypeDecl, typ *ast.RecordType, name string) error { + structType, err := p.cvt.RecordTypeToStruct(typ) + if err != nil { + decl.Delete() + return err + } + decl.InitType(p.p, structType) + delete(p.incomplete, name) + return nil +} + +func (p *Package) emptyTypeDecl(name string, doc *ast.CommentGroup) *gogen.TypeDecl { + typeBlock := p.p.NewTypeDefs() + typeBlock.SetComments(CommentGroup(doc).CommentGroup) + return typeBlock.NewType(name) +} + +func (p *Package) NewTypedefDecl(typedefDecl *ast.TypedefDecl) error { + skip, _, err := p.cvt.handleSysType(typedefDecl.Name, typedefDecl.Loc, p.curFile.sysIncPath) + if skip { + if debug { + log.Printf("NewTypedefDecl: %v is a typedef of system header file\n", typedefDecl.Name) + } + return err + } + if debug { + log.Printf("NewTypedefDecl: %v\n", typedefDecl.Name) + } + name, changed, err := p.DeclName(typedefDecl.Name.Name, true) + if err != nil { + return err + } + // todo(zzy): this block will be removed after https://github.com/goplus/llgo/pull/870 + if obj := p.p.Types.Scope().Lookup(name); obj != nil { + // for a typedef ,always appear same name like + // typedef struct foo { int a; } foo; + // For this typedef, we only need skip this + return nil + } + + genDecl := p.p.NewTypeDefs() + typ, err := p.ToType(typedefDecl.Type) + if err != nil { + return err + } + typeSpecdecl := genDecl.NewType(name) + typeSpecdecl.InitType(p.p, typ) + if _, ok := typ.(*types.Signature); ok { + genDecl.SetComments(NewTypecDocComments()) + } + if changed { + substObj(p.p.Types, p.p.Types.Scope(), typedefDecl.Name.Name, typeSpecdecl.Type().Obj()) + } + return nil +} + +// Convert ast.Expr to types.Type +func (p *Package) ToType(expr ast.Expr) (types.Type, error) { + return p.cvt.ToType(expr) +} + +func (p *Package) NewTypedefs(name string, typ types.Type) *gogen.TypeDecl { + def := p.p.NewTypeDefs() + t := def.NewType(name) + t.InitType(def.Pkg(), typ) + def.Complete() + return t +} + +func (p *Package) NewEnumTypeDecl(enumTypeDecl *ast.EnumTypeDecl) error { + skip, _, err := p.cvt.handleSysType(enumTypeDecl.Name, enumTypeDecl.Loc, p.curFile.sysIncPath) + if skip { + if debug { + log.Printf("NewEnumTypeDecl: %v is a enum type of system header file\n", enumTypeDecl.Name) + } + return err + } + if debug { + log.Printf("NewEnumTypeDecl: %v\n", enumTypeDecl.Name) + } + enumType, enumTypeName, err := p.createEnumType(enumTypeDecl.Name) + if err != nil { + return err + } + if len(enumTypeDecl.Type.Items) > 0 { + err = p.createEnumItems(enumTypeDecl.Type.Items, enumType, enumTypeName) + if err != nil { + return err + } + } + return nil +} + +func (p *Package) createEnumType(enumName *ast.Ident) (types.Type, string, error) { + var name string + var changed bool + var err error + var t *gogen.TypeDecl + if enumName != nil { + name, changed, err = p.DeclName(enumName.Name, true) + if err != nil { + return nil, "", fmt.Errorf("enum type %s already defined", enumName.Name) + } + } + enumType := p.cvt.ToDefaultEnumType() + if name != "" { + t = p.NewTypedefs(name, enumType) + enumType = p.p.Types.Scope().Lookup(name).Type() + } + if changed { + substObj(p.p.Types, p.p.Types.Scope(), enumName.Name, t.Type().Obj()) + } + return enumType, name, nil +} + +func (p *Package) createEnumItems(items []*ast.EnumItem, enumType types.Type, enumTypeName string) error { + constDefs := p.p.NewConstDefs(p.p.Types.Scope()) + for _, item := range items { + var constName string + // maybe get a new name,because the after executed name,have some situation will found same name + if enumTypeName != "" { + constName = enumTypeName + "_" + item.Name.Name + } else { + constName = item.Name.Name + } + name, changed, err := p.DeclName(constName, false) + if err != nil { + return fmt.Errorf("enum item %s already defined %w", name, err) + } + val, err := Expr(item.Value).ToInt() + if err != nil { + return err + } + constDefs.New(func(cb *gogen.CodeBuilder) int { + cb.Val(val) + return 1 + }, 0, token.NoPos, enumType, name) + if changed { + if obj := p.p.Types.Scope().Lookup(name); obj != nil { + substObj(p.p.Types, p.p.Types.Scope(), item.Name.Name, obj) + } + } + } + return nil +} + +// Write generates a Go file based on the package content. +// The output file will be generated in a subdirectory named after the package within the outputDir. +// If outputDir is not provided, the current directory will be used. +// The header file name is the go file name. +// +// Files that are already processed in dependent packages will not be output. +func (p *Package) Write(headerFile string) error { + if p.curFile.isSys { + return nil + } + fileName := names.HeaderFileToGo(headerFile) + filePath := filepath.Join(p.GetOutputDir(), fileName) + if debug { + log.Printf("Write HeaderFile [%s] from gogen:[%s] to [%s]\n", headerFile, fileName, filePath) + } + return p.writeToFile(fileName, filePath) +} + +func (p *Package) WriteLinkFile() (string, error) { + fileName := p.name + "_autogen_link.go" + filePath := filepath.Join(p.GetOutputDir(), fileName) + p.p.SetCurFile(fileName, true) + err := p.linkLib(p.conf.CppgConf.Libs) + if debug { + log.Printf("Write LinkFile [%s] from gogen:[%s] to [%s]\n", fileName, fileName, filePath) + } + if err != nil { + return "", fmt.Errorf("failed to link lib: %w", err) + } + if err := p.writeToFile(fileName, filePath); err != nil { + return "", fmt.Errorf("failed to write file: %w", err) + } + return filePath, nil +} + +// WriteDefaultFileToBuffer writes the content of the default Go file to a buffer. +// The default file is named after the package (p.Name() + ".go"). +// This method is particularly useful for testing type outputs, especially in package tests +// where there typically isn't (and doesn't need to be) a corresponding header file. +// Before calling SetCurFile, all type creations are written to this default gogen file. +// It allows for easy inspection of generated types without the need for actual file I/O. +func (p *Package) WriteDefaultFileToBuffer() (*bytes.Buffer, error) { + return p.WriteToBuffer(p.Name() + ".go") +} + +// Write the corresponding files in gogen package to the file +func (p *Package) writeToFile(genFName string, filePath string) error { + buf, err := p.WriteToBuffer(genFName) + if err != nil { + return err + } + return os.WriteFile(filePath, buf.Bytes(), 0644) +} + +// Write the corresponding files in gogen package to the buffer +func (p *Package) WriteToBuffer(genFName string) (*bytes.Buffer, error) { + for _, decl := range p.incomplete { + decl.InitType(p.p, types.NewStruct(p.cvt.defaultRecordField(), nil)) + } + p.incomplete = make(map[string]*gogen.TypeDecl, 0) + buf := new(bytes.Buffer) + err := p.p.WriteTo(buf, genFName) + if err != nil { + return nil, fmt.Errorf("failed to write to buffer: %w", err) + } + return buf, nil +} + +func (p *Package) WritePubFile() error { + return cfg.WritePubFile(filepath.Join(p.GetOutputDir(), "llcppg.pub"), p.conf.Public) +} + +func (p *Package) getAllDepPkgs(deps []*deps.CPackage) []string { + allDepIncs := make([]string, 0) + scope := p.p.Types.Scope() + for _, dep := range deps { + allDepIncs = append(allDepIncs, dep.StdIncs...) + depPkg := p.p.Import(dep.Path) + for cName, pubGoName := range dep.Pubs { + if pubGoName == "" { + pubGoName = cName + } + if obj := depPkg.TryRef(pubGoName); obj != nil { + var preObj types.Object + if pubGoName == cName { + preObj = obj + } else { + preObj = gogen.NewSubst(token.NoPos, p.p.Types, cName, obj) + } + if old := scope.Insert(preObj); old != nil { + log.Printf("conflicted name `%v` in %v, previous definition is %v\n", pubGoName, dep.Path, old) + } + } + } + } + return allDepIncs +} + +// For a decl name, if it's a current package, remove the prefixed name +// For a decl name, it should be unique +// todo(zzy): not current converter package file,need not remove prefixed name +func (p *Package) DeclName(name string, collect bool) (pubName string, changed bool, err error) { + originName := name + name = p.conf.GetGoName(name, p.curFile.inCurPkg) + // if the type is incomplete,it's ok to have the same name + if obj := p.p.Types.Scope().Lookup(name); obj != nil && p.incomplete[name] == nil { + return "", false, fmt.Errorf("type %s already defined,original name is %s", name, originName) + } + changed = name != originName + if collect && p.curFile.inCurPkg { + if changed { + p.conf.Public[originName] = name + } else { + p.conf.Public[originName] = "" + } + } + return name, changed, nil +} + +// AllDepIncs returns all std include paths of dependent packages +func (p *Package) AllDepIncs() []string { + deps, err := deps.LoadDeps(p.conf.OutputDir, p.conf.CppgConf.Deps) + if err != nil { + log.Println("failed to load deps: \n", err.Error()) + } + return p.getAllDepPkgs(deps) +} + +type PkgMapping struct { + Pattern string + Package string +} + +const ( + LLGO_C = "github.com/goplus/llgo/c" + LLGO_SYSTEM = "github.com/goplus/llgo/c/system" + LLGO_TIME = "github.com/goplus/llgo/c/time" + LLGO_MATH = "github.com/goplus/llgo/c/math" + LLGO_I18N = "github.com/goplus/llgo/c/i18n" + LLGO_COMPLEX = "github.com/goplus/llgo/c/math/cmplx" + + LLGO_PTHREAD = "github.com/goplus/llgo/c/pthread" + LLGO_UNIX_NET = "github.com/goplus/llgo/c/unix/net" +) + +// IncPathToPkg determines the Go package for a given C include path. +// +// According to the C language specification, when including a standard library, +// such as stdio.h, certain declarations must be provided (e.g., FILE type). +// However, these types don't have to be declared in the header file itself. +// On MacOS, for example, the actual declaration exists in _stdio.h. Therefore, +// each standard library header file can be viewed as defining an interface, +// independent of its implementation. +// +// In our current requirements, the matching follows this order: +// 1. First match standard library interface headers (like stdio.h, stdint.h) +// which define required types and functions +// 2. Then match implementation headers (like _stdio.h, sys/_types/_int8_t.h) +// which contain the actual type definitions +// +// For example: +// - stdio.h as interface, specifies that FILE type must be provided +// - _stdio.h as implementation, provides the actual FILE definition on MacOS +func IncPathToPkg(incPath string) (pkg string, isDefault bool) { + pkgMappings := []PkgMapping{ + // c std + {Pattern: `(^|[^a-zA-Z0-9])stdint[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])stddef[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])stdio[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])stdlib[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])string[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])stdbool[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])stdarg[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])limits[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])ctype[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])uchar[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])wchar[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])wctype[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])inttypes[^a-zA-Z0-9]`, Package: LLGO_C}, + + {Pattern: `(^|[^a-zA-Z0-9])signal[^a-zA-Z0-9]`, Package: LLGO_SYSTEM}, + {Pattern: `(^|[^a-zA-Z0-9])setjmp[^a-zA-Z0-9]`, Package: LLGO_SYSTEM}, + {Pattern: `(^|[^a-zA-Z0-9])assert[^a-zA-Z0-9]`, Package: LLGO_SYSTEM}, + {Pattern: `(^|[^a-zA-Z0-9])stdalign[^a-zA-Z0-9]`, Package: LLGO_SYSTEM}, + + {Pattern: `(^|[^a-zA-Z0-9])math[^a-zA-Z0-9]`, Package: LLGO_MATH}, + {Pattern: `(^|[^a-zA-Z0-9])fenv[^a-zA-Z0-9]`, Package: LLGO_MATH}, + {Pattern: `(^|[^a-zA-Z0-9])complex[^a-zA-Z0-9]`, Package: LLGO_COMPLEX}, + + {Pattern: `(^|[^a-zA-Z0-9])time[^a-zA-Z0-9]`, Package: LLGO_TIME}, + + {Pattern: `(^|[^a-zA-Z0-9])pthread[^a-zA-Z0-9]`, Package: LLGO_PTHREAD}, + + {Pattern: `(^|[^a-zA-Z0-9])locale[^a-zA-Z0-9]`, Package: LLGO_I18N}, + + //c posix + {Pattern: `(^|[^a-zA-Z0-9])socket[^a-zA-Z0-9]`, Package: LLGO_UNIX_NET}, + {Pattern: `(^|[^a-zA-Z0-9])arpa[^a-zA-Z0-9]`, Package: LLGO_UNIX_NET}, + {Pattern: `(^|[^a-zA-Z0-9])netinet6?[^a-zA-Z0-9]`, Package: LLGO_UNIX_NET}, + {Pattern: `(^|[^a-zA-Z0-9])net[^a-zA-Z0-9]`, Package: LLGO_UNIX_NET}, + + // impl file + {Pattern: `_int\d+_t`, Package: LLGO_C}, + {Pattern: `_uint\d+_t`, Package: LLGO_C}, + {Pattern: `_size_t`, Package: LLGO_C}, + {Pattern: `_intptr_t`, Package: LLGO_C}, + {Pattern: `_uintptr_t`, Package: LLGO_C}, + {Pattern: `_ptrdiff_t`, Package: LLGO_C}, + + {Pattern: `malloc`, Package: LLGO_C}, + {Pattern: `alloc`, Package: LLGO_C}, + + {Pattern: `(^|[^a-zA-Z0-9])clock_t[^a-zA-Z0-9]`, Package: LLGO_TIME}, + {Pattern: `(^|[^a-zA-Z0-9])tm[^a-zA-Z0-9]`, Package: LLGO_TIME}, + + // before must the special type.h such as _pthread_types.h .... + {Pattern: `\w+_t[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])types[^a-zA-Z0-9]`, Package: LLGO_C}, + {Pattern: `(^|[^a-zA-Z0-9])sys[^a-zA-Z0-9]`, Package: LLGO_SYSTEM}, + + // {Pattern: `(^|[^a-zA-Z0-9])strings\.h$`, Package: LLGO_C}, + } + + for _, mapping := range pkgMappings { + matched, err := regexp.MatchString(mapping.Pattern, incPath) + if err != nil { + panic(err) + } + if matched { + return mapping.Package, false + } + } + return LLGO_C, true +} diff --git a/cmd/gogensig/convert/package_test.go b/cmd/gogensig/convert/package_test.go new file mode 100644 index 0000000..6846794 --- /dev/null +++ b/cmd/gogensig/convert/package_test.go @@ -0,0 +1,1908 @@ +package convert_test + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/goplus/gogen" + "github.com/goplus/llgo/chore/gogensig/cmp" + cfg "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/gogensig/convert" + "github.com/goplus/llgo/chore/gogensig/convert/names" + "github.com/goplus/llgo/chore/llcppg/ast" + cppgtypes "github.com/goplus/llgo/chore/llcppg/types" +) + +func init() { + convert.SetDebug(convert.DbgFlagAll) +} + +func TestUnionDecl(t *testing.T) { + testCases := []genDeclTestCase{ + /* + union u + { + int a; + long b; + long c; + bool f; + }; + */ + { + name: "union u{int a; long b; long c; bool f;};", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "u"}, + Type: &ast.RecordType{ + Tag: ast.Union, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{ + {Name: "a"}, + }, + Type: &ast.BuiltinType{ + Kind: ast.Int}, + }, + { + Names: []*ast.Ident{ + {Name: "b"}, + }, + Type: &ast.BuiltinType{ + Kind: ast.Int, + Flags: ast.Long, + }, + }, + { + Names: []*ast.Ident{ + {Name: "c"}, + }, + Type: &ast.BuiltinType{ + Kind: ast.Int, + Flags: ast.Long, + }, + }, + { + Names: []*ast.Ident{ + {Name: "f"}, + }, + Type: &ast.BuiltinType{ + Kind: ast.Bool, + }, + }, + }, + }, + }, + }, + expected: `package testpkg +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) +type U struct { + B c.Long +}`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testGenDecl(t, tc) + }) + } +} + +func TestLinkFileOK(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_package_link") + if err != nil { + t.Fatalf("Failed to create temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: tempDir, + CppgConf: &cppgtypes.Config{ + Libs: "pkg-config --libs libcjson", + }, + }) + filePath, _ := pkg.WriteLinkFile() + _, err = os.Stat(filePath) + if os.IsNotExist(err) { + t.FailNow() + } +} + +func TestLinkFileFail(t *testing.T) { + + t.Run("not link lib", func(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_package_link") + if err != nil { + t.Fatalf("Failed to create temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: tempDir, + CppgConf: &cppgtypes.Config{}, + }) + + _, err = pkg.WriteLinkFile() + if err == nil { + t.FailNow() + } + }) + t.Run("no permission", func(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_package_link") + if err != nil { + t.Fatalf("Failed to create temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: tempDir, + CppgConf: &cppgtypes.Config{ + Libs: "${pkg-config --libs libcjson}", + }, + }) + err = os.Chmod(filepath.Join(tempDir), 0555) + if err != nil { + t.Fatalf("Failed to change directory permissions: %v", err) + } + defer os.Chmod(filepath.Join(tempDir), 0755) + _, err = pkg.WriteLinkFile() + if err == nil { + t.FailNow() + } + }) + +} + +func TestToType(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: "", + }) + + testCases := []struct { + name string + input *ast.BuiltinType + expected string + }{ + {"Void", &ast.BuiltinType{Kind: ast.Void}, "[0]byte"}, + {"Bool", &ast.BuiltinType{Kind: ast.Bool}, "bool"}, + {"Char_S", &ast.BuiltinType{Kind: ast.Char, Flags: ast.Signed}, "int8"}, + {"Char_U", &ast.BuiltinType{Kind: ast.Char, Flags: ast.Unsigned}, "int8"}, + {"WChar", &ast.BuiltinType{Kind: ast.WChar}, "int16"}, + {"Char16", &ast.BuiltinType{Kind: ast.Char16}, "int16"}, + {"Char32", &ast.BuiltinType{Kind: ast.Char32}, "int32"}, + {"Short", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Short}, "int16"}, + {"UShort", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Short | ast.Unsigned}, "uint16"}, + {"Int", &ast.BuiltinType{Kind: ast.Int}, "github.com/goplus/llgo/c.Int"}, + {"UInt", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Unsigned}, "github.com/goplus/llgo/c.Uint"}, + {"Long", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long}, "github.com/goplus/llgo/c.Long"}, + {"ULong", &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long | ast.Unsigned}, "github.com/goplus/llgo/c.Ulong"}, + {"LongLong", &ast.BuiltinType{Kind: ast.Int, Flags: ast.LongLong}, "github.com/goplus/llgo/c.LongLong"}, + {"ULongLong", &ast.BuiltinType{Kind: ast.Int, Flags: ast.LongLong | ast.Unsigned}, "github.com/goplus/llgo/c.UlongLong"}, + {"Float", &ast.BuiltinType{Kind: ast.Float}, "float32"}, + {"Double", &ast.BuiltinType{Kind: ast.Float, Flags: ast.Double}, "float64"}, + {"ComplexFloat", &ast.BuiltinType{Kind: ast.Complex}, "complex64"}, + {"ComplexDouble", &ast.BuiltinType{Kind: ast.Complex, Flags: ast.Double}, "complex128"}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, _ := pkg.ToType(tc.input) + if result != nil && result.String() != tc.expected { + t.Errorf("unexpected result:%s expected:%s", result.String(), tc.expected) + } + }) + } +} + +func TestNewPackage(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{}) + comparePackageOutput(t, pkg, ` + package testpkg + import _ "unsafe" + `) +} + +func TestPackageWrite(t *testing.T) { + verifyGeneratedFile := func(t *testing.T, expectedFilePath string) { + t.Helper() + if _, err := os.Stat(expectedFilePath); os.IsNotExist(err) { + t.Fatalf("Expected output file does not exist: %s", expectedFilePath) + } + + content, err := os.ReadFile(expectedFilePath) + if err != nil { + t.Fatalf("Unable to read generated file: %v", err) + } + + expectedContent := "package testpkg" + if !strings.Contains(string(content), expectedContent) { + t.Errorf("Generated file content does not match expected.\nExpected:\n%s\nActual:\n%s", expectedContent, string(content)) + } + } + + incPath := "mock_header.h" + filePath := filepath.Join("/path", "to", incPath) + genPath := names.HeaderFileToGo(filePath) + + t.Run("OutputToTempDir", func(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_package_write") + if err != nil { + t.Fatalf("Failed to create temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: tempDir, + }) + pkg.SetCurFile(filePath, incPath, true, true, false) + err = pkg.Write(filePath) + if err != nil { + t.Fatalf("Write method failed: %v", err) + } + + expectedFilePath := filepath.Join(tempDir, genPath) + verifyGeneratedFile(t, expectedFilePath) + }) + + t.Run("OutputToCurrentDir", func(t *testing.T) { + currentDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + testpkgDir := filepath.Join(currentDir, "testpkg") + if err := os.MkdirAll(testpkgDir, 0755); err != nil { + t.Fatalf("Failed to create testpkg directory: %v", err) + } + + defer func() { + // Clean up generated files and directory + os.RemoveAll(testpkgDir) + }() + + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: testpkgDir, + }) + pkg.SetCurFile(filePath, incPath, true, true, false) + err = pkg.Write(filePath) + if err != nil { + t.Fatalf("Write method failed: %v", err) + } + + expectedFilePath := filepath.Join(testpkgDir, genPath) + verifyGeneratedFile(t, expectedFilePath) + }) + + t.Run("InvalidOutputDir", func(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: "/nonexistent/directory", + }) + err := pkg.Write(incPath) + if err == nil { + t.Fatal("Expected an error for invalid output directory, but got nil") + } + }) + + t.Run("UnwritableOutputDir", func(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test_package_write_unwritable") + if err != nil { + t.Fatalf("Failed to create temporary directory: %v", err) + } + defer os.RemoveAll(tempDir) + + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: tempDir, + }) + + // read-only + err = os.Chmod(tempDir, 0555) + defer os.Chmod(tempDir, 0755) + if err != nil { + t.Fatalf("Failed to change directory permissions: %v", err) + } + + err = pkg.Write(incPath) + if err == nil { + t.Fatal("Expected an error for invalid output directory, but got nil") + } + }) +} + +/* + func TestPreparseOutputDir(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("no permission folder: no error?") + } + }() + convert.NewPackage(&convert.PackageConfig{ + PkgPath: ".", + Name: "testpkg", + GenConf: &gogen.Config{}, + OutputDir: "invalid\x00path", + }) + } +*/ +func TestFuncDecl(t *testing.T) { + testCases := []genDeclTestCase{ + { + name: "empty func", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: nil, + Ret: &ast.BuiltinType{Kind: ast.Void}, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expected: ` +package testpkg +import _ "unsafe" +//go:linkname Foo C.foo +func Foo()`, + }, + { + name: "variadic func", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + {Type: &ast.Variadic{}}, + }, + }, + Ret: &ast.BuiltinType{Kind: ast.Void}, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expected: ` +package testpkg +import _ "unsafe" +//go:linkname Foo C.foo +func Foo(__llgo_va_list ...interface{})`, + }, + { + name: "func not in symbol table", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: nil, + Ret: nil, + }, + }, + expectedErr: "symbol not found", + }, + { + name: "invalid function type", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "invalidFunc"}, + MangledName: "invalidFunc", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.BuiltinType{Kind: ast.Bool, Flags: ast.Long}, // invalid + }, + }, + }, + Ret: nil, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "invalidFunc", + MangleName: "invalidFunc", + GoName: "InvalidFunc", + }, + }, + expectedErr: "not found in type map", + }, + { + name: "explict void return", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: nil, + Ret: &ast.BuiltinType{Kind: ast.Void}, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expected: ` +package testpkg +import _ "unsafe" +//go:linkname Foo C.foo +func Foo()`, + }, + { + name: "builtin type", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{ + {Name: "a"}, + }, + Type: &ast.BuiltinType{ + Kind: ast.Int, + Flags: ast.Short | ast.Unsigned}, + }, + { + Names: []*ast.Ident{ + {Name: "b"}, + }, + Type: &ast.BuiltinType{ + Kind: ast.Bool, + }, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: ast.Float, + Flags: ast.Double, + }, + }, + }, + + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expected: ` +package testpkg +import _ "unsafe" +//go:linkname Foo C.foo +func Foo(a uint16, b bool) float64`, + }, + { + name: "c builtin type", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Unsigned}, + }, + { + Names: []*ast.Ident{{Name: "b"}}, + Type: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long}, + }, + }, + }, + Ret: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long | ast.Unsigned}, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) + +//go:linkname Foo C.foo +func Foo(a c.Uint, b c.Long) c.Ulong +`, + }, + { + name: "basic decl with c type", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Unsigned}, + }, + { + Names: []*ast.Ident{{Name: "b"}}, + Type: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long}, + }, + }, + }, + Ret: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long | ast.Unsigned}, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) + +//go:linkname Foo C.foo +func Foo(a c.Uint, b c.Long) c.Ulong +`, + }, + { + name: "pointer type", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Unsigned}, + }, + }, + { + Names: []*ast.Ident{{Name: "b"}}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Long}, + }, + }, + }, + }, + Ret: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: ast.Float, + Flags: ast.Double, + }, + }, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) + +//go:linkname Foo C.foo +func Foo(a *c.Uint, b *c.Long) *float64 +`, + }, + { + name: "void *", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{Kind: ast.Void}, + }, + }, + }, + }, + Ret: &ast.PointerType{ + X: &ast.BuiltinType{Kind: ast.Void}, + }, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expected: ` +package testpkg + +import "unsafe" + +//go:linkname Foo C.foo +func Foo(a unsafe.Pointer) unsafe.Pointer + `, + }, + { + name: "array", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + // Uint[] + Type: &ast.ArrayType{ + Elt: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Unsigned}, + }, + }, + { + Names: []*ast.Ident{{Name: "b"}}, + // Double[3] + Type: &ast.ArrayType{ + Elt: &ast.BuiltinType{Kind: ast.Float, Flags: ast.Double}, + Len: &ast.BasicLit{Kind: ast.IntLit, Value: "3"}, + }, + }, + }, + }, + Ret: &ast.ArrayType{ + // char[3][4] + Elt: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Signed, + }, + Len: &ast.BasicLit{Kind: ast.IntLit, Value: "4"}, + }, + Len: &ast.BasicLit{Kind: ast.IntLit, Value: "3"}, + }, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + cppgconf: &cppgtypes.Config{ + Name: "testpkg", + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) + +//go:linkname Foo C.foo +func Foo(a *c.Uint, b *float64) **int8 + `, + }, + { + name: "error array param", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.ArrayType{ + Elt: &ast.BuiltinType{Kind: ast.Int, Flags: ast.Double}, + }, + }, + }, + }, + Ret: nil, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expectedErr: "error convert elem type", + }, + { + name: "error return type", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: nil, + Ret: &ast.BuiltinType{Kind: ast.Bool, Flags: ast.Double}, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expectedErr: "error convert return type", + }, + { + name: "error nil param", + decl: &ast.FuncDecl{ + Name: &ast.Ident{Name: "foo"}, + MangledName: "foo", + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + nil, + }, + }, + Ret: nil, + }, + }, + symbs: []cfg.SymbolEntry{ + { + CppName: "foo", + MangleName: "foo", + GoName: "Foo", + }, + }, + expectedErr: "unexpected nil field", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testGenDecl(t, tc) + }) + } +} + +func TestStructDecl(t *testing.T) { + testCases := []genDeclTestCase{ + // struct Foo {} + { + name: "empty struct", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: nil, + }, + }, + expected: ` +package testpkg + +import _ "unsafe" + +type Foo struct { +}`, + }, + // invalid struct type + { + name: "invalid struct type", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "InvalidStruct"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "invalidField"}}, + Type: &ast.BuiltinType{Kind: ast.Bool, Flags: ast.Long}, + }, + }, + }, + }, + }, + expectedErr: "not found in type map", + }, + // struct Foo { int a; double b; bool c; } + { + name: "struct field builtin type", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.BuiltinType{ + Kind: ast.Int, + }, + }, + { + Names: []*ast.Ident{{Name: "b"}}, + Type: &ast.BuiltinType{ + Kind: ast.Float, + Flags: ast.Double, + }, + }, + { + Names: []*ast.Ident{{Name: "c"}}, + Type: &ast.BuiltinType{ + Kind: ast.Bool, + }, + }, + }, + }, + }, + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) + +type Foo struct { + A c.Int + B float64 + C bool +}`, + }, + // struct Foo { int* a; double* b; bool* c;void* d; } + { + name: "struct field pointer", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: ast.Int, + }, + }, + }, + { + Names: []*ast.Ident{{Name: "b"}}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: ast.Float, + Flags: ast.Double, + }}, + }, + { + Names: []*ast.Ident{{Name: "c"}}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: ast.Bool, + }, + }, + }, + { + Names: []*ast.Ident{{Name: "d"}}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: ast.Void, + }, + }, + }, + }, + }, + }, + }, + expected: ` +package testpkg + +import ( + "github.com/goplus/llgo/c" + "unsafe" +) + +type Foo struct { + A *c.Int + B *float64 + C *bool + D unsafe.Pointer +}`}, + // struct Foo { char a[4]; int b[3][4]; } + { + name: "struct array field", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Signed, + }, + Len: &ast.BasicLit{ + Kind: ast.IntLit, + Value: "4", + }, + }, + }, + { + Names: []*ast.Ident{{Name: "b"}}, + Type: &ast.ArrayType{ + Elt: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: ast.Int, + }, + Len: &ast.BasicLit{Kind: ast.IntLit, Value: "4"}, + }, + Len: &ast.BasicLit{Kind: ast.IntLit, Value: "3"}, + }, + }, + }, + }, + }, + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) + +type Foo struct { + A [4]int8 + B [3][4]c.Int +}`}, + { + name: "struct array field", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Signed, + }, + Len: &ast.BasicLit{ + Kind: ast.IntLit, + Value: "4", + }, + }, + }, + { + Names: []*ast.Ident{{Name: "b"}}, + Type: &ast.ArrayType{ + Elt: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: ast.Int, + }, + Len: &ast.BasicLit{Kind: ast.IntLit, Value: "4"}, + }, + Len: &ast.BasicLit{Kind: ast.IntLit, Value: "3"}, + }, + }, + }, + }, + }, + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) + +type Foo struct { + A [4]int8 + B [3][4]c.Int +}`}, + { + name: "anonymous struct", + decl: &ast.TypeDecl{ + Name: nil, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{}, + }, + }, + expected: ` +package testpkg +import _ "unsafe" + `}, + { + name: "struct array field without len", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Signed, + }, + }, + }, + }, + }, + }, + }, + expectedErr: "unsupport field with array without length", + }, + { + name: "struct array field without len", + decl: &ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Signed, + }, + Len: &ast.BuiltinType{Kind: ast.TypeKind(ast.Signed)}, //invalid + }, + }, + }, + }, + }, + }, + expectedErr: "can't determine the array length", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testGenDecl(t, tc) + }) + } +} + +func TestTypedefFunc(t *testing.T) { + testCases := []genDeclTestCase{ + // typedef int (*Foo) (int a, int b); + { + name: "typedef func", + decl: &ast.TypedefDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.PointerType{ + X: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.BuiltinType{ + Kind: ast.Int, + }, + Names: []*ast.Ident{{Name: "a"}}, + }, + { + Type: &ast.BuiltinType{ + Kind: ast.Int, + }, + Names: []*ast.Ident{{Name: "b"}}, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: ast.Int, + }, + }, + }, + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) +// llgo:type C +type Foo func(a c.Int, b c.Int) c.Int`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testGenDecl(t, tc) + }) + } +} + +// Test Redefine error +func TestRedef(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: "", + SymbolTable: cfg.CreateSymbolTable( + []cfg.SymbolEntry{ + {CppName: "Bar", MangleName: "Bar", GoName: "Bar"}, + }, + ), + }) + + flds := &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.BuiltinType{Kind: ast.Int}, + }, + }, + } + pkg.NewTypeDecl(&ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: flds, + }, + }) + + err := pkg.NewTypeDecl(&ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: flds, + }, + }) + if err == nil { + t.Fatal("Expect a redefine err") + } + + pkg.NewTypedefDecl(&ast.TypedefDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.Ident{Name: "Foo"}, + }) + + err = pkg.NewFuncDecl(&ast.FuncDecl{ + Name: &ast.Ident{Name: "Bar"}, + MangledName: "Bar", + Type: &ast.FuncType{ + Ret: &ast.BuiltinType{ + Kind: ast.Void, + }, + }, + }) + if err != nil { + t.Fatal("NewFuncDecl failed", err) + } + + err = pkg.NewFuncDecl(&ast.FuncDecl{ + Name: &ast.Ident{Name: "Bar"}, + MangledName: "Bar", + Type: &ast.FuncType{}, + }) + if err == nil { + t.Fatal("Expect a redefine err") + } + + err = pkg.NewEnumTypeDecl(&ast.EnumTypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.EnumType{}, + }) + + if err == nil { + t.Fatal("Expect a redefine err") + } + + err = pkg.NewEnumTypeDecl(&ast.EnumTypeDecl{ + Name: nil, + Type: &ast.EnumType{ + Items: []*ast.EnumItem{ + {Name: &ast.Ident{Name: "Foo"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "0"}}, + }, + }, + }) + + if err == nil { + t.Fatal("Expect a redefine err") + } + + var buf bytes.Buffer + err = pkg.GetGenPackage().WriteTo(&buf) + if err != nil { + t.Fatalf("WriteTo failed: %v", err) + } + + expect := ` +package testpkg + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type Foo struct { + c.Int +} +//go:linkname Bar C.Bar +func Bar() +` + comparePackageOutput(t, pkg, expect) +} + +func TestTypedef(t *testing.T) { + testCases := []genDeclTestCase{ + // typedef double DOUBLE; + { + name: "typedef double", + decl: &ast.TypedefDecl{ + Name: &ast.Ident{Name: "DOUBLE"}, + Type: &ast.BuiltinType{ + Kind: ast.Float, + Flags: ast.Double, + }, + }, + expected: ` +package testpkg + +import _ "unsafe" + +type DOUBLE float64`, + }, + // invalid typedef + { + name: "invalid typedef", + decl: &ast.TypedefDecl{ + Name: &ast.Ident{Name: "INVALID"}, + Type: &ast.BuiltinType{ + Kind: ast.Bool, + Flags: ast.Double, + }, + }, + expectedErr: "not found in type map", + }, + // typedef int INT; + { + name: "typedef int", + decl: &ast.TypedefDecl{ + Name: &ast.Ident{Name: "INT"}, + Type: &ast.BuiltinType{ + Kind: ast.Int, + }, + }, + expected: ` +package testpkg + +import ( +"github.com/goplus/llgo/c" +_ "unsafe" +) + +type INT c.Int + `, + }, + { + name: "typedef array", + decl: &ast.TypedefDecl{ + Name: &ast.Ident{Name: "name"}, + Type: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Signed, + }, + Len: &ast.BasicLit{Kind: ast.IntLit, Value: "5"}, + }, + }, + expected: ` +package testpkg + +import _ "unsafe" + +type Name [5]int8`, + }, + // typedef void* ctx; + { + name: "typedef pointer", + decl: &ast.TypedefDecl{ + Name: &ast.Ident{Name: "ctx"}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: ast.Void, + }, + }, + }, + expected: ` +package testpkg + +import "unsafe" + +type Ctx unsafe.Pointer`, + }, + + // typedef char* name; + { + name: "typedef pointer", + decl: &ast.TypedefDecl{ + Name: &ast.Ident{Name: "name"}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Signed, + }, + }, + }, + expected: ` +package testpkg +import _ "unsafe" +type Name *int8`, + }, + { + name: "typedef invalid pointer", + decl: &ast.TypedefDecl{ + Name: &ast.Ident{Name: "name"}, + Type: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Double, + }, + }, + }, + expectedErr: "error convert baseType", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testGenDecl(t, tc) + }) + } +} + +func TestEnumDecl(t *testing.T) { + testCases := []genDeclTestCase{ + { + name: "enum", + decl: &ast.EnumTypeDecl{ + Name: &ast.Ident{Name: "Color"}, + Type: &ast.EnumType{ + Items: []*ast.EnumItem{ + {Name: &ast.Ident{Name: "Red"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "0"}}, + {Name: &ast.Ident{Name: "Green"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "1"}}, + {Name: &ast.Ident{Name: "Blue"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "2"}}, + }, + }, + }, + expected: ` +package testpkg + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type Color c.Int +const ( + ColorRed Color = 0 + ColorGreen Color = 1 + ColorBlue Color = 2 +)`, + }, + { + name: "anonymous enum", + decl: &ast.EnumTypeDecl{ + Name: nil, + Type: &ast.EnumType{ + Items: []*ast.EnumItem{ + {Name: &ast.Ident{Name: "red"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "0"}}, + {Name: &ast.Ident{Name: "green"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "1"}}, + {Name: &ast.Ident{Name: "blue"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "2"}}, + }, + }, + }, + expected: ` +package testpkg + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +const ( + Red c.Int = 0 + Green c.Int = 1 + Blue c.Int = 2 +)`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testGenDecl(t, tc) + }) + } +} + +func TestIdentRefer(t *testing.T) { + t.Run("undef ident ref", func(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{}) + err := pkg.NewTypeDecl(&ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "notfound"}}, + Type: &ast.Ident{ + Name: "undefType", + }, + }, + }, + }, + }, + }) + compareError(t, err, "undefType not found") + }) + t.Run("undef tag ident ref", func(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{}) + err := pkg.NewTypeDecl(&ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "notfound"}}, + Type: &ast.TagExpr{ + Tag: ast.Class, + Name: &ast.Ident{ + Name: "undefType", + }, + }, + }, + }, + }, + }, + }) + compareError(t, err, "undefType not found") + }) + t.Run("type alias", func(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{ + CppgConf: &cppgtypes.Config{}, + }) + pkg.NewTypedefDecl(&ast.TypedefDecl{ + Name: &ast.Ident{Name: "int8_t"}, + Type: &ast.BuiltinType{ + Kind: ast.Char, + Flags: ast.Signed, + }, + }) + pkg.NewTypeDecl(&ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.Ident{ + Name: "int8_t", + }, + }, + }, + }, + }, + }) + comparePackageOutput(t, pkg, ` + package testpkg + import _ "unsafe" + type Int8T int8 + type Foo struct { + A Int8T + } + `) + }) +} + +func TestForwardDecl(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: "", + SymbolTable: cfg.CreateSymbolTable( + []cfg.SymbolEntry{ + {CppName: "Bar", MangleName: "Bar", GoName: "Bar"}, + }, + ), + }) + + // forward decl + err := pkg.NewTypeDecl(&ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{}, + }, + }) + + if err != nil { + t.Fatalf("NewTypeDecl failed: %v", err) + } + + // complete decl + err = pkg.NewTypeDecl(&ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "a"}}, + Type: &ast.BuiltinType{Kind: ast.Int}, + }, + }, + }, + }, + }) + + if err != nil { + t.Fatalf("NewTypeDecl failed: %v", err) + } + + expect := ` +package testpkg + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type Foo struct { + A c.Int +} +` + comparePackageOutput(t, pkg, expect) +} + +type genDeclTestCase struct { + name string + decl ast.Decl + symbs []cfg.SymbolEntry + cppgconf *cppgtypes.Config + expected string + expectedErr string +} + +func testGenDecl(t *testing.T, tc genDeclTestCase) { + t.Helper() + pkg := createTestPkg(t, &convert.PackageConfig{ + SymbolTable: cfg.CreateSymbolTable(tc.symbs), + CppgConf: tc.cppgconf, + }) + if pkg == nil { + t.Fatal("NewPackage failed") + } + var err error + switch d := tc.decl.(type) { + case *ast.TypeDecl: + err = pkg.NewTypeDecl(d) + case *ast.TypedefDecl: + err = pkg.NewTypedefDecl(d) + case *ast.FuncDecl: + err = pkg.NewFuncDecl(d) + case *ast.EnumTypeDecl: + err = pkg.NewEnumTypeDecl(d) + default: + t.Errorf("Unsupported declaration type: %T", tc.decl) + return + } + if tc.expectedErr != "" { + compareError(t, err, tc.expectedErr) + } else { + if err != nil { + t.Errorf("Declaration generation failed: %v", err) + } else { + comparePackageOutput(t, pkg, tc.expected) + } + } +} + +// compare error +func compareError(t *testing.T, err error, expectErr string) { + t.Helper() + if err == nil { + t.Errorf("Expected error containing %q, but got nil", expectErr) + } else if !strings.Contains(err.Error(), expectErr) { + t.Errorf("Expected error contain %q, but got %q", expectErr, err.Error()) + } +} + +func createTestPkg(t *testing.T, config *convert.PackageConfig) *convert.Package { + t.Helper() + if config.CppgConf == nil { + config.CppgConf = &cppgtypes.Config{} + } + if config.SymbolTable == nil { + config.SymbolTable = cfg.CreateSymbolTable([]cfg.SymbolEntry{}) + } + if config.CppgConf == nil { + config.CppgConf = &cppgtypes.Config{} + } + if config.SymbolTable == nil { + config.SymbolTable = cfg.CreateSymbolTable([]cfg.SymbolEntry{}) + } + pkg := convert.NewPackage(&convert.PackageConfig{ + PkgPath: ".", + Name: "testpkg", + GenConf: &gogen.Config{}, + OutputDir: config.OutputDir, + SymbolTable: config.SymbolTable, + CppgConf: config.CppgConf, + Public: make(map[string]string), + }) + if pkg == nil { + t.Fatal("NewPackage failed") + } + return pkg +} + +// compares the output of a gogen.Package with the expected +func comparePackageOutput(t *testing.T, pkg *convert.Package, expect string) { + t.Helper() + // For Test,The Test package's header filename same as package name + buf, err := pkg.WriteDefaultFileToBuffer() + if err != nil { + t.Fatalf("WriteTo failed: %v", err) + } + eq, diff := cmp.EqualStringIgnoreSpace(buf.String(), expect) + if !eq { + t.Error(diff) + } +} + +/** multiple package test **/ + +func TestTypeClean(t *testing.T) { + pkg := createTestPkg(t, &convert.PackageConfig{ + OutputDir: "", + SymbolTable: cfg.CreateSymbolTable( + []cfg.SymbolEntry{ + {CppName: "Func1", MangleName: "Func1", GoName: "Func1"}, + {CppName: "Func2", MangleName: "Func2", GoName: "Func2"}, + }, + ), + }) + + testCases := []struct { + addType func() + headerFile string + incPath string + newType string + }{ + { + addType: func() { + pkg.NewTypeDecl(&ast.TypeDecl{ + Name: &ast.Ident{Name: "Foo1"}, + Type: &ast.RecordType{Tag: ast.Struct}, + }) + }, + headerFile: "/path/to/file1.h", + incPath: "file1.h", + newType: "Foo1", + }, + { + addType: func() { + pkg.NewTypedefDecl(&ast.TypedefDecl{ + Name: &ast.Ident{Name: "Bar2"}, + Type: &ast.BuiltinType{Kind: ast.Int}, + }) + }, + headerFile: "/path/to/file2.h", + incPath: "file2.h", + newType: "Bar2", + }, + { + addType: func() { + pkg.NewFuncDecl(&ast.FuncDecl{ + Name: &ast.Ident{Name: "Func1"}, MangledName: "Func1", + Type: &ast.FuncType{Params: nil, Ret: &ast.BuiltinType{Kind: ast.Void}}, + }) + }, + headerFile: "/path/to/file3.h", + incPath: "file3.h", + newType: "Func1", + }, + } + + for i, tc := range testCases { + pkg.SetCurFile(tc.headerFile, tc.incPath, true, true, false) + tc.addType() + + goFileName := names.HeaderFileToGo(tc.headerFile) + buf, err := pkg.WriteToBuffer(goFileName) + if err != nil { + t.Fatal(err) + } + result := buf.String() + + if !strings.Contains(result, tc.newType) { + t.Errorf("Case %d: Generated type does not contain %s", i, tc.newType) + } + + for j := 0; j < i; j++ { + oldType := testCases[j].newType + if strings.Contains(result, oldType) { + t.Errorf("Case %d: Previously added type %s (from case %d) still exists", i, oldType, j) + } + } + } +} + +func TestHeaderFileToGo(t *testing.T) { + testCases := []struct { + name string + input string + expected string + }{ + { + name: "normal", + input: "/path/to/sys/dirent.h", + expected: "dirent.go", + }, + { + name: "sys", + input: "/path/to/sys/_pthread/_pthread_types.h", + expected: "X_pthread_types.go", + }, + { + name: "sys", + input: "/path/to/_types.h", + expected: "X_types.go", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := names.HeaderFileToGo(tc.input) + if result != tc.expected { + t.Errorf("Expected %s, but got %s", tc.expected, result) + } + }) + } +} + +func TestIncPathToPkg(t *testing.T) { + testCases := map[string]map[string][]string{ + // macos 14.0 + "darwin": { + convert.LLGO_C: []string{ + "alloc.h", + "_ctype.h", + "_stdio.h", + "_types.h", + "_types/_intmax_t.h", + "_types/_uint16_t.h", + "_types/_uint32_t.h", + "_types/_uint64_t.h", + "_types/_uint8_t.h", + "_types/_uintmax_t.h", + "_types/_wctrans_t.h", + "_types/_wctype_t.h", + "_wctype.h", + "arm/_types.h", + "arm/types.h", + "inttypes.h", + "malloc/_malloc.h", + "malloc/_malloc_type.h", + "secure/_stdio.h", + "stddef.h", + "stdint.h", + "stdio.h", + "stdlib.h", + "string.h", + "sys/_types.h", + "sys/_types/_ct_rune_t.h", + "sys/_types/_dev_t.h", + "sys/_types/_errno_t.h", + "sys/_types/_id_t.h", + "sys/_types/_int16_t.h", + "sys/_types/_int32_t.h", + "sys/_types/_int64_t.h", + "sys/_types/_int8_t.h", + "sys/_types/_intptr_t.h", + "sys/_types/_mbstate_t.h", + "sys/_types/_mode_t.h", + "sys/_types/_off_t.h", + "sys/_types/_pid_t.h", + "sys/_types/_ptrdiff_t.h", + "sys/_types/_rsize_t.h", + "sys/_types/_rune_t.h", + "sys/_types/_sigaltstack.h", + "sys/_types/_sigset_t.h", + "sys/_types/_size_t.h", + "sys/_types/_ssize_t.h", + // posix + // "sys/_types/_timespec.h", + // "sys/_types/_timeval.h", + "sys/_types/_u_int16_t.h", + "sys/_types/_u_int32_t.h", + "sys/_types/_u_int64_t.h", + "sys/_types/_u_int8_t.h", + "sys/_types/_ucontext.h", + "sys/_types/_uid_t.h", + "sys/_types/_uintptr_t.h", + "sys/_types/_va_list.h", + "sys/_types/_wchar_t.h", + "sys/_types/_wint_t.h", + "sys/stdio.h", + "wchar.h", + "wctype.h", + }, + convert.LLGO_PTHREAD: []string{ + "sys/_pthread/_pthread_attr_t.h", + "sys/_pthread/_pthread_types.h", + "sys/_pthread/_pthread_t.h", + }, + convert.LLGO_I18N: []string{ + "_locale.h", + "locale.h", + }, + convert.LLGO_SYSTEM: []string{ + "sys/signal.h", + "setjmp.h", + "signal.h", + "sys/errno.h", + "sys/resource.h", + "sys/wait.h", + "arm/signal.h", + }, + convert.LLGO_TIME: []string{ + "time.h", + "sys/_types/_time_t.h", + "sys/_types/_clock_t.h", + }, + convert.LLGO_UNIX_NET: []string{ + "sys/socket.h", + "arpa/inet.h", + "netinet6/in6.h", + "netinet/in.h", + "net/if.h", + "net/if_var.h", + }, + convert.LLGO_MATH: []string{ + "math.h", + "fenv.h", + }, + }, + } + for testVer, pkgMap := range testCases { + for expectPkg, incs := range pkgMap { + for _, inc := range incs { + if gotPkg, _ := convert.IncPathToPkg(inc); gotPkg != expectPkg { + t.Errorf("testVer: %s, inc: %s, expect: %s, got: %s", testVer, inc, expectPkg, gotPkg) + } + } + } + } +} diff --git a/cmd/gogensig/convert/sizes/sizes.go b/cmd/gogensig/convert/sizes/sizes.go new file mode 100644 index 0000000..f328a21 --- /dev/null +++ b/cmd/gogensig/convert/sizes/sizes.go @@ -0,0 +1,22 @@ +package sizes + +import ( + "go/types" + "runtime" +) + +var ( + std types.Sizes +) + +func init() { + if runtime.Compiler == "gopherjs" { + std = &types.StdSizes{WordSize: 4, MaxAlign: 4} + } else { + std = types.SizesFor(runtime.Compiler, runtime.GOARCH) + } +} + +func Sizeof(T types.Type) int64 { + return std.Sizeof(T) +} diff --git a/cmd/gogensig/convert/testdata/cjson/cJSON.go b/cmd/gogensig/convert/testdata/cjson/cJSON.go new file mode 100644 index 0000000..863ac8d --- /dev/null +++ b/cmd/gogensig/convert/testdata/cjson/cJSON.go @@ -0,0 +1,24 @@ +package cjson + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +type CJSON struct { + Next *CJSON + Prev *CJSON + Child *CJSON + Type c.Int + Valuestring *int8 + Valueint c.Int + Valuedouble float64 + String *int8 +} + +type CJSONHooks struct { + MallocFn func(c.SizeT) unsafe.Pointer + FreeFn func(unsafe.Pointer) +} +type CJSONBool c.Int diff --git a/cmd/gogensig/convert/testdata/cjson/llcppg.cfg b/cmd/gogensig/convert/testdata/cjson/llcppg.cfg new file mode 100644 index 0000000..ea84c21 --- /dev/null +++ b/cmd/gogensig/convert/testdata/cjson/llcppg.cfg @@ -0,0 +1,6 @@ +{ + "name": "cjsontestgen", + "cflags" :"$(pkg-config --cflags libcjson)", + "include": ["cJSON.h"], + "cplusplus":false +} diff --git a/cmd/gogensig/convert/testdata/cjson/llcppg.pub b/cmd/gogensig/convert/testdata/cjson/llcppg.pub new file mode 100644 index 0000000..3938bc1 --- /dev/null +++ b/cmd/gogensig/convert/testdata/cjson/llcppg.pub @@ -0,0 +1,3 @@ +cJSON CJSON +cJSON_Hooks CJSONHooks +cJSON_bool CJSONBool \ No newline at end of file diff --git a/cmd/gogensig/convert/type.go b/cmd/gogensig/convert/type.go new file mode 100644 index 0000000..eccafba --- /dev/null +++ b/cmd/gogensig/convert/type.go @@ -0,0 +1,379 @@ +/* +This file is used to convert type from ast type to types.Type +*/ +package convert + +import ( + "fmt" + "go/token" + "go/types" + "log" + "unsafe" + + "github.com/goplus/gogen" + "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/gogensig/convert/names" + "github.com/goplus/llgo/chore/gogensig/convert/sizes" + "github.com/goplus/llgo/chore/llcppg/ast" +) + +type HeaderInfo struct { + IncPath string // stdlib include path + Path string // full path +} + +type Header2Pkg struct { + Header *HeaderInfo + PkgPath string +} + +type TypeConv struct { + gogen.PkgRef + SysTypeLoc map[string]*HeaderInfo + SysTypePkg map[string]*Header2Pkg + symbolTable *config.SymbolTable // llcppg.symb.json + typeMap *BuiltinTypeMap + inParam bool // flag to indicate if currently processing a param + conf *TypeConfig +} + +type TypeConfig struct { + Package *Package + Types *types.Package + TypeMap *BuiltinTypeMap + SymbolTable *config.SymbolTable + TrimPrefixes []string +} + +func NewConv(conf *TypeConfig) *TypeConv { + typeConv := &TypeConv{ + symbolTable: conf.SymbolTable, + typeMap: conf.TypeMap, + conf: conf, + SysTypeLoc: make(map[string]*HeaderInfo), + SysTypePkg: make(map[string]*Header2Pkg), + } + typeConv.Types = conf.Types + return typeConv +} + +// Convert ast.Expr to types.Type +func (p *TypeConv) ToType(expr ast.Expr) (types.Type, error) { + switch t := expr.(type) { + case *ast.BuiltinType: + typ, err := p.typeMap.FindBuiltinType(*t) + return typ, err + case *ast.PointerType: + return p.handlePointerType(t) + case *ast.ArrayType: + return p.handleArrayType(t) + case *ast.FuncType: + return p.ToSignature(t) + case *ast.Ident, *ast.ScopingExpr, *ast.TagExpr: + return p.handleIdentRefer(expr) + case *ast.Variadic: + return types.NewSlice(gogen.TyEmptyInterface), nil + default: + return nil, fmt.Errorf("unsupported type: %T", expr) + } +} + +func (p *TypeConv) handleArrayType(t *ast.ArrayType) (types.Type, error) { + elemType, err := p.ToType(t.Elt) + if err != nil { + return nil, fmt.Errorf("error convert elem type: %w", err) + } + if p.inParam { + // array in the parameter,ignore the len,convert as pointer + return types.NewPointer(elemType), nil + } + + if t.Len == nil { + return nil, fmt.Errorf("%s", "unsupport field with array without length") + } + + len, err := Expr(t.Len).ToInt() + if err != nil { + return nil, fmt.Errorf("%s", "can't determine the array length") + } + + return types.NewArray(elemType, int64(len)), nil +} + +// - void* -> c.Pointer +// - Function pointers -> Function types (pointer removed) +// - Other cases -> Pointer to the base type +func (p *TypeConv) handlePointerType(t *ast.PointerType) (types.Type, error) { + baseType, err := p.ToType(t.X) + if err != nil { + return nil, fmt.Errorf("error convert baseType: %w", err) + } + // void * -> c.Pointer + // todo(zzy):alias visit the origin type unsafe.Pointer,c.Pointer is better + if p.typeMap.IsVoidType(baseType) { + return p.typeMap.CType("Pointer"), nil + } + if baseFuncType, ok := baseType.(*types.Signature); ok { + return baseFuncType, nil + } + return types.NewPointer(baseType), nil +} + +func (p *TypeConv) handleIdentRefer(t ast.Expr) (types.Type, error) { + lookup := func(name string) (types.Type, error) { + // For types defined in other packages, they should already be in current scope + // We don't check for types.Named here because the type returned from ConvertType + // for aliases like int8_t might be a built-in type (e.g., int8), + + // check if the type is a system type + obj, err := p.referSysType(name) + if err != nil { + return nil, err + } + if obj != nil { + return obj.Type(), nil + } + + obj = gogen.Lookup(p.Types.Scope(), name) + if obj == nil { + return nil, fmt.Errorf("%s not found", name) + } + return obj.Type(), nil + } + switch t := t.(type) { + case *ast.Ident: + typ, err := lookup(t.Name) + if err != nil { + return nil, fmt.Errorf("%s not found %w", t.Name, err) + } + return typ, nil + case *ast.ScopingExpr: + // todo(zzy) + case *ast.TagExpr: + // todo(zzy):scoping + if ident, ok := t.Name.(*ast.Ident); ok { + typ, err := lookup(ident.Name) + if err != nil { + return nil, fmt.Errorf("%s not found", ident.Name) + } + return typ, nil + } + // todo(zzy):scoping expr + } + return nil, fmt.Errorf("unsupported refer: %T", t) +} + +func (p *TypeConv) ToSignature(funcType *ast.FuncType) (*types.Signature, error) { + beforeInParam := p.inParam + p.inParam = true + defer func() { p.inParam = beforeInParam }() + params, variadic, err := p.fieldListToParams(funcType.Params) + if err != nil { + return nil, err + } + results, err := p.retToResult(funcType.Ret) + if err != nil { + return nil, err + } + return types.NewSignatureType(nil, nil, nil, params, results, variadic), nil +} + +// Convert ast.FieldList to types.Tuple (Function Param) +func (p *TypeConv) fieldListToParams(params *ast.FieldList) (*types.Tuple, bool, error) { + if params == nil { + return types.NewTuple(), false, nil + } + vars, err := p.fieldListToVars(params, false) + if err != nil { + return nil, false, err + } + variadic := false + if len(params.List) > 0 { + lastField := params.List[len(params.List)-1] + if _, ok := lastField.Type.(*ast.Variadic); ok { + variadic = true + } + } + return types.NewTuple(vars...), variadic, nil +} + +// Execute the ret in FuncType +func (p *TypeConv) retToResult(ret ast.Expr) (*types.Tuple, error) { + typ, err := p.ToType(ret) + if err != nil { + return nil, fmt.Errorf("error convert return type: %w", err) + } + if typ != nil && !p.typeMap.IsVoidType(typ) { + // in c havent multiple return + return types.NewTuple(types.NewVar(token.NoPos, p.Types, "", typ)), nil + } + return types.NewTuple(), nil +} + +// Convert ast.FieldList to []types.Var +func (p *TypeConv) fieldListToVars(params *ast.FieldList, isRecord bool) ([]*types.Var, error) { + var vars []*types.Var + if params == nil || params.List == nil { + return vars, nil + } + for _, field := range params.List { + fieldVar, err := p.fieldToVar(field, isRecord) + if err != nil { + return nil, err + } + if fieldVar != nil { + vars = append(vars, fieldVar) + } + } + return vars, nil +} + +// todo(zzy): use Unused [unsafe.Sizeof(0)]byte in the source code +func (p *TypeConv) defaultRecordField() []*types.Var { + return []*types.Var{ + types.NewVar(token.NoPos, p.Types, "Unused", types.NewArray(types.Typ[types.Byte], int64(unsafe.Sizeof(0)))), + } +} + +func (p *TypeConv) fieldToVar(field *ast.Field, isRecord bool) (*types.Var, error) { + if field == nil { + return nil, fmt.Errorf("unexpected nil field") + } + + //field without name + var name string + if len(field.Names) > 0 { + name = field.Names[0].Name + } + typ, err := p.ToType(field.Type) + if err != nil { + return nil, err + } + + isVariadic := false + if _, ok := field.Type.(*ast.Variadic); ok { + isVariadic = true + } + name = checkFieldName(name, isRecord, isVariadic) + return types.NewVar(token.NoPos, p.Types, name, typ), nil +} + +func (p *TypeConv) RecordTypeToStruct(recordType *ast.RecordType) (types.Type, error) { + var fields []*types.Var + flds, err := p.fieldListToVars(recordType.Fields, true) + if err != nil { + return nil, err + } + if recordType.Tag != ast.Union { + fields = flds + } else { + var maxFld *types.Var + maxSize := int64(0) + for i := len(flds) - 1; i >= 0; i-- { + fld := flds[i] + t := fld.Type() + size := sizes.Sizeof(t) + if size >= maxSize { + maxSize = size + maxFld = fld + } + } + if maxFld != nil { + fields = []*types.Var{maxFld} + } + } + return types.NewStruct(fields, nil), nil +} + +func (p *TypeConv) ToDefaultEnumType() types.Type { + return p.typeMap.CType("Int") +} + +// todo(zzy): Current forward declaration detection is imprecise +// It incorrectly treats both empty struct `struct a {}` and forward declaration `struct a` as the same +// by only checking if Fields.List is empty +// Should use recordType == nil to identify forward declarations, which requires llcppsigfetch support +func (p *TypeConv) inComplete(recordType *ast.RecordType) bool { + return recordType.Fields != nil && len(recordType.Fields.List) == 0 +} + +// typedecl,enumdecl,funcdecl,funcdecl +// true determine continue execute the type gen +// if this type is in a system header,skip the type gen & collect the type info +func (p *TypeConv) handleSysType(ident *ast.Ident, loc *ast.Location, incPath string) (skip bool, anony bool, err error) { + anony = ident == nil + if !p.conf.Package.curFile.isSys || anony { + return false, anony, nil + } + if existingLoc, ok := p.SysTypeLoc[ident.Name]; ok { + return true, anony, fmt.Errorf("type %s already defined in %s,include path: %s", ident.Name, existingLoc.Path, existingLoc.IncPath) + } + p.SysTypeLoc[ident.Name] = &HeaderInfo{ + IncPath: incPath, + Path: loc.File, + } + return true, anony, nil +} + +func (p *TypeConv) referSysType(name string) (types.Object, error) { + if info, ok := p.SysTypeLoc[name]; ok { + var obj types.Object + pkg, _ := IncPathToPkg(info.IncPath) + // in current converter process 's ref type + p.SysTypePkg[name] = &Header2Pkg{ + Header: info, + PkgPath: pkg, + } + depPkg := p.conf.Package.p.Import(pkg) + obj = depPkg.TryRef(names.CPubName(name)) + if obj == nil { + return nil, fmt.Errorf("sys type %s in %s not found in package %s, full path %s", name, info.IncPath, pkg, info.Path) + } + return obj, nil + + } + return nil, nil +} + +func (p *TypeConv) LookupSymbol(mangleName config.MangleNameType) (config.GoNameType, error) { + if p.symbolTable == nil { + return "", fmt.Errorf("symbol table not initialized") + } + e, err := p.symbolTable.LookupSymbol(mangleName) + if err != nil { + return "", err + } + return e.GoName, nil +} + +// isVariadic determines if the field is a variadic parameter +// The field or param name should be public if it's a record field +// and they will not record to the public symbol table +func checkFieldName(name string, isRecord bool, isVariadic bool) string { + if isVariadic { + return "__llgo_va_list" + } + // every field name should be public,will not be a keyword + if isRecord { + return names.CPubName(name) + } + return avoidKeyword(name) +} + +func avoidKeyword(name string) string { + if token.IsKeyword(name) { + return name + "_" + } + return name +} + +func substObj(pkg *types.Package, scope *types.Scope, origName string, real types.Object) { + old := scope.Insert(gogen.NewSubst(token.NoPos, pkg, origName, real)) + if old != nil { + if t, ok := old.Type().(*gogen.TySubst); ok { + t.Real = real + } else { + log.Panicln(origName, "redefined") + } + } +} diff --git a/cmd/gogensig/convert/type_builtin_test.go b/cmd/gogensig/convert/type_builtin_test.go new file mode 100644 index 0000000..d756e9f --- /dev/null +++ b/cmd/gogensig/convert/type_builtin_test.go @@ -0,0 +1,63 @@ +package convert + +import ( + "go/token" + "go/types" + "testing" + + "github.com/goplus/gogen" + "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/llcppg/ast" + cppgtypes "github.com/goplus/llgo/chore/llcppg/types" +) + +func TestIdentRef(t *testing.T) { + ct := &TypeConv{} + _, err := ct.handleIdentRefer(&ast.BuiltinType{Kind: ast.Bool}) + if err == nil { + t.Fatal("Expect Error") + } +} + +func TestLookupSymbolError(t *testing.T) { + p := &TypeConv{} + _, err := p.LookupSymbol("") + if err == nil { + t.Fatal("Expect Error") + } +} + +func TestSubstObj(t *testing.T) { + pkg := NewPackage(&PackageConfig{ + PkgPath: ".", + Name: "testpkg", + GenConf: &gogen.Config{}, + OutputDir: "", + SymbolTable: &config.SymbolTable{}, + CppgConf: &cppgtypes.Config{}, + }) + if pkg == nil { + t.Fatal("NewPackage failed") + } + + corg := types.NewNamed(types.NewTypeName(token.NoPos, nil, "origin", nil), types.Typ[types.Int], nil) + corg2 := types.NewNamed(types.NewTypeName(token.NoPos, nil, "origin2", nil), types.Typ[types.Int], nil) + substObj(pkg.p.Types, pkg.p.Types.Scope(), "GoPub", corg.Obj()) + name := gogen.Lookup(pkg.p.Types.Scope(), "GoPub") + if name == nil { + t.Fatal("Lookup failed") + } + if name.Type().String() != corg.String() { + t.Fatal("Type not equal") + } + + // reassign + substObj(pkg.p.Types, pkg.p.Types.Scope(), "GoPub", corg2.Obj()) + name2 := gogen.Lookup(pkg.p.Types.Scope(), "GoPub") + if name2 == nil { + t.Fatal("Lookup failed") + } + if name2.Type().String() != corg2.String() { + t.Fatal("Type not equal") + } +} diff --git a/cmd/gogensig/coverage.html b/cmd/gogensig/coverage.html new file mode 100644 index 0000000..3dafe6b --- /dev/null +++ b/cmd/gogensig/coverage.html @@ -0,0 +1,2518 @@ + + + + + + config: Go Coverage Report + + + +
+ +
+ not tracked + + not covered + covered + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/cmd/gogensig/gogensig.go b/cmd/gogensig/gogensig.go index 2079aaa..29363ff 100644 --- a/cmd/gogensig/gogensig.go +++ b/cmd/gogensig/gogensig.go @@ -16,6 +16,71 @@ package main +import ( + "io" + "os" + "path/filepath" + + "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/gogensig/convert" + "github.com/goplus/llgo/chore/gogensig/convert/basic" + "github.com/goplus/llgo/chore/gogensig/unmarshal" +) + +func runGoCmds(wd, pkg string) { + dir := filepath.Join(wd, pkg) + os.MkdirAll(dir, 0744) + os.Chdir(pkg) + config.RunCommand(dir, "go", "mod", "init", pkg) + config.RunCommand(dir, "go", "get", "github.com/goplus/llgo") +} + func main() { - // TODO(xsw): implement gogensig tool + var data []byte + var err error + if len(os.Args) <= 1 { + os.Exit(1) + } + + sigfetchFile := "llcppg.sigfetch.json" + if len(os.Args) > 1 { + sigfetchFile = os.Args[1] + } + + if sigfetchFile == "-" { + data, err = io.ReadAll(os.Stdin) + } else { + data, err = os.ReadFile(sigfetchFile) + } + check(err) + + conf, err := config.GetCppgCfgFromPath("./llcppg.cfg") + check(err) + + wd, err := os.Getwd() + check(err) + + runGoCmds(wd, conf.Name) + + p, _, err := basic.ConvertProcesser(&basic.Config{ + AstConvertConfig: convert.AstConvertConfig{ + PkgName: conf.Name, + SymbFile: filepath.Join(wd, "llcppg.symb.json"), + CfgFile: filepath.Join(wd, "llcppg.cfg"), + PubFile: filepath.Join(wd, "llcppg.pub"), + }, + }) + check(err) + + inputdata, err := unmarshal.UnmarshalFileSet(data) + check(err) + + err = p.ProcessFileSet(inputdata) + check(err) +} + +func check(err error) { + if err != nil { + panic(err) + } } diff --git a/cmd/gogensig/processor/coverage.html b/cmd/gogensig/processor/coverage.html new file mode 100644 index 0000000..4761e40 --- /dev/null +++ b/cmd/gogensig/processor/coverage.html @@ -0,0 +1,225 @@ + + + + + + processor: Go Coverage Report + + + +
+ +
+ not tracked + + not covered + covered + +
+
+
+ + + +
+ + + diff --git a/cmd/gogensig/processor/processor.go b/cmd/gogensig/processor/processor.go new file mode 100644 index 0000000..66eed4f --- /dev/null +++ b/cmd/gogensig/processor/processor.go @@ -0,0 +1,135 @@ +package processor + +import ( + "fmt" + + "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/gogensig/unmarshal" + "github.com/goplus/llgo/chore/gogensig/visitor" + "github.com/goplus/llgo/chore/llcppg/ast" +) + +type DocVisitorManager struct { + VisitorList []visitor.DocVisitor +} + +func NewDocVisitorManager(visitorList []visitor.DocVisitor) *DocVisitorManager { + return &DocVisitorManager{VisitorList: visitorList} +} + +func (p *DocVisitorManager) Visit(node ast.Node, path string, incPath string, isSys bool) bool { + for _, v := range p.VisitorList { + v.VisitStart(path, incPath, isSys) + v.Visit(node) + v.VisitDone(path) + } + return true +} + +type DocFileSetProcessor struct { + visitedFile map[string]struct{} + processing map[string]struct{} + exec Exec // execute a single file + done func() // done callback + depIncs []string // rel path +} + +type Exec func(*unmarshal.FileEntry) error + +type ProcesserConfig struct { + Exec Exec + Done func() + DepIncs []string // rel path +} + +func defaultExec(file *unmarshal.FileEntry) error { + fmt.Println(file.Path) + return nil +} + +// allDepIncs is the std path of all dependent include files +// such as sys/_types/_int8_t.h, etc. skip these files,because they are already processed +func NewDocFileSetProcessor(cfg *ProcesserConfig) *DocFileSetProcessor { + p := &DocFileSetProcessor{ + processing: make(map[string]struct{}), + visitedFile: make(map[string]struct{}), + exec: defaultExec, + done: cfg.Done, + depIncs: cfg.DepIncs, + } + if cfg.Exec != nil { + p.exec = cfg.Exec + } + return p +} + +func (p *DocFileSetProcessor) visitFile(path string, files unmarshal.FileSet) { + if _, ok := p.visitedFile[path]; ok { + return + } + if _, ok := p.processing[path]; ok { + return + } + p.processing[path] = struct{}{} + idx := FindEntry(files, path, false) + if idx < 0 { + return + } + findFile := files[idx] + for _, include := range findFile.Doc.Includes { + p.visitFile(include.Path, files) + } + p.exec(&findFile) + p.visitedFile[findFile.Path] = struct{}{} + delete(p.processing, findFile.Path) +} + +func (p *DocFileSetProcessor) ProcessFileSet(files unmarshal.FileSet) error { + //todo(zzy): may have same incPath + for _, inc := range p.depIncs { + idx := FindEntry(files, inc, true) + if idx < 0 { + continue + } + p.visitedFile[files[idx].Path] = struct{}{} + } + for _, file := range files { + p.visitFile(file.Path, files) + } + if p.done != nil { + p.done() + } + return nil +} + +func (p *DocFileSetProcessor) ProcessFileSetFromByte(data []byte) error { + fileSet, err := config.GetCppgSigfetchFromByte(data) + if err != nil { + return err + } + return p.ProcessFileSet(fileSet) +} + +func (p *DocFileSetProcessor) ProcessFileSetFromPath(filePath string) error { + data, err := config.ReadFile(filePath) + if err != nil { + return err + } + return p.ProcessFileSetFromByte(data) +} + +// FindEntry finds the entry in FileSet. If useIncPath is true, it searches by IncPath, otherwise by Path +func FindEntry(files unmarshal.FileSet, path string, isInc bool) int { + for i, e := range files { + if isInc { + if e.IncPath == path { + return i + } + } else { + if e.Path == path { + return i + } + } + } + return -1 +} diff --git a/cmd/gogensig/processor/processor_test.go b/cmd/gogensig/processor/processor_test.go new file mode 100644 index 0000000..ce9d7c4 --- /dev/null +++ b/cmd/gogensig/processor/processor_test.go @@ -0,0 +1,248 @@ +package processor_test + +import ( + "os" + "reflect" + "testing" + + "github.com/goplus/llgo/chore/gogensig/config" + "github.com/goplus/llgo/chore/gogensig/convert" + "github.com/goplus/llgo/chore/gogensig/convert/basic" + "github.com/goplus/llgo/chore/gogensig/processor" + "github.com/goplus/llgo/chore/gogensig/unmarshal" + "github.com/goplus/llgo/chore/gogensig/visitor" + "github.com/goplus/llgo/chore/llcppg/ast" +) + +func TestProcessValidSigfetchContent(t *testing.T) { + content := []map[string]interface{}{ + { + "path": "temp.h", + "doc": map[string]interface{}{ + "_Type": "File", + "decls": []map[string]interface{}{ + { + "_Type": "FuncDecl", + "Loc": map[string]interface{}{"_Type": "Location", "File": "temp.h"}, + "Doc": nil, + "Parent": nil, + "Name": map[string]interface{}{"_Type": "Ident", "Name": "go_func_name"}, + "Type": map[string]interface{}{ + "_Type": "FuncType", + "Params": map[string]interface{}{"_Type": "FieldList", "List": []interface{}{}}, + "Ret": map[string]interface{}{"_Type": "BuiltinType", "Kind": 6, "Flags": 0}, + }, + "IsInline": false, + "IsStatic": false, + "IsConst": false, + "IsExplicit": false, + "IsConstructor": false, + "IsDestructor": false, + "IsVirtual": false, + "IsOverride": false, + }, + }, + }, + }, + } + + tempFileName, err := config.CreateJSONFile("llcppg.sigfetch-test.json", content) + if err != nil { + t.Fatal(err) + } + defer os.Remove(tempFileName) + + tempDir, err := os.MkdirTemp("", "gogensig-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + + p, _, err := basic.ConvertProcesser(&basic.Config{ + AstConvertConfig: convert.AstConvertConfig{ + PkgName: "files", + SymbFile: "", + CfgFile: "", + OutputDir: tempDir, + }, + }) + if err != nil { + t.Fatal(err) + } + err = p.ProcessFileSetFromPath(tempFileName) + if err != nil { + t.Error(err) + } +} + +func TestProcessFileNotExist(t *testing.T) { + astConvert, err := convert.NewAstConvert(&convert.AstConvertConfig{ + PkgName: "error", + SymbFile: "", + CfgFile: "", + }) + if err != nil { + t.Fatal(err) + } + docVisitors := []visitor.DocVisitor{astConvert} + manager := processor.NewDocVisitorManager(docVisitors) + p := processor.NewDocFileSetProcessor(&processor.ProcesserConfig{ + Exec: func(file *unmarshal.FileEntry) error { + manager.Visit(file.Doc, file.Path, file.IncPath, file.IsSys) + return nil + }, + DepIncs: []string{}, + }) + err = p.ProcessFileSetFromPath("notexist.json") + if !os.IsNotExist(err) { + t.Error("expect no such file or directory error") + } +} + +func TestProcessInvalidSigfetchContent(t *testing.T) { + defer func() { + if e := recover(); e == nil { + t.Errorf("%s", "expect panic") + } + }() + + invalidContent := "invalid json content" + tempFileName, err := config.CreateJSONFile("llcppg.sigfetch-panic.json", invalidContent) + if err != nil { + t.Fatal(err) + } + defer os.Remove(tempFileName) + + astConvert, err := convert.NewAstConvert(&convert.AstConvertConfig{ + PkgName: "panic", + SymbFile: "", + CfgFile: "", + }) + if err != nil { + t.Fatal(err) + } + docVisitors := []visitor.DocVisitor{astConvert} + manager := processor.NewDocVisitorManager(docVisitors) + p := processor.NewDocFileSetProcessor(&processor.ProcesserConfig{ + Exec: func(file *unmarshal.FileEntry) error { + manager.Visit(file.Doc, file.Path, file.IncPath, file.IsSys) + return nil + }, + DepIncs: []string{}, + }) + err = p.ProcessFileSetFromPath(tempFileName) + if err != nil { + panic(err) + } +} + +func TestDefaultExec(t *testing.T) { + file := unmarshal.FileSet{ + { + Path: "foo.h", + IsSys: false, + Doc: &ast.File{}, + }, + } + p := processor.NewDocFileSetProcessor(&processor.ProcesserConfig{}) + p.ProcessFileSet(file) +} + +func TestExecOrder(t *testing.T) { + depIncs := []string{"int16_t.h"} + fileSet := unmarshal.FileSet{ + { + Path: "/path/to/foo.h", + IncPath: "foo.h", + IsSys: false, + Doc: &ast.File{ + Includes: []*ast.Include{ + {Path: "/path/to/cdef.h"}, + {Path: "/path/to/stdint.h"}, + }, + }, + }, + { + Path: "/path/to/cdef.h", + IncPath: "cdef.h", + IsSys: false, + Doc: &ast.File{ + Includes: []*ast.Include{ + {Path: "/path/to/int8_t.h"}, + {Path: "/path/to/int16_t.h"}, + }, + }, + }, + { + Path: "/path/to/stdint.h", + IncPath: "stdint.h", + IsSys: false, + Doc: &ast.File{ + Includes: []*ast.Include{ + {Path: "/path/to/int8_t.h"}, + {Path: "/path/to/int16_t.h"}, + }, + }, + }, + { + Path: "/path/to/int8_t.h", + IncPath: "int8_t.h", + IsSys: false, + Doc: &ast.File{ + Includes: []*ast.Include{}, + }, + }, + { + Path: "/path/to/int16_t.h", + IncPath: "int16_t.h", + IsSys: false, + Doc: &ast.File{ + Includes: []*ast.Include{}, + }, + }, + { + Path: "/path/to/bar.h", + IncPath: "bar.h", + IsSys: false, + Doc: &ast.File{ + Includes: []*ast.Include{ + {Path: "/path/to/stdint.h"}, + {Path: "/path/to/a.h"}, + }, + }, + }, + // circular dependency + { + Path: "/path/to/a.h", + IncPath: "a.h", + IsSys: false, + Doc: &ast.File{ + Includes: []*ast.Include{ + {Path: "/path/to/bar.h"}, + // will not appear in normal + {Path: "/path/to/noexist.h"}, + }, + }, + }, + } + var processFiles []string + expectedOrder := []string{ + "/path/to/int8_t.h", + "/path/to/cdef.h", + "/path/to/stdint.h", + "/path/to/foo.h", + "/path/to/a.h", + "/path/to/bar.h", + } + p := processor.NewDocFileSetProcessor(&processor.ProcesserConfig{ + Exec: func(file *unmarshal.FileEntry) error { + processFiles = append(processFiles, file.Path) + return nil + }, + DepIncs: depIncs, + }) + p.ProcessFileSet(fileSet) + if !reflect.DeepEqual(processFiles, expectedOrder) { + t.Errorf("expect %v, got %v", expectedOrder, processFiles) + } +} diff --git a/cmd/gogensig/unmarshal/unmarshal.go b/cmd/gogensig/unmarshal/unmarshal.go new file mode 100644 index 0000000..f7efa04 --- /dev/null +++ b/cmd/gogensig/unmarshal/unmarshal.go @@ -0,0 +1,619 @@ +package unmarshal + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/goplus/llgo/chore/llcppg/ast" +) + +type NodeUnmarshaler func(data []byte) (ast.Node, error) + +var nodeUnmarshalers map[string]NodeUnmarshaler + +type FileSet []FileEntry + +type FileEntry struct { + Path string + IsSys bool + IncPath string + Doc *ast.File +} + +func init() { + nodeUnmarshalers = map[string]NodeUnmarshaler{ + // Not need costum unmarshal + "Token": UnmarshalToken, + "Macro": UnmarshalMacro, + "Include": UnmarshalInclude, + "BasicLit": UnmarshalBasicLit, + "BuiltinType": UnmarshalBuiltinType, + "Ident": UnmarshalIdent, + "Variadic": UnmarshalVariadic, + + "PointerType": UnmarshalPointerType, + "LvalueRefType": UnmarshalLvalueRefType, + "RvalueRefType": UnmarshalRvalueRefType, + + "ArrayType": UnmarshalArrayType, + "Field": UnmarshalField, + "FieldList": UnmarshalFieldList, + "ScopingExpr": UnmarshalScopingExpr, + "TagExpr": UnmarshalTagExpr, + "EnumItem": UnmarshalEnumItem, + "EnumType": UnmarshalEnumType, + "FuncType": UnmarshalFuncType, + "RecordType": UnmarshalRecordType, + "TypedefDecl": UnmarshalTypeDefDecl, + + "FuncDecl": UnmarshalFuncDecl, + "TypeDecl": UnmarshalTypeDecl, + "EnumTypeDecl": UnmarshalEnumTypeDecl, + + "File": UnmarshalFile, + } +} + +func UnmarshalFileSet(data []byte) (FileSet, error) { + var filesWrapper []struct { + Path string `json:"path"` + IsSys bool `json:"isSys"` + IncPath string `json:"incPath"` + Doc json.RawMessage `json:"doc"` + } + if err := json.Unmarshal(data, &filesWrapper); err != nil { + return nil, fmt.Errorf("error unmarshalling FilesWithPath: %w", err) + } + + result := []FileEntry{} + + for _, fileData := range filesWrapper { + docNode, err := UnmarshalNode(fileData.Doc) + if err != nil { + return nil, fmt.Errorf("error unmarshalling doc for path %s: %w", fileData.Path, err) + } + + file, ok := docNode.(*ast.File) + if !ok { + return nil, fmt.Errorf("doc is not of type *ast.File for path %s", fileData.Path) + } + + result = append(result, FileEntry{ + Path: fileData.Path, + IsSys: fileData.IsSys, + IncPath: fileData.IncPath, + Doc: file, + }) + } + + return result, nil +} + +func UnmarshalNode(data []byte) (ast.Node, error) { + var temp struct { + Type string `json:"_Type"` + } + if err := json.Unmarshal(data, &temp); err != nil { + return nil, fmt.Errorf("error unmarshalling node type: %v", err) + } + + unmarshaler, ok := nodeUnmarshalers[temp.Type] + if !ok { + return nil, fmt.Errorf("unknown node type: %s", temp.Type) + } + + return unmarshaler(data) +} + +func UnmarshalToken(data []byte) (ast.Node, error) { + var node ast.Token + if err := json.Unmarshal(data, &node); err != nil { + return nil, fmt.Errorf("unmarshalling Token: %w", err) + } + return &node, nil +} + +func UnmarshalMacro(data []byte) (ast.Node, error) { + var node ast.Macro + if err := json.Unmarshal(data, &node); err != nil { + return nil, fmt.Errorf("unmarshalling Macro: %w", err) + } + return &node, nil +} + +func UnmarshalInclude(data []byte) (ast.Node, error) { + var node ast.Include + if err := json.Unmarshal(data, &node); err != nil { + return nil, fmt.Errorf("unmarshalling Include: %w", err) + } + return &node, nil +} + +func UnmarshalBasicLit(data []byte) (ast.Node, error) { + var node ast.BasicLit + if err := json.Unmarshal(data, &node); err != nil { + return nil, fmt.Errorf("unmarshalling BasicLit: %w", err) + } + return &node, nil +} + +func UnmarshalBuiltinType(data []byte) (ast.Node, error) { + var node ast.BuiltinType + if err := json.Unmarshal(data, &node); err != nil { + return nil, fmt.Errorf("unmarshalling BuiltinType: %w", err) + } + return &node, nil +} + +func UnmarshalIdent(data []byte) (ast.Node, error) { + var node ast.Ident + if err := json.Unmarshal(data, &node); err != nil { + return nil, fmt.Errorf("unmarshalling Ident: %w", err) + } + return &node, nil +} + +func UnmarshalVariadic(data []byte) (ast.Node, error) { + var node ast.Variadic + if err := json.Unmarshal(data, &node); err != nil { + return nil, fmt.Errorf("unmarshalling Variadic: %w", err) + } + return &node, nil +} + +func UnmarshalXType(data []byte, xType ast.Node) (ast.Node, error) { + var temp struct { + X json.RawMessage + } + if err := json.Unmarshal(data, &temp); err != nil { + return nil, fmt.Errorf("unmarshalling XType: %w", err) + } + + x, err := UnmarshalNode(temp.X) + if err != nil { + return nil, fmt.Errorf("unmarshalling field X: %w", err) + } + expr := x.(ast.Expr) + switch v := xType.(type) { + case *ast.PointerType: + v.X = expr + case *ast.LvalueRefType: + v.X = expr + case *ast.RvalueRefType: + v.X = expr + default: + return nil, fmt.Errorf("unexpected type: %T", xType) + } + + return xType, nil +} + +func UnmarshalPointerType(data []byte) (ast.Node, error) { + return UnmarshalXType(data, &ast.PointerType{}) +} + +func UnmarshalLvalueRefType(data []byte) (ast.Node, error) { + return UnmarshalXType(data, &ast.LvalueRefType{}) +} + +func UnmarshalRvalueRefType(data []byte) (ast.Node, error) { + return UnmarshalXType(data, &ast.RvalueRefType{}) +} + +func UnmarshalArrayType(data []byte) (ast.Node, error) { + var arrayTemp struct { + Elt json.RawMessage + Len json.RawMessage + } + if err := json.Unmarshal(data, &arrayTemp); err != nil { + return nil, fmt.Errorf("error unmarshalling ArrayType: %w", err) + } + + arrayType := &ast.ArrayType{} + + elt, err := UnmarshalNode(arrayTemp.Elt) + if err != nil { + return nil, fmt.Errorf("error unmarshalling array Elt: %w", err) + } + arrayType.Elt = elt.(ast.Expr) + + // len permit nil,for array without len + if len(arrayTemp.Len) > 0 && !isJSONNull(arrayTemp.Len) { + len, err := UnmarshalNode(arrayTemp.Len) + if err != nil { + return nil, fmt.Errorf("error unmarshalling array Len: %w", err) + } + arrayType.Len = len.(ast.Expr) + } + + return arrayType, nil +} + +func UnmarshalField(data []byte) (ast.Node, error) { + var fieldTemp struct { + Type json.RawMessage + Doc *ast.CommentGroup + Names []*ast.Ident + Comment *ast.CommentGroup + Access ast.AccessSpecifier + IsStatic bool + } + if err := json.Unmarshal(data, &fieldTemp); err != nil { + return nil, fmt.Errorf("error unmarshalling Field: %w", err) + } + typeNode, err := UnmarshalNode(fieldTemp.Type) + if err != nil { + return nil, fmt.Errorf("error unmarshalling field Type: %w", err) + } + + field := &ast.Field{ + Doc: fieldTemp.Doc, + Names: fieldTemp.Names, + Comment: fieldTemp.Comment, + Access: fieldTemp.Access, + IsStatic: fieldTemp.IsStatic, + Type: typeNode.(ast.Expr), + } + + return field, nil +} + +func UnmarshalFieldList(data []byte) (ast.Node, error) { + var fieldListTemp struct { + List []json.RawMessage + } + if err := json.Unmarshal(data, &fieldListTemp); err != nil { + return nil, fmt.Errorf("error unmarshalling FieldList: %w", err) + } + + fieldList := &ast.FieldList{} + + for _, fieldData := range fieldListTemp.List { + field, err := UnmarshalNode(fieldData) + if err != nil { + return nil, fmt.Errorf("error unmarshalling field in FieldList: %w", err) + } + fieldList.List = append(fieldList.List, field.(*ast.Field)) + } + + return fieldList, nil +} + +func UnmarshalTagExpr(data []byte) (ast.Node, error) { + var tagExpr struct { + Name json.RawMessage + Tag ast.Tag + } + if err := json.Unmarshal(data, &tagExpr); err != nil { + return nil, fmt.Errorf("error unmarshalling TagExpr: %w", err) + } + + name, err := UnmarshalNode(tagExpr.Name) + if err != nil { + return nil, fmt.Errorf("error unmarshalling TagExpr Name: %w", err) + } + + return &ast.TagExpr{ + Name: name.(ast.Expr), + Tag: tagExpr.Tag, + }, nil +} + +func UnmarshalScopingExpr(data []byte) (ast.Node, error) { + var scopingExpr struct { + Parent json.RawMessage + X json.RawMessage + } + if err := json.Unmarshal(data, &scopingExpr); err != nil { + return nil, fmt.Errorf("error unmarshalling ScopingExpr: %w", err) + } + + parent, err := UnmarshalNode(scopingExpr.Parent) + if err != nil { + return nil, err + } + x, err := UnmarshalNode(scopingExpr.X) + if err != nil { + return nil, err + } + + return &ast.ScopingExpr{ + Parent: parent.(ast.Expr), + X: x.(ast.Expr), + }, nil +} + +func UnmarshalEnumItem(data []byte) (ast.Node, error) { + var enumItemTemp struct { + Name *ast.Ident + Value json.RawMessage + } + + if err := json.Unmarshal(data, &enumItemTemp); err != nil { + return nil, fmt.Errorf("error unmarshalling EnumItem: %w", err) + } + + enumItem := &ast.EnumItem{ + Name: enumItemTemp.Name, + } + + if !isJSONNull(enumItemTemp.Value) { + value, err := UnmarshalNode(enumItemTemp.Value) + if err != nil { + return nil, fmt.Errorf("error unmarshalling EnumItem Value: %w", err) + } + enumItem.Value = value.(ast.Expr) + } + + return enumItem, nil +} + +func UnmarshalEnumType(data []byte) (ast.Node, error) { + var enumType struct { + Items []json.RawMessage + } + if err := json.Unmarshal(data, &enumType); err != nil { + return nil, fmt.Errorf("error unmarshalling EnumType: %w", err) + } + + result := &ast.EnumType{} + + for _, itemData := range enumType.Items { + item, err := UnmarshalNode(itemData) + if err != nil { + return nil, fmt.Errorf("error unmarshalling EnumType Item: %w", err) + } + result.Items = append(result.Items, item.(*ast.EnumItem)) + } + + return result, nil +} + +func UnmarshalRecordType(data []byte) (ast.Node, error) { + var recordTypeTemp struct { + Tag ast.Tag + Fields json.RawMessage + Methods []json.RawMessage + } + + if err := json.Unmarshal(data, &recordTypeTemp); err != nil { + return nil, fmt.Errorf("error unmarshalling RecordType: %w", err) + } + + recordType := &ast.RecordType{ + Tag: recordTypeTemp.Tag, + Methods: []*ast.FuncDecl{}, + } + + fields, err := UnmarshalNode(recordTypeTemp.Fields) + if err != nil { + return nil, fmt.Errorf("error unmarshalling Fields in RecordType: %w", err) + } + recordType.Fields = fields.(*ast.FieldList) + + for _, methodData := range recordTypeTemp.Methods { + method, err := UnmarshalNode(methodData) + if err != nil { + return nil, fmt.Errorf("error unmarshalling method in RecordType: %w", err) + } + recordType.Methods = append(recordType.Methods, method.(*ast.FuncDecl)) + } + + return recordType, nil +} + +func UnmarshalFuncType(data []byte) (ast.Node, error) { + var funcTypeTemp struct { + Params json.RawMessage + Ret json.RawMessage + } + if err := json.Unmarshal(data, &funcTypeTemp); err != nil { + return nil, fmt.Errorf("error unmarshalling FuncType: %w", err) + } + params, err := UnmarshalNode(funcTypeTemp.Params) + if err != nil { + return nil, fmt.Errorf("error unmarshalling Params in FuncType: %w", err) + } + + ret, err := UnmarshalNode(funcTypeTemp.Ret) + if err != nil { + return nil, fmt.Errorf("error unmarshalling Ret in FuncType: %w", err) + } + + funcType := &ast.FuncType{ + Params: params.(*ast.FieldList), + Ret: ret.(ast.Expr), + } + + return funcType, nil +} + +func UnmarshalFuncDecl(data []byte) (ast.Node, error) { + var funcDecl struct { + Name *ast.Ident + MangledName string + Type json.RawMessage + IsInline bool + IsStatic bool + IsConst bool + IsExplicit bool + IsConstructor bool + IsDestructor bool + IsVirtual bool + IsOverride bool + } + if err := json.Unmarshal(data, &funcDecl); err != nil { + return nil, fmt.Errorf("error unmarshalling FuncDecl: %w", err) + } + + typ, err := UnmarshalNode(funcDecl.Type) + if err != nil { + return nil, fmt.Errorf("error unmarshalling FuncDecl Type: %w", err) + } + + declBase, err := UnmarshalDeclBase(data) + if err != nil { + return nil, fmt.Errorf("error unmarshalling FuncDecl DeclBase: %w", err) + } + + result := &ast.FuncDecl{ + DeclBase: declBase, + Type: typ.(*ast.FuncType), + Name: funcDecl.Name, + MangledName: funcDecl.MangledName, + IsInline: funcDecl.IsInline, + IsStatic: funcDecl.IsStatic, + IsConst: funcDecl.IsConst, + IsExplicit: funcDecl.IsExplicit, + IsConstructor: funcDecl.IsConstructor, + IsDestructor: funcDecl.IsDestructor, + IsVirtual: funcDecl.IsVirtual, + IsOverride: funcDecl.IsOverride, + } + + return result, nil +} + +func UnmarshalTypeDecl(data []byte) (ast.Node, error) { + var typeDecl struct { + Name *ast.Ident + Type json.RawMessage + } + if err := json.Unmarshal(data, &typeDecl); err != nil { + return nil, fmt.Errorf("error unmarshalling TypeDecl: %w", err) + } + + typ, err := UnmarshalNode(typeDecl.Type) + if err != nil { + return nil, fmt.Errorf("error unmarshalling TypeDecl Type: %w", err) + } + + declBase, err := UnmarshalDeclBase(data) + if err != nil { + return nil, fmt.Errorf("error unmarshalling TypeDecl DeclBase: %w", err) + } + + result := &ast.TypeDecl{ + DeclBase: declBase, + Name: typeDecl.Name, + Type: typ.(*ast.RecordType), + } + + return result, nil +} + +func UnmarshalTypeDefDecl(data []byte) (ast.Node, error) { + var typeDecl struct { + Name *ast.Ident + Type json.RawMessage + } + if err := json.Unmarshal(data, &typeDecl); err != nil { + return nil, fmt.Errorf("error unmarshalling TypeDefDecl: %w", err) + } + + typ, err := UnmarshalNode(typeDecl.Type) + if err != nil { + return nil, fmt.Errorf("error unmarshalling TypeDefDecl Type: %w", err) + } + + declBase, err := UnmarshalDeclBase(data) + if err != nil { + return nil, fmt.Errorf("error unmarshalling TypeDefDecl DeclBase: %w", err) + } + + result := &ast.TypedefDecl{ + DeclBase: declBase, + Name: typeDecl.Name, + Type: typ.(ast.Expr), + } + + return result, nil +} + +func UnmarshalEnumTypeDecl(data []byte) (ast.Node, error) { + var enumTypeDecl struct { + Name *ast.Ident + Type json.RawMessage + } + if err := json.Unmarshal(data, &enumTypeDecl); err != nil { + return nil, fmt.Errorf("error unmarshalling EnumTypeDecl: %w", err) + } + + typ, err := UnmarshalNode(enumTypeDecl.Type) + if err != nil { + return nil, fmt.Errorf("error unmarshalling EnumTypeDecl Type: %w", err) + } + + declBase, err := UnmarshalDeclBase(data) + if err != nil { + return nil, fmt.Errorf("error unmarshalling EnumTypeDecl DeclBase: %w", err) + } + + result := &ast.EnumTypeDecl{ + DeclBase: declBase, + Name: enumTypeDecl.Name, + Type: typ.(*ast.EnumType), + } + + return result, nil +} + +func UnmarshalDeclBase(data []byte) (ast.DeclBase, error) { + var DeclBase struct { + Loc *ast.Location + Doc *ast.CommentGroup + Parent json.RawMessage + } + + if err := json.Unmarshal(data, &DeclBase); err != nil { + return ast.DeclBase{}, fmt.Errorf("error unmarshalling DeclBase Type: %w", err) + } + + result := ast.DeclBase{ + Loc: DeclBase.Loc, + Doc: DeclBase.Doc, + } + + if !isJSONNull(DeclBase.Parent) { + parent, err := UnmarshalNode(DeclBase.Parent) + if err != nil { + return ast.DeclBase{}, fmt.Errorf("error unmarshalling parent in DeclBase Type: %w", err) + } + result.Parent = parent.(ast.Expr) + } + + return result, nil +} + +func UnmarshalFile(data []byte) (ast.Node, error) { + var file struct { + Decls []json.RawMessage `json:"decls"` + Includes []*ast.Include `json:"includes,omitempty"` + Macros []*ast.Macro `json:"macros,omitempty"` + } + if err := json.Unmarshal(data, &file); err != nil { + return nil, fmt.Errorf("error unmarshalling File: %w", err) + } + + result := &ast.File{ + Includes: file.Includes, + Macros: file.Macros, + Decls: []ast.Decl{}, + } + + for i, declData := range file.Decls { + decl, err := UnmarshalNode(declData) + if err != nil { + fmt.Fprintf(os.Stderr, "error unmarshalling %d Decl in File: %v\n%s\n", i, err, string(declData)) + continue + } + result.Decls = append(result.Decls, decl.(ast.Decl)) + } + + return result, nil +} + +func isJSONNull(data json.RawMessage) bool { + return len(data) == 4 && string(data) == "null" +} diff --git a/cmd/gogensig/unmarshal/unmarshal_test.go b/cmd/gogensig/unmarshal/unmarshal_test.go new file mode 100644 index 0000000..53bd9c2 --- /dev/null +++ b/cmd/gogensig/unmarshal/unmarshal_test.go @@ -0,0 +1,2144 @@ +package unmarshal_test + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/goplus/llgo/chore/gogensig/unmarshal" + "github.com/goplus/llgo/chore/llcppg/ast" +) + +func TestUnmarshalNode(t *testing.T) { + testCases := []struct { + name string + json string + expected ast.Node + }{ + { + name: "Token", + json: `{ "_Type": "Token","Token": 3,"Lit": "DEBUG"}`, + expected: &ast.Token{Token: 3, Lit: "DEBUG"}, + }, + { + name: "BuiltinType", + json: `{"_Type": "BuiltinType", "Kind": 6, "Flags": 0}`, + expected: &ast.BuiltinType{Kind: ast.TypeKind(6), Flags: ast.TypeFlag(0)}, + }, + { + name: "PointerType", + json: `{ + "_Type": "PointerType", + "X": { + "_Type": "BuiltinType", + "Kind": 2, + "Flags": 1 + } + }`, + expected: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: 2, + Flags: 1, + }, + }, + }, + { + name: "PointerType", + json: `{ + "_Type": "PointerType", + "X": { + "_Type": "PointerType", + "X": { + "_Type": "PointerType", + "X": { + "_Type": "BuiltinType", + "Kind": 2, + "Flags": 1 + } + } + } + }`, + expected: &ast.PointerType{ + X: &ast.PointerType{ + X: &ast.PointerType{ + X: &ast.BuiltinType{ + Kind: 2, + Flags: 1, + }, + }, + }}, + }, + { + name: "ArrayType", + json: `{ + "_Type": "ArrayType", + "Elt": { + "_Type": "BuiltinType", + "Kind": 2, + "Flags": 1 + }, + "Len": null + }`, + expected: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: 2, + Flags: 1, + }, + Len: nil, + }, + }, + { + name: "ArrayType", + json: `{ + "_Type": "ArrayType", + "Elt": { + "_Type": "BuiltinType", + "Kind": 2, + "Flags": 1 + }, + "Len": { + "_Type": "BasicLit", + "Kind": 0, + "Value": "10" + } + }`, + expected: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: 2, + Flags: 1, + }, + Len: &ast.BasicLit{ + Kind: 0, + Value: "10", + }, + }, + }, + { + name: "ArrayType", + json: `{ + "_Type": "ArrayType", + "Elt": { + "_Type": "ArrayType", + "Elt": { + "_Type": "BuiltinType", + "Kind": 2, + "Flags": 1 + }, + "Len": { + "_Type": "BasicLit", + "Kind": 0, + "Value": "4" + } + }, + "Len": { + "_Type": "BasicLit", + "Kind": 0, + "Value": "3" + } + }`, + expected: &ast.ArrayType{ + Elt: &ast.ArrayType{ + Elt: &ast.BuiltinType{ + Kind: 2, + Flags: 1, + }, + Len: &ast.BasicLit{ + Kind: 0, + Value: "4", + }, + }, + Len: &ast.BasicLit{ + Kind: 0, + Value: "3", + }, + }, + }, + { + name: "Variadic", + json: `{"_Type": "Variadic"}`, + expected: &ast.Variadic{}, + }, + { + name: "LvalueRefType", + json: `{ + "_Type": "LvalueRefType", + "X": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + } + }`, + expected: &ast.LvalueRefType{ + X: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + }, + }, + { + name: "RvalueRefType", + json: `{ + "_Type": "RvalueRefType", + "X": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + } + }`, + expected: &ast.RvalueRefType{ + X: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + }, + }, + { + name: "Ident", + json: `{ + "_Type": "Ident", + "Name": "Foo" + }`, + expected: &ast.Ident{ + Name: "Foo", + }, + }, + { + name: "ScopingExpr", + json: `{ + "_Type": "ScopingExpr", + "X": { + "_Type": "Ident", + "Name": "b" + }, + "Parent": { + "_Type": "Ident", + "Name": "a" + } + }`, + expected: &ast.ScopingExpr{ + X: &ast.Ident{ + Name: "b", + }, + Parent: &ast.Ident{ + Name: "a", + }, + }, + }, + { + name: "ScopingExpr", + json: `{ + "_Type": "ScopingExpr", + "X": { + "_Type": "Ident", + "Name": "c" + }, + "Parent": { + "_Type": "ScopingExpr", + "X": { + "_Type": "Ident", + "Name": "b" + }, + "Parent": { + "_Type": "Ident", + "Name": "a" + } + } + }`, + expected: &ast.ScopingExpr{ + X: &ast.Ident{ + Name: "c", + }, + Parent: &ast.ScopingExpr{ + X: &ast.Ident{ + Name: "b", + }, + Parent: &ast.Ident{ + Name: "a", + }, + }, + }, + }, + { + name: "TagExpr", + json: `{ + "_Type": "TagExpr", + "Name": { + "_Type": "Ident", + "Name": "Foo" + }, + "Tag": 0 + }`, + expected: &ast.TagExpr{ + Tag: 0, + Name: &ast.Ident{ + Name: "Foo", + }, + }, + }, + { + name: "Field", + json: `{ + "_Type": "Field", + "Type": {"_Type": "Variadic"}, + "Doc": { + "_Type": "CommentGroup", + "List": [{ + "_Type": "Comment", + "Text": "/// doc" + }] + }, + "Comment": null, + "IsStatic": false, + "Access": 0, + "Names": [{ + "_Type": "Ident", + "Name": "a" + }] + }`, + expected: &ast.Field{ + Doc: &ast.CommentGroup{ + List: []*ast.Comment{{Text: "/// doc"}}, + }, + Type: &ast.Variadic{}, + IsStatic: false, + Access: ast.AccessSpecifier(0), + Names: []*ast.Ident{{Name: "a"}}, + }, + }, + { + name: "FieldList", + json: `{ + "_Type": "FieldList", + "List": [{ + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 1, + "Names": [{ + "_Type": "Ident", + "Name": "x" + }] + }] + }`, + expected: &ast.FieldList{ + List: []*ast.Field{ + { + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + Access: ast.AccessSpecifier(1), + Names: []*ast.Ident{{Name: "x"}}, + }, + }, + }, + }, + { + name: "FuncType", + json: `{ + "_Type": "FuncType", + "Params": { + "_Type": "FieldList", + "List": [{ + "_Type": "Field", + "Type": { + "_Type": "Variadic" + }, + "Doc": null, + "Comment": null, + "IsStatic": false, + "Access": 0, + "Names": [] + }, { + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [{ + "_Type": "Ident", + "Name": "a" + }] + }] + }, + "Ret": { + "_Type": "BuiltinType", + "Kind": 0, + "Flags": 0 + } + }`, + expected: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.Variadic{}, + Names: []*ast.Ident{}, + }, { + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + Names: []*ast.Ident{ + {Name: "a"}, + }, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: 0, + Flags: 0, + }, + }, + }, + { + name: "FuncDecl", + json: `{ + "_Type": "FuncDecl", + "Loc": { + "_Type": "Location", + "File": "temp.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": null, + "Name": { + "_Type": "Ident", + "Name": "foo" + }, + "Type": { + "_Type": "FuncType", + "Params": { + "_Type": "FieldList", + "List": [{ + "_Type": "Field", + "Type": { + "_Type": "Variadic" + }, + "Doc": null, + "Comment": null, + "IsStatic": false, + "Access": 0, + "Names": [] + }, { + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [{ + "_Type": "Ident", + "Name": "a" + }] + }] + }, + "Ret": { + "_Type": "BuiltinType", + "Kind": 0, + "Flags": 0 + } + }, + "IsInline": false, + "IsStatic": false, + "IsConst": false, + "IsExplicit": false, + "IsConstructor": false, + "IsDestructor": false, + "IsVirtual": false, + "IsOverride": false + }`, + expected: &ast.FuncDecl{ + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "temp.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + }, + Name: &ast.Ident{Name: "foo"}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.Variadic{}, + Names: []*ast.Ident{}, + }, { + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + Names: []*ast.Ident{ + {Name: "a"}, + }, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: 0, + Flags: 0, + }, + }, + }, + }, + { + name: "RecordType", + json: `{ + "_Type": "RecordType", + "Tag": 3, + "Fields": { + "_Type": "FieldList", + "List": [{ + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": true, + "Access": 1, + "Names": [{ + "_Type": "Ident", + "Name": "a" + }] + }, { + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 1, + "Names": [{ + "_Type": "Ident", + "Name": "b" + }] + }] + }, + "Methods": [{ + "_Type": "FuncDecl", + "Loc": { + "_Type": "Location", + "File": "temp.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": { + "_Type": "Ident", + "Name": "A" + }, + "Name": { + "_Type": "Ident", + "Name": "foo" + }, + "Type": { + "_Type": "FuncType", + "Params": { + "_Type": "FieldList", + "List": [{ + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [{ + "_Type": "Ident", + "Name": "a" + }] + }, { + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 8, + "Flags": 16 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [{ + "_Type": "Ident", + "Name": "b" + }] + }] + }, + "Ret": { + "_Type": "BuiltinType", + "Kind": 8, + "Flags": 0 + } + }, + "IsInline": false, + "IsStatic": false, + "IsConst": false, + "IsExplicit": false, + "IsConstructor": false, + "IsDestructor": false, + "IsVirtual": false, + "IsOverride": false + }] + }`, + expected: &ast.RecordType{ + Tag: 3, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + IsStatic: true, + Access: 1, + Names: []*ast.Ident{ + {Name: "a"}, + }, + }, + { + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + IsStatic: false, + Access: 1, + Names: []*ast.Ident{ + {Name: "b"}, + }, + }, + }, + }, + Methods: []*ast.FuncDecl{ + { + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "temp.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Parent: &ast.Ident{Name: "A"}, + }, + Name: &ast.Ident{Name: "foo"}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + IsStatic: false, + Access: 0, + Names: []*ast.Ident{ + {Name: "a"}, + }, + }, + { + Type: &ast.BuiltinType{ + Kind: 8, + Flags: 16, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + IsStatic: false, + Access: 0, + Names: []*ast.Ident{ + {Name: "b"}, + }, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: 8, + Flags: 0, + }, + }, + IsInline: false, + IsStatic: false, + IsConst: false, + IsExplicit: false, + IsConstructor: false, + IsDestructor: false, + IsVirtual: false, + IsOverride: false, + }, + }, + }, + }, + { + name: "TypedefDecl", + json: `{ + "_Type": "TypedefDecl", + "Loc": { + "_Type": "Location", + "File": "temp.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": null, + "Name": { + "_Type": "Ident", + "Name": "INT" + }, + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + } + }`, + expected: &ast.TypedefDecl{ + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "temp.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Parent: nil, + }, + Name: &ast.Ident{ + Name: "INT", + }, + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + }, + }, + { + name: "EnumItem", + json: `{ + "_Type": "EnumItem", + "Name": { + "_Type": "Ident", + "Name": "a" + }, + "Value": { + "_Type": "BasicLit", + "Kind": 0, + "Value": "0" + } + }`, + expected: &ast.EnumItem{ + Name: &ast.Ident{ + Name: "a", + }, + Value: &ast.BasicLit{ + Kind: 0, + Value: "0", + }, + }, + }, + { + name: "EnumTypeDecl", + json: `{ + "_Type": "EnumTypeDecl", + "Loc": { + "_Type": "Location", + "File": "temp.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": null, + "Name": { + "_Type": "Ident", + "Name": "Foo" + }, + "Type": { + "_Type": "EnumType", + "Items": [{ + "_Type": "EnumItem", + "Name": { + "_Type": "Ident", + "Name": "a" + }, + "Value": { + "_Type": "BasicLit", + "Kind": 0, + "Value": "0" + } + }, { + "_Type": "EnumItem", + "Name": { + "_Type": "Ident", + "Name": "b" + }, + "Value": { + "_Type": "BasicLit", + "Kind": 0, + "Value": "1" + } + }, { + "_Type": "EnumItem", + "Name": { + "_Type": "Ident", + "Name": "c" + }, + "Value": { + "_Type": "BasicLit", + "Kind": 0, + "Value": "2" + } + }] + } + }`, + expected: &ast.EnumTypeDecl{ + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "temp.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Parent: nil, + }, + Name: &ast.Ident{Name: "Foo"}, + Type: &ast.EnumType{ + Items: []*ast.EnumItem{ + { + Name: &ast.Ident{ + Name: "a", + }, + Value: &ast.BasicLit{ + Kind: 0, + Value: "0", + }, + }, + { + Name: &ast.Ident{ + Name: "b", + }, + Value: &ast.BasicLit{ + Kind: 0, + Value: "1", + }, + }, + { + Name: &ast.Ident{ + Name: "c", + }, + Value: &ast.BasicLit{ + Kind: 0, + Value: "2", + }, + }, + }, + }, + }, + }, + { + name: "Macro", + json: `{ + "_Type": "Macro", + "Name": "OK", + "Tokens": [{ + "_Type": "Token", + "Token": 3, + "Lit": "OK" + }, { + "_Type": "Token", + "Token": 4, + "Lit": "1" + }] + }`, + expected: &ast.Macro{ + Name: "OK", + Tokens: []*ast.Token{ + {Token: 3, Lit: "OK"}, + {Token: 4, Lit: "1"}, + }, + }, + }, + { + name: "Include", + json: `{ + "_Type": "Include", + "Path": "foo.h" + }`, + expected: &ast.Include{ + Path: "foo.h", + }, + }, + { + name: "File", + json: `{ + "_Type": "File", + "decls": [{ + "_Type": "FuncDecl", + "Loc": { + "_Type": "Location", + "File": "temp.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": null, + "Name": { + "_Type": "Ident", + "Name": "foo" + }, + "Type": { + "_Type": "FuncType", + "Params": { + "_Type": "FieldList", + "List": [{ + "_Type": "Field", + "Type": { + "_Type": "Variadic" + }, + "Doc": null, + "Comment": null, + "IsStatic": false, + "Access": 0, + "Names": [] + }, { + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [{ + "_Type": "Ident", + "Name": "a" + }] + }] + }, + "Ret": { + "_Type": "BuiltinType", + "Kind": 0, + "Flags": 0 + } + }, + "IsInline": false, + "IsStatic": false, + "IsConst": false, + "IsExplicit": false, + "IsConstructor": false, + "IsDestructor": false, + "IsVirtual": false, + "IsOverride": false + } + ], + "includes": [], + "macros": [{ + "_Type": "Macro", + "Name": "OK", + "Tokens": [{ + "_Type": "Token", + "Token": 3, + "Lit": "OK" + }, { + "_Type": "Token", + "Token": 4, + "Lit": "1" + }] + }] + }`, + expected: &ast.File{ + Decls: []ast.Decl{ + &ast.FuncDecl{ + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "temp.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + }, + Name: &ast.Ident{Name: "foo"}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.Variadic{}, + Names: []*ast.Ident{}, + }, { + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + Names: []*ast.Ident{ + {Name: "a"}, + }, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: 0, + Flags: 0, + }, + }, + }, + }, + Includes: []*ast.Include{}, + Macros: []*ast.Macro{ + { + Name: "OK", + Tokens: []*ast.Token{ + {Token: 3, Lit: "OK"}, + {Token: 4, Lit: "1"}, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + node, err := unmarshal.UnmarshalNode([]byte(tc.json)) + if err != nil { + t.Fatalf("UnmarshalNode failed: %v", err) + } + + resultJSON, err := json.MarshalIndent(node, "", " ") + if err != nil { + t.Fatalf("Failed to marshal result to JSON: %v", err) + } + + expectedJSON, err := json.MarshalIndent(tc.expected, "", " ") + + if err != nil { + t.Fatalf("Failed to marshal expected result to JSON: %v", err) + } + + if string(resultJSON) != string(expectedJSON) { + t.Errorf("JSON mismatch.\nExpected: %s\nGot: %s", string(expectedJSON), string(resultJSON)) + } + }) + } +} + +func TestUnmarshalFileSet(t *testing.T) { + files := `[ + { + "path": "/opt/homebrew/Cellar/inih/58/include/INIReader.h", + "isSys": false, + "incPath": "INIReader.h", + "doc": { + "_Type": "File", + "decls": [ + { + "_Type": "TypeDecl", + "Loc": { + "_Type": "Location", + "File": "/opt/homebrew/Cellar/inih/58/include/INIReader.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": null, + "Name": { + "_Type": "Ident", + "Name": "INIReader" + }, + "Type": { + "_Type": "RecordType", + "Tag": 3, + "Fields": { + "_Type": "FieldList", + "List": [ + { + "_Type": "Field", + "Type": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 3, + "Names": [ + { + "_Type": "Ident", + "Name": "_error" + } + ] + } + ] + }, + "Methods": [ + { + "_Type": "FuncDecl", + "Loc": { + "_Type": "Location", + "File": "/opt/homebrew/Cellar/inih/58/include/INIReader.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": { + "_Type": "Ident", + "Name": "INIReader" + }, + "Name": { + "_Type": "Ident", + "Name": "INIReader" + }, + "Type": { + "_Type": "FuncType", + "Params": { + "_Type": "FieldList", + "List": [ + { + "_Type": "Field", + "Type": { + "_Type": "LvalueRefType", + "X": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + } + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [ + { + "_Type": "Ident", + "Name": "filename" + } + ] + } + ] + }, + "Ret": { + "_Type": "BuiltinType", + "Kind": 0, + "Flags": 0 + } + }, + "IsInline": false, + "IsStatic": false, + "IsConst": false, + "IsExplicit": true, + "IsConstructor": true, + "IsDestructor": false, + "IsVirtual": false, + "IsOverride": false + }, + { + "_Type": "FuncDecl", + "Loc": { + "_Type": "Location", + "File": "/opt/homebrew/Cellar/inih/58/include/INIReader.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": { + "_Type": "Ident", + "Name": "INIReader" + }, + "Name": { + "_Type": "Ident", + "Name": "HasSection" + }, + "Type": { + "_Type": "FuncType", + "Params": { + "_Type": "FieldList", + "List": [ + { + "_Type": "Field", + "Type": { + "_Type": "LvalueRefType", + "X": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + } + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [ + { + "_Type": "Ident", + "Name": "section" + } + ] + } + ] + }, + "Ret": { + "_Type": "BuiltinType", + "Kind": 1, + "Flags": 0 + } + }, + "IsInline": false, + "IsStatic": false, + "IsConst": true, + "IsExplicit": false, + "IsConstructor": false, + "IsDestructor": false, + "IsVirtual": false, + "IsOverride": false + }, + { + "_Type": "FuncDecl", + "Loc": { + "_Type": "Location", + "File": "/opt/homebrew/Cellar/inih/58/include/INIReader.h" + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Parent": { + "_Type": "Ident", + "Name": "INIReader" + }, + "Name": { + "_Type": "Ident", + "Name": "HasValue" + }, + "Type": { + "_Type": "FuncType", + "Params": { + "_Type": "FieldList", + "List": [ + { + "_Type": "Field", + "Type": { + "_Type": "LvalueRefType", + "X": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + } + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [ + { + "_Type": "Ident", + "Name": "section" + } + ] + }, + { + "_Type": "Field", + "Type": { + "_Type": "LvalueRefType", + "X": { + "_Type": "BuiltinType", + "Kind": 6, + "Flags": 0 + } + }, + "Doc": { + "_Type": "CommentGroup", + "List": [] + }, + "Comment": { + "_Type": "CommentGroup", + "List": [] + }, + "IsStatic": false, + "Access": 0, + "Names": [ + { + "_Type": "Ident", + "Name": "name" + } + ] + } + ] + }, + "Ret": { + "_Type": "BuiltinType", + "Kind": 1, + "Flags": 0 + } + }, + "IsInline": false, + "IsStatic": false, + "IsConst": true, + "IsExplicit": false, + "IsConstructor": false, + "IsDestructor": false, + "IsVirtual": false, + "IsOverride": false + } + ] + } + } + ], + "includes": [ + { + "_Type": "Include", + "Path": "string" + } + ], + "macros": [ + { + "_Type": "Macro", + "Name": "INIREADER_H", + "Tokens": [ + { + "_Type": "Token", + "Token": 3, + "Lit": "INIREADER_H" + } + ] + }, + { + "_Type": "Macro", + "Name": "INI_API", + "Tokens": [ + { + "_Type": "Token", + "Token": 3, + "Lit": "INI_API" + }, + { + "_Type": "Token", + "Token": 2, + "Lit": "__attribute__" + }, + { + "_Type": "Token", + "Token": 1, + "Lit": "(" + }, + { + "_Type": "Token", + "Token": 1, + "Lit": "(" + }, + { + "_Type": "Token", + "Token": 3, + "Lit": "visibility" + }, + { + "_Type": "Token", + "Token": 1, + "Lit": "(" + }, + { + "_Type": "Token", + "Token": 4, + "Lit": "\"default\"" + }, + { + "_Type": "Token", + "Token": 1, + "Lit": ")" + }, + { + "_Type": "Token", + "Token": 1, + "Lit": ")" + }, + { + "_Type": "Token", + "Token": 1, + "Lit": ")" + } + ] + } + ] + } + } +]` + + expected := []struct { + Path string + IsSys bool + IncPath string + Doc *ast.File + }{ + { + Path: "/opt/homebrew/Cellar/inih/58/include/INIReader.h", + IsSys: false, + IncPath: "INIReader.h", + Doc: &ast.File{ + Decls: []ast.Decl{ + &ast.TypeDecl{ + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "/opt/homebrew/Cellar/inih/58/include/INIReader.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + }, + Name: &ast.Ident{Name: "INIReader"}, + Type: &ast.RecordType{ + Tag: 3, + Fields: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + IsStatic: false, + Access: 3, + Names: []*ast.Ident{ + {Name: "_error"}, + }, + }, + }, + }, + Methods: []*ast.FuncDecl{ + { + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "/opt/homebrew/Cellar/inih/58/include/INIReader.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Parent: &ast.Ident{Name: "INIReader"}, + }, + Name: &ast.Ident{Name: "INIReader"}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.LvalueRefType{ + X: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Names: []*ast.Ident{ + {Name: "filename"}, + }, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: 0, + Flags: 0, + }, + }, + IsExplicit: true, + IsConstructor: true, + }, + { + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "/opt/homebrew/Cellar/inih/58/include/INIReader.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Parent: &ast.Ident{Name: "INIReader"}, + }, + Name: &ast.Ident{Name: "HasSection"}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.LvalueRefType{ + X: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Names: []*ast.Ident{ + {Name: "section"}, + }, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: 1, + Flags: 0, + }, + }, + IsConst: true, + }, + { + DeclBase: ast.DeclBase{ + Loc: &ast.Location{ + File: "/opt/homebrew/Cellar/inih/58/include/INIReader.h", + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Parent: &ast.Ident{Name: "INIReader"}, + }, + Name: &ast.Ident{Name: "HasValue"}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.LvalueRefType{ + X: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Names: []*ast.Ident{ + {Name: "section"}, + }, + }, + { + Type: &ast.LvalueRefType{ + X: &ast.BuiltinType{ + Kind: 6, + Flags: 0, + }, + }, + Doc: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{}, + }, + Names: []*ast.Ident{ + {Name: "name"}, + }, + }, + }, + }, + Ret: &ast.BuiltinType{ + Kind: 1, + Flags: 0, + }, + }, + IsConst: true, + }, + }, + }, + }, + }, + Includes: []*ast.Include{ + { + Path: "string", + }, + }, + Macros: []*ast.Macro{ + { + Name: "INIREADER_H", + Tokens: []*ast.Token{ + {Token: 3, Lit: "INIREADER_H"}, + }, + }, + { + Name: "INI_API", + Tokens: []*ast.Token{ + {Token: 3, Lit: "INI_API"}, + {Token: 2, Lit: "__attribute__"}, + {Token: 1, Lit: "("}, + {Token: 1, Lit: "("}, + {Token: 3, Lit: "visibility"}, + {Token: 1, Lit: "("}, + {Token: 4, Lit: "\"default\""}, + {Token: 1, Lit: ")"}, + {Token: 1, Lit: ")"}, + {Token: 1, Lit: ")"}, + }, + }, + }, + }, + }, + } + + fileSet, err := unmarshal.UnmarshalFileSet([]byte(files)) + + if err != nil { + t.Fatalf("UnmarshalNode failed: %v", err) + } + + resultJSON, err := json.MarshalIndent(fileSet, "", " ") + if err != nil { + t.Fatalf("Failed to marshal result to JSON: %v", err) + } + + expectedJSON, err := json.MarshalIndent(expected, "", " ") + + if err != nil { + t.Fatalf("Failed to marshal expected result to JSON: %v", err) + } + + if string(resultJSON) != string(expectedJSON) { + t.Errorf("JSON mismatch.\nExpected: %s\nGot: %s", string(expectedJSON), string(resultJSON)) + } +} + +func TestUnmarshalErrors(t *testing.T) { + testCases := []struct { + name string + fn func([]byte) (ast.Node, error) + input string + expectedErr string + }{ + // UnmarshalNode errors + { + name: "UnmarshalNode - Invalid JSON", + fn: unmarshal.UnmarshalNode, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling node type", + }, + { + name: "UnmarshalNode - Unknown type", + fn: unmarshal.UnmarshalNode, + input: `{"_Type": "UnknownType"}`, + expectedErr: "unknown node type: UnknownType", + }, + + // unmarshalToken errors + { + name: "unmarshalToken - Invalid JSON", + fn: unmarshal.UnmarshalToken, + input: `{"invalid": "json"`, + expectedErr: "unmarshalling Token", + }, + + // unmarshalMacro errors + { + name: "unmarshalMacro - Invalid JSON", + fn: unmarshal.UnmarshalMacro, + input: `{"invalid": "json"`, + expectedErr: "unmarshalling Macro", + }, + + // unmarshalInclude errors + { + name: "unmarshalInclude - Invalid JSON", + fn: unmarshal.UnmarshalInclude, + input: `{"invalid": "json"`, + expectedErr: "unmarshalling Include", + }, + + // unmarshalBasicLit errors + { + name: "unmarshalBasicLit - Invalid JSON", + fn: unmarshal.UnmarshalBasicLit, + input: `{"invalid": "json"`, + expectedErr: "unmarshalling BasicLit", + }, + + // unmarshalBuiltinType errors + { + name: "unmarshalBuiltinType - Invalid JSON", + fn: unmarshal.UnmarshalBuiltinType, + input: `{"invalid": "json"`, + expectedErr: "unmarshalling BuiltinType", + }, + + // unmarshalIdent errors + { + name: "unmarshalIdent - Invalid JSON", + fn: unmarshal.UnmarshalIdent, + input: `{"invalid": "json"`, + expectedErr: "unmarshalling Ident", + }, + + // unmarshalVariadic errors + { + name: "unmarshalVariadic - Invalid JSON", + fn: unmarshal.UnmarshalVariadic, + input: `{"invalid": "json"`, + expectedErr: "unmarshalling Variadic", + }, + + // unmarshalXType errors + { + name: "unmarshalXType - Invalid JSON", + fn: unmarshal.UnmarshalPointerType, + input: `{"invalid": "json"`, + expectedErr: "unmarshalling XType", + }, + { + name: "unmarshalXType - Invalid X field", + fn: unmarshal.UnmarshalPointerType, + input: `{"X": {"_Type": "InvalidType"}}`, + expectedErr: "unmarshalling field X", + }, + { + name: "unmarshalXType - Unexpected type", + fn: func(data []byte) (ast.Node, error) { + return unmarshal.UnmarshalXType(data, &ast.BasicLit{}) + }, + input: `{"X": {"_Type": "Ident", "Name": "test"}}`, + expectedErr: "unexpected type: *ast.BasicLit", + }, + + // unmarshalArrayType errors + { + name: "unmarshalArrayType - Invalid JSON", + fn: unmarshal.UnmarshalArrayType, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling ArrayType", + }, + { + name: "unmarshalArrayType - Invalid Elt", + fn: unmarshal.UnmarshalArrayType, + input: `{"Elt": {"_Type": "InvalidType"}, "Len": null}`, + expectedErr: "error unmarshalling array Elt", + }, + { + name: "unmarshalArrayType - Invalid Len", + fn: unmarshal.UnmarshalArrayType, + input: `{"Elt": {"_Type": "BuiltinType", "Kind": 1}, "Len": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling array Len", + }, + + // unmarshalField errors + { + name: "unmarshalField - Invalid JSON", + fn: unmarshal.UnmarshalField, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling Field", + }, + { + name: "unmarshalField - Invalid Type", + fn: unmarshal.UnmarshalField, + input: `{"Type": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling field Type", + }, + + // unmarshalFieldList errors + { + name: "unmarshalFieldList - Invalid JSON", + fn: unmarshal.UnmarshalFieldList, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling FieldList", + }, + { + name: "unmarshalFieldList - Invalid field", + fn: unmarshal.UnmarshalFieldList, + input: `{"List": [{"_Type": "InvalidType"}]}`, + expectedErr: "error unmarshalling field in FieldList", + }, + { + name: "unmarshalTagExpr - Invalid JSON", + fn: unmarshal.UnmarshalTagExpr, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling TagExpr", + }, + { + name: "unmarshalTagExpr - Invalid Name", + fn: unmarshal.UnmarshalTagExpr, + input: `{"Name": {"_Type": "InvalidType"}, "Tag": 0}`, + expectedErr: "error unmarshalling TagExpr Name", + }, + + // unmarshalScopingExpr errors + { + name: "unmarshalScopingExpr - Invalid JSON", + fn: unmarshal.UnmarshalScopingExpr, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling ScopingExpr", + }, + { + name: "unmarshalScopingExpr - Invalid Parent", + fn: unmarshal.UnmarshalScopingExpr, + input: `{"Parent": {"_Type": "InvalidType"}, "X": {"_Type": "Ident", "Name": "test"}}`, + expectedErr: "unknown node type: InvalidType", + }, + { + name: "unmarshalScopingExpr - Invalid X", + fn: unmarshal.UnmarshalScopingExpr, + input: `{"Parent": {"_Type": "Ident", "Name": "test"}, "X": {"_Type": "InvalidType"}}`, + expectedErr: "unknown node type: InvalidType", + }, + + // unmarshalEnumItem errors + { + name: "unmarshalEnumItem - Invalid JSON", + fn: unmarshal.UnmarshalEnumItem, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling EnumItem", + }, + { + name: "unmarshalEnumItem - Invalid Value", + fn: unmarshal.UnmarshalEnumItem, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Value": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling EnumItem Value", + }, + + // unmarshalEnumType errors + { + name: "unmarshalEnumType - Invalid JSON", + fn: unmarshal.UnmarshalEnumType, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling EnumType", + }, + { + name: "unmarshalEnumType - Invalid Item", + fn: unmarshal.UnmarshalEnumType, + input: `{"Items": [{"_Type": "InvalidType"}]}`, + expectedErr: "error unmarshalling EnumType Item", + }, + + // unmarshalRecordType errors + { + name: "unmarshalRecordType - Invalid JSON", + fn: unmarshal.UnmarshalRecordType, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling RecordType", + }, + { + name: "unmarshalRecordType - Invalid Fields", + fn: unmarshal.UnmarshalRecordType, + input: `{"Tag": 0, "Fields": {"_Type": "InvalidType"}, "Methods": []}`, + expectedErr: "error unmarshalling Fields in RecordType", + }, + { + name: "unmarshalRecordType - Invalid Method", + fn: unmarshal.UnmarshalRecordType, + input: `{"Tag": 0, "Fields": {"_Type": "FieldList", "List": []}, "Methods": [{"_Type": "InvalidType"}]}`, + expectedErr: "error unmarshalling method in RecordType", + }, + + // unmarshalFuncType errors + { + name: "unmarshalFuncType - Invalid JSON", + fn: unmarshal.UnmarshalFuncType, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling FuncType", + }, + { + name: "unmarshalFuncType - Invalid Params", + fn: unmarshal.UnmarshalFuncType, + input: `{"Params": {"_Type": "InvalidType"}, "Ret": {"_Type": "BuiltinType", "Kind": 1}}`, + expectedErr: "error unmarshalling Params in FuncType", + }, + { + name: "unmarshalFuncType - Invalid Ret", + fn: unmarshal.UnmarshalFuncType, + input: `{"Params": {"_Type": "FieldList", "List": []}, "Ret": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling Ret in FuncType", + }, + + // unmarshalFuncDecl errors + { + name: "unmarshalFuncDecl - Invalid JSON", + fn: unmarshal.UnmarshalFuncDecl, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling FuncDecl", + }, + { + name: "unmarshalFuncDecl - Invalid Type", + fn: unmarshal.UnmarshalFuncDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Type": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling FuncDecl Type", + }, + { + name: "unmarshalFuncDecl - Invalid DeclBase", + fn: unmarshal.UnmarshalFuncDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Type": {"_Type": "FuncType", "Params": {"_Type": "FieldList", "List": []}, "Ret": {"_Type": "BuiltinType", "Kind": 1}}, "Loc": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling FuncDecl DeclBase: error unmarshalling parent in DeclBase Type", + }, + { + name: "unmarshalFuncDecl - Invalid DeclBase", + fn: unmarshal.UnmarshalFuncDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"},"Loc":false, "Type": {"_Type": "FuncType", "Params": {"_Type": "FieldList", "List": []}, "Ret": {"_Type": "BuiltinType", "Kind": 1}}, "Loc": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling DeclBase Type", + }, + // unmarshalTypeDecl errors + { + name: "unmarshalTypeDecl - Invalid JSON", + fn: unmarshal.UnmarshalTypeDecl, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling TypeDecl", + }, + { + name: "unmarshalTypeDecl - Invalid Type", + fn: unmarshal.UnmarshalTypeDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Type": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling TypeDecl Type", + }, + { + name: "unmarshalTypeDecl - Invalid DeclBase", + fn: unmarshal.UnmarshalTypeDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Type": {"_Type": "BuiltinType", "Kind": 1}, "Loc": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling TypeDecl DeclBase", + }, + // unmarshalTypeDefDecl errors + { + name: "unmarshalTypeDefDecl - Invalid JSON", + fn: unmarshal.UnmarshalTypeDefDecl, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling TypeDefDecl", + }, + { + name: "unmarshalTypeDefDecl - Invalid Type", + fn: unmarshal.UnmarshalTypeDefDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Type": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling TypeDefDecl Type", + }, + { + name: "unmarshalTypeDefDecl - Invalid DeclBase", + fn: unmarshal.UnmarshalTypeDefDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Type": {"_Type": "BuiltinType", "Kind": 1}, "Loc": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling TypeDefDecl DeclBase", + }, + // unmarshalEnumTypeDecl errors + { + name: "unmarshalEnumTypeDecl - Invalid JSON", + fn: unmarshal.UnmarshalEnumTypeDecl, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling EnumTypeDecl", + }, + { + name: "unmarshalEnumTypeDecl - Invalid Type", + fn: unmarshal.UnmarshalEnumTypeDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Type": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling EnumTypeDecl Type", + }, + { + name: "unmarshalEnumTypeDecl - Invalid DeclBase", + fn: unmarshal.UnmarshalEnumTypeDecl, + input: `{"Name": {"_Type": "Ident", "Name": "test"}, "Type": {"_Type": "EnumType", "Items": []}, "Loc": {"_Type": "InvalidType"}}`, + expectedErr: "error unmarshalling EnumTypeDecl DeclBase", + }, + + // unmarshalFile errors + { + name: "unmarshalFile - Invalid JSON", + fn: unmarshal.UnmarshalFile, + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling File", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := tc.fn([]byte(tc.input)) + if err == nil { + t.Errorf("Expected error, but got nil") + } else if !strings.Contains(err.Error(), tc.expectedErr) { + t.Errorf("Expected error containing %q, but got %q", tc.expectedErr, err.Error()) + } + }) + } +} + +func TestUnmarshalFileSetErrors(t *testing.T) { + testCases := []struct { + name string + input string + expectedErr string + }{ + { + name: "Invalid JSON", + input: `{"invalid": "json"`, + expectedErr: "error unmarshalling FilesWithPath", + }, + { + name: "Invalid doc", + input: `[{"path": "test.cpp", "doc": {"_Type": "InvalidType"}}]`, + expectedErr: "error unmarshalling doc for path test.cpp", + }, + { + name: "Doc not *ast.File", + input: `[{"path": "test.cpp", "doc": {"_Type": "Token", "Token": 1, "Lit": "test"}}]`, + expectedErr: "doc is not of type *ast.File for path test.cpp", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := unmarshal.UnmarshalFileSet([]byte(tc.input)) + if tc.expectedErr == "" { + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + } else { + if err == nil { + t.Errorf("Expected error containing %q, but got nil", tc.expectedErr) + } else if !strings.Contains(err.Error(), tc.expectedErr) { + t.Errorf("Expected error containing %q, but got: %v", tc.expectedErr, err) + } + } + }) + } +} diff --git a/cmd/gogensig/visitor/ast_visitor.go b/cmd/gogensig/visitor/ast_visitor.go new file mode 100644 index 0000000..ee89b8c --- /dev/null +++ b/cmd/gogensig/visitor/ast_visitor.go @@ -0,0 +1,107 @@ +package visitor + +import ( + "fmt" + + "github.com/goplus/llgo/chore/llcppg/ast" +) + +type DocVisitor interface { + VisitStart(path string, incPath string, isSys bool) + Visit(node ast.Node) + VisitFuncDecl(funcDecl *ast.FuncDecl) + VisitDone(path string) + VisitStruct(structName *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) + //VisitClass(className *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) + //VisitMethod(className *ast.Ident, method *ast.FuncDecl, typeDecl *ast.TypeDecl) + VisitUnion(unionName *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) + VisitEnumTypeDecl(enumTypeDecl *ast.EnumTypeDecl) + VisitTypedefDecl(typedefDecl *ast.TypedefDecl) +} + +type BaseDocVisitor struct { + DocVisitor +} + +func NewBaseDocVisitor(Visitor DocVisitor) *BaseDocVisitor { + return &BaseDocVisitor{DocVisitor: Visitor} +} + +func (p *BaseDocVisitor) visitNode(decl ast.Node) { + switch v := decl.(type) { + case *ast.FuncDecl: + p.visitFuncDecl(v) + case *ast.TypeDecl: + p.visitTypeDecl(v) + case *ast.EnumTypeDecl: + p.visitEnumTypeDecl(v) + case *ast.TypedefDecl: + p.visitTypedefDecl(v) + default: + panic(fmt.Errorf("todo visit %v", v)) + } +} + +func (p *BaseDocVisitor) Visit(node ast.Node) { + switch v := node.(type) { + case *ast.File: + for _, decl := range v.Decls { + p.visitNode(decl) + } + default: + p.visitNode(v) + } +} + +func (p *BaseDocVisitor) visitFuncDecl(funcDecl *ast.FuncDecl) { + if funcDecl == nil { + return + } + p.VisitFuncDecl(funcDecl) +} + +func (p *BaseDocVisitor) visitTypeDecl(typeDecl *ast.TypeDecl) { + if typeDecl == nil { + return + } + if typeDecl.Type.Tag == ast.Class { + p.visitClass(typeDecl.Name, typeDecl.Type.Fields, typeDecl) + for _, method := range typeDecl.Type.Methods { + p.visitMethod(typeDecl.Name, method, typeDecl) + } + } else if typeDecl.Type.Tag == ast.Struct { + p.visitStruct(typeDecl.Name, typeDecl.Type.Fields, typeDecl) + } else if typeDecl.Type.Tag == ast.Union { + p.visitUnion(typeDecl.Name, typeDecl.Type.Fields, typeDecl) + } +} + +func (p *BaseDocVisitor) visitClass(className *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) { + //p.VisitClass(className, fields, typeDecl) +} + +func (p *BaseDocVisitor) visitMethod(className *ast.Ident, method *ast.FuncDecl, typeDecl *ast.TypeDecl) { + //p.VisitMethod(className, method, typeDecl) +} + +func (p *BaseDocVisitor) visitStruct(structName *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) { + p.VisitStruct(structName, fields, typeDecl) +} + +func (p *BaseDocVisitor) visitUnion(unionName *ast.Ident, fields *ast.FieldList, typeDecl *ast.TypeDecl) { + p.VisitUnion(unionName, fields, typeDecl) +} + +func (p *BaseDocVisitor) visitEnumTypeDecl(enumTypeDecl *ast.EnumTypeDecl) { + if enumTypeDecl == nil { + return + } + p.VisitEnumTypeDecl(enumTypeDecl) +} + +func (p *BaseDocVisitor) visitTypedefDecl(typedefDecl *ast.TypedefDecl) { + if typedefDecl == nil { + return + } + p.VisitTypedefDecl(typedefDecl) +}