Improved PythonEvaluator when dealing with concept class

This commit is contained in:
2020-05-20 04:19:19 +02:00
parent 95dc147bbd
commit d357329f51
16 changed files with 288 additions and 89 deletions
+11 -1
View File
@@ -248,7 +248,7 @@ class Concept:
if token.value in variables: if token.value in variables:
key += VARIABLE_PREFIX + str(variables.index(token.value)) key += VARIABLE_PREFIX + str(variables.index(token.value))
else: else:
#value = token.value[1:-1] if token.type == TokenKind.STRING else token.value # value = token.value[1:-1] if token.type == TokenKind.STRING else token.value
key += token.value key += token.value
first = False first = False
@@ -490,6 +490,16 @@ class InfiniteRecursionResolved:
return self.value return self.value
def ensure_concept(*concepts):
if hasattr(concepts, "__iter__"):
for concept in concepts:
if not isinstance(concept, Concept):
raise TypeError(f"'{concept}' must be a concept")
else:
if not isinstance(concepts, Concept):
raise TypeError(f"'{concepts}' must be a concept")
# ################################ # ################################
# #
# Class created for tests purpose # Class created for tests purpose
+32
View File
@@ -13,6 +13,7 @@ from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConc
from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW
from core.sheerka.ExecutionContext import ExecutionContext from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka_logger import console_handler from core.sheerka_logger import console_handler
from core.tokenizer import Token, TokenKind
from printer.SheerkaPrinter import SheerkaPrinter from printer.SheerkaPrinter import SheerkaPrinter
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event from sdp.sheerkaDataProvider import SheerkaDataProvider, Event
@@ -489,6 +490,37 @@ class Sheerka(Concept):
metadata = [(index_name, key), ("id", concept_id)] if concept_id else (index_name, key) metadata = [(index_name, key), ("id", concept_id)] if concept_id else (index_name, key)
return self._get_unknown(metadata) return self._get_unknown(metadata)
def resolve(self, concept):
if concept is None:
return concept
# if the entry is a concept token, use its values.
if isinstance(concept, Token):
if concept.type != TokenKind.CONCEPT:
return None
concept = concept.value
# if the entry is a tuple
# concept[0] is the name
# concept[1] is the id
if isinstance(concept, tuple):
if concept[1]:
if self.is_known(found := self.get_by_id(concept[1])):
return found
elif concept[0]:
return found if self.is_known(found := self.get_by_name(concept[0])) else None
else:
return None
# otherwise search in db
if isinstance(concept, str):
if self.is_known(found := self.get_by_id(concept)):
return found
if self.is_known(found := self.get_by_name(concept)):
return found
return None
def has_id(self, concept_id): def has_id(self, concept_id):
""" """
Returns True if a concept with this id exists in cache Returns True if a concept with this id exists in cache
@@ -3,6 +3,7 @@ from dataclasses import dataclass
from cache.Cache import Cache from cache.Cache import Cache
from cache.ListCache import ListCache from cache.ListCache import ListCache
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import ensure_concept
from core.sheerka.services.sheerka_service import ServiceObj, BaseService from core.sheerka.services.sheerka_service import ServiceObj, BaseService
@@ -105,6 +106,7 @@ class SheerkaComparisonManager(BaseService):
:return: :return:
""" """
context.log(f"Setting concept {concept_a} is greater than {concept_b}", who=self.NAME) context.log(f"Setting concept {concept_a} is greater than {concept_b}", who=self.NAME)
ensure_concept(concept_a, concept_b)
event_digest = context.event.get_digest() event_digest = context.event.get_digest()
comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, ">", comparison_context) comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, ">", comparison_context)
@@ -121,6 +123,7 @@ class SheerkaComparisonManager(BaseService):
:return: :return:
""" """
context.log(f"Setting concept {concept_a} is less than {concept_b}", who=self.NAME) context.log(f"Setting concept {concept_a} is less than {concept_b}", who=self.NAME)
ensure_concept(concept_a, concept_b)
event_digest = context.event.get_digest() event_digest = context.event.get_digest()
comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, "<", comparison_context) comparison_obj = ComparisonObj(event_digest, prop_name, concept_a.id, concept_b.id, "<", comparison_context)
@@ -1,6 +1,6 @@
import core.utils import core.utils
from core.builtin_concepts import BuiltinConcepts, ErrorConcept from core.builtin_concepts import BuiltinConcepts, ErrorConcept
from core.concept import Concept from core.concept import Concept, DEFINITION_TYPE_DEF, ensure_concept
from core.sheerka.services.sheerka_service import BaseService from core.sheerka.services.sheerka_service import BaseService
from sdp.sheerkaDataProvider import SheerkaDataProviderDuplicateKeyError from sdp.sheerkaDataProvider import SheerkaDataProviderDuplicateKeyError
@@ -30,6 +30,8 @@ class SheerkaCreateNewConcept(BaseService):
:return: digest of the new concept :return: digest of the new concept
""" """
ensure_concept(concept)
sheerka = self.sheerka sheerka = self.sheerka
concept.init_key() concept.init_key()
@@ -49,24 +51,29 @@ class SheerkaCreateNewConcept(BaseService):
# set id before saving in db # set id before saving in db
sheerka.set_id_if_needed(concept, False) sheerka.set_id_if_needed(concept, False)
# update the dictionary of concepts by first key # compute new concepts_by_first_keyword
init_ret_value = self.bnp.get_concepts_by_first_keyword(context, [concept], True) init_ret_value = self.bnp.get_concepts_by_first_keyword(context, [concept], True)
if not init_ret_value.status: if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value)) return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
concepts_by_first_keyword = init_ret_value.body concepts_by_first_keyword = init_ret_value.body
# update resolved dictionary # computes resolved concepts_by_first_keyword
init_ret_value = self.bnp.resolve_concepts_by_first_keyword(context, concepts_by_first_keyword) init_ret_value = self.bnp.resolve_concepts_by_first_keyword(context, concepts_by_first_keyword)
if not init_ret_value.status: if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value)) return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
resolved_concepts_by_first_keyword = init_ret_value.body resolved_concepts_by_first_keyword = init_ret_value.body
concept.freeze_definition_hash() # if everything is fine
concept.freeze_definition_hash()
cache_manager.add_concept(concept) cache_manager.add_concept(concept)
cache_manager.put(sheerka.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, concepts_by_first_keyword) cache_manager.put(sheerka.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, concepts_by_first_keyword)
cache_manager.put(sheerka.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword) cache_manager.put(sheerka.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
if concept.metadata.definition_type == DEFINITION_TYPE_DEF and concept.metadata.definition != concept.name:
# allow search by definition when definition relevant
cache_manager.put(self.sheerka.CONCEPTS_BY_NAME_ENTRY, concept.metadata.definition, concept)
if concept.bnf and init_bnf_ret_value is not None and init_bnf_ret_value.status: if concept.bnf and init_bnf_ret_value is not None and init_bnf_ret_value.status:
sheerka.cache_manager.clear(sheerka.CONCEPTS_GRAMMARS_ENTRY) sheerka.cache_manager.clear(sheerka.CONCEPTS_GRAMMARS_ENTRY)
@@ -41,6 +41,7 @@ class SheerkaModifyConcept(BaseService):
# TODO : update concept by first keyword # TODO : update concept by first keyword
# TODO : update resolved by first keyword # TODO : update resolved by first keyword
# TODO : update concepts grammars # TODO : update concepts grammars
# TODO : update when definition_type = DEFINITION_TYPE_DEF
ret = self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept)) ret = self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
return ret return ret
@@ -2,7 +2,7 @@ import core.builtin_helpers
from cache.SetCache import SetCache from cache.SetCache import SetCache
from core.ast.nodes import python_to_concept from core.ast.nodes import python_to_concept
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept, ConceptParts from core.concept import Concept, ConceptParts, ensure_concept
from core.sheerka.services.sheerka_service import BaseService from core.sheerka.services.sheerka_service import BaseService
GROUP_PREFIX = 'All_' GROUP_PREFIX = 'All_'
@@ -36,6 +36,7 @@ class SheerkaSetsManager(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)
ensure_concept(concept, concept_set)
if BuiltinConcepts.ISA in concept.metadata.props and concept_set in concept.metadata.props[BuiltinConcepts.ISA]: if BuiltinConcepts.ISA in concept.metadata.props and concept_set in concept.metadata.props[BuiltinConcepts.ISA]:
return self.sheerka.ret( return self.sheerka.ret(
@@ -61,9 +62,7 @@ class SheerkaSetsManager(BaseService):
""" """
context.log(f"Adding concept {concept} to set {concept_set}", who=self.NAME) context.log(f"Adding concept {concept} to set {concept_set}", who=self.NAME)
ensure_concept(concept, concept_set)
assert concept.id
assert concept_set.id
set_elements = self.sheerka.cache_manager.get(self.CONCEPTS_GROUPS_ENTRY, concept_set.id) set_elements = self.sheerka.cache_manager.get(self.CONCEPTS_GROUPS_ENTRY, concept_set.id)
if set_elements and concept.id in set_elements: if set_elements and concept.id in set_elements:
@@ -79,6 +78,7 @@ class SheerkaSetsManager(BaseService):
"""Adding multiple concepts at the same time""" """Adding multiple concepts at the same time"""
context.log(f"Adding concepts {concepts} to set {concept_set}", who=self.NAME) context.log(f"Adding concepts {concepts} to set {concept_set}", who=self.NAME)
ensure_concept(concept_set)
already_in_set = [] already_in_set = []
for concept in concepts: for concept in concepts:
res = self.add_concept_to_set(context, concept, concept_set) res = self.add_concept_to_set(context, concept, concept_set)
@@ -103,6 +103,8 @@ class SheerkaSetsManager(BaseService):
:return: :return:
""" """
ensure_concept(concept)
def _get_set_elements(sub_concept): def _get_set_elements(sub_concept):
if not self.isaset(context, sub_concept): if not self.isaset(context, sub_concept):
return self.sheerka.new(BuiltinConcepts.NOT_A_SET, body=concept) return self.sheerka.new(BuiltinConcepts.NOT_A_SET, body=concept)
@@ -151,8 +153,9 @@ class SheerkaSetsManager(BaseService):
if isinstance(a, BuiltinConcepts): # common KSI error ;-) if isinstance(a, BuiltinConcepts): # common KSI error ;-)
raise SyntaxError("Remember that the first parameter of isinstance MUST be a concept") raise SyntaxError("Remember that the first parameter of isinstance MUST be a concept")
if not (isinstance(a, Concept) and isinstance(b, Concept)): ensure_concept(a, b)
return False # if not (isinstance(a, Concept) and isinstance(b, Concept)):
# return False
# TODO, first check the 'isa' property of a # TODO, first check the 'isa' property of a
if not (a.id and b.id): if not (a.id and b.id):
@@ -163,6 +166,7 @@ class SheerkaSetsManager(BaseService):
def isa(self, a, b): def isa(self, a, b):
ensure_concept(a, b)
if BuiltinConcepts.ISA not in a.metadata.props: if BuiltinConcepts.ISA not in a.metadata.props:
return False return False
+4 -7
View File
@@ -363,7 +363,7 @@ def unstr_concept(concept_repr):
return key if key != "" else None, id if id != "" else None return key if key != "" else None, id if id != "" else None
def encode_concept(t, use_concept=False): def encode_concept(t):
""" """
Given a tuple of concept id, concept id Given a tuple of concept id, concept id
Create a valid Python identifier that can be parsed back Create a valid Python identifier that can be parsed back
@@ -371,15 +371,13 @@ def encode_concept(t, use_concept=False):
>>> assert encode_concept(("key", "id")) == "__C__KEY_key__ID_id__C__" >>> assert encode_concept(("key", "id")) == "__C__KEY_key__ID_id__C__"
>>> assert encode_concept((None, "id")) == "__C__KEY_00None00__ID_id__C__" >>> assert encode_concept((None, "id")) == "__C__KEY_00None00__ID_id__C__"
>>> assert encode_concept(("key", None)) == "__C__KEY_key__ID_00None00__C__" >>> assert encode_concept(("key", None)) == "__C__KEY_key__ID_00None00__C__"
>>> assert encode_concept(("key", "id"), True) == "__C__USE_CONCEPT__KEY_key__ID_id__C__"
:param t: :param t:
:param use_concept:
:return: :return:
""" """
key, id_ = (t[0], t[1]) if isinstance(t, tuple) else (t.key, t.id) key, id_ = (t[0], t[1]) if isinstance(t, tuple) else (t.key, t.id)
prefix = "__C__USE_CONCEPT" if use_concept else "__C" prefix = "__C"
sanitized_key = "".join(c if c.isalnum() else "0" for c in key) if key else "00None00" sanitized_key = "".join(c if c.isalnum() else "0" for c in key) if key else "00None00"
return prefix + f"__KEY_{sanitized_key}__ID_{id_ or '00None00'}__C__" return prefix + f"__KEY_{sanitized_key}__ID_{id_ or '00None00'}__C__"
@@ -393,15 +391,14 @@ def decode_concept(text):
:param text: :param text:
:return: :return:
""" """
use_concept = text.startswith("__C__USE_CONCEPT")
m = decode_regex.search(text) m = decode_regex.search(text)
lookup = {"00None00": None} lookup = {"00None00": None}
if m: if m:
key = lookup.get(m.group(1), m.group(1)) key = lookup.get(m.group(1), m.group(1))
id_ = lookup.get(m.group(2), m.group(2)) id_ = lookup.get(m.group(2), m.group(2))
return key, id_, use_concept return key, id_
return None, None, None return None, None
def tokens_index(tokens, sub_tokens, skip=0): def tokens_index(tokens, sub_tokens, skip=0):
+76 -48
View File
@@ -57,8 +57,8 @@ class PythonEvaluator(OneReturnValueEvaluator):
# Do not evaluate if the ast refers to a concept (leave it to ConceptEvaluator) # Do not evaluate if the ast refers to a concept (leave it to ConceptEvaluator)
if isinstance(node.ast_, ast.Expression) and isinstance(node.ast_.body, ast.Name): if isinstance(node.ast_, ast.Expression) and isinstance(node.ast_.body, ast.Name):
c = context.sheerka.get_by_key(node.ast_.body.id) c = context.sheerka.resolve(node.ast_.body.id)
if not context.sheerka.isinstance(c, BuiltinConcepts.UNKNOWN_CONCEPT): if c is not None:
context.log("It's a simple concept. Not for me.", self.name) context.log("It's a simple concept. Not for me.", self.name)
not_for_me = context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=node) not_for_me = context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=node)
return sheerka.ret(self.name, False, not_for_me, parents=[return_value]) return sheerka.ret(self.name, False, not_for_me, parents=[return_value])
@@ -91,54 +91,11 @@ class PythonEvaluator(OneReturnValueEvaluator):
"BuiltinConcepts": core.builtin_concepts.BuiltinConcepts, "BuiltinConcepts": core.builtin_concepts.BuiltinConcepts,
} }
# has to tbe the first, to allow override
method_from_sheerka = self.update_globals_with_sheerka_methods(my_locals, context) method_from_sheerka = self.update_globals_with_sheerka_methods(my_locals, context)
if context.obj: self.update_globals_with_context(my_locals, context)
context.log(f"Concept '{context.obj}' is in context. Adding it and its properties to locals.", self.name) self.update_globals_with_node(my_locals, context, node)
for prop_name in context.obj.variables():
prop_value = context.obj.get_value(prop_name)
if isinstance(prop_value, Concept):
my_locals[prop_name] = context.sheerka.objvalue(prop_value)
else:
my_locals[prop_name] = prop_value
my_locals["self"] = context.obj.body
node_concept = core.ast.nodes.python_to_concept(node.ast_)
unreferenced_names_visitor = UnreferencedNamesVisitor(context.sheerka)
unreferenced_names_visitor.visit(node_concept)
for name in unreferenced_names_visitor.names:
context.log(f"Resolving '{name}'.", self.name)
if name in node.concepts:
context.log(f"Using value from node.", self.name)
concept = node.concepts[name]
return_concept = False
else:
c_key, c_id, return_concept = self.resolve_name(name)
if c_key in my_locals:
context.log(f"Using value from property.", self.name)
continue
context.log(f"Instantiating new concept with {c_key=}, {c_id=}.", self.name)
new = context.sheerka.new
concept = new((None, c_id)) if c_id else new(c_key)
if context.sheerka.isinstance(concept, BuiltinConcepts.UNKNOWN_CONCEPT):
context.log(f"({c_key=}, {c_id=}) is not a concept. Skipping.", self.name)
continue
context.log(f"Evaluating '{concept}'", self.name)
with context.push(self.name, desc=f"Evaluating '{concept}'", obj=concept) as sub_context:
sub_context.local_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
evaluated = context.sheerka.evaluate_concept(sub_context, concept)
sub_context.add_values(return_values=evaluated)
if evaluated.key == concept.key:
my_locals[name] = evaluated if return_concept else context.sheerka.objvalue(evaluated)
if self.locals: # when exta values are given. Add them if self.locals: # when exta values are given. Add them
my_locals.update(self.locals) my_locals.update(self.locals)
@@ -163,6 +120,77 @@ class PythonEvaluator(OneReturnValueEvaluator):
return methods_from_sheerka # to allow access using prefix "sheerka." return methods_from_sheerka # to allow access using prefix "sheerka."
def update_globals_with_context(self, my_locals, context):
if context.obj:
context.log(f"Concept '{context.obj}' is in context. Adding it and its properties to locals.", self.name)
for prop_name in context.obj.variables():
prop_value = context.obj.get_value(prop_name)
if isinstance(prop_value, Concept):
my_locals[prop_name] = context.sheerka.objvalue(prop_value)
else:
my_locals[prop_name] = prop_value
my_locals["self"] = context.obj.body
def update_globals_with_node(self, my_locals, context, node):
node_concept = core.ast.nodes.python_to_concept(node.ast_)
unreferenced_names_visitor = UnreferencedNamesVisitor(context.sheerka)
unreferenced_names_visitor.visit(node_concept)
for name in unreferenced_names_visitor.names:
context.log(f"Resolving '{name}'.", self.name)
if name in node.concepts:
context.log(f"Using value from node.", self.name)
concept = self.resolve_concept(context, node.concepts[name])
elif name in my_locals:
context.log(f"Using value from property.", self.name)
continue
else:
context.log(f"Instantiating new concept with {name}.", self.name)
concept = self.resolve_concept(context, name)
if concept is None:
context.log(f"Concept '{name}' is not found or cannot be instantiated. Skipping.", self.name)
continue
if concept.metadata.is_evaluated:
context.log(f"Concept {name} is already evaluated.", self.name)
else:
context.log(f"Evaluating '{concept}'", self.name)
with context.push(self.name, desc=f"Evaluating '{concept}'", obj=concept) as sub_context:
sub_context.local_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
evaluated = context.sheerka.evaluate_concept(sub_context, concept)
sub_context.add_values(return_values=evaluated)
if evaluated.key != concept.key:
context.log(f"Error while evaluating '{name}'. Skipping.", self.name)
continue
concept = evaluated
my_locals[name] = context.sheerka.objvalue(concept)
@staticmethod
def resolve_concept(context, concept_hint):
if isinstance(concept_hint, Concept):
return concept_hint
concept = context.sheerka.resolve(concept_hint)
if concept is None:
return None
new_instance = context.sheerka.new_from_template(concept, concept.key)
if isinstance(concept_hint, tuple):
# It's means that it comes from PythonParser which have found a concept token (c:xxx:)
# So a concept was explicitly required, not its value
# We mark the concept as already evaluated, so it's body will not be evaluated
new_instance.metadata.is_evaluated = True
return new_instance
@staticmethod @staticmethod
def resolve_name(to_resolve): def resolve_name(to_resolve):
""" """
+20 -4
View File
@@ -141,15 +141,22 @@ class BaseParser:
body=self.error_sink if self.has_error else tree, body=self.error_sink if self.has_error else tree,
try_parsed=try_parse) try_parsed=try_parse)
def get_input_as_text(self, parser_input, custom_switcher=None): def get_input_as_text(self, parser_input, custom_switcher=None, tracker=None):
"""
Recreate back the source code from parser_input
:param parser_input: list of Tokens
:param custom_switcher: map of [TokenKind, overridden values]
:param tracker: keep track of the value overridden by custom_switcher
:return:
"""
if isinstance(parser_input, list): if isinstance(parser_input, list):
return self.get_text_from_tokens(parser_input, custom_switcher) return self.get_text_from_tokens(parser_input, custom_switcher, tracker)
if isinstance(parser_input, ParserResultConcept): if isinstance(parser_input, ParserResultConcept):
parser_input = parser_input.source parser_input = parser_input.source
if "c:" in parser_input: if "c:" in parser_input:
return self.get_text_from_tokens(list(Tokenizer(parser_input)), custom_switcher) return self.get_text_from_tokens(list(Tokenizer(parser_input)), custom_switcher, tracker)
return parser_input return parser_input
@@ -194,7 +201,14 @@ class BaseParser:
return lst return lst
@staticmethod @staticmethod
def get_text_from_tokens(tokens, custom_switcher=None): def get_text_from_tokens(tokens, custom_switcher=None, tracker=None):
"""
Create the source code, from the list of token
:param tokens: list of tokens
:param custom_switcher: to override the behaviour (the return value) of some token
:param tracker: keep track of the original token value when custom switched
:return:
"""
if tokens is None: if tokens is None:
return "" return ""
res = "" res = ""
@@ -213,6 +227,8 @@ class BaseParser:
for token in tokens: for token in tokens:
value = switcher.get(token.type, lambda t: t.value)(token) value = switcher.get(token.type, lambda t: t.value)(token)
res += value res += value
if tracker is not None and token.type in custom_switcher:
tracker[value] = token.value
return res return res
@staticmethod @staticmethod
+4 -3
View File
@@ -72,11 +72,12 @@ class PythonParser(BaseParser):
tree = None tree = None
python_switcher = { python_switcher = {
TokenKind.CONCEPT: lambda t: core.utils.encode_concept(t.value, True) TokenKind.CONCEPT: lambda t: core.utils.encode_concept(t.value)
} }
try: try:
source = self.get_input_as_text(parser_input, python_switcher) tracker = {}
source = self.get_input_as_text(parser_input, python_switcher, tracker)
source = source.strip() source = source.strip()
parser_input = parser_input if isinstance(parser_input, str) else source parser_input = parser_input if isinstance(parser_input, str) else source
@@ -108,7 +109,7 @@ class PythonParser(BaseParser):
BuiltinConcepts.PARSER_RESULT, BuiltinConcepts.PARSER_RESULT,
parser=self, parser=self,
source=parser_input, source=parser_input,
body=PythonNode(parser_input, tree), body=PythonNode(parser_input, tree, tracker),
try_parsed=None)) try_parsed=None))
self.log_result(context, parser_input, ret) self.log_result(context, parser_input, ret)
+15
View File
@@ -122,3 +122,18 @@ class BaseTest:
concept.bnf = expression or StrMatch(name) concept.bnf = expression or StrMatch(name)
concept.metadata.definition_type = DEFINITION_TYPE_BNF concept.metadata.definition_type = DEFINITION_TYPE_BNF
return concept return concept
@staticmethod
def def_concept(name, definition, variables=None, **kwargs):
concept = Concept(name=name, definition=definition, definition_type=DEFINITION_TYPE_DEF)
if variables:
for v in variables:
concept.def_var(v)
if kwargs:
for k, v in kwargs.items():
if k in ("body", "pre", "post", "where"):
setattr(concept.metadata, k, v)
else:
concept.metadata.variables[k] = v
return concept
@@ -139,6 +139,23 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
assert res.status assert res.status
def test_i_can_get_by_name_when_created_with_def_definition(self):
sheerka = self.get_sheerka(cache_only=False)
context = self.get_context(sheerka)
concept = self.def_concept("plus", "a plus b", ["a", "b"])
res = sheerka.create_new_concept(context, concept)
assert res.status
assert sheerka.get_by_name(concept.name) == concept
assert sheerka.get_by_name(concept.metadata.definition) == concept
concept = Concept(name="foo", definition="foo", definition_type=DEFINITION_TYPE_DEF)
res = sheerka.create_new_concept(context, concept)
assert res.status
assert sheerka.get_by_name(concept.name) == concept # it's not a list, ie the entry is not duplicated
class TestSheerkaCreateNewConceptFileBased(TestUsingFileBasedSheerka): class TestSheerkaCreateNewConceptFileBased(TestUsingFileBasedSheerka):
def test_i_can_add_several_concepts(self): def test_i_can_add_several_concepts(self):
+50
View File
@@ -4,6 +4,7 @@ import pytest
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, UserInputConcept from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, UserInputConcept
from core.concept import Concept, PROPERTIES_TO_SERIALIZE, ConceptParts from core.concept import Concept, PROPERTIES_TO_SERIALIZE, ConceptParts
from core.sheerka.Sheerka import Sheerka, BASE_NODE_PARSER_CLASS from core.sheerka.Sheerka import Sheerka, BASE_NODE_PARSER_CLASS
from core.tokenizer import Token, TokenKind, Tokenizer
from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -274,6 +275,55 @@ class TestSheerkaUsingMemoryBasedSheerka(TestUsingMemoryBasedSheerka):
sheerka = self.get_sheerka() sheerka = self.get_sheerka()
assert not sheerka.is_success(sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS)) assert not sheerka.is_success(sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS))
@pytest.mark.parametrize("concept, expected", [
(None, None),
("foo", ["foo", "foo2"]),
("bar", "bar"),
("1001", "foo"), # by id take precedence over by name
("plus", "plus"),
("a mult b", "mult"),
("unknown", None),
# by tuple
((None, None), None),
(("foo", None), ["foo", "foo2"]),
(("foo", "1002"), "foo2"),
((None, "1001"), "foo"),
(("plus", None), "plus"),
(("1001", None), "1001"),
(("unknown", None), None),
((None, "unknown"), None),
#
# by token
(Token(TokenKind.CONCEPT, (None, None), 0, 0, 0), None),
(Token(TokenKind.CONCEPT, ("foo", None), 0, 0, 0), ["foo", "foo2"]),
])
def test_i_can_resolve_concept(self, concept, expected):
sheerka, context, *concepts = self.init_concepts(
"foo",
Concept("foo", body="another one"),
"bar",
self.def_concept("plus", "a plus b", ["a", "b"]),
Concept("a mult b").def_var("a").def_var("b"),
Concept("1001"),
)
cmap = {k: concepts[i] for i, k in enumerate(["foo", "foo2", "bar", "plus", "mult", "1001"])}
cmap[None] = None
if isinstance(expected, list):
assert sheerka.resolve(concept) == [cmap[e] for e in expected]
else:
assert sheerka.resolve(concept) == cmap[expected]
def test_i_can_resolve_when_searching_by_definition(self):
sheerka, context, plus = self.init_concepts(
self.def_concept("plus", "a plus b", ["a", "b"]),
create_new=True
)
assert sheerka.resolve("a plus b") == plus
class TestSheerkaUsingFileBasedSheerka(TestUsingFileBasedSheerka): class TestSheerkaUsingFileBasedSheerka(TestUsingFileBasedSheerka):
+3 -5
View File
@@ -198,7 +198,6 @@ def test_encode_concept_key_id():
assert core.utils.encode_concept(("key", "id")) == "__C__KEY_key__ID_id__C__" assert core.utils.encode_concept(("key", "id")) == "__C__KEY_key__ID_id__C__"
assert core.utils.encode_concept((None, "id")) == "__C__KEY_00None00__ID_id__C__" assert core.utils.encode_concept((None, "id")) == "__C__KEY_00None00__ID_id__C__"
assert core.utils.encode_concept(("key", None)) == "__C__KEY_key__ID_00None00__C__" assert core.utils.encode_concept(("key", None)) == "__C__KEY_key__ID_00None00__C__"
assert core.utils.encode_concept(("key", "id"), True) == "__C__USE_CONCEPT__KEY_key__ID_id__C__"
assert core.utils.encode_concept(("k + y", "id")) == "__C__KEY_k000y__ID_id__C__" assert core.utils.encode_concept(("k + y", "id")) == "__C__KEY_k000y__ID_id__C__"
concept = Concept("foo").init_key() concept = Concept("foo").init_key()
@@ -209,7 +208,6 @@ def test_encode_concept_key_id():
def test_decode_concept_key_id(): def test_decode_concept_key_id():
assert core.utils.decode_concept("__C__KEY_key__ID_id__C__") == ("key", "id", False) assert core.utils.decode_concept("__C__KEY_key__ID_id__C__") == ("key", "id")
assert core.utils.decode_concept("__C__KEY_00None00__ID_id__C__") == (None, "id", False) assert core.utils.decode_concept("__C__KEY_00None00__ID_id__C__") == (None, "id")
assert core.utils.decode_concept("__C__KEY_key__ID_00None00__C__") == ("key", None, False) assert core.utils.decode_concept("__C__KEY_key__ID_00None00__C__") == ("key", None)
assert core.utils.decode_concept("__C__USE_CONCEPT__KEY_key__ID_id__C__") == ("key", "id", True)
+22 -7
View File
@@ -1,14 +1,14 @@
import pytest import pytest
from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts
from core.concept import Concept from core.concept import Concept, DEFINITION_TYPE_DEF
from evaluators.PythonEvaluator import PythonEvaluator from evaluators.PythonEvaluator import PythonEvaluator
from parsers.PythonParser import PythonNode, PythonParser from parsers.PythonParser import PythonNode, PythonParser
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
def get_context_name(context): def get_concept_name(concept):
return context.name return concept.name
class TestPythonEvaluator(TestUsingMemoryBasedSheerka): class TestPythonEvaluator(TestUsingMemoryBasedSheerka):
@@ -118,23 +118,38 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka):
context = self.get_context() context = self.get_context()
context.sheerka.add_in_cache(Concept("foo", body="2")) context.sheerka.add_in_cache(Concept("foo", body="2"))
parsed = PythonParser().parse(context, "get_context_name(c:foo:)") parsed = PythonParser().parse(context, "get_concept_name(c:foo:)")
python_evaluator = PythonEvaluator() python_evaluator = PythonEvaluator()
python_evaluator.locals["get_context_name"] = get_context_name python_evaluator.locals["get_concept_name"] = get_concept_name
evaluated = python_evaluator.eval(context, parsed) evaluated = python_evaluator.eval(context, parsed)
assert evaluated.status assert evaluated.status
assert evaluated.value == "foo" assert evaluated.value == "foo"
# sanity, does not work otherwise # sanity, does not work otherwise
parsed = PythonParser().parse(context, "get_context_name(foo)") parsed = PythonParser().parse(context, "get_concept_name(foo)")
python_evaluator = PythonEvaluator() python_evaluator = PythonEvaluator()
python_evaluator.locals["get_context_name"] = get_context_name python_evaluator.locals["get_concept_name"] = get_concept_name
evaluated = python_evaluator.eval(context, parsed) evaluated = python_evaluator.eval(context, parsed)
assert not evaluated.status assert not evaluated.status
assert evaluated.body.body.args[0] == "'int' object has no attribute 'name'" assert evaluated.body.body.args[0] == "'int' object has no attribute 'name'"
def test_i_can_call_function_with_complex_concepts(self):
sheerka, context, plus, mult = self.init_concepts(
self.def_concept("plus", "a plus b", ["a", "b"]),
self.def_concept("mult", "a mult b", ["a", "b"]),
)
parsed = PythonParser().parse(context, "is_greater_than(BuiltinConcepts.PRECEDENCE, mult, plus)")
python_evaluator = PythonEvaluator()
evaluated = python_evaluator.eval(context, parsed)
assert evaluated.status
assert sheerka.get_concepts_weights(BuiltinConcepts.PRECEDENCE) == {'1001': 1, '1002': 2}
# @pytest.mark.parametrize("text, concept_key, concept_id, use_concept", [ # @pytest.mark.parametrize("text, concept_key, concept_id, use_concept", [
# ("__C__key__C__", "key", None, False), # ("__C__key__C__", "key", None, False),
# ("__C__key__id__C__", "key", "id", False), # ("__C__key__id__C__", "key", "id", False),
+9 -4
View File
@@ -1,9 +1,10 @@
import ast import ast
import core.utils
import pytest import pytest
from core.builtin_concepts import ParserResultConcept, NotForMeConcept from core.builtin_concepts import ParserResultConcept, NotForMeConcept
from core.tokenizer import Tokenizer, LexerError from core.tokenizer import Tokenizer, LexerError
from parsers.PythonParser import PythonNode, PythonParser, PythonErrorNode from parsers.PythonParser import PythonNode, PythonParser, PythonErrorNode
import core.utils
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -72,12 +73,16 @@ class TestPythonParser(TestUsingMemoryBasedSheerka):
assert res.value.get_value("reason")[0].text == error_text assert res.value.get_value("reason")[0].text == error_text
def test_i_can_parse_a_concept(self): def test_i_can_parse_a_concept(self):
text = "c:name|key: + 1" text = "c:name|id: + 1"
parser = PythonParser() parser = PythonParser()
res = parser.parse(self.get_context(), text) res = parser.parse(self.get_context(), text)
encoded = core.utils.encode_concept(("name", "id"))
assert res assert res
assert res.value.value == PythonNode( assert res.value.value == PythonNode(
"c:name|key: + 1", "c:name|id: + 1",
ast.parse(core.utils.encode_concept(("name", "key"), True) + "+1", mode="eval")) ast.parse(encoded + "+1", mode="eval"))
assert res.value.value.concepts == {
encoded: ("name", "id")
}