from common.global_symbols import NotInit from core.ExecutionContext import ContextActions, ExecutionContext from core.ReturnValue import ReturnValue from core.concept import DefinitionType from core.error import ErrorContext, SheerkaException from evaluators.base_evaluator import EvaluatorEvalResult, EvaluatorMatchResult, OneReturnValueEvaluator from parsers.BnfDefinitionParser import BnfDefinitionParser from parsers.ConceptDefinitionParser import ConceptDefinition from parsers.parser_utils import ParsingException from parsers.tokenizer import TokenKind, Tokenizer NAMES_TOKEN_TYPES = {TokenKind.IDENTIFIER, TokenKind.STRING} CONCEPT_PARTS_TO_USE = [ "body", "where", "pre", "post", "ret", ] class DefConceptEvaluator(OneReturnValueEvaluator): """ This evaluator create the definition of a new concept after it is recognized by the RecognizeDefConcept parser """ NAME = "DefConcept" def __init__(self): super().__init__(self.NAME, ContextActions.EVALUATION, 50) def matches(self, context: ExecutionContext, return_value: ReturnValue) -> EvaluatorMatchResult: return EvaluatorMatchResult(return_value.status and isinstance(return_value.value, ConceptDefinition)) def eval(self, context: ExecutionContext, evaluation_context: object, return_value: ReturnValue) -> EvaluatorEvalResult: try: concept_def = return_value.value variables = self._get_variables(context, concept_def) parameters = None if concept_def.definition_type == DefinitionType.BNF: self._validate_bnf(context, concept_def) ret_val = context.sheerka.define_new_concept(context, concept_def.name, False, False, concept_def.body, concept_def.where, concept_def.pre, concept_def.post, concept_def.ret, concept_def.definition, concept_def.definition_type, concept_def.auto_eval, variables=variables, parameters=parameters) if ret_val.status: new = ReturnValue(self.NAME, True, ret_val.value, parents=[return_value]) return EvaluatorEvalResult([new], [return_value]) else: error_ret_val = ReturnValue(self.NAME, False, ret_val.value, [return_value]) return EvaluatorEvalResult([error_ret_val], []) except ParsingException as ex: error_context = ErrorContext(self.NAME, context, ex.error) error_ret_val = ReturnValue(self.NAME, False, error_context, [return_value]) return EvaluatorEvalResult([error_ret_val], []) def _get_variables(self, context: ExecutionContext, concept_def: ConceptDefinition): variables_found = set() # list of names, there is no tuple definition = concept_def.definition or concept_def.name possible_vars_from_name = self._get_possible_vars_from_def(context, definition) possible_vars_from_name_as_set = set(possible_vars_from_name) for part in CONCEPT_PARTS_TO_USE: # if these possibles variables are referenced in other parts of the definition, they may be variables part_value = getattr(concept_def, part) if part_value == "": continue possible_vars_from_part = self._get_possible_vars_from_part(context, part_value) variables_found.update(possible_vars_from_name_as_set & possible_vars_from_part) # add variables from add_var if concept_def.def_var: variables_found.update(concept_def.def_var) with_default_value = [v if isinstance(v, tuple) else (v, NotInit) for v in variables_found] # variables are sorted sorted_vars = [] for possible_var in possible_vars_from_name: for found in with_default_value: if possible_var == found[0]: sorted_vars.append(found) return sorted_vars @staticmethod def _get_possible_vars_from_def(context, definition): """ :param context: :type context: :param definition: :type definition: :return: list of names :rtype: """ names = (str(t.value) for t in Tokenizer(definition) if t.type in NAMES_TOKEN_TYPES) possible_vars = filter(lambda x: not context.sheerka.is_a_concept_name(x), names) return list(possible_vars) @staticmethod def _get_possible_vars_from_part(context, part): """ :param context: :type context: :param part: :type part: :return: :rtype: """ # not the final implementation # In the final impl, # we first need to check if the part is a concept call (rather than a concept name) names = (str(t.value) for t in Tokenizer(part) if t.type in NAMES_TOKEN_TYPES) possible_vars = filter(lambda x: not context.sheerka.is_a_concept_name(x), names) return set(possible_vars) @staticmethod def _validate_bnf(context, definition): parser = BnfDefinitionParser(context, definition.definition, definition.name) parser.parse() if parser.error_sink: raise ParsingException(parser.error_sink[0]) return True