From b75e8423dcb1303b6a7072359e7339f5a938c01e Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Wed, 30 Apr 2025 16:23:07 +0200 Subject: [PATCH 1/4] Add support for Objective-C --- src/CppAst.Tests/TestObjectiveC.cs | 100 ++++++ src/CppAst/CppAst.csproj | 3 +- src/CppAst/CppAttributeKind.cs | 1 + src/CppAst/CppBlockFunctionType.cs | 24 ++ src/CppAst/CppClass.cs | 73 +++-- src/CppAst/CppClassKind.cs | 8 + src/CppAst/CppFunctionFlags.cs | 8 +- src/CppAst/CppFunctionType.cs | 53 +--- src/CppAst/CppFunctionTypeBase.cs | 81 +++++ src/CppAst/CppGenericType.cs | 49 +++ src/CppAst/CppGlobalDeclarationContainer.cs | 4 + src/CppAst/CppModelBuilder.cs | 320 ++++++++++++++++---- src/CppAst/CppNamespace.cs | 4 + src/CppAst/CppObjCCategory.cs | 88 ++++++ src/CppAst/CppPrimitiveKind.cs | 40 +++ src/CppAst/CppPrimitiveType.cs | 56 ++++ src/CppAst/CppProperty.cs | 71 +++++ src/CppAst/CppTemplateKind.cs | 4 + src/CppAst/CppTypeKind.cs | 8 + src/CppAst/ICppDeclarationContainer.cs | 5 + 20 files changed, 866 insertions(+), 134 deletions(-) create mode 100644 src/CppAst.Tests/TestObjectiveC.cs create mode 100644 src/CppAst/CppBlockFunctionType.cs create mode 100644 src/CppAst/CppFunctionTypeBase.cs create mode 100644 src/CppAst/CppGenericType.cs create mode 100644 src/CppAst/CppObjCCategory.cs create mode 100644 src/CppAst/CppProperty.cs diff --git a/src/CppAst.Tests/TestObjectiveC.cs b/src/CppAst.Tests/TestObjectiveC.cs new file mode 100644 index 00000000..bd14090c --- /dev/null +++ b/src/CppAst.Tests/TestObjectiveC.cs @@ -0,0 +1,100 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CppAst.Tests; + +public class TestObjectiveC : InlineTestBase +{ + [Test] + public void TestSimple() + { + ParseAssert(""" + // SimpleHeader.h + //#import + //@import Foundation; + + // Fake Foundation enum declaration for options + typedef enum __attribute__((flag_enum)) MyOptions : int MyOptions; + enum MyOptions : int { + MyOptions1 = 1, + MyOptions2 = 2, + MyOptions3 = 4, + }; + + @protocol MyProtocol + + (void)doSomething; + @end + + @protocol MyProtocol2 + + (void)doSomething2; + @end + + @interface TesterBase + @end + + @interface SimpleThing : TesterBase + + /// A read-write string property + @property (readonly) const char *name; + + @property int id; + + /// A class factory method + + (instancetype)thingWithName:(const char *)name; + + /// An instance method that returns a description + - (const char *)describe; + + - (void)runWithCompletion:(void (^)(int result))completion; + + @end + + @interface SimpleThing (MyCategory) + @property int categoryId; + @end + + @interface SimpleTemplate : SimpleThing + - (TArg)get_at:(int)index; + @end + + @interface SimpleTemplate2 : SimpleTemplate + @end + + """, + compilation => + { + Assert.False(compilation.HasErrors); + + + }, new() + { + ParseAsCpp = false, + TargetCpu = CppTargetCpu.ARM64, + TargetVendor = "apple", + TargetSystem = "darwin", + ParseMacros = false, + ParseSystemIncludes = true, + AdditionalArguments = + { + "-x", "objective-c", + "-std=gnu11", + //"-ObjC", + //"-fblocks", + //"-fno-modules", + //"-fmodules", + //"-fimplicit-module-maps", + //"-fno-implicit-modules", + "-isysroot", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk", + "-F", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks", + "-isystem", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include", + "-isystem", "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include", + } + } + ); + } +} \ No newline at end of file diff --git a/src/CppAst/CppAst.csproj b/src/CppAst/CppAst.csproj index 570b83af..85e942d1 100644 --- a/src/CppAst/CppAst.csproj +++ b/src/CppAst/CppAst.csproj @@ -1,6 +1,6 @@  - net8.0 + net8.0 CppAst CppAst is a .NET library providing a C/C++ parser for header files with access to the full AST, comments and macros Alexandre Mutel @@ -12,7 +12,6 @@ BSD-2-Clause git https://github.com/xoofx/CppAst - 7.3 true en-US $(NoWarn);CS1591 diff --git a/src/CppAst/CppAttributeKind.cs b/src/CppAst/CppAttributeKind.cs index a1459a42..540455eb 100644 --- a/src/CppAst/CppAttributeKind.cs +++ b/src/CppAst/CppAttributeKind.cs @@ -17,5 +17,6 @@ public enum AttributeKind AnnotateAttribute, CommentAttribute, TokenAttribute, //the attribute is parse from token, and the parser is slow. + ObjectiveCAttribute, } } \ No newline at end of file diff --git a/src/CppAst/CppBlockFunctionType.cs b/src/CppAst/CppBlockFunctionType.cs new file mode 100644 index 00000000..5a908fd1 --- /dev/null +++ b/src/CppAst/CppBlockFunctionType.cs @@ -0,0 +1,24 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace CppAst +{ + /// + /// An Objective-C block function type (e.g `void (^)(int arg1, int arg2)`) + /// + public sealed class CppBlockFunctionType : CppFunctionTypeBase + { + /// + /// Constructor of a function type. + /// + /// Return type of this function type. + public CppBlockFunctionType(CppType returnType) : base(CppTypeKind.ObjCBlockFunction, returnType) + { + } + } +} \ No newline at end of file diff --git a/src/CppAst/CppClass.cs b/src/CppAst/CppClass.cs index 6ee81ae0..824f04d8 100644 --- a/src/CppAst/CppClass.cs +++ b/src/CppAst/CppClass.cs @@ -31,6 +31,9 @@ public CppClass(string name) : base(CppTypeKind.StructOrClass) TemplateParameters = new List(); Attributes = new List(); TokenAttributes = new List(); + ObjCImplementedProtocols = new List(); + Properties = new CppContainerList(this); + ObjCCategories = new List(); } /// @@ -121,10 +124,18 @@ public override string FullName /// Get the base types of this type. /// public List BaseTypes { get; } - + + /// + /// Get the Objective-C implemented protocols. + /// + public List ObjCImplementedProtocols { get; } + /// public CppContainerList Fields { get; } + /// + public CppContainerList Properties { get; } + /// /// Gets the constructors of this instance. /// @@ -146,6 +157,11 @@ public override string FullName /// public CppContainerList Typedefs { get; } + + /// + /// Gets the Objective-C categories of this instance. + /// + public List ObjCCategories { get; } /// public List TemplateParameters { get; } @@ -192,6 +208,12 @@ public override string ToString() case CppClassKind.Union: builder.Append("union "); break; + case CppClassKind.ObjCInterface: + builder.Append("@interface "); + break; + case CppClassKind.ObjCProtocol: + builder.Append("@protocol "); + break; default: throw new ArgumentOutOfRangeException(); } @@ -200,18 +222,7 @@ public override string ToString() { builder.Append(Name); } - - if (BaseTypes.Count > 0) - { - builder.Append(" : "); - for (var i = 0; i < BaseTypes.Count; i++) - { - var baseType = BaseTypes[i]; - if (i > 0) builder.Append(", "); - builder.Append(baseType); - } - } - + //Add template arguments here if(TemplateKind != CppTemplateKind.NormalClass) { @@ -227,17 +238,39 @@ public override string ToString() } else if(TemplateKind == CppTemplateKind.TemplateClass) { - for (var i = 0; i < TemplateParameters.Count; i++) - { - if (i > 0) builder.Append(", "); - builder.Append(TemplateParameters[i].ToString()); - } - } + for (var i = 0; i < TemplateParameters.Count; i++) + { + if (i > 0) builder.Append(", "); + builder.Append(TemplateParameters[i].ToString()); + } + } builder.Append(">"); } - builder.Append(" { ... }"); + if (BaseTypes.Count > 0) + { + builder.Append(" : "); + for (var i = 0; i < BaseTypes.Count; i++) + { + var baseType = BaseTypes[i]; + if (i > 0) builder.Append(", "); + builder.Append(baseType); + } + } + + if (ObjCImplementedProtocols.Count > 0) + { + builder.Append(" <"); + for (var i = 0; i < ObjCImplementedProtocols.Count; i++) + { + var protocol = ObjCImplementedProtocols[i]; + if (i > 0) builder.Append(", "); + builder.Append(protocol.Name); + } + builder.Append(">"); + } + return builder.ToString(); } diff --git a/src/CppAst/CppClassKind.cs b/src/CppAst/CppClassKind.cs index 25613cce..5f6f55e5 100644 --- a/src/CppAst/CppClassKind.cs +++ b/src/CppAst/CppClassKind.cs @@ -21,5 +21,13 @@ public enum CppClassKind /// A C++ `union` /// Union, + /// + /// An Objective-C `@interface` + /// + ObjCInterface, + /// + /// An Objective-C `@protocol` + /// + ObjCProtocol, } } \ No newline at end of file diff --git a/src/CppAst/CppFunctionFlags.cs b/src/CppAst/CppFunctionFlags.cs index 929154aa..1bf8b0a7 100644 --- a/src/CppAst/CppFunctionFlags.cs +++ b/src/CppAst/CppFunctionFlags.cs @@ -38,7 +38,7 @@ public enum CppFunctionFlags Virtual = 1 << 3, /// - /// This is a C++ method + /// This is a C++ or Objective-C instance method /// Method = 1 << 4, @@ -71,5 +71,11 @@ public enum CppFunctionFlags /// This is a deleted function /// Deleted = 1 << 10, + + /// + /// This is an Objective-C class method + /// + ClassMethod = 1 << 11, + } } \ No newline at end of file diff --git a/src/CppAst/CppFunctionType.cs b/src/CppAst/CppFunctionType.cs index 8edc04ac..144ad690 100644 --- a/src/CppAst/CppFunctionType.cs +++ b/src/CppAst/CppFunctionType.cs @@ -11,63 +11,14 @@ namespace CppAst /// /// A C++ function type (e.g `void (*)(int arg1, int arg2)`) /// - public sealed class CppFunctionType : CppTypeDeclaration + public sealed class CppFunctionType : CppFunctionTypeBase { /// /// Constructor of a function type. /// /// Return type of this function type. - public CppFunctionType(CppType returnType) : base(CppTypeKind.Function) + public CppFunctionType(CppType returnType) : base(CppTypeKind.Function, returnType) { - ReturnType = returnType ?? throw new ArgumentNullException(nameof(returnType)); - Parameters = new CppContainerList(this); - } - - /// - /// Gets or sets the calling convention of this function type. - /// - public CppCallingConvention CallingConvention { get; set; } - - /// - /// Gets or sets the return type of this function type. - /// - public CppType ReturnType { get; set; } - - /// - /// Gets a list of the parameters. - /// - public CppContainerList Parameters { get; } - - /// - public override int SizeOf - { - get => 0; - - set => throw new InvalidOperationException("This type does not support SizeOf"); - } - - /// - public override IEnumerable Children() => Parameters; - - /// - public override CppType GetCanonicalType() => this; - - /// - public override string ToString() - { - var builder = new StringBuilder(); - builder.Append(ReturnType.GetDisplayName()); - builder.Append(" "); - builder.Append("(*)("); - for (var i = 0; i < Parameters.Count; i++) - { - var param = Parameters[i]; - if (i > 0) builder.Append(", "); - builder.Append(param); - } - - builder.Append(")"); - return builder.ToString(); } } } \ No newline at end of file diff --git a/src/CppAst/CppFunctionTypeBase.cs b/src/CppAst/CppFunctionTypeBase.cs new file mode 100644 index 00000000..6e6da312 --- /dev/null +++ b/src/CppAst/CppFunctionTypeBase.cs @@ -0,0 +1,81 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace CppAst +{ + /// + /// An Objective-C block function (e.g `void (^)(int arg1, int arg2)`) or C++ function type (e.g `void (*)(int arg1, int arg2)`) + /// + public abstract class CppFunctionTypeBase : CppTypeDeclaration + { + /// + /// Constructor of a function type. + /// + /// Return type of this function type. + protected CppFunctionTypeBase(CppTypeKind kind, CppType returnType) : base(kind) + { + ReturnType = returnType ?? throw new ArgumentNullException(nameof(returnType)); + Parameters = new CppContainerList(this); + } + + /// + /// Gets or sets the calling convention of this function type. + /// + public CppCallingConvention CallingConvention { get; set; } + + /// + /// Gets or sets the return type of this function type. + /// + public CppType ReturnType { get; set; } + + /// + /// Gets a list of the parameters. + /// + public CppContainerList Parameters { get; } + + /// + public override int SizeOf + { + get => 0; + + set => throw new InvalidOperationException("This type does not support SizeOf"); + } + + /// + public override IEnumerable Children() => Parameters; + + /// + public override CppType GetCanonicalType() => this; + + /// + public override string ToString() + { + var builder = new StringBuilder(); + builder.Append(ReturnType.GetDisplayName()); + builder.Append(" "); + // Don't complicate with a virtual methods, hardcode derived cases here + if (TypeKind == CppTypeKind.ObjCBlockFunction) + { + builder.Append("(^)("); + } + else + { + builder.Append("(*)("); + } + for (var i = 0; i < Parameters.Count; i++) + { + var param = Parameters[i]; + if (i > 0) builder.Append(", "); + builder.Append(param); + } + + builder.Append(")"); + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/CppAst/CppGenericType.cs b/src/CppAst/CppGenericType.cs new file mode 100644 index 00000000..237c632f --- /dev/null +++ b/src/CppAst/CppGenericType.cs @@ -0,0 +1,49 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System.Collections.Generic; +using System.Text; + +namespace CppAst; + +/// +/// A generic type, a type that has a base type and a list of generic type arguments. +/// +public class CppGenericType : CppType +{ + public CppGenericType(CppType baseType) : base(CppTypeKind.GenericType) + { + BaseType = baseType; + GenericArguments = new List(); + } + + public CppType BaseType { get; set; } + + public List GenericArguments { get; } + + public override int SizeOf { get; set; } + + public override CppType GetCanonicalType() => this; + + + public override string ToString() + { + var builder = new StringBuilder(); + builder.Append(BaseType.GetDisplayName()); + if (GenericArguments.Count > 0) + { + builder.Append('<'); + for (int i = 0; i < GenericArguments.Count; i++) + { + if (i > 0) + { + builder.Append(", "); + } + builder.Append(GenericArguments[i].GetDisplayName()); + } + builder.Append(">"); + } + return builder.ToString(); + } +} \ No newline at end of file diff --git a/src/CppAst/CppGlobalDeclarationContainer.cs b/src/CppAst/CppGlobalDeclarationContainer.cs index a8d4fa05..af7ad6a6 100644 --- a/src/CppAst/CppGlobalDeclarationContainer.cs +++ b/src/CppAst/CppGlobalDeclarationContainer.cs @@ -32,6 +32,7 @@ public CppGlobalDeclarationContainer() Namespaces = new CppContainerList(this); Attributes = new List(); TokenAttributes = new List(); + Properties = new CppContainerList(this); } /// @@ -45,6 +46,9 @@ public CppGlobalDeclarationContainer() /// public CppContainerList Fields { get; } + /// + public CppContainerList Properties { get; } + /// public CppContainerList Functions { get; } diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index 952098e0..499e48eb 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Linq.Expressions; +using System.Runtime.InteropServices; using System.Text; using ClangSharp; using ClangSharp.Interop; @@ -20,6 +22,7 @@ internal unsafe class CppModelBuilder private readonly CppContainerContext _rootContainerContext; private readonly Dictionary _containers; private readonly Dictionary _typedefs; + private CppClass _currentClassBeingVisited; public CppModelBuilder() { @@ -97,10 +100,22 @@ private bool TryGetDeclarationContainer(CXCursor cursor, void* data, out string typeAsCString = CXUtil.GetCursorDisplayName(cursor); } // Try to workaround anonymous types - typeKey = $"{cursor.Kind}:{typeAsCString}{(cursor.IsAnonymous ? "/" + cursor.Hash : string.Empty)}"; + typeKey = $"{typeAsCString}{(cursor.IsAnonymous ? "/" + cursor.Hash : string.Empty)}"; return _containers.TryGetValue(typeKey, out containerContext); } + private CppContainerContext GetDeclarationContainer(CXCursor cursor, void* data) + { + if (TryGetDeclarationContainer(cursor, data, out string typeKey, out var containerContext)) + { + return containerContext; + } + + // We don't have a container for this cursor + // This can happen for example for a function in a class + throw new InvalidOperationException($"Unable to find a container for this cursor {cursor}"); + } + private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, void* data) { if (TryGetDeclarationContainer(cursor, data, out string typeKey, out var containerContext)) @@ -141,11 +156,37 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi symbol = cppEnum; break; + case CXCursorKind.CXCursor_ObjCCategoryDecl: + { + // Fetch the target class for the category + CXCursor parentCursor = default; + cursor.VisitChildren((cxCursor, parent, clientData) => + { + if (cxCursor.Kind == CXCursorKind.CXCursor_ObjCClassRef) + { + parentCursor = cxCursor.Referenced; + return CXChildVisitResult.CXChildVisit_Break; + } + + return CXChildVisitResult.CXChildVisit_Continue; + }, default); + + var parentContainer = GetOrCreateDeclarationContainer(parentCursor, data).Container; + var targetClass = (CppClass)parentContainer; + + var category = new CppObjCCategory(targetClass, CXUtil.GetCursorSpelling(cursor)); + targetClass.ObjCCategories.Add(category); + symbol = category; + break; + } + case CXCursorKind.CXCursor_ClassTemplate: case CXCursorKind.CXCursor_ClassTemplatePartialSpecialization: case CXCursorKind.CXCursor_ClassDecl: case CXCursorKind.CXCursor_StructDecl: case CXCursorKind.CXCursor_UnionDecl: + case CXCursorKind.CXCursor_ObjCInterfaceDecl: + case CXCursorKind.CXCursor_ObjCProtocolDecl: var cppClass = new CppClass(CXUtil.GetCursorSpelling(cursor)); parentDeclarationContainer.Classes.Add(cppClass); symbol = cppClass; @@ -153,6 +194,8 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi switch (cursor.Kind) { case CXCursorKind.CXCursor_ClassDecl: + case CXCursorKind.CXCursor_ClassTemplate: + case CXCursorKind.CXCursor_ClassTemplatePartialSpecialization: cppClass.ClassKind = CppClassKind.Class; break; case CXCursorKind.CXCursor_StructDecl: @@ -161,24 +204,17 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi case CXCursorKind.CXCursor_UnionDecl: cppClass.ClassKind = CppClassKind.Union; break; + case CXCursorKind.CXCursor_ObjCInterfaceDecl: + cppClass.ClassKind = CppClassKind.ObjCInterface; + break; + case CXCursorKind.CXCursor_ObjCProtocolDecl: + cppClass.ClassKind = CppClassKind.ObjCProtocol; + break; } cppClass.IsAbstract = cursor.CXXRecord_IsAbstract; - - if (cursor.Kind == CXCursorKind.CXCursor_ClassTemplate) - { - cppClass.TemplateKind = CppTemplateKind.TemplateClass; - cursor.VisitChildren((childCursor, classCursor, clientData) => - { - var tmplParam = TryToCreateTemplateParameters(childCursor, clientData); - if (tmplParam != null) - { - cppClass.TemplateParameters.Add(tmplParam); - } - return CXChildVisitResult.CXChildVisit_Continue; - }, new CXClientData((IntPtr)data)); - } - else if (cursor.DeclKind == CX_DeclKind.CX_DeclKind_ClassTemplateSpecialization + + if (cursor.DeclKind == CX_DeclKind.CX_DeclKind_ClassTemplateSpecialization || cursor.DeclKind == CX_DeclKind.CX_DeclKind_ClassTemplatePartialSpecialization) { //Try to generate template class first @@ -238,7 +274,24 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi } else { - cppClass.TemplateKind = CppTemplateKind.NormalClass; + cursor.VisitChildren((childCursor, classCursor, clientData) => + { + var tmplParam = TryToCreateTemplateParameters(childCursor, clientData); + if (tmplParam != null) + { + cppClass.TemplateParameters.Add(tmplParam); + if (cppClass.ClassKind == CppClassKind.ObjCInterface || + cppClass.ClassKind == CppClassKind.ObjCProtocol) + { + cppClass.TemplateKind = CppTemplateKind.ObjCGenericClass; + } + else + { + cppClass.TemplateKind = CppTemplateKind.TemplateClass; + } + } + return CXChildVisitResult.CXChildVisit_Continue; + }, new CXClientData((IntPtr)data)); } defaultContainerVisibility = cursor.Kind == CXCursorKind.CXCursor_ClassDecl ? CppVisibility.Private : CppVisibility.Public; @@ -289,14 +342,28 @@ private CppNamespace VisitNamespace(CXCursor cursor, void* data) private CppClass VisitClassDecl(CXCursor cursor, void* data) { var cppStruct = GetOrCreateDeclarationContainer(cursor, data, out var context); - if (cursor.IsDefinition && !cppStruct.IsDefinition) + if ((cursor.IsDefinition && !cppStruct.IsDefinition) || (!context.IsChildrenVisited && (cppStruct.ClassKind == CppClassKind.ObjCInterface || cppStruct.ClassKind == CppClassKind.ObjCProtocol))) { ParseAttributes(cursor, cppStruct, false); cppStruct.IsDefinition = true; cppStruct.SizeOf = (int)cursor.Type.SizeOf; cppStruct.AlignOf = (int)cursor.Type.AlignOf; context.IsChildrenVisited = true; + var saveCurrentClassBeingVisited = _currentClassBeingVisited; + _currentClassBeingVisited = cppStruct; cursor.VisitChildren(VisitMember, new CXClientData((IntPtr)data)); + + // Resolve getter/setter methods + if (cppStruct.Properties.Count > 0) + { + foreach (var prop in cppStruct.Properties) + { + // Search getter / setter methods + prop.Getter = cppStruct.Functions.FirstOrDefault(m => m.Name == prop.GetterName); + prop.Setter = cppStruct.Functions.FirstOrDefault(m => m.Name == prop.SetterName); + } + } + _currentClassBeingVisited = saveCurrentClassBeingVisited; } return cppStruct; } @@ -337,6 +404,8 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d case CXCursorKind.CXCursor_ClassDecl: case CXCursorKind.CXCursor_StructDecl: case CXCursorKind.CXCursor_UnionDecl: + case CXCursorKind.CXCursor_ObjCInterfaceDecl: + case CXCursorKind.CXCursor_ObjCProtocolDecl: { bool isAnonymous = cursor.IsAnonymous; var cppClass = VisitClassDecl(cursor, data); @@ -381,7 +450,13 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d case CXCursorKind.CXCursor_EnumDecl: element = VisitEnumDecl(cursor, data); break; - + case CXCursorKind.CXCursor_FlagEnum: + { + var containerContext = GetOrCreateDeclarationContainer(parent, data); + var cppEnum = (CppEnum)containerContext.Container; + cppEnum.Attributes.Add(new CppAttribute("flag_enum", AttributeKind.ObjectiveCAttribute)); + break; + } case CXCursorKind.CXCursor_TypedefDecl: element = VisitTypeDefDecl(cursor, data); break; @@ -397,6 +472,8 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d case CXCursorKind.CXCursor_Constructor: case CXCursorKind.CXCursor_Destructor: case CXCursorKind.CXCursor_CXXMethod: + case CXCursorKind.CXCursor_ObjCClassMethodDecl: + case CXCursorKind.CXCursor_ObjCInstanceMethodDecl: element = VisitFunctionDecl(cursor, parent, data); break; @@ -405,7 +482,37 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d break; case CXCursorKind.CXCursor_UnexposedDecl: return CXChildVisitResult.CXChildVisit_Recurse; - + + case CXCursorKind.CXCursor_ObjCClassRef: + case CXCursorKind.CXCursor_ObjCProtocolRef: + { + var objCContainer = GetOrCreateDeclarationContainer(parent, data).Container; + if (objCContainer is CppClass cppClass) + { + var referencedType = (CppClass)GetOrCreateDeclarationContainer(cursor.Referenced, data).Container; + if (cursor.Kind == CXCursorKind.CXCursor_ObjCClassRef) + { + var cppBaseType = new CppBaseType(referencedType); + cppClass.BaseTypes.Add(cppBaseType); + } + else + { + cppClass.ObjCImplementedProtocols.Add(referencedType); + } + } + break; + } + case CXCursorKind.CXCursor_TypeRef: + if (_currentClassBeingVisited != null && _currentClassBeingVisited.BaseTypes.Count == 1) + { + var baseType = _currentClassBeingVisited.BaseTypes[0].Type; + CppGenericType genericType = baseType as CppGenericType ?? new CppGenericType(baseType); + var type = GetCppType(cursor.Referenced, cursor.Type, cursor, data); + genericType.GenericArguments.Add(type); + } + break; + + case CXCursorKind.CXCursor_CXXBaseSpecifier: { var cppClass = (CppClass)GetOrCreateDeclarationContainer(parent, data).Container; @@ -430,23 +537,37 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d case CXCursorKind.CXCursor_MacroDefinition: element = ParseMacro(cursor); break; + case CXCursorKind.CXCursor_MacroExpansion: - break; case CXCursorKind.CXCursor_InclusionDirective: - // Don't emit warning for this directive - break; - + case CXCursorKind.CXCursor_FirstRef: + case CXCursorKind.CXCursor_ObjCIvarDecl: case CXCursorKind.CXCursor_TemplateTypeParameter: - case CXCursorKind.CXCursor_AnnotateAttr: - // Don't emit warning + if (cursor.IsAttribute) + { + System.Diagnostics.Debugger.Break(); + } break; case CXCursorKind.CXCursor_LinkageSpec: cursor.VisitChildren(VisitMember, new CXClientData((IntPtr)data)); break; + case CXCursorKind.CXCursor_ObjCCategoryDecl: + element = VisitObjCCategory(cursor, data); + break; + case CXCursorKind.CXCursor_ObjCPropertyDecl: + { + var containerContext = GetOrCreateDeclarationContainer(parent, data); + element = VisitProperty(containerContext, cursor, data); + break; + } + default: - WarningUnhandled(cursor, parent); + if (!cursor.IsAttribute) + { + WarningUnhandled(cursor, parent); + } break; } @@ -478,6 +599,15 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d return CXChildVisitResult.CXChildVisit_Continue; } + private CppElement VisitObjCCategory(CXCursor cursor, void* data) + { + var categoryContainer = GetOrCreateDeclarationContainer(cursor, data).Container; + var cppCategory = (CppObjCCategory)categoryContainer; + _currentClassBeingVisited = cppCategory.TargetClass; + cursor.VisitChildren(VisitMember, new CXClientData((IntPtr)data)); + _currentClassBeingVisited = null; + return cppCategory; + } private CppComment GetComment(CXCursor cursor) { @@ -877,6 +1007,19 @@ private static bool IsAnonymousTypeUsed(CppType type, CppType anonymousType, Has return false; } + private CppProperty VisitProperty(CppContainerContext containerContext, CXCursor cursor, void* data) + { + var propertyName = CXUtil.GetCursorSpelling(cursor); + var type = GetCppType(cursor.Type.Declaration, cursor.Type, cursor, data); + + var cppProperty = new CppProperty(type, propertyName); + cppProperty.GetterName = cursor.ObjCPropertyGetterName.ToString(); + cppProperty.SetterName = cursor.ObjCPropertySetterName.ToString(); + ParseAttributes(cursor, cppProperty, true); + containerContext.DeclarationContainer.Properties.Add(cppProperty); + return cppProperty; + } + private CppField VisitFieldOrVariable(CppContainerContext containerContext, CXCursor cursor, void* data) { var fieldName = CXUtil.GetCursorSpelling(cursor); @@ -1268,6 +1411,7 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da return null; } + var cppClass = container as CppClass; var functionName = CXUtil.GetCursorSpelling(cursor); //We need ignore the function define out in the class definition here(Otherwise it will has two same functions here~)! @@ -1289,13 +1433,13 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da if (cursor.Kind == CXCursorKind.CXCursor_Constructor) { - var cppClass = (CppClass)container; + Debug.Assert(cppClass != null); cppFunction.IsConstructor = true; cppClass.Constructors.Add(cppFunction); } else if (cursor.Kind == CXCursorKind.CXCursor_Destructor) { - var cppClass = (CppClass)container; + Debug.Assert(cppClass != null); cppFunction.IsDestructor = true; cppClass.Destructors.Add(cppFunction); } @@ -1304,34 +1448,34 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da container.Functions.Add(cppFunction); } - if (cursor.kind == CXCursorKind.CXCursor_FunctionTemplate) + switch (cursor.Kind) { - cppFunction.Flags |= CppFunctionFlags.FunctionTemplate; - //Handle template argument here~ - cursor.VisitChildren((childCursor, funcCursor, clientData) => - { - var tmplParam = TryToCreateTemplateParameters(childCursor, clientData); - if (tmplParam != null) + case CXCursorKind.CXCursor_FunctionTemplate: + cppFunction.Flags |= CppFunctionFlags.FunctionTemplate; + //Handle template argument here~ + cursor.VisitChildren((childCursor, funcCursor, clientData) => { - cppFunction.TemplateParameters.Add(tmplParam); - } - return CXChildVisitResult.CXChildVisit_Continue; - }, new CXClientData((IntPtr)data)); - } - - if (cursor.Kind == CXCursorKind.CXCursor_CXXMethod) - { - cppFunction.Flags |= CppFunctionFlags.Method; - } - - if (cursor.Kind == CXCursorKind.CXCursor_Constructor) - { - cppFunction.Flags |= CppFunctionFlags.Constructor; - } - - if (cursor.Kind == CXCursorKind.CXCursor_Destructor) - { - cppFunction.Flags |= CppFunctionFlags.Destructor; + var tmplParam = TryToCreateTemplateParameters(childCursor, clientData); + if (tmplParam != null) + { + cppFunction.TemplateParameters.Add(tmplParam); + } + return CXChildVisitResult.CXChildVisit_Continue; + }, new CXClientData((IntPtr)data)); + break; + case CXCursorKind.CXCursor_ObjCInstanceMethodDecl: + case CXCursorKind.CXCursor_CXXMethod: + cppFunction.Flags |= CppFunctionFlags.Method; + break; + case CXCursorKind.CXCursor_ObjCClassMethodDecl: + cppFunction.Flags |= CppFunctionFlags.ClassMethod; + break; + case CXCursorKind.CXCursor_Constructor: + cppFunction.Flags |= CppFunctionFlags.Constructor; + break; + case CXCursorKind.CXCursor_Destructor: + cppFunction.Flags |= CppFunctionFlags.Destructor; + break; } if (cursor.IsFunctionInlined) @@ -1367,6 +1511,13 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da // Gets the return type var returnType = GetCppType(cursor.ResultType.Declaration, cursor.ResultType, cursor, data); + if (cppClass != null && cppClass.ClassKind == CppClassKind.ObjCInterface) + { + if (returnType is CppTypedef typedef && typedef.Name == "instancetype") + { + returnType = new CppPointerType(cppClass); + } + } cppFunction.ReturnType = returnType; ParseAttributes(cursor, cppFunction, true); @@ -1896,6 +2047,7 @@ private CppType GetCppTypeInternal(CXCursor cursor, CXType type, CXCursor parent case CXTypeKind.CXType_LongDouble: return CppPrimitiveType.LongDouble; + case CXTypeKind.CXType_ObjCObjectPointer: case CXTypeKind.CXType_Pointer: return new CppPointerType(GetCppType(type.PointeeType.Declaration, type.PointeeType, parent, data)) { SizeOf = (int)type.SizeOf }; @@ -1905,11 +2057,17 @@ private CppType GetCppTypeInternal(CXCursor cursor, CXType type, CXCursor parent case CXTypeKind.CXType_Record: return VisitClassDecl(cursor, data); + case CXTypeKind.CXType_ObjCInterface: + { + return VisitClassDecl(cursor, data); + } case CXTypeKind.CXType_Enum: return VisitEnumDecl(cursor, data); case CXTypeKind.CXType_FunctionProto: return VisitFunctionType(cursor, type, parent, data); + case CXTypeKind.CXType_BlockPointer: + return VisitBlockFunctionType(cursor, type, parent, data); case CXTypeKind.CXType_Typedef: return VisitTypeDefDecl(cursor, data); @@ -1956,6 +2114,38 @@ private CppType GetCppTypeInternal(CXCursor cursor, CXType type, CXCursor parent case CXTypeKind.CXType_Auto: return GetCppType(type.Declaration, type.Declaration.Type, parent, data); + case CXTypeKind.CXType_ObjCId: + return CppPrimitiveType.ObjCId; + + case CXTypeKind.CXType_ObjCSel: + return CppPrimitiveType.ObjCSel; + + case CXTypeKind.CXType_ObjCClass: + return CppPrimitiveType.ObjCClass; + + case CXTypeKind.CXType_ObjCObject: + return CppPrimitiveType.ObjCObject; + + case CXTypeKind.CXType_Int128: + return CppPrimitiveType.Int128; + + case CXTypeKind.CXType_UInt128: + return CppPrimitiveType.UInt128; + + case CXTypeKind.CXType_Float16: + return CppPrimitiveType.Float16; + + case CXTypeKind.CXType_BFloat16: + return CppPrimitiveType.BFloat16; + + case CXTypeKind.CXType_ObjCTypeParam: + { + var cppClass = _currentClassBeingVisited; + var templateName = CXUtil.GetCursorSpelling(cursor); + var templateArgType = cppClass.TemplateParameters.OfType().First(x => x.Name == templateName); + return templateArgType; + } + default: { WarningUnhandled(cursor, parent, type); @@ -1964,15 +2154,21 @@ private CppType GetCppTypeInternal(CXCursor cursor, CXType type, CXCursor parent } } - private CppFunctionType VisitFunctionType(CXCursor cursor, CXType type, CXCursor parent, void* data) + private CppFunctionTypeBase VisitBlockFunctionType(CXCursor cursor, CXType type, CXCursor parent, void* data) + { + var pointeeType = type.PointeeType; + return VisitFunctionType(cursor, pointeeType, parent, data, true); + } + + private CppFunctionTypeBase VisitFunctionType(CXCursor cursor, CXType type, CXCursor parent, void* data, bool isBlockFunctionType = false) { // Gets the return type var returnType = GetCppType(type.ResultType.Declaration, type.ResultType, cursor, data); - var cppFunction = new CppFunctionType(returnType) - { - CallingConvention = GetCallingConvention(type) - }; + var cppFunction = isBlockFunctionType + ? (CppFunctionTypeBase)new CppBlockFunctionType(returnType) + : new CppFunctionType(returnType); + cppFunction.CallingConvention = GetCallingConvention(type); // We don't use this but use the visitor children to try to recover the parameter names @@ -2079,3 +2275,7 @@ public CppContainerContext(ICppContainer container) } } } + + +[UnmanagedFunctionPointer(CallingConvention.Cdecl)] +public unsafe delegate CXChildVisitResult CXCursorBlockVisitor(CXCursor cursor, CXCursor parent); \ No newline at end of file diff --git a/src/CppAst/CppNamespace.cs b/src/CppAst/CppNamespace.cs index dbc699c2..63d145f6 100644 --- a/src/CppAst/CppNamespace.cs +++ b/src/CppAst/CppNamespace.cs @@ -27,6 +27,7 @@ public CppNamespace(string name) Namespaces = new CppContainerList(this); Attributes = new List(); TokenAttributes = new List(); + Properties = new CppContainerList(this); } /// @@ -42,6 +43,9 @@ public CppNamespace(string name) /// public CppContainerList Fields { get; } + /// + public CppContainerList Properties { get; } + /// public CppContainerList Functions { get; } diff --git a/src/CppAst/CppObjCCategory.cs b/src/CppAst/CppObjCCategory.cs new file mode 100644 index 00000000..2e1dcde8 --- /dev/null +++ b/src/CppAst/CppObjCCategory.cs @@ -0,0 +1,88 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CppAst; + +/// +/// A base Cpp container for macros, classes, fields, functions, enums, typesdefs. +/// +public class CppObjCCategory : CppElement, ICppDeclarationContainer +{ + /// + /// Create a new instance of this container. + /// + public CppObjCCategory(CppClass targetClass, string categoryName) + { + TargetClass = targetClass; + CategoryName = categoryName; + Fields = new CppContainerList(this); + Functions = new CppContainerList(this); + Enums = new CppContainerList(this); + Classes = new CppContainerList(this); + Typedefs = new CppContainerList(this); + Attributes = new List(); + TokenAttributes = new List(); + Properties = new CppContainerList(this); + } + + /// + /// Gets or sets the target class of this category. + /// + public CppClass TargetClass { get; set; } + + /// + /// Gets or sets the name of this category. + /// + public string CategoryName { get; set; } + + /// + public CppContainerList Fields { get; } + + /// + public CppContainerList Properties { get; } + + /// + public CppContainerList Functions { get; } + + /// + public CppContainerList Enums { get; } + + /// + public CppContainerList Classes { get; } + + /// + public CppContainerList Typedefs { get; } + + /// + public List Attributes { get; } + + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } + + public MetaAttributeMap MetaAttributes { get; private set; } = new MetaAttributeMap(); + + /// + public virtual IEnumerable Children() + { + return CppContainerHelper.Children(this); + } + + public override string ToString() + { + var builder = new StringBuilder(); + builder.Append("@interface "); + builder.Append(TargetClass.Name); + builder.Append(" ("); + builder.Append(CategoryName); + builder.Append(")"); + + return builder.ToString(); + } +} \ No newline at end of file diff --git a/src/CppAst/CppPrimitiveKind.cs b/src/CppAst/CppPrimitiveKind.cs index c8c02846..b77223a7 100644 --- a/src/CppAst/CppPrimitiveKind.cs +++ b/src/CppAst/CppPrimitiveKind.cs @@ -88,5 +88,45 @@ public enum CppPrimitiveKind /// C++ `long double` /// LongDouble, + + /// + /// An Objective-C `id` type. + /// + ObjCId, + + /// + /// An Objective-C `SEL` type. + /// + ObjCSel, + + /// + /// An Objective-C `Class` type. + /// + ObjCClass, + + /// + /// An Objective-C `NSObject` type. + /// + ObjCObject, + + /// + /// Unsigned 128 bits integer type. + /// + Int128, + + /// + /// 128 bits integer type. + /// + UInt128, + + /// + /// A 16 bits floating point type. + /// + Float16, + + /// + /// A 16 bits brain float type. + /// + BFloat16, } } \ No newline at end of file diff --git a/src/CppAst/CppPrimitiveType.cs b/src/CppAst/CppPrimitiveType.cs index ab032c70..d760cdca 100644 --- a/src/CppAst/CppPrimitiveType.cs +++ b/src/CppAst/CppPrimitiveType.cs @@ -91,6 +91,48 @@ public sealed class CppPrimitiveType : CppType /// public static readonly CppPrimitiveType LongDouble = new CppPrimitiveType(CppPrimitiveKind.LongDouble); + /// + /// ObjC `id` type. + /// + public static readonly CppPrimitiveType ObjCId = new CppPrimitiveType(CppPrimitiveKind.ObjCId); + + /// + /// ObjC `SEL` type. + /// + public static readonly CppPrimitiveType ObjCSel = new CppPrimitiveType(CppPrimitiveKind.ObjCSel); + + /// + /// ObjC `Class` type. + /// + public static readonly CppPrimitiveType ObjCClass = new CppPrimitiveType(CppPrimitiveKind.ObjCClass); + + /// + /// ObjC `Object` type. + /// + public static readonly CppPrimitiveType ObjCObject = new CppPrimitiveType(CppPrimitiveKind.ObjCObject); + + /// + /// Unsigned 128 bits integer type. + /// + public static readonly CppPrimitiveType UInt128 = new CppPrimitiveType(CppPrimitiveKind.UInt128); + + /// + /// 128 bits integer type. + /// + public static readonly CppPrimitiveType Int128 = new CppPrimitiveType(CppPrimitiveKind.Int128); + + /// + /// Float16 type. + /// + public static readonly CppPrimitiveType Float16 = new CppPrimitiveType(CppPrimitiveKind.Float16); + + /// + /// BFloat16 type. + /// + public static readonly CppPrimitiveType BFloat16 = new CppPrimitiveType(CppPrimitiveKind.BFloat16); + + + private readonly int _sizeOf; /// @@ -158,6 +200,20 @@ private void UpdateSize(out int sizeOf) case CppPrimitiveKind.LongDouble: sizeOf = 8; break; + case CppPrimitiveKind.ObjCId: + case CppPrimitiveKind.ObjCSel: + case CppPrimitiveKind.ObjCClass: + case CppPrimitiveKind.ObjCObject: + sizeOf = 8; // Valid only for 64 bits + break; + case CppPrimitiveKind.UInt128: + case CppPrimitiveKind.Int128: + sizeOf = 16; + break; + case CppPrimitiveKind.Float16: + case CppPrimitiveKind.BFloat16: + sizeOf = 2; + break; default: throw new ArgumentOutOfRangeException(); } diff --git a/src/CppAst/CppProperty.cs b/src/CppAst/CppProperty.cs new file mode 100644 index 00000000..1401f3bb --- /dev/null +++ b/src/CppAst/CppProperty.cs @@ -0,0 +1,71 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace CppAst; + +/// +/// An Objective-C proeprty. +/// +public sealed class CppProperty : CppDeclaration, ICppMember, ICppAttributeContainer +{ + public CppProperty(CppType type, string name) + { + Type = type ?? throw new ArgumentNullException(nameof(type)); + Name = name; + Attributes = new List(); + } + + /// + /// Gets attached attributes. Might be null. + /// + public List Attributes { get; } + + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } = new(); + + public MetaAttributeMap MetaAttributes { get; private set; } = new MetaAttributeMap(); + + /// + /// Gets the type of this field/variable. + /// + public CppType Type { get; set; } + + /// + public string Name { get; set; } + + /// + /// Gets or sets the name of the getter method. + /// + internal string GetterName { get; set; } + + /// + /// Gets or sets the getter method. + /// + public CppFunction Getter { get; set; } + + /// + /// Gets or sets the name of the setter method. + /// + internal string SetterName { get; set; } + + /// + /// Gets or sets the setter method. + /// + public CppFunction Setter { get; set; } + + /// + public override string ToString() + { + var builder = new StringBuilder(); + + builder.Append(Type.GetDisplayName()); + builder.Append(" "); + builder.Append(Name); + return builder.ToString(); + } +} \ No newline at end of file diff --git a/src/CppAst/CppTemplateKind.cs b/src/CppAst/CppTemplateKind.cs index 1367df63..0706870b 100644 --- a/src/CppAst/CppTemplateKind.cs +++ b/src/CppAst/CppTemplateKind.cs @@ -25,6 +25,10 @@ public enum CppTemplateKind /// A class with full template specialized /// TemplateSpecializedClass, + /// + /// An Objective-C class template + /// + ObjCGenericClass, } diff --git a/src/CppAst/CppTypeKind.cs b/src/CppAst/CppTypeKind.cs index a40f9288..3483fe1a 100644 --- a/src/CppAst/CppTypeKind.cs +++ b/src/CppAst/CppTypeKind.cs @@ -61,5 +61,13 @@ public enum CppTypeKind /// An unexposed type. /// Unexposed, + /// + /// An Objective-C block function type. + /// + ObjCBlockFunction, + /// + /// A generic type (e.g. Objective-C `MyType<TArg>`) + /// + GenericType, } } \ No newline at end of file diff --git a/src/CppAst/ICppDeclarationContainer.cs b/src/CppAst/ICppDeclarationContainer.cs index 25ca93de..f3063180 100644 --- a/src/CppAst/ICppDeclarationContainer.cs +++ b/src/CppAst/ICppDeclarationContainer.cs @@ -15,6 +15,11 @@ public interface ICppDeclarationContainer : ICppContainer, ICppAttributeContaine /// CppContainerList Fields { get; } + /// + /// Gets the properties. + /// + CppContainerList Properties { get; } + /// /// Gets the functions/methods. /// From b6d23fcf5a203d0bbb40cdc9027f5f1a35d5481f Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Wed, 30 Apr 2025 17:39:55 +0200 Subject: [PATCH 2/4] Improve Objective-C support and add tests --- src/CppAst.Tests/TestObjectiveC.cs | 358 +++++++++++++++++++++++------ src/CppAst/CppClass.cs | 25 +- src/CppAst/CppClassKind.cs | 6 +- src/CppAst/CppModelBuilder.cs | 112 ++++----- src/CppAst/CppObjCCategory.cs | 88 ------- src/CppAst/CppParser.cs | 18 +- src/CppAst/CppParserKind.cs | 25 ++ src/CppAst/CppParserOptions.cs | 10 +- src/CppAst/CppTypeKind.cs | 6 +- 9 files changed, 426 insertions(+), 222 deletions(-) delete mode 100644 src/CppAst/CppObjCCategory.cs create mode 100644 src/CppAst/CppParserKind.cs diff --git a/src/CppAst.Tests/TestObjectiveC.cs b/src/CppAst.Tests/TestObjectiveC.cs index bd14090c..d236b6aa 100644 --- a/src/CppAst.Tests/TestObjectiveC.cs +++ b/src/CppAst.Tests/TestObjectiveC.cs @@ -3,77 +3,30 @@ // See license.txt file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Linq; namespace CppAst.Tests; public class TestObjectiveC : InlineTestBase { [Test] - public void TestSimple() + public void TestFoundationIncludes() { + if (!OperatingSystem.IsMacOS()) + { + NUnit.Framework.Assert.Ignore("Only on MacOS"); + return; + } + ParseAssert(""" - // SimpleHeader.h - //#import - //@import Foundation; - - // Fake Foundation enum declaration for options - typedef enum __attribute__((flag_enum)) MyOptions : int MyOptions; - enum MyOptions : int { - MyOptions1 = 1, - MyOptions2 = 2, - MyOptions3 = 4, - }; - - @protocol MyProtocol - + (void)doSomething; - @end - - @protocol MyProtocol2 - + (void)doSomething2; - @end - - @interface TesterBase - @end - - @interface SimpleThing : TesterBase - - /// A read-write string property - @property (readonly) const char *name; - - @property int id; - - /// A class factory method - + (instancetype)thingWithName:(const char *)name; - - /// An instance method that returns a description - - (const char *)describe; - - - (void)runWithCompletion:(void (^)(int result))completion; - - @end - - @interface SimpleThing (MyCategory) - @property int categoryId; - @end - - @interface SimpleTemplate : SimpleThing - - (TArg)get_at:(int)index; - @end - - @interface SimpleTemplate2 : SimpleTemplate - @end - + #import """, compilation => { Assert.False(compilation.HasErrors); - - + Assert.IsTrue(compilation.System.Classes.Count > 1000); }, new() { - ParseAsCpp = false, + ParserKind = CppParserKind.ObjC, TargetCpu = CppTargetCpu.ARM64, TargetVendor = "apple", TargetSystem = "darwin", @@ -81,14 +34,7 @@ @interface SimpleThing (MyCategory) ParseSystemIncludes = true, AdditionalArguments = { - "-x", "objective-c", "-std=gnu11", - //"-ObjC", - //"-fblocks", - //"-fno-modules", - //"-fmodules", - //"-fimplicit-module-maps", - //"-fno-implicit-modules", "-isysroot", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk", "-F", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks", "-isystem", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include", @@ -97,4 +43,288 @@ @interface SimpleThing (MyCategory) } ); } + + [Test] + public void TestInterfaceWithMethods() + { + ParseAssert(""" + @interface MyInterface + - (float)helloworld; + - (void)doSomething:(int)index argSpecial:(float)arg1; + @end + """, + compilation => + { + Assert.False(compilation.HasErrors); + Assert.AreEqual(1, compilation.Classes.Count); + var myInterface = compilation.Classes[0]; + Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind); + Assert.AreEqual("MyInterface", myInterface.Name); + Assert.AreEqual(2, myInterface.Functions.Count); + + Assert.AreEqual(0, myInterface.Functions[0].Parameters.Count); + Assert.AreEqual("helloworld", myInterface.Functions[0].Name); + Assert.IsTrue(myInterface.Functions[0].ReturnType is CppPrimitiveType primitive && primitive.Kind == CppPrimitiveKind.Float); + + Assert.AreEqual(2, myInterface.Functions[1].Parameters.Count); + Assert.AreEqual("index", myInterface.Functions[1].Parameters[0].Name); + Assert.AreEqual("arg1", myInterface.Functions[1].Parameters[1].Name); + Assert.AreEqual("doSomething:argSpecial:", myInterface.Functions[1].Name); + Assert.IsTrue(myInterface.Functions[1].ReturnType is CppPrimitiveType primitive2 && primitive2.Kind == CppPrimitiveKind.Void); + Assert.IsTrue(myInterface.Functions[1].Parameters[1].Type is CppPrimitiveType primitive3 && primitive3.Kind == CppPrimitiveKind.Float); + + }, GetDefaultObjCOptions() + ); + + } + + [Test] + public void TestInterfaceWithProperties() + { + ParseAssert(""" + @interface MyInterface + @property int id; + @property (readonly) float id2; + @end + """, + compilation => + { + Assert.False(compilation.HasErrors); + Assert.AreEqual(1, compilation.Classes.Count); + var myInterface = compilation.Classes[0]; + Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind); + Assert.AreEqual("MyInterface", myInterface.Name); + Assert.AreEqual(2, myInterface.Properties.Count); + Assert.AreEqual("id", myInterface.Properties[0].Name); + Assert.AreEqual("id2", myInterface.Properties[1].Name); + + Assert.IsTrue(myInterface.Properties[0].Type is CppPrimitiveType primitive && primitive.Kind == CppPrimitiveKind.Int); + Assert.IsTrue(myInterface.Properties[0].Getter is not null); + Assert.IsTrue(myInterface.Properties[0].Setter is not null); + + Assert.IsTrue(myInterface.Properties[1].Type is CppPrimitiveType primitive2 && primitive2.Kind == CppPrimitiveKind.Float); + Assert.IsTrue(myInterface.Properties[1].Setter is null); + + Assert.AreEqual(3, myInterface.Functions.Count); + Assert.AreEqual("id", myInterface.Functions[0].Name); + Assert.IsTrue(myInterface.Functions[0].ReturnType is CppPrimitiveType primitive3 && primitive3.Kind == CppPrimitiveKind.Int); + + Assert.AreEqual("setId:", myInterface.Functions[1].Name); + Assert.IsTrue(myInterface.Functions[1].ReturnType is CppPrimitiveType primitive4 && primitive4.Kind == CppPrimitiveKind.Void); + Assert.AreEqual(1, myInterface.Functions[1].Parameters.Count); + Assert.AreEqual("id", myInterface.Functions[1].Parameters[0].Name); + Assert.IsTrue(myInterface.Functions[1].Parameters[0].Type is CppPrimitiveType primitive5 && primitive5.Kind == CppPrimitiveKind.Int); + + Assert.AreEqual("id2", myInterface.Functions[2].Name); + Assert.IsTrue(myInterface.Functions[2].ReturnType is CppPrimitiveType primitive6 && primitive6.Kind == CppPrimitiveKind.Float); + Assert.AreEqual(0, myInterface.Functions[2].Parameters.Count); + }, GetDefaultObjCOptions() + ); + } + + [Test] + public void TestInterfaceWithInstanceType() + { + ParseAssert(""" + @interface MyInterface + + (instancetype)getInstance; + @end + """, + compilation => + { + Assert.False(compilation.HasErrors); + Assert.AreEqual(1, compilation.Classes.Count); + var myInterface = compilation.Classes[0]; + Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind); + Assert.AreEqual("MyInterface", myInterface.Name); + Assert.AreEqual(1, myInterface.Functions.Count); + Assert.AreEqual("getInstance", myInterface.Functions[0].Name); + Assert.IsTrue((myInterface.Functions[0].Flags & CppFunctionFlags.ClassMethod) != 0); + var pointerType = myInterface.Functions[0].ReturnType as CppPointerType; + Assert.IsNotNull(pointerType); + Assert.AreEqual(myInterface, pointerType!.ElementType); + }, GetDefaultObjCOptions() + ); + } + + + [Test] + public void TestInterfaceWithMultipleGenericParameters() + { + ParseAssert(""" + @interface BaseInterface + @end + + // Generics require a base class + @interface MyInterface : BaseInterface + - (T1)get_at:(int)index; + - (T2)get_at2:(int)index; + @end + """, + compilation => + { + Assert.False(compilation.HasErrors); + Assert.AreEqual(2, compilation.Classes.Count); + var myInterface = compilation.Classes[1]; + Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind); + Assert.AreEqual("MyInterface", myInterface.Name); + Assert.AreEqual(2, myInterface.TemplateParameters.Count); + Assert.IsTrue(myInterface.TemplateParameters[0] is CppTemplateParameterType templateParam1 && templateParam1.Name == "T1"); + Assert.IsTrue(myInterface.TemplateParameters[1] is CppTemplateParameterType templateParam2 && templateParam2.Name == "T2"); + + Assert.AreEqual(2, myInterface.Functions.Count); + Assert.AreEqual("get_at:", myInterface.Functions[0].Name); + Assert.AreEqual("get_at2:", myInterface.Functions[1].Name); + Assert.IsTrue(myInterface.Functions[0].ReturnType is CppTemplateParameterType templateSpecialization && templateSpecialization.Name == "T1"); + Assert.IsTrue(myInterface.Functions[1].ReturnType is CppTemplateParameterType templateSpecialization2 && templateSpecialization2.Name == "T2"); + }, GetDefaultObjCOptions() + ); + } + + [Test] + public void TestBlockFunctionPointer() + { + ParseAssert(""" + typedef float (^MyBlock)(int a, int* b); + """, + compilation => + { + Assert.False(compilation.HasErrors); + Assert.AreEqual(1, compilation.Typedefs.Count); + + var typedef = compilation.Typedefs[0]; + Assert.AreEqual("MyBlock", typedef.Name); + + Assert.IsInstanceOf(typedef.ElementType); + var blockType = (CppBlockFunctionType)typedef.ElementType; + + Assert.AreEqual(CppTypeKind.ObjCBlockFunction, blockType.TypeKind); + + Assert.IsTrue(blockType.ReturnType is CppPrimitiveType primitive && primitive.Kind == CppPrimitiveKind.Float); + + Assert.AreEqual(2, blockType.Parameters.Count); + Assert.IsTrue(blockType.Parameters[0].Type is CppPrimitiveType primitive2 && primitive2.Kind == CppPrimitiveKind.Int); + Assert.IsTrue(blockType.Parameters[1].Type is CppPointerType pointerType && pointerType.ElementType is CppPrimitiveType primitive3 && primitive3.Kind == CppPrimitiveKind.Int); + }, GetDefaultObjCOptions() + ); + } + + [Test] + public void TestProtocol() + { + ParseAssert(""" + @protocol MyProtocol + @end + + @protocol MyProtocol1 + @end + + @protocol MyProtocol2 + @end + + @interface MyInterface + @end + """, + compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(4, compilation.Classes.Count); + + var myProtocol = compilation.Classes[0]; + Assert.AreEqual(CppClassKind.ObjCProtocol, myProtocol.ClassKind); + Assert.AreEqual("MyProtocol", myProtocol.Name); + + var myProtocol1 = compilation.Classes[1]; + Assert.AreEqual(CppClassKind.ObjCProtocol, myProtocol1.ClassKind); + Assert.AreEqual("MyProtocol1", myProtocol1.Name); + + var myProtocol2 = compilation.Classes[2]; + Assert.AreEqual(CppClassKind.ObjCProtocol, myProtocol2.ClassKind); + Assert.AreEqual("MyProtocol2", myProtocol2.Name); + Assert.AreEqual(2, myProtocol2.ObjCImplementedProtocols.Count); + Assert.AreEqual(myProtocol, myProtocol2.ObjCImplementedProtocols[0]); + Assert.AreEqual(myProtocol1, myProtocol2.ObjCImplementedProtocols[1]); + + var myInterface = compilation.Classes[3]; + Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind); + Assert.AreEqual("MyInterface", myInterface.Name); + Assert.AreEqual(1, myInterface.ObjCImplementedProtocols.Count); + Assert.AreEqual(myProtocol, myInterface.ObjCImplementedProtocols[0]); + + }, GetDefaultObjCOptions() + ); + } + + [Test] + public void TestInterfaceBaseType() + { + ParseAssert(""" + @interface InterfaceBase + @end + + @interface MyInterface : InterfaceBase + @end + """, + compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(2, compilation.Classes.Count); + + var myInterfaceBase = compilation.Classes[0]; + Assert.AreEqual(CppClassKind.ObjCInterface, myInterfaceBase.ClassKind); + Assert.AreEqual("InterfaceBase", myInterfaceBase.Name); + + var myInterface = compilation.Classes[1]; + Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind); + Assert.AreEqual("MyInterface", myInterface.Name); + Assert.AreEqual(1, myInterface.BaseTypes.Count); + Assert.AreEqual(myInterfaceBase, myInterface.BaseTypes[0].Type); + }, GetDefaultObjCOptions() + ); + } + + [Test] + public void TestInterfaceWithCategory() + { + ParseAssert(""" + @interface MyInterface + @end + + @interface MyInterface (MyCategory) + @end + """, + compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(2, compilation.Classes.Count); + + var myInterface = compilation.Classes[0]; + Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind); + Assert.AreEqual("MyInterface", myInterface.Name); + + var myInterfaceWithCategory = compilation.Classes[1]; + Assert.AreEqual(CppClassKind.ObjCInterfaceCategory, myInterfaceWithCategory.ClassKind); + Assert.AreEqual("MyInterface", myInterfaceWithCategory.Name); + Assert.AreEqual("MyCategory", myInterfaceWithCategory.ObjCCategoryName); + Assert.AreEqual(myInterface, myInterfaceWithCategory.ObjCCategoryTargetClass); + }, GetDefaultObjCOptions() + ); + } + + + private static CppParserOptions GetDefaultObjCOptions() + { + return new CppParserOptions + { + ParserKind = CppParserKind.ObjC, + TargetCpu = CppTargetCpu.ARM64, + TargetVendor = "apple", + TargetSystem = "darwin", + ParseMacros = false, + ParseSystemIncludes = false, + }; + } } \ No newline at end of file diff --git a/src/CppAst/CppClass.cs b/src/CppAst/CppClass.cs index 824f04d8..6cbbe279 100644 --- a/src/CppAst/CppClass.cs +++ b/src/CppAst/CppClass.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. @@ -11,7 +11,7 @@ namespace CppAst /// /// A C++ class, struct or union. /// - public sealed class CppClass : CppTypeDeclaration, ICppMemberWithVisibility, ICppDeclarationContainer, ICppTemplateOwner + public class CppClass : CppTypeDeclaration, ICppMemberWithVisibility, ICppDeclarationContainer, ICppTemplateOwner { /// /// Creates a new instance. @@ -33,7 +33,8 @@ public CppClass(string name) : base(CppTypeKind.StructOrClass) TokenAttributes = new List(); ObjCImplementedProtocols = new List(); Properties = new CppContainerList(this); - ObjCCategories = new List(); + ObjCCategories = new List(); + ObjCCategoryName = string.Empty; } /// @@ -45,6 +46,16 @@ public CppClass(string name) : base(CppTypeKind.StructOrClass) /// public string Name { get; set; } + + /// + /// Gets or sets the target of the Objective-C category. Null if this class is not an . + /// + public CppClass ObjCCategoryTargetClass { get; set; } + + /// + /// Gets or sets the name of the Objective-C category. Empty if this class is not an + /// + public string ObjCCategoryName { get; set; } public override string FullName { @@ -161,7 +172,7 @@ public override string FullName /// /// Gets the Objective-C categories of this instance. /// - public List ObjCCategories { get; } + public List ObjCCategories { get; } /// public List TemplateParameters { get; } @@ -209,6 +220,7 @@ public override string ToString() builder.Append("union "); break; case CppClassKind.ObjCInterface: + case CppClassKind.ObjCInterfaceCategory: builder.Append("@interface "); break; case CppClassKind.ObjCProtocol: @@ -258,6 +270,11 @@ public override string ToString() builder.Append(baseType); } } + + if (!string.IsNullOrEmpty(ObjCCategoryName)) + { + builder.Append(" (").Append(ObjCCategoryName).Append(')'); + } if (ObjCImplementedProtocols.Count > 0) { diff --git a/src/CppAst/CppClassKind.cs b/src/CppAst/CppClassKind.cs index 5f6f55e5..f598dafe 100644 --- a/src/CppAst/CppClassKind.cs +++ b/src/CppAst/CppClassKind.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. @@ -29,5 +29,9 @@ public enum CppClassKind /// An Objective-C `@protocol` /// ObjCProtocol, + /// + /// An Objective-C `@interface` with a category. + /// + ObjCInterfaceCategory, } } \ No newline at end of file diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index 499e48eb..8eaa6c5c 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -6,10 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Linq.Expressions; using System.Runtime.InteropServices; using System.Text; -using ClangSharp; using ClangSharp.Interop; namespace CppAst @@ -156,29 +154,6 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi symbol = cppEnum; break; - case CXCursorKind.CXCursor_ObjCCategoryDecl: - { - // Fetch the target class for the category - CXCursor parentCursor = default; - cursor.VisitChildren((cxCursor, parent, clientData) => - { - if (cxCursor.Kind == CXCursorKind.CXCursor_ObjCClassRef) - { - parentCursor = cxCursor.Referenced; - return CXChildVisitResult.CXChildVisit_Break; - } - - return CXChildVisitResult.CXChildVisit_Continue; - }, default); - - var parentContainer = GetOrCreateDeclarationContainer(parentCursor, data).Container; - var targetClass = (CppClass)parentContainer; - - var category = new CppObjCCategory(targetClass, CXUtil.GetCursorSpelling(cursor)); - targetClass.ObjCCategories.Add(category); - symbol = category; - break; - } case CXCursorKind.CXCursor_ClassTemplate: case CXCursorKind.CXCursor_ClassTemplatePartialSpecialization: @@ -187,6 +162,7 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi case CXCursorKind.CXCursor_UnionDecl: case CXCursorKind.CXCursor_ObjCInterfaceDecl: case CXCursorKind.CXCursor_ObjCProtocolDecl: + case CXCursorKind.CXCursor_ObjCCategoryDecl: var cppClass = new CppClass(CXUtil.GetCursorSpelling(cursor)); parentDeclarationContainer.Classes.Add(cppClass); symbol = cppClass; @@ -210,6 +186,33 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi case CXCursorKind.CXCursor_ObjCProtocolDecl: cppClass.ClassKind = CppClassKind.ObjCProtocol; break; + case CXCursorKind.CXCursor_ObjCCategoryDecl: + { + cppClass.ClassKind = CppClassKind.ObjCInterfaceCategory; + + // Fetch the target class for the category + CXCursor parentCursor = default; + cursor.VisitChildren((cxCursor, parent, clientData) => + { + if (cxCursor.Kind == CXCursorKind.CXCursor_ObjCClassRef) + { + parentCursor = cxCursor.Referenced; + return CXChildVisitResult.CXChildVisit_Break; + } + + return CXChildVisitResult.CXChildVisit_Continue; + }, default); + + var parentContainer = GetOrCreateDeclarationContainer(parentCursor, data).Container; + var targetClass = (CppClass)parentContainer; + cppClass.ObjCCategoryName = cppClass.Name; + cppClass.Name = targetClass.Name; + cppClass.ObjCCategoryTargetClass = targetClass; + + // Link back + targetClass.ObjCCategories.Add(cppClass); + break; + } } cppClass.IsAbstract = cursor.CXXRecord_IsAbstract; @@ -274,24 +277,7 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi } else { - cursor.VisitChildren((childCursor, classCursor, clientData) => - { - var tmplParam = TryToCreateTemplateParameters(childCursor, clientData); - if (tmplParam != null) - { - cppClass.TemplateParameters.Add(tmplParam); - if (cppClass.ClassKind == CppClassKind.ObjCInterface || - cppClass.ClassKind == CppClassKind.ObjCProtocol) - { - cppClass.TemplateKind = CppTemplateKind.ObjCGenericClass; - } - else - { - cppClass.TemplateKind = CppTemplateKind.TemplateClass; - } - } - return CXChildVisitResult.CXChildVisit_Continue; - }, new CXClientData((IntPtr)data)); + AddTemplateParameters(cursor, cppClass); } defaultContainerVisibility = cursor.Kind == CXCursorKind.CXCursor_ClassDecl ? CppVisibility.Private : CppVisibility.Public; @@ -320,6 +306,32 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi return containerContext; } + private void AddTemplateParameters(CXCursor cursor, ICppTemplateOwner templateOwner) + { + cursor.VisitChildren((childCursor, classCursor, clientData) => + { + var tmplParam = TryToCreateTemplateParameters(childCursor, clientData); + if (tmplParam != null) + { + templateOwner.TemplateParameters.Add(tmplParam); + if (templateOwner is CppClass cppClass) + { + if (cppClass.ClassKind == CppClassKind.ObjCInterface || + cppClass.ClassKind == CppClassKind.ObjCProtocol) + { + cppClass.TemplateKind = CppTemplateKind.ObjCGenericClass; + } + else + { + cppClass.TemplateKind = CppTemplateKind.TemplateClass; + } + } + } + + return CXChildVisitResult.CXChildVisit_Continue; + }, default); + } + private TCppElement GetOrCreateDeclarationContainer(CXCursor cursor, void* data, out CppContainerContext context) where TCppElement : CppElement, ICppContainer { context = GetOrCreateDeclarationContainer(cursor, data); @@ -406,6 +418,7 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d case CXCursorKind.CXCursor_UnionDecl: case CXCursorKind.CXCursor_ObjCInterfaceDecl: case CXCursorKind.CXCursor_ObjCProtocolDecl: + case CXCursorKind.CXCursor_ObjCCategoryDecl: { bool isAnonymous = cursor.IsAnonymous; var cppClass = VisitClassDecl(cursor, data); @@ -553,9 +566,6 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d cursor.VisitChildren(VisitMember, new CXClientData((IntPtr)data)); break; - case CXCursorKind.CXCursor_ObjCCategoryDecl: - element = VisitObjCCategory(cursor, data); - break; case CXCursorKind.CXCursor_ObjCPropertyDecl: { var containerContext = GetOrCreateDeclarationContainer(parent, data); @@ -599,16 +609,6 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d return CXChildVisitResult.CXChildVisit_Continue; } - private CppElement VisitObjCCategory(CXCursor cursor, void* data) - { - var categoryContainer = GetOrCreateDeclarationContainer(cursor, data).Container; - var cppCategory = (CppObjCCategory)categoryContainer; - _currentClassBeingVisited = cppCategory.TargetClass; - cursor.VisitChildren(VisitMember, new CXClientData((IntPtr)data)); - _currentClassBeingVisited = null; - return cppCategory; - } - private CppComment GetComment(CXCursor cursor) { var cxComment = cursor.ParsedComment; diff --git a/src/CppAst/CppObjCCategory.cs b/src/CppAst/CppObjCCategory.cs deleted file mode 100644 index 2e1dcde8..00000000 --- a/src/CppAst/CppObjCCategory.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. -// Licensed under the BSD-Clause 2 license. -// See license.txt file in the project root for full license information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace CppAst; - -/// -/// A base Cpp container for macros, classes, fields, functions, enums, typesdefs. -/// -public class CppObjCCategory : CppElement, ICppDeclarationContainer -{ - /// - /// Create a new instance of this container. - /// - public CppObjCCategory(CppClass targetClass, string categoryName) - { - TargetClass = targetClass; - CategoryName = categoryName; - Fields = new CppContainerList(this); - Functions = new CppContainerList(this); - Enums = new CppContainerList(this); - Classes = new CppContainerList(this); - Typedefs = new CppContainerList(this); - Attributes = new List(); - TokenAttributes = new List(); - Properties = new CppContainerList(this); - } - - /// - /// Gets or sets the target class of this category. - /// - public CppClass TargetClass { get; set; } - - /// - /// Gets or sets the name of this category. - /// - public string CategoryName { get; set; } - - /// - public CppContainerList Fields { get; } - - /// - public CppContainerList Properties { get; } - - /// - public CppContainerList Functions { get; } - - /// - public CppContainerList Enums { get; } - - /// - public CppContainerList Classes { get; } - - /// - public CppContainerList Typedefs { get; } - - /// - public List Attributes { get; } - - [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] - public List TokenAttributes { get; } - - public MetaAttributeMap MetaAttributes { get; private set; } = new MetaAttributeMap(); - - /// - public virtual IEnumerable Children() - { - return CppContainerHelper.Children(this); - } - - public override string ToString() - { - var builder = new StringBuilder(); - builder.Append("@interface "); - builder.Append(TargetClass.Name); - builder.Append(" ("); - builder.Append(CategoryName); - builder.Append(")"); - - return builder.ToString(); - } -} \ No newline at end of file diff --git a/src/CppAst/CppParser.cs b/src/CppAst/CppParser.cs index 744c6157..f1fdd86f 100644 --- a/src/CppAst/CppParser.cs +++ b/src/CppAst/CppParser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. @@ -94,9 +94,21 @@ private static unsafe CppCompilation ParseInternal(List cppFile arguments.Add("-dM"); arguments.Add("-E"); - if (options.ParseAsCpp && !arguments.Contains("-xc++")) + switch (options.ParserKind) { - arguments.Add("-xc++"); + case CppParserKind.None: + break; + case CppParserKind.Cpp: + arguments.Add("-xc++"); + break; + case CppParserKind.C: + arguments.Add("-xc"); + break; + case CppParserKind.ObjC: + arguments.Add("-xobjective-c"); + break; + default: + throw new ArgumentOutOfRangeException(); } if (!arguments.Any(x => x.StartsWith("--target="))) diff --git a/src/CppAst/CppParserKind.cs b/src/CppAst/CppParserKind.cs new file mode 100644 index 00000000..e5aba189 --- /dev/null +++ b/src/CppAst/CppParserKind.cs @@ -0,0 +1,25 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// Licensed under the BSD-Clause 2 license. +// See license.txt file in the project root for full license information. + +namespace CppAst; + +public enum CppParserKind +{ + /// + /// No parser kind defined. + /// + None = 0, + /// + /// Equivalent to -xc++. (Default) + /// + Cpp, + /// + /// Equivalent to -xc. + /// + C, + /// + /// Equivalent to -xobjective-c. + /// + ObjC +} \ No newline at end of file diff --git a/src/CppAst/CppParserOptions.cs b/src/CppAst/CppParserOptions.cs index 9a465174..1de9bb0d 100644 --- a/src/CppAst/CppParserOptions.cs +++ b/src/CppAst/CppParserOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. @@ -17,7 +17,7 @@ public class CppParserOptions /// public CppParserOptions() { - ParseAsCpp = true; + ParserKind = CppParserKind.Cpp; SystemIncludeFolders = new List(); IncludeFolders = new List(); @@ -67,10 +67,10 @@ public CppParserOptions() public List AdditionalArguments { get; private set; } /// - /// Gets or sets a boolean indicating whether the files will be parser as C++. Default is true. Otherwise parse as C. + /// Gets or sets the parser kind. Default is . This is used to select the parser to use. /// - public bool ParseAsCpp { get; set; } - + public CppParserKind ParserKind { get; set; } = CppParserKind.Cpp; + /// /// Gets or sets a boolean indicating whether to parser non-Doxygen comments in addition to Doxygen comments. Default is true /// diff --git a/src/CppAst/CppTypeKind.cs b/src/CppAst/CppTypeKind.cs index 3483fe1a..5e8a6ee4 100644 --- a/src/CppAst/CppTypeKind.cs +++ b/src/CppAst/CppTypeKind.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. @@ -69,5 +69,9 @@ public enum CppTypeKind /// A generic type (e.g. Objective-C `MyType<TArg>`) /// GenericType, + /// + /// An Objective-C interface with a category. + /// + ObjCInterfaceWithCategory, } } \ No newline at end of file From dff0cb87edb3d185ed8b41d08ecedd6477acb2ba Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Wed, 30 Apr 2025 17:45:13 +0200 Subject: [PATCH 3/4] Check no warnings for Objective-C when parsing Foundation --- src/CppAst.Tests/TestObjectiveC.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CppAst.Tests/TestObjectiveC.cs b/src/CppAst.Tests/TestObjectiveC.cs index d236b6aa..58a894e6 100644 --- a/src/CppAst.Tests/TestObjectiveC.cs +++ b/src/CppAst.Tests/TestObjectiveC.cs @@ -23,6 +23,7 @@ public void TestFoundationIncludes() compilation => { Assert.False(compilation.HasErrors); + Assert.AreEqual(0, compilation.Diagnostics.Messages.Count, "Parsing foundation headers should not generate warnings"); // No warnings Assert.IsTrue(compilation.System.Classes.Count > 1000); }, new() { From 29c8038c8943953a8a332df08081358deba63647 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Wed, 30 Apr 2025 22:46:06 +0200 Subject: [PATCH 4/4] Fix root container detection --- src/CppAst/CppModelBuilder.cs | 211 ++++++++++++++++++++-------------- 1 file changed, 123 insertions(+), 88 deletions(-) diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index 8eaa6c5c..043e4258 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -17,7 +17,9 @@ namespace CppAst /// internal unsafe class CppModelBuilder { - private readonly CppContainerContext _rootContainerContext; + private readonly CppContainerContext _userRootContainerContext; + private readonly CppContainerContext _systemRootContainerContext; + private CppContainerContext _rootContainerContext; private readonly Dictionary _containers; private readonly Dictionary _typedefs; private CppClass _currentClassBeingVisited; @@ -27,7 +29,14 @@ public CppModelBuilder() _containers = new Dictionary(); RootCompilation = new CppCompilation(); _typedefs = new Dictionary(); - _rootContainerContext = new CppContainerContext(RootCompilation); + _userRootContainerContext = new CppContainerContext(RootCompilation) + { + NameContext = "user" + }; + _systemRootContainerContext = new CppContainerContext(RootCompilation.System) + { + NameContext = "system" + }; } public bool AutoSquashTypedef { get; set; } @@ -42,15 +51,6 @@ public CppModelBuilder() public CXChildVisitResult VisitTranslationUnit(CXCursor cursor, CXCursor parent, void* data) { - _rootContainerContext.Container = RootCompilation; - - - if (cursor.Location.IsInSystemHeader) - { - if (!ParseSystemIncludes) return CXChildVisitResult.CXChildVisit_Continue; - - _rootContainerContext.Container = RootCompilation.System; - } return VisitMember(cursor, parent, data); } @@ -98,7 +98,7 @@ private bool TryGetDeclarationContainer(CXCursor cursor, void* data, out string typeAsCString = CXUtil.GetCursorDisplayName(cursor); } // Try to workaround anonymous types - typeKey = $"{typeAsCString}{(cursor.IsAnonymous ? "/" + cursor.Hash : string.Empty)}"; + typeKey = $"{_rootContainerContext.NameContext}/{typeAsCString}{(cursor.IsAnonymous ? "/" + cursor.Hash : string.Empty)}"; return _containers.TryGetValue(typeKey, out containerContext); } @@ -116,6 +116,11 @@ private CppContainerContext GetDeclarationContainer(CXCursor cursor, void* data) private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, void* data) { + while (cursor.Kind == CXCursorKind.CXCursor_LinkageSpec) + { + cursor = cursor.SemanticParent; + } + if (TryGetDeclarationContainer(cursor, data, out string typeKey, out var containerContext)) { return containerContext; @@ -383,30 +388,54 @@ private CppClass VisitClassDecl(CXCursor cursor, void* data) private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* data) { CppElement element = null; + + // Only set the root container when we know the location + // Otherwise assume that it hasn't changed + // We expect it to be always set + if (cursor.Location != CXSourceLocation.Null) + { + if (cursor.Location.IsInSystemHeader) + { + if (!ParseSystemIncludes) return CXChildVisitResult.CXChildVisit_Continue; + + _rootContainerContext = _systemRootContainerContext; + } + else + { + _rootContainerContext = _userRootContainerContext; + } + } + + if (_rootContainerContext is null) + { + RootCompilation.Diagnostics.Error($"Unexpected error with cursor location. Cannot determine Root Compilation context."); + return CXChildVisitResult.CXChildVisit_Continue; + } + switch (cursor.Kind) { case CXCursorKind.CXCursor_FieldDecl: case CXCursorKind.CXCursor_VarDecl: - { - var containerContext = GetOrCreateDeclarationContainer(parent, data); - element = VisitFieldOrVariable(containerContext, cursor, data); - break; - } + { + var containerContext = GetOrCreateDeclarationContainer(parent, data); + element = VisitFieldOrVariable(containerContext, cursor, data); + break; + } case CXCursorKind.CXCursor_EnumConstantDecl: - { - var containerContext = GetOrCreateDeclarationContainer(parent, data); - var cppEnum = (CppEnum)containerContext.Container; - var enumItem = new CppEnumItem(CXUtil.GetCursorSpelling(cursor), cursor.EnumConstantDeclValue); - ParseAttributes(cursor, enumItem, true); + { + var containerContext = GetOrCreateDeclarationContainer(parent, data); + var cppEnum = (CppEnum)containerContext.Container; + var enumItem = new CppEnumItem(CXUtil.GetCursorSpelling(cursor), cursor.EnumConstantDeclValue); + ParseAttributes(cursor, enumItem, true); - VisitInitValue(cursor, data, out var enumItemExpression, out var enumValue); - enumItem.ValueExpression = enumItemExpression; + VisitInitValue(cursor, data, out var enumItemExpression, out var enumValue); + enumItem.ValueExpression = enumItemExpression; - cppEnum.Items.Add(enumItem); - element = enumItem; - break; - } + cppEnum.Items.Add(enumItem); + element = enumItem; + break; + } case CXCursorKind.CXCursor_Namespace: element = VisitNamespace(cursor, data); @@ -419,47 +448,48 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d case CXCursorKind.CXCursor_ObjCInterfaceDecl: case CXCursorKind.CXCursor_ObjCProtocolDecl: case CXCursorKind.CXCursor_ObjCCategoryDecl: + { + bool isAnonymous = cursor.IsAnonymous; + var cppClass = VisitClassDecl(cursor, data); + var containerContext = GetOrCreateDeclarationContainer(parent, data); + // Empty struct/class/union declaration are considered as fields + if (isAnonymous) { - bool isAnonymous = cursor.IsAnonymous; - var cppClass = VisitClassDecl(cursor, data); - var containerContext = GetOrCreateDeclarationContainer(parent, data); - // Empty struct/class/union declaration are considered as fields - if (isAnonymous) + cppClass.Name = string.Empty; + Debug.Assert(string.IsNullOrEmpty(cppClass.Name)); + + // We try to recover the offset from the previous field + // Might not be always correct (with alignment rules), + // but not sure how to recover the offset without recalculating the entire offsets + var offset = 0; + var cppClassContainer = containerContext.Container as CppClass; + if (cppClassContainer is object && cppClassContainer.Fields.Count > 0) { - cppClass.Name = string.Empty; - Debug.Assert(string.IsNullOrEmpty(cppClass.Name)); - - // We try to recover the offset from the previous field - // Might not be always correct (with alignment rules), - // but not sure how to recover the offset without recalculating the entire offsets - var offset = 0; - var cppClassContainer = containerContext.Container as CppClass; - if (cppClassContainer is object && cppClassContainer.Fields.Count > 0) - { - var lastField = cppClassContainer.Fields[cppClassContainer.Fields.Count - 1]; - offset = (int)lastField.Offset + lastField.Type.SizeOf; - } - - // Create an anonymous field for the type - var cppField = new CppField(cppClass, string.Empty) - { - Visibility = containerContext.CurrentVisibility, - StorageQualifier = GetStorageQualifier(cursor), - IsAnonymous = true, - Offset = offset, - }; - ParseAttributes(cursor, cppField, true); - containerContext.DeclarationContainer.Fields.Add(cppField); - element = cppField; + var lastField = cppClassContainer.Fields[cppClassContainer.Fields.Count - 1]; + offset = (int)lastField.Offset + lastField.Type.SizeOf; } - else + + // Create an anonymous field for the type + var cppField = new CppField(cppClass, string.Empty) { - cppClass.Visibility = containerContext.CurrentVisibility; - element = cppClass; - } - break; + Visibility = containerContext.CurrentVisibility, + StorageQualifier = GetStorageQualifier(cursor), + IsAnonymous = true, + Offset = offset, + }; + ParseAttributes(cursor, cppField, true); + containerContext.DeclarationContainer.Fields.Add(cppField); + element = cppField; + } + else + { + cppClass.Visibility = containerContext.CurrentVisibility; + element = cppClass; } + break; + } + case CXCursorKind.CXCursor_EnumDecl: element = VisitEnumDecl(cursor, data); break; @@ -495,7 +525,7 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d break; case CXCursorKind.CXCursor_UnexposedDecl: return CXChildVisitResult.CXChildVisit_Recurse; - + case CXCursorKind.CXCursor_ObjCClassRef: case CXCursorKind.CXCursor_ObjCProtocolRef: { @@ -513,6 +543,7 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d cppClass.ObjCImplementedProtocols.Add(referencedType); } } + break; } case CXCursorKind.CXCursor_TypeRef: @@ -523,43 +554,40 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d var type = GetCppType(cursor.Referenced, cursor.Type, cursor, data); genericType.GenericArguments.Add(type); } + break; - - + + case CXCursorKind.CXCursor_CXXBaseSpecifier: + { + var cppClass = (CppClass)GetOrCreateDeclarationContainer(parent, data).Container; + var baseType = GetCppType(cursor.Type.Declaration, cursor.Type, cursor, data); + var cppBaseType = new CppBaseType(baseType) { - var cppClass = (CppClass)GetOrCreateDeclarationContainer(parent, data).Container; - var baseType = GetCppType(cursor.Type.Declaration, cursor.Type, cursor, data); - var cppBaseType = new CppBaseType(baseType) - { - Visibility = GetVisibility(cursor.CXXAccessSpecifier), - IsVirtual = cursor.IsVirtualBase - }; - cppClass.BaseTypes.Add(cppBaseType); - break; - } + Visibility = GetVisibility(cursor.CXXAccessSpecifier), + IsVirtual = cursor.IsVirtualBase + }; + cppClass.BaseTypes.Add(cppBaseType); + break; + } case CXCursorKind.CXCursor_CXXAccessSpecifier: - { - var containerContext = GetOrCreateDeclarationContainer(parent, data); - containerContext.CurrentVisibility = GetVisibility(cursor.CXXAccessSpecifier); - } + { + var containerContext = GetOrCreateDeclarationContainer(parent, data); + containerContext.CurrentVisibility = GetVisibility(cursor.CXXAccessSpecifier); + } break; case CXCursorKind.CXCursor_MacroDefinition: element = ParseMacro(cursor); break; - + case CXCursorKind.CXCursor_MacroExpansion: case CXCursorKind.CXCursor_InclusionDirective: - case CXCursorKind.CXCursor_FirstRef: + case CXCursorKind.CXCursor_FirstRef: case CXCursorKind.CXCursor_ObjCIvarDecl: case CXCursorKind.CXCursor_TemplateTypeParameter: - if (cursor.IsAttribute) - { - System.Diagnostics.Debugger.Break(); - } break; case CXCursorKind.CXCursor_LinkageSpec: @@ -572,19 +600,21 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d element = VisitProperty(containerContext, cursor, data); break; } - + default: if (!cursor.IsAttribute) { WarningUnhandled(cursor, parent); } + break; } if (element != null) { bool isForwardDeclaration = (element is CppClass || element is CppEnum) && !cursor.IsDefinition; - if (!isForwardDeclaration) { + if (!isForwardDeclaration) + { AssignSourceSpan(cursor, element); } } @@ -2271,6 +2301,11 @@ public CppContainerContext(ICppContainer container) public CppVisibility CurrentVisibility; + /// + /// Either "system" (include) or user. + /// + public string? NameContext { get; init; } + public bool IsChildrenVisited; } }