Added SheerkaComparisonManager
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cache.Cache import Cache
|
||||
from cache.ListCache import ListCache
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.sheerka.services.sheerka_service import ServiceObj, BaseService
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComparisonObj(ServiceObj):
|
||||
"""
|
||||
Order to store
|
||||
"""
|
||||
property: str # property to compare
|
||||
a: int # id of concept a
|
||||
b: int # id of concept b
|
||||
op: str # comparison operation
|
||||
context: str = "#" # context when the comparison is right
|
||||
|
||||
|
||||
class SheerkaComparisonManager(BaseService):
|
||||
"""
|
||||
Manage partitioning of concepts
|
||||
"""
|
||||
NAME = "ComparisonManager"
|
||||
COMPARISON_ENTRY = "Comparison"
|
||||
RESOLVED_COMPARISON_ENTRY = "Resolved_Comparison"
|
||||
|
||||
def __init__(self, sheerka):
|
||||
super().__init__(sheerka)
|
||||
|
||||
@staticmethod
|
||||
def _compute_key(prop_name, comparison_context):
|
||||
return f"{prop_name}|{comparison_context}"
|
||||
|
||||
@staticmethod
|
||||
def _compute_weights(comparison_objs):
|
||||
"""
|
||||
For every element in greater_than_s, give it a weight
|
||||
if weight(a) > weight(b) it means that a > b
|
||||
:param comparison_objs: list of greater than objects
|
||||
:return:
|
||||
"""
|
||||
|
||||
values = {}
|
||||
for comparison_obj in comparison_objs:
|
||||
values[comparison_obj.a] = 1
|
||||
values[comparison_obj.b] = 1
|
||||
|
||||
for _ in range(len(comparison_objs)):
|
||||
for comparison_obj in comparison_objs:
|
||||
if comparison_obj.op == ">":
|
||||
values[comparison_obj.a] = values[comparison_obj.b] + 1
|
||||
else:
|
||||
values[comparison_obj.b] = values[comparison_obj.a] + 1
|
||||
|
||||
return values
|
||||
|
||||
@staticmethod
|
||||
def _get_partition(weighted_concepts):
|
||||
|
||||
res = {}
|
||||
for k, v in weighted_concepts.items():
|
||||
res.setdefault(v, []).append(k)
|
||||
return res
|
||||
|
||||
def _inner_add_comparison(self, comparison_obj):
|
||||
key = self._compute_key(comparison_obj.property, comparison_obj.context)
|
||||
previous = self.sheerka.cache_manager.get(self.COMPARISON_ENTRY, key)
|
||||
|
||||
new = previous.copy() if previous else []
|
||||
new.append(comparison_obj)
|
||||
|
||||
cycles = self.detect_cycles(new)
|
||||
if cycles:
|
||||
concepts_in_cycle = [self.sheerka.get_by_id(c) for c in cycles]
|
||||
chicken_an_egg = self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body=concepts_in_cycle)
|
||||
return self.sheerka.ret(self.NAME, False, chicken_an_egg)
|
||||
|
||||
self.sheerka.cache_manager.put(self.RESOLVED_COMPARISON_ENTRY, key, self._compute_weights(new))
|
||||
self.sheerka.cache_manager.put(self.COMPARISON_ENTRY, key, comparison_obj)
|
||||
|
||||
return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
|
||||
|
||||
def initialize(self):
|
||||
cache = ListCache(default=lambda k: self.sheerka.sdp.get(self.COMPARISON_ENTRY, k))
|
||||
self.sheerka.cache_manager.register_cache(self.COMPARISON_ENTRY, cache, True, True)
|
||||
|
||||
cache = Cache()
|
||||
self.sheerka.cache_manager.register_cache(self.RESOLVED_COMPARISON_ENTRY, cache, persist=False)
|
||||
|
||||
self.sheerka.bind_service_method(self, SheerkaComparisonManager.is_greater_than)
|
||||
self.sheerka.bind_service_method(self, SheerkaComparisonManager.is_less_than)
|
||||
self.sheerka.bind_service_method(self, SheerkaComparisonManager.get_partition)
|
||||
self.sheerka.bind_service_method(self, SheerkaComparisonManager.get_concepts_weights)
|
||||
|
||||
def is_greater_than(self, context, prop_name, concept_a, concept_b, comparison_context="#"):
|
||||
"""
|
||||
Records that the property of concept a is greater than concept b's one
|
||||
:param context:
|
||||
:param prop_name:
|
||||
:param concept_a:
|
||||
:param concept_b:
|
||||
:param comparison_context:
|
||||
:return:
|
||||
"""
|
||||
context.log(f"Setting concept {concept_a} is greater than {concept_b}", who=self.NAME)
|
||||
|
||||
event_digest = context.event.get_digest()
|
||||
comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, ">", comparison_context)
|
||||
return self._inner_add_comparison(comparison_obj)
|
||||
|
||||
def is_less_than(self, context, prop_name, concept_a, concept_b, comparison_context="#"):
|
||||
"""
|
||||
Records that the property of concept a is lesser than concept b's one
|
||||
:param context:
|
||||
:param prop_name:
|
||||
:param concept_a:
|
||||
:param concept_b:
|
||||
:param comparison_context:
|
||||
:return:
|
||||
"""
|
||||
context.log(f"Setting concept {concept_a} is less than {concept_b}", who=self.NAME)
|
||||
|
||||
event_digest = context.event.get_digest()
|
||||
comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, "<", comparison_context)
|
||||
return self._inner_add_comparison(comparison_obj)
|
||||
|
||||
def get_partition(self, prop_name, comparison_context="#"):
|
||||
weighted_concept = self.get_concepts_weights(prop_name, comparison_context)
|
||||
|
||||
return self._get_partition(weighted_concept)
|
||||
|
||||
def get_concepts_weights(self, prop_name, comparison_context="#"):
|
||||
weighted_concept = self.sheerka.cache_manager.get(
|
||||
self.RESOLVED_COMPARISON_ENTRY,
|
||||
self._compute_key(prop_name, comparison_context))
|
||||
|
||||
if weighted_concept is None:
|
||||
key = self._compute_key(prop_name, comparison_context)
|
||||
entries = self.sheerka.cache_manager.get(self.COMPARISON_ENTRY, key)
|
||||
|
||||
if entries is None:
|
||||
return {}
|
||||
else:
|
||||
weighted_concept = self._compute_weights(entries)
|
||||
self.sheerka.cache_manager.put(self.RESOLVED_COMPARISON_ENTRY, key, weighted_concept)
|
||||
|
||||
return weighted_concept
|
||||
|
||||
@staticmethod
|
||||
def detect_cycles(comparison_objs):
|
||||
"""
|
||||
# Thanks to Divyanshu Mehta for contributing this code
|
||||
# https://www.geeksforgeeks.org/detect-cycle-in-a-graph/?ref=lbp
|
||||
:param comparison_objs:
|
||||
:return:
|
||||
"""
|
||||
latest = comparison_objs[-1]
|
||||
if latest.op == "=":
|
||||
return None
|
||||
|
||||
def get_graph_and_vertices():
|
||||
_graph = {}
|
||||
_vertices = set()
|
||||
for obj in comparison_objs:
|
||||
if obj.op == "=":
|
||||
continue
|
||||
|
||||
_vertices.add(obj.a)
|
||||
_vertices.add(obj.b)
|
||||
if obj.op == ">":
|
||||
_graph.setdefault(obj.a, []).append(obj.b)
|
||||
else:
|
||||
_graph.setdefault(obj.b, []).append(obj.a)
|
||||
return _graph, _vertices
|
||||
|
||||
def is_cyclic(v):
|
||||
# Mark current node as visited and
|
||||
# adds to recursion stack
|
||||
visited[v] = True
|
||||
rec_stack[v] = True
|
||||
|
||||
# Recur for all neighbours
|
||||
# if any neighbour is visited and in
|
||||
# recStack then graph is cyclic
|
||||
if v in graph:
|
||||
for neighbour in graph[v]:
|
||||
if not visited[neighbour]:
|
||||
if is_cyclic(neighbour):
|
||||
return True
|
||||
elif rec_stack[neighbour]:
|
||||
return True
|
||||
|
||||
# The node needs to be poped from
|
||||
# recursion stack before function ends
|
||||
rec_stack[v] = False
|
||||
return False
|
||||
|
||||
graph, vertices = get_graph_and_vertices()
|
||||
visited = {k: False for k in vertices}
|
||||
rec_stack = {k: False for k in vertices}
|
||||
|
||||
if is_cyclic(latest.a): # only need to check from the latest add, since the graph was not cyclic before
|
||||
return [k for k, v in rec_stack.items() if v]
|
||||
return None
|
||||
@@ -0,0 +1,70 @@
|
||||
import core.utils
|
||||
from core.builtin_concepts import BuiltinConcepts, ErrorConcept
|
||||
from core.concept import Concept
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProviderDuplicateKeyError
|
||||
|
||||
BNF_NODE_PARSER_CLASS = "parsers.BnfNodeParser_Old.BnfNodeParser"
|
||||
BASE_NODE_PARSER_CLASS = "parsers.BaseNodeParser.BaseNodeParser"
|
||||
|
||||
|
||||
class SheerkaCreateNewConcept:
|
||||
"""
|
||||
Manage the creation of a new concept
|
||||
"""
|
||||
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
self.logger_name = "CreateNewConcept"
|
||||
self.bnp = core.utils.get_class(BASE_NODE_PARSER_CLASS) # BaseNodeParser
|
||||
|
||||
def create_new_concept(self, context, concept: Concept):
|
||||
"""
|
||||
Adds a new concept to the system
|
||||
:param context:
|
||||
:param concept: DefConceptNode
|
||||
:return: digest of the new concept
|
||||
"""
|
||||
|
||||
sheerka = self.sheerka
|
||||
|
||||
concept.init_key()
|
||||
init_bnf_ret_value = None
|
||||
|
||||
cache_manager = sheerka.cache_manager
|
||||
|
||||
if cache_manager.exists(sheerka.CONCEPTS_BY_HASH_ENTRY, concept.get_definition_hash()):
|
||||
error = SheerkaDataProviderDuplicateKeyError(sheerka.CONCEPTS_BY_KEY_ENTRY + "." + concept.key,
|
||||
concept)
|
||||
return sheerka.ret(
|
||||
self.logger_name,
|
||||
False,
|
||||
sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_DEFINED, body=concept),
|
||||
error.args[0])
|
||||
|
||||
# set id before saving in db
|
||||
sheerka.set_id_if_needed(concept, False)
|
||||
|
||||
# update the dictionary of concepts by first key
|
||||
init_ret_value = self.bnp.get_concepts_by_first_keyword(context, [concept], True)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.logger_name, False, ErrorConcept(init_ret_value.value))
|
||||
concepts_by_first_keyword = init_ret_value.body
|
||||
|
||||
# update resolved dictionary
|
||||
init_ret_value = self.bnp.resolve_concepts_by_first_keyword(context, concepts_by_first_keyword)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.logger_name, False, ErrorConcept(init_ret_value.value))
|
||||
resolved_concepts_by_first_keyword = init_ret_value.body
|
||||
|
||||
concept.freeze_definition_hash()
|
||||
|
||||
cache_manager.add_concept(concept)
|
||||
cache_manager.put(sheerka.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, concepts_by_first_keyword)
|
||||
cache_manager.put(sheerka.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
|
||||
|
||||
if concept.bnf and init_bnf_ret_value is not None and init_bnf_ret_value.status:
|
||||
sheerka.cache_manager.clear(sheerka.CONCEPTS_GRAMMARS_ENTRY)
|
||||
|
||||
# process the return if needed
|
||||
ret = sheerka.ret(self.logger_name, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
|
||||
return ret
|
||||
@@ -0,0 +1,98 @@
|
||||
import os
|
||||
import pprint
|
||||
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept
|
||||
from core.sheerka.ExecutionContext import ExecutionContext
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event
|
||||
|
||||
|
||||
def get_pp():
|
||||
rows, columns = os.popen('stty size', 'r').read().split()
|
||||
pp = pprint.PrettyPrinter(width=columns, compact=True)
|
||||
return pp
|
||||
|
||||
|
||||
class SheerkaDump:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
|
||||
def dump_concepts(self):
|
||||
lst = self.sheerka.sdp.list(self.sheerka.CONCEPTS_BY_ID_ENTRY)
|
||||
for item in lst:
|
||||
if hasattr(item, "__iter__"):
|
||||
for i in item:
|
||||
self.sheerka.log.info(i)
|
||||
else:
|
||||
self.sheerka.log.info(item)
|
||||
|
||||
def dump_desc(self, *concept_names, eval=False):
|
||||
first = True
|
||||
event = Event(f"Dumping description", "")
|
||||
context = ExecutionContext("dump_desc", event, self.sheerka)
|
||||
for concept_name in concept_names:
|
||||
if isinstance(concept_name, Concept):
|
||||
concepts = concept_name
|
||||
else:
|
||||
concepts = self.sheerka.get_by_key(concept_name)
|
||||
if self.sheerka.isinstance(concepts, BuiltinConcepts.UNKNOWN_CONCEPT):
|
||||
self.sheerka.log.error(f"Concept '{concept_name}' is unknown")
|
||||
return False
|
||||
|
||||
if not hasattr(concepts, "__iter__"):
|
||||
concepts = [concepts]
|
||||
|
||||
for c in concepts:
|
||||
if eval:
|
||||
evaluated = self.sheerka.evaluate_concept(context, c)
|
||||
value = evaluated.body if evaluated.key == c.key else evaluated
|
||||
|
||||
if not first:
|
||||
self.sheerka.log.info("")
|
||||
self.sheerka.log.info(f"name : {c.name}")
|
||||
self.sheerka.log.info(f"key : {c.key}")
|
||||
self.sheerka.log.info(f"bnf : {c.metadata.definition}")
|
||||
self.sheerka.log.info(f"body : {c.metadata.body}")
|
||||
self.sheerka.log.info(f"where : {c.metadata.where}")
|
||||
if eval:
|
||||
self.sheerka.log.info(f"value : {value}")
|
||||
for v in c.values:
|
||||
self.sheerka.log.info(f"{v}: {c.get_value(v)}")
|
||||
else:
|
||||
self.sheerka.log.info("No property")
|
||||
|
||||
self.sheerka.log.info(f"digest : {c.get_origin()}")
|
||||
|
||||
if self.sheerka.isaset(context, c):
|
||||
items = self.sheerka.get_set_elements(context, c)
|
||||
self.sheerka.log.info(f"elements : {items}")
|
||||
|
||||
first = False
|
||||
|
||||
def dump_history(self, page=20, start=0):
|
||||
count = 0
|
||||
resolved_page = page if page > 0 else 50
|
||||
page_count = 0
|
||||
|
||||
while count < page if page > 0 else True:
|
||||
history = self.sheerka.history(resolved_page, start + page_count * resolved_page)
|
||||
try:
|
||||
h = next(history)
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
while True:
|
||||
try:
|
||||
if h.result:
|
||||
self.sheerka.log.info(h)
|
||||
count += 1
|
||||
h = next(history)
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
page_count += 1
|
||||
|
||||
def dump_state(self):
|
||||
snapshot = self.sheerka.sdp.get_snapshot(SheerkaDataProvider.HeadFile)
|
||||
state = self.sheerka.sdp.load_state(snapshot)
|
||||
self.sheerka.log.info(get_pp().pformat(state.data))
|
||||
@@ -0,0 +1,323 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.builtin_helpers import expect_one, only_successful
|
||||
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved
|
||||
|
||||
CONCEPT_EVALUATION_STEPS = [
|
||||
BuiltinConcepts.BEFORE_EVALUATION,
|
||||
BuiltinConcepts.EVALUATION,
|
||||
BuiltinConcepts.AFTER_EVALUATION]
|
||||
|
||||
|
||||
class SheerkaEvaluateConcept:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
self.logger_name = "EvaluateConcept"
|
||||
|
||||
@staticmethod
|
||||
def infinite_recursion_detected(context, concept):
|
||||
if concept is None:
|
||||
return False
|
||||
|
||||
parent = context.get_parent()
|
||||
while parent is not None:
|
||||
if parent.who == context.who and parent.obj == concept:
|
||||
return True
|
||||
|
||||
parent = parent.get_parent()
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_infinite_recursion_resolution(obj):
|
||||
if isinstance(obj, InfiniteRecursionResolved):
|
||||
return obj
|
||||
|
||||
if isinstance(obj, Concept) and isinstance(obj.body, InfiniteRecursionResolved):
|
||||
return obj.body
|
||||
|
||||
return None
|
||||
|
||||
def manage_infinite_recursion(self, context):
|
||||
"""
|
||||
We look for the fist parent that has a body that means something
|
||||
We use the eval function with no local or global
|
||||
:param context:
|
||||
:return:
|
||||
"""
|
||||
|
||||
parent = context
|
||||
concepts_found = set()
|
||||
while parent and parent.obj:
|
||||
if parent.who == context.who and parent.desc == context.desc:
|
||||
body = parent.obj.metadata.body
|
||||
try:
|
||||
return self.sheerka.ret(self.logger_name, True, InfiniteRecursionResolved(eval(body)))
|
||||
except Exception:
|
||||
pass
|
||||
concepts_found.add(parent.obj)
|
||||
parent = parent.get_parent()
|
||||
|
||||
return self.sheerka.ret(
|
||||
self.logger_name,
|
||||
False,
|
||||
self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body=concepts_found))
|
||||
|
||||
def initialize_concept_asts(self, context, concept: Concept):
|
||||
"""
|
||||
Updates the codes of the newly created concept
|
||||
Basically, it runs the parsers on all parts
|
||||
:param concept:
|
||||
:param context:
|
||||
:param logger:
|
||||
:return:
|
||||
"""
|
||||
def is_only_successful(r):
|
||||
# return False
|
||||
return context.sheerka.isinstance(r, BuiltinConcepts.RETURN_VALUE) and \
|
||||
context.sheerka.isinstance(r.body, BuiltinConcepts.ONLY_SUCCESSFUL)
|
||||
|
||||
steps = [BuiltinConcepts.BEFORE_PARSING, BuiltinConcepts.PARSING, BuiltinConcepts.AFTER_PARSING]
|
||||
for part_key in ConceptParts:
|
||||
if part_key in concept.compiled:
|
||||
continue
|
||||
|
||||
source = getattr(concept.metadata, part_key.value)
|
||||
if source is None or not isinstance(source, str):
|
||||
continue
|
||||
|
||||
if source.strip() == "":
|
||||
concept.compiled[part_key] = DoNotResolve(source)
|
||||
else:
|
||||
with context.push(desc=f"Initializing *compiled* for {part_key}") as sub_context:
|
||||
sub_context.add_inputs(source=source)
|
||||
to_parse = self.sheerka.ret(context.who, True,
|
||||
self.sheerka.new(BuiltinConcepts.USER_INPUT, body=source))
|
||||
res = self.sheerka.execute(sub_context, to_parse, steps)
|
||||
only_success = only_successful(sub_context, res)
|
||||
concept.compiled[part_key] = only_success.body.body if is_only_successful(only_success) else res
|
||||
sub_context.add_values(return_values=res)
|
||||
|
||||
for var_name, default_value in concept.metadata.variables:
|
||||
if var_name in concept.compiled:
|
||||
continue
|
||||
|
||||
if default_value is None or not isinstance(default_value, str):
|
||||
continue
|
||||
|
||||
if default_value.strip() == "":
|
||||
concept.compiled[var_name] = DoNotResolve(default_value)
|
||||
else:
|
||||
with context.push(desc=f"Initializing *compiled* for property {var_name}") as sub_context:
|
||||
sub_context.add_inputs(source=default_value)
|
||||
to_parse = self.sheerka.ret(context.who, True,
|
||||
self.sheerka.new(BuiltinConcepts.USER_INPUT, body=default_value))
|
||||
res = self.sheerka.execute(sub_context, to_parse, steps)
|
||||
only_success = only_successful(sub_context, res)
|
||||
concept.compiled[var_name] = only_success.body.body if is_only_successful(only_success) else res
|
||||
sub_context.add_values(return_values=res)
|
||||
|
||||
# Updates the cache of concepts when possible
|
||||
if self.sheerka.has_id(concept.id):
|
||||
self.sheerka.get_by_id(concept.id).compiled = concept.compiled
|
||||
|
||||
def resolve(self, context, to_resolve, current_prop, current_concept, force_evaluation):
|
||||
|
||||
if isinstance(to_resolve, DoNotResolve):
|
||||
return to_resolve.value
|
||||
|
||||
# manage infinite loop
|
||||
if self.infinite_recursion_detected(context, current_concept):
|
||||
with context.push(desc="Infinite recursion detected", obj=current_concept) as sub_context:
|
||||
# I create a sub context in order to log what happened
|
||||
ret_val = self.manage_infinite_recursion(context)
|
||||
sub_context.add_values(return_values=ret_val)
|
||||
return ret_val.body
|
||||
|
||||
desc = f"Evaluating {current_prop} (concept={current_concept})"
|
||||
context.log(desc, self.logger_name)
|
||||
with context.push(desc=desc, obj=current_concept) as sub_context:
|
||||
|
||||
if force_evaluation:
|
||||
sub_context.local_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
|
||||
# when it's a concept, evaluate it
|
||||
if isinstance(to_resolve, Concept) and \
|
||||
not context.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE):
|
||||
evaluated = self.evaluate_concept(sub_context, to_resolve)
|
||||
sub_context.add_values(return_values=evaluated)
|
||||
if evaluated.key == to_resolve.key:
|
||||
return evaluated
|
||||
else:
|
||||
error = evaluated
|
||||
|
||||
# otherwise, execute all return values to find out what is the value
|
||||
else:
|
||||
use_copy = [r for r in to_resolve] if hasattr(to_resolve, "__iter__") else to_resolve
|
||||
r = self.sheerka.execute(sub_context, use_copy, CONCEPT_EVALUATION_STEPS)
|
||||
|
||||
one_r = expect_one(context, r)
|
||||
sub_context.add_values(return_values=one_r)
|
||||
if one_r.status:
|
||||
return one_r.value
|
||||
else:
|
||||
error = one_r.value
|
||||
|
||||
return error if self.sheerka.isinstance(error, BuiltinConcepts.CHICKEN_AND_EGG) \
|
||||
else self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
||||
body=error,
|
||||
concept=current_concept,
|
||||
property_name=current_prop)
|
||||
|
||||
def resolve_list(self, context, list_to_resolve, current_prop, current_concept, force_evaluation):
|
||||
"""When dealing with a list, there are two possibilities"""
|
||||
# It may be a list of ReturnValueConcept to execute (always the case for metadata)
|
||||
# or a list of single values (may be the case for properties)
|
||||
# in this latter case, all values are to be processed one by one and a list should be returned
|
||||
if len(list_to_resolve) == 0:
|
||||
return []
|
||||
|
||||
if self.sheerka.isinstance(list_to_resolve[0], BuiltinConcepts.RETURN_VALUE):
|
||||
return self.resolve(context, list_to_resolve, current_prop, current_concept, force_evaluation)
|
||||
|
||||
res = []
|
||||
for to_resolve in list_to_resolve:
|
||||
# sanity check
|
||||
if self.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE):
|
||||
return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
||||
body="Mix between real values and return values",
|
||||
concept=current_concept,
|
||||
property_name=current_prop)
|
||||
|
||||
r = self.resolve(context, to_resolve, current_prop, current_concept, force_evaluation)
|
||||
if self.sheerka.isinstance(r, BuiltinConcepts.CONCEPT_EVAL_ERROR):
|
||||
return r
|
||||
res.append(r)
|
||||
|
||||
return res
|
||||
|
||||
def evaluate_concept(self, context, concept: Concept):
|
||||
"""
|
||||
Evaluation a concept
|
||||
It means that if the where clause is True, will evaluate the body
|
||||
:param context:
|
||||
:param concept:
|
||||
:return: value of the evaluation or error
|
||||
"""
|
||||
|
||||
if concept.metadata.is_evaluated:
|
||||
return concept
|
||||
|
||||
self.initialize_concept_asts(context, concept)
|
||||
|
||||
# to make sure of the order, it don't use ConceptParts.get_parts()
|
||||
# variables must be evaluated first, body must be evaluated before where
|
||||
all_metadata_to_eval = self.choose_metadata_to_eval(context, concept)
|
||||
|
||||
for metadata_to_eval in all_metadata_to_eval:
|
||||
if metadata_to_eval == "variables":
|
||||
for var_name in (v for v in concept.variables() if v in concept.compiled):
|
||||
prop_ast = concept.compiled[var_name]
|
||||
|
||||
if isinstance(prop_ast, list):
|
||||
# Do not send the current concept for the properties
|
||||
resolved = self.resolve_list(context, prop_ast, var_name, None, True)
|
||||
else:
|
||||
# Do not send the current concept for the properties
|
||||
resolved = self.resolve(context, prop_ast, var_name, None, True)
|
||||
|
||||
if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved):
|
||||
resolved.set_value("concept", concept) # since current concept was not sent
|
||||
return resolved
|
||||
else:
|
||||
concept.set_value(var_name, resolved)
|
||||
else:
|
||||
part_key = ConceptParts(metadata_to_eval)
|
||||
|
||||
# do not evaluate where when the body is a set
|
||||
# Indeed, the way that the where clause is expressed is not a valid python or concept code
|
||||
if part_key == ConceptParts.WHERE and self.sheerka.isaset(context, concept.body):
|
||||
continue
|
||||
|
||||
if part_key in concept.compiled and concept.compiled[part_key] is not None:
|
||||
metadata_ast = concept.compiled[part_key]
|
||||
# if part_key is PRE, POST or WHERE, the concepts need to be evaluated
|
||||
# otherwise the predicates cannot be resolved
|
||||
force_concept_eval = False if part_key == ConceptParts.BODY else True
|
||||
resolved = self.resolve(context, metadata_ast, part_key, concept, force_concept_eval)
|
||||
if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved):
|
||||
return resolved
|
||||
else:
|
||||
concept.set_value(part_key, self.get_infinite_recursion_resolution(resolved) or resolved)
|
||||
|
||||
#
|
||||
# TODO : Validate the PRE condition
|
||||
#
|
||||
|
||||
# validate where clause
|
||||
if ConceptParts.WHERE in concept.values:
|
||||
where_value = concept.get_value(ConceptParts.WHERE)
|
||||
if not (where_value is None or self.sheerka.objvalue(where_value)):
|
||||
return self.sheerka.new(BuiltinConcepts.WHERE_CLAUSE_FAILED, body=concept)
|
||||
|
||||
#
|
||||
# TODO : Validate the POST condition
|
||||
#
|
||||
|
||||
concept.init_key() # only does it if needed
|
||||
concept.metadata.is_evaluated = "body" in all_metadata_to_eval
|
||||
return concept
|
||||
|
||||
def choose_metadata_to_eval(self, context, concept):
|
||||
if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED):
|
||||
return ["pre", "post", "variables", "body", "where"]
|
||||
|
||||
metadata = ["pre", "post"]
|
||||
if context.in_context(BuiltinConcepts.EVAL_WHERE_REQUESTED) or concept.metadata.need_validation:
|
||||
needed = self.needed_metadata(concept, ConceptParts.WHERE)
|
||||
for e in needed:
|
||||
if e not in metadata:
|
||||
metadata.append(e)
|
||||
if "where" not in metadata:
|
||||
metadata.append("where")
|
||||
|
||||
return metadata
|
||||
|
||||
def needed_metadata(self, concept, metadata):
|
||||
"""
|
||||
Tries to find out if the evaluation of the body is necessary
|
||||
It's a very basic approach that will need to be improved
|
||||
:param concept:
|
||||
:param metadata:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if metadata not in concept.compiled:
|
||||
return []
|
||||
|
||||
return_values = concept.compiled[metadata]
|
||||
if not isinstance(return_values, list):
|
||||
return []
|
||||
|
||||
needed = []
|
||||
for return_value in return_values:
|
||||
if not self.sheerka.isinstance(return_value, BuiltinConcepts.RETURN_VALUE):
|
||||
continue
|
||||
|
||||
if not return_value.status:
|
||||
continue
|
||||
|
||||
if not self.sheerka.isinstance(return_value.body, BuiltinConcepts.PARSER_RESULT):
|
||||
continue
|
||||
|
||||
if not isinstance(return_value.body.source, str):
|
||||
continue
|
||||
|
||||
for var_name in (p[0] for p in concept.metadata.variables):
|
||||
if var_name in return_value.body.source:
|
||||
needed.append("variables")
|
||||
break
|
||||
|
||||
if "self" in return_value.body.source:
|
||||
needed.append("body")
|
||||
|
||||
return needed
|
||||
@@ -0,0 +1,247 @@
|
||||
import core.utils
|
||||
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||
|
||||
NO_MATCH = "** No Match **"
|
||||
|
||||
|
||||
class SheerkaExecute:
|
||||
"""
|
||||
Manage the execution of a process flow
|
||||
"""
|
||||
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
|
||||
def call_parsers(self, execution_context, return_values):
|
||||
|
||||
# return_values must be a list
|
||||
if not isinstance(return_values, list):
|
||||
return_values = [return_values]
|
||||
|
||||
# first make the distinguish between what is for the parsers and what is not
|
||||
result = []
|
||||
to_process = []
|
||||
for r in return_values:
|
||||
if not r.status or not self.sheerka.isinstance(r.body, BuiltinConcepts.USER_INPUT):
|
||||
result.append(r)
|
||||
else:
|
||||
to_process.append(r)
|
||||
|
||||
if not to_process:
|
||||
return result
|
||||
|
||||
# keep track of the originals user inputs, as they need to be removed at the end
|
||||
user_inputs = to_process[:]
|
||||
|
||||
# group the parsers by priorities
|
||||
instantiated_parsers = [parser(sheerka=self.sheerka) for parser in self.sheerka.parsers.values()]
|
||||
instantiated_parsers = self.preprocess(execution_context, instantiated_parsers)
|
||||
|
||||
grouped_parsers = {}
|
||||
for parser in [p for p in instantiated_parsers if p.enabled]:
|
||||
grouped_parsers.setdefault(parser.priority, []).append(parser)
|
||||
sorted_priorities = sorted(grouped_parsers.keys(), reverse=True)
|
||||
|
||||
stop_processing = False
|
||||
for priority in sorted_priorities:
|
||||
inputs_for_this_group = to_process[:]
|
||||
|
||||
for parser in grouped_parsers[priority]:
|
||||
|
||||
for return_value in inputs_for_this_group:
|
||||
|
||||
to_parse = return_value.body.body \
|
||||
if self.sheerka.isinstance(return_value.body, BuiltinConcepts.USER_INPUT) \
|
||||
else return_value.body
|
||||
|
||||
# if self.sheerka.log.isEnabledFor(logging.DEBUG):
|
||||
# debug_text = "'" + to_parse + "'" if isinstance(to_parse, str) \
|
||||
# else "'" + BaseParser.get_text_from_tokens(to_parse) + "' as tokens"
|
||||
# execution_context.log(f"Parsing {debug_text}")
|
||||
|
||||
with execution_context.push(desc=f"Parsing using {parser.name}",
|
||||
logger=parser.verbose_log) as sub_context:
|
||||
sub_context.add_inputs(to_parse=to_parse)
|
||||
res = parser.parse(sub_context, to_parse)
|
||||
if res is not None:
|
||||
if hasattr(res, "__iter__"):
|
||||
for r in res:
|
||||
if r is None:
|
||||
continue
|
||||
r.parents = [return_value]
|
||||
result.append(r)
|
||||
if self.sheerka.isinstance(r.body, BuiltinConcepts.PARSER_RESULT):
|
||||
# if a ParserResultConcept is returned, it will be used by the parsers
|
||||
# of the following groups
|
||||
to_process.append(r)
|
||||
if r.status:
|
||||
stop_processing = True
|
||||
|
||||
else:
|
||||
res.parents = [return_value]
|
||||
result.append(res)
|
||||
if self.sheerka.isinstance(res.body, BuiltinConcepts.PARSER_RESULT):
|
||||
# if a ParserResultConcept is returned, it will be used by the parsers
|
||||
# of the following groups
|
||||
to_process.append(res)
|
||||
if res.status:
|
||||
stop_processing = True
|
||||
sub_context.add_values(return_values=res)
|
||||
|
||||
if stop_processing:
|
||||
break # Do not try the other priorities if a match is found
|
||||
|
||||
result = core.utils.remove_list_from_list(result, user_inputs)
|
||||
return result
|
||||
|
||||
def call_evaluators(self, execution_context, return_values, process_step):
|
||||
|
||||
# return_values must be a list
|
||||
if not isinstance(return_values, list):
|
||||
return_values = [return_values]
|
||||
|
||||
# group the evaluators by priority and sort them
|
||||
# The first one to be applied will be the one with the highest priority
|
||||
grouped_evaluators = {}
|
||||
instantiated_evaluators = [e_class() for e_class in self.sheerka.evaluators]
|
||||
|
||||
# pre-process evaluators if needed
|
||||
instantiated_evaluators = self.preprocess(execution_context, instantiated_evaluators)
|
||||
|
||||
for evaluator in [e for e in instantiated_evaluators if e.enabled and process_step in e.steps]:
|
||||
grouped_evaluators.setdefault(evaluator.priority, []).append(evaluator)
|
||||
|
||||
# order the groups by priority, the higher first
|
||||
sorted_priorities = sorted(grouped_evaluators.keys(), reverse=True)
|
||||
|
||||
# process
|
||||
iteration = 0
|
||||
while True:
|
||||
with execution_context.push(desc=f"iteration #{iteration}", iteration=iteration) as iteration_context:
|
||||
simple_digest = return_values[:]
|
||||
iteration_context.add_inputs(return_values=simple_digest)
|
||||
|
||||
for priority in sorted_priorities:
|
||||
|
||||
original_items = return_values[:]
|
||||
evaluated_items = []
|
||||
to_delete = []
|
||||
for evaluator in grouped_evaluators[priority]:
|
||||
evaluator = self.preprocess(execution_context, evaluator.__class__()) # fresh copy
|
||||
|
||||
sub_context_desc = f"Evaluating using {evaluator.name} ({priority=})"
|
||||
with iteration_context.push(desc=sub_context_desc, logger=evaluator.verbose_log) as sub_context:
|
||||
sub_context.add_inputs(return_values=original_items)
|
||||
|
||||
# process evaluators that work on one simple return value at the time
|
||||
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
if isinstance(evaluator, OneReturnValueEvaluator):
|
||||
debug_result = []
|
||||
for item in original_items:
|
||||
if evaluator.matches(sub_context, item):
|
||||
|
||||
# init the evaluator is possible
|
||||
if hasattr(evaluator, "init_evaluator") and not evaluator.is_initialized:
|
||||
evaluator.init_evaluator(sub_context, original_items)
|
||||
|
||||
result = evaluator.eval(sub_context, item)
|
||||
if result is None:
|
||||
debug_result.append({"input": item, "return_value": None})
|
||||
continue
|
||||
|
||||
to_delete.append(item)
|
||||
if isinstance(result, list):
|
||||
evaluated_items.extend(result)
|
||||
elif isinstance(result, ReturnValueConcept):
|
||||
evaluated_items.append(result)
|
||||
else:
|
||||
error = self.sheerka.new(BuiltinConcepts.INVALID_RETURN_VALUE, body=result,
|
||||
evaluator=evaluator)
|
||||
result = self.sheerka.ret("sheerka.process", False, error, parents=[item])
|
||||
evaluated_items.append(result)
|
||||
debug_result.append({"input": item, "return_value": result})
|
||||
else:
|
||||
debug_result.append({"input": item, "return_value": NO_MATCH})
|
||||
sub_context.add_values(return_values=debug_result)
|
||||
|
||||
# process evaluators that work on all return values
|
||||
else:
|
||||
if evaluator.matches(sub_context, original_items):
|
||||
results = evaluator.eval(sub_context, original_items)
|
||||
if results is None:
|
||||
continue
|
||||
if not isinstance(results, list):
|
||||
results = [results]
|
||||
for result in results:
|
||||
evaluated_items.append(result)
|
||||
to_delete.extend(result.parents)
|
||||
sub_context.add_values(return_values=results)
|
||||
else:
|
||||
sub_context.add_values(return_values=NO_MATCH)
|
||||
|
||||
return_values = evaluated_items
|
||||
return_values.extend([item for item in original_items if item not in to_delete])
|
||||
|
||||
iteration_context.add_values(return_values=return_values[:])
|
||||
|
||||
# have we done something ?
|
||||
to_compare = return_values[:]
|
||||
if simple_digest == to_compare:
|
||||
break
|
||||
|
||||
# inc the iteration and continue
|
||||
iteration += 1
|
||||
|
||||
return return_values
|
||||
|
||||
def execute(self, execution_context, return_values, execution_steps):
|
||||
"""
|
||||
Executes process for all initial contexts
|
||||
:param execution_context:
|
||||
:param return_values:
|
||||
:param execution_steps:
|
||||
:return:
|
||||
"""
|
||||
|
||||
for step in execution_steps:
|
||||
copy = return_values[:] if hasattr(return_values, "__iter__") else [return_values]
|
||||
with execution_context.push(step=step, iteration=0, desc=f"{step=}") as sub_context:
|
||||
|
||||
if step == BuiltinConcepts.PARSING:
|
||||
return_values = self.call_parsers(sub_context, return_values)
|
||||
else:
|
||||
return_values = self.call_evaluators(sub_context, return_values, step)
|
||||
|
||||
if copy != return_values:
|
||||
sub_context.log_result(return_values)
|
||||
|
||||
sub_context.add_values(return_values=return_values)
|
||||
|
||||
return return_values
|
||||
|
||||
def preprocess(self, context, parsers_or_evaluators):
|
||||
if not context.preprocess:
|
||||
return parsers_or_evaluators
|
||||
|
||||
if not hasattr(parsers_or_evaluators, "__iter__"):
|
||||
single_one = True
|
||||
parsers_or_evaluators = [parsers_or_evaluators]
|
||||
else:
|
||||
single_one = False
|
||||
|
||||
for preprocess in context.preprocess:
|
||||
for e in parsers_or_evaluators:
|
||||
if self.matches(e.name, preprocess.get_value("name")):
|
||||
for var_name in preprocess.values:
|
||||
if var_name == "name":
|
||||
continue
|
||||
if hasattr(e, var_name):
|
||||
setattr(e, var_name, preprocess.get_value(var_name))
|
||||
return parsers_or_evaluators[0] if single_one else parsers_or_evaluators
|
||||
|
||||
@staticmethod
|
||||
def matches(parser_or_evaluator_name, preprocessor_name):
|
||||
if preprocessor_name.endswith("*"):
|
||||
return parser_or_evaluator_name.startswith(preprocessor_name[:-1])
|
||||
else:
|
||||
return parser_or_evaluator_name == preprocessor_name
|
||||
@@ -0,0 +1,63 @@
|
||||
from collections import namedtuple
|
||||
|
||||
from sdp.sheerkaDataProvider import Event
|
||||
|
||||
hist = namedtuple("HistoryTest", "text status") # tests purposes only
|
||||
|
||||
|
||||
class History:
|
||||
def __init__(self, event: Event, result):
|
||||
self.event = event
|
||||
self.result = result
|
||||
self._status = None
|
||||
|
||||
def __str__(self):
|
||||
msg = f"{self.event.get_digest()} {self.event.date.strftime('%d/%m/%Y %H:%M:%S')} : {self.event.message}"
|
||||
status = self.status
|
||||
if status is not None:
|
||||
msg += f" => {status}"
|
||||
return msg
|
||||
|
||||
def __repr__(self):
|
||||
return f"History(event={self.event!r}, status={self.status}, result={self.result})"
|
||||
|
||||
def __eq__(self, other):
|
||||
if id(self) == id(other):
|
||||
return True
|
||||
|
||||
if isinstance(other, hist):
|
||||
return self.event.message == other.text and self.status == other.status
|
||||
|
||||
if not isinstance(other, History):
|
||||
return False
|
||||
|
||||
return self.event == other.event and self.result == other.result
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
if self._status:
|
||||
return self._status
|
||||
|
||||
self._status = self.result.get_status() if self.result else None
|
||||
return self._status
|
||||
|
||||
|
||||
class SheerkaHistoryManager:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
|
||||
def history(self, depth, start):
|
||||
"""
|
||||
Load history
|
||||
:param depth: number of items
|
||||
:param start:
|
||||
:return:
|
||||
"""
|
||||
|
||||
events = list(self.sheerka.sdp.load_events(depth, start))
|
||||
for event in events:
|
||||
try:
|
||||
result = self.sheerka.sdp.load_result(event.get_digest())
|
||||
except (IOError, KeyError):
|
||||
result = None
|
||||
yield History(event, result)
|
||||
@@ -0,0 +1,41 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
|
||||
|
||||
class SheerkaModifyConcept:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
self.logger_name = "ModifyConcept"
|
||||
|
||||
def modify_concept(self, context, concept):
|
||||
old_version = self.sheerka.get_by_id(concept.id)
|
||||
|
||||
if old_version is None:
|
||||
# nothing found in cache
|
||||
return self.sheerka.ret(
|
||||
self.logger_name, False,
|
||||
self.sheerka.new(
|
||||
BuiltinConcepts.UNKNOWN_CONCEPT,
|
||||
body=[("key", concept.key), ("id", concept.id)]))
|
||||
|
||||
if not self.sheerka.is_success(old_version) and concept.key != old_version.key:
|
||||
# an error concept is returned
|
||||
return self.sheerka.ret(
|
||||
self.logger_name, False,
|
||||
old_version)
|
||||
|
||||
if old_version == concept:
|
||||
# the concept is not modified
|
||||
return self.sheerka.ret(
|
||||
self.logger_name, False,
|
||||
self.sheerka.new(
|
||||
BuiltinConcepts.CONCEPT_ALREADY_DEFINED,
|
||||
body=concept))
|
||||
|
||||
self.sheerka.cache_manager.update_concept(old_version, concept)
|
||||
|
||||
# TODO : update concept by first keyword
|
||||
# TODO : update resolved by first keyword
|
||||
# TODO : update concepts grammars
|
||||
|
||||
ret = self.sheerka.ret(self.logger_name, True, self.sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
|
||||
return ret
|
||||
@@ -0,0 +1,232 @@
|
||||
import core.builtin_helpers
|
||||
from core.ast.nodes import python_to_concept
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept, ConceptParts
|
||||
|
||||
GROUP_PREFIX = 'All_'
|
||||
|
||||
|
||||
class SheerkaSetsManager:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
self.logger_name = "SetsManager"
|
||||
|
||||
def set_isa(self, context, concept, concept_set):
|
||||
"""
|
||||
Defines that concept a is b is another concept
|
||||
:param context:
|
||||
:param concept:
|
||||
:param concept_set:
|
||||
:return:
|
||||
"""
|
||||
|
||||
context.log(f"Setting concept {concept} is a {concept_set}", who=self.logger_name)
|
||||
|
||||
if BuiltinConcepts.ISA in concept.metadata.props and concept_set in concept.metadata.props[BuiltinConcepts.ISA]:
|
||||
return self.sheerka.ret(
|
||||
self.logger_name,
|
||||
False,
|
||||
self.sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_IN_SET, body=concept, concept_set=concept_set))
|
||||
|
||||
concept.add_prop(BuiltinConcepts.ISA, concept_set)
|
||||
|
||||
res = self.sheerka.modify_concept(context, concept)
|
||||
if not res.status:
|
||||
return res
|
||||
|
||||
return self.add_concept_to_set(context, concept, concept_set)
|
||||
|
||||
def add_concept_to_set(self, context, concept, concept_set):
|
||||
"""
|
||||
Add an entry in sdp to tell that concept isa concept_set
|
||||
:param context:
|
||||
:param concept:
|
||||
:param concept_set:
|
||||
:return:
|
||||
"""
|
||||
|
||||
context.log(f"Adding concept {concept} to set {concept_set}", who=self.logger_name)
|
||||
|
||||
assert concept.id
|
||||
assert concept_set.id
|
||||
|
||||
set_elements = self.sheerka.cache_manager.get(self.sheerka.CONCEPTS_GROUPS_ENTRY, concept_set.id)
|
||||
if set_elements and concept.id in set_elements:
|
||||
return self.sheerka.ret(
|
||||
self.logger_name,
|
||||
False,
|
||||
self.sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_IN_SET, body=concept, concept_set=concept_set))
|
||||
|
||||
self.sheerka.cache_manager.put(self.sheerka.CONCEPTS_GROUPS_ENTRY, concept_set.id, concept.id)
|
||||
return self.sheerka.ret(self.logger_name, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
|
||||
|
||||
def add_concepts_to_set(self, context, concepts, concept_set):
|
||||
"""Adding multiple concepts at the same time"""
|
||||
|
||||
context.log(f"Adding concepts {concepts} to set {concept_set}", who=self.logger_name)
|
||||
already_in_set = []
|
||||
for concept in concepts:
|
||||
res = self.add_concept_to_set(context, concept, concept_set)
|
||||
if self.sheerka.isinstance(res.body, BuiltinConcepts.CONCEPT_ALREADY_IN_SET):
|
||||
already_in_set.append(res.body.body)
|
||||
|
||||
if already_in_set:
|
||||
body = self.sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
|
||||
body=already_in_set,
|
||||
concept_set=concept_set)
|
||||
else:
|
||||
body = self.sheerka.new(BuiltinConcepts.SUCCESS)
|
||||
|
||||
return self.sheerka.ret(self.logger_name, len(already_in_set) != len(concepts), body)
|
||||
|
||||
def get_set_elements(self, context, concept):
|
||||
"""
|
||||
Concept is supposed to be a set
|
||||
Returns all elements if the set
|
||||
:param context:
|
||||
:param concept:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def _get_set_elements(sub_concept):
|
||||
if not self.isaset(context, sub_concept):
|
||||
return self.sheerka.new(BuiltinConcepts.NOT_A_SET, body=concept)
|
||||
|
||||
# first, try to see if sub_context has it's own group entry
|
||||
ids = self.sheerka.cache_manager.get(self.sheerka.CONCEPTS_GROUPS_ENTRY, sub_concept.id)
|
||||
concepts = self._get_concepts(context, ids, True)
|
||||
|
||||
# aggregate with en entries from its body
|
||||
sub_concept = core.builtin_helpers.ensure_evaluated(context, sub_concept)
|
||||
if not self.sheerka.is_success(sub_concept):
|
||||
return sub_concept
|
||||
|
||||
if self.isaset(context, sub_concept.body):
|
||||
other_concepts = _get_set_elements(sub_concept.body)
|
||||
if not self.sheerka.is_success(other_concepts):
|
||||
return other_concepts
|
||||
concepts.extend(other_concepts)
|
||||
|
||||
# apply the where clause if any
|
||||
if sub_concept.metadata.where:
|
||||
new_condition = self._validate_where_clause(sub_concept)
|
||||
if not new_condition:
|
||||
return self.sheerka.new(BuiltinConcepts.WHERE_CLAUSE_FAILED, body=sub_concept)
|
||||
|
||||
# This methods sucks, but I don't have enough tools (like proper AST manipulation functions)
|
||||
# to do it properly now. It will be enhanced later
|
||||
globals_ = {"xx__concepts__xx": concepts, "sheerka": self.sheerka}
|
||||
locals_ = {}
|
||||
exec(new_condition, globals_, locals_)
|
||||
concepts = locals_["result"]
|
||||
|
||||
return concepts
|
||||
|
||||
return _get_set_elements(concept)
|
||||
|
||||
def isinset(self, a, b):
|
||||
"""
|
||||
return true if the concept a is a b
|
||||
Will handle when the keyword isa will be implemented
|
||||
:param a:
|
||||
:param b:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(a, BuiltinConcepts): # common KSI error ;-)
|
||||
raise SyntaxError("Remember that the first parameter of isinstance MUST be a concept")
|
||||
|
||||
if not (isinstance(a, Concept) and isinstance(b, Concept)):
|
||||
return False
|
||||
|
||||
# TODO, first check the 'isa' property of a
|
||||
if not (a.id and b.id):
|
||||
return False
|
||||
|
||||
group_elements = self.sheerka.cache_manager.get(self.sheerka.CONCEPTS_GROUPS_ENTRY, b.id)
|
||||
return group_elements and a.id in group_elements
|
||||
|
||||
def isa(self, a, b):
|
||||
|
||||
if BuiltinConcepts.ISA not in a.metadata.props:
|
||||
return False
|
||||
|
||||
for c in a.metadata.props[BuiltinConcepts.ISA]:
|
||||
if c == b:
|
||||
return True
|
||||
if self.isa(c, b):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def isaset(self, context, concept):
|
||||
"""
|
||||
True if exists All_<concept_id> in sdp or if concept references to a concept that has all_<concept_id>
|
||||
:param context:
|
||||
:param concept:
|
||||
:return:
|
||||
"""
|
||||
""""""
|
||||
|
||||
if not (isinstance(concept, Concept) and concept.id):
|
||||
return False
|
||||
|
||||
# check if it has a group
|
||||
# TODO: use cache instead of directly requesting sdp
|
||||
if self.sheerka.cache_manager.get(self.sheerka.CONCEPTS_GROUPS_ENTRY, concept.id):
|
||||
return True
|
||||
|
||||
# it may be a concept that references a set
|
||||
concept = core.builtin_helpers.ensure_evaluated(context, concept)
|
||||
if not context.sheerka.is_success(concept):
|
||||
return False
|
||||
|
||||
return self.isaset(context, concept.body)
|
||||
|
||||
def _validate_where_clause(self, concept):
|
||||
python_parser_result = [r for r in concept.compiled[ConceptParts.WHERE] if r.who == "parsers.Python"]
|
||||
if not python_parser_result or not python_parser_result[0].status:
|
||||
return None
|
||||
|
||||
ast_ = python_parser_result[0].body.body.ast_
|
||||
ast_as_concepts = python_to_concept(ast_)
|
||||
names = core.builtin_helpers.get_names(self.sheerka, ast_as_concepts)
|
||||
if len(names) != 1 or names[0] != concept.metadata.body:
|
||||
return None
|
||||
|
||||
condition = concept.metadata.where.replace(concept.metadata.body, "sheerka.objvalue(x)")
|
||||
expression = f"""
|
||||
result=[]
|
||||
for x in xx__concepts__xx:
|
||||
try:
|
||||
if {condition}:
|
||||
result.append(x)
|
||||
except Exception:
|
||||
pass
|
||||
"""
|
||||
return expression
|
||||
|
||||
def _get_concepts(self, context, ids, evaluate):
|
||||
"""
|
||||
Gets concepts from a list of concepts ids
|
||||
:param ids:
|
||||
:param evaluate: if True, all the elements are evaluated before returned
|
||||
:return:
|
||||
"""
|
||||
|
||||
if not ids:
|
||||
return []
|
||||
|
||||
if not evaluate:
|
||||
return [self.sheerka.get_by_id(element_id) for element_id in ids]
|
||||
|
||||
result = []
|
||||
with context.push(desc=f"Evaluating concepts of a set") as sub_context:
|
||||
sub_context.add_inputs(ids=ids)
|
||||
sub_context.local_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
for element_id in ids:
|
||||
concept = self.sheerka.get_by_id(element_id)
|
||||
evaluated = self.sheerka.evaluate_concept(sub_context, concept)
|
||||
result.append(evaluated)
|
||||
sub_context.add_inputs(return_value=result)
|
||||
return result
|
||||
@@ -0,0 +1,47 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from core.sheerka.services.sheerka_service import ServiceObj
|
||||
|
||||
|
||||
@dataclass
|
||||
class Variable(ServiceObj):
|
||||
"""
|
||||
Variable to store
|
||||
"""
|
||||
who: str # who is the modifier
|
||||
key: str # key of the variable
|
||||
value: object # value
|
||||
parents: List[str] # previous references of the variable (Note that there should be only one parent)
|
||||
|
||||
def get_key(self):
|
||||
return f"{self.who}|{self.key}"
|
||||
|
||||
|
||||
class SheerkaVariableManager:
|
||||
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
|
||||
def record(self, context, who, key, value):
|
||||
"""
|
||||
|
||||
:param context:
|
||||
:param who: entity that owns the key (acts as a namespace)
|
||||
:param key:
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
|
||||
variable = Variable(context.event.get_digest(), who, key, value, None)
|
||||
self.sheerka.cache_manager.put(self.sheerka.VARIABLES_ENTRY, variable.get_key(), variable)
|
||||
|
||||
def load(self, who, key):
|
||||
variable = self.sheerka.cache_manager.get(self.sheerka.VARIABLES_ENTRY, who + "|" + key)
|
||||
if variable is None:
|
||||
return None
|
||||
|
||||
return variable.value
|
||||
|
||||
def delete(self, context, who, key):
|
||||
self.sheerka.cache_manager.delete(self.sheerka.VARIABLES_ENTRY, who + "|" + key)
|
||||
@@ -0,0 +1,14 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceObj:
|
||||
event_id: str # event where the object is created / modified
|
||||
|
||||
|
||||
class BaseService:
|
||||
"""
|
||||
Base class for services
|
||||
"""
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
Reference in New Issue
Block a user