Fixed #12
Fixed #13
Fixed #14
This commit is contained in:
2023-05-08 17:50:28 +02:00
parent 21a397861a
commit e41094f908
95 changed files with 12168 additions and 260 deletions
+201
View File
@@ -0,0 +1,201 @@
from dataclasses import dataclass
from common.global_symbols import NotFound, NotInit
class ConceptDefaultProps:
"""
Lists metadata that can contains some code
"""
WHERE = "#where#"
PRE = "#pre#"
POST = "#post#"
BODY = "#body#"
RET = "#ret#"
DefaultProps = [v for k, v in ConceptDefaultProps.__dict__.items() if not k.startswith("_")]
class DefinitionType:
DEFAULT = "Default"
BNF = "Bnf"
@dataclass
class ConceptMetadata:
"""
Static information of the Concept
"""
id: str # unique identifier for a concept. The id will never be modified (but the key can)
name: str
key: str
is_builtin: bool
is_unique: bool
body: str # main method, can also be the value of the concept
where: str # condition to recognize variables in name
pre: str # list of preconditions before calling the main function
post: str # list of post conditions after calling the main function
ret: str # variable to return when a concept is recognized
definition: str # regex used to define the concept
definition_type: DefinitionType # definition can be done with something else than regex
desc: str # possible description for the concept
autouse: bool # indicates if eval must be automatically called on the concept once validated
bound_body: str # which property must be considered have default value for the concept
props: dict # hashmap of properties, values
variables: tuple # list of concept variables(tuple), with their default values
parameters: tuple # list of variables that are part of the name of the concept
digest: str = None
all_attrs: tuple = None
@dataclass
class ConceptRuntimeInfo:
"""
Dynamic information of the Concept
They are related to the instance of the concept
"""
is_evaluated: bool = False # True is the concept is evaluated by sheerka.eval_concept()
need_validation: bool = False # True if the properties of the concept need to be validated
recognized_by: str = None # RECOGNIZED_BY_ID, RECOGNIZED_BY_NAME, RECOGNIZED_BY_KEY (from Sheerka.py)
def copy(self):
return ConceptRuntimeInfo(self.is_evaluated,
self.need_validation,
self.recognized_by)
class Concept:
"""
Default concept object
A concept is the base object of our universe
Everything is a concept
"""
def __init__(self, metadata: ConceptMetadata):
self._metadata: ConceptMetadata = metadata
self._compiled = {} # cached ast for the where, pre, post and body parts and variables
self._compiled_context_hints = {} # context hints to use when evaluating compiled
self._bnf = None # compiled bnf expression
self._runtime_info = ConceptRuntimeInfo() # runtime settings for the concept
self._all_attrs = None
def __repr__(self):
text = f"({self._metadata.id}){self._metadata.name}"
if self._metadata.pre:
text += f", #pre={self._metadata.pre}"
for attr in [attr for attr in self.all_attrs() if not attr.startswith("#")]:
text += f", {attr}={self.get_value(attr)}"
return text
def __eq__(self, other):
# I don't want this test to be part of the recursion
# So let's just get rif ogf it
if not isinstance(other, Concept):
return False
# I chose to use an iterative algorithm in order to be able to spot circular reference
# without inner functions.
# I also think that it's a better approach for a function that can be massively called
stack = [self, other]
id_self = id(self)
while stack:
right = stack.pop()
left = stack.pop()
if id(left) == id(right):
return True
# 1. in order for two concepts to be equal, they must have the same definition
# 2. They must have the same properties and variables
if left.get_definition_digest() != right.get_definition_digest():
return False
if left.all_attrs() != right.all_attrs():
return False
for attr in left.all_attrs():
value = left.get_value(attr)
other_value = right.get_value(attr)
if isinstance(value, Concept) and isinstance(other_value, Concept):
if id(value) == id_self or id(other_value) == id_self:
# infinite recursion detected
pass
else:
stack.extend([value, other_value])
else:
if value != other_value:
return False
return True
def __hash__(self):
return self._metadata.digest
@property
def id(self):
return self._metadata.id
@property
def name(self):
return self._metadata.name
@property
def key(self):
return self._metadata.key
@property
def body(self):
return self.get_value(ConceptDefaultProps.BODY)
@property
def str_id(self):
return f"c:#{self.id}:" if self.id else f"c:{self.name}:"
def get_definition_digest(self):
return self._metadata.digest
def all_attrs(self):
if self._all_attrs is None:
return self._metadata.all_attrs
return self._all_attrs
def get_metadata(self) -> ConceptMetadata:
return self._metadata
def set_value(self, name: str, value: object):
"""
Set the resolved value of a metadata or a variable (not the metadata itself)
:param name:
:param value:
:return:
"""
setattr(self, name, value)
if name == self._metadata.bound_body:
setattr(self, ConceptDefaultProps.BODY, value)
elif self._metadata.bound_body and name == ConceptDefaultProps.BODY:
setattr(self, self._metadata.bound_body, value)
return self
def get_value(self, name: str):
"""
Gets the resolved value of a metadata
:param name:
:return:
"""
try:
return getattr(self, name)
except AttributeError:
return NotInit if name in self.all_attrs() else NotFound
def get_runtime_info(self):
return self._runtime_info