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;
 | |
|   }
 | |
| }
 |