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
This commit is contained in:
@@ -8,50 +8,15 @@ import core.utils
|
||||
from core.ast_helpers import UnreferencedNamesVisitor, NamesWithAttributesVisitor
|
||||
from core.builtin_concepts import BuiltinConcepts, ParserResultConcept
|
||||
from core.concept import ConceptParts, Concept
|
||||
from core.global_symbols import NotInit, NotFound
|
||||
from core.rule import Rule
|
||||
from core.sheerka.ExecutionContext import ExecutionContext
|
||||
from core.global_symbols import NotInit, ErrorObj
|
||||
from core.sheerka.services.SheerkaMemory import SheerkaMemory
|
||||
from core.tokenizer import Token, TokenKind
|
||||
from core.var_ref import VariableRef
|
||||
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
from parsers.PythonParser import PythonNode
|
||||
|
||||
TO_DISABLED = ["breakpoint", "callable", "compile", "delattr", "eval", "exec", "exit", "input", "locals", "open",
|
||||
"print", "quit", "setattr"]
|
||||
|
||||
|
||||
def inject_context(context):
|
||||
"""
|
||||
function Decorator used to inject the context in methods that needed
|
||||
:param context:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def wrapped(func):
|
||||
def inner(*args, **kwargs):
|
||||
return func(context, *args, **kwargs)
|
||||
|
||||
return inner
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
class Expando:
|
||||
def __init__(self, name, bag):
|
||||
self.__name = name
|
||||
for k, v in bag.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{vars(self)}"
|
||||
|
||||
def get_name(self):
|
||||
return self.__name
|
||||
from sheerkapython.python_wrapper import create_namespace, MethodAccessError
|
||||
|
||||
|
||||
@dataclass
|
||||
class PythonEvalError:
|
||||
class PythonEvalError(ErrorObj):
|
||||
error: Exception
|
||||
source: str
|
||||
traceback: str = field(repr=False)
|
||||
@@ -72,6 +37,9 @@ class PythonEvalError:
|
||||
def __hash__(self):
|
||||
return hash(self.error)
|
||||
|
||||
def get_error(self):
|
||||
return self.error
|
||||
|
||||
|
||||
class PythonEvaluator(OneReturnValueEvaluator):
|
||||
NAME = "Python"
|
||||
@@ -83,6 +51,7 @@ class PythonEvaluator(OneReturnValueEvaluator):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.EVALUATION], 50)
|
||||
self.isinstance = None
|
||||
|
||||
@staticmethod
|
||||
def initialize(sheerka):
|
||||
@@ -107,26 +76,28 @@ class PythonEvaluator(OneReturnValueEvaluator):
|
||||
context.log(f"Evaluating python node {node}.", self.name)
|
||||
|
||||
# If we evaluate a Concept metadata which is NOT the body ex (pre, post, where...)
|
||||
# We need to disable the function that may alter the state
|
||||
# or if EVAL_QUESTION_REQUESTED is explicit
|
||||
# We need to disable the functions that may alter the state
|
||||
# It's a poor way to have source code security check
|
||||
attr_under_eval = context.get_parents(lambda ec: ec.action == BuiltinConcepts.EVALUATING_ATTRIBUTE)
|
||||
if attr_under_eval:
|
||||
attr_under_eval = attr_under_eval[0]
|
||||
expression_only = attr_under_eval.action_context != ConceptParts.BODY
|
||||
expression_only = context.in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
|
||||
|
||||
if expression_only and isinstance(node.ast_, ast.Module):
|
||||
# Module execution is forbidden in where, pre, post and ret concept parts
|
||||
security_error = sheerka.new(BuiltinConcepts.PYTHON_SECURITY_ERROR,
|
||||
prop=attr_under_eval.action_context,
|
||||
body=node.source)
|
||||
return sheerka.ret(self.name, False, security_error, parents=[return_value])
|
||||
else:
|
||||
expression_only = False
|
||||
if not expression_only:
|
||||
attr_under_eval = context.get_parents(lambda ec: ec.action == BuiltinConcepts.EVALUATING_ATTRIBUTE)
|
||||
if attr_under_eval:
|
||||
attr_under_eval = attr_under_eval[0]
|
||||
expression_only = attr_under_eval.action_context != ConceptParts.BODY
|
||||
|
||||
# get globals
|
||||
my_globals = self.get_globals(context, node, expression_only)
|
||||
try:
|
||||
my_globals = self.get_globals(context, node, expression_only)
|
||||
debugger.debug_var("globals", my_globals)
|
||||
except MethodAccessError as ex:
|
||||
eval_error = PythonEvalError(ex,
|
||||
node.source,
|
||||
traceback.format_exc() if get_trace_back else None,
|
||||
None)
|
||||
|
||||
debugger.debug_var("globals", my_globals)
|
||||
return sheerka.ret(self.name, False, sheerka.err(eval_error), parents=[return_value])
|
||||
|
||||
all_possible_globals = self.get_all_possible_globals(context, my_globals)
|
||||
concepts_entries = None # entries in globals_ that refers to Concept objects
|
||||
@@ -189,104 +160,16 @@ class PythonEvaluator(OneReturnValueEvaluator):
|
||||
"""
|
||||
unreferenced_names_visitor = UnreferencedNamesVisitor(context)
|
||||
names = unreferenced_names_visitor.get_names(node.ast_)
|
||||
return self.get_globals_by_names(context, names, node, expression_only)
|
||||
if "sheerka" in names:
|
||||
sheerka_names = set()
|
||||
visitor = NamesWithAttributesVisitor()
|
||||
for sequence in visitor.get_sequences(node.ast_, "sheerka"):
|
||||
if len(sequence) > 1:
|
||||
sheerka_names.add(sequence[1])
|
||||
else:
|
||||
sheerka_names = None
|
||||
|
||||
def get_sheerka_method(self, context, name, expression_only):
|
||||
try:
|
||||
method = context.sheerka.sheerka_methods[name]
|
||||
context.log(f"Resolving '{name}'. It's a sheerka method.", self.name)
|
||||
if expression_only and method.has_side_effect:
|
||||
context.log(f"...but with side effect when {expression_only=}. Discarding.", self.name)
|
||||
return None
|
||||
else:
|
||||
return inject_context(context)(method.method) if name in context.sheerka.methods_with_context \
|
||||
else method.method
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def get_globals_by_names(self, context, names, node, expression_only):
|
||||
my_globals = {
|
||||
"Concept": core.concept.Concept,
|
||||
"BuiltinConcepts": core.builtin_concepts.BuiltinConcepts,
|
||||
"Expando": Expando,
|
||||
"ExecutionContext": ExecutionContext,
|
||||
"in_context": context.in_context,
|
||||
"SyaAssociativity": core.global_symbols.SyaAssociativity
|
||||
}
|
||||
|
||||
for name in names:
|
||||
if name in my_globals:
|
||||
continue
|
||||
|
||||
if expression_only and name in TO_DISABLED:
|
||||
my_globals[name] = None
|
||||
continue
|
||||
|
||||
# need to add it manually to avoid conflict with sheerka.isinstance
|
||||
if name == "isinstance":
|
||||
my_globals["isinstance"] = PythonEvaluator.isinstance
|
||||
continue
|
||||
|
||||
# support reference to sheerka
|
||||
if name.lower() == "sheerka":
|
||||
bag = {}
|
||||
visitor = NamesWithAttributesVisitor()
|
||||
for sequence in visitor.get_sequences(node.ast_, "sheerka"):
|
||||
if (len(sequence) > 1 and
|
||||
(method := self.get_sheerka_method(context, sequence[1], expression_only)) is not None):
|
||||
bag[sequence[1]] = method
|
||||
my_globals[name] = Expando("sheerka", bag)
|
||||
continue
|
||||
|
||||
# search in short term memory
|
||||
if (obj := context.get_from_short_term_memory(name)) is not NotFound:
|
||||
context.log(f"Resolving '{name}'. Using value found in STM.", self.name)
|
||||
my_globals[name] = obj
|
||||
continue
|
||||
|
||||
# search in memory
|
||||
if (obj := context.sheerka.get_last_from_memory(context, name)) is not NotFound:
|
||||
context.log(f"Resolving '{name}'. Using value found in Long Term Memory.", self.name)
|
||||
my_globals[name] = obj.obj
|
||||
continue
|
||||
|
||||
# search in sheerka methods
|
||||
if (method := self.get_sheerka_method(context, name, expression_only)) is not None:
|
||||
my_globals[name] = method
|
||||
continue
|
||||
|
||||
# search in context.obj (to replace by short time memory ?)
|
||||
if context.obj:
|
||||
if name == "self":
|
||||
my_globals["self"] = context.obj
|
||||
continue
|
||||
|
||||
try:
|
||||
attribute = context.obj.variables()[name]
|
||||
if attribute != NotInit:
|
||||
my_globals[name] = attribute
|
||||
continue
|
||||
context.log(f"Resolving '{name}'. It's obj attribute (obj={context.obj}).", self.name)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# search in current node (if the name was found during the parsing)
|
||||
if name in node.objects:
|
||||
context.log(f"Resolving '{name}'. Using value from node.", self.name)
|
||||
obj = self.resolve_object(context, node.objects[name])
|
||||
|
||||
# at last, try to instantiate a new concept
|
||||
else:
|
||||
context.log(f"Resolving '{name}'. Instantiating new concept.", self.name)
|
||||
obj = self.resolve_object(context, name)
|
||||
|
||||
if obj is None:
|
||||
context.log(f"...'{name}' is not found or cannot be instantiated. Skipping.", self.name)
|
||||
continue
|
||||
|
||||
my_globals[name] = obj
|
||||
|
||||
return my_globals
|
||||
return create_namespace(context, self.name, names, sheerka_names, node.objects, expression_only)
|
||||
|
||||
@staticmethod
|
||||
def get_all_possible_globals(context, my_globals):
|
||||
@@ -303,7 +186,7 @@ class PythonEvaluator(OneReturnValueEvaluator):
|
||||
:return:
|
||||
"""
|
||||
|
||||
# first pass, get all the non concept or concept with no body
|
||||
# first pass, get all the non concepts or concepts with no body
|
||||
# Note that we consider that all concepts are evaluated
|
||||
# In the future, it may be a good optimisation to defer the evaluation of the body
|
||||
# until the python evaluation fails
|
||||
@@ -330,42 +213,6 @@ class PythonEvaluator(OneReturnValueEvaluator):
|
||||
def get_concepts_values_from_globals(my_globals, names):
|
||||
return {name: my_globals[name] for name in names}
|
||||
|
||||
@staticmethod
|
||||
def resolve_object(context, name):
|
||||
"""
|
||||
Try to find a concept by its name, id or the pattern c:key|id:
|
||||
:param context:
|
||||
:param name:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(name, VariableRef):
|
||||
return getattr(name.obj, name.prop)
|
||||
|
||||
if isinstance(name, Rule):
|
||||
return context.sheerka.resolve_rule(context, name)
|
||||
|
||||
if isinstance(name, Concept):
|
||||
name = core.builtin_helpers.ensure_evaluated(context, name)
|
||||
return name
|
||||
|
||||
if isinstance(name, Token) and name.type == TokenKind.RULE:
|
||||
return context.sheerka.resolve_rule(context, name)
|
||||
|
||||
if isinstance(name, tuple):
|
||||
raise Exception()
|
||||
|
||||
# try to resolve by name
|
||||
concept = context.sheerka.fast_resolve(name)
|
||||
if concept is None:
|
||||
return None
|
||||
|
||||
if hasattr(concept, "__iter__"):
|
||||
raise NotImplementedError("Too many concepts")
|
||||
|
||||
concept = core.builtin_helpers.ensure_evaluated(context, concept)
|
||||
return concept
|
||||
|
||||
@staticmethod
|
||||
def expr_to_expression(expr):
|
||||
expr.lineno = 0
|
||||
|
||||
Reference in New Issue
Block a user