Refactored to use a single implementation for concept evaluation

This commit is contained in:
2019-12-21 15:08:06 +01:00
parent b24b858b81
commit 41e0885486
17 changed files with 920 additions and 644 deletions
+48 -11
View File
@@ -41,10 +41,11 @@ class BuiltinConcepts(Enum):
INVALID_RETURN_VALUE = "invalid return value" # the return value of an evaluator is not correct
CONCEPT_ALREADY_DEFINED = "concept already defined" # when you try to add the same concept twice
NOP = "no operation" # no operation concept. Does nothing
PROPERTY_EVAL_ERROR = "property evaluation error" # cannot evaluate a property of a concept
CONCEPT_EVAL_ERROR = "concept evaluation error" # cannot evaluate a property or metadata of a concept
ENUMERATION = "enum" # represents a list or a set
LIST = "list" # represents a list
CANNOT_RESOLVE_VALUE_ERROR = "value cannot be resolved" # don't know how to find concept value
CONCEPT_NOT_INITIALIZED = "concept not evaluated" # Try to work on a concept that is not evaluated
NODE = "node"
GENERIC_NODE = "generic node"
@@ -56,6 +57,21 @@ class BuiltinConcepts(Enum):
def __str__(self):
return "__" + self.name
BuiltinErrors = [str(e) for e in {
BuiltinConcepts.ERROR,
BuiltinConcepts.UNKNOWN_CONCEPT,
BuiltinConcepts.CONCEPT_TOO_LONG,
BuiltinConcepts.UNKNOWN_PROPERTY,
BuiltinConcepts.TOO_MANY_SUCCESS,
BuiltinConcepts.TOO_MANY_ERRORS,
BuiltinConcepts.INVALID_RETURN_VALUE,
BuiltinConcepts.CONCEPT_ALREADY_DEFINED,
BuiltinConcepts.CONCEPT_EVAL_ERROR,
BuiltinConcepts.CANNOT_RESOLVE_VALUE_ERROR,
BuiltinConcepts.CONCEPT_NOT_INITIALIZED
}]
"""
Some concepts have a specific implementation
It's mainly to a have proper __repr__ implementation, or because they are singleton (is_unique=True)
@@ -251,31 +267,52 @@ class AfterEvaluationConcept(Concept):
super().__init__(BuiltinConcepts.AFTER_EVALUATION, True, True, BuiltinConcepts.AFTER_EVALUATION)
class PropertyEvalError(Concept):
def __init__(self, property_name=None, concept=None, error=None):
super().__init__(BuiltinConcepts.PROPERTY_EVAL_ERROR,
class ConceptEvalError(Concept):
def __init__(self, error=None, concept=None, property_name=None):
super().__init__(BuiltinConcepts.CONCEPT_EVAL_ERROR,
True,
False,
BuiltinConcepts.PROPERTY_EVAL_ERROR,
property_name)
BuiltinConcepts.CONCEPT_EVAL_ERROR,
error)
self.set_prop("concept", concept)
self.set_prop("error", error)
self.set_prop("property_name", property_name)
def __repr__(self):
return f"PropertyEvalError(property={self.property_name}, concept={self.concept}), error={self.error})"
return f"ConceptEvalError(error={self.error}, concept={self.concept}, property={self.property_name})"
@property
def error(self):
return self.body
@property
def concept(self):
return self.props["concept"].value
@property
def error(self):
return self.props["error"].value
def property_name(self):
return self.props["property_name"].value
class ConceptNotInitialized(Concept):
def __init__(self, error=None, concept=None, property_name=None):
super().__init__(BuiltinConcepts.CONCEPT_NOT_INITIALIZED,
True,
False,
BuiltinConcepts.CONCEPT_NOT_INITIALIZED,
error)
self.set_prop("concept", concept)
def __repr__(self):
return f"ConceptNotInitialized(error={self.error}, concept={self.concept})"
@property
def property_name(self):
def error(self):
return self.body
@property
def concept(self):
return self.props["concept"].value
class EnumerationConcept(Concept):
def __init__(self, iteration=None):
+20 -2
View File
@@ -1,4 +1,6 @@
import ast
import logging
import core.ast.nodes
from core.ast.nodes import CallNodeConcept, GenericNodeConcept
from core.ast.visitors import UnreferencedNamesVisitor
@@ -31,12 +33,13 @@ def is_same_success(sheerka, return_values):
return True
def expect_one(context, return_values):
def expect_one(context, return_values, logger=None):
"""
Checks if there is at least one success return value
If there is more than one, check if it's the same value
:param context:
:param return_values:
:param logger:
:return:
"""
@@ -58,7 +61,6 @@ def expect_one(context, return_values):
# remove errors when a winner is found
if number_of_successful == 1:
# log.debug(f"1 / {total_items} good item found.")
return sheerka.ret(
context.who,
True,
@@ -74,6 +76,10 @@ def expect_one(context, return_values):
successful_results[0].value,
parents=return_values)
else:
if logger and logger.isEnabledFor(logging.DEBUG):
context.log(logger, f"Too many successful results found by expect_one()", context.who)
for s in successful_results:
context.log(logger, f"-> {s}", context.who)
return sheerka.ret(
context.who,
False,
@@ -81,6 +87,18 @@ def expect_one(context, return_values):
parents=return_values)
# only errors, i cannot help you
if logger and logger.isEnabledFor(logging.DEBUG):
context.log(logger, f"Too many errors found by expect_one()", context.who)
for s in successful_results:
context.log(logger, f"-> {s}", context.who)
if len(return_values) == 1:
return sheerka.ret(
context.who,
False,
return_values[0],
parents=return_values)
else:
return sheerka.ret(
context.who,
False,
+1 -1
View File
@@ -6,7 +6,6 @@ from core.sheerka_logger import get_logger
import core.utils
from core.tokenizer import Tokenizer, TokenKind
PROPERTIES_FOR_DIGEST = ("name", "key",
"definition", "definition_type",
"is_builtin", "is_unique",
@@ -44,6 +43,7 @@ class ConceptMetadata:
definition_type: str # definition can be done with something else than regex
desc: str # possible description for the concept
id: str # unique identifier for a concept. The id will never be modified (but the key can)
is_evaluated: bool = False # True is the concept is evaluated by sheerka.eval_concept()
class Concept:
+67 -27
View File
@@ -1,6 +1,6 @@
from dataclasses import dataclass, field
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors
from core.concept import Concept, ConceptParts, PROPERTIES_FOR_DIGEST
from parsers.BaseParser import BaseParser
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event, SheerkaDataProviderDuplicateKeyError
@@ -11,7 +11,10 @@ from core.sheerka_logger import console_handler, get_logger
import logging
concept_evaluation_steps = [BuiltinConcepts.EVALUATION, BuiltinConcepts.AFTER_EVALUATION]
CONCEPT_EVALUATION_STEPS = [
BuiltinConcepts.BEFORE_EVALUATION,
BuiltinConcepts.EVALUATION,
BuiltinConcepts.AFTER_EVALUATION]
CONCEPT_LEXER_PARSER_CLASS = "parsers.ConceptLexerParser.ConceptLexerParser"
DEBUG_TAB_SIZE = 4
@@ -416,17 +419,21 @@ class Sheerka(Concept):
source = getattr(concept.metadata, part_key.value)
if source is None or not isinstance(source, str) or source == "":
# the only sources that I am sure to parse are strings
# I refuse empty strings for performance, I don't want to handle useless NOPConcepts
# I refuse empty strings for performance matters, I don't want to handle useless NOPConcepts
continue
else:
to_parse = self.ret(context.who, True, self.new(BuiltinConcepts.USER_INPUT, body=source))
concept.cached_asts[part_key] = self.execute(context, to_parse, steps, logger)
for prop in concept.props:
to_parse = self.ret(context.who, True, self.new(BuiltinConcepts.USER_INPUT, body=concept.props[prop].value))
if concept.props[prop].value:
to_parse = self.ret(
context.who,
True,
self.new(BuiltinConcepts.USER_INPUT, body=concept.props[prop].value))
concept.cached_asts[prop] = self.execute(context, to_parse, steps)
# updates the code of the reference when possible
# Updates the cache of concepts when possible
if concept.key in self.concepts_cache:
entry = self.concepts_cache[concept.key]
if isinstance(entry, list):
@@ -435,32 +442,69 @@ class Sheerka(Concept):
else:
self.concepts_cache[concept.key].cached_asts = concept.cached_asts
def eval_concept(self, context, concept: Concept, properties_to_eval=None, logger=None):
def evaluate_concept(self, context, concept: Concept, logger=None):
"""
Evaluation a concept
It means that if the where clause is True, will evaluate the body
:param context:
:param concept:
:param properties_to_eval:
:param logger:
:return:
:return: value of the evaluation or error
"""
if concept.metadata.is_evaluated:
return concept
def _resolve(resolve_context, return_value):
r = self.execute(resolve_context, return_value, CONCEPT_EVALUATION_STEPS, logger or self.log)
return core.builtin_helpers.expect_one(context, r)
# WHERE condition should already be validated by the parser.
# It's a mandatory condition for the concept before it can be recognized
#
# TODO : Validate the PRE condition
#
if len(concept.cached_asts) == 0:
self.initialize_concept_asts(context, concept, logger)
if properties_to_eval is None:
properties_to_eval = ["where", "pre", "post", "body", "props"]
# to make sure of the order, it don't use ConceptParts.get_parts()
# props must be evaluated first
properties_to_eval = ["props", "where", "pre", "post", "body"]
for prop in properties_to_eval:
if prop == "props":
pass
for prop_to_eval in properties_to_eval:
if prop_to_eval == "props":
for prop_name in (p for p in concept.props if p in concept.cached_asts):
sub_context = context.push(desc=f"Evaluating property '{prop_name}'")
res = _resolve(sub_context, concept.cached_asts[prop_name])
if res.status:
concept.set_prop(prop_name, res.value)
else:
part_key = ConceptParts(prop)
if concept.cached_asts[part_key] is None:
continue
res = self.execute(context, concept.cached_asts[part_key], concept_evaluation_steps, logger)
res = core.builtin_helpers.expect_one(context, res)
setattr(concept.metadata, prop, res.value)
return self.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
body=res.value,
concept=concept,
property_name=prop_name)
else:
part_key = ConceptParts(prop_to_eval)
if part_key in concept.cached_asts and concept.cached_asts[part_key] is not None:
sub_context = context.push(desc=f"Evaluating '{part_key}'", obj=concept)
res = _resolve(sub_context, concept.cached_asts[part_key])
if res.status:
setattr(concept.metadata, prop_to_eval, res.value)
else:
return self.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
body=res.value,
concept=concept,
property_name=part_key)
#
# TODO : Validate the POST condition
#
concept.init_key() # only does it if needed
concept.metadata.is_evaluated = True
return concept
def add_in_cache(self, concept: Concept):
"""
@@ -601,16 +645,16 @@ class Sheerka(Concept):
return (self.value(obj) for obj in objs)
def is_success(self, obj):
if isinstance(obj, bool):
if isinstance(obj, bool): # quick win
return obj
if self.isinstance(obj, BuiltinConcepts.RETURN_VALUE):
if isinstance(obj, ReturnValueConcept):
return obj.status
if self.isinstance(obj, BuiltinConcepts.ERROR):
if isinstance(obj, Concept) and obj.metadata.is_builtin and obj.key in BuiltinErrors:
return False
return False
return obj
def isinstance(self, a, b):
"""
@@ -724,10 +768,6 @@ class Sheerka(Concept):
log_format = "%(message)s"
log_level = logging.INFO
# logging.root.setLevel(log_level)
# fmt = logging.Formatter(log_format, None, "%")
# console_handler.setFormatter(fmt)
logging.basicConfig(format=log_format, level=log_level, handlers=[console_handler])
+8
View File
@@ -195,6 +195,14 @@ class Tokenizer:
yield Token(TokenKind.AMPER, "&", self.i, self.line, self.column)
self.i += 1
self.column += 1
elif c == "<":
yield Token(TokenKind.LESS, "<", self.i, self.line, self.column)
self.i += 1
self.column += 1
elif c == ">":
yield Token(TokenKind.GREATER, ">", self.i, self.line, self.column)
self.i += 1
self.column += 1
elif c == "\n" or c == "\r":
newline = self.eat_newline(self.i)
yield Token(TokenKind.NEWLINE, newline, self.i, self.line, self.column)
+4 -2
View File
@@ -3,6 +3,7 @@ from core.builtin_concepts import ParserResultConcept, ReturnValueConcept
from core.builtin_helpers import get_names
from core.concept import Concept
from evaluators.BaseEvaluator import OneReturnValueEvaluator
from parsers.BaseParser import NotInitializedNode
from parsers.ConceptLexerParser import ParsingExpression, ParsingExpressionVisitor
from parsers.DefaultParser import DefConceptNode
@@ -58,7 +59,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
# put back the sources
part_ret_val = getattr(def_concept_node, prop)
if not isinstance(part_ret_val, ReturnValueConcept) or not part_ret_val.status:
continue # not quite sure that it's possible
continue # Nothing to do is not initialized
# update the parts
source = self.get_source(part_ret_val)
@@ -82,7 +83,8 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
# finish initialisation
concept.init_key(def_concept_node.name.tokens)
concept.add_codes(def_concept_node.get_asts())
if sheerka.is_success(def_concept_node.definition):
if not isinstance(def_concept_node.definition, NotInitializedNode) and \
sheerka.is_success(def_concept_node.definition):
concept.bnf = def_concept_node.definition.value.value
ret = sheerka.create_new_concept(context, concept)
+15 -33
View File
@@ -1,5 +1,4 @@
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts
import core.builtin_helpers
from core.concept import Concept, ConceptParts
from evaluators.BaseEvaluator import OneReturnValueEvaluator
@@ -31,42 +30,25 @@ class ConceptEvaluator(OneReturnValueEvaluator):
concept = return_value.value.value
context.log(self.verbose_log, f"Evaluating concept {concept}.", self.name)
# pre condition should already be validated by the parser.
# It's a mandatory condition for the concept before it can be recognized
# If the concept that is requested is in the context(at least its name), drop the call.
# Why ?
# If we evaluate Concept("foo", body="a").set_prop("a", "'property_a'")
# The body should be 'property_a', and not a concept called a in our universe
if context.obj and concept.name in context.obj.props:
return sheerka.ret(self.name, False, sheerka.new(BuiltinConcepts.NOT_FOR_ME), parents=[return_value])
if len(concept.cached_asts) == 0:
sheerka.initialize_concept_asts(context, concept, self.verbose_log)
evaluated = sheerka.evaluate_concept(context, concept, self.verbose_log)
# TODO; check pre
# if pre is not true, return Concept with a false value
# Evaluate the properties
for prop in concept.props:
sub_context = context.push(self.name, desc=f"Evaluating property '{prop}'", obj=concept)
res = self.evaluate_parsing(sheerka, sub_context, concept.cached_asts[prop])
if res.status:
concept.set_prop(prop, res.value)
else:
if evaluated.key != concept.key:
# evaluated.key != concept.key means that we have transformed the concept
# When you successfully evaluate an error, the status should not be false
return sheerka.ret(
self.name,
False,
sheerka.new(BuiltinConcepts.PROPERTY_EVAL_ERROR, body=prop, concept=concept, error=res.value),
evaluated,
parents=[return_value])
# Returns the concept when no body
if ConceptParts.BODY not in concept.cached_asts:
return sheerka.ret(self.name, True, concept, parents=[return_value])
# Evaluate the body otherwise
body = concept.cached_asts[ConceptParts.BODY]
if body is None:
raise NotImplementedError("Seems weird !")
sub_context = context.push(self.name, desc="Evaluating body", obj=concept)
res = self.evaluate_parsing(sheerka, sub_context, body)
return sheerka.ret(self.name, res.status, res.value, parents=[return_value])
def evaluate_parsing(self, sheerka, context, parsing_result):
res = sheerka.execute(context, parsing_result, self.evaluation_steps, self.log)
res = core.builtin_helpers.expect_one(context, res)
return res
if ConceptParts.BODY not in evaluated.cached_asts:
return sheerka.ret(self.name, True, evaluated, parents=[return_value])
else:
return sheerka.ret(self.name, True, evaluated.body, parents=[return_value])
+2 -1
View File
@@ -54,7 +54,8 @@ class ConceptNodeEvaluator(OneReturnValueEvaluator):
def update_concept(self, sheerka, concept, underlying, init_empty_body=True):
"""
Updates the property of the concept
Updates the properties of the concept
Goes in recursion if the property is a concept
"""
def _add_prop(c, prop_name, value):
+12 -5
View File
@@ -28,6 +28,7 @@ class PythonEvaluator(OneReturnValueEvaluator):
node = return_value.value.value
try:
context.log(self.verbose_log, f"Evaluating python node {node}.", self.name)
my_locals = self.get_locals(context, node.ast_)
context.log(self.verbose_log, f"locals={my_locals}", self.name)
@@ -51,6 +52,7 @@ class PythonEvaluator(OneReturnValueEvaluator):
if context.obj:
context.log(self.verbose_log,
f"Concept '{context.obj}' is in context. Adding its properties to locals if any.", self.name)
for prop_name, prop_value in context.obj.props.items():
my_locals[prop_name] = prop_value.value
@@ -60,18 +62,23 @@ class PythonEvaluator(OneReturnValueEvaluator):
for name in unreferenced_names_visitor.names:
context.log(self.verbose_log, f"Resolving '{name}'.", self.name)
if name in my_locals:
context.log(self.verbose_log, f"Using value from property.", self.name)
continue
concept = context.sheerka.new(name)
if context.sheerka.isinstance(concept, BuiltinConcepts.UNKNOWN_CONCEPT):
context.log(self.verbose_log, f"'{name}' is not a concept. Skipping.", self.name)
continue
context.log(self.verbose_log, f"'{name}' is a concept. Evaluating body.", self.name)
sub_context = context.push(self.name, desc=f"Evaluating {concept}'s body", obj=concept)
context.log(self.verbose_log, f"'{name}' is a concept. Evaluating.", self.name)
sub_context = context.push(self.name, desc=f"Evaluating '{concept}'", obj=concept)
sub_context.log_new(self.verbose_log)
context.sheerka.eval_concept(sub_context, concept, ["body"], self.verbose_log)
evaluated = context.sheerka.evaluate_concept(sub_context, concept, self.verbose_log)
if not context.sheerka.isa(concept.body, BuiltinConcepts.ERROR):
my_locals[name] = concept.body
if evaluated.key == concept.key:
my_locals[name] = evaluated.body or evaluated
return my_locals
+1 -73
View File
@@ -371,7 +371,7 @@ class DefaultParser(BaseParser):
self.sheerka.new(BuiltinConcepts.USER_INPUT, body=tokens))
steps = [BuiltinConcepts.PARSING]
parsed = self.sheerka.execute(new_context, to_parse, steps, self.verbose_log)
parsing_result = core.builtin_helpers.expect_one(new_context, parsed)
parsing_result = core.builtin_helpers.expect_one(new_context, parsed, self.verbose_log)
if not parsing_result.status:
self.add_error(parsing_result.value)
continue
@@ -379,75 +379,3 @@ class DefaultParser(BaseParser):
asts_found_by_parts[keyword] = parsing_result
return asts_found_by_parts
# def parse_expression(self):
# return self.parse_addition()
#
# def parse_addition(self):
# left = self.parse_multiply()
# token = self.get_token()
# if token is None or token.type == TokenKind.EOF:
# return left
#
# if token.type == TokenKind.NUMBER: # example 15 +5 or 15 -5
# right = self.parse_addition()
# return BinaryNode(self.collect_tokens(left, token, right), TokenKind.PLUS, left, right)
#
# if token.type not in (TokenKind.PLUS, TokenKind.MINUS):
# return left
#
# self.next_token()
# right = self.parse_addition()
# return BinaryNode(self.collect_tokens(left, token, right), token.type, left, right)
#
# def parse_multiply(self):
# left = self.parse_atom()
# token = self.get_token()
# if token is None or token.type == TokenKind.EOF:
# return left
#
# if token.type not in (TokenKind.STAR, TokenKind.SLASH):
# return left
#
# self.next_token()
# right = self.parse_multiply()
# return BinaryNode(self.collect_tokens(left, token, right), token.type, left, right)
#
# def parse_atom(self):
# token = self.get_token()
# if token.type == TokenKind.NUMBER:
# self.next_token()
# return NumberNode([token], float(token.value) if '.' in token.value else int(token.value))
# elif token.type == TokenKind.STRING:
# self.next_token()
# return StringNode([token], token.value[1:-1], token.value[0])
# elif token.type == TokenKind.IDENTIFIER:
# if token.value == "true":
# self.next_token()
# return TrueNode([token])
# elif token.value == "false":
# self.next_token()
# return FalseNode([token])
# elif token.value == "null":
# self.next_token()
# return NullNode([token])
# else:
# self.next_token()
# return VariableNode([token], token.value)
# elif token.type == TokenKind.LPAR:
# self.next_token()
# exp = self.parse_expression()
# token = self.get_token()
# self.next_token()
#
# if token.type != TokenKind.RPAR:
# error = UnexpectedTokenErrorNode([token], "Right parenthesis not found.", [TokenKind.RPAR])
# self.add_error(error)
# return error
#
# return exp
# else:
# error = UnexpectedTokenErrorNode([token], "Unexpected token",
# [TokenKind.NUMBER, TokenKind.STRING, TokenKind.IDENTIFIER, "true", "false",
# "null", TokenKind.LPAR])
# return self.add_error(error)
+39 -130
View File
@@ -35,9 +35,12 @@ def test_i_can_match(ret_val, expected):
assert ConceptEvaluator().matches(context, ret_val) == expected
def test_concept_is_returned_when_no_body():
def test_i_can_evaluate_concept():
context = get_context()
concept = Concept(name="one").init_key()
concept = Concept(name="foo",
where="1",
pre="2",
post="3").set_prop("a", "4").set_prop("b", "5")
evaluator = ConceptEvaluator()
item = get_return_value(concept)
@@ -45,13 +48,20 @@ def test_concept_is_returned_when_no_body():
assert result.who == evaluator.name
assert result.status
assert result.value == concept
assert result.value == Concept(name="foo",
where=1,
pre=2,
post=3).set_prop("a", 4).set_prop("b", 5).init_key()
assert result.parents == [item]
def test_body_is_evaluated_when_python_body():
def test_body_is_returned_when_defined():
context = get_context()
concept = Concept(name="one", body="1").init_key()
concept = Concept(name="foo",
body="'I have a value'",
where="1",
pre="2",
post="3").set_prop("a", "4").set_prop("b", "5")
evaluator = ConceptEvaluator()
item = get_return_value(concept)
@@ -59,136 +69,23 @@ def test_body_is_evaluated_when_python_body():
assert result.who == evaluator.name
assert result.status
assert result.value == 1
assert result.value == "I have a value"
assert result.parents == [item]
def test_body_is_evaluated_when_concept_body():
def test_i_cannot_eval_if_with_the_same_name_is_defined_in_the_context():
# If we evaluate Concept("foo", body="a").set_prop("a", "'property_a'")
# ConceptEvaluator will be called to resolve 'a' while we know that 'a' refers to the string 'property_a'
context = get_context()
concept_one = Concept(name="one").init_key()
context.sheerka.add_in_cache(concept_one)
concept_un = Concept(name="un", body="one").init_key()
context.obj = Concept("other").set_prop("foo", "'some_other_value'")
concept = Concept(name="foo")
evaluator = ConceptEvaluator()
item = get_return_value(concept_un)
result = evaluator.eval(context, item)
item = get_return_value(concept)
result = ConceptEvaluator().eval(context, item)
assert result.who == evaluator.name
assert result.status
assert result.value == concept_one
assert result.parents == [item]
def test_body_is_evaluated_when_concept_body_with_a_body():
"""
The concept refers to another concept which has a body
:return:
"""
context = get_context()
concept_one = Concept(name="one", body="1").init_key()
context.sheerka.add_in_cache(concept_one)
concept_un = Concept(name="un", body="one").init_key()
evaluator = ConceptEvaluator()
item = get_return_value(concept_un)
result = evaluator.eval(context, item)
assert result.who == evaluator.name
assert result.status
assert result.value == 1
assert result.parents == [item]
def test_i_can_evaluate_longer_chains():
context = get_context()
context.sheerka.add_in_cache(Concept(name="a", body="'a'").init_key())
context.sheerka.add_in_cache(Concept(name="b", body="a").init_key())
context.sheerka.add_in_cache(Concept(name="c", body="b").init_key())
concept_d = context.sheerka.add_in_cache(Concept(name="d", body="c").init_key())
evaluator = ConceptEvaluator()
item = get_return_value(concept_d)
result = evaluator.eval(context, item)
assert result.status
assert result.value == 'a'
def test_i_can_evaluate_longer_chains_2():
context = get_context()
concept_a = context.sheerka.add_in_cache(Concept(name="a").init_key())
context.sheerka.add_in_cache(Concept(name="b", body="a").init_key())
context.sheerka.add_in_cache(Concept(name="c", body="b").init_key())
concept_d = context.sheerka.add_in_cache(Concept(name="d", body="c").init_key())
evaluator = ConceptEvaluator()
item = get_return_value(concept_d)
result = evaluator.eval(context, item)
assert result.status
assert result.value == concept_a
def test_i_can_recognize_concept_properties():
"""
The concept 'plus' has some properties.
Let's check if they are recognized as concepts
:return:
"""
context = get_context()
concept_one = context.sheerka.add_in_cache(Concept(name="one").init_key())
concept_two = context.sheerka.add_in_cache(Concept(name="two").init_key())
concept_plus = context.sheerka.add_in_cache(Concept(name="a plus b")
.set_prop("a", "one")
.set_prop("b", "two").init_key())
evaluator = ConceptEvaluator()
item = get_return_value(concept_plus)
result = evaluator.eval(context, item)
assert result.status
assert context.sheerka.isinstance(result.value, concept_plus)
assert result.value.props["a"].value == concept_one
assert result.value.props["b"].value == concept_two
def test_i_can_recognize_concept_properties_with_body():
"""
The concept 'plus' has some properties.
Let's check if they are recognized as concepts with Python code
:return:
"""
context = get_context()
context.sheerka.add_in_cache(Concept(name="one", body="1").init_key())
context.sheerka.add_in_cache(Concept(name="two", body="2").init_key())
concept_plus = context.sheerka.add_in_cache(Concept(name="a plus b")
.set_prop("a", "one")
.set_prop("b", "two").init_key())
evaluator = ConceptEvaluator()
item = get_return_value(concept_plus)
result = evaluator.eval(context, item)
assert result.status
assert context.sheerka.isinstance(result.value, concept_plus)
assert result.value.props["a"].value == 1
assert result.value.props["b"].value == 2
def test_i_can_recognize_concept_properties_with_body_when_concept_has_a_body():
context = get_context()
context.sheerka.add_in_cache(Concept(name="one", body="1").init_key())
context.sheerka.add_in_cache(Concept(name="two", body="2").init_key())
concept_plus = context.sheerka.add_in_cache(Concept(name="a plus b", body="a + b")
.set_prop("a", "one")
.set_prop("b", "two").init_key())
evaluator = ConceptEvaluator()
item = get_return_value(concept_plus)
result = evaluator.eval(context, item)
assert result.status
assert result.value == 3
assert not result.status
assert context.sheerka.isinstance(result.value, BuiltinConcepts.NOT_FOR_ME)
def test_i_cannot_recognize_a_concept_if_one_of_the_prop_is_unknown():
@@ -203,7 +100,19 @@ def test_i_cannot_recognize_a_concept_if_one_of_the_prop_is_unknown():
result = evaluator.eval(context, item)
assert not result.status
assert context.sheerka.isinstance(result.value, BuiltinConcepts.PROPERTY_EVAL_ERROR)
assert context.sheerka.isinstance(result.value, BuiltinConcepts.CONCEPT_EVAL_ERROR)
assert result.value.property_name == "b"
assert context.sheerka.isinstance(result.value.error, BuiltinConcepts.TOO_MANY_ERRORS)
assert result.value.concept == concept_plus
def test_that_error_concepts_are_transformed_into_errors_only_if_the_key_is_different():
context = get_context()
error_concept = context.sheerka.new(BuiltinConcepts.ERROR)
item = get_return_value(error_concept)
result = ConceptEvaluator().eval(context, item)
assert not context.sheerka.is_success(error_concept) # it's indeed an error
assert result.status
assert result.value == error_concept
+54 -6
View File
@@ -1,6 +1,6 @@
import pytest
from core.builtin_concepts import ReturnValueConcept, ParserResultConcept
from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts
from core.sheerka import Sheerka, ExecutionContext
from core.concept import Concept
from evaluators.PythonEvaluator import PythonEvaluator
@@ -39,31 +39,79 @@ def test_i_can_eval(text, expected):
assert evaluated.value == expected
def test_i_can_eval_expression_with_variables():
def test_i_can_eval_expression_that_references_concepts():
context = get_context()
context.sheerka.add_in_cache(Concept("foo"))
parsed = PythonParser().parse(context, "foo")
evaluated = PythonEvaluator().eval(context, parsed)
assert evaluated.status
assert evaluated.value == Concept("foo").init_key()
def test_i_can_eval_expression_that_references_concepts_with_body():
"""
I can test expression with variables
:return:
"""
context = get_context()
context.sheerka.add_in_cache(Concept("foo", body="2"))
parsed = PythonParser().parse(context, "foo + 2")
parsed = PythonParser().parse(context, "foo")
evaluated = PythonEvaluator().eval(context, parsed)
assert evaluated.status
assert evaluated.value == 4
assert evaluated.value == 2
def test_i_can_eval_module_with_variables():
def test_i_can_eval_module_with_that_references_concepts():
"""
I can test modules with variables
:return:
"""
context = get_context()
context.sheerka.add_in_cache(Concept("foo"))
parsed = PythonParser().parse(context, "def a(b):\n return b\na(foo)")
evaluated = PythonEvaluator().eval(context, parsed)
assert evaluated.status
assert evaluated.value == Concept("foo").init_key()
def test_i_can_eval_module_with_that_references_concepts_with_body():
"""
I can test modules with variables
:return:
"""
context = get_context()
context.sheerka.add_in_cache(Concept("foo", body="2"))
parsed = PythonParser().parse(context, "def a(b):\n return b\na(foo)")
parsed = PythonParser().parse(context, "def a(b):\n return b\na(foo)")
evaluated = PythonEvaluator().eval(context, parsed)
assert evaluated.status
assert evaluated.value == 2
def test_i_can_eval_concept_with_props():
context = get_context()
context.sheerka.add_in_cache(Concept("foo").set_prop("prop", "'a'"))
parsed = PythonParser().parse(context, "foo")
evaluated = PythonEvaluator().eval(context, parsed)
assert evaluated.status
assert evaluated.value == Concept("foo").set_prop("prop", "a").init_key() # evaluated version of foo
def test_i_cannot_eval_when_body_references_unknown_concept():
context = get_context()
context.sheerka.add_in_cache(Concept("foo", body="bar"))
parsed = PythonParser().parse(context, "foo")
evaluated = PythonEvaluator().eval(context, parsed)
assert not evaluated.status
assert context.sheerka.isinstance(evaluated.value, BuiltinConcepts.ERROR)
+2 -3
View File
@@ -46,12 +46,11 @@ def test_i_can_use_expect_when_only_errors_1():
sheerka = get_sheerka()
items = [
ReturnValueConcept("who", False, None),
ReturnValueConcept("who", False, sheerka.new(BuiltinConcepts.ERROR)),
]
res = core.builtin_helpers.expect_one(get_context(sheerka), items)
assert not res.status
assert sheerka.isinstance(res.value, BuiltinConcepts.TOO_MANY_ERRORS)
assert res.value.body == items
assert res.value, items[0]
assert res.parents == items
+226 -339
View File
@@ -30,6 +30,32 @@ def init_test():
os.chdir(current_pwd)
def get_sheerka(use_dict=True, skip_builtins_in_db=True):
root = "mem://" if use_dict else root_folder
sheerka = Sheerka(skip_builtins_in_db=skip_builtins_in_db)
sheerka.initialize(root)
return sheerka
def get_context(sheerka):
return ExecutionContext("test", "xxx", sheerka)
def get_default_concept():
concept = Concept(
name="a + b",
where="isinstance(a, int) and isinstance(b, int)",
pre="isinstance(a, int) and isinstance(b, int)",
post="isinstance(res, int)",
body="def func(x,y):\n return x+y\nfunc(a,b)",
desc="specific description")
concept.set_prop("a", "value1")
concept.set_prop("b", "value2")
return concept
class ConceptWithGetValue(Concept):
def get_value(self):
return self.get_prop("my_prop")
@@ -260,7 +286,7 @@ def test_instances_are_different_when_asking_for_new():
def test_i_get_the_same_instance_when_is_unique_is_true():
sheerka = get_sheerka()
concept = get_unique_concept()
concept = Concept(name="unique", is_unique=True)
sheerka.create_new_concept(get_context(sheerka), concept)
new1 = sheerka.new(concept.key, a=10, b="value")
@@ -320,389 +346,250 @@ def test_list_of_concept_is_sorted_by_id():
assert concepts[0].id < concepts[-1].id
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#
# E V A L U A T I O N S
#
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@pytest.mark.parametrize("text, expected", [
("1 + 1", 2),
("sheerka.test()", 'I have access to Sheerka !')
@pytest.mark.parametrize("body, expected", [
(None, None),
("", ""),
("1", 1),
("1+1", 2),
("'one'", "one"),
("'one' + 'two'", "onetwo"),
("True", True),
("1 > 2", False),
])
def test_i_can_eval_python_expressions_with_no_variable(text, expected):
def test_i_can_evaluate_a_concept_with_simple_body(body, expected):
sheerka = get_sheerka()
res = sheerka.evaluate_user_input(text)
concept = Concept("foo", body=body).init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert len(res) == 1
assert res[0].status
assert res[0].value == expected
assert evaluated.key == concept.key
assert evaluated.body == expected
assert evaluated.metadata.pre is None
assert evaluated.metadata.post is None
assert evaluated.metadata.where is None
assert evaluated.props == {}
assert evaluated.metadata.is_evaluated
def test_i_can_eval_concept_with_python_body():
sheerka = get_sheerka()
concept = Concept(name="one", body="1")
sheerka.add_in_cache(concept)
text = "one"
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == 1
def test_i_can_eval_concept_with_concept_body():
sheerka = get_sheerka()
concept_one = Concept(name="one")
concept_un = Concept(name="un", body="one")
sheerka.add_in_cache(concept_one)
sheerka.add_in_cache(concept_un)
res = sheerka.evaluate_user_input("un")
return_value = res[0].value
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(return_value, concept_one)
def test_i_can_eval_concept_with_no_body():
sheerka = get_sheerka()
concept = Concept(name="one")
sheerka.add_in_cache(concept)
text = "one"
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == concept
assert id(res[0].value) != id(concept)
def test_is_unique_property_is_used_when_evaluating():
sheerka = get_sheerka()
concept = Concept(name="one", is_unique=True)
sheerka.add_in_cache(concept)
text = "one"
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == concept
assert id(res[0].value) == id(concept)
def test_i_can_eval_def_concept_request():
text = """
def concept a + b
where isinstance(a, int) and isinstance(b, int)
pre isinstance(a, int) and isinstance(b, int)
post isinstance(res, int)
as:
def func(x,y):
return x+y
func(a,b)
@pytest.mark.parametrize("expr, expected", [
("", ""),
("1", 1),
("1+1", 2),
("'one'", "one"),
("'one' + 'two'", "onetwo"),
("True", True),
("1 > 2", False),
])
def test_i_can_evaluate_the_other_metadata(expr, expected):
"""
I only test WHERE, it's the same for the others
:param expr:
:param expected:
:return:
"""
expected = get_default_concept()
expected.metadata.id = "1001"
expected.metadata.desc = None
expected.init_key()
sheerka = get_sheerka()
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
concept = Concept("foo", where=expr).init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
concept_saved = res[0].value.body
for prop in PROPERTIES_TO_SERIALIZE:
assert getattr(concept_saved.metadata, prop) == getattr(expected.metadata, prop)
assert concept_saved.key in sheerka.concepts_cache
assert sheerka.sdp.io.exists(
sheerka.sdp.io.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_saved.get_digest()))
assert evaluated.key == concept.key
assert evaluated.body is None
assert evaluated.metadata.pre is None
assert evaluated.metadata.post is None
assert evaluated.metadata.where == expected
assert evaluated.props == {}
assert evaluated.metadata.is_evaluated
def test_i_can_eval_def_concept_part_when_one_part_is_a_ref_of_another_concept():
@pytest.mark.parametrize("expr, expected", [
(None, None),
("", ""),
("1", 1),
("1+1", 2),
("'one'", "one"),
("'one' + 'two'", "onetwo"),
("True", True),
("1 > 2", False),
])
def test_i_can_evaluate_a_concept_with_prop(expr, expected):
sheerka = get_sheerka()
concept = Concept("foo").set_prop("a", expr).init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body is None
assert evaluated.metadata.pre is None
assert evaluated.metadata.post is None
assert evaluated.metadata.where is None
assert evaluated.props == {"a": Property("a", expected)}
assert evaluated.metadata.is_evaluated
def test_props_are_evaluated_before_body():
sheerka = get_sheerka()
concept = Concept("foo", body="a+1").set_prop("a", "10").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == 11
def test_i_can_evaluate_when_another_concept_is_referenced():
sheerka = get_sheerka()
concept_a = Concept("a")
sheerka.add_in_cache(concept_a)
concept = Concept("foo", body="a").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert sheerka.isinstance(evaluated.body, concept_a)
assert id(evaluated.body) != id(concept_a)
assert evaluated.metadata.is_evaluated
def test_i_can_evaluate_when_the_referenced_concept_has_a_body():
sheerka = get_sheerka()
concept_a = Concept("a", body="1")
sheerka.add_in_cache(concept_a)
concept = Concept("foo", body="a").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == 1
assert not concept_a.metadata.is_evaluated #
def test_i_can_evaluate_concept_of_concept_when_the_leaf_has_a_body():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="a", body="'a'").init_key())
sheerka.add_in_cache(Concept(name="b", body="a").init_key())
sheerka.add_in_cache(Concept(name="c", body="b").init_key())
concept_d = sheerka.add_in_cache(Concept(name="d", body="c").init_key())
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept_d)
assert evaluated.key == concept_d.key
assert evaluated.body == 'a'
def test_i_can_evaluate_concept_of_concept_does_not_have_a_body():
sheerka = get_sheerka()
concept_a = sheerka.add_in_cache(Concept(name="a").init_key())
sheerka.add_in_cache(Concept(name="b", body="a").init_key())
sheerka.add_in_cache(Concept(name="c", body="b").init_key())
concept_d = sheerka.add_in_cache(Concept(name="d", body="c").init_key())
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept_d)
assert evaluated.key == concept_d.key
assert sheerka.isinstance(evaluated.body, concept_a)
def test_i_can_evaluate_concept_when_properties_reference_others_concepts():
sheerka = get_sheerka()
concept_a = sheerka.add_in_cache(Concept(name="a").init_key())
concept = Concept("foo", body="a").set_prop("a", "a").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == concept_a
def test_i_can_evaluate_concept_when_properties_reference_others_concepts_2():
"""
In this test, we test that the properties of 'concept a xx b' (which are 'a' and 'b')
are correctly detected, thanks to the source code 'a plus b' in its body
Same test,
but the name of the property and the name of the concept are different
:return:
"""
sheerka = get_sheerka()
concept_a = sheerka.add_in_cache(Concept(name="a").init_key())
# concept 'a plus b' is known
concept_a_plus_b = Concept(name="a plus b").set_prop("a").set_prop("b")
sheerka.add_in_cache(concept_a_plus_b)
concept = Concept("foo", body="concept_a").set_prop("concept_a", "a").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
res = sheerka.evaluate_user_input("def concept a xx b as a plus b")
expected = Concept(name="a xx b", body="a plus b").set_prop("a").set_prop("b").init_key()
expected.metadata.id = "1001"
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
concept_saved = res[0].value.body
for prop in PROPERTIES_TO_SERIALIZE:
assert getattr(concept_saved.metadata, prop) == getattr(expected.metadata, prop)
assert concept_saved.key in sheerka.concepts_cache
assert sheerka.sdp.io.exists(
sheerka.sdp.io.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_saved.get_digest()))
assert evaluated.key == concept.key
assert evaluated.body == concept_a
def test_i_cannot_eval_the_same_def_concept_twice():
text = """
def concept a + b
where isinstance(a, int) and isinstance(b, int)
pre isinstance(a, int) and isinstance(b, int)
post isinstance(res, int)
as:
def func(x,y):
return x+y
func(a,b)
"""
def test_i_can_evaluate_concept_when_properties_reference_others_concepts_with_body():
sheerka = get_sheerka()
sheerka.evaluate_user_input(text)
res = sheerka.evaluate_user_input(text)
sheerka.add_in_cache(Concept(name="a", body="1").init_key())
sheerka.add_in_cache(Concept(name="b", body="2").init_key())
assert len(res) == 1
assert not res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED)
concept = Concept("foo", body="propA + propB").set_prop("propA", "a").set_prop("propB", "b").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == 3
def test_i_can_disable_an_evaluator():
sheerka = get_sheerka()
concept = Concept(name="one", body="1")
sheerka.add_in_cache(concept)
text = "one"
p = next(e for e in sheerka.evaluators if e.__name__ == "PythonEvaluator")
p.enabled = False # not that you disable the class, not the instance
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.PARSER_RESULT)
p.enabled = True # put back for the remaining unit tests
@pytest.mark.parametrize("text", [
"",
" ",
"\n",
])
def test_i_can_eval_a_empty_input(text):
def test_i_can_reference_sheerka():
sheerka = get_sheerka()
res = sheerka.evaluate_user_input(text)
concept = Concept("foo", body="sheerka.test()").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NOP)
assert evaluated.key == concept.key
assert evaluated.body == sheerka.test()
def test_i_can_eval_concept_with_variable():
def test_properties_values_takes_precedence_over_the_outside_world():
sheerka = get_sheerka()
concept_hello = Concept(name="hello a").set_prop("a")
concept_foo = Concept(name="foo")
sheerka.add_in_cache(concept_hello)
sheerka.add_in_cache(concept_foo)
sheerka.add_in_cache(Concept(name="a", body="'concept_a'").init_key())
sheerka.add_in_cache(Concept(name="b", body="'concept_b'").init_key())
res = sheerka.evaluate_user_input("hello foo")
return_value = res[0].value
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(return_value, concept_hello)
assert return_value.props["a"].value == concept_foo
concept = Concept("foo", body="a").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == 'concept_a' # this test was already done
# so check this one.
concept = Concept("foo", body="a").set_prop("a", "'property_a'").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == 'property_a'
# or this one.
concept = Concept("foo", body="a").set_prop("a", "b").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == 'concept_b'
def test_i_can_eval_concept_with_variable_and_python_as_body():
def test_i_can_reference_sub_property_of_a_property():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.add_in_cache(Concept(name="foo", body="'foo'"))
sheerka.add_in_cache(Concept(name="concept_a").set_prop("subProp", "'sub_a'").init_key())
res = sheerka.evaluate_user_input("hello foo")
assert len(res) == 1
assert res[0].status
assert res[0].value, "hello foo"
concept = Concept("foo", body="a.props['subProp'].value").set_prop("a", "concept_a").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == 'sub_a'
def test_i_can_eval_duplicate_concepts_with_same_value():
def test_i_cannot_evaluate_concept_if_property_is_in_error():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.add_in_cache(Concept(name="hello foo", body="'hello foo'"))
sheerka.add_in_cache(Concept(name="foo", body="'foo'"))
res = sheerka.evaluate_user_input("hello foo")
assert len(res) == 1
assert res[0].status
assert res[0].value, "hello foo"
assert res[0].who == sheerka.get_evaluator_name(MultipleSameSuccessEvaluator.NAME)
concept = Concept(name="concept_a").set_prop("subProp", "undef_concept").init_key()
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
assert sheerka.isinstance(evaluated, BuiltinConcepts.CONCEPT_EVAL_ERROR)
def test_i_cannot_manage_duplicate_concepts_when_the_values_are_different():
def test_key_is_initialized_by_evaluation():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.add_in_cache(Concept(name="hello foo", body="'hello foo'"))
sheerka.add_in_cache(Concept(name="foo", body="'another value'"))
concept = Concept("foo")
evaluated = sheerka.evaluate_concept(get_context(sheerka), concept)
res = sheerka.evaluate_user_input("hello foo")
assert len(res) == 1
assert not res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.TOO_MANY_SUCCESS)
concepts = res[0].value.body
assert len(concepts) == 2
sorted_values = sorted(concepts, key=lambda x: x.value)
assert sorted_values[0].value == "hello another value"
assert sorted_values[1].value == "hello foo"
assert evaluated.key == concept.init_key().key
def test_i_can_manage_concepts_with_the_same_key_when_values_are_the_same():
def test_builtin_error_concept_are_errors():
# only test a random one, it will be the same for the others
sheerka = get_sheerka()
context = get_context(sheerka)
sheerka.create_new_concept(context, Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.create_new_concept(context, Concept(name="hello b", body="'hello ' + b").set_prop("b"))
res = sheerka.evaluate_user_input("hello 'foo'")
assert len(res) == 1
assert res[0].status
assert res[0].value == "hello foo"
assert res[0].who == sheerka.get_evaluator_name(MultipleSameSuccessEvaluator.NAME)
def test_i_can_create_concepts_with_python_code_as_body():
sheerka = get_sheerka()
context = get_context(sheerka)
sheerka.create_new_concept(context, Concept(name="concepts", body="sheerka.concepts()"))
res = sheerka.evaluate_user_input("concepts")
assert len(res) == 1
assert res[0].status
assert isinstance(res[0].value, list)
def test_i_can_create_concept_with_bnf_definition():
sheerka = get_sheerka(False, False)
a = Concept("a")
sheerka.add_in_cache(a)
sheerka.concepts_definition_cache = {a: OrderedChoice("one", "two")}
res = sheerka.evaluate_user_input("def concept plus from bnf a ('plus' plus)?")
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
saved_concept = sheerka.sdp.get_safe(sheerka.CONCEPTS_ENTRY, "plus")
assert saved_concept.key == "plus"
assert saved_concept.metadata.definition == "a ('plus' plus)?"
assert "a" in saved_concept.props
assert "plus" in saved_concept.props
saved_definitions = sheerka.sdp.get_safe(sheerka.CONCEPTS_DEFINITIONS_ENTRY)
expected_bnf = Sequence(
ConceptMatch("a", rule_name="a"),
Optional(Sequence(StrMatch("plus"), ConceptMatch("plus", rule_name="plus"))))
assert saved_definitions[saved_concept] == expected_bnf
new_concept = res[0].value.body
assert new_concept.metadata.name == "plus"
assert new_concept.metadata.definition == "a ('plus' plus)?"
assert new_concept.bnf == expected_bnf
assert "a" in new_concept.props
assert "plus" in new_concept.props
def test_i_can_eval_bnf_definitions():
sheerka = get_sheerka()
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' | 'two'")[0].body.body
res = sheerka.evaluate_user_input("one")
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, concept_a)
def test_i_can_eval_bnf_definitions_with_variables():
sheerka = get_sheerka()
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' | 'two'")[0].body.body
concept_b = sheerka.evaluate_user_input("def concept b from bnf a 'three'")[0].body.body
res = sheerka.evaluate_user_input("one three")
assert len(res) == 1
assert res[0].status
return_value = res[0].value
assert sheerka.isinstance(return_value, concept_b)
assert return_value.props["a"] == Property("a", sheerka.new(concept_a.key, body="one"))
def test_i_can_eval_bnf_definitions_from_separate_instances():
"""
Same test then before,
but make sure that the BNF are correctly persisted and loaded
"""
sheerka = get_sheerka(False)
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' 'two'")[0].body.body
res = get_sheerka(False).evaluate_user_input("one two")
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, concept_a)
# add another bnf definition
concept_b = sheerka.evaluate_user_input("def concept b from bnf a 'three'")[0].body.body
res = get_sheerka(False).evaluate_user_input("one two") # previous one still works
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, concept_a)
res = get_sheerka(False).evaluate_user_input("one two three") # new one works
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, concept_b)
def get_sheerka(use_dict=True, skip_builtins_in_db=True):
root = "mem://" if use_dict else root_folder
sheerka = Sheerka(skip_builtins_in_db=skip_builtins_in_db)
sheerka.initialize(root)
return sheerka
def get_context(sheerka):
return ExecutionContext("test", "xxx", sheerka)
def get_default_concept():
concept = Concept(
name="a + b",
where="isinstance(a, int) and isinstance(b, int)",
pre="isinstance(a, int) and isinstance(b, int)",
post="isinstance(res, int)",
body="def func(x,y):\n return x+y\nfunc(a,b)",
desc="specific description")
concept.set_prop("a", "value1")
concept.set_prop("b", "value2")
return concept
def get_unique_concept():
return Concept(name="unique", is_unique=True)
assert not sheerka.is_success(sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS))
+408
View File
@@ -0,0 +1,408 @@
import pytest
import os
from os import path
import shutil
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
from core.concept import Concept, PROPERTIES_TO_SERIALIZE, Property
from core.sheerka import Sheerka, ExecutionContext
from evaluators.MutipleSameSuccessEvaluator import MultipleSameSuccessEvaluator
from parsers.ConceptLexerParser import Sequence, ZeroOrMore, StrMatch, OrderedChoice, Optional, ConceptMatch, \
ConceptLexerParser
from sdp.sheerkaDataProvider import SheerkaDataProvider
tests_root = path.abspath("../build/tests")
root_folder = "init_folder"
@pytest.fixture(autouse=True)
def init_test():
if path.exists(tests_root):
shutil.rmtree(tests_root)
if not path.exists(tests_root):
os.makedirs(tests_root)
current_pwd = os.getcwd()
os.chdir(tests_root)
yield None
os.chdir(current_pwd)
@pytest.mark.parametrize("text, expected", [
("1 + 1", 2),
("sheerka.test()", 'I have access to Sheerka !')
])
def test_i_can_eval_python_expressions_with_no_variable(text, expected):
sheerka = get_sheerka()
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == expected
def test_i_can_eval_concept_with_python_body():
sheerka = get_sheerka()
concept = Concept(name="one", body="1")
sheerka.add_in_cache(concept)
text = "one"
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == 1
def test_i_can_eval_concept_with_concept_body():
sheerka = get_sheerka()
concept_one = Concept(name="one")
concept_un = Concept(name="un", body="one")
sheerka.add_in_cache(concept_one)
sheerka.add_in_cache(concept_un)
res = sheerka.evaluate_user_input("un")
return_value = res[0].value
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(return_value, concept_one)
def test_i_can_eval_concept_with_no_body():
sheerka = get_sheerka()
concept = Concept(name="one")
sheerka.add_in_cache(concept)
text = "one"
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == concept
assert id(res[0].value) != id(concept)
def test_is_unique_property_is_used_when_evaluating():
sheerka = get_sheerka()
concept = Concept(name="one", is_unique=True)
sheerka.add_in_cache(concept)
text = "one"
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert res[0].value == concept
assert id(res[0].value) == id(concept)
def test_i_can_eval_def_concept_request():
text = """
def concept a + b
where isinstance(a, int) and isinstance(b, int)
pre isinstance(a, int) and isinstance(b, int)
post isinstance(res, int)
as:
def func(x,y):
return x+y
func(a,b)
"""
expected = get_default_concept()
expected.metadata.id = "1001"
expected.metadata.desc = None
expected.init_key()
sheerka = get_sheerka()
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
concept_saved = res[0].value.body
for prop in PROPERTIES_TO_SERIALIZE:
assert getattr(concept_saved.metadata, prop) == getattr(expected.metadata, prop)
assert concept_saved.key in sheerka.concepts_cache
assert sheerka.sdp.io.exists(
sheerka.sdp.io.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_saved.get_digest()))
def test_i_can_eval_def_concept_part_when_one_part_is_a_ref_of_another_concept():
"""
In this test, we test that the properties of 'concept a xx b' (which are 'a' and 'b')
are correctly detected, thanks to the source code 'a plus b' in its body
:return:
"""
sheerka = get_sheerka()
# concept 'a plus b' is known
concept_a_plus_b = Concept(name="a plus b").set_prop("a").set_prop("b")
sheerka.add_in_cache(concept_a_plus_b)
res = sheerka.evaluate_user_input("def concept a xx b as a plus b")
expected = Concept(name="a xx b", body="a plus b").set_prop("a").set_prop("b").init_key()
expected.metadata.id = "1001"
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
concept_saved = res[0].value.body
for prop in PROPERTIES_TO_SERIALIZE:
assert getattr(concept_saved.metadata, prop) == getattr(expected.metadata, prop)
assert concept_saved.key in sheerka.concepts_cache
assert sheerka.sdp.io.exists(
sheerka.sdp.io.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_saved.get_digest()))
def test_i_cannot_eval_the_same_def_concept_twice():
text = """
def concept a + b
where isinstance(a, int) and isinstance(b, int)
pre isinstance(a, int) and isinstance(b, int)
post isinstance(res, int)
as:
def func(x,y):
return x+y
func(a,b)
"""
sheerka = get_sheerka()
sheerka.evaluate_user_input(text)
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert not res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED)
def test_i_can_disable_an_evaluator():
sheerka = get_sheerka()
concept = Concept(name="one", body="1")
sheerka.add_in_cache(concept)
text = "one"
p = next(e for e in sheerka.evaluators if e.__name__ == "PythonEvaluator")
p.enabled = False # not that you disable the class, not the instance
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.PARSER_RESULT)
p.enabled = True # put back for the remaining unit tests
@pytest.mark.parametrize("text", [
"",
" ",
"\n",
])
def test_i_can_eval_a_empty_input(text):
sheerka = get_sheerka()
res = sheerka.evaluate_user_input(text)
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NOP)
def test_i_can_eval_concept_with_variable():
sheerka = get_sheerka()
concept_hello = Concept(name="hello a").set_prop("a")
concept_foo = Concept(name="foo")
sheerka.add_in_cache(concept_hello)
sheerka.add_in_cache(concept_foo)
res = sheerka.evaluate_user_input("hello foo")
return_value = res[0].value
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(return_value, concept_hello)
assert return_value.props["a"].value == concept_foo
def test_i_can_eval_concept_with_variable_and_python_as_body():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.add_in_cache(Concept(name="foo", body="'foo'"))
res = sheerka.evaluate_user_input("hello foo")
assert len(res) == 1
assert res[0].status
assert res[0].value, "hello foo"
def test_i_can_eval_duplicate_concepts_with_same_value():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.add_in_cache(Concept(name="hello foo", body="'hello foo'"))
sheerka.add_in_cache(Concept(name="foo", body="'foo'"))
res = sheerka.evaluate_user_input("hello foo")
assert len(res) == 1
assert res[0].status
assert res[0].value, "hello foo"
assert res[0].who == sheerka.get_evaluator_name(MultipleSameSuccessEvaluator.NAME)
def test_i_cannot_manage_duplicate_concepts_when_the_values_are_different():
sheerka = get_sheerka()
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.add_in_cache(Concept(name="hello foo", body="'hello foo'"))
sheerka.add_in_cache(Concept(name="foo", body="'another value'"))
res = sheerka.evaluate_user_input("hello foo")
assert len(res) == 1
assert not res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.TOO_MANY_SUCCESS)
concepts = res[0].value.body
assert len(concepts) == 2
sorted_values = sorted(concepts, key=lambda x: x.value)
assert sorted_values[0].value == "hello another value"
assert sorted_values[1].value == "hello foo"
def test_i_can_manage_concepts_with_the_same_key_when_values_are_the_same():
sheerka = get_sheerka()
context = get_context(sheerka)
sheerka.create_new_concept(context, Concept(name="hello a", body="'hello ' + a").set_prop("a"))
sheerka.create_new_concept(context, Concept(name="hello b", body="'hello ' + b").set_prop("b"))
res = sheerka.evaluate_user_input("hello 'foo'")
assert len(res) == 1
assert res[0].status
assert res[0].value == "hello foo"
assert res[0].who == sheerka.get_evaluator_name(MultipleSameSuccessEvaluator.NAME)
def test_i_can_create_concepts_with_python_code_as_body():
sheerka = get_sheerka()
context = get_context(sheerka)
sheerka.create_new_concept(context, Concept(name="concepts", body="sheerka.concepts()"))
res = sheerka.evaluate_user_input("concepts")
assert len(res) == 1
assert res[0].status
assert isinstance(res[0].value, list)
def test_i_can_create_concept_with_bnf_definition():
sheerka = get_sheerka(False, False)
a = Concept("a")
sheerka.add_in_cache(a)
sheerka.concepts_definition_cache = {a: OrderedChoice("one", "two")}
res = sheerka.evaluate_user_input("def concept plus from bnf a ('plus' plus)?")
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
saved_concept = sheerka.sdp.get_safe(sheerka.CONCEPTS_ENTRY, "plus")
assert saved_concept.key == "plus"
assert saved_concept.metadata.definition == "a ('plus' plus)?"
assert "a" in saved_concept.props
assert "plus" in saved_concept.props
saved_definitions = sheerka.sdp.get_safe(sheerka.CONCEPTS_DEFINITIONS_ENTRY)
expected_bnf = Sequence(
ConceptMatch("a", rule_name="a"),
Optional(Sequence(StrMatch("plus"), ConceptMatch("plus", rule_name="plus"))))
assert saved_definitions[saved_concept] == expected_bnf
new_concept = res[0].value.body
assert new_concept.metadata.name == "plus"
assert new_concept.metadata.definition == "a ('plus' plus)?"
assert new_concept.bnf == expected_bnf
assert "a" in new_concept.props
assert "plus" in new_concept.props
def test_i_can_eval_bnf_definitions():
sheerka = get_sheerka()
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' | 'two'")[0].body.body
res = sheerka.evaluate_user_input("one")
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, concept_a)
def test_i_can_eval_bnf_definitions_with_variables():
sheerka = get_sheerka()
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' | 'two'")[0].body.body
concept_b = sheerka.evaluate_user_input("def concept b from bnf a 'three'")[0].body.body
res = sheerka.evaluate_user_input("one three")
assert len(res) == 1
assert res[0].status
return_value = res[0].value
assert sheerka.isinstance(return_value, concept_b)
assert return_value.props["a"] == Property("a", sheerka.new(concept_a.key, body="one"))
def test_i_can_eval_bnf_definitions_from_separate_instances():
"""
Same test then before,
but make sure that the BNF are correctly persisted and loaded
"""
sheerka = get_sheerka(False)
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' 'two'")[0].body.body
res = get_sheerka(False).evaluate_user_input("one two")
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, concept_a)
# add another bnf definition
concept_b = sheerka.evaluate_user_input("def concept b from bnf a 'three'")[0].body.body
res = get_sheerka(False).evaluate_user_input("one two") # previous one still works
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, concept_a)
res = get_sheerka(False).evaluate_user_input("one two three") # new one works
assert len(res) == 1
assert res[0].status
assert sheerka.isinstance(res[0].value, concept_b)
def get_sheerka(use_dict=True, skip_builtins_in_db=True):
root = "mem://" if use_dict else root_folder
sheerka = Sheerka(skip_builtins_in_db=skip_builtins_in_db)
sheerka.initialize(root)
return sheerka
def get_context(sheerka):
return ExecutionContext("test", "xxx", sheerka)
def get_default_concept():
concept = Concept(
name="a + b",
where="isinstance(a, int) and isinstance(b, int)",
pre="isinstance(a, int) and isinstance(b, int)",
post="isinstance(res, int)",
body="def func(x,y):\n return x+y\nfunc(a,b)",
desc="specific description")
concept.set_prop("a", "value1")
concept.set_prop("b", "value2")
return concept
+3 -1
View File
@@ -3,7 +3,7 @@ from core.tokenizer import Tokenizer, Token, TokenKind, LexerError, Keywords
def test_i_can_tokenize():
source = "+*-/{}[]() ,;:.?\n\n\r\r\r\nidentifier_0\t \t10.15 10 'string\n' \"another string\"=|&"
source = "+*-/{}[]() ,;:.?\n\n\r\r\r\nidentifier_0\t \t10.15 10 'string\n' \"another string\"=|&<>"
tokens = list(Tokenizer(source))
assert tokens[0] == Token(TokenKind.PLUS, "+", 0, 1, 1)
assert tokens[1] == Token(TokenKind.STAR, "*", 1, 1, 2)
@@ -37,6 +37,8 @@ def test_i_can_tokenize():
assert tokens[29] == Token(TokenKind.EQUALS, '=', 76, 6, 18)
assert tokens[30] == Token(TokenKind.VBAR, '|', 77, 6, 19)
assert tokens[31] == Token(TokenKind.AMPER, '&', 78, 6, 20)
assert tokens[32] == Token(TokenKind.LESS, '<', 79, 6, 21)
assert tokens[33] == Token(TokenKind.GREATER, '>', 80, 6, 22)
@pytest.mark.parametrize("text, expected", [