269 lines
7.6 KiB
PHP
269 lines
7.6 KiB
PHP
<?php # -*- coding: utf-8 mode: php -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
|
|
namespace nur;
|
|
|
|
use AppendIterator;
|
|
use ArrayIterator;
|
|
use EmptyIterator;
|
|
use Exception;
|
|
use Generator;
|
|
use Iterator;
|
|
use IteratorAggregate;
|
|
use NoRewindIterator;
|
|
use nur\b\ICloseable;
|
|
use nur\b\StopException;
|
|
use nur\b\ValueException;
|
|
use Traversable;
|
|
|
|
/**
|
|
* Class iter: gestion des itérateurs
|
|
*/
|
|
class iter {
|
|
private static function unexpected_type($object): ValueException {
|
|
return ValueException::unexpected_type("iterable", $object);
|
|
}
|
|
|
|
/**
|
|
* fermer "proprement" un itérateur ou un générateur. retourner true en cas de
|
|
* succès, ou false si c'est un générateur et qu'il ne supporte pas l'arrêt
|
|
* avec StopException (la valeur de retour n'est alors pas disponible)
|
|
*/
|
|
static function close($it): bool {
|
|
if ($it instanceof ICloseable) {
|
|
$it->close();
|
|
return true;
|
|
} elseif ($it instanceof Generator) {
|
|
try {
|
|
$it->throw(new StopException());
|
|
return true;
|
|
} catch (StopException $e) {
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* retourner la première valeur du tableau, de l'itérateur ou de l'instance
|
|
* de Traversable, ou $default si aucun élément n'est trouvé.
|
|
*/
|
|
static final function first($values, $default=null) {
|
|
if ($values instanceof IteratorAggregate) $values = $values->getIterator();
|
|
if ($values instanceof Iterator) {
|
|
try {
|
|
$values->rewind();
|
|
$value = $values->valid()? $values->current(): $default;
|
|
} finally {
|
|
self::close($values);
|
|
}
|
|
} elseif (is_array($values) || $values instanceof Traversable) {
|
|
$value = $default;
|
|
foreach ($values as $value) {
|
|
break;
|
|
}
|
|
} else {
|
|
throw self::unexpected_type($values);
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* retourner la première clé du tableau, de l'itérateur ou de l'instance
|
|
* de Traversable, ou $default si aucun élément n'est trouvé.
|
|
*/
|
|
static final function first_key($values, $default=null) {
|
|
if ($values instanceof IteratorAggregate) $values = $values->getIterator();
|
|
if ($values instanceof Iterator) {
|
|
try {
|
|
$values->rewind();
|
|
$key = $values->valid()? $values->key(): $default;
|
|
} finally {
|
|
self::close($values);
|
|
}
|
|
} elseif (is_array($values) || $values instanceof Traversable) {
|
|
$key = $default;
|
|
foreach ($values as $key => $ignored) {
|
|
break;
|
|
}
|
|
} else {
|
|
throw self::unexpected_type($values);
|
|
}
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* retourner la première valeur du tableau ou de l'itérateur, ou $default si
|
|
* aucun élément n'est trouvé. Les instances de Traversable ne sont pas supportés.
|
|
*
|
|
* si $rewind est true, appeler rewind() à la fin pour s'assurer que
|
|
* l'itérateur est fermé correctement.
|
|
*
|
|
* retourner un tableau [$value, $have_next, $it_nexts]
|
|
* - $have_next vaut true s'il y a encore des données qui suivent
|
|
* - si $rewind==false, $it_nexts est un itérateur qui permet d'accéder aux
|
|
* données suivantes
|
|
*/
|
|
static final function one($values, $default=null, bool $rewind=false): array {
|
|
if (is_array($values)) $values = new ArrayIterator($values);
|
|
|
|
$value = $default;
|
|
$have_next = false;
|
|
$it_nexts = new EmptyIterator();
|
|
|
|
if ($values instanceof IteratorAggregate) $values = $values->getIterator();
|
|
if ($values instanceof Iterator) {
|
|
$values->rewind();
|
|
if ($values->valid()) {
|
|
$value = $values->current();
|
|
$values->next();
|
|
$have_next = $values->valid();
|
|
if ($have_next) {
|
|
$next = $values->current();
|
|
$values->next();
|
|
if (!$rewind) {
|
|
$it_nexts = new AppendIterator();
|
|
$it_nexts->append(new ArrayIterator([$next]));
|
|
$it_nexts->append(new NoRewindIterator($values));
|
|
}
|
|
}
|
|
}
|
|
if ($rewind) $values->rewind();
|
|
} else {
|
|
throw ValueException::unexpected_type(Iterator::class, $values);
|
|
}
|
|
|
|
return [$value, $have_next, $it_nexts];
|
|
}
|
|
|
|
/**
|
|
* retourner [$first, $second, $all] où:
|
|
* - $first est la première valeur de l'itérateur, ou $default si pas de
|
|
* premier élément
|
|
* - $second est la deuxième valeur de l'itérateur, ou $default si pas de
|
|
* deuxième élément
|
|
* - $all est un iterateur permettant de parcourir *toute* les valeurs, sans
|
|
* devoir rembobiner $iterator
|
|
*
|
|
* cette méthode permet de savoir:
|
|
* - si l'itérateur n'a aucun élément ($first === $default)
|
|
* - si l'itérateur n'a qu'un seul élémement ($second === $default)
|
|
* - sinon il permet de parcourir toutes les valeurs normalement
|
|
*
|
|
* si $rewind est true, appeler rewind() à la fin pour s'assurer que
|
|
* l'itérateur est fermé correctement.
|
|
*/
|
|
static final function peek($iterator, $default=null, bool $rewind=false): array {
|
|
if (is_array($iterator)) $iterator = new ArrayIterator($iterator);
|
|
|
|
$first = $default;
|
|
$second = $default;
|
|
$all = new EmptyIterator();
|
|
if ($iterator instanceof IteratorAggregate) $iterator = $iterator->getIterator();
|
|
if ($iterator instanceof Iterator) {
|
|
$fsValues = [];
|
|
$iterator->rewind();
|
|
if ($iterator->valid()) {
|
|
$first = $iterator->current();
|
|
$fsValues[$iterator->key()] = $first;
|
|
$iterator->next();
|
|
if ($iterator->valid()) {
|
|
$second = $iterator->current();
|
|
$fsValues[$iterator->key()] = $second;
|
|
$iterator->next();
|
|
}
|
|
}
|
|
if ($rewind) {
|
|
$iterator->rewind();
|
|
} else {
|
|
$all = new AppendIterator();
|
|
$all->append(new ArrayIterator($fsValues));
|
|
$all->append(new NoRewindIterator($iterator));
|
|
}
|
|
} else {
|
|
throw ValueException::unexpected_type(Iterator::class, $iterator);
|
|
}
|
|
|
|
return [$first, $second, $all];
|
|
}
|
|
|
|
#############################################################################
|
|
# outils pour gérer de façon générique des instances de {@link Iterator} ou
|
|
# des arrays
|
|
|
|
/**
|
|
* @param $it ?iterable|array
|
|
* @return bool true si l'itérateur ou le tableau ont pu être réinitialisés
|
|
*/
|
|
static function rewind(&$it, ?Exception &$exception=null): bool {
|
|
if ($it instanceof Iterator) {
|
|
try {
|
|
$exception = null;
|
|
$it->rewind();
|
|
return true;
|
|
} catch (Exception $e) {
|
|
$exception = $e;
|
|
}
|
|
} elseif ($it !== null) {
|
|
reset($it);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param $it ?iterable|array
|
|
*/
|
|
static function valid($it): bool {
|
|
if ($it instanceof Iterator) {
|
|
return $it->valid();
|
|
} elseif ($it !== null) {
|
|
return key($it) !== null;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $it ?iterable|array
|
|
*/
|
|
static function current($it, &$key=null) {
|
|
if ($it instanceof Iterator) {
|
|
$key = $it->key();
|
|
return $it->current();
|
|
} elseif ($it !== null) {
|
|
$key = key($it);
|
|
return current($it);
|
|
} else {
|
|
$key = null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $it ?iterable|array
|
|
*/
|
|
static function next(&$it, ?Exception &$exception=null): void {
|
|
if ($it instanceof Iterator) {
|
|
try {
|
|
$exception = null;
|
|
$it->next();
|
|
} catch (Exception $e) {
|
|
$exception = $e;
|
|
}
|
|
} elseif ($it !== null) {
|
|
next($it);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* obtenir la valeur de retour si $it est un générateur terminé, ou null sinon
|
|
*/
|
|
static function get_return($it) {
|
|
if ($it instanceof Generator) {
|
|
try {
|
|
return $it->getReturn();
|
|
} catch (Exception $e) {
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|