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:
@@ -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
|
||||
Reference in New Issue
Block a user