Added chicken and egg recursion detection

This commit is contained in:
2020-02-06 17:50:14 +01:00
parent afc1e22949
commit 7481b458e1
16 changed files with 358 additions and 77 deletions
@@ -1,5 +1,5 @@
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept, DoNotResolve, ConceptParts
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved
import core.builtin_helpers
CONCEPT_EVALUATION_STEPS = [
@@ -13,6 +13,55 @@ class SheerkaEvaluateConcept:
self.sheerka = sheerka
self.logger_name = self.evaluate_concept.__name__
@staticmethod
def infinite_recursion_detected(context, concept):
if concept is None:
return False
parent = context.get_parent()
while parent is not None:
if parent.who == context.who and parent.obj == concept:
return True
parent = parent.get_parent()
return False
@staticmethod
def get_infinite_recursion_resolution(obj):
if isinstance(obj, InfiniteRecursionResolved):
return obj
if isinstance(obj, Concept) and isinstance(obj.body, InfiniteRecursionResolved):
return obj.body
return None
def manage_infinite_recursion(self, context):
"""
We look for the fist parent that has a body that means something
We use the eval function with no local or global
:param context:
:return:
"""
parent = context
concepts_found = set()
while parent and parent.obj:
if parent.who == context.who and parent.desc == context.desc:
body = parent.obj.metadata.body
try:
return self.sheerka.ret(self.logger_name, True, InfiniteRecursionResolved(eval(body)))
except Exception:
pass
concepts_found.add(parent.obj)
parent = parent.get_parent()
return self.sheerka.ret(
self.logger_name,
False,
self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body=concepts_found))
def initialize_concept_asts(self, context, concept: Concept, logger=None):
"""
Updates the codes of the newly created concept
@@ -75,6 +124,15 @@ class SheerkaEvaluateConcept:
if isinstance(to_resolve, DoNotResolve):
return to_resolve.value
# manage infinite loop
if self.infinite_recursion_detected(context, current_concept):
with context.push(desc="Infinite recursion detected", obj=current_concept) as sub_context:
# I create a sub context in order to log what happened
sub_context.log_new(logger)
ret_val = self.manage_infinite_recursion(context)
sub_context.add_values(return_values=ret_val)
return ret_val.body
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:
@@ -92,7 +150,8 @@ class SheerkaEvaluateConcept:
# 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)
use_copy = [r for r in to_resolve] if hasattr(to_resolve, "__iter__") else to_resolve
r = self.sheerka.execute(sub_context, use_copy, 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:
@@ -100,10 +159,11 @@ class SheerkaEvaluateConcept:
else:
error = one_r.value
return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
body=error,
concept=current_concept,
property_name=current_prop)
return error if self.sheerka.isinstance(error, BuiltinConcepts.CHICKEN_AND_EGG) \
else 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"""
@@ -168,7 +228,7 @@ class SheerkaEvaluateConcept:
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):
if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved):
resolved.set_prop("concept", concept) # since current concept was not sent
return resolved
else:
@@ -178,10 +238,10 @@ 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, logger)
if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR):
if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved):
return resolved
else:
concept.values[part_key] = resolved
concept.values[part_key] = self.get_infinite_recursion_resolution(resolved) or resolved
# validate where clause
if concept.metadata.where is not None: