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:
@@ -53,9 +53,6 @@ woman is a human
|
||||
girl is a female
|
||||
girl is a human
|
||||
|
||||
|
||||
def concept boys
|
||||
def concept girls
|
||||
def concept shirt
|
||||
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 she ret memory("self is a human and self is a female")
|
||||
|
||||
#
|
||||
def concept sky
|
||||
def concept gender
|
||||
@@ -17,7 +17,7 @@ class BuiltinConcepts:
|
||||
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_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()
|
||||
INIT_SHEERKA = "__INIT_SHEERKA" #
|
||||
@@ -53,6 +53,7 @@ class BuiltinConcepts:
|
||||
# builtin attributes
|
||||
ISA = "__ISA" # when a concept is an instance of another one
|
||||
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
|
||||
|
||||
# object
|
||||
|
||||
@@ -971,13 +971,13 @@ class CreateObjectIdentifiers:
|
||||
self.identifiers_key = {}
|
||||
|
||||
@staticmethod
|
||||
def sanitize(identifier):
|
||||
def sanitize(identifier, default="0"):
|
||||
if identifier is None:
|
||||
return ""
|
||||
|
||||
res = ""
|
||||
for c in identifier:
|
||||
res += c if c.isalnum() else "0"
|
||||
res += c if c.isalnum() else default
|
||||
return res
|
||||
|
||||
def get_identifier(self, obj, wrapper):
|
||||
@@ -998,7 +998,7 @@ class CreateObjectIdentifiers:
|
||||
|
||||
identifier = wrapper + self.sanitize(obj.key or obj.name)
|
||||
if obj.id:
|
||||
identifier += "__" + obj.id
|
||||
identifier += "__" + self.sanitize(obj.id, "_")
|
||||
|
||||
if identifier in self.identifiers_key:
|
||||
self.identifiers_key[identifier] += 1
|
||||
|
||||
@@ -171,6 +171,7 @@ class Concept:
|
||||
self._metadata = metadata
|
||||
self._bound_body = bound_body
|
||||
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._original_definition_hash = None # concept hash before any alteration of the metadata
|
||||
self._format = None # how to print the concept
|
||||
@@ -278,6 +279,9 @@ class Concept:
|
||||
def set_compiled(self, compiled):
|
||||
self._compiled = compiled
|
||||
|
||||
def get_compiled_context_hints(self):
|
||||
return self._compiled_context_hints
|
||||
|
||||
def get_bnf(self):
|
||||
return self._bnf
|
||||
|
||||
|
||||
@@ -546,11 +546,12 @@ class Sheerka(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
|
||||
When the concept is supposed to be unique, returns the same instance
|
||||
:param concept_key:
|
||||
:param allow_dynamic:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
@@ -561,7 +562,8 @@ class Sheerka(Concept):
|
||||
else:
|
||||
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
|
||||
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
|
||||
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):
|
||||
|
||||
try:
|
||||
|
||||
@@ -403,7 +403,6 @@ class SheerkaConceptManager(BaseService):
|
||||
ensure_concept(concept)
|
||||
|
||||
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):
|
||||
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):
|
||||
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)
|
||||
|
||||
def has_id(self, concept_id):
|
||||
|
||||
@@ -123,7 +123,7 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
|
||||
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:
|
||||
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)
|
||||
return ret_val.body
|
||||
|
||||
evaluating_concept_part = current_prop in AllConceptParts
|
||||
|
||||
path = get_path(context, current_prop)
|
||||
desc = f"Evaluating {path} (concept={current_concept})"
|
||||
with context.push(BuiltinConcepts.EVALUATING_ATTRIBUTE,
|
||||
current_prop,
|
||||
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)
|
||||
|
||||
if force_evaluation:
|
||||
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
|
||||
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):
|
||||
sub_context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
|
||||
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
|
||||
if isinstance(to_resolve, Concept) and \
|
||||
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
|
||||
else:
|
||||
# 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:
|
||||
value = current_concept.get_value(var[0])
|
||||
if value != NotInit:
|
||||
@@ -636,8 +642,8 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
|
||||
if validation_only:
|
||||
# Never eval the body
|
||||
sub_context.protected_hints.add(BuiltinConcepts.VALIDATION_ONLY_REQUESTED)
|
||||
# Never call methods with side effect in this concept or sub concepts
|
||||
sub_context.protected_hints.add(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED)
|
||||
|
||||
# auto evaluate commands
|
||||
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()
|
||||
# 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)
|
||||
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:
|
||||
if metadata_to_eval == "variables":
|
||||
@@ -671,7 +679,7 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
sub_context,
|
||||
prop_ast,
|
||||
var_name,
|
||||
None,
|
||||
concept,
|
||||
True,
|
||||
not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED),
|
||||
w_clause)
|
||||
@@ -680,7 +688,7 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
resolved = self.resolve(sub_context,
|
||||
prop_ast,
|
||||
var_name,
|
||||
None,
|
||||
concept,
|
||||
True,
|
||||
not sub_context.in_context(BuiltinConcepts.EVAL_BODY_REQUESTED),
|
||||
w_clause)
|
||||
@@ -721,7 +729,7 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
if isinstance(resolved, Concept) and not sub_context.sheerka.is_success(resolved):
|
||||
if not (part_key == ConceptParts.BODY 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
|
||||
else:
|
||||
# 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:
|
||||
# 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)
|
||||
|
||||
# manage RET metadata
|
||||
@@ -786,6 +794,7 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
def compute_metadata_to_eval(self, context, concept):
|
||||
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)
|
||||
to_eval.extend(needed)
|
||||
|
||||
@@ -799,6 +808,15 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
to_eval.extend(needed)
|
||||
|
||||
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)
|
||||
variables |= v
|
||||
body |= b
|
||||
@@ -809,13 +827,6 @@ class SheerkaEvaluateConcept(BaseService):
|
||||
body |= b
|
||||
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
|
||||
|
||||
def set_auto_eval(self, context, concept):
|
||||
|
||||
@@ -12,7 +12,7 @@ class SheerkaHasAManager(BaseService):
|
||||
|
||||
def initialize(self):
|
||||
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):
|
||||
"""
|
||||
@@ -26,8 +26,13 @@ class SheerkaHasAManager(BaseService):
|
||||
context.log(f"Setting concept {concept_a} has a {concept_b}", who=self.NAME)
|
||||
ensure_concept(concept_a, concept_b)
|
||||
|
||||
if (BuiltinConcepts.HASA in concept_a.get_metadata().props and
|
||||
concept_b in concept_a.get_metadata().props[BuiltinConcepts.HASA]):
|
||||
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 (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(
|
||||
self.NAME,
|
||||
False,
|
||||
@@ -40,7 +45,9 @@ class SheerkaHasAManager(BaseService):
|
||||
|
||||
if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
|
||||
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:
|
||||
concept_a.set_prop(BuiltinConcepts.HASA, merged_concepts)
|
||||
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)
|
||||
core.builtin_helpers.ensure_concept(concept, concept_set)
|
||||
|
||||
if BuiltinConcepts.ISA in concept.get_metadata().props and \
|
||||
concept_set in concept.get_metadata().props[BuiltinConcepts.ISA]:
|
||||
if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
|
||||
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(
|
||||
self.NAME,
|
||||
False,
|
||||
@@ -56,12 +61,11 @@ class SheerkaIsAManager(BaseService):
|
||||
|
||||
if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED):
|
||||
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:
|
||||
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)
|
||||
return res
|
||||
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
|
||||
@@ -902,3 +902,20 @@ def get_safe_str_value(obj):
|
||||
return obj.str_id
|
||||
|
||||
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
|
||||
|
||||
@@ -36,7 +36,7 @@ class ExpressionEvaluator(OneReturnValueEvaluator):
|
||||
for c in conditions:
|
||||
requested_vars.update(c.variables)
|
||||
namespace = create_namespace(context, self.NAME, requested_vars, set(), {}, True, False)
|
||||
# TODO: ADD NAMESPACE TO STM
|
||||
# TODO: ADD NAMESPACE TO ShortTermMemory
|
||||
missing_vars = set()
|
||||
results = rule_evaluator.evaluate_conditions(sub_context, conditions, namespace, missing_vars)
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
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
|
||||
"""
|
||||
@@ -11,10 +12,10 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(self.NAME, [BuiltinConcepts.BEFORE_PARSING], 90)
|
||||
self.text = None
|
||||
self.inner_text = None
|
||||
|
||||
def reset(self):
|
||||
self.text = None
|
||||
self.inner_text = None
|
||||
|
||||
def matches(self, context, return_value):
|
||||
if not (return_value.status and
|
||||
@@ -26,7 +27,10 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator):
|
||||
if not text.startswith("eval "):
|
||||
return False
|
||||
|
||||
self.text = text
|
||||
self.inner_text = text[5:].strip()
|
||||
if self.inner_text == "":
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def eval(self, context, return_value):
|
||||
@@ -34,12 +38,14 @@ class PrepareEvalBodyEvaluator(OneReturnValueEvaluator):
|
||||
|
||||
new_text_to_parse = sheerka.ret(
|
||||
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)
|
||||
root = root[0] if root else context
|
||||
root.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
root.protected_hints.add(BuiltinConcepts.EVAL_WHERE_REQUESTED)
|
||||
root.protected_hints.add(BuiltinConcepts.RETURN_BODY_REQUESTED)
|
||||
self.update_context_hints(context,
|
||||
self.inner_text,
|
||||
[
|
||||
BuiltinConcepts.EVAL_BODY_REQUESTED,
|
||||
BuiltinConcepts.EVAL_WHERE_REQUESTED,
|
||||
BuiltinConcepts.RETURN_BODY_REQUESTED
|
||||
])
|
||||
|
||||
return new_text_to_parse
|
||||
|
||||
@@ -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 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
|
||||
"""
|
||||
@@ -39,11 +40,11 @@ class PrepareEvalGlobalTruthEvaluator(OneReturnValueEvaluator):
|
||||
self.name,
|
||||
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,
|
||||
BuiltinConcepts.PROCESS_INPUT))
|
||||
root = root[0] if root else context
|
||||
root.add_to_protected_hints(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED)
|
||||
root.add_to_protected_hints(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
root.add_to_protected_hints(BuiltinConcepts.RETURN_BODY_REQUESTED)
|
||||
self.update_context_hints(context,
|
||||
self.inner_text, [
|
||||
BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED,
|
||||
BuiltinConcepts.EVAL_BODY_REQUESTED,
|
||||
BuiltinConcepts.RETURN_BODY_REQUESTED
|
||||
])
|
||||
|
||||
return new_text_to_parse
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
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
|
||||
"""
|
||||
@@ -39,12 +40,12 @@ class PrepareEvalQuestionEvaluator(OneReturnValueEvaluator):
|
||||
self.name,
|
||||
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,
|
||||
BuiltinConcepts.PROCESS_INPUT))
|
||||
root = root[0] if root else context
|
||||
root.add_to_protected_hints(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
|
||||
root.add_to_protected_hints(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
|
||||
root.add_to_protected_hints(BuiltinConcepts.EVAL_BODY_REQUESTED)
|
||||
root.add_to_protected_hints(BuiltinConcepts.RETURN_BODY_REQUESTED)
|
||||
|
||||
self.update_context_hints(context,
|
||||
self.question,
|
||||
[
|
||||
BuiltinConcepts.EVAL_QUESTION_REQUESTED,
|
||||
BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED,
|
||||
BuiltinConcepts.EVAL_BODY_REQUESTED,
|
||||
BuiltinConcepts.RETURN_BODY_REQUESTED,
|
||||
])
|
||||
return new_text_to_parse
|
||||
|
||||
@@ -80,7 +80,7 @@ class PythonEvaluator(OneReturnValueEvaluator):
|
||||
# We need to disable the functions that may alter the state
|
||||
# It's a poor way to have source code security check
|
||||
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:
|
||||
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)
|
||||
except MethodAccessError as ex:
|
||||
# Quick and dirty,
|
||||
# When VALIDATION_ONLY_REQUESTED is enabled, it's normal to have some NameError exceptions
|
||||
if context.in_context(BuiltinConcepts.VALIDATION_ONLY_REQUESTED):
|
||||
# When expression_only, it's normal to have some NameError exceptions
|
||||
if context.in_context(BuiltinConcepts.EXPRESSION_ONLY_REQUESTED):
|
||||
return sheerka.ret(self.name, False, BuiltinConcepts.METHOD_ACCESS_ERROR, parents=[return_value])
|
||||
|
||||
eval_error = PythonEvalError(ex,
|
||||
|
||||
@@ -306,7 +306,7 @@ class SourceCodeWithConceptNode(LexerNode):
|
||||
super().__init__(9999, -1, None) # why not sys.maxint ?
|
||||
self.first = first_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._all_nodes = None
|
||||
self.fix_all_pos()
|
||||
|
||||
@@ -4,6 +4,7 @@ from core import builtin_helpers
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.builtin_helpers import update_concepts_hints
|
||||
from core.concept import DEFINITION_TYPE_BNF, Concept
|
||||
from core.global_symbols import NotFound
|
||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||
from core.tokenizer import Tokenizer, TokenKind
|
||||
from core.utils import strip_tokens, make_unique
|
||||
@@ -245,13 +246,7 @@ class SequenceNodeParser(BaseNodeParser):
|
||||
if token.type == TokenKind.WHITESPACE:
|
||||
return None
|
||||
|
||||
def as_list(a):
|
||||
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_name = self.as_list(self.sheerka.fast_resolve(token))
|
||||
concepts_by_first_keyword = new_instances(self.sheerka.get_concepts_by_first_token(token, self._is_eligible))
|
||||
|
||||
if concepts_by_name is None:
|
||||
@@ -299,6 +294,10 @@ class SequenceNodeParser(BaseNodeParser):
|
||||
|
||||
concepts = self.get_concepts(token, self._is_eligible)
|
||||
# self.context.log(f"concepts found for {token=}: {concepts}", who=self.name)
|
||||
|
||||
if not concepts:
|
||||
concepts = self.get_plural(token)
|
||||
|
||||
if not concepts:
|
||||
for concept_parser in concept_parser_helpers:
|
||||
concept_parser.eat_unrecognized(token, pos)
|
||||
@@ -454,3 +453,30 @@ class SequenceNodeParser(BaseNodeParser):
|
||||
self.name,
|
||||
False,
|
||||
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]
|
||||
|
||||
@@ -28,9 +28,9 @@ class ConceptHandler(BaseHandler):
|
||||
ref = default_concept
|
||||
ref_values = default_concept_values
|
||||
else:
|
||||
ref = sheerka.get_by_id(obj.id)
|
||||
ref = sheerka.get_by_id(obj.id, allow_dynamic=True)
|
||||
ref_values = ref.values()
|
||||
data[CONCEPT_ID] = (obj.key, obj.id)
|
||||
data[CONCEPT_ID] = obj.id
|
||||
|
||||
# transform metadata
|
||||
for name in CONCEPT_PROPERTIES_TO_SERIALIZE:
|
||||
@@ -51,7 +51,10 @@ class ConceptHandler(BaseHandler):
|
||||
|
||||
def new(self, data):
|
||||
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):
|
||||
pickler = self.context
|
||||
|
||||
@@ -1537,6 +1537,14 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka):
|
||||
weights = sheerka.get_weights(BuiltinConcepts.PRECEDENCE, comparison_context=CONCEPT_COMPARISON_CONTEXT)
|
||||
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):
|
||||
def test_i_can_add_several_concepts(self):
|
||||
|
||||
@@ -518,6 +518,29 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
|
||||
evaluated = sheerka.evaluate_concept(self.get_context(sheerka, False, False), concept)
|
||||
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):
|
||||
sheerka, context, one_1, one_str, plus = self.init_concepts(
|
||||
Concept("one", body="1"),
|
||||
@@ -818,10 +841,14 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
|
||||
(Concept("foo"), False, []),
|
||||
(Concept("foo", pre="pre", post="post", ret="ret", where="where"), False, ["#pre#", "#post#"]),
|
||||
(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="self"), False, ["#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"), True, ["variables", "#body#", "#pre#", "#ret#"]),
|
||||
(Concept("foo", body="body"), False, [])
|
||||
|
||||
@@ -9,7 +9,10 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka):
|
||||
|
||||
king_instance = sheerka.new("king")
|
||||
res = sheerka.set_hasa(context, king_instance, kingdom)
|
||||
|
||||
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
|
||||
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_value == kingdom
|
||||
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
|
||||
|
||||
@@ -116,7 +116,8 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka):
|
||||
|
||||
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.isaset(context, color)
|
||||
assert sheerka.isinset(blue_instance, color)
|
||||
@@ -423,6 +424,20 @@ class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka):
|
||||
foo = sheerka.get_by_id(foo.id)
|
||||
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):
|
||||
def test_i_can_add_concept_to_set_and_retrieve_it_in_another_session(self):
|
||||
|
||||
@@ -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"]}
|
||||
@@ -44,3 +44,14 @@ class TestSheerkaNonRegMemory2(TestUsingMemoryBasedSheerka):
|
||||
|
||||
assert res[0].status
|
||||
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
|
||||
|
||||
|
||||
@@ -151,8 +151,8 @@ __default__
|
||||
init = [
|
||||
"def concept one as 1",
|
||||
"def concept two as 2",
|
||||
"one",
|
||||
"two"
|
||||
"eval one",
|
||||
"eval two"
|
||||
]
|
||||
sheerka = self.init_scenario(init)
|
||||
capsys.readouterr()
|
||||
|
||||
@@ -3,6 +3,7 @@ import pytest
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept
|
||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||
from parsers.BaseNodeParser import SourceCodeWithConceptNode
|
||||
from parsers.BaseParser import ErrorSink
|
||||
from parsers.FunctionParser import FunctionParser
|
||||
from parsers.PythonParser import PythonErrorNode
|
||||
@@ -192,6 +193,17 @@ class TestFunctionParser(TestUsingMemoryBasedSheerka):
|
||||
assert expression.python_node 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", [
|
||||
("one", BuiltinConcepts.NOT_FOR_ME), # no function found
|
||||
("$*!", BuiltinConcepts.NOT_FOR_ME), # no function found
|
||||
|
||||
@@ -229,11 +229,13 @@ class TestSequenceNodeParser(TestUsingMemoryBasedSheerka):
|
||||
sheerka, context, parser = self.init_parser(concepts_map)
|
||||
|
||||
res = parser.parse(context, ParserInput("a special concept"))
|
||||
assert res.status
|
||||
lexer_nodes = res.body.body
|
||||
expected_array = compute_expected_array(concepts_map, "a special concept", ["a special concept"])
|
||||
compare_with_test_object(lexer_nodes, expected_array)
|
||||
|
||||
res = parser.parse(context, ParserInput("isa"))
|
||||
assert res.status
|
||||
lexer_nodes = res.body.body
|
||||
expected_array = compute_expected_array(concepts_map, "isa", ["isa"])
|
||||
compare_with_test_object(lexer_nodes, expected_array)
|
||||
@@ -442,3 +444,22 @@ class TestSequenceNodeParser(TestUsingMemoryBasedSheerka):
|
||||
for node in res.body.body:
|
||||
if hasattr(node, "concept"):
|
||||
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
|
||||
|
||||
@@ -159,7 +159,7 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka):
|
||||
to_string = sheerkapickle.encode(sheerka, ref_concept)
|
||||
decoded = sheerkapickle.decode(sheerka, to_string)
|
||||
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
|
||||
concept = Concept().update_from(sheerka.get_by_id(ref_concept.id))
|
||||
@@ -167,26 +167,7 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka):
|
||||
to_string = sheerkapickle.encode(sheerka, concept)
|
||||
decoded = sheerkapickle.decode(sheerka, to_string)
|
||||
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"}]]}'
|
||||
|
||||
# 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"]]}'
|
||||
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_manage_reference_of_the_same_object(self):
|
||||
sheerka = self.get_sheerka()
|
||||
@@ -331,7 +312,7 @@ class TestSheerkaPickleHandler(TestUsingMemoryBasedSheerka):
|
||||
def test_i_can_encode_decode_rule(self):
|
||||
sheerka = self.get_sheerka()
|
||||
|
||||
rule = Rule("print", "my rule", "True","Hello world")
|
||||
rule = Rule("print", "my rule", "True", "Hello world")
|
||||
rule.metadata.id = "1"
|
||||
|
||||
to_string = sheerkapickle.encode(sheerka, rule)
|
||||
@@ -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 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"]]}'
|
||||
|
||||
Reference in New Issue
Block a user