"""Specifiers and associated objects."""
import itertools
from scenic.core.lazy_eval import (DelayedArgument, toDelayedArgument, requiredProperties,
needsLazyEvaluation)
from scenic.core.distributions import toDistribution
from scenic.core.utils import RuntimeParseError
## Specifiers themselves
[docs]class Specifier:
"""Specifier providing a value for a property given dependencies.
Any optionally-specified properties are evaluated as attributes of the primary value.
"""
def __init__(self, prop, value, deps=None, optionals={}):
self.property = prop
self.value = toDelayedArgument(value)
if deps is None:
deps = set()
deps |= requiredProperties(value)
if prop in deps:
raise RuntimeParseError(f'specifier for property {prop} depends on itself')
self.requiredProperties = deps
self.optionals = optionals
[docs] def applyTo(self, obj, optionals):
"""Apply specifier to an object, including the specified optional properties."""
val = self.value.evaluateIn(obj)
val = toDistribution(val)
assert not needsLazyEvaluation(val)
setattr(obj, self.property, val)
for opt in optionals:
assert opt in self.optionals
setattr(obj, opt, getattr(val, opt))
def __str__(self):
return f'<Specifier of {self.property}>'
## Support for property defaults
[docs]class PropertyDefault:
"""A default value, possibly with dependencies."""
def __init__(self, requiredProperties, attributes, value):
self.requiredProperties = requiredProperties
self.value = value
def enabled(thing, default):
if thing in attributes:
attributes.remove(thing)
return True
else:
return default
self.isAdditive = enabled('additive', False)
for attr in attributes:
raise RuntimeParseError(f'unknown property attribute "{attr}"')
@staticmethod
def forValue(value):
if isinstance(value, PropertyDefault):
return value
else:
return PropertyDefault(set(), set(), lambda self: value)
[docs] def resolveFor(self, prop, overriddenDefs):
"""Create a Specifier for a property from this default and any superclass defaults."""
if self.isAdditive:
allReqs = self.requiredProperties
for other in overriddenDefs:
allReqs |= other.requiredProperties
def concatenator(context):
allVals = [self.value(context)]
for other in overriddenDefs:
allVals.append(other.value(context))
return tuple(allVals)
val = DelayedArgument(allReqs, concatenator)
else:
val = DelayedArgument(self.requiredProperties, self.value)
return Specifier(prop, val)