Files
Sheerka-Old/src/core/sheerka/services/SheerkaEvaluateConcept.py
T
kodjo e69745adc8 Fixed #100 : SheerkaAdmin: Add builtins() command
Fixed #99 : SheerkaQueryManager: I can manage contains predicate when filtering objects
Fixed #97 : ERROR: list indices must be integers or slices, not Concept
Fixed #96 : SequenceNodeParser: SequenceNodeParser must correctly handle concept definition
Fixed #95 : ResolveAmbiguity must not remove concepts that do not require evaluation
Fixed #94 : Concepts with the same key are lost when new ontology
Fixed #93 : Introduce BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED
Fixed #92 : ExpressionParser: Implement compile_disjunctions()
Fixed #91 : Implement get_concepts_complexity(context, concepts, concept_parts)
Fixed #90 : ResolveAmbiguity : where predicate is not used to resolve ambiguity
Fixed #89 : ResolveAmbiguityEvaluator: Concepts embedded in ConceptNode are not resolved
Fixed #88: SyaNodeParser: Parse multiple parameters when some of the are not recognized
Fixed #87: SyaNodeParser : Parse the multiple parameters
2021-07-31 08:52:00 +02:00

829 lines
37 KiB
Python

from dataclasses import dataclass
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import expect_one, only_successful, ensure_concept, is_only_successful, ensure_bnf
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved, AllConceptParts, \
concept_part_value
from core.global_symbols import NotInit, CURRENT_OBJ, INIT_AST_PARSERS, INIT_AST_QUESTION_PARSERS
from core.rule import Rule
from core.sheerka.services.SheerkaEvaluateRules import SheerkaEvaluateRules
from core.sheerka.services.SheerkaExecute import ParserInput, SheerkaExecute
from core.sheerka.services.SheerkaRuleManager import PythonConditionExprVisitor
from core.sheerka.services.sheerka_service import BaseService, FailedToCompileError, ChickenAndEggException
from core.tokenizer import Tokenizer
from core.utils import unstr_concept
from parsers.BaseExpressionParser import TrueifyVisitor
from parsers.BaseNodeParser import ConceptNode
from parsers.BnfNodeParser import BnfNodeConceptExpressionVisitor
from parsers.ExpressionParser import ExpressionParser
from parsers.LogicalOperatorParser import LogicalOperatorParser
CONCEPT_EVALUATION_STEPS = [
BuiltinConcepts.BEFORE_EVALUATION,
BuiltinConcepts.EVALUATION,
BuiltinConcepts.AFTER_EVALUATION]
@dataclass
class ConceptEvalException(Exception):
error: Concept
@dataclass
class WhereClauseDef:
concept: Concept # concept on which the where clause is applied
clause: str # original where clause
trueified: str # modified where clause (where unresolvable variables are removed)
prop: str # variable to test
conditions: list # compiled trueified
class SheerkaEvaluateConcept(BaseService):
NAME = "EvaluateConcept"
def __init__(self, sheerka):
super().__init__(sheerka)
self.rule_evaluator = None
def initialize(self):
self.sheerka.bind_service_method(self.NAME, self.evaluate_concept, True)
self.sheerka.bind_service_method(self.NAME, self.call_concept, True)
self.sheerka.bind_service_method(self.NAME, self.call_concept, False, as_name="evaluate_question")
self.sheerka.bind_service_method(self.NAME, self.set_auto_eval, True)
def initialize_deferred(self, context, is_first_time):
self.rule_evaluator = self.sheerka.services[SheerkaEvaluateRules.NAME]
@staticmethod
def infinite_recursion_detected(context, concept):
"""
Browse the parents, looking for another evaluation of the same concept
:param context:
:param concept:
:return:
"""
if concept is None:
return False
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.get_compiled() == concept.get_compiled()):
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
@staticmethod
def apply_ret(concept, eval_body=True):
"""
Check if a concept has its RET part defined
If True, returns it
:param concept:
:param eval_body: Do not return the ret is eval body is not requested
:return:
"""
if (eval_body and
ConceptParts.RET in concept.values() and
(ret_value := concept.get_value(ConceptParts.RET)) != NotInit):
return ret_value
else:
return concept
@staticmethod
def get_needed_metadata(concept, concept_part, check_vars, check_body):
"""
Check if the concept_part has to be evaluated
It also checks if the variables and the body need to be evaluated prior to it
:param concept:
:param concept_part:
:param check_vars:
:param check_body:
:return:
"""
ret = []
vars_needed = False
body_needed = False
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.get_metadata().variables):
if var_name in tokens:
vars_needed = True
ret.append("variables")
break
if check_body and "self" in tokens:
body_needed = True
ret.append(ConceptParts.BODY)
ret.append(concept_part)
return ret, vars_needed, body_needed
@staticmethod
def get_where_clause_def(context, concept, var_name):
"""
Returns the compiled code to be executed
:param context:
:param concept:
:param var_name:
:return:
"""
if concept.get_metadata().where is None or concept.get_metadata().where.strip() == "":
return None
ret = LogicalOperatorParser().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.get_metadata().variables if v[0] != var_name]
trueified_where = str(TrueifyVisitor(to_trueify, [var_name]).visit(expr))
try:
parser_input = context.sheerka.services[SheerkaExecute.NAME].get_parser_input(trueified_where)
parsed = ExpressionParser(auto_compile=False).parse(context, parser_input)
python_visitor = PythonConditionExprVisitor(context)
conditions = python_visitor.get_conditions(parsed.body.body)
return WhereClauseDef(concept, concept.get_metadata().where, trueified_where, var_name, conditions)
except FailedToCompileError:
# TODO: manage invalid where clause
return None
# tokens = [t.str_value for t in Tokenizer(trueified_where)]
# if var_name in tokens:
# compiled = None
# try:
# compiled = compile(trueified_where, "<where clause>", "eval")
# except Exception:
# pass
# return WhereClauseDef(concept, concept.get_metadata().where, trueified_where, var_name, compiled)
# else:
# return None
@staticmethod
def get_recursive_definitions(context, concept, return_values):
"""
Returns the name of the parsers that will resolve to a recursive evaluation
For example, when getting ast for Concept("a and b", body="a and b")
Chances are that we will end up with two parsers
- Python parser for 'a and b'
- ExactConcept parser that point to the concept itself
The ExactConcept will be returned (to be removed from the list as it's a cyclic reference to itself)
:param context:
:param concept:
:param return_values:
:return:
"""
if concept.name in concept.variables():
# There is a variable with the same name as the concept
# During evaluation, inner variables take precedence other concepts
# So there won't be any cyclic reference, the variable will be picked
return
for parser in [r.body for r in return_values if
r.status and context.sheerka.isinstance(r.body, BuiltinConcepts.PARSER_RESULT)]:
parsed = parser.body if isinstance(parser.body, list) else [parser.body]
for parsed_item in parsed:
if isinstance(parsed_item, Concept) and parsed_item.id == concept.id:
yield parser.parser
elif isinstance(parsed_item, ConceptNode) and parsed_item.concept.id == concept.id:
yield parser.parser
@staticmethod
def get_asts(context, who, source, concept, part_key, possible_variables):
"""
Get the return_value_concept or the concept for a given source
:param context:
:param who:
:param concept:
:param part_key: Concept part (#body#, #pre#, #where#...) being compiled
:param source: string or parser input, it does not matter
:param possible_variables: concepts that must be considered as variables
:return:
"""
def parse_token_concept(s):
"""
The source is a direct reference / call to another concept
:param s: source
:return:
"""
if s.startswith("c:") and (identifier := unstr_concept(s)) != (None, None):
return context.sheerka.fast_resolve(identifier)
return None
def get_return_value(current_context, c, s, p):
"""
:param current_context:
:param c: concept
:param s: source
:param p: part of the concept being parsed
:return:
"""
parsers_to_use = INIT_AST_QUESTION_PARSERS if p in [ConceptParts.WHERE,
ConceptParts.PRE] else INIT_AST_PARSERS
while True:
return_value = current_context.sheerka.parse_unrecognized(current_context,
s,
parsers=parsers_to_use,
who=who,
prop=p,
filter_func=only_successful,
possible_variables=possible_variables)
if not return_value.status:
if current_context.preprocess:
raise ChickenAndEggException(context.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body={c}))
else:
raise FailedToCompileError([return_value.body])
return_value = return_value.body.body if is_only_successful(context.sheerka, return_value) else \
[return_value]
if c is None:
# No concept provided, we cannot look for recursive definition
return return_value
recursive_parsers = list(SheerkaEvaluateConcept.get_recursive_definitions(context, c, return_value))
if len(recursive_parsers) == 0:
return return_value
desc = f"Removing parsers {recursive_parsers}"
current_context = current_context.push(context.action, context.action_context, desc=desc)
for recursive_parser in recursive_parsers:
current_context.add_preprocess(recursive_parser.name, enabled=False)
as_str = source.as_text() if isinstance(source, ParserInput) else source
if as_str.strip() == "":
return DoNotResolve(as_str)
else:
if concept_found := parse_token_concept(as_str):
# the compiled can be a reference to another concept...
context.log(f"Recognized concept '{concept_found}'", SheerkaEvaluateConcept.NAME)
return concept_found
else:
# ...or a list of ReturnValueConcept to resolve
return get_return_value(context, concept, source, part_key)
def apply_where_clause(self, context, where_clause_def, return_values):
"""
Apply intermediate where clause when evaluating concept variables
:param context:
:param where_clause_def:
:param return_values:
:return:
"""
ret = []
valid_return_values = [r for r in return_values if r.status]
with context.push(BuiltinConcepts.VALIDATING_CONCEPT,
{"concept": where_clause_def.concept, "attr": ConceptParts.WHERE},
desc=f"Apply where clause on '{where_clause_def.prop}'") as sub_context:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
# sub_context.sheerka.add_many_to_short_term_memory(sub_context, objects)
for r in valid_return_values:
sub_context.add_to_short_term_memory(where_clause_def.prop, r.body)
res = self.rule_evaluator.evaluate_conditions(sub_context,
where_clause_def.conditions,
{where_clause_def.prop: r.body})
if len(res) == 0:
# case where missing variables were detected
# This means that the 'where' clause can only be evaluated after all the parts are evaluated
ret.append(r)
else:
one_res = expect_one(context, res)
if one_res.status:
value = context.sheerka.objvalue(one_res)
if isinstance(value, bool) and value:
ret.append(r)
# value = context.sheerka.objvalue(res.body)
# if res.status and isinstance(bool, value) and value:
# ret.append(r)
# if where_clause_def.compiled:
# try:
# if eval(where_clause_def.compiled, {where_clause_def.prop: self.sheerka.objvalue(r)}):
# ret.append(r)
# except NameError:
# ret.append(r) # it cannot be solved unitary, let's give a chance to the global where condition
# else:
# # it means that the where condition is an expression that needs to be executed
# evaluation_res = evaluate_from_source(context,
# where_clause_def.trueified,
# desc=f"Apply where clause on '{where_clause_def.prop}'",
# expect_success=True,
# is_question=True,
# stm={where_clause_def.prop: r.body})
# one_res = expect_one(context, evaluation_res)
# if one_res.status:
# value = context.sheerka.objvalue(one_res)
# if isinstance(value, bool) and value:
# ret.append(r)
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,
reason=reason)
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.action == BuiltinConcepts.EVALUATING_CONCEPT:
body = parent.obj.get_metadata().body
try:
return self.sheerka.ret(self.NAME, True, InfiniteRecursionResolved(eval(body)))
except Exception:
pass
concepts_found.add(parent.obj)
parent = parent.get_parent()
return self.sheerka.ret(
self.NAME,
False,
self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body=concepts_found))
def initialize_concept_asts(self, context, concept: Concept):
"""
Updates the codes of the newly created concept
Basically, it runs the parsers on all parts
:param concept:
:param context:
:return:
"""
# for BNF concepts, concepts are sometimes considered as variables
# example :
# def concept a from bnf 'bar' | 'baz'
# def concept foo a as 'foo' a where a == 'baz'
# In the second concept (foo) as is a still a concept, but also a variable in the where clause
ensure_bnf(context, concept)
if concept.get_bnf():
visitor = BnfNodeConceptExpressionVisitor()
visitor.visit(concept.get_bnf())
possible_variables = [c.name if isinstance(c, Concept) else c for c in visitor.references]
else:
possible_variables = None
for part_key in AllConceptParts:
if part_key in concept.get_compiled():
continue
source = getattr(concept.get_metadata(), concept_part_value(part_key))
if source is None: # or not isinstance(source, str):
continue
if not isinstance(source, str):
raise Exception("Invalid concept init. metadata must be a string")
concept.get_compiled()[part_key] = self.get_asts(context,
self.NAME,
source,
concept,
part_key,
possible_variables)
for var_name, default_value in concept.get_metadata().variables:
if var_name in concept.get_compiled():
continue
if default_value is None:
continue
if not isinstance(default_value, str):
raise Exception("Invalid concept init. variable metadata must be a string")
concept.get_compiled()[var_name] = self.get_asts(context,
self.NAME,
default_value,
concept,
var_name,
possible_variables)
def resolve(self,
context,
to_resolve,
current_prop,
current_concept,
force_evaluation,
forbid_methods_with_side_effect,
where_clause_def):
"""
Resolve a variable or a Concept
:param context: current execution context
:param to_resolve: Concept or list of ReturnValueConcept to resolve
:param current_prop: current property or ConceptPart
:param current_concept: current concept
:param force_evaluation: force body evaluation
:param forbid_methods_with_side_effect: Do not call methods with side effect when EVAL_BODY_REQUESTED
:param where_clause_def: intermediate where clause for variables
:return:
"""
def get_path(context_, prop_name):
concept_name = f'"{context_.action_context.name}"' if isinstance(context_.action_context, Concept) \
else "'N/A'"
prefix = context_.path if hasattr(context_, "path") else concept_name
value = prop_name.name if isinstance(current_prop, ConceptParts) else prop_name
return prefix + "." + value
if isinstance(to_resolve, DoNotResolve):
return to_resolve.value
# manage infinite loop
if self.infinite_recursion_detected(context, current_concept):
with context.push(BuiltinConcepts.MANAGE_INFINITE_RECURSION,
current_concept,
desc="Infinite recursion detected",
obj=current_concept) as sub_context:
# I create a sub context in order to log what happened
ret_val = self.manage_infinite_recursion(context)
sub_context.add_values(return_values=ret_val)
return ret_val.body
path = get_path(context, current_prop)
desc = f"Evaluating {path} (concept={current_concept})"
with context.push(BuiltinConcepts.EVALUATING_ATTRIBUTE,
current_prop,
desc=desc,
obj=current_concept) as sub_context:
sub_context.add_inputs(path=path)
if force_evaluation:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
if forbid_methods_with_side_effect:
sub_context.protected_hints.add(BuiltinConcepts.VALIDATION_ONLY_REQUESTED)
if current_prop in (ConceptParts.WHERE, ConceptParts.PRE):
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVALUATING_PRE_OR_WHERE_CLAUSE)
# 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)
sub_context.add_values(return_values=evaluated)
if not context.sheerka.is_success(evaluated) and evaluated.key != to_resolve.key:
error = evaluated
else:
return evaluated
elif isinstance(to_resolve, Rule):
raise NotImplementedError() # how to resolve rules ?
# otherwise, execute all return values to find out what is the value
else:
# update short term memory with current concept variables
if current_concept:
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]))
use_copy = to_resolve.copy() if isinstance(to_resolve, list) else to_resolve
r = self.sheerka.execute(sub_context, use_copy, CONCEPT_EVALUATION_STEPS)
if where_clause_def:
# apply intermediate where clause
r = self.apply_where_clause(context, where_clause_def, r)
if self.sheerka.isinstance(r, BuiltinConcepts.CONDITION_FAILED):
return r
else:
one_r = expect_one(context, r)
sub_context.add_values(return_values=one_r)
if one_r.status:
return one_r.value
else:
error = one_r.value
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,
force_evaluation,
forbid_methods_with_side_effect,
where_clause_def):
"""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)
# in this latter case, all values are to be processed one by one and a list should be returned
if len(list_to_resolve) == 0:
return []
if self.sheerka.isinstance(list_to_resolve[0], BuiltinConcepts.RETURN_VALUE):
return self.resolve(context,
list_to_resolve,
current_prop,
current_concept,
force_evaluation,
forbid_methods_with_side_effect,
where_clause_def)
res = []
for to_resolve in list_to_resolve:
# sanity check
if self.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE):
return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
body="Mix between real values and return values",
concept=current_concept,
property_name=current_prop)
r = self.resolve(context,
to_resolve,
current_prop,
current_concept,
force_evaluation,
forbid_methods_with_side_effect,
where_clause_def)
if self.sheerka.isinstance(r, BuiltinConcepts.CONCEPT_EVAL_ERROR):
return r
res.append(r)
return res
def evaluate_concept(self, context, concept: Concept, eval_body=False, validation_only=False, metadata=None):
"""
Evaluation a concept
ie : resolve its body
:param context:
:param concept:
:param eval_body:
:param validation_only: When set, the body is never evaluated, whatever eval_body or EVAL_BODY_REQUESTED
:param metadata: list of metadata to evaluate ('pre', 'post'...)
:return: value of the evaluation or error
"""
failed_to_evaluate_body = False
if concept.get_hints().is_evaluated:
return self.apply_ret(concept, eval_body or context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED))
# if concept.get_hints().use_copy:
# raise Exception("Use copy")
# concept = concept.copy()
# concept.get_hints().use_copy = False
# 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.get_metadata().variables) == 0 and context.sheerka.has_id(concept.id):
# from_cache = context.sheerka.get_by_id(concept.id)
# if from_cache.get_hints().is_evaluated:
# concept.set_value(ConceptParts.BODY, from_cache.body)
# concept.get_hints().is_evaluated = True
# return concept
desc = f"Evaluating concept {concept}"
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)
if validation_only:
# Never eval the body
sub_context.protected_hints.add(BuiltinConcepts.VALIDATION_ONLY_REQUESTED)
# auto evaluate commands
if context.sheerka.isa(concept, context.sheerka.new(BuiltinConcepts.AUTO_EVAL)):
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
if context.in_context(BuiltinConcepts.EVALUATING_PRE_OR_WHERE_CLAUSE):
sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
sub_context.add_to_short_term_memory(CURRENT_OBJ, concept)
try:
self.initialize_concept_asts(sub_context, concept)
except ChickenAndEggException as ex:
return ex.error
# to make sure of the order, it don't use ConceptParts.get_parts()
# variables must be evaluated first, body must be evaluated before where
all_metadata_to_eval = metadata or self.compute_metadata_to_eval(sub_context, concept)
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.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
if isinstance(prop_ast, list):
# Do not send the current concept for the properties
resolved = self.resolve_list(
sub_context,
prop_ast,
var_name,
None,
True,
not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED),
w_clause)
else:
# Do not send the current concept for the properties
resolved = self.resolve(sub_context,
prop_ast,
var_name,
None,
True,
not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED),
w_clause)
if isinstance(resolved, Concept) and not sub_context.sheerka.is_success(resolved):
resolved.set_value("concept", concept) # since current concept was not sent
return resolved
else:
concept.set_value(var_name, resolved)
else:
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.get_compiled() or concept.get_compiled()[part_key] is None:
continue
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
# otherwise no need to force
force_concept_eval = False if part_key == ConceptParts.BODY else True
# resolve
resolved = self.resolve(sub_context,
metadata_ast,
part_key,
concept,
force_concept_eval,
not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED),
None)
# 'FATAL' error is detected, let's stop
if isinstance(resolved, Concept) and not sub_context.sheerka.is_success(resolved):
if not (part_key == ConceptParts.BODY and
self.sheerka.has_error(context, resolved, body=BuiltinConcepts.METHOD_ACCESS_ERROR) and
sub_context.in_context(BuiltinConcepts.VALIDATION_ONLY_REQUESTED)):
return resolved
else:
# BuiltinConcepts.METHOD_ACCESS_ERROR is returned only when the access to side effect
# method is forbidden (during validation or ast initialisation)
resolved = NotInit
failed_to_evaluate_body = True
concept.set_value(part_key, self.get_infinite_recursion_resolution(resolved) or resolved)
# 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.get_metadata(),
concept_part_value(metadata_to_eval)),
concept=concept,
prop=part_key)
#
# TODO : Validate the POST condition
#
concept.init_key() # Necessary for old unit tests. To remove someday
if ConceptParts.BODY in all_metadata_to_eval and not failed_to_evaluate_body:
concept.get_hints().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.get_metadata().variables) == 0:
# self.sheerka.om.put(self.sheerka.CONCEPTS_BY_ID_ENTRY, concept.id, concept)
if not concept.get_metadata().is_builtin:
self.sheerka.register_object(sub_context, concept.name, concept)
# manage RET metadata
return self.apply_ret(concept, sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED))
def call_concept(self, context, concept, *args, **kwargs):
"""
call the concept using either args or kwargs (not both)
:param context:
:param concept:
:param args:
:param kwargs:
:return:
"""
if concept.get_hints().use_copy or not concept.get_hints().is_instance:
concept = concept.copy()
concept.get_hints().use_copy = False
concept.get_hints().is_instance = True
concept.get_hints().is_evaluated = False # force evaluation
# TODO : update the variables before calling the concept
evaluated = self.evaluate_concept(context, concept)
if self.sheerka.has_error(context, evaluated):
raise ConceptEvalException(evaluated)
if ConceptParts.BODY in evaluated.get_compiled():
return evaluated.body
else:
return evaluated
def compute_metadata_to_eval(self, context, concept):
to_eval = []
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.get_hints().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")
needed, v, b = self.get_needed_metadata(concept, ConceptParts.WHERE, not variables, not body)
variables |= v
body |= b
to_eval.extend(needed)
if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED):
needed, v, b = self.get_needed_metadata(concept, ConceptParts.RET, not variables, not body)
variables |= v
body |= b
to_eval.extend(needed)
needed, v, b = self.get_needed_metadata(concept, ConceptParts.POST, not variables, not body)
variables |= v
body |= b
to_eval.extend(needed)
if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED):
if not variables:
to_eval.append('variables')
if not body:
to_eval.append(ConceptParts.BODY)
return to_eval
def set_auto_eval(self, context, concept):
"""
add AUTO_EVAL to ISA
:param context:
:param concept:
:return:
"""
ensure_concept(concept)
return self.sheerka.set_isa(context, concept, self.sheerka.new(BuiltinConcepts.AUTO_EVAL))