Fixed #131 : Implement ExprToConditions
Fixed #130 : ArithmeticOperatorParser Fixed #129 : python_wrapper : create_namespace Fixed #128 : ExpressionParser: Cannot parse func(x) infixed concept 'xxx'
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import core.builtin_helpers
|
||||
import core.utils
|
||||
from core.builtin_concepts_ids import BuiltinConcepts, BuiltinErrors
|
||||
from core.concept import Concept
|
||||
from core.global_symbols import ErrorObj, NotInit
|
||||
from core.sheerka.services.sheerka_service import BaseService
|
||||
|
||||
|
||||
class ErrorItem:
|
||||
def __init__(self, level: int, parent, error: object):
|
||||
self.level = level
|
||||
self.parent = parent
|
||||
self.error = error
|
||||
|
||||
|
||||
class SheerkaErrorManager(BaseService):
|
||||
NAME = "Error"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka, order=1)
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.NAME, self.get_errors, False)
|
||||
self.sheerka.bind_service_method(self.NAME, self.has_error, False)
|
||||
self.sheerka.bind_service_method(self.NAME, self.get_error_cause, False)
|
||||
|
||||
def get_errors(self, context, obj, **kwargs):
|
||||
"""
|
||||
Browse obj, looking for error
|
||||
:param context:
|
||||
:param obj:
|
||||
:param kwargs: if defined, specialize the error (example __type="PythonEvalError")
|
||||
:return:
|
||||
"""
|
||||
|
||||
def is_error(_obj):
|
||||
if isinstance(_obj, (ErrorObj, Exception)):
|
||||
return True
|
||||
|
||||
if isinstance(_obj, Concept) and _obj.get_metadata().is_builtin and _obj.key in BuiltinErrors:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def inner_get_errors(_obj, level, parent):
|
||||
if self.sheerka.isinstance(_obj, BuiltinConcepts.RETURN_VALUE) and _obj.status:
|
||||
return []
|
||||
|
||||
if isinstance(_obj, (list, set, tuple)):
|
||||
return core.utils.flatten([inner_get_errors(o, level, parent) for o in _obj])
|
||||
|
||||
if is_error(_obj):
|
||||
#error_item = ErrorItem(level, parent, _obj)
|
||||
error_item = _obj
|
||||
if isinstance(_obj, Concept) and _obj.body not in (NotInit, None):
|
||||
return [error_item] + inner_get_errors(_obj.body, level + 1, error_item)
|
||||
if isinstance(_obj, ErrorObj) and hasattr(_obj, "get_error"):
|
||||
return [error_item] + inner_get_errors(_obj.get_error(), level + 1, error_item)
|
||||
else:
|
||||
return [error_item]
|
||||
|
||||
if self.sheerka.isinstance(_obj, BuiltinConcepts.FILTERED):
|
||||
return inner_get_errors(_obj.reason, level, parent)
|
||||
|
||||
if isinstance(_obj, Concept) and _obj.body != NotInit:
|
||||
return inner_get_errors(_obj.body, level, parent)
|
||||
|
||||
return []
|
||||
|
||||
errors = inner_get_errors(obj, 0, None)
|
||||
#return self.sheerka.filter_objects(context, errors, mapping=lambda o: o.error, **kwargs)
|
||||
return self.sheerka.filter_objects(context, errors, **kwargs)
|
||||
|
||||
def has_error(self, context, obj, **kwargs):
|
||||
errors = self.get_errors(context, obj, **kwargs)
|
||||
return len(errors) > 0
|
||||
|
||||
def get_error_cause(self, obj):
|
||||
if self.sheerka.isinstance(obj, BuiltinConcepts.NOT_FOR_ME):
|
||||
res = obj.reason
|
||||
elif self.sheerka.isinstance(obj, BuiltinConcepts.ERROR):
|
||||
res = obj.body
|
||||
else:
|
||||
res = None
|
||||
|
||||
if isinstance(res, list) and len(res) == 1:
|
||||
return res[0]
|
||||
else:
|
||||
return res
|
||||
@@ -1,22 +1,20 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.builtin_helpers import ensure_bnf, ensure_concept, expect_one, is_only_successful, only_successful
|
||||
from core.builtin_helpers import ensure_bnf, ensure_concept, expect_one, get_parsed_concept, is_only_successful, \
|
||||
only_successful, variables_in_context
|
||||
from core.concept import AllConceptParts, Concept, ConceptParts, DoNotResolve, InfiniteRecursionResolved, \
|
||||
concept_part_value
|
||||
from core.global_symbols import CURRENT_OBJ, INIT_AST_PARSERS, INIT_AST_QUESTION_PARSERS, NotInit
|
||||
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, ChickenAndEggException, FailedToCompileError
|
||||
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
|
||||
from sheerkapython.ExprToConditions import ExprToConditionsVisitor
|
||||
|
||||
CONCEPT_EVALUATION_STEPS = [
|
||||
BuiltinConcepts.BEFORE_EVALUATION,
|
||||
@@ -156,6 +154,10 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
:param var_name:
|
||||
:return:
|
||||
"""
|
||||
|
||||
from parsers.LogicalOperatorParser import LogicalOperatorParser
|
||||
from parsers.ExpressionParser import ExpressionParser
|
||||
|
||||
if concept.get_metadata().where is None or concept.get_metadata().where.strip() == "":
|
||||
return None
|
||||
|
||||
@@ -171,24 +173,14 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
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)
|
||||
|
||||
expr_to_cond_python_visitor = ExprToConditionsVisitor(context)
|
||||
conditions = expr_to_cond_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):
|
||||
"""
|
||||
@@ -230,6 +222,7 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
:param possible_variables: concepts that must be considered as variables
|
||||
:return:
|
||||
"""
|
||||
|
||||
def get_filtered_by_prevent_circular_reference(return_values):
|
||||
for ret_val in return_values:
|
||||
from evaluators.PreventCircularReferenceEvaluator import PreventCircularReferenceEvaluator
|
||||
@@ -256,16 +249,18 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
: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
|
||||
evaluating_question = p in [ConceptParts.WHERE, ConceptParts.PRE]
|
||||
parsers_to_use = INIT_AST_QUESTION_PARSERS if evaluating_question else INIT_AST_PARSERS
|
||||
recursion_detected = False
|
||||
while True:
|
||||
return_value = current_context.sheerka.parse_unrecognized(current_context,
|
||||
s,
|
||||
parsers=parsers_to_use,
|
||||
parsers_to_use,
|
||||
who=who,
|
||||
concept=c,
|
||||
prop=p,
|
||||
filter_func=only_successful,
|
||||
is_question=evaluating_question,
|
||||
possible_variables=possible_variables)
|
||||
|
||||
if not return_value.status:
|
||||
@@ -318,7 +313,17 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
return concept_found
|
||||
else:
|
||||
# ...or a list of ReturnValueConcept to resolve
|
||||
return get_return_value(context, concept, source, part_key)
|
||||
ret_values = get_return_value(context, concept, source, part_key)
|
||||
for ret_val in ret_values:
|
||||
# correct when the concept is incorrectly detected as definition
|
||||
# if its parameters can be resolved, it means that it was a concept instance
|
||||
if ((inner_concept := get_parsed_concept(ret_val)) is not None and
|
||||
not inner_concept.get_hints().is_instance and
|
||||
variables_in_context(context, inner_concept.get_metadata().parameters)):
|
||||
inner_concept.get_hints().is_instance = True
|
||||
inner_concept.get_hints().is_evaluated = False
|
||||
|
||||
return ret_values
|
||||
|
||||
def apply_where_clause(self, context, where_clause_def, return_values):
|
||||
"""
|
||||
@@ -431,6 +436,7 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
|
||||
ensure_bnf(context, concept)
|
||||
if concept.get_bnf():
|
||||
from parsers.BnfNodeParser import BnfNodeConceptExpressionVisitor
|
||||
visitor = BnfNodeConceptExpressionVisitor()
|
||||
visitor.visit(concept.get_bnf())
|
||||
possible_variables = [c.name if isinstance(c, Concept) else c for c in visitor.references]
|
||||
@@ -810,20 +816,20 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
return self.apply_ret(concept, sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED))
|
||||
|
||||
def call_concept(self, context, concept, *args, **kwargs):
|
||||
return self.call_concept_with_args(context,
|
||||
concept,
|
||||
hints=EvaluationHints(eval_body=True, eval_question=False),
|
||||
*args,
|
||||
**kwargs)
|
||||
return self._inner_call_concept_with_hint_and_args(context,
|
||||
concept,
|
||||
hints=EvaluationHints(eval_body=True, eval_question=False),
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def evaluate_question(self, context, concept, *args, **kwargs):
|
||||
return self.call_concept_with_args(context,
|
||||
concept,
|
||||
hints=EvaluationHints(eval_body=True, eval_question=True),
|
||||
*args,
|
||||
**kwargs)
|
||||
return self._inner_call_concept_with_hint_and_args(context,
|
||||
concept,
|
||||
hints=EvaluationHints(eval_body=True, eval_question=True),
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def call_concept_with_args(self, context, concept, hints, *args, **kwargs):
|
||||
def _inner_call_concept_with_hint_and_args(self, context, concept, hints, *args, **kwargs):
|
||||
"""
|
||||
call the concept using either args or kwargs (not both)
|
||||
:param context:
|
||||
|
||||
@@ -120,8 +120,12 @@ class SheerkaEvaluateRules(BaseService):
|
||||
:param missing_vars: if initialized to a set, keeps tracks of the missing variables
|
||||
:return:
|
||||
"""
|
||||
|
||||
bag_variables = set(bag.keys())
|
||||
|
||||
for k, v in bag.items():
|
||||
context.add_to_short_term_memory(k, v)
|
||||
|
||||
results = []
|
||||
for compiled_condition in conditions:
|
||||
|
||||
|
||||
@@ -24,81 +24,89 @@ class ParserInput:
|
||||
Helper class that tokenizes the input once for all
|
||||
"""
|
||||
|
||||
def __init__(self, text, tokens=None, length=None, start=None, end=None, yield_oef=True):
|
||||
def __init__(self, text, tokens=None, start=None, end=None, yield_oef=True):
|
||||
# Tokenizing the text may raise an exception
|
||||
# But we do not want it to be thrown in the initializer (because it's bad !)
|
||||
# So let's keep it somewhere and raise it as soon as we can
|
||||
self.exception = None
|
||||
|
||||
self.text = text
|
||||
self.tokens = tokens or None
|
||||
if self.tokens:
|
||||
if tokens is None:
|
||||
try:
|
||||
# the eof if forced, but will not be yield if not set to.
|
||||
self.tokens = list(Tokenizer(self.text, yield_eof=True))
|
||||
except Exception as ex:
|
||||
self.tokens = None
|
||||
self.exception = ex
|
||||
else:
|
||||
# make sure tokens ends with EOF token
|
||||
# and do not modify the original token list
|
||||
if len(self.tokens) == 0:
|
||||
if len(tokens) == 0:
|
||||
self.tokens = [Token(TokenKind.EOF, "", 0, 1, 1)]
|
||||
elif (last_token := tokens[-1]).type != TokenKind.EOF:
|
||||
self.tokens = tokens + [Token(TokenKind.EOF,
|
||||
"",
|
||||
last_token.index + 1,
|
||||
last_token.line,
|
||||
last_token.column + 1)]
|
||||
else:
|
||||
self.tokens = tokens
|
||||
|
||||
elif (last_token := self.tokens[-1]).type != TokenKind.EOF:
|
||||
self.tokens = self.tokens + [Token(TokenKind.EOF,
|
||||
"",
|
||||
last_token.index + 1,
|
||||
last_token.line,
|
||||
last_token.column + 1)]
|
||||
|
||||
self.length = length # to be computed (again) in reset()
|
||||
self.length = len(self.tokens) if self.tokens else -1
|
||||
self.yield_oef = yield_oef
|
||||
|
||||
self.start = start or 0
|
||||
self.original_end = end
|
||||
if end is not None:
|
||||
self.original_end = end # forced index of the last token
|
||||
self.end = self.original_end # index of the last token => len(tokens) - 1 if full tokens
|
||||
max_end = self.get_end_from_yield_eof(self.length, self.yield_oef)
|
||||
self.end = end if end < max_end else max_end
|
||||
else:
|
||||
self.original_end = self.end = None
|
||||
self.end = self.get_end_from_yield_eof(self.length, self.yield_oef)
|
||||
|
||||
self.sub_text = None
|
||||
self.sub_tokens = None
|
||||
|
||||
self.pos = None
|
||||
self.token = None
|
||||
|
||||
self.from_tokens = tokens is not None
|
||||
self.from_tokens = text is None and tokens is not None
|
||||
|
||||
def __repr__(self):
|
||||
from_tokens = "from_tokens" if self.from_tokens else ""
|
||||
return f"ParserInput({from_tokens}'{self.text}')"
|
||||
return f"ParserInput({from_tokens}'{self.as_text()}')"
|
||||
|
||||
@staticmethod
|
||||
def get_end_from_yield_eof(length, yield_oef):
|
||||
return length - 1 if yield_oef else length - 2
|
||||
|
||||
def reset(self, yield_oef=None):
|
||||
|
||||
def _get_end_from_yield_eof(_length, _yield_oef):
|
||||
return _length - 1 if _yield_oef else _length - 2
|
||||
if self.exception:
|
||||
raise self.exception
|
||||
|
||||
if yield_oef is None:
|
||||
yield_oef = self.yield_oef
|
||||
|
||||
# make sure tokens is correctly initialized
|
||||
if self.tokens is None:
|
||||
# the eof if forced, but will not be yield if not set to.
|
||||
self.tokens = list(Tokenizer(self.text, yield_eof=True))
|
||||
|
||||
self.length = len(self.tokens)
|
||||
|
||||
if self.original_end is None:
|
||||
self.end = _get_end_from_yield_eof(self.length, yield_oef)
|
||||
self.end = self.get_end_from_yield_eof(self.length, yield_oef)
|
||||
else:
|
||||
self.end = self.original_end if self.original_end < self.length else \
|
||||
_get_end_from_yield_eof(self.length, yield_oef)
|
||||
max_end = self.get_end_from_yield_eof(self.length, yield_oef)
|
||||
self.end = self.original_end if self.original_end < max_end else max_end
|
||||
|
||||
self.pos = self.start - 1
|
||||
self.token = None
|
||||
return self
|
||||
|
||||
def as_text(self, custom_switcher=None, tracker=None):
|
||||
if not self.tokens or self.end is None:
|
||||
# as_text is requested before reset().
|
||||
# It means that we want the original text
|
||||
if not self.tokens:
|
||||
return self.text
|
||||
|
||||
if custom_switcher is None:
|
||||
if self.sub_text:
|
||||
return self.sub_text
|
||||
if self.start == 0 and self.end == self.length - 1:
|
||||
|
||||
if self.start == 0 and self.end == self.length - 1 and self.text:
|
||||
self.sub_text = self.text
|
||||
return self.sub_text
|
||||
|
||||
self.sub_text = core.utils.get_text_from_tokens(self.tokens[self.start:self.end + 1])
|
||||
return self.sub_text
|
||||
else:
|
||||
@@ -128,6 +136,11 @@ class ParserInput:
|
||||
while self.token.type in (TokenKind.WHITESPACE, TokenKind.NEWLINE):
|
||||
self.pos += 1
|
||||
if self.pos > self.end:
|
||||
self.token = Token(TokenKind.EOF,
|
||||
"",
|
||||
self.pos,
|
||||
self.token.line,
|
||||
self.token.column + 1)
|
||||
return False
|
||||
self.token = self.tokens[self.pos]
|
||||
|
||||
@@ -175,10 +188,7 @@ class ParserInput:
|
||||
if self.from_tokens and len(self.tokens) == 0:
|
||||
return True
|
||||
|
||||
if self.end == self.start:
|
||||
return True
|
||||
|
||||
if self.end and self.end == self.start + 1 and self.tokens[self.start].type == TokenKind.WHITESPACE:
|
||||
if self.end and self.end == self.start and self.tokens[self.start].type == TokenKind.WHITESPACE:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -198,7 +208,11 @@ class ParserInput:
|
||||
return clone
|
||||
|
||||
def sub_part(self, start, end, yield_oef=None):
|
||||
return ParserInput(self.text, self.tokens, self.length, start, end, yield_oef)
|
||||
return ParserInput(self.text, self.tokens, start, end, yield_oef)
|
||||
|
||||
@property
|
||||
def effective_length(self):
|
||||
return self.end - self.start + 1
|
||||
|
||||
|
||||
class SheerkaExecute(BaseService):
|
||||
@@ -426,12 +440,12 @@ class SheerkaExecute(BaseService):
|
||||
pi = self.pi_cache.get(text)
|
||||
if pi is NotFound: # when CacheManager.cache_only is True
|
||||
pi = ParserInput(text)
|
||||
self.pi_cache.put(text, pi)
|
||||
return ParserInput(text, tokens=pi.tokens,
|
||||
length=pi.length) # new instance, but no need to tokenize the text again
|
||||
if pi.tokens:
|
||||
self.pi_cache.put(text, pi)
|
||||
return ParserInput(text, tokens=pi.tokens) # new instance, but no need to tokenize the text again
|
||||
|
||||
key = text or core.utils.get_text_from_tokens(tokens)
|
||||
pi = ParserInput(key, tokens=tokens, length=len(tokens))
|
||||
pi = ParserInput(key, tokens=tokens)
|
||||
self.pi_cache.put(key, pi)
|
||||
return pi
|
||||
|
||||
@@ -491,6 +505,12 @@ class SheerkaExecute(BaseService):
|
||||
:return:
|
||||
"""
|
||||
|
||||
def _compute_key_for_parser_input(_input):
|
||||
if isinstance(_input.body, ParserInput) and _input.body.from_tokens:
|
||||
return "".join(repr(t) for t in _input.body.tokens)
|
||||
|
||||
return self.get_input_as_text(_input.body)
|
||||
|
||||
# return_values must be a list
|
||||
if not isinstance(return_values, list):
|
||||
return_values = [return_values]
|
||||
@@ -545,9 +565,7 @@ class SheerkaExecute(BaseService):
|
||||
if self.sheerka.enable_parser_caching and to_process and parsers_key:
|
||||
processed = []
|
||||
for return_value in to_process:
|
||||
to_parse_as_str = self.get_input_as_text(return_value.body.body) \
|
||||
if self.sheerka.isinstance(return_value.body, BuiltinConcepts.USER_INPUT) \
|
||||
else return_value.body.source
|
||||
to_parse_as_str = _compute_key_for_parser_input(return_value.body)
|
||||
|
||||
key_to_use = (parsers_key, to_parse_as_str)
|
||||
if key_to_use in self.parsers_cache:
|
||||
@@ -822,6 +840,7 @@ class SheerkaExecute(BaseService):
|
||||
|
||||
def parse_unrecognized(self, context, source, parsers,
|
||||
who=None,
|
||||
concept=None,
|
||||
prop=None,
|
||||
filter_func=None,
|
||||
is_question=False,
|
||||
@@ -832,31 +851,23 @@ class SheerkaExecute(BaseService):
|
||||
:param source: ParserInput if possible
|
||||
:param parsers:
|
||||
:param who: who is asking the parsing ?
|
||||
:param concept: concept being compiled
|
||||
:param prop: Extra info, when parsing a property
|
||||
:param filter_func: Once the result are found, call this function to filter them
|
||||
:param is_question: Force EVAL_QUESTION_REQUESTED
|
||||
:param possible_variables: concepts that must be considered as variables
|
||||
:return:
|
||||
"""
|
||||
|
||||
# def filter_for_circular_reference(return_values, concept):
|
||||
# for r in return_values:
|
||||
# if r.status and context.sheerka.isinstance(r.body, BuiltinConcepts.PARSER_RESULT):
|
||||
# body = r.body.body[0] if isinstance(r.body.body, list) and len(r.body.body) == 1 else r.body.body
|
||||
# if hasattr(body, "get_concept") and body.get_concept().id == concept.id:
|
||||
# continue
|
||||
# yield r
|
||||
|
||||
sheerka = context.sheerka
|
||||
|
||||
if prop:
|
||||
action_context = {"prop": prop, "source": source}
|
||||
action_context = {"concept": concept, "prop": prop, "source": source}
|
||||
desc = f"Parsing attribute '{prop}'"
|
||||
else:
|
||||
action_context = source
|
||||
desc = f"Parsing '{source}'"
|
||||
|
||||
with context.push(BuiltinConcepts.PARSING, action_context, who=who, desc=desc) as sub_context:
|
||||
with context.push(BuiltinConcepts.PARSE_CODE, action_context, who=who, desc=desc) as sub_context:
|
||||
|
||||
if (prop in (Keywords.WHERE, Keywords.PRE, ConceptParts.WHERE, ConceptParts.PRE, Keywords.WHEN) or
|
||||
is_question):
|
||||
@@ -876,14 +887,6 @@ class SheerkaExecute(BaseService):
|
||||
sheerka.new(BuiltinConcepts.USER_INPUT, body=source))
|
||||
res = sheerka.execute(sub_context, to_parse, PARSE_STEPS)
|
||||
|
||||
# # before user defined filtering, remove the return values that may lead to circular reference
|
||||
# in_recursion = list(context.search(predicate=lambda c: c.action == BuiltinConcepts.EVALUATING_CONCEPT,
|
||||
# get_obj=lambda c: c.action_context,
|
||||
# only_first=True))
|
||||
#
|
||||
# if in_recursion:
|
||||
# res = list(filter_for_circular_reference(res, in_recursion[0]))
|
||||
|
||||
if filter_func:
|
||||
res = filter_func(sub_context, res)
|
||||
|
||||
@@ -902,8 +905,8 @@ class SheerkaExecute(BaseService):
|
||||
from parsers.BaseNodeParser import SourceCodeWithConceptNode
|
||||
|
||||
sheerka = context.sheerka
|
||||
from parsers.FunctionParser import FunctionParser
|
||||
parser = FunctionParser()
|
||||
from parsers.FunctionParserOld import FunctionParserOld
|
||||
parser = FunctionParserOld()
|
||||
desc = f"Parsing function '{source}'"
|
||||
with context.push(BuiltinConcepts.PARSE_CODE, source, desc=desc) as sub_context:
|
||||
sheerka_execution = sheerka.services[SheerkaExecute.NAME]
|
||||
@@ -933,9 +936,7 @@ class SheerkaExecute(BaseService):
|
||||
from parsers.PythonParser import PythonParser
|
||||
|
||||
desc = desc or f"Compiling python '{source}'"
|
||||
with context.push(BuiltinConcepts.PARSE_CODE,
|
||||
{"language": "Python", "source": source},
|
||||
desc) as sub_context:
|
||||
with context.push(BuiltinConcepts.PARSE_CODE, source, desc) as sub_context:
|
||||
parser_input = context.sheerka.services[SheerkaExecute.NAME].get_parser_input(source)
|
||||
python_parser = PythonParser()
|
||||
return python_parser.parse(sub_context, parser_input)
|
||||
|
||||
@@ -256,7 +256,7 @@ class SheerkaIsAManager(BaseService):
|
||||
return self.sheerka.new(BuiltinConcepts.CONDITION_FAILED, body=concept)
|
||||
|
||||
predicate = concept.get_metadata().where.replace(possibles_variables[0], "sheerka.objvalue(self)")
|
||||
res = self.sheerka.filter_objects(context, results, predicate)
|
||||
res = self.sheerka.filter_objects(context, results, predicate=predicate)
|
||||
return res
|
||||
|
||||
def _get_concepts(self, context, ids, evaluate):
|
||||
|
||||
@@ -257,7 +257,7 @@ class SheerkaMemory(BaseService):
|
||||
current_list = sorted(current_list, key=lambda o: o.timestamp, reverse=True)
|
||||
current_objects = [o.obj for o in current_list]
|
||||
|
||||
res = self.sheerka.filter_objects(context, current_objects, name_to_use)
|
||||
res = self.sheerka.filter_objects(context, current_objects, predicate=name_to_use)
|
||||
if len(res) > 0:
|
||||
return res[0] # only the first, as it should have a better timestamp
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from cache.FastCache import FastCache
|
||||
from core.builtin_concepts_ids import BuiltinContainers, BuiltinConcepts
|
||||
from core.builtin_concepts_ids import BuiltinConcepts, BuiltinContainers
|
||||
from core.concept import Concept, ConceptParts
|
||||
from core.sheerka.services.SheerkaEvaluateRules import SheerkaEvaluateRules
|
||||
from core.sheerka.services.sheerka_service import BaseService
|
||||
from core.tokenizer import Tokenizer, TokenKind
|
||||
from core.tokenizer import TokenKind, Tokenizer
|
||||
from core.utils import as_bag
|
||||
from sheerkapython.python_wrapper import create_namespace, ObjectContainer, get_type
|
||||
from sheerkapython.python_wrapper import ObjectContainer, create_namespace, get_type
|
||||
from sheerkaql.lexer import Lexer
|
||||
from sheerkaql.parser import Parser
|
||||
|
||||
@@ -17,6 +17,7 @@ class SheerkaQueryManager(BaseService):
|
||||
NAME = "QueryManager"
|
||||
OBJECTS_ROOT_ALIAS = "__xxx__objects__xx__"
|
||||
QUERY_PARAMETER_PREFIX = "__xxx__query_parameter__xx__"
|
||||
MAPPING_PREFIX = "__xxx__map__xx__"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka, order=16)
|
||||
@@ -39,16 +40,19 @@ class SheerkaQueryManager(BaseService):
|
||||
def initialize_deferred(self, context, is_first_time):
|
||||
self.rule_evaluator = self.sheerka.services[SheerkaEvaluateRules.NAME]
|
||||
|
||||
def get_query_by_kwargs(self, local_namespace, **kwargs):
|
||||
def get_query_by_kwargs(self, local_namespace, use_mapping, **kwargs):
|
||||
"""
|
||||
Create a predicate using kwargs and filter the result
|
||||
:param local_namespace:
|
||||
:param use_mapping: True if a mapping is to be used
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
if not kwargs:
|
||||
return None
|
||||
|
||||
self_ident = f"{self.MAPPING_PREFIX}(self)" if use_mapping else "self"
|
||||
|
||||
objects_in_context_index = 0
|
||||
conditions = []
|
||||
for k, v in kwargs.items():
|
||||
@@ -57,28 +61,39 @@ class SheerkaQueryManager(BaseService):
|
||||
local_namespace[current_variable_name] = v
|
||||
|
||||
if k == "__type":
|
||||
conditions.append(f"get_type(self) == {current_variable_name}")
|
||||
conditions.append(f"get_type({self_ident}) == {current_variable_name}")
|
||||
|
||||
elif k in ("__self", "_"):
|
||||
conditions.append(f"self == {current_variable_name}")
|
||||
conditions.append(f"{self_ident} == {current_variable_name}")
|
||||
|
||||
elif k.endswith("_contains"):
|
||||
prop_name = k[:-9]
|
||||
conditions.append(f"{current_variable_name} in self.{prop_name}")
|
||||
conditions.append(f"{current_variable_name} in {self_ident}.{prop_name}")
|
||||
|
||||
else:
|
||||
conditions.append(f"self.{k} == {current_variable_name}")
|
||||
conditions.append(f"{self_ident}.{k} == {current_variable_name}")
|
||||
|
||||
return ' and '.join(conditions)
|
||||
|
||||
def filter_objects(self, context, objects, predicate=None, **kwargs):
|
||||
def get_updated_predicate(self, predicate, use_mapping):
|
||||
if predicate is None:
|
||||
return None
|
||||
|
||||
if use_mapping:
|
||||
predicate = predicate.replace("self", f"{self.MAPPING_PREFIX}(self)")
|
||||
|
||||
return predicate
|
||||
|
||||
def filter_objects(self, context, objects, mapping=None, predicate=None, **kwargs):
|
||||
"""
|
||||
filter the given objects using the conditions from kwargs
|
||||
for each k,v in kwargs, the equality k == v is added
|
||||
for k starting with a double underscore '__', a special treatment may be done
|
||||
__type : get the type of object (in Sheerka world)
|
||||
|
||||
:param context:
|
||||
:param objects:
|
||||
:param mapping: mapping to execute on each object before applying the predicate (lambda obj:obj)
|
||||
:param predicate:
|
||||
:param kwargs:
|
||||
:return:
|
||||
@@ -93,8 +108,9 @@ class SheerkaQueryManager(BaseService):
|
||||
|
||||
debugger.debug_entering(nb_objects=len(objects), predicate=predicate, **kwargs)
|
||||
|
||||
local_namespace = {}
|
||||
query_by_kwargs = self.get_query_by_kwargs(local_namespace, **kwargs)
|
||||
local_namespace = {} if mapping is None else {self.MAPPING_PREFIX: mapping}
|
||||
query_by_kwargs = self.get_query_by_kwargs(local_namespace, mapping is not None, **kwargs)
|
||||
predicate = self.get_updated_predicate(predicate, mapping is not None)
|
||||
|
||||
if predicate is not None and query_by_kwargs is not None:
|
||||
query = f"({predicate}) and ({query_by_kwargs})"
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
import operator
|
||||
from dataclasses import dataclass
|
||||
from typing import Union, Set, List, Tuple
|
||||
from typing import List, Set, Tuple, Union
|
||||
|
||||
from cache.Cache import Cache
|
||||
from cache.ListIfNeededCache import ListIfNeededCache
|
||||
from core.ast_helpers import UnreferencedNamesVisitor
|
||||
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||
from core.builtin_helpers import ensure_evaluated, expect_one, evaluate_from_source, \
|
||||
get_possible_variables_from_concept, is_a_question, only_successful, is_only_successful, evaluate_return_values
|
||||
from core.builtin_helpers import evaluate_from_source, expect_one
|
||||
from core.concept import Concept
|
||||
from core.global_symbols import EVENT_RULE_PRECEDENCE_MODIFIED, RULE_COMPARISON_CONTEXT, NotFound, ErrorObj, \
|
||||
EVENT_RULE_CREATED, EVENT_RULE_DELETED, NotInit, INIT_AST_PARSERS
|
||||
from core.rule import Rule, ACTION_TYPE_PRINT
|
||||
from core.sheerka.Sheerka import RECOGNIZED_BY_NAME, RECOGNIZED_BY_ID
|
||||
from core.global_symbols import EVENT_RULE_CREATED, EVENT_RULE_DELETED, EVENT_RULE_PRECEDENCE_MODIFIED, ErrorObj, \
|
||||
NotFound, NotInit, RULE_COMPARISON_CONTEXT
|
||||
from core.rule import ACTION_TYPE_PRINT, Rule
|
||||
from core.sheerka.Sheerka import RECOGNIZED_BY_ID, RECOGNIZED_BY_NAME
|
||||
from core.sheerka.services.sheerka_service import BaseService, FailedToCompileError, UnknownVariableError
|
||||
from core.tokenizer import Keywords, TokenKind, Token
|
||||
from core.utils import merge_dicts, merge_sets, get_safe_str_value
|
||||
from evaluators.PythonEvaluator import PythonEvaluator
|
||||
from parsers.BaseExpressionParser import AndNode, ExpressionVisitor, VariableNode, ComparisonNode, FunctionNode, \
|
||||
ComparisonType, NotNode, NameExprNode
|
||||
from core.tokenizer import Token, TokenKind
|
||||
from parsers.BaseExpressionParser import AndNode, ComparisonNode, ExpressionVisitor, \
|
||||
FunctionNodeOld, NameExprNode, NotNode, VariableNode
|
||||
from parsers.BaseNodeParser import ConceptNode
|
||||
from parsers.FormatRuleActionParser import FormatRuleActionParser
|
||||
from parsers.LogicalOperatorParser import LogicalOperatorParser
|
||||
from sheerkapython.ExprToConditions import ExprToConditionsVisitor
|
||||
from sheerkapython.python_wrapper import Expando, sheerka_globals
|
||||
from sheerkarete.common import V
|
||||
from sheerkarete.conditions import AndConditions, Condition, NegatedCondition, NegatedConjunctiveConditions
|
||||
@@ -199,11 +196,10 @@ class SheerkaRuleManager(BaseService):
|
||||
pass
|
||||
|
||||
# get the conditions as sheerka conditions
|
||||
try:
|
||||
python_visitor = PythonConditionExprVisitor(context)
|
||||
python_conditions = python_visitor.get_conditions(parsed)
|
||||
except FailedToCompileError as ex:
|
||||
raise ex
|
||||
python_visitor = ExprToConditionsVisitor(context)
|
||||
python_conditions = python_visitor.get_conditions(parsed)
|
||||
if python_visitor.errors:
|
||||
raise FailedToCompileError(python_visitor.errors)
|
||||
|
||||
return ConditionCompilationResult(python_conditions, rete_conditions)
|
||||
|
||||
@@ -220,9 +216,8 @@ class SheerkaRuleManager(BaseService):
|
||||
def compile_exec(self, context, source):
|
||||
parsed = context.sheerka.parse_unrecognized(context,
|
||||
source,
|
||||
parsers="all",
|
||||
"all",
|
||||
who=self.NAME,
|
||||
prop=Keywords.THEN,
|
||||
filter_func=expect_one)
|
||||
|
||||
return parsed
|
||||
@@ -425,13 +420,13 @@ class SheerkaRuleManager(BaseService):
|
||||
return rule
|
||||
# try via indirection
|
||||
if (rule_id := self.sheerka.get_from_short_term_memory(context, obj.value[1])) is not NotFound and \
|
||||
(rule := self._inner_get_by_id(str(rule_id))) is not None:
|
||||
(rule := self._inner_get_by_id(str(rule_id))) is not None:
|
||||
return rule
|
||||
|
||||
elif isinstance(obj, Rule):
|
||||
if obj.metadata.id_is_unresolved:
|
||||
if (rule_id := self.sheerka.get_from_short_term_memory(context, obj.id)) is not NotFound and \
|
||||
(rule := self._inner_get_by_id(str(rule_id))) is not None:
|
||||
(rule := self._inner_get_by_id(str(rule_id))) is not None:
|
||||
return rule
|
||||
else:
|
||||
return self._inner_get_by_id(obj.id)
|
||||
@@ -711,7 +706,7 @@ class ReteConditionExprVisitor(GetConditionExprVisitor):
|
||||
else:
|
||||
raise FailedToCompileError([expr_node])
|
||||
|
||||
def visit_FunctionNode(self, expr_node: FunctionNode):
|
||||
def visit_FunctionNodeOld(self, expr_node: FunctionNodeOld):
|
||||
if expr_node.first.value == "recognize(":
|
||||
if not isinstance(expr_node.parameters[0].value, VariableNode):
|
||||
return FailedToCompileError([f"Cannot recognize '{expr_node.parameters[0].value}'"])
|
||||
@@ -823,7 +818,7 @@ class ReteConditionExprVisitor(GetConditionExprVisitor):
|
||||
|
||||
attr = attr or FACT_SELF
|
||||
if (isinstance(value, Expando) and value.get_name() == "sheerka" or
|
||||
isinstance(value, Concept) and value.id == self.context.sheerka.id):
|
||||
isinstance(value, Concept) and value.id == self.context.sheerka.id):
|
||||
conditions.append(Condition(left, attr, "__sheerka__"))
|
||||
elif isinstance(value, Concept):
|
||||
res = self.recognize_concept(var_path, value, {})
|
||||
@@ -833,424 +828,3 @@ class ReteConditionExprVisitor(GetConditionExprVisitor):
|
||||
conditions.append(Condition(left, attr, var_root))
|
||||
else:
|
||||
conditions.append(Condition(left, attr, value))
|
||||
|
||||
|
||||
@dataclass()
|
||||
class PythonConditionExprVisitorObj:
|
||||
text: Union[str, None] # human readable
|
||||
source: Union[str, None] # python expression to compile
|
||||
objects: dict
|
||||
variables: set
|
||||
not_variables: set
|
||||
|
||||
@staticmethod
|
||||
def create_function(first, last, parameters):
|
||||
|
||||
def get_function_as_text(parameter):
|
||||
if parameter is None:
|
||||
return f"{first}{last}"
|
||||
|
||||
else:
|
||||
return f"{first}{parameter}{last}"
|
||||
|
||||
if parameters is None:
|
||||
source = get_function_as_text(None)
|
||||
return PythonConditionExprVisitorObj(source, source, {}, set(), set())
|
||||
|
||||
parameters_as_list = parameters if isinstance(parameters, list) else [parameters]
|
||||
|
||||
res = []
|
||||
for obj in parameters_as_list:
|
||||
res.append(PythonConditionExprVisitorObj(get_function_as_text(obj.text),
|
||||
get_function_as_text(obj.source),
|
||||
obj.objects,
|
||||
obj.variables,
|
||||
obj.not_variables))
|
||||
|
||||
return res[0] if len(res) == 1 else res
|
||||
|
||||
@staticmethod
|
||||
def create_and(left, right):
|
||||
|
||||
def get_source(a, b):
|
||||
if a is None and b is None:
|
||||
return None
|
||||
|
||||
if a is None or a == "":
|
||||
return b
|
||||
if b is None or b == "":
|
||||
return a
|
||||
return a + " and " + b # no need to protect with parenthesis
|
||||
|
||||
return PythonConditionExprVisitorObj(get_source(left.text, right.text),
|
||||
get_source(left.source, right.source),
|
||||
merge_dicts(left.objects, right.objects),
|
||||
merge_sets(left.variables, right.variables),
|
||||
merge_sets(left.not_variables, right.not_variables))
|
||||
|
||||
@staticmethod
|
||||
def combine_with_comma(left, right):
|
||||
|
||||
def get_source(a, b):
|
||||
if a is None and b is None:
|
||||
return None
|
||||
|
||||
if a is None or a == "":
|
||||
return b
|
||||
if b is None or b == "":
|
||||
return a
|
||||
return a + ", " + b # no need to protect with parenthesis
|
||||
|
||||
if left is None:
|
||||
return right
|
||||
|
||||
return PythonConditionExprVisitorObj(get_source(left.text, right.text),
|
||||
get_source(left.source, right.source),
|
||||
merge_dicts(left.objects, right.objects),
|
||||
merge_sets(left.variables, right.variables),
|
||||
merge_sets(left.not_variables, right.not_variables))
|
||||
|
||||
@staticmethod
|
||||
def create_not(node):
|
||||
|
||||
def get_source(a):
|
||||
return f"not ({a})"
|
||||
|
||||
return PythonConditionExprVisitorObj(get_source(node.text),
|
||||
get_source(node.source),
|
||||
node.objects,
|
||||
node.variables,
|
||||
node.not_variables)
|
||||
|
||||
@staticmethod
|
||||
def create_condition(text, op, left, right):
|
||||
def get_source(a, b):
|
||||
if op == ComparisonType.EQUALS and b == "sheerka":
|
||||
return f"is_sheerka({a})"
|
||||
else:
|
||||
return ComparisonNode.rebuild_source(a, op, b)
|
||||
|
||||
return PythonConditionExprVisitorObj(text,
|
||||
get_source(left.source, right.source),
|
||||
merge_dicts(left.objects, right.objects),
|
||||
merge_sets(left.variables, right.variables),
|
||||
merge_sets(left.not_variables, right.not_variables))
|
||||
|
||||
|
||||
class PythonConditionExprVisitor(GetConditionExprVisitor):
|
||||
|
||||
def __init__(self, context):
|
||||
super().__init__(context)
|
||||
self.check_variable_existence_only = True
|
||||
self.concepts_to_reset = set()
|
||||
|
||||
def get_conditions(self, expr_node):
|
||||
self.check_variable_existence_only = True
|
||||
self.var_counter = 0
|
||||
self.variables.clear()
|
||||
self.concepts_to_reset.clear()
|
||||
|
||||
visitor_obj = self.visit(expr_node)
|
||||
if self.check_variable_existence_only:
|
||||
return [CompiledCondition(None,
|
||||
None,
|
||||
visitor_obj.variables,
|
||||
visitor_obj.not_variables,
|
||||
visitor_obj.objects,
|
||||
self.concepts_to_reset)]
|
||||
else:
|
||||
if self.variables:
|
||||
variables_definitions = "\n".join([f"{v} = {k}" for k, v in self.variables.items()])
|
||||
source = variables_definitions + "\n" + visitor_obj.source
|
||||
text = variables_definitions + "\n" + visitor_obj.text
|
||||
else:
|
||||
source = visitor_obj.source
|
||||
text = visitor_obj.text
|
||||
|
||||
ret = self.context.sheerka.parse_python(self.context, source)
|
||||
if ret.status:
|
||||
ret.body.body.original_source = text
|
||||
ret.body.body.objects = visitor_obj.objects
|
||||
return [CompiledCondition(PythonEvaluator.NAME,
|
||||
ret,
|
||||
visitor_obj.variables,
|
||||
visitor_obj.not_variables,
|
||||
visitor_obj.objects,
|
||||
self.concepts_to_reset)]
|
||||
|
||||
else:
|
||||
errors = ret.body.reason if self.context.sheerka.isinstance(ret.body, BuiltinConcepts.NOT_FOR_ME) \
|
||||
else ret.body.body
|
||||
raise FailedToCompileError(errors)
|
||||
|
||||
def get_new_variable(self, variable_path: List[str], obj_variables):
|
||||
obj_variables.add(variable_path[0])
|
||||
var_name, var_def = self.inner_get_new_variable(variable_path)
|
||||
return var_name
|
||||
|
||||
def unpack_variable(self, variable_path: List[str], obj_variables):
|
||||
if self.is_a_possible_variable(variable_path[0]):
|
||||
obj_variables.add(variable_path[0])
|
||||
return self.inner_unpack_variable(variable_path)
|
||||
|
||||
@staticmethod
|
||||
def construct_variable(root, attribute):
|
||||
if attribute is None or attribute.strip() == "":
|
||||
return root
|
||||
return root + "." + attribute
|
||||
|
||||
def visit_VariableNode(self, expr_node: VariableNode):
|
||||
if expr_node.attributes_str is None and not expr_node.name.startswith("__"):
|
||||
# try to recognize a concept
|
||||
res = self.evaluate_from_source(expr_node.name, is_question=True)
|
||||
if res.status and isinstance(res.value, Concept):
|
||||
if self.context.possible_variables and res.value.name in self.context.possible_variables:
|
||||
variable_name = expr_node.get_source()
|
||||
return PythonConditionExprVisitorObj(variable_name, variable_name, {}, {variable_name}, set())
|
||||
|
||||
# else / otherwise
|
||||
self.check_variable_existence_only = False
|
||||
return self.manage_concept(expr_node.get_source(), res.value)
|
||||
else:
|
||||
if self.context.sheerka.has_error(self.context, res, __type=BuiltinConcepts.TOO_MANY_SUCCESS):
|
||||
raise FailedToCompileError([res])
|
||||
|
||||
variable_name = expr_node.get_source()
|
||||
if res.status:
|
||||
variables = set()
|
||||
self.check_variable_existence_only = False
|
||||
else:
|
||||
variables = {variable_name}
|
||||
return PythonConditionExprVisitorObj(variable_name, variable_name, {}, variables, set())
|
||||
|
||||
if self.check_variable_existence_only:
|
||||
# special case where we want to check the existence of the whole string
|
||||
variable_name = expr_node.get_source()
|
||||
possible_variables = {variable_name} if self.is_a_possible_variable(variable_name) else set()
|
||||
else:
|
||||
# try to detect the variable
|
||||
possible_variables = set()
|
||||
var_root, var_attr = self.unpack_variable(expr_node.unpack(), possible_variables)
|
||||
variable_name = self.construct_variable(var_root, var_attr)
|
||||
return PythonConditionExprVisitorObj(variable_name, variable_name, {}, possible_variables, set())
|
||||
|
||||
def visit_ComparisonNode(self, expr_node: ComparisonNode):
|
||||
self.check_variable_existence_only = False
|
||||
left = self.visit(expr_node.left)
|
||||
right = self.visit(expr_node.right)
|
||||
|
||||
# special case when we call recognize concept when an equality with a concept is found
|
||||
right_value = right.objects[right.source] if right.source in right.objects else None
|
||||
if expr_node.comp == ComparisonType.EQUALS and isinstance(right_value, Concept):
|
||||
res = self.recognize_concept(expr_node.left.unpack(), right_value, {}, expr_node.get_source())
|
||||
else:
|
||||
res = PythonConditionExprVisitorObj.create_condition(expr_node.get_source(), expr_node.comp, left, right)
|
||||
|
||||
return res
|
||||
|
||||
def visit_AndNode(self, expr_node: AndNode):
|
||||
current_visitor_obj = self.visit(expr_node.parts[0])
|
||||
for node in expr_node.parts[1:]:
|
||||
visitor_obj = self.visit(node)
|
||||
current_visitor_obj = PythonConditionExprVisitorObj.create_and(current_visitor_obj, visitor_obj)
|
||||
|
||||
return current_visitor_obj
|
||||
|
||||
def visit_FunctionNode(self, expr_node: FunctionNode):
|
||||
self.check_variable_existence_only = False
|
||||
|
||||
if expr_node.first.value == "recognize(":
|
||||
if not isinstance(expr_node.parameters[0].value, VariableNode):
|
||||
return FailedToCompileError([f"Cannot recognize '{expr_node.parameters[0].value}'"])
|
||||
|
||||
return self.recognize_concept(expr_node.parameters[0].value.unpack(),
|
||||
expr_node.parameters[1].value,
|
||||
{})
|
||||
else:
|
||||
params_as_visitor_obj = None
|
||||
for param in expr_node.parameters:
|
||||
current_visitor_obj = self.visit(param.value)
|
||||
params_as_visitor_obj = PythonConditionExprVisitorObj.combine_with_comma(params_as_visitor_obj,
|
||||
current_visitor_obj)
|
||||
|
||||
return PythonConditionExprVisitorObj.create_function(expr_node.first, expr_node.last, params_as_visitor_obj)
|
||||
|
||||
def visit_NotNode(self, expr_node: NotNode):
|
||||
visitor_obj = self.visit(expr_node.node)
|
||||
if self.check_variable_existence_only:
|
||||
return PythonConditionExprVisitorObj(visitor_obj.text,
|
||||
visitor_obj.source,
|
||||
visitor_obj.objects,
|
||||
visitor_obj.not_variables,
|
||||
visitor_obj.variables)
|
||||
|
||||
return PythonConditionExprVisitorObj.create_not(visitor_obj)
|
||||
|
||||
def visit_NameExprNode(self, expr_node: NameExprNode):
|
||||
self.check_variable_existence_only = False
|
||||
source = expr_node.get_source()
|
||||
res = self.context.sheerka.parse_unrecognized(self.context,
|
||||
source,
|
||||
INIT_AST_PARSERS,
|
||||
filter_func=only_successful,
|
||||
is_question=True)
|
||||
if not res.status:
|
||||
raise FailedToCompileError([expr_node])
|
||||
|
||||
return_values = res.body.body if is_only_successful(self.context.sheerka, res) else [res]
|
||||
# if the result is a concept, create an object for it, otherwise just leave the source as it is
|
||||
res = evaluate_return_values(self.context, source, return_values, is_question=True)
|
||||
if res.status:
|
||||
if isinstance(res.value, Concept):
|
||||
return self.manage_concept(expr_node.get_source(), res.value)
|
||||
else:
|
||||
obj_name, objects = self.get_object_name(res.value)
|
||||
return PythonConditionExprVisitorObj(source, obj_name, objects, set(), set())
|
||||
|
||||
else:
|
||||
if len(return_values) == 1:
|
||||
body = return_values[0].body.body
|
||||
if hasattr(body, "get_python_node"):
|
||||
python_node = return_values[0].body.body.get_python_node()
|
||||
unreferenced_names_visitor = UnreferencedNamesVisitor(self.context)
|
||||
variables = unreferenced_names_visitor.get_names(python_node.ast_)
|
||||
variables = variables - set(python_node.objects.keys())
|
||||
return PythonConditionExprVisitorObj(python_node.original_source,
|
||||
python_node.source,
|
||||
python_node.objects,
|
||||
variables,
|
||||
set())
|
||||
elif isinstance(body, Concept):
|
||||
return self.manage_concept(expr_node.get_source(), body)
|
||||
|
||||
else:
|
||||
raise FailedToCompileError([expr_node])
|
||||
|
||||
def recognize_concept(self, variable_path, concept_to_recognize, concept_variables: dict, original_source=None):
|
||||
if not isinstance(concept_to_recognize, Concept):
|
||||
concept_as_str = concept_to_recognize.get_source()
|
||||
if not concept_as_str:
|
||||
return FailedToCompileError([f"Missing concept in for {variable_path}"])
|
||||
|
||||
concept = self.evaluate_from_source(concept_as_str, return_body=True)
|
||||
else:
|
||||
concept = concept_to_recognize
|
||||
|
||||
obj_variables = set()
|
||||
var_name = self.get_new_variable(variable_path, obj_variables)
|
||||
objects = {}
|
||||
|
||||
source = f"isinstance({var_name}, Concept)"
|
||||
|
||||
if concept.get_hints().recognized_by == RECOGNIZED_BY_NAME:
|
||||
source += f" and {var_name}.name == '{concept.name}'"
|
||||
elif concept.get_hints().recognized_by == RECOGNIZED_BY_ID:
|
||||
source += f" and {var_name}.id == '{concept.id}'"
|
||||
else:
|
||||
source += f" and {var_name}.key == '{concept.key}'"
|
||||
concept_variables.update({k: v for k, v in concept.variables().items() if v is not NotInit})
|
||||
|
||||
text = source
|
||||
for var_name, var_value in concept_variables.items():
|
||||
new_var_path = variable_path.copy()
|
||||
new_var_path.append(var_name)
|
||||
obj_name, objects = self.get_object_name(var_value, objects)
|
||||
variable_condition = self.create_comparison_condition(new_var_path,
|
||||
ComparisonType.EQUALS,
|
||||
obj_name,
|
||||
var_value,
|
||||
objects,
|
||||
set())
|
||||
source += " and " + variable_condition.source
|
||||
text += " and " + variable_condition.text
|
||||
|
||||
return PythonConditionExprVisitorObj(original_source or text, source, objects, obj_variables, set())
|
||||
|
||||
def manage_concept(self, source, concept):
|
||||
if is_a_question(self.context, concept):
|
||||
return self.evaluate_concept_as_question(source, concept)
|
||||
else:
|
||||
return self.evaluate_concept(source, concept)
|
||||
|
||||
def evaluate_concept_as_question(self, original_text, concept):
|
||||
concept_var_name, objects = self.get_object_name(concept)
|
||||
source = f"evaluate_question({concept_var_name})"
|
||||
variables = get_possible_variables_from_concept(self.context, concept)
|
||||
self.concepts_to_reset.update(self.get_concepts_to_reset(concept))
|
||||
return PythonConditionExprVisitorObj(original_text, source, objects, variables, set())
|
||||
|
||||
def evaluate_concept(self, original_text, concept):
|
||||
ensure_evaluated(self.context, concept, eval_body=True)
|
||||
source, objects = self.get_object_name(concept)
|
||||
return PythonConditionExprVisitorObj(original_text, source, objects, set(), set())
|
||||
|
||||
def get_concepts_to_reset(self, concept):
|
||||
"""
|
||||
Returns all the concept that might be reset before a second evaluation
|
||||
The algo is empirical, there must be a theory to make sure that no concept is missed
|
||||
:param concept:
|
||||
:return:
|
||||
"""
|
||||
|
||||
res = set()
|
||||
|
||||
def _inner_get_concept_to_reset(_concept):
|
||||
if _concept in res: # prevent circular references
|
||||
return
|
||||
|
||||
res.add(_concept)
|
||||
assert not _concept.get_hints().use_copy
|
||||
for part_name, asts in _concept.get_compiled().items():
|
||||
if isinstance(asts, Concept):
|
||||
if is_a_question(self.context, asts):
|
||||
res.update(self.get_concepts_to_reset(asts))
|
||||
else:
|
||||
for ret_val in asts:
|
||||
body_as_list = ret_val.body.body # go through the ParserResult
|
||||
if not isinstance(body_as_list, list):
|
||||
body_as_list = [body_as_list]
|
||||
|
||||
for body in body_as_list:
|
||||
|
||||
if hasattr(body, "get_concept"): # to manage Concept and ConceptNode
|
||||
c = body.get_concept()
|
||||
if is_a_question(self.context, c):
|
||||
res.update(self.get_concepts_to_reset(c))
|
||||
|
||||
elif hasattr(body, "get_python_node"): # to manage PythonNode and SourceCodeNode like
|
||||
python_node = body.get_python_node()
|
||||
for obj in python_node.objects.values():
|
||||
if isinstance(obj, Concept) and is_a_question(self.context, obj):
|
||||
res.update(self.get_concepts_to_reset(obj))
|
||||
|
||||
_inner_get_concept_to_reset(concept)
|
||||
return res
|
||||
|
||||
def create_comparison_condition(self,
|
||||
left_path,
|
||||
op,
|
||||
right_name,
|
||||
right_value,
|
||||
objects,
|
||||
variables,
|
||||
original_source=None):
|
||||
possible_variables = variables.copy()
|
||||
var_root, var_attr = self.unpack_variable(left_path, possible_variables)
|
||||
left = self.construct_variable(var_root, var_attr)
|
||||
|
||||
if original_source is None:
|
||||
right = get_safe_str_value(right_value or right_name)
|
||||
original_source = ComparisonNode.rebuild_source(left, op, right)
|
||||
|
||||
if op == ComparisonType.EQUALS:
|
||||
if self.context.sheerka.is_sheerka(right_value):
|
||||
source = f"is_sheerka({left})"
|
||||
return PythonConditionExprVisitorObj(original_source, source, objects, possible_variables, set())
|
||||
if isinstance(right_value, Concept):
|
||||
return self.recognize_concept(left_path, right_value, {}, original_source)
|
||||
else:
|
||||
source = ComparisonNode.rebuild_source(left, op, right_name)
|
||||
return PythonConditionExprVisitorObj(original_source, source, objects, possible_variables, set())
|
||||
else:
|
||||
source = ComparisonNode.rebuild_source(left, op, right_name)
|
||||
return PythonConditionExprVisitorObj(original_source, source, objects, possible_variables, set())
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from core.concept import Concept
|
||||
from core.global_symbols import NotFound, ErrorObj
|
||||
@@ -52,7 +53,7 @@ class BaseService:
|
||||
|
||||
@dataclass()
|
||||
class FailedToCompileError(Exception, ErrorObj):
|
||||
cause: list
|
||||
cause: Union[List, Dict]
|
||||
|
||||
|
||||
@dataclass()
|
||||
|
||||
Reference in New Issue
Block a user