Refactored sheerka class: splitted to use sub handlers. Refactored unit tests to use classes.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
from core.ast.nodes import python_to_concept
|
||||
from core.builtin_concepts import ParserResultConcept, ReturnValueConcept, BuiltinConcepts
|
||||
from core.builtin_helpers import get_names
|
||||
from core.concept import Concept
|
||||
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
from parsers.BaseParser import NotInitializedNode
|
||||
from parsers.ConceptLexerParser import ParsingExpression, ParsingExpressionVisitor
|
||||
from parsers.DefaultParser import DefConceptNode
|
||||
|
||||
from parsers.PythonParser import PythonNode
|
||||
|
||||
|
||||
class ConceptOrRuleNameVisitor(ParsingExpressionVisitor):
|
||||
"""
|
||||
Gets the concepts referenced by BNF
|
||||
If a rule_name is given, it will also be considered as a potential property
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.names = set()
|
||||
|
||||
def visit_ConceptExpression(self, node):
|
||||
if node.rule_name:
|
||||
self.names.add(node.rule_name)
|
||||
elif isinstance(node.concept, Concept):
|
||||
self.names.add(node.concept.name)
|
||||
else:
|
||||
self.names.add(node.concept)
|
||||
|
||||
def visit_all(self, node):
|
||||
if node.rule_name:
|
||||
self.names.add(node.rule_name)
|
||||
|
||||
|
||||
class AddConceptEvaluator(OneReturnValueEvaluator):
|
||||
"""
|
||||
Used to add a new concept
|
||||
"""
|
||||
NAME = "AddNewConcept"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.EVALUATION], 50)
|
||||
|
||||
def matches(self, context, return_value):
|
||||
return return_value.status and \
|
||||
isinstance(return_value.value, ParserResultConcept) and \
|
||||
isinstance(return_value.value.value, DefConceptNode)
|
||||
|
||||
def eval(self, context, return_value):
|
||||
context.log(self.log, "Adding a new concept", self.name)
|
||||
def_concept_node = return_value.value.value
|
||||
sheerka = context.sheerka
|
||||
|
||||
# validate the node
|
||||
props_found = set()
|
||||
|
||||
concept = Concept(def_concept_node.name)
|
||||
for prop in ("definition", "where", "pre", "post", "body"):
|
||||
# put back the sources
|
||||
part_ret_val = getattr(def_concept_node, prop)
|
||||
if not isinstance(part_ret_val, ReturnValueConcept) or not part_ret_val.status:
|
||||
continue # Nothing to do is not initialized
|
||||
|
||||
# update the parts
|
||||
source = self.get_source(part_ret_val)
|
||||
setattr(concept.metadata, prop, source)
|
||||
|
||||
# try to find what can be a property
|
||||
concept_name = [part.value for part in def_concept_node.name.tokens]
|
||||
for p in self.get_props(sheerka, part_ret_val, concept_name):
|
||||
props_found.add(p)
|
||||
|
||||
# add props order by appearance when possible
|
||||
for token in def_concept_node.name.tokens:
|
||||
if token.value in props_found:
|
||||
concept.def_prop(token.value, None)
|
||||
|
||||
# add the remaining properties
|
||||
for p in props_found:
|
||||
if p not in concept.props:
|
||||
concept.def_prop(p, None)
|
||||
|
||||
# finish initialisation
|
||||
concept.init_key(def_concept_node.name.tokens)
|
||||
if not isinstance(def_concept_node.definition, NotInitializedNode) and \
|
||||
sheerka.is_success(def_concept_node.definition):
|
||||
concept.bnf = def_concept_node.definition.value.value
|
||||
|
||||
ret = sheerka.create_new_concept(context, concept, self.verbose_log)
|
||||
if not ret.status:
|
||||
error_cause = sheerka.value(ret.body)
|
||||
context.log(self.log, f"Failed to add concept '{concept.name}'. Reason: {error_cause}", self.name)
|
||||
return sheerka.ret(self.name, ret.status, ret.value, parents=[return_value])
|
||||
|
||||
@staticmethod
|
||||
def get_source(ret_value):
|
||||
return ret_value.value.source
|
||||
|
||||
@staticmethod
|
||||
def get_props(sheerka, ret_value, concept_name):
|
||||
"""
|
||||
Try to find out the variables
|
||||
This function can only be a draft, as there may be tons of different situations
|
||||
I guess that it can only be complete when will we have access to Sheerka memory
|
||||
"""
|
||||
|
||||
#
|
||||
# Case of python code
|
||||
#
|
||||
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, PythonNode):
|
||||
python_node = ret_value.value.value
|
||||
as_concept_node = python_to_concept(python_node.ast_)
|
||||
variables = get_names(sheerka, as_concept_node)
|
||||
variables = filter(lambda x: x in concept_name, variables)
|
||||
return list(variables)
|
||||
|
||||
#
|
||||
# case of concept
|
||||
#
|
||||
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, Concept):
|
||||
return list(ret_value.value.value.props.keys())
|
||||
|
||||
#
|
||||
# case of BNF
|
||||
#
|
||||
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, ParsingExpression):
|
||||
visitor = ConceptOrRuleNameVisitor()
|
||||
visitor.visit(ret_value.value.value)
|
||||
return sorted(list(visitor.names))
|
||||
|
||||
return []
|
||||
@@ -0,0 +1,77 @@
|
||||
import core.builtin_helpers
|
||||
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts
|
||||
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
from parsers.DefaultParser import IsaConceptNode
|
||||
|
||||
ALL_STEPS = [
|
||||
BuiltinConcepts.BEFORE_PARSING,
|
||||
BuiltinConcepts.PARSING,
|
||||
BuiltinConcepts.EVALUATION,
|
||||
BuiltinConcepts.AFTER_EVALUATION
|
||||
]
|
||||
|
||||
|
||||
class AddConceptInSetEvaluator(OneReturnValueEvaluator):
|
||||
"""
|
||||
Tells that a concept is a part of a set
|
||||
"""
|
||||
NAME = "AddConceptInSet"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.EVALUATION], 50)
|
||||
|
||||
def matches(self, context, return_value):
|
||||
return return_value.status and \
|
||||
isinstance(return_value.value, ParserResultConcept) and \
|
||||
isinstance(return_value.value.value, IsaConceptNode)
|
||||
|
||||
def eval(self, context, return_value):
|
||||
|
||||
def _resolve(name_node):
|
||||
ret_val = sheerka.ret(
|
||||
self.name,
|
||||
True,
|
||||
sheerka.new(BuiltinConcepts.USER_INPUT, body=name_node.tokens, user_name="N/A"))
|
||||
|
||||
with context.push(desc=f"Recognizing '{name_node}'") as sub_context:
|
||||
r = sheerka.execute(sub_context, ret_val, ALL_STEPS, self.verbose_log)
|
||||
one_r = core.builtin_helpers.expect_one(context, r)
|
||||
sub_context.add_values(return_values=one_r)
|
||||
return one_r
|
||||
|
||||
isa_node = return_value.value.value
|
||||
sheerka = context.sheerka
|
||||
context.log(self.log, f"Adding a concept {isa_node.concept} to set {isa_node.set}", self.name)
|
||||
|
||||
# Try to recognize the concept
|
||||
res = _resolve(isa_node.concept)
|
||||
if not res.status:
|
||||
return sheerka.ret(
|
||||
self.name,
|
||||
False,
|
||||
sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=str(isa_node.concept)),
|
||||
parents=[return_value])
|
||||
concept = res.value
|
||||
|
||||
res = _resolve(isa_node.set)
|
||||
if not res.status:
|
||||
return sheerka.ret(
|
||||
self.name,
|
||||
False,
|
||||
sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body=str(isa_node.set)),
|
||||
parents=[return_value])
|
||||
concept_set = res.value
|
||||
|
||||
res = sheerka.add_concept_to_set(context, concept, concept_set, self.verbose_log)
|
||||
if not res.status:
|
||||
context.log(self.log, f"Failed. Reason: {sheerka.value(res.body)}.", self.name)
|
||||
else:
|
||||
context.log(self.log, f"Concept added.", self.name)
|
||||
|
||||
return sheerka.ret(
|
||||
self.name,
|
||||
res.status,
|
||||
res.body,
|
||||
parents=[return_value])
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
from core.sheerka.Sheerka import ExecutionContext
|
||||
from core.sheerka_logger import get_logger
|
||||
|
||||
|
||||
class BaseEvaluator:
|
||||
"""
|
||||
Base class to evaluate ReturnValues
|
||||
"""
|
||||
|
||||
PREFIX = "evaluators."
|
||||
|
||||
def __init__(self, name, steps, priority: int, enabled=True):
|
||||
self.log = get_logger(self.PREFIX + self.__class__.__name__)
|
||||
self.init_log = get_logger("init." + self.PREFIX + self.__class__.__name__)
|
||||
self.verbose_log = get_logger("verbose." + self.PREFIX + self.__class__.__name__)
|
||||
|
||||
self.name = self.PREFIX + name
|
||||
self.steps = steps
|
||||
self.priority = priority
|
||||
self.enabled = enabled
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.name} ({self.priority})"
|
||||
|
||||
|
||||
class OneReturnValueEvaluator(BaseEvaluator):
|
||||
"""
|
||||
Evaluate one specific return value
|
||||
"""
|
||||
|
||||
def matches(self, context: ExecutionContext, return_value):
|
||||
pass
|
||||
|
||||
def eval(self, context: ExecutionContext, return_value):
|
||||
pass
|
||||
|
||||
|
||||
class AllReturnValuesEvaluator(BaseEvaluator):
|
||||
"""
|
||||
Evaluates the groups of ReturnValues
|
||||
"""
|
||||
|
||||
def __init__(self, name, steps, priority: int, enabled=True):
|
||||
super().__init__(name, steps, priority, enabled)
|
||||
self.eaten = []
|
||||
|
||||
def matches(self, context: ExecutionContext, return_values):
|
||||
pass
|
||||
|
||||
def eval(self, context: ExecutionContext, return_values):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts
|
||||
from core.concept import Concept, ConceptParts
|
||||
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
|
||||
|
||||
class ConceptEvaluator(OneReturnValueEvaluator):
|
||||
"""
|
||||
The concept evaluatuor is the main class that know what to do with a concept
|
||||
It verifies the PRE
|
||||
If ok, can execute or not the BODY
|
||||
Then checks the POST conditions
|
||||
"""
|
||||
NAME = "Concept"
|
||||
|
||||
def __init__(self, return_body=False):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.EVALUATION], 50)
|
||||
self.return_body = return_body
|
||||
|
||||
def matches(self, context, return_value):
|
||||
return return_value.status and \
|
||||
isinstance(return_value.value, ParserResultConcept) and \
|
||||
isinstance(return_value.value.value, Concept)
|
||||
|
||||
def eval(self, context, return_value):
|
||||
sheerka = context.sheerka
|
||||
concept = return_value.value.value
|
||||
context.log(self.verbose_log, f"Evaluating concept {concept}.", self.name)
|
||||
|
||||
# If the concept that is requested is in the context(at least its name), drop the call.
|
||||
# Why ?
|
||||
# If we evaluate Concept("foo", body="a").set_prop("a", "'property_a'")
|
||||
# The body should be 'property_a', and not a concept called 'a'
|
||||
if context.obj and concept.name in context.obj.props:
|
||||
value = context.obj.props[concept.name].value
|
||||
context.log(self.verbose_log, f"{concept.name} is a property. Returning value '{value}'.", self.name)
|
||||
|
||||
return sheerka.ret(self.name, True, value, parents=[return_value])
|
||||
|
||||
evaluated = sheerka.evaluate_concept(context, concept, self.verbose_log)
|
||||
|
||||
if evaluated.key != concept.key:
|
||||
# evaluated.key != concept.key means that we have transformed the concept
|
||||
# When you successfully evaluate an error, the status should not be false
|
||||
return sheerka.ret(
|
||||
self.name,
|
||||
False,
|
||||
evaluated,
|
||||
parents=[return_value])
|
||||
|
||||
if not self.return_body or ConceptParts.BODY not in evaluated.compiled:
|
||||
return sheerka.ret(self.name, True, evaluated, parents=[return_value])
|
||||
else:
|
||||
return sheerka.ret(self.name, True, evaluated.body, parents=[return_value])
|
||||
@@ -0,0 +1,43 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept
|
||||
from evaluators.BaseEvaluator import AllReturnValuesEvaluator
|
||||
|
||||
|
||||
class EvalEvaluator(AllReturnValuesEvaluator):
|
||||
"""
|
||||
Returns the body of all successful concepts
|
||||
"""
|
||||
|
||||
NAME = "Eval"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.AFTER_EVALUATION], 80)
|
||||
self.eval_requested = None
|
||||
|
||||
def matches(self, context, return_values):
|
||||
sheerka = context.sheerka
|
||||
for ret in return_values:
|
||||
if ret.status and sheerka.isinstance(ret.body, BuiltinConcepts.CONCEPT_EVAL_REQUESTED):
|
||||
self.eval_requested = ret
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def eval(self, context, return_values):
|
||||
sheerka = context.sheerka
|
||||
result = []
|
||||
|
||||
for ret_val in return_values:
|
||||
if ret_val.status and isinstance(ret_val.body, Concept) and ret_val.body.body:
|
||||
context.log(self.verbose_log, f"Evaluating {ret_val}", who=self)
|
||||
result.append(sheerka.ret(self.name, True, ret_val.body.body, parents=[ret_val, self.eval_requested]))
|
||||
|
||||
if len(result) > 0:
|
||||
return result
|
||||
else:
|
||||
# suppress the successful BuiltinConcepts.CONCEPT_EVAL_REQUESTED
|
||||
return sheerka.ret(
|
||||
self.name,
|
||||
False,
|
||||
sheerka.new(BuiltinConcepts.CONCEPT_EVAL_REQUESTED),
|
||||
parents=[self.eval_requested])
|
||||
@@ -0,0 +1,102 @@
|
||||
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts
|
||||
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
from parsers.ConceptLexerParser import ConceptNode, UnrecognizedTokensNode, SourceCodeNode
|
||||
from parsers.PythonParser import LexerNodeParserHelperForPython, PythonNode
|
||||
|
||||
|
||||
class LexerNodeEvaluator(OneReturnValueEvaluator):
|
||||
"""
|
||||
After a BNF is recognized, generates the concept or the list concepts
|
||||
"""
|
||||
|
||||
NAME = "LexerNode"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.EVALUATION], 60)
|
||||
self.identifiers = {} # cache for already created identifier (the key is id(concept))
|
||||
self.identifiers_key = {} # number of identifiers with the same root (prefix)
|
||||
|
||||
def matches(self, context, return_value):
|
||||
if not return_value.status:
|
||||
return False
|
||||
|
||||
if not isinstance(return_value.value, ParserResultConcept):
|
||||
return False
|
||||
|
||||
value = return_value.value.value
|
||||
if isinstance(value, (ConceptNode, SourceCodeNode)):
|
||||
return True
|
||||
|
||||
if hasattr(value, "__iter__"):
|
||||
for node in value:
|
||||
if not isinstance(node, (ConceptNode, SourceCodeNode)):
|
||||
return False
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def eval(self, context, return_value):
|
||||
"""
|
||||
From a concept node, creates a new concept
|
||||
and makes sure that the properties are correctly set
|
||||
"""
|
||||
nodes = return_value.value.value
|
||||
if not hasattr(nodes, "__iter__"):
|
||||
nodes = [nodes]
|
||||
|
||||
context.log(self.verbose_log, f"{nodes=}", self.name)
|
||||
|
||||
for node in nodes:
|
||||
if isinstance(node, SourceCodeNode):
|
||||
ret = self.evaluate_python_code(context, nodes)
|
||||
break
|
||||
else:
|
||||
ret = self.evaluate_concepts_only(context, nodes)
|
||||
|
||||
ret.parents = [return_value]
|
||||
return ret
|
||||
|
||||
def evaluate_concepts_only(self, context, nodes):
|
||||
concepts = []
|
||||
source = ""
|
||||
sheerka = context.sheerka
|
||||
|
||||
for node in nodes:
|
||||
if isinstance(node, ConceptNode):
|
||||
source += node.source if source == "" else (" " + node.source)
|
||||
concepts.append(node.concept)
|
||||
|
||||
if len(concepts) == 1:
|
||||
return sheerka.ret(
|
||||
self.name,
|
||||
True,
|
||||
context.sheerka.new(
|
||||
BuiltinConcepts.PARSER_RESULT,
|
||||
parser=self,
|
||||
source=source,
|
||||
body=concepts[0],
|
||||
try_parsed=None))
|
||||
|
||||
return sheerka.ret(self.name, False, sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=nodes))
|
||||
|
||||
def evaluate_python_code(self, context, nodes):
|
||||
sheerka = context.sheerka
|
||||
|
||||
helper = LexerNodeParserHelperForPython()
|
||||
result = helper.parse(context, nodes)
|
||||
|
||||
if isinstance(result, PythonNode):
|
||||
return sheerka.ret(
|
||||
self.name,
|
||||
True,
|
||||
sheerka.new(
|
||||
BuiltinConcepts.PARSER_RESULT,
|
||||
parser=self,
|
||||
source=result.source,
|
||||
body=result,
|
||||
try_parsed=None))
|
||||
else:
|
||||
return sheerka.ret(
|
||||
self.name,
|
||||
False,
|
||||
result.body)
|
||||
@@ -0,0 +1,83 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
import core.builtin_helpers
|
||||
from core.concept import Concept
|
||||
from evaluators.BaseEvaluator import AllReturnValuesEvaluator, BaseEvaluator
|
||||
from evaluators.ConceptEvaluator import ConceptEvaluator
|
||||
from evaluators.PythonEvaluator import PythonEvaluator
|
||||
from parsers.BaseParser import BaseParser
|
||||
|
||||
|
||||
class MultipleSameSuccessEvaluator(AllReturnValuesEvaluator):
|
||||
"""
|
||||
Used to filter the responses
|
||||
It has a low priority to let other evaluators try to resolve the errors
|
||||
|
||||
It reduces the responses when several evaluators give the same answer
|
||||
"""
|
||||
|
||||
NAME = "MultipleSameSuccess"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.AFTER_EVALUATION], 50)
|
||||
self.success = []
|
||||
|
||||
def matches(self, context, return_values):
|
||||
nb_successful_evaluators = 0
|
||||
only_parsers_in_error = True
|
||||
to_process = False
|
||||
|
||||
for ret in return_values:
|
||||
|
||||
if ret.status and context.sheerka.isinstance(ret.body, BuiltinConcepts.REDUCE_REQUESTED):
|
||||
to_process = True
|
||||
self.eaten.append(ret)
|
||||
elif ret.who.startswith(BaseEvaluator.PREFIX):
|
||||
if ret.status:
|
||||
nb_successful_evaluators += 1
|
||||
self.success.append(ret)
|
||||
self.eaten.append(ret)
|
||||
elif ret.who.startswith(BaseParser.PREFIX):
|
||||
self.eaten.append(ret)
|
||||
if ret.status:
|
||||
only_parsers_in_error = False
|
||||
|
||||
return to_process and nb_successful_evaluators > 1 and only_parsers_in_error
|
||||
|
||||
def eval(self, context, return_values):
|
||||
sheerka = context.sheerka
|
||||
context.log(self.verbose_log, f"{len(self.success)} successful return value(s)", who=self)
|
||||
for s in self.success:
|
||||
context.log(self.verbose_log, f"{s}", who=self)
|
||||
|
||||
if not core.builtin_helpers.is_same_success(sheerka, self.success):
|
||||
return None
|
||||
|
||||
# ######################################
|
||||
# !!!!! W A R N I N G !!!!!!!!
|
||||
# I have a massive issue with how I implement this feature
|
||||
# I have forced an arbitrary order between Concept evaluator and Python evaluator
|
||||
# I gave a random order to the other
|
||||
#
|
||||
# I guess that we need a proper algorithm to elect which return value to use if they have the same result
|
||||
# I guts feeling is that, it will depend on the intent of the user
|
||||
# So it depends on the context
|
||||
|
||||
# try to return a concept if possible
|
||||
# give the priority to the ConceptEvaluator
|
||||
for s in self.success:
|
||||
if isinstance(s.value, Concept) and s.who == ConceptEvaluator().name:
|
||||
return sheerka.ret(self.name, True, s.value, parents=self.eaten)
|
||||
|
||||
# Then the PythonEvaluator
|
||||
for s in self.success:
|
||||
if isinstance(s.value, Concept) and s.who == PythonEvaluator().name:
|
||||
return sheerka.ret(self.name, True, s.value, parents=self.eaten)
|
||||
|
||||
# Then the first concept.
|
||||
# It's not predictable, so I guess that it's not a good implementation choice
|
||||
for s in self.success:
|
||||
if isinstance(s.value, Concept):
|
||||
return sheerka.ret(self.name, True, s.value, parents=self.eaten)
|
||||
|
||||
return sheerka.ret(self.name, True, self.success[0].value, parents=self.eaten)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from evaluators.BaseEvaluator import AllReturnValuesEvaluator
|
||||
from parsers.BaseParser import BaseParser
|
||||
|
||||
|
||||
class OneErrorEvaluator(AllReturnValuesEvaluator):
|
||||
"""
|
||||
Use to reduce when there is only one evaluator in error
|
||||
The rest of the return values must be parsers in error
|
||||
"""
|
||||
|
||||
NAME = "OneError"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.AFTER_EVALUATION], 30)
|
||||
self.return_value_in_error = None
|
||||
|
||||
def matches(self, context, return_values):
|
||||
nb_evaluators_in_error = 0
|
||||
to_process = False
|
||||
|
||||
for ret in return_values:
|
||||
if ret.status and (ret.who.startswith(self.PREFIX) or ret.who.startswith(BaseParser.PREFIX)):
|
||||
return False
|
||||
elif ret.status and context.sheerka.isinstance(ret.body, BuiltinConcepts.REDUCE_REQUESTED):
|
||||
to_process = True
|
||||
self.eaten.append(ret)
|
||||
elif not ret.status and ret.who.startswith(self.PREFIX):
|
||||
nb_evaluators_in_error += 1
|
||||
self.return_value_in_error = ret
|
||||
self.eaten.append(ret)
|
||||
elif not ret.status and ret.who.startswith(BaseParser.PREFIX):
|
||||
self.eaten.append(ret)
|
||||
|
||||
return to_process and nb_evaluators_in_error == 1
|
||||
|
||||
def eval(self, context, return_values):
|
||||
context.log(self.verbose_log, f"1 return value in error, {len(self.eaten)} item(s) eaten", who=self)
|
||||
context.log(self.verbose_log, f"{self.return_value_in_error}", who=self)
|
||||
|
||||
sheerka = context.sheerka
|
||||
return sheerka.ret(self.name, False, self.return_value_in_error.value, parents=self.eaten)
|
||||
@@ -0,0 +1,44 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from evaluators.BaseEvaluator import AllReturnValuesEvaluator
|
||||
from parsers.BaseParser import BaseParser
|
||||
|
||||
|
||||
class OneSuccessEvaluator(AllReturnValuesEvaluator):
|
||||
"""
|
||||
Used to filter the responses
|
||||
It has a low priority to let other evaluators try to resolve the errors
|
||||
|
||||
Make sure that there is only one successful answer
|
||||
"""
|
||||
|
||||
NAME = "OneSuccess"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.AFTER_EVALUATION], 60) # before MultipleSameSuccess
|
||||
self.successful_return_value = None
|
||||
|
||||
def matches(self, context, return_values):
|
||||
nb_successful_evaluators = 0
|
||||
to_process = False
|
||||
|
||||
for ret in return_values:
|
||||
if ret.status and ret.who.startswith(BaseParser.PREFIX):
|
||||
return False
|
||||
elif ret.status and context.sheerka.isinstance(ret.body, BuiltinConcepts.REDUCE_REQUESTED):
|
||||
to_process = True
|
||||
self.eaten.append(ret)
|
||||
elif ret.status and ret.who.startswith(self.PREFIX):
|
||||
nb_successful_evaluators += 1
|
||||
self.successful_return_value = ret
|
||||
self.eaten.append(ret)
|
||||
elif not ret.status:
|
||||
self.eaten.append(ret)
|
||||
|
||||
return to_process and nb_successful_evaluators == 1
|
||||
|
||||
def eval(self, context, return_values):
|
||||
context.log(self.verbose_log, f"1 successful return value, {len(self.eaten)} item(s) eaten", who=self)
|
||||
context.log(self.verbose_log, f"{self.successful_return_value}", who=self)
|
||||
|
||||
sheerka = context.sheerka
|
||||
return sheerka.ret(self.name, True, self.successful_return_value.value, parents=self.eaten)
|
||||
@@ -0,0 +1,40 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
|
||||
|
||||
class PrepareEvalEvaluator(OneReturnValueEvaluator):
|
||||
"""
|
||||
To parse evaluation requests
|
||||
"""
|
||||
|
||||
NAME = "PrepareEval"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.BEFORE_PARSING], 90)
|
||||
self.text = None
|
||||
|
||||
def matches(self, context, return_value):
|
||||
if not (return_value.status and
|
||||
context.sheerka.isinstance(return_value.body, BuiltinConcepts.USER_INPUT) and
|
||||
isinstance(return_value.body.body, str)):
|
||||
return False
|
||||
|
||||
text = return_value.body.body.strip()
|
||||
if not text.startswith("eval "):
|
||||
return False
|
||||
|
||||
self.text = text
|
||||
return True
|
||||
|
||||
def eval(self, context, return_value):
|
||||
sheerka = context.sheerka
|
||||
|
||||
new_text_to_parse = sheerka.ret(
|
||||
self.name,
|
||||
True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.text[5:], user_name=context.event.user))
|
||||
|
||||
evaluation_requested = sheerka.ret(
|
||||
self.name,
|
||||
True, sheerka.new(BuiltinConcepts.CONCEPT_EVAL_REQUESTED))
|
||||
|
||||
return [new_text_to_parse, evaluation_requested]
|
||||
@@ -0,0 +1,189 @@
|
||||
import copy
|
||||
from enum import Enum
|
||||
|
||||
from core.ast.visitors import UnreferencedNamesVisitor
|
||||
from core.builtin_concepts import BuiltinConcepts, ParserResultConcept
|
||||
from core.concept import ConceptParts, Concept
|
||||
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
from parsers.PythonParser import PythonNode
|
||||
import ast
|
||||
import core.ast.nodes
|
||||
|
||||
|
||||
class PythonEvaluator(OneReturnValueEvaluator):
|
||||
NAME = "Python"
|
||||
|
||||
"""
|
||||
Evaluate a Python node, ie, evaluate some Python code
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.EVALUATION], 50)
|
||||
self.locals = {}
|
||||
|
||||
def matches(self, context, return_value):
|
||||
return return_value.status and \
|
||||
isinstance(return_value.value, ParserResultConcept) and \
|
||||
isinstance(return_value.value.value, PythonNode)
|
||||
|
||||
def eval(self, context, return_value):
|
||||
sheerka = context.sheerka
|
||||
node = return_value.value.value
|
||||
try:
|
||||
context.log(self.verbose_log, f"Evaluating python node {node}.", self.name)
|
||||
|
||||
# Do not evaluate if the ast refers to a concept (leave it to ConceptEvaluator)
|
||||
if isinstance(node.ast_, ast.Expression) and isinstance(node.ast_.body, ast.Name):
|
||||
c = context.sheerka.get(node.ast_.body.id)
|
||||
if not context.sheerka.isinstance(c, BuiltinConcepts.UNKNOWN_CONCEPT):
|
||||
context.log(self.verbose_log, "It's a simple concept. Not for me.", self.name)
|
||||
not_for_me = context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=node)
|
||||
return sheerka.ret(self.name, False, not_for_me, parents=[return_value])
|
||||
|
||||
my_locals = self.get_locals(context, node)
|
||||
context.log(self.verbose_log, f"locals={my_locals}", self.name)
|
||||
|
||||
if isinstance(node.ast_, ast.Expression):
|
||||
context.log(self.verbose_log, "Evaluating using 'eval'.", self.name)
|
||||
compiled = compile(node.ast_, "<string>", "eval")
|
||||
evaluated = eval(compiled, {}, my_locals)
|
||||
else:
|
||||
context.log(self.verbose_log, "Evaluating using 'exec'.", self.name)
|
||||
evaluated = self.exec_with_return(node.ast_, my_locals)
|
||||
|
||||
context.log(self.verbose_log, f"{evaluated=}", self.name)
|
||||
return sheerka.ret(self.name, True, evaluated, parents=[return_value])
|
||||
except Exception as error:
|
||||
context.log_error(self.verbose_log, error, self.name)
|
||||
error = sheerka.new(BuiltinConcepts.ERROR, body=error)
|
||||
return sheerka.ret(self.name, False, error, parents=[return_value])
|
||||
|
||||
def get_locals(self, context, node):
|
||||
my_locals = {
|
||||
"sheerka": context.sheerka,
|
||||
"desc": context.sheerka.dump_handler.dump_desc,
|
||||
"concepts": context.sheerka.dump_handler.dump_concepts,
|
||||
"definitions": context.sheerka.dump_handler.dump_definitions,
|
||||
}
|
||||
if context.obj:
|
||||
context.log(self.verbose_log,
|
||||
f"Concept '{context.obj}' is in context. Adding its properties to locals if any.", self.name)
|
||||
|
||||
for prop_name, prop_value in context.obj.props.items():
|
||||
if not isinstance(prop_value.value, Concept):
|
||||
my_locals[prop_name] = prop_value.value
|
||||
else:
|
||||
my_locals[prop_name] = context.sheerka.value(prop_value.value)
|
||||
|
||||
node_concept = core.ast.nodes.python_to_concept(node.ast_)
|
||||
unreferenced_names_visitor = UnreferencedNamesVisitor(context.sheerka)
|
||||
unreferenced_names_visitor.visit(node_concept)
|
||||
|
||||
for name in unreferenced_names_visitor.names:
|
||||
context.log(self.verbose_log, f"Resolving '{name}'.", self.name)
|
||||
|
||||
if name in node.concepts:
|
||||
context.log(self.verbose_log, f"Using value from node.", self.name)
|
||||
concept = node.concepts[name]
|
||||
return_concept = False
|
||||
|
||||
else:
|
||||
concept_key, concept_id, return_concept = self.resolve_name(context, name)
|
||||
|
||||
if concept_key in my_locals:
|
||||
context.log(self.verbose_log, f"Using value from property.", self.name)
|
||||
continue
|
||||
|
||||
context.log(self.verbose_log, f"Instantiating new concept.", self.name)
|
||||
concept = context.sheerka.new((concept_key, concept_id))
|
||||
if context.sheerka.isinstance(concept, BuiltinConcepts.UNKNOWN_CONCEPT):
|
||||
context.log(self.verbose_log, f"'{concept_key}' is not a concept. Skipping.", self.name)
|
||||
continue
|
||||
|
||||
context.log(self.verbose_log, f"Evaluating '{concept}'", self.name)
|
||||
with context.push(self.name, desc=f"Evaluating '{concept}'", obj=concept) as sub_context:
|
||||
sub_context.log_new(self.verbose_log)
|
||||
evaluated = context.sheerka.evaluate_concept(sub_context, concept, self.verbose_log)
|
||||
sub_context.add_values(return_values=evaluated)
|
||||
|
||||
if evaluated.key == concept.key:
|
||||
my_locals[name] = evaluated if return_concept else context.sheerka.value(evaluated)
|
||||
|
||||
if self.locals:
|
||||
my_locals.update(self.locals)
|
||||
|
||||
return my_locals
|
||||
|
||||
def resolve_name(self, context, to_resolve):
|
||||
"""
|
||||
Try to match
|
||||
__C__concept_key__C__
|
||||
or
|
||||
__C__concept_key__concept_id__C__
|
||||
|
||||
:param context:
|
||||
:param to_resolve:
|
||||
:return:
|
||||
"""
|
||||
if not to_resolve.startswith("__C__"):
|
||||
return to_resolve, None, False
|
||||
|
||||
context.log(self.verbose_log, f"Resolving name '{to_resolve}'.", self.name)
|
||||
|
||||
if len(to_resolve) >= 18 and to_resolve[:18] == "__C__USE_CONCEPT__":
|
||||
use_concept = True
|
||||
index = 18
|
||||
else:
|
||||
use_concept = False
|
||||
index = 5
|
||||
|
||||
try:
|
||||
next_index = to_resolve.index("__", index)
|
||||
if next_index == index:
|
||||
context.log(self.verbose_log, f"Error: no key between '__'.", self.name)
|
||||
return None
|
||||
concept_key = to_resolve[index: next_index]
|
||||
except ValueError:
|
||||
context.log(self.verbose_log, f"Error: Missing trailing '__'.", self.name)
|
||||
return None
|
||||
|
||||
if next_index == len(to_resolve) - 5:
|
||||
context.log(self.verbose_log, f"Recognized concept '{concept_key}'", self.name)
|
||||
return concept_key, None, use_concept
|
||||
|
||||
index = next_index + 2
|
||||
try:
|
||||
next_index = to_resolve.index("__", index)
|
||||
if next_index == index:
|
||||
context.log(self.verbose_log, f"Error: no id between '__'.", self.name)
|
||||
return None
|
||||
|
||||
concept_id = to_resolve[index: next_index]
|
||||
except ValueError:
|
||||
context.log(self.verbose_log, f"Recognized concept '{concept_key}'.", self.name)
|
||||
return concept_key, None, use_concept
|
||||
|
||||
context.log(self.verbose_log, f"Recognized concept '{concept_key}' (id='{concept_id}').", self.name)
|
||||
return concept_key, concept_id, use_concept
|
||||
|
||||
@staticmethod
|
||||
def expr_to_expression(expr):
|
||||
expr.lineno = 0
|
||||
expr.col_offset = 0
|
||||
result = ast.Expression(expr.value, lineno=0, col_offset=0)
|
||||
|
||||
return result
|
||||
|
||||
def exec_with_return(self, code_ast, my_locals):
|
||||
|
||||
init_ast = copy.deepcopy(code_ast)
|
||||
init_ast.body = code_ast.body[:-1]
|
||||
|
||||
last_ast = copy.deepcopy(code_ast)
|
||||
last_ast.body = code_ast.body[-1:]
|
||||
|
||||
exec(compile(init_ast, "<ast>", "exec"), {}, my_locals)
|
||||
if type(last_ast.body[0]) == ast.Expr:
|
||||
return eval(compile(self.expr_to_expression(last_ast.body[0]), "<ast>", "eval"), {}, my_locals)
|
||||
else:
|
||||
exec(compile(last_ast, "<ast>", "exec"), {}, my_locals)
|
||||
@@ -0,0 +1,54 @@
|
||||
import logging
|
||||
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
import core.builtin_helpers
|
||||
from evaluators.BaseEvaluator import AllReturnValuesEvaluator, BaseEvaluator
|
||||
from parsers.BaseParser import BaseParser
|
||||
|
||||
|
||||
class TooManySuccessEvaluator(AllReturnValuesEvaluator):
|
||||
"""
|
||||
Used to filter the responses
|
||||
It has a low priority to let other evaluators try to resolve the errors
|
||||
|
||||
Raises an error when that are several successful answers, with different values
|
||||
"""
|
||||
|
||||
NAME = "TooManySuccess"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.AFTER_EVALUATION], 60)
|
||||
self.success = []
|
||||
|
||||
def matches(self, context, return_values):
|
||||
to_process = False
|
||||
|
||||
for ret in return_values:
|
||||
if ret.status and ret.who.startswith(BaseParser.PREFIX):
|
||||
return False
|
||||
elif ret.status and context.sheerka.isinstance(ret.body, BuiltinConcepts.REDUCE_REQUESTED):
|
||||
to_process = True
|
||||
self.eaten.append(ret)
|
||||
elif ret.status and ret.who.startswith(self.PREFIX):
|
||||
self.success.append(ret)
|
||||
self.eaten.append(ret)
|
||||
elif not ret.status:
|
||||
self.eaten.append(ret)
|
||||
|
||||
return to_process and len(self.success) > 1
|
||||
|
||||
def eval(self, context, return_values):
|
||||
sheerka = context.sheerka
|
||||
if self.verbose_log.isEnabledFor(logging.DEBUG):
|
||||
for s in self.success:
|
||||
context.log(self.verbose_log, s, self.name)
|
||||
context.log(self.verbose_log, f"value={sheerka.value(s.value)}", self.name)
|
||||
|
||||
if not core.builtin_helpers.is_same_success(sheerka, self.success):
|
||||
context.log(self.verbose_log,
|
||||
f"Values are different. Raising {BuiltinConcepts.TOO_MANY_SUCCESS}.", self.name)
|
||||
too_many_success = sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS, body=self.success)
|
||||
return sheerka.ret(self.name, False, too_many_success, parents=self.eaten)
|
||||
|
||||
context.log(self.verbose_log, f"Values are the same. Nothing to do.", self.name)
|
||||
return None
|
||||
Reference in New Issue
Block a user