from collections import namedtuple import parso from core.builtin_concepts import BuiltinConcepts from core.sheerka.ExecutionContext import ExecutionContext from core.sheerka.services.SheerkaFunctionsParametersHistory import SheerkaFunctionsParametersHistory from evaluators.BaseEvaluator import OneReturnValueEvaluator from evaluators.PythonEvaluator import PythonEvaluator from parso.python.tree import Name, PythonNode, Operator func_found = namedtuple("func_found", "name params") class UpdateFunctionsParametersEvaluator(OneReturnValueEvaluator): """ This evaluator scans all successful PythonEvaluator results It then records the parameters of every functions found. For example, if the PythonEvaluator successfully evaluated foo(1, 'string') + bar(3.14) the parameters 1 and 'string' will be recorded for the function 'foo' and the parameter 3.14 will be recorded for the function 'bar' These records will later be used as input for auto-completion """ NAME = "UpdateFunctionsParameters" def __init__(self): super().__init__(self.NAME, [BuiltinConcepts.AFTER_EVALUATION], 79) self.enabled = False def reset(self): self.enabled = False def matches(self, context, return_value): """ True if the return value is the successful result of PythonEvaluator :param context: :param return_value: :return: """ return return_value.status and return_value.who == context.sheerka.get_evaluator_name(PythonEvaluator.NAME) def eval(self, context: ExecutionContext, return_value): sheerka = context.sheerka params_record_service = sheerka.services[SheerkaFunctionsParametersHistory.NAME] if not return_value.parents: return self.add_not_found_error(sheerka, return_value) for parent in return_value.parents: if parent.who == sheerka.get_parser_name("Python") and \ parent.status and \ sheerka.isinstance(parent.body, BuiltinConcepts.PARSER_RESULT): break else: return self.add_not_found_error(sheerka, return_value) source = parent.body.source parsed = parso.parse(source) self.process_functions(context, params_record_service, parsed) return return_value def process_functions(self, context, service, node): if not hasattr(node, "children"): return if (func := self.get_function(node)) is not None: for i, p in enumerate(func.params): service.record_function_parameter(context, func.name, i, p) function_params = node.children[1].children[1] if hasattr(function_params, "children"): for child in function_params.children: # function parameters self.process_functions(context, service, child) else: for child in node.children: self.process_functions(context, service, child) def add_not_found_error(self, sheerka, return_value): error = sheerka.ret(self.name, False, sheerka.new(BuiltinConcepts.NOT_FOUND, body="source code"), parents=[return_value]) if return_value.parents is None: return_value.parents = [error] else: return_value.parents.append(error) return return_value @staticmethod def get_function(node): if len(node.children) == 2 and \ isinstance(node.children[0], Name) and \ isinstance(node.children[1], PythonNode) and \ node.children[1].type == "trailer" and \ len(node.children[1].children) >= 2 and \ isinstance(node.children[1].children[0], Operator) and \ node.children[1].children[0].value == "(" and \ isinstance(node.children[1].children[-1], Operator) and \ node.children[1].children[-1].value == ")": name = node.children[0].value if len(node.children[1].children) == 2: params = [] else: params_nodes = node.children[1].children[1] if hasattr(params_nodes, "children"): params = [p.get_code().strip() for p in params_nodes.children if not isinstance(p, Operator)] else: params = [params_nodes.get_code().strip()] return func_found(name=name, params=params) else: return None