Skip to content
Permalink
Browse files

Merge pull request #541 from PseudoKnight/master

Support executing closures in variables using function syntax.
  • Loading branch information...
PseudoKnight committed Sep 8, 2019
2 parents 62e6867 + 80b0fd5 commit ba52fc84ff2b1042b9c506607ba2cac0e25abf2f
@@ -1842,8 +1842,8 @@ private static void checkBreaks0(ParseTree tree, long currentLoops, String lastU
//Don't care about these
return;
}
if(tree.getData().val().startsWith("_")) {
//It's a proc. We need to recurse, but not check this "function"
if(!((CFunction) tree.getData()).hasFunction()) {
//We need to recurse, but this is not expected to be a function
for(ParseTree child : tree.getChildren()) {
checkBreaks0(child, currentLoops, lastUnbreakable, compilerErrors);
}
@@ -2112,7 +2112,7 @@ private static void link(ParseTree tree, Set<ConfigCompileException> compilerErr
// Walk the children
for(ParseTree child : tree.getChildren()) {
if(child.getData() instanceof CFunction) {
if(child.getData().val().charAt(0) != '_' || child.getData().val().charAt(1) == '_') {
if(((CFunction) child.getData()).hasFunction()) {
// This will throw an exception if the function doesn't exist.
try {
FunctionList.getFunction((CFunction) child.getData(), envs);
@@ -2450,7 +2450,7 @@ private static boolean eliminateDeadCode(ParseTree tree, Environment env, Set<Cl
//For the time being, we will simply say that if a function uses execs, it
//is a branch (branches always use execs, though using execs doesn't strictly
//mean you are a branch type function).
if(tree.getData() instanceof CFunction) {
if(tree.getData() instanceof CFunction && ((CFunction) tree.getData()).hasFunction()) {
Function f;
try {
f = (Function) FunctionList.getFunction(((CFunction) tree.getData()), envs);
@@ -2491,6 +2491,9 @@ private static boolean eliminateDeadCode(ParseTree tree, Environment env, Set<Cl
}
ParseTree child = children.get(m);
if(child.getData() instanceof CFunction) {
if(!((CFunction) child.getData()).hasFunction()) {
continue;
}
Function c;
try {
c = (Function) FunctionList.getFunction(((CFunction) child.getData()), envs);
@@ -26,6 +26,7 @@
import com.laytonsmith.core.environments.GlobalEnv;
import com.laytonsmith.core.environments.InvalidEnvironmentException;
import com.laytonsmith.core.exceptions.CRE.AbstractCREException;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import com.laytonsmith.core.exceptions.CRE.CREInsufficientPermissionException;
import com.laytonsmith.core.exceptions.CRE.CREInvalidProcedureException;
import com.laytonsmith.core.exceptions.CRE.CREStackOverflowError;
@@ -280,6 +281,14 @@ public Mixed eval(ParseTree c, final Environment env) throws CancelCommandExcept
}
}

final CFunction possibleFunction;
try {
possibleFunction = (CFunction) m;
} catch (ClassCastException e) {
throw ConfigRuntimeException.CreateUncatchableException("Expected to find CFunction at runtime but found: "
+ m.val(), m.getTarget());
}

StackTraceManager stManager = env.getEnv(GlobalEnv.class).GetStackTraceManager();
boolean addedRootStackElement = false;
try {
@@ -290,7 +299,8 @@ public Mixed eval(ParseTree c, final Environment env) throws CancelCommandExcept
}
stManager.setCurrentTarget(c.getTarget());
env.getEnv(GlobalEnv.class).SetScript(this);
if(m.val().charAt(0) == '_' && m.val().charAt(1) != '_') {

if(possibleFunction.hasProcedure()) {
//Not really a function, so we can't put it in Function.
Procedure p = getProc(m.val());
if(p == null) {
@@ -307,13 +317,27 @@ public Mixed eval(ParseTree c, final Environment env) throws CancelCommandExcept
pp.stop();
}
return ret;
} else if(possibleFunction.hasIVariable()) {
//Check if this ivar is a closure and execute it
Mixed closure = env.getEnv(GlobalEnv.class).GetVarList().get(m.val(), m.getTarget(), env).ival();
if(!closure.isInstanceOf(CClosure.TYPE)) {
throw new CRECastException("Expecting variable to contain a closure to execute, but found type: "
+ closure.typeof().getSimpleName(), m.getTarget());
}
Mixed[] list = new Mixed[c.numberOfChildren()];
for(int i = 0; i < c.numberOfChildren(); i++) {
list[i] = env.getEnv(GlobalEnv.class).GetScript().seval(c.getChildAt(i), env);
}
return ((CClosure) closure).executeCallable(list);
}

final Function f;
try {
f = ((CFunction) m).getFunction();
} catch (ConfigCompileException | ClassCastException e) {
f = possibleFunction.getFunction();
} catch (ConfigCompileException e) {
//Turn it into a config runtime exception. This shouldn't ever happen though.
throw ConfigRuntimeException.CreateUncatchableException("Unable to find function " + m.val(), m.getTarget());
throw ConfigRuntimeException.CreateUncatchableException("Unable to find function at runtime: "
+ m.val(), m.getTarget());
}

ArrayList<Mixed> args = new ArrayList<>();
@@ -37,6 +37,33 @@ public boolean isDynamic() {
return true;
}

/**
* Returns true if this CFunction is expected to represent a procedure based on the format.
*
* @return
*/
public boolean hasProcedure() {
return val().charAt(0) == '_' && val().charAt(1) != '_';
}

/**
* Returns true if this CFunction is expected to represent an IVariable based on the format.
*
* @return
*/
public boolean hasIVariable() {
return val().charAt(0) == '@';
}

/**
* Returns true if this CFunction is expected to represent a function based on the format.
*
* @return
*/
public boolean hasFunction() {
return !hasProcedure() && !hasIVariable();
}

/**
* Returns the underlying function for this construct.
*
@@ -935,7 +935,7 @@ public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
}
List<DocumentLink> links = new ArrayList<>();
tree.getAllNodes().forEach(node -> {
if(node.getData() instanceof CFunction) {
if(node.getData() instanceof CFunction && ((CFunction) (node.getData())).hasFunction()) {
try {
Function f = ((CFunction) (node.getData())).getFunction();
if(f instanceof DocumentLinkProvider) {
@@ -103,6 +103,12 @@ public void testProcReturn() throws Exception {
optimize("proc(_proc, return(array(1))) _proc()[0]"));
}

@Test
public void testClosure() throws Exception {
assertEquals("sconcat(assign(@c,closure(@target,msg(concat('Hello ',@target,'!')))),@c('world'))",
optimize("@c = closure(@target) {msg('Hello '.@target.'!')}; @c('world');"));
}

@Test
public void testUnreachableCode() throws Exception {
assertEquals("if(dyn(0),sconcat(die()),sconcat(msg('2'),msg('3')))",
@@ -296,6 +296,26 @@ public void testClosure10() throws Exception {
verify(fakePlayer2).sendMessage("newlabel");
}

@Test(timeout = 10000)
public void testClosure11() throws Exception {
SRun("@c = closure(@msg){msg(@msg)};\n"
+ "@c('Hello World');", fakePlayer);
verify(fakePlayer).sendMessage("Hello World");
}

@Test(timeout = 10000)
public void testClosure12() throws Exception {
SRun("@c = closure(@msg = 'Hello World'){msg(@msg)};\n"
+ "@c();", fakePlayer);
verify(fakePlayer).sendMessage("Hello World");
}

@Test(timeout = 10000, expected = CRECastException.class)
public void testClosure13() throws Exception {
SRun("@s = 'string';\n"
+ "@s();", fakePlayer);
}

@Test
public void testToRadix() throws Exception {
assertEquals("f", SRun("to_radix(15, 16)", null));
@@ -196,7 +196,7 @@ public void testClone() throws Exception {
CArray c1 = C.Array(C.Void(), C.Void()).clone();
CBoolean c2 = C.Boolean(true).clone();
CDouble c4 = C.Double(1).clone();
CFunction c5 = new CFunction("", Target.UNKNOWN).clone();
CFunction c5 = new CFunction("__", Target.UNKNOWN).clone();
CInt c6 = C.Int(1).clone();
CNull c7 = C.Null().clone();
CString c8 = C.String("").clone();

0 comments on commit ba52fc8

Please sign in to comment.
You can’t perform that action at this time.