Fixed #29: Parsers: Implement parsing memoization

Fixed #77 : Parser: ShortTermMemoryParser should be called separately
Fixed #78 : Remove VariableNode usage
Fixed #79 : ConceptManager: Implement compile caching
Fixed #80 : SheerkaExecute : parsers_key is not correctly computed
Fixed #81 : ValidateConceptEvaluator : Validate concept's where and pre clauses right after the parsing
Fixed #82 : SheerkaIsAManager: isa() failed when the set as a body
Fixed #83 : ValidateConceptEvaluator : Support BNF and SYA Concepts
Fixed #84 : ExpressionParser: Implement the parser as a standard parser
Fixed #85 : Services: Give order to services
Fixed #86 : cannot manage smart_get_attr(the short, color)
This commit is contained in:
2021-06-07 21:14:03 +02:00
parent 1059ce25c5
commit 7dcaa9c111
92 changed files with 4263 additions and 1890 deletions
@@ -1,18 +1,21 @@
from dataclasses import dataclass
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import expect_one, only_successful, evaluate_from_source, ensure_concept
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
from core.global_symbols import NotInit, CURRENT_OBJ, INIT_AST_PARSERS, INIT_AST_QUESTION_PARSERS
from core.rule import Rule
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager
from core.sheerka.services.SheerkaExecute import ParserInput
from core.sheerka.services.sheerka_service import BaseService
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 = [
@@ -21,11 +24,6 @@ CONCEPT_EVALUATION_STEPS = [
BuiltinConcepts.AFTER_EVALUATION]
@dataclass
class ChickenAndEggException(Exception):
error: Concept
@dataclass
class ConceptEvalException(Exception):
error: Concept
@@ -37,7 +35,7 @@ class WhereClauseDef:
clause: str # original where clause
trueified: str # modified where clause (where unresolvable variables are removed)
prop: str # variable to test
compiled: object # trueified where clause Python compiled
conditions: list # compiled trueified
class SheerkaEvaluateConcept(BaseService):
@@ -45,6 +43,7 @@ class SheerkaEvaluateConcept(BaseService):
def __init__(self, sheerka):
super().__init__(sheerka)
self.rule_evaluator = None
def initialize(self):
self.sheerka.bind_service_method(self.evaluate_concept, True)
@@ -52,6 +51,9 @@ class SheerkaEvaluateConcept(BaseService):
self.sheerka.bind_service_method(self.call_concept, False, as_name="evaluate_question")
self.sheerka.bind_service_method(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):
"""
@@ -86,14 +88,20 @@ class SheerkaEvaluateConcept(BaseService):
return None
@staticmethod
def apply_ret(concept):
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:
"""
return concept.get_value(ConceptParts.RET) if ConceptParts.RET in concept.values else concept
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):
@@ -153,20 +161,37 @@ class SheerkaEvaluateConcept(BaseService):
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)]
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:
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
def get_recursive_definitions(self, concept, return_values):
# 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:
@@ -176,8 +201,9 @@ class SheerkaEvaluateConcept(BaseService):
# 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 self.sheerka.isinstance(r.body, BuiltinConcepts.PARSER_RESULT)]:
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:
@@ -185,6 +211,85 @@ class SheerkaEvaluateConcept(BaseService):
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
@@ -195,26 +300,53 @@ class SheerkaEvaluateConcept(BaseService):
"""
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:
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
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:
# 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)
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
@@ -261,57 +393,19 @@ class SheerkaEvaluateConcept(BaseService):
:return:
"""
def is_only_successful(r):
"""
# 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
:param r: return_value
:return:
"""
return context.sheerka.isinstance(r, BuiltinConcepts.RETURN_VALUE) and \
context.sheerka.isinstance(r.body, BuiltinConcepts.ONLY_SUCCESSFUL)
def parse_token_concept(s):
"""
:param s: source
:return:
"""
if s.startswith("c:") and (identifier := unstr_concept(s)) != (None, None):
return self.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:
"""
while True:
return_value = current_context.sheerka.parse_unrecognized(current_context,
s,
parsers="all",
prop=p,
filter_func=only_successful)
if not return_value.status:
if current_context.preprocess:
raise ChickenAndEggException(self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body={c}))
else:
raise Exception(f"Failed to build '{s}'. But it doesn't seems to be recursion")
return_value = return_value.body.body if is_only_successful(return_value) else [return_value]
recursive_parsers = list(self.get_recursive_definitions(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)
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():
@@ -324,16 +418,12 @@ class SheerkaEvaluateConcept(BaseService):
if not isinstance(source, str):
raise Exception("Invalid concept init. metadata must be a string")
if source.strip() == "":
concept.get_compiled()[part_key] = DoNotResolve(source)
else:
if concept_found := parse_token_concept(source):
# the compiled can be a reference to another concept...
context.log(f"Recognized concept '{concept_found}'", self.NAME)
concept.get_compiled()[part_key] = concept_found
else:
# ...or a list of ReturnValueConcept to resolve
concept.get_compiled()[part_key] = get_return_value(context, concept, source, part_key)
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():
@@ -345,22 +435,12 @@ class SheerkaEvaluateConcept(BaseService):
if not isinstance(default_value, str):
raise Exception("Invalid concept init. variable metadata must be a string")
if default_value.strip() == "":
concept.get_compiled()[var_name] = DoNotResolve(default_value)
else:
if concept_found := parse_token_concept(default_value):
# the compiled can be a reference to another concept...
context.log(f"Recognized concept '{concept_found}'", self.NAME)
concept.get_compiled()[var_name] = concept_found
else:
# ...or a list of ReturnValueConcept to resolve
concept.get_compiled()[var_name] = get_return_value(context, concept, default_value, var_name)
# Updates the cache of concepts when possible
# This piece of code is not used, a the compile part is removed by sheerka.new_from_template()
service = context.sheerka.services[SheerkaConceptManager.NAME]
if service.has_id(concept.id):
self.sheerka.get_by_id(concept.id).set_compiled(concept.get_compiled())
concept.get_compiled()[var_name] = self.get_asts(context,
self.NAME,
default_value,
concept,
var_name,
possible_variables)
def resolve(self,
context,
@@ -368,6 +448,7 @@ class SheerkaEvaluateConcept(BaseService):
current_prop,
current_concept,
force_evaluation,
forbid_methods_with_side_effect,
where_clause_def):
"""
Resolve a variable or a Concept
@@ -375,7 +456,8 @@ class SheerkaEvaluateConcept(BaseService):
: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 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:
"""
@@ -412,6 +494,9 @@ class SheerkaEvaluateConcept(BaseService):
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)
@@ -440,7 +525,7 @@ class SheerkaEvaluateConcept(BaseService):
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 = [r for r in to_resolve] if hasattr(to_resolve, "__iter__") else to_resolve
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:
@@ -469,6 +554,7 @@ class SheerkaEvaluateConcept(BaseService):
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)
@@ -483,6 +569,7 @@ class SheerkaEvaluateConcept(BaseService):
current_prop,
current_concept,
force_evaluation,
forbid_methods_with_side_effect,
where_clause_def)
res = []
@@ -499,6 +586,7 @@ class SheerkaEvaluateConcept(BaseService):
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
@@ -506,19 +594,27 @@ class SheerkaEvaluateConcept(BaseService):
return res
def evaluate_concept(self, context, concept: Concept, eval_body=False, metadata=None):
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
"""
if concept.get_metadata().is_evaluated:
return concept
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
@@ -526,9 +622,9 @@ class SheerkaEvaluateConcept(BaseService):
# 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_metadata().is_evaluated:
# if from_cache.get_hints().is_evaluated:
# concept.set_value(ConceptParts.BODY, from_cache.body)
# concept.get_metadata().is_evaluated = True
# concept.get_hints().is_evaluated = True
# return concept
desc = f"Evaluating concept {concept}"
@@ -538,6 +634,10 @@ class SheerkaEvaluateConcept(BaseService):
# 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)
@@ -563,10 +663,23 @@ class SheerkaEvaluateConcept(BaseService):
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, w_clause)
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, w_clause)
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
@@ -592,11 +705,25 @@ class SheerkaEvaluateConcept(BaseService):
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, None)
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):
return 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)
@@ -614,8 +741,8 @@ class SheerkaEvaluateConcept(BaseService):
concept.init_key() # Necessary for old unit tests. To remove someday
if ConceptParts.BODY in all_metadata_to_eval:
concept.get_metadata().is_evaluated = True
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
@@ -626,10 +753,7 @@ class SheerkaEvaluateConcept(BaseService):
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():
return concept.get_value(ConceptParts.RET)
else:
return concept
return self.apply_ret(concept, sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED))
def call_concept(self, context, concept, *args, **kwargs):
"""
@@ -656,7 +780,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.get_metadata().need_validation:
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")