| // Copyright 2019 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 net.starlark.java.eval; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableMap; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| /** |
| * A StarlarkSemantics is an immutable set of optional name/value pairs that affect the dynamic |
| * behavior of Starlark operators and built-in functions, both core and application-defined. |
| * |
| * <p>For extensibility, a StarlarkSemantics only records a name/value pair when the value differs |
| * from the default value appropriate to that name. Values of most types are accessed using a {@link |
| * Key}, which defines the name, type, and default value for an entry. Boolean values are accessed |
| * using a string key; the string must have the prefix "+" or "-", indicating the default value: + |
| * for true, - for false. The reason for the special treatment of boolean entries is that they may |
| * enable or disable methods and parameters in the StarlarkMethod annotation system, and it is not |
| * possible to refer to a Key from a Java annotation, only a string. |
| * |
| * <p>It is the client's responsibility to ensure that a StarlarkSemantics does not encounter |
| * multiple Keys of the same name but different value types. |
| * |
| * <p>For Bazel's semantics options, see {@link packages.semantics.BuildLanguageOptions}. |
| * |
| * <p>For options that affect the static behavior of the Starlark frontend (lexer, parser, |
| * validator, compiler), see {@link FileOptions}. |
| */ |
| public final class StarlarkSemantics { |
| |
| /** Returns the empty semantics, in which every option has its default value. */ |
| public static final StarlarkSemantics DEFAULT = new StarlarkSemantics(ImmutableMap.of()); |
| |
| // A map entry must be accessed by Key iff its name has no [+-] prefix. |
| // Key<Boolean> is permitted too. |
| // The map keys are sorted but we avoid ImmutableSortedMap due to observed inefficiency. |
| private final ImmutableMap<String, Object> map; |
| private final int hashCode; |
| |
| private StarlarkSemantics(ImmutableMap<String, Object> map) { |
| this.map = map; |
| this.hashCode = map.hashCode(); |
| } |
| |
| /** Returns the value of a boolean option, which must have a [+-] prefix. */ |
| public boolean getBool(String name) { |
| char prefix = name.charAt(0); |
| Preconditions.checkArgument(prefix == '+' || prefix == '-'); |
| boolean defaultValue = prefix == '+'; |
| Boolean v = (Boolean) map.get(name); // prefix => cast cannot fail |
| return v != null ? v : defaultValue; |
| } |
| |
| /** Returns the value of the option denoted by {@code key}. */ |
| public <T> T get(Key<T> key) { |
| @SuppressWarnings("unchecked") // safe, if Key.names are unique |
| T v = (T) map.get(key.name); |
| return v != null ? v : key.defaultValue; |
| } |
| |
| /** A Key identifies an option, providing its name, type, and default value. */ |
| public static class Key<T> { |
| public final String name; |
| public final T defaultValue; |
| |
| /** |
| * Constructs a key. The name must not start with [+-]. The value must not be subsequently |
| * modified. |
| */ |
| public Key(String name, T defaultValue) { |
| char prefix = name.charAt(0); |
| Preconditions.checkArgument(prefix != '-' && prefix != '+'); |
| this.name = name; |
| this.defaultValue = Preconditions.checkNotNull(defaultValue); |
| } |
| |
| @Override |
| public String toString() { |
| return this.name; |
| } |
| } |
| |
| /** |
| * Returns a new builder that initially holds the same key/value pairs as this StarlarkSemantics. |
| */ |
| public Builder toBuilder() { |
| return new Builder(new TreeMap<>(map)); |
| } |
| |
| /** Returns a new empty builder. */ |
| public static Builder builder() { |
| return new Builder(new TreeMap<>()); |
| } |
| |
| /** A Builder is a mutable container used to construct an immutable StarlarkSemantics. */ |
| public static final class Builder { |
| private final TreeMap<String, Object> map; |
| |
| private Builder(TreeMap<String, Object> map) { |
| this.map = map; |
| } |
| |
| /** Sets the value for the specified key. */ |
| public <T> Builder set(Key<T> key, T value) { |
| if (!value.equals(key.defaultValue)) { |
| map.put(key.name, value); |
| } else { |
| map.remove(key.name); |
| } |
| return this; |
| } |
| |
| /** Sets the value for the boolean key, which must have a [+-] prefix. */ |
| public Builder setBool(String name, boolean value) { |
| char prefix = name.charAt(0); |
| Preconditions.checkArgument(prefix == '+' || prefix == '-'); |
| boolean defaultValue = prefix == '+'; |
| if (value != defaultValue) { |
| map.put(name, value); |
| } else { |
| map.remove(name); |
| } |
| return this; |
| } |
| |
| /** Returns an immutable StarlarkSemantics. */ |
| public StarlarkSemantics build() { |
| return new StarlarkSemantics(ImmutableMap.copyOf(map)); |
| } |
| } |
| |
| /** |
| * Returns true if a feature attached to the given toggling flags should be enabled. |
| * |
| * <ul> |
| * <li>If both parameters are empty, this indicates the feature is not controlled by flags, and |
| * should thus be enabled. |
| * <li>If the {@code enablingFlag} parameter is non-empty, this returns true if and only if that |
| * flag is true. (This represents a feature that is only on if a given flag is *on*). |
| * <li>If the {@code disablingFlag} parameter is non-empty, this returns true if and only if |
| * that flag is false. (This represents a feature that is only on if a given flag is *off*). |
| * <li>It is illegal to pass both parameters as non-empty. |
| * </ul> |
| */ |
| boolean isFeatureEnabledBasedOnTogglingFlags(String enablingFlag, String disablingFlag) { |
| Preconditions.checkArgument( |
| enablingFlag.isEmpty() || disablingFlag.isEmpty(), |
| "at least one of 'enablingFlag' or 'disablingFlag' must be empty"); |
| if (!enablingFlag.isEmpty()) { |
| return this.getBool(enablingFlag); |
| } else if (!disablingFlag.isEmpty()) { |
| return !this.getBool(disablingFlag); |
| } else { |
| return true; |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return hashCode; |
| } |
| |
| @Override |
| public boolean equals(Object that) { |
| return this == that |
| || (that instanceof StarlarkSemantics && this.map.equals(((StarlarkSemantics) that).map)); |
| } |
| |
| /** |
| * Returns a representation of this StarlarkSemantics' non-default key/value pairs in key order. |
| */ |
| @Override |
| public String toString() { |
| // Print "StarlarkSemantics{k=v, ...}", without +/- prefixes. |
| StringBuilder buf = new StringBuilder(); |
| buf.append("StarlarkSemantics{"); |
| String sep = ""; |
| for (Map.Entry<String, Object> e : map.entrySet()) { |
| String key = e.getKey(); |
| buf.append(sep); |
| sep = ", "; |
| if (key.charAt(0) == '+' || key.charAt(0) == '-') { |
| buf.append(key, 1, key.length()); |
| } else { |
| buf.append(key); |
| } |
| buf.append('=').append(e.getValue()); |
| } |
| return buf.append('}').toString(); |
| } |
| |
| // -- semantics options affecting the Starlark interpreter itself -- |
| |
| /** Change the behavior of 'print' statements. Used in tests to verify flag propagation. */ |
| public static final String PRINT_TEST_MARKER = "-print_test_marker"; |
| } |