diff options
author | Florian Weikert <fwe@google.com> | 2015-12-16 12:38:38 +0000 |
---|---|---|
committer | Damien Martin-Guillerez <dmarting@google.com> | 2015-12-16 12:56:06 +0000 |
commit | 233a46e905f9d1fab490fd4ffebf0959d9e94bec (patch) | |
tree | a5424d8313bd4986fce6b5d30acd47e1ee833103 /src | |
parent | 3e08d11ba6c3368a5687f609cb9040cf2a11bd6e (diff) |
Skylark: implemented all() and any()
--
MOS_MIGRATED_REVID=110348607
Diffstat (limited to 'src')
4 files changed, 156 insertions, 14 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java index e63b850f69..205075cffc 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java @@ -378,18 +378,16 @@ public final class EvalUtils { public static final StackManipulation toCollection = ByteCodeUtils.invoke(EvalUtils.class, "toCollection", Object.class, Location.class); - @SuppressWarnings("unchecked") public static Collection<?> toCollection(Object o, Location loc) throws EvalException { if (o instanceof Collection) { - return (Collection<Object>) o; + return (Collection<?>) o; } else if (o instanceof SkylarkList) { return ((SkylarkList) o).getList(); - } else if (o instanceof Map<?, ?>) { - Map<Comparable<?>, Object> dict = (Map<Comparable<?>, Object>) o; + } else if (o instanceof Map) { // For dictionaries we iterate through the keys only // For determinism, we sort the keys. try { - return SKYLARK_COMPARATOR.sortedCopy(dict.keySet()); + return SKYLARK_COMPARATOR.sortedCopy(((Map<?, ?>) o).keySet()); } catch (ComparisonException e) { throw new EvalException(loc, e); } @@ -404,17 +402,14 @@ public final class EvalUtils { public static final StackManipulation toIterable = ByteCodeUtils.invoke(EvalUtils.class, "toIterable", Object.class, Location.class); - @SuppressWarnings("unchecked") public static Iterable<?> toIterable(Object o, Location loc) throws EvalException { if (o instanceof String) { // This is not as efficient as special casing String in for and dict and list comprehension // statements. However this is a more unified way. - // The regex matches every character in the string until the end of the string, - // so "abc" will be split into ["a", "b", "c"]. - return ImmutableList.<Object>copyOf(((String) o).split("(?!^)")); + return split((String) o); } else if (o instanceof Iterable) { - return (Iterable<Object>) o; - } else if (o instanceof Map<?, ?>) { + return (Iterable<?>) o; + } else if (o instanceof Map) { return toCollection(o, loc); } else { throw new EvalException(loc, @@ -422,6 +417,14 @@ public final class EvalUtils { } } + private static ImmutableList<String> split(String value) { + ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); + for (char c : value.toCharArray()) { + builder.add(String.valueOf(c)); + } + return builder.build(); + } + /** * @return the size of the Skylark object or -1 in case the object doesn't have a size. */ diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java index a199ab4bcd..dc3f50b72e 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java @@ -1030,6 +1030,51 @@ public class MethodLibrary { return (list.size() == 1) ? EvalUtils.toIterable(list.get(0), loc) : list; } + @SkylarkSignature( + name = "all", + returnType = Boolean.class, + doc = "Returns true if all elements evaluate to True or if the collection is empty.", + mandatoryPositionals = { + @Param(name = "elements", type = Object.class, doc = "A string or a collection of elements.") + }, + useLocation = true + ) + private static BuiltinFunction all = + new BuiltinFunction("all") { + @SuppressWarnings("unused") // Accessed via Reflection. + public Boolean invoke(Object collection, Location loc) throws EvalException { + return !hasElementWithBooleanValue(collection, false, loc); + } + }; + + @SkylarkSignature( + name = "any", + returnType = Boolean.class, + doc = "Returns true if at least one element evaluates to True.", + mandatoryPositionals = { + @Param(name = "elements", type = Object.class, doc = "A string or a collection of elements.") + }, + useLocation = true + ) + private static BuiltinFunction any = + new BuiltinFunction("any") { + @SuppressWarnings("unused") // Accessed via Reflection. + public Boolean invoke(Object collection, Location loc) throws EvalException { + return hasElementWithBooleanValue(collection, true, loc); + } + }; + + private static boolean hasElementWithBooleanValue(Object collection, boolean value, Location loc) + throws EvalException { + Iterable<?> iterable = EvalUtils.toIterable(collection, loc); + for (Object obj : iterable) { + if (EvalUtils.toBoolean(obj) == value) { + return true; + } + } + return false; + } + // supported list methods @SkylarkSignature( name = "sorted", @@ -1846,10 +1891,9 @@ public class MethodLibrary { static final List<BaseFunction> skylarkGlobalFunctions = ImmutableList.<BaseFunction>builder() .addAll(buildGlobalFunctions) - .add(dir, fail, getattr, hasattr, max, min, print, set, struct, type) + .add(all, any, dir, fail, getattr, hasattr, max, min, print, set, struct, type) .build(); - /** * Collect global functions for the validation environment. */ diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java index 6a6bf70a7b..f5cb497f67 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java @@ -49,6 +49,16 @@ public class EvalUtilsTest { } @Test + public void testEmptyStringToIterable() throws Exception { + assertThat(EvalUtils.toIterable("", null)).isEmpty(); + } + + @Test + public void testStringToIterable() throws Exception { + assertThat(EvalUtils.toIterable("abc", null)).hasSize(3); + } + + @Test public void testDataTypeNames() throws Exception { assertEquals("string", EvalUtils.getDataTypeName("foo")); assertEquals("int", EvalUtils.getDataTypeName(3)); diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java index de20fc2f7e..f649ce2ab1 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java @@ -266,6 +266,91 @@ public class MethodLibraryTest extends EvaluationTestCase { } @Test + public void testAllWithEmptyValue() throws Exception { + new SkylarkTest() + .testStatement("all('')", true) + .testStatement("all([])", true) + .testIfExactError("type 'NoneType' is not iterable", "any(None)"); + } + + @Test + public void testAllWithPrimitiveType() throws Exception { + new SkylarkTest() + .testStatement("all('test')", true) + .testIfErrorContains("", "all(1)"); + } + + @Test + public void testAllWithList() throws Exception { + new SkylarkTest() + .testStatement("all([False])", false) + .testStatement("all([True, False])", false) + .testStatement("all([False, False])", false) + .testStatement("all([False, True])", false) + .testStatement("all(['', True])", false) + .testStatement("all([0, True])", false) + .testStatement("all([[], True])", false) + .testStatement("all([True, 't', 1])", true); + } + + @Test + public void testAllWithSet() throws Exception { + new SkylarkTest() + .testStatement("all(set([0]))", false) + .testStatement("all(set([1, 0]))", false) + .testStatement("all(set([1]))", true); + } + + @Test + public void testAllWithDict() throws Exception { + new SkylarkTest() + .testStatement("all({1 : None})", true) + .testStatement("all({None : 1})", false); + } + + @Test + public void testAnyWithEmptyValue() throws Exception { + new SkylarkTest() + .testStatement("any('')", false) + .testStatement("any([])", false) + .testIfExactError("type 'NoneType' is not iterable", "any(None)"); + } + + @Test + public void testAnyWithPrimitiveType() throws Exception { + new SkylarkTest() + .testStatement("any('test')", true) + .testIfErrorContains("", "any(1)"); + } + + @Test + public void testAnyWithList() throws Exception { + new SkylarkTest() + .testStatement("any([False])", false) + .testStatement("any([0])", false) + .testStatement("any([''])", false) + .testStatement("any([[]])", false) + .testStatement("any([True, False])", true) + .testStatement("any([False, False])", false) + .testStatement("any([False, '', 0])", false) + .testStatement("any([False, '', 42])", true); + } + + @Test + public void testAnyWithSet() throws Exception { + new SkylarkTest() + .testStatement("any(set([0]))", false) + .testStatement("any(set([1, 0]))", true); + } + + @Test + public void testAnyWithDict() throws Exception { + new SkylarkTest() + .testStatement("any({1 : None, '' : None})", true) + .testStatement("any({None : 1, '' : 2})", false); + } + + @Test public void testStackTraceLocation() throws Exception { new SkylarkTest().testIfErrorContains( "Traceback (most recent call last):\n\t" @@ -867,7 +952,7 @@ public class MethodLibraryTest extends EvaluationTestCase { @Test public void testReversedWithStrings() throws Exception { new BothModesTest() - .testEval("reversed('')", "['']") + .testEval("reversed('')", "[]") .testEval("reversed('a')", "['a']") .testEval("reversed('abc')", "['c', 'b', 'a']") .testEval("reversed('__test ')", "[' ', ' ', 't', 's', 'e', 't', '_', '_']") |