Fixed #101 : Implement PLURIAL

Fixed #103 : Implement PlurialNodeParser
Fixed #104 : Implement dynamic concept
Fixed #107 : PrepareEvalxxxEvaluator: context hints are lost on a second evaluation
This commit is contained in:
2021-08-05 19:07:21 +02:00
parent c798c2c570
commit 71d1b1d1ca
31 changed files with 600 additions and 105 deletions
+4 -3
View File
@@ -53,9 +53,6 @@ woman is a human
girl is a female girl is a female
girl is a human girl is a human
def concept boys
def concept girls
def concept shirt def concept shirt
def concept table def concept table
@@ -76,3 +73,7 @@ def concept what is the x of y pre is_question() where x is an adjective as smar
# #
def concept he ret memory("self is a human and self is a male") def concept he ret memory("self is a human and self is a male")
def concept she ret memory("self is a human and self is a female") def concept she ret memory("self is a human and self is a female")
#
def concept sky
def concept gender
+2 -1
View File
@@ -17,7 +17,7 @@ class BuiltinConcepts:
EVAL_UNTIL_SUCCESS_REQUESTED = "__EVAL_UNTIL_SUCCESS_REQUESTED" # PythonEvaluator tries combination until True is found EVAL_UNTIL_SUCCESS_REQUESTED = "__EVAL_UNTIL_SUCCESS_REQUESTED" # PythonEvaluator tries combination until True is found
EVAL_QUESTION_REQUESTED = "__EVAL_QUESTION_REQUESTED" # the user input must be treated as question EVAL_QUESTION_REQUESTED = "__EVAL_QUESTION_REQUESTED" # the user input must be treated as question
EVAL_GLOBAL_TRUTH_REQUESTED = "__EVAL_GLOBAL_TRUTH_REQUESTED" # the user input is a global truth EVAL_GLOBAL_TRUTH_REQUESTED = "__EVAL_GLOBAL_TRUTH_REQUESTED" # the user input is a global truth
VALIDATION_ONLY_REQUESTED = "__VALIDATION_ONLY_REQUESTED" # Validation mode activated. Never evaluate the body EXPRESSION_ONLY_REQUESTED = "__EXPRESSION_ONLY_REQUESTED" # Do not allow methods with side effect
# possible actions during sheerka.execute() or sheerka.evaluate_rules() # possible actions during sheerka.execute() or sheerka.evaluate_rules()
INIT_SHEERKA = "__INIT_SHEERKA" # INIT_SHEERKA = "__INIT_SHEERKA" #
@@ -53,6 +53,7 @@ class BuiltinConcepts:
# builtin attributes # builtin attributes
ISA = "__ISA" # when a concept is an instance of another one ISA = "__ISA" # when a concept is an instance of another one
HASA = "__HASA" # when a concept has/owns another concept HASA = "__HASA" # when a concept has/owns another concept
PLURAL = "__PLURAL" # when multiple occurrence of the concept
AUTO_EVAL = "__AUTO_EVAL" # when the concept must be auto evaluated AUTO_EVAL = "__AUTO_EVAL" # when the concept must be auto evaluated
# object # object
+3 -3
View File
@@ -971,13 +971,13 @@ class CreateObjectIdentifiers:
self.identifiers_key = {} self.identifiers_key = {}
@staticmethod @staticmethod
def sanitize(identifier): def sanitize(identifier, default="0"):
if identifier is None: if identifier is None:
return "" return ""
res = "" res = ""
for c in identifier: for c in identifier:
res += c if c.isalnum() else "0" res += c if c.isalnum() else default
return res return res
def get_identifier(self, obj, wrapper): def get_identifier(self, obj, wrapper):
@@ -998,7 +998,7 @@ class CreateObjectIdentifiers:
identifier = wrapper + self.sanitize(obj.key or obj.name) identifier = wrapper + self.sanitize(obj.key or obj.name)
if obj.id: if obj.id:
identifier += "__" + obj.id identifier += "__" + self.sanitize(obj.id, "_")
if identifier in self.identifiers_key: if identifier in self.identifiers_key:
self.identifiers_key[identifier] += 1 self.identifiers_key[identifier] += 1
+4
View File
@@ -171,6 +171,7 @@ class Concept:
self._metadata = metadata self._metadata = metadata
self._bound_body = bound_body self._bound_body = bound_body
self._compiled = {} # cached ast for the where, pre, post and body parts and variables self._compiled = {} # cached ast for the where, pre, post and body parts and variables
self._compiled_context_hints = {} # context hints to use when evaluating compiled
self._bnf = None # parsing expression self._bnf = None # parsing expression
self._original_definition_hash = None # concept hash before any alteration of the metadata self._original_definition_hash = None # concept hash before any alteration of the metadata
self._format = None # how to print the concept self._format = None # how to print the concept
@@ -278,6 +279,9 @@ class Concept:
def set_compiled(self, compiled): def set_compiled(self, compiled):
self._compiled = compiled self._compiled = compiled
def get_compiled_context_hints(self):
return self._compiled_context_hints
def get_bnf(self): def get_bnf(self):
return self._bnf return self._bnf
+41 -2
View File
@@ -546,11 +546,12 @@ class Sheerka(Concept):
return new_instances(concept, recognized_by, is_instance, is_evaluated) if return_new else concept return new_instances(concept, recognized_by, is_instance, is_evaluated) if return_new else concept
def new(self, concept_key, **kwargs): def new(self, concept_key, allow_dynamic=False, **kwargs):
""" """
Returns an instance of a new concept Returns an instance of a new concept
When the concept is supposed to be unique, returns the same instance When the concept is supposed to be unique, returns the same instance
:param concept_key: :param concept_key:
:param allow_dynamic:
:param kwargs: :param kwargs:
:return: :return:
""" """
@@ -561,7 +562,8 @@ class Sheerka(Concept):
else: else:
concept_id = None concept_id = None
template = self.get_by_id(concept_id) if not concept_key else self.get_by_key(concept_key, concept_id) template = self.get_by_id(concept_id, allow_dynamic) if not concept_key else \
self.get_by_key(concept_key, concept_id)
# manage concept not found # manage concept not found
if self.isinstance(template, BuiltinConcepts.UNKNOWN_CONCEPT) and \ if self.isinstance(template, BuiltinConcepts.UNKNOWN_CONCEPT) and \
@@ -610,6 +612,43 @@ class Sheerka(Concept):
concept.get_hints().is_evaluated = True # because we have manually set the variables concept.get_hints().is_evaluated = True # because we have manually set the variables
return concept return concept
def new_dynamic(self, concept_or_key, id_suffix, name=None, props=None, attrs=None):
"""
Create a dynamic concept with the given props and attrs
A dynamic concept is a concept that is not declared by 'def concept' but can be deduced from another one
ex: 'boys' can be deduced from 'boy' (given that it's its plural).
dynamic concept are a convenient way not to define all possible concepts
:param concept_or_key:
:param id_suffix:
:param name: new name and key for the concept
:param props:
:param attrs:
:return:
"""
if not isinstance(concept_or_key, Concept):
concept = self.fast_resolve(concept_or_key, return_new=True)
else:
concept = Concept().update_from(concept_or_key, update_value=False)
if hasattr(concept, "__iter__"):
# TODO: replace exception by the Concept TOO_MANY_SUCCESS and make sure that it is correctly manage
raise NotImplementedError("Too many concepts")
concept.get_metadata().id = f"{concept.id}-{id_suffix}"
if name:
concept.get_metadata().name = name
concept.get_metadata().key = name
if props:
for k, v in props.items():
concept.set_prop(k, v)
if attrs:
for k, v in attrs.items():
concept.set_value(k, v)
return concept
def push_ontology(self, context, name, cache_only=False): def push_ontology(self, context, name, cache_only=False):
try: try:
@@ -403,7 +403,6 @@ class SheerkaConceptManager(BaseService):
ensure_concept(concept) ensure_concept(concept)
attr = attribute.str_id if isinstance(attribute, Concept) else attribute attr = attribute.str_id if isinstance(attribute, Concept) else attribute
old_value = concept.get_value(attr)
if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
old_value = self.sheerka.get_by_id(concept.id).get_value(attr) old_value = self.sheerka.get_by_id(concept.id).get_value(attr)
@@ -582,7 +581,9 @@ class SheerkaConceptManager(BaseService):
def get_by_hash(self, concept_hash, concept_id=None): def get_by_hash(self, concept_hash, concept_id=None):
return self.internal_get("hash", concept_hash, self.CONCEPTS_BY_HASH_ENTRY, concept_id) return self.internal_get("hash", concept_hash, self.CONCEPTS_BY_HASH_ENTRY, concept_id)
def get_by_id(self, concept_id): def get_by_id(self, concept_id, allow_dynamic=False):
if allow_dynamic and (index := core.utils.safe_index(concept_id, "-")) > 0:
concept_id = concept_id[:index]
return self.internal_get("id", concept_id, self.CONCEPTS_BY_ID_ENTRY, None) return self.internal_get("id", concept_id, self.CONCEPTS_BY_ID_ENTRY, None)
def has_id(self, concept_id): def has_id(self, concept_id):
@@ -123,7 +123,7 @@ class SheerkaEvaluateConcept(BaseService):
assert concept_part_source is not None assert concept_part_source is not None
tokens = [t.str_value for t in Tokenizer(concept_part_source)] tokens = [t.str_value for t in Tokenizer(concept_part_source, yield_eof=False)]
if check_vars: if check_vars:
for var_name in (v[0] for v in concept.get_metadata().variables): for var_name in (v[0] for v in concept.get_metadata().variables):
@@ -486,24 +486,30 @@ class SheerkaEvaluateConcept(BaseService):
sub_context.add_values(return_values=ret_val) sub_context.add_values(return_values=ret_val)
return ret_val.body return ret_val.body
evaluating_concept_part = current_prop in AllConceptParts
path = get_path(context, current_prop) path = get_path(context, current_prop)
desc = f"Evaluating {path} (concept={current_concept})" desc = f"Evaluating {path} (concept={current_concept})"
with context.push(BuiltinConcepts.EVALUATING_ATTRIBUTE, with context.push(BuiltinConcepts.EVALUATING_ATTRIBUTE,
current_prop, current_prop,
desc=desc, desc=desc,
obj=current_concept) as sub_context: obj=current_concept if evaluating_concept_part else None) as sub_context:
sub_context.add_inputs(path=path) sub_context.add_inputs(path=path)
if force_evaluation: if force_evaluation:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
if forbid_methods_with_side_effect: if forbid_methods_with_side_effect:
sub_context.protected_hints.add(BuiltinConcepts.VALIDATION_ONLY_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED)
if current_prop in (ConceptParts.WHERE, ConceptParts.PRE): if current_prop in (ConceptParts.WHERE, ConceptParts.PRE):
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
sub_context.protected_hints.add(BuiltinConcepts.EVALUATING_PRE_OR_WHERE_CLAUSE) sub_context.protected_hints.add(BuiltinConcepts.EVALUATING_PRE_OR_WHERE_CLAUSE)
if current_prop in current_concept.get_compiled_context_hints():
for hint in current_concept.get_compiled_context_hints()[current_prop]:
sub_context.protected_hints.add(hint)
# when it's a concept, evaluate it # when it's a concept, evaluate it
if isinstance(to_resolve, Concept) and \ if isinstance(to_resolve, Concept) and \
not context.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE): not context.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE):
@@ -521,7 +527,7 @@ class SheerkaEvaluateConcept(BaseService):
# otherwise, execute all return values to find out what is the value # otherwise, execute all return values to find out what is the value
else: else:
# update short term memory with current concept variables # update short term memory with current concept variables
if current_concept: if evaluating_concept_part: # only update when variables are supposed to be initialized
for var in current_concept.get_metadata().variables: for var in current_concept.get_metadata().variables:
value = current_concept.get_value(var[0]) value = current_concept.get_value(var[0])
if value != NotInit: if value != NotInit:
@@ -636,8 +642,8 @@ class SheerkaEvaluateConcept(BaseService):
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
if validation_only: if validation_only:
# Never eval the body # Never call methods with side effect in this concept or sub concepts
sub_context.protected_hints.add(BuiltinConcepts.VALIDATION_ONLY_REQUESTED) sub_context.protected_hints.add(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED)
# auto evaluate commands # auto evaluate commands
if context.sheerka.isa(concept, context.sheerka.new(BuiltinConcepts.AUTO_EVAL)): if context.sheerka.isa(concept, context.sheerka.new(BuiltinConcepts.AUTO_EVAL)):
@@ -656,6 +662,8 @@ class SheerkaEvaluateConcept(BaseService):
# to make sure of the order, it don't use ConceptParts.get_parts() # to make sure of the order, it don't use ConceptParts.get_parts()
# variables must be evaluated first, body must be evaluated before where # variables must be evaluated first, body must be evaluated before where
all_metadata_to_eval = metadata or self.compute_metadata_to_eval(sub_context, concept) all_metadata_to_eval = metadata or self.compute_metadata_to_eval(sub_context, concept)
if validation_only and ConceptParts.BODY in all_metadata_to_eval:
all_metadata_to_eval.remove(ConceptParts.BODY)
for metadata_to_eval in all_metadata_to_eval: for metadata_to_eval in all_metadata_to_eval:
if metadata_to_eval == "variables": if metadata_to_eval == "variables":
@@ -671,7 +679,7 @@ class SheerkaEvaluateConcept(BaseService):
sub_context, sub_context,
prop_ast, prop_ast,
var_name, var_name,
None, concept,
True, True,
not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED), not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED),
w_clause) w_clause)
@@ -680,7 +688,7 @@ class SheerkaEvaluateConcept(BaseService):
resolved = self.resolve(sub_context, resolved = self.resolve(sub_context,
prop_ast, prop_ast,
var_name, var_name,
None, concept,
True, True,
not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED), not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED),
w_clause) w_clause)
@@ -721,7 +729,7 @@ class SheerkaEvaluateConcept(BaseService):
if isinstance(resolved, Concept) and not sub_context.sheerka.is_success(resolved): if isinstance(resolved, Concept) and not sub_context.sheerka.is_success(resolved):
if not (part_key == ConceptParts.BODY and if not (part_key == ConceptParts.BODY and
self.sheerka.has_error(context, resolved, body=BuiltinConcepts.METHOD_ACCESS_ERROR) and self.sheerka.has_error(context, resolved, body=BuiltinConcepts.METHOD_ACCESS_ERROR) and
sub_context.in_context(BuiltinConcepts.VALIDATION_ONLY_REQUESTED)): sub_context.in_context(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED)):
return resolved return resolved
else: else:
# BuiltinConcepts.METHOD_ACCESS_ERROR is returned only when the access to side effect # BuiltinConcepts.METHOD_ACCESS_ERROR is returned only when the access to side effect
@@ -751,7 +759,7 @@ class SheerkaEvaluateConcept(BaseService):
# if len(concept.get_metadata().variables) == 0: # if len(concept.get_metadata().variables) == 0:
# self.sheerka.om.put(self.sheerka.CONCEPTS_BY_ID_ENTRY, concept.id, concept) # self.sheerka.om.put(self.sheerka.CONCEPTS_BY_ID_ENTRY, concept.id, concept)
if not concept.get_metadata().is_builtin: if not concept.get_metadata().is_builtin and concept.get_hints().is_evaluated:
self.sheerka.register_object(sub_context, concept.name, concept) self.sheerka.register_object(sub_context, concept.name, concept)
# manage RET metadata # manage RET metadata
@@ -786,6 +794,7 @@ class SheerkaEvaluateConcept(BaseService):
def compute_metadata_to_eval(self, context, concept): def compute_metadata_to_eval(self, context, concept):
to_eval = [] to_eval = []
# variables, body are shortcut for 'variables are already added' and 'body is already added'
needed, variables, body = self.get_needed_metadata(concept, ConceptParts.PRE, True, True) needed, variables, body = self.get_needed_metadata(concept, ConceptParts.PRE, True, True)
to_eval.extend(needed) to_eval.extend(needed)
@@ -799,6 +808,15 @@ class SheerkaEvaluateConcept(BaseService):
to_eval.extend(needed) to_eval.extend(needed)
if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED): if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED):
if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED):
if not variables:
to_eval.append('variables')
variables = True
if not body:
to_eval.append(ConceptParts.BODY)
body = True
needed, v, b = self.get_needed_metadata(concept, ConceptParts.RET, not variables, not body) needed, v, b = self.get_needed_metadata(concept, ConceptParts.RET, not variables, not body)
variables |= v variables |= v
body |= b body |= b
@@ -809,13 +827,6 @@ class SheerkaEvaluateConcept(BaseService):
body |= b body |= b
to_eval.extend(needed) to_eval.extend(needed)
if context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED):
if not variables:
to_eval.append('variables')
if not body:
to_eval.append(ConceptParts.BODY)
return to_eval return to_eval
def set_auto_eval(self, context, concept): def set_auto_eval(self, context, concept):
@@ -12,7 +12,7 @@ class SheerkaHasAManager(BaseService):
def initialize(self): def initialize(self):
self.sheerka.bind_service_method(self.NAME, self.set_hasa, True) self.sheerka.bind_service_method(self.NAME, self.set_hasa, True)
self.sheerka.bind_service_method(self.NAME, self.hasa, True) self.sheerka.bind_service_method(self.NAME, self.hasa, False)
def set_hasa(self, context, concept_a, concept_b): def set_hasa(self, context, concept_a, concept_b):
""" """
@@ -26,8 +26,13 @@ class SheerkaHasAManager(BaseService):
context.log(f"Setting concept {concept_a} has a {concept_b}", who=self.NAME) context.log(f"Setting concept {concept_a} has a {concept_b}", who=self.NAME)
ensure_concept(concept_a, concept_b) ensure_concept(concept_a, concept_b)
if (BuiltinConcepts.HASA in concept_a.get_metadata().props and if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
concept_b in concept_a.get_metadata().props[BuiltinConcepts.HASA]): concept_to_use = self.sheerka.get_by_id(concept_a.id)
else:
concept_to_use = concept_a
if (BuiltinConcepts.HASA in concept_to_use.get_metadata().props and
concept_b in concept_to_use.get_metadata().props[BuiltinConcepts.HASA]):
return self.sheerka.ret( return self.sheerka.ret(
self.NAME, self.NAME,
False, False,
@@ -40,7 +45,9 @@ class SheerkaHasAManager(BaseService):
if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
to_add = {"props": {BuiltinConcepts.HASA: merged_concepts}} to_add = {"props": {BuiltinConcepts.HASA: merged_concepts}}
return self.sheerka.modify_concept(context, concept_a, to_add, modify_source=True) res = self.sheerka.modify_concept(context, concept_to_use, to_add)
concept_a.set_prop(BuiltinConcepts.HASA, merged_concepts)
return res
else: else:
concept_a.set_prop(BuiltinConcepts.HASA, merged_concepts) concept_a.set_prop(BuiltinConcepts.HASA, merged_concepts)
return self.sheerka.ret(self.NAME, True, concept_a) return self.sheerka.ret(self.NAME, True, concept_a)
@@ -43,8 +43,13 @@ class SheerkaIsAManager(BaseService):
context.log(f"Setting concept {concept} is a {concept_set}", who=self.NAME) context.log(f"Setting concept {concept} is a {concept_set}", who=self.NAME)
core.builtin_helpers.ensure_concept(concept, concept_set) core.builtin_helpers.ensure_concept(concept, concept_set)
if BuiltinConcepts.ISA in concept.get_metadata().props and \ if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
concept_set in concept.get_metadata().props[BuiltinConcepts.ISA]: concept_to_use = self.sheerka.get_by_id(concept.id)
else:
concept_to_use = concept
if BuiltinConcepts.ISA in concept_to_use.get_metadata().props and \
concept_set in concept_to_use.get_metadata().props[BuiltinConcepts.ISA]:
return self.sheerka.ret( return self.sheerka.ret(
self.NAME, self.NAME,
False, False,
@@ -56,12 +61,11 @@ class SheerkaIsAManager(BaseService):
if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
to_add = {"props": {BuiltinConcepts.ISA: new_concept_set}} to_add = {"props": {BuiltinConcepts.ISA: new_concept_set}}
res = self.sheerka.modify_concept(context, concept, to_add, modify_source=True) res = self.sheerka.modify_concept(context, concept_to_use, to_add)
if not res.status: if not res.status:
return res return res
else:
concept = res.body.body
concept.set_prop(BuiltinConcepts.ISA, new_concept_set)
res = self.add_concept_to_set(context, concept, concept_set) res = self.add_concept_to_set(context, concept, concept_set)
return res return res
else: else:
@@ -0,0 +1,100 @@
from dataclasses import dataclass
from cache.Cache import Cache
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import ensure_concept
from core.global_symbols import NotFound
from core.sheerka.services.sheerka_service import BaseService, ServiceObj
@dataclass
class KnownPluralObj(ServiceObj):
"""
Order to store
"""
concept: str # id of the concept
plural: str # id of its plural
class SheerkaPluralManager(BaseService):
NAME = "PluralManager"
KNOWN_PLURAL_ENTRY = "SheerkaPluralManager:KnownPlural"
def __init__(self, sheerka):
super().__init__(sheerka, order=22)
def initialize(self):
cache = Cache().auto_configure(self.KNOWN_PLURAL_ENTRY)
self.sheerka.om.register_cache(self.KNOWN_PLURAL_ENTRY, cache)
self.sheerka.bind_service_method(self.NAME, self.set_plural, True)
self.sheerka.bind_service_method(self.NAME, self.is_plural, False)
self.sheerka.bind_service_method(self.NAME, self.known_plural, False)
def set_plural(self, context, concept_a, concept_b):
"""
Set that a concept_a is the plural of concept_b
:param context:
:param concept_a:
:param concept_b:
:return:
"""
context.log(f"Setting concept {concept_a} as plural of {concept_b}", who=self.NAME)
ensure_concept(concept_a, concept_b)
if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
concept_to_use = self.sheerka.get_by_id(concept_a.id)
else:
concept_to_use = concept_a
if concept_to_use.get_prop(BuiltinConcepts.PLURAL) == concept_b:
return self.sheerka.ret(
self.NAME,
False,
self.sheerka.new(BuiltinConcepts.PROPERTY_ALREADY_DEFINED,
property_value=concept_b,
property_name=BuiltinConcepts.PLURAL,
concept=concept_a))
if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
# update the list of known plurals
known_plural = KnownPluralObj(context.event.get_digest(), concept_b.id, concept_a.id)
self.sheerka.om.put(self.KNOWN_PLURAL_ENTRY, concept_b.id, known_plural)
# update the concept
to_add = {"props": {BuiltinConcepts.PLURAL: concept_b}}
res = self.sheerka.modify_concept(context, concept_to_use, to_add)
concept_a.set_prop(BuiltinConcepts.PLURAL, concept_b) # do it AFTER calling modify_concept()
return res
else:
concept_a.set_prop(BuiltinConcepts.PLURAL, concept_b)
return self.sheerka.ret(self.NAME, True, concept_a)
def is_plural(self, concept_a, concept_b=None):
"""
True if concept_a is a plural if concept_b is omitted
True if concept_a is the plural of concept_b if concept_b is given
:param concept_a:
:param concept_b:
:return:
"""
ensure_concept(concept_a)
if concept_b is None:
return BuiltinConcepts.PLURAL in concept_a.get_metadata().props
ensure_concept(concept_b)
return concept_a.get_prop(BuiltinConcepts.PLURAL) == concept_b
def known_plural(self, concept):
"""
Return the id of the concept's known plural if any. NotFound otherwise
:param concept:
:return:
"""
ensure_concept(concept)
res = self.sheerka.om.get(self.KNOWN_PLURAL_ENTRY, concept.id)
if res is NotFound:
return NotFound
return res.plural
+17
View File
@@ -902,3 +902,20 @@ def get_safe_str_value(obj):
return obj.str_id return obj.str_id
return str(obj) return str(obj)
def safe_index(obj, sub_str):
"""
Return -1 is sub_str is not found.
Note that it is usually a bad idea to return -1 as it's a valid index in Python (but not in our case)
:param obj:
:param sub_str:
:return:
"""
if obj is None:
return -1
try:
return obj.index(sub_str)
except ValueError:
return -1
+1 -1
View File
@@ -36,7 +36,7 @@ class ExpressionEvaluator(OneReturnValueEvaluator):
for c in conditions: for c in conditions:
requested_vars.update(c.variables) requested_vars.update(c.variables)
namespace = create_namespace(context, self.NAME, requested_vars, set(), {}, True, False) namespace = create_namespace(context, self.NAME, requested_vars, set(), {}, True, False)
# TODO: ADD NAMESPACE TO STM # TODO: ADD NAMESPACE TO ShortTermMemory
missing_vars = set() missing_vars = set()
results = rule_evaluator.evaluate_conditions(sub_context, conditions, namespace, missing_vars) results = rule_evaluator.evaluate_conditions(sub_context, conditions, namespace, missing_vars)
+16 -10
View File
@@ -1,8 +1,9 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from evaluators.BaseEvaluator import OneReturnValueEvaluator from evaluators.BaseEvaluator import OneReturnValueEvaluator
from evaluators.PrepareEvalCommon import PrepareEvalCommon
class PrepareEvalBodyEvaluator(OneReturnValueEvaluator): class PrepareEvalBodyEvaluator(OneReturnValueEvaluator, PrepareEvalCommon):
""" """
To recognize when the user input is an (body) evaluation To recognize when the user input is an (body) evaluation
""" """
@@ -11,10 +12,10 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(self.NAME, [BuiltinConcepts.BEFORE_PARSING], 90) super().__init__(self.NAME, [BuiltinConcepts.BEFORE_PARSING], 90)
self.text = None self.inner_text = None
def reset(self): def reset(self):
self.text = None self.inner_text = None
def matches(self, context, return_value): def matches(self, context, return_value):
if not (return_value.status and if not (return_value.status and
@@ -26,7 +27,10 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator):
if not text.startswith("eval "): if not text.startswith("eval "):
return False return False
self.text = text self.inner_text = text[5:].strip()
if self.inner_text == "":
return False
return True return True
def eval(self, context, return_value): def eval(self, context, return_value):
@@ -34,12 +38,14 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator):
new_text_to_parse = sheerka.ret( new_text_to_parse = sheerka.ret(
self.name, self.name,
True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.text[5:], user_name=context.event.user_id)) True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.inner_text, user_name=context.event.user_id))
root = context.get_parents(lambda ec: ec.action == BuiltinConcepts.PROCESS_INPUT) self.update_context_hints(context,
root = root[0] if root else context self.inner_text,
root.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED) [
root.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED) BuiltinConcepts.EVAL_BODY_REQUESTED,
root.protected_hints.add(BuiltinConcepts.RETURN_BODY_REQUESTED) BuiltinConcepts.EVAL_WHERE_REQUESTED,
BuiltinConcepts.RETURN_BODY_REQUESTED
])
return new_text_to_parse return new_text_to_parse
+28
View File
@@ -0,0 +1,28 @@
from core.builtin_concepts_ids import BuiltinConcepts
class PrepareEvalCommon:
@staticmethod
def update_context_hints(context, source, hints):
"""
:param context:
:param source:
:param hints:
:return:
"""
root = context.get_parents(lambda ec: ec.action in (BuiltinConcepts.EVALUATING_CONCEPT,
BuiltinConcepts.PROCESS_INPUT))
root = root[0] if root else context
if root.action == BuiltinConcepts.EVALUATING_CONCEPT:
concept = root.action_context
if source in concept.variables():
concept.get_compiled_context_hints()[source] = hints
else:
parsing_context = context.get_parents(lambda ec: ec.action == BuiltinConcepts.PARSING)
concept_part = parsing_context[0].action_context["prop"]
concept.get_compiled_context_hints()[concept_part] = hints
else:
for hint in hints:
root.add_to_protected_hints(hint)
@@ -1,8 +1,9 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from evaluators.BaseEvaluator import OneReturnValueEvaluator from evaluators.BaseEvaluator import OneReturnValueEvaluator
from evaluators.PrepareEvalCommon import PrepareEvalCommon
class PrepareEvalGlobalTruthEvaluator(OneReturnValueEvaluator): class PrepareEvalGlobalTruthEvaluator(OneReturnValueEvaluator, PrepareEvalCommon):
""" """
To recognize when the user input is a global truth To recognize when the user input is a global truth
""" """
@@ -39,11 +40,11 @@ class PrepareEvalGlobalTruthEvaluator(OneReturnValueEvaluator):
self.name, self.name,
True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.inner_text, user_name=context.event.user_id)) True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.inner_text, user_name=context.event.user_id))
root = context.get_parents(lambda ec: ec.action in (BuiltinConcepts.EVALUATING_CONCEPT, self.update_context_hints(context,
BuiltinConcepts.PROCESS_INPUT)) self.inner_text, [
root = root[0] if root else context BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED,
root.add_to_protected_hints(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) BuiltinConcepts.EVAL_BODY_REQUESTED,
root.add_to_protected_hints(BuiltinConcepts.EVAL_BODY_REQUESTED) BuiltinConcepts.RETURN_BODY_REQUESTED
root.add_to_protected_hints(BuiltinConcepts.RETURN_BODY_REQUESTED) ])
return new_text_to_parse return new_text_to_parse
+10 -9
View File
@@ -1,8 +1,9 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from evaluators.BaseEvaluator import OneReturnValueEvaluator from evaluators.BaseEvaluator import OneReturnValueEvaluator
from evaluators.PrepareEvalCommon import PrepareEvalCommon
class PrepareEvalQuestionEvaluator(OneReturnValueEvaluator): class PrepareEvalQuestionEvaluator(OneReturnValueEvaluator, PrepareEvalCommon):
""" """
To recognize when the user input is a question To recognize when the user input is a question
""" """
@@ -39,12 +40,12 @@ class PrepareEvalQuestionEvaluator(OneReturnValueEvaluator):
self.name, self.name,
True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.question, user_name=context.event.user_id)) True, sheerka.new(BuiltinConcepts.USER_INPUT, body=self.question, user_name=context.event.user_id))
root = context.get_parents(lambda ec: ec.action in (BuiltinConcepts.EVALUATING_CONCEPT, self.update_context_hints(context,
BuiltinConcepts.PROCESS_INPUT)) self.question,
root = root[0] if root else context [
root.add_to_protected_hints(BuiltinConcepts.EVAL_QUESTION_REQUESTED) BuiltinConcepts.EVAL_QUESTION_REQUESTED,
root.add_to_protected_hints(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED) BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED,
root.add_to_protected_hints(BuiltinConcepts.EVAL_BODY_REQUESTED) BuiltinConcepts.EVAL_BODY_REQUESTED,
root.add_to_protected_hints(BuiltinConcepts.RETURN_BODY_REQUESTED) BuiltinConcepts.RETURN_BODY_REQUESTED,
])
return new_text_to_parse return new_text_to_parse
+3 -3
View File
@@ -80,7 +80,7 @@ class PythonEvaluator(OneReturnValueEvaluator):
# We need to disable the functions that may alter the state # We need to disable the functions that may alter the state
# It's a poor way to have source code security check # It's a poor way to have source code security check
expression_only = context.in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED) or \ expression_only = context.in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED) or \
context.in_context(BuiltinConcepts.VALIDATION_ONLY_REQUESTED) context.in_context(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED)
if not expression_only: if not expression_only:
attr_under_eval = context.get_parents(lambda ec: ec.action == BuiltinConcepts.EVALUATING_ATTRIBUTE) attr_under_eval = context.get_parents(lambda ec: ec.action == BuiltinConcepts.EVALUATING_ATTRIBUTE)
@@ -94,8 +94,8 @@ class PythonEvaluator(OneReturnValueEvaluator):
debugger.debug_var("globals", my_globals) debugger.debug_var("globals", my_globals)
except MethodAccessError as ex: except MethodAccessError as ex:
# Quick and dirty, # Quick and dirty,
# When VALIDATION_ONLY_REQUESTED is enabled, it's normal to have some NameError exceptions # When expression_only, it's normal to have some NameError exceptions
if context.in_context(BuiltinConcepts.VALIDATION_ONLY_REQUESTED): if context.in_context(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED):
return sheerka.ret(self.name, False, BuiltinConcepts.METHOD_ACCESS_ERROR, parents=[return_value]) return sheerka.ret(self.name, False, BuiltinConcepts.METHOD_ACCESS_ERROR, parents=[return_value])
eval_error = PythonEvalError(ex, eval_error = PythonEvalError(ex,
+1 -1
View File
@@ -306,7 +306,7 @@ class SourceCodeWithConceptNode(LexerNode):
super().__init__(9999, -1, None) # why not sys.maxint ? super().__init__(9999, -1, None) # why not sys.maxint ?
self.first = first_node self.first = first_node
self.last = last_node self.last = last_node
self.nodes = content_nodes or [] self.nodes = content_nodes or [] # parts of the source code (without the first and the last)
self.has_unrecognized = has_unrecognized self.has_unrecognized = has_unrecognized
self._all_nodes = None self._all_nodes = None
self.fix_all_pos() self.fix_all_pos()
+33 -7
View File
@@ -4,6 +4,7 @@ from core import builtin_helpers
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import update_concepts_hints from core.builtin_helpers import update_concepts_hints
from core.concept import DEFINITION_TYPE_BNF, Concept from core.concept import DEFINITION_TYPE_BNF, Concept
from core.global_symbols import NotFound
from core.sheerka.services.SheerkaExecute import ParserInput from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import Tokenizer, TokenKind from core.tokenizer import Tokenizer, TokenKind
from core.utils import strip_tokens, make_unique from core.utils import strip_tokens, make_unique
@@ -245,13 +246,7 @@ class SequenceNodeParser(BaseNodeParser):
if token.type == TokenKind.WHITESPACE: if token.type == TokenKind.WHITESPACE:
return None return None
def as_list(a): concepts_by_name = self.as_list(self.sheerka.fast_resolve(token))
if a is None:
return None
return a if isinstance(a, list) else [a]
concepts_by_name = as_list(self.sheerka.fast_resolve(token))
concepts_by_first_keyword = new_instances(self.sheerka.get_concepts_by_first_token(token, self._is_eligible)) concepts_by_first_keyword = new_instances(self.sheerka.get_concepts_by_first_token(token, self._is_eligible))
if concepts_by_name is None: if concepts_by_name is None:
@@ -299,6 +294,10 @@ class SequenceNodeParser(BaseNodeParser):
concepts = self.get_concepts(token, self._is_eligible) concepts = self.get_concepts(token, self._is_eligible)
# self.context.log(f"concepts found for {token=}: {concepts}", who=self.name) # self.context.log(f"concepts found for {token=}: {concepts}", who=self.name)
if not concepts:
concepts = self.get_plural(token)
if not concepts: if not concepts:
for concept_parser in concept_parser_helpers: for concept_parser in concept_parser_helpers:
concept_parser.eat_unrecognized(token, pos) concept_parser.eat_unrecognized(token, pos)
@@ -454,3 +453,30 @@ class SequenceNodeParser(BaseNodeParser):
self.name, self.name,
False, False,
context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=parser_input.as_text())) context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=parser_input.as_text()))
def get_plural(self, token):
if not token.type == TokenKind.IDENTIFIER:
return None
if not token.value.endswith("s"):
return None
concept_name = token.value[:-1] # remove the trailing 's'
concepts = self.as_list(self.sheerka.fast_resolve(concept_name))
if concepts is None:
return None
eligible = [c for c in concepts if self.sheerka.known_plural(c) == NotFound]
if not eligible:
return None
return [self.sheerka.new_dynamic(c, BuiltinConcepts.PLURAL, name=token.value, props={BuiltinConcepts.PLURAL: c})
for c in concepts]
@staticmethod
def as_list(obj):
if obj is None:
return None
return obj if isinstance(obj, list) else [obj]
+6 -3
View File
@@ -28,9 +28,9 @@ class ConceptHandler(BaseHandler):
ref = default_concept ref = default_concept
ref_values = default_concept_values ref_values = default_concept_values
else: else:
ref = sheerka.get_by_id(obj.id) ref = sheerka.get_by_id(obj.id, allow_dynamic=True)
ref_values = ref.values() ref_values = ref.values()
data[CONCEPT_ID] = (obj.key, obj.id) data[CONCEPT_ID] = obj.id
# transform metadata # transform metadata
for name in CONCEPT_PROPERTIES_TO_SERIALIZE: for name in CONCEPT_PROPERTIES_TO_SERIALIZE:
@@ -51,7 +51,10 @@ class ConceptHandler(BaseHandler):
def new(self, data): def new(self, data):
sheerka = self.sheerka sheerka = self.sheerka
return sheerka.new(tuple(data[CONCEPT_ID])) if CONCEPT_ID in data else Concept() if CONCEPT_ID in data:
return sheerka.new((None, data[CONCEPT_ID]), allow_dynamic=True)
else:
return Concept()
def restore(self, data, instance): def restore(self, data, instance):
pickler = self.context pickler = self.context
+8
View File
@@ -1537,6 +1537,14 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka):
weights = sheerka.get_weights(BuiltinConcepts.PRECEDENCE, comparison_context=CONCEPT_COMPARISON_CONTEXT) weights = sheerka.get_weights(BuiltinConcepts.PRECEDENCE, comparison_context=CONCEPT_COMPARISON_CONTEXT)
assert weights == {'c:one|1001:': 2, 'c:two|1002:': 1} assert weights == {'c:one|1001:': 2, 'c:two|1002:': 1}
def test_i_can_get_a_dynamic_concept_by_id_when_allow_dynamic(self):
sheerka, context, foo = self.init_concepts("foo")
dynamic_foo = sheerka.new_dynamic(foo, "SUFFIX")
assert sheerka.isinstance(sheerka.get_by_id(dynamic_foo.id), BuiltinConcepts.UNKNOWN_CONCEPT)
assert sheerka.isinstance(sheerka.get_by_id(dynamic_foo.id, allow_dynamic=True), foo)
class TestSheerkaConceptManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka): class TestSheerkaConceptManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka):
def test_i_can_add_several_concepts(self): def test_i_can_add_several_concepts(self):
+28 -1
View File
@@ -518,6 +518,29 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
evaluated = sheerka.evaluate_concept(self.get_context(sheerka, False, False), concept) evaluated = sheerka.evaluate_concept(self.get_context(sheerka, False, False), concept)
assert evaluated.key == concept.key assert evaluated.key == concept.key
def test_i_can_evaluate_context_hint_multiple_times(self):
"""
Previous behaviour (that we want to change)
When def concept foo as global_truth(xxx) pre yyy
the context hint for the global truth is set during the initialisation of the ast
if the body is computed straight away it's ok,
But if the concept is evaluated a second time, the asts is not computed again, so the context hint is not set
:return:
"""
sheerka, context, foo = self.init_concepts(
Concept("foo", body="global_truth(in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED))")
)
foo_instance = sheerka.new("foo")
evaluated = sheerka.evaluate_concept(context, foo_instance, eval_body=False)
assert ConceptParts.BODY in evaluated.get_compiled()
assert evaluated.body == NotInit
assert not evaluated.get_hints().is_evaluated
evaluated = sheerka.evaluate_concept(context, foo_instance, eval_body=True) # evaluate the body this time
assert isinstance(evaluated.body, bool) and evaluated.body
def test_i_can_apply_intermediate_where_condition_using_python(self): def test_i_can_apply_intermediate_where_condition_using_python(self):
sheerka, context, one_1, one_str, plus = self.init_concepts( sheerka, context, one_1, one_str, plus = self.init_concepts(
Concept("one", body="1"), Concept("one", body="1"),
@@ -818,10 +841,14 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
(Concept("foo"), False, []), (Concept("foo"), False, []),
(Concept("foo", pre="pre", post="post", ret="ret", where="where"), False, ["#pre#", "#post#"]), (Concept("foo", pre="pre", post="post", ret="ret", where="where"), False, ["#pre#", "#post#"]),
(Concept("foo", pre="pr", post="p", ret="r", where="w"), True, (Concept("foo", pre="pr", post="p", ret="r", where="w"), True,
["#pre#", "#ret#", "#post#", "variables", "#body#"]), ["#pre#", "variables", "#body#", "#ret#", "#post#"]),
(Concept("foo", pre="pre", body="body"), False, ["#pre#"]),
(Concept("foo", pre="pre", body="body"), True, ["#pre#", "variables", "#body#"]),
(Concept("foo", pre="a").def_var("a"), False, ["variables", "#pre#"]), (Concept("foo", pre="a").def_var("a"), False, ["variables", "#pre#"]),
(Concept("foo", pre="self"), False, ["#body#", "#pre#"]), (Concept("foo", pre="self"), False, ["#body#", "#pre#"]),
(Concept("foo", pre="self + a").def_var("a"), False, ["variables", "#body#", "#pre#"]), (Concept("foo", pre="self + a").def_var("a"), False, ["variables", "#body#", "#pre#"]),
(Concept("foo", pre="self + a", ret="ret").def_var("a"), False, ["variables", "#body#", "#pre#"]), (Concept("foo", pre="self + a", ret="ret").def_var("a"), False, ["variables", "#body#", "#pre#"]),
(Concept("foo", pre="self + a", ret="ret").def_var("a"), True, ["variables", "#body#", "#pre#", "#ret#"]), (Concept("foo", pre="self + a", ret="ret").def_var("a"), True, ["variables", "#body#", "#pre#", "#ret#"]),
(Concept("foo", body="body"), False, []) (Concept("foo", body="body"), False, [])
+17
View File
@@ -9,7 +9,10 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka):
king_instance = sheerka.new("king") king_instance = sheerka.new("king")
res = sheerka.set_hasa(context, king_instance, kingdom) res = sheerka.set_hasa(context, king_instance, kingdom)
assert res.status assert res.status
assert king_instance.get_prop(BuiltinConcepts.HASA) == {kingdom}
assert sheerka.hasa(king_instance, kingdom)
# when global truth is not activated, only the current instance is modified # when global truth is not activated, only the current instance is modified
another_king = sheerka.get_by_key("king") another_king = sheerka.get_by_key("king")
@@ -56,3 +59,17 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka):
assert res.body.property_name == BuiltinConcepts.HASA assert res.body.property_name == BuiltinConcepts.HASA
assert res.body.property_value == kingdom assert res.body.property_value == kingdom
assert res.body.concept == sheerka.new("king") assert res.body.concept == sheerka.new("king")
def test_i_can_set_hase_twice_when_global_truth_is_true_the_second_time(self):
sheerka, context, king, kingdom = self.init_concepts("king", "kingdom")
global_truth_context = self.get_context(sheerka, global_truth=True)
king_instance = sheerka.new("king")
sheerka.set_hasa(context, king_instance, kingdom)
# set it again with global_truth = True
res = sheerka.set_hasa(global_truth_context, king_instance, kingdom)
assert res.status
assert sheerka.hasa(king_instance, kingdom)
assert sheerka.hasa(sheerka.new("king"), kingdom) # try another instance
+16 -1
View File
@@ -116,7 +116,8 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka):
context.add_to_private_hints(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED) context.add_to_private_hints(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED)
sheerka.set_isa(context, blue_instance, color) res = sheerka.set_isa(context, blue_instance, color)
assert res.status
assert sheerka.isa(blue_instance, color) assert sheerka.isa(blue_instance, color)
assert sheerka.isaset(context, color) assert sheerka.isaset(context, color)
assert sheerka.isinset(blue_instance, color) assert sheerka.isinset(blue_instance, color)
@@ -423,6 +424,20 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka):
foo = sheerka.get_by_id(foo.id) foo = sheerka.get_by_id(foo.id)
assert not sheerka.isa(foo, group2) assert not sheerka.isa(foo, group2)
def test_i_can_set_isa_twice_when_global_truth_is_true_the_second_time(self):
sheerka, context, blue, color = self.init_concepts(Concept("blue"), Concept("color"))
global_truth_context = self.get_context(sheerka, global_truth=True)
blue_instance = sheerka.new("blue")
sheerka.set_isa(context, blue_instance, color)
# set it again with global_truth = True
res = sheerka.set_isa(global_truth_context, blue_instance, color)
assert res.status
assert sheerka.isa(blue_instance, color)
assert sheerka.isa(sheerka.new("blue"), color) # try another instance
class TestSheerkaSetsManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka): class TestSheerkaSetsManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka):
def test_i_can_add_concept_to_set_and_retrieve_it_in_another_session(self): def test_i_can_add_concept_to_set_and_retrieve_it_in_another_session(self):
+76
View File
@@ -0,0 +1,76 @@
from core.builtin_concepts_ids import BuiltinConcepts
from core.global_symbols import NotFound
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
class TestSheerkaPluralManager(TestUsingMemoryBasedSheerka):
def test_i_can_set_plural(self):
sheerka, context, man, men = self.init_concepts("man", "men")
men_instance = sheerka.new("men")
res = sheerka.set_plural(context, men_instance, man)
assert res.status
assert men_instance.get_prop(BuiltinConcepts.PLURAL) == man
assert sheerka.is_plural(men_instance)
assert sheerka.is_plural(men_instance, man)
# global truth is not set
another_instance = sheerka.new("men")
assert another_instance.get_prop(BuiltinConcepts.PLURAL) is None
def test_i_can_set_plural_when_global_truth_is_set(self):
sheerka, context, man, men = self.init_concepts("man", "men", global_truth=True)
assert sheerka.known_plural(man) == NotFound
res = sheerka.set_plural(context, men, man)
assert res.status
assert sheerka.is_plural(men)
another_instance = sheerka.new("men")
assert sheerka.is_plural(another_instance)
assert sheerka.known_plural(man) == men.id
def test_i_cannot_set_plural_twice(self):
sheerka, context, man, men = self.init_concepts("man", "men")
men_instance = sheerka.new("men")
sheerka.set_plural(context, men_instance, man)
res = sheerka.set_plural(context, men_instance, man)
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.PROPERTY_ALREADY_DEFINED)
assert res.body.concept == men_instance
assert res.body.property_name == BuiltinConcepts.PLURAL
assert res.body.property_value == man
def test_i_cannot_set_plural_twice_when_global_truth(self):
sheerka, context, man, men = self.init_concepts("man", "men", global_truth=True)
sheerka.set_plural(context, men, man)
another_instance = sheerka.new("men")
res = sheerka.set_plural(context, another_instance, man)
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.PROPERTY_ALREADY_DEFINED)
assert res.body.concept == another_instance
assert res.body.property_name == BuiltinConcepts.PLURAL
assert res.body.property_value == man
def test_i_can_set_plural_twice_when_global_truth_is_true_the_second_time(self):
sheerka, context, man, men = self.init_concepts("man", "men")
global_truth_context = self.get_context(sheerka, global_truth=True)
men_instance = sheerka.new("men")
sheerka.set_plural(context, men_instance, man)
# set it again with global_truth = True
res = sheerka.set_plural(global_truth_context, men_instance, man)
assert res.status
assert men_instance.get_prop(BuiltinConcepts.PLURAL) == man
# and it's now true for all newly created context
assert sheerka.is_plural(sheerka.new("men"), man)
@@ -0,0 +1,59 @@
from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import Concept, ConceptParts
from evaluators.PrepareEvalCommon import PrepareEvalCommon
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
class TestPrepareEvalCommon(TestUsingMemoryBasedSheerka):
def test_i_can_update_the_current_root(self):
sheerka, context = self.init_concepts()
PrepareEvalCommon.update_context_hints(context, "prop_name", ["to_put_in_context"])
assert context.in_context("to_put_in_context")
def test_i_can_update_process_input(self):
sheerka, context = self.init_concepts()
process_input_context = context.push(BuiltinConcepts.PROCESS_INPUT, "some input", desc=f"some desc")
level1 = process_input_context.push(BuiltinConcepts.TESTING, "some stuff")
level2 = level1.push(BuiltinConcepts.TESTING, "some stuff") # another level for the fun
PrepareEvalCommon.update_context_hints(level2, "prop_name", ["to_put_in_context"])
assert not context.in_context("to_put_in_context")
assert process_input_context.in_context("to_put_in_context")
assert not level1.in_context("to_put_in_context")
assert not level2.in_context("to_put_in_context")
def test_i_can_update_process_when_evaluating_a_concept(self):
sheerka, context, foo = self.init_concepts("foo")
eval_context = context.push(BuiltinConcepts.EVALUATING_CONCEPT, foo, desc=f"some desc")
parsing_prop_context = eval_context.push(BuiltinConcepts.PARSING, {"prop": ConceptParts.BODY})
level1 = parsing_prop_context.push(BuiltinConcepts.TESTING, "some stuff")
level2 = level1.push(BuiltinConcepts.TESTING, "some stuff") # another level for the fun
PrepareEvalCommon.update_context_hints(level2, "prop_name", ["to_put_in_context"])
assert not context.in_context("to_put_in_context")
assert not eval_context.in_context("to_put_in_context")
assert not parsing_prop_context.in_context("to_put_in_context")
assert not level1.in_context("to_put_in_context")
assert not level2.in_context("to_put_in_context")
assert foo.get_compiled_context_hints() == {ConceptParts.BODY: ["to_put_in_context"]}
def test_i_can_update_for_the_correct_variable(self):
# when source is the name of the variable,
# use this name, rather than the attribute being parsed
sheerka, context, foo = self.init_concepts(Concept("foo").def_var("var_name"))
eval_context = context.push(BuiltinConcepts.EVALUATING_CONCEPT, foo, desc=f"some desc")
parsing_prop_context = eval_context.push(BuiltinConcepts.PARSING, {"prop": ConceptParts.BODY})
level1 = parsing_prop_context.push(BuiltinConcepts.TESTING, "some stuff")
level2 = level1.push(BuiltinConcepts.TESTING, "some stuff") # another level for the fun
PrepareEvalCommon.update_context_hints(level2, "var_name", ["to_put_in_context"])
assert not context.in_context("to_put_in_context")
assert not eval_context.in_context("to_put_in_context")
assert not parsing_prop_context.in_context("to_put_in_context")
assert not level1.in_context("to_put_in_context")
assert not level2.in_context("to_put_in_context")
assert foo.get_compiled_context_hints() == {"var_name": ["to_put_in_context"]}
+11
View File
@@ -44,3 +44,14 @@ class TestSheerkaNonRegMemory2(TestUsingMemoryBasedSheerka):
assert res[0].status assert res[0].status
assert sheerka.isa(sheerka.new("one"), sheerka.new("number")) assert sheerka.isa(sheerka.new("one"), sheerka.new("number"))
# def test_i_can_define_plural(self):
# init = [
# "def concept man",
# "def concept men as set_plural(man) ret man auto_eval True",
# ]
# sheerka = self.init_scenario(init)
#
# res = sheerka.evaluate_user_input("men")
# assert res[0].status
+2 -2
View File
@@ -151,8 +151,8 @@ __default__
init = [ init = [
"def concept one as 1", "def concept one as 1",
"def concept two as 2", "def concept two as 2",
"one", "eval one",
"two" "eval two"
] ]
sheerka = self.init_scenario(init) sheerka = self.init_scenario(init)
capsys.readouterr() capsys.readouterr()
+12
View File
@@ -3,6 +3,7 @@ import pytest
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept from core.concept import Concept
from core.sheerka.services.SheerkaExecute import ParserInput from core.sheerka.services.SheerkaExecute import ParserInput
from parsers.BaseNodeParser import SourceCodeWithConceptNode
from parsers.BaseParser import ErrorSink from parsers.BaseParser import ErrorSink
from parsers.FunctionParser import FunctionParser from parsers.FunctionParser import FunctionParser
from parsers.PythonParser import PythonErrorNode from parsers.PythonParser import PythonErrorNode
@@ -192,6 +193,17 @@ class TestFunctionParser(TestUsingMemoryBasedSheerka):
assert expression.python_node is not None assert expression.python_node is not None
assert expression.return_value is not None assert expression.return_value is not None
def test_i_can_parse_when_the_parameter_is_a_dynamic_concept(self):
sheerka, context, parser = self.init_parser()
text = "func(ones)"
res = parser.parse(context, ParserInput(text))
assert res.status
assert isinstance(res.body.body, SourceCodeWithConceptNode)
assert res.body.body.python_node.source == 'func(__C__ones__1001___PLURAL__C__)'
assert "__C__ones__1001___PLURAL__C__" in res.body.body.python_node.objects
@pytest.mark.parametrize("text, expected_error_type", [ @pytest.mark.parametrize("text, expected_error_type", [
("one", BuiltinConcepts.NOT_FOR_ME), # no function found ("one", BuiltinConcepts.NOT_FOR_ME), # no function found
("$*!", BuiltinConcepts.NOT_FOR_ME), # no function found ("$*!", BuiltinConcepts.NOT_FOR_ME), # no function found
+21
View File
@@ -229,11 +229,13 @@ class TestSequenceNodeParser(TestUsingMemoryBasedSheerka):
sheerka, context, parser = self.init_parser(concepts_map) sheerka, context, parser = self.init_parser(concepts_map)
res = parser.parse(context, ParserInput("a special concept")) res = parser.parse(context, ParserInput("a special concept"))
assert res.status
lexer_nodes = res.body.body lexer_nodes = res.body.body
expected_array = compute_expected_array(concepts_map, "a special concept", ["a special concept"]) expected_array = compute_expected_array(concepts_map, "a special concept", ["a special concept"])
compare_with_test_object(lexer_nodes, expected_array) compare_with_test_object(lexer_nodes, expected_array)
res = parser.parse(context, ParserInput("isa")) res = parser.parse(context, ParserInput("isa"))
assert res.status
lexer_nodes = res.body.body lexer_nodes = res.body.body
expected_array = compute_expected_array(concepts_map, "isa", ["isa"]) expected_array = compute_expected_array(concepts_map, "isa", ["isa"])
compare_with_test_object(lexer_nodes, expected_array) compare_with_test_object(lexer_nodes, expected_array)
@@ -442,3 +444,22 @@ class TestSequenceNodeParser(TestUsingMemoryBasedSheerka):
for node in res.body.body: for node in res.body.body:
if hasattr(node, "concept"): if hasattr(node, "concept"):
assert node.concept.get_hints().use_copy assert node.concept.get_hints().use_copy
def test_i_can_parse_plural(self):
concepts_map = {
"boy": Concept("boy"),
}
sheerka, context, parser = self.init_parser(concepts_map)
boy = concepts_map['boy']
res = parser.parse(context, ParserInput("boys"))
assert res.status
lexer_nodes = res.body.body
assert len(lexer_nodes) == 1
concept_found = lexer_nodes[0].concept
assert concept_found.id == f"{boy.id}-{BuiltinConcepts.PLURAL}"
assert concept_found.name == "boys"
assert concept_found.key == "boys"
assert concept_found.get_prop(BuiltinConcepts.PLURAL) == boy
+20 -21
View File
@@ -159,7 +159,7 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka):
to_string = sheerkapickle.encode(sheerka, ref_concept) to_string = sheerkapickle.encode(sheerka, ref_concept)
decoded = sheerkapickle.decode(sheerka, to_string) decoded = sheerkapickle.decode(sheerka, to_string)
assert decoded == ref_concept assert decoded == ref_concept
assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": ["my_key", "1001"]}' assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": "1001"}'
# same test, modify a value and check if this modification is correctly saved # same test, modify a value and check if this modification is correctly saved
concept = Concept().update_from(sheerka.get_by_id(ref_concept.id)) concept = Concept().update_from(sheerka.get_by_id(ref_concept.id))
@@ -167,26 +167,7 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka):
to_string = sheerkapickle.encode(sheerka, concept) to_string = sheerkapickle.encode(sheerka, concept)
decoded = sheerkapickle.decode(sheerka, to_string) decoded = sheerkapickle.decode(sheerka, to_string)
assert decoded == concept assert decoded == concept
assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": ["my_key", "1001"], "values": [["#body#", {"_sheerka/obj": "core.concept.Concept", "meta.name": "bar"}]]}' assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": "1001", "values": [["#body#", {"_sheerka/obj": "core.concept.Concept", "meta.name": "bar"}]]}'
# def test_i_can_encode_decode_when_variable_is_a_concept(self):
# sheerka = self.get_sheerka()
#
# foo = Concept("foo")
# sheerka.create_new_concept(self.get_context(sheerka), foo)
#
# concept = Concept("my_name")
# sheerka.create_new_concept(self.get_context(sheerka), concept)
# concept.def_var(foo, "a value")
# concept.set_value(foo, "another value")
# concept.get_metadata().full_serialization = True
#
# to_string = sheerkapickle.encode(sheerka, concept)
# decoded = sheerkapickle.decode(sheerka, to_string)
# assert decoded == concept
# assert to_string == '{"_sheerka/obj": "core.concept.Concept", "meta.name": "my_name", "meta.key": "my_name", ' + \
# '"meta.variables": [[{"_sheerka/obj": "core.concept.Concept", "concept/id": ["foo", "1001"]}, "a value"]], ' + \
# '"meta.id": "1002", "values": [[{"_sheerka/id": 1}, "another value"]]}'
def test_i_can_manage_reference_of_the_same_object(self): def test_i_can_manage_reference_of_the_same_object(self):
sheerka = self.get_sheerka() sheerka = self.get_sheerka()
@@ -339,3 +320,21 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka):
assert to_string == '{"_sheerka/obj": "core.rule.Rule", "rule/id": "1", "name": "my rule", "predicate": "True", "action_type": "print", "action": "Hello world"}' assert to_string == '{"_sheerka/obj": "core.rule.Rule", "rule/id": "1", "name": "my rule", "predicate": "True", "action_type": "print", "action": "Hello world"}'
assert decoded == rule assert decoded == rule
def test_i_can_encode_decode_dynamic_concept(self):
sheerka, context, foo = self.init_concepts("foo", global_truth=True, create_new=True)
sheerka.set_attr(context, foo, "attr", "attr_value")
sheerka.set_property(context, foo, "prop", "prop_value", all_concepts=True)
foo_instance = sheerka.new(foo)
dynamic_foo = sheerka.new_dynamic(foo_instance,
"SUFFIX",
"new_name",
props={"new_prop": "value"},
attrs={"new_attr": "value"})
to_string = sheerkapickle.encode(sheerka, dynamic_foo)
decoded = sheerkapickle.decode(sheerka, to_string)
assert decoded == dynamic_foo
assert to_string == '{"_sheerka/obj": "core.concept.Concept", "concept/id": "1001-SUFFIX", "meta.name": "new_name", "meta.key": "new_name", "meta.props": {"prop": "prop_value", "new_prop": "value"}, "meta.id": "1001-SUFFIX", "values": [["new_attr", "value"]]}'