Skip to content

Commit 7b1e0da

Browse files
balazssomogyisombalazsxoofx
authored
Improve attribute parsing (#82)
Adding support for parsing attributes defined as a macro, and parsing function attributes that are on a separate line before the function declaration. Co-authored-by: Balazs Somogyi <[email protected]> Co-authored-by: Alexandre Mutel <[email protected]>
1 parent ab110bd commit 7b1e0da

File tree

3 files changed

+126
-17
lines changed

3 files changed

+126
-17
lines changed

src/CppAst.Tests/AttributesTest/TestTokenAttributes.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,34 @@ struct [[deprecated(""old"")]] TestMessage{
151151
);
152152
}
153153

154+
[Test]
155+
public void TestCpp11StructAttributesWithMacro()
156+
{
157+
ParseAssert(@"
158+
#define CLASS_ATTRIBUTE [[complex_attribute::attribute_name(""attribute_argument"")]]
159+
struct
160+
CLASS_ATTRIBUTE
161+
Test{
162+
int a;
163+
int b;
164+
};", compilation =>
165+
{
166+
Assert.False(compilation.HasErrors);
167+
168+
Assert.AreEqual(1, compilation.Classes.Count);
169+
Assert.AreEqual(1, compilation.Classes[0].TokenAttributes.Count);
170+
{
171+
var attr = compilation.Classes[0].TokenAttributes[0];
172+
Assert.AreEqual("complex_attribute", attr.Scope);
173+
Assert.AreEqual("attribute_name", attr.Name);
174+
Assert.AreEqual("\"attribute_argument\"", attr.Arguments);
175+
}
176+
},
177+
// we are using a C++14 attribute because it can be used everywhere
178+
new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true }
179+
);
180+
}
181+
154182
[Test]
155183
public void TestCpp11VariablesAttributes()
156184
{
@@ -204,6 +232,27 @@ public void TestCpp11FunctionsAttributes()
204232
);
205233
}
206234

235+
[Test]
236+
public void TestCpp11FunctionsAttributesOnNewLine()
237+
{
238+
ParseAssert(@"
239+
[[noreturn]]
240+
void x() {};", compilation =>
241+
{
242+
Assert.False(compilation.HasErrors);
243+
244+
Assert.AreEqual(1, compilation.Functions.Count);
245+
Assert.AreEqual(1, compilation.Functions[0].TokenAttributes.Count);
246+
{
247+
var attr = compilation.Functions[0].TokenAttributes[0];
248+
Assert.AreEqual("noreturn", attr.Name);
249+
}
250+
},
251+
// we are using a C++14 attribute because it can be used everywhere
252+
new CppParserOptions() { AdditionalArguments = { "-std=c++14" }, ParseTokenAttributes = true }
253+
);
254+
}
255+
207256
[Test]
208257
public void TestCpp11NamespaceAttributes()
209258
{

src/CppAst/CppModelBuilder.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,19 +1541,19 @@ private void ParseAttributes(CXCursor cursor, ICppAttributeContainer attrContain
15411541
bool hasOnlineAttribute = CppTokenUtil.TryToSeekOnlineAttributes(cursor, out var onLineRange);
15421542
if (hasOnlineAttribute)
15431543
{
1544-
CppTokenUtil.ParseAttributesInRange(cursor.TranslationUnit, onLineRange, ref tokenAttributes);
1544+
CppTokenUtil.ParseAttributesInRange(_rootContainerContext.Container as CppGlobalDeclarationContainer, cursor.TranslationUnit, onLineRange, ref tokenAttributes);
15451545
}
15461546
}
15471547

15481548
//Parse attributes contains in cursor
15491549
if (attrContainer is CppFunction)
15501550
{
15511551
var func = attrContainer as CppFunction;
1552-
CppTokenUtil.ParseFunctionAttributes(cursor, func.Name, ref tokenAttributes);
1552+
CppTokenUtil.ParseFunctionAttributes(_rootContainerContext.Container as CppGlobalDeclarationContainer, cursor, func.Name, ref tokenAttributes);
15531553
}
15541554
else
15551555
{
1556-
CppTokenUtil.ParseCursorAttributs(cursor, ref tokenAttributes);
1556+
CppTokenUtil.ParseCursorAttributs(_rootContainerContext.Container as CppGlobalDeclarationContainer, cursor, ref tokenAttributes);
15571557
}
15581558

15591559
attrContainer.TokenAttributes.AddRange(tokenAttributes);

src/CppAst/CppTokenUtil.cs

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace CppAst
1414
{
1515
static internal unsafe class CppTokenUtil
1616
{
17-
public static void ParseCursorAttributs(CXCursor cursor, ref List<CppAttribute> attributes)
17+
public static void ParseCursorAttributs(CppGlobalDeclarationContainer globalContainer, CXCursor cursor, ref List<CppAttribute> attributes)
1818
{
1919
var tokenizer = new AttributeTokenizer(cursor);
2020
var tokenIt = new TokenIterator(tokenizer);
@@ -25,7 +25,7 @@ public static void ParseCursorAttributs(CXCursor cursor, ref List<CppAttribute>
2525

2626
while (tokenIt.CanPeek)
2727
{
28-
if (ParseAttributes(tokenIt, ref attributes))
28+
if (ParseAttributes(globalContainer, tokenIt, ref attributes))
2929
{
3030
continue;
3131
}
@@ -42,7 +42,7 @@ public static void ParseCursorAttributs(CXCursor cursor, ref List<CppAttribute>
4242
}
4343

4444

45-
public static void ParseFunctionAttributes(CXCursor cursor, string functionName, ref List<CppAttribute> attributes)
45+
public static void ParseFunctionAttributes(CppGlobalDeclarationContainer globalContainer, CXCursor cursor, string functionName, ref List<CppAttribute> attributes)
4646
{
4747
// TODO: This function is not 100% correct when parsing tokens up to the function name
4848
// we assume to find the function name immediately followed by a `(`
@@ -58,7 +58,7 @@ public static void ParseFunctionAttributes(CXCursor cursor, string functionName,
5858
// Parse leading attributes
5959
while (tokenIt.CanPeek)
6060
{
61-
if (ParseAttributes(tokenIt, ref attributes))
61+
if (ParseAttributes(globalContainer, tokenIt, ref attributes))
6262
{
6363
continue;
6464
}
@@ -103,7 +103,7 @@ public static void ParseFunctionAttributes(CXCursor cursor, string functionName,
103103

104104
while (tokenIt.CanPeek)
105105
{
106-
if (ParseAttributes(tokenIt, ref attributes))
106+
if (ParseAttributes(globalContainer, tokenIt, ref attributes))
107107
{
108108
continue;
109109
}
@@ -115,7 +115,7 @@ public static void ParseFunctionAttributes(CXCursor cursor, string functionName,
115115
}
116116

117117

118-
public static void ParseAttributesInRange(CXTranslationUnit tu, CXSourceRange range, ref List<CppAttribute> collectAttributes)
118+
public static void ParseAttributesInRange(CppGlobalDeclarationContainer globalContainer, CXTranslationUnit tu, CXSourceRange range, ref List<CppAttribute> collectAttributes)
119119
{
120120
var tokenizer = new AttributeTokenizer(tu, range);
121121
var tokenIt = new TokenIterator(tokenizer);
@@ -134,7 +134,7 @@ public static void ParseAttributesInRange(CXTranslationUnit tu, CXSourceRange ra
134134

135135
while (tokenIt.CanPeek)
136136
{
137-
if (ParseAttributes(tokenIt, ref collectAttributes))
137+
if (ParseAttributes(globalContainer, tokenIt, ref collectAttributes))
138138
{
139139
continue;
140140
}
@@ -615,17 +615,17 @@ bool HasInlineTypeDefinition(CXCursor varDecl)
615615
CXSourceLocation GetNextLocation(CXSourceLocation loc, int inc = 1)
616616
{
617617
CXSourceLocation value;
618-
loc.GetSpellingLocation(out var f, out var u, out var z, out var originalOffset);
619-
var offset = IncOffset(inc, z);
620-
var shouldUseLine = (z != 0 && (offset != 0 || offset != uint.MaxValue));
618+
loc.GetSpellingLocation(out var file, out var line, out var column, out var originalOffset);
619+
var signedOffset = (int)column + inc;
620+
var shouldUseLine = (column != 0 && signedOffset > 0);
621621
if (shouldUseLine)
622622
{
623-
value = tu.GetLocation(f, u, offset);
623+
value = tu.GetLocation(file, line, (uint)signedOffset);
624624
}
625625
else
626626
{
627-
offset = IncOffset(inc, originalOffset);
628-
value = tu.GetLocationForOffset(f, offset);
627+
var offset = IncOffset(inc, originalOffset);
628+
value = tu.GetLocationForOffset(file, offset);
629629
}
630630

631631
return value;
@@ -911,8 +911,42 @@ private enum AttributeLexerParseStatus
911911
Error,
912912
}
913913

914+
private static (string, string) GetNameSpaceAndAttribute(string fullAttribute)
915+
{
916+
string[] colons = { "::" };
917+
string[] tokens = fullAttribute.Split(colons, System.StringSplitOptions.None);
918+
if (tokens.Length == 2)
919+
{
920+
return (tokens[0], tokens[1]);
921+
}
922+
else
923+
{
924+
return (null, tokens[0]);
925+
}
926+
}
927+
928+
929+
private static (string, string) GetNameAndArguments(string name)
930+
{
931+
if (name.Contains("("))
932+
{
933+
Char[] seperator = { '(' };
934+
var argumentTokens = name.Split(seperator, 2);
935+
var length = argumentTokens[1].LastIndexOf(')');
936+
string argument = null;
937+
if (length > 0)
938+
{
939+
argument = argumentTokens[1].Substring(0, length);
940+
}
941+
return (argumentTokens[0], argument);
942+
}
943+
else
944+
{
945+
return (name, null);
946+
}
947+
}
914948

915-
private static bool ParseAttributes(TokenIterator tokenIt, ref List<CppAttribute> attributes)
949+
private static bool ParseAttributes(CppGlobalDeclarationContainer globalContainer, TokenIterator tokenIt, ref List<CppAttribute> attributes)
916950
{
917951
// Parse C++ attributes
918952
// [[<attribute>]]
@@ -985,6 +1019,32 @@ private static bool ParseAttributes(TokenIterator tokenIt, ref List<CppAttribute
9851019
return tokenIt.Skip(")"); ;
9861020
}
9871021

1022+
// See if we have a macro
1023+
var value = tokenIt.PeekText();
1024+
var macro = globalContainer.Macros.Find(v => v.Name == value);
1025+
if (macro != null)
1026+
{
1027+
if (macro.Value.StartsWith("[[") && macro.Value.EndsWith("]]"))
1028+
{
1029+
CppAttribute attribute = null;
1030+
var fullAttribute = macro.Value.Substring(2, macro.Value.Length - 4);
1031+
var (scope, name) = GetNameSpaceAndAttribute(fullAttribute);
1032+
var (attributeName, arguments) = GetNameAndArguments(name);
1033+
1034+
attribute = new CppAttribute(attributeName, AttributeKind.TokenAttribute);
1035+
attribute.Scope = scope;
1036+
attribute.Arguments = arguments;
1037+
1038+
if (attributes == null)
1039+
{
1040+
attributes = new List<CppAttribute>();
1041+
}
1042+
attributes.Add(attribute);
1043+
tokenIt.Next();
1044+
return true;
1045+
}
1046+
}
1047+
9881048
return false;
9891049
}
9901050

0 commit comments

Comments
 (0)