-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
378 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\DataStructures\Internal; | ||
|
||
use Typhoon\DataStructures\MutableTypedMap; | ||
use Typhoon\DataStructures\TypedKey; | ||
use Typhoon\DataStructures\TypedMap; | ||
|
||
/** | ||
* @internal | ||
* @psalm-internal Typhoon\DataStructures | ||
*/ | ||
final class ArrayTypedMap extends MutableTypedMap | ||
{ | ||
/** | ||
* @var array<non-empty-string, mixed> | ||
*/ | ||
private array $values = []; | ||
|
||
/** | ||
* @return non-empty-string | ||
*/ | ||
private static function keyToString(TypedKey $key): string | ||
{ | ||
return $key::class . '::' . $key->name; | ||
} | ||
|
||
protected function doPut(TypedKey $key, mixed $value): void | ||
{ | ||
$this->values[self::keyToString($key)] = $value; | ||
} | ||
|
||
public function putAll(TypedMap $map): void | ||
{ | ||
if ($map instanceof self) { | ||
$this->values = [...$this->values, ...$map->values]; | ||
} else { | ||
parent::putAll($map); | ||
} | ||
} | ||
|
||
public function contains(TypedKey $key): bool | ||
{ | ||
return isset($this->values[self::keyToString($key)]); | ||
} | ||
|
||
/** | ||
* @template V | ||
* @template D | ||
* @param TypedKey<V> $key | ||
* @param callable(): D $or | ||
* @return V|D | ||
*/ | ||
public function getOr(TypedKey $key, callable $or): mixed | ||
{ | ||
$keyString = self::keyToString($key); | ||
|
||
if (\array_key_exists($keyString, $this->values)) { | ||
/** @var V */ | ||
return $this->values[$keyString]; | ||
} | ||
|
||
return $or(); | ||
} | ||
|
||
public function remove(TypedKey ...$keys): void | ||
{ | ||
foreach ($keys as $key) { | ||
unset($this->values[self::keyToString($key)]); | ||
} | ||
} | ||
|
||
public function count(): int | ||
{ | ||
return \count($this->values); | ||
} | ||
|
||
protected function all(): \Traversable | ||
{ | ||
foreach ($this->values as $keyString => $value) { | ||
/** @var TypedKey $key */ | ||
$key = \constant($keyString); | ||
yield $key => $value; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\DataStructures\Internal; | ||
|
||
use Typhoon\DataStructures\MutableTypedMap; | ||
use Typhoon\DataStructures\TypedKey; | ||
use Typhoon\DataStructures\TypedMap; | ||
|
||
/** | ||
* @internal | ||
* @psalm-internal Typhoon\DataStructures | ||
* @psalm-suppress UnusedClass | ||
*/ | ||
final class SplObjectStorageTypedMap extends MutableTypedMap | ||
{ | ||
/** | ||
* @var \SplObjectStorage<TypedKey, mixed> | ||
*/ | ||
private \SplObjectStorage $values; | ||
|
||
public function __construct() | ||
{ | ||
/** @var \SplObjectStorage<TypedKey, mixed> */ | ||
$this->values = new \SplObjectStorage(); | ||
} | ||
|
||
protected function doPut(TypedKey $key, mixed $value): void | ||
{ | ||
$this->values->attach($key, $value); | ||
} | ||
|
||
public function contains(TypedKey $key): bool | ||
{ | ||
return $this->values->contains($key); | ||
} | ||
|
||
/** | ||
* @template V | ||
* @template D | ||
* @param TypedKey<V> $key | ||
* @param callable(): D $or | ||
* @return V|D | ||
*/ | ||
public function getOr(mixed $key, callable $or): mixed | ||
{ | ||
if ($this->values->contains($key)) { | ||
/** @var V */ | ||
return $this->values->offsetGet($key); | ||
} | ||
|
||
return $or(); | ||
} | ||
|
||
public function remove(TypedKey ...$keys): void | ||
{ | ||
foreach ($keys as $key) { | ||
$this->values->detach($key); | ||
} | ||
} | ||
|
||
public function putAll(TypedMap $map): void | ||
{ | ||
if ($map instanceof self) { | ||
$this->values->addAll($map->values); | ||
} else { | ||
parent::putAll($map); | ||
} | ||
} | ||
|
||
public function count(): int | ||
{ | ||
return \count($this->values); | ||
} | ||
|
||
protected function all(): \Traversable | ||
{ | ||
foreach ($this->values as $key) { | ||
yield $key => $this->values->getInfo(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\DataStructures; | ||
|
||
/** | ||
* @api | ||
*/ | ||
abstract class MutableTypedMap extends TypedMap | ||
{ | ||
/** | ||
* @template V | ||
* @param TypedKey<V> $key | ||
* @param V $value | ||
*/ | ||
final public function with(TypedKey $key, mixed $value): static | ||
{ | ||
if ($key instanceof OptionalTypedKey && $value === $key->default($this)) { | ||
if ($this->contains($key)) { | ||
$copy = clone $this; | ||
$copy->remove($key); | ||
|
||
return $copy; | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
$copy = clone $this; | ||
$copy->doPut($key, $value); | ||
|
||
return $copy; | ||
} | ||
|
||
final public function withAll(TypedMap $map): static | ||
{ | ||
$copy = clone $this; | ||
foreach ($map->all() as $key => $value) { | ||
$copy->put($key, $value); | ||
} | ||
|
||
return $copy; | ||
} | ||
|
||
final public function without(TypedKey ...$keys): static | ||
{ | ||
if ($keys === []) { | ||
return $this; | ||
} | ||
|
||
$copy = clone $this; | ||
$copy->remove(...$keys); | ||
|
||
return $copy; | ||
} | ||
|
||
/** | ||
* @template V | ||
* @param TypedKey<V> $key | ||
* @param V $value | ||
*/ | ||
final public function put(TypedKey $key, mixed $value): void | ||
{ | ||
if ($key instanceof OptionalTypedKey && $value === $key->default($this)) { | ||
$this->remove($key); | ||
} else { | ||
$this->doPut($key, $value); | ||
} | ||
} | ||
|
||
/** | ||
* @template V | ||
* @param TypedKey<V> $key | ||
* @param V $value | ||
*/ | ||
abstract protected function doPut(TypedKey $key, mixed $value): void; | ||
|
||
public function putAll(TypedMap $map): void | ||
{ | ||
foreach ($map->all() as $key => $value) { | ||
$this->put($key, $value); | ||
} | ||
} | ||
|
||
abstract public function remove(TypedKey ...$keys): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\DataStructures; | ||
|
||
/** | ||
* @api | ||
* @template-covariant TValue | ||
* @extends TypedKey<TValue> | ||
*/ | ||
interface OptionalTypedKey extends TypedKey | ||
{ | ||
/** | ||
* @return TValue | ||
*/ | ||
public function default(TypedMap $map): mixed; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\DataStructures; | ||
|
||
/** | ||
* @api | ||
* @template-covariant TValue | ||
*/ | ||
interface TypedKey extends \UnitEnum {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\DataStructures; | ||
|
||
use Typhoon\DataStructures\Internal\ArrayTypedMap; | ||
|
||
/** | ||
* @api | ||
* @implements \ArrayAccess<TypedKey, mixed> | ||
* @psalm-suppress UnusedClass | ||
*/ | ||
abstract class TypedMap implements \ArrayAccess, \Countable | ||
{ | ||
public static function create(): static | ||
{ | ||
/** @var static */ | ||
return new ArrayTypedMap(); | ||
} | ||
|
||
/** | ||
* @template V | ||
* @param TypedKey<V> $key | ||
* @param V $value | ||
*/ | ||
abstract public function with(TypedKey $key, mixed $value): static; | ||
|
||
abstract public function withAll(self $map): static; | ||
|
||
abstract public function without(TypedKey ...$keys): static; | ||
|
||
abstract public function contains(TypedKey $key): bool; | ||
|
||
final public function offsetExists(mixed $offset): bool | ||
{ | ||
return $this->contains($offset); | ||
} | ||
|
||
/** | ||
* @template V | ||
* @template D | ||
* @param TypedKey<V> $key | ||
* @param D $default | ||
* @return V|D | ||
*/ | ||
final public function get(TypedKey $key, mixed $default = null): mixed | ||
{ | ||
return $this->getOr($key, static fn(): mixed => $default); | ||
} | ||
|
||
/** | ||
* @template V | ||
* @template D | ||
* @param TypedKey<V> $key | ||
* @param callable(): D $or | ||
* @return V|D | ||
*/ | ||
abstract public function getOr(TypedKey $key, callable $or): mixed; | ||
|
||
/** | ||
* @template V | ||
* @param TypedKey<V> $offset | ||
* @return V | ||
* @throws KeyIsNotDefined | ||
*/ | ||
final public function offsetGet(mixed $offset): mixed | ||
{ | ||
return $this->getOr($offset, static function () use ($offset): void { throw new KeyIsNotDefined($offset); }); | ||
} | ||
|
||
/** | ||
* @return non-negative-int | ||
*/ | ||
abstract public function count(): int; | ||
|
||
/** | ||
* @return \Traversable<TypedKey, mixed> | ||
*/ | ||
abstract protected function all(): \Traversable; | ||
|
||
public function offsetSet(mixed $offset, mixed $value): never | ||
{ | ||
throw new \BadMethodCallException(); | ||
} | ||
|
||
public function offsetUnset(mixed $offset): never | ||
{ | ||
throw new \BadMethodCallException(); | ||
} | ||
} |