001/*
002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package gololang;
012
013import org.eclipse.golo.runtime.InvalidDestructuringException;
014
015/**
016 * Structure having a head and a tail.
017 * <p>
018 * This interface can be used to define any recursive sequence of object,
019 * such as lists or generators. It is well suited to recursive algorithms, and
020 * thus can be seen as an alternative to the {@code Iterable} interface.
021 * <p>
022 * For example, one can compute the size of such an object with:
023 * <pre class="listing"><code class="lang-java" data-lang="java">
024 * static int size(HeadTail<?> o) {
025 *   if (o.isEmpty()) { return 0; }
026 *   return 1 + size(o.tail());
027 * }
028 * </code></pre>
029 * or in Golo:
030 * <pre class="listing"><code class="lang-golo" data-lang="golo">
031 * function size = |ht| -> match {
032 *   when ht: isEmpty() then 0
033 *   otherwhise 1 + size(ht: tail())
034 * }
035 * </code></pre>
036 * <p>
037 * Note that the {@code size} method is not provided since this interface
038 * can be implemented by infinite generators.
039 * <p>
040 * A {@code List} augmentation is provided, as well as corresponding special
041 * methods on arrays ({@code Object[]}/{@code array[]})
042 *
043 * @param <E> the type of the elements held in this structure.
044 */
045public interface HeadTail<E> extends Iterable<E> {
046
047  /**
048   * Get the head of the structure.
049   *
050   * @return the first element of the structure, or {@code null} if the structure is empty.
051   */
052  E head();
053
054  /**
055   * Get the tail of the structure.
056   * <p>
057   * To be side effect free, this method should return a deep copy of the original structure or an
058   * immutable view on it.
059   *
060   * @return a new {@code HeadTail} containing all the elements but the first, or a empty one
061   * if no elements remains.
062   */
063  HeadTail<E> tail();
064
065  /**
066   * Checks if the structure is empty or not.
067   *
068   * @return {@code true} if the structure contains no element, {@code false} otherwise.
069   */
070  boolean isEmpty();
071
072  /**
073   * Util method to wrap a {@code HeadTail} instance into an {@code Iterable}
074   *
075   * @param headTail the instance to wrap
076   * @return an iterable on the values contained in the wrapped instance
077   */
078  static <E> Iterable<E> toIterable(HeadTail<E> headTail) {
079    return () -> new HeadTailIterator<>(headTail);
080  }
081
082  /**
083   * New style destructuring helper.
084   *
085   * <p>If a remainer if included, it will be the tail of the last extracted value (even if skipped).
086   *
087   * @param number number of variable that will be affected.
088   * @param substruct whether the destructuring is complete or should contains a remainer.
089   * @param toSkip a boolean array indicating the elements to skip.
090   * @return an array containing the values to assign.
091   */
092  default Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) {
093    Object[] destruct = new Object[number];
094    HeadTail<E> current = this;
095    for (int i = 0; i < number - 1; i++) {
096      if (current.isEmpty()) {
097        throw InvalidDestructuringException.tooManyValues(number);
098      }
099      if (Boolean.valueOf(false).equals(toSkip[i])) {
100        destruct[i] = current.head();
101      }
102      current = current.tail();
103    }
104    if (substruct && Boolean.valueOf(false).equals(toSkip[number - 1])) {
105      destruct[number - 1] = current;
106    } else if (current.isEmpty()) {
107      throw InvalidDestructuringException.tooManyValues(number);
108    } else if (!current.tail().isEmpty() && Boolean.valueOf(false).equals(toSkip[number - 1])) {
109      throw InvalidDestructuringException.notEnoughValues(number, substruct);
110    } else if (Boolean.valueOf(false).equals(toSkip[number - 1])) {
111      destruct[number - 1] = current.head();
112    }
113    return destruct;
114  }
115}