from core.builtin_concepts import BuiltinConcepts from core.concept import Concept, DoNotResolve, ConceptParts import core.builtin_helpers CONCEPT_EVALUATION_STEPS = [ BuiltinConcepts.BEFORE_EVALUATION, BuiltinConcepts.EVALUATION, BuiltinConcepts.AFTER_EVALUATION] class SheerkaEvaluateConcept: def __init__(self, sheerka): self.sheerka = sheerka self.logger_name = self.evaluate_concept.__name__ def initialize_concept_asts(self, context, concept: Concept, logger=None): """ Updates the codes of the newly created concept Basically, it runs the parsers on all parts :param concept: :param context: :param logger: :return: """ steps = [BuiltinConcepts.BEFORE_PARSING, BuiltinConcepts.PARSING, BuiltinConcepts.AFTER_PARSING] for part_key in ConceptParts: if part_key in concept.compiled: continue source = getattr(concept.metadata, part_key.value) if source is None or not isinstance(source, str): continue if source.strip() == "": concept.compiled[part_key] = DoNotResolve(source) else: with context.push(desc=f"Initializing compiled for {part_key}") as sub_context: sub_context.log_new(logger) sub_context.add_inputs(source=source) to_parse = self.sheerka.ret(context.who, True, self.sheerka.new(BuiltinConcepts.USER_INPUT, body=source)) res = self.sheerka.execute(sub_context, to_parse, steps, logger) concept.compiled[part_key] = res sub_context.add_values(return_values=res) for prop, default_value in concept.metadata.props: if prop in concept.compiled: continue if default_value is None or not isinstance(default_value, str): continue if default_value.strip() == "": concept.compiled[prop] = DoNotResolve(default_value) else: with context.push(desc=f"Initializing AST for property {prop}") as sub_context: sub_context.log_new(logger) sub_context.add_inputs(source=default_value) to_parse = self.sheerka.ret(context.who, True, self.sheerka.new(BuiltinConcepts.USER_INPUT, body=default_value)) res = self.sheerka.execute(context, to_parse, steps) concept.compiled[prop] = res sub_context.add_values(return_values=res) # Updates the cache of concepts when possible if concept.key in self.sheerka.cache_by_key: entry = self.sheerka.cache_by_key[concept.key] if isinstance(entry, list): # TODO : manage when there are multiple entries pass else: self.sheerka.cache_by_key[concept.key].compiled = concept.compiled def resolve(self, context, to_resolve, current_prop, current_concept, logger): if isinstance(to_resolve, DoNotResolve): return to_resolve.value desc = f"Evaluating {current_prop} (concept={current_concept})" context.log(logger, desc, self.logger_name) with context.push(desc=desc, obj=current_concept) as sub_context: sub_context.log_new(logger) # when it's a concept, evaluate it if isinstance(to_resolve, Concept) and \ not context.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE): evaluated = self.evaluate_concept(sub_context, to_resolve, logger) sub_context.add_values(return_values=evaluated) if evaluated.key == to_resolve.key: return evaluated else: error = evaluated # otherwise, execute all return values to find out what is the value else: r = self.sheerka.execute(sub_context, to_resolve, CONCEPT_EVALUATION_STEPS, logger) one_r = core.builtin_helpers.expect_one(context, r) sub_context.add_values(return_values=one_r) if one_r.status: return one_r.value else: error = one_r.value return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR, body=error, concept=current_concept, property_name=current_prop) def resolve_list(self, context, list_to_resolve, current_prop, current_concept, logger): """When dealing with a list, there are two possibilities""" # It may be a list of ReturnValueConcept to execute (always the case for metadata) # or a list of single values (may be the case for properties) # in this latter case, all values are to be processed one by one and a list should be returned if len(list_to_resolve) == 0: return [] if self.sheerka.isinstance(list_to_resolve[0], BuiltinConcepts.RETURN_VALUE): return self.resolve(context, list_to_resolve, current_prop, current_concept, logger) res = [] for to_resolve in list_to_resolve: # sanity check if self.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE): return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR, body="Mix between real values and return values", concept=current_concept, property_name=current_prop) r = self.resolve(context, to_resolve, current_prop, current_concept, logger) if self.sheerka.isinstance(r, BuiltinConcepts.CONCEPT_EVAL_ERROR): return r res.append(r) return res def evaluate_concept(self, context, concept: Concept, logger=None): """ Evaluation a concept It means that if the where clause is True, will evaluate the body :param context: :param concept: :param logger: :return: value of the evaluation or error """ logger = logger or self.sheerka.log if concept.metadata.is_evaluated: return concept # WHERE condition should already be validated by the parser. # It's a mandatory condition for the concept before it can be recognized # # TODO : Validate the PRE condition # self.initialize_concept_asts(context, concept, logger) # to make sure of the order, it don't use ConceptParts.get_parts() # props must be evaluated first all_metadata_to_eval = ["props", "where", "pre", "post", "body"] for metadata_to_eval in all_metadata_to_eval: if metadata_to_eval == "props": for prop_name in (p for p in concept.props if p in concept.compiled): prop_ast = concept.compiled[prop_name] if isinstance(prop_ast, list): # Do not send the current concept for the properties resolved = self.resolve_list(context, prop_ast, prop_name, None, logger) else: # Do not send the current concept for the properties resolved = self.resolve(context, prop_ast, prop_name, None, logger) if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR): resolved.set_prop("concept", concept) # since current concept was not sent return resolved else: concept.set_prop(prop_name, resolved) else: part_key = ConceptParts(metadata_to_eval) if part_key in concept.compiled and concept.compiled[part_key] is not None: metadata_ast = concept.compiled[part_key] resolved = self.resolve(context, metadata_ast, part_key, concept, logger) if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR): return resolved else: concept.values[part_key] = resolved # # TODO : Validate the POST condition # concept.init_key() # only does it if needed concept.metadata.is_evaluated = True return concept