Source code for dustmaker.variable

"""
Module defining the Dustforce variable representation
"""
import abc
import collections.abc
from enum import IntEnum
from typing import Any, Dict, List, Optional, Tuple, Type


[docs]class VariableType(IntEnum): """Enumeration of Var type IDs.""" #: Special code used in the internal format. Null variables cannot be created. NULL = 0 BOOL = 1 INT = 2 UINT = 3 FLOAT = 4 #: Dustforce strings are actually byte arrays STRING = 5 #: Vec2 is a (float, float) tuple VEC2 = 10 #: Generic mapping/object type STRUCT = 14 ARRAY = 15
[docs]class Variable(metaclass=abc.ABCMeta): """Variable base class. Variables are a mechanism Dustforce uses to make structure metadata easily available to the game script. Variables will raise an `AssertionError` if they are created with an invalid :attr:`value` attribute. Variables support the equality and hashing interface. Attributes: value: The internal value of this variable. Its type will depend on the actual variable type. """ _TYPES: Dict[VariableType, Type["Variable"]] = {} _vtype = VariableType.NULL def __init__(self, value: Any) -> None: self.value = value self._assert_types() @classmethod def __init_subclass__(cls): """record type id to type class mapping""" if cls._vtype is not VariableType.NULL: Variable._TYPES[cls._vtype] = cls def __eq__(self, oth) -> bool: # pylint: disable=no-member if not isinstance(oth, Variable): return False return self._vtype == oth._vtype and self.value == oth.value def __hash__(self): # pylint: disable=no-member return hash((self._vtype, self.value))
[docs] def assert_types(self) -> None: """Checks if the type of :attr:`value` matches our concrete variable type. Raises: ValueError: :attr:`value`'s type is invalid """ try: self._assert_types() except AssertionError as e: raise ValueError("value does not match variable type") from e
@abc.abstractmethod def _assert_types(self) -> None: """Assert that value types are correct, recursively""" def __repr__(self) -> str: # pylint: disable=no-member return "Variable(%s, %s)" % (repr(self._vtype), repr(self.value))
[docs]class VariableBool(Variable): """Represents a boolean variable of type :class:`bool`.""" _vtype = VariableType.BOOL def __init__(self, value: bool = False) -> None: super().__init__(value) def _assert_types(self) -> None: """Ensure value is boolean""" assert isinstance(self.value, bool)
[docs]class VariableInt(Variable): """Represents a 32-bit signed int variable of type :class:`int`.""" _vtype = VariableType.INT def __init__(self, value: int = 0) -> None: super().__init__(value) def _assert_types(self) -> None: """Ensure value is int in range.""" assert isinstance(self.value, int) and -(2 ** 31) <= self.value < 2 ** 31
[docs]class VariableUInt(Variable): """Represents a 32-bit unsigned int variable of type :class:`int`.""" _vtype = VariableType.UINT def __init__(self, value: int = 0) -> None: super().__init__(value) def _assert_types(self) -> None: """Ensure value is int in range.""" assert isinstance(self.value, int) and 0 <= self.value < 2 ** 32
[docs]class VariableFloat(Variable): """Represents a floating point variable of type :class:`float`.""" _vtype = VariableType.FLOAT def __init__(self, value: float = 0.0) -> None: super().__init__(value) def _assert_types(self) -> None: """Ensure value is float.""" assert isinstance(self.value, float)
[docs]class VariableString(Variable): """Represents a string variable of type :class:`bytes`.""" _vtype = VariableType.STRING def __init__(self, value: bytes = b"") -> None: super().__init__(value) def _assert_types(self) -> None: """Ensure value is bytes.""" assert isinstance(self.value, bytes)
[docs]class VariableVec2(Variable): """Represents a 2-dimensional vector of type `(float, float)`.""" _vtype = VariableType.VEC2 def __init__(self, value: Tuple[float, float] = (0.0, 0.0)) -> None: super().__init__(value) def _assert_types(self) -> None: """Ensure tuple of two floats.""" assert ( isinstance(self.value, tuple) and len(self.value) == 2 and isinstance(self.value[0], float) and isinstance(self.value[1], float) )
[docs]class VariableStruct(Variable): """Represents a struct (dictionary) mapping of type `dict[str, Variable]`.""" _vtype = VariableType.STRUCT def __init__(self, value: Optional[Dict[str, Variable]] = None) -> None: super().__init__(dict(value or {})) def _assert_types(self) -> None: """Ensure value is dictionary of strs to Variables.""" assert isinstance(self.value, dict) for key, val in self.value.items(): assert isinstance(key, str) and isinstance(val, Variable) val._assert_types()
[docs]class VariableArray(Variable, collections.abc.MutableSequence): """Represents an array variable. Arrays are stored a bit differently because they must explicitly encode the type of their sub-elements (heterogenous arrays are not allowed). :class:`VariableArray` implements the :class:`collections.abc.MutableSequence` interface, automatically boxing and unboxing accessed elements. Arguments: element_type (type[Variable]): Variable type of all elements values (list[element_type]): Array of variables of type `element_type` Attributes: value (element_type, list[element_type]): A tuple containing the element type and the element list. Prefer using :attr:`element_type` and the :class:`MutableSequence` interface provided by VariableArray instead of accessing these elements through :attr:`value`. """ _vtype = VariableType.ARRAY def __init__( self, element_type: Type[Variable], values: Optional[List[Variable]] = None ) -> None: super().__init__((element_type, list(values or []))) @property def element_type(self) -> Type[Variable]: """Element type of this array""" return self.value[0] def _box(self, val): """Helper method to box values in a Variable object. Basically just VariableArray is weird. """ etype = self.value[0] if etype is VariableArray: return etype(*val) return etype(val) # Implement the MutableMapping abstract methods by forwarding to the # backing list. We unbox variables from their Variable packaging. This # is a little weird with arrays where the unboxed type is a (type, list) # tuple but works in principle. def __getitem__(self, key): return self.value[1][key].value def __setitem__(self, key, val): self.value[1][key] = self._box(val) def __delitem__(self, key): del self.value[1][key] def __len__(self): return len(self.value[1]) # Method labeled as private since none of the other MutableSequence methods # are directly included in the docs. def insert(self, index, value): """Impelements the :class:`MutableSequence.insert` interface by inserting the boxed value into the backing variable list. :meta private: """ self.value[1].insert(index, self._box(value)) def _assert_types(self) -> None: """Ensure value is tuple of type and list of variables.""" assert ( isinstance(self.value, tuple) and len(self.value) == 2 and isinstance(self.value[0], type) and issubclass(self.value[0], Variable) and isinstance(self.value[1], list) ) for elem in self.value[1]: assert isinstance(elem, self.value[0]) elem._assert_types()