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

This commit is contained in:
2020-11-20 13:41:45 +01:00
parent cd066881b4
commit 315f8ea09b
156 changed files with 8388 additions and 2852 deletions
+112 -50
View File
@@ -1,17 +1,26 @@
import logging
import os
import pprint
import time
from core.builtin_concepts import BuiltinConcepts, ParserResultConcept
from core.concept import Concept
from core.concept import Concept, get_concept_attrs
from core.global_symbols import CONTEXT_DISPOSED
from core.sheerka.services.SheerkaExecute import NO_MATCH
from core.sheerka.services.SheerkaMemory import SheerkaMemory
from core.sheerka_logger import get_logger
from core.utils import CONSOLE_COLORS_MAP as CCM
from sdp.sheerkaDataProvider import Event
try:
rows, columns = os.popen('stty size', 'r').read().split()
except ValueError:
rows, columns = 50, 80
pp = pprint.PrettyPrinter(indent=2, width=columns)
DEBUG_TAB_SIZE = 4
PROPERTIES_TO_SERIALIZE = ("_id",
"_bag",
"_children",
"_start",
"_stop",
@@ -50,19 +59,17 @@ class ExecutionContext:
logger=None,
global_hints=None,
errors=None,
**kwargs):
obj=None,
concepts=None):
self._id = ExecutionContext.get_id(event.get_digest()) if event else None
self._parent = None
self._children = []
self._tab = ""
self._bag = {} # context variables
self._start = 0 # when the execution starts (to measure elapsed time)
self._stop = 0 # when the execution stops (to measure elapses time)
self._logger = logger
self._format_instructions = None # how to print the execution context
self._stat_log = get_logger("stats")
self._show_stats = False
self._push = None
self.who = who # who is asking
self.event = event # what was the (original) trigger
@@ -70,6 +77,8 @@ class ExecutionContext:
self.action = action
self.action_context = action_context
self.desc = desc # human description of what is going on
self.preprocess_parsers = None
self.preprocess_evaluators = None
self.preprocess = None
self.stm = False # True if the context has short term memory entries
@@ -80,13 +89,11 @@ class ExecutionContext:
self.inputs = {} # what were the parameters of the execution context
self.values = {} # what was produced by the execution context
self.obj = kwargs.pop("obj", None) # current obj we are working on
self.obj = obj
self.concepts = concepts
self.concepts = kwargs.pop("concepts", {}) # known concepts specific to this context
# update the other elements
for k, v in kwargs.items():
self._bag[k] = v
self_debug, self.debug_mode = sheerka.get_context_debug_mode(self.id)
self.debug_enabled = self_debug is not None
@property
def elapsed(self):
@@ -117,24 +124,19 @@ class ExecutionContext:
"""
return self._children
def __getattr__(self, item):
if item in self._bag:
return self._bag[item]
raise AttributeError(f"'ExecutionContext' object has no attribute '{item}'")
def __enter__(self):
self._start = time.time_ns()
self.log_new()
# self.log_new()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._push:
return
if self.stm:
self.sheerka.services[SheerkaMemory.NAME].remove_context(self)
self.sheerka.publish(self, CONTEXT_DISPOSED)
self._stop = time.time_ns()
if self._show_stats:
self._stat_log.debug(f"[{self._id:2}]" + self._tab + "Execution time: " + self.elapsed_str)
def __repr__(self):
msg = f"ExecutionContext(who={self.who}, id={self._id}, action={self.action}, context={self.action_context}"
@@ -143,11 +145,6 @@ class ExecutionContext:
msg += ")"
return msg
# def __str__(self):
# msg = self.desc or "New Context"
# msg += f", who={self.who}, id={self.id}"
# return msg
def __eq__(self, other):
if id(self) == id(other):
return True
@@ -168,12 +165,12 @@ class ExecutionContext:
return True
def push(self, action: BuiltinConcepts, action_context, who=None, desc=None, logger=None, **kwargs):
def push(self, action: BuiltinConcepts, action_context, who=None, desc=None, logger=None, obj=None, concepts=None):
if self._push:
return self._push
who = who or self.who
logger = logger or self._logger
_kwargs = {"obj": self.obj, "concepts": self.concepts}
_kwargs.update(self._bag)
_kwargs.update(kwargs)
new = ExecutionContext(
who,
self.event,
@@ -184,19 +181,40 @@ class ExecutionContext:
logger,
self.global_hints,
self.errors,
**_kwargs)
obj or self.obj,
concepts or self.concepts)
new._parent = self
new._tab = self._tab + " " * DEBUG_TAB_SIZE
new.preprocess = self.preprocess
new.preprocess_parsers = self.preprocess_parsers
new.preprocess_evaluators = self.preprocess_evaluators
new.protected_hints.update(self.protected_hints)
if new.debug_mode is None and self.debug_mode == "protected":
new.debug_mode = "protected"
new.debug_enabled = True
self._children.append(new)
return new
def deactivate_push(self):
self._push = self.push(BuiltinConcepts.NOP, None)
self._push._push = self._push
if self.stm:
bag = self.sheerka.services[SheerkaMemory.NAME].get_all_short_term_memory(self)
self.sheerka.add_many_to_short_term_memory(self._push, bag)
def activate_push(self):
if self._push:
if self._push.stm:
self.sheerka.publish(self._push, CONTEXT_DISPOSED)
self._push._stop = time.time_ns()
self._push = None
def add_preprocess(self, name, **kwargs):
preprocess = self.sheerka.new(BuiltinConcepts.EVALUATOR_PRE_PROCESS)
preprocess.set_value("name", name)
preprocess.set_value("preprocess_name", name)
for k, v in kwargs.items():
preprocess.set_value(k, v)
@@ -206,13 +224,17 @@ class ExecutionContext:
return self
def add_inputs(self, **kwargs):
for k, v in kwargs.items():
self.inputs[k] = v
if self._push:
return
self.inputs.update(kwargs)
return self
def add_values(self, **kwargs):
for k, v in kwargs.items():
self.values[k] = v
if self._push:
return
self.values.update(kwargs)
return self
def add_to_short_term_memory(self, key, concept):
@@ -224,6 +246,9 @@ class ExecutionContext:
"""
self.sheerka.add_to_short_term_memory(self, key, concept)
def clear_short_term_memory(self):
self.sheerka.clear_short_term_memory(self)
def get_from_short_term_memory(self, key):
"""
@@ -237,11 +262,10 @@ class ExecutionContext:
if isinstance(self.obj, Concept):
if self.obj.key == key:
return self.obj
for var_name in self.obj.values:
if var_name == key:
value = self.obj.get_value(var_name)
if isinstance(value, Concept):
return value
if key in get_concept_attrs(self.obj):
value = self.obj.get_value(key)
if isinstance(value, Concept):
return value
# search in concepts
if self.concepts:
@@ -296,8 +320,34 @@ class ExecutionContext:
to_str = self.return_value_to_str(r)
self._logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
def debug(self, text):
print(text)
def get_debugger(self, who, method_name):
return self.sheerka.get_debugger(self, who, method_name)
def debug(self, who, method_name, variable_name, text, is_error=False):
activated = self.sheerka.debug_activated_for(who)
if activated:
str_text = pp.pformat(text)
color = 'red' if is_error else 'green'
if "\n" not in str(str_text):
self.sheerka.debug(
f"[{self._id:3}] {CCM[color]}{who}.{method_name}.{variable_name}: {CCM['reset']}{str_text}")
else:
self.sheerka.debug(f"[{self._id:3}] {CCM[color]}{who}.{method_name}.{variable_name}: {CCM['reset']}")
self.sheerka.debug(str_text)
def debug_entering(self, who, method_name, **kwargs):
if self.sheerka.debug_activated_for(who):
str_text = pp.pformat(kwargs)
if "\n" not in str(str_text):
self.sheerka.debug(
f"[{self._id:3}] {CCM['blue']}Entering {who}.{method_name} with {CCM['reset']}{str_text}")
else:
self.sheerka.debug(f"[{self._id:3}] {CCM['blue']}Entering {who}.{method_name}:{CCM['reset']}")
self.sheerka.debug(f"[{self._id:3}] {str_text}")
def debug_log(self, who, text):
if self.sheerka.debug_activated_for(who):
self.sheerka.debug(f"[{self._id:3}] {CCM['blue']}{text}{CCM['reset']}")
def get_parent(self):
return self._parent
@@ -384,9 +434,6 @@ class ExecutionContext:
And it removes the visibility from the other attributes/methods
"""
bag = {}
for k, v in self._bag.items():
bag[k] = v
bag["bag." + k] = v
for prop in ("id", "who", "action", "desc", "obj", "inputs", "values", "concepts"):
bag[prop] = getattr(self, prop)
bag["context"] = self.action_context
@@ -396,6 +443,7 @@ class ExecutionContext:
bag["elapsed"] = self.elapsed
bag["elapsed_str"] = self.elapsed_str
bag["digest"] = self.event.get_digest() if self.event else None
bag["_children"] = self._children
return bag
@staticmethod
@@ -438,3 +486,17 @@ class ExecutionContext:
break
current = current._parent
def has_parent(self, context_id):
current = self
while current._parent:
current = current._parent
if current.id == context_id:
return True
if current.id < context_id:
return False
return False
+167 -81
View File
@@ -11,8 +11,10 @@ from cache.IncCache import IncCache
from cache.ListIfNeededCache import ListIfNeededCache
from cache.SetCache import SetCache
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors, BuiltinUnique, \
UnknownConcept
from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW
UnknownConcept, AllBuiltinConcepts
from core.concept import Concept, ConceptParts, NotInit, get_concept_attrs
from core.error import ErrorObj
from core.profiling import profile
from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka_logger import console_handler
from core.tokenizer import Token, TokenKind
@@ -66,15 +68,18 @@ class Sheerka(Concept):
MAX_EXECUTION_HISTORY = 100
MAX_RETURN_VALUES_HISTORY = 100
ALL_ATTRIBUTES = []
def __init__(self, cache_only=False, debug=False, loggers=None):
self.init_logging(debug, loggers)
self.loggers = loggers
super().__init__(BuiltinConcepts.SHEERKA, True, True, BuiltinConcepts.SHEERKA)
self.log.debug("Starting Sheerka.")
# self.log.debug("Starting Sheerka.")
self.bnp = None # reference to the BaseNodeParser class (to compute first keyword token)
self.return_value_concept_id = None
self.error_concept_id = None
# a concept can be instantiated
# ex: File is a concept, but File('foo.txt') is an instance
@@ -85,7 +90,7 @@ class Sheerka(Concept):
# ex: hello => say('hello')
self.rules = []
self.sdp: SheerkaDataProvider = None # SheerkaDataProvider
self.sdp: SheerkaDataProvider = None
self.cache_manager = CacheManager(cache_only)
self.services = {} # sheerka plugins
@@ -103,6 +108,7 @@ class Sheerka(Concept):
self._builtins_classes_cache = None
self.save_execution_context = True
self.enable_process_return_values = False
self.methods_with_context = {"test_using_context"} # only the names, the method is defined in sheerka_methods
self.sheerka_methods = {
@@ -171,17 +177,22 @@ class Sheerka(Concept):
"""
self.sheerka_pipeables[func_name] = SheerkaMethod(function, has_side_effect)
def initialize(self, root_folder: str = None, save_execution_context=True):
def initialize(self, root_folder: str = None, save_execution_context=None, enable_process_return_values=None):
"""
Starting Sheerka
Loads the current configuration
Notes that when it's the first time, it also create the needed working folders
:param root_folder: root configuration folder
:param save_execution_context:
:param enable_process_return_values:
:return: ReturnValue(Success or Error)
"""
self.save_execution_context = save_execution_context
if save_execution_context is not None:
self.save_execution_context = save_execution_context
if enable_process_return_values is not None:
self.enable_process_return_values = enable_process_return_values
try:
from sheerkapickle.sheerka_handlers import initialize_pickle_handlers
@@ -189,7 +200,10 @@ class Sheerka(Concept):
self.sdp = SheerkaDataProvider(root_folder, self)
self.initialize_caching()
self.get_builtin_parsers()
self.get_builtin_evaluators()
self.initialize_services()
self.initialize_builtin_evaluators()
event = Event("Initializing Sheerka.", user_id=self.name)
self.sdp.save_event(event)
@@ -198,25 +212,24 @@ class Sheerka(Concept):
self,
BuiltinConcepts.INIT_SHEERKA,
None,
desc="Initializing Sheerka.",
logger=self.init_log) as exec_context:
desc="Initializing Sheerka.") as exec_context:
if self.sdp.first_time:
self.first_time_initialisation(exec_context)
self.initialize_builtin_parsers()
self.initialize_builtin_evaluators()
self.initialize_builtin_concepts()
self.initialize_concept_node_parsing(exec_context)
res = ReturnValueConcept(self, True, self)
self.initialize_services_deferred(exec_context, self.sdp.first_time)
res = ReturnValueConcept(self, True, self)
exec_context.add_values(return_values=res)
if self.cache_manager.is_dirty:
self.cache_manager.commit(exec_context)
if save_execution_context:
if self.save_execution_context:
self.sdp.save_result(exec_context, is_admin=True)
self.init_log.debug(f"Sheerka successfully initialized")
# self.init_log.debug(f"Sheerka successfully initialized")
except IOError as e:
res = ReturnValueConcept(self, False, self.get(BuiltinConcepts.ERROR), e)
@@ -276,7 +289,7 @@ class Sheerka(Concept):
Introspect to find services and bind them
:return:
"""
self.init_log.debug("Initializing services")
# self.init_log.debug("Initializing services")
core.utils.import_module_and_sub_module('core.sheerka.services')
base_class = "core.sheerka.services.sheerka_service.BaseService"
@@ -286,49 +299,63 @@ class Sheerka(Concept):
instance.initialize()
self.services[service.NAME] = instance
def initialize_services_deferred(self, context, is_first_time):
"""
Initialize part of services that may takes some time or that need the execution context
TODO: Create a separate thread for these initialisations as they may take time
:return:
"""
# self.init_log.debug("Initializing services (deferred)")
for service in self.services.values():
if hasattr(service, "initialize_deferred"):
service.initialize_deferred(context, is_first_time)
def first_time_initialisation(self, context):
self.cache_manager.put(self.CONCEPTS_KEYS_ENTRY, self.USER_CONCEPTS_KEYS, 1000)
self.record(context, self.name, "save_execution_context", True)
self.record_var(context, self.name, "save_execution_context", True)
def initialize_builtin_concepts(self):
"""
Initializes the builtin concepts
:return: None
"""
self.init_log.debug("Initializing builtin concepts")
# self.init_log.debug("Initializing builtin concepts")
builtins_classes = self.get_builtins_classes_as_dict()
# this all initialization of the builtins seems to be little bit complicated
# why do we need to update it from DB ?
for key in BuiltinConcepts:
for key in AllBuiltinConcepts:
concept = self if key == BuiltinConcepts.SHEERKA \
else builtins_classes[str(key)]() if str(key) in builtins_classes \
else Concept(key, True, False, key)
if key in BuiltinUnique:
concept.metadata.is_unique = True
concept.metadata.is_evaluated = True
concept._metadata.is_unique = True
concept._metadata.is_evaluated = True
if not concept.metadata.is_unique and str(key) in builtins_classes:
if not concept._metadata.is_unique and str(key) in builtins_classes:
self.builtin_cache[key] = builtins_classes[str(key)]
from_db = self.cache_manager.get(self.CONCEPTS_BY_KEY_ENTRY, concept.metadata.key)
from_db = self.cache_manager.get(self.CONCEPTS_BY_KEY_ENTRY, concept._metadata.key)
if from_db is None:
self.init_log.debug(f"'{concept.name}' concept is not found in db. Adding.")
# self.init_log.debug(f"'{concept.name}' concept is not found in db. Adding.")
self.set_id_if_needed(concept, True)
self.cache_manager.add_concept(concept)
if key == BuiltinConcepts.RETURN_VALUE:
self.return_value_concept_id = concept.id
elif key == BuiltinConcepts.ERROR:
self.error_concept_id = concept.id
else:
self.init_log.debug(f"Found concept '{from_db}' in db. Updating.")
# self.init_log.debug(f"Found concept '{from_db}' in db. Updating.")
concept.update_from(from_db)
return
def initialize_builtin_parsers(self):
def get_builtin_parsers(self):
"""
Init the parsers
:return:
@@ -343,7 +370,7 @@ class Sheerka(Concept):
continue
qualified_name = core.utils.get_full_qualified_name(parser)
self.init_log.debug(f"Adding builtin parser '{qualified_name}'")
# self.init_log.debug(f"Adding builtin parser '{qualified_name}'")
temp_result[qualified_name] = parser
# keep a reference to base_node_parser
@@ -361,22 +388,29 @@ class Sheerka(Concept):
self.parsers[name] = temp_result[name]
def get_builtin_evaluators(self):
"""
get all evaluators
:return:
"""
core.utils.import_module_and_sub_module("evaluators")
evaluators = core.utils.get_sub_classes("evaluators", "evaluators.BaseEvaluator.OneReturnValueEvaluator")
evaluators.extend(core.utils.get_sub_classes("evaluators", "evaluators.BaseEvaluator.AllReturnValuesEvaluator"))
for evaluator in evaluators:
self.evaluators.append(evaluator)
def initialize_builtin_evaluators(self):
"""
Init the evaluators
:return:
"""
core.utils.import_module_and_sub_module("evaluators")
for evaluator in core.utils.get_sub_classes("evaluators", "evaluators.BaseEvaluator.OneReturnValueEvaluator"):
self.init_log.debug(f"Adding builtin evaluator '{evaluator.__name__}'")
self.evaluators.append(evaluator)
for evaluator in core.utils.get_sub_classes("evaluators", "evaluators.BaseEvaluator.AllReturnValuesEvaluator"):
self.init_log.debug(f"Adding builtin evaluator '{evaluator.__name__}'")
self.evaluators.append(evaluator)
for evaluator in self.evaluators:
if hasattr(evaluator, "initialize"):
evaluator.initialize(self)
def initialize_concept_node_parsing(self, context):
self.init_log.debug("siInitializing concepts by first keyword.")
# self.init_log.debug("Initializing concepts by first keyword.")
concepts_by_first_keyword = self.cache_manager.copy(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY)
res = self.bnp.resolve_concepts_by_first_keyword(context, concepts_by_first_keyword)
@@ -391,11 +425,16 @@ class Sheerka(Concept):
service.initialize()
else:
self.cache_manager.clear()
for service in self.services.values():
if hasattr(service, "reset"):
service.reset()
self.printer_handler.reset()
self.sdp.reset()
self.locals = {}
# @profile()
# @profile(filename="profile_80")
def evaluate_user_input(self, text: str, user_name="kodjo"):
"""
Note to KSI: If you try to add execution context to this function,
@@ -404,19 +443,20 @@ class Sheerka(Concept):
:param user_name:
:return:
"""
self.log.debug(f"Processing user input '{text}', {user_name=}.")
# self.log.debug(f"Processing user input '{text}', {user_name=}.")
event = Event(text, user_name)
evt_digest = self.sdp.save_event(event)
self.log.debug(f"{evt_digest=}")
self.sdp.save_event(event)
with ExecutionContext(self.key,
event,
self,
BuiltinConcepts.PROCESS_INPUT,
text,
desc=f"Evaluating '{text}'",
logger=self.log) as execution_context:
desc=f"Evaluating '{text}'") as execution_context:
user_input = self.ret(self.name, True, self.new(BuiltinConcepts.USER_INPUT, body=text, user_name=user_name))
# TODO. Must be a context hint, not a return value
reduce_requested = self.ret(self.name, True, self.new(BuiltinConcepts.REDUCE_REQUESTED))
ret = self.execute(execution_context, [user_input, reduce_requested], EXECUTE_STEPS)
@@ -425,17 +465,21 @@ class Sheerka(Concept):
if self.cache_manager.is_dirty:
self.cache_manager.commit(execution_context)
try:
if self.save_execution_context and self.load(self.name, "save_execution_context"):
# exec_count = ExecutionContext.ids[execution_context.event.get_digest()]
# print("Execution Context Count:", exec_count)
if self.save_execution_context:
try:
# if exec_count > 3400:
# print("Saving result. digest=", execution_context.event.get_digest())
self.sdp.save_result(execution_context)
except Exception as ex:
self.log.error(f"Failed to save execution context. Reason: {ex}")
except Exception as ex:
print(f"Failed to save execution context. Reason: {ex}")
pass
# self.log.error(f"Failed to save execution context. Reason: {ex}")
# # hack to save valid concept definition
# if not self.during_restore:
# if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
# with open(CONCEPTS_FILE, "a") as f:
# f.write(text + "\n")
# Do not save execution contexts from process_return_values
if self.enable_process_return_values:
self.process_return_values(execution_context, ret)
self.execution_count += 1
self._last_execution = execution_context
@@ -461,17 +505,16 @@ class Sheerka(Concept):
def set_id_if_needed(self, obj: Concept, is_builtin: bool):
"""
Set the key for the concept if needed
For test purpose only !!!!!
:param obj:
:param is_builtin:
:return:
"""
if obj.metadata.id is not None:
if obj._metadata.id is not None:
return
key = self.BUILTIN_CONCEPTS_KEYS if is_builtin else self.USER_CONCEPTS_KEYS
obj.metadata.id = str(self.cache_manager.get(self.CONCEPTS_KEYS_ENTRY, key))
self.log.debug(f"Setting id '{obj.metadata.id}' to concept '{obj.metadata.name}'.")
obj._metadata.id = str(self.cache_manager.get(self.CONCEPTS_KEYS_ENTRY, key))
# self.log.debug(f"Setting id '{obj.metadata.id}' to concept '{obj.metadata.name}'.")
def force_sya_def(self, context, list_of_def):
"""
@@ -588,9 +631,9 @@ class Sheerka(Concept):
# ##############
# if the entry is a concept token, use its values.
if isinstance(concept, Token):
if concept.type != TokenKind.CONCEPT:
if concept.type == TokenKind.RULE: # do not recognize rules !!!
return None
concept = concept.value
concept = concept.value # concept is now a tuple
if isinstance(concept, str) and \
concept.startswith("c:") and \
@@ -607,7 +650,7 @@ class Sheerka(Concept):
if concept[1]:
if self.is_known(found := self.get_by_id(concept[1])):
instance = self.new_from_template(found, found.key)
instance.metadata.is_evaluated = True
instance._metadata.is_evaluated = True
return instance
elif concept[0]:
if self.is_known(found := self.get_by_name(concept[0])):
@@ -626,6 +669,28 @@ class Sheerka(Concept):
return None
def fast_resolve(self, key, return_new=True):
def new_instances(concepts):
if hasattr(concepts, "__iter__"):
return [self.new_from_template(c, c.key) for c in concepts]
return self.new_from_template(concepts, concepts.key)
if isinstance(key, Token):
if key.type == TokenKind.RULE: # do not recognize rules !!!
return None
if key.value[1]:
concept = self.cache_manager.get(self.CONCEPTS_BY_ID_ENTRY, key.value[1])
else:
concept = self.cache_manager.get(self.CONCEPTS_BY_NAME_ENTRY, key.value[0])
else:
concept = self.cache_manager.get(self.CONCEPTS_BY_NAME_ENTRY, key)
if concept is None:
return None
return new_instances(concept) if return_new else concept
def has_id(self, concept_id):
"""
Returns True if a concept with this id exists in cache
@@ -694,13 +759,13 @@ class Sheerka(Concept):
def new_from_template(self, template, key, **kwargs):
# core.utils.my_debug(f"Created {template}, {key=}, {kwargs=}")
# manage singleton
if template.metadata.is_unique:
if template.get_metadata().is_unique:
return template
# otherwise, create another instance
concept = self.builtin_cache[key]() if key in self.builtin_cache else Concept()
concept.update_from(template, update_value=False)
concept.freeze_definition_hash()
# concept.freeze_definition_hash()
if len(kwargs) == 0:
return concept
@@ -708,17 +773,17 @@ class Sheerka(Concept):
# update the properties, values, attributes
# Not quite sure that this is the correct process order
for k, v in kwargs.items():
if k in concept.values:
if k in get_concept_attrs(concept):
concept.set_value(k, v)
elif k in PROPERTIES_FOR_NEW:
concept.set_value(ConceptParts(k), v)
elif k == "body":
concept.set_value(ConceptParts.BODY, v)
elif hasattr(concept, k):
setattr(concept, k, v)
else:
return self.new(BuiltinConcepts.UNKNOWN_PROPERTY, body=k, concept=concept)
# TODO : add the concept to the list of known concepts (self.instances)
concept.metadata.is_evaluated = True # because we have manually set the variables
concept._metadata.is_evaluated = True # because we have manually set the variables
return concept
def ret(self, who: str, status: bool, value, message=None, parents=None):
@@ -732,22 +797,16 @@ class Sheerka(Concept):
:return:
"""
# 1 second saved every twenty seconds in unit tests
return ReturnValueConcept(
who=who,
status=status,
value=value,
message=message,
parents=parents,
concept_id=self.return_value_concept_id
)
# return self.new(
# BuiltinConcepts.RETURN_VALUE,
# who=who,
# status=status,
# value=value,
# message=message,
# parents=parents)
def err(self, body):
return ErrorConcept(body, self.error_concept_id)
def objvalue(self, obj, reduce_simple_list=False):
if obj is None:
@@ -759,7 +818,7 @@ class Sheerka(Concept):
if not isinstance(obj, Concept):
return obj
if obj.body is BuiltinConcepts.NOT_INITIALIZED:
if obj.body is NotInit:
return obj
if reduce_simple_list and (isinstance(obj.body, list) or isinstance(obj.body, set)) and len(obj.body) == 1:
@@ -796,7 +855,7 @@ class Sheerka(Concept):
return self.value_by_concept(obj.body, concept)
def get_error(self, obj):
if isinstance(obj, Concept) and obj.metadata.is_builtin and obj.key in BuiltinErrors:
if isinstance(obj, Concept) and obj._metadata.is_builtin and obj.key in BuiltinErrors:
return obj
if isinstance(obj, (list, set, tuple)):
@@ -848,9 +907,9 @@ class Sheerka(Concept):
def test(self):
return f"I have access to Sheerka !"
def test_using_context(self, context, param1, param2):
def test_using_context(self, context, param):
event = context.event.get_digest()
return f"I have access to Sheerka ! {param1=}, {param2=}, {event=}."
return f"I have access to Sheerka ! {param=}, {event=}."
def test_error(self):
raise Exception("I can raise an error")
@@ -863,14 +922,17 @@ class Sheerka(Concept):
if isinstance(obj, ReturnValueConcept):
return obj.status
if isinstance(obj, ErrorObj):
return False
# other cases ?
# ...
# manage internal errors
if isinstance(obj, Concept) and obj.metadata.is_builtin and obj.key in BuiltinErrors:
if isinstance(obj, Concept) and obj._metadata.is_builtin and obj.key in BuiltinErrors:
return False
return obj
return bool(obj)
@staticmethod
def is_known(obj):
@@ -914,9 +976,7 @@ class Sheerka(Concept):
unknown_concept = UnknownConcept() # don't use new() for prevent circular reference
unknown_concept.set_value(ConceptParts.BODY, metadata)
for meta in (metadata if isinstance(metadata, list) else [metadata]):
unknown_concept.set_value(meta[0], meta[1])
unknown_concept.metadata.is_evaluated = True
unknown_concept._metadata.is_evaluated = True
return unknown_concept
@staticmethod
@@ -924,7 +984,7 @@ class Sheerka(Concept):
res = {}
for c in core.utils.get_classes("core.builtin_concepts"):
if issubclass(c, Concept) and c != Concept:
res[c().metadata.key] = c
res[c()._metadata.key] = c
return res
@@ -967,3 +1027,29 @@ class Sheerka(Concept):
logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
# uncomment the following line to enable colors
# logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit)
def to_profile():
sheerka = Sheerka()
sheerka.initialize(save_execution_context=False, enable_process_return_values=False)
event = Event("test", "kodjoko")
execution_context = ExecutionContext(sheerka.name,
event,
sheerka,
BuiltinConcepts.PROCESS_INPUT,
None)
profile_push(execution_context)
@profile(filename="profile_push")
def profile_push(execution_context):
for i in range(177942):
execution_context.push(BuiltinConcepts.NOP,
{"action": "fake"},
execution_context.sheerka.name,
desc="a proper description")
if __name__ == '__main__':
to_profile()
+82 -19
View File
@@ -1,12 +1,14 @@
import sys
import time
from os import path
from core.builtin_concepts import BuiltinConcepts
from core.builtin_concepts import BuiltinConcepts, BuiltinContainers
from core.concept import Concept
from core.sheerka.services.sheerka_service import BaseService
CONCEPTS_FILE_LITE = "_concepts_lite.txt"
CONCEPTS_FILE_ALL_CONCEPTS = "_concepts.txt"
CONCEPTS_FILE_TO_USE = CONCEPTS_FILE_ALL_CONCEPTS
CONCEPTS_FILE_FULL = "_concepts_full.txt"
CONCEPTS_FILE_TO_USE = CONCEPTS_FILE_FULL
class SheerkaAdmin(BaseService):
@@ -22,6 +24,10 @@ class SheerkaAdmin(BaseService):
self.sheerka.bind_service_method(self.concepts, False)
self.sheerka.bind_service_method(self.last_created_concept, False)
self.sheerka.bind_service_method(self.last_ret, False)
self.sheerka.bind_service_method(self.last_error_ret, False)
self.sheerka.bind_service_method(self.extended_isinstance, False)
self.sheerka.bind_service_method(self.is_container, False)
self.sheerka.bind_service_method(self.format_rules, False)
def caches_names(self):
"""
@@ -53,18 +59,19 @@ class SheerkaAdmin(BaseService):
def restore_from_file(file_name):
_nb_lines, _nb_instructions, _nb_lines_in_error = 0, 0, 0
if not path.exists(file_name):
self.sheerka.log.error(f"\u001b[31mFile '{file_name}' is not found !\u001b[0m")
file_path = path.join(path.dirname(sys.argv[0]), file_name)
if not path.exists(file_path):
print(f"\u001b[31mFile '{file_path}' is not found !\u001b[0m")
return 0, 0, 1
with open(file_name, "r") as f:
with open(file_path, "r") as f:
for line in f.readlines():
_nb_lines += 1
line = line.strip()
if line.startswith("#import "):
to_import = "_concepts_" + line[8:] + ".txt"
self.sheerka.log.info(f"Importing {to_import}")
print(f"Importing {to_import}")
res = restore_from_file(to_import)
_nb_lines += res[0]
_nb_instructions += res[1]
@@ -74,42 +81,49 @@ class SheerkaAdmin(BaseService):
if line == "" or line.startswith("#"):
continue
self.sheerka.log.info(line)
print(line)
_nb_instructions += 1
res = self.sheerka.evaluate_user_input(line)
if len(res) > 1 or not res[0].status:
_nb_lines_in_error += 1
self.sheerka.log.error("\u001b[31mError detected !\u001b[0m")
print("\u001b[31mError detected !\u001b[0m")
return _nb_lines, _nb_instructions, _nb_lines_in_error
if concept_file == "full":
concept_file = CONCEPTS_FILE_ALL_CONCEPTS
elif not concept_file.startswith("_concepts"):
if not concept_file.startswith("_concepts"):
concept_file = f"_concepts_{concept_file}.txt"
try:
start = time.time_ns()
self.sheerka.during_restore = True
self.sheerka.save_execution_context = False
enable_process_return_values_previous_value = self.sheerka.enable_process_return_values
self.sheerka.enable_process_return_values = False
nb_lines, nb_instructions, nb_lines_in_error = restore_from_file(concept_file)
self.sheerka.enable_process_return_values = enable_process_return_values_previous_value
self.sheerka.save_execution_context = True
self.sheerka.during_restore = False
stop = time.time_ns()
nano_sec = stop - start
dt = nano_sec / 1e6
elapsed = f"{dt} ms" if dt < 1000 else f"{dt / 1000} s"
self.sheerka.log.info(f"Imported {nb_lines} line(s) in {elapsed}.")
self.sheerka.log.info(f"{nb_instructions} instruction(s).")
print(f"Imported {nb_lines} line(s) in {elapsed}.")
print(f"{nb_instructions} instruction(s).")
if nb_lines_in_error > 0:
self.sheerka.log.info(f"\u001b[31m{nb_lines_in_error} errors(s) found.\u001b[0m")
print(f"\u001b[31m{nb_lines_in_error} errors(s) found.\u001b[0m")
else:
self.sheerka.log.info(f"No error.")
print(f"No error.")
except IOError as e:
raise e
def concepts(self):
return self.sheerka.sdp.list(self.sheerka.CONCEPTS_BY_ID_ENTRY)
return self.sheerka.new(BuiltinConcepts.TO_LIST, body=self.sheerka.sdp.list(self.sheerka.CONCEPTS_BY_ID_ENTRY))
def format_rules(self):
return self.sheerka.new(BuiltinConcepts.TO_LIST, items=self.sheerka.get_format_rules())
def last_created_concept(self, use_history=False):
for exec_result in reversed(self.sheerka.last_executions):
@@ -124,4 +138,53 @@ class SheerkaAdmin(BaseService):
return self.sheerka.new(BuiltinConcepts.NOT_FOUND)
def last_ret(self, context, index=-1):
return self.sheerka.last_return_values[index]
try:
last = self.sheerka.last_return_values[index]
return last[0] if isinstance(last, list) and len(last) == 1 else last
except IndexError:
return None
def last_error_ret(self, context, index=-1):
while index >= -len(self.sheerka.last_return_values):
last = self.sheerka.last_return_values[index]
last = [last] if not hasattr(last, "__iter__") else last
last = [ret_val for ret_val in last if not ret_val.status]
if len(last) == 0:
index -= 1
continue
if len(last) > 1:
return context.sheerka.ret(SheerkaAdmin.NAME,
False,
context.sheerka.new(BuiltinConcepts.TOO_MANY_ERRORS, body=last))
return last[0]
return context.sheerka.ret(SheerkaAdmin.NAME,
False,
context.sheerka.new(BuiltinConcepts.NOT_FOUND))
def extended_isinstance(self, a, b):
"""
switch between sheerka.isinstance and builtin.isinstance
:param a:
:param b:
:return:
"""
if isinstance(b, (type, tuple)):
return isinstance(a, b)
return self.sheerka.isinstance(a, b)
@staticmethod
def is_container(obj):
"""
A container concept is a builtin concept that embed a result
:param obj:
:return:
"""
if not isinstance(obj, Concept):
return False
return obj.key in BuiltinContainers
@@ -3,7 +3,11 @@ from dataclasses import dataclass
from cache.Cache import Cache
from cache.ListCache import ListCache
from core.builtin_concepts import BuiltinConcepts
from core.concept import ensure_concept, Concept
from core.global_symbols import CONCEPT_PRECEDENCE_MODIFIED, RULE_PRECEDENCE_MODIFIED, RULE_COMPARISON_CONTEXT, \
CONCEPT_COMPARISON_CONTEXT
from core.builtin_helpers import ensure_concept_or_rule
from core.concept import Concept
from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager
from core.sheerka.services.sheerka_service import ServiceObj, BaseService
@@ -43,7 +47,7 @@ class SheerkaComparisonManager(BaseService):
:return:
"""
if isinstance(prop_name, Concept):
prefix = prop_name.key if prop_name.metadata.is_builtin else prop_name.id
prefix = prop_name.key if prop_name.get_metadata().is_builtin else prop_name.id
else:
prefix = prop_name
@@ -66,9 +70,11 @@ class SheerkaComparisonManager(BaseService):
for _ in range(len(comparison_objs)):
for comparison_obj in comparison_objs:
if comparison_obj.op == ">":
values[comparison_obj.a] = values[comparison_obj.b] + 1
if values[comparison_obj.a] <= values[comparison_obj.b]:
values[comparison_obj.a] = values[comparison_obj.b] + 1
else:
values[comparison_obj.b] = values[comparison_obj.a] + 1
if values[comparison_obj.b] <= values[comparison_obj.a]:
values[comparison_obj.b] = values[comparison_obj.a] + 1
return values
@@ -128,17 +134,17 @@ class SheerkaComparisonManager(BaseService):
res.setdefault(v, []).append(k)
return res
def _add_comparison(self, comparison_obj):
def _add_comparison(self, context, comparison_obj):
key = self._compute_key(comparison_obj.property, comparison_obj.context)
previous = self.sheerka.cache_manager.get(self.COMPARISON_ENTRY, key)
new = previous.copy() if previous else []
for co in new:
if co.property == comparison_obj.property and \
co.a == comparison_obj.a and \
co.b == comparison_obj.b and \
co.op == comparison_obj.op and \
co.context == comparison_obj.context:
co.a == comparison_obj.a and \
co.b == comparison_obj.b and \
co.op == comparison_obj.op and \
co.context == comparison_obj.context:
return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_DEFINED))
new.append(comparison_obj)
@@ -166,7 +172,7 @@ class SheerkaComparisonManager(BaseService):
cycles = self.detect_cycles(new)
if cycles:
concepts_in_cycle = [self.sheerka.get_by_id(c) for c in cycles]
concepts_in_cycle = [self.sheerka.resolve(c) for c in cycles]
chicken_an_egg = self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body=concepts_in_cycle)
return self.sheerka.ret(self.NAME, False, chicken_an_egg)
@@ -175,6 +181,12 @@ class SheerkaComparisonManager(BaseService):
lesser_objs_ids,
greatest_objs_ids))
if comparison_obj.property == BuiltinConcepts.PRECEDENCE:
if comparison_obj.context == CONCEPT_COMPARISON_CONTEXT:
self.sheerka.publish(context, CONCEPT_PRECEDENCE_MODIFIED)
elif comparison_obj.context == RULE_COMPARISON_CONTEXT:
self.sheerka.publish(context, RULE_PRECEDENCE_MODIFIED)
return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def initialize(self):
@@ -191,41 +203,51 @@ class SheerkaComparisonManager(BaseService):
self.sheerka.bind_service_method(self.get_partition, False)
self.sheerka.bind_service_method(self.get_concepts_weights, False)
def set_is_greater_than(self, context, prop_name, concept_a, concept_b, comparison_context="#"):
def set_is_greater_than(self, context, prop_name, item_a, item_b, comparison_context="#"):
"""
Records that the property of concept a is greater than concept b's one
:param context:
:param prop_name:
:param concept_a:
:param concept_b:
:param item_a:
:param item_b:
:param comparison_context:
:return:
"""
context.log(f"Setting concept {concept_a} is greater than {concept_b}", who=self.NAME)
ensure_concept(concept_a, concept_b)
context.log(f"Setting item {item_a} is greater than {item_b}", who=self.NAME)
ensure_concept_or_rule(item_a, item_b)
event_digest = context.event.get_digest()
comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, ">", comparison_context)
return self._add_comparison(comparison_obj)
comparison_obj = ComparisonObj(event_digest,
prop_name,
item_a.str_id,
item_b.str_id,
">",
comparison_context)
return self._add_comparison(context, comparison_obj)
def set_is_less_than(self, context, prop_name, concept_a, concept_b, comparison_context="#"):
def set_is_less_than(self, context, prop_name, item_a, item_b, comparison_context="#"):
"""
Records that the property of concept a is lesser than concept b's one
:param context:
:param prop_name:
:param concept_a:
:param concept_b:
:param item_a:
:param item_b:
:param comparison_context:
:return:
"""
context.log(f"Setting concept {concept_a} is less than {concept_b}", who=self.NAME)
ensure_concept(concept_a, concept_b)
context.log(f"Setting item {item_a} is less than {item_b}", who=self.NAME)
ensure_concept_or_rule(item_a, item_b)
event_digest = context.event.get_digest()
comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, "<", comparison_context)
return self._add_comparison(comparison_obj)
comparison_obj = ComparisonObj(event_digest,
prop_name,
item_a.str_id,
item_b.str_id,
"<",
comparison_context)
return self._add_comparison(context, comparison_obj)
def set_is_lesser(self, context, prop_name, concept, comparison_context="#"):
def set_is_lesser(self, context, prop_name, item, comparison_context="#"):
"""
Records that the concept is less than any other concept if no direct comparison is given
@@ -235,18 +257,23 @@ class SheerkaComparisonManager(BaseService):
* All lesser concepts that have no comparison directive are greater than the others (and share the same weight)
:param context:
:param prop_name:
:param concept:
:param item:
:param comparison_context:
:return:
"""
context.log(f"Setting concept {concept} is lesser", who=self.NAME)
ensure_concept(concept)
context.log(f"Setting item {item} is lesser", who=self.NAME)
ensure_concept_or_rule(item)
event_digest = context.event.get_digest()
comparison_obj = ComparisonObj(event_digest, prop_name, concept.id, None, "<<", comparison_context)
return self._add_comparison(comparison_obj)
comparison_obj = ComparisonObj(event_digest,
prop_name,
item.str_id,
None,
"<<",
comparison_context)
return self._add_comparison(context, comparison_obj)
def set_is_greatest(self, context, prop_name, concept, comparison_context="#"):
def set_is_greatest(self, context, prop_name, item, comparison_context="#"):
"""
Records that the concept is greater than any other concept if no direct comparison is given
@@ -256,16 +283,21 @@ class SheerkaComparisonManager(BaseService):
* All greatest concepts that have no comparison directive are less than the others (and share the same weight)
:param context:
:param prop_name:
:param concept:
:param item:
:param comparison_context:
:return:
"""
context.log(f"Setting concept {concept} is greatest", who=self.NAME)
ensure_concept(concept)
context.log(f"Setting item {item} is greatest", who=self.NAME)
ensure_concept_or_rule(item)
event_digest = context.event.get_digest()
comparison_obj = ComparisonObj(event_digest, prop_name, concept.id, None, ">>", comparison_context)
return self._add_comparison(comparison_obj)
comparison_obj = ComparisonObj(event_digest,
prop_name,
item.str_id,
None,
">>",
comparison_context)
return self._add_comparison(context, comparison_obj)
def set_are_equivalent(self, context, prop_name, concept_a, concept_b, comparison_context="#"):
"""
@@ -281,9 +313,6 @@ class SheerkaComparisonManager(BaseService):
"""
pass
def set_are_equiv(self, context, prop_name, concept_a, concept_b, comparison_context="#"):
pass
def get_partition(self, prop_name, comparison_context="#"):
"""
Returns the equivalent classes for the property, using the comparison_context
@@ -2,7 +2,8 @@ from dataclasses import dataclass
from operator import attrgetter
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept, ensure_concept
from core.builtin_helpers import ensure_concept
from core.concept import Concept
from core.sheerka.Sheerka import Sheerka
from core.sheerka.services.sheerka_service import BaseService
@@ -81,13 +82,13 @@ class SheerkaConceptsAlgebra(BaseService):
:param key:
:return:
"""
if key not in source.metadata.props:
if key not in source.get_metadata().props:
return
if key in destination.metadata.props:
destination.metadata.props[key].update(source.metadata.props[key])
if key in destination.get_metadata().props:
destination.get_metadata().props[key].update(source.get_metadata().props[key])
else:
destination.metadata.props[key] = source.metadata.props[key].copy()
destination.get_metadata().props[key] = source.get_metadata().props[key].copy()
def sub_props(self, destination, source, key):
"""
@@ -97,11 +98,11 @@ class SheerkaConceptsAlgebra(BaseService):
:param key:
:return:
"""
if key not in source.metadata.props or key not in destination.metadata.props:
if key not in source.get_metadata().props or key not in destination.get_metadata().props:
return
for item in source.metadata.props[key]:
destination.metadata.props[key].discard(item)
for item in source.get_metadata().props[key]:
destination.get_metadata().props[key].discard(item)
def recognize(self, concept, all_scores=False):
"""
@@ -118,7 +119,7 @@ class SheerkaConceptsAlgebra(BaseService):
return res
all_concepts = self.sheerka.cache_manager.copy(Sheerka.CONCEPTS_BY_ID_ENTRY).values() \
if self.sheerka.cache_manager.cache_only else self.sheerka.concepts()
if self.sheerka.cache_manager.cache_only else self.sheerka.sdp.list(self.sheerka.CONCEPTS_BY_ID_ENTRY)
for c in all_concepts:
score = self._compute_score(c, concept, step_b=round(1 / nb_props, 2))
@@ -127,8 +128,8 @@ class SheerkaConceptsAlgebra(BaseService):
if len(res) == 0:
props = []
for p in [p for p in PROPERTIES_TO_COMPUTE if p in concept.metadata.props]:
props.append((p, concept.metadata.props[p]))
for p in [p for p in PROPERTIES_TO_COMPUTE if p in concept.get_metadata().props]:
props.append((p, concept.get_metadata().props[p]))
return self.sheerka.get_unknown(props)
res.sort(key=attrgetter('score'), reverse=True)
@@ -158,9 +159,9 @@ class SheerkaConceptsAlgebra(BaseService):
# adds step_b for every property that are in both a and b
for prop in PROPERTIES_TO_COMPUTE:
if prop in b.metadata.props and prop in a.metadata.props:
for prop_value in b.metadata.props[prop]:
if prop_value in a.metadata.props[prop]:
if prop in b.get_metadata().props and prop in a.get_metadata().props:
for prop_value in b.get_metadata().props[prop]:
if prop_value in a.get_metadata().props[prop]:
score += step_b
if not step_a:
@@ -171,11 +172,11 @@ class SheerkaConceptsAlgebra(BaseService):
# remove step_a for every property that is in a, but not in b
for prop in PROPERTIES_TO_COMPUTE:
if prop in a.metadata.props and prop not in a.metadata.props:
score += step_a * len(a.metadata.props)
elif prop in a.metadata.props and prop in a.metadata.props:
for prop_value in a.metadata.props[prop]:
if prop_value not in b.metadata.props[prop]:
if prop in a.get_metadata().props and prop not in a.get_metadata().props:
score += step_a * len(a.get_metadata().props)
elif prop in a.get_metadata().props and prop in a.get_metadata().props:
for prop_value in a.get_metadata().props[prop]:
if prop_value not in b.get_metadata().props[prop]:
score -= step_b
return score
@@ -189,6 +190,6 @@ class SheerkaConceptsAlgebra(BaseService):
"""
nb_props = 0
for prop in PROPERTIES_TO_COMPUTE:
if prop in concept.metadata.props:
nb_props += len(concept.metadata.props[prop])
if prop in concept.get_metadata().props:
nb_props += len(concept.get_metadata().props[prop])
return nb_props
@@ -1,6 +1,7 @@
import core.utils
from core.builtin_concepts import BuiltinConcepts, ErrorConcept
from core.concept import Concept, DEFINITION_TYPE_DEF, ensure_concept, DEFINITION_TYPE_BNF
from core.builtin_helpers import ensure_concept
from core.concept import Concept, DEFINITION_TYPE_DEF, DEFINITION_TYPE_BNF, freeze_concept_attrs
from core.sheerka.services.sheerka_service import BaseService
from sdp.sheerkaDataProvider import SheerkaDataProviderDuplicateKeyError
@@ -28,7 +29,6 @@ class SheerkaCreateNewConcept(BaseService):
:param concept: DefConceptNode
:return: digest of the new concept
"""
ensure_concept(concept)
sheerka = self.sheerka
@@ -50,6 +50,9 @@ class SheerkaCreateNewConcept(BaseService):
# set id before saving in db
sheerka.set_id_if_needed(concept, False)
# freeze attributes
freeze_concept_attrs(concept)
# compute new concepts_by_first_keyword
init_ret_value = self.bnp.get_concepts_by_first_token(context, [concept], True)
if not init_ret_value.status:
@@ -68,9 +71,9 @@ class SheerkaCreateNewConcept(BaseService):
cache_manager.put(sheerka.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, concepts_by_first_keyword)
cache_manager.put(sheerka.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
if concept.metadata.definition_type == DEFINITION_TYPE_DEF and concept.metadata.definition != concept.name:
if concept.get_metadata().definition_type == DEFINITION_TYPE_DEF and concept.get_metadata().definition != concept.name:
# allow search by definition when definition relevant
cache_manager.put(self.sheerka.CONCEPTS_BY_NAME_ENTRY, concept.metadata.definition, concept)
cache_manager.put(self.sheerka.CONCEPTS_BY_NAME_ENTRY, concept.get_metadata().definition, concept)
# update references
for ref in self.compute_references(concept):
@@ -78,7 +81,7 @@ class SheerkaCreateNewConcept(BaseService):
# TODO : this line seems to be useless
# The grammar is never reset
if concept.bnf and init_bnf_ret_value is not None and init_bnf_ret_value.status:
if concept.get_bnf() and init_bnf_ret_value is not None and init_bnf_ret_value.status:
sheerka.cache_manager.clear(sheerka.CONCEPTS_GRAMMARS_ENTRY)
# process the return if needed
@@ -94,10 +97,10 @@ class SheerkaCreateNewConcept(BaseService):
"""
refs = set()
if concept.metadata.definition_type == DEFINITION_TYPE_BNF:
if concept.get_metadata().definition_type == DEFINITION_TYPE_BNF:
from parsers.BnfNodeParser import BnfNodeConceptExpressionVisitor
other_concepts_visitor = BnfNodeConceptExpressionVisitor()
other_concepts_visitor.visit(concept.bnf)
other_concepts_visitor.visit(concept.get_bnf())
for concept in other_concepts_visitor.references:
if isinstance(concept, str):
@@ -0,0 +1,440 @@
import os
import pprint
import re
from dataclasses import dataclass
from core.builtin_concepts import BuiltinConcepts
from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka.services.sheerka_service import BaseService
from core.utils import CONSOLE_COLORS_MAP as CCM
from core.utils import evaluate_expression, as_bag
try:
rows, columns = os.popen('stty size', 'r').read().split()
except ValueError:
rows, columns = 50, 80
pp = pprint.PrettyPrinter(indent=2, width=columns)
class BaseDebugLogger:
ids = {}
@staticmethod
def next_id(hint):
if hint in BaseDebugLogger.ids:
BaseDebugLogger.ids[hint] += 1
else:
BaseDebugLogger.ids[hint] = 0
return BaseDebugLogger.ids[hint]
def __init__(self, debug_manager, who, method_name, context_id, debug_id):
pass
def debug_entering(self, **kwargs):
pass
def debug_var(self, name, value, is_error=False):
pass
def debug_rule(self, rule, results):
pass
def debug_log(self, text):
pass
class NullDebugLogger(BaseDebugLogger):
def __init__(self):
pass
class ConsoleDebugLogger(BaseDebugLogger):
def __init__(self, debug_manager, service_name, method_name, context_id, debug_id):
BaseDebugLogger.__init__(self, debug_manager, service_name, method_name, context_id, debug_id)
self.debug_manager = debug_manager
self.service_name = service_name
self.method_name = method_name
self.context_id = context_id
self.debug_id = debug_id
self.is_highlighted = ""
def debug_entering(self, **kwargs):
super().debug_entering(**kwargs)
str_text = f"{CCM['blue']}Entering {self.service_name}.{self.method_name} with {CCM['reset']}"
str_vars = pp.pformat(kwargs)
if "\n" not in str(str_vars):
self.debug_manager.debug(self.prefix() + str_text + str_vars)
else:
self.debug_manager.debug(self.prefix() + str_text)
self.debug_manager.debug(self.prefix() + str_vars)
def debug_var(self, name, value, is_error=False):
enabled = self.debug_manager.compute_var_debug(self.service_name,
self.method_name,
self.context_id,
name,
self.context_id)
if enabled == False:
return
color = 'red' if is_error else 'green'
str_text = f"{CCM[color]}..{name}={CCM['reset']}"
str_vars = "" if isinstance(enabled, str) else pp.pformat(value)
if "\n" not in str(str_vars):
self.debug_manager.debug(self.prefix() + str_text + str_vars)
else:
self.debug_manager.debug(self.prefix() + str_text)
self.debug_manager.debug(self.prefix() + str_vars)
def debug_rule(self, rule, results):
if not self.debug_manager.compute_debug_rule(rule.id, self.context_id, self.debug_id):
return
str_text = f"{CCM['green']}..results({rule.id})={CCM['reset']}"
str_vars = pp.pformat(results)
if "\n" not in str(str_vars):
self.debug_manager.debug(self.prefix() + str_text + str_vars)
else:
self.debug_manager.debug(self.prefix() + str_text)
self.debug_manager.debug(self.prefix() + str_vars)
def debug_log(self, text):
self.debug_manager.debug(self.prefix() + f"{CCM['blue']}..{text}{CCM['reset']}")
def prefix(self):
return f"[{self.context_id:2}][{self.debug_id:2}] {self.is_highlighted}"
@dataclass
class DebugVarSetting:
service_name: str
method_name: str
variable_name: str
context_id: int
context_children: bool
debug_id: int
debug_children: bool
enabled: bool
@dataclass
class DebugRuleSetting:
rule_id: str
context_id: int
debug_id: int
enabled: bool
class SheerkaDebugManager(BaseService):
NAME = "Debug"
PREFIX = "debug."
children_activation_regex = re.compile(r"(\d+)\+")
def __init__(self, sheerka):
super().__init__(sheerka)
self.activated = False # is debug activated
self.explicit = False # No need to activate context debug when debug mode is on
self.context_cache = set() # debug for specific context
self.variable_cache = set() # debug for specific variable
self.debug_vars_settings = []
self.debug_rules_settings = []
def initialize(self):
self.sheerka.bind_service_method(self.set_debug, True)
self.sheerka.bind_service_method(self.set_explicit, True)
self.sheerka.bind_service_method(self.activate_debug_for, True)
self.sheerka.bind_service_method(self.deactivate_debug_for, True)
self.sheerka.bind_service_method(self.debug_activated, False)
self.sheerka.bind_service_method(self.debug_activated_for, False)
self.sheerka.bind_service_method(self.get_context_debug_mode, False)
self.sheerka.bind_service_method(self.debug_rule, True)
self.sheerka.bind_service_method(self.debug_rule_activated, False)
self.sheerka.bind_service_method(self.inspect, False)
self.sheerka.bind_service_method(self.debug, False, visible=False)
self.sheerka.bind_service_method(self.get_debugger, False)
self.sheerka.bind_service_method(self.debug_var, False)
self.sheerka.bind_service_method(self.reset_debug, False)
self.sheerka.bind_service_method(self.get_debug_settings, False, as_name="debug_settings")
def initialize_deferred(self, context, is_first_time):
self.restore_values("activated",
"explicit",
"context_cache",
"variable_cache",
"debug_vars_settings",
"debug_rules_settings")
def reset(self):
"""
For test purpose
:return:
"""
self.activated = False
self.context_cache.clear()
self.variable_cache.clear()
self.debug_vars_settings.clear()
self.debug_rules_settings.clear()
def set_debug(self, context, value=True):
self.activated = value
self.sheerka.record_var(context, self.NAME, "activated", self.activated)
return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def set_explicit(self, context, value=True):
self.explicit = value
self.sheerka.record_var(context, self.NAME, "explicit", self.explicit)
return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def activate_debug_for(self, context, debug_id, children=False):
"""
:param context:
:param debug_id: if debug_id is str, activate variable cache, context_cache otherwise
:param children:
:return:
"""
# preprocess
if isinstance(debug_id, str) and (m := self.children_activation_regex.match(debug_id)):
debug_id = int(m.group(1))
children = True
if isinstance(debug_id, str):
self.variable_cache.add(debug_id)
self.sheerka.record_var(context, self.NAME, "variable_cache", self.variable_cache)
else:
self.context_cache.add(debug_id)
if children:
self.context_cache.add(str(debug_id) + "+")
self.sheerka.record_var(context, self.NAME, "context_cache", self.context_cache)
return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def deactivate_debug_for(self, context, debug_id, children=False):
if isinstance(debug_id, str):
self.variable_cache.discard(debug_id)
self.sheerka.record_var(context, self.NAME, "variable_cache", self.variable_cache)
else:
self.context_cache.discard(debug_id)
if children:
self.context_cache.discard(str(debug_id) + "+")
self.sheerka.record_var(context, self.NAME, "context_cache", self.context_cache)
return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def debug_activated(self):
return self.activated
def debug_activated_for(self, debug_id):
if not self.activated:
return None
return debug_id in self.variable_cache
def debug_rule_activated(self, rule_id, context_id):
"""
:param rule_id:
:param context_id:
:return:
"""
key = f"{rule_id}|{context_id}"
return key in self.rules_cache
def get_context_debug_mode(self, context_id):
if not self.activated:
return None, None
debug_for_children = "protected" if str(context_id) + "+" in self.context_cache else None
debug_for_self = "private" if not self.explicit or context_id in self.context_cache else None
return debug_for_self, debug_for_children
def inspect(self, context, context_id, *props):
"""
Print
:param context:
:param context_id:
:return:
"""
to_inspect = self.sheerka.get_execution_item(context, context_id)
if not isinstance(to_inspect, ExecutionContext):
return to_inspect
if not props:
props = ["inputs", "values.return_values"]
bag = as_bag(to_inspect)
res = {}
for prop in props:
res[prop] = evaluate_expression(prop, bag)
# res = {
# "return_values": to_inspect.values.get("return_values", None)
# }
pp.pprint(res)
return None
def debug(self, *args, **kwargs):
print(*args, **kwargs)
def get_debugger(self, context, who, method_name):
if self.compute_debug(who, method_name, context):
debug_id = ConsoleDebugLogger.next_id(context.event.get_digest() + str(context.id))
return ConsoleDebugLogger(self, who, method_name, context.id, debug_id)
return NullDebugLogger()
def debug_var(self, context,
service=None,
method=None,
variable=None,
context_id=None,
context_children=False,
debug_id=None,
debug_children=False,
enabled=True):
for setting in self.debug_vars_settings:
if setting.service_name == service and \
setting.method_name == method and \
setting.variable_name == variable and \
setting.context_id == context_id and \
setting.context_children == context_children and \
setting.debug_id == debug_id and \
setting.debug_children == debug_children:
setting.enabled = enabled
break
else:
self.debug_vars_settings.append(DebugVarSetting(service,
method,
variable,
context_id,
context_children,
debug_id,
debug_children,
enabled))
self.sheerka.record_var(context, self.NAME, "debug_vars_settings", self.debug_vars_settings)
return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def reset_debug(self, context):
self.debug_vars_settings.clear()
self.debug_rules_settings.clear()
self.sheerka.record_var(context, self.NAME, "debug_vars_settings", self.debug_vars_settings)
self.sheerka.record_var(context, self.NAME, "debug_rules_settings", self.debug_vars_settings)
return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def compute_debug(self, service_name, method_name, context):
if not self.activated:
return False
selected = []
for setting in self.debug_vars_settings:
if setting.service_name is None and setting.method_name is None and setting.context_id is None:
continue
if (setting.service_name is None or setting.service_name == service_name) and \
(setting.method_name is None or setting.method_name == method_name) and \
(setting.context_id is None or setting.context_id == context.id or (
setting.context_children and context.has_parent(setting.context_id))):
selected.append(setting.enabled)
if len(selected) == 0:
return False
res = selected[0]
for enabled in selected[1:]:
res &= enabled
return res
def compute_var_debug(self, service_name, method_name, context_id, variable_name, debug_id):
if not self.activated:
return False
selected = []
for setting in self.debug_vars_settings:
if setting.variable_name is None and setting.debug_id is None:
continue
if (setting.service_name is None or setting.service_name == service_name) and \
(setting.method_name is None or setting.method_name == method_name) and \
(setting.context_id is None or setting.context_id == context_id) and \
(setting.variable_name is None or
setting.variable_name == "*" or
setting.variable_name == variable_name) and \
(setting.debug_id is None or setting.debug_id == debug_id):
selected.append(setting.enabled)
if len(selected) == 0:
return False
res = selected[0]
for enabled in selected[1:]:
if res == False or enabled == False:
return False
if isinstance(res, str):
continue
res = enabled
return res
def debug_rule(self, context, rule=None, context_id=None, debug_id=None, enabled=True):
"""
Add a debug rule request
:param context:
:param rule:
:param context_id:
:param debug_id:
:param enabled:
:return:
"""
rule = str(rule) if rule is not None else None
for setting in self.debug_rules_settings:
if setting.rule_id == rule and \
setting.context_id == context_id and \
setting.debug_id == debug_id:
setting.enabled = enabled
break
else:
self.debug_rules_settings.append(DebugRuleSetting(rule,
context_id,
debug_id,
enabled))
self.sheerka.record_var(context, self.NAME, "debug_rules_settings", self.debug_rules_settings)
return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def compute_debug_rule(self, rule_id, context_id, debug_id):
if not self.activated:
return False
selected = []
for setting in self.debug_rules_settings:
if (setting.rule_id is None or setting.rule_id == rule_id) and \
(setting.context_id is None or setting.context_id == context_id) and \
(setting.debug_id is None or setting.debug_id == debug_id):
selected.append(setting.enabled)
if len(selected) == 0:
return False
res = selected[0]
for enabled in selected[1:]:
res &= enabled
return res
def reset_debug_rules(self, context):
self.debug_rules_settings.clear()
self.sheerka.record_var(context, self.NAME, "debug_rules_settings", self.debug_rules_settings)
return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def get_debug_settings(self):
return self.debug_vars_settings
+22 -23
View File
@@ -21,7 +21,7 @@ class SheerkaDump(BaseService):
super().__init__(sheerka)
def initialize(self):
self.sheerka.bind_service_method(self.dump_desc, True, "desc") # because concept is evaluated
self.sheerka.bind_service_method(self.dump_desc, True, "desc") # has_side_effect 'cause concept is evaluated
self.sheerka.bind_service_method(self.dump_sdp, False, "dump_sdp")
def dump_desc(self, *concept_names, eval=False):
@@ -34,7 +34,7 @@ class SheerkaDump(BaseService):
else:
concepts = self.sheerka.get_by_key(concept_name)
if self.sheerka.isinstance(concepts, BuiltinConcepts.UNKNOWN_CONCEPT):
self.sheerka.log.error(f"Concept '{concept_name}' is unknown")
print(f"Concept '{concept_name}' is unknown")
return False
if not hasattr(concepts, "__iter__"):
@@ -46,36 +46,35 @@ class SheerkaDump(BaseService):
value = evaluated.body if evaluated.key == c.key else evaluated
if not first:
self.sheerka.log.info("")
self.sheerka.log.info(f"id : {c.id}")
self.sheerka.log.info(f"name : {c.name}")
self.sheerka.log.info(f"key : {c.key}")
self.sheerka.log.info(f"definition : {c.metadata.definition}")
self.sheerka.log.info(f"type : {c.metadata.definition_type}")
self.sheerka.log.info(f"body : {c.metadata.body}")
self.sheerka.log.info(f"where : {c.metadata.where}")
self.sheerka.log.info(f"pre : {c.metadata.pre}")
self.sheerka.log.info(f"post : {c.metadata.post}")
self.sheerka.log.info(f"ret : {c.metadata.ret}")
self.sheerka.log.info(f"vars : {c.metadata.variables}")
self.sheerka.log.info(f"props : {c.metadata.props}")
print("")
print(f"id : {c.id}")
print(f"name : {c.name}")
print(f"key : {c.key}")
print(f"definition : {c.get_metadata().definition}")
print(f"type : {c.get_metadata().definition_type}")
print(f"body : {c.get_metadata().body}")
print(f"where : {c.get_metadata().where}")
print(f"pre : {c.get_metadata().pre}")
print(f"post : {c.get_metadata().post}")
print(f"ret : {c.get_metadata().ret}")
print(f"vars : {c.get_metadata().variables}")
print(f"props : {c.get_metadata().props}")
if eval:
self.sheerka.log.info(f"value : {value}")
if c.values:
for v in c.values:
self.sheerka.log.info(f"{v}: {c.get_value(v)}")
print(f"value : {value}")
for v in c.values():
print(f"{v}: {c.get_value(v)}")
else:
self.sheerka.log.info("No variable")
print("No variable")
self.sheerka.log.info(f"digest : {c.get_origin()}")
print(f"digest : {c.get_origin()}")
if self.sheerka.isaset(context, c):
items = self.sheerka.get_set_elements(context, c)
self.sheerka.log.info(f"elements : {items}")
print(f"elements : {items}")
first = False
def dump_sdp(self):
snapshot = self.sheerka.sdp.get_snapshot(SheerkaDataProvider.HeadFile)
state = self.sheerka.sdp.load_state(snapshot)
self.sheerka.log.info(get_pp().pformat(state.data))
print(get_pp().pformat(state.data))
@@ -1,8 +1,9 @@
from dataclasses import dataclass
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import expect_one, only_successful, parse_unrecognized, evaluate
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved, NotInit, ensure_concept
from core.builtin_helpers import expect_one, only_successful, parse_unrecognized, evaluate, ensure_concept
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved, NotInit, AllConceptParts, \
concept_part_value
from core.sheerka.services.SheerkaExecute import ParserInput
from core.sheerka.services.sheerka_service import BaseService
from core.tokenizer import Tokenizer
@@ -47,8 +48,10 @@ class SheerkaEvaluateConcept(BaseService):
parent = context.get_parent()
while parent is not None:
if parent.who == context.who and parent.action == BuiltinConcepts.EVALUATING_CONCEPT and \
parent.obj == concept and parent.obj.compiled == concept.compiled:
if (parent.who == context.who and
parent.action == BuiltinConcepts.EVALUATING_CONCEPT and
parent.obj == concept and
parent.obj.get_compiled() == concept.get_compiled()):
return True
parent = parent.get_parent()
@@ -90,15 +93,15 @@ class SheerkaEvaluateConcept(BaseService):
vars_needed = False
body_needed = False
if concept_part in concept.compiled and concept.compiled[concept_part] is not None:
concept_part_source = getattr(concept.metadata, concept_part.value)
if concept_part in concept.get_compiled() and concept.get_compiled()[concept_part] is not None:
concept_part_source = getattr(concept.get_metadata(), concept_part_value(concept_part))
assert concept_part_source is not None
tokens = [t.str_value for t in Tokenizer(concept_part_source)]
if check_vars:
for var_name in (v[0] for v in concept.metadata.variables):
for var_name in (v[0] for v in concept.get_metadata().variables):
if var_name in tokens:
vars_needed = True
ret.append("variables")
@@ -106,9 +109,9 @@ class SheerkaEvaluateConcept(BaseService):
if check_body and "self" in tokens:
body_needed = True
ret.append("body")
ret.append(ConceptParts.BODY)
ret.append(concept_part.value)
ret.append(concept_part)
return ret, vars_needed, body_needed
@@ -121,16 +124,16 @@ class SheerkaEvaluateConcept(BaseService):
:param var_name:
:return:
"""
if concept.metadata.where is None or concept.metadata.where.strip() == "":
if concept.get_metadata().where is None or concept.get_metadata().where.strip() == "":
return None
ret = ExpressionParser().parse(context, ParserInput(concept.metadata.where))
ret = ExpressionParser().parse(context, ParserInput(concept.get_metadata().where))
if not ret.status:
# TODO: manage invalid where clause
return None
expr = ret.body.body
to_trueify = [v[0] for v in concept.metadata.variables if v[0] != var_name]
to_trueify = [v[0] for v in concept.get_metadata().variables if v[0] != var_name]
trueified_where = str(TrueifyVisitor(to_trueify, [var_name]).visit(expr))
tokens = [t.str_value for t in Tokenizer(trueified_where)]
@@ -140,7 +143,7 @@ class SheerkaEvaluateConcept(BaseService):
compiled = compile(trueified_where, "<where clause>", "eval")
except Exception:
pass
return WhereClauseDef(concept, concept.metadata.where, trueified_where, var_name, compiled)
return WhereClauseDef(concept, concept.get_metadata().where, trueified_where, var_name, compiled)
else:
return None
@@ -153,7 +156,8 @@ class SheerkaEvaluateConcept(BaseService):
:return:
"""
ret = []
for r in [r for r in return_values if r.status]:
valid_return_values = [r for r in return_values if r.status]
for r in valid_return_values:
if where_clause_def.compiled:
try:
if eval(where_clause_def.compiled, {where_clause_def.prop: self.sheerka.objvalue(r)}):
@@ -177,10 +181,13 @@ class SheerkaEvaluateConcept(BaseService):
if len(ret) > 0:
return ret
reason = [r.body for r in return_values] if len(valid_return_values) == 0 else None
return self.sheerka.new(BuiltinConcepts.CONDITION_FAILED,
body=where_clause_def.clause,
concept=where_clause_def.concept,
prop=where_clause_def.prop)
prop=where_clause_def.prop,
reason=reason)
def manage_infinite_recursion(self, context):
"""
@@ -194,7 +201,7 @@ class SheerkaEvaluateConcept(BaseService):
concepts_found = set()
while parent and parent.obj:
if parent.who == context.who and parent.action == BuiltinConcepts.EVALUATING_CONCEPT:
body = parent.obj.metadata.body
body = parent.obj.get_metadata().body
try:
return self.sheerka.ret(self.NAME, True, InfiniteRecursionResolved(eval(body)))
except Exception:
@@ -226,11 +233,11 @@ class SheerkaEvaluateConcept(BaseService):
return self.sheerka.resolve(identifier)
return None
for part_key in ConceptParts:
if part_key in concept.compiled:
for part_key in AllConceptParts:
if part_key in concept.get_compiled():
continue
source = getattr(concept.metadata, part_key.value)
source = getattr(concept.get_metadata(), concept_part_value(part_key))
if source is None: # or not isinstance(source, str):
continue
@@ -238,22 +245,22 @@ class SheerkaEvaluateConcept(BaseService):
raise Exception("Invalid concept init. metadata must be a string")
if source.strip() == "":
concept.compiled[part_key] = DoNotResolve(source)
concept.get_compiled()[part_key] = DoNotResolve(source)
else:
# first case, when the metadata references another concept via c:xxx: keyword
if concept_found := parse_token_concept(source):
context.log(f"Recognized concept '{concept_found}'", self.NAME)
concept.compiled[part_key] = concept_found
concept.get_compiled()[part_key] = concept_found
else:
res = parse_unrecognized(context,
source,
parsers="all",
prop=part_key,
filter_func=only_successful)
concept.compiled[part_key] = res.body.body if is_only_successful(res) else res
concept.get_compiled()[part_key] = res.body.body if is_only_successful(res) else res
for var_name, default_value in concept.metadata.variables:
if var_name in concept.compiled:
for var_name, default_value in concept.get_metadata().variables:
if var_name in concept.get_compiled():
continue
if default_value is None:
@@ -263,23 +270,23 @@ class SheerkaEvaluateConcept(BaseService):
raise Exception("Invalid concept init. variable metadata must be a string")
if default_value.strip() == "":
concept.compiled[var_name] = DoNotResolve(default_value)
concept.get_compiled()[var_name] = DoNotResolve(default_value)
else:
# first case, when the metadata references another concept via c:xxx: keyword
if concept_found := parse_token_concept(default_value):
context.log(f"Recognized concept '{concept_found}'", self.NAME)
concept.compiled[var_name] = concept_found
concept.get_compiled()[var_name] = concept_found
else:
res = parse_unrecognized(context,
default_value,
parsers="all",
prop=var_name,
filter_func=only_successful)
concept.compiled[var_name] = res.body.body if is_only_successful(res) else res
concept.get_compiled()[var_name] = res.body.body if is_only_successful(res) else res
# Updates the cache of concepts when possible
if self.sheerka.has_id(concept.id):
self.sheerka.get_by_id(concept.id).compiled = concept.compiled
self.sheerka.get_by_id(concept.id).set_compiled(concept.get_compiled())
def resolve(self,
context,
@@ -325,8 +332,8 @@ class SheerkaEvaluateConcept(BaseService):
with context.push(BuiltinConcepts.EVALUATING_ATTRIBUTE,
current_prop,
desc=desc,
obj=current_concept,
path=path) as sub_context:
obj=current_concept) as sub_context:
sub_context.add_inputs(path=path)
if force_evaluation:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
@@ -352,7 +359,7 @@ class SheerkaEvaluateConcept(BaseService):
else:
# update short term memory with current concept variables
if current_concept:
for var in current_concept.metadata.variables:
for var in current_concept.get_metadata().variables:
value = current_concept.get_value(var[0])
if value != NotInit:
sub_context.add_to_short_term_memory(var[0], current_concept.get_value(var[0]))
@@ -433,23 +440,23 @@ class SheerkaEvaluateConcept(BaseService):
:return: value of the evaluation or error
"""
if concept.metadata.is_evaluated:
if concept.get_metadata().is_evaluated:
return concept
# I cannot use cache because of concept like 'number'.
# They don't have variables, but their values change every time they are instantiated
# TODO: Need to find a way to cache despite of them
# need_body = eval_body or context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED)
# if need_body and len(concept.metadata.variables) == 0 and context.sheerka.has_id(concept.id):
# if need_body and len(concept.get_metadata().variables) == 0 and context.sheerka.has_id(concept.id):
# from_cache = context.sheerka.get_by_id(concept.id)
# if from_cache.metadata.is_evaluated:
# if from_cache.get_metadata().is_evaluated:
# concept.set_value(ConceptParts.BODY, from_cache.body)
# concept.metadata.is_evaluated = True
# concept.get_metadata().is_evaluated = True
# return concept
desc = f"Evaluating concept {concept}"
with context.push(BuiltinConcepts.EVALUATING_CONCEPT, concept, desc=desc, eval_body=eval_body) as sub_context:
with context.push(BuiltinConcepts.EVALUATING_CONCEPT, concept, desc=desc) as sub_context:
sub_context.add_inputs(eval_body=eval_body)
if eval_body:
# ask for body evaluation
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
@@ -466,8 +473,8 @@ class SheerkaEvaluateConcept(BaseService):
for metadata_to_eval in all_metadata_to_eval:
if metadata_to_eval == "variables":
for var_name in (v for v in concept.variables() if v in concept.compiled):
prop_ast = concept.compiled[var_name]
for var_name in (v for v in concept.variables() if v in concept.get_compiled()):
prop_ast = concept.get_compiled()[var_name]
w_clause = self.get_where_clause_def(context, concept, var_name)
# TODO, manage when the where clause cannot be parsed
@@ -485,17 +492,17 @@ class SheerkaEvaluateConcept(BaseService):
else:
concept.set_value(var_name, resolved)
else:
part_key = ConceptParts(metadata_to_eval)
part_key = metadata_to_eval
# do not evaluate where when the body is a set
# Indeed, the way that the where clause is expressed is not a valid python or concept code
if part_key == ConceptParts.WHERE and self.sheerka.isaset(sub_context, concept.body):
continue
if part_key not in concept.compiled or concept.compiled[part_key] is None:
if part_key not in concept.get_compiled() or concept.get_compiled()[part_key] is None:
continue
metadata_ast = concept.compiled[part_key]
metadata_ast = concept.get_compiled()[part_key]
# if part_key is PRE, POST or WHERE, the concept need to be evaluated
# if we want the predicates to be resolved => so force_eval = True
@@ -514,7 +521,7 @@ class SheerkaEvaluateConcept(BaseService):
# validate PRE and WHERE condition
if part_key in (ConceptParts.PRE, ConceptParts.WHERE) and not self.sheerka.objvalue(resolved):
return self.sheerka.new(BuiltinConcepts.CONDITION_FAILED,
body=getattr(concept.metadata, metadata_to_eval),
body=getattr(concept.get_metadata(), concept_part_value(metadata_to_eval)),
concept=concept,
prop=part_key)
@@ -524,19 +531,19 @@ class SheerkaEvaluateConcept(BaseService):
concept.init_key() # Necessary for old unit tests. To remove someday
if "body" in all_metadata_to_eval:
concept.metadata.is_evaluated = True
if ConceptParts.BODY in all_metadata_to_eval:
concept.get_metadata().is_evaluated = True
# # update the cache for concepts with no variables
# Cannot use cache. See the comment at the beginning of this method
# if len(concept.metadata.variables) == 0:
# if len(concept.get_metadata().variables) == 0:
# self.sheerka.cache_manager.put(self.sheerka.CONCEPTS_BY_ID_ENTRY, concept.id, concept)
if not concept.metadata.is_builtin:
if not concept.get_metadata().is_builtin:
self.sheerka.register_object(sub_context, concept.name, concept)
# manage RET metadata
if sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED) and ConceptParts.RET in concept.values:
if sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED) and ConceptParts.RET in concept.values():
return concept.get_value(ConceptParts.RET)
else:
return concept
@@ -547,7 +554,7 @@ class SheerkaEvaluateConcept(BaseService):
needed, variables, body = self.get_needed_metadata(concept, ConceptParts.PRE, True, True)
to_eval.extend(needed)
if context.in_context(BuiltinConcepts.EVAL_WHERE_REQUESTED) or concept.metadata.need_validation:
if context.in_context(BuiltinConcepts.EVAL_WHERE_REQUESTED) or concept.get_metadata().need_validation:
# What are the cases where we do not need a validation ?
# see test_sheerka_non_reg::test_i_can_evaluate_bnf_concept_with_where_clause()
# res = sheerka.evaluate_user_input("foobar")
@@ -572,7 +579,7 @@ class SheerkaEvaluateConcept(BaseService):
to_eval.append('variables')
if not body:
to_eval.append("body")
to_eval.append(ConceptParts.BODY)
return to_eval
@@ -0,0 +1,114 @@
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import expect_one
from core.sheerka.services.sheerka_service import BaseService
from evaluators.ConceptEvaluator import ConceptEvaluator
DISABLED_RULES = "#disabled#"
LOW_PRIORITY_RULES = "#low_priority#"
class SheerkaEvaluateRules(BaseService):
NAME = "EvaluateRules"
def __init__(self, sheerka):
super().__init__(sheerka)
self.evaluators_by_name = None
def initialize(self):
self.sheerka.bind_service_method(self.evaluate_format_rules, False)
self.reset_evaluators()
def reset_evaluators(self):
# instantiate evaluators, once for all, only keep when it's enabled
evaluators = [e_class() for e_class in self.sheerka.evaluators]
evaluators = [e for e in evaluators if e.enabled]
self.evaluators_by_name = {e.short_name: e for e in evaluators}
def evaluate_format_rules(self, context, bag, disabled):
return self.evaluate_rules(context, self.sheerka.get_format_rules(), bag, disabled)
def evaluate_rules(self, context, rules, bag, disabled):
"""
evaluate the format rules, in the context of 'bag'
CAUTION : the rules MUST be sorted by priority
:param context:
:param rules:
:param bag:
:param disabled: disabled rules (because they have already been fired or whatever)
:return: { True : list of success, False :list of failed, '#disabled"': list of disabled...}
"""
with context.push(BuiltinConcepts.EVALUATING_RULES, bag, desc="Evaluating rules...") as sub_context:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
sub_context.add_inputs(bag=bag)
debugger = sub_context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rules")
debugger.debug_entering(bag=bag)
results = {}
sub_context.sheerka.add_many_to_short_term_memory(sub_context, bag)
success_priority = None
for rule in rules:
if not rule.metadata.is_enabled or rule.id in disabled:
results.setdefault(DISABLED_RULES, []).append(rule)
continue
if success_priority and rule.priority != success_priority:
results.setdefault(LOW_PRIORITY_RULES, []).append(rule)
continue
res = self.evaluate_rule(sub_context, rule, bag)
ok = res.status and self.sheerka.is_success(self.sheerka.objvalue(res))
results.setdefault(ok, []).append(rule)
if ok and success_priority is None:
success_priority = rule.priority
debugger.debug_var("results", self.get_debug_format(results))
sub_context.add_values(rules_result=results)
return results
def evaluate_rule(self, context, rule, bag):
"""
Evaluate all the predicate
:param context:
:param rule:
:param bag:
:return:
"""
results = []
for rule_predicate in rule.compiled_predicate:
if rule_predicate.source in bag:
# simple case where the rule is an item of the bag. No need of complicate evaluation
results.append(context.sheerka.ret(self.NAME, True, bag[rule_predicate.source]))
else:
# do not forget to reset the 'is_evaluated' in the case of a concept
if rule_predicate.evaluator == ConceptEvaluator.NAME:
rule_predicate.concept.get_metadata().is_evaluated = False
evaluator = self.evaluators_by_name[rule_predicate.evaluator]
results.append(evaluator.eval(context, rule_predicate.predicate))
debugger = context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rule")
debugger.debug_rule(rule, results)
# if context.sheerka.debug_rule_activated(rule_id, context.id):
# context.debug(SheerkaEvaluateRules.NAME, "evaluate_rules", f"result(#{rule_id})", results)
return expect_one(context, results)
@staticmethod
def get_debug_format(result):
"""
Return the same dictionary, the with the short formatting of the rules
eg without the action clause
:param result:
:return:
"""
return {key: [str(r) if key == True else r.short_str() for r in rules] for key, rules in result.items()}
@@ -0,0 +1,60 @@
from threading import RLock
from core.sheerka.services.sheerka_service import BaseService
class SheerkaEventManager(BaseService):
"""
This class implement a very basic publish and subscribe mechanism
It supposes that the subscriber has a little knowledge of how the publisher works
"""
NAME = "EventManager"
def __init__(self, sheerka):
super().__init__(sheerka)
self._lock = RLock()
self.subscribers = {}
def initialize(self):
self.sheerka.bind_service_method(self.subscribe, True, visible=False)
self.sheerka.bind_service_method(self.publish, True, visible=False)
def subscribe(self, topic, callback):
"""
To subscribe to a topic, just give the callback to call
Note that the callback must be a function whose first argument is a context
:param topic:
:param callback:
:return:
"""
with self._lock:
self.subscribers.setdefault(topic, []).append(callback)
def publish(self, context, topic, data=None):
"""
Publish on a topic
The data is not mandatory
:param context:
:param topic:
:param data:
:return:
"""
with self._lock:
try:
subscribers = self.subscribers[topic]
if data:
for callback in subscribers:
callback(context, data)
else:
for callback in subscribers:
callback(context)
except KeyError:
pass
def reset_topic(self, topic):
"""
Remove all subsccribers from a given topic
:param topic:
:return:
"""
self.subscribers[topic].clear()
+179 -80
View File
@@ -5,6 +5,16 @@ from core.sheerka.services.sheerka_service import BaseService
from core.tokenizer import Tokenizer, TokenKind, Token
NO_MATCH = "** No Match **"
EVALUATOR_STEPS = [
BuiltinConcepts.BEFORE_PARSING,
BuiltinConcepts.AFTER_PARSING,
BuiltinConcepts.BEFORE_EVALUATION,
BuiltinConcepts.EVALUATION,
BuiltinConcepts.AFTER_EVALUATION,
BuiltinConcepts.BEFORE_RENDERING,
BuiltinConcepts.RENDERING,
BuiltinConcepts.AFTER_RENDERING,
]
class ParserInput:
@@ -74,10 +84,10 @@ class ParserInput:
if self.start == 0 and self.end == self.length:
self.sub_text = self.text
return self.sub_text
self.sub_text = self.get_text_from_tokens(self.tokens[self.start:self.end])
self.sub_text = core.utils.get_text_from_tokens(self.tokens[self.start:self.end])
return self.sub_text
else:
return self.get_text_from_tokens(self.as_tokens(), custom_switcher, tracker)
return core.utils.get_text_from_tokens(self.as_tokens(), custom_switcher, tracker)
def as_tokens(self):
if self.sub_tokens:
@@ -145,36 +155,6 @@ class ParserInput:
return True
return False
@staticmethod
def get_text_from_tokens(tokens, custom_switcher=None, tracker=None):
"""
Create the source code, from the list of token
:param tokens: list of tokens
:param custom_switcher: to override the behaviour (the return value) of some token
:param tracker: keep track of the original token value when custom switched
:return:
"""
if tokens is None:
return ""
res = ""
if not hasattr(tokens, "__iter__"):
tokens = [tokens]
switcher = {
TokenKind.CONCEPT: lambda t: core.utils.str_concept(t.value),
}
if custom_switcher:
switcher.update(custom_switcher)
for token in tokens:
value = switcher.get(token.type, lambda t: t.value)(token)
res += value
if tracker is not None and token.type in custom_switcher:
tracker[value] = token.value
return res
class SheerkaExecute(BaseService):
"""
@@ -187,18 +167,156 @@ class SheerkaExecute(BaseService):
def __init__(self, sheerka):
super().__init__(sheerka)
self.pi_cache = Cache(default=lambda key: ParserInput(key), max_size=20)
self.instantiated_evaluators = None
self.evaluators_by_name = None
self.grouped_evaluators_cache = {} # key=step, value=tuple(evaluators for this step, sorted priorities)
self.old_values = []
def initialize(self):
self.sheerka.bind_service_method(self.execute, True)
self.sheerka.cache_manager.register_cache(self.PARSERS_INPUTS_ENTRY, self.pi_cache, False)
self.reset_evaluators()
def reset_evaluators(self):
# instantiate evaluators, once for all, only keep when it's enabled
self.instantiated_evaluators = [e_class() for e_class in self.sheerka.evaluators]
self.instantiated_evaluators = [e for e in self.instantiated_evaluators if e.enabled]
self.evaluators_by_name = {e.short_name: e for e in self.instantiated_evaluators}
# get default evaluators by process step
for process_step in EVALUATOR_STEPS:
self.grouped_evaluators_cache[f"{process_step}|__default"] = self.get_grouped_evaluators(
[e for e in self.instantiated_evaluators if process_step in e.steps])
# @staticmethod
# def get_grouped_evaluators(instantiated_evaluators, process_step):
# """
# For a given list of evaluators and a given process step
# Computes
# * the evaluators eligible for this step
# * the list of sorted priorities for theses evaluators
# :param instantiated_evaluators:
# :param process_step:
# :return:
# """
# grouped = {}
# for evaluator in [e for e in instantiated_evaluators if e.enabled and process_step in e.steps]:
# grouped.setdefault(evaluator.priority, []).append(evaluator)
#
# sorted_groups = sorted(grouped.keys(), reverse=True)
# return grouped, sorted_groups
@staticmethod
def get_grouped_evaluators(evaluators):
"""
For a given list of evaluators,
group them by priorities
sort the priorities
:param evaluators:
:return: tuple({priority: List of evaluators with this priority}, list of sorted priorities)
"""
grouped = {}
for evaluator in evaluators:
grouped.setdefault(evaluator.priority, []).append(evaluator)
sorted_groups = sorted(grouped.keys(), reverse=True)
return grouped, sorted_groups
def preprocess(self, items, preprocess_definitions):
for preprocess in preprocess_definitions:
for item in items:
if self.matches(item.name, preprocess.get_value("preprocess_name")):
for var_name, value in preprocess.values().items():
if var_name == "preprocess_name":
continue
if hasattr(item, var_name):
self.old_values.append((item, var_name, getattr(item, var_name)))
setattr(item, var_name, value)
def preprocess_old(self, context, parsers_or_evaluators, mode):
if mode == "parsers":
if not context.preprocess and not context.preprocess_parsers:
return parsers_or_evaluators
items = context.preprocess_parsers
elif mode == "evaluators":
if not context.preprocess and not context.preprocess_evaluators:
return parsers_or_evaluators
items = context.preprocess_evaluators
else:
raise ValueError(mode)
if not hasattr(parsers_or_evaluators, "__iter__"):
single_one = True
parsers_or_evaluators = [parsers_or_evaluators]
else:
single_one = False
if items:
res = []
for item in items:
for e in parsers_or_evaluators:
if item == e.name:
res.append(e)
break
else:
raise ValueError(f"{item} not found.")
parsers_or_evaluators = res
if context.preprocess:
for preprocess in context.preprocess:
for e in parsers_or_evaluators:
if self.matches(e.name, preprocess.get_value("name")):
for var_name in preprocess.values:
if var_name == "name":
continue
if hasattr(e, var_name):
self.old_values.append((e, var_name, getattr(e, var_name)))
setattr(e, var_name, preprocess.get_value(var_name))
return parsers_or_evaluators[0] if single_one else parsers_or_evaluators
def get_evaluators(self, context, process_step):
"""
Returns the list of evaluators to use for a specific test
:param context:
:param process_step:
:return:
"""
# Normal case, the evaluators are the default one
if not context.preprocess_evaluators and not context.preprocess:
return self.grouped_evaluators_cache[f"{process_step}|__default"]
# First case, only use a subset of evaluators
if context.preprocess_evaluators and not context.preprocess:
key = str(process_step) + "|" + "|".join(context.preprocess_evaluators)
try:
return self.grouped_evaluators_cache[key]
except KeyError:
evaluators = [self.evaluators_by_name[e] for e in context.preprocess_evaluators]
grouped = self.get_grouped_evaluators(evaluators)
self.grouped_evaluators_cache[key] = grouped
return grouped
# final case, evaluators attributes are modified by the context
# So first, get the modified evaluators
evaluators = [self.evaluators_by_name[e] for e in
context.preprocess_evaluators] if context.preprocess_evaluators else self.instantiated_evaluators
self.preprocess(evaluators, context.preprocess)
evaluators = [e for e in evaluators if e.enabled] # make sure they are still enabled
key = str(process_step) + "|" + "|".join([e.name for e in evaluators if e.enabled])
try:
return self.grouped_evaluators_cache[key]
except KeyError:
grouped = self.get_grouped_evaluators(evaluators)
self.grouped_evaluators_cache[key] = grouped
return grouped
def get_parser_input(self, text, tokens=None):
"""
Returns new or existing parser input
:param text:
:param tokens:
:param length:
:return:
"""
@@ -212,7 +330,7 @@ class SheerkaExecute(BaseService):
self.pi_cache.put(text, pi)
return pi
key = text or ParserInput.get_text_from_tokens(tokens)
key = text or core.utils.get_text_from_tokens(tokens)
pi = ParserInput(key, tokens)
self.pi_cache.put(key, pi)
return pi
@@ -251,7 +369,7 @@ class SheerkaExecute(BaseService):
# group the parsers by priorities
instantiated_parsers = [parser(sheerka=self.sheerka) for parser in self.sheerka.parsers.values()]
instantiated_parsers = self.preprocess(context, instantiated_parsers)
instantiated_parsers = self.preprocess_old(context, instantiated_parsers, "parsers")
grouped_parsers = {}
for parser in [p for p in instantiated_parsers if p.enabled]:
@@ -272,13 +390,12 @@ class SheerkaExecute(BaseService):
# if self.sheerka.log.isEnabledFor(logging.DEBUG):
# debug_text = "'" + to_parse + "'" if isinstance(to_parse, str) \
# else "'" + BaseParser.get_text_from_tokens(to_parse) + "' as tokens"
# else "'" + core.utils.get_text_from_tokens(to_parse) + "' as tokens"
# context.log(f"Parsing {debug_text}")
with context.push(BuiltinConcepts.PARSING,
{"parser": parser.name},
desc=f"Parsing using {parser.name}",
logger=parser.verbose_log) as sub_context:
desc=f"Parsing using {parser.name}") as sub_context:
sub_context.add_inputs(to_parse=to_parse)
res = parser.parse(sub_context, to_parse)
if res is not None:
@@ -318,27 +435,13 @@ class SheerkaExecute(BaseService):
if not isinstance(return_values, list):
return_values = [return_values]
# group the evaluators by priority and sort them
# The first one to be applied will be the one with the highest priority
grouped_evaluators = {}
instantiated_evaluators = [e_class() for e_class in self.sheerka.evaluators]
grouped_evaluators, sorted_priorities = self.get_evaluators(context, process_step)
# pre-process evaluators if needed
instantiated_evaluators = self.preprocess(context, instantiated_evaluators)
for evaluator in [e for e in instantiated_evaluators if e.enabled and process_step in e.steps]:
grouped_evaluators.setdefault(evaluator.priority, []).append(evaluator)
# order the groups by priority, the higher first
sorted_priorities = sorted(grouped_evaluators.keys(), reverse=True)
# process
iteration = 0
while True:
with context.push(process_step,
{"iteration": iteration},
desc=f"iteration #{iteration}",
iteration=iteration) as iteration_context:
{"step": process_step, "iteration": iteration},
desc=f"iteration #{iteration}") as iteration_context:
simple_digest = return_values[:]
iteration_context.add_inputs(return_values=simple_digest)
@@ -348,13 +451,14 @@ class SheerkaExecute(BaseService):
evaluated_items = []
to_delete = []
for evaluator in grouped_evaluators[priority]:
evaluator = self.preprocess(context, evaluator.__class__()) # fresh copy
evaluator.reset()
sub_context_desc = f"Evaluating using {evaluator.name} ({priority=})"
with iteration_context.push(process_step,
{"iteration": iteration, "evaluator": evaluator.name},
desc=sub_context_desc,
logger=evaluator.verbose_log) as sub_context:
{"step": process_step,
"iteration": iteration,
"evaluator": evaluator.name},
desc=sub_context_desc) as sub_context:
sub_context.add_inputs(return_values=original_items)
# process evaluators that work on one simple return value at the time
@@ -365,6 +469,8 @@ class SheerkaExecute(BaseService):
if evaluator.matches(sub_context, item):
# init the evaluator is possible
# KSI. 20201102 : Evaluators are now instantiated at startup,
# Can we move this section into reset_evaluators()
if hasattr(evaluator, "init_evaluator") and not evaluator.is_initialized:
evaluator.init_evaluator(sub_context, original_items)
@@ -401,6 +507,7 @@ class SheerkaExecute(BaseService):
# process evaluators that work on all return values
else:
if evaluator.matches(sub_context, original_items):
results = evaluator.eval(sub_context, original_items)
if results is None:
continue
@@ -427,6 +534,8 @@ class SheerkaExecute(BaseService):
# inc the iteration and continue
iteration += 1
self.undo_preprocess()
return return_values
def execute(self, context, return_values, execution_steps):
@@ -441,40 +550,30 @@ class SheerkaExecute(BaseService):
for step in execution_steps:
copy = return_values[:] if hasattr(return_values, "__iter__") else [return_values]
with context.push(BuiltinConcepts.PROCESSING,
{"step": step},
step=step, iteration=0, desc=f"{step=}") as sub_context:
{"step": step, "iteration": 0},
desc=f"{step=}") as sub_context:
sub_context.add_inputs(return_values=copy)
if step == BuiltinConcepts.PARSING:
return_values = self.call_parsers(sub_context, return_values)
else:
return_values = self.call_evaluators(sub_context, return_values, step)
if copy != return_values:
has_changed = copy != return_values
if has_changed:
sub_context.log_result(return_values)
sub_context.add_values(return_values=return_values)
sub_context.add_values(has_changed=has_changed)
return return_values
def preprocess(self, context, parsers_or_evaluators):
if not context.preprocess:
return parsers_or_evaluators
def undo_preprocess(self):
for item, var_name, value in self.old_values:
setattr(item, var_name, value)
if not hasattr(parsers_or_evaluators, "__iter__"):
single_one = True
parsers_or_evaluators = [parsers_or_evaluators]
else:
single_one = False
for preprocess in context.preprocess:
for e in parsers_or_evaluators:
if self.matches(e.name, preprocess.get_value("name")):
for var_name in preprocess.values:
if var_name == "name":
continue
if hasattr(e, var_name):
setattr(e, var_name, preprocess.get_value(var_name))
return parsers_or_evaluators[0] if single_one else parsers_or_evaluators
self.old_values.clear()
@staticmethod
def matches(parser_or_evaluator_name, preprocessor_name):
@@ -1,5 +1,5 @@
from core.builtin_concepts import BuiltinConcepts
from core.concept import ensure_concept
from core.builtin_helpers import ensure_concept
from core.sheerka.services.sheerka_service import BaseService
@@ -25,8 +25,8 @@ class SheerkaHasAManager(BaseService):
context.log(f"Setting concept {concept_a} has a {concept_b}", who=self.NAME)
ensure_concept(concept_a, concept_b)
if (BuiltinConcepts.HASA in concept_a.metadata.props and
concept_b in concept_a.metadata.props[BuiltinConcepts.HASA]):
if (BuiltinConcepts.HASA in concept_a.get_metadata().props and
concept_b in concept_a.get_metadata().props[BuiltinConcepts.HASA]):
return self.sheerka.ret(
self.NAME,
False,
@@ -49,5 +49,5 @@ class SheerkaHasAManager(BaseService):
"""
ensure_concept(concept_a, concept_b)
return (BuiltinConcepts.HASA in concept_a.metadata.props and
concept_b in concept_a.metadata.props[BuiltinConcepts.HASA])
return (BuiltinConcepts.HASA in concept_a.get_metadata().props and
concept_b in concept_a.get_metadata().props[BuiltinConcepts.HASA])
+43 -12
View File
@@ -1,8 +1,10 @@
from dataclasses import dataclass
from cache.FastCache import FastCache
from cache.ListIfNeededCache import ListIfNeededCache
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept
from core.global_symbols import CONTEXT_DISPOSED
from core.sheerka.services.sheerka_service import BaseService, ServiceObj
@@ -13,48 +15,72 @@ class MemoryObject(ServiceObj):
class SheerkaMemory(BaseService):
NAME = "Memory"
GLOBAL = "global"
SHORT_TERM_OBJECTS_ENTRY = "Memory:ShortTermMemoryObjects"
OBJECTS_ENTRY = "Memory:Objects"
def __init__(self, sheerka):
super().__init__(sheerka)
self.short_term_objects = ListIfNeededCache()
self.short_term_objects = FastCache()
self.objects = ListIfNeededCache(default=lambda k: self.sheerka.sdp.get(self.OBJECTS_ENTRY, k))
self.registration = {}
def initialize(self):
self.sheerka.bind_service_method(self.get_from_short_term_memory, False, visible=False)
self.sheerka.bind_service_method(self.add_to_short_term_memory, True, visible=False)
self.sheerka.bind_service_method(self.remove_context, True, as_name="clear_short_term_memory", visible=False)
self.sheerka.bind_service_method(self.add_to_memory, True, visible=False)
self.sheerka.bind_service_method(self.add_many_to_short_term_memory, True, visible=False)
self.sheerka.bind_service_method(self.get_from_memory, False)
self.sheerka.bind_service_method(self.register_object, True, visible=False)
self.sheerka.bind_service_method(self.unregister_object, True, visible=False)
self.sheerka.bind_service_method(self.add_registered_objects, True, visible=False)
self.sheerka.bind_service_method(self.memory, False)
self.sheerka.cache_manager.register_cache(self.SHORT_TERM_OBJECTS_ENTRY, self.short_term_objects, persist=False)
self.sheerka.cache_manager.register_cache(self.OBJECTS_ENTRY, self.objects, persist=True, use_ref=True)
def reset(self):
self.short_term_objects.clear()
def initialize_deferred(self, context, is_first_time):
self.sheerka.subscribe(CONTEXT_DISPOSED, self.remove_context)
def get_from_short_term_memory(self, context, key):
while True:
key_to_use = (str(context.id) if context else "") + ":" + key
if (obj := self.sheerka.cache_manager.get(self.SHORT_TERM_OBJECTS_ENTRY, key_to_use)) is not None:
return obj
try:
id_to_use = context.id if context else self.GLOBAL
return self.short_term_objects.cache[id_to_use][key]
except KeyError:
if context is None:
return None
if context is None:
return None
context = context.get_parent()
context = context.get_parent()
def get_all_short_term_memory(self, context):
return self.short_term_objects.get(context.id)
def add_to_short_term_memory(self, context, key, concept):
def add_to_short_term_memory(self, context, key, value):
if context:
context.stm = True
key_to_use = (str(context.id) if context else "") + ":" + key
return self.sheerka.cache_manager.put(self.SHORT_TERM_OBJECTS_ENTRY, key_to_use, concept)
id_to_use = context.id
else:
id_to_use = SheerkaMemory.GLOBAL
if id_to_use in self.short_term_objects.cache:
self.short_term_objects.cache[id_to_use][key] = value
else:
self.short_term_objects.put(id_to_use, {key: value})
def add_many_to_short_term_memory(self, context, bag):
context.stm = True
self.short_term_objects.put(context.id if context else self.GLOBAL, bag)
def remove_context(self, context):
self.short_term_objects.evict_by_key(lambda k: k.startswith(str(context.id) + ":"))
try:
del self.short_term_objects.cache[context.id]
except KeyError:
pass
def add_to_memory(self, context, key, concept):
"""
@@ -74,6 +100,11 @@ class SheerkaMemory(BaseService):
def register_object(self, context, key, concept):
"""
Before adding objects to memory, they first need to be registered
More:
We don't want to add all evaluated concept into memory
(because some of them may be ref to concept already in memory)
So we first register them, and add the end of sheerka.evaluate_user_input()
all remaining registered concepts will be added to memory
:param context:
:param key:
:param concept:
@@ -1,7 +1,8 @@
from core.builtin_concepts import BuiltinConcepts
from core.concept import ensure_concept
from core.builtin_helpers import ensure_concept
from core.concept import NotInit, freeze_concept_attrs, Concept
from core.sheerka.services.sheerka_service import BaseService
from parsers.BnfParser import BnfParser
from parsers.BnfDefinitionParser import BnfDefinitionParser
class SheerkaModifyConcept(BaseService):
@@ -47,6 +48,9 @@ class SheerkaModifyConcept(BaseService):
BuiltinConcepts.CONCEPT_ALREADY_DEFINED,
body=concept))
# update attributes
freeze_concept_attrs(concept)
self.sheerka.cache_manager.update_concept(old_version, concept)
# TODO : update concept by first keyword : have a look at update_references() below
@@ -72,8 +76,8 @@ class SheerkaModifyConcept(BaseService):
for concept_id in refs:
concept = self.sheerka.get_by_id(concept_id)
if concept.bnf is not None:
BnfParser.update_recurse_id(context, concept_id, concept.bnf)
if concept.get_bnf() is not None:
BnfDefinitionParser.update_recurse_id(context, concept_id, concept.get_bnf())
# remove the grammar entry so that it can be recreated
self.sheerka.cache_manager.delete(self.sheerka.CONCEPTS_GRAMMARS_ENTRY, concept_id)
@@ -88,7 +92,9 @@ class SheerkaModifyConcept(BaseService):
:return:
"""
ensure_concept(concept)
concept.set_value(attribute, value)
attr = attribute.str_id if isinstance(attribute, Concept) else attribute
concept.set_value(attr, value)
return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def get_attr(self, concept, attribute):
@@ -103,6 +109,8 @@ class SheerkaModifyConcept(BaseService):
if not self.sheerka.is_success(concept):
return concept
if (value := concept.get_value(attribute)) == BuiltinConcepts.NOT_INITIALIZED:
attr = attribute.str_id if isinstance(attribute, Concept) else attribute
if (value := concept.get_value(attr)) == NotInit:
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"#concept": concept, "#attr": attribute})
return value
+100
View File
@@ -0,0 +1,100 @@
from core.builtin_concepts import BuiltinConcepts
from core.sheerka.services.sheerka_service import BaseService
from core.utils import as_bag
from out.ConsoleVisistor import ConsoleVisitor
from out.DeveloperVisitor import DeveloperVisitor
class SheerkaOut(BaseService):
NAME = "Out"
def __init__(self, sheerka):
super().__init__(sheerka)
self.out_visitors = [ConsoleVisitor()]
def initialize(self):
self.sheerka.bind_service_method(self.process_return_values, False)
def create_out_tree(self, context, obj):
return self.create_out_tree_recursive(context, {'__obj': obj}, DeveloperVisitor(self, set(), 0))
def create_out_tree_recursive(self, context, bag, visitor):
debugger = context.get_debugger(SheerkaOut.NAME, "create_out_tree")
debugger.debug_entering(bag=bag)
current_obj = bag["__obj"]
bag = self.update_bag(bag, visitor.list_recursion_depth)
valid_rules = self.sheerka.evaluate_format_rules(context, bag, visitor.already_seen).get(True, None)
res = None
if valid_rules:
if len(valid_rules) > 1:
# TODO manage when too many rules
pass
rule = valid_rules[0]
if rule.id in visitor.already_seen:
debugger.debug_log(f"Rule #{rule.id} already fired.")
else:
debugger.debug_log(f"Applying rule {rule}.")
visitor.already_seen.add(rule.id)
bag.update(as_bag(current_obj)) # update with the current obj attributes
visitor.visit(context, rule.compiled_action, bag)
res = visitor.get_result()
if res is None:
debugger.debug_log(f"No matching rule.")
res = current_obj
debugger.debug_var("out_tree", res)
return res
def process_return_values(self, context, ret):
with context.push(BuiltinConcepts.BEFORE_RENDERING,
None,
desc=f"step='{BuiltinConcepts.BEFORE_RENDERING}'") as sub_context:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
# sub_context.deactivate_push()
out_tree = self.create_out_tree(sub_context, ret)
# sub_context.activate_push()
if out_tree:
for visitor in self.out_visitors:
visitor.visit(context, out_tree, None)
if hasattr(visitor, "finalize"):
visitor.finalize()
return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.NO_RESULT))
def update_bag(self, bag, depth):
obj = bag["__obj"]
bag["__tab"] = " " * depth
if self.sheerka.isinstance(obj, BuiltinConcepts.RETURN_VALUE):
bag["__ret"] = obj
if self.sheerka.is_container(obj.body):
bag["__ret_container"] = obj.body
bag["__ret_value"] = self.simplify_list(obj.body.body)
bag["__ret_val"] = bag["__ret_value"]
else:
bag["__ret_value"] = self.simplify_list(obj.body)
bag["__ret_val"] = bag["__ret_value"]
elif isinstance(obj, list) and len(obj) > 0 and self.sheerka.isinstance(obj[0], BuiltinConcepts.RETURN_VALUE):
bag["__rets"] = obj
return bag
@staticmethod
def simplify_list(item):
try:
return item[0] if hasattr(item, "__len__") and len(item) == 1 else item
except KeyError:
return item # Caution. it's a dict, not a list !
@@ -1,5 +1,8 @@
import ast
from core.builtin_concepts import BuiltinConcepts
from core.sheerka.services.sheerka_service import BaseService
from core.utils import as_bag
class SheerkaResultConcept(BaseService):
@@ -14,10 +17,31 @@ class SheerkaResultConcept(BaseService):
self.sheerka.bind_service_method(self.get_results_by_command, True) # digest is recorded
self.sheerka.bind_service_method(self.get_last_results, True) # digest is recorded
self.sheerka.bind_service_method(self.get_results, False)
self.sheerka.bind_service_method(self.get_execution_item, False)
def get_results_by_digest(self, context, digest, record_digest=True):
@staticmethod
def get_predicate(**kwargs):
if len(kwargs) == 0:
return None
res = []
if "filter" in kwargs:
res.append(kwargs["filter"])
kwargs.pop("filter")
for k, v in kwargs.items():
if k in ("depth", "recursion_depth"):
continue
if isinstance(v, str):
v = '"' + v.translate(str.maketrans({'"': r'\"'})) + '"'
res.append(f"{k} == {v}")
predicate = " and ".join(res)
return compile(ast.parse(predicate, mode="eval"), "<SheerkaResultManager.get_predicate>", mode="eval")
def get_results_by_digest(self, context, digest, filter=None, record_digest=True, **kwargs):
"""
Gets the entire execution tree for the given event digest
:param filter:
:param context:
:param digest:
:param record_digest:
@@ -26,27 +50,39 @@ class SheerkaResultConcept(BaseService):
if digest is None:
return None
if filter is not None:
kwargs["filter"] = filter
try:
result = self.sheerka.sdp.load_result(digest)
event = self.sheerka.sdp.load_event(digest)
if record_digest:
context.log(f"Recording digest '{digest}'")
self.sheerka.record(context, self.NAME, "digest", digest)
self.sheerka.record_var(context, self.NAME, "digest", digest)
explanation = self.sheerka.new(BuiltinConcepts.EXPLANATION,
digest=event.get_digest(),
command=event.message,
body=self.as_list(result, self.get_predicate(**kwargs)))
# add format instructions if applicable
if (depth := kwargs.get("depth", None)) is not None or \
(depth := kwargs.get("recursion_depth", None)) is not None:
explanation.set_format_instr(recursion_depth=depth, recurse_on="_children")
return explanation
return self.sheerka.new(BuiltinConcepts.EXPLANATION,
digest=event.get_digest(),
command=event.message,
body=self.as_list(result))
except FileNotFoundError as ex:
context.log_error(f"Digest {digest} is not found.", self.NAME, ex)
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"digest": digest})
def get_results_by_command(self, context, command, record_digest=True):
def get_results_by_command(self, context, command, filter=None, record_digest=True, **kwargs):
"""
Get the result of the command that starts with command
:param context:
:param command:
:param filter:
:param record_digest:
:return:
"""
@@ -59,7 +95,7 @@ class SheerkaResultConcept(BaseService):
for event in self.sheerka.sdp.load_events(self.page_size, start):
consumed += 1
if event.message.startswith(command):
return self.get_results_by_digest(context, event.get_digest(), record_digest)
return self.get_results_by_digest(context, event.get_digest(), filter, record_digest, **kwargs)
if consumed < self.page_size:
break
@@ -69,10 +105,11 @@ class SheerkaResultConcept(BaseService):
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"command": command})
def get_last_results(self, context, record_digest=True):
def get_last_results(self, context, filter=None, record_digest=True, **kwargs):
"""
Gets the results of the last command
:param context:
:param filter:
:param record_digest:
:return:
"""
@@ -84,7 +121,7 @@ class SheerkaResultConcept(BaseService):
for event in self.sheerka.sdp.load_events(page_size, start):
consumed += 1
if self.sheerka.sdp.has_result(event.get_digest()):
return self.get_results_by_digest(context, event.get_digest(), record_digest)
return self.get_results_by_digest(context, event.get_digest(), filter, record_digest, **kwargs)
if consumed < page_size:
break
@@ -97,27 +134,45 @@ class SheerkaResultConcept(BaseService):
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"query": "last"})
def get_results(self, context):
def get_results(self, context, filter=None, **kwargs):
"""
Use the last digest saved to get the execution results
:param context:
:param filter:
:return:
"""
digest = self.sheerka.load(self.NAME, "digest")
digest = self.sheerka.load_var(self.NAME, "digest")
if digest is None:
context.log("No recorded digest found.")
return None
return self.get_results_by_digest(context, digest, False)
return self.get_results_by_digest(context, digest, filter, False, **kwargs)
def get_execution_item(self, context, item_id):
digest = self.sheerka.load_var(self.NAME, "digest")
if digest is None:
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body="no digest")
try:
result = self.sheerka.sdp.load_result(digest)
items = list(self.as_list(result, self.get_predicate(id=item_id)))
if len(items) == 0:
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"id": item_id})
return items[0]
except FileNotFoundError as ex:
context.log_error(f"Digest {digest} is not found.", self.NAME, ex)
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"digest": digest})
@staticmethod
def as_list(execution_context):
def as_list(execution_context, predicate):
def _yield_result(lst):
for e in lst:
yield e
if predicate is None or eval(predicate, as_bag(e)):
yield e
if e._children:
yield from _yield_result(e._children)
@@ -0,0 +1,642 @@
import operator
import re
from dataclasses import dataclass
from typing import Union
from cache.Cache import Cache
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
from core.builtin_helpers import parse_unrecognized, only_successful, ensure_rule
from core.concept import Concept
from core.global_symbols import RULE_PRECEDENCE_MODIFIED, RULE_COMPARISON_CONTEXT
from core.rule import Rule
from core.sheerka.services.sheerka_service import BaseService
from core.tokenizer import Keywords, TokenKind, Token, IterParser
from core.utils import index_tokens, COLORS, get_text_from_tokens
from evaluators.ConceptEvaluator import ConceptEvaluator
from evaluators.PythonEvaluator import PythonEvaluator
from parsers.BaseNodeParser import SourceCodeWithConceptNode, ConceptNode, SourceCodeNode
from parsers.PythonParser import PythonNode
identifier_regex = re.compile(r"[\w _.]+")
@dataclass
class FormatRuleError:
pass
@dataclass
class BraceMismatch(FormatRuleError):
lbrace: Token
@dataclass
class UnexpectedEof(FormatRuleError):
message: str
token: Token = None
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, UnexpectedEof):
return False
return self.message == other.message and (other.token is None or other.token == self.token)
def __hash__(self):
return hash(self.message, self.token)
@dataclass
class FormatRuleSyntaxError(FormatRuleError):
message: str
token: Token
@dataclass
class FormatAstNode:
@staticmethod
def repr_value(items):
if items is None:
return ""
return ", ".join(repr(item) for item in items)
@dataclass
class FormatAstRawText(FormatAstNode):
text: str
@dataclass
class FormatAstVariable(FormatAstNode):
name: str
format: Union[str, None] = None
value: object = None
index: object = None
@dataclass
class FormatAstVariableNotFound(FormatAstNode):
name: str
@dataclass
class FormatAstGrid(FormatAstNode):
pass
@dataclass
class FormatAstList(FormatAstNode):
variable: str
items_prop: str = None # where to search the list if variable does not resolve to an iterable
recurse_on: str = None
recursion_depth: int = 0
items: object = None
@dataclass
class FormatAstColor(FormatAstNode):
def __init__(self, color, format_ast):
self.color = color
self.format_ast = format_ast
def __repr__(self):
return f"{self.color}({self.format_ast})"
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, FormatAstColor):
return False
return self.color == other.color and self.format_ast == other.format_ast
def __hash__(self):
return hash((self.color, self.format_ast))
@dataclass
class FormatAstFunction(FormatAstNode):
name: str
args: list = None
kwargs: dict = None
class FormatAstSequence(FormatAstNode):
def __init__(self, items):
self.items = items
def __repr__(self):
return "FormatAstSequence(" + self.repr_value(self.items) + ")"
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, FormatAstSequence):
return False
return self.items == other.items
class FormatRuleParser(IterParser):
@staticmethod
def to_text(list_or_dict_of_tokens):
"""
Works on list of list of tokens
or dict of list of tokens
:param list_or_dict_of_tokens:
:return:
"""
get_text = get_text_from_tokens
if isinstance(list_or_dict_of_tokens, list):
return [get_text(i) for i in list_or_dict_of_tokens]
if isinstance(list_or_dict_of_tokens, dict):
return {k: get_text(v) for k, v in list_or_dict_of_tokens.items()}
raise NotImplementedError("")
def to_value(self, tokens):
"""
Works on list of tokens
return string or numeric value of the tokens
:return:
"""
value = get_text_from_tokens(tokens)
if value[0] in ("'", '"'):
return value[1:-1]
try:
return int(value)
except ValueError:
pass
try:
return float(value)
except ValueError:
self.error_sink = FormatRuleSyntaxError(f"'{value}' is not numeric", None)
def parse(self):
"""
Parses a format rule
format ::= {variable'} | function(...) | rawtext
:return:
"""
if self.source == "":
return FormatAstRawText("")
buffer = []
result = []
res = None
escaped = False
def _flush_buffer():
if len(buffer) > 0:
result.append(FormatAstRawText(get_text_from_tokens(buffer)))
buffer.clear()
while self.next_token(skip_whitespace=False):
if not escaped:
if self.token.type == TokenKind.IDENTIFIER and self.the_token_after().type == TokenKind.LPAR:
_flush_buffer()
res = self.parse_function(self.token)
elif self.token.type == TokenKind.LBRACE:
_flush_buffer()
res = self.parse_variable(self.token)
elif self.token.type == TokenKind.BACK_SLASH:
escaped = True
else:
buffer.append(self.token)
else:
escaped = False
buffer.append(self.token)
if self.error_sink:
break
if res:
result.append(res)
res = None
_flush_buffer()
return [] if len(result) == 0 else result[0] if len(result) == 1 else FormatAstSequence(result)
def parse_function(self, func_name):
self.next_token()
self.next_token()
if self.token.type == TokenKind.EOF:
self.error_sink = UnexpectedEof("while parsing function", func_name)
return None
param_buffer = []
args = []
kwargs = {}
get_text = get_text_from_tokens
def _process_parameters():
if len(param_buffer) == 0:
self.error_sink = FormatRuleSyntaxError("no parameter found", self.token)
return None
if (index := index_tokens(param_buffer, "=")) > 0:
kwargs[get_text(param_buffer[:index])] = param_buffer[index + 1:]
else:
args.append(param_buffer.copy())
param_buffer.clear()
while True:
if self.token.type == TokenKind.RPAR:
if len(param_buffer) > 0:
_process_parameters()
break
elif self.token.type == TokenKind.COMMA:
_process_parameters()
if self.error_sink:
break
else:
param_buffer.append(self.token)
if not self.next_token():
break
if self.error_sink:
return None
if self.token.type != TokenKind.RPAR:
self.error_sink = UnexpectedEof("while parsing function", func_name)
return None
if func_name.value in COLORS:
return self.return_color(func_name.value, args, kwargs)
elif func_name.value == "list":
return self.return_list(args, kwargs)
return FormatAstFunction(func_name.value, self.to_text(args), self.to_text(kwargs))
def parse_variable(self, lbrace):
self.next_token()
if self.token.type == TokenKind.EOF:
self.error_sink = UnexpectedEof("while parsing variable", lbrace)
return None
buffer = []
while True:
if self.token.type == TokenKind.RBRACE:
break
buffer.append(self.token)
if not self.next_token():
break
# if self.error_sink:
# return None
if self.token.type != TokenKind.RBRACE:
self.error_sink = UnexpectedEof("while parsing variable", lbrace)
return None
if len(buffer) == 0:
self.error_sink = FormatRuleSyntaxError("variable name not found", None)
return None
variable = get_text_from_tokens(buffer)
try:
index = variable.index(":")
return FormatAstVariable(variable[:index], variable[index + 1:])
except ValueError:
return FormatAstVariable(variable)
def return_color(self, color, args, kwargs):
if len(kwargs) > 0:
self.error_sink = FormatRuleSyntaxError("keyword arguments are not supported", None)
return None
if len(args) == 0:
return FormatAstColor(color, FormatAstRawText(""))
if len(args) > 1:
self.error_sink = FormatRuleSyntaxError("only one parameter supported", args[1][0])
return None
source = get_text_from_tokens(args[0])
if len(source) > 1 and source[0] in ("'", '"') and source[-1] in ("'", '"'):
source = source[1:-1]
parser = FormatRuleParser(source)
res = parser.parse()
self.error_sink = parser.error_sink
return FormatAstColor(color, res)
else:
try:
index = source.index(":")
variable, vformat = source[:index], source[index + 1:]
except ValueError:
variable, vformat = source, None
if not identifier_regex.fullmatch(variable):
self.error_sink = FormatRuleSyntaxError("Invalid identifier", None)
return None
return FormatAstColor(color, FormatAstVariable(variable, vformat))
def return_list(self, args, kwargs):
len_args = len(args)
if len_args < 1:
self.error_sink = FormatRuleSyntaxError("variable name not found", None)
return None
if len_args > 3:
self.error_sink = FormatRuleSyntaxError("too many positional arguments", args[3][0])
return None
variable_name = get_text_from_tokens(args[0])
recurse_on, recursion_depth, items_prop = None, 0, None
if len_args == 2:
recursion_depth = self.to_value(args[1])
elif len_args == 3:
recursion_depth = self.to_value(args[1])
recurse_on = self.to_value(args[2])
if "recurse_on" in kwargs:
recurse_on = self.to_value(kwargs["recurse_on"])
if "recursion_depth" in kwargs:
recursion_depth = self.to_value(kwargs["recursion_depth"])
if "items_prop" in kwargs:
items_prop = self.to_value(kwargs["items_prop"])
if self.error_sink:
return None
if not isinstance(recursion_depth, int):
self.error_sink = FormatRuleSyntaxError("'recursion_depth' must be an integer", None)
return None
return FormatAstList(variable_name, items_prop, recurse_on, recursion_depth)
@dataclass()
class RulePredicate:
source: str
evaluator: str
predicate: ReturnValueConcept
concept: Union[Concept, None]
class SheerkaRuleManager(BaseService):
NAME = "RuleManager"
RULE_IDS = "Rules_Ids"
FORMAT_RULE_ENTRY = "RuleManager:FormatRules"
EXEC_RULE_ENTRY = "RuleManager:ExecRules"
def __init__(self, sheerka):
super().__init__(sheerka)
self.format_rule_cache = Cache(default=lambda k: self.sheerka.sdp.get(self.FORMAT_RULE_ENTRY, k))
self.exec_rule_cache = Cache(default=lambda k: self.sheerka.sdp.get(self.EXEC_RULE_ENTRY, k))
self._format_rules = None # sorted by priority
def initialize(self):
self.sheerka.bind_service_method(self.create_new_rule, True, visible=False)
self.sheerka.bind_service_method(self.get_rule_by_id, False)
self.sheerka.bind_service_method(self.dump_desc_rule, False, as_name="desc_rule")
self.sheerka.bind_service_method(self.get_format_rules, False, visible=False)
self.sheerka.cache_manager.register_cache(self.FORMAT_RULE_ENTRY, self.format_rule_cache, True, True)
self.sheerka.cache_manager.register_cache(self.EXEC_RULE_ENTRY, self.exec_rule_cache, True, True)
def initialize_deferred(self, context, is_first_time):
if is_first_time:
# add builtin rules if it's the first initialization of Sheerka
self.init_builtin_rules(context)
# adds the other rules (when it's not the first time)
self.format_rule_cache.populate(lambda: self.sheerka.sdp.list(self.FORMAT_RULE_ENTRY), lambda rule: rule.id)
self.exec_rule_cache.populate(lambda: self.sheerka.sdp.list(self.EXEC_RULE_ENTRY), lambda rule: rule.id)
# compile all the rules
for rule_id in self.format_rule_cache:
rule = self.init_rule(context, self.format_rule_cache.get(rule_id))
# update rules priorities
self.update_rules_priorities(context)
self.sheerka.subscribe(RULE_PRECEDENCE_MODIFIED, self.update_rules_priorities)
def update_rules_priorities(self, context):
"""
Ask the SheerkaComparisonManager for the priorities
:return:
"""
# get the priorities
rules_weights = self.sheerka.get_concepts_weights(BuiltinConcepts.PRECEDENCE, RULE_COMPARISON_CONTEXT)
# compile all the rules
for rule_id in self.format_rule_cache:
rule = self.format_rule_cache.get(rule_id)
if rule.str_id in rules_weights:
rule.priority = rules_weights[rule.str_id]
self._format_rules = None
def init_rule(self, context, rule: Rule):
if rule.metadata.is_compiled:
return
if rule.compiled_predicate is None:
res = self.compile_when(context, self.NAME, rule.metadata.predicate)
if not isinstance(res, list):
rule.error_sink = [res.body]
return
rule.compiled_predicate = res
if rule.compiled_action is None:
res = self.compile_print(context, rule.metadata.action)
if not res.status:
rule.error_sink = [res.body]
return
rule.compiled_action = res.body
# rule.variables = self.get_variables()
rule.metadata.is_compiled = True
rule.metadata.is_enabled = True
return rule
def compile_when(self, context, name, source):
# parser_input = self.sheerka.services[SheerkaExecute.NAME].get_parser_input(source)
parsed = parse_unrecognized(context,
source,
parsers="all",
who=name,
prop=Keywords.WHEN,
filter_func=only_successful)
if not parsed.status:
return parsed
if self.sheerka.isinstance(parsed.body, BuiltinConcepts.ONLY_SUCCESSFUL):
parsed = parsed.body.body
return self.add_evaluators(source, parsed if hasattr(parsed, "__iter__") else [parsed])
def compile_print(self, context, source):
parser = FormatRuleParser(source)
parsed = parser.parse()
if parser.error_sink:
return self.sheerka.ret(self.NAME,
False,
self.sheerka.new(BuiltinConcepts.ERROR, body=[parser.error_sink]))
else:
return self.sheerka.ret(self.NAME, True, parsed)
def set_id_if_needed(self, rule: Rule):
"""
Set the id for the concept if needed
:param rule:
:return:
"""
if rule.metadata.id is not None:
return
rule.metadata.id = str(self.sheerka.cache_manager.get(self.sheerka.CONCEPTS_KEYS_ENTRY, self.RULE_IDS))
def create_new_rule(self, context, rule):
"""
Saves the new rule in DB
:param context:
:param rule:
:return:
"""
sheerka = self.sheerka
# set id before saving in db
self.set_id_if_needed(rule)
if rule.compiled_predicate and rule.compiled_action:
rule.metadata.is_compiled = True
rule.metadata.is_enabled = True
# save it
if rule.metadata.action_type == "print":
self.sheerka.cache_manager.put(self.FORMAT_RULE_ENTRY, rule.metadata.id, rule)
self._format_rules = None
else:
self.sheerka.cache_manager.put(self.EXEC_RULE_ENTRY, rule.metadata.id, rule)
# process the return if needed
ret = sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_RULE, body=rule))
return ret
def init_builtin_rules(self, context):
# self.sheerka.init_log.debug("Initializing default rules")
rules = [
Rule("print", "Print return values", "__rets", "list(__rets)"),
Rule("print", "Print ReturnValue",
"__ret",
"\\ReturnValue(who={__ret.who}, status={__ret.status}, value={__ret.value})"),
Rule("print", "Failed ReturnValue in red",
"__ret and not __ret.status",
"red(__ret)"),
Rule("print", "List explanations",
"isinstance(__ret_container, BuiltinConcepts.EXPLANATION)",
"blue(__ret_container.digest) : {__ret_container.command}\nlist(__ret_container)"),
Rule("print", "Print ExecutionContext",
"isinstance(__obj, ExecutionContext)",
"[{id:3}] {__tab}{desc} ({status})"),
Rule("print", "Display formatted list",
"isinstance(__ret_container, BuiltinConcepts.TO_LIST)",
"list(__ret_container)"),
]
for r in rules:
self.create_new_rule(context, 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_greatest(context, BuiltinConcepts.PRECEDENCE, rules[0], RULE_COMPARISON_CONTEXT)
def get_rule_by_id(self, rule_id):
"""
Looks in the caches for a specific rule id
:param rule_id:
:return:
"""
if rule_id is None:
return None
rule = self.format_rule_cache.get(rule_id)
if rule:
return rule
rule = self.exec_rule_cache.get(rule_id)
if rule:
return rule
metadata = [("id", rule_id)]
return self.sheerka.new(BuiltinConcepts.UNKNOWN_RULE, body=metadata)
def dump_desc_rule(self, rules):
"""
dumps the definition of a rule
:param rules:
:return:
"""
ensure_rule(rules)
if not hasattr(rules, "__iter__"):
rules = [rules]
first = True
for rule in rules:
if not first:
self.sheerka.log.info("")
self.sheerka.log.info(f"id : {rule.id}")
self.sheerka.log.info(f"name : {rule.metadata.name}")
self.sheerka.log.info(f"type : {rule.metadata.action_type}")
self.sheerka.log.info(f"predicate : {rule.metadata.predicate}")
self.sheerka.log.info(f"action : {rule.metadata.action}")
self.sheerka.log.info(f"compiled : {rule.metadata.is_compiled}")
self.sheerka.log.info(f"enabled : {rule.metadata.is_enabled}")
def get_format_rules(self):
if self._format_rules:
return self._format_rules
self._format_rules = sorted(self.format_rule_cache.get_all(), key=operator.attrgetter('priority'), reverse=True)
return self._format_rules
def add_evaluators(self, source, ret_vals):
"""
Browse the ReturnValueConcepts to determine the evaluator to use
Returns a list of tuple (evaluator_name, return_value)
:param source:
:param ret_vals:
:return:
"""
res = []
for r in ret_vals:
underlying = self.sheerka.objvalue(r)
if isinstance(underlying, PythonNode):
res.append(RulePredicate(source, PythonEvaluator.NAME, r, None))
elif isinstance(underlying, SourceCodeWithConceptNode):
res.append(RulePredicate(source, PythonEvaluator.NAME, r, None))
elif isinstance(underlying, SourceCodeNode):
res.append(RulePredicate(source, PythonEvaluator.NAME, r, None))
elif isinstance(underlying, Concept):
res.append(RulePredicate(source, ConceptEvaluator.NAME, r, underlying))
elif hasattr(underlying, "__iter__") and len(underlying) == 1 and isinstance(underlying[0], ConceptNode):
res.append(RulePredicate(source, ConceptEvaluator.NAME, r, underlying[0].concept))
else:
raise NotImplementedError(r)
return res
+22 -21
View File
@@ -1,9 +1,9 @@
import core.builtin_helpers
from cache.Cache import Cache
from cache.SetCache import SetCache
from core.ast.nodes import python_to_concept
from core.ast_helpers import UnreferencedVariablesVisitor
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept, ConceptParts, ensure_concept, DEFINITION_TYPE_BNF
from core.concept import Concept, ConceptParts, DEFINITION_TYPE_BNF
from core.sheerka.services.SheerkaModifyConcept import SheerkaModifyConcept
from core.sheerka.services.sheerka_service import BaseService
@@ -41,9 +41,10 @@ class SheerkaSetsManager(BaseService):
"""
context.log(f"Setting concept {concept} is a {concept_set}", who=self.NAME)
ensure_concept(concept, concept_set)
core.builtin_helpers.ensure_concept(concept, concept_set)
if BuiltinConcepts.ISA in concept.metadata.props and concept_set in concept.metadata.props[BuiltinConcepts.ISA]:
if BuiltinConcepts.ISA in concept.get_metadata().props and concept_set in concept.get_metadata().props[
BuiltinConcepts.ISA]:
return self.sheerka.ret(
self.NAME,
False,
@@ -71,7 +72,7 @@ class SheerkaSetsManager(BaseService):
"""
context.log(f"Adding concept {concept} to set {concept_set}", who=self.NAME)
ensure_concept(concept, concept_set)
core.builtin_helpers.ensure_concept(concept, concept_set)
set_elements = self.sets.get(concept_set.id)
if set_elements and concept.id in set_elements:
@@ -98,7 +99,7 @@ class SheerkaSetsManager(BaseService):
"""
context.log(f"Adding concepts {concepts} to set {concept_set}", who=self.NAME)
ensure_concept(concept_set)
core.builtin_helpers.ensure_concept(concept_set)
already_in_set = []
for concept in concepts:
res = self.add_concept_to_set(context, concept, concept_set)
@@ -124,7 +125,7 @@ class SheerkaSetsManager(BaseService):
:return:
"""
ensure_concept(concept)
core.builtin_helpers.ensure_concept(concept)
def _get_set_elements(sub_concept):
if not self.isaset(context, sub_concept):
@@ -146,8 +147,8 @@ class SheerkaSetsManager(BaseService):
concepts.extend(other_concepts)
# apply the where clause if any
if sub_concept.metadata.where:
new_condition = self._validate_where_clause(sub_concept)
if sub_concept.get_metadata().where:
new_condition = self._validate_where_clause(context, sub_concept)
if not new_condition:
return self.sheerka.new(BuiltinConcepts.CONDITION_FAILED, body=sub_concept)
@@ -179,7 +180,7 @@ class SheerkaSetsManager(BaseService):
:return:
"""
ensure_concept(a, b)
core.builtin_helpers.ensure_concept(a, b)
# TODO, first check the 'isa' property of a
if not (a.id and b.id):
@@ -190,11 +191,11 @@ class SheerkaSetsManager(BaseService):
def isa(self, a, b):
ensure_concept(a, b)
if BuiltinConcepts.ISA not in a.metadata.props:
core.builtin_helpers.ensure_concept(a, b)
if BuiltinConcepts.ISA not in a.get_metadata().props:
return False
for c in a.metadata.props[BuiltinConcepts.ISA]:
for c in a.get_metadata().props[BuiltinConcepts.ISA]:
if c == b:
return True
if self.isa(self.sheerka.get_by_id(c.id), b):
@@ -216,7 +217,7 @@ class SheerkaSetsManager(BaseService):
# KSI 20200629
# To resolve infinite recursion between group concepts and BNF concepts
if concept.metadata.definition_type == DEFINITION_TYPE_BNF:
if concept.get_metadata().definition_type == DEFINITION_TYPE_BNF:
return False
# check if it has a group
@@ -231,18 +232,18 @@ class SheerkaSetsManager(BaseService):
return self.isaset(context, concept.body)
def _validate_where_clause(self, concept):
python_parser_result = [r for r in concept.compiled[ConceptParts.WHERE] if r.who == "parsers.Python"]
def _validate_where_clause(self, context, concept):
python_parser_result = [r for r in concept.get_compiled()[ConceptParts.WHERE] if r.who == "parsers.Python"]
if not python_parser_result or not python_parser_result[0].status:
return None
ast_ = python_parser_result[0].body.body.ast_
ast_as_concepts = python_to_concept(ast_)
names = core.builtin_helpers.get_names(self.sheerka, ast_as_concepts)
if len(names) != 1 or names[0] != concept.metadata.body:
visitor = UnreferencedVariablesVisitor(context)
names = list(visitor.get_names(ast_))
if len(names) != 1 or names[0] != concept.get_metadata().body:
return None
condition = concept.metadata.where.replace(concept.metadata.body, "sheerka.objvalue(x)")
condition = concept.get_metadata().where.replace(concept.get_metadata().body, "sheerka.objvalue(x)")
expression = f"""
result=[]
for x in xx__concepts__xx:
@@ -277,7 +278,7 @@ for x in xx__concepts__xx:
errors = []
for element_id in ids:
concept = self.sheerka.get_by_id(element_id)
if len(concept.metadata.variables) == 0:
if len(concept.get_metadata().variables) == 0:
# The concepts are directly taken from Sheerka.get_by_id, so variable cannot be filled
# It's the reason why we only evaluate concept with no variable
evaluated = self.sheerka.evaluate_concept(sub_context, concept)
@@ -19,6 +19,9 @@ class Variable(ServiceObj):
def get_key(self):
return f"{self.who}|{self.key}"
def __str__(self):
return f"({self.who}){self.key}={self.value}"
class SheerkaVariableManager(BaseService):
NAME = "VariableManager"
@@ -26,18 +29,28 @@ class SheerkaVariableManager(BaseService):
def __init__(self, sheerka):
super().__init__(sheerka)
self.bound = {
"sheerka.enable_process_return_values": "enable_process_return_values",
"sheerka.save_execution_context": "save_execution_context"
}
def initialize(self):
self.sheerka.bind_service_method(self.record, True)
self.sheerka.bind_service_method(self.load, False)
self.sheerka.bind_service_method(self.delete, True)
self.sheerka.bind_service_method(self.set, True)
self.sheerka.bind_service_method(self.get, False)
self.sheerka.bind_service_method(self.record_var, True, visible=False)
self.sheerka.bind_service_method(self.load_var, False, visible=False)
self.sheerka.bind_service_method(self.delete_var, True, visible=False)
self.sheerka.bind_service_method(self.set_var, True)
self.sheerka.bind_service_method(self.get_var, False)
self.sheerka.bind_service_method(self.list_vars, False)
cache = Cache(default=lambda k: self.sheerka.sdp.get(self.VARIABLES_ENTRY, k))
cache = Cache()
cache.populate(lambda: self.sheerka.sdp.list(self.VARIABLES_ENTRY), lambda var: var.get_key())
self.sheerka.cache_manager.register_cache(self.VARIABLES_ENTRY, cache, True, True)
def record(self, context, who, key, value):
for variable in cache.get_all():
if variable.key in self.bound:
setattr(self.sheerka, self.bound[variable.key], variable.value)
def record_var(self, context, who, key, value):
"""
:param context:
@@ -49,20 +62,33 @@ class SheerkaVariableManager(BaseService):
variable = Variable(context.event.get_digest(), who, key, value, None)
self.sheerka.cache_manager.put(self.VARIABLES_ENTRY, variable.get_key(), variable)
# TODO: manage credentials
if key in self.bound:
setattr(self.sheerka, self.bound[key], value)
return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
def load(self, who, key):
def load_var(self, who, key):
variable = self.sheerka.cache_manager.get(self.VARIABLES_ENTRY, who + "|" + key)
if variable is None:
return None
return variable.value
def delete(self, context, who, key):
def delete_var(self, context, who, key):
self.sheerka.cache_manager.delete(self.VARIABLES_ENTRY, who + "|" + key)
def set(self, context, key, value):
return self.record(context, context.event.user_id, key, value)
def set_var(self, context, key, value):
return self.record_var(context, context.event.user_id, key, value)
def get(self, context, key):
return self.load(context.event.user_id, key)
def get_var(self, context, key):
return self.load_var(context.event.user_id, key)
def list_vars(self, context, all_vars=False):
if all_vars:
res = [str(v) for v in self.sheerka.cache_manager.copy(self.VARIABLES_ENTRY).values()]
else:
res = [str(v) for v in self.sheerka.cache_manager.copy(self.VARIABLES_ENTRY).values() if
v.who == context.event.user_id]
return res
@@ -10,6 +10,7 @@ class BaseService:
"""
Base class for services
"""
def __init__(self, sheerka):
self.sheerka = sheerka
@@ -19,3 +20,13 @@ class BaseService:
:return:
"""
pass
def restore_values(self, *args):
"""
Use Variable Manager to restore the state of a service
:param args:
:return:
"""
for prop_name in args:
if (value := self.sheerka.load_var(self.NAME, prop_name)) is not None:
setattr(self, prop_name, value)