From 85c92e14529a575faec70db4579cf0d1dd9ae49c Mon Sep 17 00:00:00 2001 From: Steven Whitman Date: Wed, 20 Apr 2022 02:00:11 -0400 Subject: [PATCH] Add some support for parsing attributes defined by macros (#51) Only basic parsing is performed by this PR but it does allow for a macro to hold an scoped attribute with arguments. --- .../AttributesTest/TestTokenAttributes.cs | 19 ++ src/CppAst/CppModelBuilder.cs | 225 ++++++++++++++++++ 2 files changed, 244 insertions(+) diff --git a/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs b/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs index fd18307..bd450f7 100644 --- a/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs +++ b/src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs @@ -177,6 +177,25 @@ public void TestCpp11StructAttributesWithMacro() // we are using a C++14 attribute because it can be used everywhere new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true } ); + ParseAssert(@" +#define CLASS_ATTRIBUTE [[deprecated]] +struct CLASS_ATTRIBUTE Test{ + int a; + int b; +};", compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Classes.Count); + Assert.AreEqual(1, compilation.Classes[0].Attributes.Count); + { + var attr = compilation.Classes[0].Attributes[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" }, ParseAttributes = true } + ); } [Test] diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index 478f7ee..90356aa 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -1630,6 +1630,231 @@ private void ParseAttributes(CXCursor cursor, ICppAttributeContainer attrContain attrContainer.TokenAttributes.AddRange(tokenAttributes); } + private (string, string) GetNameSpaceAndAttribute(string fullAttribute) + { + string[] colons = { "::" }; + string[] tokens = fullAttribute.Split(colons, System.StringSplitOptions.None); + if (tokens.Length == 2) + { + return (tokens[0], tokens[1]); + } + else + { + return (null, tokens[0]); + } + } + + + private (string, string) GetNameAndArguments(string name) + { + if (name.Contains("(")) + { + Char[] seperator = { '(' }; + var argumentTokens = name.Split(seperator, 2); + var length = argumentTokens[1].LastIndexOf(')'); + string argument = null; + if (length > 0) + { + argument = argumentTokens[1].Substring(0, length); + } + return (argumentTokens[0], argument); + } + else + { + return (name, null); + } + } + + private bool ParseAttributes(TokenIterator tokenIt, ref List attributes) + { + // Parse C++ attributes + // [[]] + if (tokenIt.Skip("[", "[")) + { + CppAttribute attribute; + while (ParseAttribute(tokenIt, out 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__", "(", "(")) + { + CppAttribute attribute; + while (ParseAttribute(tokenIt, out attribute)) + { + if (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + + tokenIt.Skip(","); + } + + return tokenIt.Skip(")", ")"); + } + + // Parse MSVC attributes + // __declspec() + if (tokenIt.Skip("__declspec", "(")) + { + CppAttribute attribute; + while (ParseAttribute(tokenIt, out 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") + { + CppAttribute attribute; + while (ParseAttribute(tokenIt, out attribute)) + { + if (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + + break; + } + + return tokenIt.Skip(")"); ; + } + + // See if we have a macro + var value = tokenIt.PeekText(); + var globalContainer = (CppGlobalDeclarationContainer)_rootContainerContext.DeclarationContainer; + var macro = globalContainer.Macros.Find(v => v.Name == value); + if (macro != null) + { + if (macro.Value.StartsWith("[[") && macro.Value.EndsWith("]]")) + { + CppAttribute attribute = null; + var fullAttribute = macro.Value.Substring(2, macro.Value.Length - 4); + var (scope, name) = GetNameSpaceAndAttribute(fullAttribute); + var (attributeName, arguments) = GetNameAndArguments(name); + + attribute = new CppAttribute(attributeName); + attribute.Scope = scope; + attribute.Arguments = arguments; + + if (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + tokenIt.Next(); + return true; + } + } + 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 (attributes == null) + { + attributes = new List(); + } + attributes.Add(attribute); + return true; + } + + return false; + } + + private 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) + { + Span = new CppSourceSpan(firstToken.Span.Start, previousToken.Span.End), + Scope = scope, + Arguments = arguments, + IsVariadic = isVariadic, + }; + return true; + } private CppType VisitTypeAliasDecl(CXCursor cursor, void* data) {