Index: clang/bindings/python/clang/__init__.py =================================================================== --- clang/bindings/python/clang/__init__.py +++ clang/bindings/python/clang/__init__.py @@ -20,13 +20,5 @@ Bindings for the Clang indexing library. """ - -# Python 3 uses unicode for strings. The bindings, in particular the interaction -# with ctypes, need modifying to handle conversions between unicode and -# c-strings. -import sys -if sys.version_info[0] != 2: - raise Exception("Only Python 2 is supported.") - __all__ = ['cindex'] Index: clang/bindings/python/clang/cindex.py =================================================================== --- clang/bindings/python/clang/cindex.py +++ clang/bindings/python/clang/cindex.py @@ -67,6 +67,60 @@ import clang.enumerations +import sys +if sys.version_info[0] == 3: + # Python 3 strings are unicode, translate them to/from utf8 for C-interop. + class c_interop_string(c_char_p): + + def __init__(self, p=None): + if p is None: + p = "" + if isinstance(p, str): + p = p.encode("utf8") + super(c_char_p, self).__init__(p) + + def __str__(self): + return self.value + + @property + def value(self): + if super(c_char_p, self).value is None: + return None + return super(c_char_p, self).value.decode("utf8") + + @classmethod + def from_param(cls, param): + if isinstance(param, str): + return cls(param) + if isinstance(param, bytes): + return cls(param) + raise TypeError("Cannot convert '{}' to '{}'".format(type(param).__name__, cls.__name__)) + + @staticmethod + def to_python_string(x, *args): + return x.value + + def b(x): + if isinstance(x, bytes): + return x + return x.encode('utf8') + + xrange = range + +elif sys.version_info[0] == 2: + # Python 2 strings are utf8 byte strings, no translation is needed for + # C-interop. + c_interop_string = c_char_p + + def _to_python_string(x, *args): + return x + + c_interop_string.to_python_string = staticmethod(_to_python_string) + + def b(x): + return x + + # 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 @@ -157,6 +211,7 @@ assert isinstance(res, _CXString) return conf.lib.clang_getCString(res) + class SourceLocation(Structure): """ A SourceLocation represents a particular location within a source file. @@ -596,7 +651,7 @@ @staticmethod def get_all_kinds(): """Return all CursorKind enumeration instances.""" - return filter(None, CursorKind._kinds) + return [x for x in CursorKind._kinds if not x is None] def is_declaration(self): """Test if this is a declaration kind.""" @@ -2128,7 +2183,7 @@ """ Retrieve the offset of a field in the record. """ - return conf.lib.clang_Type_getOffsetOf(self, c_char_p(fieldname)) + return conf.lib.clang_Type_getOffsetOf(self, fieldname) def get_ref_qualifier(self): """ @@ -2233,13 +2288,13 @@ self.__kindNumberCache = -1 def __repr__(self): - return "{'" + self.spelling + "', " + str(self.kind) + "}" + return "{'" + str(self.spelling) + "', " + str(self.kind) + "}" @CachedProperty def spelling(self): if self.__kindNumber in SpellingCache: return SpellingCache[self.__kindNumber] - return conf.lib.clang_getCompletionChunkText(self.cs, self.key).spelling + return conf.lib.clang_getCompletionChunkText(self.cs, self.key) # We do not use @CachedProperty here, as the manual implementation is # apparently still significantly faster. Please profile carefully if you @@ -2345,7 +2400,7 @@ return " | ".join([str(a) for a in self]) \ + " || Priority: " + str(self.priority) \ + " || Availability: " + str(self.availability) \ - + " || Brief comment: " + str(self.briefComment.spelling) + + " || Brief comment: " + str(self.briefComment) availabilityKinds = { 0: CompletionChunk.Kind("Available"), @@ -2542,7 +2597,7 @@ args_array = None if len(args) > 0: - args_array = (c_char_p * len(args))(* args) + args_array = (c_char_p * len(args))(*[b(x) for x in args]) unsaved_array = None if len(unsaved_files) > 0: @@ -2551,11 +2606,11 @@ if hasattr(contents, "read"): contents = contents.read() - unsaved_array[i].name = name - unsaved_array[i].contents = contents + unsaved_array[i].name = b(name) + unsaved_array[i].contents = b(contents) unsaved_array[i].length = len(contents) - ptr = conf.lib.clang_parseTranslationUnit(index, filename, args_array, + ptr = conf.lib.clang_parseTranslationUnit(index, b(filename), args_array, len(args), unsaved_array, len(unsaved_files), options) @@ -2797,8 +2852,8 @@ print(value) if not isinstance(value, str): raise TypeError('Unexpected unsaved file contents.') - unsaved_files_array[i].name = name - unsaved_files_array[i].contents = value + unsaved_files_array[i].name = b(name) + unsaved_files_array[i].contents = b(value) unsaved_files_array[i].length = len(value) ptr = conf.lib.clang_codeCompleteAt(self, path, line, column, unsaved_files_array, len(unsaved_files), options) @@ -2833,7 +2888,7 @@ @property def name(self): """Return the complete file and path name of the file.""" - return conf.lib.clang_getCString(conf.lib.clang_getFileName(self)) + return conf.lib.clang_getFileName(self) @property def time(self): @@ -3064,7 +3119,7 @@ [c_object_p]), ("clang_CompilationDatabase_fromDirectory", - [c_char_p, POINTER(c_uint)], + [c_interop_string, POINTER(c_uint)], c_object_p, CompilationDatabase.from_result), @@ -3074,7 +3129,7 @@ CompileCommands.from_result), ("clang_CompilationDatabase_getCompileCommands", - [c_object_p, c_char_p], + [c_object_p, c_interop_string], c_object_p, CompileCommands.from_result), @@ -3109,7 +3164,7 @@ c_uint), ("clang_codeCompleteAt", - [TranslationUnit, c_char_p, c_int, c_int, c_void_p, c_int, c_int], + [TranslationUnit, c_interop_string, c_int, c_int, c_void_p, c_int, c_int], POINTER(CCRStructure)), ("clang_codeCompleteGetDiagnostic", @@ -3125,7 +3180,7 @@ c_object_p), ("clang_createTranslationUnit", - [Index, c_char_p], + [Index, c_interop_string], c_object_p), ("clang_CXXConstructor_isConvertingConstructor", @@ -3215,7 +3270,8 @@ ("clang_formatDiagnostic", [Diagnostic, c_uint], - _CXString), + _CXString, + _CXString.from_result), ("clang_getArgType", [Type, c_uint], @@ -3255,7 +3311,8 @@ ("clang_getCompletionBriefComment", [c_void_p], - _CXString), + _CXString, + _CXString.from_result), ("clang_getCompletionChunkCompletionString", [c_void_p, c_int], @@ -3267,7 +3324,8 @@ ("clang_getCompletionChunkText", [c_void_p, c_int], - _CXString), + _CXString, + _CXString.from_result), ("clang_getCompletionPriority", [c_void_p], @@ -3275,7 +3333,8 @@ ("clang_getCString", [_CXString], - c_char_p), + c_interop_string, + c_interop_string.to_python_string), ("clang_getCursor", [TranslationUnit, SourceLocation], @@ -3422,12 +3481,13 @@ Type.from_result), ("clang_getFile", - [TranslationUnit, c_char_p], + [TranslationUnit, c_interop_string], c_object_p), ("clang_getFileName", [File], - _CXString), # TODO go through _CXString.from_result? + _CXString, + _CXString.from_result), ("clang_getFileTime", [File], @@ -3551,7 +3611,8 @@ ("clang_getTUResourceUsageName", [c_uint], - c_char_p), + c_interop_string, + c_interop_string.to_python_string), ("clang_getTypeDeclaration", [Type], @@ -3646,7 +3707,7 @@ bool), ("clang_parseTranslationUnit", - [Index, c_char_p, c_void_p, c_int, c_void_p, c_int, c_int], + [Index, c_interop_string, c_void_p, c_int, c_void_p, c_int, c_int], c_object_p), ("clang_reparseTranslationUnit", @@ -3654,7 +3715,7 @@ c_int), ("clang_saveTranslationUnit", - [TranslationUnit, c_char_p, c_uint], + [TranslationUnit, c_interop_string, c_uint], c_int), ("clang_tokenize", @@ -3726,7 +3787,7 @@ Type.from_result), ("clang_Type_getOffsetOf", - [Type, c_char_p], + [Type, c_interop_string], c_longlong), ("clang_Type_getSizeOf", @@ -3785,7 +3846,8 @@ def register(item): return register_function(lib, item, ignore_errors) - map(register, functionList) + for f in functionList: + register(f) class Config: library_path = None Index: clang/bindings/python/tests/cindex/test_translation_unit.py =================================================================== --- clang/bindings/python/tests/cindex/test_translation_unit.py +++ clang/bindings/python/tests/cindex/test_translation_unit.py @@ -59,9 +59,12 @@ assert spellings[-1] == 'y' def test_unsaved_files_2(): - import StringIO + try: + from StringIO import StringIO + except: + from io import StringIO tu = TranslationUnit.from_source('fake.c', unsaved_files = [ - ('fake.c', StringIO.StringIO('int x;'))]) + ('fake.c', StringIO('int x;'))]) spellings = [c.spelling for c in tu.cursor.get_children()] assert spellings[-1] == 'x'