Refactored to use a single implementation for concept evaluation
This commit is contained in:
+48
-11
@@ -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
@@ -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
@@ -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
@@ -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])
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user