196 lines
8.5 KiB
Python
196 lines
8.5 KiB
Python
from core.builtin_concepts import BuiltinConcepts
|
|
from core.concept import Concept, DoNotResolve, ConceptParts
|
|
import core.builtin_helpers
|
|
|
|
CONCEPT_EVALUATION_STEPS = [
|
|
BuiltinConcepts.BEFORE_EVALUATION,
|
|
BuiltinConcepts.EVALUATION,
|
|
BuiltinConcepts.AFTER_EVALUATION]
|
|
|
|
|
|
class SheerkaEvaluateConcept:
|
|
def __init__(self, sheerka):
|
|
self.sheerka = sheerka
|
|
self.logger_name = self.evaluate_concept.__name__
|
|
|
|
def initialize_concept_asts(self, context, concept: Concept, logger=None):
|
|
"""
|
|
Updates the codes of the newly created concept
|
|
Basically, it runs the parsers on all parts
|
|
:param concept:
|
|
:param context:
|
|
:param logger:
|
|
:return:
|
|
"""
|
|
steps = [BuiltinConcepts.BEFORE_PARSING, BuiltinConcepts.PARSING, BuiltinConcepts.AFTER_PARSING]
|
|
for part_key in ConceptParts:
|
|
if part_key in concept.compiled:
|
|
continue
|
|
|
|
source = getattr(concept.metadata, part_key.value)
|
|
if source is None or not isinstance(source, str):
|
|
continue
|
|
|
|
if source.strip() == "":
|
|
concept.compiled[part_key] = DoNotResolve(source)
|
|
else:
|
|
with context.push(desc=f"Initializing compiled for {part_key}") as sub_context:
|
|
sub_context.log_new(logger)
|
|
sub_context.add_inputs(source=source)
|
|
to_parse = self.sheerka.ret(context.who, True,
|
|
self.sheerka.new(BuiltinConcepts.USER_INPUT, body=source))
|
|
res = self.sheerka.execute(sub_context, to_parse, steps, logger)
|
|
concept.compiled[part_key] = res
|
|
sub_context.add_values(return_values=res)
|
|
|
|
for prop, default_value in concept.metadata.props:
|
|
if prop in concept.compiled:
|
|
continue
|
|
|
|
if default_value is None or not isinstance(default_value, str):
|
|
continue
|
|
|
|
if default_value.strip() == "":
|
|
concept.compiled[prop] = DoNotResolve(default_value)
|
|
else:
|
|
with context.push(desc=f"Initializing AST for property {prop}") as sub_context:
|
|
sub_context.log_new(logger)
|
|
sub_context.add_inputs(source=default_value)
|
|
to_parse = self.sheerka.ret(context.who, True,
|
|
self.sheerka.new(BuiltinConcepts.USER_INPUT, body=default_value))
|
|
res = self.sheerka.execute(context, to_parse, steps)
|
|
concept.compiled[prop] = res
|
|
sub_context.add_values(return_values=res)
|
|
|
|
# Updates the cache of concepts when possible
|
|
if concept.key in self.sheerka.cache_by_key:
|
|
entry = self.sheerka.cache_by_key[concept.key]
|
|
if isinstance(entry, list):
|
|
# TODO : manage when there are multiple entries
|
|
pass
|
|
else:
|
|
self.sheerka.cache_by_key[concept.key].compiled = concept.compiled
|
|
|
|
def resolve(self, context, to_resolve, current_prop, current_concept, logger):
|
|
if isinstance(to_resolve, DoNotResolve):
|
|
return to_resolve.value
|
|
|
|
desc = f"Evaluating {current_prop} (concept={current_concept})"
|
|
context.log(logger, desc, self.logger_name)
|
|
with context.push(desc=desc, obj=current_concept) as sub_context:
|
|
sub_context.log_new(logger)
|
|
|
|
# when it's a concept, evaluate it
|
|
if isinstance(to_resolve, Concept) and \
|
|
not context.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE):
|
|
evaluated = self.evaluate_concept(sub_context, to_resolve, logger)
|
|
sub_context.add_values(return_values=evaluated)
|
|
if evaluated.key == to_resolve.key:
|
|
return evaluated
|
|
else:
|
|
error = evaluated
|
|
|
|
# otherwise, execute all return values to find out what is the value
|
|
else:
|
|
r = self.sheerka.execute(sub_context, to_resolve, CONCEPT_EVALUATION_STEPS, logger)
|
|
one_r = core.builtin_helpers.expect_one(context, r)
|
|
sub_context.add_values(return_values=one_r)
|
|
if one_r.status:
|
|
return one_r.value
|
|
else:
|
|
error = one_r.value
|
|
|
|
return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
|
body=error,
|
|
concept=current_concept,
|
|
property_name=current_prop)
|
|
|
|
def resolve_list(self, context, list_to_resolve, current_prop, current_concept, logger):
|
|
"""When dealing with a list, there are two possibilities"""
|
|
# It may be a list of ReturnValueConcept to execute (always the case for metadata)
|
|
# or a list of single values (may be the case for properties)
|
|
# in this latter case, all values are to be processed one by one and a list should be returned
|
|
if len(list_to_resolve) == 0:
|
|
return []
|
|
|
|
if self.sheerka.isinstance(list_to_resolve[0], BuiltinConcepts.RETURN_VALUE):
|
|
return self.resolve(context, list_to_resolve, current_prop, current_concept, logger)
|
|
|
|
res = []
|
|
for to_resolve in list_to_resolve:
|
|
# sanity check
|
|
if self.sheerka.isinstance(to_resolve, BuiltinConcepts.RETURN_VALUE):
|
|
return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
|
body="Mix between real values and return values",
|
|
concept=current_concept,
|
|
property_name=current_prop)
|
|
|
|
r = self.resolve(context, to_resolve, current_prop, current_concept, logger)
|
|
if self.sheerka.isinstance(r, BuiltinConcepts.CONCEPT_EVAL_ERROR):
|
|
return r
|
|
res.append(r)
|
|
|
|
return res
|
|
|
|
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 logger:
|
|
:return: value of the evaluation or error
|
|
"""
|
|
|
|
logger = logger or self.sheerka.log
|
|
|
|
if concept.metadata.is_evaluated:
|
|
return concept
|
|
|
|
# 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
|
|
#
|
|
|
|
self.initialize_concept_asts(context, concept, logger)
|
|
|
|
# to make sure of the order, it don't use ConceptParts.get_parts()
|
|
# props must be evaluated first
|
|
all_metadata_to_eval = ["props", "where", "pre", "post", "body"]
|
|
|
|
for metadata_to_eval in all_metadata_to_eval:
|
|
if metadata_to_eval == "props":
|
|
for prop_name in (p for p in concept.props if p in concept.compiled):
|
|
prop_ast = concept.compiled[prop_name]
|
|
|
|
if isinstance(prop_ast, list):
|
|
# Do not send the current concept for the properties
|
|
resolved = self.resolve_list(context, prop_ast, prop_name, None, logger)
|
|
else:
|
|
# Do not send the current concept for the properties
|
|
resolved = self.resolve(context, prop_ast, prop_name, None, logger)
|
|
if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR):
|
|
resolved.set_prop("concept", concept) # since current concept was not sent
|
|
return resolved
|
|
else:
|
|
concept.set_prop(prop_name, resolved)
|
|
else:
|
|
part_key = ConceptParts(metadata_to_eval)
|
|
if part_key in concept.compiled and concept.compiled[part_key] is not None:
|
|
metadata_ast = concept.compiled[part_key]
|
|
resolved = self.resolve(context, metadata_ast, part_key, concept, logger)
|
|
if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR):
|
|
return resolved
|
|
else:
|
|
concept.values[part_key] = resolved
|
|
|
|
#
|
|
# TODO : Validate the POST condition
|
|
#
|
|
|
|
concept.init_key() # only does it if needed
|
|
concept.metadata.is_evaluated = True
|
|
return concept
|