We can now use concept sets in BNF definitions

This commit is contained in:
2020-01-19 21:48:43 +01:00
parent a7b239c167
commit 821614a6c4
16 changed files with 643 additions and 93 deletions
+14
View File
@@ -29,6 +29,7 @@ class BuiltinConcepts(Enum):
SUCCESS = "success"
ERROR = "error"
UNKNOWN_CONCEPT = "unknown concept" # the request concept is not recognized
CANNOT_RESOLVE_CONCEPT = "cannot resolve concept" # when too many concepts with the same name
RETURN_VALUE = "return value" # a value is returned
CONCEPT_TOO_LONG = "concept too long" # concept cannot be processed by exactConcept parser
NEW_CONCEPT = "new concept" # when a new concept is added
@@ -48,6 +49,7 @@ class BuiltinConcepts(Enum):
EVALUATOR_PRE_PROCESS = "evaluator pre process" # used modify / tweak behaviour of evaluators
CONCEPT_EVAL_REQUESTED = "concept eval requested"
REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible
NOT_A_SET = "not a set" # the concept has no entry in sets
NODE = "node"
GENERIC_NODE = "generic node"
@@ -79,6 +81,7 @@ BuiltinUnique = [
BuiltinErrors = [str(e) for e in {
BuiltinConcepts.ERROR,
BuiltinConcepts.UNKNOWN_CONCEPT,
BuiltinConcepts.CANNOT_RESOLVE_CONCEPT,
BuiltinConcepts.CONCEPT_TOO_LONG,
BuiltinConcepts.UNKNOWN_PROPERTY,
BuiltinConcepts.TOO_MANY_SUCCESS,
@@ -87,6 +90,7 @@ BuiltinErrors = [str(e) for e in {
BuiltinConcepts.CONCEPT_ALREADY_DEFINED,
BuiltinConcepts.CONCEPT_EVAL_ERROR,
BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
BuiltinConcepts.NOT_A_SET,
}]
"""
@@ -124,6 +128,16 @@ class ErrorConcept(Concept):
return f"({self.id}){self.name}: {self.body}"
class UnknownConcept(Concept):
def __init__(self, metadata=None):
super().__init__(BuiltinConcepts.UNKNOWN_CONCEPT, True, False, BuiltinConcepts.UNKNOWN_CONCEPT)
self.set_metadata_value(ConceptParts.BODY, metadata)
self.metadata.is_evaluated = True
def __repr__(self):
return f"({self.id}){self.name}: {self.body}"
class ReturnValueConcept(Concept):
"""
This class represents the result of a data flow processing
+122 -23
View File
@@ -1,4 +1,5 @@
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors, BuiltinUnique
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors, BuiltinUnique, \
UnknownConcept
from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW, DoNotResolve
from parsers.BaseParser import BaseParser
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event, SheerkaDataProviderDuplicateKeyError
@@ -17,6 +18,7 @@ CONCEPT_EVALUATION_STEPS = [
CONCEPT_LEXER_PARSER_CLASS = "parsers.ConceptLexerParser.ConceptLexerParser"
DEBUG_TAB_SIZE = 4
GROUP_PREFIX = 'All_'
class Sheerka(Concept):
@@ -25,6 +27,7 @@ class Sheerka(Concept):
"""
CONCEPTS_ENTRY = "All_Concepts" # to store all the concepts
CONCEPTS_BY_ID_ENTRY = "Concepts_By_ID"
CONCEPTS_DEFINITIONS_ENTRY = "Concepts_Definitions" # to store definitions (bnf) of concepts
BUILTIN_CONCEPTS_KEYS = "Builtins_Concepts" # sequential key for builtin concepts
USER_CONCEPTS_KEYS = "User_Concepts" # sequential key for user defined concepts
@@ -40,7 +43,8 @@ class Sheerka(Concept):
# They are used as a footprint for instantiation
# Except of source when the concept is supposed to be unique
# key is the key of the concept (not the name or the id)
self.concepts_cache = {}
self.cache_by_key = {}
self.cache_by_id = {}
# cache for concept definitions,
# Primarily used for unit test that does not have access to sdp
@@ -176,6 +180,20 @@ class Sheerka(Concept):
self.concepts_grammars = lexer_parser.concepts_grammars
def reset_cache(self, filter_to_use=None):
"""
reset the different cache that exists
:param filter_to_use:
:return:
"""
if filter_to_use is None:
self.cache_by_key = {}
self.cache_by_id = {}
else:
raise NotImplementedError()
return self
def evaluate_user_input(self, text: str, user_name="kodjo"):
"""
Note to KSI: If you try to add execution context to this function,
@@ -455,6 +473,7 @@ class Sheerka(Concept):
def set_id_if_needed(self, obj: Concept, is_builtin: bool):
"""
Set the key for the concept if needed
For test purpose only !!!!!
:param obj:
:param is_builtin:
:return:
@@ -509,7 +528,12 @@ class Sheerka(Concept):
# save the new concept in sdp
try:
# TODO : needs to make these calls atomic (or at least one single call)
self.sdp.add(context.event.get_digest(), self.CONCEPTS_ENTRY, concept, use_ref=True)
self.sdp.add(context.event.get_digest(),
self.CONCEPTS_BY_ID_ENTRY,
{concept.id: concept.get_digest()},
is_ref=True)
if concepts_definitions is not None:
self.sdp.set(context.event.get_digest(),
self.CONCEPTS_DEFINITIONS_ENTRY,
@@ -523,7 +547,8 @@ class Sheerka(Concept):
error.args[0])
# Updates the caches
self.concepts_cache[concept.key] = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.key)
self.cache_by_key[concept.key] = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.key) # reset from sdp
self.cache_by_id[concept.id] = concept # no need to reset
if init_ret_value is not None and init_ret_value.status:
self.concepts_grammars = init_ret_value.body
@@ -548,7 +573,7 @@ class Sheerka(Concept):
assert concept_set.id
try:
ret = self.sdp.add_unique(context.event.get_digest(), "All_" + str(concept_set.id), concept.id)
ret = self.sdp.add_unique(context.event.get_digest(), GROUP_PREFIX + concept_set.id, concept.id)
if ret == (None, None): # concept already in set
return self.ret(
self.add_concept_to_set.__name__,
@@ -560,6 +585,23 @@ class Sheerka(Concept):
context.log_error(logger, "Failed to add to set.", who=self.add_concept_to_set.__name__)
return self.ret(self.create_new_concept.__name__, False, ErrorConcept(error), error.args[0])
def get_set_elements(self, concept):
"""
Concept is supposed to be a set
Returns all elements if the set
:param concept:
:return:
"""
assert concept.id
ids = self.sdp.get_safe(GROUP_PREFIX + concept.id)
if ids is None:
return self.new(BuiltinConcepts.NOT_A_SET, body=concept)
elements = [self.get_by_id(element_id) for element_id in ids]
return elements
def initialize_concept_asts(self, context, concept: Concept, logger=None):
"""
Updates the codes of the newly created concept
@@ -608,13 +650,13 @@ class Sheerka(Concept):
sub_context.add_values(return_values=res)
# Updates the cache of concepts when possible
if concept.key in self.concepts_cache:
entry = self.concepts_cache[concept.key]
if concept.key in self.cache_by_key:
entry = self.cache_by_key[concept.key]
if isinstance(entry, list):
# TODO : manage when there are multiple entries
pass
else:
self.concepts_cache[concept.key].compiled = concept.compiled
self.cache_by_key[concept.key].compiled = concept.compiled
def evaluate_concept(self, context, concept: Concept, logger=None):
"""
@@ -751,7 +793,11 @@ class Sheerka(Concept):
if concept.key is None:
raise KeyError()
self.concepts_cache[concept.key] = concept
self.cache_by_key[concept.key] = concept
if concept.id:
self.cache_by_id[concept.id] = concept
return concept
def get(self, concept_key, concept_id=None):
@@ -771,7 +817,7 @@ class Sheerka(Concept):
concept_key = str(concept_key)
# first search in cache
result = self.concepts_cache[concept_key] if concept_key in self.concepts_cache else \
result = self.cache_by_key[concept_key] if concept_key in self.cache_by_key else \
self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
if result and (concept_id is None or not isinstance(result, list)):
@@ -785,14 +831,18 @@ class Sheerka(Concept):
else:
return result
# else return new Unknown concept
# Note that I don't call the new() method to prevent cyclic call
unknown_concept = Concept()
template = self.concepts_cache[str(BuiltinConcepts.UNKNOWN_CONCEPT)]
unknown_concept.update_from(template)
unknown_concept.set_metadata_value(ConceptParts.BODY, concept_key)
unknown_concept.metadata.is_evaluated = True
return unknown_concept
metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key)
return self._get_unknown(metadata)
def get_by_id(self, concept_id):
if concept_id is None:
return ErrorConcept("Concept id is undefined.")
# first search in cache
result = self.cache_by_id[concept_id] if concept_id in self.cache_by_id else \
self.sdp.get_safe(self.CONCEPTS_BY_ID_ENTRY, concept_id)
return result or self._get_unknown(('id', concept_id))
def new(self, concept_key, **kwargs):
"""
@@ -945,14 +995,20 @@ class Sheerka(Concept):
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):
return False
assert isinstance(a, Concept)
assert isinstance(b, Concept)
b_key = b.key if isinstance(b, Concept) else str(b)
# TODO, first check the 'isa' property of a
# TODO : manage when a is the list of all possible b
# for example, if a is a color, it will be found the entry 'All_Colors'
return a.key == b_key
return self.sdp.exists(GROUP_PREFIX + b.id, a.id)
def isagroup(self, concept):
"""True if exists All_<concept_id> in sdp"""
if not concept.id:
return None
res = self.sdp.get_safe(GROUP_PREFIX + concept.id)
return res is not None
def get_evaluator_name(self, name):
if self.evaluators_prefix is None:
@@ -1029,6 +1085,30 @@ class Sheerka(Concept):
self.log.info(f"digest : {c.get_digest()}")
first = False
@staticmethod
def _get_unknown(metadata):
"""
Returns the concept 'UnknownConcept' for a requested id or key
Note that I don't call the new() method to prevent cyclic call
:param metadata:
:return:
"""
# metadata is a list of tuple that contains the known metadata for this concept
# ex : (key, 'not_found)
# or
# (id, invalid_id)
#
# the metadata can be a list, if several attributes where given
# (key, 'not_found), (id, invalid_id)
unknown_concept = UnknownConcept()
unknown_concept.set_metadata_value(ConceptParts.BODY, metadata)
for meta in (metadata if isinstance(metadata, list) else [metadata]):
unknown_concept.set_prop(meta[0], meta[1])
unknown_concept.metadata.is_evaluated = True
return unknown_concept
@staticmethod
def get_builtins_classes_as_dict():
res = {}
@@ -1145,6 +1225,25 @@ class ExecutionContext:
self.values[k] = v
return self
def get_concept(self, key):
# search in obj
if isinstance(self.obj, Concept):
if self.obj.key == key:
return self.obj
for prop in self.obj.props:
if prop == key:
value = self.obj.props[prop].value
if isinstance(value, Concept):
return value
# search in concepts
if self.concepts:
for k, c in self.concepts.items():
if k == key:
return c
return self.sheerka.get(key)
def new_concept(self, key, **kwargs):
# search in obj
if self.obj: