Added first version of DebugManager. Implemented draft of the rule engine

This commit is contained in:
2020-11-20 13:41:45 +01:00
parent cd066881b4
commit 315f8ea09b
156 changed files with 8388 additions and 2852 deletions
@@ -1,8 +1,9 @@
from dataclasses import dataclass
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import expect_one, only_successful, parse_unrecognized, evaluate
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved, NotInit, ensure_concept
from core.builtin_helpers import expect_one, only_successful, parse_unrecognized, evaluate, ensure_concept
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved, NotInit, AllConceptParts, \
concept_part_value
from core.sheerka.services.SheerkaExecute import ParserInput
from core.sheerka.services.sheerka_service import BaseService
from core.tokenizer import Tokenizer
@@ -47,8 +48,10 @@ class SheerkaEvaluateConcept(BaseService):
parent = context.get_parent()
while parent is not None:
if parent.who == context.who and parent.action == BuiltinConcepts.EVALUATING_CONCEPT and \
parent.obj == concept and parent.obj.compiled == concept.compiled:
if (parent.who == context.who and
parent.action == BuiltinConcepts.EVALUATING_CONCEPT and
parent.obj == concept and
parent.obj.get_compiled() == concept.get_compiled()):
return True
parent = parent.get_parent()
@@ -90,15 +93,15 @@ class SheerkaEvaluateConcept(BaseService):
vars_needed = False
body_needed = False
if concept_part in concept.compiled and concept.compiled[concept_part] is not None:
concept_part_source = getattr(concept.metadata, concept_part.value)
if concept_part in concept.get_compiled() and concept.get_compiled()[concept_part] is not None:
concept_part_source = getattr(concept.get_metadata(), concept_part_value(concept_part))
assert concept_part_source is not None
tokens = [t.str_value for t in Tokenizer(concept_part_source)]
if check_vars:
for var_name in (v[0] for v in concept.metadata.variables):
for var_name in (v[0] for v in concept.get_metadata().variables):
if var_name in tokens:
vars_needed = True
ret.append("variables")
@@ -106,9 +109,9 @@ class SheerkaEvaluateConcept(BaseService):
if check_body and "self" in tokens:
body_needed = True
ret.append("body")
ret.append(ConceptParts.BODY)
ret.append(concept_part.value)
ret.append(concept_part)
return ret, vars_needed, body_needed
@@ -121,16 +124,16 @@ class SheerkaEvaluateConcept(BaseService):
:param var_name:
:return:
"""
if concept.metadata.where is None or concept.metadata.where.strip() == "":
if concept.get_metadata().where is None or concept.get_metadata().where.strip() == "":
return None
ret = ExpressionParser().parse(context, ParserInput(concept.metadata.where))
ret = ExpressionParser().parse(context, ParserInput(concept.get_metadata().where))
if not ret.status:
# TODO: manage invalid where clause
return None
expr = ret.body.body
to_trueify = [v[0] for v in concept.metadata.variables if v[0] != var_name]
to_trueify = [v[0] for v in concept.get_metadata().variables if v[0] != var_name]
trueified_where = str(TrueifyVisitor(to_trueify, [var_name]).visit(expr))
tokens = [t.str_value for t in Tokenizer(trueified_where)]
@@ -140,7 +143,7 @@ class SheerkaEvaluateConcept(BaseService):
compiled = compile(trueified_where, "<where clause>", "eval")
except Exception:
pass
return WhereClauseDef(concept, concept.metadata.where, trueified_where, var_name, compiled)
return WhereClauseDef(concept, concept.get_metadata().where, trueified_where, var_name, compiled)
else:
return None
@@ -153,7 +156,8 @@ class SheerkaEvaluateConcept(BaseService):
:return:
"""
ret = []
for r in [r for r in return_values if r.status]:
valid_return_values = [r for r in return_values if r.status]
for r in valid_return_values:
if where_clause_def.compiled:
try:
if eval(where_clause_def.compiled, {where_clause_def.prop: self.sheerka.objvalue(r)}):
@@ -177,10 +181,13 @@ class SheerkaEvaluateConcept(BaseService):
if len(ret) > 0:
return ret
reason = [r.body for r in return_values] if len(valid_return_values) == 0 else None
return self.sheerka.new(BuiltinConcepts.CONDITION_FAILED,
body=where_clause_def.clause,
concept=where_clause_def.concept,
prop=where_clause_def.prop)
prop=where_clause_def.prop,
reason=reason)
def manage_infinite_recursion(self, context):
"""
@@ -194,7 +201,7 @@ class SheerkaEvaluateConcept(BaseService):
concepts_found = set()
while parent and parent.obj:
if parent.who == context.who and parent.action == BuiltinConcepts.EVALUATING_CONCEPT:
body = parent.obj.metadata.body
body = parent.obj.get_metadata().body
try:
return self.sheerka.ret(self.NAME, True, InfiniteRecursionResolved(eval(body)))
except Exception:
@@ -226,11 +233,11 @@ class SheerkaEvaluateConcept(BaseService):
return self.sheerka.resolve(identifier)
return None
for part_key in ConceptParts:
if part_key in concept.compiled:
for part_key in AllConceptParts:
if part_key in concept.get_compiled():
continue
source = getattr(concept.metadata, part_key.value)
source = getattr(concept.get_metadata(), concept_part_value(part_key))
if source is None: # or not isinstance(source, str):
continue
@@ -238,22 +245,22 @@ class SheerkaEvaluateConcept(BaseService):
raise Exception("Invalid concept init. metadata must be a string")
if source.strip() == "":
concept.compiled[part_key] = DoNotResolve(source)
concept.get_compiled()[part_key] = DoNotResolve(source)
else:
# first case, when the metadata references another concept via c:xxx: keyword
if concept_found := parse_token_concept(source):
context.log(f"Recognized concept '{concept_found}'", self.NAME)
concept.compiled[part_key] = concept_found
concept.get_compiled()[part_key] = concept_found
else:
res = parse_unrecognized(context,
source,
parsers="all",
prop=part_key,
filter_func=only_successful)
concept.compiled[part_key] = res.body.body if is_only_successful(res) else res
concept.get_compiled()[part_key] = res.body.body if is_only_successful(res) else res
for var_name, default_value in concept.metadata.variables:
if var_name in concept.compiled:
for var_name, default_value in concept.get_metadata().variables:
if var_name in concept.get_compiled():
continue
if default_value is None:
@@ -263,23 +270,23 @@ class SheerkaEvaluateConcept(BaseService):
raise Exception("Invalid concept init. variable metadata must be a string")
if default_value.strip() == "":
concept.compiled[var_name] = DoNotResolve(default_value)
concept.get_compiled()[var_name] = DoNotResolve(default_value)
else:
# first case, when the metadata references another concept via c:xxx: keyword
if concept_found := parse_token_concept(default_value):
context.log(f"Recognized concept '{concept_found}'", self.NAME)
concept.compiled[var_name] = concept_found
concept.get_compiled()[var_name] = concept_found
else:
res = parse_unrecognized(context,
default_value,
parsers="all",
prop=var_name,
filter_func=only_successful)
concept.compiled[var_name] = res.body.body if is_only_successful(res) else res
concept.get_compiled()[var_name] = res.body.body if is_only_successful(res) else res
# Updates the cache of concepts when possible
if self.sheerka.has_id(concept.id):
self.sheerka.get_by_id(concept.id).compiled = concept.compiled
self.sheerka.get_by_id(concept.id).set_compiled(concept.get_compiled())
def resolve(self,
context,
@@ -325,8 +332,8 @@ class SheerkaEvaluateConcept(BaseService):
with context.push(BuiltinConcepts.EVALUATING_ATTRIBUTE,
current_prop,
desc=desc,
obj=current_concept,
path=path) as sub_context:
obj=current_concept) as sub_context:
sub_context.add_inputs(path=path)
if force_evaluation:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
@@ -352,7 +359,7 @@ class SheerkaEvaluateConcept(BaseService):
else:
# update short term memory with current concept variables
if current_concept:
for var in current_concept.metadata.variables:
for var in current_concept.get_metadata().variables:
value = current_concept.get_value(var[0])
if value != NotInit:
sub_context.add_to_short_term_memory(var[0], current_concept.get_value(var[0]))
@@ -433,23 +440,23 @@ class SheerkaEvaluateConcept(BaseService):
:return: value of the evaluation or error
"""
if concept.metadata.is_evaluated:
if concept.get_metadata().is_evaluated:
return concept
# I cannot use cache because of concept like 'number'.
# They don't have variables, but their values change every time they are instantiated
# TODO: Need to find a way to cache despite of them
# need_body = eval_body or context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED)
# if need_body and len(concept.metadata.variables) == 0 and context.sheerka.has_id(concept.id):
# if need_body and len(concept.get_metadata().variables) == 0 and context.sheerka.has_id(concept.id):
# from_cache = context.sheerka.get_by_id(concept.id)
# if from_cache.metadata.is_evaluated:
# if from_cache.get_metadata().is_evaluated:
# concept.set_value(ConceptParts.BODY, from_cache.body)
# concept.metadata.is_evaluated = True
# concept.get_metadata().is_evaluated = True
# return concept
desc = f"Evaluating concept {concept}"
with context.push(BuiltinConcepts.EVALUATING_CONCEPT, concept, desc=desc, eval_body=eval_body) as sub_context:
with context.push(BuiltinConcepts.EVALUATING_CONCEPT, concept, desc=desc) as sub_context:
sub_context.add_inputs(eval_body=eval_body)
if eval_body:
# ask for body evaluation
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
@@ -466,8 +473,8 @@ class SheerkaEvaluateConcept(BaseService):
for metadata_to_eval in all_metadata_to_eval:
if metadata_to_eval == "variables":
for var_name in (v for v in concept.variables() if v in concept.compiled):
prop_ast = concept.compiled[var_name]
for var_name in (v for v in concept.variables() if v in concept.get_compiled()):
prop_ast = concept.get_compiled()[var_name]
w_clause = self.get_where_clause_def(context, concept, var_name)
# TODO, manage when the where clause cannot be parsed
@@ -485,17 +492,17 @@ class SheerkaEvaluateConcept(BaseService):
else:
concept.set_value(var_name, resolved)
else:
part_key = ConceptParts(metadata_to_eval)
part_key = metadata_to_eval
# do not evaluate where when the body is a set
# Indeed, the way that the where clause is expressed is not a valid python or concept code
if part_key == ConceptParts.WHERE and self.sheerka.isaset(sub_context, concept.body):
continue
if part_key not in concept.compiled or concept.compiled[part_key] is None:
if part_key not in concept.get_compiled() or concept.get_compiled()[part_key] is None:
continue
metadata_ast = concept.compiled[part_key]
metadata_ast = concept.get_compiled()[part_key]
# if part_key is PRE, POST or WHERE, the concept need to be evaluated
# if we want the predicates to be resolved => so force_eval = True
@@ -514,7 +521,7 @@ class SheerkaEvaluateConcept(BaseService):
# validate PRE and WHERE condition
if part_key in (ConceptParts.PRE, ConceptParts.WHERE) and not self.sheerka.objvalue(resolved):
return self.sheerka.new(BuiltinConcepts.CONDITION_FAILED,
body=getattr(concept.metadata, metadata_to_eval),
body=getattr(concept.get_metadata(), concept_part_value(metadata_to_eval)),
concept=concept,
prop=part_key)
@@ -524,19 +531,19 @@ class SheerkaEvaluateConcept(BaseService):
concept.init_key() # Necessary for old unit tests. To remove someday
if "body" in all_metadata_to_eval:
concept.metadata.is_evaluated = True
if ConceptParts.BODY in all_metadata_to_eval:
concept.get_metadata().is_evaluated = True
# # update the cache for concepts with no variables
# Cannot use cache. See the comment at the beginning of this method
# if len(concept.metadata.variables) == 0:
# if len(concept.get_metadata().variables) == 0:
# self.sheerka.cache_manager.put(self.sheerka.CONCEPTS_BY_ID_ENTRY, concept.id, concept)
if not concept.metadata.is_builtin:
if not concept.get_metadata().is_builtin:
self.sheerka.register_object(sub_context, concept.name, concept)
# manage RET metadata
if sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED) and ConceptParts.RET in concept.values:
if sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED) and ConceptParts.RET in concept.values():
return concept.get_value(ConceptParts.RET)
else:
return concept
@@ -547,7 +554,7 @@ class SheerkaEvaluateConcept(BaseService):
needed, variables, body = self.get_needed_metadata(concept, ConceptParts.PRE, True, True)
to_eval.extend(needed)
if context.in_context(BuiltinConcepts.EVAL_WHERE_REQUESTED) or concept.metadata.need_validation:
if context.in_context(BuiltinConcepts.EVAL_WHERE_REQUESTED) or concept.get_metadata().need_validation:
# What are the cases where we do not need a validation ?
# see test_sheerka_non_reg::test_i_can_evaluate_bnf_concept_with_where_clause()
# res = sheerka.evaluate_user_input("foobar")
@@ -572,7 +579,7 @@ class SheerkaEvaluateConcept(BaseService):
to_eval.append('variables')
if not body:
to_eval.append("body")
to_eval.append(ConceptParts.BODY)
return to_eval