Added chicken and egg recursion detection
This commit is contained in:
@@ -40,3 +40,6 @@ eighteen isa number
|
|||||||
nineteen isa number
|
nineteen isa number
|
||||||
twenty isa number
|
twenty isa number
|
||||||
def concept twenties from bnf twenty number where number < 10 as twenty + number
|
def concept twenties from bnf twenty number where number < 10 as twenty + number
|
||||||
|
def concept thirty as 30
|
||||||
|
def concept forty as 40
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class BuiltinConcepts(Enum):
|
|||||||
REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible
|
REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible
|
||||||
NOT_A_SET = "not a set" # the concept has no entry in sets
|
NOT_A_SET = "not a set" # the concept has no entry in sets
|
||||||
WHERE_CLAUSE_FAILED = "where clause failed" # failed to validate where clause during evaluation
|
WHERE_CLAUSE_FAILED = "where clause failed" # failed to validate where clause during evaluation
|
||||||
|
CHICKEN_AND_EGG = "chicken and egg" # infinite recursion when declaring concept
|
||||||
|
|
||||||
NODE = "node"
|
NODE = "node"
|
||||||
GENERIC_NODE = "generic node"
|
GENERIC_NODE = "generic node"
|
||||||
@@ -92,7 +93,8 @@ BuiltinErrors = [str(e) for e in {
|
|||||||
BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
||||||
BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
|
BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
|
||||||
BuiltinConcepts.NOT_A_SET,
|
BuiltinConcepts.NOT_A_SET,
|
||||||
BuiltinConcepts.WHERE_CLAUSE_FAILED
|
BuiltinConcepts.WHERE_CLAUSE_FAILED,
|
||||||
|
BuiltinConcepts.CHICKEN_AND_EGG
|
||||||
}]
|
}]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import logging
|
|||||||
import core.ast.nodes
|
import core.ast.nodes
|
||||||
from core.ast.nodes import CallNodeConcept, GenericNodeConcept
|
from core.ast.nodes import CallNodeConcept, GenericNodeConcept
|
||||||
from core.ast.visitors import UnreferencedNamesVisitor
|
from core.ast.visitors import UnreferencedNamesVisitor
|
||||||
|
|
||||||
from core.builtin_concepts import BuiltinConcepts
|
from core.builtin_concepts import BuiltinConcepts
|
||||||
|
|
||||||
|
|
||||||
@@ -100,11 +99,22 @@ def expect_one(context, return_values, logger=None):
|
|||||||
return_values[0],
|
return_values[0],
|
||||||
parents=return_values)
|
parents=return_values)
|
||||||
else:
|
else:
|
||||||
return sheerka.ret(
|
# test if only one evaluator in error
|
||||||
context.who,
|
from evaluators.OneErrorEvaluator import OneErrorEvaluator
|
||||||
False,
|
one_error_evaluator = OneErrorEvaluator()
|
||||||
sheerka.new(BuiltinConcepts.TOO_MANY_ERRORS, body=return_values),
|
reduce_requested = sheerka.ret(context.who, True, sheerka.new(BuiltinConcepts.REDUCE_REQUESTED))
|
||||||
parents=return_values)
|
if one_error_evaluator.matches(context, return_values + [reduce_requested]):
|
||||||
|
return sheerka.ret(
|
||||||
|
context.who,
|
||||||
|
False,
|
||||||
|
one_error_evaluator.eval(context, return_values).body,
|
||||||
|
parents=return_values)
|
||||||
|
else:
|
||||||
|
return sheerka.ret(
|
||||||
|
context.who,
|
||||||
|
False,
|
||||||
|
sheerka.new(BuiltinConcepts.TOO_MANY_ERRORS, body=return_values),
|
||||||
|
parents=return_values)
|
||||||
|
|
||||||
|
|
||||||
def get_names(sheerka, concept_node):
|
def get_names(sheerka, concept_node):
|
||||||
@@ -210,5 +220,3 @@ def _extract_predicates(sheerka, node, variables_to_include, variables_to_exclud
|
|||||||
predicates.append(res)
|
predicates.append(res)
|
||||||
|
|
||||||
return predicates
|
return predicates
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+27
-18
@@ -242,24 +242,24 @@ class Concept:
|
|||||||
def body(self):
|
def body(self):
|
||||||
return self.values[ConceptParts.BODY] if ConceptParts.BODY in self.values else None
|
return self.values[ConceptParts.BODY] if ConceptParts.BODY in self.values else None
|
||||||
|
|
||||||
def add_codes(self, codes):
|
# def add_codes(self, codes):
|
||||||
"""
|
# """
|
||||||
Gets the ASTs for 'where', 'pre', 'post' and 'body'
|
# Gets the ASTs for 'where', 'pre', 'post' and 'body'
|
||||||
There ASTs are know when the concept is freshly parsed.
|
# There ASTs are know when the concept is freshly parsed.
|
||||||
So the values are kept in cache.
|
# So the values are kept in cache.
|
||||||
|
#
|
||||||
For concepts loaded from sdp, these ASTs must be created again
|
# For concepts loaded from sdp, these ASTs must be created again
|
||||||
TODO : Seems to be a service method. Can be put somewhere else
|
# TODO : Seems to be a service method. Can be put somewhere else
|
||||||
:param codes:
|
# :param codes:
|
||||||
:return:
|
# :return:
|
||||||
"""
|
# """
|
||||||
if codes is None:
|
# if codes is None:
|
||||||
return
|
# return
|
||||||
|
#
|
||||||
for key in codes:
|
# for key in codes:
|
||||||
self.compiled[key] = codes[key]
|
# self.compiled[key] = codes[key]
|
||||||
|
#
|
||||||
return self
|
# return self
|
||||||
|
|
||||||
def get_digest(self):
|
def get_digest(self):
|
||||||
"""
|
"""
|
||||||
@@ -406,3 +406,12 @@ class DoNotResolve:
|
|||||||
set concept.compiled[BODY] to DoNotResolve(value)
|
set concept.compiled[BODY] to DoNotResolve(value)
|
||||||
"""
|
"""
|
||||||
value: object
|
value: object
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass()
|
||||||
|
class InfiniteRecursionResolved:
|
||||||
|
"""This class is used to when we managed to break an infinite recursion concept definition"""
|
||||||
|
value: object
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return self.value
|
||||||
|
|||||||
@@ -222,6 +222,8 @@ class ExecutionContext:
|
|||||||
to_str = self.return_value_to_str(r)
|
to_str = self.return_value_to_str(r)
|
||||||
logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
|
logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
|
||||||
|
|
||||||
|
def get_parent(self):
|
||||||
|
return self._parent
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def return_value_to_str(r):
|
def return_value_to_str(r):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from core.builtin_concepts import BuiltinConcepts
|
from core.builtin_concepts import BuiltinConcepts
|
||||||
from core.concept import Concept
|
from core.concept import Concept
|
||||||
from sdp.sheerkaDataProvider import SheerkaDataProvider
|
from core.sheerka.ExecutionContext import ExecutionContext
|
||||||
|
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event
|
||||||
import pprint
|
import pprint
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ class SheerkaDump:
|
|||||||
defs = self.sheerka.sdp.get(self.sheerka.CONCEPTS_DEFINITIONS_ENTRY)
|
defs = self.sheerka.sdp.get(self.sheerka.CONCEPTS_DEFINITIONS_ENTRY)
|
||||||
self.sheerka.log.info(defs)
|
self.sheerka.log.info(defs)
|
||||||
|
|
||||||
def dump_desc(self, *concept_names):
|
def dump_desc(self, *concept_names, eval=False):
|
||||||
first = True
|
first = True
|
||||||
for concept_name in concept_names:
|
for concept_name in concept_names:
|
||||||
if isinstance(concept_name, Concept):
|
if isinstance(concept_name, Concept):
|
||||||
@@ -43,13 +44,20 @@ class SheerkaDump:
|
|||||||
concepts = [concepts]
|
concepts = [concepts]
|
||||||
|
|
||||||
for c in concepts:
|
for c in concepts:
|
||||||
|
if eval:
|
||||||
|
event = Event(f"Evaluating {c}", "")
|
||||||
|
context = ExecutionContext("dump_desc", event, self.sheerka)
|
||||||
|
evaluated = self.sheerka.evaluate_concept(context, c)
|
||||||
|
value = evaluated.body if evaluated.key == c.key else evaluated
|
||||||
|
|
||||||
if not first:
|
if not first:
|
||||||
self.sheerka.log.info("")
|
self.sheerka.log.info("")
|
||||||
self.sheerka.log.info(f"name : {c.name}")
|
self.sheerka.log.info(f"name : {c.name}")
|
||||||
self.sheerka.log.info(f"bnf : {c.metadata.definition}")
|
self.sheerka.log.info(f"bnf : {c.metadata.definition}")
|
||||||
self.sheerka.log.info(f"key : {c.key}")
|
self.sheerka.log.info(f"key : {c.key}")
|
||||||
self.sheerka.log.info(f"body : {c.metadata.body}")
|
self.sheerka.log.info(f"body : {c.metadata.body}")
|
||||||
self.sheerka.log.info(f"value : {c.body}")
|
if eval:
|
||||||
|
self.sheerka.log.info(f"value : {value}")
|
||||||
self.sheerka.log.info(f"digest : {c.get_digest()}")
|
self.sheerka.log.info(f"digest : {c.get_digest()}")
|
||||||
|
|
||||||
if self.sheerka.isaset(c):
|
if self.sheerka.isaset(c):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from core.builtin_concepts import BuiltinConcepts
|
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
|
import core.builtin_helpers
|
||||||
|
|
||||||
CONCEPT_EVALUATION_STEPS = [
|
CONCEPT_EVALUATION_STEPS = [
|
||||||
@@ -13,6 +13,55 @@ class SheerkaEvaluateConcept:
|
|||||||
self.sheerka = sheerka
|
self.sheerka = sheerka
|
||||||
self.logger_name = self.evaluate_concept.__name__
|
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):
|
def initialize_concept_asts(self, context, concept: Concept, logger=None):
|
||||||
"""
|
"""
|
||||||
Updates the codes of the newly created concept
|
Updates the codes of the newly created concept
|
||||||
@@ -75,6 +124,15 @@ class SheerkaEvaluateConcept:
|
|||||||
if isinstance(to_resolve, DoNotResolve):
|
if isinstance(to_resolve, DoNotResolve):
|
||||||
return to_resolve.value
|
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})"
|
desc = f"Evaluating {current_prop} (concept={current_concept})"
|
||||||
context.log(logger, desc, self.logger_name)
|
context.log(logger, desc, self.logger_name)
|
||||||
with context.push(desc=desc, obj=current_concept) as sub_context:
|
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
|
# otherwise, execute all return values to find out what is the value
|
||||||
else:
|
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)
|
one_r = core.builtin_helpers.expect_one(context, r)
|
||||||
sub_context.add_values(return_values=one_r)
|
sub_context.add_values(return_values=one_r)
|
||||||
if one_r.status:
|
if one_r.status:
|
||||||
@@ -100,10 +159,11 @@ class SheerkaEvaluateConcept:
|
|||||||
else:
|
else:
|
||||||
error = one_r.value
|
error = one_r.value
|
||||||
|
|
||||||
return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
return error if self.sheerka.isinstance(error, BuiltinConcepts.CHICKEN_AND_EGG) \
|
||||||
body=error,
|
else self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
||||||
concept=current_concept,
|
body=error,
|
||||||
property_name=current_prop)
|
concept=current_concept,
|
||||||
|
property_name=current_prop)
|
||||||
|
|
||||||
def resolve_list(self, context, list_to_resolve, current_prop, current_concept, logger):
|
def resolve_list(self, context, list_to_resolve, current_prop, current_concept, logger):
|
||||||
"""When dealing with a list, there are two possibilities"""
|
"""When dealing with a list, there are two possibilities"""
|
||||||
@@ -168,7 +228,7 @@ class SheerkaEvaluateConcept:
|
|||||||
else:
|
else:
|
||||||
# Do not send the current concept for the properties
|
# Do not send the current concept for the properties
|
||||||
resolved = self.resolve(context, prop_ast, prop_name, None, logger)
|
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
|
resolved.set_prop("concept", concept) # since current concept was not sent
|
||||||
return resolved
|
return resolved
|
||||||
else:
|
else:
|
||||||
@@ -178,10 +238,10 @@ class SheerkaEvaluateConcept:
|
|||||||
if part_key in concept.compiled and concept.compiled[part_key] is not None:
|
if part_key in concept.compiled and concept.compiled[part_key] is not None:
|
||||||
metadata_ast = concept.compiled[part_key]
|
metadata_ast = concept.compiled[part_key]
|
||||||
resolved = self.resolve(context, metadata_ast, part_key, concept, logger)
|
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
|
return resolved
|
||||||
else:
|
else:
|
||||||
concept.values[part_key] = resolved
|
concept.values[part_key] = self.get_infinite_recursion_resolution(resolved) or resolved
|
||||||
|
|
||||||
# validate where clause
|
# validate where clause
|
||||||
if concept.metadata.where is not None:
|
if concept.metadata.where is not None:
|
||||||
|
|||||||
+31
-18
@@ -249,11 +249,12 @@ class Sheerka(Concept):
|
|||||||
if not self.skip_builtins_in_db:
|
if not self.skip_builtins_in_db:
|
||||||
self.sdp.save_result(self, execution_context)
|
self.sdp.save_result(self, execution_context)
|
||||||
|
|
||||||
# hack to save valid concept definition
|
# hack to save valid concept definition
|
||||||
if not self.during_restore:
|
# if not self.during_restore:
|
||||||
if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
|
# if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
|
||||||
with open(CONCEPTS_FILE, "a") as f:
|
# with open(CONCEPTS_FILE, "a") as f:
|
||||||
f.write(text + "\n")
|
# f.write(text + "\n")
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def execute(self, execution_context, return_values, execution_steps, logger=None):
|
def execute(self, execution_context, return_values, execution_steps, logger=None):
|
||||||
@@ -364,19 +365,26 @@ class Sheerka(Concept):
|
|||||||
concept_key = str(concept_key)
|
concept_key = str(concept_key)
|
||||||
|
|
||||||
# first search in cache
|
# first search in cache
|
||||||
result = self.cache_by_key[concept_key] if concept_key in self.cache_by_key else \
|
if concept_key in self.cache_by_key:
|
||||||
self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
|
result = self.cache_by_key[concept_key]
|
||||||
|
else:
|
||||||
|
result = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
|
||||||
|
if result is None:
|
||||||
|
metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key)
|
||||||
|
result = self._get_unknown(metadata)
|
||||||
|
|
||||||
if result and (concept_id is None or not isinstance(result, list)):
|
self.cache_by_key[concept_key] = result
|
||||||
|
for r in (result if isinstance(result, list) else [result]):
|
||||||
|
if r.id:
|
||||||
|
self.cache_by_id[r.id] = r
|
||||||
|
|
||||||
|
if not (isinstance(result, list) and concept_id):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if isinstance(result, list):
|
# result is a list, but we have the concept_id to discriminate
|
||||||
if concept_id:
|
for c in result:
|
||||||
for c in result:
|
if c.id == concept_id:
|
||||||
if c.id == concept_id:
|
return c
|
||||||
return c
|
|
||||||
else:
|
|
||||||
return result
|
|
||||||
|
|
||||||
metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key)
|
metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key)
|
||||||
return self._get_unknown(metadata)
|
return self._get_unknown(metadata)
|
||||||
@@ -386,10 +394,15 @@ class Sheerka(Concept):
|
|||||||
return ErrorConcept("Concept id is undefined.")
|
return ErrorConcept("Concept id is undefined.")
|
||||||
|
|
||||||
# first search in cache
|
# first search in cache
|
||||||
result = self.cache_by_id[concept_id] if concept_id in self.cache_by_id else \
|
if concept_id in self.cache_by_id:
|
||||||
self.sdp.get_safe(self.CONCEPTS_BY_ID_ENTRY, concept_id)
|
result = self.cache_by_id[concept_id]
|
||||||
|
else:
|
||||||
|
result = self.sdp.get_safe(self.CONCEPTS_BY_ID_ENTRY, concept_id)
|
||||||
|
if result is None:
|
||||||
|
result = self._get_unknown(('id', concept_id))
|
||||||
|
self.cache_by_id[concept_id] = result
|
||||||
|
|
||||||
return result or self._get_unknown(('id', concept_id))
|
return result
|
||||||
|
|
||||||
def get_concepts_definitions(self, context):
|
def get_concepts_definitions(self, context):
|
||||||
if self.concepts_definitions_cache:
|
if self.concepts_definitions_cache:
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
|||||||
from parsers.BaseParser import NotInitializedNode
|
from parsers.BaseParser import NotInitializedNode
|
||||||
from parsers.ConceptLexerParser import ParsingExpression, ParsingExpressionVisitor
|
from parsers.ConceptLexerParser import ParsingExpression, ParsingExpressionVisitor
|
||||||
from parsers.DefaultParser import DefConceptNode
|
from parsers.DefaultParser import DefConceptNode
|
||||||
|
|
||||||
from parsers.PythonParser import PythonNode
|
from parsers.PythonParser import PythonNode
|
||||||
|
import core.utils
|
||||||
|
|
||||||
|
|
||||||
class ConceptOrRuleNameVisitor(ParsingExpressionVisitor):
|
class ConceptOrRuleNameVisitor(ParsingExpressionVisitor):
|
||||||
@@ -66,7 +66,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
|
|||||||
setattr(concept.metadata, prop, source)
|
setattr(concept.metadata, prop, source)
|
||||||
|
|
||||||
# try to find what can be a property
|
# try to find what can be a property
|
||||||
concept_name = [part.value for part in def_concept_node.name.tokens]
|
concept_name = [part.value for part in core.utils.strip_tokens(def_concept_node.name.tokens, True)]
|
||||||
for p in self.get_props(sheerka, part_ret_val, concept_name):
|
for p in self.get_props(sheerka, part_ret_val, concept_name):
|
||||||
props_found.add(p)
|
props_found.add(p)
|
||||||
|
|
||||||
@@ -108,11 +108,12 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
|
|||||||
# Case of python code
|
# Case of python code
|
||||||
#
|
#
|
||||||
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, PythonNode):
|
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, PythonNode):
|
||||||
python_node = ret_value.value.value
|
if len(concept_name) > 1:
|
||||||
as_concept_node = python_to_concept(python_node.ast_)
|
python_node = ret_value.value.value
|
||||||
variables = get_names(sheerka, as_concept_node)
|
as_concept_node = python_to_concept(python_node.ast_)
|
||||||
variables = filter(lambda x: x in concept_name, variables)
|
variables = get_names(sheerka, as_concept_node)
|
||||||
return list(variables)
|
variables = filter(lambda x: x in concept_name, variables)
|
||||||
|
return list(variables)
|
||||||
|
|
||||||
#
|
#
|
||||||
# case of concept
|
# case of concept
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ class BaseTest:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pretval(concept, source=None, parser="parsers.name"):
|
def pretval(concept, source=None, parser="parsers.name"):
|
||||||
"""ParserResult ret_val"""
|
"""ParserResult ret_val (p stands for ParserResult)"""
|
||||||
return ReturnValueConcept(
|
return ReturnValueConcept(
|
||||||
"some_name",
|
"some_name",
|
||||||
True,
|
True,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from core.builtin_concepts import BuiltinConcepts
|
from core.builtin_concepts import BuiltinConcepts
|
||||||
from core.concept import PROPERTIES_TO_SERIALIZE
|
from core.concept import PROPERTIES_TO_SERIALIZE, Concept
|
||||||
from sdp.sheerkaDataProvider import SheerkaDataProvider
|
from sdp.sheerkaDataProvider import SheerkaDataProvider
|
||||||
|
|
||||||
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
||||||
@@ -80,12 +80,10 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
|
|||||||
sheerka.cache_by_key = {} # reset the cache
|
sheerka.cache_by_key = {} # reset the cache
|
||||||
loaded = sheerka.get(concept.key)
|
loaded = sheerka.get(concept.key)
|
||||||
|
|
||||||
assert loaded is not None
|
|
||||||
assert loaded == concept
|
assert loaded == concept
|
||||||
|
|
||||||
# I can also get it by its id
|
# I can also get it by its id
|
||||||
loaded = sheerka.sdp.get(sheerka.CONCEPTS_BY_ID_ENTRY, concept.id)
|
loaded = sheerka.sdp.get(sheerka.CONCEPTS_BY_ID_ENTRY, concept.id)
|
||||||
assert loaded is not None
|
|
||||||
assert loaded == concept
|
assert loaded == concept
|
||||||
|
|
||||||
def test_i_can_get_a_concept_by_its_id(self):
|
def test_i_can_get_a_concept_by_its_id(self):
|
||||||
@@ -96,7 +94,6 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
|
|||||||
sheerka.cache_by_key = {} # reset the cache
|
sheerka.cache_by_key = {} # reset the cache
|
||||||
loaded = sheerka.get_by_id(concept.id)
|
loaded = sheerka.get_by_id(concept.id)
|
||||||
|
|
||||||
assert loaded is not None
|
|
||||||
assert loaded == concept
|
assert loaded == concept
|
||||||
|
|
||||||
def test_i_can_get_list_of_concept_when_same_key_when_no_cache(self):
|
def test_i_can_get_list_of_concept_when_same_key_when_no_cache(self):
|
||||||
@@ -178,3 +175,12 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
|
|||||||
|
|
||||||
result = sheerka.get(concept1.key, "wrong id")
|
result = sheerka.get(concept1.key, "wrong id")
|
||||||
assert sheerka.isinstance(result, BuiltinConcepts.UNKNOWN_CONCEPT)
|
assert sheerka.isinstance(result, BuiltinConcepts.UNKNOWN_CONCEPT)
|
||||||
|
|
||||||
|
def test_concept_that_references_itself_is_correctly_created(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
concept = Concept("foo", body="foo")
|
||||||
|
|
||||||
|
res = sheerka.create_new_concept(self.get_context(sheerka), concept)
|
||||||
|
|
||||||
|
assert res.status
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, ParserResultConcept
|
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, ParserResultConcept
|
||||||
from core.concept import Concept, simplec, DoNotResolve, ConceptParts, Property
|
from core.concept import Concept, simplec, DoNotResolve, ConceptParts, Property, InfiniteRecursionResolved
|
||||||
from parsers.PythonParser import PythonNode
|
from parsers.PythonParser import PythonNode
|
||||||
|
|
||||||
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
||||||
@@ -369,3 +369,91 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
|
|||||||
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
|
||||||
assert sheerka.isinstance(evaluated, BuiltinConcepts.WHERE_CLAUSE_FAILED)
|
assert sheerka.isinstance(evaluated, BuiltinConcepts.WHERE_CLAUSE_FAILED)
|
||||||
assert evaluated.body == concept
|
assert evaluated.body == concept
|
||||||
|
|
||||||
|
def test_i_can_detect_infinite_recursion_with_numeric_constant(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
one_str = Concept("one", body="1")
|
||||||
|
one_digit = Concept("1", body="one")
|
||||||
|
|
||||||
|
sheerka.add_in_cache(one_str)
|
||||||
|
sheerka.add_in_cache(one_digit)
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), one_digit)
|
||||||
|
assert evaluated.key == one_digit.key
|
||||||
|
assert evaluated.body == InfiniteRecursionResolved(1)
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), one_str)
|
||||||
|
assert evaluated.key == one_str.key
|
||||||
|
assert evaluated.body == InfiniteRecursionResolved(1)
|
||||||
|
|
||||||
|
def test_i_can_detect_infinite_recursion_with_boolean_constant(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
true_str = Concept("true", body="True")
|
||||||
|
true_bool = Concept("True", body="true")
|
||||||
|
|
||||||
|
sheerka.add_in_cache(true_str)
|
||||||
|
sheerka.add_in_cache(true_bool)
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), true_str)
|
||||||
|
assert evaluated.key == true_str.key
|
||||||
|
assert evaluated.body == InfiniteRecursionResolved(True)
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), true_bool)
|
||||||
|
assert evaluated.key == true_bool.key
|
||||||
|
assert evaluated.body == InfiniteRecursionResolved(True)
|
||||||
|
|
||||||
|
def test_i_can_detect_infinite_recursion_with_constant_with_more_concepts(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
c1 = sheerka.add_in_cache(Concept("one", body="1"))
|
||||||
|
c2 = sheerka.add_in_cache(Concept("1", body="2"))
|
||||||
|
c3 = sheerka.add_in_cache(Concept("2", body="3"))
|
||||||
|
c4 = sheerka.add_in_cache(Concept("3", body="one"))
|
||||||
|
|
||||||
|
for concept in (c1, c2, c3, c4):
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
|
||||||
|
assert evaluated.key == concept.key
|
||||||
|
assert evaluated.body == InfiniteRecursionResolved(3)
|
||||||
|
|
||||||
|
def test_i_can_detect_infinite_recursion_when_no_constant(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
foo = sheerka.add_in_cache(Concept("foo", body="bar"))
|
||||||
|
bar = sheerka.add_in_cache(Concept("bar", body="baz"))
|
||||||
|
baz = sheerka.add_in_cache(Concept("baz", body="qux"))
|
||||||
|
qux = sheerka.add_in_cache(Concept("qux", body="foo"))
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), foo)
|
||||||
|
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
|
||||||
|
assert evaluated.body == {foo, bar, baz, qux}
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), bar)
|
||||||
|
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
|
||||||
|
assert evaluated.body == {foo, bar, baz, qux}
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), baz)
|
||||||
|
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
|
||||||
|
assert evaluated.body == {foo, bar, baz, qux}
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), qux)
|
||||||
|
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
|
||||||
|
assert evaluated.body == {foo, bar, baz, qux}
|
||||||
|
|
||||||
|
def test_i_can_detect_auto_recursion(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
foo = sheerka.add_in_cache(Concept("foo", body="foo"))
|
||||||
|
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), foo)
|
||||||
|
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
|
||||||
|
assert evaluated.body == {foo}
|
||||||
|
|
||||||
|
def test_i_can_manage_auto_recursion_when_constant(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
one = Concept("1", body="1")
|
||||||
|
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), one)
|
||||||
|
assert evaluated.key == one.key
|
||||||
|
assert evaluated.body == 1
|
||||||
|
|||||||
@@ -242,3 +242,41 @@ class TestSheerka(TestUsingFileBasedSheerka):
|
|||||||
# only test a random one, it will be the same for the others
|
# only test a random one, it will be the same for the others
|
||||||
sheerka = self.get_sheerka()
|
sheerka = self.get_sheerka()
|
||||||
assert not sheerka.is_success(sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS))
|
assert not sheerka.is_success(sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS))
|
||||||
|
|
||||||
|
def test_cache_is_updated_after_get(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
# updated when by_key returns one element
|
||||||
|
sheerka.create_new_concept(self.get_context(sheerka), Concept("foo", body="1"))
|
||||||
|
sheerka.reset_cache()
|
||||||
|
sheerka.get("foo")
|
||||||
|
assert "foo" in sheerka.cache_by_key
|
||||||
|
assert "1001" in sheerka.cache_by_id
|
||||||
|
|
||||||
|
# updated when by_key returns two elements
|
||||||
|
sheerka.create_new_concept(self.get_context(sheerka), Concept("foo", body="2"))
|
||||||
|
sheerka.reset_cache()
|
||||||
|
sheerka.get("foo")
|
||||||
|
assert "foo" in sheerka.cache_by_key
|
||||||
|
assert "1001" in sheerka.cache_by_id
|
||||||
|
assert "1002" in sheerka.cache_by_id
|
||||||
|
|
||||||
|
# updated when by_id
|
||||||
|
sheerka.reset_cache()
|
||||||
|
sheerka.get_by_id("1001")
|
||||||
|
assert "1001" in sheerka.cache_by_id
|
||||||
|
assert "foo" not in sheerka.cache_by_key # cache_by_key not updated as "1001" is not the only one
|
||||||
|
|
||||||
|
def test_i_can_get_by_key_several_times(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
sheerka.create_new_concept(self.get_context(sheerka), Concept("foo", body="1"))
|
||||||
|
sheerka.create_new_concept(self.get_context(sheerka), Concept("foo", body="2"))
|
||||||
|
|
||||||
|
sheerka.reset_cache()
|
||||||
|
sheerka.get("foo", "1001") # only one element requested. But the cache must be updated with two elements
|
||||||
|
|
||||||
|
# let's check it
|
||||||
|
concepts = sheerka.get("foo")
|
||||||
|
assert len(concepts) == 2
|
||||||
|
assert concepts[0].id == "1001"
|
||||||
|
assert concepts[1].id == "1002"
|
||||||
|
|||||||
@@ -69,9 +69,8 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
|
|||||||
return ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=concept))
|
return ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=concept))
|
||||||
|
|
||||||
@pytest.mark.parametrize("ret_val, expected", [
|
@pytest.mark.parametrize("ret_val, expected", [
|
||||||
(
|
(ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=DefConceptNode([]))),
|
||||||
ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=DefConceptNode([]))),
|
True),
|
||||||
True),
|
|
||||||
(ReturnValueConcept(BaseParser.PREFIX + "some_name", False, ParserResultConcept(value=DefConceptNode([]))),
|
(ReturnValueConcept(BaseParser.PREFIX + "some_name", False, ParserResultConcept(value=DefConceptNode([]))),
|
||||||
False),
|
False),
|
||||||
(ReturnValueConcept(BaseParser.PREFIX + "some_name", True, "not a ParserResultConcept"), False),
|
(ReturnValueConcept(BaseParser.PREFIX + "some_name", True, "not a ParserResultConcept"), False),
|
||||||
@@ -133,11 +132,17 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
|
|||||||
|
|
||||||
assert from_db.compiled == {} # ast is not saved in db
|
assert from_db.compiled == {} # ast is not saved in db
|
||||||
|
|
||||||
def test_i_can_get_props_from_python_node(self):
|
def test_i_can_get_props_from_python_node_when_long_name(self):
|
||||||
ret_val = self.get_concept_part("isinstance(a, str)")
|
ret_val = self.get_concept_part("isinstance(a, str)")
|
||||||
context = self.get_context()
|
context = self.get_context()
|
||||||
|
|
||||||
assert AddConceptEvaluator.get_props(context.sheerka, ret_val, ["a"]) == ["a"]
|
assert AddConceptEvaluator.get_props(context.sheerka, ret_val, ["a", "b"]) == ["a"]
|
||||||
|
|
||||||
|
def test_i_cannot_get_props_from_python_node_when_name_has_only_one_token(self):
|
||||||
|
ret_val = self.get_concept_part("isinstance(a, str)")
|
||||||
|
context = self.get_context()
|
||||||
|
|
||||||
|
assert AddConceptEvaluator.get_props(context.sheerka, ret_val, ["a"]) == []
|
||||||
|
|
||||||
def test_i_can_get_props_from_another_concept(self):
|
def test_i_can_get_props_from_another_concept(self):
|
||||||
concept = Concept("hello").def_prop("a").def_prop("b")
|
concept = Concept("hello").def_prop("a").def_prop("b")
|
||||||
@@ -153,3 +158,16 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
|
|||||||
ret_val = self.get_return_value("mult (('+'|'-') add)?", parsing_expression)
|
ret_val = self.get_return_value("mult (('+'|'-') add)?", parsing_expression)
|
||||||
|
|
||||||
assert AddConceptEvaluator.get_props(self.get_context(), ret_val, []) == ["add", "mult"]
|
assert AddConceptEvaluator.get_props(self.get_context(), ret_val, []) == ["add", "mult"]
|
||||||
|
|
||||||
|
def test_concept_that_references_itself_is_correctly_created(self):
|
||||||
|
context = self.get_context()
|
||||||
|
def_concept_as_return_value = self.get_def_concept("foo", body="foo")
|
||||||
|
|
||||||
|
ret_val = AddConceptEvaluator().eval(context, def_concept_as_return_value)
|
||||||
|
|
||||||
|
assert ret_val.status
|
||||||
|
new_concept = ret_val.body.body
|
||||||
|
assert new_concept.name == 'foo'
|
||||||
|
assert new_concept.metadata.body == 'foo'
|
||||||
|
assert new_concept.props == {}
|
||||||
|
assert new_concept.metadata.props == []
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
|
|||||||
assert context.sheerka.isinstance(result.value, BuiltinConcepts.CONCEPT_EVAL_ERROR)
|
assert context.sheerka.isinstance(result.value, BuiltinConcepts.CONCEPT_EVAL_ERROR)
|
||||||
assert result.value.concept == concept_plus
|
assert result.value.concept == concept_plus
|
||||||
assert result.value.property_name == "b"
|
assert result.value.property_name == "b"
|
||||||
assert context.sheerka.isinstance(result.value.error, BuiltinConcepts.TOO_MANY_ERRORS)
|
assert context.sheerka.isinstance(result.value.error, BuiltinConcepts.ERROR)
|
||||||
|
|
||||||
def test_that_error_concepts_are_transformed_into_errors_only_if_the_key_is_different(self):
|
def test_that_error_concepts_are_transformed_into_errors_only_if_the_key_is_different(self):
|
||||||
context = self.get_context()
|
context = self.get_context()
|
||||||
|
|||||||
@@ -671,3 +671,28 @@ as:
|
|||||||
|
|
||||||
res = sheerka.evaluate_user_input("eval twenty three")
|
res = sheerka.evaluate_user_input("eval twenty three")
|
||||||
assert len(res) > 1
|
assert len(res) > 1
|
||||||
|
|
||||||
|
def test_i_can_detect_when_only_one_evaluator_is_in_error(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
sheerka.evaluate_user_input("def concept 1 as one")
|
||||||
|
res = sheerka.evaluate_user_input("1")
|
||||||
|
assert len(res) == 1
|
||||||
|
assert not res[0].status
|
||||||
|
assert sheerka.isinstance(res[0].body, BuiltinConcepts.CONCEPT_EVAL_ERROR)
|
||||||
|
|
||||||
|
def test_i_can_manage_some_type_of_infinite_recursion(self):
|
||||||
|
sheerka = self.get_sheerka()
|
||||||
|
|
||||||
|
sheerka.evaluate_user_input("def concept one as 1")
|
||||||
|
sheerka.evaluate_user_input("def concept 1 as one")
|
||||||
|
res = sheerka.evaluate_user_input("one + 1")
|
||||||
|
assert len(res) == 1
|
||||||
|
assert res[0].status
|
||||||
|
assert res[0].body == 2
|
||||||
|
|
||||||
|
res = sheerka.evaluate_user_input("1 + 1")
|
||||||
|
assert len(res) == 1
|
||||||
|
assert res[0].status
|
||||||
|
assert res[0].body == 2
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user