Fixed #32 : concept groups are not correctly updated
Fixed #35 : Refactor test helper class (CNC, CC, CIO) Fixed #36 : Concept values are not used when declared with variable expression Fixed #37 : Objects in memory lose their values are restart Fixed #38 : func(a=b, c) (which is not allowed) raise an exception
This commit is contained in:
@@ -5,11 +5,11 @@ from cache.Cache import Cache
|
||||
from core.ast_helpers import ast_to_props
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept, ConceptParts, DEFINITION_TYPE_BNF, concept_part_value
|
||||
from core.global_symbols import NotInit, NotFound
|
||||
from core.global_symbols import NotInit, NotFound, CURRENT_OBJ
|
||||
from core.rule import Rule
|
||||
from core.utils import as_bag
|
||||
from parsers.BaseNodeParser import SourceCodeNode, ConceptNode, UnrecognizedTokensNode, SourceCodeWithConceptNode, \
|
||||
RuleNode
|
||||
RuleNode, VariableNode
|
||||
from parsers.BaseParser import ParsingError
|
||||
|
||||
PARSE_STEPS = [BuiltinConcepts.BEFORE_PARSING, BuiltinConcepts.PARSING, BuiltinConcepts.AFTER_PARSING]
|
||||
@@ -498,6 +498,18 @@ def get_lexer_nodes_from_unrecognized(context, unrecognized_tokens_node, parsers
|
||||
:return:
|
||||
"""
|
||||
|
||||
# first look into short term memory to see if the unrecognized is not a variable of the current object
|
||||
if (current_obj := context.sheerka.get_from_short_term_memory(context, CURRENT_OBJ)) is not NotFound:
|
||||
if isinstance(current_obj, Concept):
|
||||
source = unrecognized_tokens_node.source
|
||||
if source in current_obj.get_compiled() or source in current_obj.variables():
|
||||
return [[VariableNode(current_obj,
|
||||
source,
|
||||
unrecognized_tokens_node.start,
|
||||
unrecognized_tokens_node.end,
|
||||
unrecognized_tokens_node.tokens,
|
||||
unrecognized_tokens_node.source)]]
|
||||
|
||||
res = context.sheerka.parse_unrecognized(context, unrecognized_tokens_node.source, parsers)
|
||||
res = only_parsers_results(context, res)
|
||||
|
||||
|
||||
+22
-291
@@ -1,9 +1,7 @@
|
||||
import hashlib
|
||||
from collections import namedtuple
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
from threading import RLock
|
||||
from typing import Union
|
||||
|
||||
import core.utils
|
||||
from core.builtin_concepts_ids import BuiltinDynamicAttrs
|
||||
@@ -68,14 +66,21 @@ all_attributes_lock = RLock()
|
||||
|
||||
|
||||
def get_concept_attrs(concept):
|
||||
# look for instance attributes
|
||||
if concept.get_all_attributes() is not None:
|
||||
return concept.get_all_attributes()
|
||||
|
||||
# look for class attributes defined within the concept (for real classes that inherit from Concept)
|
||||
if concept.ALL_ATTRIBUTES is not None:
|
||||
return concept.ALL_ATTRIBUTES
|
||||
|
||||
try:
|
||||
# class attributes defined globally
|
||||
return ALL_ATTRIBUTES[concept.id]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# create a class attribute
|
||||
with all_attributes_lock:
|
||||
all_attributes = [k for k in concept.__dict__ if k[0] != "_" and k[0] != "#"]
|
||||
if concept.id and concept.key not in BuiltinDynamicAttrs:
|
||||
@@ -97,7 +102,8 @@ def copy_concepts_attrs():
|
||||
def load_concepts_attrs(attrs):
|
||||
global ALL_ATTRIBUTES
|
||||
with all_attributes_lock:
|
||||
ALL_ATTRIBUTES = attrs
|
||||
ALL_ATTRIBUTES.clear()
|
||||
ALL_ATTRIBUTES.update(attrs)
|
||||
|
||||
|
||||
class Concept:
|
||||
@@ -107,7 +113,7 @@ class Concept:
|
||||
Everything is a concept
|
||||
"""
|
||||
|
||||
ALL_ATTRIBUTES = None
|
||||
ALL_ATTRIBUTES = None # class attributes (only use when for Concept subclasses where it id is not yet defined)
|
||||
|
||||
def __init__(self, name=None,
|
||||
is_builtin=False,
|
||||
@@ -151,22 +157,16 @@ class Concept:
|
||||
self._original_definition_hash = None # concept hash before any alteration of the metadata
|
||||
self._format = None # how to print the concept
|
||||
self._hints = {} # extra processing information to help processing
|
||||
self._all_attributes = None # instance attributes
|
||||
|
||||
def __repr__(self):
|
||||
text = f"({self._metadata.id}){self._metadata.name}"
|
||||
return text + " (" + self._metadata.pre + ")" if self._metadata.pre else text
|
||||
|
||||
def __eq__(self, other):
|
||||
|
||||
if id(self) == id(other):
|
||||
return True
|
||||
|
||||
if isinstance(other, simplec):
|
||||
return self.name == other.name and self.body == other.body
|
||||
|
||||
if isinstance(other, (CC, CB, CV, CMV, CIO)):
|
||||
return other == self
|
||||
|
||||
if not isinstance(other, Concept):
|
||||
return False
|
||||
|
||||
@@ -208,14 +208,8 @@ class Concept:
|
||||
def __hash__(self):
|
||||
return hash(self._metadata.name)
|
||||
|
||||
# def __getattr__(self, item):
|
||||
# # I have this complicated implementation because of the usage of Pickle
|
||||
#
|
||||
# if 'values' in vars(self) and item in self.values:
|
||||
# return self.get_value(item)
|
||||
#
|
||||
# name = self.name if 'metadata' in vars(self) else 'Concept'
|
||||
# raise AttributeError(f"'{name}' concept has no attribute '{item}'")
|
||||
def get_all_attributes(self):
|
||||
return self._all_attributes
|
||||
|
||||
def def_var(self, var_name, default_value=None):
|
||||
"""
|
||||
@@ -466,6 +460,15 @@ class Concept:
|
||||
setattr(self, ConceptParts.BODY, value)
|
||||
elif self._bound_body and name == ConceptParts.BODY:
|
||||
setattr(self, self._bound_body, value)
|
||||
|
||||
# KSI 2021-03-04
|
||||
# I am not sure how cost efficient it is to check for new attribute everytime
|
||||
# Need to find a better way
|
||||
if name not in get_concept_attrs(self):
|
||||
if self._all_attributes is None:
|
||||
self._all_attributes = get_concept_attrs(self).copy()
|
||||
self._all_attributes.append(name)
|
||||
|
||||
except AttributeError:
|
||||
print(f"Cannot set {name}")
|
||||
return self
|
||||
@@ -607,275 +610,3 @@ class InfiniteRecursionResolved:
|
||||
|
||||
def get_obj_value(self):
|
||||
return self.value
|
||||
|
||||
|
||||
# ################################
|
||||
#
|
||||
# Class created for tests purpose
|
||||
#
|
||||
# ################################
|
||||
|
||||
|
||||
class CC:
|
||||
"""
|
||||
Concept class for test purpose
|
||||
CC means concept for compiled (or concept with compiled)
|
||||
It matches a concept if the compiles are equals
|
||||
"""
|
||||
|
||||
# The only properties that are testes are concept_key and compiled
|
||||
# The other properties (concept, source, start and end)
|
||||
# are used in tests/parsers/parsers_utils.py to help creating helper objects
|
||||
|
||||
def __init__(self, concept, source=None, exclude_body=False, **kwargs):
|
||||
self.concept_key = concept.key if isinstance(concept, Concept) else concept
|
||||
self.compiled = kwargs
|
||||
self.concept = concept if isinstance(concept, Concept) else None
|
||||
self.source = source # to use when the key is different from the sub str to search when filling start and stop
|
||||
self.start = None # for debug purpose, indicate where the concept starts
|
||||
self.end = None # for debug purpose, indicate where the concept ends
|
||||
self.exclude_body = exclude_body
|
||||
|
||||
if "body" in self.compiled:
|
||||
self.compiled[ConceptParts.BODY] = self.compiled["body"]
|
||||
del self.compiled["body"]
|
||||
|
||||
def __eq__(self, other):
|
||||
if id(self) == id(other):
|
||||
return True
|
||||
|
||||
if isinstance(other, Concept):
|
||||
if other.key != self.concept_key:
|
||||
return False
|
||||
if self.exclude_body:
|
||||
to_compare = {k: v for k, v in other.get_compiled().items() if k != ConceptParts.BODY}
|
||||
else:
|
||||
to_compare = other.get_compiled()
|
||||
if self.compiled == to_compare:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if not isinstance(other, CC):
|
||||
return False
|
||||
|
||||
if self.concept_key != other.concept_key:
|
||||
return False
|
||||
|
||||
return self.compiled == other.compiled
|
||||
|
||||
def __hash__(self):
|
||||
if self.concept:
|
||||
return hash(self.concept)
|
||||
return hash(self.concept_key)
|
||||
|
||||
def __repr__(self):
|
||||
if self.concept:
|
||||
txt = f"CC(concept='{self.concept}'"
|
||||
else:
|
||||
txt = f"CC(concept_key='{self.concept_key}'"
|
||||
|
||||
for k, v in self.compiled.items():
|
||||
txt += f", {k}='{v}'"
|
||||
return txt + ")"
|
||||
|
||||
def fix_pos(self, node):
|
||||
start = node.start if hasattr(node, "start") else \
|
||||
node[0] if isinstance(node, tuple) else None
|
||||
end = node.end if hasattr(node, "end") else \
|
||||
node[1] if isinstance(node, tuple) else None
|
||||
|
||||
if start is not None:
|
||||
if self.start is None or start < self.start:
|
||||
self.start = start
|
||||
|
||||
if end is not None:
|
||||
if self.end is None or end > self.end:
|
||||
self.end = end
|
||||
return self
|
||||
|
||||
def to_compare(self, other, to_compare_delegate):
|
||||
"""
|
||||
Transform other into CNC, to ease the comparison
|
||||
:param other:
|
||||
:param to_compare_delegate:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(other, CC):
|
||||
return other
|
||||
|
||||
if isinstance(other, Concept):
|
||||
if self.exclude_body:
|
||||
compiled = {k: v for k, v in other.get_compiled().items() if k != ConceptParts.BODY}
|
||||
else:
|
||||
compiled = other.get_compiled()
|
||||
|
||||
self_compile_to_use = self.compiled or compiled
|
||||
|
||||
compiled = to_compare_delegate(self_compile_to_use, compiled, to_compare_delegate)
|
||||
return CC(other,
|
||||
self.source,
|
||||
self.exclude_body,
|
||||
**compiled)
|
||||
|
||||
raise NotImplementedError(f"CC, {other=}")
|
||||
|
||||
|
||||
@dataclass()
|
||||
class CB:
|
||||
"""
|
||||
Concept with body only
|
||||
Test class that tests only the body of the concept
|
||||
"""
|
||||
concept: Union[str, Concept]
|
||||
body: object
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Concept):
|
||||
key = self.concept if isinstance(self.concept, str) else self.concept.key
|
||||
return key == other.key and self.body == other.body
|
||||
|
||||
if not isinstance(other, CB):
|
||||
return False
|
||||
|
||||
return self.concept == other.concept and self.body == other.body
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.concept, self.body))
|
||||
|
||||
def __repr__(self):
|
||||
return f"CB({self.body})"
|
||||
|
||||
|
||||
class CV:
|
||||
"""
|
||||
Concept with all values
|
||||
Test class that tests all the values (not the metadata, so not the properties) of a concept
|
||||
"""
|
||||
|
||||
def __init__(self, concept, **kwargs):
|
||||
self.concept_key = concept.key if isinstance(concept, Concept) else concept
|
||||
self.concept = concept if isinstance(concept, Concept) else None
|
||||
self.values = {}
|
||||
for k, v in kwargs.items():
|
||||
if f"#{k}#" in AllConceptParts:
|
||||
self.values[f"#{k}#"] = v
|
||||
else:
|
||||
self.values[k] = v
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Concept):
|
||||
if self.concept_key != other.key:
|
||||
return False
|
||||
for k, v in self.values.items():
|
||||
if self.values[k] != other.get_value(k):
|
||||
return False
|
||||
return True
|
||||
|
||||
if not isinstance(other, CV):
|
||||
return False
|
||||
|
||||
return self.concept_key == other.concept_key and self.values == other.values
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.concept_key, self.values))
|
||||
|
||||
def __repr__(self):
|
||||
return f"CV(key={self.concept_key}, values={self.values})"
|
||||
|
||||
|
||||
class CMV:
|
||||
"""
|
||||
Concept with metadata variables
|
||||
CMV stands for Concept Metadata Variables
|
||||
Test class that only compare the key and the metadata variables
|
||||
"""
|
||||
|
||||
def __init__(self, concept, **kwargs):
|
||||
self.concept_key = concept.key if isinstance(concept, Concept) else concept
|
||||
self.concept = concept if isinstance(concept, Concept) else None
|
||||
self.variables = kwargs
|
||||
|
||||
def __eq__(self, other):
|
||||
if id(self) == id(other):
|
||||
return True
|
||||
|
||||
if isinstance(other, Concept):
|
||||
if other.key != self.concept_key:
|
||||
return False
|
||||
|
||||
if len(other._metadata.variables) != len(self.variables):
|
||||
return False
|
||||
|
||||
for name, value in other._metadata.variables:
|
||||
if self.variables[name] != value:
|
||||
return False
|
||||
return True
|
||||
|
||||
if not isinstance(other, CMV):
|
||||
return False
|
||||
|
||||
if self.concept_key != other.concept_key:
|
||||
return False
|
||||
|
||||
return self.variables == other.variables
|
||||
|
||||
def __hash__(self):
|
||||
if self.concept:
|
||||
return hash(self.concept)
|
||||
return hash(self.concept_key)
|
||||
|
||||
def __repr__(self):
|
||||
if self.concept:
|
||||
txt = f"CMV(concept='{self.concept}'"
|
||||
else:
|
||||
txt = f"CMV(concept_key='{self.concept_key}'"
|
||||
|
||||
for k, v in self.variables.items():
|
||||
txt += f", {k}='{v}'"
|
||||
return txt + ")"
|
||||
|
||||
|
||||
class CIO:
|
||||
"""
|
||||
Concept id only
|
||||
only test the id
|
||||
"""
|
||||
|
||||
def __init__(self, concept, source=None):
|
||||
if isinstance(concept, str):
|
||||
self.concept_name = concept
|
||||
self.concept_id = None
|
||||
self.concept = None
|
||||
elif isinstance(concept, Concept):
|
||||
self.concept_id = concept.id
|
||||
self.concept = concept
|
||||
self.source = source
|
||||
self.start = None
|
||||
self.end = None
|
||||
|
||||
def set_concept(self, concept):
|
||||
self.concept = concept
|
||||
self.concept_id = concept.id
|
||||
|
||||
def __eq__(self, other):
|
||||
if id(self) == id(other):
|
||||
return True
|
||||
|
||||
if isinstance(other, Concept):
|
||||
return self.concept_id == other.id
|
||||
|
||||
if not isinstance(other, CIO):
|
||||
return False
|
||||
|
||||
return self.concept_id == other.concept_id
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.concept_id)
|
||||
|
||||
def __repr__(self):
|
||||
return f"CIO(concept='{self.concept}')" if self.concept else f"CIO(name='{self.concept_name}')"
|
||||
|
||||
|
||||
simplec = namedtuple("concept", "name body") # for simple concept (tests purposes only)
|
||||
|
||||
@@ -31,6 +31,9 @@ class CustomType:
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, CustomType) and self.value == other.value
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.value)
|
||||
|
||||
|
||||
class NotInitType(CustomType):
|
||||
def __init__(self):
|
||||
@@ -63,3 +66,6 @@ class ErrorObj:
|
||||
To indicate that somehow, the underlying object is (or has) an error
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
CURRENT_OBJ = "__obj"
|
||||
|
||||
@@ -35,13 +35,15 @@ class SheerkaAdmin(BaseService):
|
||||
self.sheerka.bind_service_method(self.ontologies, False)
|
||||
self.sheerka.bind_service_method(self.in_memory, False)
|
||||
self.sheerka.bind_service_method(self.admin_history, False, as_name="history")
|
||||
self.sheerka.bind_service_method(self.admin_history, False, as_name="history")
|
||||
self.sheerka.bind_service_method(self.sdp, False)
|
||||
|
||||
def caches_names(self):
|
||||
"""
|
||||
Returns the name of all the caches
|
||||
:return:
|
||||
"""
|
||||
return list(self.sheerka.om.current_cache_manager().caches.keys())
|
||||
return self.sheerka.new(BuiltinConcepts.TO_LIST, body=self.sheerka.om.current_cache_manager().caches.keys())
|
||||
|
||||
def cache(self, name, *keys):
|
||||
"""
|
||||
@@ -58,6 +60,16 @@ class SheerkaAdmin(BaseService):
|
||||
|
||||
return {key: self.sheerka.om.get(name, key) for key in keys}
|
||||
|
||||
def sdp(self, name=None):
|
||||
if name:
|
||||
for ontology in self.sheerka.om.ontologies:
|
||||
if ontology.name == name:
|
||||
return ontology.cache_manager.sdp
|
||||
|
||||
return self.sheerka.err(self.sheerka.new(BuiltinConcepts.NOT_FOUND, {"sdp_name", name}))
|
||||
|
||||
return self.sheerka.om.current_sdp()
|
||||
|
||||
def restore(self, concept_file=CONCEPTS_FILE_TO_USE):
|
||||
"""
|
||||
Restore the state with all previous valid concept definitions
|
||||
|
||||
@@ -237,36 +237,22 @@ class SheerkaConceptManager(BaseService):
|
||||
except Exception as ex:
|
||||
return sheerka.ret(self.NAME, False, ex.args[0])
|
||||
|
||||
# compute first token and/or first regex
|
||||
init_ret_value = self.compute_concepts_by_first_item(context, [concept], True)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
|
||||
by_first_keyword, by_first_regex = init_ret_value.body
|
||||
|
||||
# computes resolved concepts_by_first_keyword
|
||||
init_ret_value = self.resolve_concepts_by_first_keyword(context, by_first_keyword)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
|
||||
resolved_concepts_by_first_keyword = init_ret_value.body
|
||||
|
||||
# compile regex
|
||||
compile_ret = self.compile_concepts_by_first_regex(context, by_first_regex)
|
||||
if not compile_ret.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(compile_ret.value))
|
||||
compiled_concepts_by_first_regex = compile_ret.body
|
||||
# recompute concepts by first tokens and concept by first regex
|
||||
update_items_res = self.recompute_first_items(context, None, [concept])
|
||||
if not update_items_res.status:
|
||||
return update_items_res
|
||||
by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex = update_items_res.body
|
||||
|
||||
# if everything is fine
|
||||
freeze_concept_attrs(concept)
|
||||
concept.freeze_definition_hash()
|
||||
|
||||
om.add_concept(concept)
|
||||
om.put(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, by_first_keyword)
|
||||
om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
|
||||
om.put(self.CONCEPTS_BY_REGEX_ENTRY, False, {k.serialize(): v for k, v in by_first_regex.items()})
|
||||
|
||||
# update the compiled regex
|
||||
self.compiled_concepts_by_regex.clear()
|
||||
self.compiled_concepts_by_regex.extend(compiled_concepts_by_first_regex)
|
||||
self.update_first_items_caches(context,
|
||||
by_first_keyword,
|
||||
by_first_regex,
|
||||
resolved_by_first_keyword,
|
||||
compiled_by_first_regex)
|
||||
|
||||
if concept.get_metadata().definition_type == DEFINITION_TYPE_DEF and concept.get_metadata().definition != concept.name:
|
||||
# allow search by definition when definition relevant
|
||||
@@ -284,9 +270,7 @@ class SheerkaConceptManager(BaseService):
|
||||
# publish the new concept
|
||||
sheerka.publish(context, EVENT_CONCEPT_CREATED, concept)
|
||||
|
||||
# process the return if needed
|
||||
ret = sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
|
||||
return ret
|
||||
return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
|
||||
|
||||
def modify_concept(self, context, concept, to_add=None, to_remove=None, modify_source=False):
|
||||
"""
|
||||
@@ -324,40 +308,17 @@ class SheerkaConceptManager(BaseService):
|
||||
|
||||
# modify the metadata. Almost all ConceptMetadata attributes except variables and props
|
||||
new_concept = sheerka.new_from_template(concept, concept.key) # reload from cache or database ?
|
||||
|
||||
res = self._update_concept(context, new_concept, to_add, to_remove)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
# To update concept by first keyword and first regex
|
||||
# first remove old first token and first regex entries
|
||||
concepts_by_first_keyword, concepts_by_regex = self._remove_concept_first_token_and_first_regex(concept)
|
||||
# recompute concepts by first tokens and concept by first regex
|
||||
update_items_res = self.recompute_first_items(context, concept, [new_concept])
|
||||
if not update_items_res.status:
|
||||
return update_items_res
|
||||
by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex = update_items_res.body
|
||||
|
||||
# and then update
|
||||
init_ret_value = self.compute_concepts_by_first_item(context,
|
||||
[new_concept],
|
||||
False,
|
||||
concepts_by_first_keyword,
|
||||
concepts_by_regex)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
|
||||
concepts_by_first_keyword, concepts_by_regex = init_ret_value.body
|
||||
|
||||
# computes resolved concepts_by_first_keyword
|
||||
init_ret_value = self.resolve_concepts_by_first_keyword(context,
|
||||
concepts_by_first_keyword,
|
||||
{new_concept.id: new_concept})
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
|
||||
resolved_concepts_by_first_keyword = init_ret_value.body
|
||||
|
||||
# compile new regex
|
||||
compile_ret = self.compile_concepts_by_first_regex(context, concepts_by_regex)
|
||||
if not compile_ret.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(compile_ret.value))
|
||||
compiled_concepts_by_first_regex = compile_ret.body
|
||||
|
||||
# update concept that referenced the old concept and clear old references
|
||||
# update concepts that referenced the old concept and clear old references
|
||||
self.update_references(context, concept, new_concept, to_add)
|
||||
for ref in self.compute_references(concept):
|
||||
om.delete(self.CONCEPTS_REFERENCES_ENTRY, ref, concept.id)
|
||||
@@ -368,13 +329,11 @@ class SheerkaConceptManager(BaseService):
|
||||
|
||||
# everything is ok, update the caches
|
||||
om.update_concept(concept, new_concept)
|
||||
om.put(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, concepts_by_first_keyword)
|
||||
om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
|
||||
om.put(self.CONCEPTS_BY_REGEX_ENTRY, False, {k.serialize(): v for k, v in concepts_by_regex.items()})
|
||||
|
||||
# update the compiled regex
|
||||
self.compiled_concepts_by_regex.clear()
|
||||
self.compiled_concepts_by_regex.extend(compiled_concepts_by_first_regex)
|
||||
self.update_first_items_caches(context,
|
||||
by_first_keyword,
|
||||
by_first_regex,
|
||||
resolved_by_first_keyword,
|
||||
compiled_by_first_regex)
|
||||
|
||||
# everything seems to be fine. Update the list of attributes
|
||||
# Caution. Must be done AFTER update_concept()
|
||||
@@ -386,8 +345,7 @@ class SheerkaConceptManager(BaseService):
|
||||
self._update_concept(context, concept, to_add, to_remove)
|
||||
|
||||
# KSI 2021-02-16 publish the modification of the concept only when someone needs it
|
||||
ret = sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=new_concept))
|
||||
return ret
|
||||
return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=new_concept))
|
||||
|
||||
def remove_concept(self, context, concept):
|
||||
"""
|
||||
@@ -410,30 +368,19 @@ class SheerkaConceptManager(BaseService):
|
||||
refs_instances = [sheerka.new_from_template(c, c.key) for c in [self.get_by_id(ref) for ref in refs]]
|
||||
return sheerka.ret(self.NAME, False, sheerka.err(ConceptIsReferenced(refs_instances)))
|
||||
|
||||
concepts_by_first_keyword, concepts_by_regex = self._remove_concept_first_token_and_first_regex(concept)
|
||||
|
||||
# computes resolved concepts_by_first_keyword
|
||||
init_ret_value = self.resolve_concepts_by_first_keyword(context, concepts_by_first_keyword)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
|
||||
resolved_concepts_by_first_keyword = init_ret_value.body
|
||||
|
||||
# compile new regex
|
||||
compile_ret = self.compile_concepts_by_first_regex(context, concepts_by_regex)
|
||||
if not compile_ret.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(compile_ret.value))
|
||||
compiled_concepts_by_first_regex = compile_ret.body
|
||||
# recompute concepts by first tokens and concept by first regex
|
||||
update_items_res = self.recompute_first_items(context, concept, None)
|
||||
if not update_items_res.status:
|
||||
return update_items_res
|
||||
by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex = update_items_res.body
|
||||
|
||||
# everything seems fine. I can commit the modification and remove
|
||||
om.remove_concept(concept)
|
||||
|
||||
om.put(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, concepts_by_first_keyword)
|
||||
om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
|
||||
om.put(self.CONCEPTS_BY_REGEX_ENTRY, False, {k.serialize(): v for k, v in concepts_by_regex.items()})
|
||||
|
||||
# update the compiled regex
|
||||
self.compiled_concepts_by_regex.clear()
|
||||
self.compiled_concepts_by_regex.extend(compiled_concepts_by_first_regex)
|
||||
self.update_first_items_caches(context,
|
||||
by_first_keyword,
|
||||
by_first_regex,
|
||||
resolved_by_first_keyword,
|
||||
compiled_by_first_regex)
|
||||
|
||||
sheerka.publish(context, EVENT_CONCEPT_DELETED, concept)
|
||||
return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.SUCCESS))
|
||||
@@ -568,9 +515,9 @@ class SheerkaConceptManager(BaseService):
|
||||
"""
|
||||
Updates all the concepts that reference concept
|
||||
:param context:
|
||||
:param concept:
|
||||
:param modified_concept:
|
||||
:param modifications:
|
||||
:param concept: Old version of the concept
|
||||
:param modified_concept: new version of the concept
|
||||
:param modifications: what are the modification
|
||||
:return:
|
||||
"""
|
||||
|
||||
@@ -582,11 +529,12 @@ class SheerkaConceptManager(BaseService):
|
||||
# remove the grammar entry so that it can be recreated
|
||||
self.sheerka.om.delete(self.CONCEPTS_BNF_DEFINITIONS_ENTRY, concept_id)
|
||||
|
||||
to_update = self.get_by_id(concept_id)
|
||||
metadata = to_update.get_metadata()
|
||||
|
||||
# reset the bnf definition if needed
|
||||
if modified_concept:
|
||||
if self.has_id(concept_id):
|
||||
to_update = self.get_by_id(concept_id)
|
||||
metadata = to_update.get_metadata()
|
||||
if self.has_id(concept_id): # reset only it the bnf definition is in cache
|
||||
if metadata.definition_type == DEFINITION_TYPE_BNF and self._name_has_changed(modifications):
|
||||
tokens = list(Tokenizer(metadata.definition))
|
||||
modified = False
|
||||
@@ -601,6 +549,19 @@ class SheerkaConceptManager(BaseService):
|
||||
to_update.get_metadata().definition = core.utils.get_text_from_tokens(tokens)
|
||||
to_update.set_bnf(None)
|
||||
|
||||
# update concept_by_first_token
|
||||
if metadata.definition_type == DEFINITION_TYPE_BNF:
|
||||
# recompute concepts by first tokens and concept by first regex
|
||||
res = self.recompute_first_items(context, concept, [concept])
|
||||
if not res.status:
|
||||
return res
|
||||
by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex = res.body
|
||||
self.update_first_items_caches(context,
|
||||
by_first_keyword,
|
||||
by_first_regex,
|
||||
resolved_by_first_keyword,
|
||||
compiled_by_first_regex)
|
||||
|
||||
def compute_references(self, concept):
|
||||
"""
|
||||
We need to keep a track of all concepts used by the current concept
|
||||
@@ -723,6 +684,8 @@ class SheerkaConceptManager(BaseService):
|
||||
else:
|
||||
return sheerka.ret(self.NAME, False, sheerka.err(UnknownAttribute(k)))
|
||||
core.utils.remove_list_from_list(concept.get_metadata().variables, variables_to_remove)
|
||||
if concept.get_all_attributes():
|
||||
core.utils.remove_list_from_list(concept.get_all_attributes(), [v[0] for v in variables_to_remove])
|
||||
|
||||
concept.get_metadata().key = None
|
||||
if self._definition_has_changed(to_add) and concept.get_metadata().definition_type == DEFINITION_TYPE_BNF:
|
||||
@@ -1046,3 +1009,78 @@ class SheerkaConceptManager(BaseService):
|
||||
|
||||
def get_concepts_bnf_definitions(self):
|
||||
return self.sheerka.om.current_cache_manager().caches[self.CONCEPTS_BNF_DEFINITIONS_ENTRY].cache
|
||||
|
||||
def recompute_first_items(self, context, old_concept, new_concepts):
|
||||
"""
|
||||
Recompute
|
||||
concepts fy first items
|
||||
resolved concept by first items
|
||||
concepts by first regex
|
||||
compiled concepts by first regex
|
||||
|
||||
Do not update anything
|
||||
:param context:
|
||||
:param old_concept:
|
||||
:param new_concepts:
|
||||
:return:
|
||||
"""
|
||||
sheerka = context.sheerka
|
||||
|
||||
if old_concept and not new_concepts:
|
||||
# remove
|
||||
modified_concepts = None
|
||||
by_first_keyword, by_first_regex = self._remove_concept_first_token_and_first_regex(old_concept)
|
||||
|
||||
elif old_concept and new_concepts:
|
||||
# remove
|
||||
by_first_keyword, by_first_regex = self._remove_concept_first_token_and_first_regex(old_concept)
|
||||
|
||||
# and then update
|
||||
init_ret_value = self.compute_concepts_by_first_item(context,
|
||||
new_concepts,
|
||||
False,
|
||||
by_first_keyword,
|
||||
by_first_regex)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
|
||||
by_first_keyword, by_first_regex = init_ret_value.body
|
||||
modified_concepts = {new_concept.id: new_concept for new_concept in new_concepts}
|
||||
|
||||
elif not old_concept and new_concepts:
|
||||
# only update
|
||||
modified_concepts = None
|
||||
init_ret_value = self.compute_concepts_by_first_item(context, new_concepts, True)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
|
||||
by_first_keyword, by_first_regex = init_ret_value.body
|
||||
|
||||
# computes resolved concepts_by_first_keyword
|
||||
init_ret_value = self.resolve_concepts_by_first_keyword(context, by_first_keyword, modified_concepts)
|
||||
if not init_ret_value.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
|
||||
resolved_by_first_keyword = init_ret_value.body
|
||||
|
||||
# compile new regex
|
||||
compile_ret = self.compile_concepts_by_first_regex(context, by_first_regex)
|
||||
if not compile_ret.status:
|
||||
return sheerka.ret(self.NAME, False, ErrorConcept(compile_ret.value))
|
||||
compiled_by_first_regex = compile_ret.body
|
||||
|
||||
return sheerka.ret(self.NAME,
|
||||
True,
|
||||
(by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex))
|
||||
|
||||
def update_first_items_caches(self,
|
||||
context,
|
||||
by_first_keyword,
|
||||
by_first_regex,
|
||||
resolved_by_first_keyword,
|
||||
compiled_by_first_regex):
|
||||
om = context.sheerka.om
|
||||
om.put(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, by_first_keyword)
|
||||
om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_by_first_keyword)
|
||||
om.put(self.CONCEPTS_BY_REGEX_ENTRY, False, {k.serialize(): v for k, v in by_first_regex.items()})
|
||||
|
||||
# update the compiled regex
|
||||
self.compiled_concepts_by_regex.clear()
|
||||
self.compiled_concepts_by_regex.extend(compiled_by_first_regex)
|
||||
|
||||
@@ -4,7 +4,7 @@ from core.builtin_concepts import BuiltinConcepts
|
||||
from core.builtin_helpers import expect_one, only_successful, evaluate, ensure_concept
|
||||
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved, AllConceptParts, \
|
||||
concept_part_value
|
||||
from core.global_symbols import NotInit
|
||||
from core.global_symbols import NotInit, CURRENT_OBJ
|
||||
from core.rule import Rule
|
||||
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager
|
||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||
@@ -535,6 +535,8 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
if context.sheerka.isa(concept, context.sheerka.new(BuiltinConcepts.AUTO_EVAL)):
|
||||
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
|
||||
sub_context.add_to_short_term_memory(CURRENT_OBJ, concept)
|
||||
|
||||
try:
|
||||
self.initialize_concept_asts(sub_context, concept)
|
||||
except ChickenAndEggException as ex:
|
||||
|
||||
@@ -280,17 +280,16 @@ for x in xx__concepts__xx:
|
||||
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
errors = []
|
||||
for element_id in ids:
|
||||
concept = self.sheerka.get_by_id(element_id)
|
||||
if len(concept.get_metadata().variables) == 0:
|
||||
# The concepts are directly taken from Sheerka.get_by_id, so variable cannot be filled
|
||||
# It's the reason why we only evaluate concept with no variable
|
||||
evaluated = self.sheerka.evaluate_concept(sub_context, concept)
|
||||
if context.sheerka.is_success(evaluated):
|
||||
result.append(evaluated)
|
||||
concept = self.sheerka.fast_resolve((None, element_id))
|
||||
if concept:
|
||||
if len(concept.get_metadata().variables) == 0:
|
||||
evaluated = self.sheerka.evaluate_concept(sub_context, concept)
|
||||
if context.sheerka.is_success(evaluated):
|
||||
result.append(evaluated)
|
||||
else:
|
||||
errors.append(evaluated)
|
||||
else:
|
||||
errors.append(evaluated)
|
||||
else:
|
||||
result.append(concept)
|
||||
result.append(concept)
|
||||
sub_context.add_values(return_value=result)
|
||||
sub_context.add_values(errors=errors)
|
||||
return result
|
||||
|
||||
@@ -12,6 +12,15 @@ from core.sheerka.services.sheerka_service import BaseService, ServiceObj
|
||||
class MemoryObject(ServiceObj):
|
||||
obj: object
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, MemoryObject):
|
||||
return False
|
||||
|
||||
return self.obj == other.obj and self.event_id == other.event_id
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.event_id, self.obj))
|
||||
|
||||
|
||||
class SheerkaMemory(BaseService):
|
||||
NAME = "Memory"
|
||||
@@ -26,6 +35,7 @@ class SheerkaMemory(BaseService):
|
||||
|
||||
def initialize(self):
|
||||
self.sheerka.bind_service_method(self.get_from_short_term_memory, False, visible=False)
|
||||
self.sheerka.bind_service_method(self.get_all_short_term_memory, False, visible=False)
|
||||
self.sheerka.bind_service_method(self.add_to_short_term_memory, True, visible=False)
|
||||
self.sheerka.bind_service_method(self.remove_context, True, as_name="clear_short_term_memory", visible=False)
|
||||
self.sheerka.bind_service_method(self.add_to_memory, True, visible=False)
|
||||
@@ -62,8 +72,26 @@ class SheerkaMemory(BaseService):
|
||||
|
||||
context = context.get_parent()
|
||||
|
||||
def get_all_short_term_memory(self, context):
|
||||
return self.short_term_objects.get(context.id)
|
||||
def get_all_short_term_memory(self, context, recursive=False):
|
||||
id_to_use = context.id if context else self.GLOBAL
|
||||
|
||||
if not recursive:
|
||||
return self.short_term_objects.get(id_to_use)
|
||||
|
||||
all_vars = {}
|
||||
while True:
|
||||
try:
|
||||
all_vars.update(self.short_term_objects.cache[id_to_use])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if id_to_use == self.GLOBAL:
|
||||
break
|
||||
else:
|
||||
context = context.get_parent()
|
||||
id_to_use = context.id if context else self.GLOBAL
|
||||
|
||||
return all_vars
|
||||
|
||||
def add_to_short_term_memory(self, context, key, value):
|
||||
if context:
|
||||
@@ -99,12 +127,12 @@ class SheerkaMemory(BaseService):
|
||||
if last is NotFound:
|
||||
self.sheerka.om.put(SheerkaMemory.OBJECTS_ENTRY, key, MemoryObject(context.event.get_digest(), concept))
|
||||
return
|
||||
|
||||
|
||||
if not isinstance(last, list) and last.obj == concept:
|
||||
self.sheerka.om.delete(SheerkaMemory.OBJECTS_ENTRY, key, last)
|
||||
self.sheerka.om.put(SheerkaMemory.OBJECTS_ENTRY, key, MemoryObject(context.event.get_digest(), concept))
|
||||
return
|
||||
|
||||
|
||||
if isinstance(last, list) and last[-1].obj == concept:
|
||||
self.sheerka.om.delete(SheerkaMemory.OBJECTS_ENTRY, key, last[-1])
|
||||
self.sheerka.om.put(SheerkaMemory.OBJECTS_ENTRY, key, MemoryObject(context.event.get_digest(), concept))
|
||||
|
||||
+1
-1
@@ -444,7 +444,7 @@ def str_concept(t, drop_name=None, prefix="c:"):
|
||||
elif prefix == "r:":
|
||||
name, id_ = t.metadata.name, t.id
|
||||
else:
|
||||
name, id_ = t.key, t.id
|
||||
name, id_ = t.key or t.name, t.id
|
||||
|
||||
if name is None and id_ is None:
|
||||
return ""
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from core.concept import Concept
|
||||
from core.rule import Rule
|
||||
|
||||
|
||||
class VariableRef:
|
||||
"""
|
||||
Represents the reference to the property/attribute of an object
|
||||
"""
|
||||
|
||||
def __init__(self, obj, prop):
|
||||
self.obj = obj
|
||||
self.prop = prop
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, VariableRef):
|
||||
return False
|
||||
|
||||
return self.obj == other.obj and self.prop == other.prop
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.obj, self.prop))
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self.obj.key or self.obj.name if isinstance(self.obj, (Concept, Rule)) else type(self.obj).__name__
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
if self.prop is None:
|
||||
return ""
|
||||
|
||||
return self.prop
|
||||
Reference in New Issue
Block a user