diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/MixedModeFunction.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/syntax/MixedModeFunction.java | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MixedModeFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/MixedModeFunction.java new file mode 100644 index 0000000000..0427157717 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/syntax/MixedModeFunction.java @@ -0,0 +1,187 @@ +// Copyright 2014 Google Inc. 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.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.packages.Type.ConversionException; + +import java.util.List; +import java.util.Map; + +/** + * Abstract implementation of Function for functions that accept a mixture of + * positional and keyword parameters, as in Python. + */ +public abstract class MixedModeFunction extends AbstractFunction { + + // Nomenclature: + // "Parameters" are formal parameters of a function definition. + // "Arguments" are actual parameters supplied at the call site. + + // Number of regular named parameters (excluding *p and **p) in the + // equivalent Python function definition). + private final List<String> parameters; + + // Number of leading "parameters" which are mandatory + private final int numMandatoryParameters; + + // True if this function requires all arguments to be named + // TODO(bazel-team): replace this by a count of arguments before the * with optional arg, + // in the style Python 3 or PEP 3102. + private final boolean onlyNamedArguments; + + // Location of the function definition, or null for builtin functions. + protected final Location location; + + /** + * Constructs an instance of Function that supports Python-style mixed-mode + * parameter passing. + * + * @param parameters a list of named parameters + * @param numMandatoryParameters the number of leading parameters which are + * considered mandatory; the remaining ones may be omitted, in which + * case they will have the default value of null. + */ + public MixedModeFunction(String name, + Iterable<String> parameters, + int numMandatoryParameters, + boolean onlyNamedArguments) { + this(name, parameters, numMandatoryParameters, onlyNamedArguments, null); + } + + protected MixedModeFunction(String name, + Iterable<String> parameters, + int numMandatoryParameters, + boolean onlyNamedArguments, + Location location) { + super(name); + this.parameters = ImmutableList.copyOf(parameters); + this.numMandatoryParameters = numMandatoryParameters; + this.onlyNamedArguments = onlyNamedArguments; + this.location = location; + } + + @Override + public Object call(List<Object> args, + Map<String, Object> kwargs, + FuncallExpression ast, + Environment env) + throws EvalException, InterruptedException { + + // ast is null when called from Java (as there's no Skylark call site). + Location loc = ast == null ? location : ast.getLocation(); + if (onlyNamedArguments && args.size() > 0) { + throw new EvalException(loc, + getSignature() + " does not accept positional arguments"); + } + + if (kwargs == null) { + kwargs = ImmutableMap.<String, Object>of(); + } + + int numParams = parameters.size(); + int numArgs = args.size(); + Object[] namedArguments = new Object[numParams]; + + // first, positional arguments: + if (numArgs > numParams) { + throw new EvalException(loc, + "too many positional arguments in call to " + getSignature()); + } + for (int ii = 0; ii < numArgs; ++ii) { + namedArguments[ii] = args.get(ii); + } + + // TODO(bazel-team): here, support *varargs splicing + + // second, keyword arguments: + for (Map.Entry<String, Object> entry : kwargs.entrySet()) { + String keyword = entry.getKey(); + int pos = parameters.indexOf(keyword); + if (pos == -1) { + throw new EvalException(loc, + "unexpected keyword '" + keyword + + "' in call to " + getSignature()); + } else { + if (namedArguments[pos] != null) { + throw new EvalException(loc, getSignature() + + " got multiple values for keyword argument '" + keyword + "'"); + } + namedArguments[pos] = kwargs.get(keyword); + } + } + + // third, defaults: + for (int ii = 0; ii < numMandatoryParameters; ++ii) { + if (namedArguments[ii] == null) { + throw new EvalException(loc, + getSignature() + " received insufficient arguments"); + } + } + // (defaults are always null so nothing extra to do here.) + + try { + return call(namedArguments, ast, env); + } catch (ConversionException | IllegalArgumentException | IllegalStateException + | ClassCastException e) { + throw new EvalException(loc, e.getMessage()); + } + } + + /** + * Like Function.call, but generalised to support Python-style mixed-mode + * keyword and positional parameter passing. + * + * @param args an array of argument values corresponding to the list + * of named parameters passed to the constructor. + */ + protected Object call(Object[] args, FuncallExpression ast) + throws EvalException, ConversionException, InterruptedException { + throw new UnsupportedOperationException("Method not overridden"); + } + + /** + * Override this method instead of the one above, if you need to access + * the environment. + */ + protected Object call(Object[] args, FuncallExpression ast, Environment env) + throws EvalException, ConversionException, InterruptedException { + return call(args, ast); + } + + /** + * Render this object in the form of an equivalent Python function signature. + */ + public String getSignature() { + StringBuffer sb = new StringBuffer(); + sb.append(getName()).append('('); + int ii = 0; + int len = parameters.size(); + for (; ii < len; ++ii) { + String parameter = parameters.get(ii); + if (ii > 0) { + sb.append(", "); + } + sb.append(parameter); + if (ii >= numMandatoryParameters) { + sb.append(" = null"); + } + } + sb.append(')'); + return sb.toString(); + } + +} |