From 432d715c2189ca772c96b102a1c2bc2a063988fa Mon Sep 17 00:00:00 2001 From: Francois-Rene Rideau Date: Thu, 18 Feb 2016 16:33:03 +0000 Subject: Implement pop(), popitem() and setdefault() for dict -- MOS_MIGRATED_REVID=114966513 --- .../devtools/build/lib/syntax/MethodLibrary.java | 132 ++++++++++++++++++++- .../google/devtools/build/lib/syntax/Runtime.java | 25 ++++ .../devtools/build/lib/syntax/SkylarkDict.java | 54 ++++++++- .../lib/syntax/SkylarkSignatureProcessor.java | 1 + 4 files changed, 207 insertions(+), 5 deletions(-) (limited to 'src/main/java') 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 52231c9a92..44cbdb956a 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 @@ -1342,7 +1342,8 @@ public class MethodLibrary { returnType = Object.class, doc = "Removes the item at the given position in the list, and returns it. " - + "If no index is specified, it removes and returns the last item in the list.", + + "If no index is specified, " + + "it removes and returns the last item in the list.", mandatoryPositionals = { @Param(name = "self", type = MutableList.class, doc = "This list."), }, @@ -1370,11 +1371,138 @@ public class MethodLibrary { } }; + @SkylarkSignature( + name = "pop", + objectType = SkylarkDict.class, + returnType = Object.class, + doc = + "Removes a key from the dict, and returns the associated value. " + + "If entry with that key was found, return the specified default value;" + + "if no default value was specified, fail instead.", + mandatoryPositionals = { + @Param(name = "self", type = SkylarkDict.class, doc = "This dict."), + @Param(name = "key", type = Object.class, doc = "The key."), + }, + optionalPositionals = { + @Param(name = "default", type = Object.class, defaultValue = "unbound", + doc = "a default value if the key is absent."), + }, + useLocation = true, + useEnvironment = true + ) + private static BuiltinFunction dictPop = + new BuiltinFunction("pop") { + public Object invoke(SkylarkDict self, Object key, Object defaultValue, + Location loc, Environment env) + throws EvalException { + Object value = self.get(key); + if (value != null) { + self.remove(key, loc, env); + return value; + } + if (defaultValue != Runtime.UNBOUND) { + return defaultValue; + } + throw new EvalException(loc, Printer.format("KeyError: %r", key)); + } + }; + + @SkylarkSignature( + name = "popitem", + objectType = SkylarkDict.class, + returnType = Tuple.class, + doc = + "Remove and return an arbitrary (key, value) pair from the dictionary. " + + "popitem() is useful to destructively iterate over a dictionary, " + + "as often used in set algorithms. " + + "If the dictionary is empty, calling popitem() fails. " + + "Note that in Skylark, as opposed to Python, " + + "the dictionary keys are actually sorted, " + + "and it is deterministic which pair will returned: that with the first key, " + + "according to the builtin total order. " + + "Thus if keys are numbers, the smallest key is returned first; " + + "if they are lists or strings, they are compared lexicographically, etc.", + mandatoryPositionals = { + @Param(name = "self", type = SkylarkDict.class, doc = "This dict.") + }, + useLocation = true, + useEnvironment = true + ) + private static BuiltinFunction dictPopItem = + new BuiltinFunction("popitem") { + public Tuple invoke(SkylarkDict self, + Location loc, Environment env) + throws EvalException { + if (self.isEmpty()) { + throw new EvalException(loc, "popitem(): dictionary is empty"); + } + Object key = self.firstKey(); + Object value = self.get(key); + self.remove(key, loc, env); + return Tuple.of(key, value); + } + }; + + @SkylarkSignature( + name = "clear", + objectType = SkylarkDict.class, + returnType = Runtime.NoneType.class, + doc = "Remove all items from the dictionary.", + mandatoryPositionals = { + @Param(name = "self", type = SkylarkDict.class, doc = "This dict.") + }, + useLocation = true, + useEnvironment = true + ) + private static BuiltinFunction dictClear = + new BuiltinFunction("clear") { + public Runtime.NoneType invoke(SkylarkDict self, + Location loc, Environment env) + throws EvalException { + self.clear(loc, env); + return Runtime.NONE; + } + }; + + @SkylarkSignature( + name = "setdefault", + objectType = SkylarkDict.class, + returnType = Object.class, + doc = + "If key is in the dictionary, return its value. " + + "If not, insert key with a value of default " + + "and return default. " + + "default defaults to None.", + mandatoryPositionals = { + @Param(name = "self", type = SkylarkDict.class, doc = "This dict."), + @Param(name = "key", type = Object.class, doc = "The key."), + }, + optionalPositionals = { + @Param(name = "default", type = Object.class, defaultValue = "None", + doc = "a default value if the key is absent."), + }, + useLocation = true, + useEnvironment = true + ) + private static BuiltinFunction dictSetDefault = + new BuiltinFunction("setdefault") { + public Object invoke(SkylarkDict self, Object key, Object defaultValue, + Location loc, Environment env) + throws EvalException { + Object value = self.get(key); + if (value != null) { + return value; + } + self.put(key, defaultValue, loc, env); + return defaultValue; + } + }; + // dictionary access operator @SkylarkSignature(name = "$index", documented = false, objectType = SkylarkDict.class, doc = "Looks up a value in a dictionary.", mandatoryPositionals = { - @Param(name = "self", type = SkylarkDict.class, doc = "This object."), + @Param(name = "self", type = SkylarkDict.class, doc = "This dict."), @Param(name = "key", type = Object.class, doc = "The index or key to access.")}, useLocation = true, useEnvironment = true) private static BuiltinFunction dictIndexOperator = new BuiltinFunction("$index") { diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java b/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java index f4b55ad4c4..2795da10c2 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java @@ -69,6 +69,31 @@ public final class Runtime { } } + /** Marker for unbound variables in cases where neither Java null nor Skylark None is suitable. */ + @Immutable + public static final class UnboundMarker implements SkylarkValue { + private UnboundMarker() {} + + @Override + public String toString() { + return ""; + } + + @Override + public boolean isImmutable() { + return true; + } + + @Override + public void write(Appendable buffer, char quotationMark) { + Printer.append(buffer, ""); + } + } + + @SkylarkSignature(name = "", returnType = UnboundMarker.class, documented = false, + doc = "Marker for unbound values in cases where neither Skylark None nor Java null can do.") + public static final UnboundMarker UNBOUND = new UnboundMarker(); + /** * Load {@link #NONE} on the stack. *

Kept close to the definition to avoid reflection errors when changing it. diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkDict.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkDict.java index 3c8db4ee1c..8cb1926bb5 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkDict.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkDict.java @@ -94,7 +94,7 @@ public final class SkylarkDict } /** - * The underlying contents is a (usually) mutable data structure. + * @return The underlying contents is a (usually) mutable data structure. * Read access is forwarded to these contents. * This object must not be modified outside an {@link Environment} * with a correct matching {@link Mutability}, @@ -106,16 +106,64 @@ public final class SkylarkDict return contents; } + /** + * Put an entry into a SkylarkDict. + * @param k the key + * @param v the associated value + * @param loc a {@link Location} in case of error + * @param env an {@link Environment}, to check Mutability + * @throws EvalException if the key is invalid + */ public void put(K k, V v, Location loc, Environment env) throws EvalException { checkMutable(loc, env); EvalUtils.checkValidDictKey(k); contents.put(k, v); } - public void putAll(Map m, Location loc, Environment env) + /** + * Put all the entries from a given dict into the SkylarkDict. + * @param m the map to copy + * @param loc a {@link Location} in case of error + * @param env an {@link Environment}, to check Mutability + * @throws EvalException if some key is invalid + */ + public void putAll(Map m, Location loc, Environment env) throws EvalException { checkMutable(loc, env); - putAllUnsafe(m); + for (Map.Entry e : m.entrySet()) { + KK k = e.getKey(); + EvalUtils.checkValidDictKey(k); + contents.put(k, e.getValue()); + } + } + + /** @return the first key in the dict */ + K firstKey() { + return contents.firstKey(); + } + + /** + * Delete the entry associated to a key. + * @param key the key to delete + * @param loc a {@link Location} in case of error + * @param env an {@link Environment}, to check Mutability + * @return the value associated to the key, or {@code null} if not present + * @throws EvalException if the dict is frozen. + */ + V remove(Object key, Location loc, Environment env) throws EvalException { + checkMutable(loc, env); + return contents.remove(key); + } + + /** + * Clear the dict. + * @param loc a {@link Location} in case of error + * @param env an {@link Environment}, to check Mutability + * @throws EvalException if the dict is frozen. + */ + void clear(Location loc, Environment env) throws EvalException { + checkMutable(loc, env); + contents.clear(); } // Other methods diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java index c27da119ed..1348267540 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java @@ -189,6 +189,7 @@ public class SkylarkSignatureProcessor { .setGlobals(Environment.CONSTANTS_ONLY) .setEventHandler(Environment.FAIL_FAST_HANDLER) .build() + .update("unbound", Runtime.UNBOUND) .eval(param.defaultValue()); } catch (Exception e) { throw new RuntimeException(String.format( -- cgit v1.2.3