package com.laytonsmith.core.functions; import com.laytonsmith.core.MethodScriptCompiler; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Script; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.IVariable; import com.laytonsmith.core.constructs.IVariableList; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigCompileGroupException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.exceptions.FunctionReturnException; import java.util.HashMap; import java.util.Map; /** * A CompositeFunction is a function that executes MethodScript ultimately. It is * written entirely in MethodScript, and does not directly run any java. */ public abstract class CompositeFunction extends AbstractFunction { private static final Map<Class<? extends CompositeFunction>, ParseTree> cachedScripts = new HashMap<>(); @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { ParseTree tree; if(!cachedScripts.containsKey(this.getClass())){ try { String script = script(); tree = MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, null, true)) // the root of the tree is null, so go ahead and pull it up .getChildAt(0); } catch (ConfigCompileException | ConfigCompileGroupException ex) { // This is really bad. throw new Error(ex); } if(cacheCompile()){ cachedScripts.put(this.getClass(), tree); } } else { tree = cachedScripts.get(this.getClass()); } GlobalEnv env = environment.getEnv(GlobalEnv.class); IVariableList oldVariables = env.GetVarList(); IVariableList newVariables = new IVariableList(); newVariables.set(new IVariable(new CClassType("array", t), "@arguments", new CArray(t, args.length, args), t)); env.SetVarList(newVariables); Construct ret = CVoid.VOID; try { env.GetScript().eval(tree, environment); } catch(FunctionReturnException ex){ ret = ex.getReturn(); } env.SetVarList(oldVariables); return ret; } /** * The script that will be compiled and run when this function is executed. The value array @arguments will be set with the * function inputs. Variables set in this script will not leak to the actual script environment, but in general, the rest of * the environment is identical, and so any other changes to the environment will persist. To return a value, use return(). * @return */ protected abstract String script(); /** * This method can be overridden to return false if the script should not be compiled and cached. * @return */ protected boolean cacheCompile(){ return true; } @Override public final Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { throw new Error(this.getClass().toString()); } @Override public final boolean useSpecialExec() { // This defeats the purpose, so don't allow this. return false; } }