| // Copyright 2018 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 static com.google.common.base.Preconditions.checkNotNull; | |
| import com.google.common.collect.Maps; | |
| import java.util.ArrayList; | |
| import java.util.LinkedHashMap; | |
| import net.starlark.java.syntax.Location; | |
| import net.starlark.java.syntax.StarlarkType; | |
| import net.starlark.java.syntax.TypeContext; | |
| import net.starlark.java.syntax.Types; | |
| /** | |
| * The StarlarkCallable interface is implemented by all Starlark values that may be called from | |
| * Starlark like a function, including built-in functions and methods, Starlark functions, and | |
| * application-defined objects (such as rules, aspects, and providers in Bazel). | |
| * | |
| * <p>It defines two methods: {@code fastcall}, for performance, or {@code call} for convenience. By | |
| * default, {@code fastcall} delegates to {@code call}, and call throws an exception, so an | |
| * implementer may override either one. | |
| */ | |
| public interface StarlarkCallable extends StarlarkValue { | |
| @Override | |
| default boolean isAcyclic() { | |
| return true; | |
| } | |
| /** | |
| * Defines the "convenient" implementation of function calling for a callable value. | |
| * | |
| * <p>Do not call this function directly. Use the {@link Starlark#call} function to make a call, | |
| * as it handles necessary book-keeping such as maintenance of the call stack, exception handling, | |
| * and so on. | |
| * | |
| * <p>The default implementation throws an EvalException. | |
| * | |
| * <p>See {@link Starlark#fastcall} for basic information about function calls. | |
| * | |
| * @param thread the StarlarkThread in which the function is called | |
| * @param args a tuple of the arguments passed by position | |
| * @param kwargs a new, mutable dict of the arguments passed by keyword. Iteration order is | |
| * determined by keyword order in the call expression. | |
| */ | |
| default Object call(StarlarkThread thread, Tuple args, Dict<String, Object> kwargs) | |
| throws EvalException, InterruptedException { | |
| throw Starlark.errorf("function %s not implemented", getName()); | |
| } | |
| /** | |
| * Defines the "fast" implementation variant of function calling with only positional arguments. | |
| * | |
| * <p>Do not call this function directly. Use the {@link Starlark#easycall} function to make a | |
| * call, as it handles necessary book-keeping such as maintenance of the call stack, exception | |
| * handling, and so on. | |
| * | |
| * <p>The fastcall implementation takes ownership of the {@code positional} array, and may retain | |
| * it indefinitely or modify it. The caller must not modify or even access the array after making | |
| * the call. | |
| * | |
| * <p>The default implementation forwards the call to {@code call}. | |
| * | |
| * @param thread the StarlarkThread in which the function is called | |
| * @param positional a list of positional arguments | |
| */ | |
| default Object positionalOnlyCall(StarlarkThread thread, Object... positional) | |
| throws EvalException, InterruptedException { | |
| ArgumentProcessor argumentProcessor = requestArgumentProcessor(thread); | |
| for (Object value : positional) { | |
| argumentProcessor.addPositionalArg(value); | |
| } | |
| return argumentProcessor.call(thread); | |
| } | |
| /** | |
| * Defines a helper object for invoking a StarlarkCallable. | |
| * | |
| * <p>An ArgumentProcessor implementation is returned by {@link #requestArgumentProcessor}. The | |
| * ArgumentProcessor implementation must then be used to first place the arguments, and then its | |
| * method {@link #call} is used to make the invocation. | |
| */ | |
| abstract static class ArgumentProcessor { | |
| protected final StarlarkThread thread; | |
| public ArgumentProcessor(StarlarkThread thread) { | |
| this.thread = thread; | |
| } | |
| public abstract void addPositionalArg(Object value) throws EvalException; | |
| public abstract void addNamedArg(String name, Object value) throws EvalException; | |
| public abstract StarlarkCallable getCallable(); | |
| public abstract Object call(StarlarkThread thread) throws EvalException, InterruptedException; | |
| /** | |
| * Throws a given {@code EvalException} from inside {@link #addPositionalArg} or {@link | |
| * #addNamedArg}. | |
| * | |
| * <p>In the Starlark evaluation model, the work of ArgumentProcessor is logically part of the | |
| * callable's evaluation, so the stack trace for any exceptions thrown during argument | |
| * processing needs to contain the name and location of the callable. This method pushes the | |
| * stack before throwing the exception, ensuring that the stack trace is as expected. | |
| */ | |
| protected void pushCallableAndThrow(EvalException e) throws EvalException { | |
| thread.push(getCallable()); | |
| throw e; | |
| } | |
| } | |
| /** | |
| * A default implementation of ArgumentProcessor that simply stores the arguments in a list and a | |
| * LinkedHashMap and then passes them to the StarlarkCallable.call() method. | |
| */ | |
| static final class DefaultArgumentProcessor extends ArgumentProcessor { | |
| private final StarlarkCallable owner; | |
| private final ArrayList<Object> positional; | |
| private final LinkedHashMap<String, Object> named; | |
| DefaultArgumentProcessor(StarlarkCallable owner, StarlarkThread thread) { | |
| super(thread); | |
| this.owner = owner; | |
| this.positional = new ArrayList<>(); | |
| this.named = Maps.newLinkedHashMapWithExpectedSize(0); | |
| } | |
| @Override | |
| public void addPositionalArg(Object value) throws EvalException { | |
| positional.add(value); | |
| } | |
| @Override | |
| public void addNamedArg(String name, Object value) throws EvalException { | |
| if (named.put(name, value) != null) { | |
| pushCallableAndThrow( | |
| Starlark.errorf("%s got multiple values for parameter '%s'", owner, name)); | |
| } | |
| } | |
| @Override | |
| public StarlarkCallable getCallable() { | |
| return owner; | |
| } | |
| @Override | |
| public Object call(StarlarkThread thread) throws EvalException, InterruptedException { | |
| return owner.call( | |
| thread, Tuple.wrap(positional.toArray()), Dict.wrap(thread.mutability(), named)); | |
| } | |
| } | |
| /** | |
| * Returns an {@link ArgumentProcessor} which the callable can implement for faster calling. | |
| * | |
| * <p>By default this returns a {@link DefaultArgumentProcessor} which can be used to call the | |
| * callable via {@link #call}. | |
| */ | |
| default ArgumentProcessor requestArgumentProcessor(StarlarkThread thread) throws EvalException { | |
| return new DefaultArgumentProcessor(this, thread); | |
| } | |
| /** Returns the form this callable value should take in a stack trace. */ | |
| String getName(); | |
| /** | |
| * Returns the location of the definition of this callable value, or BUILTIN if it was not defined | |
| * in Starlark code. | |
| */ | |
| default Location getLocation() { | |
| return Location.BUILTIN; | |
| } | |
| /** | |
| * Returns the first {@link Types.CallableType} in this callable's Starlark type hierarchy. | |
| * | |
| * @throws NullPointerException if no {@link Types.CallableType} was found. | |
| */ | |
| default Types.CallableType getCallableType( | |
| StarlarkSemantics starlarkSemantics, TypeContext typeContext) { | |
| StarlarkType type = Starlark.getStarlarkType(this, starlarkSemantics); | |
| return checkNotNull( | |
| Types.toCallableType(type, typeContext), | |
| "Expected type '%s' of '%s' to be callable", | |
| type, | |
| this); | |
| } | |
| } |