1054 lines
28 KiB
Python
1054 lines
28 KiB
Python
from typing import Tuple, ItemsView
|
|
|
|
from biopro.util.text import part_suffix
|
|
from . import *
|
|
from .expression import *
|
|
|
|
|
|
class DeviceParameterError(RuntimeError):
|
|
__slots__ = ()
|
|
|
|
def __init__(self, parameter_name: str, message: str):
|
|
super().__init__('parameter ' + parameter_name + ' ' + message)
|
|
|
|
|
|
class IllegalParameterValueError(DeviceParameterError):
|
|
__slots__ = ()
|
|
|
|
def __init__(self, parameter_name: str, message: Optional[str] = None):
|
|
super().__init__(parameter_name, message if message is not None else 'illegal value')
|
|
|
|
|
|
class ConstantDomainError(DeviceParameterError):
|
|
"""Any un-allowed operation on constant parameter will raise this error."""
|
|
|
|
__slots__ = ()
|
|
|
|
def __init__(self, parameter_name: str, message: Optional[str] = None):
|
|
m = message if message is not None else 'is a constant'
|
|
super().__init__(parameter_name, m)
|
|
|
|
|
|
class PropertyDomainError(DeviceParameterError):
|
|
"""Any un-allowed operation on property parameter will raise this error."""
|
|
|
|
__slots__ = ()
|
|
|
|
def __init__(self, parameter_name: str, message: Optional[str] = None):
|
|
m = message if message is not None else 'is a property'
|
|
super().__init__(parameter_name, m)
|
|
|
|
|
|
class ActionDomainError(DeviceParameterError):
|
|
"""Any operation on action parameter will raise this error"""
|
|
|
|
__slots__ = ()
|
|
|
|
def __init__(self, parameter_name: str, message: Optional[str] = None):
|
|
m = message if message is not None else 'is a action parameter'
|
|
super().__init__(parameter_name, m)
|
|
|
|
|
|
class PropertySetInterrupt(DeviceParameterError):
|
|
__slots__ = ('_parameter_update_table',)
|
|
|
|
def __init__(self, property_name: str, parameter_update_table: Dict[str, Any]):
|
|
super().__init__(property_name, 'property set')
|
|
self._parameter_update_table = parameter_update_table
|
|
|
|
def items(self) -> ItemsView[str, Any]:
|
|
return self._parameter_update_table.items()
|
|
|
|
|
|
class ParameterDomain(JsonSerialize, metaclass=abc.ABCMeta):
|
|
"""parameter value domain.
|
|
|
|
**json format**
|
|
|
|
::
|
|
|
|
ParameterDomain
|
|
= ParameterConstantDomain # type: "constant"
|
|
| ParameterTypeDomain
|
|
| ParameterBoolDomain # value: "bool"
|
|
| ParameterIntDomain # value: "int"
|
|
| ParameterValueDomain
|
|
| ParameterConstantRangeDomain # type: list[int]
|
|
| ParameterRangeDomain # type: list[Expression[int]]
|
|
| ParameterCollectionDomain
|
|
| ParameterListDomain # type: dict {"list": ParameterDomain}
|
|
| ParameterSetDomain # type: dict {"set": ParameterDomain}
|
|
| ParameterArrayDomain # type: dict {"array": ParameterDomain}
|
|
| PropertyDomain # value: "property"
|
|
| ActionDomain # value: "action"
|
|
|
|
"""
|
|
|
|
__slots__ = ()
|
|
|
|
@abc.abstractmethod
|
|
def init_para(self, initial: Optional[Any] = None) -> Any:
|
|
"""
|
|
|
|
:param initial: P value
|
|
:return: legal P value
|
|
:raise ValueError: illegal `initial` value
|
|
"""
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def valid_para(self, value: int) -> bool:
|
|
"""exam **value** in this domain
|
|
|
|
:param value: P value
|
|
:return: valid
|
|
"""
|
|
pass
|
|
|
|
def __repr__(self):
|
|
return str(self)
|
|
|
|
@classmethod
|
|
def parse(cls, json: Optional[JSON], constant: ConstantScope) -> Optional['ParameterDomain']:
|
|
if json == 'constant':
|
|
return ParameterConstantDomain
|
|
|
|
elif json == 'bool':
|
|
return ParameterBoolDomain
|
|
|
|
elif json == 'int':
|
|
return ParameterIntDomain
|
|
|
|
elif json == 'property':
|
|
return ParameterPropertyDomain
|
|
|
|
elif json == 'action':
|
|
return ParameterActionDomain
|
|
|
|
elif isinstance(json, list):
|
|
return ParameterValueDomain.parse(json, constant)
|
|
|
|
elif isinstance(json, dict):
|
|
return ParameterCollectionDomain.parse(json, constant)
|
|
|
|
raise RuntimeError('illegal value domain : ' + _truncate(json))
|
|
|
|
|
|
class ParameterConstantDomainType(ParameterDomain):
|
|
"""constant P value domain."""
|
|
|
|
__slots__ = ()
|
|
|
|
def init_para(self, initial: Optional[Any] = None) -> NoReturn:
|
|
"""
|
|
|
|
:param initial: P value
|
|
:raises: ConstantDomainError, always
|
|
"""
|
|
raise ConstantDomainError('<unknown>')
|
|
|
|
def valid_para(self, value: int) -> bool:
|
|
"""
|
|
|
|
:param value:
|
|
:return: always False
|
|
"""
|
|
return False
|
|
|
|
def as_json(self):
|
|
return 'constant'
|
|
|
|
def __str__(self):
|
|
return 'constant'
|
|
|
|
|
|
ParameterConstantDomain = ParameterConstantDomainType()
|
|
|
|
|
|
class ParameterPropertyDomainType(ParameterDomain):
|
|
"""Property parameter only has V value, but no P value, which means that the value only
|
|
present in the device configuration. The property parameter should has at least
|
|
*value* and *value_set* property which also represent as getter and setter respectively.
|
|
It give a way to get a V value (combine from one or more other parameter which has P value)
|
|
or set a V value (assign to one or more other parameter with calculated P value).
|
|
|
|
"""
|
|
|
|
__slots__ = ()
|
|
|
|
def init_para(self, initial: Optional[Any] = None) -> NoReturn:
|
|
"""
|
|
|
|
:param initial:
|
|
:raises: PropertyDomainError, always
|
|
"""
|
|
raise PropertyDomainError('<unknown>')
|
|
|
|
def valid_para(self, value: int) -> bool:
|
|
"""
|
|
|
|
:param value:
|
|
:return: always False
|
|
"""
|
|
return False
|
|
|
|
def as_json(self):
|
|
return 'property'
|
|
|
|
def __str__(self):
|
|
return 'property'
|
|
|
|
|
|
ParameterPropertyDomain = ParameterPropertyDomainType()
|
|
|
|
|
|
class ParameterActionDomainType(ParameterDomain):
|
|
"""Action parameter has neither P value nor V value. This parameter used to fire observer
|
|
event.
|
|
|
|
"""
|
|
|
|
__slots__ = ()
|
|
|
|
def init_para(self, initial: Optional[Any] = None) -> Any:
|
|
"""
|
|
|
|
:param initial:
|
|
:raises: ActionDomainError, always
|
|
"""
|
|
raise ActionDomainError('<unknown>')
|
|
|
|
def valid_para(self, value: int) -> bool:
|
|
"""
|
|
|
|
:param value:
|
|
:return: always False
|
|
"""
|
|
return False
|
|
|
|
def as_json(self):
|
|
return 'action'
|
|
|
|
def __str__(self):
|
|
return 'action'
|
|
|
|
|
|
ParameterActionDomain = ParameterActionDomainType()
|
|
|
|
|
|
class ParameterTypeDomain(ParameterDomain, metaclass=abc.ABCMeta):
|
|
"""parameter domain which value present a python primitive type."""
|
|
|
|
__slots__ = ()
|
|
|
|
@abc.abstractmethod
|
|
def init_para(self, initial: Optional[Any] = None):
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def valid_para(self, value: int) -> bool:
|
|
pass
|
|
|
|
def as_json(self):
|
|
return str(self)
|
|
|
|
|
|
class ParameterBoolDomainType(ParameterTypeDomain):
|
|
__slots__ = ()
|
|
|
|
def init_para(self, initial: Optional[Any] = None) -> bool:
|
|
if initial is None:
|
|
return False
|
|
else:
|
|
return int(initial) != 0
|
|
|
|
def valid_para(self, value: int) -> bool:
|
|
return True
|
|
|
|
def __str__(self):
|
|
return "bool"
|
|
|
|
|
|
ParameterBoolDomain = ParameterBoolDomainType()
|
|
|
|
|
|
class ParameterIntDomainType(ParameterTypeDomain):
|
|
__slots__ = ()
|
|
|
|
def init_para(self, initial: Optional[Any] = None) -> int:
|
|
if initial is None:
|
|
return 0
|
|
else:
|
|
return int(initial)
|
|
|
|
def valid_para(self, value: int) -> bool:
|
|
return True
|
|
|
|
def __str__(self):
|
|
return "int"
|
|
|
|
|
|
ParameterIntDomain = ParameterIntDomainType()
|
|
|
|
|
|
class ParameterValueDomain(ParameterDomain, metaclass=abc.ABCMeta):
|
|
"""limited/ranged P value domain """
|
|
|
|
__slots__ = ()
|
|
|
|
def init_para(self, initial: Optional[Any] = None) -> int:
|
|
if initial is None:
|
|
return self.range[0]
|
|
else:
|
|
initial = int(initial)
|
|
f, t = self.range
|
|
|
|
if f <= initial < t:
|
|
return initial
|
|
else:
|
|
return f
|
|
|
|
def valid_para(self, value: Any) -> bool:
|
|
f, t = self.range
|
|
return f <= float(value) < t
|
|
|
|
@property
|
|
@abc.abstractmethod
|
|
def range(self) -> Tuple[int, int]:
|
|
"""P values range
|
|
|
|
:return: P min value, P max value
|
|
"""
|
|
pass
|
|
|
|
def __str__(self):
|
|
return "[%d, %d)" % self.range
|
|
|
|
@classmethod
|
|
def parse(cls, json: JSON_ARRAY, constant: ConstantScope) -> 'ParameterValueDomain':
|
|
|
|
if len(json) == 0:
|
|
raise RuntimeError('empty range expression : ' + _truncate(json))
|
|
|
|
elif len(json) == 1:
|
|
_stop = json[0]
|
|
if isinstance(_stop, int):
|
|
return ParameterConstantRangeDomain(0, _stop)
|
|
|
|
_start = ConstantExpression(0)
|
|
_stop = Expression.parse(_stop)
|
|
|
|
elif len(json) == 2:
|
|
_start = json[0]
|
|
_stop = json[1]
|
|
|
|
if isinstance(_start, int) and isinstance(_stop, int):
|
|
return ParameterConstantRangeDomain(_start, _stop)
|
|
|
|
_start = Expression.parse(json[0])
|
|
_stop = Expression.parse(json[1])
|
|
|
|
else:
|
|
raise RuntimeError('not a range expression : ' + _truncate(json))
|
|
|
|
return ParameterRangeDomain(_start, _stop, constant)
|
|
|
|
|
|
class ParameterConstantRangeDomain(ParameterValueDomain):
|
|
"""constant limited/ranged P value domain
|
|
|
|
|
|
**json format**
|
|
|
|
::
|
|
|
|
ParameterConstantValueDomain
|
|
= [int]
|
|
| [int, int]
|
|
|
|
The value would be resolve when this class be initialized.
|
|
The variable search scope is constant pool.
|
|
"""
|
|
|
|
__slots__ = ('_start', '_stop')
|
|
|
|
def __init__(self, start: int, stop: int):
|
|
"""
|
|
|
|
:param start: start P value
|
|
:param stop: stop P value (exclude)
|
|
"""
|
|
self._start: int = start
|
|
self._stop: int = stop
|
|
|
|
@property
|
|
def range(self) -> Tuple[int, int]:
|
|
"""P values range
|
|
|
|
:return: P min value, P max value
|
|
"""
|
|
return self._start, self._stop
|
|
|
|
def as_json(self):
|
|
return [self._start, self._stop]
|
|
|
|
|
|
class ParameterRangeDomain(ParameterValueDomain):
|
|
"""limited/ranged P value domain
|
|
|
|
|
|
**json format**
|
|
|
|
::
|
|
|
|
ParameterRangeDomain
|
|
= [Expression[int]]
|
|
| [Expression[int], Expression[int]]
|
|
|
|
The value would be resolve when this class be initialized.
|
|
The variable search scope is constant pool.
|
|
"""
|
|
|
|
__slots__ = ('_constant',
|
|
'__start', '__stop',
|
|
'_start', '_stop')
|
|
|
|
def __init__(self, start: Expression, stop: Expression, constant: ConstantScope):
|
|
"""
|
|
|
|
:param start: value start expression
|
|
:param stop: value stop (exclude) expression
|
|
:param constant: scope
|
|
"""
|
|
|
|
self._constant = constant
|
|
self.__start: Expression = start
|
|
self.__stop: Expression = stop
|
|
|
|
# start
|
|
ret = self.__start.value(self._constant)
|
|
|
|
try:
|
|
self._start = int(ret)
|
|
except (ValueError, TypeError) as e:
|
|
raise RuntimeError('start not a number : ' + str(ret)) from e
|
|
|
|
# stop
|
|
ret = self.__stop.value(self._constant)
|
|
|
|
try:
|
|
self._stop = int(ret)
|
|
except (ValueError, TypeError) as e:
|
|
raise RuntimeError('stop not a number : ' + str(ret)) from e
|
|
|
|
@property
|
|
def range(self) -> Tuple[int, int]:
|
|
"""P values range
|
|
|
|
:return: P min value, P max value
|
|
"""
|
|
return self._start, self._stop
|
|
|
|
def as_json(self):
|
|
return [self.__start.as_json(), self.__stop.as_json()]
|
|
|
|
|
|
class ParameterCollectionValueOperator(metaclass=abc.ABCMeta):
|
|
"""
|
|
**format**
|
|
|
|
========================= ============= ========== ===========
|
|
expression set list array
|
|
========================= ============= ========== ===========
|
|
``VALUE`` add value add value add value
|
|
``*`` add all value
|
|
``:=`` clear all clear all
|
|
``INDEX=VALUE`` del value set value set value
|
|
``INDEX:TO=VALUE,...`` set value replace replace
|
|
========================= ============= ========== ===========
|
|
|
|
|
|
"""
|
|
|
|
__slots__ = ()
|
|
|
|
@abc.abstractmethod
|
|
def index(self, index_range: int) -> Union[None, int, slice]:
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def value(self, domain: ParameterDomain) -> Union[int, List[int]]:
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def __str__(self):
|
|
pass
|
|
|
|
def __repr__(self):
|
|
return str(self)
|
|
|
|
@classmethod
|
|
def parse(cls, expr: Union[int, str]) -> 'ParameterCollectionValueOperator':
|
|
if isinstance(expr, int):
|
|
return SingleValueOperator(None, expr)
|
|
|
|
elif expr == '*':
|
|
return AddAllValueOperator
|
|
|
|
elif expr == ':=':
|
|
return CollectValueOperator((None, None), [])
|
|
|
|
elif '=' in expr:
|
|
a, v = part_suffix(expr, '=')
|
|
|
|
if ':' in a:
|
|
v = list(map(int, v.split(',')))
|
|
|
|
if a == ':':
|
|
return CollectValueOperator((None, None), v)
|
|
|
|
else:
|
|
a, b = part_suffix(a, ':')
|
|
a = int(a) if len(a) else None
|
|
b = int(b) if len(b) else None
|
|
|
|
return CollectValueOperator((a, b), v)
|
|
|
|
else:
|
|
return SingleValueOperator(int(a), int(v))
|
|
else:
|
|
return SingleValueOperator(None, int(expr))
|
|
|
|
|
|
class SingleValueOperator(ParameterCollectionValueOperator):
|
|
__slots__ = ('_index', '_value')
|
|
|
|
def __init__(self, index: Optional[int], value: int):
|
|
self._index = index
|
|
self._value = value
|
|
|
|
def index(self, index_range: int) -> Optional[int]:
|
|
return self._index
|
|
|
|
def value(self, domain: ParameterDomain) -> int:
|
|
return self._value
|
|
|
|
def __str__(self):
|
|
if self._index is None:
|
|
return str(self._value)
|
|
else:
|
|
return '%d=%d' % (self._index, self._value)
|
|
|
|
|
|
class CollectValueOperator(ParameterCollectionValueOperator):
|
|
__slots__ = ('_index', '_value')
|
|
|
|
def __init__(self, index: Tuple[Optional[int], Optional[int]], value: List[int]):
|
|
self._index = index
|
|
self._value = value
|
|
|
|
def index(self, index_range: int) -> slice:
|
|
a, b = self._index
|
|
a = a if a is not None else 0
|
|
b = min(b, index_range) if b is not None else index_range
|
|
|
|
return slice(a, b)
|
|
|
|
def value(self, domain: ParameterDomain) -> List[int]:
|
|
return self._value
|
|
|
|
def __str__(self):
|
|
a, b = self._index
|
|
|
|
return '%s:%s=%s' % (str(a) if a is not None else '',
|
|
str(b) if b is not None else '',
|
|
','.join(map(str, self._value)))
|
|
|
|
|
|
class AllValueOperatorType(ParameterCollectionValueOperator):
|
|
__slots__ = ()
|
|
|
|
def index(self, index_range: int) -> slice:
|
|
return slice(index_range)
|
|
|
|
def value(self, domain: ParameterDomain) -> List[int]:
|
|
if isinstance(domain, ParameterValueDomain):
|
|
return list(range(*domain.range))
|
|
else:
|
|
raise RuntimeError()
|
|
|
|
def __str__(self):
|
|
return '*'
|
|
|
|
|
|
AddAllValueOperator = AllValueOperatorType()
|
|
|
|
|
|
class ParameterCollectionDomain(ParameterDomain, metaclass=abc.ABCMeta):
|
|
"""special collection with P values.
|
|
|
|
**special value**
|
|
|
|
===== ========
|
|
value describe
|
|
===== ========
|
|
'*' all
|
|
===== ========
|
|
|
|
"""
|
|
|
|
__slots__ = ('_domain',)
|
|
|
|
def __init__(self, domain: ParameterDomain):
|
|
super().__init__()
|
|
|
|
if isinstance(domain, ParameterCollectionDomain):
|
|
raise RuntimeError('cannot compose with parameter collection domain')
|
|
|
|
self._domain = domain
|
|
|
|
@property
|
|
@abc.abstractmethod
|
|
def collection_type(self):
|
|
pass
|
|
|
|
@property
|
|
def element_domain(self) -> ParameterDomain:
|
|
return self._domain
|
|
|
|
def init_para(self, initial: Optional[Any] = None):
|
|
"""
|
|
|
|
:param initial: ignore
|
|
:return: empty collection
|
|
"""
|
|
if initial is None:
|
|
return self.collection_type()
|
|
else:
|
|
ret = self.collection_type(initial)
|
|
|
|
for v in ret:
|
|
if not self.valid_para(v):
|
|
raise ValueError('illegal value domain : ' + str(v))
|
|
|
|
return ret
|
|
|
|
def valid_para(self, value: Any) -> bool:
|
|
"""
|
|
|
|
:param value: P value or special value
|
|
:return:
|
|
"""
|
|
if value == '*':
|
|
return True
|
|
|
|
return self._domain.valid_para(value)
|
|
|
|
@abc.abstractmethod
|
|
def oper_para(self, target: Any, oper: ParameterCollectionValueOperator, context: Scope):
|
|
"""operator parameter *value* for the collection *target*.
|
|
|
|
:param target: target collection
|
|
:param oper: value operator
|
|
:param context: general case root
|
|
"""
|
|
pass
|
|
|
|
@classmethod
|
|
def parse(cls, json: JSON_OBJECT, constant: ConstantScope) -> 'ParameterCollectionDomain':
|
|
if 'list' in json:
|
|
return ParameterListDomain.parse(json, constant)
|
|
elif 'set' in json:
|
|
return ParameterSetDomain.parse(json, constant)
|
|
elif 'array' in json:
|
|
return ParameterArrayDomain.parse(json, constant)
|
|
else:
|
|
raise RuntimeError('illegal parameter collection domain : ' + _truncate(json))
|
|
|
|
|
|
class ParameterListDomain(ParameterCollectionDomain):
|
|
"""P value list.
|
|
|
|
**json format**
|
|
|
|
::
|
|
|
|
ParameterListDomain = {
|
|
"list" :ParameterValueDomain
|
|
"limit"? :Expression[int] # list size
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
__slots__ = ('__limit', '_limit')
|
|
|
|
def __init__(self,
|
|
domain: ParameterDomain,
|
|
constant: ConstantScope,
|
|
limit: Union[None, int, str, Expression[int]] = None):
|
|
"""
|
|
|
|
:param domain:
|
|
:param limit: list size limit
|
|
"""
|
|
super().__init__(domain)
|
|
|
|
self.__limit = limit
|
|
|
|
if limit is not None:
|
|
if isinstance(limit, Expression):
|
|
limit = limit.value(constant)
|
|
|
|
if isinstance(limit, str):
|
|
limit = constant[limit]
|
|
|
|
if not isinstance(limit, int):
|
|
raise TypeError('illegal limit type : ' + str(limit))
|
|
|
|
self._limit: Optional[int] = limit
|
|
|
|
@property
|
|
def collection_type(self):
|
|
return list
|
|
|
|
def oper_para(self, target: List[Any], oper: ParameterCollectionValueOperator, context: Scope):
|
|
"""operator *value* for the list *target*
|
|
|
|
:param target: target list
|
|
:param oper:
|
|
:param context: general case root
|
|
"""
|
|
if not isinstance(target, list):
|
|
raise TypeError('not a list : ' + str(target))
|
|
|
|
if oper == AddAllValueOperator:
|
|
raise ValueError("list parameter cannot accept '*' (all) value")
|
|
|
|
d = self.element_domain
|
|
sz = len(target)
|
|
i = oper.index(sz)
|
|
v = oper.value(d)
|
|
|
|
if i is None and v is None:
|
|
pass
|
|
|
|
elif i is None:
|
|
# add value
|
|
|
|
if isinstance(v, list):
|
|
if not self._valid_list_limit(target, len(v)):
|
|
raise RuntimeError('over size limit : ' + str(sz) + ' + ' + str(len(v)))
|
|
|
|
for _v in v:
|
|
if not d.valid_para(_v):
|
|
raise RuntimeError('illegal value : ' + str(_v))
|
|
|
|
target.extend(v)
|
|
|
|
else:
|
|
if not self._valid_list_limit(target, 1):
|
|
raise RuntimeError('over size limit : ' + str(sz) + ' + 1')
|
|
|
|
if not d.valid_para(v):
|
|
raise RuntimeError('illegal value : ' + str(v))
|
|
|
|
target.append(v)
|
|
|
|
elif len(v) == 0:
|
|
# delete value
|
|
del target[i]
|
|
|
|
else:
|
|
# replace value
|
|
|
|
if isinstance(i, int):
|
|
i = slice(i, i + 1)
|
|
|
|
if isinstance(v, int):
|
|
v = [v]
|
|
|
|
target[i] = v
|
|
|
|
def _valid_list_limit(self, target: List[Any], inc: int) -> bool:
|
|
if self._limit is not None:
|
|
if self._limit < len(target) + inc:
|
|
return False
|
|
|
|
return True
|
|
|
|
def as_json(self):
|
|
return {
|
|
'list': self._domain.as_json(),
|
|
'limit': JsonSerialize.to_json(self.__limit)
|
|
}
|
|
|
|
def __str__(self):
|
|
return "[" + str(self._domain) + "]"
|
|
|
|
@classmethod
|
|
def parse(cls, json: JSON_OBJECT, constant: ConstantScope) -> 'ParameterListDomain':
|
|
col = _get(json, 'list', None, 'parameter list domain')
|
|
|
|
limit = _get(json, 'limit', None, 'parameter list domain', nullable=True)
|
|
if limit is not None:
|
|
limit = Expression.parse(limit)
|
|
|
|
return ParameterListDomain(ParameterDomain.parse(col, constant), constant, limit)
|
|
|
|
|
|
class ParameterSetDomain(ParameterCollectionDomain):
|
|
"""P value set.
|
|
|
|
**json format**
|
|
|
|
::
|
|
|
|
ParameterSetDomain = {
|
|
"set" :ParameterValueDomain
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
__slots__ = ()
|
|
|
|
def __init__(self, domain: ParameterDomain):
|
|
super().__init__(domain)
|
|
|
|
@property
|
|
def collection_type(self):
|
|
return set
|
|
|
|
def oper_para(self, target: Set[Any], oper: ParameterCollectionValueOperator, context: Scope):
|
|
"""operator *value* for the set *target*
|
|
|
|
:param target: target set
|
|
:param oper: P value or special value
|
|
:param context: general case root
|
|
"""
|
|
|
|
if not isinstance(target, set):
|
|
raise TypeError('not a set : ' + str(target))
|
|
|
|
d = self.element_domain
|
|
|
|
if oper == AddAllValueOperator:
|
|
# add all
|
|
if not isinstance(d, ParameterValueDomain):
|
|
raise RuntimeError('domain not limited')
|
|
|
|
for p in range(*d.range):
|
|
target.add(p)
|
|
|
|
elif isinstance(oper, SingleValueOperator):
|
|
i = oper.index(0)
|
|
v = oper.value(d)
|
|
|
|
if i is None:
|
|
# add value
|
|
|
|
if not d.valid_para(v):
|
|
raise RuntimeError('illegal value : ' + str(v))
|
|
|
|
target.add(v)
|
|
else:
|
|
# remove value
|
|
|
|
try:
|
|
target.remove(v)
|
|
except KeyError:
|
|
pass
|
|
|
|
else:
|
|
assert isinstance(oper, CollectValueOperator)
|
|
# set value
|
|
v = oper.value(d)
|
|
|
|
for _v in v:
|
|
if not d.valid_para(_v):
|
|
raise RuntimeError('illegal value : ' + str(_v))
|
|
|
|
target.clear()
|
|
|
|
if len(v):
|
|
target.update(v)
|
|
|
|
def as_json(self):
|
|
return {'set': self._domain.as_json()}
|
|
|
|
def __str__(self):
|
|
return "{" + str(self._domain) + "}"
|
|
|
|
@classmethod
|
|
def parse(cls, json: JSON_OBJECT, constant: ConstantScope) -> 'ParameterSetDomain':
|
|
col = _get(json, 'set', None, 'parameter list domain')
|
|
return ParameterSetDomain(ParameterDomain.parse(col, constant))
|
|
|
|
|
|
class ParameterArrayDomain(ParameterCollectionDomain):
|
|
"""P value array. It work like as :class:`ParameterListDomain`, but different in view which only present
|
|
one V value instead a list of V values.
|
|
|
|
**json format**
|
|
|
|
::
|
|
|
|
ParameterArrayDomain = {
|
|
"array" :ParameterValueDomain
|
|
"index" :parameter_name
|
|
"size" :int | str | Expression[int]
|
|
}
|
|
|
|
"""
|
|
|
|
__slots__ = ('_index', '__size', '_size')
|
|
|
|
def __init__(self,
|
|
domain: ParameterDomain,
|
|
constant: ConstantScope,
|
|
index: str,
|
|
size: Union[int, str, Expression[int]]):
|
|
"""
|
|
|
|
:param domain:
|
|
:param index:
|
|
:param size:
|
|
"""
|
|
super().__init__(domain)
|
|
|
|
self._index = index
|
|
self.__size = size
|
|
|
|
# evaluation size
|
|
if isinstance(size, Expression):
|
|
size = size.value(constant)
|
|
|
|
elif isinstance(size, str):
|
|
size = constant[size]
|
|
|
|
if not isinstance(size, int):
|
|
raise TypeError('illegal size type : ' + str(size))
|
|
|
|
self._size: int = size
|
|
|
|
@property
|
|
def collection_type(self):
|
|
return list
|
|
|
|
def init_para(self, initial: Optional[Any] = None):
|
|
"""
|
|
|
|
:param initial: ignore
|
|
:return: empty collection
|
|
"""
|
|
if initial is None:
|
|
z = self.element_domain.init_para()
|
|
ret = [z for _ in range(self._size)]
|
|
|
|
elif isinstance(initial, (list, tuple)):
|
|
if len(initial) != self._size:
|
|
raise ValueError('size not match : %d != %d' % (len(initial), self._size))
|
|
|
|
ret = list(initial)
|
|
|
|
else:
|
|
ret = [initial for _ in range(self._size)]
|
|
|
|
for v in ret:
|
|
if not self.valid_para(v):
|
|
raise ValueError('illegal value domain : ' + str(v))
|
|
|
|
return ret
|
|
|
|
@property
|
|
def index_parameter(self) -> str:
|
|
return self._index
|
|
|
|
def index(self, context: Scope) -> int:
|
|
return int(context[self._index])
|
|
|
|
@property
|
|
def size(self) -> int:
|
|
return self._size
|
|
|
|
def get_para(self, target: Any, context: Scope) -> Any:
|
|
"""get P value from array *target*
|
|
|
|
:param target: target array
|
|
:param context: general case root
|
|
:return: P' value
|
|
"""
|
|
if isinstance(target, list):
|
|
return target[self.index(context)]
|
|
else:
|
|
return target
|
|
|
|
def oper_para(self, target: List[Any], oper: Any, context: Scope):
|
|
"""set *value* into the set *target*
|
|
|
|
:param target: target array
|
|
:param oper: P' value or special value
|
|
:param context: general case root
|
|
"""
|
|
if not isinstance(target, list):
|
|
raise TypeError('not a array : ' + str(target))
|
|
|
|
if oper == AddAllValueOperator:
|
|
raise ValueError("array parameter cannot accept '*' (all) value")
|
|
|
|
sz = len(target)
|
|
d = self.element_domain
|
|
|
|
if isinstance(oper, SingleValueOperator):
|
|
# set value
|
|
|
|
i = oper.index(sz)
|
|
v = oper.value(d)
|
|
|
|
if i is None:
|
|
i = self.index(context)
|
|
|
|
if not d.valid_para(v):
|
|
raise RuntimeError('illegal value : ' + str(v))
|
|
|
|
target[i] = v
|
|
|
|
else:
|
|
# set array of value
|
|
|
|
assert isinstance(oper, CollectValueOperator)
|
|
i = oper.index(sz)
|
|
i = list(range(*i.indices(sz)))
|
|
|
|
v = oper.value(d)
|
|
|
|
if len(i) != len(v):
|
|
raise RuntimeError('array index range not match value size')
|
|
|
|
for _i, _j in enumerate(i):
|
|
target[_j] = v[_i]
|
|
|
|
def as_json(self):
|
|
return {
|
|
'list': self._domain.as_json(),
|
|
'index': self._index,
|
|
'size': JsonSerialize.to_json(self.__size)
|
|
}
|
|
|
|
def __str__(self):
|
|
return "[" + str(self._domain) + "][" + self._index + ']'
|
|
|
|
@classmethod
|
|
def parse(cls, json: JSON_OBJECT, constant: ConstantScope) -> 'ParameterArrayDomain':
|
|
col = _get(json, 'array', None, 'parameter array domain')
|
|
|
|
index = _get(json, 'index', str, 'parameter array domain')
|
|
|
|
size = _get(json, 'size', None, 'parameter array domain')
|
|
if isinstance(size, dict):
|
|
size = Expression.parse(size)
|
|
|
|
return ParameterArrayDomain(ParameterDomain.parse(col, constant),
|
|
constant, index, size)
|