Implemented a first and basic version of a Rete rule engine
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
import core.utils
|
||||
from core.builtin_concepts import ReturnValueConcept
|
||||
from core.builtin_concepts_ids import BuiltinConcepts
|
||||
from core.global_symbols import NotInit
|
||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||
from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager, FormatAstNode, RuleCompiledPredicate
|
||||
from core.sheerka.services.sheerka_service import FailedToCompileError
|
||||
from core.tokenizer import Keywords, TokenKind
|
||||
from parsers.BaseCustomGrammarParser import BaseCustomGrammarParser, NameNode, KeywordNotFound, SyntaxErrorNode
|
||||
from parsers.BaseParser import Node, UnexpectedEofParsingError
|
||||
from sheerkarete.conditions import Condition
|
||||
|
||||
|
||||
@dataclass()
|
||||
class DefRuleNode(Node):
|
||||
tokens: dict
|
||||
name: NameNode = NotInit
|
||||
when: List[RuleCompiledPredicate] = NotInit
|
||||
rete: List[List[Condition]] = NotInit
|
||||
|
||||
|
||||
@dataclass()
|
||||
class DefExecRuleNode(DefRuleNode):
|
||||
then: ReturnValueConcept = NotInit
|
||||
|
||||
|
||||
@dataclass
|
||||
class DefFormatRuleNode(DefRuleNode):
|
||||
print: FormatAstNode = NotInit
|
||||
|
||||
|
||||
class DefRuleParser(BaseCustomGrammarParser):
|
||||
DEF_KEYWORDS = [Keywords.RULE, Keywords.AS]
|
||||
DEF_KEYWORDS_VALUES = [k.value for k in DEF_KEYWORDS]
|
||||
|
||||
RULE_KEYWORDS = [Keywords.WHEN, Keywords.THEN, Keywords.PRINT]
|
||||
RULE_KEYWORDS_VALUES = [k.value for k in RULE_KEYWORDS]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
BaseCustomGrammarParser.__init__(self, "DefRule", 60)
|
||||
|
||||
def parse(self, context, parser_input: ParserInput):
|
||||
if not isinstance(parser_input, ParserInput):
|
||||
return None
|
||||
|
||||
# rule parser can only manage string text
|
||||
if parser_input.from_tokens:
|
||||
ret = context.sheerka.ret(
|
||||
self.name,
|
||||
False,
|
||||
context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=parser_input))
|
||||
self.log_result(context, parser_input, ret)
|
||||
return ret
|
||||
|
||||
context.log(f"Parsing '{parser_input}' with DefRuleParser", self.name)
|
||||
sheerka = context.sheerka
|
||||
|
||||
if parser_input.is_empty():
|
||||
return sheerka.ret(self.name,
|
||||
False,
|
||||
sheerka.new(BuiltinConcepts.IS_EMPTY))
|
||||
|
||||
if not self.reset_parser(context, parser_input):
|
||||
return self.sheerka.ret(self.name,
|
||||
False,
|
||||
context.sheerka.new(BuiltinConcepts.ERROR, body=self.error_sink))
|
||||
|
||||
self.parser_input.next_token()
|
||||
node = self.parse_def_rule()
|
||||
|
||||
body = self.get_return_value_body(sheerka, parser_input.as_text(), node, node)
|
||||
ret = sheerka.ret(self.name, not self.has_error, body)
|
||||
|
||||
self.log_result(context, parser_input.as_text(), ret)
|
||||
return ret
|
||||
|
||||
def parse_def_rule(self):
|
||||
token = self.parser_input.token
|
||||
if token.value == Keywords.DEF.value:
|
||||
return self.parse_rule_name()
|
||||
elif token.value in (Keywords.WHEN.value, Keywords.PRINT.value):
|
||||
return self.parse_rule()
|
||||
else:
|
||||
self.add_error(KeywordNotFound([], [Keywords.WHEN.value]))
|
||||
return None
|
||||
|
||||
def parse_rule_name(self):
|
||||
"""
|
||||
Parses def rule xxx as yyyy
|
||||
"""
|
||||
self.parser_input.next_token() # eat def
|
||||
token = self.parser_input.token
|
||||
if token.value != Keywords.RULE.value:
|
||||
self.add_error(KeywordNotFound([token], [Keywords.RULE.value]))
|
||||
return None
|
||||
|
||||
buffer = []
|
||||
while self.parser_input.next_token(skip_whitespace=False):
|
||||
token = self.parser_input.token
|
||||
if token.value == Keywords.AS.value:
|
||||
break
|
||||
else:
|
||||
buffer.append(token)
|
||||
else: # 'as' keyword not found
|
||||
self.add_error(KeywordNotFound([], [Keywords.AS.value]))
|
||||
return None
|
||||
|
||||
if not self.parser_input.next_token(): # eat as
|
||||
self.add_error(UnexpectedEofParsingError("While parsing 'when'."))
|
||||
return None
|
||||
|
||||
rule = self.parse_rule()
|
||||
|
||||
name_node = self.get_concept_name(buffer)
|
||||
if name_node is None:
|
||||
return rule
|
||||
|
||||
rule.name = name_node
|
||||
return rule
|
||||
|
||||
def parse_rule(self):
|
||||
""""
|
||||
Parses 'when xxx then yyy'
|
||||
or 'when xxx print yyy'
|
||||
"""
|
||||
parts = self.get_parts(self.RULE_KEYWORDS_VALUES, strip_tokens=True)
|
||||
if Keywords.THEN in parts and Keywords.PRINT in parts:
|
||||
self.add_error(SyntaxErrorNode([], "Cannot have both 'print' and 'then' keywords"))
|
||||
return None
|
||||
|
||||
if Keywords.THEN not in parts and Keywords.PRINT not in parts:
|
||||
self.add_error(KeywordNotFound([], [Keywords.THEN.value, Keywords.PRINT.value]))
|
||||
return None
|
||||
|
||||
return self.parse_format_rule(parts) if Keywords.PRINT in parts else self.parse_exec_rule(parts)
|
||||
|
||||
def parse_exec_rule(self, parts):
|
||||
node = DefExecRuleNode(parts)
|
||||
try:
|
||||
compiled_result = self.get_when(parts[Keywords.WHEN])
|
||||
if compiled_result is None:
|
||||
return node
|
||||
node.when = compiled_result.compiled_predicates
|
||||
node.rete = compiled_result.rete_disjunctions
|
||||
|
||||
parsed = self.get_then(parts[Keywords.THEN])
|
||||
if parsed is None:
|
||||
return node
|
||||
node.then = parsed
|
||||
except KeyError as e:
|
||||
self.add_error(KeywordNotFound([], [e.args[0].value]))
|
||||
return None
|
||||
|
||||
return node
|
||||
|
||||
def parse_format_rule(self, parts):
|
||||
node = DefFormatRuleNode(parts)
|
||||
try:
|
||||
compiled_result = self.get_when(parts[Keywords.WHEN])
|
||||
if compiled_result is None:
|
||||
return node
|
||||
node.when = compiled_result.compiled_predicates
|
||||
node.rete = compiled_result.rete_disjunctions
|
||||
|
||||
parsed = self.get_print(parts[Keywords.PRINT])
|
||||
if parsed is None:
|
||||
return node
|
||||
node.print = parsed
|
||||
except KeyError as e:
|
||||
self.add_error(KeywordNotFound([], [e.args[0].value]))
|
||||
return None
|
||||
|
||||
return node
|
||||
|
||||
def get_when(self, tokens):
|
||||
"""
|
||||
Validate the when part of the rule.
|
||||
:param tokens:
|
||||
:return:
|
||||
"""
|
||||
source = core.utils.get_text_from_tokens(core.utils.strip_tokens(tokens[1:]))
|
||||
try:
|
||||
rule_manager_service = self.sheerka.services[SheerkaRuleManager.NAME]
|
||||
compiled_result = rule_manager_service.compile_when(self.context, self.name, source)
|
||||
except FailedToCompileError as ex:
|
||||
for c in ex.cause:
|
||||
self.add_error(c)
|
||||
return None
|
||||
|
||||
return compiled_result
|
||||
|
||||
def get_then(self, tokens):
|
||||
source = core.utils.get_text_from_tokens(core.utils.strip_tokens(tokens[1:]))
|
||||
res = self.sheerka.services[SheerkaRuleManager.NAME].compile_exec(self.context, source)
|
||||
|
||||
if not res.status:
|
||||
self.add_error(res.value)
|
||||
return None
|
||||
|
||||
return res
|
||||
|
||||
def get_print(self, tokens):
|
||||
"""
|
||||
Validate the print part
|
||||
:param tokens:
|
||||
:return:
|
||||
"""
|
||||
source = core.utils.get_text_from_tokens(core.utils.strip_tokens(tokens[1:]))
|
||||
res = self.sheerka.services[SheerkaRuleManager.NAME].compile_print(self.context, source)
|
||||
if not res.status:
|
||||
self.add_error(res.value)
|
||||
return None
|
||||
|
||||
return res.body
|
||||
|
||||
def get_concept_name(self, tokens):
|
||||
name_tokens = core.utils.strip_tokens(tokens)
|
||||
if len(name_tokens) == 0:
|
||||
self.add_error(SyntaxErrorNode([], "Name is mandatory"))
|
||||
return None
|
||||
|
||||
for token in name_tokens:
|
||||
if token.type == TokenKind.NEWLINE:
|
||||
self.add_error(SyntaxErrorNode([token], "Newline are not allowed in name."))
|
||||
return None
|
||||
|
||||
name_node = NameNode(name_tokens) # skip the first token
|
||||
return name_node
|
||||
Reference in New Issue
Block a user