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
+3
View File
@@ -40,3 +40,6 @@ eighteen isa number
nineteen isa number
twenty isa number
def concept twenties from bnf twenty number where number < 10 as twenty + number
def concept thirty as 30
def concept forty as 40
+3 -1
View File
@@ -51,6 +51,7 @@ class BuiltinConcepts(Enum):
REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible
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
CHICKEN_AND_EGG = "chicken and egg" # infinite recursion when declaring concept
NODE = "node"
GENERIC_NODE = "generic node"
@@ -92,7 +93,8 @@ BuiltinErrors = [str(e) for e in {
BuiltinConcepts.CONCEPT_EVAL_ERROR,
BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
BuiltinConcepts.NOT_A_SET,
BuiltinConcepts.WHERE_CLAUSE_FAILED
BuiltinConcepts.WHERE_CLAUSE_FAILED,
BuiltinConcepts.CHICKEN_AND_EGG
}]
"""
+16 -8
View File
@@ -4,7 +4,6 @@ import logging
import core.ast.nodes
from core.ast.nodes import CallNodeConcept, GenericNodeConcept
from core.ast.visitors import UnreferencedNamesVisitor
from core.builtin_concepts import BuiltinConcepts
@@ -100,11 +99,22 @@ def expect_one(context, return_values, logger=None):
return_values[0],
parents=return_values)
else:
return sheerka.ret(
context.who,
False,
sheerka.new(BuiltinConcepts.TOO_MANY_ERRORS, body=return_values),
parents=return_values)
# test if only one evaluator in error
from evaluators.OneErrorEvaluator import OneErrorEvaluator
one_error_evaluator = OneErrorEvaluator()
reduce_requested = sheerka.ret(context.who, True, sheerka.new(BuiltinConcepts.REDUCE_REQUESTED))
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):
@@ -210,5 +220,3 @@ def _extract_predicates(sheerka, node, variables_to_include, variables_to_exclud
predicates.append(res)
return predicates
+27 -18
View File
@@ -242,24 +242,24 @@ class Concept:
def body(self):
return self.values[ConceptParts.BODY] if ConceptParts.BODY in self.values else None
def add_codes(self, codes):
"""
Gets the ASTs for 'where', 'pre', 'post' and 'body'
There ASTs are know when the concept is freshly parsed.
So the values are kept in cache.
For concepts loaded from sdp, these ASTs must be created again
TODO : Seems to be a service method. Can be put somewhere else
:param codes:
:return:
"""
if codes is None:
return
for key in codes:
self.compiled[key] = codes[key]
return self
# def add_codes(self, codes):
# """
# Gets the ASTs for 'where', 'pre', 'post' and 'body'
# There ASTs are know when the concept is freshly parsed.
# So the values are kept in cache.
#
# For concepts loaded from sdp, these ASTs must be created again
# TODO : Seems to be a service method. Can be put somewhere else
# :param codes:
# :return:
# """
# if codes is None:
# return
#
# for key in codes:
# self.compiled[key] = codes[key]
#
# return self
def get_digest(self):
"""
@@ -406,3 +406,12 @@ class DoNotResolve:
set concept.compiled[BODY] to DoNotResolve(value)
"""
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
+2
View File
@@ -222,6 +222,8 @@ class ExecutionContext:
to_str = self.return_value_to_str(r)
logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
def get_parent(self):
return self._parent
@staticmethod
def return_value_to_str(r):
+11 -3
View File
@@ -1,6 +1,7 @@
from core.builtin_concepts import BuiltinConcepts
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 os
@@ -28,7 +29,7 @@ class SheerkaDump:
defs = self.sheerka.sdp.get(self.sheerka.CONCEPTS_DEFINITIONS_ENTRY)
self.sheerka.log.info(defs)
def dump_desc(self, *concept_names):
def dump_desc(self, *concept_names, eval=False):
first = True
for concept_name in concept_names:
if isinstance(concept_name, Concept):
@@ -43,13 +44,20 @@ class SheerkaDump:
concepts = [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:
self.sheerka.log.info("")
self.sheerka.log.info(f"name : {c.name}")
self.sheerka.log.info(f"bnf : {c.metadata.definition}")
self.sheerka.log.info(f"key : {c.key}")
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()}")
if self.sheerka.isaset(c):
@@ -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:
+31 -18
View File
@@ -249,11 +249,12 @@ class Sheerka(Concept):
if not self.skip_builtins_in_db:
self.sdp.save_result(self, execution_context)
# hack to save valid concept definition
if not self.during_restore:
if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
with open(CONCEPTS_FILE, "a") as f:
f.write(text + "\n")
# hack to save valid concept definition
# if not self.during_restore:
# if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
# with open(CONCEPTS_FILE, "a") as f:
# f.write(text + "\n")
return ret
def execute(self, execution_context, return_values, execution_steps, logger=None):
@@ -364,19 +365,26 @@ class Sheerka(Concept):
concept_key = str(concept_key)
# first search in cache
result = self.cache_by_key[concept_key] if concept_key in self.cache_by_key else \
self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
if concept_key in self.cache_by_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
if isinstance(result, list):
if concept_id:
for c in result:
if c.id == concept_id:
return c
else:
return result
# result is a list, but we have the concept_id to discriminate
for c in result:
if c.id == concept_id:
return c
metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key)
return self._get_unknown(metadata)
@@ -386,10 +394,15 @@ class Sheerka(Concept):
return ErrorConcept("Concept id is undefined.")
# first search in cache
result = self.cache_by_id[concept_id] if concept_id in self.cache_by_id else \
self.sdp.get_safe(self.CONCEPTS_BY_ID_ENTRY, concept_id)
if concept_id in self.cache_by_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):
if self.concepts_definitions_cache:
+8 -7
View File
@@ -6,8 +6,8 @@ from evaluators.BaseEvaluator import OneReturnValueEvaluator
from parsers.BaseParser import NotInitializedNode
from parsers.ConceptLexerParser import ParsingExpression, ParsingExpressionVisitor
from parsers.DefaultParser import DefConceptNode
from parsers.PythonParser import PythonNode
import core.utils
class ConceptOrRuleNameVisitor(ParsingExpressionVisitor):
@@ -66,7 +66,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
setattr(concept.metadata, prop, source)
# 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):
props_found.add(p)
@@ -108,11 +108,12 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
# Case of python code
#
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, PythonNode):
python_node = ret_value.value.value
as_concept_node = python_to_concept(python_node.ast_)
variables = get_names(sheerka, as_concept_node)
variables = filter(lambda x: x in concept_name, variables)
return list(variables)
if len(concept_name) > 1:
python_node = ret_value.value.value
as_concept_node = python_to_concept(python_node.ast_)
variables = get_names(sheerka, as_concept_node)
variables = filter(lambda x: x in concept_name, variables)
return list(variables)
#
# case of concept
+1 -1
View File
@@ -48,7 +48,7 @@ class BaseTest:
@staticmethod
def pretval(concept, source=None, parser="parsers.name"):
"""ParserResult ret_val"""
"""ParserResult ret_val (p stands for ParserResult)"""
return ReturnValueConcept(
"some_name",
True,
+10 -4
View File
@@ -1,5 +1,5 @@
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 tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -80,12 +80,10 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
sheerka.cache_by_key = {} # reset the cache
loaded = sheerka.get(concept.key)
assert loaded is not None
assert loaded == concept
# I can also get it by its id
loaded = sheerka.sdp.get(sheerka.CONCEPTS_BY_ID_ENTRY, concept.id)
assert loaded is not None
assert loaded == concept
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
loaded = sheerka.get_by_id(concept.id)
assert loaded is not None
assert loaded == concept
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")
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
+89 -1
View File
@@ -1,6 +1,6 @@
import pytest
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 tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -369,3 +369,91 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
assert sheerka.isinstance(evaluated, BuiltinConcepts.WHERE_CLAUSE_FAILED)
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
+38
View File
@@ -242,3 +242,41 @@ class TestSheerka(TestUsingFileBasedSheerka):
# only test a random one, it will be the same for the others
sheerka = self.get_sheerka()
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"
+23 -5
View File
@@ -69,9 +69,8 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
return ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=concept))
@pytest.mark.parametrize("ret_val, expected", [
(
ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=DefConceptNode([]))),
True),
(ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=DefConceptNode([]))),
True),
(ReturnValueConcept(BaseParser.PREFIX + "some_name", False, ParserResultConcept(value=DefConceptNode([]))),
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
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)")
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):
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)
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 == []
+1 -1
View File
@@ -103,7 +103,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
assert context.sheerka.isinstance(result.value, BuiltinConcepts.CONCEPT_EVAL_ERROR)
assert result.value.concept == concept_plus
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):
context = self.get_context()
+25
View File
@@ -671,3 +671,28 @@ as:
res = sheerka.evaluate_user_input("eval twenty three")
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