Implemented a first and basic version of a Rete rule engine

This commit is contained in:
2021-02-09 16:06:32 +01:00
parent 821dbed189
commit a2a8d5c5e5
110 changed files with 7301 additions and 1654 deletions
+65 -4
View File
@@ -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)