aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
new file mode 100644
index 0000000000..ef9fe1014e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
@@ -0,0 +1,373 @@
+// Copyright 2014 Google Inc. 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.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.events.Location;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A class to handle lists and tuples in Skylark.
+ */
+@SkylarkModule(name = "list",
+ doc = "A language built-in type to support lists. Example of list literal:<br>"
+ + "<pre class=language-python>l = [1, 2, 3]</pre>"
+ + "Accessing elements is possible using indexing (starts from <code>0</code>):<br>"
+ + "<pre class=language-python>e = l[1] # e == 2</pre>"
+ + "Lists support the <code>+</code> operator to concatenate two lists. Example:<br>"
+ + "<pre class=language-python>l = [1, 2] + [3, 4] # l == [1, 2, 3, 4]\n"
+ + "l = [\"a\", \"b\"]\n"
+ + "l += [\"c\"] # l == [\"a\", \"b\", \"c\"]</pre>"
+ + "List elements have to be of the same type, <code>[1, 2, \"c\"]</code> results in an "
+ + "error. Lists - just like everything - are immutable, therefore <code>l[1] = \"a\""
+ + "</code> is not supported.")
+public abstract class SkylarkList implements Iterable<Object> {
+
+ private final boolean tuple;
+ private final Class<?> genericType;
+
+ private SkylarkList(boolean tuple, Class<?> genericType) {
+ this.tuple = tuple;
+ this.genericType = genericType;
+ }
+
+ /**
+ * The size of the list.
+ */
+ public abstract int size();
+
+ /**
+ * Returns true if the list is empty.
+ */
+ public abstract boolean isEmpty();
+
+ /**
+ * Returns the i-th element of the list.
+ */
+ public abstract Object get(int i);
+
+ /**
+ * Returns true if this list is a tuple.
+ */
+ public boolean isTuple() {
+ return tuple;
+ }
+
+ @VisibleForTesting
+ public Class<?> getGenericType() {
+ return genericType;
+ }
+
+ @Override
+ public String toString() {
+ return toList().toString();
+ }
+
+ // TODO(bazel-team): we should be very careful using this method. Check and remove
+ // auto conversions on the Java-Skylark interface if possible.
+ /**
+ * Converts this Skylark list to a Java list.
+ */
+ public abstract List<?> toList();
+
+ @SuppressWarnings("unchecked")
+ public <T> Iterable<T> to(Class<T> type) {
+ Preconditions.checkArgument(this == EMPTY_LIST || type.isAssignableFrom(genericType));
+ return (Iterable<T>) this;
+ }
+
+ private static final class EmptySkylarkList extends SkylarkList {
+ private EmptySkylarkList(boolean tuple) {
+ super(tuple, Object.class);
+ }
+
+ @Override
+ public Iterator<Object> iterator() {
+ return ImmutableList.of().iterator();
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+
+ @Override
+ public Object get(int i) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<?> toList() {
+ return isTuple() ? ImmutableList.of() : Lists.newArrayList();
+ }
+
+ @Override
+ public String toString() {
+ return "[]";
+ }
+ }
+
+ /**
+ * An empty Skylark list.
+ */
+ public static final SkylarkList EMPTY_LIST = new EmptySkylarkList(false);
+
+ private static final class SimpleSkylarkList extends SkylarkList {
+ private final ImmutableList<Object> list;
+
+ private SimpleSkylarkList(ImmutableList<Object> list, boolean tuple, Class<?> genericType) {
+ super(tuple, genericType);
+ this.list = Preconditions.checkNotNull(list);
+ }
+
+ @Override
+ public Iterator<Object> iterator() {
+ return list.iterator();
+ }
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return list.isEmpty();
+ }
+
+ @Override
+ public Object get(int i) {
+ return list.get(i);
+ }
+
+ @Override
+ public List<?> toList() {
+ return isTuple() ? list : Lists.newArrayList(list);
+ }
+
+ @Override
+ public String toString() {
+ return list.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return list.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SimpleSkylarkList)) {
+ return false;
+ }
+ SimpleSkylarkList other = (SimpleSkylarkList) obj;
+ return other.list.equals(this.list);
+ }
+ }
+
+ /**
+ * A Skylark list to support lazy iteration (i.e. we only call iterator on the object this
+ * list masks when it's absolutely necessary). This is useful if iteration is expensive
+ * (e.g. NestedSet-s). Size(), get() and isEmpty() are expensive operations but
+ * concatenation is quick.
+ */
+ private static final class LazySkylarkList extends SkylarkList {
+ private final Iterable<Object> iterable;
+ private ImmutableList<Object> list = null;
+
+ private LazySkylarkList(Iterable<Object> iterable, boolean tuple, Class<?> genericType) {
+ super(tuple, genericType);
+ this.iterable = Preconditions.checkNotNull(iterable);
+ }
+
+ @Override
+ public Iterator<Object> iterator() {
+ return iterable.iterator();
+ }
+
+ @Override
+ public int size() {
+ return Iterables.size(iterable);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return Iterables.isEmpty(iterable);
+ }
+
+ @Override
+ public Object get(int i) {
+ return getList().get(i);
+ }
+
+ @Override
+ public List<?> toList() {
+ return getList();
+ }
+
+ private ImmutableList<Object> getList() {
+ if (list == null) {
+ list = ImmutableList.copyOf(iterable);
+ }
+ return list;
+ }
+ }
+
+ /**
+ * A Skylark list to support quick concatenation of lists. Concatenation is O(1),
+ * size(), isEmpty() is O(n), get() is O(h).
+ */
+ private static final class ConcatenatedSkylarkList extends SkylarkList {
+ private final SkylarkList left;
+ private final SkylarkList right;
+
+ private ConcatenatedSkylarkList(
+ SkylarkList left, SkylarkList right, boolean tuple, Class<?> genericType) {
+ super(tuple, genericType);
+ this.left = Preconditions.checkNotNull(left);
+ this.right = Preconditions.checkNotNull(right);
+ }
+
+ @Override
+ public Iterator<Object> iterator() {
+ return Iterables.concat(left, right).iterator();
+ }
+
+ @Override
+ public int size() {
+ // We shouldn't evaluate the size function until it's necessary, because it can be expensive
+ // for lazy lists (e.g. lists containing a NestedSet).
+ // TODO(bazel-team): make this class more clever to store the size and empty parameters
+ // for every non-LazySkylarkList member.
+ return left.size() + right.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return left.isEmpty() && right.isEmpty();
+ }
+
+ @Override
+ public Object get(int i) {
+ int leftSize = left.size();
+ if (i < leftSize) {
+ return left.get(i);
+ } else {
+ return right.get(i - leftSize);
+ }
+ }
+
+ @Override
+ public List<?> toList() {
+ return ImmutableList.<Object>builder().addAll(left).addAll(right).build();
+ }
+ }
+
+ /**
+ * Returns a Skylark list containing elements without a type check. Only use if all elements
+ * are of the same type.
+ */
+ public static SkylarkList list(Collection<?> elements, Class<?> genericType) {
+ if (elements.isEmpty()) {
+ return EMPTY_LIST;
+ }
+ return new SimpleSkylarkList(ImmutableList.copyOf(elements), false, genericType);
+ }
+
+ /**
+ * Returns a Skylark list containing elements without a type check and without creating
+ * an immutable copy. Therefore the iterable containing elements must be immutable
+ * (which is not checked here so callers must be extra careful). This way
+ * it's possibly to create a SkylarkList without requesting the original iterator. This
+ * can be useful for nested set - list conversions.
+ */
+ @SuppressWarnings("unchecked")
+ public static SkylarkList lazyList(Iterable<?> elements, Class<?> genericType) {
+ return new LazySkylarkList((Iterable<Object>) elements, false, genericType);
+ }
+
+ /**
+ * Returns a Skylark list containing elements. Performs type check and throws an exception
+ * in case the list contains elements of different type.
+ */
+ public static SkylarkList list(Collection<?> elements, Location loc) throws EvalException {
+ if (elements.isEmpty()) {
+ return EMPTY_LIST;
+ }
+ return new SimpleSkylarkList(
+ ImmutableList.copyOf(elements), false, getGenericType(elements, loc));
+ }
+
+ private static Class<?> getGenericType(Collection<?> elements, Location loc)
+ throws EvalException {
+ Class<?> genericType = elements.iterator().next().getClass();
+ for (Object element : elements) {
+ Class<?> type = element.getClass();
+ if (!EvalUtils.getSkylarkType(genericType).equals(EvalUtils.getSkylarkType(type))) {
+ throw new EvalException(loc, String.format(
+ "Incompatible types in list: found a %s but the first element is a %s",
+ EvalUtils.getDataTypeNameFromClass(type),
+ EvalUtils.getDataTypeNameFromClass(genericType)));
+ }
+ }
+ return genericType;
+ }
+
+ /**
+ * Returns a Skylark list created from Skylark lists left and right. Throws an exception
+ * if they are not of the same generic type.
+ */
+ public static SkylarkList concat(SkylarkList left, SkylarkList right, Location loc)
+ throws EvalException {
+ if (left.isTuple() != right.isTuple()) {
+ throw new EvalException(loc, "cannot concatenate lists and tuples");
+ }
+ if (left == EMPTY_LIST) {
+ return right;
+ }
+ if (right == EMPTY_LIST) {
+ return left;
+ }
+ if (!left.genericType.equals(right.genericType)) {
+ throw new EvalException(loc, String.format("cannot concatenate list of %s with list of %s",
+ EvalUtils.getDataTypeNameFromClass(left.genericType),
+ EvalUtils.getDataTypeNameFromClass(right.genericType)));
+ }
+ return new ConcatenatedSkylarkList(left, right, left.isTuple(), left.genericType);
+ }
+
+ /**
+ * Returns a Skylark tuple containing elements.
+ */
+ public static SkylarkList tuple(List<?> elements) {
+ // Tuple elements do not have to have the same type.
+ return new SimpleSkylarkList(ImmutableList.copyOf(elements), true, Object.class);
+ }
+}