Googler | bf6ac61 | 2019-10-18 13:05:54 -0700 | [diff] [blame] | 1 | // Copyright 2019 The Bazel Authors. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package com.google.devtools.build.lib.syntax; |
| 15 | |
Googler | 6901c79 | 2019-11-07 18:01:49 -0800 | [diff] [blame] | 16 | import com.google.common.collect.ImmutableMap; |
Googler | 8ac2210 | 2019-11-26 19:07:38 -0800 | [diff] [blame] | 17 | import com.google.common.collect.Iterables; |
Googler | b19f3ca | 2019-11-11 15:09:52 -0800 | [diff] [blame] | 18 | import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
Googler | 2e63b2a | 2019-11-11 08:59:12 -0800 | [diff] [blame] | 19 | import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils; |
| 20 | import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; |
Googler | bf6ac61 | 2019-10-18 13:05:54 -0700 | [diff] [blame] | 21 | import com.google.devtools.build.lib.util.Pair; |
adonovan | 821dd82 | 2019-12-19 12:24:41 -0800 | [diff] [blame] | 22 | import com.google.errorprone.annotations.CheckReturnValue; |
| 23 | import com.google.errorprone.annotations.FormatMethod; |
Googler | 084b64b | 2019-11-19 14:41:30 -0800 | [diff] [blame] | 24 | import java.util.List; |
Googler | bf6ac61 | 2019-10-18 13:05:54 -0700 | [diff] [blame] | 25 | import java.util.Map; |
| 26 | import java.util.Set; |
| 27 | import javax.annotation.Nullable; |
| 28 | |
| 29 | /** |
| 30 | * The Starlark class defines the most important entry points, constants, and functions needed by |
| 31 | * all clients of the Starlark interpreter. |
| 32 | */ |
| 33 | // TODO(adonovan): move these here: |
adonovan | 770606a | 2019-12-11 11:52:31 -0800 | [diff] [blame] | 34 | // equal, compare, getattr, index, slice, parse, exec, eval, and so on. |
Googler | bf6ac61 | 2019-10-18 13:05:54 -0700 | [diff] [blame] | 35 | public final class Starlark { |
| 36 | |
| 37 | private Starlark() {} // uninstantiable |
| 38 | |
Googler | 229080d | 2019-11-11 08:19:08 -0800 | [diff] [blame] | 39 | /** The Starlark None value. */ |
Googler | 641bdf7 | 2019-11-12 10:32:26 -0800 | [diff] [blame] | 40 | public static final NoneType NONE = NoneType.NONE; |
Googler | 229080d | 2019-11-11 08:19:08 -0800 | [diff] [blame] | 41 | |
Googler | e571224 | 2019-11-06 12:34:36 -0800 | [diff] [blame] | 42 | /** |
Googler | f7e471b | 2019-11-11 10:10:07 -0800 | [diff] [blame] | 43 | * A sentinel value passed to optional parameters of SkylarkCallable-annotated methods to indicate |
| 44 | * that no argument value was supplied. |
| 45 | */ |
Googler | b19f3ca | 2019-11-11 15:09:52 -0800 | [diff] [blame] | 46 | public static final Object UNBOUND = new UnboundMarker(); |
| 47 | |
| 48 | @Immutable |
Googler | 34f7058 | 2019-11-25 12:27:34 -0800 | [diff] [blame] | 49 | private static final class UnboundMarker implements StarlarkValue { |
Googler | b19f3ca | 2019-11-11 15:09:52 -0800 | [diff] [blame] | 50 | private UnboundMarker() {} |
| 51 | |
| 52 | @Override |
| 53 | public String toString() { |
| 54 | return "<unbound>"; |
| 55 | } |
| 56 | |
| 57 | @Override |
| 58 | public boolean isImmutable() { |
| 59 | return true; |
| 60 | } |
| 61 | |
| 62 | @Override |
Googler | 34f7058 | 2019-11-25 12:27:34 -0800 | [diff] [blame] | 63 | public void repr(Printer printer) { |
Googler | b19f3ca | 2019-11-11 15:09:52 -0800 | [diff] [blame] | 64 | printer.append("<unbound>"); |
| 65 | } |
| 66 | } |
Googler | f7e471b | 2019-11-11 10:10:07 -0800 | [diff] [blame] | 67 | |
| 68 | /** |
Googler | 6901c79 | 2019-11-07 18:01:49 -0800 | [diff] [blame] | 69 | * The universal bindings predeclared in every Starlark file, such as None, True, len, and range. |
| 70 | */ |
| 71 | public static final ImmutableMap<String, Object> UNIVERSE = makeUniverse(); |
| 72 | |
| 73 | private static ImmutableMap<String, Object> makeUniverse() { |
| 74 | ImmutableMap.Builder<String, Object> env = ImmutableMap.builder(); |
| 75 | env // |
| 76 | .put("False", false) |
| 77 | .put("True", true) |
Googler | 641bdf7 | 2019-11-12 10:32:26 -0800 | [diff] [blame] | 78 | .put("None", Starlark.NONE); |
Googler | 2e63b2a | 2019-11-11 08:59:12 -0800 | [diff] [blame] | 79 | addMethods(env, new MethodLibrary()); |
Googler | 6901c79 | 2019-11-07 18:01:49 -0800 | [diff] [blame] | 80 | return env.build(); |
| 81 | } |
| 82 | |
| 83 | /** |
Googler | 084b64b | 2019-11-19 14:41:30 -0800 | [diff] [blame] | 84 | * Reports whether the argument is a legal Starlark value: a string, boolean, integer, or |
| 85 | * StarlarkValue. |
| 86 | */ |
| 87 | public static boolean valid(Object x) { |
Googler | 34f7058 | 2019-11-25 12:27:34 -0800 | [diff] [blame] | 88 | return x instanceof StarlarkValue |
Googler | 084b64b | 2019-11-19 14:41:30 -0800 | [diff] [blame] | 89 | || x instanceof String |
| 90 | || x instanceof Boolean |
| 91 | || x instanceof Integer; |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * Returns {@code x} if it is a {@link #valid} Starlark value, otherwise throws |
| 96 | * IllegalArgumentException. |
| 97 | */ |
| 98 | public static <T> T checkValid(T x) { |
| 99 | if (!valid(x)) { |
| 100 | throw new IllegalArgumentException("invalid Starlark value: " + x.getClass()); |
| 101 | } |
| 102 | return x; |
| 103 | } |
| 104 | |
| 105 | /** |
| 106 | * Converts a Java value {@code x} to a Starlark one, if x is not already a valid Starlark value. |
| 107 | * A Java List or Map is converted to a Starlark list or dict, respectively, and null becomes |
| 108 | * {@link #NONE}. Any other non-Starlark value causes the function to throw |
| 109 | * IllegalArgumentException. |
| 110 | * |
| 111 | * <p>This function is applied to the results of @SkylarkCallable-annotated Java methods. |
| 112 | */ |
| 113 | public static Object fromJava(Object x, @Nullable Mutability mutability) { |
| 114 | if (x == null) { |
| 115 | return NONE; |
| 116 | } else if (Starlark.valid(x)) { |
| 117 | return x; |
| 118 | } else if (x instanceof List) { |
| 119 | return StarlarkList.copyOf(mutability, (List<?>) x); |
| 120 | } else if (x instanceof Map) { |
| 121 | return Dict.copyOf(mutability, (Map<?, ?>) x); |
| 122 | } else { |
| 123 | throw new IllegalArgumentException( |
| 124 | "cannot expose internal type to Starlark: " + x.getClass()); |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | /** |
Googler | e571224 | 2019-11-06 12:34:36 -0800 | [diff] [blame] | 129 | * Returns the truth value of a valid Starlark value, as if by the Starlark expression {@code |
| 130 | * bool(x)}. |
| 131 | */ |
| 132 | public static boolean truth(Object x) { |
| 133 | if (x instanceof Boolean) { |
| 134 | return (Boolean) x; |
Googler | 34f7058 | 2019-11-25 12:27:34 -0800 | [diff] [blame] | 135 | } else if (x instanceof StarlarkValue) { |
| 136 | return ((StarlarkValue) x).truth(); |
Googler | e571224 | 2019-11-06 12:34:36 -0800 | [diff] [blame] | 137 | } else if (x instanceof String) { |
| 138 | return !((String) x).isEmpty(); |
| 139 | } else if (x instanceof Integer) { |
| 140 | return (Integer) x != 0; |
| 141 | } else { |
| 142 | throw new IllegalArgumentException("invalid Starlark value: " + x.getClass()); |
| 143 | } |
| 144 | } |
| 145 | |
Googler | 292a5c0 | 2019-11-25 14:53:08 -0800 | [diff] [blame] | 146 | /** |
| 147 | * Returns an iterable view of {@code x} if it is an iterable Starlark value; throws EvalException |
| 148 | * otherwise. |
| 149 | * |
| 150 | * <p>Whereas the interpreter temporarily freezes the iterable value using {@link EvalUtils#lock} |
| 151 | * and {@link EvalUtils#unlock} while iterating in {@code for} loops and comprehensions, iteration |
| 152 | * using this method does not freeze the value. Callers should exercise care not to mutate the |
| 153 | * underlying object during iteration. |
| 154 | */ |
| 155 | public static Iterable<?> toIterable(Object x) throws EvalException { |
| 156 | if (x instanceof StarlarkIterable) { |
| 157 | return (Iterable<?>) x; |
| 158 | } |
adonovan | 821dd82 | 2019-12-19 12:24:41 -0800 | [diff] [blame] | 159 | throw errorf("type '%s' is not iterable", EvalUtils.getDataTypeName(x)); |
Googler | 292a5c0 | 2019-11-25 14:53:08 -0800 | [diff] [blame] | 160 | } |
| 161 | |
Googler | 8ac2210 | 2019-11-26 19:07:38 -0800 | [diff] [blame] | 162 | /** |
| 163 | * Returns a new array containing the elements of Starlark iterable value {@code x}. A Starlark |
| 164 | * value is iterable if it implements {@link StarlarkIterable}. |
| 165 | */ |
| 166 | public static Object[] toArray(Object x) throws EvalException { |
| 167 | // Specialize Sequence and Dict to avoid allocation and/or indirection. |
| 168 | if (x instanceof Sequence) { |
| 169 | return ((Sequence<?>) x).toArray(); |
| 170 | } else if (x instanceof Dict) { |
| 171 | return ((Dict<?, ?>) x).keySet().toArray(); |
| 172 | } else { |
| 173 | return Iterables.toArray(toIterable(x), Object.class); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /** |
adonovan | f6846f6 | 2019-12-04 11:36:21 -0800 | [diff] [blame] | 178 | * Returns the length of a Starlark string, sequence (such as a list or tuple), dict, or other |
| 179 | * iterable, as if by the Starlark expression {@code len(x)}, or -1 if the value is valid but has |
| 180 | * no length. |
Googler | 8ac2210 | 2019-11-26 19:07:38 -0800 | [diff] [blame] | 181 | */ |
| 182 | public static int len(Object x) { |
| 183 | if (x instanceof String) { |
| 184 | return ((String) x).length(); |
| 185 | } else if (x instanceof Sequence) { |
| 186 | return ((Sequence) x).size(); |
| 187 | } else if (x instanceof Dict) { |
| 188 | return ((Dict) x).size(); |
| 189 | } else if (x instanceof StarlarkIterable) { |
adonovan | f6846f6 | 2019-12-04 11:36:21 -0800 | [diff] [blame] | 190 | // Iterables.size runs in constant time if x implements Collection. |
Googler | 8ac2210 | 2019-11-26 19:07:38 -0800 | [diff] [blame] | 191 | return Iterables.size((Iterable<?>) x); |
| 192 | } else { |
adonovan | f6846f6 | 2019-12-04 11:36:21 -0800 | [diff] [blame] | 193 | checkValid(x); |
| 194 | return -1; // valid but not a sequence |
Googler | 8ac2210 | 2019-11-26 19:07:38 -0800 | [diff] [blame] | 195 | } |
| 196 | } |
| 197 | |
Googler | b1e232d | 2019-11-22 15:29:43 -0800 | [diff] [blame] | 198 | /** Returns the string form of a value as if by the Starlark expression {@code str(x)}. */ |
| 199 | public static String str(Object x) { |
| 200 | return Printer.getPrinter().str(x).toString(); |
| 201 | } |
| 202 | |
| 203 | /** Returns the string form of a value as if by the Starlark expression {@code repr(x)}. */ |
| 204 | public static String repr(Object x) { |
| 205 | return Printer.getPrinter().repr(x).toString(); |
| 206 | } |
| 207 | |
| 208 | /** Returns a string formatted as if by the Starlark expression {@code pattern % arguments}. */ |
| 209 | public static String format(String pattern, Object... arguments) { |
| 210 | return Printer.getPrinter().format(pattern, arguments).toString(); |
| 211 | } |
| 212 | |
| 213 | /** Returns a string formatted as if by the Starlark expression {@code pattern % arguments}. */ |
| 214 | public static String formatWithList(String pattern, List<?> arguments) { |
| 215 | return Printer.getPrinter().formatWithList(pattern, arguments).toString(); |
| 216 | } |
| 217 | |
Googler | 2e63b2a | 2019-11-11 08:59:12 -0800 | [diff] [blame] | 218 | /** |
adonovan | 972ade8 | 2019-12-18 13:58:14 -0800 | [diff] [blame] | 219 | * Calls the function-like value {@code fn} in the specified thread, passing it the given |
| 220 | * positional and named arguments, as if by the Starlark expression {@code fn(*args, **kwargs)}. |
adonovan | 770606a | 2019-12-11 11:52:31 -0800 | [diff] [blame] | 221 | */ |
| 222 | public static Object call( |
| 223 | StarlarkThread thread, |
| 224 | Object fn, |
adonovan | 770606a | 2019-12-11 11:52:31 -0800 | [diff] [blame] | 225 | List<Object> args, |
| 226 | Map<String, Object> kwargs) |
| 227 | throws EvalException, InterruptedException { |
adonovan | 972ade8 | 2019-12-18 13:58:14 -0800 | [diff] [blame] | 228 | Object[] named = new Object[2 * kwargs.size()]; |
| 229 | int i = 0; |
| 230 | for (Map.Entry<String, Object> e : kwargs.entrySet()) { |
| 231 | named[i++] = e.getKey(); |
| 232 | named[i++] = e.getValue(); |
| 233 | } |
adonovan | c332799 | 2020-01-08 09:09:11 -0800 | [diff] [blame] | 234 | return fastcall(thread, fn, args.toArray(), named); |
adonovan | 972ade8 | 2019-12-18 13:58:14 -0800 | [diff] [blame] | 235 | } |
| 236 | |
| 237 | /** |
| 238 | * Calls the function-like value {@code fn} in the specified thread, passing it the given |
| 239 | * positional and named arguments in the "fastcall" array representation. |
| 240 | */ |
| 241 | public static Object fastcall( |
adonovan | c332799 | 2020-01-08 09:09:11 -0800 | [diff] [blame] | 242 | StarlarkThread thread, Object fn, Object[] positional, Object[] named) |
adonovan | 972ade8 | 2019-12-18 13:58:14 -0800 | [diff] [blame] | 243 | throws EvalException, InterruptedException { |
adonovan | 770606a | 2019-12-11 11:52:31 -0800 | [diff] [blame] | 244 | StarlarkCallable callable; |
| 245 | if (fn instanceof StarlarkCallable) { |
| 246 | callable = (StarlarkCallable) fn; |
| 247 | } else { |
| 248 | // @SkylarkCallable(selfCall)? |
| 249 | MethodDescriptor desc = |
| 250 | CallUtils.getSelfCallMethodDescriptor(thread.getSemantics(), fn.getClass()); |
| 251 | if (desc == null) { |
adonovan | c332799 | 2020-01-08 09:09:11 -0800 | [diff] [blame] | 252 | throw Starlark.errorf("'%s' object is not callable", EvalUtils.getDataTypeName(fn)); |
adonovan | 770606a | 2019-12-11 11:52:31 -0800 | [diff] [blame] | 253 | } |
| 254 | callable = new BuiltinCallable(fn, desc.getName(), desc); |
| 255 | } |
| 256 | |
adonovan | c332799 | 2020-01-08 09:09:11 -0800 | [diff] [blame] | 257 | thread.push(callable); |
adonovan | 1d444be | 2019-12-11 12:22:27 -0800 | [diff] [blame] | 258 | try { |
adonovan | c332799 | 2020-01-08 09:09:11 -0800 | [diff] [blame] | 259 | return callable.fastcall(thread, thread.getCallerLocation(), positional, named); |
adonovan | 1d444be | 2019-12-11 12:22:27 -0800 | [diff] [blame] | 260 | } finally { |
| 261 | thread.pop(); |
| 262 | } |
adonovan | 770606a | 2019-12-11 11:52:31 -0800 | [diff] [blame] | 263 | } |
| 264 | |
| 265 | /** |
adonovan | 821dd82 | 2019-12-19 12:24:41 -0800 | [diff] [blame] | 266 | * Returns a new EvalException with no location and an error message produced by Java-style string |
| 267 | * formatting ({@code String.format(format, args)}). Use {@code errorf("%s", msg)} to produce an |
| 268 | * error message from a non-constant expression {@code msg}. |
| 269 | */ |
| 270 | @FormatMethod |
| 271 | @CheckReturnValue // don't forget to throw it |
| 272 | public static EvalException errorf(String format, Object... args) { |
| 273 | return new EvalException(null, String.format(format, args)); |
| 274 | } |
| 275 | |
| 276 | /** |
Googler | 2e63b2a | 2019-11-11 08:59:12 -0800 | [diff] [blame] | 277 | * Adds to the environment {@code env} all {@code StarlarkCallable}-annotated fields and methods |
| 278 | * of value {@code v}. The class of {@code v} must have or inherit a {@code SkylarkModule} or |
| 279 | * {@code SkylarkGlobalLibrary} annotation. |
| 280 | */ |
| 281 | public static void addMethods(ImmutableMap.Builder<String, Object> env, Object v) { |
| 282 | Class<?> cls = v.getClass(); |
| 283 | if (!SkylarkInterfaceUtils.hasSkylarkGlobalLibrary(cls) |
| 284 | && SkylarkInterfaceUtils.getSkylarkModule(cls) == null) { |
| 285 | throw new IllegalArgumentException( |
| 286 | cls.getName() + " is annotated with neither @SkylarkGlobalLibrary nor @SkylarkModule"); |
| 287 | } |
Googler | 15a700c | 2019-11-27 12:45:29 -0800 | [diff] [blame] | 288 | // TODO(adonovan): logically this should be a parameter. |
| 289 | StarlarkSemantics semantics = StarlarkSemantics.DEFAULT_SEMANTICS; |
| 290 | for (String name : CallUtils.getMethodNames(semantics, v.getClass())) { |
adonovan | 4e2b495 | 2019-12-10 12:19:20 -0800 | [diff] [blame] | 291 | // We pass desc=null instead of the descriptor that CallUtils.getMethod would |
| 292 | // return because DEFAULT_SEMANTICS is probably incorrect for the call. |
| 293 | // The effect is that the default semantics determine which methods appear in |
| 294 | // env, but the thread's semantics determine which method calls succeed. |
| 295 | env.put(name, new BuiltinCallable(v, name, /*desc=*/ null)); |
Googler | 2e63b2a | 2019-11-11 08:59:12 -0800 | [diff] [blame] | 296 | } |
| 297 | } |
| 298 | |
| 299 | /** |
| 300 | * Adds to the environment {@code env} the value {@code v}, under its annotated name. The class of |
| 301 | * {@code v} must have or inherit a {@code SkylarkModule} annotation. |
| 302 | */ |
| 303 | public static void addModule(ImmutableMap.Builder<String, Object> env, Object v) { |
| 304 | Class<?> cls = v.getClass(); |
| 305 | SkylarkModule annot = SkylarkInterfaceUtils.getSkylarkModule(cls); |
| 306 | if (annot == null) { |
| 307 | throw new IllegalArgumentException(cls.getName() + " is not annotated with @SkylarkModule"); |
| 308 | } |
| 309 | env.put(annot.name(), v); |
| 310 | } |
| 311 | |
adonovan | 972ade8 | 2019-12-18 13:58:14 -0800 | [diff] [blame] | 312 | /** |
| 313 | * Checks the {@code positional} and {@code named} arguments supplied to an implementation of |
| 314 | * {@link StarlarkCallable#fastcall} to ensure they match the {@code signature}. It returns an |
| 315 | * array of effective parameter values corresponding to the parameters of the signature. Newly |
| 316 | * allocated values (e.g. a {@code **kwargs} dict) use the Mutability {@code mu}. |
| 317 | * |
| 318 | * <p>If the function has optional parameters, their default values must be supplied by {@code |
| 319 | * defaults}; see {@link BaseFunction#getDefaultValues} for details. |
| 320 | * |
| 321 | * <p>The caller is responsible for accessing the correct element and casting to an appropriate |
| 322 | * type. |
| 323 | * |
| 324 | * <p>On failure, it throws an EvalException incorporating {@code func.toString()}. |
| 325 | */ |
| 326 | public static Object[] matchSignature( |
| 327 | FunctionSignature signature, |
| 328 | StarlarkCallable func, // only used in error messages |
| 329 | @Nullable Tuple<Object> defaults, |
| 330 | @Nullable Mutability mu, |
| 331 | Object[] positional, |
| 332 | Object[] named) |
| 333 | throws EvalException { |
| 334 | // TODO(adonovan): move implementation here. |
| 335 | return BaseFunction.matchSignature(signature, func, defaults, mu, positional, named); |
| 336 | } |
| 337 | |
Googler | bf6ac61 | 2019-10-18 13:05:54 -0700 | [diff] [blame] | 338 | // TODO(adonovan): |
| 339 | // |
| 340 | // The code below shows the API that is the destination toward which all of the recent |
| 341 | // tiny steps are headed. It doesn't work yet, but it helps to remember our direction. |
| 342 | // |
| 343 | // The API assumes that the "universe" portion (None, len, str) of the "predeclared" lexical block |
| 344 | // is always available, so clients needn't mention it in the API. Starlark.UNIVERSE will expose it |
| 345 | // as a public constant. |
| 346 | // |
| 347 | // Q. is there any value to returning the Module as opposed to just its global bindings as a Map? |
| 348 | // The Go implementation does the latter and it works well. |
| 349 | // This would allow the the Module class to be private. |
| 350 | // The Bazel "Label" function, and various Bazel caller whitelists, depend on |
| 351 | // being able to dig the label metadata out of a function's module, |
| 352 | // but this could be addressed with a StarlarkFunction.getModuleLabel accessor. |
| 353 | // A. The Module has an associated mutability (that of the thread), |
| 354 | // and it might benefit from a 'freeze' method. |
| 355 | // (But longer term, we might be able to eliminate Thread.mutability, |
| 356 | // and the concept of a shared Mutability entirely, as in go.starlark.net.) |
| 357 | // |
| 358 | // Any FlagRestrictedValues among 'predeclared' and 'env' maps are implicitly filtered by the |
| 359 | // semantics or thread.semantics. |
| 360 | // |
| 361 | // For exec(file), 'predeclared' corresponds exactly to the predeclared environment (sans |
| 362 | // UNIVERSE) as described in the language spec. For eval(expr), 'env' is the complete environment |
| 363 | // in which the expression is evaluated, which might include a mixture of predeclared, global, |
| 364 | // file-local, and function-local variables, as when (for example) the debugger evaluates an |
| 365 | // expression as if at a particular point in the source. As far as 'eval' is concerned, there is |
| 366 | // no difference in kind between these bindings. |
| 367 | // |
| 368 | // The API does not rely on StarlarkThread acting as an environment, or on thread.globals. |
| 369 | // |
| 370 | // These functions could be implemented today with minimal effort. |
| 371 | // The challenge is to migrate all the callers from the old API, |
| 372 | // and in particular to reduce their assumptions about thread.globals, |
| 373 | // which is going away. |
| 374 | |
| 375 | // ---- One shot execution API: parse, compile, and execute ---- |
| 376 | |
| 377 | /** |
| 378 | * Parse the input as a file, validate it in the specified predeclared environment, compile it, |
| 379 | * and execute it. On success, the module is returned; on failure, it throws an exception. |
| 380 | */ |
| 381 | public static Module exec( |
| 382 | StarlarkThread thread, ParserInput input, Map<String, Object> predeclared) |
| 383 | throws SyntaxError, EvalException, InterruptedException { |
| 384 | // Pseudocode: |
| 385 | // file = StarlarkFile.parse(input) |
| 386 | // validateFile(file, predeclared.keys, thread.semantics) |
| 387 | // prog = compile(file.statements) |
| 388 | // module = new module(predeclared) |
| 389 | // toplevel = new StarlarkFunction(prog.toplevel, module) |
| 390 | // call(thread, toplevel) |
| 391 | // return module # or module.globals? |
| 392 | throw new UnsupportedOperationException(); |
| 393 | } |
| 394 | |
| 395 | /** |
| 396 | * Parse the input as an expression, validate it in the specified environment, compile it, and |
| 397 | * evaluate it. On success, the expression's value is returned; on failure, it throws an |
| 398 | * exception. |
| 399 | */ |
| 400 | public static Object eval(StarlarkThread thread, ParserInput input, Map<String, Object> env) |
| 401 | throws SyntaxError, EvalException, InterruptedException { |
| 402 | // Pseudocode: |
| 403 | // StarlarkFunction fn = exprFunc(input, env, thread.semantics) |
| 404 | // return call(thread, fn) |
| 405 | throw new UnsupportedOperationException(); |
| 406 | } |
| 407 | |
| 408 | /** |
| 409 | * Parse the input as a file, validate it in the specified environment, compile it, and execute |
| 410 | * it. If the final statement is an expression, return its value. |
| 411 | * |
| 412 | * <p>This complicated function, which combines exec and eval, is intended for use in a REPL or |
| 413 | * debugger. In case of parse of validation error, it throws SyntaxError. In case of execution |
| 414 | * error, the function returns partial results: the incomplete module plus the exception. |
| 415 | * |
| 416 | * <p>Assignments in the input act as updates to a new module created by this function, which is |
| 417 | * returned. |
| 418 | * |
| 419 | * <p>In a typical REPL, the module bindings may be provided as predeclared bindings to the next |
| 420 | * call. |
| 421 | * |
| 422 | * <p>In a typical debugger, predeclared might contain the complete environment at a particular |
| 423 | * point in a running program, including its predeclared, global, and local variables. Assignments |
| 424 | * in the debugger affect only the ephemeral module created by this call, not the values of |
| 425 | * bindings observable by the debugged Starlark program. Thus execAndEval("x = 1; x + x") will |
| 426 | * return a value of 2, and a module containing x=1, but it will not affect the value of any |
| 427 | * variable named x in the debugged program. |
| 428 | * |
| 429 | * <p>A REPL will typically set the legacy "load binds globally" semantics flag, otherwise the |
| 430 | * names bound by a load statement will not be visible in the next REPL chunk. |
| 431 | */ |
| 432 | public static ModuleAndValue execAndEval( |
| 433 | StarlarkThread thread, ParserInput input, Map<String, Object> predeclared) |
| 434 | throws SyntaxError { |
| 435 | // Pseudocode: |
| 436 | // file = StarlarkFile.parse(input) |
| 437 | // validateFile(file, predeclared.keys, thread.semantics) |
| 438 | // prog = compile(file.statements + [return lastexpr]) |
| 439 | // module = new module(predeclared) |
| 440 | // toplevel = new StarlarkFunction(prog.toplevel, module) |
| 441 | // value = call(thread, toplevel) |
| 442 | // return (module, value, error) # or module.globals? |
| 443 | throw new UnsupportedOperationException(); |
| 444 | } |
| 445 | |
| 446 | /** |
| 447 | * The triple returned by {@link #execAndEval}. At most one of {@code value} and {@code error} is |
| 448 | * set. |
| 449 | */ |
| 450 | public static class ModuleAndValue { |
| 451 | /** The module, containing global values from top-level assignments. */ |
| 452 | public Module module; |
| 453 | /** The value of the final expression, if any, on success. */ |
| 454 | @Nullable public Object value; |
| 455 | /** An EvalException or InterruptedException, if execution failed. */ |
| 456 | @Nullable public Exception error; |
| 457 | } |
| 458 | |
| 459 | // ---- Two-stage API: compilation and execution are separate --- |
| 460 | |
| 461 | /** |
| 462 | * Parse the input as a file, validates it in the specified predeclared environment (a set of |
| 463 | * names, optionally filtered by the semantics), and compiles it to a Program. It throws |
| 464 | * SyntaxError in case of scan/parse/validation error. |
| 465 | * |
| 466 | * <p>In addition to the program, it returns the validated syntax tree. This permits clients such |
| 467 | * as Bazel to inspect the syntax (for BUILD dialect checks, glob prefetching, etc.) |
| 468 | */ |
| 469 | public static Pair<Program, StarlarkFile> compileFile( |
| 470 | ParserInput input, // |
| 471 | Set<String> predeclared, |
| 472 | StarlarkSemantics semantics) |
| 473 | throws SyntaxError { |
| 474 | // Pseudocode: |
| 475 | // file = StarlarkFile.parse(input) |
| 476 | // validateFile(file, predeclared.keys, thread.semantics) |
| 477 | // prog = compile(file.statements) |
| 478 | // return (prog, file) |
| 479 | throw new UnsupportedOperationException(); |
| 480 | } |
| 481 | |
| 482 | /** |
| 483 | * An opaque executable representation of a StarlarkFile. Programs may be efficiently serialized |
| 484 | * and deserialized without parsing and recompiling. |
| 485 | */ |
| 486 | public static class Program { |
| 487 | |
| 488 | /** |
| 489 | * Execute the toplevel function of a compiled program and returns the module populated by its |
| 490 | * top-level assignments. |
| 491 | * |
| 492 | * <p>The keys of predeclared must match the set used when creating the Program. |
| 493 | */ |
| 494 | public Module init( |
| 495 | StarlarkThread thread, // |
| 496 | Map<String, Object> predeclared, |
| 497 | @Nullable Object label) // a regrettable Bazelism we needn't widely expose in the API |
| 498 | throws EvalException, InterruptedException { |
| 499 | // Pseudocode: |
| 500 | // module = new module(predeclared, label=label) |
| 501 | // toplevel = new StarlarkFunction(prog.toplevel, module) |
| 502 | // call(thread, toplevel) |
| 503 | // return module # or module.globals? |
| 504 | throw new UnsupportedOperationException(); |
| 505 | } |
| 506 | } |
| 507 | |
| 508 | /** |
| 509 | * Parse the input as an expression, validates it in the specified environment, and returns a |
| 510 | * callable Starlark no-argument function value that computes and returns the value of the |
| 511 | * expression. |
| 512 | */ |
| 513 | private static StarlarkFunction exprFunc( |
| 514 | ParserInput input, // |
| 515 | Map<String, Object> env, |
| 516 | StarlarkSemantics semantics) |
| 517 | throws SyntaxError { |
| 518 | // Pseudocode: |
| 519 | // expr = Expression.parse(input) |
| 520 | // validateExpr(expr, env.keys, semantics) |
| 521 | // prog = compile([return expr]) |
| 522 | // module = new module(env) |
| 523 | // return new StarlarkFunction(prog.toplevel, module) |
| 524 | throw new UnsupportedOperationException(); |
| 525 | } |
| 526 | } |