package com.laytonsmith.core;
import com.laytonsmith.core.compiler.OptimizationUtilities;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.testing.StaticTest;
import static org.junit.Assert.assertEquals;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* This class tests optimizations by looking at the tree after optimization occurs to see
* if it matches expectation.
*
*/
public class OptimizationTest {
@BeforeClass
public static void setUpClass(){
StaticTest.InstallFakeServerFrontend();
}
public String optimize(String script) throws Exception {
return OptimizationUtilities.optimize(script, null);
}
@Test public void testTestFramework() throws Exception{
//This just tests to see that the basic framework works. This shouldn't optimize.
assertEquals("msg('this is a string','so is this')", optimize("msg(\n 'this is a string',\nso is this\n)"));
assertEquals("msg('\\'quoted\\'')", optimize("msg( '\\'quoted\\'' )"));
}
@Test public void testIfBasic() throws Exception{
assertEquals("msg('hi')", optimize("if(true){ msg('hi') } else { msg('fail') }"));
}
@Test public void testIfWithBraces() throws Exception{
assertEquals("if(dyn(),msg('hi'),msg('hi'))", optimize("if(dyn()){ msg('hi') } else { msg('hi') }"));
}
@Test public void testIfElse() throws Exception {
assertEquals("ifelse(dyn(1),msg(''),dyn(2),msg(''),msg(''))", optimize("if(dyn(1)){ msg('') } else if(dyn(2)){ msg('') } else { msg('') }"));
}
@Test public void testIfElseWithDie() throws Exception {
assertEquals("ifelse(is_null($pl),die(''),not(ponline(player($pl))),die(concat($pl,'')))",
optimize("if(is_null($pl)) {\ndie('') } else if(!ponline(player($pl))){ die($pl.'') }"));
}
// Need to add this back too
// @Test public void testNestedIfsWithRemoval() throws Exception {
// assertEquals("p()", optimize("ifelse(1, if(0, msg('')), msg(''))"));
// }
@Test public void testMultipleLinesInBraces() throws Exception{
assertEquals("if(dyn(false),msg('nope'),sconcat(msg('hi'),msg('hi')))", optimize("if(dyn(false)){\n"
+ "msg('nope')\n"
+ "} else {\n"
+ " msg('hi')\n"
+ " msg('hi')\n"
+ "}"));
}
//TODO: These tests are not intended to be corrected in master, so I'm removing them for now
// @Test public void testProcOptimization1() throws Exception{
// //The proc stays there, but the call to it should be consolidated
// assertEquals("sconcat(proc('_add',@a,@b,return(add(@a,@b))),4)", optimize("proc(_add, @a, @b, return(@a + @b)) _add(2, 2)"));
// }
@Test public void testProcOptimizationRecursion() throws Exception{
assertEquals("sconcat(proc('_loop',@a,if(gt(@a,0),_loop(subtract(@a,1)),return(@a))),_loop(2))",
optimize("proc(_loop, @a, if(@a > 0, _loop(@a - 1), return(@a))) _loop(2)"));
}
// @Test(expected=ConfigCompileException.class)
// public void testProcOptimization2() throws Exception{
// optimize("proc(_divide, @a, return(@a / 0)) _divide(1)");
// }
@Test
public void testProcOptimization3() throws Exception{
//Rather, lack of optimization
assertEquals("sconcat(proc('_nope',msg('Hi')),_nope())", optimize("proc(_nope, msg('Hi')) _nope()"));
}
// @Test
// public void testProcOptimiztion4() throws Exception{
// //Test embedded procs
// assertEquals("sconcat(proc('_outer',sconcat(proc('_inner',@a,return(@a)),'blah')),_inner('huh'))",
// optimize("proc(_outer, proc(_inner, @a, return(@a)) _inner('blah')) _inner('huh')"));
// }
@Test public void testProcReturn() throws Exception{
assertEquals("sconcat(proc('_proc',return(array(1))),array_get(_proc(),0))",
optimize("proc(_proc, return(array(1))) _proc()[0]"));
}
@Test public void testUnreachableCode() throws Exception{
assertEquals("sconcat(assign(@a,0),if(@a,string(die()),sconcat(msg('2'),msg('3'))))", optimize("assign(@a, 0) if(@a){ die() msg('1') } else { msg('2') msg('3') }"));
assertEquals("string(die())", optimize("if(true){ die() msg('1') } else { msg('2') msg('3') }"));
}
@Test public void testUnreachableCodeWithBranchTypeFunction() throws Exception{
assertEquals("if(@var,die(),msg(''))", optimize("if(@var){ die() } else { msg('') }"));
}
@Test public void testRegSplitOptimization1() throws Exception{
assertEquals("split('pattern',dyn('subject'))", optimize("reg_split('pattern', dyn('subject'))"));
}
@Test public void testRegSplitOptimization2() throws Exception{
assertEquals("split('.',dyn('subject'))", optimize("reg_split(reg_escape('.'), dyn('subject'))"));
}
@Test public void testRegReplaceOptimization1() throws Exception{
assertEquals("replace('this is a thing','thing',dyn('hi'))", optimize("reg_replace('thing', dyn('hi'), 'this is a thing')"));
}
@Test public void testTrivialAssignmentWithEqualsSymbol() throws Exception{
assertEquals("assign(@a,1)", optimize("@a = 1"));
}
@Test public void testAssignWithEqualsSymbol() throws Exception {
assertEquals("sconcat(assign(@var,'ab'),'c')", optimize("@var = 'a'.'b' 'c'"));
}
@Test public void testAssignWithOperators() throws Exception{
assertEquals("assign(@one,add(@one,1))", optimize("@one += 1"));
assertEquals("assign(@one,subtract(@one,1))", optimize("@one -= 1"));
assertEquals("assign(@one,multiply(@one,1))", optimize("@one *= 1"));
assertEquals("assign(@one,divide(@one,1))", optimize("@one /= 1"));
assertEquals("assign(@one,concat(@one,1))", optimize("@one .= 1"));
}
@Test public void testMultiAssign() throws Exception{
assertEquals("assign(@one,assign(@two,''))", optimize("@one = @two = ''"));
assertEquals("sconcat(assign(@one,assign(@two,'')),'blah')", optimize("@one = @two = '' 'blah'"));
}
@Test public void testAssignmentMixedWithAddition1() throws Exception{
assertEquals("add(1,assign(@a,1))", optimize("1 + @a = 1"));
}
@Test public void testAssignmentMixedWithAddition2() throws Exception{
assertEquals("add(1,assign(@a,add(@b,2)))", optimize("1 + @a = @b + 2"));
}
@Test public void testAssignmentMixedWithAddition3() throws Exception{
assertEquals("add(1,assign(@a,add(@b,@c,2)))", optimize("1 + @a = @b + @c + 2"));
}
@Test public void testAssignmentMixedWithAddition4() throws Exception{
assertEquals("add(1,assign(@a,add(@a,@b,@c,2)))", optimize("1 + @a += @b + @c + 2"));
}
@Test public void testAssignmentMixedWithAddition5() throws Exception{
assertEquals("add(1,assign(@_,assign(@a,add(@a,@b,@c,2))))", optimize("1 + @_ = @a += @b + @c + 2"));
}
@Test public void testAssignmentMixedWithAddition6() throws Exception{
assertEquals("sconcat(add(1,assign(@_,assign(@a,add(@a,@b,@c,2)))),'blah')", optimize("1 + @_ = @a += @b + @c + 2 'blah'"));
}
@Test public void testInnerIfAnded() throws Exception{
assertEquals("if(and(@a,@b),msg(''))", optimize("if(@a){ if(@b){ msg('') } }"));
}
@Test public void testInnerIfWithOtherStatements1() throws Exception{
assertEquals("if(@a,if(@b,msg(''),msg('')))", optimize("if(@a){ if(@b){ msg('') } else { msg('') } }"));
}
@Test public void testInnerIfWithOtherStatements2() throws Exception{
assertEquals("if(@a,sconcat(if(@b,msg('')),msg('')))", optimize("if(@a){ if(@b){ msg('') } msg('') }"));
}
@Test public void testInnerIfWithExistingAnd() throws Exception{
assertEquals("if(and(@a,@b,@c),msg(''))", optimize("if(@a && @b){ if(@c){ msg('') } }"));
}
@Test public void testForWithPostfix() throws Exception{
assertEquals("for(assign(@i,0),lt(@i,5),inc(@i),msg(''))", optimize("for(@i = 0, @i < 5, @i++, msg(''))"));
assertEquals("for(assign(@i,0),lt(@i,5),dec(@i),msg(''))", optimize("for(@i = 0, @i < 5, @i--, msg(''))"));
}
// Need to add this back too
// @Test public void testIfelseWithInnerDynamic() throws Exception{
// assertEquals("if(dyn(),msg('success'))", optimize("ifelse(1, if(dyn(), msg('success')),msg('fail'))"));
// }
@Test public void testAndOrPullsUp() throws Exception{
assertEquals("or(dyn(),dyn(),dyn())", optimize("dyn() || dyn() || dyn()"));
assertEquals("and(dyn(),dyn(),dyn())", optimize("dyn() && dyn() && dyn()"));
}
@Test public void testAndRemovesTrues() throws Exception{
assertEquals("and(dyn(),dyn())", optimize("and(true, dyn(), true, dyn())"));
assertEquals("true", optimize("and(true, true, true)"));
}
@Test public void testOrRemovesFalses() throws Exception{
assertEquals("or(dyn(),dyn())", optimize("or(false, dyn(), false, dyn())"));
assertEquals("false", optimize("or(false, false, false)"));
}
// This will be a nice optimization to add back soon
// @Test public void testNoOperationIf() throws Exception {
// assertEquals("g(dyn(1))", optimize("if(dyn(1)){ }"));
// }
//This won't work as easily, because the reg_count has already been evaluated
// @Test public void testNoSideEffectsRemovesUnusedBranches1() throws Exception {
// assertEquals("msg('hi')", optimize("if(reg_count('hi', 'hi')){ } msg('hi')"));
// }
// This is actually invalid, because the dyn has side effects (or rather, isn't registered to not
// have them). ALL nodes in the condition tree must have no side effects for this to be valid. For
// now, this isn't implemented anyways.
// @Test public void testNoSideEffectsRemovesUnusedBranches2() throws Exception {
// assertEquals("msg('hi')", optimize("if(reg_count('hi', dyn('hi'))){ } msg('hi')"));
// }
//tests the new switch syntax
@Test public void testSwitch1() throws Exception {
assertEquals("switch(@a,array(1,2),msg('1, 2'),"
+ "array(3..4),sconcat(msg('3'),msg('4')),"
+ "array(false),msg('false'),"
+ "array(0.07),msg(0.07),"
+ "msg('default'))",
optimize("switch(@a){"
+ " case 1:"
+ " case 2:"
+ " msg('1, 2');"
+ " case 3..4:"
+ " msg('3');"
+ " msg('4');"
+ " case false:"
+ " msg('false');"
+ " case 00.07:"
+ " msg(00.07);"
+ " case 'ignored':"
+ " default:"
+ " msg('default');"
+ "}"));
}
@Test(expected = ConfigCompileException.class)
public void testSwitch2() throws Exception {
optimize("switch(@a){"
+ " case 1:"
+ " case 0..2:"
+ " msg('invalid');"
+ "}");
}
@Test
public void testEmptySwitch() throws Exception {
assertEquals("switch(dyn(1))", optimize("switch(dyn(1)){ case 1: case 2: default: }"));
}
// Tests "-" signs in front of values to negate them.
@Test public void testMinusWithoutValueInFront() throws Exception{
assertEquals("assign(@b,neg(@a))", optimize("@b = -@a"));
assertEquals("assign(@b,neg(@a))", optimize("@b = - @a"));
assertEquals("assign(@b,array(neg(@a)))", optimize("@b = array(-@a)"));
assertEquals("assign(@b,array(neg(@a)))", optimize("@b = array(- @a)"));
assertEquals("assign(@b,neg(array_get(@a,1)))", optimize("@b = -@a[1]"));
assertEquals("assign(@b,neg(array_get(@a,1)))", optimize("@b = - @a[1]"));
assertEquals("assign(@b,neg(dec(@a)))", optimize("@b = -dec(@a)"));
assertEquals("assign(@b,neg(dec(@a)))", optimize("@b = - dec(@a)"));
assertEquals("assign(@b,neg(array_get(array(1,2,3),1)))", optimize("@b = -array(1,2,3)[1]"));
assertEquals("assign(@b,neg(array_get(array_get(array_get(array(array(array(2))),0),0),0)))", optimize("@b = -array(array(array(2)))[0][0][0]"));
assertEquals("assign(@b,neg(array_get(array_get(array_get(array(array(array(2))),neg(array_get(array(1,0),1))),0),0)))",
optimize("@b = -array(array(array(2)))[-array(1,0)[1]][0][0]"));
// Test behaviour where the value should not be negated.
assertEquals("assign(@c,subtract(@a,@b))", optimize("@c = @a - @b"));
assertEquals("assign(@c,subtract(array_get(@a,0),@b))", optimize("@c = @a[0] - @b"));
assertEquals("assign(@c,subtract(abs(@a),@b))", optimize("@c = abs(@a) - @b"));
assertEquals("assign(@c,subtract(if(@bool,2,3),@b))", optimize("@c = if(@bool) {2} else {3} - @b"));
assertEquals("assign(@b,subtract(dec(@a),2))", optimize("@b = dec(@a)-2"));
assertEquals("assign(@b,subtract(dec(@a),2))", optimize("@b = dec(@a)- 2"));
}
//TODO: This is a bit ambitious for now, put this back at some point, and then make it pass.
// @Test public void testAssign() throws Exception{
// //In this test, there's no way it won't ever be 'hi', so do a replacement (we still need to keep
// //the assign, because it does need to go into the variable table for reflective purposes)
// assertEquals("sconcat(assign(@a,'hi'),msg('hi'))", optimize("assign(@a, 'hi') msg(@a)"));
// //In this case, the first use may be hardcoded, but after the if, it may have changed, so we
// //can no longer assume it's always going to be 'hi'
// assertEquals("sconcat(assign(@a,'hi'),msg('hi'),if(dyn(),assign(@a,'bye')),msg(@a))",
// optimize(""
// + "assign(@a, 'hi')"
// + "msg(@a)"
// + "if(dyn(), assign(@a, 'bye'))"
// + "msg(@a)"));
// //In this case, we have a worthless assignment; We know @a is already 'hi' and it's always going
// //to be that, and we're trying to assign 'hi' again, so we can completely remove this from
// //the code, at which point the last msg can be optimized.
// assertEquals("sconcat(assign(@a,'hi'),msg('hi'),if(dyn(),null),msg('hi'))",
// optimize(""
// + "assign(@a, 'hi')"
// + "msg(@a)"
// + "if(dyn(), assign(@a, 'hi'))"
// + "msg(@a)"));
// }
@Test
public void testNotinstanceofKeyword() throws Exception {
assertEquals("msg(not(instanceof(dyn(2),int)))", optimize("msg(dyn(2) notinstanceof int);"));
}
@Test
public void testDor() throws Exception {
assertEquals("dor(dyn(''),dyn('a'))", optimize("dyn('') ||| dyn('a')"));
assertEquals("dor(dyn(''),dyn('a'),dyn('b'))", optimize("dyn('') ||| dyn('a') ||| dyn('b')"));
}
@Test
public void testDand() throws Exception {
assertEquals("dand(dyn(''),dyn('a'))", optimize("dyn('') &&& dyn('a')"));
assertEquals("dand(dyn(''),dyn('a'),dyn('b'))", optimize("dyn('') &&& dyn('a') &&& dyn('b')"));
}
@Test
public void testDorOptimization() throws Exception {
assertEquals("'a'", optimize("dor(false, false, 'a')"));
}
@Test
public void testDandOptimization() throws Exception {
assertEquals("''", optimize("dand(true, true, '')"));
}
}