blob: 0427157717d29d6584741bca278e783df25384c9 [file] [log] [blame]
// 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();
}
}