import hashlib from enum import Enum import logging from core.tokenizer import Tokenizer, TokenKind log = logging.getLogger(__name__) class ConceptParts(Enum): WHERE = "where" PRE = "pre" POST = "post" BODY = "body" class Concept: """ Default concept object A concept is a the base object of our universe Everything is a concept """ props_to_serialize = ("id", "is_builtin", "name", "where", "pre", "post", "body", "desc") PROPERTY_PREFIX = "__var__" def __init__(self, name=None, is_builtin=False, where=None, pre=None, post=None, body=None, desc=None, key=None): self.name = name self.is_builtin = is_builtin self.where = where # condition to recognize variables in name self.pre = pre # list of pre conditions before calling the main function self.post = post # list of post conditions after calling the main function self.body = body # main method, can also be the value of the concept self.desc = desc self.id = None self.key = key self.props = {} # list of Property for this concept self.functions = {} # list of helper functions self.codes = {} # cached ast for the where, pre, post and body parts def __repr__(self): return f"({self.id}){self.name}" def __eq__(self, other): if not isinstance(other, Concept): return False return self.name == other.name and \ self.where == other.where and \ self.pre == other.pre and \ self.post == other.post and \ self.body == other.body def __hash__(self): return hash(self.name) def get_key(self): return self.key def init_key(self, tokens=None): """ Create the key for this concept. Must be called only when the concept if fully initialized The method is not called set_key to make sure that no other class set the key by mistake :param tokens: :return: """ if self.key is not None: return self.key if tokens is None: tokens = iter(Tokenizer(self.name)) variables = list(self.props.keys()) key = "" first = True for token in tokens: if token.type == TokenKind.EOF: break if token.type == TokenKind.WHITESPACE: continue if not first: key += " " if variables is not None and token.value in variables: key += self.PROPERTY_PREFIX + str(variables.index(token.value)) else: key += token.value[1:-1] if token.type == TokenKind.STRING else token.value first = False self.key = key return self def add_codes(self, codes): """ Gets the ASTs for 'where', 'pre', 'post' and 'body' There ASTs are know when the concept is freshly parsed. So the values are kept in cache. For concepts loaded from sdp, these ASTs must be created again :param codes: :return: """ possibles_codes = set(item.value for item in ConceptParts) if codes is None: return for key in codes: if key in possibles_codes: self.codes[ConceptParts(key)] = codes[key] return self def get_digest(self): """ Returns the digest of the event :return: hexa form of the sha256 """ return hashlib.sha256(f"Concept:{self.name}{self.pre}{self.post}{self.body}".encode("utf-8")).hexdigest() def to_dict(self): """ Returns a dict representing 'self' :return: """ props_as_dict = dict((prop, getattr(self, prop)) for prop in self.props_to_serialize) props_as_dict["props"] = [(p, self.props[p].value) for p in self.props] return props_as_dict def from_dict(self, as_dict): """ Initializes 'self' from a dict :param as_dict: :return: """ for prop in self.props_to_serialize: if prop in as_dict: setattr(self, prop, as_dict[prop]) if "props" in as_dict: for n, v in as_dict["props"]: self.set_prop(n, v) return self def update_from(self, other): """ Update self using the properties of another concept This method is to mimic the class to instance pattern 'other' is the class, the template, and 'self' is a new instance :param other: :return: """ for prop in self.props_to_serialize: setattr(self, prop, getattr(other, prop)) return self def set_prop(self, prop_name, prop_value): self.props[prop_name] = Property(prop_name, prop_value) def set_prop_by_index(self, index, prop_value): prop_name = list(self.props.keys())[index] self.props[prop_name] = Property(prop_name, prop_value) class ErrorConcept(Concept): NAME = "Error" def __init__(self, where=None, pre=None, post=None, body=None, desc=None): 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}" 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: """ Defines a behaviour of Concept """ def __init__(self, name, value): self.name = name self.value = value def __repr__(self): return f"{self.name}={self.value}"