| // 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.skylarkinterface.SkylarkCallable; |
| import com.google.devtools.build.lib.syntax.Environment.LexicalFrame; |
| import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor; |
| 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.getAnnotation()); |
| configure(obj, descriptor); |
| } |
| |
| @Override |
| protected int getArgArraySize () { |
| return innerArgumentCount; |
| } |
| |
| private static List<ExtraArgKind> getExtraArgs(SkylarkCallable annotation) { |
| ImmutableList.Builder<ExtraArgKind> extraArgs = ImmutableList.builder(); |
| if (annotation.useLocation()) { |
| extraArgs.add(ExtraArgKind.LOCATION); |
| } |
| if (annotation.useAst()) { |
| extraArgs.add(ExtraArgKind.SYNTAX_TREE); |
| } |
| if (annotation.useEnvironment()) { |
| extraArgs.add(ExtraArgKind.ENVIRONMENT); |
| } |
| if (annotation.useSkylarkSemantics()) { |
| 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, |
| 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++; |
| } |
| |
| Profiler.instance().startTask(ProfilerTask.SKYLARK_BUILTIN_FN, getName()); |
| |
| try { |
| env.enterScope(this, SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS, ast, env.getGlobals()); |
| return FuncallExpression.callMethod( |
| descriptor, getName(), obj, args, ast.getLocation(), env); |
| } finally { |
| Profiler.instance().completeTask(ProfilerTask.SKYLARK_BUILTIN_FN); |
| env.exitScope(); |
| } |
| } |
| } |