from __future__ import annotations
import sys
from typing import Any, Optional, TypeVar
from ._cstd import c_free, c_malloc, c_realloc
from .base_pointers import BaseAllocatedPointer, IterDereferencable
from .exceptions import AllocationError, InvalidSizeError
from .stack_pointer import StackAllocatedPointer
from .util import handle
__all__ = ("AllocatedPointer", "malloc", "free", "realloc")
T = TypeVar("T")
A = TypeVar("A", bound=BaseAllocatedPointer)
class AllocatedPointer(IterDereferencable[T], BaseAllocatedPointer[T]):
"""Pointer to allocated memory."""
def __init__(
self,
address: int,
size: int,
assigned: bool = False,
parent: AllocatedPointer[T] | None = None,
) -> None:
"""
Args:
address: Address of the allocated memory.
size: Size of the allocated memory.
assigned: Whether an object is currently inside the memory.
"""
self._address = address
self._size = size
self._freed = False
self._assigned = assigned
self._parent: AllocatedPointer[T] | None = parent
def _indexed(self, amount: int) -> AllocatedPointer[T]:
return AllocatedPointer(
self.ensure() + amount,
self.size - amount,
self.assigned,
parent=self._parent,
)
def _get_parent(self) -> AllocatedPointer[T]:
parent = self
while parent._parent:
parent = parent._parent
return parent
@property
def freed(self) -> bool:
return self._get_parent()._freed
@freed.setter
def freed(self, value: bool) -> None:
self._get_parent()._freed = value
@property
def address(self) -> Optional[int]:
return self._address
@address.setter
def address(self, value: int) -> None:
self._address = value
def __repr__(self) -> str:
return f"AllocatedPointer(address={self.address}, size={self.size})"
def __add__(self, amount: int) -> AllocatedPointer[T]:
return self._indexed(amount)
def __sub__(self, amount: int) -> AllocatedPointer[T]:
return self._indexed(amount)
@handle
def free(self) -> None:
self.ensure_valid()
c_free(self.make_ct_pointer())
self.freed = True
@handle
def __getitem__(self, index: int) -> AllocatedPointer[T]:
if not isinstance(index, int):
raise ValueError(
f"memory indices must be int, not {type(index).__name__}",
)
return self._indexed(index)
@handle
def __setitem__(self, index: int, value: T) -> None:
if not isinstance(index, int):
raise ValueError(
f"memory indices must be int, not {type(index).__name__}",
)
ptr = self._indexed(index)
ptr A:
"""Resize a memory block created by malloc.
Args:
target: Pointer to reallocate.
size: New allocation size.
Returns:
Original object.
Raises:
InvalidSizeError: Object inside allocation is larger than attempted reallocation.
AllocationError: Raised when allocation fails, presumably due to no memory.
Example:
```py
ptr = malloc(1)
realloc(ptr, 2)
```
""" # noqa
if type(target) is StackAllocatedPointer:
raise TypeError("pointers to items on the stack may not be resized")
tsize: int = sys.getsizeof(~target)
if target.assigned and (tsize > size):
raise InvalidSizeError(
f"object inside memory is of size {tsize}, so memory cannot be set to size {size}", # noqa
)
addr = c_realloc(target.address, size)
if not addr:
raise AllocationError("failed to resize memory")
target.size = size
target.address = addr
return target