Fixed #125: SheerkaErrorManager

Fixed #135: Change services service priorities
Fixed #136: ErrorManager: Implement recognize_error
Fixed #137: BNFNodeParser : Error when parsing regex with sub parsers
Fixed #138: get_last_errors(): real errors sources are lost
Fixed #139: OneError return value removes the origin of the error
Fixed #140: Concept variables are not correctly handled when parsing sub expression
Fixed #143: Implement has_unknown_concepts()
This commit is contained in:
2021-10-28 14:04:41 +02:00
parent 48ab72fd9c
commit 87cab44fb8
56 changed files with 1391 additions and 1286 deletions
+7 -1
View File
@@ -2,7 +2,11 @@
push_ontology("admin")
def concept explain as get_results(id=0, depth=2) auto_eval True
def concept explain last as get_last_results(id=0, depth=2) auto_eval True
def concept explain last results as get_last_results(id=0, depth=2) auto_eval True
def concept explain x as get_results(id=x, depth=3) auto_eval True
def concept explain last errors as get_last_errors(level=0) auto_eval True
def concept explain errors as get_errors(level=0) auto_eval True
def concept explain error x as get_errors(id=x, depth=99) where isinstance(x, int) auto_eval True
def concept precedence a > precedence b as set_is_greater_than(__PRECEDENCE, a, b, 'Sya') auto_eval True
@@ -16,13 +20,15 @@ def concept debug off as set_debug(False) auto_eval True
def concept activate debug on x as set_debug_var(x) auto_eval True
def concept activate debug on x id=y as set_debug_var(x, y) auto_eval True
def concept debug x as set_debug_var(x) auto_eval True
def concept activate concept debug on x as set_debug_concept(x) auto_eval True
def concept debug var x as set_debug_var(x) auto_eval True
def concept debug variable x as set_debug_var(variable=x) auto_eval True
def concept debug method x as set_debug_var(method=x) auto_eval True
def concept debug service x as set_debug_var(service=x) auto_eval True
def concept deactivate debug on x as debug_var(x, enabled=False) where x auto_eval True
def concept deactivate debug on x as set_debug_var(x, enabled=False) where x auto_eval True
def concept deactivate concept debug on x as set_debug_concept(x, enabled=False) auto_eval True
def concept activate return values processing as set_var("sheerka.enable_process_return_values", True) auto_eval True
def concept deactivate return values processing as set_var("sheerka.enable_process_return_values", False) auto_eval True
+2 -1
View File
@@ -1,6 +1,7 @@
push_ontology("english")
def concept x is a string pre is_question() as isinstance(x, str)
def concept x is a integer pre is_question() as isinstance(x, int)
def concept x is an integer pre is_question() as isinstance(x, int)
def concept x is an int pre is_question() as isinstance(x, int)
def concept x starts with y pre is_question() where x is a string as x.startswith(y)
def concept sha256 from bnf r'[a-f0-9]{64}'
def concept sha512 from bnf r'[a-f0-9]{128}'
+9
View File
@@ -16,6 +16,15 @@ class FastCache:
def __contains__(self, item):
return self.has(item)
def __iter__(self):
yield from self.cache
def __next__(self):
return next(iter(self.cache))
def __len__(self):
return len(self.cache)
def put(self, key, value):
if len(self.cache) == self.max_size:
del self.cache[self.lru.pop(0)]
+17 -3
View File
@@ -165,6 +165,9 @@ class ParserResultConcept(Concept):
from parsers.BaseParser import BaseParser
return parser.name if isinstance(parser, BaseParser) else str(parser)
def get_error(self):
return None
class RuleEvaluationResultConcept(Concept):
"""
@@ -273,6 +276,9 @@ class FilteredConcept(Concept):
self.set_value("reason", reason)
self._hints.is_evaluated = True
def get_error(self):
return self.get_value('reason')
class ConceptAlreadyInSet(Concept, ErrorObj):
ALL_ATTRIBUTES = ["concept", "concept_set"]
@@ -312,9 +318,9 @@ class PropertyAlreadyDefined(Concept, ErrorObj):
class ConditionFailed(Concept, ErrorObj):
ALL_ATTRIBUTES = ["condition", "concept", "prop", "reason"]
ALL_ATTRIBUTES = ["condition", "concept", "prop", "reason", "args"]
def __init__(self, condition=None, concept=None, prop=None, reason=None):
def __init__(self, condition=None, concept=None, prop=None, reason=None, args=None):
Concept.__init__(self,
BuiltinConcepts.CONDITION_FAILED,
True,
@@ -325,10 +331,15 @@ class ConditionFailed(Concept, ErrorObj):
self.set_value("concept", concept)
self.set_value("prop", prop)
self.set_value("reason", reason)
self.set_value("args", args)
self._hints.is_evaluated = True
def __repr__(self):
return f"ConditionFailed(condition='{self.body}', concept='{self.concept}', prop='{self.prop}')"
text = f"ConditionFailed(condition='{self.body}', concept='{self.concept}', prop='{self.prop}'"
if self.args:
for k, v in self.args.items():
text += f", {k}={v}"
return text + ")"
class NotForMeConcept(Concept): # Not considered as an error ?
@@ -348,6 +359,9 @@ class NotForMeConcept(Concept): # Not considered as an error ?
def __repr__(self):
return f"NotForMeConcept(source={self.body}, reason={self.get_value('reason')})"
def get_error(self):
return self.get_value('reason')
class ExplanationConcept(Concept):
ALL_ATTRIBUTES = ["digest", "command", "title", "instructions", "execution_result"]
+2 -2
View File
@@ -930,11 +930,11 @@ def get_inner_body(context, concept):
return concept.body
def get_possible_variables_from_concept(context, concept):
def get_possible_parameters_from_concept(context, concept):
"""
Given concept definition,
gives the variables of the concept that can be considered as a parameter in another function
>>> gpvfc = get_possible_variables_from_concept
>>> gpvfc = get_possible_parameters_from_concept
>>> assert gpvfc(Concept("a plus b").def_var("a").def_var("b")) == {"a", "b"}
>>> assert gpvfc(Concept("twenties", definition="twenty (one|two)=n").def_var("n")) == set()
:param context:
+26
View File
@@ -70,6 +70,12 @@ class ErrorObj:
pass
class SheerkaException(Exception, ErrorObj):
def __init__(self, value):
super().__init__(value)
self.value = value
CURRENT_OBJ = "__obj"
@@ -100,3 +106,23 @@ INIT_AST_QUESTION_PARSERS = ["Expression"]
DEFAULT_EVALUATORS = ["Python", "Concept", "LexerNode", "Expression", "ValidateConcept", "ResolveAmbiguity"]
OBJECTS_COUNTER_KEY = "__#OBJECTS_COUNTER_KEY#__"
class ErrorItem:
""""
Description of an error
"""
def __init__(self, identifier: int, level: int, root, error: object, children=None):
self.id = identifier
self.level = level
self.root = root
self.error = error
self.children = children
def __repr__(self):
return f"ErrorItem(id={self.id}, level={self.level}, error={self.error}, source={self.source})"
@property
def source(self):
return self.root.who if hasattr(self.root, "who") else str(self.root)
+10 -14
View File
@@ -102,7 +102,8 @@ class Sheerka(Concept):
self.services = {} # sheerka plugins
self.builtin_cache = {} # cache for builtin concepts
self.builtin_cache = {} # cache for builtin concepts <concept name, Python class>
self.builtin_cache_by_class_name = {} # <Python Class's name, concept name>
self.parsers = {} # cache for builtin parsers
self.evaluators = [] # cache for builtin evaluators
@@ -203,7 +204,7 @@ class Sheerka(Concept):
self.bind_service_method(self.name, self.objvalue, False)
self.om = SheerkaOntologyManager(self, root_folder, self.cache_only)
self.builtin_cache = self.get_builtins_classes_as_dict()
self.builtin_cache, self.builtin_cache_by_class_name = self.get_builtins_classes_as_dict()
self.initialize_caching()
self.get_builtin_parsers()
@@ -849,7 +850,7 @@ class Sheerka(Concept):
if isinstance(b, tuple):
for item in b:
item_key = item.key if isinstance(item, Concept) else str(item)
if a.key == item_key.key:
if a.key == item_key:
return True
return False
@@ -903,12 +904,14 @@ class Sheerka(Concept):
@staticmethod
def get_builtins_classes_as_dict():
res = {}
by_concept_name = {}
by_class_name = {}
for c in core.utils.get_classes("core.builtin_concepts"):
if issubclass(c, Concept) and c != Concept:
res[c()._metadata.key] = c
return res
concept = c()
by_concept_name[concept._metadata.key] = c
by_class_name[c.__name__] = concept._metadata.name
return by_concept_name, by_class_name
@staticmethod
def init_logging(debug, loggers):
@@ -993,13 +996,6 @@ class Sheerka(Concept):
return concept
@staticmethod
def deepdiff(a, b):
from deepdiff import DeepDiff
ddiff = DeepDiff(a, b, ignore_order=True)
print(ddiff)
return ddiff
def to_profile():
sheerka = Sheerka()
@@ -1184,14 +1184,13 @@ class SheerkaConceptManager(BaseService):
"""
Go thru all the declared regular expressions and try to see if there is a match
:param expr:
:param pos:
:param pos: starting pos (try to match at this starting position)
:return:
"""
result = []
for compiled_regex, concept_ids in self.compiled_concepts_by_regex:
if compiled_regex.match(expr, pos):
result.extend([self.sheerka.get_by_id(concept_id) for concept_id in concept_ids])
return result
def get_concepts_bnf_definitions(self):
@@ -417,8 +417,16 @@ class ListDebugLogger(BaseDebugLogger):
if not self.should_i_log_concept(concept.id):
return
concept_id = f"{concept.id}{text}" if text else f"{concept.id}"
self.items.append(ListDebugLogger.DebugItem(ListDebugLogger.ITEM_TYPE_CONCEPT, concept_id, False, kwargs))
raw = kwargs.pop('raw', None)
str_vars = raw if raw else pp.pformat(kwargs) if kwargs else ""
text = " - " + text if text is not None else ""
colon = ": " if str_vars else ""
str_text = f"..concept#{concept.id}{text}{colon}"
self.items.append(ListDebugLogger.DebugItem(ListDebugLogger.ITEM_TYPE_CONCEPT,
str_text + str_vars,
False,
kwargs))
class TeeDebugLogger(BaseDebugLogger):
+329 -36
View File
@@ -1,78 +1,121 @@
import core.builtin_helpers
import core.utils
from cache.FastCache import FastCache
from core.builtin_concepts_ids import BuiltinConcepts, BuiltinErrors
from core.concept import Concept
from core.global_symbols import ErrorObj, NotInit
from core.global_symbols import EVENT_USER_INPUT_EVALUATED, ErrorItem, ErrorObj, NotInit, SheerkaException
from core.sheerka.services.sheerka_service import BaseService
from core.tokenizer import TokenKind, Tokenizer
MAX_EXECUTION_HISTORY = 100
CURRENT_ERRORS_EVENT_ID = "current_errors_event_id"
CURRENT_ERRORS_EVENT_MESSAGE = "current_errors_event_message"
LAST_DETECTED_ERRORS_EVENT_ID = "last_detected_errors_event_id"
LAST_DETECTED_ERRORS_EVENT_MESSAGE = "last_detected_errors_event_message"
class ErrorItem:
def __init__(self, level: int, parent, error: object):
self.level = level
self.parent = parent
self.error = error
LAST_UNKNOWN_CONCEPTS = "__last_unknown_concepts"
class SheerkaErrorManager(BaseService):
NAME = "Error"
def __init__(self, sheerka):
super().__init__(sheerka, order=1)
super().__init__(sheerka, order=6)
self.last_detected_errors_event_id = None # updated every time an user input fails
self.last_detected_errors_event_message = None # updated every time an user input fails
self.current_errors_event_id = None # updated only when get_last_errors() is called
self.current_errors_event_message = None # updated only when get_last_errors() is called
self.last_return_values = None
self.state_vars = [CURRENT_ERRORS_EVENT_ID,
CURRENT_ERRORS_EVENT_MESSAGE,
LAST_DETECTED_ERRORS_EVENT_ID,
LAST_DETECTED_ERRORS_EVENT_MESSAGE]
self.executions_contexts_cache = FastCache(MAX_EXECUTION_HISTORY)
self.get_unknown_names_chain = [self._get_unknown_from_unknown_concept,
self._get_unknown_from_name_error,
self._get_unknown_from_undefined_symbol]
def initialize(self):
self.sheerka.bind_service_method(self.NAME, self.get_errors, False)
self.sheerka.bind_service_method(self.NAME, self.get_obj_errors, False, visible=False)
self.sheerka.bind_service_method(self.NAME, self.has_error, False)
self.sheerka.bind_service_method(self.NAME, self.get_error_cause, False)
self.sheerka.bind_service_method(self.NAME, self.get_errors, False)
self.sheerka.bind_service_method(self.NAME, self.get_last_errors, False)
self.sheerka.bind_service_method(self.NAME, self.recognize_error, False)
self.sheerka.bind_service_method(self.NAME, self.has_unknown_concepts, False)
def get_errors(self, context, obj, **kwargs):
self.sheerka.subscribe(EVENT_USER_INPUT_EVALUATED, self.on_user_input_evaluated)
def initialize_deferred(self, context, is_first_time):
self.restore_values(*self.state_vars)
def reset_state(self):
self.last_detected_errors_event_id = None
self.last_detected_errors_event_message = None
self.current_errors_event_id = None
self.current_errors_event_message = None
self.last_return_values = None
def is_error(self, 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
if self.sheerka.isinstance(obj, BuiltinConcepts.NOT_FOR_ME):
return True # In this situation, we want NOT_FOR_ME to appear in the list of errors
return False
def get_obj_errors(self, context, obj, predicate=None, **kwargs):
"""
Browse obj, looking for error
Quickly retrieve all errors from obj (which can be a list)
All errors are flatten. No hierarchy
:param context:
:param obj:
:param predicate: if defined, filtering function
: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):
def inner_get_errors(_obj):
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])
return core.utils.flatten([inner_get_errors(o) for o in _obj])
if self.is_error(_obj):
if hasattr(_obj, "get_error"):
return [_obj] + inner_get_errors(_obj.get_error())
elif isinstance(_obj, Concept) and _obj.body not in (NotInit, None):
return [_obj] + inner_get_errors(_obj.body)
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]
return [_obj]
if self.sheerka.isinstance(_obj, BuiltinConcepts.FILTERED):
return inner_get_errors(_obj.reason, level, parent)
return inner_get_errors(_obj.reason)
if isinstance(_obj, Concept) and _obj.body != NotInit:
return inner_get_errors(_obj.body, level, parent)
return inner_get_errors(_obj.body)
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)
errors = inner_get_errors(obj)
def has_error(self, context, obj, **kwargs):
errors = self.get_errors(context, obj, **kwargs)
predicate = self.enhance_error_predicate(predicate)
return self.sheerka.filter_objects(context, errors, predicate=predicate, **kwargs)
def has_error(self, context, obj, predicate=None, **kwargs):
errors = self.get_obj_errors(context, obj, predicate, **kwargs)
return len(errors) > 0
def get_error_cause(self, obj):
@@ -87,3 +130,253 @@ class SheerkaErrorManager(BaseService):
return res[0]
else:
return res
def get_errors_items(self, returns_values):
"""
Returns a list or ErrorItems
They are numbered and have hierarchy
:param returns_values:
:return:
"""
identifier = 0
def inner_get_errors_items(obj, level, root):
nonlocal identifier
if self.is_error(obj) or hasattr(obj, "get_error"):
error_item = ErrorItem(identifier, level, root, obj)
identifier += 1
if hasattr(obj, "get_error"):
error_item.children = list(inner_get_errors_items(obj.get_error(), level + 1, root))
elif isinstance(obj, Concept) and obj.body not in (NotInit, None):
error_item.children = list(inner_get_errors_items(obj.body, level + 1, root))
else:
error_item.children = []
yield error_item
elif isinstance(obj, Concept) and obj.body != NotInit:
if self.sheerka.isinstance(obj, BuiltinConcepts.RETURN_VALUE) and not obj.status:
root_to_use = obj
else:
root_to_use = root
yield from inner_get_errors_items(obj.body, level, root_to_use)
elif hasattr(obj, "__iter__") and not isinstance(obj, str):
for item in obj:
yield from inner_get_errors_items(item, level, root)
for ret_val in [r for r in returns_values if not r.status]:
yield from inner_get_errors_items(ret_val.body, 0, ret_val)
def get_all_error_items(self, return_values):
def _as_list(error_items):
for error_item in error_items:
yield error_item
yield from _as_list(error_item.children)
return _as_list(self.get_errors_items(return_values))
def get_return_values_in_error(self):
if self.current_errors_event_id is None:
error = self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"query": "get_last_error"})
raise SheerkaException(error)
# search in history
if self.current_errors_event_id in self.executions_contexts_cache:
execution_result = self.executions_contexts_cache.get(self.current_errors_event_id)
else:
try:
execution_result = self.sheerka.om.current_sdp().load_result(self.current_errors_event_id)
self.executions_contexts_cache.put(self.current_errors_event_id, execution_result)
except FileNotFoundError as ex:
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"digest": self.current_errors_event_id})
return [ret for ret in execution_result.values["return_values"] if not ret.status]
def get_errors(self, context, predicate=None, **kwargs):
"""
Returns
:param context:
:param predicate:
:param kwargs:
:return:
"""
try:
in_error = self.get_return_values_in_error()
error_items = self.get_all_error_items(in_error)
depth = kwargs.pop("depth", None)
depth = kwargs.pop("recursion_depth", depth)
filtered = self.filter_error_items(context, list(error_items), predicate, **kwargs)
explanation = self.sheerka.new(BuiltinConcepts.EXPLANATION,
digest=self.current_errors_event_id,
command=self.current_errors_event_message,
body=filtered)
# add format instructions if applicable
if depth is not None:
explanation.set_format_instr(recursion_depth=depth, recurse_on="children")
return explanation
except SheerkaException as ex:
return ex.value
def get_last_errors(self, context, predicate=None, **kwargs):
"""
:param context:
:param predicate:
:param kwargs:
:return:
"""
if self.last_detected_errors_event_id is None:
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"query": "get_last_error"})
self.current_errors_event_id = self.last_detected_errors_event_id
self.current_errors_event_message = self.last_detected_errors_event_message
self.store_values(context, CURRENT_ERRORS_EVENT_ID, CURRENT_ERRORS_EVENT_MESSAGE)
return self.get_errors(context, predicate, **kwargs)
def recognize_error(self, context, predicate=None, **kwargs):
"""
Browse the last errors to see if the given error can be recognized
:param context:
:param predicate:
:param kwargs:
:return:
"""
if self.last_return_values is None:
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"query": "get_last_error"})
error_items = self.get_all_error_items(self.last_return_values)
predicate = self.enhance_error_predicate(predicate)
filtered = self.filter_error_items(context, list(error_items), predicate, **kwargs)
if len(filtered) == 0:
return False
return True
def has_unknown_concepts(self, context):
"""
Browse the last errors looking for unknown concepts
:param context:
:return:
"""
if self.last_return_values is None:
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"query": "get_last_error"})
errors = self.get_obj_errors(context, self.last_return_values)
# use all the known ways to detect unrecognized concepts
unknown_concepts = set()
for get_unknown_names in self.get_unknown_names_chain:
items = get_unknown_names(context, errors)
if items:
unknown_concepts.update(items)
if len(unknown_concepts) > 0:
# use all the ways to reduce the list ?
errors_names = self._remove_recognized(unknown_concepts)
if len(errors_names) == 1:
self.sheerka.set_in_memory(context, LAST_UNKNOWN_CONCEPTS, next(iter(errors_names)))
else:
self.sheerka.set_in_memory(context, LAST_UNKNOWN_CONCEPTS, errors_names)
return True
def _get_unknown_from_unknown_concept(self, context, errors):
from_unknown = self.sheerka.filter_objects(context, errors, __type=BuiltinConcepts.UNKNOWN_CONCEPT)
return [e.body for e in from_unknown] if from_unknown else None
def _get_unknown_from_name_error(self, context, errors):
from_name_error = self.sheerka.filter_objects(context, errors, __type="NameError")
if not from_name_error:
return None
return [list(Tokenizer(e.args[0], yield_eof=False))[2].strip_quote for e in from_name_error]
def _get_unknown_from_undefined_symbol(self, context, errors):
from_undefined_error = self.sheerka.filter_objects(context, errors, __type="UndefinedSymbolError")
return [e.symbol for e in from_undefined_error] if from_undefined_error else None
def _remove_recognized(self, errors_names):
"""
parse error names a split it if some parts are known concept
ex 'dress' is a known concept
'red dress' will not be recognized, but only 'red' should be in error
:param errors_names:
:return:
"""
# remove parts that are known
res = set()
unrecognized = []
for error_name in [e.strip() for e in errors_names]:
unrecognized.clear()
for token in Tokenizer(error_name, yield_eof=False):
if (token.type == TokenKind.WHITESPACE and
len(unrecognized) > 0 and
unrecognized[-1].type != TokenKind.WHITESPACE):
unrecognized.append(token)
continue
elif self.sheerka.fast_resolve(token.value, return_new=False):
unrecognized_name = core.utils.get_text_from_tokens(unrecognized).strip()
if unrecognized_name:
res.add(unrecognized_name)
unrecognized.clear()
else:
unrecognized.append(token)
if unrecognized:
unrecognized_name = core.utils.get_text_from_tokens(unrecognized).strip()
if unrecognized_name:
res.add(unrecognized_name)
return res
def on_user_input_evaluated(self, execution_context):
"""
Track user input result in error
:param execution_context:
:return:
"""
self.last_return_values = [ret for ret in execution_context.values["return_values"] if not ret.status]
if self.last_return_values:
self.last_detected_errors_event_id = execution_context.event.get_digest()
self.last_detected_errors_event_message = execution_context.event.message
self.store_values(execution_context, LAST_DETECTED_ERRORS_EVENT_ID, LAST_DETECTED_ERRORS_EVENT_MESSAGE)
self.executions_contexts_cache.put(execution_context.event.get_digest(), execution_context)
def filter_error_items(self, context, error_items, predicate=None, **kwargs):
error_items_args = {}
for key in ["id", "level", "source"]:
if key in kwargs:
error_items_args[key] = kwargs.pop(key)
filtered = self.sheerka.filter_objects(context, list(error_items), **error_items_args)
return self.sheerka.filter_objects(context, list(filtered), lambda x: x.error, predicate, **kwargs)
def enhance_error_predicate(self, predicate):
if isinstance(predicate, type):
if issubclass(predicate, Concept):
c = predicate()
predicate = f"get_type(self) == {core.utils.get_safe_str_value(c.name)}"
else:
predicate = f"get_type(self) == {core.utils.get_safe_str_value(predicate.__name__)}"
elif isinstance(predicate, str) and " " not in predicate.strip():
if predicate in self.sheerka.builtin_cache_by_class_name:
concept_name = self.sheerka.builtin_cache_by_class_name[predicate]
predicate = f"get_type(self) == {core.utils.get_safe_str_value(concept_name)}"
else:
predicate = f"get_type(self) == {core.utils.get_safe_str_value(predicate)}"
return predicate
@@ -1,7 +1,7 @@
from dataclasses import dataclass
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import ensure_bnf, ensure_concept, expect_one, get_parsed_concept, is_only_successful, \
from core.builtin_helpers import 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
@@ -15,6 +15,7 @@ from core.utils import unstr_concept
from parsers.BaseExpressionParser import TrueifyVisitor
from parsers.BaseNodeParser import ConceptNode
from sheerkapython.ExprToConditions import ExprToConditionsVisitor
from sheerkapython.python_wrapper import get_possible_variables_from_concept
CONCEPT_EVALUATION_STEPS = [
BuiltinConcepts.BEFORE_EVALUATION,
@@ -174,7 +175,8 @@ class SheerkaEvaluateConcept(BaseService):
parser_input = context.sheerka.services[SheerkaExecute.NAME].get_parser_input(trueified_where)
parsed = ExpressionParser(auto_compile=False).parse(context, parser_input)
expr_to_cond_python_visitor = ExprToConditionsVisitor(context)
possible_variables = get_possible_variables_from_concept(context, concept)
expr_to_cond_python_visitor = ExprToConditionsVisitor(context, known_variables=possible_variables)
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:
@@ -392,7 +394,8 @@ class SheerkaEvaluateConcept(BaseService):
body=where_clause_def.clause,
concept=where_clause_def.concept,
prop=where_clause_def.prop,
reason=reason)
reason=reason,
args=where_clause_def.concept.variables())
def manage_infinite_recursion(self, context):
"""
@@ -434,15 +437,7 @@ class SheerkaEvaluateConcept(BaseService):
# 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():
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]
else:
possible_variables = None
possible_variables = get_possible_variables_from_concept(context, concept)
for part_key in AllConceptParts:
if part_key in concept.get_compiled():
continue
+8 -36
View File
@@ -44,11 +44,17 @@ class ParserInput:
if len(tokens) == 0:
self.tokens = [Token(TokenKind.EOF, "", 0, 1, 1)]
elif (last_token := tokens[-1]).type != TokenKind.EOF:
if isinstance(last_token.value, str):
offset = len(last_token.value)
else:
# KSI 2021-10-23
# Not really true. So it will be to really handled later
offset = 1
self.tokens = tokens + [Token(TokenKind.EOF,
"",
last_token.index + 1,
last_token.index + offset,
last_token.line,
last_token.column + 1)]
last_token.column + offset)]
else:
self.tokens = tokens
@@ -259,7 +265,6 @@ class SheerkaExecute(BaseService):
self.sheerka.bind_service_method(self.NAME, self.execute, True, visible=False)
self.sheerka.bind_service_method(self.NAME, self.execute_rules, True, visible=False)
self.sheerka.bind_service_method(self.NAME, self.parse_unrecognized, False, visible=False)
self.sheerka.bind_service_method(self.NAME, self.parse_function, False, visible=False)
self.sheerka.bind_service_method(self.NAME, self.parse_python, False, visible=False)
self.sheerka.bind_service_method(self.NAME, self.parse_expression, False, visible=False)
self.sheerka.bind_service_method(self.NAME, self.clear_parser_cache, True)
@@ -893,39 +898,6 @@ class SheerkaExecute(BaseService):
sub_context.add_values(return_values=res)
return res
def parse_function(self, context, source, tokens=None, start=0):
"""
Helper function that parses what is supposed to be a function
:param context:
:param source:
:param tokens:
:param start: start index for the source code node
:return:
"""
from parsers.BaseNodeParser import SourceCodeWithConceptNode
sheerka = context.sheerka
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]
res = parser.parse(sub_context, sheerka_execution.get_parser_input(source, tokens))
if not isinstance(res, list):
res = [res]
for r in [r for r in res if sheerka.isinstance(r.body, BuiltinConcepts.PARSER_RESULT)]:
r.body.body.start += start
r.body.body.end += start
if isinstance(r.body.body, SourceCodeWithConceptNode):
for n in [r.body.body.first, r.body.body.last] + r.body.body.nodes:
n.start += start
n.end += start
return res
def parse_python(self, context, source, desc=None):
"""
Helper function that parses what is known to be Python source code
@@ -4,7 +4,7 @@ from core.global_symbols import NotInit
from core.sheerka.services.sheerka_service import BaseService
from sdp.sheerkaDataProvider import Event
hist = namedtuple("HistoryTest", "text status") # tests purposes only
hist = namedtuple("HistoryTest", "message status") # tests purposes only
class History:
@@ -37,7 +37,7 @@ class History:
return True
if isinstance(other, hist):
return self.event.message == other.text and self.status == other.status
return self.event.message == other.message and self.status == other.status
if not isinstance(other, History):
return False
+26 -2
View File
@@ -44,13 +44,14 @@ class SheerkaMemory(BaseService):
self.sheerka.bind_service_method(self.NAME, self.add_to_short_term_memory, True, visible=False)
self.sheerka.bind_service_method(self.NAME, self.remove_context, True, as_name="clear_short_term_memory",
visible=False)
self.sheerka.bind_service_method(self.NAME, self.add_to_memory, True)
self.sheerka.bind_service_method(self.NAME, self.add_many_to_short_term_memory, True, visible=False)
self.sheerka.bind_service_method(self.NAME, self.get_from_memory, False)
self.sheerka.bind_service_method(self.NAME, self.get_last_from_memory, False)
self.sheerka.bind_service_method(self.NAME, self.register_object, True, visible=False)
self.sheerka.bind_service_method(self.NAME, self.unregister_object, True, visible=False)
self.sheerka.bind_service_method(self.NAME, self.commit_registered_objects, True, visible=False)
self.sheerka.bind_service_method(self.NAME, self.add_to_memory, True)
self.sheerka.bind_service_method(self.NAME, self.set_in_memory, True)
self.sheerka.bind_service_method(self.NAME, self.memory, False)
self.sheerka.bind_service_method(self.NAME, self.mem, False)
@@ -123,7 +124,8 @@ class SheerkaMemory(BaseService):
def add_to_memory(self, context, key, concept):
"""
Adds an object to memory
Adds an object to memory.
Create a list if the entry already exists
:param context:
:param key:
:param concept:
@@ -157,6 +159,28 @@ class SheerkaMemory(BaseService):
context.event.date.timestamp(),
concept))
def set_in_memory(self, context, key, value):
"""
Add an element in memory
Replace the old one if the entry already exists
:param context:
:param key:
:param value:
:return:
"""
last = self.sheerka.om.get(SheerkaMemory.OBJECTS_ENTRY, key)
if last is NotFound:
self.sheerka.om.put(SheerkaMemory.OBJECTS_ENTRY, key, MemoryObject(context.event.get_digest(),
context.event.date.timestamp(),
value))
return
# replace with the new one
self.sheerka.om.delete(SheerkaMemory.OBJECTS_ENTRY, key, last)
self.sheerka.om.put(SheerkaMemory.OBJECTS_ENTRY, key, MemoryObject(context.event.get_digest(),
context.event.date.timestamp(),
value))
def get_from_memory(self, context, key):
""""
"""
+1 -1
View File
@@ -33,7 +33,7 @@ class SheerkaOut(BaseService):
if valid_rules:
if len(valid_rules) > 1:
# TODO manage when too many rules
print("TODO: TOO MANY RULES !!!!!")
print("TODO: TOO MANY RULES !!!!!", [r.id for r in valid_rules])
pass
rule = valid_rules[0]
@@ -20,7 +20,7 @@ class SheerkaQueryManager(BaseService):
MAPPING_PREFIX = "__xxx__map__xx__"
def __init__(self, sheerka):
super().__init__(sheerka, order=16)
super().__init__(sheerka, order=5)
self.queries = FastCache()
self.conditions = FastCache()
self.lexer = Lexer()
@@ -31,10 +31,9 @@ class SheerkaQueryManager(BaseService):
self.sheerka.bind_service_method(self.NAME, self.select_objects, False)
self.sheerka.bind_service_method(self.NAME, self.collect_attributes, False)
self.sheerka.bind_service_method(self.NAME, self.filter_objects, False)
self.sheerka.bind_service_method(self.NAME, self.where_on_objects, False, as_name="pipe_where")
self.sheerka.bind_service_method(self.NAME, self.select_objects, False, as_name="pipe_select")
self.sheerka.bind_service_method(self.NAME, self.collect_attributes, False, as_name="pipe_props")
self.sheerka.bind_service_method(self.NAME, self.where_on_objects, False, as_name="pipe_where")
self.sheerka.register_debug_vars(SheerkaQueryManager.NAME, "filter_objects", "query")
@@ -62,6 +61,15 @@ class SheerkaQueryManager(BaseService):
local_namespace[current_variable_name] = v
if k == "__type":
if isinstance(v, type):
if issubclass(v, Concept):
c = v()
local_namespace[current_variable_name] = c.name
else:
local_namespace[current_variable_name] = v.__name__
elif v in self.sheerka.builtin_cache_by_class_name:
local_namespace[current_variable_name] = self.sheerka.builtin_cache_by_class_name[v]
conditions.append(f"get_type({self_ident}) == {current_variable_name}")
elif k in ("__self", "_"):
@@ -97,7 +105,7 @@ class SheerkaQueryManager(BaseService):
:param mapping: mapping to execute on each object before applying the predicate (lambda obj:obj)
:param predicate:
:param kwargs:
:return:
:return: empty list when nothing is found
"""
debugger = context.get_debugger(SheerkaQueryManager.NAME, "filter_objects")
@@ -1,11 +1,11 @@
import ast
import os
from cache.Cache import Cache
from cache.FastCache import FastCache
from core.builtin_concepts import BuiltinConcepts
from core.global_symbols import EVENT_USER_INPUT_EVALUATED, EVENT_CONCEPT_CREATED, NotFound, EVENT_CONCEPT_MODIFIED, \
EVENT_CONCEPT_DELETED, EVENT_CONCEPT_PRECEDENCE_MODIFIED, EVENT_RULE_CREATED, EVENT_RULE_DELETED, \
EVENT_RULE_PRECEDENCE_MODIFIED, SHEERKA_BACKUP_FOLDER, SHEERKA_BACKUP_FILE
from core.global_symbols import EVENT_CONCEPT_CREATED, EVENT_CONCEPT_DELETED, EVENT_CONCEPT_MODIFIED, \
EVENT_CONCEPT_PRECEDENCE_MODIFIED, EVENT_RULE_CREATED, EVENT_RULE_DELETED, EVENT_RULE_PRECEDENCE_MODIFIED, \
EVENT_USER_INPUT_EVALUATED, NotFound, SHEERKA_BACKUP_FILE, SHEERKA_BACKUP_FOLDER
from core.sheerka.services.sheerka_service import BaseService
from core.utils import CONSOLE_COLORS_MAP as CCM
from core.utils import as_bag
@@ -22,7 +22,7 @@ class SheerkaResultManager(BaseService):
def __init__(self, sheerka, page_size=30):
super().__init__(sheerka)
self.page_size = page_size
self.executions_contexts_cache = Cache(MAX_EXECUTION_HISTORY)
self.executions_contexts_cache = FastCache(MAX_EXECUTION_HISTORY)
self.last_execution = None
self.last_created_concept = None
self.last_created_concept_id = None
@@ -42,7 +42,7 @@ class SheerkaResultManager(BaseService):
self.sheerka.bind_service_method(self.NAME, self.get_last_error, False, as_name="last_err")
self.sheerka.subscribe(EVENT_USER_INPUT_EVALUATED, self.user_input_evaluated)
self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.new_concept_created)
self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.on_new_concept_created)
self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.on_global_state_is_modified)
self.sheerka.subscribe(EVENT_CONCEPT_MODIFIED, self.on_global_state_is_modified)
@@ -331,7 +331,7 @@ class SheerkaResultManager(BaseService):
return self.last_errors
def new_concept_created(self, context, concept):
def on_new_concept_created(self, context, concept):
"""
Subscriber (callback) when a new concept is created
:param context:
+40 -27
View File
@@ -13,8 +13,8 @@ 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 Token, TokenKind
from parsers.BaseExpressionParser import AndNode, ComparisonNode, ExpressionVisitor, \
FunctionNodeOld, NameExprNode, NotNode, VariableNode
from parsers.BaseExpressionParser import AndNode, ComparisonNode, ExpressionVisitor, NameExprNode, \
NotNode, VariableNode
from parsers.BaseNodeParser import ConceptNode
from parsers.FormatRuleActionParser import FormatRuleActionParser
from parsers.LogicalOperatorParser import LogicalOperatorParser
@@ -271,9 +271,12 @@ class SheerkaRuleManager(BaseService):
"""
Remove a rule
"""
original_rule = rule
rule = self.resolve_rule(context, rule)
if rule is None:
return
sheerka = context.sheerka
err = sheerka.new(BuiltinConcepts.NOT_FOUND, body={"rule": original_rule})
return sheerka.ret(self.NAME, False, err)
# rule will be deleted. publish the event first, as the rule may not be available after
self.sheerka.publish(context, EVENT_RULE_DELETED, rule)
@@ -292,6 +295,26 @@ class SheerkaRuleManager(BaseService):
def init_builtin_rules(self, context):
# self.sheerka.init_log.debug("Initializing default rules")
rules_by_names = {}
def is_less_than(r1, r2):
self.sheerka.set_is_less_than(context,
BuiltinConcepts.PRECEDENCE,
rules_by_names[r1], rules_by_names[r2],
RULE_COMPARISON_CONTEXT)
def is_greater_than(r1, r2):
self.sheerka.set_is_greater_than(context,
BuiltinConcepts.PRECEDENCE,
rules_by_names[r1], rules_by_names[r2],
RULE_COMPARISON_CONTEXT)
def is_greatest(r1):
self.sheerka.set_is_greatest(context,
BuiltinConcepts.PRECEDENCE,
rules_by_names[r1],
RULE_COMPARISON_CONTEXT)
rules = [
# index=[0] in code, id=1 Rule #2 in debug
Rule("print", "Print return values", "__rets", "list(__rets)"),
@@ -336,25 +359,24 @@ class SheerkaRuleManager(BaseService):
"isinstance(__ret_container, BuiltinConcepts.MULTIPLE_SUCCESS)",
"list(__ret_container.body)"),
# # index=[9] in code, id=10 in debug
# Rule("print", "Display simple result when only one success",
# "len(__rets)==1 and __rets[0].status == True",
# "{__rets[0].body}"),
# index=[9] in code, id=10 in debug
Rule("print", "Print ErrorItem",
"isinstance(__obj, ErrorItem)",
"[{id:3}] {__tab}green(source) red(error)"),
]
for r in rules:
self.create_new_rule(context, r)
rules_by_names[r.name] = r
self.sheerka.set_is_less_than(context, BuiltinConcepts.PRECEDENCE, rules[1], rules[2], RULE_COMPARISON_CONTEXT)
self.sheerka.set_is_less_than(context, BuiltinConcepts.PRECEDENCE, rules[1], rules[3], RULE_COMPARISON_CONTEXT)
self.sheerka.set_is_less_than(context, BuiltinConcepts.PRECEDENCE, rules[1], rules[5], RULE_COMPARISON_CONTEXT)
self.sheerka.set_is_less_than(context, BuiltinConcepts.PRECEDENCE, rules[1], rules[6], RULE_COMPARISON_CONTEXT)
self.sheerka.set_is_less_than(context, BuiltinConcepts.PRECEDENCE, rules[1], rules[8], RULE_COMPARISON_CONTEXT)
self.sheerka.set_is_greater_than(context, BuiltinConcepts.PRECEDENCE, rules[7], rules[6],
RULE_COMPARISON_CONTEXT)
self.sheerka.set_is_greater_than(context, BuiltinConcepts.PRECEDENCE, rules[7], rules[5],
RULE_COMPARISON_CONTEXT)
self.sheerka.set_is_greatest(context, BuiltinConcepts.PRECEDENCE, rules[0], RULE_COMPARISON_CONTEXT)
is_less_than("Print ReturnValue", "Failed ReturnValue in red")
is_less_than("Print ReturnValue", "List explanations")
is_less_than("Print ReturnValue", "Display formatted list")
is_less_than("Print ReturnValue", "Display formatted dict")
is_less_than("Print ReturnValue", "Display multiple success")
is_greater_than("Display multiple outputs", "Display formatted dict")
is_greater_than("Display multiple outputs", "Display formatted list")
is_greatest("Print return values")
def get_rule_by_id(self, rule_id):
"""
@@ -585,7 +607,7 @@ class GetConditionExprVisitor(ExpressionVisitor):
if return_body:
if not res.status:
python_eval_error = self.context.sheerka.get_errors(self.context, res, __type="PythonEvalError")
python_eval_error = self.context.sheerka.get_obj_errors(self.context, res, __type="PythonEvalError")
if python_eval_error and isinstance(python_eval_error[0].error, NameError):
raise UnknownVariableError(python_eval_error[0].source)
raise FailedToCompileError(res.body)
@@ -706,15 +728,6 @@ class ReteConditionExprVisitor(GetConditionExprVisitor):
else:
raise FailedToCompileError([expr_node])
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}'"])
return self.recognize_concept(expr_node.parameters[0].value.unpack(),
expr_node.parameters[1].value,
{})
def visit_NotNode(self, expr_node: NotNode):
def get_sub_conditions(conditions_):
-21
View File
@@ -876,27 +876,6 @@ def escape_str(x):
return x
def new_array(size):
res = []
for _ in range(size):
res.append(0)
return res
class NextIdManager:
"""
solely return the next integer
"""
def __init__(self):
self.id = -1
def get_next_id(self):
self.id += 1
return self.id
def compute_hash(obj):
try:
if isinstance(obj, (list, tuple)):
+1 -1
View File
@@ -54,5 +54,5 @@ class OneErrorEvaluator(AllReturnValuesEvaluator):
context.log(f"{self.return_value_in_error}", who=self)
sheerka = context.sheerka
to_return = self.return_value_in_error.value if self.return_value_in_error else self.return_value_filtered.body
to_return = self.return_value_in_error if self.return_value_in_error else self.return_value_filtered
return sheerka.ret(self.name, False, to_return, parents=self.eaten.copy())
+1 -73
View File
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from itertools import product
from typing import List, Union
from typing import List
from core.builtin_concepts_ids import BuiltinConcepts
from core.sheerka.services.SheerkaExecute import ParserInput
@@ -572,72 +572,6 @@ class FunctionNode(ExprNode):
self.parameters.clone())
@dataclass()
class FunctionParameter:
"""
class the represent result of the parameter parsing
"""
value: NameExprNode # value parsed
separator: NameExprNode = None # holds the value and the position of the separator
def add_sep(self, start, end, tokens):
self.separator = NameExprNode(start, end, tokens)
def value_to_unrecognized(self):
return UnrecognizedTokensNode(self.value.start, self.value.end, self.value.tokens).fix_source()
def separator_to_unrecognized(self):
if self.separator is None:
return None
return UnrecognizedTokensNode(self.separator.start, self.separator.end, self.separator.tokens).fix_source()
def clone(self):
return FunctionParameter(self.value.clone(), self.separator.clone())
class FunctionNodeOld(ExprNode):
def __init__(self, start, end, tokens,
first: NameExprNode, last: NameExprNode, parameters: Union[None, List[FunctionParameter]]):
super().__init__(start, end, tokens)
self.first = first
self.last = last
self.parameters = parameters
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, FunctionNodeOld):
return False
return (self.first == other.first and
self.last == other.last and
self.parameters == other.parameters)
def __hash__(self):
return hash((self.first, self.last, self.parameters))
def __repr__(self):
return f"FunctionNodeOld(start={self.start}, end={self.end}, {self.first!r} {self.last} {self.parameters!r})"
def __str__(self):
return f"{self.first} {self.parameters} {self.last}"
def clone(self):
if self.parameters is not None:
parameters = [p.clone for p in self.parameters]
else:
parameters = None
return FunctionNodeOld(self.start,
self.end,
self.tokens.copy(),
self.first.clone(),
self.last.clone(),
parameters)
@dataclass()
class Comprehension:
target: ExprNode
@@ -1013,12 +947,6 @@ class IsAQuestionVisitor(ExpressionVisitor):
return True
return None
def visit_FunctionNodeOld(self, expr_node: FunctionNodeOld):
if tokens_are_matching(expr_node.tokens, is_question_tokens) or \
tokens_are_matching(expr_node.tokens, in_context_tokens):
return True
return None
def visit_FunctionNode(self, expr_node: FunctionNode):
if tokens_are_matching(expr_node.tokens, is_question_tokens) or \
tokens_are_matching(expr_node.tokens, in_context_tokens):
+4
View File
@@ -1,8 +1,10 @@
from dataclasses import dataclass
from typing import Union
import core.utils
from cache.FastCache import FastCache
from core import builtin_helpers
from core.concept import Concept
from core.sheerka.ExecutionContext import ExecutionContext
from core.tokenizer import Token, TokenKind
from core.var_ref import VariableRef
@@ -483,11 +485,13 @@ class VariableNode(LexerNode):
@dataclass()
class GrammarErrorNode(ParsingError):
message: str
concept: Union[Concept, None] = None
@dataclass()
class NoMatchingTokenError(ParsingError):
pos: int
concept: Union[Concept, None] = None
class UnrecognizedTokensCache:
+82 -22
View File
@@ -10,7 +10,7 @@ import re
from collections import defaultdict
from dataclasses import dataclass, field
from operator import attrgetter
from typing import List
from typing import List, Union
import core.builtin_helpers
import core.utils
@@ -23,11 +23,21 @@ from core.tokenizer import Token, TokenKind, Tokenizer
from core.utils import CONSOLE_COLORS_MAP as CCM
from parsers.BaseNodeParser import BaseNodeParser, ConceptNode, GrammarErrorNode, NoMatchingTokenError, RuleNode, \
SourceCodeNode, SourceCodeWithConceptNode, UnrecognizedTokensCache, UnrecognizedTokensNode
from parsers.BaseParser import ParsingError
PARSERS = ["Sequence", "Sya", "Python"]
VARIABLE_EXPR_PARSER = ["Sequence", "Sya", "Python", "Bnf"]
@dataclass
class UndefinedSymbolError(ParsingError):
"""
When parsing a concept, returns an error when a symbol is not recognized as a concept, a rule, or python code...
"""
symbol: str
concept: Union[Concept, None] = None
@dataclass(eq=True)
class RegExDef:
to_match: str = None
@@ -469,6 +479,9 @@ class VariableExpression(ParsingExpression):
def __hash__(self):
return hash(("VariableExpression", self.rule_name))
def debug_prefix(self, parser_helper):
return super().debug_prefix("VariableExpression", parser_helper)
def init_parsing(self):
"""
Get the instance of the following VariableExpression if they exists,
@@ -485,6 +498,9 @@ class VariableExpression(ParsingExpression):
def get_nodes_sequences_when_variables_are_first(self, parser_helper):
if len(parser_helper.sequence) < len(self.expected_variables):
# variable(s) is/are expected. But nothing found
if parser_helper.debugger.is_enabled():
debug_prefix = self.debug_prefix(parser_helper)
parser_helper.debug_concept(debug_prefix, color="red", raw="Failed! Too few elements")
return None
# only take the requested number of variables
@@ -499,6 +515,11 @@ class VariableExpression(ParsingExpression):
end = parser_helper.get_last_token_pos()
nodes_sequences = self.get_nodes_sequences_from_tokens(parser_helper, start, end, tokens)
if not nodes_sequences:
unrecognized = core.utils.get_text_from_tokens(tokens)
parser_helper.add_error(UndefinedSymbolError(unrecognized, parser_helper.get_current_concept()))
if parser_helper.debugger.is_enabled():
debug_prefix = self.debug_prefix(parser_helper)
parser_helper.debug_concept(debug_prefix, color="red", raw=f"Failed to recognize '{unrecognized}'")
return nodes_sequences
# only take the requested number of variables
@@ -527,7 +548,7 @@ class VariableExpression(ParsingExpression):
def _parse(self, parser_helper):
if parser_helper.debugger.is_enabled():
debug_prefix = self.debug_prefix("VariableExpression", parser_helper)
debug_prefix = self.debug_prefix(parser_helper)
debug_vars = {"pos": parser_helper.pos,
"expected variables": self.expected_variables,
"next to match": self.next_node_to_parse}
@@ -541,8 +562,14 @@ class VariableExpression(ParsingExpression):
else:
nodes_sequences = self.get_nodes_sequences_when_variables_are_in_between(parser_helper)
if nodes_sequences is None or self.has_unrecognized(nodes_sequences):
# nothing is recognized or only part is recognized
if nodes_sequences is None:
# nothing is recognized
return None
if self.has_unrecognized(nodes_sequences):
if parser_helper.debugger.is_enabled():
debug_prefix = self.debug_prefix(parser_helper)
parser_helper.debug_concept(debug_prefix, color="red", raw="Some parts are not recognized")
return None
all_results = []
@@ -558,7 +585,7 @@ class VariableExpression(ParsingExpression):
resolved = self.get_resolved(node)
if resolved is None:
parser_helper.errors.append(f"Failed to recognize {node.source}")
parser_helper.add_error(UndefinedSymbolError(node.source))
break
ptree_nodes.append(TerminalNode(variable_expr, node.start, node.end, node.source, resolved))
@@ -575,6 +602,11 @@ class VariableExpression(ParsingExpression):
all_results.append(ptree_nodes)
if len(all_results) == 0:
if parser_helper.debugger.is_enabled():
debug_prefix = self.debug_prefix(parser_helper)
parser_helper.debug_concept(debug_prefix, color="red",
raw="No result matching the expected number of variables",
nodes_sequences=nodes_sequences)
return None
# every seems to be fine. We can pop the nodes from parser_helper used as variable
@@ -618,7 +650,8 @@ class VariableExpression(ParsingExpression):
return None
utn = UnrecognizedTokensNode(start, end, tokens)
nodes_sequences = parser_helper.parser.cache2.get_lexer_nodes_from_unrecognized(parser_helper.parser.context,
nodes_sequences = parser_helper.parser.variable_expr_cache.get_lexer_nodes_from_unrecognized(
parser_helper.parser.context,
utn)
return nodes_sequences
@@ -1043,21 +1076,24 @@ class StrMatch(Match):
if parser_helper.debugger.is_enabled():
debug_prefix = self.debug_prefix("StrMatch", parser_helper)
debug_text = f"pos={parser_helper.pos}, to_match={self.to_match}, token={token.str_value}"
parser_helper.debug_concept(debug_prefix, raw=f"{CCM['green']}{debug_text}{CCM['reset']}")
debug_vars = {"pos": parser_helper.pos,
"to_match": self.to_match,
"token": token.str_value}
debug_text = self.debug_to_raw(debug_vars)
parser_helper.debug_concept(debug_prefix, color="cyan", raw=debug_text)
m = token.str_value.lower() == self.to_match.lower() if self.ignore_case \
else token.strip_quote == self.to_match
if m:
if parser_helper.debugger.is_enabled():
parser_helper.debug_concept(debug_prefix, raw=f"{CCM['green']}{debug_text}{CCM['reset']}")
parser_helper.debug_concept(debug_prefix, color="green", raw=f"matched")
node = TerminalNode(self, parser_helper.pos, parser_helper.pos, token.str_value, token.str_value)
parser_helper.next_token(self.skip_white_space)
return node
if parser_helper.debugger.is_enabled():
parser_helper.debug_concept(debug_prefix, raw=f"{CCM['red']}{debug_text}{CCM['reset']}")
parser_helper.debug_concept(debug_prefix, color="red", raw=f"not matched")
return None
@@ -1110,17 +1146,18 @@ class RegExMatch(Match):
# debug_text = f"pos={parser_helper.pos}, to_match={self.to_match}, text={text_debug}"
# parser_helper.debug_concept(debug_prefix, raw=f"{CCM['green']}{debug_text}{CCM['reset']}")
m = self.regex.match(text, parser_helper.token.index)
m = self.regex.match(text, parser_helper.token.index - parser_helper.token_offset)
if m:
matched = m.group()
# TODO: Add debug info here
if matched:
# the match is only valid if it fits with the actual tokens
# the match is only valid it ends at the end of a token
next_pos = parser_helper.get_next_matching_pos(m.end())
if next_pos is NotFound:
parser_helper.errors.append(NoMatchingTokenError(m.end()))
current_concept = parser_helper.get_current_concept()
parser_helper.add_error(NoMatchingTokenError(m.end(), concept=current_concept))
return None
node = TerminalNode(self, parser_helper.pos, next_pos - 1, matched, matched)
@@ -1242,11 +1279,11 @@ class HasAChoiceExpressionVisitor(ParsingExpressionVisitor):
class BnfConceptParserHelper:
def __init__(self, parser, debugger):
def __init__(self, parser, token_offset, debugger):
self.parser = parser
self.debugger = debugger
self.debug = [] # keep track of the tokens
self.errors = [] # sink of errors
self.errors = [] # sink of errors, sorted by bnf concepts
self.sequence = [] # output. List of lexer nodes correctly parsed
self.concepts = [] # stack of concepts being processed (fed by ConceptExpression)
self.concepts_ids = [] # ids if the concept to increase speed
@@ -1260,6 +1297,7 @@ class BnfConceptParserHelper:
self.forked = []
self.token = None
self.token_offset = token_offset
self.pos = -1
def __repr__(self):
@@ -1282,6 +1320,12 @@ class BnfConceptParserHelper:
if len(self.concepts) <= 2:
self.debugger.debug_concept(self.concepts[0], text, **kwargs)
def get_current_concept(self):
return self.concepts[-1] if len(self.concepts) > 0 else None
def add_error(self, error):
self.errors.append(error)
def get_current_rule_name(self):
for rule_name in reversed(self.rules_names):
if rule_name:
@@ -1328,15 +1372,26 @@ class BnfConceptParserHelper:
def get_next_matching_pos(self, token_index):
"""
Given the token, tries to find a token (within the remaining tokens) that matches the index
Why do we do that ?
The regex matching is done against the text of the parser input, not its tokens
With this function, we check that the matching found correspond to an exact number of tokens.
Token is "abcdef" and we manage to match "abc"
-> 'abc' falls in the middle of the token, so it's not a valid match
:param token_index:
:return:
"""
current = self.pos
token_index += self.token_offset # add the offset when using sub parser input
while current <= self.parser.parser_input.end:
if self.parser.parser_input.tokens[current].index == token_index:
return current
current += 1
# last chance when dealing with sub parser input (that do not end with token(EOF))
if current < self.parser.parser_input.length:
if self.parser.parser_input.tokens[current].index == token_index:
return current
# No matching token
return NotFound
@@ -1384,7 +1439,7 @@ class BnfConceptParserHelper:
error_msg = f"Failed to parse concept '{concept}'"
if parsing_expression is not None:
error_msg += f". Reason: '{parsing_expression}'"
self.errors.append(GrammarErrorNode(error_msg))
self.add_error(GrammarErrorNode(error_msg, concept=concept))
return
self.pos = self.parser.parser_input.pos
@@ -1468,7 +1523,7 @@ class BnfConceptParserHelper:
self.unrecognized_tokens = UnrecognizedTokensNode(-1, -1, [])
def clone(self):
clone = BnfConceptParserHelper(self.parser, self.debugger)
clone = BnfConceptParserHelper(self.parser, self.token_offset, self.debugger)
clone.debug = self.debug[:]
clone.errors = self.errors[:]
clone.sequence = self.sequence[:]
@@ -1636,8 +1691,11 @@ class BnfNodeParser(BaseNodeParser):
else:
self.concepts_grammars = Cache()
# this cache is for unrecognized tokens that cannot be BNF expression
self.cache = UnrecognizedTokensCache(PARSERS)
self.cache2 = UnrecognizedTokensCache(VARIABLE_EXPR_PARSER)
# This cache is for variable expression. It can be anything
self.variable_expr_cache = UnrecognizedTokensCache(VARIABLE_EXPR_PARSER)
self.ignore_case = True
@staticmethod
@@ -1730,10 +1788,12 @@ class BnfNodeParser(BaseNodeParser):
return list1 + list2
sheerka = context.sheerka
forked = []
debugger = context.get_debugger(self.NAME, "parse")
debugger.debug_entering(source=self.parser_input.as_text())
concept_parser_helpers = [BnfConceptParserHelper(self, debugger)]
token_offset = self.parser_input.tokens[self.parser_input.start].index
concept_parser_helpers = [BnfConceptParserHelper(self, token_offset, debugger)]
while self.parser_input.next_token(False):
@@ -1753,8 +1813,8 @@ class BnfNodeParser(BaseNodeParser):
debugger.debug_log(debug_prefix + ", all parsers are locked. Nothing to do.")
continue
by_token = context.sheerka.get_concepts_by_first_token(token, self._is_eligible, strip_quotes=False)
by_regex = context.sheerka.get_concepts_by_first_regex(self.parser_input.sub_text, token.index)
by_token = sheerka.get_concepts_by_first_token(token, self._is_eligible, strip_quotes=False)
by_regex = sheerka.get_concepts_by_first_regex(self.parser_input.sub_text, token.index - token_offset)
concepts = _merge(by_token, by_regex)
@@ -2120,7 +2180,7 @@ class BnfNodeParser(BaseNodeParser):
debugger = context.get_debugger(self.NAME, "parse")
if debugger.is_enabled:
debugger.debug_var("stats", self.cache.to_dict())
#debugger.debug_var("stats", self.cache2.to_dict())
# debugger.debug_var("stats", self.variable_expr_cache.to_dict())
if valid_parser_helpers is None:
return self.sheerka.ret(
+2 -9
View File
@@ -3,11 +3,9 @@ from core.sheerka.services.SheerkaExecute import ParserInput
from parsers.ArithmericOperatorParser import ArithmeticOperatorParser
from parsers.BaseExpressionParser import BaseExpressionParser, ChoiceParser
from parsers.FunctionParser import FunctionParser
from parsers.FunctionParserOld import FunctionParserOld
from parsers.ListParser import ListParser
from parsers.LogicalOperatorParser import LogicalOperatorParser
from parsers.RelationalOperatorParser import RelationalOperatorParser
from parsers.VariableOrNamesParser import VariableOrNamesParser
from sheerkapython.ExprToConditions import ExprToConditionsVisitor
from sheerkapython.ExprToPython import PythonExprVisitor
@@ -19,18 +17,13 @@ class ExpressionParser(BaseExpressionParser):
NAME = "Expression"
def __init__(self, auto_compile=True, old_style=False, known_variables=None, **kwargs):
def __init__(self, auto_compile=True, known_variables=None, **kwargs):
super().__init__(ExpressionParser.NAME,
0,
True,
yield_eof=False,
hints={BuiltinConcepts.EVAL_QUESTION_REQUESTED: 60})
if old_style:
self.variable_parser = VariableOrNamesParser()
self.function_parser = FunctionParserOld(expr_parser=self, tokens_parser=self.variable_parser)
self.relational_parser = RelationalOperatorParser(old_style=old_style, expr_parser=self.function_parser)
self.logical_parser = LogicalOperatorParser(expr_parser=self.relational_parser)
else:
function_parser = FunctionParser(expr_parser=self)
list_parser = ListParser(expr_parser=self)
choice_parser = ChoiceParser(list_parser, function_parser)
-343
View File
@@ -1,343 +0,0 @@
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import get_lexer_nodes_from_unrecognized, update_compiled
from core.concept import Concept
from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import TokenKind
from core.utils import get_n_clones
from parsers.BaseExpressionParser import NameExprNode, FunctionNodeOld, FunctionParameter, BaseExpressionParser
from parsers.BaseNodeParser import SourceCodeNode, SourceCodeWithConceptNode
from parsers.BaseParser import UnexpectedTokenParsingError, UnexpectedEofParsingError, ErrorSink
from parsers.BnfNodeParser import BnfNodeParser
from parsers.PythonWithConceptsParser import PythonWithConceptsParser
from parsers.RuleParser import RuleParser
from parsers.SequenceNodeParser import SequenceNodeParser
from parsers.SyaNodeParser import SyaNodeParser
PARSERS = [RuleParser.NAME,
SequenceNodeParser.NAME,
BnfNodeParser.NAME,
SyaNodeParser.NAME]
class FunctionParserOld(BaseExpressionParser):
"""
The parser will be used to parse func(x, y, z)
where x, y and z can be source code, concepts or other functions
It will return a SourceCodeNode or SourceCodeNodeWithConcept
"""
NAME = "FunctionOld"
def __init__(self, sep=",", longest_concepts_only=True, strict=True, **kwargs):
"""
:param sep:
:param longest_concepts_only: When multiples concepts are found, only keep the longest one
so 'twenty one' will resolve to [[c:twenty one:]], not [[c:twenty one:], [c:twenty:, c:one:]]
:param strict: Only allow expression that start by a function definition so 'xxx f(x)' will be refused
:param kwargs:
"""
super().__init__(self.NAME, 55, enabled=False)
self.sep = sep
self.longest_concepts_only = longest_concepts_only
self.strict = strict
self.expr_parser = kwargs.get("expr_parser", None)
self.tokens_parser = kwargs.get("tokens_parser", None)
def function_parser_get_return_value_body(self, context, source, source_code_node):
if source_code_node.error_when_parsing:
return context.sheerka.new(BuiltinConcepts.ERROR,
body=source_code_node.error_when_parsing)
return context.sheerka.new(BuiltinConcepts.PARSER_RESULT,
parser=self,
source=source,
body=source_code_node,
try_parsed=source_code_node)
def parse(self, context, parser_input: ParserInput):
ret = super().parse(context, parser_input)
if ret is None:
return None
if not ret.status:
return ret
# FunctionParserOld returns LexerNodes, rather than an ExprNode
# I know that is is not very logical, but at the beginning, the FunctionParserOld was
# uses exclusively by the SyaNodeParser.
# It has been refactored to fit in ExpressionParser. So it has two main usages
node = ret.body.body
source_code_nodes = self.to_source_code_node(context, node)
res = []
for source_code_node in source_code_nodes:
body = self.function_parser_get_return_value_body(context, parser_input.as_text(), source_code_node)
res.append(context.sheerka.ret(self.name, source_code_node.python_node is not None, body))
return res[0] if len(res) == 1 else res
def parse_input(self, context, parser_input, error_sink):
# when FunctionParserOld is used by LexerNode or SheerkaExecute, it must fail if no function is found
# when it is used by ExpressionParser, it must default to VariableOrNamesParser
# KSI 20210910 - Not quite quite it's the best approach. It seems a little bit complicated
pos = parser_input.pos
res = self.parse_function(context, parser_input, error_sink)
if (not res or error_sink.has_error) and self.tokens_parser:
parser_input.seek(pos)
error_sink.clear()
return self.tokens_parser.parse_input(context, parser_input, error_sink)
return res
def parse_function(self, context, parser_input, error_sink):
unrecognized_tokens_start = unrecognized_tokens_end = parser_input.pos
if not self.strict:
# eat everything that is not part of the function
token = parser_input.token
while token.type != TokenKind.EOF:
if token.type == TokenKind.IDENTIFIER and parser_input.the_token_after().type == TokenKind.LPAR:
break
else:
unrecognized_tokens_end += 1
parser_input.next_token(skip_whitespace=False)
token = parser_input.token
start = parser_input.pos
token = parser_input.token
if token.type != TokenKind.IDENTIFIER:
error_sink.add_error(UnexpectedTokenParsingError(f"{token.repr_value} is not a identifier",
token,
[TokenKind.IDENTIFIER]))
return None
if not parser_input.next_token():
error_sink.add_error(UnexpectedEofParsingError(f"while parsing left parenthesis"))
return None
token = parser_input.token
if token.type != TokenKind.LPAR:
error_sink.add_error(UnexpectedTokenParsingError(f"{token.repr_value} is not a left parenthesis",
token,
[TokenKind.LPAR]))
return None
start_node = NameExprNode(start, start + 1, parser_input.tokens[start:start + 2])
if not parser_input.next_token():
error_sink.add_error(UnexpectedEofParsingError(f"after left parenthesis"))
return FunctionNodeOld(start, start + 1, [], start_node, None, None)
params = self.parse_parameters(context, parser_input, error_sink)
if error_sink.has_error:
return FunctionNodeOld(start, parser_input.pos, [], start_node, None, params)
token = parser_input.token
if not token or token.type != TokenKind.RPAR:
error_sink.add_error(UnexpectedTokenParsingError(f"Right parenthesis not found",
token,
[TokenKind.RPAR]))
return FunctionNodeOld(start, parser_input.pos, [], start_node, None, params)
function_node = FunctionNodeOld(start,
parser_input.pos,
parser_input.tokens[start:parser_input.pos + 1],
start_node,
NameExprNode(parser_input.pos, parser_input.pos, [token]),
params)
parser_input.next_token() # do not forget to eat the trailing parenthesis
# if unrecognized_tokens_end != unrecognized_tokens_start:
# if self.expr_parser:
# sub_parser = parser_input.sub_part(unrecognized_tokens_start,
# unrecognized_tokens_end,
# yield_oef=False).reset()
# sub_parser.next_token()
# expr_node = self.expr_parser(context, parser_input, error_sink)
# else:
# expr_node = NameExprNode(unrecognized_tokens_start,
# unrecognized_tokens_end,
# parser_input.tokens[unrecognized_tokens_start: unrecognized_tokens_end + 1])
#
# return SequenceNode(unrecognized_tokens_start, function_node.end,
# parser_input.tokens[unrecognized_tokens_start: function_node.end + 1],
# expr_node, function_node)
return function_node
def parse_parameters(self, context, parser_input, error_sink):
nodes = []
while True:
param_value = self.parse_parameter_value(context, parser_input, error_sink)
if not param_value:
break
function_parameter = FunctionParameter(param_value)
nodes.append(function_parameter)
token = parser_input.token
if token.type == TokenKind.EOF:
error_sink.add_error(UnexpectedEofParsingError(f"while parsing parameters"))
return None
if token.type == TokenKind.RPAR:
break
if token.value == self.sep:
sep_pos = parser_input.pos
has_next = parser_input.next_token() # it's before add_sep() to capture trailing whitespace
function_parameter.add_sep(sep_pos,
parser_input.pos - 1,
parser_input.tokens[sep_pos: parser_input.pos])
if not has_next:
break
return nodes
def parse_parameter_value(self, context, parser_input, error_sink):
# check if the parameter is a function
start_pos = parser_input.pos
new_error_sink = ErrorSink()
func = self.parse_function(context, parser_input, new_error_sink)
if func and not new_error_sink.has_error:
return func
# otherwise, eat until LPAR or separator
parser_input.seek(start_pos)
return self.parse_tokens(context,
parser_input,
error_sink,
self.parse_tokens_stop_condition,
self.expr_parser,
self.expr_parser)
def parse_tokens_stop_condition(self, token, parser_input):
return token.value == self.sep
def to_source_code_node(self, context, function_node: FunctionNodeOld):
python_parser = PythonWithConceptsParser()
def update_source_code_node(scn, nodes, sep):
if hasattr(nodes, "__iter__"):
for n in nodes:
scn.add_node(n)
else:
scn.add_node(nodes)
if sep:
scn.add_node(sep.to_unrecognized())
def get_errors_from_python_parsing(parsing_res):
if parsing_res.status:
return None
if context.sheerka.isinstance(parsing_res.body, BuiltinConcepts.NOT_FOR_ME):
return parsing_res.body.reason
else:
return parsing_res.body.body
if len(function_node.parameters) == 0:
# validate the source
nodes_to_parse = [function_node.first.to_unrecognized(), function_node.last.to_unrecognized()]
python_parsing_res = python_parser.parse_nodes(context, nodes_to_parse)
python_node = python_parsing_res.body.body if python_parsing_res.status else None
return [SourceCodeNode(start=function_node.first.start,
end=function_node.last.end,
tokens=function_node.first.tokens + function_node.last.tokens,
python_node=python_node,
return_value=python_parsing_res,
error_when_parsing=get_errors_from_python_parsing(python_parsing_res))]
res = [SourceCodeWithConceptNode(function_node.first.to_unrecognized(), function_node.last.to_unrecognized())]
# try to recognize every parameter, one by one
for param in function_node.parameters:
if isinstance(param.value, NameExprNode):
# try to recognize concepts
unrecognized = param.value.to_unrecognized()
nodes_sequences = get_lexer_nodes_from_unrecognized(context,
unrecognized,
PARSERS)
else:
# the parameter is also a function
nodes_sequences = self.to_source_code_node(context, param.value)
if self.longest_concepts_only:
nodes_sequences = self.get_longest_concepts(nodes_sequences)
if nodes_sequences is None:
# no concept found
for source_code_node in res:
update_source_code_node(source_code_node, unrecognized, param.separator)
elif len(nodes_sequences) == 1:
# only one result
# It is the same code than when there are multiple results
# But here, we save the creation of the tmp_res object (not sure it worth it)
for source_code_node in res:
update_source_code_node(source_code_node, nodes_sequences[0], param.separator)
else:
# multiple result, make the cartesian product
tmp_res = []
for source_code_node in res:
instances = get_n_clones(source_code_node, len(nodes_sequences))
tmp_res.extend(instances)
for instance, node_sequence in zip(instances, nodes_sequences):
update_source_code_node(instance, node_sequence, param.separator)
res = tmp_res
# check if it is a valid source code
for source_code_node in res:
source_code_node.fix_all_pos()
source_code_node.pseudo_fix_source()
python_parsing_res = python_parser.parse_nodes(context, source_code_node.get_all_nodes())
if python_parsing_res.status:
source_code_node.python_node = python_parsing_res.body.body
source_code_node.return_value = python_parsing_res
# make sure that concepts found can be evaluated
errors = []
for c in [c for c in source_code_node.python_node.objects.values() if isinstance(c, Concept)]:
update_compiled(context, c, errors)
if errors:
source_code_node.error_when_parsing = errors
else:
source_code_node.error_when_parsing = get_errors_from_python_parsing(python_parsing_res)
return res
@staticmethod
def get_longest_concepts(nodes_sequences):
"""
The longest sequences are the ones that have the less number of concepts
For example
'twenty one' resolves to
[c:twenty one:]
[c:twenty:, c:one:]
[c:twenty one:] has only one concept, so it's the longest one (two tokens against one token twice)
:param nodes_sequences:
:return:
"""
if nodes_sequences is None:
return None
res = []
min_len = -1
for current_sequence in nodes_sequences:
# awful hack to remove when NodeSequence and ConceptSequence will be implemented
current_len = len(current_sequence) if hasattr(current_sequence, "__len__") else 1
if len(res) == 0:
res.append(current_sequence)
min_len = current_len
elif current_len == min_len:
res.append(current_sequence)
elif current_len < min_len:
res.clear()
res.append(current_sequence)
min_len = current_len
return res
+3 -15
View File
@@ -1,6 +1,6 @@
from core.tokenizer import TokenKind
from parsers.BaseExpressionParser import ComparisonNode, ComparisonType, \
ParenthesisNode, BaseExpressionParser, open_parenthesis_mapping
from parsers.BaseExpressionParser import BaseExpressionParser, ComparisonNode, ComparisonType, ParenthesisNode, \
open_parenthesis_mapping
from parsers.BaseParser import UnexpectedTokenParsingError
from parsers.ListParser import ListParser
@@ -13,10 +13,9 @@ class RelationalOperatorParser(BaseExpressionParser):
NAME = "RelationalOperator"
def __init__(self, old_style=False, **kwargs):
def __init__(self, **kwargs):
super().__init__(self.NAME, 60, False, yield_eof=True)
self.expr_parser = kwargs.get("expr_parser", None)
self.old_style = old_style
self.list_parser = ListParser()
def parse_input(self, context, parser_input, error_sink):
@@ -36,17 +35,6 @@ class RelationalOperatorParser(BaseExpressionParser):
if (comp := self.eat_comparison(parser_input)) is None:
return left
if self.old_style:
right = self.parse_tokens(context,
parser_input,
error_sink,
self.parse_tokens_stop_condition,
self.expr_parser,
self)
if comp == ComparisonType.IN and not isinstance(right, ParenthesisNode):
t = right.tokens[0]
error_sink.add_error(UnexpectedTokenParsingError(f"Expected parenthesis", t, [TokenKind.LPAR]))
else:
if comp in (ComparisonType.IN, ComparisonType.NOT_IN):
token = parser_input.token
if token.type not in open_parenthesis_mapping:
+3
View File
@@ -1007,8 +1007,11 @@ class SyaNodeParser(BaseNodeParser):
result.append(tokens_parser)
elif tokens_parser.has_sya_concept and tokens_parser.has_error():
in_error.append(tokens_parser)
for err in tokens_parser.errors:
tokens_parser.debugger.debug_log(f"Validation Error: {err}", True)
else:
tokens_parser.errors.append(NoSyaConceptFound())
tokens_parser.debugger.debug_log(f"Validation Error: No Sya Found", True)
not_for_me.append(tokens_parser)
# recurse on the forks
+1 -1
View File
@@ -52,7 +52,7 @@ class Event(object):
if self._digest:
return self._digest
if self.message == "" and self.user_id == "":
if self.user_id == "" and (self.message == "" or self.message.startswith("xxxTESTxxx::")):
self._digest = "xxx" # to speed unit tests
return self._digest
+9 -3
View File
@@ -57,14 +57,15 @@ check_existence = ExprTransformHints(check_variable_existence=True,
class BaseExprTransform(ExpressionVisitorWithHint):
def __init__(self, context, obj_counter):
def __init__(self, context, obj_counter, known_variables=None):
self.context = context
self.obj_counter = obj_counter
self.objects_by_id = {}
self.objects_by_name = {}
self.errors = {}
self.known_variables = known_variables if known_variables is not None else set()
from parsers.ExpressionParser import ExpressionParser
self.expression_parser = ExpressionParser(auto_compile=False, old_style=False)
self.expression_parser = ExpressionParser(auto_compile=False)
def visit_NotNode(self, expr_node: NotNode, hint: ExprTransformHints):
"""
@@ -482,7 +483,12 @@ class BaseExprTransform(ExpressionVisitorWithHint):
identifier = self.get_object_identifier(concept)
objects = {}
concept_variables = get_variables_from_concept_asts(self.context, concept, variables, parameters_only=True)
known_variables = self.known_variables.copy()
known_variables.update(variables)
concept_variables = get_variables_from_concept_asts(self.context,
concept,
known_variables,
parameters_only=True)
parameters_to_compute = {}
for var_name, default_value in [(k, v) for k, v in concept.get_metadata().variables if k in concept_variables]:
if isinstance(concept.get_compiled()[var_name], Concept):
+1 -2
View File
@@ -8,9 +8,8 @@ from sheerkapython.python_wrapper import is_variable
class ExprToConditionsVisitor(BaseExprTransform):
def __init__(self, context, obj_counter=0, known_variables=None):
super().__init__(context, obj_counter)
super().__init__(context, obj_counter, known_variables)
self.intermediate_variables = {}
self.known_variables = known_variables if known_variables is not None else set()
def get_conditions(self, expr_node):
# first transform expr_node into list of conjunctions
+1 -17
View File
@@ -3,7 +3,7 @@ from itertools import product
from core.builtin_concepts_ids import BuiltinConcepts
from core.sheerka.services.sheerka_service import FailedToCompileError
from core.tokenizer import TokenKind
from parsers.BaseExpressionParser import AndNode, FunctionNodeOld, ListComprehensionNode, NameExprNode, VariableNode, \
from parsers.BaseExpressionParser import AndNode, ListComprehensionNode, NameExprNode, VariableNode, \
end_parenthesis_mapping, open_parenthesis_mapping
from sheerkapython.BaseExprTransform import BaseExprTransform, ExprTransformHints, do_not_eval_source_hint, \
is_a_question_hint, not_a_question_hint, wrap_concept_call_hint
@@ -128,19 +128,3 @@ class PythonExprVisitor(BaseExprTransform):
:return:
"""
return self.visit_or_or_and_node("or", expr_node, hint)
def visit_FunctionNodeOld(self, expr_node: FunctionNodeOld, hint: ExprTransformHints):
visitor_objects = []
source = expr_node.get_source()
parameters_objects = []
for parameter in expr_node.parameters:
parameters_objects.append(self.visit(parameter.value, hint))
for parameters in product(*parameters_objects):
visitor_objects.append(self.create_function_old(source,
expr_node.first.get_source(),
expr_node.last.get_source(),
parameters))
return visitor_objects
+27 -1
View File
@@ -6,7 +6,7 @@ from core.ast_helpers import UnreferencedVariablesVisitor
from core.builtin_concepts import ReturnValueConcept
from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import AllConceptParts, Concept
from core.global_symbols import ErrorObj, NotFound, NotInit, SyaAssociativity
from core.global_symbols import ErrorItem, ErrorObj, NotFound, NotInit, SyaAssociativity
from core.rule import Rule
from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka.services.SheerkaAdmin import SheerkaAdmin
@@ -110,8 +110,14 @@ sheerka_globals = {
"get_type": get_type,
"hasattr": sheerka_hasattr,
"getattr": sheerka_getattr,
"ErrorItem": ErrorItem
}
# Adds all concepts that have their own class definition
for c in core.utils.get_classes("core.builtin_concepts"):
if issubclass(c, Concept) and c != Concept:
sheerka_globals[c.__name__] = c
def inject_context(context):
"""
@@ -285,6 +291,7 @@ def get_variables_from_concept_asts(context, concept, known_variables, parameter
"""
From a given concept that is already compiled,
browse the compiled to see if there is any symbol that is unknown, eg variable
It is used to detect all mandatory variables before concept evaluation
:param context:
:param concept:
:param known_variables:
@@ -348,6 +355,25 @@ def get_variables_from_concept_asts(context, concept, known_variables, parameter
return variables
def get_possible_variables_from_concept(context, concept):
"""
Given a concept, get its symbols that may be considered as variables for other concepts
:param context:
:param concept:
:return:
"""
possible_variables = set(concept.get_metadata().parameters)
core.builtin_helpers.ensure_bnf(context, concept)
if concept.get_bnf():
from parsers.BnfNodeParser import BnfNodeConceptExpressionVisitor
visitor = BnfNodeConceptExpressionVisitor()
visitor.visit(concept.get_bnf())
possible_variables.update([c.name if isinstance(c, Concept) else c for c in visitor.references])
return possible_variables
def is_variable(context, name):
"""
tells whether or not the name can be a variable
+13 -4
View File
@@ -48,7 +48,7 @@ class InitTestHelper:
c.get_metadata().definition_type = DEFINITION_TYPE_BNF
else:
raise Exception(f"Error in bnf definition '{c.get_metadata().definition}'",
self.sheerka.get_errors(self.context, res))
self.sheerka.get_obj_errors(self.context, res))
self._update_concept_parameters(c)
if create_new:
@@ -92,8 +92,7 @@ class InitTestHelper:
if create_new:
res = self.sheerka.create_new_rule(self.context, rule)
if not res.status:
raise Exception(f"Error in rule definition '{res.body}'",
self.sheerka.get_errors(res))
raise Exception(f"Error in rule definition '{res.body}'", self.sheerka.get_obj_errors(res))
self.items.append(res.body.body)
else:
self.items.append(rule)
@@ -294,16 +293,26 @@ class BaseTest:
return [ret_val for ret_val in return_values if ret_val.status]
@staticmethod
def activate_debug(context, pattern="Sya.*.*"):
def activate_debug(context, pattern="Sya.*.*", debug_var=True, debug_concept=False, debug_rule=False):
sheerka = context.sheerka
sheerka.set_debug(context, True)
sheerka.set_debug_logger_definition(ListDebugLogger)
if isinstance(pattern, list):
for p in pattern:
if debug_var:
sheerka.set_debug_var(context, p)
if debug_concept:
sheerka.set_debug_concept(context, p)
if debug_rule:
sheerka.set_debug_rule(context, p)
else:
if debug_var:
sheerka.set_debug_var(context, pattern)
if debug_concept:
sheerka.set_debug_concept(context, pattern)
if debug_rule:
sheerka.set_debug_rule(context, pattern)
# the see the logs, do not forget to add
# logs = sheerka.get_debugger_logs()
+18 -1
View File
@@ -1,8 +1,10 @@
import shutil
from os import path
from conftest import SHEERKA_TEST_FOLDER
from core.global_symbols import EVENT_ONTOLOGY_CREATED
from core.sheerka.Sheerka import Sheerka
from core.sheerka.SheerkaOntologyManager import SheerkaOntologyManager
from tests.BaseTest import BaseTest
@@ -45,3 +47,18 @@ class TestUsingFileBasedSheerka(BaseTest):
self.sheerka.push_ontology(self.context, ontology_name, cache_only=cache_only)
return TestUsingFileBasedSheerka.sheerka
@staticmethod
def commit(context):
sheerka = context.sheerka
event = context.event
sheerka.om.save_event(event)
if sheerka.om.is_dirty():
sheerka.om.commit(context)
@staticmethod
def reset_hard_test_env():
if path.exists(SHEERKA_TEST_FOLDER):
shutil.rmtree(SHEERKA_TEST_FOLDER)
TestUsingFileBasedSheerka.sheerka = None
+463
View File
@@ -0,0 +1,463 @@
import pytest
from core.builtin_concepts import ReturnValueConcept, UnknownConcept
from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import Concept
from core.sheerka.services.SheerkaConceptManager import ValueNotFound
from core.sheerka.services.SheerkaErrorManager import LAST_UNKNOWN_CONCEPTS, SheerkaErrorManager
from core.sheerka.services.SheerkaExecute import ParserInput
from core.sheerka.services.SheerkaRuleManager import NoConditionFound
from evaluators.PythonEvaluator import PythonEvaluator
from parsers.BaseParser import ParsingError, UnexpectedEofParsingError
from parsers.BnfNodeParser import BnfNodeParser
from parsers.ExactConceptParser import ExactConceptParser
from parsers.PythonParser import PythonErrorNode, PythonParser
from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
def inner_errors(lst):
return [e.error for e in lst]
class TestSheerkaErrorManger(TestUsingMemoryBasedSheerka):
@pytest.mark.parametrize("obj, expected", [
("a string", []),
(True, []),
(False, []),
(Concept("foo"), []),
(Concept("foo", body=False).auto_init(), []),
(UnknownConcept(), [UnknownConcept()]),
(Concept("foo", body=UnknownConcept()).auto_init(), [UnknownConcept()]),
(PythonErrorNode("msg", None), [PythonErrorNode("msg", None)])
])
def test_i_can_get_error_for_simple_objects(self, obj, expected):
sheerka, context = self.init_test().unpack()
assert sheerka.get_obj_errors(context, obj) == expected
def test_i_can_get_error_when_builtin_concept_in_error(self):
sheerka, context = self.init_test().unpack()
obj = sheerka.new(BuiltinConcepts.ONTOLOGY_ALREADY_DEFINED)
assert sheerka.get_obj_errors(context, obj) == [obj]
def test_i_can_get_error_when_return_value(self):
sheerka, context = self.init_test().unpack()
error = sheerka.err("an error")
ret_val = ReturnValueConcept("Test", False, sheerka.err("an error"))
assert sheerka.get_obj_errors(context, ret_val) == [error]
def test_i_can_get_inner_error(self):
sheerka, context = self.init_test().unpack()
error = sheerka.err("an error")
ret_val = ReturnValueConcept("Test", False, sheerka.err("an error"))
assert sheerka.get_obj_errors(context, ret_val) == [error]
def test_i_can_get_error_when_embedded_errors(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT)
not_an_error = sheerka.new(BuiltinConcepts.AUTO_EVAL)
error = sheerka.err([concept_eval_error, unknown_concept, not_an_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_obj_errors(context, ret_val)
assert errors_found == [error, concept_eval_error, unknown_concept]
def test_i_can_get_embedded_errors_from_get_error_method(self):
sheerka, context = self.init_test().unpack()
embedded_error = NotImplementedError()
error = sheerka.new(BuiltinConcepts.NOT_FOR_ME, body="some text", reason=embedded_error)
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_obj_errors(context, ret_val)
assert errors_found == [error, embedded_error]
def test_i_can_get_error_from_list(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT)
not_an_error = sheerka.new(BuiltinConcepts.AUTO_EVAL)
error = sheerka.err([concept_eval_error, unknown_concept, not_an_error])
ret_val_1 = ReturnValueConcept("Test", False, error)
python_error = PythonErrorNode("msg", Exception())
value_not_found = ValueNotFound("item", "value")
multiple_error = sheerka.new(BuiltinConcepts.MULTIPLE_ERRORS, body=[python_error, value_not_found])
ret_val_2 = ReturnValueConcept("Test", False, multiple_error)
errors_found = sheerka.get_obj_errors(context, [ret_val_1, ret_val_2])
assert errors_found == [error, concept_eval_error, unknown_concept,
multiple_error, python_error, value_not_found]
def test_i_can_filter_error_by_concept_key(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT)
python_error = PythonErrorNode("msg", Exception())
error = sheerka.err([concept_eval_error, unknown_concept, python_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_obj_errors(context, ret_val, __type=BuiltinConcepts.CONCEPT_EVAL_ERROR)
assert errors_found == [concept_eval_error]
def test_i_can_filter_error_by_class_name(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT)
python_error = PythonErrorNode("msg", Exception())
error = sheerka.err([concept_eval_error, unknown_concept, python_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_obj_errors(context, ret_val, __type="PythonErrorNode")
assert errors_found == [python_error]
def test_i_can_filter_error_by_concept_attribute(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, concept_ref="a_concept_ref")
python_error = PythonErrorNode("msg", Exception())
error = sheerka.err([concept_eval_error, unknown_concept, python_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_obj_errors(context, ret_val, concept_ref="a_concept_ref")
assert errors_found == [unknown_concept]
def test_i_can_filter_error_by_class_attribute(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, concept_ref="a_concept_ref")
python_error = PythonErrorNode("error source", Exception())
error = sheerka.err([concept_eval_error, unknown_concept, python_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_obj_errors(context, ret_val, source="error source")
assert errors_found == [python_error]
def test_i_can_filter_error_by_exception_name_and_type(self):
sheerka, context = self.init_test().unpack()
from evaluators.PythonEvaluator import PythonEvalError
name_error = NameError("foo")
python_eval_error = PythonEvalError(name_error, "foo", None, None)
error = sheerka.err([python_eval_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_obj_errors(context, ret_val, __type="NameError")
assert errors_found == [name_error]
errors_found = sheerka.get_obj_errors(context, ret_val, __type=NameError)
assert errors_found == [name_error]
def test_i_can_filter_error_on_multiple_criteria(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, concept_ref="a_concept_ref")
value_not_found = ValueNotFound("an_item", "a value")
error = sheerka.err([concept_eval_error, unknown_concept, value_not_found])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_obj_errors(context, ret_val, __type="ValueNotFound", item="an_item", value="a value")
assert errors_found == [value_not_found]
def test_i_cannot_get_error_when_return_value_s_status_is_true(self):
sheerka, context = self.init_test().unpack()
ret_val = ReturnValueConcept("Test", True, sheerka.err("an error"))
assert sheerka.get_obj_errors(context, ret_val) == []
def test_i_can_get_error_items(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
simple_error = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=Concept("foo"))
ret1 = sheerka.ret("Test1", False, simple_error)
sub_error_is_an_exception = sheerka.new(BuiltinConcepts.NOT_FOR_ME, reason=NotImplementedError())
ret2 = sheerka.ret("Test2", False, sub_error_is_an_exception)
inner_error = sheerka.err([ParsingError(), UnexpectedEofParsingError("EOF", 0)])
sub_error_is_an_error = sheerka.new(BuiltinConcepts.NOT_FOR_ME, reason=inner_error)
ret3 = sheerka.ret("Test3", False, sub_error_is_an_error)
parsing_error = sheerka.new(BuiltinConcepts.PARSER_RESULT,
parser=PythonParser(),
source="not a valid source",
body="Not totally valid parsing")
ret4 = sheerka.ret("Test4", False, parsing_error)
multiple_level_errors1 = sheerka.err("Err1")
multiple_level_errors2 = sheerka.err(multiple_level_errors1)
multiple_level_errors3 = sheerka.err(multiple_level_errors2)
ret5 = sheerka.ret("Test5", False, multiple_level_errors3)
filtered = sheerka.new(BuiltinConcepts.FILTERED, reason="not really given")
ret6 = sheerka.ret("Test6", False, filtered)
ret7 = sheerka.ret("Test5", True, None)
error_items = list(service.get_errors_items([ret1, ret2, ret3, ret4, ret5, ret6, ret7]))
assert len(error_items) == 6
# ret1 : simple_error
assert len(error_items[0].children) == 0
# ret2 : sub_error_is_an_exception
assert len(error_items[1].children) == 1
assert len(error_items[1].children[0].children) == 0
# ret3 : inner_error
assert len(error_items[2].children) == 1
assert len(error_items[2].children[0].children) == 2
assert len(error_items[2].children[0].children[0].children) == 0
assert len(error_items[2].children[0].children[1].children) == 0
# ret4 : parsing_error
assert len(error_items[3].children) == 0
# ret5 : multiple_level_errors
assert len(error_items[4].children) == 1
assert len(error_items[4].children[0].children) == 1
assert len(error_items[4].children[0].children[0].children) == 0
# ret6 Filtered without a real reason
assert len(error_items[5].children) == 0
def test_i_can_get_all_error_items(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
simple_error = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=Concept("foo"))
ret1 = sheerka.ret("Test1", False, simple_error)
inner_error = sheerka.err([ParsingError(), UnexpectedEofParsingError("EOF", 0)])
sub_error_is_an_error = sheerka.new(BuiltinConcepts.NOT_FOR_ME, reason=inner_error)
ret2 = sheerka.ret("Test3", False, sub_error_is_an_error)
multiple_level_errors1 = sheerka.err("Err1")
multiple_level_errors2 = sheerka.err(multiple_level_errors1)
multiple_level_errors3 = sheerka.err(multiple_level_errors2)
ret3 = sheerka.ret("Test5", False, multiple_level_errors3)
all_error_items = list(service.get_all_error_items([ret1, ret2, ret3]))
assert len(all_error_items) == 8
def test_i_can_keep_the_correct_error_source(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
simple_error = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=Concept("foo"))
ret1 = sheerka.ret("Source1", False, simple_error)
sub_error_is_an_exception = sheerka.new(BuiltinConcepts.NOT_FOR_ME, reason=NotImplementedError())
ret2 = sheerka.ret("Source2", False, sub_error_is_an_exception)
multiple_errors = sheerka.new(BuiltinConcepts.MULTIPLE_ERRORS, body=[ret1, ret2])
ret3 = sheerka.ret("Source3", False, multiple_errors)
error_items = list(service.get_errors_items([ret3]))
assert len(error_items) == 1
assert error_items[0].source == "Source3"
assert error_items[0].children[0].source == "Source1"
assert error_items[0].children[1].source == "Source2"
def test_i_can_get_filtered_error_explanation(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
simple_error = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=Concept("foo"))
ret1 = sheerka.ret("Test1", False, simple_error)
sub_error_is_an_exception = sheerka.new(BuiltinConcepts.NOT_FOR_ME, reason=NotImplementedError())
ret2 = sheerka.ret("Test2", False, sub_error_is_an_exception)
context.add_values(return_values=[ret1, ret2])
service.on_user_input_evaluated(context)
res = sheerka.get_last_errors(context)
assert sheerka.isinstance(res, BuiltinConcepts.EXPLANATION)
assert len(res.body) == 3
res = sheerka.get_last_errors(context, __type=BuiltinConcepts.UNKNOWN_CONCEPT)
assert sheerka.isinstance(res, BuiltinConcepts.EXPLANATION)
assert len(res.body) == 1
def test_i_cannot_recognize_error_when_no_error(self):
sheerka, context = self.init_test().unpack()
res = sheerka.recognize_error(context, "Some error")
assert sheerka.isinstance(res, BuiltinConcepts.NOT_FOUND)
def test_i_can_recognized_error_when_error_is_a_builtin_concept(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
simple_error = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=Concept("foo"))
ret1 = sheerka.ret("Test1", False, simple_error)
context.add_values(return_values=[ret1])
service.on_user_input_evaluated(context)
assert sheerka.recognize_error(context, __type=BuiltinConcepts.UNKNOWN_CONCEPT)
assert sheerka.recognize_error(context, __type=UnknownConcept)
assert sheerka.recognize_error(context, __type="UnknownConcept")
assert sheerka.recognize_error(context, BuiltinConcepts.UNKNOWN_CONCEPT)
assert sheerka.recognize_error(context, UnknownConcept)
assert sheerka.recognize_error(context, "UnknownConcept")
assert sheerka.recognize_error(context, f"self.name == '{BuiltinConcepts.UNKNOWN_CONCEPT}'")
assert sheerka.recognize_error(context, f"get_type(self) == '{BuiltinConcepts.UNKNOWN_CONCEPT}'")
def test_i_can_recognized_error_when_error_is_a_error_ErrorObj(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
simple_error = sheerka.new(BuiltinConcepts.ERROR, body=NoConditionFound())
ret1 = sheerka.ret("Test1", False, simple_error)
context.add_values(return_values=[ret1])
service.on_user_input_evaluated(context)
assert sheerka.recognize_error(context, __type=NoConditionFound)
assert sheerka.recognize_error(context, __type="NoConditionFound")
def test_i_cannot_recognize_unknown_concept_when_no_error(self):
sheerka, context = self.init_test().unpack()
res = sheerka.has_unknown_concepts(context)
assert sheerka.isinstance(res, BuiltinConcepts.NOT_FOUND)
def test_i_can_manage_direct_unknown_concept(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
exact_concept_parser = ExactConceptParser()
# a simple unknown concept
ret = exact_concept_parser.parse(context, ParserInput("foo bar"))
context.add_values(return_values=[ret])
service.on_user_input_evaluated(context)
assert sheerka.has_unknown_concepts(context)
assert sheerka.get_from_memory(context, LAST_UNKNOWN_CONCEPTS).obj == "foo bar"
def test_i_can_manage_multiple_direct_unknown_concept(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
exact_concept_parser = ExactConceptParser()
# a simple unknown concept
ret1 = exact_concept_parser.parse(context, ParserInput("foo"))
ret2 = exact_concept_parser.parse(context, ParserInput("bar"))
context.add_values(return_values=[ret1, ret2])
service.on_user_input_evaluated(context)
assert sheerka.has_unknown_concepts(context)
assert sheerka.get_from_memory(context, LAST_UNKNOWN_CONCEPTS).obj == {"foo", "bar"}
@pytest.mark.parametrize("source, expected", [
("foo bar", "foo"),
("bar foo", "foo"),
("foo bar baz", {"foo", "baz"}),
("my foo bar my baz", {"my foo", "my baz"}),
])
def test_i_can_manage_unknown_concept_when_parts_are_recognized(self, source, expected):
sheerka, context, foo = self.init_concepts("bar")
service = sheerka.services[SheerkaErrorManager.NAME]
exact_concept_parser = ExactConceptParser()
ret1 = exact_concept_parser.parse(context, ParserInput(source))
context.add_values(return_values=[ret1])
service.on_user_input_evaluated(context)
assert sheerka.has_unknown_concepts(context)
assert sheerka.get_from_memory(context, LAST_UNKNOWN_CONCEPTS).obj == expected
def test_i_can_manage_multiple_unknown_concept_when_parts_are_recognized(self):
sheerka, context, foo = self.init_concepts("bar")
service = sheerka.services[SheerkaErrorManager.NAME]
exact_concept_parser = ExactConceptParser()
ret1 = exact_concept_parser.parse(context, ParserInput("foo bar"))
ret2 = exact_concept_parser.parse(context, ParserInput("my foo bar my baz"))
context.add_values(return_values=[ret1, ret2])
service.on_user_input_evaluated(context)
assert sheerka.has_unknown_concepts(context)
assert sheerka.get_from_memory(context, LAST_UNKNOWN_CONCEPTS).obj == {"foo", "my foo", "my baz"}
def test_i_can_manage_unknown_concept_from_name_error(self):
sheerka, context = self.init_test().unpack()
service = sheerka.services[SheerkaErrorManager.NAME]
python_parser = PythonParser()
python_evaluator = PythonEvaluator()
#
ret = python_parser.parse(context, ParserInput("foo"))
ret = python_evaluator.eval(context, ret)
context.add_values(return_values=[ret])
service.on_user_input_evaluated(context)
assert sheerka.has_unknown_concepts(context)
assert sheerka.get_from_memory(context, LAST_UNKNOWN_CONCEPTS).obj == "foo"
def test_i_can_manage_unknown_concept_from_bnf_error(self):
sheerka, context, quantify_x = self.init_concepts(
Concept("quantify x", definition="('one' | 'two')=unit x").def_var("x"),
create_new=True
)
service = sheerka.services[SheerkaErrorManager.NAME]
bnf_node_parser = BnfNodeParser()
# a simple unknown concept
ret = bnf_node_parser.parse(context, ParserInput("one foo"))
context.add_values(return_values=[ret])
service.on_user_input_evaluated(context)
assert sheerka.has_unknown_concepts(context)
assert sheerka.get_from_memory(context, LAST_UNKNOWN_CONCEPTS).obj == "foo"
class TestFileBasedSheerkaErrorManger(TestUsingFileBasedSheerka):
def test_i_can_remember_last_error(self):
sheerka = self.get_sheerka()
context = self.get_context(message="TestingErrorManagement::get_last_errors()")
service = sheerka.services[SheerkaErrorManager.NAME]
simple_error = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=Concept("foo"))
ret1 = sheerka.ret("Test1", False, simple_error)
sub_error_is_an_exception = sheerka.new(BuiltinConcepts.NOT_FOR_ME, reason=NotImplementedError())
ret2 = sheerka.ret("Test2", False, sub_error_is_an_exception)
context.add_values(return_values=[ret1, ret2])
service.on_user_input_evaluated(context)
new_context = self.get_context(sheerka)
explanations = sheerka.get_last_errors(new_context)
self.commit(context)
assert sheerka.isinstance(explanations, BuiltinConcepts.EXPLANATION)
assert len(explanations.body) == 3
assert service.last_detected_errors_event_id == context.event.get_digest()
assert service.last_detected_errors_event_message == context.event.message
assert service.current_errors_event_id == context.event.get_digest()
assert service.current_errors_event_message == context.event.message
sheerka = self.new_sheerka_instance()
service = sheerka.services[SheerkaErrorManager.NAME]
assert service.last_detected_errors_event_id == context.event.get_digest()
assert service.last_detected_errors_event_message == context.event.message
assert service.current_errors_event_id == context.event.get_digest()
assert service.current_errors_event_message == context.event.message
self.reset_hard_test_env()
+7 -3
View File
@@ -21,7 +21,9 @@ class TestSheerkaHistoryManager(TestUsingFileBasedSheerka):
sheerka.evaluate_user_input("five")
h = list(service.history(-1)) # all
assert h == [
transformed_h = [hist(item.event.message, item.status) for item in h]
assert transformed_h == [
hist("five", True),
hist("def concept five as 5", True),
hist("four", True),
@@ -37,13 +39,15 @@ class TestSheerkaHistoryManager(TestUsingFileBasedSheerka):
]
h = list(service.history(2))
assert h == [
transformed_h = [hist(item.event.message, item.status) for item in h]
assert transformed_h == [
hist("five", True),
hist("def concept five as 5", True)
]
h = list(service.history(2, 2))
assert h == [
transformed_h = [hist(item.event.message, item.status) for item in h]
assert transformed_h == [
hist("four", True),
hist("def concept four as 4", True),
]
+29 -3
View File
@@ -3,11 +3,10 @@ from core.builtin_helpers import ensure_evaluated
from core.concept import Concept
from core.global_symbols import NotFound
from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka.services.SheerkaMemory import SheerkaMemory, MemoryObject
from core.sheerka.services.SheerkaMemory import MemoryObject, SheerkaMemory
from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
from tests.parsers.parsers_utils import CV, compare_with_test_object, CB
from tests.parsers.parsers_utils import CB, CV, compare_with_test_object
class TestSheerkaMemory(TestUsingMemoryBasedSheerka):
@@ -92,6 +91,33 @@ class TestSheerkaMemory(TestUsingMemoryBasedSheerka):
foo)}
assert id(sheerka.get_from_memory(context, "a").obj) == id(foo)
# another object is appended
bar = Concept("bar")
sheerka.add_to_memory(context, "a", bar)
assert sheerka.om.copy(SheerkaMemory.OBJECTS_ENTRY) == {
"a": [
MemoryObject(context.event.get_digest(), context.event.date.timestamp(), foo),
MemoryObject(context.event.get_digest(), context.event.date.timestamp(), bar)
]}
def test_i_can_set_and_retrieve_from_memory(self):
sheerka, context = self.init_test().unpack()
assert sheerka.get_from_memory(context, "a") is NotFound
foo = Concept("foo")
sheerka.set_in_memory(context, "a", foo)
assert sheerka.om.copy(SheerkaMemory.OBJECTS_ENTRY) == {"a": MemoryObject(context.event.get_digest(),
context.event.date.timestamp(),
foo)}
assert id(sheerka.get_from_memory(context, "a").obj) == id(foo)
# another object replace the first one
sheerka.set_in_memory(context, "a", "foo")
assert sheerka.om.copy(SheerkaMemory.OBJECTS_ENTRY) == {"a": MemoryObject(context.event.get_digest(),
context.event.date.timestamp(),
"foo")}
def test_i_can_use_memory_with_a_string(self):
sheerka, context = self.init_test().unpack()
+23
View File
@@ -2,6 +2,7 @@ from dataclasses import dataclass
import pytest
from core.builtin_concepts import NotFoundConcept
from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import Concept
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -143,6 +144,28 @@ class TestSheerkaQueryManager(TestUsingMemoryBasedSheerka):
res = sheerka.filter_objects(context, lst, mapping=lambda o: o.prop1, predicate="self is a concept")
assert res == [lst[1], lst[3]]
def test_i_can_filter_objects_by_type(self):
sheerka, context = self.init_test().unpack()
lst = [A("a11", "a12"), Concept("foo"), NotImplementedError(), NotFoundConcept()]
assert sheerka.filter_objects(context, lst, __type="A") == [lst[0]]
assert sheerka.filter_objects(context, lst, __type="foo") == [lst[1]]
assert sheerka.filter_objects(context, lst, __type="NotImplementedError") == [lst[2]]
assert sheerka.filter_objects(context, lst, __type=NotImplementedError) == [lst[2]]
assert sheerka.filter_objects(context, lst, __type=BuiltinConcepts.NOT_FOUND) == [lst[3]]
assert sheerka.filter_objects(context, lst, __type=NotFoundConcept) == [lst[3]]
assert sheerka.filter_objects(context, lst, __type="NotFoundConcept") == [lst[3]]
def test_empty_list_is_returned_when_nothing_is_found(self):
sheerka, context = self.init_test().unpack()
lst = [A("a11", "a12")]
assert sheerka.filter_objects(context, lst, __type="foo") == []
assert sheerka.filter_objects(context, [], __type="foo") == []
assert sheerka.filter_objects(context, lst, predicate="self.name == 'bar'") == []
def test_i_must_select_object_property_using_string(self):
sheerka, context = self.init_test().unpack()
+10 -10
View File
@@ -345,23 +345,23 @@ class TestSheerkaRuleManager(TestUsingMemoryBasedSheerka):
("not __ret.status", "__ret", [NEGCOND("#__x_00__|__name__|'__ret.status'")]),
("__ret and not __ret.status", "__ret",
["#__x_00__|__name__|'__ret'", NEGCOND("#__x_01__|__name__|'__ret.status'")]),
("not recognize(__ret.body, hello sheerka)", "__ret", ["#__x_00__|__name__|'__ret'",
NCCOND(["#__x_00__|body|#__x_01__",
"#__x_01__|__is_concept__|True",
"#__x_01__|key|'hello __var__0'",
"#__x_01__|a|'__sheerka__'"])]),
("__ret and not __error", "__ret", ["#__x_00__|__name__|'__ret'", NEGCOND("#__x_01__|__name__|'__error'")]),
("not recognize(self, hello sheerka)", "__ret", ["#__x_00__|__name__|'self'",
NCCOND(["#__x_00__|__is_concept__|True",
"#__x_00__|key|'hello __var__0'",
"#__x_00__|a|'__sheerka__'"])]),
# ("not recognize(__ret.body, hello sheerka)", "__ret", ["#__x_00__|__name__|'__ret'",
# NCCOND(["#__x_00__|body|#__x_01__",
# "#__x_01__|__is_concept__|True",
# "#__x_01__|key|'hello __var__0'",
# "#__x_01__|a|'__sheerka__'"])]),
# ("not recognize(self, hello sheerka)", "__ret", ["#__x_00__|__name__|'self'",
# NCCOND(["#__x_00__|__is_concept__|True",
# "#__x_00__|key|'hello __var__0'",
# "#__x_00__|a|'__sheerka__'"])]),
])
def test_i_can_get_rete_using_not(self, expression, bag_key, expected):
sheerka, context, greetings, foo = self.init_test().with_concepts(
Concept("greetings", definition="hello a", definition_type=DEFINITION_TYPE_DEF).def_var("a"),
Concept("foo"),
).unpack()
parser = ExpressionParser(old_style=True)
parser = ExpressionParser()
expected_conditions = get_rete_conditions(*expected)
error_sink = ErrorSink()
@@ -25,7 +25,7 @@ class BaseTestSheerkaRuleManagerRulesCompilation(TestUsingMemoryBasedSheerka):
@staticmethod
def get_conditions(context, expression):
parser = ExpressionParser(old_style=True)
parser = ExpressionParser()
error_sink = ErrorSink()
parser_input = ParserInput(expression)
parser.reset_parser_input(parser_input, error_sink)
@@ -927,7 +927,7 @@ class TestSheerkaRuleManagerRulesCompilationRecognizeConcept(BaseTestSheerkaRule
Concept("my best friend"),
create_new=True
).unpack()
parser = ExpressionParser(old_style=True)
parser = ExpressionParser()
expected = get_rete_conditions(*expected_as_list_of_str)
error_sink = ErrorSink()
+15 -156
View File
@@ -3,14 +3,13 @@ import os
import pytest
from conftest import SHEERKA_TEST_FOLDER
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, UnknownConcept, UserInputConcept
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, UserInputConcept
from core.builtin_concepts_ids import AllBuiltinConcepts
from core.concept import Concept, ConceptParts, PROPERTIES_TO_SERIALIZE, get_concept_attrs
from core.global_symbols import NotInit
from core.sheerka.Sheerka import BASE_NODE_PARSER_CLASS, Sheerka
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager, ValueNotFound
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager
from core.tokenizer import Token, TokenKind
from parsers.PythonParser import PythonErrorNode
from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -44,21 +43,25 @@ class TestSheerkaUsingMemoryBasedSheerka(TestUsingMemoryBasedSheerka):
def test_i_can_list_builtin_concepts(self):
sheerka = self.get_sheerka()
builtins = list(sheerka.get_builtins_classes_as_dict())
builtins, _ = sheerka.get_builtins_classes_as_dict()
assert str(BuiltinConcepts.ERROR) in builtins
assert str(BuiltinConcepts.RETURN_VALUE) in builtins
assert BuiltinConcepts.ERROR in builtins
assert BuiltinConcepts.RETURN_VALUE in builtins
def test_i_can_get_a_builtin_concept_by_their_enum_or_the_string(self):
"""
Checks that a concept can be found its name
even when there are variables in the name (ex 'hello + a' or 'a + b' )
Also returns the mapping between concept name and specific class name
:return:
"""
sheerka = self.get_sheerka()
for key in sheerka.get_builtins_classes_as_dict():
builtins_by_key, builtins_by_class_name = sheerka.get_builtins_classes_as_dict()
for key in builtins_by_key:
assert sheerka.get_by_key(key) is not None
assert sheerka.get_by_key(str(key)) is not None
for k, v in builtins_by_class_name.items():
assert type(sheerka.get_by_key(v)).__name__ == k
def test_i_cannot_get_when_key_is_none(self):
sheerka = self.get_sheerka()
@@ -98,7 +101,8 @@ class TestSheerkaUsingMemoryBasedSheerka(TestUsingMemoryBasedSheerka):
assert ret.value == "value"
# check the others
for key, concept_class in sheerka.get_builtins_classes_as_dict().items():
builtins_by_key, _ = sheerka.get_builtins_classes_as_dict()
for key, concept_class in builtins_by_key.items():
assert isinstance(sheerka.get_by_key(key), concept_class)
def test_i_can_instantiate_a_builtin_concept_when_no_specific_class(self):
@@ -426,151 +430,6 @@ class TestSheerkaUsingMemoryBasedSheerka(TestUsingMemoryBasedSheerka):
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.ONTOLOGY_ALREADY_DEFINED)
@pytest.mark.parametrize("obj, expected", [
("a string", []),
(True, []),
(False, []),
(Concept("foo"), []),
(Concept("foo", body=False).auto_init(), []),
(UnknownConcept(), [UnknownConcept()]),
(Concept("foo", body=UnknownConcept()).auto_init(), [UnknownConcept()]),
(PythonErrorNode("msg", None), [PythonErrorNode("msg", None)])
])
def test_i_can_get_error_for_simple_objects(self, obj, expected):
sheerka, context = self.init_test().unpack()
assert sheerka.get_errors(context, obj) == expected
def test_i_can_get_error_when_builtin_concept_in_error(self):
sheerka, context = self.init_test().unpack()
obj = sheerka.new(BuiltinConcepts.ONTOLOGY_ALREADY_DEFINED)
assert sheerka.get_errors(context, obj) == [obj]
def test_i_can_get_error_when_return_value(self):
sheerka, context = self.init_test().unpack()
error = sheerka.err("an error")
ret_val = ReturnValueConcept("Test", False, sheerka.err("an error"))
assert sheerka.get_errors(context, ret_val) == [error]
def test_i_can_get_inner_error(self):
sheerka, context = self.init_test().unpack()
error = sheerka.err("an error")
ret_val = ReturnValueConcept("Test", False, sheerka.err("an error"))
assert sheerka.get_errors(context, ret_val) == [error]
def test_i_can_get_error_when_embedded_errors(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT)
not_an_error = sheerka.new(BuiltinConcepts.AUTO_EVAL)
error = sheerka.err([concept_eval_error, unknown_concept, not_an_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_errors(context, ret_val)
assert errors_found == [error, concept_eval_error, unknown_concept]
def test_i_can_get_error_from_list(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT)
not_an_error = sheerka.new(BuiltinConcepts.AUTO_EVAL)
error = sheerka.err([concept_eval_error, unknown_concept, not_an_error])
ret_val_1 = ReturnValueConcept("Test", False, error)
python_error = PythonErrorNode("msg", Exception())
value_not_found = ValueNotFound("item", "value")
multiple_error = sheerka.new(BuiltinConcepts.MULTIPLE_ERRORS, body=[python_error, value_not_found])
ret_val_2 = ReturnValueConcept("Test", False, multiple_error)
errors_found = sheerka.get_errors(context, [ret_val_1, ret_val_2])
assert errors_found == [error, concept_eval_error, unknown_concept,
multiple_error, python_error, value_not_found]
def test_i_can_filter_error_by_concept_key(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT)
python_error = PythonErrorNode("msg", Exception())
error = sheerka.err([concept_eval_error, unknown_concept, python_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_errors(context, ret_val, __type=BuiltinConcepts.CONCEPT_EVAL_ERROR)
assert errors_found == [concept_eval_error]
def test_i_can_filter_error_by_class_name(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT)
python_error = PythonErrorNode("msg", Exception())
error = sheerka.err([concept_eval_error, unknown_concept, python_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_errors(context, ret_val, __type="PythonErrorNode")
assert errors_found == [python_error]
def test_i_can_filter_error_by_concept_attribute(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, concept_ref="a_concept_ref")
python_error = PythonErrorNode("msg", Exception())
error = sheerka.err([concept_eval_error, unknown_concept, python_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_errors(context, ret_val, concept_ref="a_concept_ref")
assert errors_found == [unknown_concept]
def test_i_can_filter_error_by_class_attribute(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, concept_ref="a_concept_ref")
python_error = PythonErrorNode("error source", Exception())
error = sheerka.err([concept_eval_error, unknown_concept, python_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_errors(context, ret_val, source="error source")
assert errors_found == [python_error]
def test_i_can_filter_error_by_exception_name(self):
sheerka, context = self.init_test().unpack()
from evaluators.PythonEvaluator import PythonEvalError
name_error = NameError("foo")
python_eval_error = PythonEvalError(name_error, "foo", None, None)
error = sheerka.err([python_eval_error])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_errors(context, ret_val, __type="NameError")
assert errors_found == [name_error]
def test_i_can_filter_error_on_multiple_criteria(self):
sheerka, context = self.init_test().unpack()
concept_eval_error = sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR)
unknown_concept = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, concept_ref="a_concept_ref")
value_not_found = ValueNotFound("an_item", "a value")
error = sheerka.err([concept_eval_error, unknown_concept, value_not_found])
ret_val = ReturnValueConcept("Test", False, error)
errors_found = sheerka.get_errors(context, ret_val, __type="ValueNotFound", item="an_item", value="a value")
assert errors_found == [value_not_found]
def test_i_cannot_get_error_when_return_value_s_status_is_true(self):
sheerka, context = self.init_test().unpack()
ret_val = ReturnValueConcept("Test", True, sheerka.err("an error"))
assert sheerka.get_errors(context, ret_val) == []
def test_i_can_add_and_remove_global_context_hints(self):
sheerka, context = self.init_test().unpack()
@@ -592,7 +451,7 @@ class TestSheerkaUsingMemoryBasedSheerka(TestUsingMemoryBasedSheerka):
).unpack()
res = sheerka.evaluate_user_input("eval foo", "testing_user")
assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONDITION_FAILED) # sanity check
assert sheerka.isinstance(res[0].value.value, BuiltinConcepts.CONDITION_FAILED) # sanity check
sheerka.add_to_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED)
res = sheerka.evaluate_user_input("eval foo", "testing_user")
@@ -600,7 +459,7 @@ class TestSheerkaUsingMemoryBasedSheerka(TestUsingMemoryBasedSheerka):
sheerka.remove_fom_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED)
res = sheerka.evaluate_user_input("eval foo", "testing_user")
assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONDITION_FAILED)
assert sheerka.isinstance(res[0].value.value, BuiltinConcepts.CONDITION_FAILED)
class TestSheerkaUsingFileBasedSheerka(TestUsingFileBasedSheerka):
+6 -6
View File
@@ -1316,11 +1316,11 @@ class TestSheerkaOntology(TestUsingMemoryBasedSheerka):
sheerka.create_new_rule(context, Rule(ACTION_TYPE_EXEC, "rule3", "id3.attr3 == 'value'", "True"))
sheerka.pop_ontology(context)
assert rules_by_ontology_from_cache() == {'#unit_test#': {'10'}}
assert ontologies_from_cache() == {'10': '#unit_test#'}
assert rules_by_ontology_from_cache() == {'#unit_test#': {'11'}}
assert ontologies_from_cache() == {'11': '#unit_test#'}
# check that the 'rule is deleted' events are raised
assert events_raised == {'11', '12'}
assert events_raised == {'12', '13'}
# def test_i_can_list_by_key_when_dictionaries(self):
# sheerka = self.get_sheerka(cache_only=False)
@@ -1664,6 +1664,6 @@ class TestSheerkaOntologyWithFileBasedSheerka(TestUsingFileBasedSheerka):
rules_from_db = sheerka.om.self_cache_manager.sdp.get(SheerkaOntologyManager.RULES_BY_ONTOLOGY_ENTRY)
del rules_from_db["__default__"]
assert rules_from_db == {
'#unit_test#': {'10'},
'another ontology': {'13'},
'new ontology': {'12'}}
'#unit_test#': {'11'},
'another ontology': {'14'},
'new ontology': {'13'}}
+7 -5
View File
@@ -49,7 +49,8 @@ class TestOneErrorEvaluator(TestUsingMemoryBasedSheerka):
res = evaluator.eval(context, return_values)
assert not res.status
assert res.body == "evaluators.one error"
assert context.sheerka.isinstance(res.body, BuiltinConcepts.RETURN_VALUE)
assert res.body.body == "evaluators.one error"
assert len(res.parents) == 4
def test_unwanted_return_values_are_not_eaten(self):
@@ -71,7 +72,8 @@ class TestOneErrorEvaluator(TestUsingMemoryBasedSheerka):
res = evaluator.eval(context, return_values)
assert not res.status
assert res.body == "evaluators.one error"
assert context.sheerka.isinstance(res.body, BuiltinConcepts.RETURN_VALUE)
assert res.body.body == "evaluators.one error"
assert len(res.parents) == 4
assert a_successful_concept not in res.parents
@@ -89,7 +91,7 @@ class TestOneErrorEvaluator(TestUsingMemoryBasedSheerka):
assert evaluator.matches(context, return_values)
res = evaluator.eval(context, return_values)
assert not res.status
assert res.body == false_1.body
assert res.body.body == false_1.body
assert res.parents == return_values
evaluator.reset()
@@ -97,7 +99,7 @@ class TestOneErrorEvaluator(TestUsingMemoryBasedSheerka):
assert evaluator.matches(context, return_values)
res = evaluator.eval(context, return_values)
assert not res.status
assert res.body == filtered
assert res.body.body == filtered
assert res.parents == return_values
evaluator.reset()
@@ -105,5 +107,5 @@ class TestOneErrorEvaluator(TestUsingMemoryBasedSheerka):
assert evaluator.matches(context, return_values)
res = evaluator.eval(context, return_values)
assert not res.status
assert res.body == false_1.body
assert res.body.body == false_1.body
assert res.parents == return_values
+5 -5
View File
@@ -11,7 +11,7 @@ from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import Tokenizer
from evaluators.PythonEvaluator import PythonEvaluator, PythonEvalError
from parsers.BaseNodeParser import SourceCodeNode, SourceCodeWithConceptNode
from parsers.FunctionParserOld import FunctionParserOld
from parsers.FunctionParser import FunctionParser
from parsers.PythonParser import PythonNode, PythonParser
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
from tests.parsers.parsers_utils import CB, compare_with_test_object
@@ -318,7 +318,7 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka):
context = self.get_context()
context.add_to_short_term_memory("get_obj_name", get_obj_name)
parsed = FunctionParserOld().parse(context, ParserInput("get_obj_name(r:|1:)"))
parsed = FunctionParser().parse(context, ParserInput("get_obj_name(r:|1:)"))
python_evaluator = PythonEvaluator()
evaluated = python_evaluator.eval(context, parsed)
@@ -344,7 +344,7 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka):
context = self.get_context()
context.add_to_short_term_memory("return_return_value", return_return_value)
parsed = FunctionParserOld().parse(context, ParserInput(method))
parsed = FunctionParser().parse(context, ParserInput(method))
python_evaluator = PythonEvaluator()
evaluated = python_evaluator.eval(context, parsed)
ret_val = return_return_value(expected_status)
@@ -371,9 +371,9 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka):
@pytest.mark.parametrize("parser, value", [
(PythonParser(), "3"),
(FunctionParserOld(), "3"),
(FunctionParser(), "3"),
(PythonParser(), 3),
(FunctionParserOld(), 3),
(FunctionParser(), 3),
])
def test_i_can_eval_unresolved_rules(self, parser, value):
context = self.get_context()
@@ -9,6 +9,7 @@ from evaluators.ValidateConceptEvaluator import ValidateConceptEvaluator
from parsers.BaseNodeParser import ConceptNode
from parsers.BaseParser import BaseParser
from parsers.BnfNodeParser import BnfNodeParser
from parsers.ExactConceptParser import ExactConceptParser
from parsers.SyaNodeParser import SyaNodeParser
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
from tests.evaluators.EvaluatorTestsUtils import p_ret_val, pr_ret_val, ret_val
@@ -185,3 +186,15 @@ class TestValidateConceptEvaluator(TestUsingMemoryBasedSheerka):
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.FILTERED)
assert res.body.body == ret_val.body.body
def test_i_can_eval_exact_parser_concepts(self):
sheerka, context, explain_x = self.init_concepts(
Concept("explain x", where="isinstance(x, int)").def_var("x")
)
evaluator = ValidateConceptEvaluator()
ret_val = ExactConceptParser().parse(context, ParserInput("explain 1"))[0]
assert evaluator.matches(context, ret_val)
res = evaluator.eval(context, ret_val)
assert sheerka.isinstance(res.body.body, explain_x)
+6 -6
View File
@@ -166,7 +166,7 @@ as:
assert len(res) == 1
assert not res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED)
assert sheerka.isinstance(res[0].value.value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED)
@pytest.mark.parametrize("text", [
"",
@@ -740,7 +740,7 @@ as:
res = sheerka.evaluate_user_input("eval foobar") # where clause is forced
assert len(res) == 1 # error
assert not res[0].status
assert sheerka.isinstance(res[0].body, BuiltinConcepts.CONDITION_FAILED)
assert sheerka.isinstance(res[0].body.value, BuiltinConcepts.CONDITION_FAILED)
@pytest.mark.skip("Not ready for that")
def test_i_can_manage_missing_variables_from_bnf_parsing(self):
@@ -921,15 +921,15 @@ as:
res = sheerka.evaluate_user_input("eval foo plus one")
assert not res[0].status
assert context.sheerka.isinstance(res[0].body, BuiltinConcepts.CONCEPT_EVAL_ERROR)
assert context.sheerka.isinstance(res[0].body.body, BuiltinConcepts.ERROR)
assert context.sheerka.isinstance(res[0].body.body, BuiltinConcepts.CONCEPT_EVAL_ERROR)
assert context.sheerka.isinstance(res[0].body.body.body, BuiltinConcepts.ERROR)
error0 = res[0].body.body.body[0]
error0 = res[0].body.body.body.body[0]
assert isinstance(error0, PythonEvalError)
assert isinstance(error0.error, TypeError)
assert error0.error.args[0] == "unsupported operand type(s) for +: 'Concept' and 'int'"
error1 = res[0].body.body.body[1]
error1 = res[0].body.body.body.body[1]
assert isinstance(error1, PythonEvalError)
assert isinstance(error1.error, TypeError)
assert error1.error.args[0] == 'can only concatenate str (not "int") to str'
+1 -1
View File
@@ -123,7 +123,7 @@ class TestSheerkaNonRegMemory2(TestUsingMemoryBasedSheerka):
assert len(res) == 1
assert not res[0].status
assert isinstance(res[0].body, ConceptEvalError)
assert isinstance(res[0].body.body, ConceptEvalError)
def test_74_keyword_parameters_are_no_longer_recognized_when_a_concept_that_redefines_equality_is_created(self):
init = [
+39
View File
@@ -1,3 +1,7 @@
from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import Concept
from core.sheerka.services.SheerkaErrorManager import SheerkaErrorManager
from core.utils import no_color_str
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -200,4 +204,39 @@ two: (1002)two
assert captured.out == """DebugItem(type=vars, setting=Sya.parsers.*, context_id=45, debug_id=None, context_children=False, debug_children=False (enabled=True))
DebugItem(type=concepts, setting=c:|1015.*.*, context_id=13, debug_id=None, context_children=True, debug_children=False (enabled=True))
DebugItem(type=rules, setting=Out.*.*, context_id=None, debug_id=None, context_children=False, debug_children=False (enabled=True))
"""
def test_i_can_display_errors(self, capsys):
sheerka = self.get_sheerka()
context = self.get_context(message="xxxTESTxxx::get_last_errors()")
service = sheerka.services[SheerkaErrorManager.NAME]
simple_error = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=Concept("foo"))
ret1 = sheerka.ret("Test1", False, simple_error)
multiple_level_errors1 = sheerka.err("Err1")
multiple_level_errors2 = sheerka.err(multiple_level_errors1)
multiple_level_errors3 = sheerka.err(multiple_level_errors2)
ret2 = sheerka.ret("Test5", False, multiple_level_errors3)
context.add_values(return_values=[ret1, ret2])
service.on_user_input_evaluated(context)
sheerka.enable_process_return_values = True
sheerka.evaluate_user_input("get_last_errors(level=0)")
captured = capsys.readouterr()
colorless_captured = no_color_str(captured.out)
assert colorless_captured == """xxx : xxxTESTxxx::get_last_errors()
[ 0] Test1 (47)__UNKNOWN_CONCEPT: (None)foo
[ 1] Test5 (46)__ERROR: (46)__ERROR: (46)__ERROR: Err1
"""
sheerka.evaluate_user_input("get_last_errors(id=1, depth=3)")
captured = capsys.readouterr()
colorless_captured = no_color_str(captured.out)
assert colorless_captured == """xxx : xxxTESTxxx::get_last_errors()
[ 1] Test5 (46)__ERROR: (46)__ERROR: (46)__ERROR: Err1
[ 2] Test5 (46)__ERROR: (46)__ERROR: Err1
[ 3] Test5 (46)__ERROR: Err1
"""
-95
View File
@@ -10,12 +10,10 @@ from core.tokenizer import Token, TokenKind, Tokenizer
from core.utils import get_text_from_tokens, str_concept, tokens_index
from parsers.BaseExpressionParser import AndNode, BinaryNode, ComparisonNode, ComparisonType, Comprehension, \
FunctionNode, \
FunctionParameter, \
ListComprehensionNode, ListNode, NameExprNode, \
NotNode, OrNode, SequenceNode, VariableNode, t_comma
from parsers.BaseNodeParser import ConceptNode, RuleNode, SourceCodeNode, SourceCodeWithConceptNode, \
UnrecognizedTokensNode
from parsers.FunctionParserOld import FunctionNodeOld
from parsers.PythonParser import PythonNode
from sheerkapython.python_wrapper import sheerka_globals
from sheerkarete.common import V
@@ -387,99 +385,6 @@ class LC(ExprTestObj): # for List Comprehension node
return ListComprehensionNode(start, end, full_text_as_tokens[start: end + 1], element, comprehensions)
class FNOld(ExprTestObj):
"""
Test class only
It matches with FunctionNodeOld but with less constraints
Thereby,
FNOld("first", "last", ["param1," ...]) can be compared to
FunctionNodeOld(NameExprNode("first"), NameExprNode("second"), [FunctionParameter(NamesNodes("param1"), NamesNodes(", ")])
Note that FunctionParameter can easily be defined with a single string
* "param" -> FunctionParameter(NameExprNode("param"), None)
* "param, " -> FunctionParameter(NameExprNode("param"), NameExprNode(", "))
For more complicated situations, you can use a tuple (value, sep) to define the value part and the separator part
"""
def __init__(self, first, last, parameters):
self.first = first
self.last = last
self.parameters = []
for param in parameters:
if isinstance(param, tuple):
self.parameters.append(param)
elif isinstance(param, str) and (pos := param.find(",")) != -1:
self.parameters.append((param[:pos], param[pos:]))
else:
self.parameters.append((param, None))
def __repr__(self):
res = self.first
for param in self.parameters:
if param[1]:
res += f"{param[0]}{param[1]} "
else:
res += f"{param[0]}"
return res + self.last
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, FNOld):
return self.first == other.first and self.last == other.last and self.parameters == other.parameters
return False
def __hash__(self):
return hash((self.first, self.last, self.parameters))
def transform_real_obj(self, other, get_test_obj_delegate):
if isinstance(other, FNOld):
return other
if isinstance(other, FunctionNodeOld):
params = []
for self_parameter, other_parameter in zip(self.parameters, other.parameters):
if isinstance(self_parameter[0], str):
value = other_parameter.value.value
else:
value = get_test_obj_delegate(other_parameter.value, self_parameter[0])
sep = other_parameter.separator.value if other_parameter.separator else None
params.append((value, sep))
return FNOld(other.first.value, other.last.value, params)
raise Exception(f"Expecting FunctionNodeOld but received {other=}")
def get_expr_node(self, full_text_as_tokens, default_expr_obj):
start, end = self.get_pos_from_source(self.first, full_text_as_tokens)
first = NameExprNode(start, end, full_text_as_tokens[start: end + 1])
start, end = self.get_pos_from_source(self.last, full_text_as_tokens)
last = NameExprNode(start, end, full_text_as_tokens[start: end + 1])
parameters = []
for param_value, sep in self.parameters:
if isinstance(param_value, str):
start, end = self.get_pos_from_source(param_value, full_text_as_tokens)
param_as_expr_node = NameExprNode(start, end, full_text_as_tokens[start: end + 1])
else:
param_as_expr_node = param_value.get_expr_node(full_text_as_tokens, default_expr_obj)
if sep:
sep_tokens = Tokenizer(sep, yield_eof=False)
start = param_as_expr_node.end + 1
end = start + len(list(sep_tokens)) - 1
sep_as_expr_node = NameExprNode(start, end, full_text_as_tokens[start: end + 1])
else:
sep_as_expr_node = None
parameters.append(FunctionParameter(param_as_expr_node, sep_as_expr_node))
start, end = first.start, last.end
return FunctionNodeOld(start, end, full_text_as_tokens[start: end + 1], first, last, parameters)
class HelperWithPos:
def __init__(self, start=None, end=None):
self.start = start
+40 -1
View File
@@ -9,6 +9,7 @@ from core.global_symbols import NotInit
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager
from core.sheerka.services.SheerkaExecute import ParserInput
from core.sheerka.services.SheerkaIsAManager import SheerkaIsAManager
from core.tokenizer import Tokenizer
from parsers.BaseNodeParser import NoMatchingTokenError
from parsers.BnfDefinitionParser import BnfDefinitionParser
from parsers.BnfNodeParser import BnfNodeFirstTokenVisitor, BnfNodeParser, ConceptExpression, Match, NonTerminalNode, \
@@ -1075,6 +1076,44 @@ class TestBnfNodeParser(TestUsingMemoryBasedSheerka):
expected = [CNC("foo", "one onyx", x=CC("one"))]
self.validate_get_concepts_sequences(my_map, text, expected)
def test_i_can_match_regex_when_sub_parser_input(self):
my_map = {
"foo": self.bnf_concept("foo", RegExMatch("[a-f0-9]{4}")),
}
text = "begin 0af0 end"
parser_input = ParserInput(text).reset()
sub_parser = parser_input.sub_part(2, 3)
sheerka, context, parser = self.init_parser(my_map)
res = parser.parse(context, sub_parser)
assert res.status
concept_nodes = res.body.body
expected = [CN("foo", "0af0")]
actual, expected = tests.parsers.parsers_utils.prepare_nodes_comparison(my_map, text, concept_nodes, expected)
assert actual == expected
def test_i_can_match_regex_when_from_tokens(self):
my_map = {
"foo": self.bnf_concept("foo", RegExMatch("[a-f0-9]{4}")),
}
text = "begin 0af0 end"
tokens = list(Tokenizer(text))
parser_input = ParserInput(None, tokens[2:4]).reset()
sheerka, context, parser = self.init_parser(my_map)
res = parser.parse(context, parser_input)
assert res.status
concept_nodes = res.body.body
expected = [CN("foo", "0af0")]
actual, expected = tests.parsers.parsers_utils.prepare_nodes_comparison(my_map, "0af0", concept_nodes, expected)
assert actual == expected
def test_i_can_reuse_the_same_variable(self):
# in this test, the variable appears several times, but only once in concept.compiled
my_map = {
@@ -1831,7 +1870,7 @@ class TestBnfNodeParser(TestUsingMemoryBasedSheerka):
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.NOT_FOR_ME)
assert res.body.reason == [NoMatchingTokenError(4)]
assert res.body.reason == [NoMatchingTokenError(4, concept=foo)]
@pytest.mark.parametrize("text", [
"one",
+2 -2
View File
@@ -16,7 +16,7 @@ from parsers.BnfNodeParser import OrderedChoice, ConceptExpression, StrMatch, Se
from parsers.DefConceptParser import DefConceptParser, NameNode, SyntaxErrorNode, CannotHandleParsingError
from parsers.DefConceptParser import UnexpectedTokenParsingError, DefConceptNode
from parsers.ExpressionParser import ExpressionParser
from parsers.FunctionParserOld import FunctionParserOld
from parsers.FunctionParser import FunctionParser
from parsers.PythonParser import PythonParser, PythonNode
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
from tests.parsers.parsers_utils import compute_expected_array, SCWC, compare_with_test_object, CIO
@@ -77,7 +77,7 @@ def get_concept_part(part, use_expression=False):
status=True,
value=ParserResultConcept(
source=part.source,
parser=FunctionParserOld(),
parser=FunctionParser(),
value=nodes[0],
try_parsed=nodes[0]))
-249
View File
@@ -1,249 +0,0 @@
import pytest
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept
from core.sheerka.services.SheerkaExecute import ParserInput
from parsers.BaseNodeParser import SourceCodeWithConceptNode
from parsers.BaseParser import ErrorSink
from parsers.FunctionParserOld import FunctionParserOld
from parsers.PythonParser import PythonErrorNode
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
from tests.parsers.parsers_utils import compute_expected_array, SCN, SCWC, CN, UTN, CNC, RN, FNOld, get_test_obj, \
get_expr_node_from_test_node
cmap = {
"one": Concept("one"),
"two": Concept("two"),
"twenties": Concept("twenties", definition="'twenty' (one|two)=unit").def_var("unit"),
"plus": Concept("a plus b").def_var("a").def_var("b"),
}
class TestFunctionParserOld(TestUsingMemoryBasedSheerka):
shared_ontology = None
@classmethod
def setup_class(cls):
init_test_helper = cls().init_test(cache_only=False, ontology="#TestFunctionParserOld#")
sheerka, context, *updated = init_test_helper.with_concepts(*cmap.values(), create_new=True).unpack()
for i, concept_name in enumerate(cmap):
cmap[concept_name] = updated[i]
cls.shared_ontology = sheerka.get_ontology(context)
sheerka.pop_ontology(context)
def init_parser(self, my_concepts_map=None, **kwargs):
if my_concepts_map is None:
sheerka, context = self.init_test().unpack()
sheerka.add_ontology(context, self.shared_ontology)
else:
sheerka, context, *updated = self.init_test().with_concepts(*my_concepts_map.values(), **kwargs).unpack()
for i, pair in enumerate(my_concepts_map):
my_concepts_map[pair] = updated[i]
parser = FunctionParserOld()
return sheerka, context, parser
def init_parser_with_source(self, source):
sheerka, context, parser = self.init_parser()
error_sink = ErrorSink()
parser_input = ParserInput(source)
parser.reset_parser_input(parser_input, error_sink)
return sheerka, context, parser, parser_input, error_sink
def test_i_can_detect_empty_expression(self):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(""))
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.IS_EMPTY)
def test_input_must_be_a_parser_input(self):
sheerka, context, parser = self.init_parser()
parser.parse(context, "not a parser input") is None
def test_i_cannot_parse_when_not_a_function(self):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput("not a function"))
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.NOT_FOR_ME)
@pytest.mark.parametrize("expression, expected", [
("func()", FNOld("func(", ")", [])),
("concept(one)", FNOld("concept(", ")", ["one"])),
("func(one)", FNOld("func(", ")", ["one"])),
("func(a long two, 'three', ;:$*)", FNOld("func(", ")", ["a long two, ", "'three', ", ";:$*"])),
("func(func1(one), two, func2(func3(), func4(three)))", FNOld("func(", (")", 4), [
(FNOld("func1(", ")", ["one"]), ", "),
"two, ",
(FNOld("func2(", (")", 3), [
(FNOld("func3(", (")", 1), []), ", "),
(FNOld("func4(", (")", 2), ["three"]), None),
]), None)
])),
("func(r:|1:)", FNOld("func(", ")", ["r:|1:"]))
])
def test_i_can_parse_function(self, expression, expected):
sheerka, context, parser, parser_input, error_sink = self.init_parser_with_source(expression)
expected = get_expr_node_from_test_node(expression, expected)
parsed = parser.parse_input(context, parser_input, error_sink)
assert not error_sink.has_error
assert parsed == expected
@pytest.mark.parametrize("text, expected", [
("func()", SCN("func()")),
(" func()", SCN("func()")),
("func(one)", SCWC("func(", ")", CN("one"))),
("func(one, unknown, two)", SCWC("func(", ")", CN("one"), ", ", UTN("unknown"), (", ", 1), CN("two"))),
("func(one, twenty two)", SCWC("func(", ")", "one", ", ", CN("twenties", "twenty two"))),
("func(one plus two, three)", SCWC("func(", ")", CNC("plus", a="one", b="two"), ", ", UTN("three"))),
("func(func1(one), two)", SCWC("func(", (")", 1), SCWC("func1(", ")", "one"), ", ", "two"))
])
def test_i_can_parse(self, text, expected):
sheerka, context, parser = self.init_parser()
resolved_expected = compute_expected_array(cmap, text, [expected])[0]
res = parser.parse(context, ParserInput(text))
parser_result = res.body
expression = res.body.body
assert res.status
assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT)
transformed_expression = get_test_obj(expression, resolved_expected)
assert transformed_expression == resolved_expected
assert expression.python_node is not None
assert expression.return_value is not None
def test_i_can_parse_when_multiple_results_when_requested(self):
# the previous output was
# [
# SCWC("func(", ")", "one", ", ", "twenty ", "two"),
# SCWC("func(", ")", "one", ", ", CN("twenties", "twenty two"))
# ]
# But the first one is now filtered out, as it's not a valid python function call
sheerka, context, parser = self.init_parser()
parser.longest_concepts_only = False
text = "func(one, twenty two)"
expected = [SCWC("func(", ")", "one", ", ", CN("twenties", "twenty two"))]
resolved_expected = compute_expected_array(cmap, text, expected)
results = parser.parse(context, ParserInput(text))
assert len(results) == 2
res = results[0]
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR)
assert len(res.body.body) == 1
assert (res.body.body[0], PythonErrorNode)
res = results[1]
parser_result = res.body
expressions = res.body.body
assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT)
transformed_expressions = get_test_obj(expressions, resolved_expected[0])
assert transformed_expressions == resolved_expected[0]
def test_i_can_parse_when_the_parameter_is_not_a_concept(self):
"""
It's not a concept, but it can be a valid short term memory object
:return:
"""
sheerka, context, parser = self.init_parser()
text = "func(unknown_concept)"
res = parser.parse(context, ParserInput(text))
expected = [SCWC("func(", ")", "unknown_concept")]
resolved_expected = compute_expected_array(cmap, text, expected)
assert res.status
parsed = res.body.body
transformed_parsed = get_test_obj([parsed], resolved_expected)
assert transformed_parsed == resolved_expected
def test_i_can_parse_when_the_concept_is_not_found(self):
"""
We do not check yet if it's a valid concept
If you find a cheap way to do so, simply remove this test
:return:
"""
sheerka, context, parser = self.init_parser()
text = "func(c:|xxx:)"
res = parser.parse(context, ParserInput(text))
assert res.status
def test_i_can_parse_when_rules(self):
sheerka, context, parser = self.init_parser()
text = "func(r:|1:)"
expected = SCWC("func(", ")", RN("1"))
resolved_expected = compute_expected_array(cmap, text, [expected])[0]
res = parser.parse(context, ParserInput(text))
parser_result = res.body
expression = res.body.body
transformed_expression = get_test_obj(expression, resolved_expected)
assert res.status
assert sheerka.isinstance(parser_result, BuiltinConcepts.PARSER_RESULT)
assert transformed_expression == resolved_expected
assert expression.python_node is not None
assert expression.return_value is not None
def test_i_can_parse_when_the_parameter_is_a_dynamic_concept(self):
sheerka, context, parser = self.init_parser()
text = "func(ones)"
res = parser.parse(context, ParserInput(text))
assert res.status
assert isinstance(res.body.body, SourceCodeWithConceptNode)
assert res.body.body.python_node.source == 'func(__C__ones__1001___PLURAL__C__)'
assert "__C__ones__1001___PLURAL__C__" in res.body.body.python_node.objects
@pytest.mark.parametrize("text, expected_error_type", [
("one", BuiltinConcepts.NOT_FOR_ME), # no function found
("$*!", BuiltinConcepts.NOT_FOR_ME), # no function found
("func(", BuiltinConcepts.ERROR), # function found, but incomplete
("func(one", BuiltinConcepts.ERROR), # function found, but incomplete
("func(one, two, ", BuiltinConcepts.ERROR), # function found, but incomplete
("func(one) and func(two)", BuiltinConcepts.ERROR), # to many function
("one func(one)", BuiltinConcepts.NOT_FOR_ME), # function not found ! (as it is not the first)
("func(a=b, c)", BuiltinConcepts.ERROR), # function found, but cannot be parsed
("func(one two)", BuiltinConcepts.ERROR), # function found, but cannot be parsed
])
def test_i_cannot_parse(self, text, expected_error_type):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput(text))
assert not res.status
assert sheerka.isinstance(res.body, expected_error_type)
@pytest.mark.parametrize("sequence, expected", [
(None, None),
([["a"]], [["a"]]),
([["a"], ["b", "c"]], [["a"]]),
([["b", "c"], ["a"]], [["a"]]),
([["b", "c"], ["a"], ["d", "e"], ["f"]], [["a"], ["f"]]),
])
def test_i_can_get_the_longest_concept_sequence(self, sequence, expected):
assert FunctionParserOld.get_longest_concepts(sequence) == expected
def test_concepts_found_are_fully_initialized(self):
sheerka, context, parser = self.init_parser()
res = parser.parse(context, ParserInput("func(one plus three)"))
concept = res.body.body.nodes[0].concept
assert res.status
assert isinstance(concept.get_compiled()["a"], Concept)
# three is not recognized,
# so it will be transformed into list of ReturnValueConcept that indicate how to recognized it
assert isinstance(concept.get_compiled()["b"], list)
for item in concept.get_compiled()["b"]:
assert sheerka.isinstance(item, BuiltinConcepts.RETURN_VALUE)
+1 -29
View File
@@ -9,8 +9,7 @@ from core.sheerka.Sheerka import RECOGNIZED_BY_KEY
from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import Token, TokenKind, Tokenizer, comparable_tokens
from core.utils import get_text_from_tokens
from parsers.BaseExpressionParser import BinaryNode, FunctionNode, FunctionNodeOld, FunctionParameter, ListNode, \
NameExprNode, VariableNode
from parsers.BaseExpressionParser import BinaryNode, FunctionNode, ListNode, NameExprNode, VariableNode
from parsers.BaseNodeParser import ConceptNode, SourceCodeNode, UnrecognizedTokensNode
from parsers.PythonParser import PythonNode
from parsers.SyaNodeParser import FunctionDetected, NoSyaConceptFound, NotEnoughParameters, SyaConceptParser, \
@@ -1564,33 +1563,6 @@ class TestSyaNodeParser(TestUsingMemoryBasedSheerka):
_stack, _expected = prepare_nodes_comparison(concepts_map, text, lexer_nodes, expected)
assert _stack == _expected
def test_i_can_parse_when_function_old_style_expr_tokens(self):
sheerka, context, parser = self.init_parser()
text = "one plus func(twenty two)"
tokens = list(Tokenizer(text, yield_eof=False))
fun_token = tokens[4]
expr = FunctionNodeOld(4, 9, tokens[4:10],
NameExprNode(4, 5, tokens[4:6]),
NameExprNode(9, 9, tokens[9:10]),
[FunctionParameter(NameExprNode(6, 8, tokens[6:9]), None)])
tokens[4:] = [Token(TokenKind.EXPR, expr, fun_token.index, fun_token.line, fun_token.column)]
res = parser.parse(context, ParserInput(None, tokens=tokens))
wrapper = res.body
lexer_nodes = res.body.body
assert res.status
assert context.sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT)
expected_ret_val = RETVAL("func(twenty two)", objects={"__o_00__": CIO("twenties", source="twenty two")})
expected = [CNC("plus", a=CC("one"), b=[expected_ret_val], source=text)]
_stack, _expected = prepare_nodes_comparison(cmap, text, lexer_nodes, expected)
assert _stack == _expected
# check the metadata
expected_concept = lexer_nodes[0].concept
assert expected_concept.get_metadata().variables == [("a", "one"), ("b", "func(twenty two)")]
def test_i_can_parse_when_function_style_expr_tokens(self):
sheerka, context, parser = self.init_parser()
+4 -27
View File
@@ -1,13 +1,13 @@
from core.concept import Concept, ConceptParts
from core.global_symbols import NotInit
from core.rule import Rule, ACTION_TYPE_EXEC
from core.rule import ACTION_TYPE_EXEC, Rule
from core.sheerka.services.SheerkaExecute import ParserInput
from parsers.BaseNodeParser import RuleNode
from parsers.BnfNodeParser import BnfNodeParser
from parsers.FunctionParserOld import FunctionParserOld
from parsers.FunctionParser import FunctionParser
from parsers.SyaNodeParser import SyaNodeParser
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
from tests.parsers.parsers_utils import get_test_obj, CNC, CC, CN, SCN, SCWC, UTN, RN, CB
from tests.parsers.parsers_utils import CB, CC, CN, CNC, RN, SCN, get_test_obj
class TestParsersUtils(TestUsingMemoryBasedSheerka):
@@ -133,7 +133,7 @@ class TestParsersUtils(TestUsingMemoryBasedSheerka):
def test_i_can_get_test_obj_when_SCN(self):
sheerka, context = self.init_test().unpack()
parser = FunctionParserOld()
parser = FunctionParser()
scn = parser.parse(context, ParserInput("test()")).body.body
scn_res = get_test_obj(scn, SCN("", start=0, end=1))
@@ -145,29 +145,6 @@ class TestParsersUtils(TestUsingMemoryBasedSheerka):
assert isinstance(scn_res, SCN)
assert scn_res == SCN("test()", start=None, end=None)
def test_i_can_get_test_obj_when_SCWC(self):
sheerka, context = self.init_test().unpack()
parser = FunctionParserOld()
scwc = parser.parse(context, ParserInput("test(param1, test2())")).body.body
scwc_res = get_test_obj(scwc, SCWC(UTN(""), UTN(""), UTN(""), UTN(""), SCN("", None, 0, 0)))
assert isinstance(scwc_res, SCWC)
expected = SCWC(UTN("test(", 0, 1),
UTN(")", 8, 8),
UTN("param1", 2, 2),
UTN(", ", 3, 4),
SCN("test2()", None, 5, 7))
expected.start = 0
expected.end = 8
assert scwc_res == expected
assert isinstance(scwc_res.first, UTN)
assert isinstance(scwc_res.last, UTN)
assert isinstance(scwc_res.content[0], UTN)
assert isinstance(scwc_res.content[1], UTN)
assert isinstance(scwc_res.content[2], SCN)
def test_i_can_get_test_obj_when_RN(self):
rule = Rule(ACTION_TYPE_EXEC, "test_rule", "True", "True")
rn = RuleNode(rule, 1, 1, source="r:|xxx:")
@@ -87,7 +87,7 @@ class TestExprToConditionsVisitor(TestUsingMemoryBasedSheerka):
@staticmethod
def get_conditions_from_expression(context, expression, parser=None):
parser = parser or ExpressionParser(old_style=False)
parser = parser or ExpressionParser()
error_sink = ErrorSink()
parser_input = ParserInput(expression)
parser.reset_parser_input(parser_input, error_sink)
@@ -293,6 +293,21 @@ class TestExprToConditionsVisitor(TestUsingMemoryBasedSheerka):
resolved_expected_objects = {k: cmap[v].id for k, v in {"__o_00__": "two"}.items()}
assert resolved_objects == resolved_expected_objects
def test_i_can_manage_possible_variable_indication(self):
my_map = {
"isa": Concept("x is an int").def_var("x") # no pre condition
}
sheerka, context = self.initialize_test(my_map)
parser = ExpressionParser(known_variables={"x"})
conditions = self.get_conditions_from_expression(context, "x is an int", parser)
python_source = conditions[0].return_value.body.body.source
assert python_source == "call_concept(__o_00__, x=x)"
resolved_objects = {k: v.id for k, v in conditions[0].objects.items()}
resolved_expected_objects = {k: my_map[v].id for k, v in {"__o_00__": "isa"}.items()}
assert resolved_objects == resolved_expected_objects
def test_i_can_parse_when_variables_are_missing(self):
sheerka, context = self.initialize_test()
expression = "x equals 1"
@@ -17,7 +17,7 @@ class TestPythonExprVisitor(TestUsingMemoryBasedSheerka):
@staticmethod
def get_expr_node(context, expression, parser=None):
parser = parser or ExpressionParser(old_style=False)
parser = parser or ExpressionParser()
error_sink = ErrorSink()
parser_input = ParserInput(expression)
parser.reset_parser_input(parser_input, error_sink)