| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.syntax; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.Iterables; | |
| import com.google.common.collect.ObjectArrays; | |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; | |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; | |
| import java.util.AbstractCollection; | |
| import java.util.AbstractList; | |
| import java.util.Arrays; | |
| import java.util.Iterator; | |
| /** | |
| * A Skylark tuple, i.e. the value represented by {@code (1, 2, 3)}. Tuples are always immutable | |
| * (regardless of the {@link StarlarkThread} they are created in). | |
| */ | |
| @SkylarkModule( | |
| name = "tuple", | |
| category = SkylarkModuleCategory.BUILTIN, | |
| doc = | |
| "The built-in tuple type. Example tuple expressions:<br>" | |
| + "<pre class=language-python>x = (1, 2, 3)</pre>" | |
| + "Accessing elements is possible using indexing (starts from <code>0</code>):<br>" | |
| + "<pre class=language-python>e = x[1] # e == 2</pre>" | |
| + "Lists support the <code>+</code> operator to concatenate two tuples. Example:<br>" | |
| + "<pre class=language-python>x = (1, 2) + (3, 4) # x == (1, 2, 3, 4)\n" | |
| + "x = (\"a\", \"b\")\n" | |
| + "x += (\"c\",) # x == (\"a\", \"b\", \"c\")</pre>" | |
| + "Similar to lists, tuples support slice operations:" | |
| + "<pre class=language-python>('a', 'b', 'c', 'd')[1:3] # ('b', 'c')\n" | |
| + "('a', 'b', 'c', 'd')[::2] # ('a', 'c')\n" | |
| + "('a', 'b', 'c', 'd')[3:0:-1] # ('d', 'c', 'b')</pre>" | |
| + "Tuples are immutable, therefore <code>x[1] = \"a\"</code> is not supported.") | |
| public final class Tuple<E> extends AbstractList<E> implements Sequence<E> { | |
| private final Object[] elems; | |
| private Tuple(Object[] elems) { | |
| this.elems = elems; | |
| } | |
| // The shared (sole) empty tuple. | |
| private static final Tuple<?> EMPTY = new Tuple<>(new Object[0]); | |
| /** Returns the empty tuple, cast to have an arbitrary content type. */ | |
| @SuppressWarnings("unchecked") | |
| public static <T> Tuple<T> empty() { | |
| return (Tuple<T>) EMPTY; // unchecked | |
| } | |
| /** | |
| * Returns a Tuple that wraps the specified array, which must not be subsequently modified. The | |
| * caller is additionally trusted to choose an appropriate type T. | |
| */ | |
| static <T> Tuple<T> wrap(Object[] array) { | |
| return array.length == 0 ? empty() : new Tuple<T>(array); | |
| } | |
| /** Returns a tuple containing the given elements. */ | |
| @SuppressWarnings("unchecked") | |
| public static <T> Tuple<T> copyOf(Iterable<? extends T> seq) { | |
| if (seq instanceof Tuple) { | |
| return (Tuple<T>) seq; // unchecked | |
| } | |
| return wrap(Iterables.toArray(seq, Object.class)); | |
| } | |
| /** Returns a tuple containing the given elements. */ | |
| public static <T> Tuple<T> of(T... elems) { | |
| if (elems.length == 0) { | |
| return empty(); | |
| } | |
| return new Tuple<T>(Arrays.copyOf(elems, elems.length)); | |
| } | |
| /** Returns a two-element tuple. */ | |
| public static <T> Tuple<T> pair(T a, T b) { | |
| // Equivalent to of(a, b) but avoids variadic array allocation. | |
| return wrap(new Object[] {a, b}); | |
| } | |
| /** Returns a three-element tuple. */ | |
| public static <T> Tuple<T> triple(T a, T b, T c) { | |
| // Equivalent to of(a, b, c) but avoids variadic array allocation. | |
| return wrap(new Object[] {a, b, c}); | |
| } | |
| /** Returns a tuple that is the concatenation of two tuples. */ | |
| public static <T> Tuple<T> concat(Tuple<? extends T> x, Tuple<? extends T> y) { | |
| // TODO(adonovan): opt: exploit x + () == x; y + () == y. | |
| return wrap(ObjectArrays.concat(x.elems, y.elems, Object.class)); | |
| } | |
| @Override | |
| public boolean isImmutable() { | |
| for (Object x : elems) { | |
| if (!EvalUtils.isImmutable(x)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean isHashable() { | |
| for (Object x : elems) { | |
| if (!EvalUtils.isHashable(x)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return 9857 + 8167 * Arrays.hashCode(elems); | |
| } | |
| @Override | |
| public boolean equals(Object that) { | |
| // This slightly violates the java.util.List equivalence contract | |
| // because it considers the class, not just the elements. | |
| return this == that | |
| || (that instanceof Tuple && Arrays.equals(this.elems, ((Tuple) that).elems)); | |
| } | |
| @Override | |
| @SuppressWarnings("unchecked") | |
| public E get(int i) { | |
| return (E) elems[i]; // unchecked | |
| } | |
| @Override | |
| public int size() { | |
| return elems.length; | |
| } | |
| @Override | |
| public Tuple<E> subList(int from, int to) { | |
| return wrap(Arrays.copyOfRange(elems, from, to)); | |
| } | |
| @Override | |
| public Object[] toArray() { | |
| return Arrays.copyOf(elems, elems.length); | |
| } | |
| @Override | |
| public void repr(Printer printer) { | |
| // TODO(adonovan): when Printer moves into this package, | |
| // inline and simplify this, the sole call with isTuple=true. | |
| printer.printList(this, /*isTuple=*/ true); | |
| } | |
| // TODO(adonovan): StarlarkValue has 3 String methods yet still we need this fourth. Why? | |
| @Override | |
| public String toString() { | |
| return Starlark.repr(this); | |
| } | |
| @Override | |
| public ImmutableList<E> getImmutableList() { | |
| // Share the array with this (immutable) Tuple. | |
| return wrapImmutable(elems); | |
| } | |
| /** | |
| * Returns a new ImmutableList<T> backed by {@code array}, which must not be subsequently | |
| * modified. | |
| */ | |
| // TODO(adonovan): move this somewhere more appropriate. | |
| static <T> ImmutableList<T> wrapImmutable(Object[] array) { | |
| // Construct an ImmutableList that shares the array. | |
| // ImmutableList relies on the implementation of Collection.toArray | |
| // not subsequently modifying the returned array. | |
| return ImmutableList.copyOf( | |
| new AbstractCollection<T>() { | |
| @Override | |
| public Object[] toArray() { | |
| return array; | |
| } | |
| @Override | |
| public int size() { | |
| return array.length; | |
| } | |
| @Override | |
| public Iterator<T> iterator() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| }); | |
| } | |
| @Override | |
| public Tuple<E> getSlice(Mutability mu, int start, int stop, int step) { | |
| RangeList indices = new RangeList(start, stop, step); | |
| int n = indices.size(); | |
| if (step == 1) { // common case | |
| return subList(indices.at(0), indices.at(n)); | |
| } | |
| Object[] res = new Object[n]; | |
| for (int i = 0; i < n; ++i) { | |
| res[i] = elems[indices.at(i)]; | |
| } | |
| return wrap(res); | |
| } | |
| /** Returns a Tuple containing n consecutive repeats of this tuple. */ | |
| Tuple<E> repeat(int n) { | |
| if (n <= 0 || isEmpty()) { | |
| return empty(); | |
| } | |
| // TODO(adonovan): reject unreasonably large n. | |
| Object[] res = new Object[n * elems.length]; | |
| for (int i = 0; i < n; i++) { | |
| System.arraycopy(elems, 0, res, i * elems.length, elems.length); | |
| } | |
| return wrap(res); | |
| } | |
| } |