1#!/usr/bin/env python3 2""" 3# Created: Mon Jun 20 17:45:39 2022 (-0400) 4# @author: Jacob Faibussowitsch 5""" 6from __future__ import annotations 7 8from .._typing import * 9 10import enum 11import ctypes 12import clang.cindex as clx # type: ignore[import] 13 14class CXTranslationUnit(enum.IntFlag): 15 """ 16 clang.cindex.TranslationUnit does not have all latest flags 17 18 see: https://clang.llvm.org/doxygen/group__CINDEX__TRANSLATION__UNIT.html 19 """ 20 NONE = 0x0 21 DetailedPreprocessingRecord = 0x01 22 Incomplete = 0x02 23 PrecompiledPreamble = 0x04 24 CacheCompletionResults = 0x08 25 ForSerialization = 0x10 26 SkipFunctionBodies = 0x40 27 IncludeBriefCommentsInCodeCompletion = 0x80 28 CreatePreambleOnFirstParse = 0x100 29 KeepGoing = 0x200 30 SingleFileParse = 0x400 31 LimitSkipFunctionBodiesToPreamble = 0x800 32 IncludeAttributedTypes = 0x1000 33 VisitImplicitAttributes = 0x2000 34 IgnoreNonErrorsFromIncludedFiles = 0x4000 35 RetainExcludedConditionalBlocks = 0x8000 36 37# clang options used for parsing files 38base_clang_options = ( 39 CXTranslationUnit.PrecompiledPreamble | 40 #CXTranslationUnit.DetailedPreprocessingRecord | 41 CXTranslationUnit.SkipFunctionBodies | 42 CXTranslationUnit.LimitSkipFunctionBodiesToPreamble 43) 44 45# clang options for creating the precompiled megaheader 46base_pch_clang_options = ( 47 CXTranslationUnit.CreatePreambleOnFirstParse | 48 CXTranslationUnit.Incomplete | 49 CXTranslationUnit.ForSerialization | 50 CXTranslationUnit.KeepGoing 51) 52 53# Cursors that may be attached to function-like usage 54clx_func_call_cursor_kinds = { 55 clx.CursorKind.FUNCTION_DECL, 56 clx.CursorKind.CALL_EXPR 57} 58 59# Cursors that may be attached to mathematical operations or types 60clx_math_cursor_kinds = { 61 clx.CursorKind.INTEGER_LITERAL, 62 clx.CursorKind.UNARY_OPERATOR, 63 clx.CursorKind.BINARY_OPERATOR 64} 65 66# Cursors that contain base literal types 67clx_literal_cursor_kinds = { 68 clx.CursorKind.INTEGER_LITERAL, 69 clx.CursorKind.STRING_LITERAL 70} 71 72# Cursors that may be attached to casting 73clx_cast_cursor_kinds = { 74 clx.CursorKind.CSTYLE_CAST_EXPR, 75 clx.CursorKind.CXX_STATIC_CAST_EXPR, 76 clx.CursorKind.CXX_DYNAMIC_CAST_EXPR, 77 clx.CursorKind.CXX_REINTERPRET_CAST_EXPR, 78 clx.CursorKind.CXX_CONST_CAST_EXPR, 79 clx.CursorKind.CXX_FUNCTIONAL_CAST_EXPR 80} 81 82# Cursors that may be attached when types are converted 83clx_conversion_cursor_kinds = clx_cast_cursor_kinds | {clx.CursorKind.UNEXPOSED_EXPR} 84 85clx_var_token_kinds = {clx.TokenKind.IDENTIFIER} 86 87clx_function_type_kinds = {clx.TypeKind.FUNCTIONPROTO, clx.TypeKind.FUNCTIONNOPROTO} 88 89# General Array types, note this doesn't contain the pointer type since that is usually handled 90# differently 91clx_array_type_kinds = { 92 clx.TypeKind.INCOMPLETEARRAY, 93 clx.TypeKind.CONSTANTARRAY, 94 clx.TypeKind.VARIABLEARRAY 95} 96 97clx_pointer_type_kinds = clx_array_type_kinds | {clx.TypeKind.POINTER} 98 99# Specific types 100clx_enum_type_kinds = {clx.TypeKind.ENUM} 101clx_bool_type_kinds = {clx.TypeKind.BOOL} 102clx_char_type_kinds = {clx.TypeKind.CHAR_S, clx.TypeKind.UCHAR} 103clx_mpiint_type_kinds = {clx.TypeKind.INT} 104clx_int_type_kinds = clx_enum_type_kinds | clx_mpiint_type_kinds | { 105 clx.TypeKind.USHORT, 106 clx.TypeKind.SHORT, 107 clx.TypeKind.UINT, 108 clx.TypeKind.LONG, 109 clx.TypeKind.ULONG, 110 clx.TypeKind.LONGLONG, 111 clx.TypeKind.ULONGLONG 112} 113 114clx_real_type_kinds = { 115 clx.TypeKind.FLOAT, 116 clx.TypeKind.DOUBLE, 117 clx.TypeKind.LONGDOUBLE, 118 clx.TypeKind.FLOAT128 119} 120 121clx_scalar_type_kinds = clx_real_type_kinds | {clx.TypeKind.COMPLEX} 122 123_T = TypeVar('_T') 124_U = TypeVar('_U', covariant=True) 125 126class CTypesCallable(Protocol[_T, _U]): 127 # work around a bug in mypy: 128 # error: "CTypesCallable[_T, _U]" has no attribute "__name__" 129 __name__: str 130 131 @property 132 def argtypes(self) -> Sequence[type[_T]]: ... 133 134 def __call__(*args: _T) -> _U: ... 135 136class ClangFunction(Generic[_T, _U]): 137 r"""A wrapper to enable safely calling a clang function from python. 138 139 Automatically check the return-type (if it is some kind of int) and raises a RuntimeError if an 140 error is detected 141 """ 142 __slots__ = ('_function',) 143 144 _function: CTypesCallable[_T, _U] 145 146 def __init__(self, function: CTypesCallable[_T, _U]) -> None: 147 r"""Construct a `ClangFunction` 148 149 Parameters 150 ---------- 151 function : callable 152 the underlying clang function 153 """ 154 self._function = function 155 return 156 157 def __getattr__(self, attr: str) -> Any: 158 return getattr(self._function, attr) 159 160 # Unfortunately, this type-hint does not really do what we want yet... it says that 161 # every entry in *args must be of type _T. This is both good and bad: 162 # 163 # The good news is that if len(*args) == 1, or all arguments are indeed the same type, 164 # then this __call__() will be properly type checked. 165 # 166 # The bad new is if *args does take multiple different argument types, then _T 167 # will be deduced to Any in get_clang_function(), and this call will be completely 168 # unchecked. At least the type checkers won't through spurious warnings though... 169 def __call__(self, *args, check: bool = True) -> _U: 170 r"""Invoke the clang function 171 172 Parameters 173 ---------- 174 *args : 175 arguments to pass to the clang function 176 check : optional 177 if the return type is ctype.c_uint, check that it is 0 178 179 Returns 180 ------- 181 ret : 182 the return value of the clang function 183 184 Raises 185 ------ 186 ValueError 187 if the clang function is called with the wrong number of Arguments 188 TypeError 189 if the clang function was called with the wrong argument types 190 RuntimeError 191 if the clang function returned a nonzero exit code 192 """ 193 if len(args) != len(self._function.argtypes): 194 mess = f'Trying to call {self._function.__name__}(). Wrong number of arguments for function, expected {len(self._function.argtypes)} got {len(args)}' 195 raise ValueError(mess) 196 for i, (arg, expected) in enumerate(zip(args, self._function.argtypes)): 197 if type(arg) != expected: 198 mess = f'Trying to call {self._function.__name__}(). Argument type for argument #{i} does not match. Expected {expected}, got {type(arg)}' 199 raise TypeError(mess) 200 ret = self._function(*args) 201 if check and isinstance(ret, int) and ret != 0: 202 raise RuntimeError(f'{self._function.__name__}() returned nonzero exit code {ret}') 203 return ret 204 205@overload 206def get_clang_function(name: str, arg_types: Sequence[type[_T]]) -> ClangFunction[_T, ctypes.c_uint]: 207 ... 208 209@overload 210def get_clang_function(name: str, arg_types: Sequence[type[_T]], ret_type: type[_U]) -> ClangFunction[_T, _U]: 211 ... 212 213def get_clang_function(name: str, arg_types: Sequence[type[_T]], ret_type: Optional[type[_U]] = None) -> ClangFunction[_T, _U]: 214 r"""Get (or register) the clang function RET_TYPE (NAME *)(ARG_TYPES...) 215 216 A useful helper routine to reduce verbiage when retrieving a clang function which may or may not 217 already be exposed by clang.cindex 218 219 Parameters 220 ---------- 221 name : 222 the name of the clang function 223 arg_types : 224 the argument types of the clang function 225 ret_type : optional 226 the return type of the clang function, or ctypes.c_uint if None 227 228 Returns 229 ------- 230 clang_func : 231 the callable clang function 232 """ 233 if ret_type is None: 234 # cast needed, otherwise 235 # 236 # error: Incompatible types in assignment (expression has type "Type[c_uint]", 237 # variable has type "Optional[Type[_U]]") 238 ret_type = TYPE_CAST(type[_U], ctypes.c_uint) 239 240 clxlib = clx.conf.lib 241 try: 242 func = getattr(clxlib, name) 243 if (func.argtypes is None) and (func.errcheck is None): 244 # if this hasn't been registered before these will be none 245 raise AttributeError 246 except AttributeError: 247 # have to do the book-keeping ourselves since it may not be properly hooked up 248 clx.register_function(clxlib, (name, arg_types, ret_type), False) 249 func = getattr(clxlib, name) 250 return ClangFunction(TYPE_CAST(CTypesCallable[_T, _U], func)) 251