package water.rapids.ast;
import water.rapids.Env;
import water.rapids.Rapids;
import water.rapids.Val;
import water.rapids.vals.ValFun;
import water.util.SB;
import java.util.ArrayList;
/**
* Define a function
* Syntax: { ids... . expr }
* IDs are bound within expr
*/
public class AstFunction extends AstPrimitive {
final String[] _ids; // Identifier names
final AstRoot _body; // The function body
// If this function is being evaluated, record the arguments and parent lexical scope
final Val[] _args; // Evaluated arguments to a function
final AstFunction _parent; // Parent lexical scope
public AstFunction() {
_ids = null;
_body = null;
_args = null;
_parent = null;
}
public AstFunction(ArrayList<String> ids, AstRoot body) {
_ids = ids.toArray(new String[ids.size()]);
_body = body;
_args = null; // This is a template of an uncalled function
_parent = null;
}
// A function applied to arguments
public AstFunction(AstFunction fun, Val[] args, AstFunction parent) {
_ids = fun._ids;
_body = fun._body;
_parent = parent;
_args = args;
}
@Override
public String str() {
SB sb = new SB().p('{');
penv(sb);
for (String id : _ids)
sb.p(id).p(' ');
sb.p(". ").p(_body.toString()).p('}');
return sb.toString();
}
@Override
public String example() {
return "{ ...args . expr }";
}
@Override
public String description() {
return "Function definition: a list of tokens in curly braces. All initial tokens (which must be valid " +
"identifiers) become function arguments, then a single dot '.' must follow, and finally an expression which " +
"is the body of the function. Functions with variable number of arguments are not supported. Example: " +
"squaring function `{x . (^ x 2)}`";
}
// Print environment
private void penv(SB sb) {
if (_parent != null) _parent.penv(sb);
if (_args != null)
for (int i = 1; i < _ids.length; i++)
sb.p(_ids[i]).p('=').p(_args[i].toString()).p(' ');
}
// Function execution. Just throw self on stack like a constant. However,
// capture the existing global scope.
@Override
public ValFun exec(Env env) {
return new ValFun(new AstFunction(this, null, env._scope));
}
// Expected argument count, plus self
@Override
public int nargs() {
return _ids.length;
}
@Override
public String[] args() { return _ids; }
// Do a ID lookup, returning the matching argument if found
public Val lookup(String id) {
for (int i = 1; i < _ids.length; i++)
if (id.equals(_ids[i]))
return _args[i]; // Hit, return found argument
return _parent == null ? null : _parent.lookup(id);
}
// Apply this function: evaluate all arguments, push a lexical scope mapping
// the IDs to the ARGs, then evaluate the body. After execution pop the
// lexical scope and return the results.
@Override
public Val apply(Env env, Env.StackHelp stk, AstRoot asts[]) {
// Evaluation all arguments
Val[] args = new Val[asts.length];
for (int i = 1; i < asts.length; i++)
args[i] = stk.track(asts[i].exec(env));
AstFunction old = env._scope;
env._scope = new AstFunction(this, args, _parent); // Push a new lexical scope, extended from the old
Val res = stk.untrack(_body.exec(env));
env._scope = old; // Pop the lexical scope off (by restoring the old unextended scope)
return res;
}
}