From 233a46e905f9d1fab490fd4ffebf0959d9e94bec Mon Sep 17 00:00:00 2001 From: Florian Weikert Date: Wed, 16 Dec 2015 12:38:38 +0000 Subject: Skylark: implemented all() and any() -- MOS_MIGRATED_REVID=110348607 --- .../devtools/build/lib/syntax/EvalUtils.java | 25 ++++--- .../devtools/build/lib/syntax/MethodLibrary.java | 48 +++++++++++- .../devtools/build/lib/syntax/EvalUtilsTest.java | 10 +++ .../build/lib/syntax/MethodLibraryTest.java | 87 +++++++++++++++++++++- 4 files changed, 156 insertions(+), 14 deletions(-) (limited to 'src') 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) o; + return (Collection) o; } else if (o instanceof SkylarkList) { return ((SkylarkList) o).getList(); - } else if (o instanceof Map) { - Map, Object> dict = (Map, 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.copyOf(((String) o).split("(?!^)")); + return split((String) o); } else if (o instanceof Iterable) { - return (Iterable) 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 split(String value) { + ImmutableList.Builder 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 skylarkGlobalFunctions = ImmutableList.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 @@ -48,6 +48,16 @@ public class EvalUtilsTest { return new LinkedHashMap<>(); } + @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")); 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 @@ -265,6 +265,91 @@ public class MethodLibraryTest extends EvaluationTestCase { .testStatement("'\\tA\\n'.istitle()", true); } + @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( @@ -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', '_', '_']") -- cgit v1.2.3