Implemented a first and basic version of a Rete rule engine
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
import pprint
|
||||
import time
|
||||
|
||||
@@ -87,9 +86,6 @@ class ExecutionContext:
|
||||
self.obj = obj
|
||||
self.concepts = concepts
|
||||
|
||||
self_debug, self.debug_mode = sheerka.get_context_debug_mode(self.id)
|
||||
self.debug_enabled = self_debug is not None
|
||||
|
||||
@property
|
||||
def elapsed(self):
|
||||
if self._start == 0:
|
||||
@@ -184,10 +180,6 @@ class ExecutionContext:
|
||||
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
|
||||
@@ -315,9 +307,10 @@ class ExecutionContext:
|
||||
to_str = self.return_value_to_str(r)
|
||||
self._logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
|
||||
|
||||
def get_debugger(self, who, method_name):
|
||||
return self.sheerka.get_debugger(self, who, method_name)
|
||||
def get_debugger(self, who, method_name, new_debug_id=True):
|
||||
return self.sheerka.get_debugger(self, who, method_name, new_debug_id)
|
||||
|
||||
# TODO: TO REMOVE
|
||||
def debug(self, who, method_name, variable_name, text, is_error=False):
|
||||
activated = self.sheerka.debug_activated_for(who)
|
||||
if activated:
|
||||
@@ -330,6 +323,7 @@ class ExecutionContext:
|
||||
self.sheerka.debug(f"[{self._id:3}] {CCM[color]}{who}.{method_name}.{variable_name}: {CCM['reset']}")
|
||||
self.sheerka.debug(str_text)
|
||||
|
||||
# TODO: TO REMOVE
|
||||
def debug_entering(self, who, method_name, **kwargs):
|
||||
if self.sheerka.debug_activated_for(who):
|
||||
str_text = pp.pformat(kwargs)
|
||||
@@ -340,6 +334,7 @@ class ExecutionContext:
|
||||
self.sheerka.debug(f"[{self._id:3}] {CCM['blue']}Entering {who}.{method_name}:{CCM['reset']}")
|
||||
self.sheerka.debug(f"[{self._id:3}] {str_text}")
|
||||
|
||||
# TODO: TO REMOVE
|
||||
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']}")
|
||||
@@ -493,5 +488,3 @@ class ExecutionContext:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
+80
-28
@@ -1,6 +1,7 @@
|
||||
import inspect
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from operator import attrgetter
|
||||
|
||||
import core.builtin_helpers
|
||||
import core.utils
|
||||
@@ -10,8 +11,7 @@ from cache.IncCache import IncCache
|
||||
from core.builtin_concepts import ErrorConcept, ReturnValueConcept, UnknownConcept
|
||||
from core.builtin_concepts_ids import BuiltinErrors, BuiltinConcepts
|
||||
from core.concept import Concept, ConceptParts, get_concept_attrs
|
||||
from core.error import ErrorObj
|
||||
from core.global_symbols import EVENT_USER_INPUT_EVALUATED, NotInit, NotFound
|
||||
from core.global_symbols import EVENT_USER_INPUT_EVALUATED, NotInit, NotFound, ErrorObj, EVENT_ONTOLOGY_CREATED
|
||||
from core.profiling import profile
|
||||
from core.sheerka.ExecutionContext import ExecutionContext
|
||||
from core.sheerka.SheerkaOntologyManager import SheerkaOntologyManager, OntologyAlreadyExists
|
||||
@@ -31,6 +31,23 @@ EXECUTE_STEPS = [
|
||||
BuiltinConcepts.AFTER_EVALUATION
|
||||
]
|
||||
|
||||
RULES_EVALUATE_STEPS = [
|
||||
BuiltinConcepts.BEFORE_RULES_EVALUATION,
|
||||
BuiltinConcepts.RULES_EVALUATION,
|
||||
BuiltinConcepts.AFTER_RULES_EVALUATION,
|
||||
]
|
||||
|
||||
RULES_EXECUTE_STEPS = [
|
||||
BuiltinConcepts.BEFORE_EVALUATION,
|
||||
BuiltinConcepts.EVALUATION,
|
||||
BuiltinConcepts.AFTER_EVALUATION
|
||||
]
|
||||
|
||||
# when a concept is instantiated via resolve or false_resolve
|
||||
# It indicate which parameter was used to recognize the concept
|
||||
RECOGNIZED_BY_ID = "by_id"
|
||||
RECOGNIZED_BY_NAME = "by_name"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SheerkaMethod:
|
||||
@@ -92,17 +109,25 @@ class Sheerka(Concept):
|
||||
|
||||
self.save_execution_context = True
|
||||
self.enable_process_return_values = True
|
||||
self.enable_process_rules = True
|
||||
|
||||
self.methods_with_context = {"test_using_context"} # only the names, the method is defined in sheerka_methods
|
||||
self.sheerka_methods = {
|
||||
"test": SheerkaMethod(self.test, False),
|
||||
"test_using_context": SheerkaMethod(self.test_using_context, False),
|
||||
"test_dict": SheerkaMethod(self.test_dict, False)
|
||||
"test_dict": SheerkaMethod(self.test_dict, False),
|
||||
"test_error": SheerkaMethod(self.test_error, False),
|
||||
}
|
||||
|
||||
self.locals = {}
|
||||
self.concepts_ids = None
|
||||
|
||||
def __copy__(self):
|
||||
return self
|
||||
|
||||
def __deepcopy__(self, memodict={}):
|
||||
return self
|
||||
|
||||
@property
|
||||
def concepts_grammars(self):
|
||||
"""
|
||||
@@ -138,7 +163,7 @@ class Sheerka(Concept):
|
||||
|
||||
setattr(self, bound_method.__name__, bound_method)
|
||||
|
||||
def initialize(self, root_folder: str = None, save_execution_context=None, enable_process_return_values=None):
|
||||
def initialize(self, root_folder: str = None, **kwargs):
|
||||
"""
|
||||
Starting Sheerka
|
||||
Loads the current configuration
|
||||
@@ -149,11 +174,10 @@ class Sheerka(Concept):
|
||||
:return: ReturnValue(Success or Error)
|
||||
"""
|
||||
|
||||
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
|
||||
self.save_execution_context = kwargs.get("save_execution_context", self.save_execution_context)
|
||||
self.enable_process_return_values = kwargs.get("enable_process_return_values",
|
||||
self.enable_process_return_values)
|
||||
self.enable_process_rules = kwargs.get("enable_process_rules", self.enable_process_rules)
|
||||
|
||||
try:
|
||||
self.during_initialisation = True
|
||||
@@ -168,6 +192,7 @@ class Sheerka(Concept):
|
||||
self.get_builtin_evaluators()
|
||||
self.initialize_services()
|
||||
self.initialize_builtin_evaluators()
|
||||
self.om.init_subscribers()
|
||||
|
||||
event = Event("Initializing Sheerka.", user_id=self.name)
|
||||
self.om.save_event(event)
|
||||
@@ -234,11 +259,12 @@ class Sheerka(Concept):
|
||||
|
||||
core.utils.import_module_and_sub_module('core.sheerka.services')
|
||||
base_class = "core.sheerka.services.sheerka_service.BaseService"
|
||||
for service in core.utils.get_sub_classes("core.sheerka.services", base_class):
|
||||
instance = service(self)
|
||||
if hasattr(instance, "initialize"):
|
||||
instance.initialize()
|
||||
self.services[service.NAME] = instance
|
||||
services = [service(self) for service in core.utils.get_sub_classes("core.sheerka.services", base_class)]
|
||||
services.sort(key=attrgetter("order"))
|
||||
for service in services:
|
||||
if hasattr(service, "initialize"):
|
||||
service.initialize()
|
||||
self.services[service.NAME] = service
|
||||
|
||||
def initialize_services_deferred(self, context, is_first_time):
|
||||
"""
|
||||
@@ -325,7 +351,6 @@ class Sheerka(Concept):
|
||||
ontologies = self.om.current_sdp().load_ontologies()
|
||||
if not ontologies:
|
||||
return
|
||||
|
||||
for ontology_name in list(reversed(ontologies))[1:]:
|
||||
self.om.push_ontology(ontology_name, False)
|
||||
self.initialize_services_deferred(context, False)
|
||||
@@ -360,6 +385,10 @@ class Sheerka(Concept):
|
||||
ret = self.execute(execution_context, [user_input, reduce_requested], EXECUTE_STEPS)
|
||||
execution_context.add_values(return_values=ret)
|
||||
|
||||
# rule management
|
||||
if self.enable_process_rules:
|
||||
ret = self.execute_rules(execution_context, ret, RULES_EVALUATE_STEPS, RULES_EXECUTE_STEPS)
|
||||
|
||||
if self.om.is_dirty:
|
||||
self.om.commit(execution_context)
|
||||
|
||||
@@ -388,10 +417,14 @@ class Sheerka(Concept):
|
||||
:return:
|
||||
"""
|
||||
|
||||
def new_instances(concepts):
|
||||
def add_recognized_by(c, _recognized_by):
|
||||
c.set_hint(BuiltinConcepts.RECOGNIZED_BY, _recognized_by)
|
||||
return c
|
||||
|
||||
def new_instances(concepts, _recognized_by):
|
||||
if hasattr(concepts, "__iter__"):
|
||||
return [self.new_from_template(c, c.key) for c in concepts]
|
||||
return self.new_from_template(concepts, concepts.key)
|
||||
return [add_recognized_by(self.new_from_template(c, c.key), _recognized_by) for c in concepts]
|
||||
return add_recognized_by(self.new_from_template(concepts, concepts.key), _recognized_by)
|
||||
|
||||
if concept is None:
|
||||
return None
|
||||
@@ -421,10 +454,11 @@ class Sheerka(Concept):
|
||||
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.set_hint(BuiltinConcepts.RECOGNIZED_BY, RECOGNIZED_BY_ID)
|
||||
return instance
|
||||
elif concept[0]:
|
||||
if self.is_known(found := self.get_by_name(concept[0])):
|
||||
instances = new_instances(found)
|
||||
instances = new_instances(found, RECOGNIZED_BY_NAME)
|
||||
core.builtin_helpers.set_is_evaluated(instances)
|
||||
return instances
|
||||
else:
|
||||
@@ -433,17 +467,22 @@ class Sheerka(Concept):
|
||||
# otherwise search in db
|
||||
if isinstance(concept, str):
|
||||
if self.is_known(found := self.get_by_name(concept)):
|
||||
instances = new_instances(found)
|
||||
instances = new_instances(found, RECOGNIZED_BY_NAME)
|
||||
core.builtin_helpers.set_is_evaluated(instances, check_nb_variables=True)
|
||||
return instances
|
||||
|
||||
return None
|
||||
|
||||
def fast_resolve(self, key, return_new=True):
|
||||
def new_instances(concepts):
|
||||
def add_recognized_by(c, _recognized_by):
|
||||
c.set_hint(BuiltinConcepts.RECOGNIZED_BY, _recognized_by)
|
||||
return c
|
||||
|
||||
def new_instances(concepts, _recognized_by):
|
||||
if hasattr(concepts, "__iter__"):
|
||||
return [self.new_from_template(c, c.key) for c in concepts]
|
||||
return self.new_from_template(concepts, concepts.key)
|
||||
return [add_recognized_by(self.new_from_template(c, c.key), _recognized_by) for c in concepts]
|
||||
|
||||
return add_recognized_by(self.new_from_template(concepts, concepts.key), _recognized_by)
|
||||
|
||||
if isinstance(key, Token):
|
||||
if key.type == TokenKind.RULE: # do not recognize rules !!!
|
||||
@@ -459,14 +498,17 @@ class Sheerka(Concept):
|
||||
|
||||
if key[1]:
|
||||
concept = self.om.get(self.CONCEPTS_BY_ID_ENTRY, key[1])
|
||||
recognized_by = RECOGNIZED_BY_ID
|
||||
else:
|
||||
concept = self.om.get(self.CONCEPTS_BY_NAME_ENTRY, key[0])
|
||||
recognized_by = RECOGNIZED_BY_NAME
|
||||
else:
|
||||
concept = self.om.get(self.CONCEPTS_BY_NAME_ENTRY, key)
|
||||
recognized_by = RECOGNIZED_BY_NAME
|
||||
|
||||
if concept is NotFound:
|
||||
return None
|
||||
return new_instances(concept) if return_new else concept
|
||||
return new_instances(concept, recognized_by) if return_new else concept
|
||||
|
||||
def new(self, concept_key, **kwargs):
|
||||
"""
|
||||
@@ -478,6 +520,8 @@ class Sheerka(Concept):
|
||||
"""
|
||||
if isinstance(concept_key, tuple):
|
||||
concept_key, concept_id = concept_key[0], concept_key[1]
|
||||
elif isinstance(concept_key, Concept):
|
||||
concept_key, concept_id = concept_key.key, concept_key.id
|
||||
else:
|
||||
concept_id = None
|
||||
|
||||
@@ -547,12 +591,13 @@ class Sheerka(Concept):
|
||||
if name in self.om.current_sdp().load_ontologies():
|
||||
self.initialize_services_deferred(context, False)
|
||||
|
||||
self.om.save_ontologies()
|
||||
self.om.save_ontologies_names()
|
||||
self.publish(context, EVENT_ONTOLOGY_CREATED, name)
|
||||
|
||||
return self.ret(self.name, True, self.new(BuiltinConcepts.SUCCESS))
|
||||
|
||||
def pop_ontology(self):
|
||||
ontology = self.om.pop_ontology()
|
||||
def pop_ontology(self, context):
|
||||
ontology = self.om.pop_ontology(context)
|
||||
|
||||
self.om.reset_sheerka_state()
|
||||
for service in self.services.values():
|
||||
@@ -561,7 +606,7 @@ class Sheerka(Concept):
|
||||
if hasattr(service, "reset_state"):
|
||||
service.reset_state()
|
||||
|
||||
self.om.save_ontologies()
|
||||
self.om.save_ontologies_names()
|
||||
return self.ret(self.name, True, self.new(BuiltinConcepts.ONTOLOGY_REMOVED, body=ontology))
|
||||
|
||||
def get_ontology(self, context):
|
||||
@@ -699,6 +744,13 @@ class Sheerka(Concept):
|
||||
|
||||
return bool(obj)
|
||||
|
||||
@staticmethod
|
||||
def is_error(obj):
|
||||
"""
|
||||
opposite of is_success
|
||||
"""
|
||||
return not Sheerka.is_success(obj)
|
||||
|
||||
@staticmethod
|
||||
def is_known(obj):
|
||||
if not isinstance(obj, Concept):
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from cache.Cache import Cache
|
||||
from cache.CacheManager import CacheManager
|
||||
from cache.DictionaryCache import DictionaryCache
|
||||
from cache.SetCache import SetCache
|
||||
from core.concept import copy_concepts_attrs, load_concepts_attrs
|
||||
from core.global_symbols import NotFound, Removed
|
||||
from core.global_symbols import NotFound, Removed, EVENT_CONCEPT_CREATED, EVENT_CONCEPT_DELETED, EVENT_RULE_CREATED, \
|
||||
EVENT_RULE_DELETED, EVENT_CONCEPT_ID_DELETED, EVENT_RULE_ID_DELETED
|
||||
from core.utils import sheerka_deepcopy
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProvider
|
||||
|
||||
@@ -88,6 +91,11 @@ class Ontology:
|
||||
|
||||
class SheerkaOntologyManager:
|
||||
ROOT_ONTOLOGY_NAME = "__default__"
|
||||
SELF_CACHE_MANAGER = "__ontology_manager__" # cache to store SheerkaOntologyManager info
|
||||
CONCEPTS_BY_ONTOLOGY_ENTRY = "ConceptsByOntologyEntry"
|
||||
RULES_BY_ONTOLOGY_ENTRY = "RulesByOntologyEntry"
|
||||
ONTOLOGY_BY_CONCEPT_ENTRY = "OntologyByConceptEntry"
|
||||
ONTOLOGY_BY_RULE_ENTRY = "OntologyByRuleEntry"
|
||||
|
||||
def __init__(self, sheerka, root_folder, cache_only):
|
||||
self.sheerka = sheerka
|
||||
@@ -98,6 +106,20 @@ class SheerkaOntologyManager:
|
||||
ref_cache_manager = CacheManager(self.cache_only, sdp=SheerkaDataProvider(root_folder, self.sheerka))
|
||||
self.ontologies = [Ontology(self.ROOT_ONTOLOGY_NAME, ref_cache_manager, None)]
|
||||
|
||||
self_sdp = SheerkaDataProvider(root_folder, self.sheerka, self.SELF_CACHE_MANAGER)
|
||||
self.self_cache_manager = CacheManager(self.cache_only, sdp=self_sdp)
|
||||
cache = SetCache(max_size=None).auto_configure(self.CONCEPTS_BY_ONTOLOGY_ENTRY)
|
||||
self.self_cache_manager.register_cache(self.CONCEPTS_BY_ONTOLOGY_ENTRY, cache)
|
||||
|
||||
cache = SetCache(max_size=None).auto_configure(self.RULES_BY_ONTOLOGY_ENTRY)
|
||||
self.self_cache_manager.register_cache(self.RULES_BY_ONTOLOGY_ENTRY, cache)
|
||||
|
||||
cache = Cache(max_size=None).auto_configure(self.ONTOLOGY_BY_CONCEPT_ENTRY)
|
||||
self.self_cache_manager.register_cache(self.ONTOLOGY_BY_CONCEPT_ENTRY, cache)
|
||||
|
||||
cache = Cache(max_size=None).auto_configure(self.ONTOLOGY_BY_RULE_ENTRY)
|
||||
self.self_cache_manager.register_cache(self.ONTOLOGY_BY_RULE_ENTRY, cache)
|
||||
|
||||
@property
|
||||
def ontologies_names(self):
|
||||
return [o.name for o in self.ontologies]
|
||||
@@ -111,6 +133,12 @@ class SheerkaOntologyManager:
|
||||
self.frozen = False
|
||||
return self
|
||||
|
||||
def init_subscribers(self):
|
||||
self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.on_concept_created)
|
||||
self.sheerka.subscribe(EVENT_CONCEPT_DELETED, self.on_concept_deleted)
|
||||
self.sheerka.subscribe(EVENT_RULE_CREATED, self.on_rule_created)
|
||||
self.sheerka.subscribe(EVENT_RULE_DELETED, self.on_rule_deleted)
|
||||
|
||||
def push_ontology(self, name, cache_only=None):
|
||||
"""
|
||||
Add an ontology layer
|
||||
@@ -138,7 +166,7 @@ class SheerkaOntologyManager:
|
||||
self.ontologies.insert(0, Ontology(name, cache_manager, alt_sdp))
|
||||
return self
|
||||
|
||||
def pop_ontology(self):
|
||||
def pop_ontology(self, context):
|
||||
"""
|
||||
Remove the top ontology layer
|
||||
"""
|
||||
@@ -148,6 +176,22 @@ class SheerkaOntologyManager:
|
||||
if len(self.ontologies) == 1:
|
||||
raise OntologyManagerCannotPopLatest()
|
||||
|
||||
# remove concepts and rules tracking for the ontology to pop
|
||||
ontology_name = self.current_ontology().name
|
||||
concepts = self.self_cache_manager.get(self.CONCEPTS_BY_ONTOLOGY_ENTRY, ontology_name)
|
||||
if concepts is not NotFound:
|
||||
for concept in concepts:
|
||||
self.sheerka.publish(context, EVENT_CONCEPT_ID_DELETED, concept)
|
||||
self.self_cache_manager.delete(self.ONTOLOGY_BY_CONCEPT_ENTRY, concept)
|
||||
self.self_cache_manager.delete(self.CONCEPTS_BY_ONTOLOGY_ENTRY, ontology_name)
|
||||
|
||||
rules = self.self_cache_manager.get(self.RULES_BY_ONTOLOGY_ENTRY, ontology_name)
|
||||
if rules is not NotFound:
|
||||
for rule in rules:
|
||||
self.sheerka.publish(context, EVENT_RULE_ID_DELETED, rule)
|
||||
self.self_cache_manager.delete(self.ONTOLOGY_BY_RULE_ENTRY, rule)
|
||||
self.self_cache_manager.delete(self.RULES_BY_ONTOLOGY_ENTRY, ontology_name)
|
||||
|
||||
return self.ontologies.pop(0)
|
||||
|
||||
def add_ontology(self, ontology: Ontology):
|
||||
@@ -179,7 +223,7 @@ class SheerkaOntologyManager:
|
||||
|
||||
raise KeyError(name)
|
||||
|
||||
def save_ontologies(self):
|
||||
def save_ontologies_names(self):
|
||||
self.current_sdp().save_ontologies(self.ontologies_names)
|
||||
|
||||
# def load_ontologies(self):
|
||||
@@ -446,6 +490,7 @@ class SheerkaOntologyManager:
|
||||
:param context:
|
||||
:return:
|
||||
"""
|
||||
self.self_cache_manager.commit(context)
|
||||
return self.current_cache_manager().commit(context)
|
||||
|
||||
def clear(self, cache_name=None):
|
||||
@@ -468,3 +513,21 @@ class SheerkaOntologyManager:
|
||||
|
||||
def is_dirty(self):
|
||||
return self.current_cache_manager().is_dirty
|
||||
|
||||
def on_concept_created(self, context, concept):
|
||||
self.self_cache_manager.put(self.CONCEPTS_BY_ONTOLOGY_ENTRY, self.current_ontology().name, concept.id)
|
||||
self.self_cache_manager.put(self.ONTOLOGY_BY_CONCEPT_ENTRY, concept.id, self.current_ontology().name)
|
||||
|
||||
def on_concept_deleted(self, context, concept):
|
||||
ontology_name = self.self_cache_manager.get(self.ONTOLOGY_BY_CONCEPT_ENTRY, concept.id)
|
||||
self.self_cache_manager.delete(self.CONCEPTS_BY_ONTOLOGY_ENTRY, ontology_name, concept.id)
|
||||
self.self_cache_manager.delete(self.ONTOLOGY_BY_CONCEPT_ENTRY, concept.id)
|
||||
|
||||
def on_rule_created(self, context, rule):
|
||||
self.self_cache_manager.put(self.RULES_BY_ONTOLOGY_ENTRY, self.current_ontology().name, rule.id)
|
||||
self.self_cache_manager.put(self.ONTOLOGY_BY_RULE_ENTRY, rule.id, self.current_ontology().name)
|
||||
|
||||
def on_rule_deleted(self, context, rule):
|
||||
ontology_name = self.self_cache_manager.get(self.ONTOLOGY_BY_RULE_ENTRY, rule.id)
|
||||
self.self_cache_manager.delete(self.RULES_BY_ONTOLOGY_ENTRY, ontology_name, rule.id)
|
||||
self.self_cache_manager.delete(self.ONTOLOGY_BY_RULE_ENTRY, rule.id)
|
||||
|
||||
@@ -3,7 +3,7 @@ import time
|
||||
from os import path
|
||||
|
||||
from core.builtin_concepts_ids import BuiltinConcepts, BuiltinContainers
|
||||
from core.builtin_helpers import ensure_concept
|
||||
from core.builtin_helpers import ensure_concept_or_rule
|
||||
from core.concept import Concept
|
||||
from core.sheerka.services.SheerkaMemory import SheerkaMemory
|
||||
from core.sheerka.services.sheerka_service import BaseService
|
||||
@@ -28,6 +28,7 @@ class SheerkaAdmin(BaseService):
|
||||
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)
|
||||
self.sheerka.bind_service_method(self.exec_rules, False)
|
||||
self.sheerka.bind_service_method(self.admin_push_ontology, True, as_name="push_ontology")
|
||||
self.sheerka.bind_service_method(self.admin_pop_ontology, True, as_name="pop_ontology")
|
||||
self.sheerka.bind_service_method(self.ontologies, False)
|
||||
@@ -127,24 +128,36 @@ class SheerkaAdmin(BaseService):
|
||||
concepts = sorted(self.sheerka.om.list(self.sheerka.CONCEPTS_BY_ID_ENTRY), key=lambda item: int(item.id))
|
||||
return self.sheerka.new(BuiltinConcepts.TO_LIST, body=concepts)
|
||||
|
||||
def desc(self, *concepts):
|
||||
ensure_concept(*concepts)
|
||||
def desc(self, *items):
|
||||
ensure_concept_or_rule(*items)
|
||||
res = []
|
||||
for c in concepts:
|
||||
bag = {
|
||||
"id": c.id,
|
||||
"name": c.name,
|
||||
"key": c.key,
|
||||
"definition": c.get_metadata().definition,
|
||||
"type": c.get_metadata().definition_type,
|
||||
"body": c.get_metadata().body,
|
||||
"where": c.get_metadata().where,
|
||||
"pre": c.get_metadata().pre,
|
||||
"post": c.get_metadata().post,
|
||||
"ret": c.get_metadata().ret,
|
||||
"vars": c.get_metadata().variables,
|
||||
"props": c.get_metadata().props,
|
||||
}
|
||||
for item in items:
|
||||
if isinstance(item, Concept):
|
||||
bag = {
|
||||
"id": item.id,
|
||||
"name": item.name,
|
||||
"key": item.key,
|
||||
"definition": item.get_metadata().definition,
|
||||
"type": item.get_metadata().definition_type,
|
||||
"body": item.get_metadata().body,
|
||||
"where": item.get_metadata().where,
|
||||
"pre": item.get_metadata().pre,
|
||||
"post": item.get_metadata().post,
|
||||
"ret": item.get_metadata().ret,
|
||||
"vars": item.get_metadata().variables,
|
||||
"props": item.get_metadata().props,
|
||||
}
|
||||
else:
|
||||
bag = {
|
||||
"id": item.id,
|
||||
"name": item.metadata.name,
|
||||
"type": item.metadata.action_type,
|
||||
"predicate": item.metadata.predicate,
|
||||
"action": item.metadata.action,
|
||||
"priority": item.priority,
|
||||
"compiled": item.metadata.is_compiled,
|
||||
"enabled": item.metadata.is_enabled,
|
||||
}
|
||||
res.append(self.sheerka.new(BuiltinConcepts.TO_DICT, body=bag))
|
||||
|
||||
return res[0] if len(res) == 1 else self.sheerka.new(BuiltinConcepts.TO_LIST, body=res)
|
||||
@@ -152,6 +165,9 @@ class SheerkaAdmin(BaseService):
|
||||
def format_rules(self):
|
||||
return self.sheerka.new(BuiltinConcepts.TO_LIST, items=self.sheerka.get_format_rules())
|
||||
|
||||
def exec_rules(self):
|
||||
return self.sheerka.new(BuiltinConcepts.TO_LIST, items=self.sheerka.get_exec_rules())
|
||||
|
||||
def extended_isinstance(self, a, b):
|
||||
"""
|
||||
switch between sheerka.isinstance and builtin.isinstance
|
||||
@@ -180,8 +196,8 @@ class SheerkaAdmin(BaseService):
|
||||
def admin_push_ontology(self, context, name):
|
||||
return self.sheerka.push_ontology(context, name, False)
|
||||
|
||||
def admin_pop_ontology(self):
|
||||
return self.sheerka.pop_ontology()
|
||||
def admin_pop_ontology(self, context):
|
||||
return self.sheerka.pop_ontology(context)
|
||||
|
||||
def ontologies(self):
|
||||
ontologies = self.sheerka.om.ontologies_names
|
||||
|
||||
@@ -38,7 +38,7 @@ class SheerkaComparisonManager(BaseService):
|
||||
DEFAULT_COMPARISON_VALUE = 1
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=14)
|
||||
|
||||
def initialize(self):
|
||||
cache = ListCache().auto_configure(self.COMPARISON_ENTRY)
|
||||
|
||||
@@ -12,8 +12,7 @@ from core.builtin_concepts_ids import BuiltinConcepts, AllBuiltinConcepts, Built
|
||||
from core.builtin_helpers import ensure_concept, ensure_bnf
|
||||
from core.concept import Concept, DEFINITION_TYPE_DEF, DEFINITION_TYPE_BNF, freeze_concept_attrs, ConceptMetadata, \
|
||||
VARIABLE_PREFIX
|
||||
from core.error import ErrorObj
|
||||
from core.global_symbols import EVENT_CONCEPT_CREATED, NotInit, NotFound
|
||||
from core.global_symbols import EVENT_CONCEPT_CREATED, NotInit, NotFound, ErrorObj, EVENT_CONCEPT_DELETED
|
||||
from core.sheerka.services.sheerka_service import BaseService
|
||||
from core.tokenizer import Tokenizer, TokenKind
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProviderDuplicateKeyError
|
||||
@@ -100,7 +99,7 @@ class SheerkaConceptManager(BaseService):
|
||||
RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY = "ConceptManager:Resolved_Concepts_By_First_Keyword"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=11)
|
||||
self.forbidden_meta = {"is_builtin", "key", "id", "props", "variables"}
|
||||
self.allowed_meta = {attr for attr in vars(ConceptMetadata) if
|
||||
not attr.startswith("_") and attr not in self.forbidden_meta}
|
||||
@@ -147,7 +146,7 @@ class SheerkaConceptManager(BaseService):
|
||||
self.sheerka.om.put(self.sheerka.OBJECTS_IDS_ENTRY, self.USER_CONCEPTS_IDS, 1000)
|
||||
|
||||
# initialize the dictionary of first tokens
|
||||
self.sheerka.om.get(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, None) # to init the cache with the values from sdp
|
||||
self.sheerka.om.get(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, None) # to init the cache with the values from sdp
|
||||
concepts_by_first_keyword = self.sheerka.om.current_cache_manager().copy(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY)
|
||||
res = self.resolve_concepts_by_first_keyword(context, concepts_by_first_keyword)
|
||||
self.sheerka.om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, res.body)
|
||||
@@ -353,6 +352,8 @@ class SheerkaConceptManager(BaseService):
|
||||
:param concept:
|
||||
:return:
|
||||
"""
|
||||
# TODO : resolve concept first
|
||||
|
||||
sheerka = context.sheerka
|
||||
refs = self.sheerka.om.get(self.CONCEPTS_REFERENCES_ENTRY, concept.id)
|
||||
if refs is not NotFound:
|
||||
@@ -361,6 +362,7 @@ class SheerkaConceptManager(BaseService):
|
||||
|
||||
try:
|
||||
sheerka.om.remove_concept(concept)
|
||||
sheerka.publish(context, EVENT_CONCEPT_DELETED, concept)
|
||||
return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.SUCCESS))
|
||||
except ConceptNotFound as ex:
|
||||
return sheerka.ret(self.NAME, False, sheerka.err(ex))
|
||||
|
||||
@@ -91,6 +91,14 @@ class BaseDebugLogger:
|
||||
BaseDebugLogger.ids[hint] = 0
|
||||
return BaseDebugLogger.ids[hint]
|
||||
|
||||
@staticmethod
|
||||
def current_id(hint):
|
||||
if hint in BaseDebugLogger.ids:
|
||||
return BaseDebugLogger.ids[hint]
|
||||
else:
|
||||
BaseDebugLogger.ids[hint] = 0
|
||||
return 0
|
||||
|
||||
def __init__(self, debug_manager, context, who, method_name, debug_id):
|
||||
pass
|
||||
|
||||
@@ -276,7 +284,7 @@ class SheerkaDebugManager(BaseService):
|
||||
children_activation_regex = re.compile(r"(\d+)\+")
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=1)
|
||||
self.activated = False # is debug activated
|
||||
self.explicit = False # No need to activate context debug when debug mode is on # to remove ?
|
||||
self.context_cache = set() # debug for specific context # to remove ?
|
||||
@@ -295,16 +303,6 @@ class SheerkaDebugManager(BaseService):
|
||||
]
|
||||
|
||||
def initialize(self):
|
||||
# TO REMOVE ???
|
||||
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_activated, False)
|
||||
self.sheerka.bind_service_method(self.debug, False, visible=False)
|
||||
|
||||
self.sheerka.bind_service_method(self.set_debug, True)
|
||||
self.sheerka.bind_service_method(self.inspect, False)
|
||||
self.sheerka.bind_service_method(self.get_debugger, False)
|
||||
@@ -341,79 +339,13 @@ class SheerkaDebugManager(BaseService):
|
||||
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 debug(self, *args, **kwargs):
|
||||
print(*args, **kwargs)
|
||||
|
||||
def get_debugger(self, context, who, method_name):
|
||||
def get_debugger(self, context, who, method_name, new_debug_id=True):
|
||||
if self.compute_debug(context, who, method_name):
|
||||
debug_id = ConsoleDebugLogger.next_id(context.event.get_digest() + str(context.id))
|
||||
debug_id = ConsoleDebugLogger.next_id(context.event.get_digest() + str(context.id)) if new_debug_id \
|
||||
else ConsoleDebugLogger.current_id(context.event.get_digest() + str(context.id))
|
||||
return ConsoleDebugLogger(self, context, who, method_name, debug_id)
|
||||
|
||||
return NullDebugLogger()
|
||||
|
||||
@@ -11,7 +11,8 @@ from core.sheerka.services.sheerka_service import BaseService
|
||||
from core.tokenizer import Tokenizer
|
||||
from core.utils import unstr_concept
|
||||
from parsers.BaseNodeParser import ConceptNode
|
||||
from parsers.ExpressionParser import ExpressionParser, TrueifyVisitor
|
||||
from parsers.ExpressionParser import ExpressionParser
|
||||
from parsers.expressions import TrueifyVisitor
|
||||
|
||||
CONCEPT_EVALUATION_STEPS = [
|
||||
BuiltinConcepts.BEFORE_EVALUATION,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.builtin_helpers import expect_one
|
||||
from core.global_symbols import EVENT_RULE_CREATED, EVENT_RULE_DELETED, EVENT_RULE_ID_DELETED
|
||||
from core.sheerka.services.sheerka_service import BaseService
|
||||
from evaluators.ConceptEvaluator import ConceptEvaluator
|
||||
from sheerkarete.network import ReteNetwork
|
||||
|
||||
DISABLED_RULES = "#disabled#"
|
||||
LOW_PRIORITY_RULES = "#low_priority#"
|
||||
@@ -11,12 +13,18 @@ class SheerkaEvaluateRules(BaseService):
|
||||
NAME = "EvaluateRules"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
# order must be before RuleManager because of event subscription
|
||||
super().__init__(sheerka, 4)
|
||||
self.evaluators_by_name = None
|
||||
self.network = ReteNetwork()
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.evaluate_format_rules, False)
|
||||
self.sheerka.bind_service_method(self.evaluate_format_rules, False, visible=False)
|
||||
self.sheerka.bind_service_method(self.evaluate_exec_rules, False, visible=False)
|
||||
self.reset_evaluators()
|
||||
self.sheerka.subscribe(EVENT_RULE_CREATED, self.on_rule_created)
|
||||
self.sheerka.subscribe(EVENT_RULE_DELETED, self.on_rule_deleted)
|
||||
self.sheerka.subscribe(EVENT_RULE_ID_DELETED, self.on_rule_deleted)
|
||||
|
||||
def reset_evaluators(self):
|
||||
# instantiate evaluators, once for all, only keep when it's enabled
|
||||
@@ -24,6 +32,20 @@ class SheerkaEvaluateRules(BaseService):
|
||||
evaluators = [e for e in evaluators if e.enabled]
|
||||
self.evaluators_by_name = {e.short_name: e for e in evaluators}
|
||||
|
||||
def evaluate_exec_rules(self, context, return_values):
|
||||
# self.network.add_obj("__rets", return_values)
|
||||
for ret in return_values:
|
||||
self.network.add_obj("__ret", ret)
|
||||
|
||||
results = [] # list of return values, for activated rules
|
||||
for match in self.network.matches:
|
||||
for rule in match.pnode.rules:
|
||||
body = context.sheerka.new(BuiltinConcepts.RULE_EVALUATION_RESULT, rule=rule)
|
||||
return_value = context.sheerka.ret(self.NAME, True, body)
|
||||
results.append(return_value)
|
||||
|
||||
return results
|
||||
|
||||
def evaluate_format_rules(self, context, bag, disabled):
|
||||
return self.evaluate_rules(context, self.sheerka.get_format_rules(), bag, disabled)
|
||||
|
||||
@@ -37,7 +59,7 @@ class SheerkaEvaluateRules(BaseService):
|
||||
: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:
|
||||
with context.push(BuiltinConcepts.RULES_EVALUATION, 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)
|
||||
@@ -81,7 +103,7 @@ class SheerkaEvaluateRules(BaseService):
|
||||
"""
|
||||
|
||||
results = []
|
||||
for rule_predicate in rule.compiled_predicate:
|
||||
for rule_predicate in rule.compiled_predicates:
|
||||
|
||||
if rule_predicate.source in bag:
|
||||
# simple case where the rule is an item of the bag. No need of complicate evaluation
|
||||
@@ -94,15 +116,44 @@ class SheerkaEvaluateRules(BaseService):
|
||||
rule_predicate.concept.get_metadata().is_evaluated = False
|
||||
|
||||
evaluator = self.evaluators_by_name[rule_predicate.evaluator]
|
||||
results.append(evaluator.eval(context, rule_predicate.predicate))
|
||||
res = evaluator.eval(context, rule_predicate.predicate)
|
||||
if res.status and isinstance(res.body, bool) and res.body:
|
||||
# one successful value found. No need to look any further
|
||||
results = [res]
|
||||
break
|
||||
else:
|
||||
results.append(res)
|
||||
|
||||
debugger = context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rule")
|
||||
debugger = context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rule", new_debug_id=False)
|
||||
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)
|
||||
|
||||
def remove_from_rete_memory(self, lst):
|
||||
if lst is None:
|
||||
return
|
||||
|
||||
for obj in lst:
|
||||
self.network.remove_obj(obj)
|
||||
|
||||
def on_rule_created(self, context, rule):
|
||||
"""
|
||||
When a new rule is added to the system, update the network
|
||||
"""
|
||||
if rule.metadata.is_enabled and rule.rete_disjunctions:
|
||||
self.network.add_rule(rule)
|
||||
|
||||
def on_rule_deleted(self, context, rule):
|
||||
"""
|
||||
When a rule is deleted from the system, remove it from the network
|
||||
"""
|
||||
if isinstance(rule, str):
|
||||
rule = self.sheerka.get_rule_by_id(rule)
|
||||
if not self.sheerka.is_known(rule):
|
||||
return
|
||||
|
||||
self.network.remove_rule(rule)
|
||||
|
||||
@staticmethod
|
||||
def get_debug_format(result):
|
||||
"""
|
||||
|
||||
@@ -11,7 +11,7 @@ class SheerkaEventManager(BaseService):
|
||||
NAME = "EventManager"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=2)
|
||||
self._lock = RLock()
|
||||
self.subscribers = {}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import core.utils
|
||||
from cache.Cache import Cache
|
||||
from cache.FastCache import FastCache
|
||||
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||
from core.global_symbols import NotFound
|
||||
@@ -16,6 +15,8 @@ EVALUATOR_STEPS = [
|
||||
BuiltinConcepts.BEFORE_RENDERING,
|
||||
BuiltinConcepts.RENDERING,
|
||||
BuiltinConcepts.AFTER_RENDERING,
|
||||
BuiltinConcepts.BEFORE_RULES_EVALUATION,
|
||||
BuiltinConcepts.AFTER_RULES_EVALUATION,
|
||||
]
|
||||
|
||||
|
||||
@@ -120,6 +121,10 @@ class ParserInput:
|
||||
return self.pos < self.end
|
||||
|
||||
def the_token_after(self, skip_whitespace=True):
|
||||
"""
|
||||
Returns the token after the current one
|
||||
Never returns None (returns TokenKind.EOF instead)
|
||||
"""
|
||||
my_pos = self.pos + 1
|
||||
if my_pos >= self.end:
|
||||
return Token(TokenKind.EOF, "", -1, -1, -1)
|
||||
@@ -167,7 +172,8 @@ class SheerkaExecute(BaseService):
|
||||
PARSERS_INPUTS_ENTRY = "Execute:ParserInput" # entry for admin or internal variables
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
# order must be after SheerkaEvaluateRules because of self.rules_evaluation_service
|
||||
super().__init__(sheerka, order=5)
|
||||
self.pi_cache = FastCache(default=lambda key: ParserInput(key), max_size=20)
|
||||
self.instantiated_evaluators = None
|
||||
self.evaluators_by_name = None
|
||||
@@ -191,12 +197,18 @@ class SheerkaExecute(BaseService):
|
||||
# Except 2 : we store the type of the parser, not its instance
|
||||
self.grouped_parsers_cache = {}
|
||||
|
||||
self.rules_eval_service = None
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.execute, True)
|
||||
self.sheerka.bind_service_method(self.execute, True, visible=False)
|
||||
self.sheerka.bind_service_method(self.execute_rules, True, visible=False)
|
||||
|
||||
self.reset_registered_evaluators()
|
||||
self.reset_registered_parsers()
|
||||
|
||||
from core.sheerka.services.SheerkaEvaluateRules import SheerkaEvaluateRules
|
||||
self.rules_eval_service = self.sheerka.services[SheerkaEvaluateRules.NAME]
|
||||
|
||||
def reset_state(self):
|
||||
self.pi_cache.clear()
|
||||
|
||||
@@ -347,7 +359,7 @@ class SheerkaExecute(BaseService):
|
||||
if pi is NotFound: # when CacheManager.cache_only is True
|
||||
pi = ParserInput(text)
|
||||
self.pi_cache.put(text, pi)
|
||||
return pi
|
||||
return ParserInput(text, pi.tokens) # new instance, but no need to tokenize the text again
|
||||
|
||||
key = text or core.utils.get_text_from_tokens(tokens)
|
||||
pi = ParserInput(key, tokens)
|
||||
@@ -582,6 +594,55 @@ class SheerkaExecute(BaseService):
|
||||
|
||||
return return_values
|
||||
|
||||
def execute_rules(self, context, return_values, rules_steps, evaluation_steps):
|
||||
""""
|
||||
Executes the execution rules until no match is found
|
||||
:param context:
|
||||
:param return_values: input return values
|
||||
:param rules_steps: steps are configurable
|
||||
:param evaluation_steps: steps are configurable
|
||||
:return: out return_values
|
||||
"""
|
||||
continue_execution = True
|
||||
counter = 0
|
||||
in_rete_memory = None
|
||||
while continue_execution:
|
||||
with context.push(BuiltinConcepts.PROCESSING, {"counter": counter}, desc=f"{counter=}") as sub_context:
|
||||
|
||||
# apply rule evaluation steps
|
||||
for step in rules_steps:
|
||||
if step == BuiltinConcepts.RULES_EVALUATION:
|
||||
eval_res = self.rules_eval_service.evaluate_exec_rules(sub_context, return_values)
|
||||
if not eval_res:
|
||||
self.rules_eval_service.remove_from_rete_memory(return_values)
|
||||
continue_execution = False
|
||||
break
|
||||
else:
|
||||
in_rete_memory = return_values.copy()
|
||||
return_values = eval_res
|
||||
else:
|
||||
return_values = self.call_evaluators(sub_context, return_values, step)
|
||||
|
||||
if not continue_execution:
|
||||
break
|
||||
|
||||
# evaluate the result
|
||||
return_values = [r.body.body.compiled_action for r in return_values]
|
||||
while True:
|
||||
copy = return_values[:]
|
||||
for step in evaluation_steps:
|
||||
return_values = self.call_evaluators(sub_context, return_values, step)
|
||||
|
||||
if copy == return_values[:]:
|
||||
break
|
||||
|
||||
# evaluation is done. Remove object in Rete memory
|
||||
self.rules_eval_service.remove_from_rete_memory(in_rete_memory)
|
||||
|
||||
counter += 1
|
||||
|
||||
return return_values
|
||||
|
||||
def undo_preprocess(self):
|
||||
for item, var_name, value in self.old_values:
|
||||
setattr(item, var_name, value)
|
||||
|
||||
@@ -7,7 +7,7 @@ class SheerkaHasAManager(BaseService):
|
||||
NAME = "HasAManager"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=22)
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.set_hasa, True)
|
||||
|
||||
@@ -15,7 +15,7 @@ class SheerkaIsAManager(BaseService):
|
||||
CONCEPTS_IN_GROUPS_ENTRY = "IsAManager:Concepts_In_Groups" # cache for get_set_elements()
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=21)
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.set_isa, True)
|
||||
|
||||
@@ -20,7 +20,7 @@ class SheerkaMemory(BaseService):
|
||||
OBJECTS_ENTRY = "Memory:Objects"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=13)
|
||||
self.short_term_objects = FastCache()
|
||||
self.registration = {}
|
||||
|
||||
@@ -39,6 +39,8 @@ class SheerkaMemory(BaseService):
|
||||
|
||||
cache = ListIfNeededCache().auto_configure(self.OBJECTS_ENTRY)
|
||||
self.sheerka.om.register_cache(self.OBJECTS_ENTRY, cache, persist=True, use_ref=True)
|
||||
|
||||
self.sheerka.subscribe(EVENT_CONTEXT_DISPOSED, self.remove_context)
|
||||
|
||||
def reset(self):
|
||||
self.short_term_objects.clear()
|
||||
@@ -48,9 +50,6 @@ class SheerkaMemory(BaseService):
|
||||
self.short_term_objects.clear()
|
||||
self.registration.clear()
|
||||
|
||||
def initialize_deferred(self, context, is_first_time):
|
||||
self.sheerka.subscribe(EVENT_CONTEXT_DISPOSED, self.remove_context)
|
||||
|
||||
def get_from_short_term_memory(self, context, key):
|
||||
while True:
|
||||
try:
|
||||
|
||||
@@ -31,6 +31,7 @@ class SheerkaOut(BaseService):
|
||||
if valid_rules:
|
||||
if len(valid_rules) > 1:
|
||||
# TODO manage when too many rules
|
||||
print("TODO: TOO MANY RULES !!!!!")
|
||||
pass
|
||||
|
||||
rule = valid_rules[0]
|
||||
|
||||
@@ -37,11 +37,12 @@ class SheerkaResultManager(BaseService):
|
||||
self.sheerka.bind_service_method(self.get_last_created_concept, False, as_name="last_created_concept")
|
||||
self.sheerka.bind_service_method(self.get_last_error, False, as_name="last_err")
|
||||
|
||||
def initialize_deferred(self, context, is_first_time):
|
||||
self.restore_values(*self.state_vars)
|
||||
self.sheerka.subscribe(EVENT_USER_INPUT_EVALUATED, self.user_input_evaluated)
|
||||
self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.new_concept_created)
|
||||
|
||||
def initialize_deferred(self, context, is_first_time):
|
||||
self.restore_values(*self.state_vars)
|
||||
|
||||
def test_only_reset(self):
|
||||
self.executions_contexts_cache.clear()
|
||||
self.last_execution = None
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
import operator
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
from typing import Union, Set, List
|
||||
|
||||
from cache.Cache import Cache
|
||||
from cache.ListIfNeededCache import ListIfNeededCache
|
||||
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||
from core.builtin_helpers import parse_unrecognized, only_successful, ensure_rule
|
||||
from core.builtin_helpers import parse_unrecognized, is_a_question, parse_python, \
|
||||
ensure_evaluated, expect_one, parse_expression
|
||||
from core.concept import Concept
|
||||
from core.global_symbols import EVENT_RULE_PRECEDENCE_MODIFIED, RULE_COMPARISON_CONTEXT, NotFound
|
||||
from core.rule import Rule
|
||||
from core.sheerka.services.sheerka_service import BaseService
|
||||
from core.global_symbols import EVENT_RULE_PRECEDENCE_MODIFIED, RULE_COMPARISON_CONTEXT, NotFound, ErrorObj, \
|
||||
EVENT_RULE_CREATED, EVENT_RULE_DELETED
|
||||
from core.rule import Rule, ACTION_TYPE_PRINT
|
||||
from core.sheerka.Sheerka import RECOGNIZED_BY_NAME, RECOGNIZED_BY_ID
|
||||
from core.sheerka.services.sheerka_service import BaseService, FailedToCompileError
|
||||
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 evaluators.PythonEvaluator import PythonEvaluator, Expando
|
||||
from parsers.BaseNodeParser import SourceCodeWithConceptNode, ConceptNode, SourceCodeNode
|
||||
from parsers.ExpressionParser import AndNode, ExpressionParser
|
||||
from parsers.PythonParser import PythonNode
|
||||
from sheerkarete.conditions import AndConditions
|
||||
|
||||
CONCEPTS_ONLY_PARSERS = ["ExactConcept", "Bnf", "Sya", "Sequence"]
|
||||
|
||||
identifier_regex = re.compile(r"[\w _.]+")
|
||||
|
||||
|
||||
@dataclass
|
||||
class FormatRuleError:
|
||||
class FormatRuleError(ErrorObj):
|
||||
pass
|
||||
|
||||
|
||||
@@ -199,7 +206,7 @@ class FormatAstMulti(FormatAstNode):
|
||||
**kwargs)
|
||||
|
||||
|
||||
class FormatRuleParser(IterParser):
|
||||
class FormatRuleActionParser(IterParser):
|
||||
|
||||
@staticmethod
|
||||
def to_text(list_or_dict_of_tokens):
|
||||
@@ -242,7 +249,7 @@ class FormatRuleParser(IterParser):
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Parses a format rule
|
||||
Parses the print part of the format rule
|
||||
format ::= {variable'} | function(...) | rawtext
|
||||
:return:
|
||||
"""
|
||||
@@ -394,7 +401,7 @@ class FormatRuleParser(IterParser):
|
||||
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)
|
||||
parser = FormatRuleActionParser(source)
|
||||
res = parser.parse()
|
||||
self.error_sink = parser.error_sink
|
||||
return FormatAstColor(color, res)
|
||||
@@ -497,12 +504,139 @@ class FormatRuleParser(IterParser):
|
||||
return FormatAstMulti(get_text_from_tokens(args[0]))
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmitPythonCodeException(Exception):
|
||||
error: object
|
||||
|
||||
|
||||
class PythonCodeEmitter:
|
||||
|
||||
def __init__(self, context, text=None):
|
||||
self.context = context
|
||||
self.text = text or ""
|
||||
self.var_counter = 0
|
||||
self.variables = []
|
||||
|
||||
def add(self, text):
|
||||
self.text += f" and {text}" if self.text else text
|
||||
return self
|
||||
|
||||
def recognize(self, obj, as_name, root=True):
|
||||
if isinstance(obj, str):
|
||||
return self.recognize_str(obj, as_name)
|
||||
elif isinstance(obj, (int, float)):
|
||||
return self.recognize_int(obj, as_name)
|
||||
elif isinstance(obj, Concept):
|
||||
return self.recognize_concept(obj, as_name, root)
|
||||
elif isinstance(obj, Expando):
|
||||
return self.recognize_expando(obj, as_name, root)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
def recognize_str(self, text, as_name):
|
||||
if self.text:
|
||||
self.text += " and "
|
||||
|
||||
if "'" in text and '"' in text:
|
||||
self.text += f"{as_name} == '{text}'"
|
||||
elif "'" in text:
|
||||
self.text += f'{as_name} == "{text}"'
|
||||
else:
|
||||
self.text += f"{as_name} == '{text}'"
|
||||
return self
|
||||
|
||||
def recognize_int(self, value, as_name):
|
||||
if self.text:
|
||||
self.text += " and "
|
||||
|
||||
self.text += f"{as_name} == {value}"
|
||||
return self
|
||||
|
||||
def recognize_expando(self, value, as_name, root=True):
|
||||
if self.text:
|
||||
self.text += " and "
|
||||
|
||||
if not root:
|
||||
as_name = self.add_variable(as_name)
|
||||
|
||||
self.text += f"isinstance({as_name}, Expando) and {as_name}.get_name() == '{value.get_name()}'"
|
||||
return self
|
||||
|
||||
def recognize_concept(self, concept, as_name, root=True):
|
||||
if self.text:
|
||||
self.text += " and "
|
||||
|
||||
if not root:
|
||||
as_name = self.add_variable(as_name)
|
||||
|
||||
if concept.get_hint(BuiltinConcepts.RECOGNIZED_BY) == RECOGNIZED_BY_NAME:
|
||||
self.text += f"isinstance({as_name}, Concept) and {as_name}.name == '{concept.name}'"
|
||||
elif concept.get_hint(BuiltinConcepts.RECOGNIZED_BY) == RECOGNIZED_BY_ID:
|
||||
self.text += f"isinstance({as_name}, Concept) and {as_name}.id == '{concept.id}'"
|
||||
else:
|
||||
self.text += f"isinstance({as_name}, Concept) and {as_name}.key == '{concept.key}'"
|
||||
if len(concept.get_metadata().variables) > 0:
|
||||
# add variables constraints
|
||||
evaluated = ensure_evaluated(self.context, concept, eval_body=False, metadata=["variables"])
|
||||
|
||||
if not self.context.sheerka.is_success(evaluated) and evaluated.key != concept.key:
|
||||
raise EmitPythonCodeException(evaluated)
|
||||
|
||||
for k, v in concept.variables().items():
|
||||
self.recognize(v, f"{as_name}.get_value('{k}')", root=False)
|
||||
|
||||
return self
|
||||
|
||||
def add_variable(self, target):
|
||||
var_name = f"__x_{self.var_counter:02}__"
|
||||
self.var_counter += 1
|
||||
self.variables.append((var_name, target))
|
||||
return var_name
|
||||
|
||||
def get_text(self):
|
||||
if self.variables:
|
||||
variables_as_str = '\n'.join([f"{k} = {v}" for k, v in self.variables])
|
||||
return variables_as_str + "\n" + self.text
|
||||
|
||||
return self.text
|
||||
|
||||
|
||||
class NoConditionFound(ErrorObj):
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, NoConditionFound)
|
||||
|
||||
def __hash__(self):
|
||||
return 0
|
||||
|
||||
|
||||
@dataclass()
|
||||
class RulePredicate:
|
||||
source: str
|
||||
evaluator: str
|
||||
predicate: ReturnValueConcept
|
||||
concept: Union[Concept, None]
|
||||
class RuleCompiledPredicate:
|
||||
"""
|
||||
The 'when' expression is parsed to have a ReturnValueConcept or a Concept that can then be evaluated
|
||||
Depending on the evaluator, the 'predicate' attribute or the 'concept' attribute will be used
|
||||
"""
|
||||
source: str # what was compiled # DO NOT REMOVE
|
||||
action: str # sheerka action when the rule must be executed # can be removed
|
||||
|
||||
# when used as a list of predicate to iterate thru
|
||||
evaluator: str # evaluator to use when the rule will be evaluated
|
||||
predicate: ReturnValueConcept # compiled source as ReturnValue
|
||||
concept: Union[Concept, None] # compiled source as concept
|
||||
|
||||
variables: Set[str] = None # TODO: set of required variables
|
||||
|
||||
|
||||
@dataclass
|
||||
class CompiledWhenResult:
|
||||
"""
|
||||
For a given source to compile (a given 'when')
|
||||
List of RuleCompiledPredicate found
|
||||
and list of Rete Conditions
|
||||
|
||||
The two ways of evaluating a 'when' are used by Sheerka
|
||||
"""
|
||||
compiled_predicates: List[RuleCompiledPredicate]
|
||||
rete_disjunctions: List[AndConditions]
|
||||
|
||||
|
||||
class SheerkaRuleManager(BaseService):
|
||||
@@ -513,15 +647,18 @@ class SheerkaRuleManager(BaseService):
|
||||
RULES_BY_NAME_ENTRY = "RuleManager:Rules_By_Name"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=12)
|
||||
self._format_rules = None # sorted by priority
|
||||
self._exec_rules = None # sorted by priority
|
||||
self.expression_parser = ExpressionParser()
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.create_new_rule, True, visible=False)
|
||||
self.sheerka.bind_service_method(self.remove_rule, True)
|
||||
self.sheerka.bind_service_method(self.get_rule_by_id, False)
|
||||
self.sheerka.bind_service_method(self.get_rule_by_name, 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.bind_service_method(self.get_exec_rules, False, visible=False)
|
||||
self.sheerka.bind_service_method(self.resolve_rule, False, visible=False)
|
||||
|
||||
cache = Cache().auto_configure(self.FORMAT_RULE_ENTRY)
|
||||
@@ -531,6 +668,8 @@ class SheerkaRuleManager(BaseService):
|
||||
cache = ListIfNeededCache().auto_configure(self.RULES_BY_NAME_ENTRY)
|
||||
self.sheerka.om.register_cache(self.RULES_BY_NAME_ENTRY, cache, True, True)
|
||||
|
||||
self.sheerka.subscribe(EVENT_RULE_PRECEDENCE_MODIFIED, self.update_rules_priorities)
|
||||
|
||||
def initialize_deferred(self, context, is_first_time):
|
||||
|
||||
if is_first_time:
|
||||
@@ -551,15 +690,17 @@ class SheerkaRuleManager(BaseService):
|
||||
|
||||
# compile all format the rules
|
||||
for rule_id, rule_def in self.sheerka.om.get_all(self.FORMAT_RULE_ENTRY, cache_only=True).items():
|
||||
rule = self.init_rule(context, rule_def)
|
||||
self.init_rule(context, rule_def)
|
||||
|
||||
for rule_id, rule_def in self.sheerka.om.get_all(self.EXEC_RULE_ENTRY, cache_only=True).items():
|
||||
self.init_rule(context, rule_def)
|
||||
|
||||
# update rules priorities
|
||||
self.update_rules_priorities(context)
|
||||
|
||||
self.sheerka.subscribe(EVENT_RULE_PRECEDENCE_MODIFIED, self.update_rules_priorities)
|
||||
|
||||
def reset_state(self):
|
||||
self._format_rules = None
|
||||
self._exec_rules = None
|
||||
|
||||
def update_rules_priorities(self, context):
|
||||
"""
|
||||
@@ -575,50 +716,93 @@ class SheerkaRuleManager(BaseService):
|
||||
rule.priority = rules_weights[rule.str_id]
|
||||
|
||||
self._format_rules = None
|
||||
self._exec_rules = None
|
||||
|
||||
def init_rule(self, context, rule: Rule):
|
||||
if rule.metadata.is_compiled:
|
||||
return
|
||||
return rule
|
||||
|
||||
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_predicates is None:
|
||||
try:
|
||||
compiled_result = self.compile_when(context, self.NAME, rule.metadata.predicate)
|
||||
rule.compiled_predicates = compiled_result.compiled_predicates
|
||||
rule.rete_disjunctions = compiled_result.rete_disjunctions
|
||||
except FailedToCompileError as ex:
|
||||
rule.compiled_predicates = None
|
||||
rule.rete_disjunctions = None
|
||||
rule.error_sink = {"when": ex.cause}
|
||||
|
||||
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
|
||||
compile_method = self.compile_print if rule.metadata.action_type == ACTION_TYPE_PRINT else self.compile_exec
|
||||
|
||||
# rule.variables = self.get_variables()
|
||||
res = compile_method(context, rule.metadata.action)
|
||||
if not res.status:
|
||||
rule.compiled_action = None
|
||||
if rule.error_sink is None:
|
||||
rule.error_sink = {rule.metadata.action_type: res.body}
|
||||
else:
|
||||
rule.error_sink[rule.metadata.action_type] = res.body
|
||||
else:
|
||||
rule.compiled_action = res.body if rule.metadata.action_type == ACTION_TYPE_PRINT else res
|
||||
|
||||
rule.metadata.is_compiled = True
|
||||
rule.metadata.is_enabled = True
|
||||
rule.metadata.is_enabled = rule.error_sink is None
|
||||
|
||||
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)
|
||||
def compile_when(self, context, who, source):
|
||||
"""
|
||||
Compile the predicate
|
||||
:param context:
|
||||
:param who: service which requested the compilation
|
||||
:param source: what to compile
|
||||
"""
|
||||
|
||||
if not parsed.status:
|
||||
return parsed
|
||||
# first, try to parse using expression parser
|
||||
# -> Detect xxx and yyy or not zzz
|
||||
|
||||
if self.sheerka.isinstance(parsed.body, BuiltinConcepts.ONLY_SUCCESSFUL):
|
||||
parsed = parsed.body.body
|
||||
action = None
|
||||
parsed = []
|
||||
errors = []
|
||||
all_rete_disjunctions = []
|
||||
parsed_expr_ret = parse_expression(context, source)
|
||||
if parsed_expr_ret.status:
|
||||
conjunctions = parsed_expr_ret.body.body.parts if isinstance(parsed_expr_ret.body.body, AndNode) else \
|
||||
[parsed_expr_ret.body.body]
|
||||
|
||||
return self.add_evaluators(source, parsed if hasattr(parsed, "__iter__") else [parsed])
|
||||
# recognize __action == ''
|
||||
if (action := self._recognized_action_definition(conjunctions[0].tokens)) is not None:
|
||||
conjunctions = conjunctions[1:]
|
||||
|
||||
if len(conjunctions) == 0:
|
||||
errors.append(NoConditionFound())
|
||||
else:
|
||||
# compile conditions
|
||||
try:
|
||||
return_values, rete_disjunctions = self.expression_parser.compile_conjunctions(context,
|
||||
conjunctions,
|
||||
who)
|
||||
|
||||
parsed.extend(return_values)
|
||||
all_rete_disjunctions.extend(rete_disjunctions)
|
||||
|
||||
except FailedToCompileError as ex:
|
||||
errors.append(ex.cause)
|
||||
|
||||
if len(parsed) == 0:
|
||||
raise FailedToCompileError(errors)
|
||||
|
||||
try:
|
||||
compiled_predicates = self.add_evaluators(context,
|
||||
source,
|
||||
action,
|
||||
parsed if hasattr(parsed, "__iter__") else [parsed])
|
||||
return CompiledWhenResult(compiled_predicates, all_rete_disjunctions)
|
||||
except EmitPythonCodeException as ex:
|
||||
raise FailedToCompileError([ex.error])
|
||||
|
||||
def compile_print(self, context, source):
|
||||
parser = FormatRuleParser(source)
|
||||
parser = FormatRuleActionParser(source)
|
||||
parsed = parser.parse()
|
||||
if parser.error_sink:
|
||||
return self.sheerka.ret(self.NAME,
|
||||
@@ -627,6 +811,16 @@ class SheerkaRuleManager(BaseService):
|
||||
else:
|
||||
return self.sheerka.ret(self.NAME, True, parsed)
|
||||
|
||||
def compile_exec(self, context, source):
|
||||
parsed = parse_unrecognized(context,
|
||||
source,
|
||||
parsers="all",
|
||||
who=self.NAME,
|
||||
prop=Keywords.THEN,
|
||||
filter_func=expect_one)
|
||||
|
||||
return parsed
|
||||
|
||||
def set_id_if_needed(self, rule: Rule):
|
||||
"""
|
||||
Set the id for the concept if needed
|
||||
@@ -649,25 +843,52 @@ class SheerkaRuleManager(BaseService):
|
||||
|
||||
# set id before saving in db
|
||||
self.set_id_if_needed(rule)
|
||||
if rule.compiled_predicate and rule.compiled_action:
|
||||
if rule.compiled_predicates and rule.compiled_action:
|
||||
rule.metadata.is_compiled = True
|
||||
rule.metadata.is_enabled = True
|
||||
|
||||
# save it
|
||||
if rule.metadata.action_type == "print":
|
||||
self.sheerka.om.put(self.FORMAT_RULE_ENTRY, rule.metadata.id, rule)
|
||||
if rule.metadata.action_type == ACTION_TYPE_PRINT:
|
||||
sheerka.om.put(self.FORMAT_RULE_ENTRY, rule.metadata.id, rule)
|
||||
self._format_rules = None
|
||||
else:
|
||||
self.sheerka.om.put(self.EXEC_RULE_ENTRY, rule.metadata.id, rule)
|
||||
sheerka.om.put(self.EXEC_RULE_ENTRY, rule.metadata.id, rule)
|
||||
self._exec_rules = None
|
||||
|
||||
# save by name if needed
|
||||
if rule.metadata.name:
|
||||
self.sheerka.om.put(self.RULES_BY_NAME_ENTRY, rule.metadata.name, rule)
|
||||
|
||||
# rule is created. publish the event
|
||||
sheerka.publish(context, EVENT_RULE_CREATED, rule)
|
||||
|
||||
# process the return if needed
|
||||
ret = sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_RULE, body=rule))
|
||||
return ret
|
||||
|
||||
def remove_rule(self, context, rule):
|
||||
"""
|
||||
Remove a rule
|
||||
"""
|
||||
rule = self.resolve_rule(context, rule)
|
||||
if rule is None:
|
||||
return
|
||||
|
||||
# rule will be deleted. publish the event first, as the rule may not be available after
|
||||
self.sheerka.publish(context, EVENT_RULE_DELETED, rule)
|
||||
|
||||
if rule.metadata.action_type == ACTION_TYPE_PRINT:
|
||||
self.sheerka.om.delete(self.FORMAT_RULE_ENTRY, rule.metadata.id)
|
||||
self._format_rules = None
|
||||
else:
|
||||
self.sheerka.om.delete(self.EXEC_RULE_ENTRY, rule.metadata.id)
|
||||
self._exec_rules = None
|
||||
|
||||
if rule.metadata.name:
|
||||
self.sheerka.om.delete(self.RULES_BY_NAME_ENTRY, rule.metadata.name)
|
||||
|
||||
return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
|
||||
|
||||
def init_builtin_rules(self, context):
|
||||
# self.sheerka.init_log.debug("Initializing default rules")
|
||||
rules = [
|
||||
@@ -760,29 +981,6 @@ class SheerkaRuleManager(BaseService):
|
||||
|
||||
return rule
|
||||
|
||||
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
|
||||
@@ -792,32 +990,56 @@ class SheerkaRuleManager(BaseService):
|
||||
reverse=True)
|
||||
return self._format_rules
|
||||
|
||||
def add_evaluators(self, source, ret_vals):
|
||||
def get_exec_rules(self):
|
||||
if self._exec_rules:
|
||||
return self._exec_rules
|
||||
|
||||
self._exec_rules = sorted(self.sheerka.om.list(self.EXEC_RULE_ENTRY, cache_only=True),
|
||||
key=operator.attrgetter('priority'),
|
||||
reverse=True)
|
||||
return self._exec_rules
|
||||
|
||||
def add_evaluators(self, context, source, action, ret_vals):
|
||||
"""
|
||||
Browse the ReturnValueConcepts to determine the evaluator to use
|
||||
Returns a list of tuple (evaluator_name, return_value)
|
||||
Returns a list of RulePredicate, basically a tuple (evaluator_name, return_value)
|
||||
:param context:
|
||||
:param source:
|
||||
:param ret_vals:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def get_rule_predicate_from_concept(c):
|
||||
if is_a_question(context, c):
|
||||
return RuleCompiledPredicate(source, action, ConceptEvaluator.NAME, r, c)
|
||||
else:
|
||||
to_parse = PythonCodeEmitter(context, "__ret.status").recognize_concept(c, "__ret.body").get_text()
|
||||
return RuleCompiledPredicate(source, action, PythonEvaluator.NAME, parse_python(context, to_parse),
|
||||
None)
|
||||
|
||||
res = []
|
||||
for r in ret_vals:
|
||||
underlying = self.sheerka.objvalue(r)
|
||||
if isinstance(underlying, PythonNode):
|
||||
res.append(RulePredicate(source, PythonEvaluator.NAME, r, None))
|
||||
res.append(RuleCompiledPredicate(source, action, PythonEvaluator.NAME, r, None))
|
||||
elif isinstance(underlying, SourceCodeWithConceptNode):
|
||||
res.append(RulePredicate(source, PythonEvaluator.NAME, r, None))
|
||||
res.append(RuleCompiledPredicate(source, action, PythonEvaluator.NAME, r, None))
|
||||
elif isinstance(underlying, SourceCodeNode):
|
||||
res.append(RulePredicate(source, PythonEvaluator.NAME, r, None))
|
||||
res.append(RuleCompiledPredicate(source, action, PythonEvaluator.NAME, r, None))
|
||||
elif isinstance(underlying, Concept):
|
||||
res.append(RulePredicate(source, ConceptEvaluator.NAME, r, underlying))
|
||||
res.append(get_rule_predicate_from_concept(underlying))
|
||||
elif hasattr(underlying, "__iter__") and len(underlying) == 1 and isinstance(underlying[0], ConceptNode):
|
||||
res.append(RulePredicate(source, ConceptEvaluator.NAME, r, underlying[0].concept))
|
||||
res.append(get_rule_predicate_from_concept(underlying[0].concept))
|
||||
else:
|
||||
raise NotImplementedError(r)
|
||||
return res
|
||||
|
||||
def resolve_rule(self, context, obj):
|
||||
"""
|
||||
Given obj, try to find the corresponding rule
|
||||
:param context:
|
||||
:param obj:
|
||||
"""
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
@@ -841,7 +1063,7 @@ class SheerkaRuleManager(BaseService):
|
||||
(rule := self._inner_get_by_id(str(rule_id))) is not None:
|
||||
return rule
|
||||
else:
|
||||
return obj
|
||||
return self._inner_get_by_id(obj.id)
|
||||
|
||||
return None
|
||||
|
||||
@@ -855,3 +1077,41 @@ class SheerkaRuleManager(BaseService):
|
||||
return rule
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _recognized_action_definition(tokens):
|
||||
"""
|
||||
Tries to recognize the pattern __action = xxx in the tokens
|
||||
"""
|
||||
iter_token = iter(tokens)
|
||||
try:
|
||||
token = next(iter_token)
|
||||
if token.value != "__action":
|
||||
return None
|
||||
token = next(iter_token)
|
||||
if token.type == TokenKind.WHITESPACE:
|
||||
token = next(iter_token)
|
||||
if token.type != TokenKind.EQUALSEQUALS:
|
||||
return None
|
||||
token = next(iter_token)
|
||||
if token.type == TokenKind.WHITESPACE:
|
||||
token = next(iter_token)
|
||||
if token.type == TokenKind.STRING:
|
||||
return token.strip_quote
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_parsed_concept(context, return_value):
|
||||
if not context.sheerka.isinstance(return_value.body, BuiltinConcepts.PARSER_RESULT):
|
||||
return None
|
||||
|
||||
if isinstance(return_value.body.body, Concept):
|
||||
return return_value.body.body
|
||||
|
||||
if isinstance(return_value.body.body, ConceptNode):
|
||||
return return_value.body.body.concept
|
||||
|
||||
return None
|
||||
|
||||
@@ -41,9 +41,9 @@ class SheerkaVariableManager(BaseService):
|
||||
INTERNAL_VARIABLES_ENTRY = "VariableManager:InternalVariables" # internal to current process (can store lambda)
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
super().__init__(sheerka, order=3)
|
||||
self.bound_variables = {
|
||||
self.sheerka.name: {"enable_process_return_values", "save_execution_context"}
|
||||
self.sheerka.name: {"enable_process_return_values", "save_execution_context", "enable_process_rules"}
|
||||
}
|
||||
|
||||
def initialize(self):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from core.global_symbols import NotFound
|
||||
from core.global_symbols import NotFound, ErrorObj
|
||||
from core.utils import sheerka_deepcopy
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ class BaseService:
|
||||
Base class for services
|
||||
"""
|
||||
|
||||
def __init__(self, sheerka):
|
||||
def __init__(self, sheerka, order=999):
|
||||
self.sheerka = sheerka
|
||||
self.order = order # initialisation order. The lowest is initialized first
|
||||
|
||||
def initialize(self):
|
||||
"""
|
||||
@@ -46,3 +47,8 @@ class BaseService:
|
||||
Store/record the value of an attribute
|
||||
"""
|
||||
self.sheerka.record_var(context, self.NAME, var_name, getattr(self, var_name))
|
||||
|
||||
|
||||
@dataclass()
|
||||
class FailedToCompileError(Exception, ErrorObj):
|
||||
cause: list
|
||||
|
||||
Reference in New Issue
Block a user