def get_loops(self): """Return the recursives loops in the rules definitions of the source AST. For example, a rule A which calls to a rule B provokes a recursive loop if that rule B calls the rule A.""" def rulecalls_of(node): if type(node) is RuleDefinition: rulecalls = [] for subnode in node.definition: rulecalls += rulecalls_of(subnode) return rulecalls elif type(node) is Choices: rulecalls = [] for option in node.options: for subnode in option.definition: rulecalls += rulecalls_of(subnode) return rulecalls elif type(node) is OptionalGroup: rulecalls = [] for subnode in node.definition: rulecalls += rulecalls_of(subnode) return rulecalls elif type(node) is RuleCall: return [node.whole_name] else: return [] loops = {} rules = [r for r in self.code.ast if type(r) is RuleDefinition] for rule in rules: loops[rule.whole_name] = [] calls = rulecalls_of(rule) for call in calls: subcalls = rulecalls_of(get_rule(self.code, call)) calls_trace = subcalls.copy() while subcalls and rule.whole_name not in subcalls: new_subcalls = [] for subcall in subcalls: new_subcalls += rulecalls_of( get_rule(self.code, subcall)) subcalls = [ c for c in new_subcalls.copy() if c not in calls_trace ] calls_trace += new_subcalls.copy() if rule.whole_name in subcalls: loops[rule.whole_name].append(call) return loops
def _check_uniline_inspection(self, node): assert type(node) is UnilineInspection, "Error in the checker code." self._current_inspected_rule = get_rule(self.code, node.whole_name) if not get_rule(self.code, node.whole_name): raise KombuError( "{} : 'inspect {}' : Cannot define an inspection on a not-existing rule." .format(self.formatted_pos(node.pos), node.name)) for node in [n for n in node.definition if type(n) in [NodeCall]]: self._check_node(node)
def _find_recursion_guards(self, node, base_rule=None): self._checker.code.ast = self._ast if type(node) is RuleDefinition: definition = get_rule(Code(self._ast), node.whole_name).definition guards = None i = 1 while not guards == '{!ARR}' and not guards and i <= len(definition): guards = self._find_recursion_guards(definition[-i], base_rule=node.whole_name if not base_rule else base_rule) i += 1 if guards != '{!ARR}': return guards else: return "" elif type(node) is Match: return escape_quotes(node.string) elif type(node) is RegexMatch: return 'REGEX~' + node.regex elif type(node) is Choices: guards = [] for option in node.options: for subnode in option.definition: option_guards = self._find_recursion_guards(subnode, base_rule=base_rule) if type(option_guards) is list: guards += option_guards elif type(option_guards) is str and option_guards != '{!ARR}': guards.append(option_guards) return guards elif type(node) is RuleCall: # If there is left recursives calls, it means this rule'll need recursions guards and cannot be used # To set recursions guards for the father rule. left_calls = self._checker.left_calls(get_rule(Code(self._ast), node.whole_name)) if not common(left_calls, self._calls_loops[node.whole_name]) and base_rule not in self._calls_loops[node.whole_name]: # Use this recursions guards. return self._find_recursion_guards(get_rule(Code(self._ast), node.whole_name)) else: return '{!ARR}' # Already Recursive Rulecall.
def find_left_recursive_rule_call(rule): for node in rule.definition: if type(node) is Match: return None elif type(node) is Choices: for option in node.options: for subnode in option.definition: if type(subnode) is Match: return None elif type(node) is RuleCall: if not [s for s in rule.definition if type(s) is Match]: return get_rule(self.code, node.whole_name) return None
def _check_multiline_inspection(self, node): assert type(node) is MultilineInspection, "Error in the checker code." self._current_inspected_rule = get_rule(self.code, node.whole_name) if not self._current_inspected_rule: raise KombuError( "{} : 'inspect {}' : Cannot define an inspection on a not-existing rule." .format(self.formatted_pos(node.pos), node.name)) inspection_definition = split(node.definition, NewLine(), include_at_beginning=True)[1:] base_indentation = len(inspection_definition[0][0].indentation.replace( '\t', ' ' * 16)) for i, line in enumerate(inspection_definition): indentation = len(line[0].indentation.replace('\t', ' ' * 16)) if indentation < base_indentation and len(line) > 1: raise KombuError("{} : This line is under-indented.".format( self.formatted_pos(line[0].pos) + i + 1))
def _check_constant_declaration(self, node): assert type(node) is SetConstant, "Error in the Checker code." if node.constantname == 'axiom': self._check_option_value(node, True) found = get_rule(self.code, node.value) assert found, "{} : Axiom is defined by {}, but that rule doesn't exist.".format( self.formatted_pos(node.pos), node.value) elif node.constantname == 'libspath': self._check_option_value(node, True) elif node.constantname == 'name': self._check_option_value(node, True) elif node.constantname == 'keep_whitespaces': self._check_option_value(node, False) elif node.constantname == 'cut_end': self._check_option_value(node, False) elif node.constantname == 'get_time': self._check_option_value(node, False) elif node.constantname == 'show_ast': self._check_option_value(node, False) else: raise KombuError("{} : '{}' isn't an option name.".format( self.formatted_pos(node.pos), node.constantname))
def _find_node(self, condition, rule=None, expected=1): def search_in(node, condition): found = [] if condition(node): found.append(node) if type(node) in [RuleDefinition, OptionalGroup, Option]: for subnode in node.definition: result = search_in(subnode, condition) if result: found.extend(result) elif type(node) is Choices: for option in node.options: result = search_in(option, condition) if result: found.extend(result) elif type(node) in [WarningTrigger, ErrorTrigger]: found.extend(search_in(node.link, condition)) return found if rule: result = search_in(get_rule(self.code, rule), condition) else: result = [] for rule in [ r for r in self.code.ast if type(r) is RuleDefinition ]: result.extend(search_in(rule, condition)) if expected == 0: return result elif expected == 1: return result[0] if result else None else: return result[:expected] if len(result) >= expected else None
def import_rules_from_lib(self, lib_name, rules): to_parse, directory_path = self._open_lib_file(lib_name) from KomBuInterpreter.KomBuParser import KomBuParser from KomBuInterpreter.KomBuChecker import KomBuChecker imported_libs_parser = KomBuParser() imported_code = imported_libs_parser.parse(to_parse, directory_path, lib_name + '.kb', self.imports_cache) for rule_to_import in rules: if not get_rule(imported_code, lib_name + '__' + rule_to_import): raise NameError( "File {} : 'with {} from {}' : Cannot import {} from file {}.kb" .format(self.filename, ", ".join(rules), lib_name, rule_to_import, lib_name)) else: self.new_namespaces[rule_to_import] = lib_name self.imports_cache[lib_name] = imported_code self.imported_code.add(imported_code)
def import_(self, imports_list): for toimport in imports_list: if type( toimport ) is Import and toimport.toimport not in self.imports_cache.keys(): self.import_lib(toimport.toimport) elif type(toimport) is Import: cached_code = self.imports_cache.get(toimport.toimport) if cached_code: for rule in [ r for r in cached_code.ast if type(r) is RuleDefinition ]: self.new_namespaces[rule] = toimport.toimport elif type( toimport ) is FromImport and toimport.from_file not in self.imports_cache.keys( ): self.import_rules_from_lib(toimport.from_file, toimport.toimport) elif type(toimport) is FromImport: for rule in toimport.toimport: cached_code = self.imports_cache.get(toimport.from_file) if cached_code and not get_rule( cached_code, toimport.from_file + '__' + rule): raise NameError( "File {} : 'with {} from {}' : Cannot import {} from file {}.kb" .format(self.filename, ", ".join(toimport.toimport), toimport.from_file, rule, toimport.from_file)) else: self.new_namespaces[rule] = toimport.from_file