blob: ea925d381e030fe1543ca276f98c083f4cfa8235 [file] [log] [blame]
Googlerbf6ac612019-10-18 13:05:54 -07001// 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.
14package com.google.devtools.build.lib.syntax;
15
Googler6901c792019-11-07 18:01:49 -080016import com.google.common.collect.ImmutableMap;
Googler8ac22102019-11-26 19:07:38 -080017import com.google.common.collect.Iterables;
Googlerb19f3ca2019-11-11 15:09:52 -080018import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
Googler2e63b2a2019-11-11 08:59:12 -080019import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
20import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
Googlerbf6ac612019-10-18 13:05:54 -070021import com.google.devtools.build.lib.util.Pair;
adonovan821dd822019-12-19 12:24:41 -080022import com.google.errorprone.annotations.CheckReturnValue;
23import com.google.errorprone.annotations.FormatMethod;
Googler084b64b2019-11-19 14:41:30 -080024import java.util.List;
Googlerbf6ac612019-10-18 13:05:54 -070025import java.util.Map;
26import java.util.Set;
27import 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:
adonovan770606a2019-12-11 11:52:31 -080034// equal, compare, getattr, index, slice, parse, exec, eval, and so on.
Googlerbf6ac612019-10-18 13:05:54 -070035public final class Starlark {
36
37 private Starlark() {} // uninstantiable
38
Googler229080d2019-11-11 08:19:08 -080039 /** The Starlark None value. */
Googler641bdf72019-11-12 10:32:26 -080040 public static final NoneType NONE = NoneType.NONE;
Googler229080d2019-11-11 08:19:08 -080041
Googlere5712242019-11-06 12:34:36 -080042 /**
Googlerf7e471b2019-11-11 10:10:07 -080043 * A sentinel value passed to optional parameters of SkylarkCallable-annotated methods to indicate
44 * that no argument value was supplied.
45 */
Googlerb19f3ca2019-11-11 15:09:52 -080046 public static final Object UNBOUND = new UnboundMarker();
47
48 @Immutable
Googler34f70582019-11-25 12:27:34 -080049 private static final class UnboundMarker implements StarlarkValue {
Googlerb19f3ca2019-11-11 15:09:52 -080050 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
Googler34f70582019-11-25 12:27:34 -080063 public void repr(Printer printer) {
Googlerb19f3ca2019-11-11 15:09:52 -080064 printer.append("<unbound>");
65 }
66 }
Googlerf7e471b2019-11-11 10:10:07 -080067
68 /**
Googler6901c792019-11-07 18:01:49 -080069 * 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)
Googler641bdf72019-11-12 10:32:26 -080078 .put("None", Starlark.NONE);
Googler2e63b2a2019-11-11 08:59:12 -080079 addMethods(env, new MethodLibrary());
Googler6901c792019-11-07 18:01:49 -080080 return env.build();
81 }
82
83 /**
Googler084b64b2019-11-19 14:41:30 -080084 * Reports whether the argument is a legal Starlark value: a string, boolean, integer, or
85 * StarlarkValue.
86 */
87 public static boolean valid(Object x) {
Googler34f70582019-11-25 12:27:34 -080088 return x instanceof StarlarkValue
Googler084b64b2019-11-19 14:41:30 -080089 || 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 /**
Googlere5712242019-11-06 12:34:36 -0800129 * 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;
Googler34f70582019-11-25 12:27:34 -0800135 } else if (x instanceof StarlarkValue) {
136 return ((StarlarkValue) x).truth();
Googlere5712242019-11-06 12:34:36 -0800137 } 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
Googler292a5c02019-11-25 14:53:08 -0800146 /**
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 }
adonovan821dd822019-12-19 12:24:41 -0800159 throw errorf("type '%s' is not iterable", EvalUtils.getDataTypeName(x));
Googler292a5c02019-11-25 14:53:08 -0800160 }
161
Googler8ac22102019-11-26 19:07:38 -0800162 /**
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 /**
adonovanf6846f62019-12-04 11:36:21 -0800178 * 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.
Googler8ac22102019-11-26 19:07:38 -0800181 */
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) {
adonovanf6846f62019-12-04 11:36:21 -0800190 // Iterables.size runs in constant time if x implements Collection.
Googler8ac22102019-11-26 19:07:38 -0800191 return Iterables.size((Iterable<?>) x);
192 } else {
adonovanf6846f62019-12-04 11:36:21 -0800193 checkValid(x);
194 return -1; // valid but not a sequence
Googler8ac22102019-11-26 19:07:38 -0800195 }
196 }
197
Googlerb1e232d2019-11-22 15:29:43 -0800198 /** 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
Googler2e63b2a2019-11-11 08:59:12 -0800218 /**
adonovan972ade82019-12-18 13:58:14 -0800219 * 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)}.
adonovan770606a2019-12-11 11:52:31 -0800221 */
222 public static Object call(
223 StarlarkThread thread,
224 Object fn,
adonovan770606a2019-12-11 11:52:31 -0800225 List<Object> args,
226 Map<String, Object> kwargs)
227 throws EvalException, InterruptedException {
adonovan972ade82019-12-18 13:58:14 -0800228 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 }
adonovanc3327992020-01-08 09:09:11 -0800234 return fastcall(thread, fn, args.toArray(), named);
adonovan972ade82019-12-18 13:58:14 -0800235 }
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(
adonovanc3327992020-01-08 09:09:11 -0800242 StarlarkThread thread, Object fn, Object[] positional, Object[] named)
adonovan972ade82019-12-18 13:58:14 -0800243 throws EvalException, InterruptedException {
adonovan770606a2019-12-11 11:52:31 -0800244 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) {
adonovanc3327992020-01-08 09:09:11 -0800252 throw Starlark.errorf("'%s' object is not callable", EvalUtils.getDataTypeName(fn));
adonovan770606a2019-12-11 11:52:31 -0800253 }
254 callable = new BuiltinCallable(fn, desc.getName(), desc);
255 }
256
adonovanc3327992020-01-08 09:09:11 -0800257 thread.push(callable);
adonovan1d444be2019-12-11 12:22:27 -0800258 try {
adonovanc3327992020-01-08 09:09:11 -0800259 return callable.fastcall(thread, thread.getCallerLocation(), positional, named);
adonovan1d444be2019-12-11 12:22:27 -0800260 } finally {
261 thread.pop();
262 }
adonovan770606a2019-12-11 11:52:31 -0800263 }
264
265 /**
adonovan821dd822019-12-19 12:24:41 -0800266 * 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 /**
Googler2e63b2a2019-11-11 08:59:12 -0800277 * 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 }
Googler15a700c2019-11-27 12:45:29 -0800288 // TODO(adonovan): logically this should be a parameter.
289 StarlarkSemantics semantics = StarlarkSemantics.DEFAULT_SEMANTICS;
290 for (String name : CallUtils.getMethodNames(semantics, v.getClass())) {
adonovan4e2b4952019-12-10 12:19:20 -0800291 // 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));
Googler2e63b2a2019-11-11 08:59:12 -0800296 }
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
adonovan972ade82019-12-18 13:58:14 -0800312 /**
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
Googlerbf6ac612019-10-18 13:05:54 -0700338 // 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}