1## -*- python -*- 2## pylint: disable-msg=W0142,R0921 3 4""" 5Base classes for all parameter/return type handlers, 6and base interfaces for wrapper generators. 7""" 8 9from pybindgen.typehandlers import codesink 10import warnings 11from pybindgen.typehandlers import ctypeparser 12import sys 13 14PY3 = (sys.version_info[0] >= 3) 15if PY3: 16 import types 17 string_types = str, 18else: 19 string_types = basestring 20 21import logging 22logger = logging.getLogger("pybindgen.typehandlers") 23 24 25try: 26 all 27except NameError: # for compatibility with Python < 2.5 28 def all(iterable): 29 "Returns True if all elements are true" 30 for element in iterable: 31 if not element: 32 return False 33 return True 34try: 35 set 36except NameError: 37 from sets import Set as set # Python 2.3 fallback 38 39 40 41 42class CodegenErrorBase(Exception): 43 pass 44 45class NotSupportedError(CodegenErrorBase): 46 """Exception that is raised when declaring an interface configuration 47 that is not supported or not implemented.""" 48 49class CodeGenerationError(CodegenErrorBase): 50 """Exception that is raised when wrapper generation fails for some reason.""" 51 52class TypeLookupError(CodegenErrorBase): 53 """Exception that is raised when lookup of a type handler fails""" 54 55class TypeConfigurationError(CodegenErrorBase): 56 """Exception that is raised when a type handler does not find some 57 information it needs, such as owernship transfer semantics.""" 58 59 60def join_ctype_and_name(ctype, name): 61 """ 62 Utility method that joins a C type and a variable name into 63 a single string 64 65 >>> join_ctype_and_name('void*', 'foo') 66 'void *foo' 67 >>> join_ctype_and_name('void *', 'foo') 68 'void *foo' 69 >>> join_ctype_and_name("void**", "foo") 70 'void **foo' 71 >>> join_ctype_and_name("void **", "foo") 72 'void **foo' 73 >>> join_ctype_and_name('C*', 'foo') 74 'C *foo' 75 """ 76 if ctype[-1] == '*': 77 for i in range(-1, -len(ctype) - 1, -1): 78 if ctype[i] != '*': 79 if ctype[i] == ' ': 80 return "".join([ctype[:i+1], ctype[i+1:], name]) 81 else: 82 return "".join([ctype[:i+1], ' ', ctype[i+1:], name]) 83 raise ValueError((ctype, name)) 84 else: 85 return " ".join([ctype, name]) 86 87 88class CodeBlock(object): 89 '''An intelligent code block that keeps track of cleanup actions. 90 This object is to be used by TypeHandlers when generating code.''' 91 92 class CleanupHandle(object): 93 """Handle for some cleanup code""" 94 __slots__ = ['code_block', 'position'] 95 def __init__(self, code_block, position): 96 """Create a handle given code_block and position""" 97 self.code_block = code_block 98 self.position = position 99 100 def __cmp__(self, other): 101 comp = cmp(self.code_block, other.code_block) 102 if comp: 103 return comp 104 return cmp(self.position, other.position) 105 106 def cancel(self): 107 """Cancel the cleanup code""" 108 self.code_block.remove_cleanup_code(self) 109 110 def get_position(self): 111 "returns the cleanup code relative position" 112 return self.position 113 114 115 def __init__(self, error_return, declarations, predecessor=None): 116 ''' 117 CodeBlock constructor 118 119 >>> block = CodeBlock("return NULL;", DeclarationsScope()) 120 >>> block.write_code("foo();") 121 >>> cleanup1 = block.add_cleanup_code("clean1();") 122 >>> cleanup2 = block.add_cleanup_code("clean2();") 123 >>> cleanup3 = block.add_cleanup_code("clean3();") 124 >>> cleanup2.cancel() 125 >>> block.write_error_check("error()", "error_clean()") 126 >>> block.write_code("bar();") 127 >>> block.write_cleanup() 128 >>> print block.sink.flush().rstrip() 129 foo(); 130 if (error()) { 131 error_clean() 132 clean3(); 133 clean1(); 134 return NULL; 135 } 136 bar(); 137 clean3(); 138 clean1(); 139 140 :param error_return: code that is generated on error conditions 141 (detected by write_error_check()); normally 142 it returns from the wrapper function, 143 e.g. return NULL; 144 :param predecessor: optional predecessor code block; a 145 predecessor is used to search for additional 146 cleanup actions. 147 ''' 148 assert isinstance(declarations, DeclarationsScope) 149 assert predecessor is None or isinstance(predecessor, CodeBlock) 150 self.sink = codesink.MemoryCodeSink() 151 self.predecessor = predecessor 152 self._cleanup_actions = {} 153 self._last_cleanup_position = 0 154 self.error_return = error_return 155 self.declarations = declarations 156 157 def clear(self): 158 self._cleanup_actions = {} 159 self._last_cleanup_position = 0 160 self.sink = codesink.MemoryCodeSink() 161 162 def declare_variable(self, type_, name, initializer=None, array=None): 163 """ 164 Calls declare_variable() on the associated DeclarationsScope object. 165 """ 166 if ':' in name: 167 raise ValueError("invalid variable name: %s " % name) 168 return self.declarations.declare_variable(type_, name, initializer, array) 169 170 def write_code(self, code): 171 '''Write out some simple code''' 172 self.sink.writeln(code) 173 174 def indent(self, level=4): 175 '''Add a certain ammount of indentation to all lines written 176 from now on and until unindent() is called''' 177 self.sink.indent(level) 178 179 def unindent(self): 180 '''Revert indentation level to the value before last indent() call''' 181 self.sink.unindent() 182 183 def add_cleanup_code(self, cleanup_code): 184 '''Add a chunk of code used to cleanup previously allocated resources 185 186 Returns a handle used to cancel the cleanup code 187 ''' 188 self._last_cleanup_position += 1 189 handle = self.CleanupHandle(self, self._last_cleanup_position) 190 self._cleanup_actions[handle.get_position()] = cleanup_code 191 return handle 192 193 def remove_cleanup_code(self, handle): 194 '''Remove cleanup code previously added with add_cleanup_code() 195 ''' 196 assert isinstance(handle, self.CleanupHandle) 197 del self._cleanup_actions[handle.get_position()] 198 199 def get_cleanup_code(self): 200 '''return a new list with all cleanup actions, including the 201 ones from predecessor code blocks; Note: cleanup actions are 202 executed in reverse order than when they were added.''' 203 cleanup = [] 204 items = list(self._cleanup_actions.items()) 205 items.sort() 206 for dummy, code in items: 207 cleanup.append(code) 208 cleanup.reverse() 209 if self.predecessor is not None: 210 cleanup.extend(self.predecessor.get_cleanup_code()) 211 return cleanup 212 213 def write_error_check(self, failure_expression, failure_cleanup=None): 214 '''Add a chunk of code that checks for a possible error 215 216 :param failure_expression: C boolean expression that is true when 217 an error occurred 218 :param failure_cleanup: optional extra cleanup code to write only 219 for the the case when failure_expression is 220 true; this extra cleanup code comes before 221 all other cleanup code previously registered. 222 ''' 223 self.sink.writeln("if (%s) {" % (failure_expression,)) 224 self.sink.indent() 225 if failure_cleanup is not None: 226 self.sink.writeln(failure_cleanup) 227 self.write_error_return() 228 self.sink.unindent() 229 self.sink.writeln("}") 230 231 def write_cleanup(self): 232 """Write the current cleanup code.""" 233 for cleanup_action in self.get_cleanup_code(): 234 self.sink.writeln(cleanup_action) 235 236 def write_error_return(self): 237 '''Add a chunk of code that cleans up and returns an error. 238 ''' 239 self.write_cleanup() 240 self.sink.writeln(self.error_return) 241 242 243 244class ParseTupleParameters(object): 245 "Object to keep track of PyArg_ParseTuple (or similar) parameters" 246 247 def __init__(self): 248 """ 249 >>> tuple_params = ParseTupleParameters() 250 >>> tuple_params.add_parameter('i', ['&foo'], 'foo') 251 1 252 >>> tuple_params.add_parameter('s', ['&bar'], 'bar', optional=True) 253 2 254 >>> tuple_params.get_parameters() 255 ['"i|s"', '&foo', '&bar'] 256 >>> tuple_params.get_keywords() 257 ['foo', 'bar'] 258 259 >>> tuple_params = ParseTupleParameters() 260 >>> tuple_params.add_parameter('i', ['&foo'], 'foo') 261 1 262 >>> tuple_params.add_parameter('s', ['&bar'], 'bar', prepend=True) 263 2 264 >>> tuple_params.get_parameters() 265 ['"si"', '&bar', '&foo'] 266 >>> tuple_params.get_keywords() 267 ['bar', 'foo'] 268 269 >>> tuple_params = ParseTupleParameters() 270 >>> tuple_params.add_parameter('i', ['&foo']) 271 1 272 >>> print tuple_params.get_keywords() 273 None 274 """ 275 self._parse_tuple_items = [] # (template, param_values, param_name, optional) 276 277 def clear(self): 278 self._parse_tuple_items = [] 279 280 def add_parameter(self, param_template, param_values, param_name=None, 281 prepend=False, optional=False): 282 """ 283 Adds a new parameter specification 284 285 :param param_template: template item, see documentation for 286 PyArg_ParseTuple for more information 287 :param param_values: list of parameters, see documentation 288 for PyArg_ParseTuple for more information 289 :param prepend: whether this parameter should be parsed first 290 :param optional: whether the parameter is optional; note that after 291 the first optional parameter, all remaining 292 parameters must also be optional 293 """ 294 assert isinstance(param_values, list) 295 assert isinstance(param_template, string_types) 296 item = (param_template, param_values, param_name, optional) 297 if prepend: 298 self._parse_tuple_items.insert(0, item) 299 else: 300 self._parse_tuple_items.append(item) 301 return len(self._parse_tuple_items) 302 303 def is_empty(self): 304 return self.get_parameters() == ['""'] 305 306 def get_parameters(self): 307 """ 308 returns a list of parameters to pass into a 309 PyArg_ParseTuple-style function call, the first paramter in 310 the list being the template string. 311 """ 312 template = ['"'] 313 last_was_optional = False 314 for (param_template, dummy, 315 param_name, optional) in self._parse_tuple_items: 316 if last_was_optional and not optional: 317 raise ValueError("Error: optional parameter followed by a non-optional one (%r)" 318 " (debug: self._parse_tuple_parameters=%r)" % (param_name, self._parse_tuple_items)) 319 if not last_was_optional and optional: 320 template.append('|') 321 last_was_optional = True 322 template.append(param_template) 323 template.append('"') 324 params = [''.join(template)] 325 for (dummy, param_values, 326 dummy, dummy) in self._parse_tuple_items: 327 params.extend(param_values) 328 return params 329 330 def get_keywords(self): 331 """ 332 returns list of keywords (parameter names), or None if none of 333 the parameters had a name; should only be called if names were 334 given for all parameters or none of them. 335 """ 336 keywords = [] 337 for (dummy, dummy, name, dummy) in self._parse_tuple_items: 338 if name is None: 339 if keywords: 340 raise ValueError("mixing parameters with and without keywords") 341 else: 342 keywords.append(name) 343 if keywords: 344 if len(keywords) != len(self._parse_tuple_items): 345 raise ValueError("mixing parameters with and without keywords") 346 return keywords 347 else: 348 return None 349 350 351class BuildValueParameters(object): 352 "Object to keep track of Py_BuildValue (or similar) parameters" 353 354 def __init__(self): 355 """ 356 >>> bld = BuildValueParameters() 357 >>> bld.add_parameter('i', [123, 456]) 358 >>> bld.add_parameter('s', ["hello"]) 359 >>> bld.get_parameters() 360 ['"is"', 123, 456, 'hello'] 361 >>> bld = BuildValueParameters() 362 >>> bld.add_parameter('i', [123]) 363 >>> bld.add_parameter('s', ["hello"], prepend=True) 364 >>> bld.get_parameters() 365 ['"si"', 'hello', 123] 366 """ 367 self._build_value_items = [] # (template, param_value, cleanup_handle) 368 369 def clear(self): 370 self._build_value_items = [] 371 372 def add_parameter(self, param_template, param_values, 373 prepend=False, cancels_cleanup=None): 374 """ 375 Adds a new parameter to the Py_BuildValue (or similar) statement. 376 377 :param param_template: template item, see documentation for 378 Py_BuildValue for more information 379 :param param_values: list of C expressions to use as value, see documentation 380 for Py_BuildValue for more information 381 :param prepend: whether this parameter should come first in the tuple being built 382 :param cancels_cleanup: optional handle to a cleanup action, 383 that is removed after the call. Typically 384 this is used for 'N' parameters, which 385 already consume an object reference 386 """ 387 item = (param_template, param_values, cancels_cleanup) 388 if prepend: 389 self._build_value_items.insert(0, item) 390 else: 391 self._build_value_items.append(item) 392 393 def get_parameters(self, force_tuple_creation=False): 394 """returns a list of parameters to pass into a 395 Py_BuildValue-style function call, the first paramter in 396 the list being the template string. 397 398 :param force_tuple_creation: if True, Py_BuildValue is 399 instructed to always create a tuple, even for zero or 1 400 values. 401 """ 402 template = ['"'] 403 if force_tuple_creation: 404 template.append('(') 405 params = [None] 406 for (param_template, param_values, dummy) in self._build_value_items: 407 template.append(param_template) 408 params.extend(param_values) 409 if force_tuple_creation: 410 template.append(')') 411 template.append('"') 412 params[0] = ''.join(template) 413 return params 414 415 def get_cleanups(self): 416 """Get a list of handles to cleanup actions""" 417 return [cleanup for (dummy, dummy, cleanup) in self._build_value_items] 418 419 420class DeclarationsScope(object): 421 """Manages variable declarations in a given scope.""" 422 423 def __init__(self, parent_scope=None): 424 """ 425 Constructor 426 427 >>> scope = DeclarationsScope() 428 >>> scope.declare_variable('int', 'foo') 429 'foo' 430 >>> scope.declare_variable('char*', 'bar') 431 'bar' 432 >>> scope.declare_variable('int', 'foo') 433 'foo2' 434 >>> scope.declare_variable('int', 'foo', '1') 435 'foo3' 436 >>> scope.declare_variable('const char *', 'kwargs', '{"hello", NULL}', '[]') 437 'kwargs' 438 >>> print scope.get_code_sink().flush().rstrip() 439 int foo; 440 char *bar; 441 int foo2; 442 int foo3 = 1; 443 const char *kwargs[] = {"hello", NULL}; 444 445 :param parent_scope: optional 'parent scope'; if given, 446 declarations in this scope will avoid clashing 447 with names in the parent scope, and vice 448 versa. 449 """ 450 self._declarations = codesink.MemoryCodeSink() 451 ## name -> number of variables with that name prefix 452 if parent_scope is None: 453 self.declared_variables = {} 454 else: 455 assert isinstance(parent_scope, DeclarationsScope) 456 self.declared_variables = parent_scope.declared_variables 457 458 def clear(self): 459 self._declarations = codesink.MemoryCodeSink() 460 self.declared_variables.clear() 461 462 def declare_variable(self, type_, name, initializer=None, array=None): 463 """Add code to declare a variable. Returns the actual variable 464 name used (uses 'name' as base, with a number in case of conflict.) 465 466 :param type_: C type name of the variable 467 :param name: base name of the variable; actual name used can be 468 slightly different in case of name conflict. 469 :param initializer: optional, value to initialize the variable with 470 :param array: optional, array size specifiction, e.g. '[]', or '[100]' 471 """ 472 try: 473 num = self.declared_variables[name] 474 except KeyError: 475 num = 0 476 num += 1 477 self.declared_variables[name] = num 478 if num == 1: 479 varname = name 480 else: 481 varname = "%s%i" % (name, num) 482 decl = join_ctype_and_name(type_, varname) 483 if array is not None: 484 decl += array 485 if initializer is not None: 486 decl += ' = ' + initializer 487 self._declarations.writeln(decl + ';') 488 return varname 489 490 def reserve_variable(self, name): 491 """Reserve a variable name, to be used later. 492 493 :param name: base name of the variable; actual name used can be 494 slightly different in case of name conflict. 495 """ 496 try: 497 num = self.declared_variables[name] 498 except KeyError: 499 num = 0 500 num += 1 501 self.declared_variables[name] = num 502 if num == 1: 503 varname = name 504 else: 505 varname = "%s%i" % (name, num) 506 return varname 507 508 def get_code_sink(self): 509 """Returns the internal MemoryCodeSink that holds all declararions.""" 510 return self._declarations 511 512 513 514class ReverseWrapperBase(object): 515 """Generic base for all reverse wrapper generators. 516 517 Reverse wrappers all have the following general structure in common: 518 519 1. 'declarations' -- variable declarations; for compatibility with 520 older C compilers it is very important that all declarations 521 come before any simple statement. Declarations can be added 522 with the add_declaration() method on the 'declarations' 523 attribute. Two standard declarations are always predeclared: 524 '<return-type> retval', unless return-type is void, and 'PyObject 525 \\*py_retval'; 526 527 2. 'code before call' -- this is a code block dedicated to contain 528 all code that is needed before calling into Python; code can be 529 freely added to it by accessing the 'before_call' (a CodeBlock 530 instance) attribute; 531 532 3. 'call into python' -- this is realized by a 533 PyObject_CallMethod(...) or similar Python API call; the list 534 of parameters used in this call can be customized by accessing 535 the 'build_params' (a BuildValueParameters instance) attribute; 536 537 4. 'code after call' -- this is a code block dedicated to contain 538 all code that must come after calling into Python; code can be 539 freely added to it by accessing the 'after_call' (a CodeBlock 540 instance) attribute; 541 542 5. A 'return retval' statement (or just 'return' if return_value is void) 543 544 """ 545 546 NO_GIL_LOCKING = False 547 548 def __init__(self, return_value, parameters, error_return=None): 549 ''' 550 Base constructor 551 552 :param return_value: type handler for the return value 553 :param parameters: a list of type handlers for the parameters 554 555 ''' 556 557 assert isinstance(return_value, TypeHandler) 558 assert isinstance(parameters, list) 559 assert all([isinstance(param, Parameter) for param in parameters]) 560 561 self.return_value = return_value 562 self.parameters = parameters 563 564 if error_return is None: 565 error_return = return_value.get_c_error_return() 566 self.error_return = error_return 567 self.declarations = DeclarationsScope() 568 self.before_call = CodeBlock(error_return, self.declarations) 569 self.after_call = CodeBlock(error_return, self.declarations, 570 predecessor=self.before_call) 571 self.build_params = BuildValueParameters() 572 self.parse_params = ParseTupleParameters() 573 self._generate_gil_code() 574 575 def set_error_return(self, error_return): 576 self.error_return = error_return 577 self.before_call.error_return = error_return 578 self.after_call.error_return = error_return 579 580 def reset_code_generation_state(self): 581 self.declarations.clear() 582 self.before_call.clear() 583 self.after_call.clear() 584 self.build_params.clear() 585 self.parse_params.clear() 586 self._generate_gil_code() 587 588 def _generate_gil_code(self): 589 if self.NO_GIL_LOCKING: 590 return 591 ## reverse wrappers are called from C/C++ code, when the Python GIL may not be held... 592 gil_state_var = self.declarations.declare_variable('PyGILState_STATE', '__py_gil_state') 593 self.before_call.write_code('%s = (PyEval_ThreadsInitialized() ? PyGILState_Ensure() : (PyGILState_STATE) 0);' 594 % gil_state_var) 595 self.before_call.add_cleanup_code('if (PyEval_ThreadsInitialized())\n' 596 ' PyGILState_Release(%s);' % gil_state_var) 597 598 599 def generate_python_call(self): 600 """Generates the code (into self.before_call) to call into 601 Python, storing the result in the variable 'py_retval'; should 602 also check for call error. 603 """ 604 raise NotImplementedError 605 606 def generate(self, code_sink, wrapper_name, decl_modifiers=('static',), 607 decl_post_modifiers=()): 608 """Generate the wrapper 609 610 :param code_sink: a CodeSink object that will receive the code 611 :param wrapper_name: C/C++ identifier of the function/method to generate 612 :param decl_modifiers: list of C/C++ declaration modifiers, e.g. 'static' 613 """ 614 assert isinstance(decl_modifiers, (list, tuple)) 615 assert all([isinstance(mod, string_types) for mod in decl_modifiers]) 616 617 #import sys 618 #print("generate", self, file=sys.stderr) 619 py_retval = self.declarations.declare_variable('PyObject*', 'py_retval') 620 assert py_retval == "py_retval", "py_retval already declared: "\ 621 "generating the same wrapper twice without a reset() in between?" 622 623 if self.return_value.ctype != 'void' \ 624 and not self.return_value.REQUIRES_ASSIGNMENT_CONSTRUCTOR \ 625 and not self.return_value.NO_RETVAL_DECL: 626 self.declarations.declare_variable(self.return_value.ctype, 'retval') 627 628 ## convert the input parameters 629 for param in self.parameters: 630 param.convert_c_to_python(self) 631 632 ## generate_python_call should include something like 633 ## self.after_call.write_error_check('py_retval == NULL') 634 self.generate_python_call() 635 636 ## convert the return value(s) 637 self.return_value.convert_python_to_c(self) 638 639 if self.parse_params.is_empty(): 640 self.before_call.write_error_check('py_retval != Py_None', 641 'PyErr_SetString(PyExc_TypeError, "function/method should return None");') 642 else: 643 ## parse the return value 644 ## this ensures that py_retval is always a tuple 645 self.before_call.write_code('py_retval = Py_BuildValue((char*) "(N)", py_retval);') 646 647 parse_tuple_params = ['py_retval'] 648 parse_params = self.parse_params.get_parameters() 649 assert parse_params[0][0] == '"' 650 parse_params[0] = '(char *) ' + parse_params[0] 651 parse_tuple_params.extend(parse_params) 652 self.before_call.write_error_check('!PyArg_ParseTuple(%s)' % 653 (', '.join(parse_tuple_params),), 654 failure_cleanup='PyErr_Print();') 655 656 ## cleanup and return 657 self.after_call.write_cleanup() 658 if self.return_value.ctype == 'void': 659 self.after_call.write_code('return;') 660 else: 661 self.after_call.write_code('return retval;') 662 663 ## now write out the wrapper function itself 664 665 ## open function 666 retline = list(decl_modifiers) 667 retline.append(self.return_value.ctype) 668 code_sink.writeln(' '.join(retline)) 669 670 params_list = ', '.join([join_ctype_and_name(param.ctype, param.name) 671 for param in self.parameters]) 672 code_sink.writeln("%s(%s)%s" % (wrapper_name, params_list, 673 ' '.join([''] + list(decl_post_modifiers)))) 674 675 ## body 676 code_sink.writeln('{') 677 code_sink.indent() 678 self.declarations.get_code_sink().flush_to(code_sink) 679 code_sink.writeln() 680 self.before_call.sink.flush_to(code_sink) 681 self.after_call.sink.flush_to(code_sink) 682 683 ## close function 684 code_sink.unindent() 685 code_sink.writeln('}') 686 687 688 689class ForwardWrapperBase(object): 690 """Generic base for all forward wrapper generators. 691 692 Forward wrappers all have the following general structure in common: 693 694 1. 'declarations' -- variable declarations; for compatibility 695 with older C compilers it is very important that all 696 declarations come before any simple statement. 697 Declarations can be added with the add_declaration() 698 method on the 'declarations' attribute. Two standard 699 declarations are always predeclared: '<return-type> 700 retval', unless return-type is void, and 'PyObject 701 \\*py_retval'; 702 703 2. 'code before parse' -- code before the 704 PyArg_ParseTupleAndKeywords call; code can be freely added to 705 it by accessing the 'before_parse' (a CodeBlock instance) 706 attribute; 707 708 3. A PyArg_ParseTupleAndKeywords call; uses items from the 709 parse_params object; 710 711 4. 'code before call' -- this is a code block dedicated to contain 712 all code that is needed before calling the C function; code can be 713 freely added to it by accessing the 'before_call' (a CodeBlock 714 instance) attribute; 715 716 5. 'call into C' -- this is realized by a C/C++ call; the list of 717 parameters that should be used is in the 'call_params' wrapper 718 attribute; 719 720 6. 'code after call' -- this is a code block dedicated to contain 721 all code that must come after calling into Python; code can be 722 freely added to it by accessing the 'after_call' (a CodeBlock 723 instance) attribute; 724 725 7. A py_retval = Py_BuildValue(...) call; this call can be 726 customized, so that out/inout parameters can add additional 727 return values, by accessing the 'build_params' (a 728 BuildValueParameters instance) attribute; 729 730 8. Cleanup and return. 731 732 Object constructors cannot return values, and so the step 7 is to 733 be omitted for them. 734 735 """ 736 737 PARSE_TUPLE = 1 738 PARSE_TUPLE_AND_KEYWORDS = 2 739 740 HAVE_RETURN_VALUE = False # change to true if the wrapper 741 # generates a return value even if the 742 # return_value attribute is None 743 744 def __init__(self, return_value, parameters, 745 parse_error_return, error_return, 746 force_parse=None, no_c_retval=False, 747 unblock_threads=False): 748 ''' 749 Base constructor 750 751 :param return_value: type handler for the return value 752 :param parameters: a list of type handlers for the parameters 753 :param parse_error_return: statement to return an error during parameter parsing 754 :param error_return: statement to return an error after parameter parsing 755 :param force_parse: force generation of code to parse parameters even if there are none 756 :param no_c_retval: force the wrapper to not have a C return value 757 :param unblock_threads: generate code to unblock python threads during the C function call 758 ''' 759 assert isinstance(return_value, ReturnValue) or return_value is None 760 assert isinstance(parameters, list) 761 assert all([isinstance(param, Parameter) for param in parameters]) 762 763 self.return_value = return_value 764 self.parameters = parameters 765 self.declarations = DeclarationsScope() 766 self.before_parse = CodeBlock(parse_error_return, self.declarations) 767 self.before_call = CodeBlock(parse_error_return, self.declarations, 768 predecessor=self.before_parse) 769 self.after_call = CodeBlock(error_return, self.declarations, 770 predecessor=self.before_call) 771 self.build_params = BuildValueParameters() 772 self.parse_params = ParseTupleParameters() 773 self.call_params = [] 774 self.force_parse = force_parse 775 self.meth_flags = [] 776 self.unblock_threads = unblock_threads 777 self.no_c_retval = no_c_retval 778 self.overload_index = None 779 self.deprecated = False 780 781 # The following 3 variables describe the C wrapper function 782 # prototype; do not confuse with the python function/method! 783 self.wrapper_actual_name = None # name of the wrapper function/method 784 self.wrapper_return = None # C type expression for the wrapper return 785 self.wrapper_args = None # list of arguments to the wrapper function 786 787 self._init_code_generation_state() 788 789 def _init_code_generation_state(self): 790 if self.return_value is not None or self.HAVE_RETURN_VALUE: 791 self.declarations.declare_variable('PyObject*', 'py_retval') 792 if (not self.no_c_retval and (self.return_value is not None or self.HAVE_RETURN_VALUE) 793 and self.return_value.ctype != 'void' 794 and not self.return_value.REQUIRES_ASSIGNMENT_CONSTRUCTOR 795 and not self.return_value.NO_RETVAL_DECL): 796 self.declarations.declare_variable(str(self.return_value.type_traits.ctype_no_const_no_ref), 'retval') 797 798 self.declarations.reserve_variable('args') 799 self.declarations.reserve_variable('kwargs') 800 801 def reset_code_generation_state(self): 802 self.declarations.clear() 803 self.before_parse.clear() 804 self.before_call.clear() 805 self.after_call.clear() 806 self.build_params.clear() 807 self.parse_params.clear() 808 self.call_params = [] 809 self.meth_flags = [] 810 811 self._init_code_generation_state() 812 813 def set_parse_error_return(self, parse_error_return): 814 self.before_parse.error_return = parse_error_return 815 self.before_call.error_return = parse_error_return 816 817 def generate_call(self): 818 """Generates the code (into self.before_call) to call into 819 Python, storing the result in the variable 'py_retval'; should 820 also check for call error. 821 """ 822 raise NotImplementedError 823 824 def _before_call_hook(self): 825 """ 826 Optional hook that lets subclasses add code after all 827 parameters are parsed, but before the C function/method call. 828 Subclasses may add code to self.before_call. 829 """ 830 pass 831 832 def _before_return_hook(self): 833 """ 834 Optional hook that lets subclasses add code after all 835 parameters are parsed and after the function C return value is 836 processed, but after the python wrapper return value (py_ret) 837 is built and returned. Subclasses may add code to 838 self.after_call, which will be placed before py_ret is 839 created. 840 """ 841 pass 842 843 def write_open_wrapper(self, code_sink, add_static=False): 844 assert self.wrapper_actual_name is not None 845 assert self.wrapper_return is not None 846 assert isinstance(self.wrapper_args, list) 847 if add_static: 848 code_sink.writeln("static " + self.wrapper_return) 849 else: 850 code_sink.writeln(self.wrapper_return) 851 code_sink.writeln("%s(%s)" % (self.wrapper_actual_name, ', '.join(self.wrapper_args))) 852 code_sink.writeln('{') 853 code_sink.indent() 854 855 def write_close_wrapper(self, code_sink): 856 code_sink.unindent() 857 code_sink.writeln('}') 858 859 860 def generate_body(self, code_sink, gen_call_params=()): 861 """Generate the wrapper function body 862 code_sink -- a CodeSink object that will receive the code 863 """ 864 865 if self.unblock_threads: 866 py_thread_state = self.declarations.declare_variable("PyThreadState*", "py_thread_state", "NULL") 867 self.after_call.write_code( 868 "\nif (%s)\n" 869 " PyEval_RestoreThread(%s);\n" % (py_thread_state, py_thread_state)) 870 871 ## convert the input parameters 872 for param in self.parameters: 873 try: 874 param.convert_python_to_c(self) 875 except NotImplementedError: 876 raise CodeGenerationError( 877 'convert_python_to_c method of parameter %s not implemented' 878 % (param.ctype,)) 879 880 if self.deprecated: 881 if isinstance(self.deprecated, string_types): 882 msg = self.deprecated 883 else: 884 msg = "Deprecated" 885 self.before_call.write_error_check( 'PyErr_Warn(PyExc_DeprecationWarning, (char *) "%s")' % msg) 886 887 self._before_call_hook() 888 889 if self.unblock_threads: 890 self.before_call.write_code( 891 "\nif (PyEval_ThreadsInitialized ())\n" 892 " %s = PyEval_SaveThread();\n" 893 % (py_thread_state, )) 894 895 self.generate_call(*gen_call_params) 896 897 params = self.parse_params.get_parameters() 898 assert params[0][0] == '"' 899 params_empty = (params == ['""']) 900 params[0] = '(char *) ' + params[0] 901 keywords = self.parse_params.get_keywords() 902 if not params_empty or self.force_parse != None: 903 self.meth_flags.append("METH_VARARGS") 904 if keywords is None \ 905 and self.force_parse != self.PARSE_TUPLE_AND_KEYWORDS: 906 907 param_list = ['args'] + params 908 self.before_parse.write_error_check('!PyArg_ParseTuple(%s)' % 909 (', '.join(param_list),)) 910 else: 911 if keywords is None: 912 keywords = [] 913 keywords_var = self.declarations.declare_variable( 914 'const char *', 'keywords', 915 '{' + ', '.join(['"%s"' % kw for kw in keywords] + ['NULL']) + '}', 916 '[]') 917 param_list = ['args', 'kwargs', params[0], '(char **) ' + keywords_var] + params[1:] 918 self.before_parse.write_error_check('!PyArg_ParseTupleAndKeywords(%s)' % 919 (', '.join(param_list),)) 920 self.meth_flags.append("METH_KEYWORDS") 921 else: 922 self.meth_flags.append("METH_NOARGS") 923 924 ## convert the return value(s) 925 if self.return_value is None and not self.HAVE_RETURN_VALUE: 926 927 assert self.build_params.get_parameters() == ['""'], \ 928 "this wrapper is not supposed to return values" 929 self._before_return_hook() 930 self.after_call.write_cleanup() 931 932 else: 933 934 if self.return_value is not None: 935 try: 936 self.return_value.convert_c_to_python(self) 937 except NotImplementedError: 938 raise CodeGenerationError( 939 'convert_c_to_python method of return value %s not implemented' 940 % (self.return_value.ctype,)) 941 942 self._before_return_hook() 943 944 params = self.build_params.get_parameters() 945 if params: 946 if params == ['""']: 947 self.after_call.write_code('Py_INCREF(Py_None);') 948 self.after_call.write_code('py_retval = Py_None;') 949 else: 950 assert params[0][0] == '"' 951 params[0] = "(char *) " + params[0] 952 self.after_call.write_code('py_retval = Py_BuildValue(%s);' % 953 (', '.join(params),)) 954 955 ## cleanup and return 956 self.after_call.write_cleanup() 957 self.after_call.write_code('return py_retval;') 958 959 ## now write out the wrapper function body itself 960 self.declarations.get_code_sink().flush_to(code_sink) 961 code_sink.writeln() 962 self.before_parse.sink.flush_to(code_sink) 963 self.before_call.sink.flush_to(code_sink) 964 self.after_call.sink.flush_to(code_sink) 965 966 def get_py_method_def_flags(self): 967 """ 968 Get a list of PyMethodDef flags that should be used for this wrapper. 969 """ 970 flags = set(self.meth_flags) 971 if flags: 972 return list(flags) 973 974 tmp_sink = codesink.NullCodeSink() 975 try: 976# try: 977# self.generate_body(tmp_sink) 978# except CodegenErrorBase: 979# return [] 980# else: 981# return list(set(self.meth_flags)) 982 self.generate_body(tmp_sink) 983 return list(set(self.meth_flags)) 984 finally: 985 self.reset_code_generation_state() 986 987 988class TypeTransformation(object): 989 """ 990 Type transformations are used to register handling of special 991 types that are simple transformation over another type that is 992 already registered. This way, only the original type is 993 registered, and the type transformation only does the necessary 994 adjustments over the original type handler to make it handle the 995 transformed type as well. 996 997 This is typically used to get smart pointer templated types 998 working. 999 1000 """ 1001 1002 def get_untransformed_name(self, name): 1003 """ 1004 Given a transformed named, get the original C type name. 1005 E.g., given a smart pointer transformation, MySmartPointer:: 1006 1007 get_untransformed_name('MySmartPointer<Foo>') -> 'Foo\\*' 1008 """ 1009 raise NotImplementedError 1010 1011 def create_type_handler(self, type_handler_class, *args, **kwargs): 1012 """ 1013 Given a type_handler class, create an instance with proper customization. 1014 1015 :param type_handler_class: type handler class 1016 :param args: arguments 1017 :param kwargs: keywords arguments 1018 """ 1019 raise NotImplementedError 1020 1021 def transform(self, type_handler, declarations, code_block, value): 1022 """ 1023 Transforms a value expression of the original type to an 1024 equivalent value expression in the transformed type. 1025 1026 Example, with the transformation:: 1027 'T\\*' -> 'boost::shared_ptr<T>' 1028 Then:: 1029 transform(wrapper, 'foo') -> 'boost::shared_ptr<%s>(foo)' % type_handler.untransformed_ctype 1030 """ 1031 raise NotImplementedError 1032 1033 def untransform(self, type_handler, declarations, code_block, value): 1034 """ 1035 Transforms a value expression of the transformed type to an 1036 equivalent value expression in the original type. 1037 1038 Example, with the transformation:: 1039 'T\\*' -> 'boost::shared_ptr<T>' 1040 Then:: 1041 untransform(wrapper, 'foo') -> 'foo->get_pointer()' 1042 """ 1043 raise NotImplementedError 1044 1045 1046class NullTypeTransformation(object): 1047 """ 1048 Null type transformation, returns everything unchanged. 1049 """ 1050 def get_untransformed_name(self, name): 1051 "identity transformation" 1052 return name 1053 def create_type_handler(self, type_handler_class, *args, **kwargs): 1054 "identity transformation" 1055 return type_handler_class(*args, **kwargs) 1056 def transform(self, type_handler, declarations, code_block, value): 1057 "identity transformation" 1058 return value 1059 def untransform(self, type_handler, declarations, code_block, value): 1060 "identity transformation" 1061 return value 1062 1063class TypeHandler(object): 1064 SUPPORTS_TRANSFORMATIONS = False 1065 1066 def __init__(self, ctype, is_const=False): 1067 if ctype is None: 1068 self.ctype = None 1069 self.untransformed_ctype = None 1070 self.type_traits = None 1071 else: 1072 if isinstance(ctype, ctypeparser.TypeTraits): 1073 self.type_traits = ctype 1074 if is_const: 1075 warnings.warn("is_const is deprecated, put a 'const' in the C type instead.", DeprecationWarning) 1076 if self.type_traits.type_is_pointer or self.type_traits.type_is_reference: 1077 self.type_traits.make_target_const() 1078 else: 1079 self.type_traits.make_const() 1080 elif isinstance(ctype, string_types): 1081 if is_const: 1082 warnings.warn("is_const is deprecated, put a 'const' in the C type instead.", DeprecationWarning) 1083 self.type_traits = ctypeparser.TypeTraits('const %s' % ctype) 1084 else: 1085 self.type_traits = ctypeparser.TypeTraits(ctype) 1086 else: 1087 raise TypeError 1088 self.ctype = str(self.type_traits.ctype) 1089 self.untransformed_ctype = self.ctype 1090 self.transformation = NullTypeTransformation() 1091 1092 def _get_ctype_no_const(self): 1093 return str(self.type_traits.ctype_no_const) 1094 ctype_no_const = property(_get_ctype_no_const) 1095 1096 def set_tranformation(self, transformation, untransformed_ctype): 1097 warnings.warn("Typo: set_tranformation -> set_transformation", DeprecationWarning, stacklevel=2) 1098 return self.set_transformation(transformation, untransformed_ctype) 1099 1100 def set_transformation(self, transformation, untransformed_ctype): 1101 "Set the type transformation to use in this type handler" 1102 1103 assert isinstance(transformation, TypeTransformation) 1104 assert untransformed_ctype != self.ctype 1105 assert isinstance(self.transformation, NullTypeTransformation) 1106 assert self.SUPPORTS_TRANSFORMATIONS 1107 1108 self.transformation = transformation 1109 self.untransformed_ctype = untransformed_ctype 1110 1111 1112 1113class ReturnValueMeta(type): 1114 "Metaclass for automatically registering parameter type handlers" 1115 def __init__(mcs, name, bases, dict_): 1116 "metaclass __init__" 1117 type.__init__(mcs, name, bases, dict_) 1118 if __debug__: 1119 try: 1120 iter(mcs.CTYPES) 1121 except (TypeError, AttributeError): 1122 sys.stderr.write("ERROR: missing CTYPES on class %s.%s\n" % (mcs.__module__, mcs.__name__)) 1123 1124 for ctype in mcs.CTYPES: 1125 return_type_matcher.register(ctype, mcs) 1126 1127 1128class _ReturnValue(TypeHandler): 1129 '''Abstract base class for all classes dedicated to handle 1130 specific return value types''' 1131 1132 ## list of C type names it can handle 1133 CTYPES = [] 1134 1135 ## whether it supports type transformations 1136 SUPPORTS_TRANSFORMATIONS = False 1137 1138 REQUIRES_ASSIGNMENT_CONSTRUCTOR = False 1139 NO_RETVAL_DECL = False 1140 1141 #@classmethod 1142 def new(cls, *args, **kwargs): 1143 """ 1144 >>> import inttype 1145 >>> isinstance(ReturnValue.new('int'), inttype.IntReturn) 1146 True 1147 """ 1148 if cls is ReturnValue: 1149 ctype = args[0] 1150 type_handler_class, transformation, type_traits = \ 1151 return_type_matcher.lookup(ctype) 1152 assert type_handler_class is not None 1153 if transformation is None: 1154 args = list(args) 1155 args[0] = type_traits 1156 args = tuple(args) 1157 try: 1158 return type_handler_class(*args, **kwargs) 1159 except TypeError: 1160 ex = sys.exc_info()[1] 1161 warnings.warn("Exception %r in type handler %s constructor" % (str(ex), type_handler_class)) 1162 raise 1163 else: 1164 return transformation.create_type_handler(type_handler_class, *args, **kwargs) 1165 else: 1166 return cls(*args, **kwargs) 1167 1168 new = classmethod(new) 1169 1170 def __init__(self, ctype, is_const=False): 1171 ''' 1172 Creates a return value object 1173 1174 Keywork Arguments: 1175 1176 :param ctype: actual C/C++ type being used 1177 ''' 1178 if type(self) is ReturnValue: 1179 raise TypeError('ReturnValue is an abstract class; use ReturnValue.new(...)') 1180 super(_ReturnValue, self).__init__(ctype, is_const) 1181 self.value = 'retval' 1182 1183 def get_c_error_return(self): 1184 '''Return a "return <value>" code string, for use in case of error''' 1185 raise NotImplementedError 1186 1187 def convert_python_to_c(self, wrapper): 1188 ''' 1189 Writes code to convert the Python return value into the C "retval" variable. 1190 ''' 1191 raise NotImplementedError 1192 #assert isinstance(wrapper, ReverseWrapperBase) 1193 1194 def convert_c_to_python(self, wrapper): 1195 ''' 1196 Writes code to convert the C return value the Python return. 1197 ''' 1198 raise NotImplementedError 1199 #assert isinstance(wrapper, ReverseWrapperBase) 1200 1201if PY3: 1202 ReturnValue = types.new_class("ReturnValue", (_ReturnValue,), dict(metaclass=ReturnValueMeta)) 1203else: 1204 class ReturnValue(_ReturnValue): 1205 __metaclass__ = ReturnValueMeta 1206 1207ReturnValue.CTYPES = NotImplemented 1208 1209 1210 1211 1212class PointerReturnValue(ReturnValue): 1213 """Base class for all pointer-to-something handlers""" 1214 CTYPES = [] 1215 def __init__(self, ctype, is_const=False, caller_owns_return=None, free_after_copy=None): 1216 super(PointerReturnValue, self).__init__(ctype, is_const) 1217 self.call_owns_return = caller_owns_return 1218 self.free_after_copy = free_after_copy 1219 1220PointerReturnValue.CTYPES = NotImplemented 1221 1222 1223class ParameterMeta(type): 1224 "Metaclass for automatically registering parameter type handlers" 1225 def __init__(mcs, name, bases, dict_): 1226 "metaclass __init__" 1227 type.__init__(mcs, name, bases, dict_) 1228 if __debug__: 1229 try: 1230 iter(mcs.CTYPES) 1231 except TypeError: 1232 sys.stderr.write("ERROR: missing CTYPES on class %s\n" % mcs) 1233 for ctype in mcs.CTYPES: 1234 param_type_matcher.register(ctype, mcs) 1235 1236 1237class _Parameter(TypeHandler): 1238 '''Abstract base class for all classes dedicated to handle specific parameter types''' 1239 1240 ## bit mask values 1241 DIRECTION_IN = 1 1242 DIRECTION_OUT = 2 1243 DIRECTION_INOUT = DIRECTION_IN|DIRECTION_OUT 1244 1245 ## list of possible directions for this type 1246 DIRECTIONS = NotImplemented 1247 ## whether it supports type transformations 1248 SUPPORTS_TRANSFORMATIONS = False 1249 ## list of C type names it can handle 1250 CTYPES = [] 1251 1252 def _direction_value_to_name(cls, value): 1253 if value == cls.DIRECTION_IN: 1254 return "DIRECTION_IN" 1255 elif value == cls.DIRECTION_OUT: 1256 return "DIRECTION_OUT" 1257 elif value == cls.DIRECTION_INOUT: 1258 return "DIRECTION_INOUT" 1259 else: 1260 return "(invalid %r)" % value 1261 _direction_value_to_name = classmethod(_direction_value_to_name) 1262 1263 1264 #@classmethod 1265 def new(cls, *args, **kwargs): 1266 """ 1267 >>> import inttype 1268 >>> isinstance(Parameter.new('int', 'name'), inttype.IntParam) 1269 True 1270 """ 1271 if cls is Parameter: 1272 # support calling Parameter("typename", ...) 1273 ctype = args[0] 1274 type_handler_class, transformation, type_traits = \ 1275 param_type_matcher.lookup(ctype) 1276 assert type_handler_class is not None 1277 if transformation is None: 1278 args = list(args) 1279 args[0] = type_traits 1280 args = tuple(args) 1281 try: 1282 return type_handler_class(*args, **kwargs) 1283 except TypeError: 1284 _, ex, _ = sys.exc_info() 1285 warnings.warn("Exception %r in type handler %s constructor" % (str(ex), type_handler_class)) 1286 raise 1287 else: 1288 return transformation.create_type_handler(type_handler_class, *args, **kwargs) 1289 else: 1290 return cls(*args, **kwargs) 1291 1292 new = classmethod(new) 1293 1294 def __init__(self, ctype, name, direction=DIRECTION_IN, is_const=False, default_value=None): 1295 ''' 1296 Creates a parameter object 1297 1298 :param ctype: actual C/C++ type being used 1299 :param name: parameter name 1300 :param direction: direction of the parameter transfer, valid values 1301 are DIRECTION_IN, DIRECTION_OUT, and 1302 DIRECTION_IN|DIRECTION_OUT 1303 ''' 1304 if type(self) is Parameter: 1305 raise TypeError('Parameter is an abstract class; use Parameter.new(...)') 1306 super(_Parameter, self).__init__(ctype, is_const) 1307 self.name = name 1308 assert direction in self.DIRECTIONS, \ 1309 "Error: requested direction %s for type handler %r (ctype=%r), but it only supports directions %r"\ 1310 % (self._direction_value_to_name(direction), type(self), self.ctype, 1311 [self._direction_value_to_name(d) for d in self.DIRECTIONS]) 1312 self.direction = direction 1313 self.value = name 1314 self.default_value = default_value 1315 1316 def convert_c_to_python(self, wrapper): 1317 '''Write some code before calling the Python method.''' 1318 #assert isinstance(wrapper, ReverseWrapperBase) 1319 raise NotImplementedError 1320 1321 def convert_python_to_c(self, wrapper): 1322 '''Write some code before calling the C method.''' 1323 #assert isinstance(wrapper, ReverseWrapperBase) 1324 raise NotImplementedError 1325 1326if PY3: 1327 Parameter = types.new_class("Parameter", (_Parameter,), dict(metaclass=ParameterMeta)) 1328else: 1329 class Parameter(_Parameter): 1330 __metaclass__ = ParameterMeta 1331 1332 1333Parameter.CTYPES = NotImplemented 1334 1335class PointerParameter(Parameter): 1336 """Base class for all pointer-to-something handlers""" 1337 1338 CTYPES = [] 1339 1340 def __init__(self, ctype, name, direction=Parameter.DIRECTION_IN, is_const=False, default_value=None, 1341 transfer_ownership=False, free_after_copy=False): 1342 super(PointerParameter, self).__init__(ctype, name, direction, is_const, default_value) 1343 self.transfer_ownership = transfer_ownership 1344 self.free_after_copy = free_after_copy 1345 1346PointerParameter.CTYPES = NotImplemented 1347 1348 1349class TypeMatcher(object): 1350 """ 1351 Type matcher object: maps C type names to classes that handle 1352 those types. 1353 """ 1354 1355 def __init__(self): 1356 """Constructor""" 1357 self._types = {} 1358 self._transformations = [] 1359 self._type_aliases = {} 1360 self._type_aliases_rev = {} 1361 1362 1363 def register_transformation(self, transformation): 1364 "Register a type transformation object" 1365 assert isinstance(transformation, TypeTransformation) 1366 self._transformations.append(transformation) 1367 1368 def register(self, name, type_handler): 1369 """Register a new handler class for a given C type 1370 1371 :param name: C type name 1372 1373 :param type_handler: class to handle this C type 1374 """ 1375 name = ctypeparser.normalize_type_string(name) 1376 if name in self._types: 1377 raise ValueError("return type %s already registered" % (name,)) 1378 self._types[name] = type_handler 1379 1380 def _raw_lookup_with_alias_support(self, name): 1381 already_tried = [] 1382 return self._raw_lookup_with_alias_support_recursive(name, already_tried) 1383 1384 def _raw_lookup_with_alias_support_recursive(self, name, already_tried): 1385 try: 1386 return self._types[name] 1387 except KeyError: 1388 aliases_to_try = [] 1389 try: 1390 aliases_to_try.append(self._type_aliases[name]) 1391 except KeyError: 1392 pass 1393 try: 1394 aliases_to_try.append(self._type_aliases_rev[name]) 1395 except KeyError: 1396 pass 1397 for alias in aliases_to_try: 1398 if alias in already_tried: 1399 continue 1400 already_tried.append(name) 1401 #if 'Time' in name or 'Time' in alias: 1402 # import sys 1403 # print >> sys.stderr, "**** trying name %r in place of %r" % (alias, name) 1404 return self._raw_lookup_with_alias_support_recursive(alias, already_tried) 1405 raise KeyError 1406 1407 def lookup(self, name): 1408 """ 1409 lookup(name) -> type_handler, type_transformation, type_traits 1410 1411 :param name: C type name, possibly transformed (e.g. MySmartPointer<Foo> looks up Foo*) 1412 :returns: a handler with the given ctype name, or raises KeyError. 1413 1414 Supports type transformations. 1415 1416 """ 1417 logger.debug("TypeMatcher.lookup(%r)", name) 1418 given_type_traits = ctypeparser.TypeTraits(name) 1419 noconst_name = str(given_type_traits.ctype_no_modifiers) 1420 tried_names = [noconst_name] 1421 try: 1422 rv = self._raw_lookup_with_alias_support(noconst_name), None, given_type_traits 1423 except KeyError: 1424 logger.debug("try to lookup type handler for %r => failure", name) 1425 ## Now try all the type transformations 1426 for transf in self._transformations: 1427 untransformed_name = transf.get_untransformed_name(name) 1428 if untransformed_name is None: 1429 continue 1430 untransformed_type_traits = ctypeparser.TypeTraits(untransformed_name) 1431 untransformed_name = str(untransformed_type_traits.ctype_no_modifiers) 1432 try: 1433 rv = self._raw_lookup_with_alias_support(untransformed_name), transf, untransformed_type_traits 1434 except KeyError as ex: 1435 logger.debug("try to lookup type handler for %r => failure (%r)", untransformed_name, str(ex)) 1436 tried_names.append(untransformed_name) 1437 continue 1438 else: 1439 logger.debug("try to lookup type handler for %r => success (%r)", untransformed_name, rv) 1440 return rv 1441 else: 1442 #if 'Time' in name: 1443 # existing = [k for k in self._types.iterkeys() if 'Time' in k] 1444 # existing.sort() 1445 # raise TypeLookupError((tried_names, existing, self._type_aliases)) 1446 #else: 1447 raise TypeLookupError(tried_names) 1448 else: 1449 logger.debug("try to lookup type handler for %r => success (%r)", name, rv) 1450 return rv 1451 1452 def items(self): 1453 "Returns an iterator over all registered items" 1454 return iter(self._types.items()) 1455 1456 def add_type_alias(self, from_type_name, to_type_name): 1457 from_type_name_normalized = str(ctypeparser.TypeTraits(from_type_name).ctype) 1458 to_type_name_normalized = str(ctypeparser.TypeTraits(to_type_name).ctype) 1459 self._type_aliases[to_type_name_normalized] = from_type_name_normalized 1460 self._type_aliases_rev[from_type_name_normalized] = to_type_name_normalized 1461 1462return_type_matcher = TypeMatcher() 1463param_type_matcher = TypeMatcher() 1464 1465def add_type_alias(from_type_name, to_type_name): 1466 return_type_matcher.add_type_alias(from_type_name, to_type_name) 1467 param_type_matcher.add_type_alias(from_type_name, to_type_name) 1468 1469