diff --git a/_cmptest/testdata/sqlite3/3.49.1/sqlite3/llcppg.pub b/_cmptest/testdata/sqlite3/3.49.1/sqlite3/llcppg.pub index af95480d..b34755ea 100644 --- a/_cmptest/testdata/sqlite3/3.49.1/sqlite3/llcppg.pub +++ b/_cmptest/testdata/sqlite3/3.49.1/sqlite3/llcppg.pub @@ -15,7 +15,10 @@ sqlite3_context Context sqlite3_destructor_type DestructorType sqlite3_file File sqlite3_filename Filename +sqlite3_index_constraint IndexConstraint +sqlite3_index_constraint_usage IndexConstraintUsage sqlite3_index_info IndexInfo +sqlite3_index_orderby IndexOrderby sqlite3_int64 Int64 sqlite3_io_methods IoMethods sqlite3_loadext_entry LoadextEntry diff --git a/_cmptest/testdata/sqlite3/3.49.1/sqlite3/sqlite3.go b/_cmptest/testdata/sqlite3/3.49.1/sqlite3/sqlite3.go index 21e0b3a5..59c9717d 100644 --- a/_cmptest/testdata/sqlite3/3.49.1/sqlite3/sqlite3.go +++ b/_cmptest/testdata/sqlite3/3.49.1/sqlite3/sqlite3.go @@ -5689,15 +5689,22 @@ type Module struct { } type IndexConstraint struct { - Unused [8]uint8 + IColumn c.Int + Op c.Char + Usable c.Char + ITermOffset c.Int } type IndexOrderby struct { - Unused [8]uint8 + IColumn c.Int + Desc c.Char } +/* Outputs */ + type IndexConstraintUsage struct { - Unused [8]uint8 + ArgvIndex c.Int + Omit c.Char } /* diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 30d3099d..7fb3cf73 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -242,27 +242,18 @@ func (ct *Converter) visitTop(cursor, parent clang.Cursor) clang.ChildVisitResul case clang.CursorClassDecl: classDecl := ct.ProcessClassDecl(cursor) + // todo(zzy):class need consider nested struct situation ct.file.Decls = append(ct.file.Decls, classDecl) // class havent anonymous situation ct.logln("visitTop: ProcessClassDecl END", classDecl.Name.Name) case clang.CursorStructDecl: - structDecl := ct.ProcessStructDecl(cursor) - ct.file.Decls = append(ct.file.Decls, structDecl) + decls := ct.ProcessStructDecl(cursor) + ct.file.Decls = append(ct.file.Decls, decls...) ct.logf("visitTop: ProcessStructDecl END") - if structDecl.Name != nil { - ct.logln(structDecl.Name.Name) - } else { - ct.logln("ANONY") - } case clang.CursorUnionDecl: - unionDecl := ct.ProcessUnionDecl(cursor) - ct.file.Decls = append(ct.file.Decls, unionDecl) + decls := ct.ProcessUnionDecl(cursor) + ct.file.Decls = append(ct.file.Decls, decls...) ct.logf("visitTop: ProcessUnionDecl END") - if unionDecl.Name != nil { - ct.logln(unionDecl.Name.Name) - } else { - ct.logln("ANONY") - } case clang.CursorFunctionDecl, clang.CursorCXXMethod, clang.CursorConstructor, clang.CursorDestructor: // Handle functions and class methods (including out-of-class method) // Example: void MyClass::myMethod() { ... } out-of-class method @@ -759,12 +750,32 @@ func (ct *Converter) ProcessMethods(cursor clang.Cursor) []*ast.FuncDecl { return methods } -func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) *ast.TypeDecl { +func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) []ast.Decl { + var decls []ast.Decl ct.incIndent() defer ct.decIndent() cursorName, cursorKind := getCursorDesc(cursor) ct.logln("ProcessRecordDecl: CursorName:", cursorName, "CursorKind:", cursorKind) + childs := PostOrderVisitChildren(cursor, func(child, parent clang.Cursor) bool { + return (child.Kind == clang.CursorStructDecl || child.Kind == clang.CursorUnionDecl) && child.IsAnonymous() == 0 + }) + + for _, child := range childs { + // Check if this is a named nested struct/union + typ := ct.ProcessRecordType(child) + // note(zzy):use len(typ.Fields.List) to ensure it has fields not a forward declaration + // but maybe make the forward decl in to AST is also good. + if child.IsAnonymous() == 0 && len(typ.Fields.List) > 0 { + childName := clang.GoString(child.String()) + ct.logln("ProcessRecordDecl: Found named nested struct:", childName) + decls = append(decls, &ast.TypeDecl{ + Object: ct.CreateObject(child, &ast.Ident{Name: childName}), + Type: ct.ProcessRecordType(child), + }) + } + } + decl := &ast.TypeDecl{ Object: ct.CreateObject(cursor, nil), Type: ct.ProcessRecordType(cursor), @@ -778,14 +789,15 @@ func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) *ast.TypeDecl { ct.logln("ProcessRecordDecl: is anonymous") } - return decl + decls = append(decls, decl) + return decls } -func (ct *Converter) ProcessStructDecl(cursor clang.Cursor) *ast.TypeDecl { +func (ct *Converter) ProcessStructDecl(cursor clang.Cursor) []ast.Decl { return ct.ProcessRecordDecl(cursor) } -func (ct *Converter) ProcessUnionDecl(cursor clang.Cursor) *ast.TypeDecl { +func (ct *Converter) ProcessUnionDecl(cursor clang.Cursor) []ast.Decl { return ct.ProcessRecordDecl(cursor) } @@ -959,6 +971,19 @@ func (ct *Converter) BuildScopingExpr(cursor clang.Cursor) ast.Expr { return buildScopingFromParts(parts) } +func PostOrderVisitChildren(cursor clang.Cursor, collect func(c, p clang.Cursor) bool) []clang.Cursor { + var children []clang.Cursor + clangutils.VisitChildren(cursor, func(child, parent clang.Cursor) clang.ChildVisitResult { + if collect(child, parent) { + childs := PostOrderVisitChildren(child, collect) + children = append(children, childs[:]...) + children = append(children, child) + } + return clang.ChildVisit_Continue + }) + return children +} + func IsExplicitSigned(t clang.Type) bool { return t.Kind == clang.TypeCharS || t.Kind == clang.TypeSChar } diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 3d1536e8..0e10a904 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -19,18 +19,27 @@ import ( "github.com/goplus/llgo/xtool/clang/preprocessor" ) -func TestParser(t *testing.T) { +func TestParserCppMode(t *testing.T) { cases := []string{"class", "comment", "enum", "func", "scope", "struct", "typedef", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof"} // https://github.com/goplus/llgo/issues/1114 // todo(zzy):use os.ReadDir for _, folder := range cases { t.Run(folder, func(t *testing.T) { - testFrom(t, filepath.Join("testdata", folder), "temp.h", false) + testFrom(t, filepath.Join("testdata", folder), "temp.h", true, false) }) } } -func testFrom(t *testing.T, dir string, filename string, gen bool) { +func TestParserCMode(t *testing.T) { + cases := []string{"named_nested_struct"} + for _, folder := range cases { + t.Run(folder, func(t *testing.T) { + testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false) + }) + } +} + +func testFrom(t *testing.T, dir string, filename string, isCpp, gen bool) { var expect string var err error if !gen { @@ -42,7 +51,7 @@ func testFrom(t *testing.T, dir string, filename string, gen bool) { } ast, err := parser.Do(&parser.ConverterConfig{ File: filepath.Join(dir, filename), - IsCpp: true, + IsCpp: isCpp, Args: []string{"-fparse-all-comments"}, }) if err != nil { @@ -618,3 +627,42 @@ func compareOutput(t *testing.T, expected, actual string) { t.Fatalf("Test failed: expected \n%s \ngot \n%s", expected, actual) } } + +func TestPostOrderVisitChildren(t *testing.T) { + config := &clangutils.Config{ + File: "./testdata/named_nested_struct/temp.h", + Temp: false, + IsCpp: false, + } + + name := make(map[string]bool) + visit(config, func(cursor, parent clang.Cursor) clang.ChildVisitResult { + if cursor.Kind == clang.CursorStructDecl { + if !name[clang.GoString(cursor.String())] { + name[clang.GoString(cursor.String())] = true + file, line, column := clangutils.GetPresumedLocation(cursor.Location()) + fmt.Println("StructDecl Name:", clang.GoString(cursor.String()), file, line, column) + } + } + return clang.ChildVisit_Recurse + }) + + index, unit, err := clangutils.CreateTranslationUnit(config) + if err != nil { + panic(err) + } + defer index.Dispose() + defer unit.Dispose() + + childStr := make([]string, 6) + childs := parser.PostOrderVisitChildren(unit.Cursor(), func(child, parent clang.Cursor) bool { + return child.Kind == clang.CursorStructDecl + }) + for i, child := range childs { + childStr[i] = clang.GoString(child.String()) + } + expect := []string{"c", "d", "b", "f", "e", "a"} + if !reflect.DeepEqual(expect, childStr) { + fmt.Println("Unexpected child order:", childStr) + } +} diff --git a/_xtool/internal/parser/testdata/named_nested_struct/expect.json b/_xtool/internal/parser/testdata/named_nested_struct/expect.json new file mode 100755 index 00000000..3a01cf5f --- /dev/null +++ b/_xtool/internal/parser/testdata/named_nested_struct/expect.json @@ -0,0 +1,304 @@ +{ + "_Type": "File", + "decls": [ + { + "Doc": null, + "Loc": { + "File": "testdata/named_nested_struct/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "c", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "a", + "_Type": "Ident" + } + ], + "Type": { + "Flags": 0, + "Kind": 6, + "_Type": "BuiltinType" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/named_nested_struct/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "d", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "b", + "_Type": "Ident" + } + ], + "Type": { + "Flags": 0, + "Kind": 6, + "_Type": "BuiltinType" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/named_nested_struct/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "b", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "c_field", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "c", + "_Type": "Ident" + }, + "Tag": 0, + "_Type": "TagExpr" + }, + "_Type": "Field" + }, + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "d_field", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "d", + "_Type": "Ident" + }, + "Tag": 0, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/named_nested_struct/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "f", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "b", + "_Type": "Ident" + } + ], + "Type": { + "Flags": 0, + "Kind": 6, + "_Type": "BuiltinType" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/named_nested_struct/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "e", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "f_field", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "f", + "_Type": "Ident" + }, + "Tag": 0, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/named_nested_struct/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "a", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "b_field", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "b", + "_Type": "Ident" + }, + "Tag": 0, + "_Type": "TagExpr" + }, + "_Type": "Field" + }, + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "e_field", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "e", + "_Type": "Ident" + }, + "Tag": 0, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + } + ], + "includes": null, + "macros": null +} \ No newline at end of file diff --git a/_xtool/internal/parser/testdata/named_nested_struct/temp.h b/_xtool/internal/parser/testdata/named_nested_struct/temp.h new file mode 100644 index 00000000..c9fc5fe2 --- /dev/null +++ b/_xtool/internal/parser/testdata/named_nested_struct/temp.h @@ -0,0 +1,15 @@ +struct a { + struct b { + struct c { + int a; + } c_field; + struct d { + int b; + } d_field; + } b_field; + struct e { + struct f { + int b; + } f_field; + } e_field; +}; diff --git a/cl/internal/convert/_testdata/issue507/conf/llcppg.cfg b/cl/internal/convert/_testdata/issue507/conf/llcppg.cfg new file mode 100644 index 00000000..21381ce6 --- /dev/null +++ b/cl/internal/convert/_testdata/issue507/conf/llcppg.cfg @@ -0,0 +1,6 @@ +{ + "name": "issue507", + "include": ["temp.h"], + "libs": "$(pkg-config --libs xxx)", + "cplusplus":false +} diff --git a/cl/internal/convert/_testdata/issue507/gogensig.expect b/cl/internal/convert/_testdata/issue507/gogensig.expect new file mode 100644 index 00000000..31c76418 --- /dev/null +++ b/cl/internal/convert/_testdata/issue507/gogensig.expect @@ -0,0 +1,85 @@ +===== issue507_autogen_link.go ===== +package issue507 + +import _ "github.com/goplus/lib/c" + +const LLGoPackage string = "link: $(pkg-config --libs xxx);" + +===== temp.go ===== +package issue507 + +import ( + "github.com/goplus/lib/c" + _ "unsafe" +) + +type Ip6Addr struct { + Zone c.Int +} +type Ip6AddrT Ip6Addr + +type IpAddr struct { + UAddr struct { + Ip6 Ip6AddrT + } + Type c.Int +} +type IpAddrT IpAddr + +type Ip4Addr struct { + Addr c.Int +} +type Ip4AddrT Ip4Addr + +/** Args to LWIP_NSC_LINK_CHANGED callback */ + +type LinkChangedS struct { + State c.Int +} + +/** Args to LWIP_NSC_STATUS_CHANGED callback */ + +type StatusChangedS struct { + State c.Int +} + +/** Args to LWIP_NSC_IPV4_ADDRESS_CHANGED|LWIP_NSC_IPV4_GATEWAY_CHANGED|LWIP_NSC_IPV4_NETMASK_CHANGED|LWIP_NSC_IPV4_SETTINGS_CHANGED callback */ + +type Ipv4ChangedS struct { + OldAddress *IpAddrT + OldNetmask *IpAddrT + OldGw *IpAddrT +} + +/** Args to LWIP_NSC_IPV6_SET callback */ + +type Ipv6SetS struct { + AddrIndex c.Int + OldAddress *IpAddrT +} + +/** Args to LWIP_NSC_IPV6_ADDR_STATE_CHANGED callback */ + +type Ipv6AddrStateChangedS struct { + AddrIndex c.Int + OldState c.Int + Address *IpAddrT +} + +type NetifExtCallbackArgsT struct { + Ipv4Changed Ipv4ChangedS +} + +===== llcppg.pub ===== +ip4_addr Ip4Addr +ip4_addr_t Ip4AddrT +ip6_addr Ip6Addr +ip6_addr_t Ip6AddrT +ip_addr IpAddr +ip_addr_t IpAddrT +ipv4_changed_s Ipv4ChangedS +ipv6_addr_state_changed_s Ipv6AddrStateChangedS +ipv6_set_s Ipv6SetS +link_changed_s LinkChangedS +netif_ext_callback_args_t NetifExtCallbackArgsT +status_changed_s StatusChangedS \ No newline at end of file diff --git a/cl/internal/convert/_testdata/issue507/hfile/temp.h b/cl/internal/convert/_testdata/issue507/hfile/temp.h new file mode 100644 index 00000000..9debade6 --- /dev/null +++ b/cl/internal/convert/_testdata/issue507/hfile/temp.h @@ -0,0 +1,71 @@ + +struct ip6_addr +{ + int zone; +}; + +/** IPv6 address */ +typedef struct ip6_addr ip6_addr_t; + +typedef struct ip_addr +{ + union + { + ip6_addr_t ip6; + ip4_addr_t ip4; + } u_addr; + /** @ref lwip_ip_addr_type */ + int type; +} ip_addr_t; + +struct ip4_addr +{ + int addr; +}; + +/** ip4_addr_t uses a struct for convenience only, so that the same defines can + * operate both on ip4_addr_t as well as on ip4_addr_p_t. */ +typedef struct ip4_addr ip4_addr_t; + +typedef union +{ + /** Args to LWIP_NSC_LINK_CHANGED callback */ + struct link_changed_s + { + /** 1: up; 0: down */ + int state; + } link_changed; + /** Args to LWIP_NSC_STATUS_CHANGED callback */ + struct status_changed_s + { + /** 1: up; 0: down */ + int state; + } status_changed; + /** Args to LWIP_NSC_IPV4_ADDRESS_CHANGED|LWIP_NSC_IPV4_GATEWAY_CHANGED|LWIP_NSC_IPV4_NETMASK_CHANGED|LWIP_NSC_IPV4_SETTINGS_CHANGED callback */ + struct ipv4_changed_s + { + /** Old IPv4 address */ + const ip_addr_t *old_address; + const ip_addr_t *old_netmask; + const ip_addr_t *old_gw; + } ipv4_changed; + /** Args to LWIP_NSC_IPV6_SET callback */ + struct ipv6_set_s + { + /** Index of changed IPv6 address */ + int addr_index; + /** Old IPv6 address */ + const ip_addr_t *old_address; + } ipv6_set; + /** Args to LWIP_NSC_IPV6_ADDR_STATE_CHANGED callback */ + struct ipv6_addr_state_changed_s + { + /** Index of affected IPv6 address */ + int addr_index; + /** Old IPv6 address state */ + int old_state; + /** Affected IPv6 address */ + const ip_addr_t *address; + } ipv6_addr_state_changed; +} netif_ext_callback_args_t; + diff --git a/cl/internal/convert/_testdata/nested/gogensig.expect b/cl/internal/convert/_testdata/nested/gogensig.expect index f2abfce3..b7736df2 100644 --- a/cl/internal/convert/_testdata/nested/gogensig.expect +++ b/cl/internal/convert/_testdata/nested/gogensig.expect @@ -21,6 +21,16 @@ type Struct1 struct { } } +type InnerStruct struct { + L c.Long +} + +// https://github.com/goplus/llcppg/issues/514 +// named nested struct +type StructWithNested struct { + Init InnerStruct +} + type Struct2 struct { B *c.Char Size c.SizeT @@ -46,8 +56,43 @@ type Union2 struct { } } +type C struct { + A c.Int +} + +type D struct { + B c.Int +} + +type B struct { + CField C + DField D +} + +type F struct { + B c.Int +} + +type E struct { + FField F +} + +// https://github.com/goplus/llcppg/issues/514 +type A struct { + BField B + EField E +} + ===== llcppg.pub ===== +a A +b B +c C +d D +e E +f F +inner_struct InnerStruct struct1 Struct1 struct2 Struct2 +struct_with_nested StructWithNested union1 Union1 union2 Union2 \ No newline at end of file diff --git a/cl/internal/convert/_testdata/nested/hfile/temp.h b/cl/internal/convert/_testdata/nested/hfile/temp.h index 9d8a2c2b..570de213 100644 --- a/cl/internal/convert/_testdata/nested/hfile/temp.h +++ b/cl/internal/convert/_testdata/nested/hfile/temp.h @@ -10,6 +10,15 @@ struct struct1 } init; }; + +// https://github.com/goplus/llcppg/issues/514 +// named nested struct +struct struct_with_nested { + struct inner_struct { + long l; + } init; +}; + struct struct2 { char *b; @@ -47,4 +56,22 @@ union union2 char b[60]; struct2 rec; } init; -}; \ No newline at end of file +}; + + +// https://github.com/goplus/llcppg/issues/514 +struct a { + struct b { + struct c { + int a; + } c_field; + struct d { + int b; + } d_field; + } b_field; + struct e { + struct f { + int b; + } f_field; + } e_field; +};