Files
Sheerka-Old/src/evaluators/DefConceptEvaluator.py
T
kodjo 1059ce25c5 Fixed #68: Implement SheerkaQL
Fixed #70: SheerkaFilterManager : Pipe functions
Fixed #71: SheerkaFilterManager : filter_objects
Fixed #75: SheerkaMemory: Enhance memory() to use the filtering capabilities
Fixed #76: SheerkaEvaluateConcept: Concepts that modify the state of the system must not be evaluated during question
2021-04-26 19:13:47 +02:00

305 lines
13 KiB
Python

from dataclasses import dataclass
import core.utils
from core.ast_helpers import UnreferencedVariablesVisitor
from core.builtin_concepts import ParserResultConcept, ReturnValueConcept, BuiltinConcepts
from core.concept import Concept, DEFINITION_TYPE_BNF, DEFINITION_TYPE_DEF
from core.global_symbols import NotInit
from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import TokenKind, Tokenizer
from evaluators.BaseEvaluator import OneReturnValueEvaluator
from parsers.BnfNodeParser import ParsingExpression, ParsingExpressionVisitor
from parsers.DefConceptParser import DefConceptNode, NameNode
from parsers.PythonParser import get_python_node
@dataclass(eq=True, frozen=True)
class ConceptEvaluatorVariable:
name: str
@dataclass(eq=True, frozen=True)
class MandatoryVariable(ConceptEvaluatorVariable):
"""
When we are searching for variables, we are searching for potential variable
So if the variable found has no match in the concept definition, it's not a problem
for example:
def concept foo x as isinstance(x, str)
{x, str} will be detected as potential variable, but 'str' will find no match.
But there are cases where the variable found must exist, otherwise, it's an error
example:
def concept foo from bnf unknown_concept
'unknown_concept' will be detected and considered as a variable . But it is not, as it's not
declared in the name of the concept.
We return MandatoryVariable (instead of a variable name) to let the evaluator know that if the variable is not
declared in the name of the concept, it's an error
"""
def __hash__(self):
return hash(("MandatoryVariable", self.name))
@dataclass(eq=True, frozen=True)
class PossibleVariable(ConceptEvaluatorVariable):
"""
When a name/identifier is found in a concept part (pre, post, where, body...)
It is considered as a possible variable. It will only added as a variable if the exact name / identifier
is found in the name of the concept
example:
def concept a plus b as a + b
'a' and 'b' are found in the body and thy also exist in the name of the concept
-> They will be added as variable
"""
def __hash__(self):
return hash(("PossibleVariable", self.name))
@dataclass(eq=True, frozen=True)
class CertainVariable(ConceptEvaluatorVariable):
"""
A certain variable will be added as a variable regardless of a possible match in the name
example:
def concept number
def concept plus from bnf number=n1 plus number=n2
'n1' and 'n2' do not appear in the name of the concept, but they are variables
"""
def __hash__(self):
return hash(("PossibleVariable", self.name))
class ConceptOrRuleVariableVisitor(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):
super().__init__()
self.variables = []
def visit_ConceptExpression(self, node):
if node.rule_name:
self.variables.append(CertainVariable(node.rule_name))
elif isinstance(node.concept, Concept):
self.variables.append(CertainVariable(node.concept.name))
else:
self.variables.append(CertainVariable(node.concept))
def visit_VariableExpression(self, node):
self.variables.append(MandatoryVariable(node.rule_name))
def visit_all(self, node):
if node.rule_name:
self.variables.append(CertainVariable(node.rule_name))
class DefConceptEvaluator(OneReturnValueEvaluator):
"""
Used to add a new concept
"""
NAME = "DefConcept"
def __init__(self):
super().__init__(self.NAME, [BuiltinConcepts.EVALUATION], 50)
def matches(self, context, return_value):
debugger = context.get_debugger(self.NAME, "matches")
debugger.debug_entering(return_value=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("Adding a new concept", self.name)
def_concept_node = return_value.value.value
sheerka = context.sheerka
debugger = context.get_debugger(self.NAME, "eval")
debugger.debug_entering(def_concept=def_concept_node)
# validate the node
variables_found = set()
mandatory_variables = set() # these variable MUST have a match in the name (if the name is not None)
certain_variables = []
skip_variables_resolution = False
concept = Concept(str(def_concept_node.name))
concept.get_metadata().definition_type = def_concept_node.definition_type
name_to_use = self.get_name_to_use(def_concept_node)
if def_concept_node.variables != NotInit:
certain_variables = def_concept_node.variables.copy()
skip_variables_resolution = True
# get variables and set the sources
for prop in ("definition", "where", "pre", "post", "body", "ret"):
part_ret_val = getattr(def_concept_node, prop)
# put back the sources
if part_ret_val is NotInit:
continue
elif isinstance(part_ret_val, NameNode):
source = str(part_ret_val)
elif isinstance(part_ret_val, ReturnValueConcept) and part_ret_val.status:
source = part_ret_val.value.source.as_text() if isinstance(part_ret_val.value.source,
ParserInput) else part_ret_val.value.source
else:
raise Exception("Unexpected")
setattr(concept.get_metadata(), prop, source)
if skip_variables_resolution:
continue
# Do not try to resolve variables from itself
if prop == "definition" and concept.get_metadata().definition_type == DEFINITION_TYPE_DEF:
continue
# try to find what can be a property
for p in self.get_variables(context, part_ret_val, name_to_use):
variables_found.add(p.name)
if isinstance(p, MandatoryVariable):
mandatory_variables.add(p.name)
elif isinstance(p, CertainVariable):
certain_variables.append(p.name)
# add variables by order of appearance
for name_part in [name_part for name_part in name_to_use if str(name_part).isalnum()]:
if name_part in variables_found:
concept.def_var(name_part, None)
# check that all mandatory variables are defined in the name
# KSI: 2021-02-17
# The mandatory variables come from bnf definition where it was not possible to resolve to a concept
# So rather that issuing a 'UnresolvedVariableError' I prefer UNKNOWN_CONCEPT
if (diff := mandatory_variables.difference(set(name_to_use))) != set():
unknown_concepts = [sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body={"name": c}) for c in sorted(diff)]
error = sheerka.new(BuiltinConcepts.ERROR, body=unknown_concepts)
return sheerka.ret(self.name, False, error, parents=[return_value])
# add the remaining properties
# They mainly come from BNF definition
for p in certain_variables:
if p not in concept.values():
concept.def_var(p, None)
# initialize the key
key_source = def_concept_node.definition.tokens if \
def_concept_node.definition_type == DEFINITION_TYPE_DEF else \
def_concept_node.name.tokens
concept.init_key(key_source)
# update the bnf definition if needed
if def_concept_node.definition is not NotInit and \
def_concept_node.definition_type == DEFINITION_TYPE_BNF:
concept.set_bnf(def_concept_node.definition.value.value)
# manage auto eval
if def_concept_node.auto_eval:
concept.add_prop(BuiltinConcepts.ISA, sheerka.new(BuiltinConcepts.AUTO_EVAL))
ret = sheerka.create_new_concept(context, concept)
if not ret.status:
error_cause = sheerka.objvalue(ret.body)
context.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_name_to_use(node):
source = node.definition if node.definition_type == DEFINITION_TYPE_DEF else node.name
return [part.str_value for part in core.utils.strip_tokens(source.tokens, True)]
@staticmethod
def get_variables(context, 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
"""
def get_inner_concept(parsing_result):
if not isinstance(parsing_result, ParserResultConcept):
return None
if isinstance(parsing_result.body, Concept):
return parsing_result.body
# manage other cases (conceptNode) later
return None
debugger = context.get_debugger(DefConceptEvaluator.NAME, "get_variables")
#
# Case of NameNode
#
if isinstance(ret_value, NameNode):
names = [str(t.value) for t in ret_value.tokens if t.type in (TokenKind.IDENTIFIER,
TokenKind.STRING,
TokenKind.KEYWORD)]
possible_vars = filter(lambda x: x in concept_name and context.sheerka.is_not_a_concept_name(x), names)
debugger.debug_var("names", names, hint="from NameNode")
debugger.debug_var("possible_vars", possible_vars, hint="from NameNode")
return [PossibleVariable(v) for v in possible_vars]
#
# case of BNF
#
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, ParsingExpression):
visitor = ConceptOrRuleVariableVisitor()
visitor.visit(ret_value.value.value)
debugger.debug_var("names", visitor.variables, hint="from BNF")
return visitor.variables
#
# Case of python code
#
if (python_node := get_python_node(ret_value.value.value)) is not None:
if len(concept_name) > 1:
visitor = UnreferencedVariablesVisitor(context)
names = visitor.get_names(python_node.ast_)
possible_vars = filter(lambda x: x in concept_name and context.sheerka.is_not_a_concept_name(x), names)
debugger.debug_var("names", names, hint="from python node")
debugger.debug_var("possible_vars", possible_vars, hint="from python node")
return [PossibleVariable(v) for v in possible_vars]
else:
return []
#
# Case of Concept
#
if (concept := get_inner_concept(ret_value.value)) is not None and len(concept_name) > 1:
# use the variables of the concept is any
names = [var_value or var_name for var_name, var_value in concept.get_metadata().variables]
possible_vars = filter(lambda x: context.sheerka.is_not_a_concept_name(x), names)
debugger.debug_var("names", names, hint="from concept")
debugger.debug_var("possible_vars", possible_vars, hint="from concept")
return [PossibleVariable(v) for v in possible_vars]
#
# Other cases
#
if isinstance(ret_value.value, ParserResultConcept) and len(concept_name) > 1:
source = ret_value.value.source.as_text() if isinstance(ret_value.value.source, ParserInput) else \
ret_value.value.source
tokens = ret_value.value.tokens or list(Tokenizer(source, yield_eof=False))
names = []
for t in tokens:
if t.type == TokenKind.RULE:
for v in [v for v in t.value if v is not None]:
names.append(v)
else:
names.append(t.str_value)
possible_vars = filter(lambda x: context.sheerka.is_not_a_concept_name(x), names)
debugger.debug_var("names", names, hint="from source")
debugger.debug_var("possible_vars", possible_vars, hint="from source")
return [PossibleVariable(v) for v in possible_vars]
return []