package com.laytonsmith.core.constructs;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.annotations.typeof;
import com.laytonsmith.core.CHLog;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.core.MethodScriptCompiler;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.environments.GlobalEnv;
import com.laytonsmith.core.exceptions.CRE.AbstractCREException;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import com.laytonsmith.core.exceptions.CancelCommandException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.exceptions.FunctionReturnException;
import com.laytonsmith.core.exceptions.LoopManipulationException;
import com.laytonsmith.core.exceptions.ProgramFlowManipulationException;
import com.laytonsmith.core.exceptions.StackTraceManager;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A closure is just an anonymous procedure.
*
*
*/
@typeof("closure")
public class CClosure extends Construct {
public static final long serialVersionUID = 1L;
protected ParseTree node;
protected final Environment env;
protected final String[] names;
protected final Construct[] defaults;
protected final CClassType[] types;
protected final CClassType returnType;
public CClosure(ParseTree node, Environment env, CClassType returnType, String[] names, Construct[] defaults, CClassType[] types, Target t) {
super(node != null ? node.toString() : "", ConstructType.CLOSURE, t);
this.node = node;
this.env = env;
this.names = names;
this.defaults = defaults;
this.types = types;
this.returnType = returnType;
for(String pName : names){
if(pName.equals("@arguments")){
CHLog.GetLogger().w(CHLog.Tags.COMPILER, "This closure overrides the builtin @arguments parameter", t);
break;
}
}
}
@Override
public String val() {
StringBuilder b = new StringBuilder();
condense(getNode(), b);
return b.toString();
}
private void condense(ParseTree node, StringBuilder b) {
if(node == null){
return;
}
if (node.getData() instanceof CFunction) {
b.append(( (CFunction) node.getData() ).val()).append("(");
for (int i = 0; i < node.numberOfChildren(); i++) {
condense(node.getChildAt(i), b);
if (i != node.numberOfChildren() - 1 && !( (CFunction) node.getData() ).val().equals("__autoconcat__")) {
b.append(",");
}
}
b.append(")");
} else if (node.getData() instanceof CString) {
CString data = (CString) node.getData();
// Convert: \ -> \\ and ' -> \'
b.append("'").append(data.val().replace("\\", "\\\\").replaceAll("\t", "\\\\t").replaceAll("\n", "\\\\n").replace("'", "\\'")).append("'");
} else if(node.getData() instanceof IVariable){
b.append(((IVariable)node.getData()).getVariableName());
} else {
b.append(node.getData().val());
}
}
public ParseTree getNode() {
return node;
}
@Override
public CClosure clone() throws CloneNotSupportedException {
CClosure clone = (CClosure) super.clone();
if (this.node != null) {
clone.node = this.node.clone();
}
return clone;
}
/**
* If meta code needs to affect this closure's environment, it can access it
* with this function. Note that changing this will only affect future runs
* of the closure, it will not affect the currently running closure, (if
* any) due to the environment being cloned right before running.
*
* @return
*/
public synchronized Environment getEnv() {
return env;
}
/**
* Executes the closure, giving it the supplied arguments. {@code values}
* may be null, which means that no arguments are being sent.
*
* LoopManipulationExceptions will never bubble up past this point, because they are
* never allowed, so they are handled automatically, but
* other ProgramFlowManipulationExceptions will, . ConfigRuntimeExceptions will
* also bubble up past this, since an execution mechanism may need to do custom
* handling.
*
* A typical execution will include the following code:
* <pre>
* try {
* closure.execute();
* } catch(ConfigRuntimeException e){
* ConfigRuntimeException.HandleUncaughtException(e);
* } catch(ProgramFlowManipulationException e){
* // Ignored
* }
* </pre>
* @param values The values to be passed to the closure
* @throws ConfigRuntimeException If any call inside the closure causes a CRE
* @throws ProgramFlowManipulationException If any ProgramFlowManipulationException is thrown
* (other than a LoopManipulationException) within the closure
* @throws FunctionReturnException If the closure has a return() call in it.
*/
public void execute(Construct... values) throws ConfigRuntimeException, ProgramFlowManipulationException, FunctionReturnException, CancelCommandException {
if(node == null){
return;
}
StackTraceManager stManager = env.getEnv(GlobalEnv.class).GetStackTraceManager();
stManager.addStackTraceElement(new ConfigRuntimeException.StackTraceElement("<<closure>>", getTarget()));
try {
Environment environment;
synchronized (this) {
environment = env.clone();
}
if (values != null) {
for (int i = 0; i < names.length; i++) {
String name = names[i];
Construct value;
try {
value = values[i];
}
catch (Exception e) {
value = defaults[i].clone();
}
environment.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(types[i], name, value, getTarget()));
}
}
boolean hasArgumentsParam = false;
for(String pName : this.names){
if(pName.equals("@arguments")){
hasArgumentsParam = true;
break;
}
}
if(!hasArgumentsParam){
CArray arguments = new CArray(node.getData().getTarget());
if (values != null) {
for (Construct value : values) {
arguments.push(value, node.getData().getTarget());
}
}
environment.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(new CClassType("array", Target.UNKNOWN), "@arguments", arguments, node.getData().getTarget()));
}
ParseTree newNode = new ParseTree(new CFunction("g", getTarget()), node.getFileOptions());
List<ParseTree> children = new ArrayList<ParseTree>();
children.add(node);
newNode.setChildren(children);
try {
MethodScriptCompiler.execute(newNode, environment, null, environment.getEnv(GlobalEnv.class).GetScript());
} catch (LoopManipulationException e){
//This shouldn't ever happen.
LoopManipulationException lme = ((LoopManipulationException)e);
Target t = lme.getTarget();
ConfigRuntimeException.HandleUncaughtException(ConfigRuntimeException.CreateUncatchableException("A " + lme.getName() + "() bubbled up to the top of"
+ " a closure, which is unexpected behavior.", t), environment);
} catch (FunctionReturnException ex){
// Check the return type of the closure to see if it matches the defined type
// Normal execution.
Construct ret = ex.getReturn();
if(!InstanceofUtil.isInstanceof(ret, returnType)){
throw new CRECastException("Expected closure to return a value of type " + returnType.val()
+ " but a value of type " + ret.typeof() + " was returned instead", ret.getTarget());
}
// Now rethrow it
throw ex;
} catch (CancelCommandException e){
// die()
} catch(ConfigRuntimeException ex){
if(ex instanceof AbstractCREException){
((AbstractCREException)ex).freezeStackTraceElements(stManager);
}
throw ex;
} catch(Throwable t){
// Not sure. Pop and re-throw.
throw t;
} finally {
stManager.popStackTraceElement();
}
// If we got here, then there was no return type. This is fine, but only for returnType void or auto.
if(!(returnType.equals(CClassType.AUTO) || returnType.equals(CClassType.VOID))){
throw new CRECastException("Expecting closure to return a value of type " + returnType.val() + ","
+ " but no value was returned.", node.getTarget());
}
}
catch (CloneNotSupportedException ex) {
Logger.getLogger(CClosure.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public boolean isDynamic() {
return false;
}
@Override
public String docs() {
return "A closure is a data type that contains executable code. This is similar to a procedure, but the value is first class,"
+ " and can be stored in variables, and executed.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
}