Added ExactConceptParser

This commit is contained in:
2019-11-09 17:29:50 +01:00
parent a636198222
commit 576ce77740
12 changed files with 603 additions and 169 deletions
+76 -5
View File
@@ -2,6 +2,8 @@ import hashlib
from enum import Enum
import logging
from core.tokenizer import Tokenizer, TokenKind
log = logging.getLogger(__name__)
@@ -20,6 +22,8 @@ class 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
@@ -31,7 +35,7 @@ class Concept:
self.id = None
self.key = key
self.props = [] # list of Property for this concept
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
@@ -54,10 +58,48 @@ class Concept:
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):
"""
From a dict of <ConceptParts, AST>
fill the 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:
"""
@@ -68,6 +110,8 @@ class Concept:
if key in possibles_codes:
self.codes[ConceptParts(key)] = codes[key]
return self
def get_digest(self):
"""
Returns the digest of the event
@@ -76,23 +120,47 @@ class Concept:
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.name, p.value) for p in self.props]
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.props.append(Property(n, v))
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"
@@ -132,3 +200,6 @@ class Property:
def __init__(self, name, value):
self.name = name
self.value = value
def __repr__(self):
return f"{self.name}={self.value}"