Skip to content

Commit 728e9d3

Browse files
authored
Merge pull request #110 from xoofx/objective-c
Add support for Objective-C
2 parents 02183f7 + 29c8038 commit 728e9d3

22 files changed

+1195
-223
lines changed

src/CppAst.Tests/TestObjectiveC.cs

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
// Copyright (c) Alexandre Mutel. All rights reserved.
2+
// Licensed under the BSD-Clause 2 license.
3+
// See license.txt file in the project root for full license information.
4+
5+
using System;
6+
7+
namespace CppAst.Tests;
8+
9+
public class TestObjectiveC : InlineTestBase
10+
{
11+
[Test]
12+
public void TestFoundationIncludes()
13+
{
14+
if (!OperatingSystem.IsMacOS())
15+
{
16+
NUnit.Framework.Assert.Ignore("Only on MacOS");
17+
return;
18+
}
19+
20+
ParseAssert("""
21+
#import <Foundation/Foundation.h>
22+
""",
23+
compilation =>
24+
{
25+
Assert.False(compilation.HasErrors);
26+
Assert.AreEqual(0, compilation.Diagnostics.Messages.Count, "Parsing foundation headers should not generate warnings"); // No warnings
27+
Assert.IsTrue(compilation.System.Classes.Count > 1000);
28+
}, new()
29+
{
30+
ParserKind = CppParserKind.ObjC,
31+
TargetCpu = CppTargetCpu.ARM64,
32+
TargetVendor = "apple",
33+
TargetSystem = "darwin",
34+
ParseMacros = false,
35+
ParseSystemIncludes = true,
36+
AdditionalArguments =
37+
{
38+
"-std=gnu11",
39+
"-isysroot", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk",
40+
"-F", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks",
41+
"-isystem", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include",
42+
"-isystem", "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include",
43+
}
44+
}
45+
);
46+
}
47+
48+
[Test]
49+
public void TestInterfaceWithMethods()
50+
{
51+
ParseAssert("""
52+
@interface MyInterface
53+
- (float)helloworld;
54+
- (void)doSomething:(int)index argSpecial:(float)arg1;
55+
@end
56+
""",
57+
compilation =>
58+
{
59+
Assert.False(compilation.HasErrors);
60+
Assert.AreEqual(1, compilation.Classes.Count);
61+
var myInterface = compilation.Classes[0];
62+
Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind);
63+
Assert.AreEqual("MyInterface", myInterface.Name);
64+
Assert.AreEqual(2, myInterface.Functions.Count);
65+
66+
Assert.AreEqual(0, myInterface.Functions[0].Parameters.Count);
67+
Assert.AreEqual("helloworld", myInterface.Functions[0].Name);
68+
Assert.IsTrue(myInterface.Functions[0].ReturnType is CppPrimitiveType primitive && primitive.Kind == CppPrimitiveKind.Float);
69+
70+
Assert.AreEqual(2, myInterface.Functions[1].Parameters.Count);
71+
Assert.AreEqual("index", myInterface.Functions[1].Parameters[0].Name);
72+
Assert.AreEqual("arg1", myInterface.Functions[1].Parameters[1].Name);
73+
Assert.AreEqual("doSomething:argSpecial:", myInterface.Functions[1].Name);
74+
Assert.IsTrue(myInterface.Functions[1].ReturnType is CppPrimitiveType primitive2 && primitive2.Kind == CppPrimitiveKind.Void);
75+
Assert.IsTrue(myInterface.Functions[1].Parameters[1].Type is CppPrimitiveType primitive3 && primitive3.Kind == CppPrimitiveKind.Float);
76+
77+
}, GetDefaultObjCOptions()
78+
);
79+
80+
}
81+
82+
[Test]
83+
public void TestInterfaceWithProperties()
84+
{
85+
ParseAssert("""
86+
@interface MyInterface
87+
@property int id;
88+
@property (readonly) float id2;
89+
@end
90+
""",
91+
compilation =>
92+
{
93+
Assert.False(compilation.HasErrors);
94+
Assert.AreEqual(1, compilation.Classes.Count);
95+
var myInterface = compilation.Classes[0];
96+
Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind);
97+
Assert.AreEqual("MyInterface", myInterface.Name);
98+
Assert.AreEqual(2, myInterface.Properties.Count);
99+
Assert.AreEqual("id", myInterface.Properties[0].Name);
100+
Assert.AreEqual("id2", myInterface.Properties[1].Name);
101+
102+
Assert.IsTrue(myInterface.Properties[0].Type is CppPrimitiveType primitive && primitive.Kind == CppPrimitiveKind.Int);
103+
Assert.IsTrue(myInterface.Properties[0].Getter is not null);
104+
Assert.IsTrue(myInterface.Properties[0].Setter is not null);
105+
106+
Assert.IsTrue(myInterface.Properties[1].Type is CppPrimitiveType primitive2 && primitive2.Kind == CppPrimitiveKind.Float);
107+
Assert.IsTrue(myInterface.Properties[1].Setter is null);
108+
109+
Assert.AreEqual(3, myInterface.Functions.Count);
110+
Assert.AreEqual("id", myInterface.Functions[0].Name);
111+
Assert.IsTrue(myInterface.Functions[0].ReturnType is CppPrimitiveType primitive3 && primitive3.Kind == CppPrimitiveKind.Int);
112+
113+
Assert.AreEqual("setId:", myInterface.Functions[1].Name);
114+
Assert.IsTrue(myInterface.Functions[1].ReturnType is CppPrimitiveType primitive4 && primitive4.Kind == CppPrimitiveKind.Void);
115+
Assert.AreEqual(1, myInterface.Functions[1].Parameters.Count);
116+
Assert.AreEqual("id", myInterface.Functions[1].Parameters[0].Name);
117+
Assert.IsTrue(myInterface.Functions[1].Parameters[0].Type is CppPrimitiveType primitive5 && primitive5.Kind == CppPrimitiveKind.Int);
118+
119+
Assert.AreEqual("id2", myInterface.Functions[2].Name);
120+
Assert.IsTrue(myInterface.Functions[2].ReturnType is CppPrimitiveType primitive6 && primitive6.Kind == CppPrimitiveKind.Float);
121+
Assert.AreEqual(0, myInterface.Functions[2].Parameters.Count);
122+
}, GetDefaultObjCOptions()
123+
);
124+
}
125+
126+
[Test]
127+
public void TestInterfaceWithInstanceType()
128+
{
129+
ParseAssert("""
130+
@interface MyInterface
131+
+ (instancetype)getInstance;
132+
@end
133+
""",
134+
compilation =>
135+
{
136+
Assert.False(compilation.HasErrors);
137+
Assert.AreEqual(1, compilation.Classes.Count);
138+
var myInterface = compilation.Classes[0];
139+
Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind);
140+
Assert.AreEqual("MyInterface", myInterface.Name);
141+
Assert.AreEqual(1, myInterface.Functions.Count);
142+
Assert.AreEqual("getInstance", myInterface.Functions[0].Name);
143+
Assert.IsTrue((myInterface.Functions[0].Flags & CppFunctionFlags.ClassMethod) != 0);
144+
var pointerType = myInterface.Functions[0].ReturnType as CppPointerType;
145+
Assert.IsNotNull(pointerType);
146+
Assert.AreEqual(myInterface, pointerType!.ElementType);
147+
}, GetDefaultObjCOptions()
148+
);
149+
}
150+
151+
152+
[Test]
153+
public void TestInterfaceWithMultipleGenericParameters()
154+
{
155+
ParseAssert("""
156+
@interface BaseInterface
157+
@end
158+
159+
// Generics require a base class
160+
@interface MyInterface<T1, T2> : BaseInterface
161+
- (T1)get_at:(int)index;
162+
- (T2)get_at2:(int)index;
163+
@end
164+
""",
165+
compilation =>
166+
{
167+
Assert.False(compilation.HasErrors);
168+
Assert.AreEqual(2, compilation.Classes.Count);
169+
var myInterface = compilation.Classes[1];
170+
Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind);
171+
Assert.AreEqual("MyInterface", myInterface.Name);
172+
Assert.AreEqual(2, myInterface.TemplateParameters.Count);
173+
Assert.IsTrue(myInterface.TemplateParameters[0] is CppTemplateParameterType templateParam1 && templateParam1.Name == "T1");
174+
Assert.IsTrue(myInterface.TemplateParameters[1] is CppTemplateParameterType templateParam2 && templateParam2.Name == "T2");
175+
176+
Assert.AreEqual(2, myInterface.Functions.Count);
177+
Assert.AreEqual("get_at:", myInterface.Functions[0].Name);
178+
Assert.AreEqual("get_at2:", myInterface.Functions[1].Name);
179+
Assert.IsTrue(myInterface.Functions[0].ReturnType is CppTemplateParameterType templateSpecialization && templateSpecialization.Name == "T1");
180+
Assert.IsTrue(myInterface.Functions[1].ReturnType is CppTemplateParameterType templateSpecialization2 && templateSpecialization2.Name == "T2");
181+
}, GetDefaultObjCOptions()
182+
);
183+
}
184+
185+
[Test]
186+
public void TestBlockFunctionPointer()
187+
{
188+
ParseAssert("""
189+
typedef float (^MyBlock)(int a, int* b);
190+
""",
191+
compilation =>
192+
{
193+
Assert.False(compilation.HasErrors);
194+
Assert.AreEqual(1, compilation.Typedefs.Count);
195+
196+
var typedef = compilation.Typedefs[0];
197+
Assert.AreEqual("MyBlock", typedef.Name);
198+
199+
Assert.IsInstanceOf<CppBlockFunctionType>(typedef.ElementType);
200+
var blockType = (CppBlockFunctionType)typedef.ElementType;
201+
202+
Assert.AreEqual(CppTypeKind.ObjCBlockFunction, blockType.TypeKind);
203+
204+
Assert.IsTrue(blockType.ReturnType is CppPrimitiveType primitive && primitive.Kind == CppPrimitiveKind.Float);
205+
206+
Assert.AreEqual(2, blockType.Parameters.Count);
207+
Assert.IsTrue(blockType.Parameters[0].Type is CppPrimitiveType primitive2 && primitive2.Kind == CppPrimitiveKind.Int);
208+
Assert.IsTrue(blockType.Parameters[1].Type is CppPointerType pointerType && pointerType.ElementType is CppPrimitiveType primitive3 && primitive3.Kind == CppPrimitiveKind.Int);
209+
}, GetDefaultObjCOptions()
210+
);
211+
}
212+
213+
[Test]
214+
public void TestProtocol()
215+
{
216+
ParseAssert("""
217+
@protocol MyProtocol
218+
@end
219+
220+
@protocol MyProtocol1
221+
@end
222+
223+
@protocol MyProtocol2 <MyProtocol, MyProtocol1>
224+
@end
225+
226+
@interface MyInterface <MyProtocol>
227+
@end
228+
""",
229+
compilation =>
230+
{
231+
Assert.False(compilation.HasErrors);
232+
233+
Assert.AreEqual(4, compilation.Classes.Count);
234+
235+
var myProtocol = compilation.Classes[0];
236+
Assert.AreEqual(CppClassKind.ObjCProtocol, myProtocol.ClassKind);
237+
Assert.AreEqual("MyProtocol", myProtocol.Name);
238+
239+
var myProtocol1 = compilation.Classes[1];
240+
Assert.AreEqual(CppClassKind.ObjCProtocol, myProtocol1.ClassKind);
241+
Assert.AreEqual("MyProtocol1", myProtocol1.Name);
242+
243+
var myProtocol2 = compilation.Classes[2];
244+
Assert.AreEqual(CppClassKind.ObjCProtocol, myProtocol2.ClassKind);
245+
Assert.AreEqual("MyProtocol2", myProtocol2.Name);
246+
Assert.AreEqual(2, myProtocol2.ObjCImplementedProtocols.Count);
247+
Assert.AreEqual(myProtocol, myProtocol2.ObjCImplementedProtocols[0]);
248+
Assert.AreEqual(myProtocol1, myProtocol2.ObjCImplementedProtocols[1]);
249+
250+
var myInterface = compilation.Classes[3];
251+
Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind);
252+
Assert.AreEqual("MyInterface", myInterface.Name);
253+
Assert.AreEqual(1, myInterface.ObjCImplementedProtocols.Count);
254+
Assert.AreEqual(myProtocol, myInterface.ObjCImplementedProtocols[0]);
255+
256+
}, GetDefaultObjCOptions()
257+
);
258+
}
259+
260+
[Test]
261+
public void TestInterfaceBaseType()
262+
{
263+
ParseAssert("""
264+
@interface InterfaceBase
265+
@end
266+
267+
@interface MyInterface : InterfaceBase
268+
@end
269+
""",
270+
compilation =>
271+
{
272+
Assert.False(compilation.HasErrors);
273+
274+
Assert.AreEqual(2, compilation.Classes.Count);
275+
276+
var myInterfaceBase = compilation.Classes[0];
277+
Assert.AreEqual(CppClassKind.ObjCInterface, myInterfaceBase.ClassKind);
278+
Assert.AreEqual("InterfaceBase", myInterfaceBase.Name);
279+
280+
var myInterface = compilation.Classes[1];
281+
Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind);
282+
Assert.AreEqual("MyInterface", myInterface.Name);
283+
Assert.AreEqual(1, myInterface.BaseTypes.Count);
284+
Assert.AreEqual(myInterfaceBase, myInterface.BaseTypes[0].Type);
285+
}, GetDefaultObjCOptions()
286+
);
287+
}
288+
289+
[Test]
290+
public void TestInterfaceWithCategory()
291+
{
292+
ParseAssert("""
293+
@interface MyInterface
294+
@end
295+
296+
@interface MyInterface (MyCategory)
297+
@end
298+
""",
299+
compilation =>
300+
{
301+
Assert.False(compilation.HasErrors);
302+
303+
Assert.AreEqual(2, compilation.Classes.Count);
304+
305+
var myInterface = compilation.Classes[0];
306+
Assert.AreEqual(CppClassKind.ObjCInterface, myInterface.ClassKind);
307+
Assert.AreEqual("MyInterface", myInterface.Name);
308+
309+
var myInterfaceWithCategory = compilation.Classes[1];
310+
Assert.AreEqual(CppClassKind.ObjCInterfaceCategory, myInterfaceWithCategory.ClassKind);
311+
Assert.AreEqual("MyInterface", myInterfaceWithCategory.Name);
312+
Assert.AreEqual("MyCategory", myInterfaceWithCategory.ObjCCategoryName);
313+
Assert.AreEqual(myInterface, myInterfaceWithCategory.ObjCCategoryTargetClass);
314+
}, GetDefaultObjCOptions()
315+
);
316+
}
317+
318+
319+
private static CppParserOptions GetDefaultObjCOptions()
320+
{
321+
return new CppParserOptions
322+
{
323+
ParserKind = CppParserKind.ObjC,
324+
TargetCpu = CppTargetCpu.ARM64,
325+
TargetVendor = "apple",
326+
TargetSystem = "darwin",
327+
ParseMacros = false,
328+
ParseSystemIncludes = false,
329+
};
330+
}
331+
}

src/CppAst/CppAst.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFrameworks>net8.0</TargetFrameworks>
3+
<TargetFramework>net8.0</TargetFramework>
44
<PackageId>CppAst</PackageId>
55
<Description>CppAst is a .NET library providing a C/C++ parser for header files with access to the full AST, comments and macros</Description>
66
<Copyright>Alexandre Mutel</Copyright>
@@ -12,7 +12,6 @@
1212
<PackageLicenseExpression>BSD-2-Clause</PackageLicenseExpression>
1313
<RepositoryType>git</RepositoryType>
1414
<RepositoryUrl>https://github.com/xoofx/CppAst</RepositoryUrl>
15-
<LangVersion>7.3</LangVersion>
1615
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1716
<NeutralLanguage>en-US</NeutralLanguage>
1817
<NoWarn>$(NoWarn);CS1591</NoWarn>

src/CppAst/CppAttributeKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ public enum AttributeKind
1717
AnnotateAttribute,
1818
CommentAttribute,
1919
TokenAttribute, //the attribute is parse from token, and the parser is slow.
20+
ObjectiveCAttribute,
2021
}
2122
}

src/CppAst/CppBlockFunctionType.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Alexandre Mutel. All rights reserved.
2+
// Licensed under the BSD-Clause 2 license.
3+
// See license.txt file in the project root for full license information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Text;
8+
9+
namespace CppAst
10+
{
11+
/// <summary>
12+
/// An Objective-C block function type (e.g `void (^)(int arg1, int arg2)`)
13+
/// </summary>
14+
public sealed class CppBlockFunctionType : CppFunctionTypeBase
15+
{
16+
/// <summary>
17+
/// Constructor of a function type.
18+
/// </summary>
19+
/// <param name="returnType">Return type of this function type.</param>
20+
public CppBlockFunctionType(CppType returnType) : base(CppTypeKind.ObjCBlockFunction, returnType)
21+
{
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)