Added basic implementation for Python code evaluation
This commit is contained in:
+25
-2
@@ -95,11 +95,34 @@ class Concept:
|
||||
|
||||
|
||||
class ErrorConcept(Concept):
|
||||
NAME = "Error"
|
||||
|
||||
def __init__(self, where=None, pre=None, post=None, body=None, desc=None):
|
||||
Concept.__init__(self, "error", is_builtin=True, where=where, pre=pre, post=post, body=body, desc=desc)
|
||||
Concept.__init__(self, self.NAME, is_builtin=True, where=where, pre=pre, post=post, body=body, desc=desc)
|
||||
self.key = self.NAME
|
||||
|
||||
def __repr__(self):
|
||||
return f"({self.id}){self.name} : {self.body}"
|
||||
return f"({self.id}){self.name}: {self.body}"
|
||||
|
||||
|
||||
class TooManySuccessConcept(Concept):
|
||||
NAME = "Too many successful items"
|
||||
|
||||
def __init__(self, items=None):
|
||||
super().__init__(self.NAME, body=items)
|
||||
self.key = self.NAME
|
||||
|
||||
|
||||
class ReturnValueConcept(Concept):
|
||||
NAME = "Return Value"
|
||||
|
||||
def __init__(self, return_value=None):
|
||||
super().__init__(self.NAME, body=return_value)
|
||||
self.key = self.NAME
|
||||
|
||||
def __repr__(self):
|
||||
return f"({self.id}){self.name}: {self.body}"
|
||||
|
||||
|
||||
class Property:
|
||||
"""
|
||||
|
||||
+121
-41
@@ -1,9 +1,10 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from core.concept import Concept, ErrorConcept, Property
|
||||
from core.concept import Concept, ErrorConcept, Property, TooManySuccessConcept, ReturnValueConcept
|
||||
from parsers.PythonParser import PythonParser, PythonGetNamesVisitor, PythonNode
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event, SheerkaDataProviderDuplicateKeyError
|
||||
from parsers.DefaultParser import DefaultParser, DefConceptNode
|
||||
import core.utils
|
||||
|
||||
import logging
|
||||
|
||||
@@ -26,6 +27,7 @@ class ReturnValue:
|
||||
To avoid using the try/except pattern for each and every call
|
||||
To give context (ie return message) even when the call is successful
|
||||
"""
|
||||
who: object
|
||||
status: bool
|
||||
value: Concept
|
||||
message: str = None
|
||||
@@ -36,29 +38,29 @@ class ExecutionContext:
|
||||
"""
|
||||
To keep track of the execution of a request
|
||||
"""
|
||||
sheerka: object
|
||||
event_digest: str
|
||||
|
||||
|
||||
class Sheerka(Concept, metaclass=Singleton):
|
||||
class Sheerka(Concept):
|
||||
"""
|
||||
Main controller for the project
|
||||
"""
|
||||
|
||||
NAME = "Sheerka"
|
||||
UNKNOWN_CONCEPT_NAME = "Unknown Concept"
|
||||
ERROR_CONCEPT_NAME = "Error"
|
||||
SUCCESS_CONCEPT_NAME = "Success"
|
||||
|
||||
CONCEPTS_ENTRY = "Concepts"
|
||||
BUILTIN_CONCEPTS_KEYS = "Builtins"
|
||||
USER_CONCEPTS_KEYS = "Concepts"
|
||||
CONCEPTS_ENTRY = "All_Concepts"
|
||||
BUILTIN_CONCEPTS_KEYS = "Builtins_Concepts"
|
||||
USER_CONCEPTS_KEYS = "User_Concepts"
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, debug=False):
|
||||
log.debug("Starting Sheerka.")
|
||||
super().__init__(Sheerka.NAME)
|
||||
|
||||
# cache of the most used concepts
|
||||
self.concepts_cache = []
|
||||
self.concepts_cache = {}
|
||||
|
||||
# a concept can be instantiated
|
||||
# ex: File is a concept, but File('foo.txt') is an instance
|
||||
@@ -71,30 +73,39 @@ class Sheerka(Concept, metaclass=Singleton):
|
||||
|
||||
self.sdp = None
|
||||
self.parsers = []
|
||||
self.evaluators = []
|
||||
|
||||
self.key = self.NAME
|
||||
self.debug = debug
|
||||
|
||||
def initialize(self, root_folder=None):
|
||||
"""
|
||||
Starting Sheerka
|
||||
Loads the current configuration
|
||||
Notes that when it's the first time, it also create the needed working folders
|
||||
:param debug:
|
||||
:param root_folder: root configuration folder
|
||||
:return: ReturnValue(Success or Error)
|
||||
"""
|
||||
|
||||
try:
|
||||
self.init_logging()
|
||||
self.sdp = SheerkaDataProvider(root_folder)
|
||||
self.parsers.append(lambda text: DefaultParser(text, PythonParser))
|
||||
self.parsers.append(lambda text: PythonParser(text))
|
||||
|
||||
self.evaluators.append(core.utils.get_object("evaluators.DefaultEvaluator.DefaultEvaluator"))
|
||||
self.evaluators.append(core.utils.get_object("evaluators.AddConceptEvaluator.AddConceptEvaluator"))
|
||||
self.evaluators.append(core.utils.get_object("evaluators.PythonEvaluator.PythonEvaluator"))
|
||||
|
||||
if self.sdp.first_time:
|
||||
self.sdp.set_key(self.USER_CONCEPTS_KEYS, 1000)
|
||||
|
||||
self.create_builtin_concepts()
|
||||
except IOError as e:
|
||||
return ReturnValue(False, self.get_concept(Sheerka.ERROR_CONCEPT_NAME), e)
|
||||
return ReturnValue(self, False, self.get(Sheerka.ERROR_CONCEPT_NAME), e)
|
||||
|
||||
return ReturnValue(True, self.get_concept(Sheerka.SUCCESS_CONCEPT_NAME))
|
||||
return ReturnValue(self, True, self.get(Sheerka.SUCCESS_CONCEPT_NAME))
|
||||
|
||||
def set_id_if_needed(self, obj, is_builtin):
|
||||
"""
|
||||
@@ -118,11 +129,13 @@ class Sheerka(Concept, metaclass=Singleton):
|
||||
self,
|
||||
Concept(Sheerka.UNKNOWN_CONCEPT_NAME, key=Sheerka.UNKNOWN_CONCEPT_NAME),
|
||||
Concept(Sheerka.SUCCESS_CONCEPT_NAME, key=Sheerka.SUCCESS_CONCEPT_NAME),
|
||||
Concept(Sheerka.ERROR_CONCEPT_NAME, key=Sheerka.ERROR_CONCEPT_NAME),
|
||||
ErrorConcept(),
|
||||
TooManySuccessConcept(),
|
||||
ReturnValueConcept(),
|
||||
]
|
||||
|
||||
for concept in builtins:
|
||||
from_db = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.name)
|
||||
from_db = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.key)
|
||||
if from_db is None:
|
||||
log.debug(f"'{concept.name}' concept is not found. Adding.")
|
||||
self.set_id_if_needed(concept, True)
|
||||
@@ -130,18 +143,32 @@ class Sheerka(Concept, metaclass=Singleton):
|
||||
else:
|
||||
log.debug(f"Found concept '{from_db}'. Updating.")
|
||||
concept.update_from(from_db)
|
||||
self.concepts_cache[concept.key] = concept
|
||||
|
||||
def init_logging(self):
|
||||
if self.debug:
|
||||
log_format = "%(asctime)s %(name)s [%(levelname)s] %(message)s"
|
||||
log_level = logging.DEBUG
|
||||
else:
|
||||
log_format = "%(message)s"
|
||||
log_level = logging.INFO
|
||||
|
||||
logging.basicConfig(format=log_format, level=log_level)
|
||||
|
||||
def eval(self, text):
|
||||
evt_digest = self.sdp.save_event(Event(text))
|
||||
exec_context = ExecutionContext(evt_digest)
|
||||
result = self.try_parse(text)
|
||||
exec_context = ExecutionContext(self, evt_digest)
|
||||
return_values = self.try_parse(text)
|
||||
return_values = self.try_eval(exec_context, return_values)
|
||||
|
||||
return_values = []
|
||||
for parser_name, status, node in result:
|
||||
if not status:
|
||||
return_values.append(ReturnValue(False, ErrorConcept(body=node)))
|
||||
elif status and isinstance(node, DefConceptNode):
|
||||
return_values.append(self.add_concept(exec_context, node))
|
||||
# return_values = []
|
||||
# for parser_name, status, node in result:
|
||||
# if not status:
|
||||
# return_values.append(ReturnValue(False, ErrorConcept(body=node)))
|
||||
# elif status and isinstance(node, DefConceptNode):
|
||||
# return_values.append(self.add_concept(exec_context, node))
|
||||
# else:
|
||||
# return_values.append(ReturnValue(True, node))
|
||||
|
||||
return return_values
|
||||
|
||||
@@ -156,20 +183,36 @@ class Sheerka(Concept, metaclass=Singleton):
|
||||
# except Exception as e:
|
||||
# result.append((p.name, e))
|
||||
tree = p.parse()
|
||||
result.append((p.name, not p.has_error, p.error_sink if p.has_error else tree))
|
||||
result.append(ReturnValue(p.name, not p.has_error, p.error_sink if p.has_error else tree))
|
||||
return result
|
||||
|
||||
def get_concept(self, name):
|
||||
"""
|
||||
Given a concept name, tries to find it
|
||||
:param name: name of the concept to look for
|
||||
:param is_builtin: is it a builtin concept ?
|
||||
:return: concept if found, UNKNOWN_CONCEPT otherwise
|
||||
"""
|
||||
for concept in self.concepts_cache:
|
||||
if concept.name == name:
|
||||
return concept
|
||||
return ErrorConcept()
|
||||
def try_eval(self, context, items):
|
||||
log.debug("Evaluating parsing result.")
|
||||
# group the evaluators by priority and sort them
|
||||
# The first one to be applied will be the one with the highest priority
|
||||
grouped_evaluators = {}
|
||||
for item in self.evaluators:
|
||||
grouped_evaluators.setdefault(item.priority, []).append(item)
|
||||
sorted_priorities = sorted(grouped_evaluators.keys(), reverse=True)
|
||||
|
||||
for priority in sorted_priorities:
|
||||
log.debug("Processing priority " + str(priority))
|
||||
for item in items:
|
||||
log.debug(item)
|
||||
original_items = items[:]
|
||||
evaluated_items = []
|
||||
for evaluator in grouped_evaluators[priority]:
|
||||
if evaluator.matches(context, original_items):
|
||||
result = evaluator.eval(context, original_items)
|
||||
if isinstance(result, list):
|
||||
evaluated_items.extend(result)
|
||||
else:
|
||||
evaluated_items.append(result)
|
||||
|
||||
# what was computed by this group will be the input of the following group
|
||||
items = evaluated_items if len(evaluated_items) > 0 else original_items
|
||||
|
||||
return items
|
||||
|
||||
def add_concept(self, exec_context, def_concept_node: DefConceptNode):
|
||||
"""
|
||||
@@ -203,16 +246,53 @@ class Sheerka(Concept, metaclass=Singleton):
|
||||
try:
|
||||
self.sdp.add(exec_context.event_digest, self.CONCEPTS_ENTRY, concept, use_ref=True)
|
||||
except SheerkaDataProviderDuplicateKeyError as error:
|
||||
return ReturnValue(False, ErrorConcept(body=error), error.args[0])
|
||||
return ReturnValue(True, concept)
|
||||
return ReturnValue(self.add_concept.__name__, False, ErrorConcept(body=error), error.args[0])
|
||||
return ReturnValue(self.add_concept.__name__, True, concept)
|
||||
|
||||
@staticmethod
|
||||
def concept_equals(concept1, concept2):
|
||||
"""True if the two concepts refer to the same concept"""
|
||||
if concept1 is None and concept2 is None:
|
||||
return True
|
||||
def get(self, concept_name):
|
||||
"""
|
||||
Tries to find a concept
|
||||
:param concept_name:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if concept1 is None or concept2 is None:
|
||||
# first search in cache
|
||||
if concept_name in self.concepts_cache:
|
||||
return self.concepts_cache[concept_name]
|
||||
|
||||
return self.sdp.get(self.CONCEPTS_ENTRY, concept_name)
|
||||
|
||||
def new(self, concept, **kwargs):
|
||||
"""
|
||||
Returns an instance of a new concept
|
||||
:param concept:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if isinstance(concept, str):
|
||||
concept = self.get(concept)
|
||||
|
||||
for k, v in kwargs.items():
|
||||
if hasattr(concept, k):
|
||||
setattr(concept, k, v)
|
||||
|
||||
return concept
|
||||
|
||||
def isinstance(self, a, b):
|
||||
"""
|
||||
return true if the concept a is an instance of the concept b
|
||||
:param a:
|
||||
:param b:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if not isinstance(a, Concept) or not isinstance(b, Concept):
|
||||
return False
|
||||
|
||||
return concept1.key == concept2.key
|
||||
# TODO : manage when a is the list of all possible b
|
||||
return a.key == b.key
|
||||
|
||||
@staticmethod
|
||||
def test():
|
||||
return "I have access to Sheerka !"
|
||||
|
||||
+39
-1
@@ -1,4 +1,3 @@
|
||||
|
||||
def sysarg_to_string(argv):
|
||||
"""
|
||||
Transform a list of strings into a single string
|
||||
@@ -18,3 +17,42 @@ def sysarg_to_string(argv):
|
||||
first = False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_class(kls):
|
||||
"""
|
||||
Loads a class from its string full qualified name
|
||||
:param kls:
|
||||
:return:
|
||||
"""
|
||||
parts = kls.split('.')
|
||||
module = ".".join(parts[:-1])
|
||||
m = __import__(module)
|
||||
for comp in parts[1:]:
|
||||
m = getattr(m, comp)
|
||||
return m
|
||||
|
||||
|
||||
def get_object(kls, *args, **kwargs):
|
||||
"""
|
||||
New instance of an object
|
||||
:param kls:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
obj_type = get_class(kls)
|
||||
return obj_type(*args, **kwargs)
|
||||
|
||||
|
||||
def get_full_qualified_name(obj):
|
||||
"""
|
||||
Returns the full qualified name of a class (including its module name )
|
||||
:param obj:
|
||||
:return:
|
||||
"""
|
||||
module = obj.__class__.__module__
|
||||
if module is None or module == str.__class__.__module__:
|
||||
return obj.__class__.__name__ # Avoid reporting __builtin__
|
||||
else:
|
||||
return module + '.' + obj.__class__.__name__
|
||||
|
||||
Reference in New Issue
Block a user