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):
+25 -7
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,11 +87,23 @@ def expect_one(context, return_values):
parents=return_values)
# only errors, i cannot help you
return sheerka.ret(
context.who,
False,
sheerka.new(BuiltinConcepts.TOO_MANY_ERRORS, body=return_values),
parents=return_values)
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,
sheerka.new(BuiltinConcepts.TOO_MANY_ERRORS, body=return_values),
parents=return_values)
def get_names(sheerka, concept_node):
+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:
+68 -28
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))
concept.cached_asts[prop] = self.execute(context, to_parse, steps)
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:
return self.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
body=res.value,
concept=concept,
property_name=prop_name)
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)
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)