|  | // 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 com.google.devtools.build.lib.syntax; | 
|  |  | 
|  | import com.google.common.base.Preconditions; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.devtools.build.lib.events.Location; | 
|  | import com.google.devtools.build.lib.profiler.Profiler; | 
|  | import com.google.devtools.build.lib.profiler.ProfilerTask; | 
|  | import com.google.devtools.build.lib.profiler.SilentCloseable; | 
|  | import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; | 
|  | import com.google.devtools.build.lib.syntax.Environment.LexicalFrame; | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  | import javax.annotation.Nullable; | 
|  |  | 
|  | /** | 
|  | * A function-object abstraction on object methods exposed to skylark using {@link SkylarkCallable}. | 
|  | */ | 
|  | public class BuiltinCallable extends BaseFunction { | 
|  |  | 
|  | /** Represents a required interpreter parameter as dictated by {@link SkylarkCallable} */ | 
|  | public enum ExtraArgKind { | 
|  | LOCATION, // SkylarkCallable.useLocation | 
|  | SYNTAX_TREE, // SkylarkCallable.useAst | 
|  | ENVIRONMENT, // SkylarkCallable.useEnvironment | 
|  | SEMANTICS; // SkylarkCallable.useSemantics | 
|  | } | 
|  |  | 
|  | // Builtins cannot create or modify variable bindings. So it's sufficient to use a shared | 
|  | // instance. | 
|  | private static final LexicalFrame SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS = | 
|  | LexicalFrame.create(Mutability.IMMUTABLE); | 
|  |  | 
|  | private final Object obj; | 
|  | private final MethodDescriptor descriptor; | 
|  | private final List<ExtraArgKind> extraArgs; | 
|  | private int innerArgumentCount; | 
|  |  | 
|  | public BuiltinCallable(String name, Object obj, MethodDescriptor descriptor) { | 
|  | super(name); | 
|  | this.obj = obj; | 
|  | this.descriptor = descriptor; | 
|  | this.extraArgs = getExtraArgs(descriptor); | 
|  | configure(obj, descriptor); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected int getArgArraySize () { | 
|  | return innerArgumentCount; | 
|  | } | 
|  |  | 
|  | private static List<ExtraArgKind> getExtraArgs(MethodDescriptor method) { | 
|  | ImmutableList.Builder<ExtraArgKind> extraArgs = ImmutableList.builder(); | 
|  | if (method.isUseLocation()) { | 
|  | extraArgs.add(ExtraArgKind.LOCATION); | 
|  | } | 
|  | if (method.isUseAst()) { | 
|  | extraArgs.add(ExtraArgKind.SYNTAX_TREE); | 
|  | } | 
|  | if (method.isUseEnvironment()) { | 
|  | extraArgs.add(ExtraArgKind.ENVIRONMENT); | 
|  | } | 
|  | if (method.isUseSkylarkSemantics()) { | 
|  | extraArgs.add(ExtraArgKind.SEMANTICS); | 
|  | } | 
|  | return extraArgs.build(); | 
|  | } | 
|  |  | 
|  | /** Configure a BaseFunction from a @SkylarkCallable-annotated method */ | 
|  | private void configure(Object obj, MethodDescriptor descriptor) { | 
|  | Preconditions.checkState(!isConfigured()); // must not be configured yet | 
|  |  | 
|  | this.paramDoc = new ArrayList<>(); | 
|  | this.signature = SkylarkSignatureProcessor.getSignatureForCallable( | 
|  | getName(), descriptor, paramDoc, getEnforcedArgumentTypes()); | 
|  | this.objectType = obj.getClass(); | 
|  | this.innerArgumentCount = signature.getSignature().getShape().getArguments() + extraArgs.size(); | 
|  | configure(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | @Nullable | 
|  | public Object call(Object[] args, @Nullable FuncallExpression ast, Environment env) | 
|  | throws EvalException, InterruptedException { | 
|  | Preconditions.checkNotNull(env); | 
|  |  | 
|  | // ast is null when called from Java (as there's no Skylark call site). | 
|  | Location loc = ast == null ? Location.BUILTIN : ast.getLocation(); | 
|  |  | 
|  | // Add extra arguments, if needed | 
|  | int index = args.length - extraArgs.size(); | 
|  | for (ExtraArgKind extraArg : extraArgs) { | 
|  | switch(extraArg) { | 
|  | case LOCATION: | 
|  | args[index] = loc; | 
|  | break; | 
|  |  | 
|  | case SYNTAX_TREE: | 
|  | args[index] = ast; | 
|  | break; | 
|  |  | 
|  | case ENVIRONMENT: | 
|  | args[index] = env; | 
|  | break; | 
|  |  | 
|  | case SEMANTICS: | 
|  | args[index] = env.getSemantics(); | 
|  | break; | 
|  | } | 
|  | index++; | 
|  | } | 
|  |  | 
|  | try (SilentCloseable c = | 
|  | Profiler.instance().profile(ProfilerTask.SKYLARK_BUILTIN_FN, getName())) { | 
|  | env.enterScope(this, SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS, ast, env.getGlobals()); | 
|  | return descriptor.call(obj, args, ast.getLocation(), env); | 
|  | } finally { | 
|  | env.exitScope(); | 
|  | } | 
|  | } | 
|  | } |