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:
2021-04-26 19:13:47 +02:00
parent bef5f3208c
commit 1059ce25c5
57 changed files with 5759 additions and 1302 deletions
+279
View File
@@ -0,0 +1,279 @@
import functools
from dataclasses import dataclass
import core.builtin_helpers
from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import Concept
from core.global_symbols import SyaAssociativity, NotFound, NotInit, ErrorObj
from core.rule import Rule
from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka.services.SheerkaAdmin import SheerkaAdmin
from core.tokenizer import Token, TokenKind
from core.utils import sheerka_hasattr, sheerka_getattr
from core.var_ref import VariableRef
TO_DISABLED = ["breakpoint", "callable", "compile", "delattr", "eval", "exec", "exit", "input", "locals", "open",
"print", "quit", "setattr"]
@dataclass
class MethodAccessError(Exception, ErrorObj):
method_name: str
class ObjectContainer:
"""
Container for list of object (or whatever), to easily use SheerkaQueryLanguage on collection
"""
def __init__(self, items):
self.items = items
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
def __eq__(self, other):
if id(other) == id(self):
return True
if not isinstance(other, Expando):
return False
if other.get_name() != self.get_name():
return False
for k, v in vars(self).items():
if getattr(other, k) != v:
return False
return True
def __hash__(self):
hash_content = [self.__name] + list(vars(self).keys())
return hash(tuple(hash_content))
class Pipe:
"""
https://github.com/JulienPalard/Pipe/pull/23
Represent a Pipeable Element :
Described as :
first = Pipe(lambda iterable: next(iter(iterable)))
and used as :
print [1, 2, 3] | first
printing 1
Or represent a Pipeable Function :
It's a function returning a Pipe
Described as :
select = Pipe(lambda iterable, predicate: (predicate(x) for x in iterable))
and used as :
print [1, 2, 3] | select(lambda x: x * 2)
# 2, 4, 6
"""
def __init__(self, function):
self.function = function
functools.update_wrapper(self, function)
def __ror__(self, other):
return self.function(other)
def __call__(self, *args, **kwargs):
return Pipe(lambda x: self.function(x, *args, **kwargs))
def get_type(obj):
if isinstance(obj, Concept):
return obj.name
else:
return type(obj).__name__
sheerka_globals = {
"Concept": Concept,
"BuiltinConcepts": BuiltinConcepts,
"Expando": Expando,
"ExecutionContext": ExecutionContext,
"SyaAssociativity": SyaAssociativity,
"get_type": get_type,
"hasattr": sheerka_hasattr,
"getattr": sheerka_getattr,
}
def inject_context(context):
"""
function Decorator used to inject the context in methods that needed
:param context:
:return:
"""
def wrapped(func):
@functools.wraps(func)
def inner(*args, **kwargs):
return func(context, *args, **kwargs)
return inner
return wrapped
def resolve_object(context, who, obj):
"""
Try to find a concept by its obj, id or the pattern c:key|id:
:param context:
:param who:
:param obj:
:return:
"""
if isinstance(obj, VariableRef):
return getattr(obj.obj, obj.prop)
if isinstance(obj, Rule):
return context.sheerka.resolve_rule(context, obj)
if isinstance(obj, Concept):
obj = core.builtin_helpers.ensure_evaluated(context, obj)
return obj
if isinstance(obj, Token) and obj.type == TokenKind.RULE:
return context.sheerka.resolve_rule(context, obj)
if isinstance(obj, tuple):
# To make sure that there is no tuple that resembles to a concept
raise Exception()
if (isinstance(obj, str) and obj.startswith("c:")) or isinstance(obj, Token):
concept = context.sheerka.fast_resolve(obj)
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
return obj
def get_sheerka_method(context, who, name, expression_only):
try:
method = context.sheerka.sheerka_methods[name]
context.log(f"Resolving '{name}'. It's a sheerka method.", who)
if expression_only and method.has_side_effect:
context.log(f"...but with side effect when {expression_only=}. Discarding.", who)
raise MethodAccessError(name)
else:
method_to_use = inject_context(context)(method.method) if name in context.sheerka.methods_with_context \
else method.method
if name in context.sheerka.pipe_functions:
return Pipe(method_to_use)
else:
return method_to_use
except KeyError:
return None
def create_namespace(context, who, names, sheerka_names, objects, expression_only, allow_builtins=False):
"""
Create a namespace for the requested names
:param context:
:param who: who is asking
:param names: requested names
:param sheerka_names: requested sheerka names (ex sheerka.isinstance)
:param objects: local objects that can be added
:param expression_only: if true, discard method that can alter the global state
:param allow_builtins: automatically add python builtins symbols
:return:
"""
result = dict(__builtins__) if allow_builtins else {}
for name in names:
if name in sheerka_globals:
result[name] = sheerka_globals[name]
continue
if expression_only and name in TO_DISABLED:
result[name] = None
continue
if name == "in_context":
result[name] = context.in_context
continue
# need to add it manually to avoid conflict with sheerka.isinstance
if name == "isinstance":
result["isinstance"] = context.sheerka.services[SheerkaAdmin.NAME].extended_isinstance
continue
# support reference to sheerka
if name.lower() == "sheerka":
bag = {}
for sheerka_name in sheerka_names:
if (method := get_sheerka_method(context, who, sheerka_name, expression_only)) is not None:
bag[sheerka_name] = method
result[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.", who)
result[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.", who)
result[name] = obj.obj
continue
# search in sheerka methods
if (method := get_sheerka_method(context, who, name, expression_only)) is not None:
result[name] = method
continue
# search in context.obj (to replace by short time memory ?)
if context.obj:
if name == "self":
result["self"] = context.obj
continue
try:
attribute = context.obj.variables()[name]
if attribute != NotInit:
result[name] = attribute
continue
context.log(f"Resolving '{name}'. It's obj attribute (obj={context.obj}).", who)
except KeyError:
pass
# search in current node (if the name was found during the parsing)
if name in objects:
context.log(f"Resolving '{name}'. Using value from node.", who)
obj = resolve_object(context, who, objects[name])
# at last, try to instantiate a new concept
else:
context.log(f"Resolving '{name}'. Instantiating new concept.", who)
obj = resolve_object(context, who, f"c:{name}:")
if obj is None:
context.log(f"...'{name}' is not found or cannot be instantiated. Skipping.", who)
continue
result[name] = obj
return result