First version of explain. Creating a new parser was a wrong approach. Need to reimplement
This commit is contained in:
@@ -58,6 +58,7 @@ class BuiltinConcepts(Enum):
|
||||
WHERE_CLAUSE_FAILED = "where clause failed" # failed to validate where clause during evaluation
|
||||
CHICKEN_AND_EGG = "chicken and egg" # infinite recursion when declaring concept
|
||||
ISA = "is a" # builtin concept to express that a concept is an instance of another one
|
||||
EXPLANATION = "explanation"
|
||||
|
||||
NODE = "node"
|
||||
GENERIC_NODE = "generic node"
|
||||
@@ -436,3 +437,17 @@ class NotForMeConcept(Concept):
|
||||
|
||||
def __repr__(self):
|
||||
return f"NotForMeConcept(source={self.body}, reason={self.get_prop('reason')})"
|
||||
|
||||
|
||||
class ExplanationConcept(Concept):
|
||||
def __init__(self, digest=None, command=None, title=None, instructions=None, execution_result=None):
|
||||
super().__init__(BuiltinConcepts.EXPLANATION,
|
||||
True,
|
||||
False,
|
||||
BuiltinConcepts.EXPLANATION)
|
||||
self.def_prop("digest", digest) # event digest
|
||||
self.def_prop("command", command) # explain command parameters
|
||||
self.def_prop("title", title) # a title to the explanation
|
||||
self.def_prop("instructions", instructions) # instructions for SheerkaPrint
|
||||
self.set_metadata_value(ConceptParts.BODY, execution_result) # list of results
|
||||
self.metadata.is_evaluated = True
|
||||
|
||||
@@ -2,7 +2,7 @@ import ast
|
||||
import logging
|
||||
|
||||
import core.ast.nodes
|
||||
from core.ast.nodes import CallNodeConcept, GenericNodeConcept
|
||||
from core.ast.nodes import CallNodeConcept
|
||||
from core.ast.visitors import UnreferencedNamesVisitor
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept
|
||||
|
||||
@@ -276,6 +276,10 @@ class Concept:
|
||||
def to_dict(self, props_to_use=None):
|
||||
"""
|
||||
Returns a dict representing 'self'
|
||||
to_dict() is used for serializing the definition of the concept
|
||||
You will not that it does not dump the actual values of the properties, nor the body
|
||||
|
||||
If you need a dictionary version of the Concept, use to_bag()
|
||||
:return:
|
||||
"""
|
||||
|
||||
@@ -368,6 +372,7 @@ class Concept:
|
||||
:return:
|
||||
"""
|
||||
self.values[metadata] = value
|
||||
return self
|
||||
|
||||
def get_metadata_value(self, metadata: ConceptParts):
|
||||
"""
|
||||
@@ -407,6 +412,20 @@ class Concept:
|
||||
def get_original_definition_hash(self):
|
||||
return self.original_definition_hash
|
||||
|
||||
def to_bag(self):
|
||||
"""
|
||||
Creates a dictionary with the useful properties of the concept
|
||||
It quicker to implement than creating the actual property mechanism with @property
|
||||
And it removes the visibility from the other attributes/methods
|
||||
"""
|
||||
bag = {}
|
||||
for prop in self.props:
|
||||
bag[prop] = self.get_prop(prop)
|
||||
bag["prop." + prop] = self.get_prop(prop)
|
||||
for prop in ("id", "name", "key", "body"):
|
||||
bag[prop] = getattr(self, prop)
|
||||
return bag
|
||||
|
||||
|
||||
class Property:
|
||||
"""
|
||||
|
||||
@@ -3,6 +3,7 @@ import time
|
||||
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
from core.concept import Concept
|
||||
from core.sheerka.Services.SheerkaExecute import NO_MATCH
|
||||
from core.sheerka_logger import get_logger
|
||||
from sdp.sheerkaDataProvider import Event
|
||||
|
||||
@@ -261,6 +262,74 @@ class ExecutionContext:
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _is_return_value(obj):
|
||||
return isinstance(obj, Concept) and obj.key == str(BuiltinConcepts.RETURN_VALUE)
|
||||
|
||||
def _at_least_one_success(self, return_values):
|
||||
status = False
|
||||
for ret_val in return_values:
|
||||
if not self._is_return_value(ret_val):
|
||||
return None
|
||||
status |= ret_val.status
|
||||
return status
|
||||
|
||||
def _all_success(self, return_values):
|
||||
status = True
|
||||
for ret_val in return_values:
|
||||
if not self._is_return_value(ret_val):
|
||||
return None
|
||||
status &= ret_val.status
|
||||
return status
|
||||
|
||||
def get_status(self):
|
||||
# In the function, I cannot use sheerka.isinstance() as self.sheerka may not be initialized
|
||||
# This is the case when ExecutionContext is deserialized
|
||||
|
||||
if "return_values" not in self.values:
|
||||
return None
|
||||
|
||||
if hasattr(self.values["return_values"], "__iter__"):
|
||||
values = self.values["return_values"]
|
||||
if len(values) == 0:
|
||||
return None
|
||||
|
||||
if isinstance(values, str):
|
||||
return "No Match" if values == NO_MATCH else values
|
||||
|
||||
if isinstance(values[0], dict):
|
||||
for result in values:
|
||||
if "return_value" not in result:
|
||||
return None
|
||||
if self._is_return_value(result["return_value"]):
|
||||
return result["return_value"].status
|
||||
return "No Match"
|
||||
else:
|
||||
return self._at_least_one_success(self.values["return_values"])
|
||||
|
||||
else:
|
||||
ret_val = self.values["return_values"]
|
||||
if not isinstance(ret_val, Concept) or not ret_val.key == str(BuiltinConcepts.RETURN_VALUE):
|
||||
return None
|
||||
return ret_val.status
|
||||
|
||||
def to_bag(self):
|
||||
"""
|
||||
Creates a dictionary with the useful properties of the concept
|
||||
It quicker to implement than creating the actual property mechanism with @property
|
||||
And it removes the visibility from the other attributes/methods
|
||||
"""
|
||||
bag = {}
|
||||
for k, v in self._bag.items():
|
||||
bag[k] = v
|
||||
bag["bag." + k] = v
|
||||
for prop in ("id", "who", "desc", "obj", "inputs", "values", "concepts"):
|
||||
bag[prop] = getattr(self, prop)
|
||||
bag["status"] = self.get_status()
|
||||
bag["elapsed"] = self.elapsed
|
||||
bag["digest"] = self.event.get_digest() if self.event else None
|
||||
return bag
|
||||
|
||||
@staticmethod
|
||||
def return_value_to_str(r):
|
||||
value = str(r.value)
|
||||
|
||||
@@ -86,7 +86,7 @@ class SheerkaDump:
|
||||
|
||||
while True:
|
||||
try:
|
||||
if h.event.user != self.sheerka.name:
|
||||
if h.result:
|
||||
self.sheerka.log.info(h)
|
||||
count += 1
|
||||
h = next(history)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||
import core.utils
|
||||
|
||||
NO_MATCH = "** No Match **"
|
||||
|
||||
class SheerkaExecute:
|
||||
"""
|
||||
@@ -159,7 +160,7 @@ class SheerkaExecute:
|
||||
evaluated_items.append(result)
|
||||
debug_result.append({"input": item, "return_value": result})
|
||||
else:
|
||||
debug_result.append({"input": item, "return_value": "** No Match **"})
|
||||
debug_result.append({"input": item, "return_value": NO_MATCH})
|
||||
sub_context.add_values(return_values=debug_result)
|
||||
|
||||
# process evaluators that work on all return values
|
||||
@@ -175,7 +176,7 @@ class SheerkaExecute:
|
||||
to_delete.extend(result.parents)
|
||||
sub_context.add_values(return_values=results)
|
||||
else:
|
||||
sub_context.add_values(return_values="** No Match **")
|
||||
sub_context.add_values(return_values=NO_MATCH)
|
||||
|
||||
return_values = evaluated_items
|
||||
return_values.extend([item for item in original_items if item not in to_delete])
|
||||
|
||||
@@ -2,7 +2,7 @@ from collections import namedtuple
|
||||
|
||||
from sdp.sheerkaDataProvider import Event
|
||||
|
||||
hist = namedtuple("History", "text status") # tests purposes only
|
||||
hist = namedtuple("HistoryTest", "text status") # tests purposes only
|
||||
|
||||
|
||||
class History:
|
||||
@@ -38,34 +38,23 @@ class History:
|
||||
if self._status:
|
||||
return self._status
|
||||
|
||||
if not self.result or "return_values" not in self.result.values:
|
||||
return
|
||||
|
||||
if hasattr(self.result.values["return_values"], "__iter__"):
|
||||
if len(self.result.values["return_values"]) != 1:
|
||||
self._status = False
|
||||
return self._status
|
||||
else:
|
||||
self._status = self.result.values["return_values"][0].status
|
||||
return self._status
|
||||
else:
|
||||
self._status = self.result.values["return_values"].status
|
||||
return self._status
|
||||
self._status = self.result.get_status() if self.result else None
|
||||
return self._status
|
||||
|
||||
|
||||
class SheerkaHistoryManager:
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
|
||||
def history(self, depth_or_digest, start):
|
||||
def history(self, depth, start):
|
||||
"""
|
||||
Load history
|
||||
:param depth_or_digest: number of items or digest
|
||||
:param depth: number of items
|
||||
:param start:
|
||||
:return:
|
||||
"""
|
||||
|
||||
events = list(self.sheerka.sdp.load_events(depth_or_digest, start))
|
||||
events = list(self.sheerka.sdp.load_events(depth, start))
|
||||
for event in events:
|
||||
try:
|
||||
result = self.sheerka.sdp.load_result(event.get_digest())
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from sdp.sheerkaSerializer import Serializer
|
||||
|
||||
|
||||
@dataclass
|
||||
class Variable:
|
||||
"""
|
||||
Variable to store
|
||||
"""
|
||||
event_id: str # event where the variable is modified
|
||||
who: str # who is the modifier
|
||||
key: str # key of the variable
|
||||
value: object # value
|
||||
parents: List[str] # previous references of the variable (Note that there should be only one parent)
|
||||
|
||||
def get_key(self):
|
||||
return f"{self.who}.{self.key}"
|
||||
|
||||
|
||||
class SheerkaVariableManager:
|
||||
VARIABLES_ENTRY = "All_Variables" # to store all the concepts
|
||||
|
||||
def __init__(self, sheerka):
|
||||
self.sheerka = sheerka
|
||||
|
||||
def record(self, context, who, key, value):
|
||||
"""Persist a variable"""
|
||||
# first check if there is a previous version of the variable
|
||||
try:
|
||||
old = self.sheerka.sdp.get(self.VARIABLES_ENTRY, who + "." + key)
|
||||
if old.value == value:
|
||||
return
|
||||
|
||||
parent = getattr(old, Serializer.ORIGIN)
|
||||
except IndexError:
|
||||
parent = None
|
||||
|
||||
variable = Variable(context.event.get_digest(), who, key, value, [parent] if parent else None)
|
||||
self.sheerka.sdp.set(context.event.get_digest(), self.VARIABLES_ENTRY, variable, use_ref=True)
|
||||
|
||||
def load(self, who, key):
|
||||
variable = self.sheerka.sdp.get_safe(self.VARIABLES_ENTRY, who + "." + key)
|
||||
if variable is None:
|
||||
return None
|
||||
|
||||
return variable.value
|
||||
|
||||
def delete(self, context, who, key):
|
||||
self.sheerka.sdp.remove(
|
||||
context.event.get_digest(),
|
||||
self.VARIABLES_ENTRY,
|
||||
lambda _key, _var: _key == who + "." + key)
|
||||
@@ -1,3 +1,7 @@
|
||||
import logging
|
||||
|
||||
import core.builtin_helpers
|
||||
import core.utils
|
||||
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept, BuiltinErrors, BuiltinUnique, \
|
||||
UnknownConcept
|
||||
from core.concept import Concept, ConceptParts, PROPERTIES_FOR_NEW
|
||||
@@ -9,13 +13,10 @@ from core.sheerka.Services.SheerkaExecute import SheerkaExecute
|
||||
from core.sheerka.Services.SheerkaHistoryManager import SheerkaHistoryManager
|
||||
from core.sheerka.Services.SheerkaModifyConcept import SheerkaModifyConcept
|
||||
from core.sheerka.Services.SheerkaSetsManager import SheerkaSetsManager
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event
|
||||
import core.utils
|
||||
import core.builtin_helpers
|
||||
|
||||
from core.sheerka.Services.SheerkaVariableManager import SheerkaVariableManager
|
||||
from core.sheerka_logger import console_handler
|
||||
|
||||
import logging
|
||||
from printer.SheerkaPrinter import SheerkaPrinter
|
||||
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event
|
||||
|
||||
CONCEPT_LEXER_PARSER_CLASS = "parsers.BnfNodeParser.BnfNodeParser"
|
||||
BNF_PARSER_CLASS = "parsers.BnfParser.BnfParser"
|
||||
@@ -93,6 +94,8 @@ class Sheerka(Concept):
|
||||
self.sets_handler = SheerkaSetsManager(self)
|
||||
self.evaluate_concept_handler = SheerkaEvaluateConcept(self)
|
||||
self.history_handler = SheerkaHistoryManager(self)
|
||||
self.printer_handler = SheerkaPrinter(self)
|
||||
self.variable_handler = SheerkaVariableManager(self)
|
||||
|
||||
self.during_restore = False
|
||||
self._builtins_classes_cache = None
|
||||
@@ -127,7 +130,7 @@ class Sheerka(Concept):
|
||||
|
||||
exec_context.add_values(return_values=res)
|
||||
if not self.skip_builtins_in_db:
|
||||
self.sdp.save_result(exec_context)
|
||||
self.sdp.save_result(exec_context, is_admin=True)
|
||||
self.init_log.debug(f"Sheerka successfully initialized")
|
||||
|
||||
except IOError as e:
|
||||
@@ -299,9 +302,26 @@ class Sheerka(Concept):
|
||||
# if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
|
||||
# with open(CONCEPTS_FILE, "a") as f:
|
||||
# f.write(text + "\n")
|
||||
|
||||
return ret
|
||||
|
||||
def print(self, result, instructions=None):
|
||||
"""
|
||||
Print the result to output
|
||||
:param result:
|
||||
:param instructions:
|
||||
:return:
|
||||
"""
|
||||
self.printer_handler.print(result, instructions)
|
||||
|
||||
def record(self, context, who, key, value):
|
||||
return self.variable_handler.record(context, who, key, value)
|
||||
|
||||
def load(self, who, key):
|
||||
return self.variable_handler.load(who, key)
|
||||
|
||||
def delete(self, context, who, key):
|
||||
return self.variable_handler.delete(context, who, key)
|
||||
|
||||
def execute(self, execution_context, return_values, execution_steps):
|
||||
"""
|
||||
Executes process for all initial contexts
|
||||
@@ -639,12 +659,27 @@ class Sheerka(Concept):
|
||||
|
||||
return self.value(body_to_use)
|
||||
|
||||
def value_by_concept(self, obj, concept):
|
||||
if obj is None:
|
||||
return None
|
||||
|
||||
if not isinstance(obj, Concept):
|
||||
return None
|
||||
|
||||
if isinstance(concept, tuple) and obj.key in [str(key) for key in concept]:
|
||||
return obj
|
||||
|
||||
if obj.key == str(concept):
|
||||
return obj
|
||||
|
||||
return self.value_by_concept(obj.body, concept)
|
||||
|
||||
def get_error(self, obj):
|
||||
if isinstance(obj, Concept) and obj.metadata.is_builtin and obj.key in BuiltinErrors:
|
||||
return obj
|
||||
|
||||
if isinstance(obj, list):
|
||||
return obj
|
||||
return obj
|
||||
|
||||
if self.isinstance(obj, BuiltinConcepts.RETURN_VALUE):
|
||||
if obj.status:
|
||||
|
||||
+30
-4
@@ -46,6 +46,8 @@ class TokenKind(Enum):
|
||||
TILDE = "tilde" # ~
|
||||
UNDERSCORE = "underscore" # _
|
||||
DEGREE = "degree" # °
|
||||
WORD = "word"
|
||||
EQUALSEQUALS = "=="
|
||||
|
||||
|
||||
@dataclass()
|
||||
@@ -99,12 +101,13 @@ class Tokenizer:
|
||||
|
||||
KEYWORDS = set(x.value for x in Keywords)
|
||||
|
||||
def __init__(self, text):
|
||||
def __init__(self, text, parse_word=False):
|
||||
self.text = text
|
||||
self.text_len = len(text)
|
||||
self.column = 1
|
||||
self.line = 1
|
||||
self.i = 0
|
||||
self.parse_word = parse_word
|
||||
|
||||
def __iter__(self):
|
||||
|
||||
@@ -175,9 +178,14 @@ class Tokenizer:
|
||||
self.i += 1
|
||||
self.column += 1
|
||||
elif c == "=":
|
||||
yield Token(TokenKind.EQUALS, "=", self.i, self.line, self.column)
|
||||
self.i += 1
|
||||
self.column += 1
|
||||
if self.i + 1 < self.text_len and self.text[self.i + 1] == "=":
|
||||
yield Token(TokenKind.EQUALSEQUALS, "==", self.i, self.line, self.column)
|
||||
self.i += 2
|
||||
self.column += 2
|
||||
else:
|
||||
yield Token(TokenKind.EQUALS, "=", self.i, self.line, self.column)
|
||||
self.i += 1
|
||||
self.column += 1
|
||||
elif c == " " or c == "\t":
|
||||
whitespace = self.eat_whitespace(self.i)
|
||||
yield Token(TokenKind.WHITESPACE, whitespace, self.i, self.line, self.column)
|
||||
@@ -270,6 +278,11 @@ class Tokenizer:
|
||||
yield Token(TokenKind.CONCEPT, (name, id), self.i, self.line, self.column)
|
||||
self.i += length + 2
|
||||
self.column += length + 2
|
||||
elif self.parse_word and (c.isalpha() or c.isdigit()):
|
||||
word = self.eat_word(self.i)
|
||||
yield Token(TokenKind.WORD, word, self.i, self.line, self.column)
|
||||
self.i += len(word)
|
||||
self.column += len(word)
|
||||
elif c.isalpha() or c == "_":
|
||||
identifier = self.eat_identifier(self.i)
|
||||
token_type = TokenKind.KEYWORD if identifier in self.KEYWORDS else TokenKind.IDENTIFIER
|
||||
@@ -419,3 +432,16 @@ class Tokenizer:
|
||||
1 if lines_count > 0 else start_column + len(result))
|
||||
|
||||
return result, lines_count
|
||||
|
||||
def eat_word(self, start):
|
||||
result = self.text[start]
|
||||
i = start + 1
|
||||
while i < self.text_len:
|
||||
c = self.text[i]
|
||||
if c.isalpha() or c.isdigit():
|
||||
result += c
|
||||
i += 1
|
||||
else:
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
+1
-11
@@ -176,7 +176,7 @@ def product(a, b):
|
||||
res = []
|
||||
for item_b in b:
|
||||
for item_a in a:
|
||||
#items = item_a + [item_b]
|
||||
# items = item_a + [item_b]
|
||||
items = item_a[:]
|
||||
if hasattr(item_b, "__iter__"):
|
||||
items.extend(item_b)
|
||||
@@ -235,16 +235,6 @@ def escape_char(text, to_escape):
|
||||
return res
|
||||
|
||||
|
||||
def pp(items):
|
||||
if not hasattr(items, "__iter__"):
|
||||
return str(items)
|
||||
|
||||
if len(items) == 0:
|
||||
return str(items)
|
||||
|
||||
return " \n" + " \n".join(str(item) for item in items)
|
||||
|
||||
|
||||
def decode_enum(enum_repr: str):
|
||||
"""
|
||||
Tries to transform ClassName.Name into an enum
|
||||
|
||||
Reference in New Issue
Block a user