Concepts bodies are now evaluated on demand

This commit is contained in:
2020-02-27 10:48:45 +01:00
parent 7cd94e888f
commit ef31a4807d
25 changed files with 468 additions and 172 deletions
@@ -1,6 +1,6 @@
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved
import core.builtin_helpers
from core.builtin_helpers import add_to_ret_val, remove_from_ret_val, expect_one
CONCEPT_EVALUATION_STEPS = [
BuiltinConcepts.BEFORE_EVALUATION,
@@ -118,7 +118,7 @@ class SheerkaEvaluateConcept:
else:
self.sheerka.cache_by_key[concept.key].compiled = concept.compiled
def resolve(self, context, to_resolve, current_prop, current_concept):
def resolve(self, context, to_resolve, current_prop, current_concept, evaluate_body):
if isinstance(to_resolve, DoNotResolve):
return to_resolve.value
@@ -137,7 +137,7 @@ class SheerkaEvaluateConcept:
# 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)
evaluated = self.evaluate_concept(sub_context, to_resolve, evaluate_body)
sub_context.add_values(return_values=evaluated)
if evaluated.key == to_resolve.key:
return evaluated
@@ -147,8 +147,11 @@ class SheerkaEvaluateConcept:
# otherwise, execute all return values to find out what is the value
else:
use_copy = [r for r in to_resolve] if hasattr(to_resolve, "__iter__") else to_resolve
if evaluate_body:
sub_context.extra_info.append(BuiltinConcepts.CONCEPT_EVAL_REQUESTED)
r = self.sheerka.execute(sub_context, use_copy, CONCEPT_EVALUATION_STEPS)
one_r = core.builtin_helpers.expect_one(context, r)
one_r = expect_one(context, r)
sub_context.add_values(return_values=one_r)
if one_r.status:
return one_r.value
@@ -161,7 +164,7 @@ class SheerkaEvaluateConcept:
concept=current_concept,
property_name=current_prop)
def resolve_list(self, context, list_to_resolve, current_prop, current_concept):
def resolve_list(self, context, list_to_resolve, current_prop, current_concept, evaluate_body):
"""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)
@@ -170,7 +173,7 @@ class SheerkaEvaluateConcept:
return []
if self.sheerka.isinstance(list_to_resolve[0], BuiltinConcepts.RETURN_VALUE):
return self.resolve(context, list_to_resolve, current_prop, current_concept)
return self.resolve(context, list_to_resolve, current_prop, current_concept, evaluate_body)
res = []
for to_resolve in list_to_resolve:
@@ -181,35 +184,31 @@ class SheerkaEvaluateConcept:
concept=current_concept,
property_name=current_prop)
r = self.resolve(context, to_resolve, current_prop, current_concept)
r = self.resolve(context, to_resolve, current_prop, current_concept, evaluate_body)
if self.sheerka.isinstance(r, BuiltinConcepts.CONCEPT_EVAL_ERROR):
return r
res.append(r)
return res
def evaluate_concept(self, context, concept: Concept):
def evaluate_concept(self, context, concept: Concept, evaluate_body=False):
"""
Evaluation a concept
It means that if the where clause is True, will evaluate the body
:param context:
:param concept:
:param logger:
:param evaluate_body: If false, only evaluate body when necessary
:return: value of the evaluation or error
"""
if concept.metadata.is_evaluated:
return concept
#
# TODO : Validate the PRE condition
#
self.initialize_concept_asts(context, concept)
# to make sure of the order, it don't use ConceptParts.get_parts()
# props must be evaluated first, body must be evaluated before where
all_metadata_to_eval = ["pre", "post", "props", "body", "where"]
all_metadata_to_eval = self.choose_metadata_to_eval(concept, evaluate_body)
for metadata_to_eval in all_metadata_to_eval:
if metadata_to_eval == "props":
@@ -218,10 +217,10 @@ class SheerkaEvaluateConcept:
if isinstance(prop_ast, list):
# Do not send the current concept for the properties
resolved = self.resolve_list(context, prop_ast, prop_name, None)
resolved = self.resolve_list(context, prop_ast, prop_name, None, True)
else:
# Do not send the current concept for the properties
resolved = self.resolve(context, prop_ast, prop_name, None)
resolved = self.resolve(context, prop_ast, prop_name, None, True)
if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved):
resolved.set_prop("concept", concept) # since current concept was not sent
return resolved
@@ -237,12 +236,16 @@ class SheerkaEvaluateConcept:
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)
resolved = self.resolve(context, metadata_ast, part_key, concept, evaluate_body)
if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved):
return resolved
else:
concept.values[part_key] = self.get_infinite_recursion_resolution(resolved) or resolved
#
# TODO : Validate the PRE condition
#
# validate where clause
if ConceptParts.WHERE in concept.values:
where_value = concept.values[ConceptParts.WHERE]
@@ -254,5 +257,52 @@ class SheerkaEvaluateConcept:
#
concept.init_key() # only does it if needed
concept.metadata.is_evaluated = True
concept.metadata.is_evaluated = "body" in all_metadata_to_eval
return concept
def choose_metadata_to_eval(self, concept, evaluate_body):
if evaluate_body:
return ["pre", "post", "props", "body", "where"]
metadata = ["pre", "post"] + self.needed_metadata(concept) + ["where"]
return metadata
def needed_metadata(self, concept):
"""
Tries to find out if the evaluation of the body is necessary
It's a very basic approach that will need to be improved
:param concept:
:return:
"""
needed = []
for metadata in (ConceptParts.PRE, ConceptParts.POST, ConceptParts.WHERE):
if metadata not in concept.compiled:
continue
return_values = concept.compiled[metadata]
if not isinstance(return_values, list):
continue
for return_value in return_values:
if not self.sheerka.isinstance(return_value, BuiltinConcepts.RETURN_VALUE):
continue
if not return_value.status:
continue
if not self.sheerka.isinstance(return_value.body, BuiltinConcepts.PARSER_RESULT):
continue
if not isinstance(return_value.body.source, str):
continue
for prop_name in (p[0] for p in concept.metadata.props):
if prop_name in return_value.body.source:
needed.append("props")
break
if "self" in return_value.body.source:
needed.append("body")
return needed