|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
import re
|
|
|
|
|
import ast
|
|
|
|
|
import dis
|
|
|
|
|
from contextlib import contextmanager
|
|
|
|
|
from inspect import signature, getsource
|
|
|
|
|
from types import FunctionType, LambdaType
|
|
|
|
@ -12,15 +13,19 @@ class VariableType:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DependenceError(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableError(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableTypeError(Exception):
|
|
|
|
|
class VariableTypeError(VariableError):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DependenceError(Exception):
|
|
|
|
|
class VariableNotFoundError(VariableError):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -41,7 +46,9 @@ class IntegerType(VariableType):
|
|
|
|
|
integer_pattern = re.compile(r"^-?\d+$")
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def check(self, value: str) -> bool:
|
|
|
|
|
def check(self, value) -> bool:
|
|
|
|
|
if isinstance(value, int):
|
|
|
|
|
return True
|
|
|
|
|
return self.integer_pattern.match(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -49,12 +56,18 @@ class FloatType(VariableType):
|
|
|
|
|
float_pattern = re.compile(r"^-?\d+(\.\d+)?$")
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def check(self, value: str) -> bool:
|
|
|
|
|
def check(self, value) -> bool:
|
|
|
|
|
if isinstance(value, float):
|
|
|
|
|
return True
|
|
|
|
|
return self.integer_pattern.match(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BooleanType(VariableType):
|
|
|
|
|
pass
|
|
|
|
|
bool_available = {'True', 'False'}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def check(self, value) -> bool:
|
|
|
|
|
return isinstance(value, bool) or value in self.bool_available
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ListType(VariableType):
|
|
|
|
@ -66,11 +79,16 @@ class ReadonlyType(VariableType):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Dependence:
|
|
|
|
|
current_namespace = None
|
|
|
|
|
datavars_root = None
|
|
|
|
|
|
|
|
|
|
def __init__(self, *variables, depend=None):
|
|
|
|
|
self._subscriptions = variables
|
|
|
|
|
self.depend_function = depend
|
|
|
|
|
|
|
|
|
|
def check(self):
|
|
|
|
|
'''Метод для запуска проверки корректности функции зависимости, а также
|
|
|
|
|
сопоставления ее числу заданных зависимостей.'''
|
|
|
|
|
if self.depend_function is None:
|
|
|
|
|
if len(self._subscriptions) > 1:
|
|
|
|
|
raise DependenceError('the depend function is needed if the'
|
|
|
|
@ -84,16 +102,19 @@ class Dependence:
|
|
|
|
|
def calculate_value(self):
|
|
|
|
|
'''Метод для расчета значения переменной с использованием зависимостей
|
|
|
|
|
и заданной для них функции.'''
|
|
|
|
|
args_values = tuple(subscription.get_value() for subscription
|
|
|
|
|
in self._subscriptions)
|
|
|
|
|
print('args_values: {}'.format(args_values))
|
|
|
|
|
try:
|
|
|
|
|
if self.depend_function is None:
|
|
|
|
|
return self._subscriptions[-1].get_value()
|
|
|
|
|
return self.depend_function(*(subscription.get_value()
|
|
|
|
|
for subscription
|
|
|
|
|
in self._subscriptions))
|
|
|
|
|
return args_values[-1]
|
|
|
|
|
return self.depend_function(*args_values)
|
|
|
|
|
except Exception as error:
|
|
|
|
|
raise DependenceError('can not calculate using dependencies: {}'
|
|
|
|
|
' reason: {}'.format(', '.join(
|
|
|
|
|
self._subscriptions),
|
|
|
|
|
[subscription.fullname
|
|
|
|
|
for subscription
|
|
|
|
|
in self._subscriptions]),
|
|
|
|
|
str(error)))
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@ -123,6 +144,65 @@ class Dependence:
|
|
|
|
|
len(function_signature.parameters),
|
|
|
|
|
len(self._subscriptions)))
|
|
|
|
|
|
|
|
|
|
def _get_variables(self, variables_names):
|
|
|
|
|
print('current_namespace: {}'.format(self.current_namespace.fullname))
|
|
|
|
|
print('root:')
|
|
|
|
|
for namespace_name, namespace in self.datavars_root:
|
|
|
|
|
print(namespace_name)
|
|
|
|
|
|
|
|
|
|
variables = list()
|
|
|
|
|
for variable_name in variables_names:
|
|
|
|
|
result = None
|
|
|
|
|
name_parts = variable_name.split('.')
|
|
|
|
|
if not name_parts[0]:
|
|
|
|
|
namespace = self.current_namespace
|
|
|
|
|
index = 1
|
|
|
|
|
while not name_parts[index]:
|
|
|
|
|
namespace = namespace.parent
|
|
|
|
|
index += 1
|
|
|
|
|
name_parts = name_parts[index:]
|
|
|
|
|
else:
|
|
|
|
|
namespace = self.datavars_root
|
|
|
|
|
result = namespace
|
|
|
|
|
for part in name_parts:
|
|
|
|
|
result = result[part]
|
|
|
|
|
variables.append(result)
|
|
|
|
|
|
|
|
|
|
return variables
|
|
|
|
|
|
|
|
|
|
def __ne__(self, other):
|
|
|
|
|
return not self == other
|
|
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
|
if not isinstance(other, Dependence):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
if not self._compare_depend_functions(self.depend_function,
|
|
|
|
|
other.depend_function):
|
|
|
|
|
return False
|
|
|
|
|
for l_var, r_var in zip(self._subscriptions, other._subscriptions):
|
|
|
|
|
if l_var != r_var:
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def _compare_depend_functions(self, l_func: FunctionType,
|
|
|
|
|
r_func: FunctionType) -> bool:
|
|
|
|
|
'''Метод для сравнения функций путем сравнения инструкций, полученных
|
|
|
|
|
в результате их дизассемблирования.'''
|
|
|
|
|
l_instructions = list(dis.get_instructions(l_func))
|
|
|
|
|
r_instructions = list(dis.get_instructions(r_func))
|
|
|
|
|
|
|
|
|
|
for l_instr, r_instr in zip(l_instructions, r_instructions):
|
|
|
|
|
if l_instr.opname != r_instr.opname:
|
|
|
|
|
return False
|
|
|
|
|
if l_instr.arg != r_instr.arg:
|
|
|
|
|
return False
|
|
|
|
|
if ((l_instr.argval != l_instr.argrepr) and
|
|
|
|
|
(r_instr.argval != r_instr.argrepr)):
|
|
|
|
|
if r_instr.argval != l_instr.argval:
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableNode:
|
|
|
|
|
def __init__(self, name, namespace, variable_type=StringType, source=None):
|
|
|
|
@ -151,8 +231,6 @@ class VariableNode:
|
|
|
|
|
if self.calculating:
|
|
|
|
|
raise CyclicVariableError(self.name)
|
|
|
|
|
|
|
|
|
|
self._invalidate()
|
|
|
|
|
|
|
|
|
|
if self._source is None:
|
|
|
|
|
raise VariableError("No sources to update variable '{}'".
|
|
|
|
|
format(self.fullname))
|
|
|
|
@ -172,16 +250,18 @@ class VariableNode:
|
|
|
|
|
|
|
|
|
|
@source.setter
|
|
|
|
|
def source(self, source):
|
|
|
|
|
self._invalidate()
|
|
|
|
|
self._source = source
|
|
|
|
|
if isinstance(source, Dependence):
|
|
|
|
|
for subscription in source._subscriptions:
|
|
|
|
|
subscription.subscribers.add(self)
|
|
|
|
|
if self._source != source:
|
|
|
|
|
self._invalidate()
|
|
|
|
|
self._source = source
|
|
|
|
|
if isinstance(source, Dependence):
|
|
|
|
|
for subscription in source._subscriptions:
|
|
|
|
|
subscription.subscribers.add(self)
|
|
|
|
|
|
|
|
|
|
def _invalidate(self):
|
|
|
|
|
self.value = None
|
|
|
|
|
for subscriber in self.subscribers:
|
|
|
|
|
subscriber._invalidate()
|
|
|
|
|
if self.value is not None:
|
|
|
|
|
self.value = None
|
|
|
|
|
for subscriber in self.subscribers:
|
|
|
|
|
subscriber._invalidate()
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
|
def _start_calculate(self):
|
|
|
|
@ -194,6 +274,7 @@ class VariableNode:
|
|
|
|
|
self.calculating = False
|
|
|
|
|
|
|
|
|
|
def get_value(self):
|
|
|
|
|
'''Метод для получения значения переменной.'''
|
|
|
|
|
if self.value is None:
|
|
|
|
|
self.update_value()
|
|
|
|
|
return self.value
|
|
|
|
@ -233,20 +314,24 @@ class NamespaceNode:
|
|
|
|
|
else:
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
def get_variable_node(self, name):
|
|
|
|
|
if name in self.variables:
|
|
|
|
|
return self.variables[name]
|
|
|
|
|
def __getattr__(self, name: str):
|
|
|
|
|
'''Метод возвращает ноду пространства имен или значение переменной.'''
|
|
|
|
|
if name in self.namespaces:
|
|
|
|
|
return self.namespaces[name]
|
|
|
|
|
elif name in self.variables:
|
|
|
|
|
return self.variables[name].get_value()
|
|
|
|
|
else:
|
|
|
|
|
raise VariableError("Variable '{variable_name}' is not found in"
|
|
|
|
|
" the namespace '{namespace_name}'".format(
|
|
|
|
|
raise VariableError("'{variable_name}' is not found in the"
|
|
|
|
|
" namespace '{namespace_name}'".format(
|
|
|
|
|
variable_name=name,
|
|
|
|
|
namespace_name=self.name))
|
|
|
|
|
|
|
|
|
|
def __getattr__(self, name: str):
|
|
|
|
|
def __getitem__(self, name: str):
|
|
|
|
|
'''Метод возвращает ноду пространства имен или ноду переменной.'''
|
|
|
|
|
if name in self.namespaces:
|
|
|
|
|
return self.namespaces[name]
|
|
|
|
|
elif name in self.variables:
|
|
|
|
|
return self.variables[name].get_value()
|
|
|
|
|
return self.variables[name]
|
|
|
|
|
else:
|
|
|
|
|
raise VariableError("'{variable_name}' is not found in the"
|
|
|
|
|
" namespace '{namespace_name}'".format(
|
|
|
|
@ -261,12 +346,20 @@ class NamespaceNode:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VariableAPI:
|
|
|
|
|
'''Класс для создания переменных при задании их через
|
|
|
|
|
python-скрипты.'''
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.current_namespace = None
|
|
|
|
|
# TODO Продумать другой способ обработки ошибок.
|
|
|
|
|
self.errors = []
|
|
|
|
|
|
|
|
|
|
def __call__(self, name: str, source=None):
|
|
|
|
|
variable = VariableNode(name, self.current_namespace)
|
|
|
|
|
'''Метод для создания переменных внутри with Namespace('name').'''
|
|
|
|
|
if name not in self.current_namespace.variables:
|
|
|
|
|
variable = VariableNode(name, self.current_namespace)
|
|
|
|
|
else:
|
|
|
|
|
variable = self.current_namespace[name]
|
|
|
|
|
|
|
|
|
|
if isinstance(source, Dependence):
|
|
|
|
|
try:
|
|
|
|
|
source.check()
|
|
|
|
@ -280,6 +373,8 @@ class VariableAPI:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NamespaceAPI:
|
|
|
|
|
'''Класс для создания пространств имен при задании переменных через
|
|
|
|
|
python-скрипты.'''
|
|
|
|
|
def __init__(self, var_fabric: VariableAPI,
|
|
|
|
|
datavars_root=NamespaceNode('<root>')):
|
|
|
|
|
self._datavars = datavars_root
|
|
|
|
@ -287,28 +382,51 @@ class NamespaceAPI:
|
|
|
|
|
self.namespace_stack = [datavars_root]
|
|
|
|
|
self._variables_fabric.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
Dependence.current_namespace = self._datavars
|
|
|
|
|
Dependence.datavars_root = self._datavars
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def datavars(self):
|
|
|
|
|
'''Метод для получения корневого пространства имен, через которое далее
|
|
|
|
|
можно получить доступ к переменным.'''
|
|
|
|
|
return self._datavars
|
|
|
|
|
|
|
|
|
|
def set_root(self, datavars_root: NamespaceNode):
|
|
|
|
|
'''Метод для установки корневого пространства имен, которое пока что
|
|
|
|
|
будет использоваться для предоставления доступа к переменным.'''
|
|
|
|
|
self._datavars = datavars_root
|
|
|
|
|
self.namespace_stack = [datavars_root]
|
|
|
|
|
self._variables_fabric.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
|
'''Метод для сброса корневого пространства имен.'''
|
|
|
|
|
self._datavars = NamespaceNode('<root>')
|
|
|
|
|
self.namespace_stack = [self._datavars]
|
|
|
|
|
self._variables_fabric.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
Dependence.current_namespace = self._datavars
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
|
def __call__(self, namespace_name):
|
|
|
|
|
new_namespace = NamespaceNode(
|
|
|
|
|
namespace_name,
|
|
|
|
|
parent=self.namespace_stack[-1])
|
|
|
|
|
self.namespace_stack[-1].add_namespace(new_namespace)
|
|
|
|
|
self.namespace_stack.append(new_namespace)
|
|
|
|
|
self._variables_fabric.current_namespace = new_namespace
|
|
|
|
|
'''Метод для создания пространств имен с помощью with.'''
|
|
|
|
|
if namespace_name not in self.namespace_stack[-1].namespaces:
|
|
|
|
|
namespace = NamespaceNode(namespace_name,
|
|
|
|
|
parent=self.namespace_stack[-1])
|
|
|
|
|
else:
|
|
|
|
|
namespace = self.namespace_stack[-1].namespaces[namespace_name]
|
|
|
|
|
|
|
|
|
|
self.namespace_stack[-1].add_namespace(namespace)
|
|
|
|
|
self.namespace_stack.append(namespace)
|
|
|
|
|
self._variables_fabric.current_namespace = namespace
|
|
|
|
|
|
|
|
|
|
Dependence.datavars_root = self._datavars
|
|
|
|
|
Dependence.current_namespace = namespace
|
|
|
|
|
try:
|
|
|
|
|
yield self
|
|
|
|
|
finally:
|
|
|
|
|
self._variables_fabric.current_namespace =\
|
|
|
|
|
self.namespace_stack.pop()
|
|
|
|
|
current_namespace = self.namespace_stack.pop()
|
|
|
|
|
self._variables_fabric.current_namespace = current_namespace
|
|
|
|
|
Dependence.current_namespace = current_namespace
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Variable = VariableAPI()
|
|
|
|
|