UserDefinedFunctionExecutor.java
/**
* VStar: a statistical analysis tool for variable star data.
* Copyright (C) 2010 AAVSO (http://www.aavso.org/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.aavso.tools.vstar.vela;
import java.util.List;
import java.util.Optional;
/**
* This class executes user defined functions.
*/
public class UserDefinedFunctionExecutor extends FunctionExecutor {
private VeLaInterpreter vela;
private Optional<AST> ast;
private VeLaScope env;
/**
* Construct a named function definition
*
* @param vela The interpreter instance from which this object is
* being created.
* @param funcName The function's name.
* @param parameterNames The function's formal parameter names.
* @param parameterTypes The function's formal parameter types.
* @param returnType The function's return type.
* @param ast The AST corresponding to the body of the function.
* @param helpString The optional help string.
*/
public UserDefinedFunctionExecutor(VeLaInterpreter vela, Optional<String> funcName, List<String> parameterNames,
List<Type> parameterTypes, Optional<Type> returnType, Optional<AST> ast, Optional<String> helpString) {
super(funcName, parameterNames, parameterTypes, returnType, helpString);
this.vela = vela;
this.ast = ast;
// Capture current environment by coalescing all but the global scope
// into one environment, starting from the first to the last such that
// newer definitions override older ones.
env = new VeLaScope();
List<VeLaScope> scopes = vela.getScopes();
for (int i = 1; i < scopes.size(); i++) {
env.addAll(scopes.get(i));
}
}
/**
* Construct an anonymous function definition
*
* @param vela The interpreter instance from which this object is
* being created.
* @param parameterNames The function's formal parameter names.
* @param parameterTypes The function's formal parameter types.
* @param returnType The function's return type.
* @param ast The AST corresponding to the body of the function.
* @param helpString The optional help string.
*/
public UserDefinedFunctionExecutor(VeLaInterpreter vela, List<String> parameterNames, List<Type> parameterTypes,
Optional<Type> returnType, Optional<AST> ast, Optional<String> helpString) {
this(vela, Optional.empty(), parameterNames, parameterTypes, returnType, ast, helpString);
}
@Override
public Optional<Operand> apply(List<Operand> operands) throws VeLaEvalError {
// If the function has a body, push a new scope and the environment if
// non-empty, bind the actual parameters to the formal parameters,
// evaluate the body AST and pop the scope and the environment if
// non-empty.
int initialStackSize = vela.getStack().size();
if (ast.isPresent()) {
if (!isTailRecursive()) {
if (!env.isEmpty()) {
vela.pushEnvironment(env);
}
vela.pushEnvironment(new VeLaScope());
}
for (int i = 0; i < operands.size(); i++) {
vela.peekEnvironment().bind(parameterNames.get(i), operands.get(i), false);
}
vela.eval(ast.get());
if (!isTailRecursive()) {
vela.popEnvironment();
if (!env.isEmpty()) {
vela.popEnvironment();
}
}
}
// The result, if any, will be on the stack.
Optional<Operand> result = Optional.empty();
if (vela.getStack().size() > initialStackSize) {
result = Optional.of(vela.getStack().pop());
}
return result;
}
/**
* Is the function body tail recursive?
*/
private boolean isTailRecursive() {
boolean isTailRecursive = false;
AST lastChild = ast.get().lastChild();
if (lastChild.getOp() == Operation.FUNCALL) {
if (getFuncName().isPresent() && getFuncName().get().equalsIgnoreCase(lastChild.getToken())) {
isTailRecursive = true;
}
}
return isTailRecursive;
}
}