1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
|
# -*- coding: utf-8 -*-
from ctypes import *
def get_cindex_library():
# FIXME: It's probably not the case that the library is actually found in
# this location. We need a better system of identifying and loading the
# CIndex library. It could be on path or elsewhere, or versioned, etc.
import platform
name = platform.system()
if name == 'Darwin':
return cdll.LoadLibrary('libCIndex.dylib')
elif name == 'Windows':
return cdll.LoadLibrary('libCIndex.dll')
else:
return cdll.LoadLibrary('libCIndex.so')
## Utility Types and Functions ##
def alloc_string_vector(strs):
"""
Allocate a string buffer large enough to accommodate the given list of
python strings.
"""
n = 0
for i in strs: n += len(i) + 1
return create_string_buffer(n)
def copy_string_vector(vec, strs):
"""
Copy the contents of each string into the vector, preserving null
terminated elements.
"""
n = 0
for i in strs:
# This is terribly inefficient, but I can't figure out how to copy a
# chunk of characters into the resultant vector. t should be: something
# like this: vec[n:n + len(i)] = i[:]; n += len(i) + 1
for j in i:
vec[n] = j
n += 1
n += 1
def create_string_vector(strs):
"""
Create a string vector (char *[]) from the given list of strings.
"""
vec = alloc_string_vector(strs)
copy_string_vector(vec, strs)
return vec
# ctypes doesn't implicitly convert c_void_p to the appropriate wrapper
# object. This is a problem, because it means that from_parameter will see an
# integer and pass the wrong value on platforms where int != void*. Work around
# this by marshalling object arguments as void**.
c_object_p = POINTER(c_void_p)
lib = get_cindex_library()
## Typedefs ##
CursorKind = c_int
### Structures and Utility Classes ###
class _CXString(Structure):
"""Helper for transforming CXString results."""
_fields_ = [("spelling", c_char_p), ("free", c_int)]
def __del__(self):
_CXString_dispose(self)
@staticmethod
def from_result(res, fn, args):
assert isinstance(res, _CXString)
return _CXString_getCString(res)
class SourceLocation(Structure):
"""
A SourceLocation represents a particular location within a source file.
"""
_fields_ = [("ptr_data", c_void_p), ("int_data", c_uint)]
def init(self):
"""
Initialize the source location, setting its file, line and column.
"""
f, l, c = c_object_p(), c_uint(), c_uint()
SourceLocation_loc(self, byref(f), byref(l), byref(c))
f = File(f) if f else None
self.file, self.line, self.column = f, int(l.value), int(c.value)
return self
def __repr__(self):
return "<SourceLocation file %r, line %r, column %r>" % (
self.file.name if self.file else None, self.line, self.column)
class SourceRange(Structure):
"""
A SourceRange describes a range of source locations within the source
code.
"""
_fields_ = [
("ptr_data", c_void_p),
("begin_int_data", c_uint),
("end_int_data", c_uint)]
@property
def start(self):
"""
Return a SourceLocation representing the first character within a
source range.
"""
return SourceRange_start(self).init()
@property
def end(self):
"""
Return a SourceLocation representing the last character within a
source range.
"""
return SourceRange_end(self).init()
class Cursor(Structure):
"""
The Cursor class represents a reference to an element within the AST. It
acts as a kind of iterator.
"""
_fields_ = [("kind", c_int), ("data", c_void_p * 3)]
def __eq__(self, other):
return Cursor_eq(self, other)
def __ne__(self, other):
return not Cursor_eq(self, other)
def is_declaration(self):
"""Return True if the cursor points to a declaration."""
return Cursor_is_decl(self.kind)
def is_reference(self):
"""Return True if the cursor points to a reference."""
return Cursor_is_ref(self.kind)
def is_expression(self):
"""Return True if the cursor points to an expression."""
return Cursor_is_expr(self.kind)
def is_statement(self):
"""Return True if the cursor points to a statement."""
return Cursor_is_stmt(self.kind)
def is_translation_unit(self):
"""Return True if the cursor points to a translation unit."""
return Cursor_is_tu(self.kind)
def is_invalid(self):
"""Return True if the cursor points to an invalid entity."""
return Cursor_is_inv(self.kind)
def is_definition(self):
"""
Returns true if the declaration pointed at by the cursor is also a
definition of that entity.
"""
return Cursor_is_def(self)
def get_definition(self):
"""
If the cursor is a reference to a declaration or a declaration of
some entity, return a cursor that points to the definition of that
entity.
"""
# TODO: Should probably check that this is either a reference or
# declaration prior to issuing the lookup.
return Cursor_def(self)
@property
def spelling(self):
"""Return the spelling of the entity pointed at by the cursor."""
if not self.is_declaration():
# FIXME: This should be documented in Index.h
raise ValueError("Cursor does not refer to a Declaration")
return Cursor_spelling(self)
@property
def location(self):
"""
Return the source location (the starting character) of the entity
pointed at by the cursor.
"""
return Cursor_loc(self).init()
@property
def extent(self):
"""
Return the source range (the range of text) occupied by the entity
pointed at by the cursor.
"""
return Cursor_extent(self)
def get_children(self):
"""Return an iterator for the accessing the children of this cursor."""
# FIXME: Expose iteration from CIndex, PR6125.
def visitor(child, parent, children):
# FIXME: Document this assertion in API.
# FIXME: There should just be an isNull method.
assert child != Cursor_null()
children.append(child)
return 1 # continue
children = []
Cursor_visit(self, Callback(visitor), children)
return iter(children)
@staticmethod
def from_result(res, fn, args):
assert isinstance(res, Cursor)
# FIXME: There should just be an isNull method.
if res == Cursor_null():
return None
return res
## CIndex Objects ##
# CIndex objects (derived from ClangObject) are essentially lightweight
# wrappers attached to some underlying object, which is exposed via CIndex as
# a void*.
class ClangObject(object):
"""
A helper for Clang objects. This class helps act as an intermediary for
the ctypes library and the Clang CIndex library.
"""
def __init__(self, obj):
assert isinstance(obj, c_object_p) and obj
self.obj = self._as_parameter_ = obj
def from_param(self):
return self._as_parameter_
class Index(ClangObject):
"""
The Index type provides the primary interface to the Clang CIndex library,
primarily by providing an interface for reading and parsing translation
units.
"""
@staticmethod
def create(excludeDecls=False, displayDiags=False):
"""
Create a new Index.
Parameters:
excludeDecls -- Exclude local declarations from translation units.
displayDiags -- Display diagnostics during translation unit creation.
"""
return Index(Index_create(excludeDecls, displayDiags))
def __del__(self):
Index_dispose(self)
def read(self, path):
"""Load the translation unit from the given AST file."""
return TranslationUnit.read(self, path)
def parse(self, path, args = []):
"""
Load the translation unit from the given source code file by running
clang and generating the AST before loading. Additional command line
parameters can be passed to clang via the args parameter.
"""
return TranslationUnit.parse(self, path, args)
class TranslationUnit(ClangObject):
"""
The TranslationUnit class represents a source code translation unit and
provides read-only access to its top-level declarations.
"""
def __del__(self):
TranslationUnit_dispose(self)
@property
def cursor(self):
"""Retrieve the cursor that represents the given translation unit."""
return TranslationUnit_cursor(self)
@property
def spelling(self):
"""Get the original translation unit source file name."""
return TranslationUnit_spelling(self)
@staticmethod
def read(ix, path):
"""Create a translation unit from the given AST file."""
ptr = TranslationUnit_read(ix, path)
return TranslationUnit(ptr) if ptr else None
@staticmethod
def parse(ix, path, args = []):
"""
Construct a translation unit from the given source file, applying
the given command line argument.
"""
# TODO: Support unsaved files.
argc, argv = len(args), create_string_vector(args)
ptr = TranslationUnit_parse(ix, path, argc, byref(argv), 0, 0)
return TranslationUnit(ptr) if ptr else None
class File(ClangObject):
"""
The File class represents a particular source file that is part of a
translation unit.
"""
@property
def name(self):
"""Return the complete file and path name of the file, if valid."""
return File_name(self)
@property
def time(self):
"""Return the last modification time of the file, if valid."""
return File_time(self)
# Additional Functions and Types
# Wrap calls to TranslationUnit._load and Decl._load.
Callback = CFUNCTYPE(c_int, Cursor, Cursor, py_object)
# String Functions
_CXString_dispose = lib.clang_disposeString
_CXString_dispose.argtypes = [_CXString]
_CXString_getCString = lib.clang_getCString
_CXString_getCString.argtypes = [_CXString]
_CXString_getCString.restype = c_char_p
# Source Location Functions
SourceLocation_loc = lib.clang_getInstantiationLocation
SourceLocation_loc.argtypes = [SourceLocation, POINTER(c_object_p),
POINTER(c_uint), POINTER(c_uint)]
# Source Range Functions
SourceRange_start = lib.clang_getRangeStart
SourceRange_start.argtypes = [SourceRange]
SourceRange_start.restype = SourceLocation
SourceRange_end = lib.clang_getRangeEnd
SourceRange_end.argtypes = [SourceRange]
SourceRange_end.restype = SourceLocation
# Cursor Functions
# TODO: Implement this function
Cursor_get = lib.clang_getCursor
Cursor_get.argtypes = [TranslationUnit, SourceLocation]
Cursor_get.restype = Cursor
Cursor_null = lib.clang_getNullCursor
Cursor_null.restype = Cursor
Cursor_kind = lib.clang_getCursorKind
Cursor_kind.argtypes = [Cursor]
Cursor_kind.restype = c_int
# FIXME: Not really sure what a USR is or what this function actually does...
Cursor_usr = lib.clang_getCursorUSR
Cursor_is_decl = lib.clang_isDeclaration
Cursor_is_decl.argtypes = [CursorKind]
Cursor_is_decl.restype = bool
Cursor_is_ref = lib.clang_isReference
Cursor_is_ref.argtypes = [CursorKind]
Cursor_is_ref.restype<
|