From 25290126956174ac3acda7f78d7a1ee5100393fa Mon Sep 17 00:00:00 2001 From: fangshen Date: Tue, 21 Mar 2023 17:45:52 +0800 Subject: [PATCH 1/4] Enhance for function template & CppType FullName support. 1. add support for function template 2. add inline namespace support 3. change FullName as a CppType property, and deduce the CppClass full name with specialized template right, you can just use CppClass full name in a generated c++ codes now. 4. FindByFullName() now can search auto ignore inline namespace now(such as clang std::__1::vector, now you can just use std::vector to search) 5. fix crash when the CppClass has a specialized template with PartialSpecializedTemplateDecl 6. fix crash when typedef with a AliasTemplateDecl for Underlying type. --- src/CppAst.Tests/TestFunctions.cs | 27 +++++ src/CppAst.Tests/TestNamespaces.cs | 43 ++++++++ src/CppAst/CppBaseType.cs | 25 +++++ src/CppAst/CppClass.cs | 51 ++++++++-- src/CppAst/CppElement.cs | 67 +++++++------ src/CppAst/CppEnum.cs | 31 +++--- src/CppAst/CppFunction.cs | 1 + src/CppAst/CppFunctionFlags.cs | 5 + src/CppAst/CppGlobalDeclarationContainer.cs | 17 +++- src/CppAst/CppModelBuilder.cs | 103 +++++++++++++------- src/CppAst/CppNamespace.cs | 5 + src/CppAst/CppTemplateArgument.cs | 48 ++++++--- src/CppAst/CppType.cs | 11 +++ src/CppAst/CppTypeKind.cs | 4 + src/CppAst/CppTypedef.cs | 32 +++--- 15 files changed, 355 insertions(+), 115 deletions(-) diff --git a/src/CppAst.Tests/TestFunctions.cs b/src/CppAst.Tests/TestFunctions.cs index 3932d81..c4b01af 100644 --- a/src/CppAst.Tests/TestFunctions.cs +++ b/src/CppAst.Tests/TestFunctions.cs @@ -256,5 +256,32 @@ public void TestFunctionVariadic() ); } + + + [Test] + public void TestFunctionTemplate() + { + ParseAssert(@" +template +void function0(T t); +", + compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Functions.Count); + + { + var cppFunction = compilation.Functions[0]; + Assert.AreEqual(1, cppFunction.Parameters.Count); + Assert.AreEqual("void", cppFunction.ReturnType.ToString()); + Assert.AreEqual(cppFunction.IsFunctionTemplate, true); + Assert.AreEqual(cppFunction.TemplateParameters.Count, 1); + } + + } + ); + } + } } \ No newline at end of file diff --git a/src/CppAst.Tests/TestNamespaces.cs b/src/CppAst.Tests/TestNamespaces.cs index c986fa8..0ce8125 100644 --- a/src/CppAst.Tests/TestNamespaces.cs +++ b/src/CppAst.Tests/TestNamespaces.cs @@ -107,5 +107,48 @@ struct MyStruct; } ); } + + [Test] + public void TestInlineNamespace() + { + var text = @" +namespace A +{ + +inline namespace __1 +{ + // Test using Template + template + struct MyStruct; + + using MyStructInt = MyStruct; +} + +} + +"; + + ParseAssert(text, + compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Namespaces.Count); + + var inlineNs = compilation.Namespaces[0].Namespaces[0]; + Assert.AreEqual(inlineNs.Name, "__1"); + Assert.AreEqual(true, inlineNs.IsInlineNamespace); + + var cppStruct = compilation.FindByFullName("A::MyStruct"); + Assert.AreEqual(inlineNs.Classes[0], cppStruct); + Assert.AreEqual(cppStruct.FullName, "A::MyStruct"); + + var cppTypedef = compilation.FindByFullName("A::MyStructInt"); + var cppStructInt = cppTypedef.ElementType as CppClass; + //So now we can use this full name in exporter convenience. + Assert.AreEqual(cppStructInt.FullName, "A::MyStruct"); + } + ); + } } } \ No newline at end of file diff --git a/src/CppAst/CppBaseType.cs b/src/CppAst/CppBaseType.cs index a854315..a5eccc9 100644 --- a/src/CppAst/CppBaseType.cs +++ b/src/CppAst/CppBaseType.cs @@ -51,6 +51,31 @@ public override string ToString() } builder.Append(Type.GetDisplayName()); + + var cls = Type as CppClass; + if(cls != null && cls.TemplateKind != CppTemplateKind.NormalClass) + { + builder.Append("<"); + + if (cls.TemplateKind == CppTemplateKind.TemplateSpecializedClass) + { + for (var i = 0; i < cls.TemplateSpecializedArguments.Count; i++) + { + if (i > 0) builder.Append(", "); + builder.Append(cls.TemplateSpecializedArguments[i].ToString()); + } + } + else if (cls.TemplateKind == CppTemplateKind.TemplateClass) + { + for (var i = 0; i < cls.TemplateParameters.Count; i++) + { + if (i > 0) builder.Append(", "); + builder.Append(cls.TemplateParameters[i].ToString()); + } + } + + builder.Append(">"); + } return builder.ToString(); } } diff --git a/src/CppAst/CppClass.cs b/src/CppAst/CppClass.cs index 91472cb..df3e569 100644 --- a/src/CppAst/CppClass.cs +++ b/src/CppAst/CppClass.cs @@ -41,20 +41,57 @@ public CppClass(string name) : base(CppTypeKind.StructOrClass) /// public string Name { get; set; } - public string FullName - { - get + public override string FullName + { + get { + StringBuilder sb = new StringBuilder(); string fullparent = FullParentName; - if(string.IsNullOrEmpty(fullparent)) + if (string.IsNullOrEmpty(fullparent)) { - return Name; + sb.Append(Name); } else { - return $"{fullparent}{Name}"; + sb.Append($"{fullparent}::{Name}"); + } + + if (TemplateKind == CppTemplateKind.TemplateClass + || TemplateKind == CppTemplateKind.PartialTemplateClass) + { + sb.Append('<'); + for (int i = 0; i < TemplateParameters.Count; i++) + { + var tp = TemplateParameters[i]; + if (i != 0) + { + sb.Append(", "); + } + sb.Append(tp.ToString()); + } + sb.Append('>'); } - } + else if (TemplateKind == CppTemplateKind.TemplateSpecializedClass) + { + sb.Append('<'); + for (int i = 0; i < TemplateSpecializedArguments.Count; i++) + { + var ta = TemplateSpecializedArguments[i]; + if (i != 0) + { + sb.Append(", "); + } + sb.Append(ta.ArgString); + } + sb.Append('>'); + } + //else if(TemplateKind == CppTemplateKind.PartialTemplateClass) + //{ + // sb.Append('<'); + // sb.Append('>'); + //} + return sb.ToString(); + } } /// diff --git a/src/CppAst/CppElement.cs b/src/CppAst/CppElement.cs index 944f377..7e801da 100644 --- a/src/CppAst/CppElement.cs +++ b/src/CppAst/CppElement.cs @@ -25,35 +25,46 @@ public string FullParentName { get { - string tmpname = ""; - var p = Parent; - while (p != null) - { - if (p is CppClass) - { - var cpp = p as CppClass; - tmpname = $"{cpp.Name}::{tmpname}"; - p = cpp.Parent; - } - else if (p is CppNamespace) - { - var ns = p as CppNamespace; - tmpname = $"{ns.Name}::{tmpname}"; - p = ns.Parent; - } - else if (p is CppCompilation) - { - // root namespace here, just ignore~ - p = null; - } - else - { - throw new NotImplementedException("Can not be here, not support type here!"); - } - } + string tmpname = ""; + var p = Parent; + while (p != null) + { + if (p is CppClass) + { + var cpp = p as CppClass; + tmpname = $"{cpp.Name}::{tmpname}"; + p = cpp.Parent; + } + else if (p is CppNamespace) + { + var ns = p as CppNamespace; - return tmpname; - } + //Just ignore inline namespace + if (!ns.IsInlineNamespace) + { + tmpname = $"{ns.Name}::{tmpname}"; + } + p = ns.Parent; + } + else if (p is CppCompilation) + { + // root namespace here, just ignore~ + p = null; + } + else + { + throw new NotImplementedException("Can not be here, not support type here!"); + } + } + + //Try to remove not need `::` in string tails. + if (tmpname.EndsWith("::")) + { + tmpname = tmpname.Substring(0, tmpname.Length - 2); + } + + return tmpname; + } } /// diff --git a/src/CppAst/CppEnum.cs b/src/CppAst/CppEnum.cs index dc4dbcf..43f6273 100644 --- a/src/CppAst/CppEnum.cs +++ b/src/CppAst/CppEnum.cs @@ -30,22 +30,21 @@ public CppEnum(string name) : base(CppTypeKind.Enum) /// public string Name { get; set; } - public string FullName - { - get - { - string fullparent = FullParentName; - if (string.IsNullOrEmpty(fullparent)) - { - return Name; - } - else - { - return $"{fullparent}{Name}"; - } - } - } - + public override string FullName + { + get + { + string fullparent = FullParentName; + if (string.IsNullOrEmpty(fullparent)) + { + return Name; + } + else + { + return $"{fullparent}::{Name}"; + } + } + } /// /// Gets or sets a boolean indicating if this enum is scoped. diff --git a/src/CppAst/CppFunction.cs b/src/CppAst/CppFunction.cs index 99addaa..a1ca7a3 100644 --- a/src/CppAst/CppFunction.cs +++ b/src/CppAst/CppFunction.cs @@ -97,6 +97,7 @@ public int DefaultParamCount public bool IsConst => ((int)Flags & (int)CppFunctionFlags.Const) != 0; + public bool IsFunctionTemplate => ((int)Flags & (int)CppFunctionFlags.FunctionTemplate) != 0; /// public List TemplateParameters { get; } diff --git a/src/CppAst/CppFunctionFlags.cs b/src/CppAst/CppFunctionFlags.cs index 4f2ce47..96e6937 100644 --- a/src/CppAst/CppFunctionFlags.cs +++ b/src/CppAst/CppFunctionFlags.cs @@ -61,5 +61,10 @@ public enum CppFunctionFlags /// This is a variadic function (has `...` parameter) /// Variadic = 1 << 8, + + /// + /// This is a function template (has template params in function) + /// + FunctionTemplate = 1 << 9, } } \ No newline at end of file diff --git a/src/CppAst/CppGlobalDeclarationContainer.cs b/src/CppAst/CppGlobalDeclarationContainer.cs index 90f3a29..29fdd10 100644 --- a/src/CppAst/CppGlobalDeclarationContainer.cs +++ b/src/CppAst/CppGlobalDeclarationContainer.cs @@ -109,6 +109,21 @@ private CppElement SearchForChild(CppElement parent, string child_name) if (t != null) return t; } + //Not found, try to find in inline namespace. + if(parent is CppNamespace) + { + var ns = parent as CppNamespace; + foreach(var sn in ns.Namespaces) + { + if (sn.IsInlineNamespace) + { + var findElem = SearchForChild(sn, child_name); + //Find it in inline namespace, just return. + if(findElem != null) return findElem; + } + } + } + return null; } @@ -117,7 +132,7 @@ private CppElement SearchForChild(CppElement parent, string child_name) /// /// Name of the element to find /// The CppElement found or null if not found - public CppElement FindByFullName(string name) + public CppElement FindByFullName(string name) { var arr = name.Split(new string[] { "::" }, StringSplitOptions.RemoveEmptyEntries); if(arr.Length == 0) return null; diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index 43fe513..f5aa55e 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -51,16 +51,15 @@ public CXChildVisitResult VisitTranslationUnit(CXCursor cursor, CXCursor parent, return VisitMember(cursor, parent, data); } - private void TryToCreateTemplateParameters(CppClass parentclass, CXCursor cursor, void* data) + private CppType TryToCreateTemplateParameters(CXCursor cursor, void* data) { switch (cursor.Kind) { case CXCursorKind.CXCursor_TemplateTypeParameter: { var parameterTypeName = new CppTemplateParameterType(GetCursorSpelling(cursor)); - parentclass.TemplateParameters.Add(parameterTypeName); + return parameterTypeName; } - break; case CXCursorKind.CXCursor_NonTypeTemplateParameter: { //Just use low level ClangSharp object to do the logic @@ -69,22 +68,20 @@ private void TryToCreateTemplateParameters(CppClass parentclass, CXCursor cursor var tmpname = cursor.Spelling.ToString(); var noneTypeParam = new CppTemplateParameterNonType(tmpname, tmpcpptype); - parentclass.TemplateParameters.Add(noneTypeParam); + return noneTypeParam; } - break; case CXCursorKind.CXCursor_TemplateTemplateParameter: { //ToDo: add template template parameter support here~~ Debug.WriteLine("[Warning] template template parameter maybe not handle right here!"); var tmplparam = new CppTemplateParameterType(GetCursorSpelling(cursor)); - parentclass.TemplateParameters.Add(tmplparam); + return tmplparam; } - break; } + return null; } - private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, void* data) { var typeAsCString = clang.getCursorUSR(cursor).CString.ToString(); @@ -117,6 +114,7 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi Debug.Assert(parentGlobalDeclarationContainer != null); var ns = new CppNamespace(GetCursorSpelling(cursor)); symbol = ns; + ns.IsInlineNamespace = cursor.IsInlineNamespace; defaultContainerVisibility = CppVisibility.Default; parentGlobalDeclarationContainer.Namespaces.Add(ns); break; @@ -132,6 +130,7 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi break; case CXCursorKind.CXCursor_ClassTemplate: + case CXCursorKind.CXCursor_ClassTemplatePartialSpecialization: case CXCursorKind.CXCursor_ClassDecl: case CXCursorKind.CXCursor_StructDecl: case CXCursorKind.CXCursor_UnionDecl: @@ -160,33 +159,46 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi cppClass.TemplateKind = CppTemplateKind.TemplateClass; cursor.VisitChildren((childCursor, classCursor, clientData) => { - TryToCreateTemplateParameters(cppClass, childCursor, 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) + else if (cursor.DeclKind == CX_DeclKind.CX_DeclKind_ClassTemplateSpecialization + || cursor.DeclKind == CX_DeclKind.CX_DeclKind_ClassTemplatePartialSpecialization) { //Try to generate template class first cppClass.SpecializedTemplate = (CppClass)GetOrCreateDeclarationContainer(cursor.SpecializedCursorTemplate, data).Container; - cppClass.TemplateKind = CppTemplateKind.TemplateSpecializedClass; + if (cursor.DeclKind == CX_DeclKind.CX_DeclKind_ClassTemplatePartialSpecialization) + { + cppClass.TemplateKind = CppTemplateKind.PartialTemplateClass; + } + else + { + cppClass.TemplateKind = CppTemplateKind.TemplateSpecializedClass; + } + //Just use low level api to call ClangSharp - var temp_params = cppClass.SpecializedTemplate.TemplateParameters; + var tempArgsCount = cursor.NumTemplateArguments; + var tempParams = cppClass.SpecializedTemplate.TemplateParameters; - var temp_args_count = cursor.NumTemplateArguments; - //Just use template class template params here - foreach (var param in temp_params) + foreach (var param in tempParams) { cppClass.TemplateParameters.Add(param); } - ////var temp_decl = specilize_decl.SpecializedTemplate; - - Debug.Assert(cppClass.SpecializedTemplate.TemplateParameters.Count == temp_args_count); + if(cppClass.TemplateKind == CppTemplateKind.TemplateSpecializedClass) + { + Debug.Assert(cppClass.SpecializedTemplate.TemplateParameters.Count == tempArgsCount); + } + - for (uint i = 0; i < temp_args_count; i++) + for (uint i = 0; i < tempArgsCount; i++) { var arg = cursor.GetTemplateArgument(i); switch (arg.kind) @@ -194,32 +206,25 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi case CXTemplateArgumentKind.CXTemplateArgumentKind_Type: { var argh = arg.AsType; - var arg_type = GetCppType(argh.Declaration, argh, cursor, data); - cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(temp_params[(int)i], arg_type)); + var argType = GetCppType(argh.Declaration, argh, cursor, data); + cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(tempParams[(int)i], argType)); } break; case CXTemplateArgumentKind.CXTemplateArgumentKind_Integral: { - cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(temp_params[(int)i], arg.AsIntegral)); + cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(tempParams[(int)i], arg.AsIntegral)); } break; default: { Debug.WriteLine($"[Warning]template argument in class:{cppClass.FullName} with type: {arg.kind.ToString()} do not handle right now!"); - cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(temp_params[(int)i], arg.ToString())); + cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(tempParams[(int)i], arg.ToString())); } break; } } } - else if (cursor.DeclKind == CX_DeclKind.CX_DeclKind_ClassTemplatePartialSpecialization) - { - Debug.WriteLine("[Warning]Maybe can not run to here in CppAst!"); - //Try to generate template class first - cppClass.SpecializedTemplate = (CppClass)GetOrCreateDeclarationContainer(cursor.SpecializedCursorTemplate, data).Container; - cppClass.TemplateKind = CppTemplateKind.PartialTemplateClass; - } else { cppClass.TemplateKind = CppTemplateKind.NormalClass; @@ -1229,6 +1234,15 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da } var functionName = GetCursorSpelling(cursor); + + //We need ignore the function define out in the class definition here(Otherwise it will has two same functions here~)! + var semKind = cursor.SemanticParent.Kind; + if ((semKind == CXCursorKind.CXCursor_StructDecl || semKind == CXCursorKind.CXCursor_ClassDecl) + && cursor.LexicalParent != cursor.SemanticParent) + { + return null; + } + var cppFunction = new CppFunction(functionName) { Visibility = contextContainer.CurrentVisibility, @@ -1247,6 +1261,21 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da container.Functions.Add(cppFunction); } + if (cursor.kind == CXCursorKind.CXCursor_FunctionTemplate) + { + cppFunction.Flags |= CppFunctionFlags.FunctionTemplate; + //Handle template argument here~ + cursor.VisitChildren((childCursor, funcCursor, clientData) => + { + var tmplParam = TryToCreateTemplateParameters(childCursor, clientData); + if (tmplParam != null) + { + cppFunction.TemplateParameters.Add(tmplParam); + } + return CXChildVisitResult.CXChildVisit_Continue; + }, new CXClientData((IntPtr)data)); + } + if (cursor.Kind == CXCursorKind.CXCursor_CXXMethod) { cppFunction.Flags |= CppFunctionFlags.Method; @@ -1697,9 +1726,17 @@ private CppType VisitTypeAliasDecl(CXCursor cursor, void* data) } var contextContainer = GetOrCreateDeclarationContainer(cursor.SemanticParent, data); - var underlyingTypeDefType = GetCppType(cursor.TypedefDeclUnderlyingType.Declaration, cursor.TypedefDeclUnderlyingType, cursor, data); - var typedefName = GetCursorSpelling(cursor); + var kind = cursor.Kind; + + CXCursor usedCursor = cursor; + if(kind == CXCursorKind.CXCursor_TypeAliasTemplateDecl) + { + usedCursor = cursor.TemplatedDecl; + } + + var underlyingTypeDefType = GetCppType(usedCursor.TypedefDeclUnderlyingType.Declaration, usedCursor.TypedefDeclUnderlyingType, usedCursor, data); + var typedefName = GetCursorSpelling(usedCursor); if (AutoSquashTypedef && underlyingTypeDefType is ICppMember cppMember && (string.IsNullOrEmpty(cppMember.Name) || typedefName == cppMember.Name)) { diff --git a/src/CppAst/CppNamespace.cs b/src/CppAst/CppNamespace.cs index d673c2a..4a42b17 100644 --- a/src/CppAst/CppNamespace.cs +++ b/src/CppAst/CppNamespace.cs @@ -33,6 +33,11 @@ public CppNamespace(string name) /// public string Name { get; set; } + /// + /// Is the namespace inline or not(such as std::__1::vector). + /// + public bool IsInlineNamespace { get; set; } + /// public CppContainerList Fields { get; } diff --git a/src/CppAst/CppTemplateArgument.cs b/src/CppAst/CppTemplateArgument.cs index 64cfc08..2f97827 100644 --- a/src/CppAst/CppTemplateArgument.cs +++ b/src/CppAst/CppTemplateArgument.cs @@ -10,30 +10,30 @@ namespace CppAst /// /// For c++ specialized template argument /// - public class CppTemplateArgument : CppElement + public class CppTemplateArgument : CppType { - public CppTemplateArgument(CppType sourceParam, CppType typeArg) + public CppTemplateArgument(CppType sourceParam, CppType typeArg) : base(CppTypeKind.TemplateArgumentType) { SourceParam = sourceParam ?? throw new ArgumentNullException(nameof(sourceParam)); ArgAsType = typeArg ?? throw new ArgumentNullException(nameof(typeArg)); ArgKind = CppTemplateArgumentKind.AsType; } - public CppTemplateArgument(CppType sourceParam, long intArg) - { + public CppTemplateArgument(CppType sourceParam, long intArg) : base(CppTypeKind.TemplateArgumentType) + { SourceParam = sourceParam ?? throw new ArgumentNullException(nameof(sourceParam)); ArgAsInteger = intArg; - ArgKind = CppTemplateArgumentKind.AsInteger; - } + ArgKind = CppTemplateArgumentKind.AsInteger; + } - public CppTemplateArgument(CppType sourceParam, string unknownStr) + public CppTemplateArgument(CppType sourceParam, string unknownStr) : base(CppTypeKind.TemplateArgumentType) { SourceParam = sourceParam ?? throw new ArgumentNullException(nameof(sourceParam)); ArgAsUnknown = unknownStr; - ArgKind = CppTemplateArgumentKind.Unknown; - } + ArgKind = CppTemplateArgumentKind.Unknown; + } - public CppTemplateArgumentKind ArgKind { get; } + public CppTemplateArgumentKind ArgKind { get; } public CppType ArgAsType { get; } @@ -45,17 +45,17 @@ public string ArgString { get { - switch(ArgKind) + switch (ArgKind) { case CppTemplateArgumentKind.AsType: - return ArgAsType.ToString(); + return ArgAsType.FullName; case CppTemplateArgumentKind.AsInteger: return ArgAsInteger.ToString(); - case CppTemplateArgumentKind.Unknown: + case CppTemplateArgumentKind.Unknown: return ArgAsUnknown; default: return "?"; - } + } } } @@ -65,6 +65,20 @@ public string ArgString /// public CppType SourceParam { get; } + + /// + public override int SizeOf + { + get => 0; + set => throw new InvalidOperationException("This type does not support SizeOf"); + } + + /// + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is CppTemplateArgument other && Equals(other); + } + /// public override int GetHashCode() { @@ -75,6 +89,12 @@ public override int GetHashCode() } + /// + public override CppType GetCanonicalType() => this; + + /// + + /// public override string ToString() => $"{SourceParam} = {ArgString}"; } diff --git a/src/CppAst/CppType.cs b/src/CppAst/CppType.cs index 683f31e..0d2f01e 100644 --- a/src/CppAst/CppType.cs +++ b/src/CppAst/CppType.cs @@ -48,5 +48,16 @@ public override int GetHashCode() /// /// A canonical type of this type instance public abstract CppType GetCanonicalType(); + + /// + /// We can use this name in exporter to use this type. + /// + public virtual string FullName + { + get + { + return ToString(); + } + } } } \ No newline at end of file diff --git a/src/CppAst/CppTypeKind.cs b/src/CppAst/CppTypeKind.cs index 21e329c..a40f928 100644 --- a/src/CppAst/CppTypeKind.cs +++ b/src/CppAst/CppTypeKind.cs @@ -54,6 +54,10 @@ public enum CppTypeKind /// TemplateParameterNonType, /// + /// A template specialized argument type. + /// + TemplateArgumentType, + /// /// An unexposed type. /// Unexposed, diff --git a/src/CppAst/CppTypedef.cs b/src/CppAst/CppTypedef.cs index 4404677..4aade4c 100644 --- a/src/CppAst/CppTypedef.cs +++ b/src/CppAst/CppTypedef.cs @@ -34,23 +34,23 @@ public CppTypedef(string name, CppType type) : base(CppTypeKind.Typedef) /// public string Name { get; set; } - public string FullName - { - get - { - string fullparent = FullParentName; - if (string.IsNullOrEmpty(fullparent)) - { - return Name; - } - else - { - return $"{fullparent}{Name}"; - } - } - } + public override string FullName + { + get + { + string fullparent = FullParentName; + if (string.IsNullOrEmpty(fullparent)) + { + return Name; + } + else + { + return $"{fullparent}::{Name}"; + } + } + } - private bool Equals(CppTypedef other) + private bool Equals(CppTypedef other) { return base.Equals(other) && string.Equals(Name, other.Name); } From aeea1e17ec535f99d10631c7f0831a9dcf229332 Mon Sep 17 00:00:00 2001 From: fangshen Date: Mon, 5 Jun 2023 14:38:03 +0800 Subject: [PATCH 2/4] 1. Add a test for partial specialized template && 2. Add a IsSpecializedArgument for CppTemplateArgument, so we can detect is a specialized argument or not. --- src/CppAst.Tests/TestTypes.cs | 54 +++++++++++++++++++++++++++++++ src/CppAst/CppModelBuilder.cs | 3 +- src/CppAst/CppTemplateArgument.cs | 6 +++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/CppAst.Tests/TestTypes.cs b/src/CppAst.Tests/TestTypes.cs index 8d8c36a..368589c 100644 --- a/src/CppAst.Tests/TestTypes.cs +++ b/src/CppAst.Tests/TestTypes.cs @@ -147,6 +147,60 @@ class Derived : public ::BaseTemplate<::Derived> ); } + + [Test] + public void TestTemplatePartialSpecializationInheritance() + { + ParseAssert(@" +template +struct foo {}; + +template +struct foo {}; + +foo foobar; +", + compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(3, compilation.Classes.Count); + Assert.AreEqual(1, compilation.Fields.Count); + + var baseTemplate = compilation.Classes[0]; + var fullSpecializedClass = compilation.Classes[1]; + var partialSpecializedTemplate = compilation.Classes[2]; + + var field = compilation.Fields[0]; + Assert.AreEqual(field.Name, "foobar"); + + Assert.AreEqual(baseTemplate.TemplateKind, CppAst.CppTemplateKind.TemplateClass); + Assert.AreEqual(fullSpecializedClass.TemplateKind, CppAst.CppTemplateKind.TemplateSpecializedClass); + Assert.AreEqual(partialSpecializedTemplate.TemplateKind, CppAst.CppTemplateKind.PartialTemplateClass); + + //Need be a specialized for partial template here + Assert.AreEqual(fullSpecializedClass.SpecializedTemplate, partialSpecializedTemplate); + + //Need be a full specialized class for this field + Assert.AreEqual(field.Type, fullSpecializedClass); + + Assert.AreEqual(partialSpecializedTemplate.TemplateSpecializedArguments.Count, 2); + //The first argument is integer now + Assert.AreEqual(partialSpecializedTemplate.TemplateSpecializedArguments[0].ArgString, "int"); + //The second argument is not a specialized argument, we do not specialized a `B` template parameter here(partial specialized template) + Assert.AreEqual(partialSpecializedTemplate.TemplateSpecializedArguments[1].IsSpecializedArgument, false); + + //The field use type is a full specialized type here~, so we can have two `int` template parmerater here + //It's a not template or partial template class, so we can instantiate it, see `foo foobar;` before. + Assert.AreEqual(fullSpecializedClass.TemplateSpecializedArguments.Count, 2); + //The first argument is integer now + Assert.AreEqual(fullSpecializedClass.TemplateSpecializedArguments[0].ArgString, "int"); + //The second argument is not a specialized argument + Assert.AreEqual(fullSpecializedClass.TemplateSpecializedArguments[1].ArgString, "int"); + } + ); + } + [Test] public void TestClassPrototype() { diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index f5aa55e..20b9b0e 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -207,7 +207,8 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi { var argh = arg.AsType; var argType = GetCppType(argh.Declaration, argh, cursor, data); - cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(tempParams[(int)i], argType)); + var depend = arg.Dependence; + cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(tempParams[(int)i], argType, argh.TypeClass != CX_TypeClass.CX_TypeClass_TemplateTypeParm)); } break; case CXTemplateArgumentKind.CXTemplateArgumentKind_Integral: diff --git a/src/CppAst/CppTemplateArgument.cs b/src/CppAst/CppTemplateArgument.cs index 2f97827..4efa3aa 100644 --- a/src/CppAst/CppTemplateArgument.cs +++ b/src/CppAst/CppTemplateArgument.cs @@ -12,11 +12,12 @@ namespace CppAst /// public class CppTemplateArgument : CppType { - public CppTemplateArgument(CppType sourceParam, CppType typeArg) : base(CppTypeKind.TemplateArgumentType) + public CppTemplateArgument(CppType sourceParam, CppType typeArg, bool isSpecializedArgument) : base(CppTypeKind.TemplateArgumentType) { SourceParam = sourceParam ?? throw new ArgumentNullException(nameof(sourceParam)); ArgAsType = typeArg ?? throw new ArgumentNullException(nameof(typeArg)); ArgKind = CppTemplateArgumentKind.AsType; + IsSpecializedArgument = isSpecializedArgument; } public CppTemplateArgument(CppType sourceParam, long intArg) : base(CppTypeKind.TemplateArgumentType) @@ -24,6 +25,7 @@ public CppTemplateArgument(CppType sourceParam, long intArg) : base(CppTypeKind. SourceParam = sourceParam ?? throw new ArgumentNullException(nameof(sourceParam)); ArgAsInteger = intArg; ArgKind = CppTemplateArgumentKind.AsInteger; + IsSpecializedArgument = true; } public CppTemplateArgument(CppType sourceParam, string unknownStr) : base(CppTypeKind.TemplateArgumentType) @@ -31,6 +33,7 @@ public CppTemplateArgument(CppType sourceParam, string unknownStr) : base(CppTyp SourceParam = sourceParam ?? throw new ArgumentNullException(nameof(sourceParam)); ArgAsUnknown = unknownStr; ArgKind = CppTemplateArgumentKind.Unknown; + IsSpecializedArgument = true; } public CppTemplateArgumentKind ArgKind { get; } @@ -65,6 +68,7 @@ public string ArgString /// public CppType SourceParam { get; } + public bool IsSpecializedArgument { get; } /// public override int SizeOf From 814e4c5f20be8fe22dd8e59d3e239b7a84126744 Mon Sep 17 00:00:00 2001 From: fangshen Date: Mon, 26 Jun 2023 12:15:24 +0800 Subject: [PATCH 3/4] --other=1. move Tokenizer to CppTokenUtil class && 2. add a sperate list for token parser attributes and mark it as deprecated --- .../AttributesTest/TestAnnotateAttributes.cs | 142 +++ .../TestSystemAttributes.cs} | 51 +- .../AttributesTest/TestTokenAttributes.cs | 560 ++++++++++ src/CppAst.Tests/TestComments.cs | 2 +- src/CppAst.Tests/TestFunctions.cs | 4 +- src/CppAst.Tests/TestTypes.cs | 2 +- src/CppAst/CppAttribute.cs | 21 +- src/CppAst/CppAttributeKind.cs | 21 + src/CppAst/CppClass.cs | 7 +- src/CppAst/CppEnum.cs | 9 +- src/CppAst/CppEnumItem.cs | 8 +- src/CppAst/CppField.cs | 9 +- src/CppAst/CppFunction.cs | 10 +- src/CppAst/CppGlobalDeclarationContainer.cs | 8 +- src/CppAst/CppModelBuilder.cs | 957 +++-------------- src/CppAst/CppNamespace.cs | 8 +- src/CppAst/CppParser.cs | 3 +- src/CppAst/CppParserOptions.cs | 20 +- src/CppAst/CppTokenUtil.cs | 981 ++++++++++++++++++ src/CppAst/ICppAttributeContainer.cs | 22 + src/CppAst/ICppDeclarationContainer.cs | 8 +- 21 files changed, 1959 insertions(+), 894 deletions(-) create mode 100644 src/CppAst.Tests/AttributesTest/TestAnnotateAttributes.cs rename src/CppAst.Tests/{TestAttributes.cs => AttributesTest/TestSystemAttributes.cs} (92%) create mode 100644 src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs create mode 100644 src/CppAst/CppAttributeKind.cs create mode 100644 src/CppAst/CppTokenUtil.cs create mode 100644 src/CppAst/ICppAttributeContainer.cs diff --git a/src/CppAst.Tests/AttributesTest/TestAnnotateAttributes.cs b/src/CppAst.Tests/AttributesTest/TestAnnotateAttributes.cs new file mode 100644 index 0000000..c8f9a14 --- /dev/null +++ b/src/CppAst.Tests/AttributesTest/TestAnnotateAttributes.cs @@ -0,0 +1,142 @@ +// 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 NUnit.Framework; + +namespace CppAst.Tests +{ + public class TestAnnotateAttributes : InlineTestBase + { + [Test] + public void TestAnnotateAttribute() + { + var text = @" + +#if !defined(__cppast) +#define __cppast(...) +#endif + +__cppast(script, is_browsable=true, desc=""a function"") +void TestFunc() +{ +} + +enum class __cppast(script, is_browsable=true, desc=""a enum"") TestEnum +{ +}; + +class __cppast(script, is_browsable=true, desc=""a class"") TestClass +{ + public: + __cppast(desc=""a member function"") + void TestMemberFunc(); + + __cppast(desc=""a member field"") + int X; +}; +"; + + ParseAssert(text, + compilation => + { + Assert.False(compilation.HasErrors); + + //annotate attribute support on global function + var cppFunc = compilation.Functions[0]; + Assert.AreEqual(1, cppFunc.Attributes.Count); + Assert.AreEqual(cppFunc.Attributes[0].Kind, AttributeKind.AnnotateAttribute); + Assert.AreEqual(cppFunc.Attributes[0].Arguments, "script, is_browsable=true, desc=\"a function\""); + + //annotate attribute support on enum + var cppEnum = compilation.Enums[0]; + Assert.AreEqual(1, cppEnum.Attributes.Count); + Assert.AreEqual(cppEnum.Attributes[0].Kind, AttributeKind.AnnotateAttribute); + Assert.AreEqual(cppEnum.Attributes[0].Arguments, "script, is_browsable=true, desc=\"a enum\""); + + //annotate attribute support on class + var cppClass = compilation.Classes[0]; + Assert.AreEqual(1, cppClass.Attributes.Count); + Assert.AreEqual(cppClass.Attributes[0].Kind, AttributeKind.AnnotateAttribute); + Assert.AreEqual(cppClass.Attributes[0].Arguments, "script, is_browsable=true, desc=\"a class\""); + + Assert.AreEqual(1, cppClass.Functions.Count); + var memFunc = cppClass.Functions[0]; + Assert.AreEqual(1, memFunc.Attributes.Count); + Assert.AreEqual(memFunc.Attributes[0].Arguments, "desc=\"a member function\""); + + + Assert.AreEqual(1, cppClass.Fields.Count); + var memField = cppClass.Fields[0]; + Assert.AreEqual(1, memField.Attributes.Count); + Assert.AreEqual(memField.Attributes[0].Arguments, "desc=\"a member field\""); + } + ); + } + + + [Test] + public void TestAnnotateAttributeInNamespace() + { + var text = @" + +#if !defined(__cppast) +#define __cppast(...) +#endif + +namespace __cppast(script, is_browsable=true, desc=""a namespace test"") TestNs{ + +} + +"; + + ParseAssert(text, + compilation => + { + Assert.False(compilation.HasErrors); + + //annotate attribute support on namespace + var ns = compilation.Namespaces[0]; + Assert.AreEqual(1, ns.Attributes.Count); + Assert.AreEqual(ns.Attributes[0].Kind, AttributeKind.AnnotateAttribute); + Assert.AreEqual(ns.Attributes[0].Arguments, "script, is_browsable=true, desc=\"a namespace test\""); + + } + ); + } + + [Test] + public void TestAnnotateAttributeWithMacro() + { + var text = @" + +#if !defined(__cppast) +#define __cppast(...) +#endif + +#define UUID() 12345 + +__cppast(id=UUID(), desc=""a function with macro"") +void TestFunc() +{ +} + +"; + + ParseAssert(text, + compilation => + { + Assert.False(compilation.HasErrors); + + //annotate attribute support on namespace + var func = compilation.Functions[0]; + Assert.AreEqual(1, func.Attributes.Count); + Assert.AreEqual(func.Attributes[0].Kind, AttributeKind.AnnotateAttribute); + Assert.AreEqual(func.Attributes[0].Arguments, "id=12345, desc=\"a function with macro\""); + + } + ); + } + } +} diff --git a/src/CppAst.Tests/TestAttributes.cs b/src/CppAst.Tests/AttributesTest/TestSystemAttributes.cs similarity index 92% rename from src/CppAst.Tests/TestAttributes.cs rename to src/CppAst.Tests/AttributesTest/TestSystemAttributes.cs index 384da68..6ea9fec 100644 --- a/src/CppAst.Tests/TestAttributes.cs +++ b/src/CppAst.Tests/AttributesTest/TestSystemAttributes.cs @@ -7,7 +7,7 @@ namespace CppAst.Tests { - public class TestAttributes : InlineTestBase + public class TestSystemAttributes : InlineTestBase { [Test] public void TestSimple() @@ -60,7 +60,7 @@ public void TestSimple() Assert.AreEqual("alloc_align(1)", compilation.Functions[2].Attributes[0].ToString()); }, - new CppParserOptions() { ParseAttributes = true }.ConfigureForWindowsMsvc() // Force using X86 to get __stdcall calling convention + new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() // Force using X86 to get __stdcall calling convention ); } @@ -93,7 +93,7 @@ struct __declspec(uuid(""1841e5c8-16b0-489b-bcc8-44cfb0d5deae"")) __declspec(nov Assert.Null(attr.Arguments); } }, - new CppParserOptions() { ParseAttributes = true }.ConfigureForWindowsMsvc()); + new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc()); } [Test] @@ -112,7 +112,7 @@ public void TestCpp11VarAlignas() } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -132,7 +132,7 @@ struct alignas(8) S {};", compilation => } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -140,7 +140,7 @@ struct alignas(8) S {};", compilation => public void TestCpp11StructAlignasWithAttribute() { ParseAssert(@" -struct [[deprecated]] alignas(8) S {};", compilation => +struct [[deprecated(""abc"")]] alignas(8) S {};", compilation => { Assert.False(compilation.HasErrors); @@ -157,7 +157,7 @@ struct [[deprecated]] alignas(8) S {};", compilation => } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -192,7 +192,7 @@ struct [[deprecated(""old"")]] TestMessage{ } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -225,7 +225,7 @@ struct Test{ } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -245,7 +245,7 @@ public void TestCpp11FunctionsAttributes() } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -265,7 +265,7 @@ namespace [[deprecated]] cppast {};", compilation => } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -285,7 +285,7 @@ enum [[deprecated]] E { };", compilation => } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -307,7 +307,7 @@ public void TestCpp11TemplateStructAttributes() } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); } @@ -344,7 +344,7 @@ struct [[cppast(""old"")]] TestMessage{ // C++17 says if the compile encounters a attribute it doesn't understand // it will ignore that attribute and not throw an error, we still want to // parse this. - new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } ); } @@ -369,7 +369,7 @@ public void TestCommentParen() Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); }, - new CppParserOptions() { ParseAttributes = true }); + new CppParserOptions() { ParseTokenAttributes = true }); } [Test] @@ -393,7 +393,7 @@ public void TestCommentParenWithAttribute() Assert.AreEqual(1, compilation.Functions[0].Attributes.Count); }, - new CppParserOptions() { ParseAttributes = true }); + new CppParserOptions() { ParseTokenAttributes = true }); } [Test] @@ -420,7 +420,7 @@ [[infinite loop]] Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); }, - new CppParserOptions() { ParseAttributes = true }); + new CppParserOptions() { ParseTokenAttributes = true }); } [Test] @@ -433,7 +433,7 @@ public void TestAttributeInvalidBracketEnd() Assert.False(compilation.HasErrors); Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); }, - new CppParserOptions() { ParseAttributes = true }); + new CppParserOptions() { ParseTokenAttributes = true }); } [Test] @@ -446,7 +446,7 @@ public void TestAttributeInvalidParenEnd() Assert.False(compilation.HasErrors); Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); }, - new CppParserOptions() { ParseAttributes = true }); + new CppParserOptions() { ParseTokenAttributes = true }); } [Test] @@ -474,7 +474,7 @@ struct Test{ // C++17 says if the compile encounters a attribute it doesn't understand // it will ignore that attribute and not throw an error, we still want to // parse this. - new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } ); } @@ -499,7 +499,7 @@ struct Test{ // C++17 says if the compile encounters a attribute it doesn't understand // it will ignore that attribute and not throw an error, we still want to // parse this. - new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } ); } @@ -515,7 +515,7 @@ public void TestCppNoParseOptionsAttributes() Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseAttributes = false } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = false } ); } @@ -542,7 +542,7 @@ class EXPORT_API TestClass Assert.True(cppClass.IsPublicExport()); }, - new CppParserOptions() { ParseAttributes = true } + new CppParserOptions() { ParseTokenAttributes = true } ); ParseAssert(text, compilation => @@ -552,8 +552,9 @@ class EXPORT_API TestClass var cppClass = compilation.Classes[0]; Assert.AreEqual(1, cppClass.Attributes.Count); Assert.True(cppClass.IsPublicExport()); - }, new CppParserOptions() { ParseAttributes = true }.ConfigureForWindowsMsvc() + }, new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() ); - } + } + } } diff --git a/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs b/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs new file mode 100644 index 0000000..6b56d2e --- /dev/null +++ b/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs @@ -0,0 +1,560 @@ +// 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 NUnit.Framework; + +namespace CppAst.Tests +{ + public class TestTokenAttributes : InlineTestBase + { + [Test] + public void TestSimple() + { + ParseAssert(@" +__declspec(dllimport) int i; +__declspec(dllexport) void func0(); +extern ""C"" void __stdcall func1(int a, int b, int c); +void *fun2(int align) __attribute__((alloc_align(1))); +", + compilation => + { + + // Print diagnostic messages + foreach (var message in compilation.Diagnostics.Messages) + Console.WriteLine(message); + + // Print All enums + foreach (var cppEnum in compilation.Enums) + Console.WriteLine(cppEnum); + + // Print All functions + foreach (var cppFunction in compilation.Functions) + Console.WriteLine(cppFunction); + + // Print All classes, structs + foreach (var cppClass in compilation.Classes) + Console.WriteLine(cppClass); + + // Print All typedefs + foreach (var cppTypedef in compilation.Typedefs) + Console.WriteLine(cppTypedef); + + + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Fields.Count); + Assert.NotNull(compilation.Fields[0].TokenAttributes); + Assert.AreEqual("dllimport", compilation.Fields[0].TokenAttributes[0].Arguments); + + Assert.AreEqual(3, compilation.Functions.Count); + Assert.NotNull(compilation.Functions[0].TokenAttributes); + Assert.AreEqual(1, compilation.Functions[0].TokenAttributes.Count); + Assert.AreEqual("dllexport", compilation.Functions[0].TokenAttributes[0].Arguments); + + Assert.AreEqual(CppCallingConvention.X86StdCall, compilation.Functions[1].CallingConvention); + + Assert.NotNull(compilation.Functions[2].TokenAttributes); + Assert.AreEqual(1, compilation.Functions[2].TokenAttributes.Count); + Assert.AreEqual("alloc_align(1)", compilation.Functions[2].TokenAttributes[0].ToString()); + + }, + new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() // Force using X86 to get __stdcall calling convention + ); + } + + [Test] + public void TestStructAttributes() + { + ParseAssert(@" +struct __declspec(uuid(""1841e5c8-16b0-489b-bcc8-44cfb0d5deae"")) __declspec(novtable) Test{ + int a; + int b; +};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Classes.Count); + + Assert.NotNull(compilation.Classes[0].TokenAttributes); + + Assert.AreEqual(2, compilation.Classes[0].TokenAttributes.Count); + + { + var attr = compilation.Classes[0].TokenAttributes[0]; + Assert.AreEqual("uuid", attr.Name); + Assert.AreEqual("\"1841e5c8-16b0-489b-bcc8-44cfb0d5deae\"", attr.Arguments); + } + + { + var attr = compilation.Classes[0].TokenAttributes[1]; + Assert.AreEqual("novtable", attr.Name); + Assert.Null(attr.Arguments); + } + }, + new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc()); + } + + [Test] + public void TestCpp11VarAlignas() + { + ParseAssert(@" +alignas(128) char cacheline[128];", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Fields.Count); + Assert.AreEqual(1, compilation.Fields[0].TokenAttributes.Count); + { + var attr = compilation.Fields[0].TokenAttributes[0]; + Assert.AreEqual("alignas", attr.Name); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp11StructAlignas() + { + ParseAssert(@" +struct alignas(8) S {};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Classes.Count); + Assert.AreEqual(1, compilation.Classes[0].TokenAttributes.Count); + { + var attr = compilation.Classes[0].TokenAttributes[0]; + Assert.AreEqual("alignas", attr.Name); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp11StructAlignasWithAttribute() + { + ParseAssert(@" +struct [[deprecated(""abc"")]] alignas(8) S {};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Classes.Count); + Assert.AreEqual(2, compilation.Classes[0].TokenAttributes.Count); + { + var attr = compilation.Classes[0].TokenAttributes[0]; + Assert.AreEqual("deprecated", attr.Name); + } + + { + var attr = compilation.Classes[0].TokenAttributes[1]; + Assert.AreEqual("alignas", attr.Name); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp11StructAttributes() + { + ParseAssert(@" +struct [[deprecated]] Test{ + int a; + int b; +}; + +struct [[deprecated(""old"")]] TestMessage{ + int a; + int b; +};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(2, compilation.Classes.Count); + Assert.AreEqual(1, compilation.Classes[0].TokenAttributes.Count); + { + var attr = compilation.Classes[0].TokenAttributes[0]; + Assert.AreEqual("deprecated", attr.Name); + } + + Assert.AreEqual(1, compilation.Classes[1].TokenAttributes.Count); + { + var attr = compilation.Classes[1].TokenAttributes[0]; + Assert.AreEqual("deprecated", attr.Name); + Assert.AreEqual("\"old\"", attr.Arguments); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp11VariablesAttributes() + { + ParseAssert(@" +struct Test{ + [[deprecated]] int a; + int b; +}; + +[[deprecated]] int x;", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Classes.Count); + Assert.AreEqual(2, compilation.Classes[0].Fields.Count); + Assert.AreEqual(1, compilation.Classes[0].Fields[0].TokenAttributes.Count); + { + var attr = compilation.Classes[0].Fields[0].TokenAttributes[0]; + Assert.AreEqual("deprecated", attr.Name); + } + + Assert.AreEqual(1, compilation.Fields.Count); + Assert.AreEqual(1, compilation.Fields[0].TokenAttributes.Count); + { + var attr = compilation.Fields[0].TokenAttributes[0]; + Assert.AreEqual("deprecated", attr.Name); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp11FunctionsAttributes() + { + ParseAssert(@" +[[noreturn]] void x() {};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Functions.Count); + Assert.AreEqual(1, compilation.Functions[0].TokenAttributes.Count); + { + var attr = compilation.Functions[0].TokenAttributes[0]; + Assert.AreEqual("noreturn", attr.Name); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp11NamespaceAttributes() + { + ParseAssert(@" +namespace [[deprecated]] cppast {};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Namespaces.Count); + Assert.AreEqual(1, compilation.Namespaces[0].TokenAttributes.Count); + { + var attr = compilation.Namespaces[0].TokenAttributes[0]; + Assert.AreEqual("deprecated", attr.Name); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp11EnumAttributes() + { + ParseAssert(@" +enum [[deprecated]] E { };", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Enums.Count); + Assert.AreEqual(1, compilation.Enums[0].TokenAttributes.Count); + { + var attr = compilation.Enums[0].TokenAttributes[0]; + Assert.AreEqual("deprecated", attr.Name); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp11TemplateStructAttributes() + { + ParseAssert(@" +template struct X {}; +template<> struct [[deprecated]] X {};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(2, compilation.Classes.Count); + Assert.AreEqual(0, compilation.Classes[0].TokenAttributes.Count); + Assert.AreEqual(1, compilation.Classes[1].TokenAttributes.Count); + { + var attr = compilation.Classes[1].TokenAttributes[0]; + Assert.AreEqual("deprecated", attr.Name); + } + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp17StructUnknownAttributes() + { + ParseAssert(@" +struct [[cppast]] Test{ + int a; + int b; +}; + +struct [[cppast(""old"")]] TestMessage{ + int a; + int b; +};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(2, compilation.Classes.Count); + Assert.AreEqual(1, compilation.Classes[0].TokenAttributes.Count); + { + var attr = compilation.Classes[0].TokenAttributes[0]; + Assert.AreEqual("cppast", attr.Name); + } + + Assert.AreEqual(1, compilation.Classes[1].TokenAttributes.Count); + { + var attr = compilation.Classes[1].TokenAttributes[0]; + Assert.AreEqual("cppast", attr.Name); + Assert.AreEqual("\"old\"", attr.Arguments); + } + }, + // C++17 says if the compile encounters a attribute it doesn't understand + // it will ignore that attribute and not throw an error, we still want to + // parse this. + new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCommentParen() + { + ParseAssert(@" +// [infinite loop) +int function1(int a, int b); +", compilation => + { + Assert.False(compilation.HasErrors); + + var expectedText = @"[infinite loop)"; + + Assert.AreEqual(1, compilation.Functions.Count); + var resultText = compilation.Functions[0].Comment?.ToString(); + + expectedText = expectedText.Replace("\r\n", "\n"); + resultText = resultText?.Replace("\r\n", "\n"); + Assert.AreEqual(expectedText, resultText); + + Assert.AreEqual(0, compilation.Functions[0].TokenAttributes.Count); + }, + new CppParserOptions() { ParseTokenAttributes = true }); + } + + [Test] + public void TestCommentParenWithAttribute() + { + ParseAssert(@" +// [infinite loop) +[[noreturn]] int function1(int a, int b); +", compilation => + { + Assert.False(compilation.HasErrors); + + var expectedText = @"[infinite loop)"; + + Assert.AreEqual(1, compilation.Functions.Count); + var resultText = compilation.Functions[0].Comment?.ToString(); + + expectedText = expectedText.Replace("\r\n", "\n"); + resultText = resultText?.Replace("\r\n", "\n"); + Assert.AreEqual(expectedText, resultText); + + Assert.AreEqual(1, compilation.Functions[0].TokenAttributes.Count); + }, + new CppParserOptions() { ParseTokenAttributes = true }); + } + + [Test] + public void TestCommentWithAttributeCharacters() + { + ParseAssert(@" +// (infinite loop) +// [[infinite loop]] +// bug(infinite loop) +int function1(int a, int b);", compilation => + { + Assert.False(compilation.HasErrors); + + var expectedText = @"(infinite loop) +[[infinite loop]] +bug(infinite loop)"; + + Assert.AreEqual(1, compilation.Functions.Count); + var resultText = compilation.Functions[0].Comment?.ToString(); + + expectedText = expectedText.Replace("\r\n", "\n"); + resultText = resultText?.Replace("\r\n", "\n"); + Assert.AreEqual(expectedText, resultText); + + Assert.AreEqual(0, compilation.Functions[0].TokenAttributes.Count); + }, + new CppParserOptions() { ParseTokenAttributes = true }); + } + + [Test] + public void TestAttributeInvalidBracketEnd() + { + ParseAssert(@" +// noreturn]] +int function1(int a, int b);", compilation => + { + Assert.False(compilation.HasErrors); + Assert.AreEqual(0, compilation.Functions[0].TokenAttributes.Count); + }, + new CppParserOptions() { ParseTokenAttributes = true }); + } + + [Test] + public void TestAttributeInvalidParenEnd() + { + ParseAssert(@" +// noreturn) +int function1(int a, int b);", compilation => + { + Assert.False(compilation.HasErrors); + Assert.AreEqual(0, compilation.Functions[0].TokenAttributes.Count); + }, + new CppParserOptions() { ParseTokenAttributes = true }); + } + + [Test] + public void TestCpp17VarTemplateAttribute() + { + ParseAssert(@" +template +struct TestT { +}; + +struct Test{ + [[cppast]] TestT channels; +};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(3, compilation.Classes.Count); + Assert.AreEqual(1, compilation.Classes[1].Fields.Count); + Assert.AreEqual(1, compilation.Classes[1].Fields[0].TokenAttributes.Count); + { + var attr = compilation.Classes[1].Fields[0].TokenAttributes[0]; + Assert.AreEqual("cppast", attr.Name); + } + }, + // C++17 says if the compile encounters a attribute it doesn't understand + // it will ignore that attribute and not throw an error, we still want to + // parse this. + new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCpp17FunctionTemplateAttribute() + { + ParseAssert(@" +struct Test{ + template [[cppast]] W GetFoo(); +};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Classes.Count); + Assert.AreEqual(1, compilation.Classes[0].Functions.Count); + Assert.AreEqual(1, compilation.Classes[0].Functions[0].TokenAttributes.Count); + { + var attr = compilation.Classes[0].Functions[0].TokenAttributes[0]; + Assert.AreEqual("cppast", attr.Name); + } + }, + // C++17 says if the compile encounters a attribute it doesn't understand + // it will ignore that attribute and not throw an error, we still want to + // parse this. + new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } + ); + } + + [Test] + public void TestCppNoParseOptionsAttributes() + { + ParseAssert(@" +[[noreturn]] void x() {};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Functions.Count); + Assert.AreEqual(0, compilation.Functions[0].TokenAttributes.Count); + }, + // we are using a C++14 attribute because it can be used everywhere + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = false } + ); + } + + [Test] + public void TestClassPublicExportAttribute() + { + var text = @" +#ifdef WIN32 +#define EXPORT_API __declspec(dllexport) +#else +#define EXPORT_API __attribute__((visibility(""default""))) +#endif +class EXPORT_API TestClass +{ +}; +"; + ParseAssert(text, + compilation => + { + Assert.False(compilation.HasErrors); + + var cppClass = compilation.Classes[0]; + Assert.AreEqual(1, cppClass.TokenAttributes.Count); + Assert.True(cppClass.IsPublicExport()); + + }, + new CppParserOptions() { ParseTokenAttributes = true } + ); + ParseAssert(text, + compilation => + { + Assert.False(compilation.HasErrors); + + var cppClass = compilation.Classes[0]; + Assert.AreEqual(1, cppClass.TokenAttributes.Count); + Assert.True(cppClass.IsPublicExport()); + }, new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() + ); + } + + } +} diff --git a/src/CppAst.Tests/TestComments.cs b/src/CppAst.Tests/TestComments.cs index bee6e16..9cf2a26 100644 --- a/src/CppAst.Tests/TestComments.cs +++ b/src/CppAst.Tests/TestComments.cs @@ -136,7 +136,7 @@ public void TestParen() resultText = resultText?.Replace("\r\n", "\n"); Assert.AreEqual(expectedText, resultText); }, - new CppParserOptions() { ParseAttributes = true }); + new CppParserOptions() { ParseTokenAttributes = true }); } } } \ No newline at end of file diff --git a/src/CppAst.Tests/TestFunctions.cs b/src/CppAst.Tests/TestFunctions.cs index c4b01af..1369918 100644 --- a/src/CppAst.Tests/TestFunctions.cs +++ b/src/CppAst.Tests/TestFunctions.cs @@ -191,7 +191,7 @@ public void TestFunctionExport() Assert.True(cppFunction.IsPublicExport()); } }, - new CppParserOptions() { ParseAttributes = true } + new CppParserOptions() { ParseTokenAttributes = true } ); ParseAssert(text, @@ -211,7 +211,7 @@ public void TestFunctionExport() Assert.AreEqual(0, cppFunction.Attributes.Count); Assert.True(cppFunction.IsPublicExport()); } - }, new CppParserOptions() { ParseAttributes = true }.ConfigureForWindowsMsvc() + }, new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() ); } diff --git a/src/CppAst.Tests/TestTypes.cs b/src/CppAst.Tests/TestTypes.cs index 368589c..69b2fe7 100644 --- a/src/CppAst.Tests/TestTypes.cs +++ b/src/CppAst.Tests/TestTypes.cs @@ -149,7 +149,7 @@ class Derived : public ::BaseTemplate<::Derived> [Test] - public void TestTemplatePartialSpecializationInheritance() + public void TestTemplatePartialSpecialization() { ParseAssert(@" template diff --git a/src/CppAst/CppAttribute.cs b/src/CppAst/CppAttribute.cs index 29a2092..fac64c7 100644 --- a/src/CppAst/CppAttribute.cs +++ b/src/CppAst/CppAttribute.cs @@ -12,9 +12,10 @@ namespace CppAst /// public class CppAttribute : CppElement { - public CppAttribute(string name) + public CppAttribute(string name, AttributeKind kind) { Name = name ?? throw new ArgumentNullException(nameof(name)); + Kind = kind; } /// @@ -37,15 +38,15 @@ public CppAttribute(string name) /// public bool IsVariadic { get; set; } + public AttributeKind Kind { get; } + /// public override string ToString() { var builder = new StringBuilder(); - if (Scope != null) - { - builder.Append(Scope).Append("::"); - } + + builder.Append("[["); builder.Append(Name); if (Arguments != null) @@ -58,6 +59,16 @@ public override string ToString() builder.Append("..."); } + builder.Append("]]"); + + + if (Scope != null) + { + builder.Append(" { scope:"); + builder.Append(Scope).Append("::"); + builder.Append("}"); + } + return builder.ToString(); } } diff --git a/src/CppAst/CppAttributeKind.cs b/src/CppAst/CppAttributeKind.cs new file mode 100644 index 0000000..af1a556 --- /dev/null +++ b/src/CppAst/CppAttributeKind.cs @@ -0,0 +1,21 @@ +// 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.Text; + +namespace CppAst +{ + /// + /// Attribute kind enum here + /// + public enum AttributeKind + { + CxxSystemAttribute, + CxxCustomAttribute, + AnnotateAttribute, + CommentAttribute, + TokenAttribute, //the attribute is parse from token, and the parser is slow. + } +} \ No newline at end of file diff --git a/src/CppAst/CppClass.cs b/src/CppAst/CppClass.cs index df3e569..3ca9e9b 100644 --- a/src/CppAst/CppClass.cs +++ b/src/CppAst/CppClass.cs @@ -28,7 +28,7 @@ public CppClass(string name) : base(CppTypeKind.StructOrClass) Classes = new CppContainerList(this); Typedefs = new CppContainerList(this); TemplateParameters = new List(); - Attributes = new CppContainerList(this); + Attributes = new List(); } /// @@ -98,7 +98,10 @@ public override string FullName public CppVisibility Visibility { get; set; } /// - public CppContainerList Attributes { get; } + public List Attributes { get; } + + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } /// /// Gets or sets a boolean indicating if this type is a definition. If false the type was only declared but is not defined. diff --git a/src/CppAst/CppEnum.cs b/src/CppAst/CppEnum.cs index 43f6273..c5715de 100644 --- a/src/CppAst/CppEnum.cs +++ b/src/CppAst/CppEnum.cs @@ -11,7 +11,7 @@ namespace CppAst /// /// A C++ standard or scoped enum. /// - public sealed class CppEnum : CppTypeDeclaration, ICppMemberWithVisibility + public sealed class CppEnum : CppTypeDeclaration, ICppMemberWithVisibility, ICppAttributeContainer { /// /// Creates a new instance of this enum. @@ -21,7 +21,8 @@ public CppEnum(string name) : base(CppTypeKind.Enum) { Name = name; Items = new CppContainerList(this); - Attributes = new CppContainerList(this); + Attributes = new List(); + TokenAttributes = new List(); } /// @@ -67,7 +68,9 @@ public override string FullName /// /// Gets the list of attached attributes. /// - public CppContainerList Attributes { get; } + public List Attributes { get; } + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } private bool Equals(CppEnum other) { diff --git a/src/CppAst/CppEnumItem.cs b/src/CppAst/CppEnumItem.cs index 0d820b0..8095ea5 100644 --- a/src/CppAst/CppEnumItem.cs +++ b/src/CppAst/CppEnumItem.cs @@ -3,13 +3,14 @@ // See license.txt file in the project root for full license information. using System; +using System.Collections.Generic; namespace CppAst { /// /// An enum item of . /// - public sealed class CppEnumItem : CppDeclaration, ICppMember + public sealed class CppEnumItem : CppDeclaration, ICppMember, ICppAttributeContainer { /// /// Creates a new instance of this enum item. @@ -35,6 +36,11 @@ public CppEnumItem(string name, long value) /// public CppExpression ValueExpression { get; set; } + /// + public List Attributes { get; } = new List(); + + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } = new List(); /// public override string ToString() diff --git a/src/CppAst/CppField.cs b/src/CppAst/CppField.cs index 47670bf..830c541 100644 --- a/src/CppAst/CppField.cs +++ b/src/CppAst/CppField.cs @@ -11,12 +11,14 @@ namespace CppAst /// /// A C++ field (of a struct/class) or global variable. /// - public sealed class CppField : CppDeclaration, ICppMemberWithVisibility + public sealed class CppField : CppDeclaration, ICppMemberWithVisibility , ICppAttributeContainer { public CppField(CppType type, string name) { Type = type ?? throw new ArgumentNullException(nameof(type)); Name = name; + Attributes = new List(); + TokenAttributes = new List(); } /// @@ -30,7 +32,10 @@ public CppField(CppType type, string name) /// /// Gets attached attributes. Might be null. /// - public List Attributes { get; set; } + public List Attributes { get; } + + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } /// /// Gets the type of this field/variable. diff --git a/src/CppAst/CppFunction.cs b/src/CppAst/CppFunction.cs index a1ca7a3..ca94a83 100644 --- a/src/CppAst/CppFunction.cs +++ b/src/CppAst/CppFunction.cs @@ -11,7 +11,7 @@ namespace CppAst /// /// A C++ function/method declaration. /// - public sealed class CppFunction : CppDeclaration, ICppMemberWithVisibility, ICppTemplateOwner, ICppContainer + public sealed class CppFunction : CppDeclaration, ICppMemberWithVisibility, ICppTemplateOwner, ICppContainer, ICppAttributeContainer { /// /// Creates a new instance of a function/method with the specified name. @@ -22,7 +22,8 @@ public CppFunction(string name) Name = name; Parameters = new CppContainerList(this); TemplateParameters = new List(); - Attributes = new CppContainerList(this); + Attributes = new List(); + TokenAttributes = new List(); } /// @@ -36,7 +37,10 @@ public CppFunction(string name) /// /// Gets the attached attributes. /// - public CppContainerList Attributes { get; } + public List Attributes { get; } + + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } /// /// Gets or sets the storage qualifier. diff --git a/src/CppAst/CppGlobalDeclarationContainer.cs b/src/CppAst/CppGlobalDeclarationContainer.cs index 29fdd10..6f5060d 100644 --- a/src/CppAst/CppGlobalDeclarationContainer.cs +++ b/src/CppAst/CppGlobalDeclarationContainer.cs @@ -30,7 +30,8 @@ public CppGlobalDeclarationContainer() Classes = new CppContainerList(this); Typedefs = new CppContainerList(this); Namespaces = new CppContainerList(this); - Attributes = new CppContainerList(this); + Attributes = new List(); + TokenAttributes = new List(); } /// @@ -60,7 +61,10 @@ public CppGlobalDeclarationContainer() public CppContainerList Namespaces { get; } /// - public CppContainerList Attributes { get; } + public List Attributes { get; } + + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } /// public virtual IEnumerable Children() diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index 20b9b0e..aaf5007 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -33,7 +33,9 @@ public CppModelBuilder() public bool ParseSystemIncludes { get; set; } - public bool ParseAttributeEnabled { get; set; } + public bool ParseTokenAttributeEnabled { get; set; } + + public bool ParseCommentAttributeEnabled { get; set; } public CppCompilation RootCompilation { get; } @@ -207,7 +209,6 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi { var argh = arg.AsType; var argType = GetCppType(argh.Declaration, argh, cursor, data); - var depend = arg.Dependence; cppClass.TemplateSpecializedArguments.Add(new CppTemplateArgument(tempParams[(int)i], argType, argh.TypeClass != CX_TypeClass.CX_TypeClass_TemplateTypeParm)); } break; @@ -266,7 +267,7 @@ private CppNamespace VisitNamespace(CXCursor cursor, void* data) { // Create the container if not already created var ns = GetOrCreateDeclarationContainer(cursor, data, out var context); - ns.Attributes.AddRange(ParseAttributes(cursor)); + ParseAttributes(cursor, ns, false); cursor.VisitChildren(VisitMember, new CXClientData((IntPtr)data)); return ns; } @@ -276,7 +277,7 @@ private CppClass VisitClassDecl(CXCursor cursor, void* data) var cppStruct = GetOrCreateDeclarationContainer(cursor, data, out var context); if (cursor.IsDefinition && !cppStruct.IsDefinition) { - cppStruct.Attributes.AddRange(ParseAttributes(cursor)); + ParseAttributes(cursor, cppStruct, false); cppStruct.IsDefinition = true; cppStruct.SizeOf = (int)cursor.Type.SizeOf; cppStruct.AlignOf = (int)cursor.Type.AlignOf; @@ -304,6 +305,7 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d var containerContext = GetOrCreateDeclarationContainer(parent, data); var cppEnum = (CppEnum)containerContext.Container; var enumItem = new CppEnumItem(GetCursorSpelling(cursor), cursor.EnumConstantDeclValue); + ParseAttributes(cursor, enumItem, true); VisitInitValue(cursor, data, out var enumItemExpression, out var enumValue); enumItem.ValueExpression = enumItemExpression; @@ -348,8 +350,8 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d StorageQualifier = GetStorageQualifier(cursor), IsAnonymous = true, Offset = offset, - Attributes = ParseAttributes(cursor) }; + ParseAttributes(cursor, cppField, true); containerContext.DeclarationContainer.Fields.Add(cppField); element = cppField; } @@ -420,7 +422,7 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d var cppClass = containerContext as CppClass; if (cppClass != null) { - CppAttribute attribute = new CppAttribute("visibility"); + CppAttribute attribute = new CppAttribute("visibility", AttributeKind.CxxSystemAttribute); AssignSourceSpan(cursor, attribute); attribute.Arguments = string.Format("\"{0}\"", cursor.DisplayName.ToString()); cppClass.Attributes.Add(attribute); @@ -433,7 +435,7 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d var cppClass = containerContext as CppClass; if (cppClass != null) { - CppAttribute attribute = new CppAttribute("dllimport"); + CppAttribute attribute = new CppAttribute("dllimport", AttributeKind.CxxSystemAttribute); AssignSourceSpan(cursor, attribute); cppClass.Attributes.Add(attribute); } @@ -445,7 +447,7 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d var cppClass = containerContext as CppClass; if (cppClass != null) { - CppAttribute attribute = new CppAttribute("dllexport"); + CppAttribute attribute = new CppAttribute("dllexport", AttributeKind.CxxSystemAttribute); AssignSourceSpan(cursor, attribute); cppClass.Attributes.Add(attribute); } @@ -457,6 +459,7 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d case CXCursorKind.CXCursor_Destructor: case CXCursorKind.CXCursor_TemplateTypeParameter: + case CXCursorKind.CXCursor_AnnotateAttr: // Don't emit warning break; default: @@ -472,6 +475,13 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d if (element is ICppDeclaration cppDeclaration) { cppDeclaration.Comment = GetComment(cursor); + + var attrContainer = cppDeclaration as ICppAttributeContainer; + //Only handle commnet attribute when we need + if(attrContainer != null && ParseCommentAttributeEnabled) + { + TryToParseAttributesFromComment(cppDeclaration.Comment, attrContainer); + } } return CXChildVisitResult.CXChildVisit_Continue; @@ -871,7 +881,7 @@ private CppField VisitFieldOrVariable(CppContainerContext containerContext, CXCu Offset = cursor.OffsetOfField / 8, }; containerContext.DeclarationContainer.Fields.Add(cppField); - cppField.Attributes = ParseAttributes(cursor); + ParseAttributes(cursor, cppField, true); if (cursor.Kind == CXCursorKind.CXCursor_VarDecl) { @@ -895,7 +905,7 @@ private void AddAnonymousTypeWithField(CppContainerContext containerContext, CXC Offset = cursor.OffsetOfField / 8, }; containerContext.DeclarationContainer.Fields.Add(cppField); - cppField.Attributes = ParseAttributes(cursor); + ParseAttributes(cursor, cppField, true); } private void VisitInitValue(CXCursor cursor, void* data, out CppExpression expression, out CppValue value) @@ -995,7 +1005,7 @@ private CppExpression VisitExpression(CXCursor cursor, void* data) visitChildren = true; break; case CXCursorKind.CXCursor_UnaryOperator: - var tokens = new Tokenizer(cursor); + var tokens = new CppTokenUtil.Tokenizer(cursor); expr = new CppUnaryExpression(CppExpressionKind.UnaryOperator) { Operator = tokens.Count > 0 ? tokens.GetString(0) : string.Empty @@ -1185,7 +1195,7 @@ private void AppendTokensToExpression(CXCursor cursor, CppExpression expression) { if (expression is CppRawExpression tokensExpr) { - var tokenizer = new Tokenizer(cursor); + var tokenizer = new CppTokenUtil.Tokenizer(cursor); for (int i = 0; i < tokenizer.Count; i++) { tokensExpr.Tokens.Add(tokenizer[i]); @@ -1202,7 +1212,7 @@ private CppEnum VisitEnumDecl(CXCursor cursor, void* data) var integralType = cursor.EnumDecl_IntegerType; cppEnum.IntegerType = GetCppType(integralType.Declaration, integralType, cursor, data); cppEnum.IsScoped = cursor.EnumDecl_IsScoped; - cppEnum.Attributes.AddRange(ParseAttributes(cursor)); + ParseAttributes(cursor, cppEnum, false); context.IsChildrenVisited = true; cursor.VisitChildren(VisitMember, new CXClientData((IntPtr)data)); } @@ -1323,7 +1333,7 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da var returnType = GetCppType(cursor.ResultType.Declaration, cursor.ResultType, cursor, data); cppFunction.ReturnType = returnType; - cppFunction.Attributes.AddRange(ParseFunctionAttributes(cursor, cppFunction.Name)); + ParseAttributes(cursor, cppFunction, true); cppFunction.CallingConvention = GetCallingConvention(cursor.Type); int i = 0; @@ -1430,294 +1440,140 @@ private static CppCallingConvention GetCallingConvention(CXType type) } } - private void SkipTemplates(TokenIterator iter) + private List ParseSystemAndAnnotateAttributeInCursor(CXCursor cursor) { - if (iter.CanPeek) + List collectAttributes = new List(); + cursor.VisitChildren((argCursor, parentCursor, clientData) => { - if (iter.Skip("template")) + var sourceSpan = new CppSourceSpan(GetSourceLocation(argCursor.SourceRange.Start), GetSourceLocation(argCursor.SourceRange.End)); + var meta = argCursor.Spelling.CString; + switch (argCursor.Kind) { - iter.Next(); // skip the first > - int parentCount = 1; - while (parentCount > 0 && iter.CanPeek) - { - var text = iter.PeekText(); - if (text == ">") + case CXCursorKind.CXCursor_AnnotateAttr: { - parentCount--; - } - iter.Next(); - } - } - } - } - - private List ParseAttributes(CXCursor cursor) - { - if (!ParseAttributeEnabled) return null; - - var tokenizer = new AttributeTokenizer(cursor); - var tokenIt = new TokenIterator(tokenizer); - - // if this is a template then we need to skip that ? - if (tokenIt.CanPeek && tokenIt.PeekText() == "template") - SkipTemplates(tokenIt); - - List attributes = null; - while (tokenIt.CanPeek) - { - if (ParseAttributes(tokenIt, ref attributes)) - { - continue; - } - - // If we have a keyword, try to skip it and process following elements - // for example attribute put right after a struct __declspec(uuid("...")) Test {...} - if (tokenIt.Peek().Kind == CppTokenKind.Keyword) - { - tokenIt.Next(); - continue; - } - break; - } - return attributes; - } - - private List ParseFunctionAttributes(CXCursor cursor, string functionName) - { - if (!ParseAttributeEnabled) return null; - - // TODO: This function is not 100% correct when parsing tokens up to the function name - // we assume to find the function name immediately followed by a `(` - // but some return type parameter could actually interfere with that - // Ideally we would need to parse more properly return type and skip parenthesis for example - var tokenizer = new AttributeTokenizer(cursor); - var tokenIt = new TokenIterator(tokenizer); - - // if this is a template then we need to skip that ? - if (tokenIt.CanPeek && tokenIt.PeekText() == "template") - SkipTemplates(tokenIt); + var attribute = new CppAttribute("annotate", AttributeKind.AnnotateAttribute) + { + Span = sourceSpan, + Scope = "", + Arguments = meta, + IsVariadic = false, + }; - // Parse leading attributes - List attributes = null; - while (tokenIt.CanPeek) - { - if (ParseAttributes(tokenIt, ref attributes)) - { - continue; - } - break; - } + collectAttributes.Add(attribute); + } + break; + case CXCursorKind.CXCursor_AlignedAttr: + { + var attrKindSpelling = argCursor.AttrKindSpelling.ToLower(); + var attribute = new CppAttribute("alignas", AttributeKind.CxxSystemAttribute) + { + Span = sourceSpan, + Scope = "", + Arguments = "", + IsVariadic = false, + }; - if (!tokenIt.CanPeek) - { - return attributes; - } + collectAttributes.Add(attribute); + } + break; + case CXCursorKind.CXCursor_FirstAttr: + { + var attrKind = argCursor.AttrKind; + var attrKindSpelling = argCursor.AttrKindSpelling.ToLower(); + + var attribute = new CppAttribute(attrKindSpelling, AttributeKind.CxxSystemAttribute) + { + Span = sourceSpan, + Scope = "", + Arguments = "", + IsVariadic = false, + }; - // Find function name (We only support simple function name declaration) - if (!tokenIt.Find(functionName, "(")) - { - return attributes; - } + collectAttributes.Add(attribute); + } + break; + case CXCursorKind.CXCursor_DLLImport: + case CXCursorKind.CXCursor_DLLExport: + { + var attrKind = argCursor.AttrKind; + var attrKindSpelling = argCursor.AttrKindSpelling.ToLower(); - Debug.Assert(tokenIt.PeekText() == functionName); - tokenIt.Next(); - Debug.Assert(tokenIt.PeekText() == "("); - tokenIt.Next(); + var attribute = new CppAttribute(attrKindSpelling, AttributeKind.CxxSystemAttribute) + { + Span = sourceSpan, + Scope = "", + Arguments = "", + IsVariadic = false, + }; - int parentCount = 1; - while (parentCount > 0 && tokenIt.CanPeek) - { - var text = tokenIt.PeekText(); - if (text == "(") - { - parentCount++; - } - else if (text == ")") - { - parentCount--; + collectAttributes.Add(attribute); + } + break; + + // Don't generate a warning for unsupported cursor + default: + break; } - tokenIt.Next(); - } - - if (parentCount != 0) - { - return attributes; - } - while (tokenIt.CanPeek) - { - if (ParseAttributes(tokenIt, ref attributes)) - { - continue; - } - // Skip the token if we can parse it. - tokenIt.Next(); - } + return CXChildVisitResult.CXChildVisit_Continue; - return attributes; + }, new CXClientData((IntPtr)0)); + return collectAttributes; } - private bool ParseAttributes(TokenIterator tokenIt, ref List attributes) + private void TryToParseAttributesFromComment(CppComment comment, ICppAttributeContainer attrContainer) { - // Parse C++ attributes - // [[]] - if (tokenIt.Skip("[", "[")) - { - while (ParseAttribute(tokenIt, out var attribute)) - { - if (attributes == null) - { - attributes = new List(); - } - attributes.Add(attribute); - - tokenIt.Skip(","); - } - - return tokenIt.Skip("]", "]"); - } + if (comment == null) return; - // Parse GCC or clang attributes - // __attribute__(()) - if (tokenIt.Skip("__attribute__", "(", "(")) + if(comment is CppCommentText ctxt) { - while (ParseAttribute(tokenIt, out var attribute)) + var txt = ctxt.Text.Trim(); + if(txt.StartsWith("[[") && txt.EndsWith("]]")) { - if (attributes == null) + attrContainer.Attributes.Add(new CppAttribute("comment", AttributeKind.CommentAttribute) { - attributes = new List(); - } - attributes.Add(attribute); - - tokenIt.Skip(","); - } - - return tokenIt.Skip(")", ")"); - } - - // Parse MSVC attributes - // __declspec() - if (tokenIt.Skip("__declspec", "(")) - { - while (ParseAttribute(tokenIt, out var attribute)) - { - if (attributes == null) - { - attributes = new List(); - } - attributes.Add(attribute); - - tokenIt.Skip(","); - } - return tokenIt.Skip(")"); - } - - // Parse C++11 alignas attribute - // alignas(expression) - if (tokenIt.PeekText() == "alignas") - { - while (ParseAttribute(tokenIt, out var attribute)) - { - if (attributes == null) - { - attributes = new List(); - } - attributes.Add(attribute); - - break; + Arguments = txt, + Scope = "", + IsVariadic = false, + }) ; } - - return tokenIt.Skip(")"); ; } - return false; - } - - private bool ParseDirectAttribute(CXCursor cursor, ref List attributes) - { - var tokenizer = new AttributeTokenizer(cursor); - var tokenIt = new TokenIterator(tokenizer); - if (ParseAttribute(tokenIt, out var attribute)) + if (comment.Children != null) { - if (attributes == null) + foreach (var child in comment.Children) { - attributes = new List(); + TryToParseAttributesFromComment(child, attrContainer); } - attributes.Add(attribute); - return true; } - - return false; } - private bool ParseAttribute(TokenIterator tokenIt, out CppAttribute attribute) + private void ParseAttributes(CXCursor cursor, ICppAttributeContainer attrContainer, bool needOnlineSeek = false) { - // (identifier ::)? identifier ('(' tokens ')' )? (...)? - attribute = null; - var token = tokenIt.Peek(); - if (token == null || !token.Kind.IsIdentifierOrKeyword()) - { - return false; - } - tokenIt.Next(out token); + //Try to handle annotate in cursor first + //Low spend handle here, just open always + attrContainer.Attributes.AddRange(ParseSystemAndAnnotateAttributeInCursor(cursor)); - var firstToken = token; + //Low performance tokens handle here + if (!ParseTokenAttributeEnabled) return; - // try (identifier ::)? - string scope = null; - if (tokenIt.Skip("::")) + var tokenAttributes = new List(); + //Parse attributes online + if (needOnlineSeek) { - scope = token.Text; - - token = tokenIt.Peek(); - if (token == null || !token.Kind.IsIdentifierOrKeyword()) + bool hasOnlineAttribute = CppTokenUtil.TryToSeekOnlineAttributes(cursor, out var onLineRange); + if (hasOnlineAttribute) { - return false; + CppTokenUtil.ParseAttributesInRange(cursor.TranslationUnit, onLineRange, ref tokenAttributes); } - tokenIt.Next(out token); } - // identifier - string tokenIdentifier = token.Text; - - string arguments = null; - - // ('(' tokens ')' )? - if (tokenIt.Skip("(")) - { - var builder = new StringBuilder(); - var previousTokenKind = CppTokenKind.Punctuation; - while (tokenIt.PeekText() != ")" && tokenIt.Next(out token)) - { - if (token.Kind.IsIdentifierOrKeyword() && previousTokenKind.IsIdentifierOrKeyword()) - { - builder.Append(" "); - } - previousTokenKind = token.Kind; - builder.Append(token.Text); - } - - if (!tokenIt.Skip(")")) - { - return false; - } - arguments = builder.ToString(); - } - - var isVariadic = tokenIt.Skip("..."); - - var previousToken = tokenIt.PreviousToken(); - - attribute = new CppAttribute(tokenIdentifier) - { - Span = new CppSourceSpan(firstToken.Span.Start, previousToken.Span.End), - Scope = scope, - Arguments = arguments, - IsVariadic = isVariadic, - }; - return true; + //Parse attributes contains in cursor + CppTokenUtil.ParseDirectAttribute(cursor, ref tokenAttributes); + attrContainer.TokenAttributes.AddRange(tokenAttributes); } + private CppType VisitTypeAliasDecl(CXCursor cursor, void* data) { var fulltypeDefName = clang.getCursorUSR(cursor).CString; @@ -1810,11 +1666,11 @@ private CppType VisitElaboratedDecl(CXCursor cursor, CXType type, CXCursor paren return GetCppType(type.CanonicalType.Declaration, type.CanonicalType, parent, data); } - private static string GetCursorAsText(CXCursor cursor) => new Tokenizer(cursor).TokensToString(); + private static string GetCursorAsText(CXCursor cursor) => new CppTokenUtil.Tokenizer(cursor).TokensToString(); private string GetCursorAsTextBetweenOffset(CXCursor cursor, int startOffset, int endOffset) { - var tokenizer = new Tokenizer(cursor); + var tokenizer = new CppTokenUtil.Tokenizer(cursor); var builder = new StringBuilder(); var previousTokenKind = CppTokenKind.Punctuation; for (int i = 0; i < tokenizer.Count; i++) @@ -2081,575 +1937,6 @@ private List ParseTemplateSpecializedArguments(CXCursor cursor, CXType return templateCppTypes; } - /// - /// Internal class to iterate on tokens - /// - private class TokenIterator - { - private readonly Tokenizer _tokens; - private int _index; - - public TokenIterator(Tokenizer tokens) - { - _tokens = tokens; - } - - public bool Skip(string expectedText) - { - if (_index < _tokens.Count) - { - if (_tokens.GetString(_index) == expectedText) - { - _index++; - return true; - } - } - - return false; - } - - public CppToken PreviousToken() - { - if (_index > 0) - { - return _tokens[_index - 1]; - } - - return null; - } - - public bool Skip(params string[] expectedTokens) - { - var startIndex = _index; - foreach (var expectedToken in expectedTokens) - { - if (startIndex < _tokens.Count) - { - if (_tokens.GetString(startIndex) == expectedToken) - { - startIndex++; - continue; - } - } - return false; - } - _index = startIndex; - return true; - } - - public bool Find(params string[] expectedTokens) - { - var startIndex = _index; - restart: - while (startIndex < _tokens.Count) - { - var firstIndex = startIndex; - foreach (var expectedToken in expectedTokens) - { - if (startIndex < _tokens.Count) - { - if (_tokens.GetString(startIndex) == expectedToken) - { - startIndex++; - continue; - } - } - startIndex = firstIndex + 1; - goto restart; - } - _index = firstIndex; - return true; - } - return false; - } - - public bool Next(out CppToken token) - { - token = null; - if (_index < _tokens.Count) - { - token = _tokens[_index]; - _index++; - return true; - } - return false; - } - - public bool CanPeek => _index < _tokens.Count; - - public bool Next() - { - if (_index < _tokens.Count) - { - _index++; - return true; - } - return false; - } - - public CppToken Peek() - { - if (_index < _tokens.Count) - { - return _tokens[_index]; - } - return null; - } - - public string PeekText() - { - if (_index < _tokens.Count) - { - return _tokens.GetString(_index); - } - return null; - } - } - - /// - /// Internal class to tokenize - /// - [DebuggerTypeProxy(typeof(TokenizerDebuggerType))] - private class Tokenizer - { - private readonly CXToken[] _tokens; - private CppToken[] _cppTokens; - protected readonly CXTranslationUnit _tu; - - public Tokenizer(CXCursor cursor) - { - _tu = cursor.TranslationUnit; - var range = GetRange(cursor); - _tokens = _tu.Tokenize(range).ToArray(); - } - - public Tokenizer(CXTranslationUnit tu, CXSourceRange range) - { - _tu = tu; - _tokens = _tu.Tokenize(range).ToArray(); - } - - public virtual CXSourceRange GetRange(CXCursor cursor) - { - return cursor.Extent; - } - - public int Count => _tokens?.Length ?? 0; - - public CppToken this[int i] - { - get - { - // Only create a tokenizer if necessary - if (_cppTokens == null) - { - _cppTokens = new CppToken[_tokens.Length]; - } - - ref var cppToken = ref _cppTokens[i]; - if (cppToken != null) - { - return cppToken; - } - - var token = _tokens[i]; - CppTokenKind cppTokenKind = 0; - switch (token.Kind) - { - case CXTokenKind.CXToken_Punctuation: - cppTokenKind = CppTokenKind.Punctuation; - break; - case CXTokenKind.CXToken_Keyword: - cppTokenKind = CppTokenKind.Keyword; - break; - case CXTokenKind.CXToken_Identifier: - cppTokenKind = CppTokenKind.Identifier; - break; - case CXTokenKind.CXToken_Literal: - cppTokenKind = CppTokenKind.Literal; - break; - case CXTokenKind.CXToken_Comment: - cppTokenKind = CppTokenKind.Comment; - break; - default: - break; - } - - var tokenStr = token.GetSpelling(_tu).CString; - var tokenLocation = token.GetLocation(_tu); - - var tokenRange = token.GetExtent(_tu); - cppToken = new CppToken(cppTokenKind, tokenStr) - { - Span = new CppSourceSpan(GetSourceLocation(tokenRange.Start), GetSourceLocation(tokenRange.End)) - }; - return cppToken; - } - } - - public string GetString(int i) - { - var token = _tokens[i]; - return token.GetSpelling(_tu).CString; - } - - public string TokensToString() - { - if (_tokens == null) - { - return null; - } - - var tokens = new List(_tokens.Length); - - for (int i = 0; i < _tokens.Length; i++) - { - tokens.Add(this[i]); - } - - return CppToken.TokensToString(tokens); - } - - public string GetStringForLength(int length) - { - StringBuilder result = new StringBuilder(length); - for (var cur = 0; cur < Count; ++cur) - { - result.Append(GetString(cur)); - if (result.Length >= length) - return result.ToString(); - } - return result.ToString(); - } - } - - private class AttributeTokenizer : Tokenizer - { - public AttributeTokenizer(CXCursor cursor) : base(cursor) - { - } - - public AttributeTokenizer(CXTranslationUnit tu, CXSourceRange range) : base(tu, range) - { - - } - - private uint IncOffset(int inc, uint offset) - { - if (inc >= 0) - offset += (uint)inc; - else - offset -= (uint)-inc; - return offset; - } - - private Tuple GetExtent(CXTranslationUnit tu, CXCursor cur) - { - var cursorExtend = cur.Extent; - var begin = cursorExtend.Start; - var end = cursorExtend.End; - - bool CursorIsFunction(CXCursorKind inKind) - { - return inKind == CXCursorKind.CXCursor_FunctionDecl || inKind == CXCursorKind.CXCursor_CXXMethod - || inKind == CXCursorKind.CXCursor_Constructor || inKind == CXCursorKind.CXCursor_Destructor - || inKind == CXCursorKind.CXCursor_ConversionFunction; - } - - bool CursorIsVar(CXCursorKind inKind) - { - return inKind == CXCursorKind.CXCursor_VarDecl || inKind == CXCursorKind.CXCursor_FieldDecl; - } - - bool IsInRange(CXSourceLocation loc, CXSourceRange range) - { - var xbegin = range.Start; - var xend = range.End; - - loc.GetSpellingLocation(out var fileLocation, out var lineLocation, out var u1, out var u2); - xbegin.GetSpellingLocation(out var fileBegin, out var lineBegin, out u1, out u2); - xend.GetSpellingLocation(out var fileEnd, out var lineEnd, out u1, out u2); - - return lineLocation >= lineBegin && lineLocation < lineEnd && (fileLocation.Equals(fileBegin)); - } - - bool HasInlineTypeDefinition(CXCursor varDecl) - { - var typeDecl = varDecl.Type.Declaration; - if (typeDecl.IsNull) - return false; - - var typeLocation = typeDecl.Location; - var varRange = typeDecl.Extent; - return IsInRange(typeLocation, varRange); - } - - CXSourceLocation GetNextLocation(CXSourceLocation loc, int inc = 1) - { - CXSourceLocation value; - loc.GetSpellingLocation(out var f, out var u, out var z, out var originalOffset); - var offset = IncOffset(inc, z); - var shouldUseLine = (z != 0 && (offset != 0 || offset != uint.MaxValue)); - if (shouldUseLine) - { - value = tu.GetLocation(f, u, offset); - } - else - { - offset = IncOffset(inc, originalOffset); - value = tu.GetLocationForOffset(f, offset); - } - - return value; - } - - CXSourceLocation GetPrevLocation(CXSourceLocation loc, int tokenLength) - { - var inc = 1; - while (true) - { - var locBefore = GetNextLocation(loc, -inc); - CXToken* tokens; - uint size; - clang.tokenize(tu, clang.getRange(locBefore, loc), &tokens, &size); - if (size == 0) - return CXSourceLocation.Null; - - var tokenLocation = tokens[0].GetLocation(tu); - if (locBefore.Equals(tokenLocation)) - { - return GetNextLocation(loc, -1 * (inc + tokenLength - 1)); - } - else - ++inc; - } - } - - bool TokenIsBefore(CXSourceLocation loc, string tokenString) - { - var length = tokenString.Length; - var locBefore = GetPrevLocation(loc, length); - - var tokenizer = new Tokenizer(tu, clang.getRange(locBefore, loc)); - if (tokenizer.Count == 0) return false; - - return tokenizer.GetStringForLength(length) == tokenString; - } - - bool TokenAtIs(CXSourceLocation loc, string tokenString) - { - var length = tokenString.Length; - - var locAfter = GetNextLocation(loc, length); - var tokenizer = new Tokenizer(tu, clang.getRange(locAfter, loc)); - - return tokenizer.GetStringForLength(length) == tokenString; - } - - bool ConsumeIfTokenAtIs(ref CXSourceLocation loc, string tokenString) - { - var length = tokenString.Length; - - var locAfter = GetNextLocation(loc, length); - var tokenizer = new Tokenizer(tu, clang.getRange(locAfter, loc)); - if (tokenizer.Count == 0) - return false; - - if (tokenizer.GetStringForLength(length) == tokenString) - { - loc = locAfter; - return true; - } - else - return false; - } - - bool ConsumeIfTokenBeforeIs(ref CXSourceLocation loc, string tokenString) - { - var length = tokenString.Length; - - var locBefore = GetPrevLocation(loc, length); - - var tokenizer = new Tokenizer(tu, clang.getRange(locBefore, loc)); - if (tokenizer.GetStringForLength(length) == tokenString) - { - loc = locBefore; - return true; - } - else - return false; - } - - bool CheckIfValidOrReset(ref CXSourceLocation checkedLocation, CXSourceLocation resetLocation) - { - bool isValid = true; - if (checkedLocation.Equals(CXSourceLocation.Null)) - { - checkedLocation = resetLocation; - isValid = false; - } - - return isValid; - } - - var kind = cur.Kind; - if (CursorIsFunction(kind) || CursorIsFunction(cur.TemplateCursorKind) - || kind == CXCursorKind.CXCursor_VarDecl || kind == CXCursorKind.CXCursor_FieldDecl || kind == CXCursorKind.CXCursor_ParmDecl - || kind == CXCursorKind.CXCursor_NonTypeTemplateParameter) - { - while (TokenIsBefore(begin, "]]") || TokenIsBefore(begin, ")")) - { - var saveBegin = begin; - if (ConsumeIfTokenBeforeIs(ref begin, "]]")) - { - bool isValid = true; - while (!ConsumeIfTokenBeforeIs(ref begin, "[[") && isValid) - { - begin = GetPrevLocation(begin, 1); - isValid = CheckIfValidOrReset(ref begin, saveBegin); - } - - if (!isValid) - { - break; - } - } - else if (ConsumeIfTokenBeforeIs(ref begin, ")")) - { - var parenCount = 1; - for (var lastBegin = begin; parenCount != 0; lastBegin = begin) - { - if (TokenIsBefore(begin, "(")) - --parenCount; - else if (TokenIsBefore(begin, ")")) - ++parenCount; - - begin = GetPrevLocation(begin, 1); - - // We have reached the end of the source of trying to deal - // with the potential of alignas, so we just break, which - // will cause ConsumeIfTokenBeforeIs(ref begin, "alignas") to be false - // and thus fall back to saveBegin which is the correct behavior - if (!CheckIfValidOrReset(ref begin, saveBegin)) - break; - } - - if (!ConsumeIfTokenBeforeIs(ref begin, "alignas")) - { - begin = saveBegin; - break; - } - } - } - - if (CursorIsVar(kind) || CursorIsVar(cur.TemplateCursorKind)) - { - if (HasInlineTypeDefinition(cur)) - { - var typeCursor = clang.getTypeDeclaration(clang.getCursorType(cur)); - var typeExtent = clang.getCursorExtent(typeCursor); - - var typeBegin = clang.getRangeStart(typeExtent); - var typeEnd = clang.getRangeEnd(typeExtent); - - return new Tuple(clang.getRange(begin, typeBegin), clang.getRange(typeEnd, end)); - } - } - else if (kind == CXCursorKind.CXCursor_TemplateTypeParameter && TokenAtIs(end, "(")) - { - var next = GetNextLocation(end, 1); - var prev = end; - for (var parenCount = 1; parenCount != 0; next = GetNextLocation(next, 1)) - { - if (TokenAtIs(next, "(")) - ++parenCount; - else if (TokenAtIs(next, ")")) - --parenCount; - prev = next; - } - end = next; - } - else if (kind == CXCursorKind.CXCursor_TemplateTemplateParameter && TokenAtIs(end, "<")) - { - var next = GetNextLocation(end, 1); - for (var angleCount = 1; angleCount != 0; next = GetNextLocation(next, 1)) - { - if (TokenAtIs(next, ">")) - --angleCount; - else if (TokenAtIs(next, ">>")) - angleCount -= 2; - else if (TokenAtIs(next, "<")) - ++angleCount; - } - - while (!TokenAtIs(next, ">") && !TokenAtIs(next, ",")) - next = GetNextLocation(next, 1); - - end = GetPrevLocation(next, 1); - } - else if ((kind == CXCursorKind.CXCursor_TemplateTypeParameter || kind == CXCursorKind.CXCursor_NonTypeTemplateParameter - || kind == CXCursorKind.CXCursor_TemplateTemplateParameter)) - { - ConsumeIfTokenAtIs(ref end, "..."); - } - else if (kind == CXCursorKind.CXCursor_EnumConstantDecl && !TokenAtIs(end, ",")) - { - var parent = clang.getCursorLexicalParent(cur); - end = clang.getRangeEnd(clang.getCursorExtent(parent)); - } - } - - return new Tuple(clang.getRange(begin, end), clang.getNullRange()); - } - - public override CXSourceRange GetRange(CXCursor cursor) - { - /* This process is complicated when parsing attributes that use - C++11 syntax, essentially even if libClang understands them - it doesn't always return them back as parse of the token range. - - This is kind of frustrating when you want to be able to do something - with custom or even compiler attributes in your parsing. Thus we have - to do things a little manually in order to make this work. - - This code supports stepping back when its valid to parse attributes, it - doesn't currently support all cases but it supports most valid cases. - */ - var range = GetExtent(_tu, cursor); - - var beg = range.Item1.Start; - var end = range.Item1.End; - if (!range.Item2.Equals(CXSourceRange.Null)) - end = range.Item2.End; - - return clang.getRange(beg, end); - } - } - - private class TokenizerDebuggerType - { - private readonly Tokenizer _tokenizer; - - public TokenizerDebuggerType(Tokenizer tokenizer) - { - _tokenizer = tokenizer; - } - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public object[] Items - { - get - { - var array = new object[_tokenizer.Count]; - for (int i = 0; i < _tokenizer.Count; i++) - { - array[i] = _tokenizer[i]; - } - return array; - } - } - } - private class CppContainerContext { public CppContainerContext(ICppContainer container) diff --git a/src/CppAst/CppNamespace.cs b/src/CppAst/CppNamespace.cs index 4a42b17..88349e8 100644 --- a/src/CppAst/CppNamespace.cs +++ b/src/CppAst/CppNamespace.cs @@ -25,7 +25,8 @@ public CppNamespace(string name) Classes = new CppContainerList(this); Typedefs = new CppContainerList(this); Namespaces = new CppContainerList(this); - Attributes = new CppContainerList(this); + Attributes = new List(); + TokenAttributes = new List(); } /// @@ -57,7 +58,10 @@ public CppNamespace(string name) public CppContainerList Namespaces { get; } /// - public CppContainerList Attributes { get; } + public List Attributes { get; } + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + public List TokenAttributes { get; } + protected bool Equals(CppNamespace other) { diff --git a/src/CppAst/CppParser.cs b/src/CppAst/CppParser.cs index 6f631d3..68fb40d 100644 --- a/src/CppAst/CppParser.cs +++ b/src/CppAst/CppParser.cs @@ -128,7 +128,8 @@ private static unsafe CppCompilation ParseInternal(List cppFile { AutoSquashTypedef = options.AutoSquashTypedef, ParseSystemIncludes = options.ParseSystemIncludes, - ParseAttributeEnabled = options.ParseAttributes, + ParseTokenAttributeEnabled = options.ParseTokenAttributes, + ParseCommentAttributeEnabled = options.ParseCommentAttribute, }; var compilation = builder.RootCompilation; diff --git a/src/CppAst/CppParserOptions.cs b/src/CppAst/CppParserOptions.cs index e9a6abc..e580dd5 100644 --- a/src/CppAst/CppParserOptions.cs +++ b/src/CppAst/CppParserOptions.cs @@ -20,7 +20,13 @@ public CppParserOptions() ParseAsCpp = true; SystemIncludeFolders = new List(); IncludeFolders = new List(); - Defines = new List(); + + //Add a default macro here for CppAst.Net + Defines = new List() { + "CPPAST_GENERATOR", //Help us for identify the CppAst.Net handler + @"__cppast_impl(...)=__attribute__((annotate(#__VA_ARGS__)))", //Help us for use annotate attribute convenience + @"__cppast(...)=__cppast_impl(__VA_ARGS__)", //Add a macro wrapper here, so the argument with macro can be handle right for compiler. + }; AdditionalArguments = new List() { "-Wno-pragma-once-outside-header" @@ -29,7 +35,8 @@ public CppParserOptions() ParseMacros = false; ParseComments = true; ParseSystemIncludes = true; - ParseAttributes = false; + ParseTokenAttributes = false; + ParseCommentAttribute = false; // Default triple targets TargetCpu = IntPtr.Size == 8 ? CppTargetCpu.X86_64 : CppTargetCpu.X86; @@ -85,9 +92,14 @@ public CppParserOptions() public bool ParseSystemIncludes { get; set; } /// - /// Gets or sets a boolean indicating whether to parse Attributes. Default is false + /// Gets or sets a boolean indicating whether to parse meta attributes. Default is false + /// + public bool ParseTokenAttributes { get; set; } + + /// + /// Gets or sets a boolean indicating whether to parse comment attributes. Default is false /// - public bool ParseAttributes { get; set; } + public bool ParseCommentAttribute { get; set; } /// /// Sets to true and return this instance. diff --git a/src/CppAst/CppTokenUtil.cs b/src/CppAst/CppTokenUtil.cs new file mode 100644 index 0000000..06fc580 --- /dev/null +++ b/src/CppAst/CppTokenUtil.cs @@ -0,0 +1,981 @@ +// 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.Diagnostics; +using System.Linq.Expressions; +using System.Text; +using ClangSharp; +using ClangSharp.Interop; + +namespace CppAst +{ + static internal unsafe class CppTokenUtil + { + public static bool ParseDirectAttribute(CXCursor cursor, ref List attributes) + { + var tokenizer = new AttributeTokenizer(cursor); + var tokenIt = new TokenIterator(tokenizer); + if (ParseAttribute(tokenIt, out var attribute)) + { + if (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + return true; + } + + return false; + } + + public static void ParseAttributesInRange(CXTranslationUnit tu, CXSourceRange range, ref List collectAttributes) + { + var tokenizer = new AttributeTokenizer(tu, range); + var tokenIt = new TokenIterator(tokenizer); + + var tokenIt2 = new TokenIterator(tokenizer); + StringBuilder sb = new StringBuilder(); + while (tokenIt.CanPeek) + { + sb.Append(tokenIt.PeekText()); + tokenIt.Next(); + } + + // if this is a template then we need to skip that ? + if (tokenIt.CanPeek && tokenIt.PeekText() == "template") + SkipTemplates(tokenIt); + + while (tokenIt.CanPeek) + { + if (ParseAttributes(tokenIt, ref collectAttributes)) + { + continue; + } + + // If we have a keyword, try to skip it and process following elements + // for example attribute put right after a struct __declspec(uuid("...")) Test {...} + if (tokenIt.Peek().Kind == CppTokenKind.Keyword) + { + tokenIt.Next(); + continue; + } + break; + } + } + + public static bool TryToSeekOnlineAttributes(CXCursor cursor, out CXSourceRange range) + { + + + int SkipWhiteSpace(ReadOnlySpan cnt, int cntOffset) + { + while (cntOffset > 0) + { + char ch = (char)cnt[cntOffset]; + if (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t') + { + cntOffset--; + } + else + { + break; + } + } + + return cntOffset; + }; + + int ToLineStart(ReadOnlySpan cnt, int cntOffset) + { + for (int i = cntOffset; i >= 0; i--) + { + char ch = (char)cnt[i]; + if (ch == '\n') + { + return i + 1; + } + } + return 0; + }; + + bool IsAttributeEnd(ReadOnlySpan cnt, int cntOffset) + { + if (cntOffset < 1) return false; + + char ch0 = (char)cnt[cntOffset]; + char ch1 = (char)cnt[cntOffset - 1]; + + return ch0 == ch1 && ch0 == ']'; + }; + + bool IsAttributeStart(ReadOnlySpan cnt, int cntOffset) + { + if (cntOffset < 1) return false; + + char ch0 = (char)cnt[cntOffset]; + char ch1 = (char)cnt[cntOffset - 1]; + + return ch0 == ch1 && ch0 == '['; + }; + + bool SeekAttributeStartSingleChar(ReadOnlySpan cnt, int cntOffset, out int outSeekOffset) + { + outSeekOffset = cntOffset; + while (cntOffset > 0) + { + char ch = (char)cnt[cntOffset]; + if (ch == '[') + { + outSeekOffset = cntOffset; + return true; + } + cntOffset--; + } + return false; + }; + + int SkipAttributeStartOrEnd(ReadOnlySpan cnt, int cntOffset) + { + cntOffset -= 2; + return cntOffset; + }; + + string QueryLineContent(ReadOnlySpan cnt, int startOffset, int endOffset) + { + StringBuilder sb = new StringBuilder(); + for (int i = startOffset; i <= endOffset; i++) + { + sb.Append((char)cnt[i]); + } + return sb.ToString(); + }; + + CXSourceLocation location = cursor.Extent.Start; + location.GetFileLocation(out var file, out var line, out var column, out var offset); + var contents = cursor.TranslationUnit.GetFileContents(file, out var fileSize); + + AttributeLexerParseStatus status = AttributeLexerParseStatus.SeekAttributeEnd; + int offsetStart = (int)offset - 1; //Try to ignore start char here + int lastSeekOffset = offsetStart; + int curOffset = offsetStart; + while (curOffset > 0) + { + curOffset = SkipWhiteSpace(contents, curOffset); + + switch (status) + { + case AttributeLexerParseStatus.SeekAttributeEnd: + { + if (!IsAttributeEnd(contents, curOffset)) + { + status = AttributeLexerParseStatus.Error; + } + else + { + curOffset = SkipAttributeStartOrEnd(contents, curOffset); + status = AttributeLexerParseStatus.SeekAttributeStart; + } + } + break; + case AttributeLexerParseStatus.SeekAttributeStart: + { + if (!SeekAttributeStartSingleChar(contents, curOffset, out var queryOffset)) + { + status = AttributeLexerParseStatus.Error; + } + else + { + if (IsAttributeStart(contents, queryOffset)) + { + curOffset = SkipAttributeStartOrEnd(contents, queryOffset); + lastSeekOffset = curOffset + 1; + status = AttributeLexerParseStatus.SeekAttributeEnd; + } + else + { + status = AttributeLexerParseStatus.Error; + } + } + } + break; + } + + if (status == AttributeLexerParseStatus.Error) + { + break; + } + } + if (lastSeekOffset == offsetStart) + { + range = new CXSourceRange(); + return false; + } + else + { + var startLoc = cursor.TranslationUnit.GetLocationForOffset(file, (uint)lastSeekOffset); + var endLoc = cursor.TranslationUnit.GetLocationForOffset(file, (uint)offsetStart); + range = clang.getRange(startLoc, endLoc); + return true; + } + } + + #region "Nested Types" + + /// + /// Internal class to iterate on tokens + /// + private class TokenIterator + { + private readonly Tokenizer _tokens; + private int _index; + + public TokenIterator(Tokenizer tokens) + { + _tokens = tokens; + } + + public bool Skip(string expectedText) + { + if (_index < _tokens.Count) + { + if (_tokens.GetString(_index) == expectedText) + { + _index++; + return true; + } + } + + return false; + } + + public CppToken PreviousToken() + { + if (_index > 0) + { + return _tokens[_index - 1]; + } + + return null; + } + + public bool Skip(params string[] expectedTokens) + { + var startIndex = _index; + foreach (var expectedToken in expectedTokens) + { + if (startIndex < _tokens.Count) + { + if (_tokens.GetString(startIndex) == expectedToken) + { + startIndex++; + continue; + } + } + return false; + } + _index = startIndex; + return true; + } + + public bool Find(params string[] expectedTokens) + { + var startIndex = _index; + restart: + while (startIndex < _tokens.Count) + { + var firstIndex = startIndex; + foreach (var expectedToken in expectedTokens) + { + if (startIndex < _tokens.Count) + { + if (_tokens.GetString(startIndex) == expectedToken) + { + startIndex++; + continue; + } + } + startIndex = firstIndex + 1; + goto restart; + } + _index = firstIndex; + return true; + } + return false; + } + + public bool Next(out CppToken token) + { + token = null; + if (_index < _tokens.Count) + { + token = _tokens[_index]; + _index++; + return true; + } + return false; + } + + public bool CanPeek => _index < _tokens.Count; + + public bool Next() + { + if (_index < _tokens.Count) + { + _index++; + return true; + } + return false; + } + + public CppToken Peek() + { + if (_index < _tokens.Count) + { + return _tokens[_index]; + } + return null; + } + + public string PeekText() + { + if (_index < _tokens.Count) + { + return _tokens.GetString(_index); + } + return null; + } + } + + /// + /// Internal class to tokenize + /// + [DebuggerTypeProxy(typeof(TokenizerDebuggerType))] + internal class Tokenizer + { + private readonly CXToken[] _tokens; + private CppToken[] _cppTokens; + protected readonly CXTranslationUnit _tu; + + public Tokenizer(CXCursor cursor) + { + _tu = cursor.TranslationUnit; + var range = GetRange(cursor); + _tokens = _tu.Tokenize(range).ToArray(); + } + + public Tokenizer(CXTranslationUnit tu, CXSourceRange range) + { + _tu = tu; + _tokens = _tu.Tokenize(range).ToArray(); + } + + public virtual CXSourceRange GetRange(CXCursor cursor) + { + return cursor.Extent; + } + + public int Count => _tokens?.Length ?? 0; + + public CppToken this[int i] + { + get + { + // Only create a tokenizer if necessary + if (_cppTokens == null) + { + _cppTokens = new CppToken[_tokens.Length]; + } + + ref var cppToken = ref _cppTokens[i]; + if (cppToken != null) + { + return cppToken; + } + + var token = _tokens[i]; + CppTokenKind cppTokenKind = 0; + switch (token.Kind) + { + case CXTokenKind.CXToken_Punctuation: + cppTokenKind = CppTokenKind.Punctuation; + break; + case CXTokenKind.CXToken_Keyword: + cppTokenKind = CppTokenKind.Keyword; + break; + case CXTokenKind.CXToken_Identifier: + cppTokenKind = CppTokenKind.Identifier; + break; + case CXTokenKind.CXToken_Literal: + cppTokenKind = CppTokenKind.Literal; + break; + case CXTokenKind.CXToken_Comment: + cppTokenKind = CppTokenKind.Comment; + break; + default: + break; + } + + var tokenStr = token.GetSpelling(_tu).CString; + var tokenLocation = token.GetLocation(_tu); + + var tokenRange = token.GetExtent(_tu); + cppToken = new CppToken(cppTokenKind, tokenStr) + { + Span = new CppSourceSpan(CppModelBuilder.GetSourceLocation(tokenRange.Start), CppModelBuilder.GetSourceLocation(tokenRange.End)) + }; + return cppToken; + } + } + + public string GetString(int i) + { + var token = _tokens[i]; + return token.GetSpelling(_tu).CString; + } + + public string TokensToString() + { + if (_tokens == null) + { + return null; + } + + var tokens = new List(_tokens.Length); + + for (int i = 0; i < _tokens.Length; i++) + { + tokens.Add(this[i]); + } + + return CppToken.TokensToString(tokens); + } + + public string GetStringForLength(int length) + { + StringBuilder result = new StringBuilder(length); + for (var cur = 0; cur < Count; ++cur) + { + result.Append(GetString(cur)); + if (result.Length >= length) + return result.ToString(); + } + return result.ToString(); + } + } + + + private class AttributeTokenizer : Tokenizer + { + public AttributeTokenizer(CXCursor cursor) : base(cursor) + { + } + + public AttributeTokenizer(CXTranslationUnit tu, CXSourceRange range) : base(tu, range) + { + + } + + private uint IncOffset(int inc, uint offset) + { + if (inc >= 0) + offset += (uint)inc; + else + offset -= (uint)-inc; + return offset; + } + + private Tuple GetExtent(CXTranslationUnit tu, CXCursor cur) + { + var cursorExtend = cur.Extent; + var begin = cursorExtend.Start; + var end = cursorExtend.End; + + bool CursorIsFunction(CXCursorKind inKind) + { + return inKind == CXCursorKind.CXCursor_FunctionDecl || inKind == CXCursorKind.CXCursor_CXXMethod + || inKind == CXCursorKind.CXCursor_Constructor || inKind == CXCursorKind.CXCursor_Destructor + || inKind == CXCursorKind.CXCursor_ConversionFunction; + } + + bool CursorIsVar(CXCursorKind inKind) + { + return inKind == CXCursorKind.CXCursor_VarDecl || inKind == CXCursorKind.CXCursor_FieldDecl; + } + + bool IsInRange(CXSourceLocation loc, CXSourceRange range) + { + var xbegin = range.Start; + var xend = range.End; + + loc.GetSpellingLocation(out var fileLocation, out var lineLocation, out var u1, out var u2); + xbegin.GetSpellingLocation(out var fileBegin, out var lineBegin, out u1, out u2); + xend.GetSpellingLocation(out var fileEnd, out var lineEnd, out u1, out u2); + + return lineLocation >= lineBegin && lineLocation < lineEnd && (fileLocation.Equals(fileBegin)); + } + + bool HasInlineTypeDefinition(CXCursor varDecl) + { + var typeDecl = varDecl.Type.Declaration; + if (typeDecl.IsNull) + return false; + + var typeLocation = typeDecl.Location; + var varRange = typeDecl.Extent; + return IsInRange(typeLocation, varRange); + } + + CXSourceLocation GetNextLocation(CXSourceLocation loc, int inc = 1) + { + CXSourceLocation value; + loc.GetSpellingLocation(out var f, out var u, out var z, out var originalOffset); + var offset = IncOffset(inc, z); + var shouldUseLine = (z != 0 && (offset != 0 || offset != uint.MaxValue)); + if (shouldUseLine) + { + value = tu.GetLocation(f, u, offset); + } + else + { + offset = IncOffset(inc, originalOffset); + value = tu.GetLocationForOffset(f, offset); + } + + return value; + } + + CXSourceLocation GetPrevLocation(CXSourceLocation loc, int tokenLength) + { + var inc = 1; + while (true) + { + var locBefore = GetNextLocation(loc, -inc); + CXToken* tokens; + uint size; + clang.tokenize(tu, clang.getRange(locBefore, loc), &tokens, &size); + if (size == 0) + return CXSourceLocation.Null; + + var tokenLocation = tokens[0].GetLocation(tu); + if (locBefore.Equals(tokenLocation)) + { + return GetNextLocation(loc, -1 * (inc + tokenLength - 1)); + } + else + ++inc; + } + } + + bool TokenIsBefore(CXSourceLocation loc, string tokenString) + { + var length = tokenString.Length; + var locBefore = GetPrevLocation(loc, length); + + var tokenizer = new Tokenizer(tu, clang.getRange(locBefore, loc)); + if (tokenizer.Count == 0) return false; + + return tokenizer.GetStringForLength(length) == tokenString; + } + + bool TokenAtIs(CXSourceLocation loc, string tokenString) + { + var length = tokenString.Length; + + var locAfter = GetNextLocation(loc, length); + var tokenizer = new Tokenizer(tu, clang.getRange(locAfter, loc)); + + return tokenizer.GetStringForLength(length) == tokenString; + } + + bool ConsumeIfTokenAtIs(ref CXSourceLocation loc, string tokenString) + { + var length = tokenString.Length; + + var locAfter = GetNextLocation(loc, length); + var tokenizer = new Tokenizer(tu, clang.getRange(locAfter, loc)); + if (tokenizer.Count == 0) + return false; + + if (tokenizer.GetStringForLength(length) == tokenString) + { + loc = locAfter; + return true; + } + else + return false; + } + + bool ConsumeIfTokenBeforeIs(ref CXSourceLocation loc, string tokenString) + { + var length = tokenString.Length; + + var locBefore = GetPrevLocation(loc, length); + + var tokenizer = new Tokenizer(tu, clang.getRange(locBefore, loc)); + if (tokenizer.GetStringForLength(length) == tokenString) + { + loc = locBefore; + return true; + } + else + return false; + } + + bool CheckIfValidOrReset(ref CXSourceLocation checkedLocation, CXSourceLocation resetLocation) + { + bool isValid = true; + if (checkedLocation.Equals(CXSourceLocation.Null)) + { + checkedLocation = resetLocation; + isValid = false; + } + + return isValid; + } + + var kind = cur.Kind; + if (CursorIsFunction(kind) || CursorIsFunction(cur.TemplateCursorKind) + || kind == CXCursorKind.CXCursor_VarDecl || kind == CXCursorKind.CXCursor_FieldDecl || kind == CXCursorKind.CXCursor_ParmDecl + || kind == CXCursorKind.CXCursor_NonTypeTemplateParameter) + { + while (TokenIsBefore(begin, "]]") || TokenIsBefore(begin, ")")) + { + var saveBegin = begin; + if (ConsumeIfTokenBeforeIs(ref begin, "]]")) + { + bool isValid = true; + while (!ConsumeIfTokenBeforeIs(ref begin, "[[") && isValid) + { + begin = GetPrevLocation(begin, 1); + isValid = CheckIfValidOrReset(ref begin, saveBegin); + } + + if (!isValid) + { + break; + } + } + else if (ConsumeIfTokenBeforeIs(ref begin, ")")) + { + var parenCount = 1; + for (var lastBegin = begin; parenCount != 0; lastBegin = begin) + { + if (TokenIsBefore(begin, "(")) + --parenCount; + else if (TokenIsBefore(begin, ")")) + ++parenCount; + + begin = GetPrevLocation(begin, 1); + + // We have reached the end of the source of trying to deal + // with the potential of alignas, so we just break, which + // will cause ConsumeIfTokenBeforeIs(ref begin, "alignas") to be false + // and thus fall back to saveBegin which is the correct behavior + if (!CheckIfValidOrReset(ref begin, saveBegin)) + break; + } + + if (!ConsumeIfTokenBeforeIs(ref begin, "alignas")) + { + begin = saveBegin; + break; + } + } + } + + if (CursorIsVar(kind) || CursorIsVar(cur.TemplateCursorKind)) + { + if (HasInlineTypeDefinition(cur)) + { + var typeCursor = clang.getTypeDeclaration(clang.getCursorType(cur)); + var typeExtent = clang.getCursorExtent(typeCursor); + + var typeBegin = clang.getRangeStart(typeExtent); + var typeEnd = clang.getRangeEnd(typeExtent); + + return new Tuple(clang.getRange(begin, typeBegin), clang.getRange(typeEnd, end)); + } + } + else if (kind == CXCursorKind.CXCursor_TemplateTypeParameter && TokenAtIs(end, "(")) + { + var next = GetNextLocation(end, 1); + var prev = end; + for (var parenCount = 1; parenCount != 0; next = GetNextLocation(next, 1)) + { + if (TokenAtIs(next, "(")) + ++parenCount; + else if (TokenAtIs(next, ")")) + --parenCount; + prev = next; + } + end = next; + } + else if (kind == CXCursorKind.CXCursor_TemplateTemplateParameter && TokenAtIs(end, "<")) + { + var next = GetNextLocation(end, 1); + for (var angleCount = 1; angleCount != 0; next = GetNextLocation(next, 1)) + { + if (TokenAtIs(next, ">")) + --angleCount; + else if (TokenAtIs(next, ">>")) + angleCount -= 2; + else if (TokenAtIs(next, "<")) + ++angleCount; + } + + while (!TokenAtIs(next, ">") && !TokenAtIs(next, ",")) + next = GetNextLocation(next, 1); + + end = GetPrevLocation(next, 1); + } + else if ((kind == CXCursorKind.CXCursor_TemplateTypeParameter || kind == CXCursorKind.CXCursor_NonTypeTemplateParameter + || kind == CXCursorKind.CXCursor_TemplateTemplateParameter)) + { + ConsumeIfTokenAtIs(ref end, "..."); + } + else if (kind == CXCursorKind.CXCursor_EnumConstantDecl && !TokenAtIs(end, ",")) + { + var parent = clang.getCursorLexicalParent(cur); + end = clang.getRangeEnd(clang.getCursorExtent(parent)); + } + } + + return new Tuple(clang.getRange(begin, end), clang.getNullRange()); + } + + public override CXSourceRange GetRange(CXCursor cursor) + { + /* This process is complicated when parsing attributes that use + C++11 syntax, essentially even if libClang understands them + it doesn't always return them back as parse of the token range. + + This is kind of frustrating when you want to be able to do something + with custom or even compiler attributes in your parsing. Thus we have + to do things a little manually in order to make this work. + + This code supports stepping back when its valid to parse attributes, it + doesn't currently support all cases but it supports most valid cases. + */ + var range = GetExtent(_tu, cursor); + + var beg = range.Item1.Start; + var end = range.Item1.End; + if (!range.Item2.Equals(CXSourceRange.Null)) + end = range.Item2.End; + + return clang.getRange(beg, end); + } + } + + private class TokenizerDebuggerType + { + private readonly Tokenizer _tokenizer; + + public TokenizerDebuggerType(Tokenizer tokenizer) + { + _tokenizer = tokenizer; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public object[] Items + { + get + { + var array = new object[_tokenizer.Count]; + for (int i = 0; i < _tokenizer.Count; i++) + { + array[i] = _tokenizer[i]; + } + return array; + } + } + } + + #endregion + + + #region "Private Functions" + + private static void SkipTemplates(TokenIterator iter) + { + if (iter.CanPeek) + { + if (iter.Skip("template")) + { + iter.Next(); // skip the first > + int parentCount = 1; + while (parentCount > 0 && iter.CanPeek) + { + var text = iter.PeekText(); + if (text == ">") + { + parentCount--; + } + iter.Next(); + } + } + } + } + + private enum AttributeLexerParseStatus + { + SeekAttributeEnd, + SeekAttributeStart, + Error, + } + + + private static bool ParseAttributes(TokenIterator tokenIt, ref List attributes) + { + // Parse C++ attributes + // [[]] + if (tokenIt.Skip("[", "[")) + { + while (ParseAttribute(tokenIt, out var attribute)) + { + if (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + + tokenIt.Skip(","); + } + + return tokenIt.Skip("]", "]"); + } + + // Parse GCC or clang attributes + // __attribute__(()) + if (tokenIt.Skip("__attribute__", "(", "(")) + { + while (ParseAttribute(tokenIt, out var attribute)) + { + if (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + + tokenIt.Skip(","); + } + + return tokenIt.Skip(")", ")"); + } + + // Parse MSVC attributes + // __declspec() + if (tokenIt.Skip("__declspec", "(")) + { + while (ParseAttribute(tokenIt, out var attribute)) + { + if (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + + tokenIt.Skip(","); + } + return tokenIt.Skip(")"); + } + + // Parse C++11 alignas attribute + // alignas(expression) + if (tokenIt.PeekText() == "alignas") + { + while (ParseAttribute(tokenIt, out var attribute)) + { + if (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + + break; + } + + return tokenIt.Skip(")"); ; + } + + return false; + } + + private static bool ParseAttribute(TokenIterator tokenIt, out CppAttribute attribute) + { + // (identifier ::)? identifier ('(' tokens ')' )? (...)? + attribute = null; + var token = tokenIt.Peek(); + if (token == null || !token.Kind.IsIdentifierOrKeyword()) + { + return false; + } + tokenIt.Next(out token); + + var firstToken = token; + + // try (identifier ::)? + string scope = null; + if (tokenIt.Skip("::")) + { + scope = token.Text; + + token = tokenIt.Peek(); + if (token == null || !token.Kind.IsIdentifierOrKeyword()) + { + return false; + } + tokenIt.Next(out token); + } + + // identifier + string tokenIdentifier = token.Text; + + string arguments = null; + + // ('(' tokens ')' )? + if (tokenIt.Skip("(")) + { + var builder = new StringBuilder(); + var previousTokenKind = CppTokenKind.Punctuation; + while (tokenIt.PeekText() != ")" && tokenIt.Next(out token)) + { + if (token.Kind.IsIdentifierOrKeyword() && previousTokenKind.IsIdentifierOrKeyword()) + { + builder.Append(" "); + } + previousTokenKind = token.Kind; + builder.Append(token.Text); + } + + if (!tokenIt.Skip(")")) + { + return false; + } + arguments = builder.ToString(); + } + + var isVariadic = tokenIt.Skip("..."); + + var previousToken = tokenIt.PreviousToken(); + + attribute = new CppAttribute(tokenIdentifier, AttributeKind.TokenAttribute) + { + Span = new CppSourceSpan(firstToken.Span.Start, previousToken.Span.End), + Scope = scope, + Arguments = arguments, + IsVariadic = isVariadic, + }; + return true; + } + + + #endregion + } + +} + + diff --git a/src/CppAst/ICppAttributeContainer.cs b/src/CppAst/ICppAttributeContainer.cs new file mode 100644 index 0000000..cc5fa7a --- /dev/null +++ b/src/CppAst/ICppAttributeContainer.cs @@ -0,0 +1,22 @@ +// 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; + +namespace CppAst +{ + /// + /// Base interface for all with attribute element. + /// + public interface ICppAttributeContainer + { + /// + /// Gets the attributes from element. + /// + List Attributes { get; } + + [Obsolete("TokenAttributes is deprecated. please use system attributes and annotate attributes")] + List TokenAttributes { get; } + } +} \ No newline at end of file diff --git a/src/CppAst/ICppDeclarationContainer.cs b/src/CppAst/ICppDeclarationContainer.cs index b8d1a5a..25ca93d 100644 --- a/src/CppAst/ICppDeclarationContainer.cs +++ b/src/CppAst/ICppDeclarationContainer.cs @@ -8,7 +8,7 @@ namespace CppAst /// Base interface of a containing fields, functions, enums, classes, typedefs. /// /// - public interface ICppDeclarationContainer : ICppContainer + public interface ICppDeclarationContainer : ICppContainer, ICppAttributeContainer { /// /// Gets the fields/variables. @@ -35,9 +35,7 @@ public interface ICppDeclarationContainer : ICppContainer /// CppContainerList Typedefs { get; } - /// - /// Gets the attributes. - /// - CppContainerList Attributes { get; } + //Just use ICppAttributeContainer here(enum can support attribute, so we just use ICppAttributeContainer here)~~ + //CppContainerList Attributes { get; } } } \ No newline at end of file From 2a155186f5cf82d621c68844fd43c04264bca0d2 Mon Sep 17 00:00:00 2001 From: fangshen Date: Mon, 26 Jun 2023 15:44:59 +0800 Subject: [PATCH 4/4] --other= 1. add a document for new attribute, in location doc/attributes.md && 2. new attribute support by __cppast() && 3. default system attribute support not use token parser --- doc/attributes.md | 154 ++++++++++++ .../AttributesTest/TestSystemAttributes.cs | 234 ++---------------- .../AttributesTest/TestTokenAttributes.cs | 85 +------ src/CppAst.Tests/TestFunctions.cs | 4 +- src/CppAst/CppAttribute.cs | 18 +- src/CppAst/CppAttributeKind.cs | 2 +- src/CppAst/CppClass.cs | 1 + src/CppAst/CppModelBuilder.cs | 63 ++--- src/CppAst/CppParserOptions.cs | 2 +- src/CppAst/CppTokenUtil.cs | 98 +++++++- 10 files changed, 304 insertions(+), 357 deletions(-) create mode 100644 doc/attributes.md diff --git a/doc/attributes.md b/doc/attributes.md new file mode 100644 index 0000000..df0fe59 --- /dev/null +++ b/doc/attributes.md @@ -0,0 +1,154 @@ +## 1. `cppast.net 0.12` Support for `attributes` +The original support of `cppast.net` for various types of `attributes`, including the `meta attribute` of `c++17`, is restricted due to the limitation of `libclang` itself `Api`. We need to rely on token-level parsing to implement related functions. In the implementation of `cppast.net 0.12` and previous versions, we used parsing `token` to implement related functions. Even some `attributes` that `libclang` supports well, such as `dllexport`, `dllimport`, etc., `cppast.net` most of the time also uses token parsing. Although this approach is flexible and we can always try to parse the related `attributes` from the `token` level, it also brings some problems and restrictions, including: +1. `ParseAttributes()` is extremely time-consuming, which led to the addition of the `ParseAttributes` parameter in later versions to control whether to parse `attributes`. However, in some cases, we need to rely on `attributes` to complete the related functions, which is obviously inconvenient. +2. There are defects in the parsing of `meta attribute` - `[[]]`. For `meta attribute` defined above `Function` and `Field`, it is obviously legal at the semantic level, but `cppast.net` does not support this type of `meta attribute` defined above the object very well (there are some exceptions here, like `namespace`, `class`, `enum` these `attribute` declarations, the attribute definition itself cannot be at the top, the compiler will report an error directly for the related usage, it can only be after the related keywords, such as `class [[deprecated]] Abc{};`). +3. Individual parameters of `meta attribute` use macros. Because our original implementation is based on `token` parsing, macros during compilation obviously cannot be correctly handled in this case. + +--- +## 2. A brief introduction to cases where `attribute` is needed + +--- +### 2.1 System-level `attribute` +Taking the code segment in the `cppast.net` test case as an example: +```cpp +#ifdef WIN32 +#define EXPORT_API __declspec(dllexport) +#else +#define EXPORT_API __attribute__((visibility(""default""))) +#endif +``` +For `attributes` like `dllexport` and `visibility` that control interface visibility, we definitely use them more often, not only when `ParseAttributes` is turned on to make it work. We need to provide a high-performance solution for these basic system attributes, and the implementation should not be affected by the switch. + +--- +### 2.2 Injection of Additional Information by Export Tools and Other Tools +  Let's take the following class definition as an example: +```cpp +#if !defined(__cppast) +#define __cppast(...) +#endif + +struct __cppast(msgid = 1) TestPBMessage { + public: + __cppast(id = 1) + float x; + __cppast(id = 2) + double y; + __cppast(id = 3) + uint64_t z; +}; +``` +To better support serialization and deserialization of `TestPBMessage`, and to have a certain degree of fault tolerance, we have added some additional information based on the original struct definition: +1. The msgid of `TestPBMessage`, here it is directly specified as integer `1`. +2. The `id` of x, y, and z, here directly using `1`, `2`, and `3` respectively. +This way, if we use `cppast.net` to create our offline processing tools, we definitely need to conveniently read out the various 'meta attributes' injected by `__cppast()` which do not directly impact the original code compilation in the tool, and use them appropriately. However, the performance of this part in `cppast.net 0.12` and previous versions is rather poor and has limitations. For example, it can't support cases like the one above where the `attribute` is directly defined on the `Field`. + +--- +## 3. New Implementation and Adjustment +  The new implementation is mainly based on the limitations of the current implementation mentioned earlier, and the various application scenarios mentioned in the previous chapter. We have re-categorized the `attribute` into three types: +1. `AttributeKind.CxxSystemAttribute` - It corresponds to various system `attributes` that `libclang` itself can parse very well, such as `visibility` mentioned above, as well as `[[deprecated]]`, `[[noreturn]]`, etc. With the help of `ClangSharp`, we can efficiently parse and handle them, so there is no need to worry about switch issues. +2. `AttributeKind.TokenAttribute` - As the name suggests, this corresponds to the `attribute` in the original version of `cppast.net`. It has been marked as `deprecated`, but the parsing of `token` is always a fallback implementation mechanism. We will keep the relevant `Tokenizer` code and use them cautiously to implement some complex features when `ClangSharp` is unable to implement related functions. +3. `AttributeKind.AnnotateAttribute` - This is used to replace the original `meta attribute` implemented based on `token` parsing, aiming to inject methods for classes and members with high performance and low restrictions as introduced earlier. + +Next, we will briefly introduce the implementation ideas and usage of various types of `attributes`. + +--- +### 3.1 `AttributeKind.CxxSystemAttribute` +  We added a function to handle various `attributes` that `ClangSharp` itself supports: +```cs + private List ParseSystemAndAnnotateAttributeInCursor(CXCursor cursor) + { + List collectAttributes = new List(); + cursor.VisitChildren((argCursor, parentCursor, clientData) => + { + var sourceSpan = new CppSourceSpan(GetSourceLocation(argCursor.SourceRange.Start), GetSourceLocation(argCursor.SourceRange.End)); + var meta = argCursor.Spelling.CString; + switch (argCursor.Kind) + { + case CXCursorKind.CXCursor_VisibilityAttr: + //... + break; + case CXCursorKind.CXCursor_AnnotateAttr: + //... + break; + case CXCursorKind.CXCursor_AlignedAttr: + //... + break; + //... + default: + break; + } + + return CXChildVisitResult.CXChildVisit_Continue; + + }, new CXClientData((IntPtr)0)); + return collectAttributes; + } +``` +With the existing features of `ClangSharp`, such as `visibility attribute`, can be efficiently handled here. Note that here the use of `AnnotateAttr` and `meta attribute` will be introduced. It is also the key to our high-performance `meta attribute` usage. We can directly access the relevant `cursor` on `libclang`'s `AST`, thus avoiding handling related data at the high performance-cost `token` level. + +--- +### 3.2 `AttributeKind.TokenAttribute` +  For the original `attribute` implemented based on `token` parsing, for compatibility with older versions, it has temporarily been moved from the original `Attributes` property to the `TokenAttributes` property. The new `CxxSystemAttribute` and `AnnotateAttribute` are stored in the original `Attributes` property. You can refer to the relevant test cases to understand their specific usage. + +--- +### 3.3 `AttributeKind.AnnotateAttribute` +  We need a mechanism to implement `meta attribute` that bypasses `token` parsing. Here we cleverly use the `annotate` + + attribute to achieve this. From the several new built-in macros, we can see how it works: +```cs + //Add a default macro here for CppAst.Net + Defines = new List() { + "__cppast_run__", //Help us for identify the CppAst.Net handler + @"__cppast_impl(...)=__attribute__((annotate(#__VA_ARGS__)))", //Help us for use annotate attribute convenience + @"__cppast(...)=__cppast_impl(__VA_ARGS__)", //Add a macro wrapper here, so the argument with macro can be handle right for compiler. + }; +``` +> [!note] +> These three system macros will not be parsed into `CppMacro` and added to the final parsing result to avoid polluting the output. + +In the end, we simply convert the variable argument `__VA_ARGS__` to a string and use `__attribute__((annotate(???)))` to inject information. Thus, if we, like the test code, add the following at the right place: +```cpp +#if !defined(__cppast) +#define __cppast(...) +#endif +``` +When the code is parsed by `cppast.net`, the relevant input will be correctly identified and read as an `annotate attribute`. In non-`cppast.net` scenarios, the data injected in `__cppast()` will be correctly ignored to avoid interfering with the actual compilation and execution of the code. In this way, we indirectly achieve the purpose of injecting and reading `meta attribute`. + +For the macro case: +```cpp +#if !defined(__cppast) +#define __cppast(...) +#endif + +#define UUID() 12345 + +__cppast(id=UUID(), desc=""a function with macro"") +void TestFunc() +{ +} +``` +Relevant test code: +```cs +//annotate attribute support on namespace +var func = compilation.Functions[0]; + Assert.AreEqual(1, func.Attributes.Count); + Assert.AreEqual(func.Attributes[0].Kind, AttributeKind.AnnotateAttribute); + Assert.AreEqual(func.Attributes[0].Arguments, "id=12345, desc=\"a function with macro\""); +``` +Because we did a `wrapper` packaging when defining `__cppast`, we find that macros also work well in `meta attribute` state. + +As for the case of `outline attribute`, like `Function`, `Field`, it can support well, and even you can define multiple `attributes` on an object, which is also legal: +```cpp +__cppast(id = 1) +__cppast(name = "x") +__cppast(desc = "???") +float x; +``` + +--- +## 4. Conclusion +  This article mainly introduces the new `attributes` supported by `cppast.net`, which are mainly divided into three categories: +1. CxxSystemAttribute +2. TokenAttribute +3. AnnotateAttribute +We recommend using `CxxSystemAttribute` and `AnnotateAttribute`, which do not require switch control. The existence of `TokenAttribute` is mainly for compatibility with old implementations. The related attributes have been moved into a separate `TokenAttributes` to distinguish from the first two. And `CppParserOptions` corresponding switch is adjusted to `ParseTokenAttributes`. Due to performance and usage limitations, it is not recommended to continue to use it. \ No newline at end of file diff --git a/src/CppAst.Tests/AttributesTest/TestSystemAttributes.cs b/src/CppAst.Tests/AttributesTest/TestSystemAttributes.cs index 6ea9fec..20e645c 100644 --- a/src/CppAst.Tests/AttributesTest/TestSystemAttributes.cs +++ b/src/CppAst.Tests/AttributesTest/TestSystemAttributes.cs @@ -46,21 +46,21 @@ public void TestSimple() Assert.AreEqual(1, compilation.Fields.Count); Assert.NotNull(compilation.Fields[0].Attributes); - Assert.AreEqual("dllimport", compilation.Fields[0].Attributes[0].ToString()); + Assert.AreEqual("dllimport", compilation.Fields[0].Attributes[0].Name); Assert.AreEqual(3, compilation.Functions.Count); Assert.NotNull(compilation.Functions[0].Attributes); Assert.AreEqual(1, compilation.Functions[0].Attributes.Count); - Assert.AreEqual("dllexport", compilation.Functions[0].Attributes[0].ToString()); + Assert.AreEqual("dllexport", compilation.Functions[0].Attributes[0].Name); Assert.AreEqual(CppCallingConvention.X86StdCall, compilation.Functions[1].CallingConvention); Assert.NotNull(compilation.Functions[2].Attributes); Assert.AreEqual(1, compilation.Functions[2].Attributes.Count); - Assert.AreEqual("alloc_align(1)", compilation.Functions[2].Attributes[0].ToString()); + Assert.AreEqual("allocalign", compilation.Functions[2].Attributes[0].Name); }, - new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() // Force using X86 to get __stdcall calling convention + new CppParserOptions() { }.ConfigureForWindowsMsvc() // Force using X86 to get __stdcall calling convention ); } @@ -84,16 +84,14 @@ struct __declspec(uuid(""1841e5c8-16b0-489b-bcc8-44cfb0d5deae"")) __declspec(nov { var attr = compilation.Classes[0].Attributes[0]; Assert.AreEqual("uuid", attr.Name); - Assert.AreEqual("\"1841e5c8-16b0-489b-bcc8-44cfb0d5deae\"", attr.Arguments); } { var attr = compilation.Classes[0].Attributes[1]; - Assert.AreEqual("novtable", attr.Name); - Assert.Null(attr.Arguments); + Assert.AreEqual("msnovtable", attr.Name); } }, - new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc()); + new CppParserOptions() { }.ConfigureForWindowsMsvc()); } [Test] @@ -112,7 +110,7 @@ public void TestCpp11VarAlignas() } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" } } ); } @@ -132,7 +130,7 @@ struct alignas(8) S {};", compilation => } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" } } ); } @@ -157,7 +155,7 @@ struct [[deprecated(""abc"")]] alignas(8) S {};", compilation => } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }} ); } @@ -188,11 +186,10 @@ struct [[deprecated(""old"")]] TestMessage{ { var attr = compilation.Classes[1].Attributes[0]; Assert.AreEqual("deprecated", attr.Name); - Assert.AreEqual("\"old\"", attr.Arguments); } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" } } ); } @@ -225,7 +222,7 @@ struct Test{ } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" } } ); } @@ -241,11 +238,11 @@ public void TestCpp11FunctionsAttributes() Assert.AreEqual(1, compilation.Functions[0].Attributes.Count); { var attr = compilation.Functions[0].Attributes[0]; - Assert.AreEqual("noreturn", attr.Name); + Assert.AreEqual("cxx11noreturn", attr.Name); } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" } } ); } @@ -265,7 +262,7 @@ namespace [[deprecated]] cppast {};", compilation => } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" } } ); } @@ -285,7 +282,7 @@ enum [[deprecated]] E { };", compilation => } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" } } ); } @@ -307,201 +304,10 @@ public void TestCpp11TemplateStructAttributes() } }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" } } ); } - [Test] - public void TestCpp17StructUnknownAttributes() - { - ParseAssert(@" -struct [[cppast]] Test{ - int a; - int b; -}; - -struct [[cppast(""old"")]] TestMessage{ - int a; - int b; -};", compilation => - { - Assert.False(compilation.HasErrors); - - Assert.AreEqual(2, compilation.Classes.Count); - Assert.AreEqual(1, compilation.Classes[0].Attributes.Count); - { - var attr = compilation.Classes[0].Attributes[0]; - Assert.AreEqual("cppast", attr.Name); - } - - Assert.AreEqual(1, compilation.Classes[1].Attributes.Count); - { - var attr = compilation.Classes[1].Attributes[0]; - Assert.AreEqual("cppast", attr.Name); - Assert.AreEqual("\"old\"", attr.Arguments); - } - }, - // C++17 says if the compile encounters a attribute it doesn't understand - // it will ignore that attribute and not throw an error, we still want to - // parse this. - new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } - ); - } - - [Test] - public void TestCommentParen() - { - ParseAssert(@" -// [infinite loop) -int function1(int a, int b); -", compilation => - { - Assert.False(compilation.HasErrors); - - var expectedText = @"[infinite loop)"; - - Assert.AreEqual(1, compilation.Functions.Count); - var resultText = compilation.Functions[0].Comment?.ToString(); - - expectedText = expectedText.Replace("\r\n", "\n"); - resultText = resultText?.Replace("\r\n", "\n"); - Assert.AreEqual(expectedText, resultText); - - Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); - }, - new CppParserOptions() { ParseTokenAttributes = true }); - } - - [Test] - public void TestCommentParenWithAttribute() - { - ParseAssert(@" -// [infinite loop) -[[noreturn]] int function1(int a, int b); -", compilation => - { - Assert.False(compilation.HasErrors); - - var expectedText = @"[infinite loop)"; - - Assert.AreEqual(1, compilation.Functions.Count); - var resultText = compilation.Functions[0].Comment?.ToString(); - - expectedText = expectedText.Replace("\r\n", "\n"); - resultText = resultText?.Replace("\r\n", "\n"); - Assert.AreEqual(expectedText, resultText); - - Assert.AreEqual(1, compilation.Functions[0].Attributes.Count); - }, - new CppParserOptions() { ParseTokenAttributes = true }); - } - - [Test] - public void TestCommentWithAttributeCharacters() - { - ParseAssert(@" -// (infinite loop) -// [[infinite loop]] -// bug(infinite loop) -int function1(int a, int b);", compilation => - { - Assert.False(compilation.HasErrors); - - var expectedText = @"(infinite loop) -[[infinite loop]] -bug(infinite loop)"; - - Assert.AreEqual(1, compilation.Functions.Count); - var resultText = compilation.Functions[0].Comment?.ToString(); - - expectedText = expectedText.Replace("\r\n", "\n"); - resultText = resultText?.Replace("\r\n", "\n"); - Assert.AreEqual(expectedText, resultText); - - Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); - }, - new CppParserOptions() { ParseTokenAttributes = true }); - } - - [Test] - public void TestAttributeInvalidBracketEnd() - { - ParseAssert(@" -// noreturn]] -int function1(int a, int b);", compilation => - { - Assert.False(compilation.HasErrors); - Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); - }, - new CppParserOptions() { ParseTokenAttributes = true }); - } - - [Test] - public void TestAttributeInvalidParenEnd() - { - ParseAssert(@" -// noreturn) -int function1(int a, int b);", compilation => - { - Assert.False(compilation.HasErrors); - Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); - }, - new CppParserOptions() { ParseTokenAttributes = true }); - } - - [Test] - public void TestCpp17VarTemplateAttribute() - { - ParseAssert(@" -template -struct TestT { -}; - -struct Test{ - [[cppast]] TestT channels; -};", compilation => - { - Assert.False(compilation.HasErrors); - - Assert.AreEqual(3, compilation.Classes.Count); - Assert.AreEqual(1, compilation.Classes[1].Fields.Count); - Assert.AreEqual(1, compilation.Classes[1].Fields[0].Attributes.Count); - { - var attr = compilation.Classes[1].Fields[0].Attributes[0]; - Assert.AreEqual("cppast", attr.Name); - } - }, - // C++17 says if the compile encounters a attribute it doesn't understand - // it will ignore that attribute and not throw an error, we still want to - // parse this. - new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } - ); - } - - [Test] - public void TestCpp17FunctionTemplateAttribute() - { - ParseAssert(@" -struct Test{ - template [[cppast]] W GetFoo(); -};", compilation => - { - Assert.False(compilation.HasErrors); - - Assert.AreEqual(1, compilation.Classes.Count); - Assert.AreEqual(1, compilation.Classes[0].Functions.Count); - Assert.AreEqual(1, compilation.Classes[0].Functions[0].Attributes.Count); - { - var attr = compilation.Classes[0].Functions[0].Attributes[0]; - Assert.AreEqual("cppast", attr.Name); - } - }, - // C++17 says if the compile encounters a attribute it doesn't understand - // it will ignore that attribute and not throw an error, we still want to - // parse this. - new CppParserOptions() { AdditionalArguments = { "-std=c++17" }, ParseTokenAttributes = true } - ); - } [Test] public void TestCppNoParseOptionsAttributes() @@ -512,10 +318,10 @@ public void TestCppNoParseOptionsAttributes() Assert.False(compilation.HasErrors); Assert.AreEqual(1, compilation.Functions.Count); - Assert.AreEqual(0, compilation.Functions[0].Attributes.Count); + Assert.AreEqual(1, compilation.Functions[0].Attributes.Count); }, // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = false } + new CppParserOptions() { AdditionalArguments = { "-std=c++14" }} ); } @@ -542,7 +348,7 @@ class EXPORT_API TestClass Assert.True(cppClass.IsPublicExport()); }, - new CppParserOptions() { ParseTokenAttributes = true } + new CppParserOptions() { } ); ParseAssert(text, compilation => @@ -552,7 +358,7 @@ class EXPORT_API TestClass var cppClass = compilation.Classes[0]; Assert.AreEqual(1, cppClass.Attributes.Count); Assert.True(cppClass.IsPublicExport()); - }, new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() + }, new CppParserOptions() { }.ConfigureForWindowsMsvc() ); } diff --git a/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs b/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs index 6b56d2e..11ef50d 100644 --- a/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs +++ b/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs @@ -46,12 +46,12 @@ public void TestSimple() Assert.AreEqual(1, compilation.Fields.Count); Assert.NotNull(compilation.Fields[0].TokenAttributes); - Assert.AreEqual("dllimport", compilation.Fields[0].TokenAttributes[0].Arguments); + Assert.AreEqual("dllimport", compilation.Fields[0].TokenAttributes[0].Name); Assert.AreEqual(3, compilation.Functions.Count); Assert.NotNull(compilation.Functions[0].TokenAttributes); Assert.AreEqual(1, compilation.Functions[0].TokenAttributes.Count); - Assert.AreEqual("dllexport", compilation.Functions[0].TokenAttributes[0].Arguments); + Assert.AreEqual("dllexport", compilation.Functions[0].TokenAttributes[0].Name); Assert.AreEqual(CppCallingConvention.X86StdCall, compilation.Functions[1].CallingConvention); @@ -116,51 +116,6 @@ public void TestCpp11VarAlignas() ); } - [Test] - public void TestCpp11StructAlignas() - { - ParseAssert(@" -struct alignas(8) S {};", compilation => - { - Assert.False(compilation.HasErrors); - - Assert.AreEqual(1, compilation.Classes.Count); - Assert.AreEqual(1, compilation.Classes[0].TokenAttributes.Count); - { - var attr = compilation.Classes[0].TokenAttributes[0]; - Assert.AreEqual("alignas", attr.Name); - } - }, - // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } - ); - } - - [Test] - public void TestCpp11StructAlignasWithAttribute() - { - ParseAssert(@" -struct [[deprecated(""abc"")]] alignas(8) S {};", compilation => - { - Assert.False(compilation.HasErrors); - - Assert.AreEqual(1, compilation.Classes.Count); - Assert.AreEqual(2, compilation.Classes[0].TokenAttributes.Count); - { - var attr = compilation.Classes[0].TokenAttributes[0]; - Assert.AreEqual("deprecated", attr.Name); - } - - { - var attr = compilation.Classes[0].TokenAttributes[1]; - Assert.AreEqual("alignas", attr.Name); - } - }, - // we are using a C++14 attribute because it can be used everywhere - new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } - ); - } - [Test] public void TestCpp11StructAttributes() { @@ -518,43 +473,7 @@ public void TestCppNoParseOptionsAttributes() new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = false } ); } - - [Test] - public void TestClassPublicExportAttribute() - { - var text = @" -#ifdef WIN32 -#define EXPORT_API __declspec(dllexport) -#else -#define EXPORT_API __attribute__((visibility(""default""))) -#endif -class EXPORT_API TestClass -{ -}; -"; - ParseAssert(text, - compilation => - { - Assert.False(compilation.HasErrors); - var cppClass = compilation.Classes[0]; - Assert.AreEqual(1, cppClass.TokenAttributes.Count); - Assert.True(cppClass.IsPublicExport()); - - }, - new CppParserOptions() { ParseTokenAttributes = true } - ); - ParseAssert(text, - compilation => - { - Assert.False(compilation.HasErrors); - - var cppClass = compilation.Classes[0]; - Assert.AreEqual(1, cppClass.TokenAttributes.Count); - Assert.True(cppClass.IsPublicExport()); - }, new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() - ); - } } } diff --git a/src/CppAst.Tests/TestFunctions.cs b/src/CppAst.Tests/TestFunctions.cs index 1369918..eed91b0 100644 --- a/src/CppAst.Tests/TestFunctions.cs +++ b/src/CppAst.Tests/TestFunctions.cs @@ -191,7 +191,7 @@ public void TestFunctionExport() Assert.True(cppFunction.IsPublicExport()); } }, - new CppParserOptions() { ParseTokenAttributes = true } + new CppParserOptions() { } ); ParseAssert(text, @@ -211,7 +211,7 @@ public void TestFunctionExport() Assert.AreEqual(0, cppFunction.Attributes.Count); Assert.True(cppFunction.IsPublicExport()); } - }, new CppParserOptions() { ParseTokenAttributes = true }.ConfigureForWindowsMsvc() + }, new CppParserOptions() { }.ConfigureForWindowsMsvc() ); } diff --git a/src/CppAst/CppAttribute.cs b/src/CppAst/CppAttribute.cs index fac64c7..f3a29ae 100644 --- a/src/CppAst/CppAttribute.cs +++ b/src/CppAst/CppAttribute.cs @@ -45,8 +45,7 @@ public override string ToString() { var builder = new StringBuilder(); - - builder.Append("[["); + ////builder.Append("[["); builder.Append(Name); if (Arguments != null) @@ -59,15 +58,14 @@ public override string ToString() builder.Append("..."); } - builder.Append("]]"); - + ////builder.Append("]]"); - if (Scope != null) - { - builder.Append(" { scope:"); - builder.Append(Scope).Append("::"); - builder.Append("}"); - } + ////if (Scope != null) + ////{ + //// builder.Append(" { scope:"); + //// builder.Append(Scope).Append("::"); + //// builder.Append("}"); + ////} return builder.ToString(); } diff --git a/src/CppAst/CppAttributeKind.cs b/src/CppAst/CppAttributeKind.cs index af1a556..a1459a4 100644 --- a/src/CppAst/CppAttributeKind.cs +++ b/src/CppAst/CppAttributeKind.cs @@ -13,7 +13,7 @@ namespace CppAst public enum AttributeKind { CxxSystemAttribute, - CxxCustomAttribute, + ////CxxCustomAttribute, AnnotateAttribute, CommentAttribute, TokenAttribute, //the attribute is parse from token, and the parser is slow. diff --git a/src/CppAst/CppClass.cs b/src/CppAst/CppClass.cs index 3ca9e9b..cff4f41 100644 --- a/src/CppAst/CppClass.cs +++ b/src/CppAst/CppClass.cs @@ -29,6 +29,7 @@ public CppClass(string name) : base(CppTypeKind.StructOrClass) Typedefs = new CppContainerList(this); TemplateParameters = new List(); Attributes = new List(); + TokenAttributes = new List(); } /// diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index aaf5007..c562bbf 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -415,44 +415,6 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d break; case CXCursorKind.CXCursor_MacroExpansion: break; - - case CXCursorKind.CXCursor_VisibilityAttr: - { - var containerContext = GetOrCreateDeclarationContainer(parent, data).Container; - var cppClass = containerContext as CppClass; - if (cppClass != null) - { - CppAttribute attribute = new CppAttribute("visibility", AttributeKind.CxxSystemAttribute); - AssignSourceSpan(cursor, attribute); - attribute.Arguments = string.Format("\"{0}\"", cursor.DisplayName.ToString()); - cppClass.Attributes.Add(attribute); - } - } - break; - case CXCursorKind.CXCursor_DLLImport: - { - var containerContext = GetOrCreateDeclarationContainer(parent, data).Container; - var cppClass = containerContext as CppClass; - if (cppClass != null) - { - CppAttribute attribute = new CppAttribute("dllimport", AttributeKind.CxxSystemAttribute); - AssignSourceSpan(cursor, attribute); - cppClass.Attributes.Add(attribute); - } - } - break; - case CXCursorKind.CXCursor_DLLExport: - { - var containerContext = GetOrCreateDeclarationContainer(parent, data).Container; - var cppClass = containerContext as CppClass; - if (cppClass != null) - { - CppAttribute attribute = new CppAttribute("dllexport", AttributeKind.CxxSystemAttribute); - AssignSourceSpan(cursor, attribute); - cppClass.Attributes.Add(attribute); - } - } - break; case CXCursorKind.CXCursor_InclusionDirective: // Don't emit warning for this directive break; @@ -744,6 +706,12 @@ private CppMacro ParseMacro(CXCursor cursor) var tokens = tu.Tokenize(range); var name = GetCursorSpelling(cursor); + if(name.StartsWith("__cppast")) + { + //cppast system macros, just ignore here + return null; + } + var cppMacro = new CppMacro(name); uint previousLine = 0; @@ -1449,6 +1417,14 @@ private List ParseSystemAndAnnotateAttributeInCursor(CXCursor curs var meta = argCursor.Spelling.CString; switch (argCursor.Kind) { + case CXCursorKind.CXCursor_VisibilityAttr: + { + CppAttribute attribute = new CppAttribute("visibility", AttributeKind.CxxSystemAttribute); + AssignSourceSpan(argCursor, attribute); + attribute.Arguments = string.Format("\"{0}\"", argCursor.DisplayName.ToString()); + collectAttributes.Add(attribute); + } + break; case CXCursorKind.CXCursor_AnnotateAttr: { var attribute = new CppAttribute("annotate", AttributeKind.AnnotateAttribute) @@ -1569,7 +1545,16 @@ private void ParseAttributes(CXCursor cursor, ICppAttributeContainer attrContain } //Parse attributes contains in cursor - CppTokenUtil.ParseDirectAttribute(cursor, ref tokenAttributes); + if(attrContainer is CppFunction) + { + var func = attrContainer as CppFunction; + CppTokenUtil.ParseFunctionAttributes(cursor, func.Name, ref tokenAttributes); + } + else + { + CppTokenUtil.ParseCursorAttributs(cursor, ref tokenAttributes); + } + attrContainer.TokenAttributes.AddRange(tokenAttributes); } diff --git a/src/CppAst/CppParserOptions.cs b/src/CppAst/CppParserOptions.cs index e580dd5..9a46517 100644 --- a/src/CppAst/CppParserOptions.cs +++ b/src/CppAst/CppParserOptions.cs @@ -23,7 +23,7 @@ public CppParserOptions() //Add a default macro here for CppAst.Net Defines = new List() { - "CPPAST_GENERATOR", //Help us for identify the CppAst.Net handler + "__cppast_run__", //Help us for identify the CppAst.Net handler @"__cppast_impl(...)=__attribute__((annotate(#__VA_ARGS__)))", //Help us for use annotate attribute convenience @"__cppast(...)=__cppast_impl(__VA_ARGS__)", //Add a macro wrapper here, so the argument with macro can be handle right for compiler. }; diff --git a/src/CppAst/CppTokenUtil.cs b/src/CppAst/CppTokenUtil.cs index 06fc580..c8b2384 100644 --- a/src/CppAst/CppTokenUtil.cs +++ b/src/CppAst/CppTokenUtil.cs @@ -14,23 +14,107 @@ namespace CppAst { static internal unsafe class CppTokenUtil { - public static bool ParseDirectAttribute(CXCursor cursor, ref List attributes) + public static void ParseCursorAttributs(CXCursor cursor, ref List attributes) { var tokenizer = new AttributeTokenizer(cursor); var tokenIt = new TokenIterator(tokenizer); - if (ParseAttribute(tokenIt, out var attribute)) + + // if this is a template then we need to skip that ? + if (tokenIt.CanPeek && tokenIt.PeekText() == "template") + SkipTemplates(tokenIt); + + while (tokenIt.CanPeek) { - if (attributes == null) + if (ParseAttributes(tokenIt, ref attributes)) { - attributes = new List(); + continue; } - attributes.Add(attribute); - return true; + + // If we have a keyword, try to skip it and process following elements + // for example attribute put right after a struct __declspec(uuid("...")) Test {...} + if (tokenIt.Peek().Kind == CppTokenKind.Keyword) + { + tokenIt.Next(); + continue; + } + break; } + } - return false; + + public static void ParseFunctionAttributes(CXCursor cursor, string functionName, ref List attributes) + { + // TODO: This function is not 100% correct when parsing tokens up to the function name + // we assume to find the function name immediately followed by a `(` + // but some return type parameter could actually interfere with that + // Ideally we would need to parse more properly return type and skip parenthesis for example + var tokenizer = new AttributeTokenizer(cursor); + var tokenIt = new TokenIterator(tokenizer); + + // if this is a template then we need to skip that ? + if (tokenIt.CanPeek && tokenIt.PeekText() == "template") + SkipTemplates(tokenIt); + + // Parse leading attributes + while (tokenIt.CanPeek) + { + if (ParseAttributes(tokenIt, ref attributes)) + { + continue; + } + break; + } + + if (!tokenIt.CanPeek) + { + return; + } + + // Find function name (We only support simple function name declaration) + if (!tokenIt.Find(functionName, "(")) + { + return; + } + + Debug.Assert(tokenIt.PeekText() == functionName); + tokenIt.Next(); + Debug.Assert(tokenIt.PeekText() == "("); + tokenIt.Next(); + + int parentCount = 1; + while (parentCount > 0 && tokenIt.CanPeek) + { + var text = tokenIt.PeekText(); + if (text == "(") + { + parentCount++; + } + else if (text == ")") + { + parentCount--; + } + tokenIt.Next(); + } + + if (parentCount != 0) + { + return; + } + + while (tokenIt.CanPeek) + { + if (ParseAttributes(tokenIt, ref attributes)) + { + continue; + } + // Skip the token if we can parse it. + tokenIt.Next(); + } + + return; } + public static void ParseAttributesInRange(CXTranslationUnit tu, CXSourceRange range, ref List collectAttributes) { var tokenizer = new AttributeTokenizer(tu, range);