File indexing completed on 2024-03-24 03:55:10
0001 #!/usr/bin/env python 0002 # 0003 # SPDX-FileCopyrightText: 2016 by Shaheed Haque <srhaque@theiet.org> 0004 # SPDX-FileCopyrightText: 2016 Stephen Kelly <steveire@gmail.com> 0005 # 0006 # SPDX-License-Identifier: BSD-3-Clause 0007 0008 """SIP file generation rules engine.""" 0009 0010 from __future__ import print_function 0011 0012 from abc import * 0013 import argparse 0014 import gettext 0015 import inspect 0016 import logging 0017 import os 0018 import re 0019 import sys 0020 import textwrap 0021 from copy import deepcopy 0022 from clang.cindex import CursorKind 0023 0024 from clang.cindex import AccessSpecifier 0025 0026 class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): 0027 pass 0028 0029 0030 logger = logging.getLogger(__name__) 0031 gettext.install(__name__) 0032 _SEPARATOR = "\x00" 0033 0034 def _parents(container): 0035 parents = [] 0036 parent = container.semantic_parent 0037 while parent and parent.kind != CursorKind.TRANSLATION_UNIT: 0038 parents.append(parent.spelling) 0039 parent = parent.semantic_parent 0040 if parents: 0041 parents = "::".join(reversed(parents)) 0042 else: 0043 parents = os.path.basename(container.translation_unit.spelling) 0044 return parents 0045 0046 0047 class Rule(object): 0048 def __init__(self, db, rule_number, fn, pattern_zip): 0049 # 0050 # Derive a useful name for diagnostic purposes. 0051 # 0052 caller = os.path.basename(inspect.stack()[3][1]) 0053 self.name = "{}:{}[{}],{}".format(caller, type(db).__name__, rule_number, fn.__name__) 0054 self.rule_number = rule_number 0055 self.fn = fn 0056 self.usage = 0 0057 try: 0058 groups = ["(?P<{}>{})".format(name, pattern) for pattern, name in pattern_zip] 0059 groups = _SEPARATOR.join(groups) 0060 # 0061 # We'll use re.match to anchor the start of the match, and so need a $ to anchor the end. 0062 # 0063 self.matcher = re.compile(groups + "$") 0064 except Exception as e: 0065 groups = ["{} '{}'".format(name, pattern) for pattern, name in pattern_zip] 0066 groups = ", ".join(groups) 0067 raise RuntimeError(_("Bad {}: {}: {}").format(self, groups, e)) 0068 0069 def match(self, candidate): 0070 return self.matcher.match(candidate) 0071 0072 def trace_result(self, parents, item, original, modified): 0073 """ 0074 Record any modification both in the log and the returned result. If a rule fired, but 0075 caused no modification, that is logged. 0076 0077 :return: Modifying rule or None. 0078 """ 0079 fqn = parents + "::" + original["name"] + "[" + str(item.extent.start.line) + "]" 0080 return self._trace_result(fqn, original, modified) 0081 0082 def _trace_result(self, fqn, original, modified): 0083 """ 0084 Record any modification both in the log and the returned result. If a rule fired, but 0085 caused no modification, that is logged. 0086 0087 :return: Modifying rule or None. 0088 """ 0089 if not modified["name"]: 0090 logger.debug(_("Rule {} suppressed {}, {}").format(self, fqn, original)) 0091 else: 0092 delta = False 0093 for k, v in original.items(): 0094 if v != modified[k]: 0095 delta = True 0096 break 0097 if delta: 0098 logger.debug(_("Rule {} modified {}, {}->{}").format(self, fqn, original, modified)) 0099 else: 0100 logger.warn(_("Rule {} did not modify {}, {}").format(self, fqn, original)) 0101 return None 0102 return self 0103 0104 def __str__(self): 0105 return self.name 0106 0107 0108 class AbstractCompiledRuleDb(object): 0109 __metaclass__ = ABCMeta 0110 0111 def __init__(self, db, parameter_names): 0112 self.db = db 0113 self.compiled_rules = [] 0114 for i, raw_rule in enumerate(db()): 0115 if len(raw_rule) != len(parameter_names) + 1: 0116 raise RuntimeError(_("Bad raw rule {}: {}: {}").format(db.__name__, raw_rule, parameter_names)) 0117 z = zip(raw_rule[:-1], parameter_names) 0118 self.compiled_rules.append(Rule(self, i, raw_rule[-1], z)) 0119 self.candidate_formatter = _SEPARATOR.join(["{}"] * len(parameter_names)) 0120 0121 def _match(self, *args): 0122 candidate = self.candidate_formatter.format(*args) 0123 for rule in self.compiled_rules: 0124 matcher = rule.match(candidate) 0125 if matcher: 0126 # 0127 # Only use the first matching rule. 0128 # 0129 rule.usage += 1 0130 return matcher, rule 0131 return None, None 0132 0133 @abstractmethod 0134 def apply(self, *args): 0135 raise NotImplemented(_("Missing subclass")) 0136 0137 def dump_usage(self, fn): 0138 """ Dump the usage counts.""" 0139 for rule in self.compiled_rules: 0140 fn(str(rule), rule.usage) 0141 0142 0143 class ContainerRuleDb(AbstractCompiledRuleDb): 0144 """ 0145 THE RULES FOR CONTAINERS. 0146 0147 These are used to customize the behaviour of the SIP generator by allowing 0148 the declaration for any container (class, namespace, struct, union) to be 0149 customized, for example to add SIP compiler annotations. 0150 0151 Each entry in the raw rule database must be a list with members as follows: 0152 0153 0. A regular expression which matches the fully-qualified name of the 0154 "container" enclosing the container. 0155 0156 1. A regular expression which matches the container name. 0157 0158 2. A regular expression which matches any template parameters. 0159 0160 3. A regular expression which matches the container declaration. 0161 0162 4. A regular expression which matches any base specifiers. 0163 0164 5. A function. 0165 0166 In use, the database is walked in order from the first entry. If the regular 0167 expressions are matched, the function is called, and no further entries are 0168 walked. The function is called with the following contract: 0169 0170 def container_xxx(container, sip, matcher): 0171 ''' 0172 Return a modified declaration for the given container. 0173 0174 :param container: The clang.cindex.Cursor for the container. 0175 :param sip: A dict with the following keys: 0176 0177 name The name of the container. 0178 template_parameters Any template parameters. 0179 decl The declaration. 0180 base_specifiers Any base specifiers. 0181 body The body, less the outer 0182 pair of braces. 0183 annotations Any SIP annotations. 0184 0185 :param matcher: The re.Match object. This contains named 0186 groups corresponding to the key names above 0187 EXCEPT body and annotations. 0188 0189 :return: An updated set of sip.xxx values. Setting sip.name to the 0190 empty string will cause the container to be suppressed. 0191 ''' 0192 0193 :return: The compiled form of the rules. 0194 """ 0195 def __init__(self, db): 0196 super(ContainerRuleDb, self).__init__(db, ["parents", "container", "template_parameters", "decl", "base_specifiers"]) 0197 0198 def apply(self, container, sip): 0199 """ 0200 Walk over the rules database for containers, applying the first matching transformation. 0201 0202 :param container: The clang.cindex.Cursor for the container. 0203 :param sip: The SIP dict (may be modified on return). 0204 :return: Modifying rule or None (even if a rule matched, it may not modify things). 0205 """ 0206 parents = _parents(container) 0207 matcher, rule = self._match(parents, sip["name"], 0208 ", ".join(sip["template_parameters"]), 0209 sip["decl"], 0210 ", ".join(sip["base_specifiers"])) 0211 if matcher: 0212 before = deepcopy(sip) 0213 rule.fn(container, sip, matcher) 0214 return rule.trace_result(parents, container, before, sip) 0215 return None 0216 0217 0218 class ForwardDeclarationRuleDb(AbstractCompiledRuleDb): 0219 """ 0220 THE RULES FOR FORWARD DECLARATIONS. 0221 0222 These are used to customize the behaviour of the SIP generator by allowing 0223 the forward declaration for any container (class, struct, union) to be 0224 customized, for example to add SIP compiler annotations. 0225 0226 Each entry in the raw rule database must be a list with members as follows: 0227 0228 0. A regular expression which matches the fully-qualified name of the 0229 "container" enclosing the container. 0230 0231 1. A regular expression which matches the container name. 0232 0233 2. A regular expression which matches any template parameters. 0234 0235 3. A function. 0236 0237 In use, the database is walked in order from the first entry. If the regular 0238 expressions are matched, the function is called, and no further entries are 0239 walked. The function is called with the following contract: 0240 0241 def declaration_xxx(container, sip, matcher): 0242 ''' 0243 Return a modified declaration for the given container. 0244 0245 :param container: The clang.cindex.Cursor for the container. 0246 :param sip: A dict with the following keys: 0247 0248 name The name of the container. 0249 template_parameters Any template parameters. 0250 annotations Any SIP annotations. 0251 0252 :param matcher: The re.Match object. This contains named 0253 groups corresponding to the key names above 0254 EXCEPT body and annotations. 0255 0256 :return: An updated set of sip.xxx values. Setting sip.name to the 0257 empty string will cause the container to be suppressed. 0258 ''' 0259 0260 :return: The compiled form of the rules. 0261 """ 0262 def __init__(self, db): 0263 super(ForwardDeclarationRuleDb, self).__init__(db, ["parents", "container", "template_parameters"]) 0264 0265 def apply(self, container, sip): 0266 """ 0267 Walk over the rules database for containers, applying the first matching transformation. 0268 0269 :param container: The clang.cindex.Cursor for the container. 0270 :param sip: The SIP dict (may be modified on return). 0271 :return: Modifying rule or None (even if a rule matched, it may not modify things). 0272 """ 0273 parents = _parents(container) 0274 matcher, rule = self._match(parents, sip["name"], 0275 ", ".join(sip["template_parameters"])) 0276 if matcher: 0277 before = deepcopy(sip) 0278 rule.fn(container, sip, matcher) 0279 return rule.trace_result(parents, container, before, sip) 0280 return None 0281 0282 0283 class FunctionRuleDb(AbstractCompiledRuleDb): 0284 """ 0285 THE RULES FOR FUNCTIONS. 0286 0287 These are used to customize the behaviour of the SIP generator by allowing 0288 the declaration for any function to be customized, for example to add SIP 0289 compiler annotations. 0290 0291 Each entry in the raw rule database must be a list with members as follows: 0292 0293 0. A regular expression which matches the fully-qualified name of the 0294 "container" enclosing the function. 0295 0296 1. A regular expression which matches the function name. 0297 0298 2. A regular expression which matches any template parameters. 0299 0300 3. A regular expression which matches the function result. 0301 0302 4. A regular expression which matches the function parameters (e.g. 0303 "int a, void *b" for "int foo(int a, void *b)"). 0304 0305 5. A function. 0306 0307 In use, the database is walked in order from the first entry. If the regular 0308 expressions are matched, the function is called, and no further entries are 0309 walked. The function is called with the following contract: 0310 0311 def function_xxx(container, function, sip, matcher): 0312 ''' 0313 Return a modified declaration for the given function. 0314 0315 :param container: The clang.cindex.Cursor for the container. 0316 :param function: The clang.cindex.Cursor for the function. 0317 :param sip: A dict with the following keys: 0318 0319 name The name of the function. 0320 template_parameters Any template parameters. 0321 fn_result Result, if not a constructor. 0322 parameters The parameters. 0323 prefix Leading keyworks ("static"). Separated by space, 0324 ends with a space. 0325 suffix Trailing keywords ("const"). Separated by space, starts with 0326 space. 0327 annotations Any SIP annotations. 0328 0329 :param matcher: The re.Match object. This contains named 0330 groups corresponding to the key names above 0331 EXCEPT annotations. 0332 0333 :return: An updated set of sip.xxx values. Setting sip.name to the 0334 empty string will cause the container to be suppressed. 0335 ''' 0336 0337 :return: The compiled form of the rules. 0338 """ 0339 def __init__(self, db): 0340 super(FunctionRuleDb, self).__init__(db, ["container", "function", "template_parameters", "fn_result", "parameters"]) 0341 0342 def apply(self, container, function, sip): 0343 """ 0344 Walk over the rules database for functions, applying the first matching transformation. 0345 0346 :param container: The clang.cindex.Cursor for the container. 0347 :param function: The clang.cindex.Cursor for the function. 0348 :param sip: The SIP dict (may be modified on return). 0349 :return: Modifying rule or None (even if a rule matched, it may not modify things). 0350 """ 0351 parents = _parents(function) 0352 matcher, rule = self._match(parents, sip["name"], ", ".join(sip["template_parameters"]), sip["fn_result"], ", ".join(sip["parameters"])) 0353 if matcher: 0354 sip.setdefault("code", "") 0355 before = deepcopy(sip) 0356 rule.fn(container, function, sip, matcher) 0357 return rule.trace_result(parents, function, before, sip) 0358 return None 0359 0360 0361 class ParameterRuleDb(AbstractCompiledRuleDb): 0362 """ 0363 THE RULES FOR FUNCTION PARAMETERS. 0364 0365 These are used to customize the behaviour of the SIP generator by allowing 0366 the declaration for any parameter in any function to be customized, for 0367 example to add SIP compiler annotations. 0368 0369 Each entry in the raw rule database must be a list with members as follows: 0370 0371 0. A regular expression which matches the fully-qualified name of the 0372 "container" enclosing the function enclosing the parameter. 0373 0374 1. A regular expression which matches the function name enclosing the 0375 parameter. 0376 0377 2. A regular expression which matches the parameter name. 0378 0379 3. A regular expression which matches the parameter declaration (e.g. 0380 "int foo"). 0381 0382 4. A regular expression which matches the parameter initialiser (e.g. 0383 "Xyz:MYCONST + 42"). 0384 0385 5. A function. 0386 0387 In use, the database is walked in order from the first entry. If the regular 0388 expressions are matched, the function is called, and no further entries are 0389 walked. The function is called with the following contract: 0390 0391 def parameter_xxx(container, function, parameter, sip, init, matcher): 0392 ''' 0393 Return a modified declaration and initialiser for the given parameter. 0394 0395 :param container: The clang.cindex.Cursor for the container. 0396 :param function: The clang.cindex.Cursor for the function. 0397 :param parameter: The clang.cindex.Cursor for the parameter. 0398 :param sip: A dict with the following keys: 0399 0400 name The name of the function. 0401 decl The declaration. 0402 init Any initialiser. 0403 annotations Any SIP annotations. 0404 0405 :param matcher: The re.Match object. This contains named 0406 groups corresponding to the key names above 0407 EXCEPT annotations. 0408 0409 :return: An updated set of sip.xxx values. 0410 ''' 0411 0412 :return: The compiled form of the rules. 0413 """ 0414 def __init__(self, db): 0415 super(ParameterRuleDb, self).__init__(db, ["container", "function", "parameter", "decl", "init"]) 0416 0417 def apply(self, container, function, parameter, sip): 0418 """ 0419 Walk over the rules database for parameters, applying the first matching transformation. 0420 0421 :param container: The clang.cindex.Cursor for the container. 0422 :param function: The clang.cindex.Cursor for the function. 0423 :param parameter: The clang.cindex.Cursor for the parameter. 0424 :param sip: The SIP dict (may be modified on return). 0425 :return: Modifying rule or None (even if a rule matched, it may not modify things). 0426 """ 0427 parents = _parents(function) 0428 matcher, rule = self._match(parents, function.spelling, sip["name"], sip["decl"], sip["init"]) 0429 if matcher: 0430 sip.setdefault("code", "") 0431 before = deepcopy(sip) 0432 rule.fn(container, function, parameter, sip, matcher) 0433 return rule.trace_result(parents, parameter, before, sip) 0434 return None 0435 0436 0437 class TypedefRuleDb(AbstractCompiledRuleDb): 0438 """ 0439 THE RULES FOR TYPEDEFS. 0440 0441 These are used to customize the behaviour of the SIP generator by allowing 0442 the declaration for any typedef to be customized, for example to add SIP 0443 compiler annotations. 0444 0445 Each entry in the raw rule database must be a list with members as follows: 0446 0447 0. A regular expression which matches the fully-qualified name of the 0448 "container" enclosing the typedef. 0449 0450 1. A regular expression which matches the typedef name. 0451 0452 2. A function. 0453 0454 In use, the database is walked in order from the first entry. If the regular 0455 expressions are matched, the function is called, and no further entries are 0456 walked. The function is called with the following contract: 0457 0458 def typedef_xxx(container, typedef, sip, matcher): 0459 ''' 0460 Return a modified declaration for the given function. 0461 0462 :param container: The clang.cindex.Cursor for the container. 0463 :param typedef: The clang.cindex.Cursor for the typedef. 0464 :param sip: A dict with the following keys: 0465 0466 name The name of the typedef. 0467 annotations Any SIP annotations. 0468 0469 :param matcher: The re.Match object. This contains named 0470 groups corresponding to the key names above 0471 EXCEPT annotations. 0472 0473 :return: An updated set of sip.xxx values. Setting sip.name to the 0474 empty string will cause the container to be suppressed. 0475 ''' 0476 0477 :return: The compiled form of the rules. 0478 """ 0479 def __init__(self, db): 0480 super(TypedefRuleDb, self).__init__(db, ["container", "typedef"]) 0481 0482 def apply(self, container, typedef, sip): 0483 """ 0484 Walk over the rules database for typedefs, applying the first matching transformation. 0485 0486 :param container: The clang.cindex.Cursor for the container. 0487 :param typedef: The clang.cindex.Cursor for the typedef. 0488 :param sip: The SIP dict. 0489 """ 0490 parents = _parents(typedef) 0491 matcher, rule = self._match(parents, sip["name"]) 0492 if matcher: 0493 before = deepcopy(sip) 0494 rule.fn(container, typedef, sip, matcher) 0495 return rule.trace_result(parents, typedef, before, sip) 0496 return None 0497 0498 0499 class VariableRuleDb(AbstractCompiledRuleDb): 0500 """ 0501 THE RULES FOR VARIABLES. 0502 0503 These are used to customize the behaviour of the SIP generator by allowing 0504 the declaration for any variable to be customized, for example to add SIP 0505 compiler annotations. 0506 0507 Each entry in the raw rule database must be a list with members as follows: 0508 0509 0. A regular expression which matches the fully-qualified name of the 0510 "container" enclosing the variable. 0511 0512 1. A regular expression which matches the variable name. 0513 0514 2. A regular expression which matches the variable declaration (e.g. 0515 "int foo"). 0516 0517 3. A function. 0518 0519 In use, the database is walked in order from the first entry. If the regular 0520 expressions are matched, the function is called, and no further entries are 0521 walked. The function is called with the following contract: 0522 0523 def variable_xxx(container, variable, sip, matcher): 0524 ''' 0525 Return a modified declaration for the given variable. 0526 0527 :param container: The clang.cindex.Cursor for the container. 0528 :param variable: The clang.cindex.Cursor for the variable. 0529 :param sip: A dict with the following keys: 0530 0531 name The name of the variable. 0532 decl The declaration. 0533 annotations Any SIP annotations. 0534 0535 :param matcher: The re.Match object. This contains named 0536 groups corresponding to the key names above 0537 EXCEPT annotations. 0538 0539 :return: An updated set of sip.xxx values. Setting sip.name to the 0540 empty string will cause the container to be suppressed. 0541 ''' 0542 0543 :return: The compiled form of the rules. 0544 """ 0545 def __init__(self, db): 0546 super(VariableRuleDb, self).__init__(db, ["container", "variable", "decl"]) 0547 0548 def apply(self, container, variable, sip): 0549 """ 0550 Walk over the rules database for variables, applying the first matching transformation. 0551 0552 :param container: The clang.cindex.Cursor for the container. 0553 :param variable: The clang.cindex.Cursor for the variable. 0554 :param sip: The SIP dict (may be modified on return). 0555 :return: Modifying rule or None (even if a rule matched, it may not modify things). 0556 """ 0557 parents = _parents(variable) 0558 matcher, rule = self._match(parents, sip["name"], sip["decl"]) 0559 if matcher: 0560 sip.setdefault("code", "") 0561 before = deepcopy(sip) 0562 rule.fn(container, variable, sip, matcher) 0563 return rule.trace_result(parents, variable, before, sip) 0564 return None 0565 0566 0567 class AbstractCompiledCodeDb(object): 0568 __metaclass__ = ABCMeta 0569 0570 def __init__(self, db): 0571 caller = os.path.basename(inspect.stack()[2][1]) 0572 self.name = "{}:{}".format(caller, type(self).__name__) 0573 self.db = db 0574 0575 @abstractmethod 0576 def apply(self, function, sip): 0577 raise NotImplemented(_("Missing subclass")) 0578 0579 def trace_result(self, parents, item, original, modified): 0580 """ 0581 Record any modification both in the log and the returned result. If a rule fired, but 0582 caused no modification, that is logged. 0583 0584 :return: Modifying rule or None. 0585 """ 0586 fqn = parents + "::" + original["name"] + "[" + str(item.extent.start.line) + "]" 0587 self._trace_result(fqn, original, modified) 0588 0589 def _trace_result(self, fqn, original, modified): 0590 """ 0591 Record any modification both in the log and the returned result. If a rule fired, but 0592 caused no modification, that is logged. 0593 0594 :return: Modifying rule or None. 0595 """ 0596 if not modified["name"]: 0597 logger.debug(_("Rule {} suppressed {}, {}").format(self, fqn, original)) 0598 else: 0599 delta = False 0600 for k, v in original.items(): 0601 if v != modified[k]: 0602 delta = True 0603 break 0604 if delta: 0605 logger.debug(_("Rule {} modified {}, {}->{}").format(self, fqn, original, modified)) 0606 else: 0607 logger.warn(_("Rule {} did not modify {}, {}").format(self, fqn, original)) 0608 return None 0609 return self 0610 0611 @abstractmethod 0612 def dump_usage(self, fn): 0613 raise NotImplemented(_("Missing subclass")) 0614 0615 def __str__(self): 0616 return self.name 0617 0618 class MethodCodeDb(AbstractCompiledCodeDb): 0619 """ 0620 THE RULES FOR INJECTING METHOD-RELATED CODE (such as %MethodCode, 0621 %VirtualCatcherCode, %VirtualCallCode and other method-level directives). 0622 0623 These are used to customize the behaviour of the SIP generator by allowing 0624 method-level code injection. 0625 0626 The raw rule database must be an outer dictionary as follows: 0627 0628 0. Each key is the fully-qualified name of a "container" enclosing 0629 methods. 0630 0631 1. Each value is an inner dictionary, each of whose keys is the name 0632 of a method. 0633 0634 Each inner dictionary has entries which update the declaration as follows: 0635 0636 "name": Optional string. If present, overrides the name of the 0637 method. 0638 "parameters": Optional list. If present, update the argument list. 0639 0640 "fn_result": Optional string. If present, update the return type. 0641 0642 "code": Required. Either a string, with the %XXXCode content, 0643 or a callable. 0644 0645 In use, the database is directly indexed by "container" and then method 0646 name. If "code" entry is a string, then the other optional keys are 0647 interpreted as above. If "code" is a callable, it is called with the 0648 following contract: 0649 0650 def methodcode_xxx(function, sip, entry): 0651 ''' 0652 Return a modified declaration for the given function. 0653 0654 :param function: The clang.cindex.Cursor for the function. 0655 :param sip: A dict with keys as for function rules and (string) 0656 "code" keys described above. 0657 :param entry: The inner dictionary entry. 0658 0659 :return: An updated set of sip.xxx values. 0660 ''' 0661 0662 :return: The compiled form of the rules. 0663 """ 0664 __metaclass__ = ABCMeta 0665 0666 def __init__(self, db): 0667 super(MethodCodeDb, self).__init__(db) 0668 0669 for k, v in self.db.items(): 0670 for l in v.keys(): 0671 v[l]["usage"] = 0 0672 0673 def _get(self, item, name): 0674 0675 parents = _parents(item) 0676 entries = self.db.get(parents, None) 0677 if not entries: 0678 return None 0679 0680 entry = entries.get(name, None) 0681 if not entry: 0682 return None 0683 entry["usage"] += 1 0684 return entry 0685 0686 def apply(self, function, sip): 0687 """ 0688 Walk over the code database for functions, applying the first matching transformation. 0689 0690 :param function: The clang.cindex.Cursor for the function. 0691 :param sip: The SIP dict (may be modified on return). 0692 :return: Modifying rule or None (even if a rule matched, it may not modify things). 0693 """ 0694 entry = self._get(function, sip["name"]) 0695 sip.setdefault("code", "") 0696 if entry: 0697 before = deepcopy(sip) 0698 if callable(entry["code"]): 0699 fn = entry["code"] 0700 fn_file = os.path.basename(inspect.getfile(fn)) 0701 trace = "// Generated (by {}:{}): {}\n".format(fn_file, fn.__name__, {k:v for (k,v) in entry.items() if k != "code"}) 0702 fn(function, sip, entry) 0703 else: 0704 trace = "// Inserted (by {}:{}): {}\n".format(_parents(function), function.spelling, {k:v for (k,v) in entry.items() if k != "code"}) 0705 sip["code"] = entry["code"] 0706 sip["parameters"] = entry.get("parameters", sip["parameters"]) 0707 sip["fn_result"] = entry.get("fn_result", sip["fn_result"]) 0708 # 0709 # Fetch/format the code. 0710 # 0711 sip["code"] = trace + textwrap.dedent(sip["code"]).strip() + "\n" 0712 return self.trace_result(_parents(function), function, before, sip) 0713 return None 0714 0715 def dump_usage(self, fn): 0716 """ Dump the usage counts.""" 0717 for k in sorted(self.db.keys()): 0718 vk = self.db[k] 0719 for l in sorted(vk.keys()): 0720 vl = vk[l] 0721 fn(str(self) + " for " + k + "," + l, vl["usage"]) 0722 0723 class ModuleCodeDb(AbstractCompiledCodeDb): 0724 """ 0725 THE RULES FOR INJECTING MODULE-RELATED CODE (such as %ExportedHeaderCode, 0726 %ModuleCode, %ModuleHeaderCode or other module-level directives). 0727 0728 These are used to customize the behaviour of the SIP generator by allowing 0729 module-level code injection. 0730 0731 The raw rule database must be a dictionary as follows: 0732 0733 0. Each key is the basename of a header file. 0734 0735 1. Each value has entries which update the declaration as follows: 0736 0737 "code": Required. Either a string, with the %XXXCode content, 0738 or a callable. 0739 0740 If "code" is a callable, it is called with the following contract: 0741 0742 def module_xxx(filename, sip, entry): 0743 ''' 0744 Return a string to insert for the file. 0745 0746 :param filename: The filename. 0747 :param sip: A dict with the key "name" for the filename, 0748 "decl" for the module body plus the "code" key 0749 described above. 0750 :param entry: The dictionary entry. 0751 0752 :return: A string. 0753 ''' 0754 0755 :return: The compiled form of the rules. 0756 """ 0757 __metaclass__ = ABCMeta 0758 0759 def __init__(self, db): 0760 super(ModuleCodeDb, self).__init__(db) 0761 # 0762 # Add a usage count and other diagnostic support for each item in the database. 0763 # 0764 for k, v in self.db.items(): 0765 v["usage"] = 0 0766 0767 def _get(self, filename): 0768 # 0769 # Lookup for an actual hit. 0770 # 0771 entry = self.db.get(filename, None) 0772 if not entry: 0773 return None 0774 entry["usage"] += 1 0775 return entry 0776 0777 def apply(self, filename, sip): 0778 """ 0779 Walk over the code database for modules, applying the first matching transformation. 0780 0781 :param filename: The file for the module. 0782 :param sip: The SIP dict (may be modified on return). 0783 :return: Modifying rule or None (even if a rule matched, it may not modify things). 0784 """ 0785 entry = self._get(filename) 0786 sip.setdefault("code", "") 0787 if entry: 0788 before = deepcopy(sip) 0789 if callable(entry["code"]): 0790 fn = entry["code"] 0791 fn_file = os.path.basename(inspect.getfile(fn)) 0792 trace = "\n// Generated (by {}:{}): {}".format(fn_file, fn.__name__, {k:v for (k,v) in entry.items() if k != "code"}) 0793 fn(filename, sip, entry) 0794 sip["code"] = trace + sip["code"] 0795 else: 0796 sip["code"] = entry["code"] 0797 # 0798 # Fetch/format the code. 0799 # 0800 sip["code"] = textwrap.dedent(sip["code"]).strip() + "\n" 0801 fqn = filename + "::" + before["name"] 0802 self._trace_result(fqn, before, sip) 0803 0804 def dump_usage(self, fn): 0805 """ Dump the usage counts.""" 0806 for k in sorted(self.db.keys()): 0807 v = self.db[k] 0808 fn(str(self) + " for " + k, v["usage"]) 0809 0810 0811 class RuleSet(object): 0812 """ 0813 To implement your own binding, create a subclass of RuleSet, also called 0814 RuleSet in your own Python module. Your subclass will expose the raw rules 0815 along with other ancillary data exposed through the subclass methods. 0816 0817 You then simply run the SIP generation and SIP compilation programs passing 0818 in the name of your rules file 0819 """ 0820 __metaclass__ = ABCMeta 0821 0822 @abstractmethod 0823 def container_rules(self): 0824 """ 0825 Return a compiled list of rules for containers. 0826 0827 :return: A ContainerRuleDb instance 0828 """ 0829 raise NotImplemented(_("Missing subclass implementation")) 0830 0831 @abstractmethod 0832 def forward_declaration_rules(self): 0833 """ 0834 Return a compiled list of rules for containers. 0835 0836 :return: A ForwardDeclarationRuleDb instance 0837 """ 0838 raise NotImplemented(_("Missing subclass implementation")) 0839 0840 @abstractmethod 0841 def function_rules(self): 0842 """ 0843 Return a compiled list of rules for functions. 0844 0845 :return: A FunctionRuleDb instance 0846 """ 0847 raise NotImplemented(_("Missing subclass implementation")) 0848 0849 @abstractmethod 0850 def parameter_rules(self): 0851 """ 0852 Return a compiled list of rules for function parameters. 0853 0854 :return: A ParameterRuleDb instance 0855 """ 0856 raise NotImplemented(_("Missing subclass implementation")) 0857 0858 @abstractmethod 0859 def typedef_rules(self): 0860 """ 0861 Return a compiled list of rules for typedefs. 0862 0863 :return: A TypedefRuleDb instance 0864 """ 0865 raise NotImplemented(_("Missing subclass implementation")) 0866 0867 @abstractmethod 0868 def variable_rules(self): 0869 """ 0870 Return a compiled list of rules for variables. 0871 0872 :return: A VariableRuleDb instance 0873 """ 0874 raise NotImplemented(_("Missing subclass implementation")) 0875 0876 @abstractmethod 0877 def methodcode_rules(self): 0878 """ 0879 Return a compiled list of rules for method-related code. 0880 0881 :return: A MethodCodeDb instance 0882 """ 0883 raise NotImplemented(_("Missing subclass implementation")) 0884 0885 @abstractmethod 0886 def modulecode_rules(self): 0887 """ 0888 Return a compiled list of rules for module-related code. 0889 0890 :return: A ModuleCodeDb instance 0891 """ 0892 raise NotImplemented(_("Missing subclass implementation")) 0893 0894 def dump_unused(self): 0895 """Usage statistics, to identify unused rules.""" 0896 def dumper(rule, usage): 0897 if usage: 0898 logger.info(_("Rule {} used {} times".format(rule, usage))) 0899 else: 0900 logger.warn(_("Rule {} was not used".format(rule))) 0901 0902 for db in [self.container_rules(), self.forward_declaration_rules(), self.function_rules(), 0903 self.parameter_rules(), self.typedef_rules(), 0904 self.variable_rules(), self.methodcode_rules(), self.modulecode_rules()]: 0905 db.dump_usage(dumper) 0906 0907 @abstractmethod 0908 def methodcode(self, container, function): 0909 """ 0910 Lookup %MethodCode. 0911 """ 0912 raise NotImplemented(_("Missing subclass implementation")) 0913 0914 @abstractmethod 0915 def modulecode(self, filename): 0916 """ 0917 Lookup %ModuleCode and friends. 0918 """ 0919 raise NotImplemented(_("Missing subclass implementation")) 0920 0921 0922 def container_discard(container, sip, matcher): 0923 sip["name"] = "" 0924 0925 def function_discard(container, function, sip, matcher): 0926 sip["name"] = "" 0927 0928 def parameter_transfer_to_parent(container, function, parameter, sip, matcher): 0929 if function.is_static_method(): 0930 sip["annotations"].add("Transfer") 0931 else: 0932 sip["annotations"].add("TransferThis") 0933 0934 def param_rewrite_mode_t_as_int(container, function, parameter, sip, matcher): 0935 sip["decl"] = sip["decl"].replace("mode_t", "unsigned int") 0936 0937 def return_rewrite_mode_t_as_int(container, function, sip, matcher): 0938 sip["fn_result"] = "unsigned int" 0939 0940 def variable_discard(container, variable, sip, matcher): 0941 sip["name"] = "" 0942 0943 def parameter_strip_class_enum(container, function, parameter, sip, matcher): 0944 sip["decl"] = sip["decl"].replace("class ", "").replace("enum ", "") 0945 0946 def function_discard_impl(container, function, sip, matcher): 0947 if function.extent.start.column == 1: 0948 sip["name"] = "" 0949 0950 def typedef_discard(container, typedef, sip, matcher): 0951 sip["name"] = "" 0952 0953 def discard_QSharedData_base(container, sip, matcher): 0954 sip["base_specifiers"].remove("QSharedData") 0955 0956 def mark_forward_declaration_external(container, sip, matcher): 0957 sip["annotations"].add("External") 0958 0959 def container_mark_abstract(container, sip, matcher): 0960 sip["annotations"].add("Abstract") 0961 0962 def rules(project_rules): 0963 """ 0964 Constructor. 0965 0966 :param project_rules: The rules file for the project. 0967 """ 0968 import imp 0969 imp.load_source("project_rules", project_rules) 0970 # 0971 # Statically prepare the rule logic. This takes the rules provided by the user and turns them into code. 0972 # 0973 return getattr(sys.modules["project_rules"], "RuleSet")()