Skip to content

Commit

Permalink
Add some support for parsing attributes defined by macros (xoofx#51)
Browse files Browse the repository at this point in the history
Only basic parsing is performed by this PR but it does allow for
a macro to hold an scoped attribute with arguments.
  • Loading branch information
stevew-cgnx authored and THarsszegi committed Dec 11, 2023
1 parent d6e459e commit 85c92e1
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
225 changes: 225 additions & 0 deletions src/CppAst/CppModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CppAttribute> attributes)
{
// Parse C++ attributes
// [[<attribute>]]
if (tokenIt.Skip("[", "["))
{
CppAttribute attribute;
while (ParseAttribute(tokenIt, out attribute))
{
if (attributes == null)
{
attributes = new List<CppAttribute>();
}
attributes.Add(attribute);

tokenIt.Skip(",");
}

return tokenIt.Skip("]", "]");
}

// Parse GCC or clang attributes
// __attribute__((<attribute>))
if (tokenIt.Skip("__attribute__", "(", "("))
{
CppAttribute attribute;
while (ParseAttribute(tokenIt, out attribute))
{
if (attributes == null)
{
attributes = new List<CppAttribute>();
}
attributes.Add(attribute);

tokenIt.Skip(",");
}

return tokenIt.Skip(")", ")");
}

// Parse MSVC attributes
// __declspec(<attribute>)
if (tokenIt.Skip("__declspec", "("))
{
CppAttribute attribute;
while (ParseAttribute(tokenIt, out attribute))
{
if (attributes == null)
{
attributes = new List<CppAttribute>();
}
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<CppAttribute>();
}
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<CppAttribute>();
}
attributes.Add(attribute);
tokenIt.Next();
return true;
}
}
return false;
}

private bool ParseDirectAttribute(CXCursor cursor, ref List<CppAttribute> attributes)
{
var tokenizer = new AttributeTokenizer(cursor);
var tokenIt = new TokenIterator(tokenizer);
if (ParseAttribute(tokenIt, out var attribute))
{
if (attributes == null)
{
attributes = new List<CppAttribute>();
}
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)
{
Expand Down

0 comments on commit 85c92e1

Please sign in to comment.