package com.laytonsmith.core.functions; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.breakable; import com.laytonsmith.annotations.core; import com.laytonsmith.annotations.seealso; import com.laytonsmith.core.ArgumentValidation; import com.laytonsmith.core.CHVersion; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Script; import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.compiler.OptimizationUtilities; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CFunction; import com.laytonsmith.core.constructs.CIdentifier; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CKeyword; import com.laytonsmith.core.constructs.CLabel; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CSlice; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.exceptions.CRE.CRECastException; import com.laytonsmith.core.exceptions.CRE.CREFormatException; import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException; import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.exceptions.LoopBreakException; import java.util.ArrayList; import java.util.Comparator; import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * */ @core public class BasicLogic { public static String docs() { return "These functions provide basic logical operations."; } @api public static class _if extends AbstractFunction implements Optimizable { @Override public String getName() { return "if"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { for (ParseTree node : nodes) { if (node.getData() instanceof CIdentifier) { return new ifelse().execs(t, env, parent, nodes); } } ParseTree condition = nodes[0]; ParseTree __if = nodes[1]; ParseTree __else = null; if (nodes.length == 3) { __else = nodes[2]; } if (Static.getBoolean(parent.seval(condition, env))) { return parent.seval(__if, env); } else { if (__else == null) { return CVoid.VOID; } return parent.seval(__else, env); } } @Override public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { return CVoid.VOID; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public String docs() { return "mixed {cond, trueRet, [falseRet]} If the first argument evaluates to a true value, the second argument is returned, otherwise the third argument is returned." + " If there is no third argument, it returns void."; } @Override public boolean isRestricted() { return false; } @Override public boolean preResolveVariables() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } //Doesn't matter, this function is run out of state @Override public Boolean runAsync() { return false; } @Override public boolean useSpecialExec() { return true; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.OPTIMIZE_DYNAMIC ); } private static final String and = new and().getName(); @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> args, FileOptions fileOptions) throws ConfigCompileException { //Check for too many/few arguments if (args.size() < 2) { throw new ConfigCompileException("Too few arguments passed to if()", t); } if (args.size() > 3) { throw new ConfigCompileException("if() can only have 3 parameters", t); } if (args.get(0).isConst()) { // We can optimize this one way or the other, since the condition is const if (Static.getBoolean(args.get(0).getData())) { // It's true, return the true condition return args.get(1); } else // If there are three args, return the else condition, otherwise, // have it entirely remove us from the parse tree. if (args.size() == 3) { return args.get(2); } else { return Optimizable.REMOVE_ME; } } // If the code looks like this: // if(@a){ // if(@b){ // } // } // then we can turn this into if(@a && @b){ }, as they are functionally // equivalent, and this construct tends to be faster (less stack frames, presumably). // The caveat is that if the inner if statement has an else statement (or is ifelse) // or there are other nodes inside the statement, or we have an else clause // we cannot do this optimization, as it then has side effects. if (args.get(1).getData() instanceof CFunction && args.get(1).getData().val().equals("if") && args.size() == 2) { ParseTree _if = args.get(1); if (_if.getChildren().size() == 2) { // All the conditions are met, move this up ParseTree myCondition = args.get(0); ParseTree theirCondition = _if.getChildAt(0); ParseTree theirCode = _if.getChildAt(1); ParseTree andClause = new ParseTree(new CFunction(and, t), fileOptions); // If it's already an and(), just tack the other condition on if (myCondition.getData() instanceof CFunction && myCondition.getData().val().equals(and)) { andClause = myCondition; andClause.addChild(theirCondition); } else { andClause.addChild(myCondition); andClause.addChild(theirCondition); } args.set(0, andClause); args.set(1, theirCode); } } return null; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "if(true, msg('This is true'), msg('This is false'))"), new ExampleScript("With braces, true condition", "if(true){\n\tmsg('This is true')\n}"), new ExampleScript("With braces, false condition", "msg('Start')\nif(false){\n\tmsg('This will not show')\n}\nmsg('Finish')"),}; } } @api(environments = {GlobalEnv.class}) public static class ifelse extends AbstractFunction implements Optimizable { @Override public String getName() { return "ifelse"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "mixed {[boolean1, code]..., [elseCode]} Provides a more convenient method" + " for running if/else chains. If none of the conditions are true, and" + " there is no 'else' condition, void is returned."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREInsufficientArgumentsException.class}; } @Override public boolean isRestricted() { return false; } @Override public boolean preResolveVariables() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { return CNull.NULL; } @Override public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { if (nodes.length < 2) { throw new CREInsufficientArgumentsException("ifelse expects at least 2 arguments", t); } for (int i = 0; i <= nodes.length - 2; i += 2) { ParseTree statement = nodes[i]; ParseTree code = nodes[i + 1]; Construct evalStatement = parent.seval(statement, env); if (evalStatement instanceof CIdentifier) { evalStatement = parent.seval(((CIdentifier) evalStatement).contained(), env); } if (Static.getBoolean(evalStatement)) { Construct ret = env.getEnv(GlobalEnv.class).GetScript().eval(code, env); return ret; } } if (nodes.length % 2 == 1) { Construct ret = env.getEnv(GlobalEnv.class).GetScript().seval(nodes[nodes.length - 1], env); if (ret instanceof CIdentifier) { return parent.seval(((CIdentifier) ret).contained(), env); } else { return ret; } } return CVoid.VOID; } @Override public boolean useSpecialExec() { return true; } @Override public Set<Optimizable.OptimizationOption> optimizationOptions() { return EnumSet.of( Optimizable.OptimizationOption.OPTIMIZE_DYNAMIC ); } private static final String g = new DataHandling.g().getName(); @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { // TODO: Redo this optimization. return null; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "ifelse(false, msg('This is false'), true, msg('This is true'))"), new ExampleScript("With braces", "if(false){\n\tmsg('This is false')\n} else {\n\tmsg('This is true')\n}"), new ExampleScript("With braces, with else if", "if(false){\n\tmsg('This will not show')\n} else if(false){\n" + "\n\tmsg('This will not show')\n} else {\n\tmsg('This will show')\n}"),}; } } @api @breakable public static class _switch extends AbstractFunction implements Optimizable { @Override public String getName() { return "switch"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "mixed {value, [equals, code]..., [defaultCode]} Provides a switch statement. If none of the conditions" + " match, and no default is provided, void is returned." + " See the documentation on [[CommandHelper/Logic|Logic]] for more information. ----" + " In addition, slices may be used to indicate ranges of integers that should trigger the specified" + " case. Slices embedded in an array are fine as well. Switch statements also support brace/case/default" + " syntax, as in most languages, althrough unlike most languages, fallthrough isn't supported. Breaking" + " with break() isn't required, but recommended. A number greater than 1 may be sent to break, and breaking" + " out of the switch will consume a \"break counter\" and the break will continue up the chain." + " If you do use break(), the return value of switch is ignored. See the examples for usage" + " of brace/case/default syntax, which is highly recommended."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREInsufficientArgumentsException.class}; } @Override public boolean isRestricted() { return false; } @Override public boolean preResolveVariables() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { return CNull.NULL; } @Override public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { Construct value = parent.seval(nodes[0], env); equals equals = new equals(); try { for (int i = 1; i <= nodes.length - 2; i += 2) { ParseTree statement = nodes[i]; ParseTree code = nodes[i + 1]; Construct evalStatement = parent.seval(statement, env); if (evalStatement instanceof CSlice) { //More specific subclass of array, we can do more optimal handling here long rangeLeft = ((CSlice) evalStatement).getStart(); long rangeRight = ((CSlice) evalStatement).getFinish(); if (value instanceof CInt) { long v = Static.getInt(value, t); if ((rangeLeft < rangeRight && v >= rangeLeft && v <= rangeRight) || (rangeLeft > rangeRight && v >= rangeRight && v <= rangeLeft) || (rangeLeft == rangeRight && v == rangeLeft)) { return parent.seval(code, env); } } } else if (evalStatement instanceof CArray) { for (String index : ((CArray) evalStatement).stringKeySet()) { Construct inner = ((CArray) evalStatement).get(index, t); if (inner instanceof CSlice) { long rangeLeft = ((CSlice) inner).getStart(); long rangeRight = ((CSlice) inner).getFinish(); if (value instanceof CInt) { long v = Static.getInt(value, t); if ((rangeLeft < rangeRight && v >= rangeLeft && v <= rangeRight) || (rangeLeft > rangeRight && v >= rangeRight && v <= rangeLeft) || (rangeLeft == rangeRight && v == rangeLeft)) { return parent.seval(code, env); } } } else if (equals.exec(t, env, value, inner).getBoolean()) { return parent.seval(code, env); } } } else if (equals.exec(t, env, value, evalStatement).getBoolean()) { return parent.seval(code, env); } } if (nodes.length % 2 == 0) { return parent.seval(nodes[nodes.length - 1], env); } } catch (LoopBreakException ex) { //Ignored, unless the value passed in is greater than 1, in which case //we rethrow. if (ex.getTimes() > 1) { ex.setTimes(ex.getTimes() - 1); throw ex; } } return CVoid.VOID; } @Override public boolean useSpecialExec() { return true; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("With braces/case/default", "switch('theValue'){\n" + "\tcase 'notTheValue':\n" + "\t\tmsg('Nope')\n" + "\t\tbreak();\n" + "\tcase 'theValue':\n" + "\t\tmsg('Success')\n" + "\t\tbreak();\n" + "}"), new ExampleScript("With braces/case/default. Note the lack of fallthrough, even without a break()," + " except where two cases are directly back to back.", "@a = 5\nswitch(@a){\n" + "\tcase 1:\n" + "\tcase 2:\n" + "\t\tmsg('1 or 2');\n" + "\tcase 3..4:\n" + "\t\tmsg('3 or 4');\n" + "\t\tbreak(); // This is optional, as it would break here anyways, but is recommended.\n" + "\tcase 5..6:\n" + "\tcase 8:\n" + "\t\tmsg('5, 6, or 8')\n" + "\tdefault:\n" + "\t\tmsg('Any other value'); # A default is optional\n" + "}\n"), new ExampleScript("With default condition", "switch('noMatch'){\n" + "\tcase 'notIt1':\n" + "\t\tmsg('Nope');\n" + "\t\tbreak();\n" + "\tcase 'notIt2':\n" + "\t\tmsg('Nope');\n" + "\t\tbreak();\n" + "\tdefault:\n" + "\t\tmsg('Success');\n" + "\t\tbreak();\n" + "}"), new ExampleScript("With slices", "switch(5){\n" + "\tcase 1..2:\n" + "\t\tmsg('First');\n" + "\t\tbreak();\n" + "\tcase 3..5:\n" + "\t\tmsg('Second');\n" + "\t\tbreak();\n" + "\tcase 6..8:\n" + "\t\tmsg('Third');\n" + "\t\tbreak();\n" + "}"), new ExampleScript("Functional usage", "switch('theValue',\n" + "\t'notTheValue',\n" + "\t\tmsg('Nope'),\n" + "\t'theValue',\n" + "\t\tmsg('Success')\n" + ")"), new ExampleScript("With multiple matches using an array", "switch('string',\n" + "\tarray('value1', 'value2', 'string'),\n" + "\t\tmsg('Match'),\n" + "\t'value3',\n" + "\t\tmsg('No match')\n" + ")"), new ExampleScript("With slices in an array", "switch(5,\n" + "\tarray(1..2, 3..5),\n" + "\t\tmsg('First'),\n" + "\t6..8,\n" + "\t\tmsg('Second')\n" + ")"),}; } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { if (children.size() > 1 && children.get(1).getData() instanceof CFunction && new StringHandling.sconcat().getName().equals(children.get(1).getData().val())) { //This is the brace/case/default usage of switch, probably. We need //to refactor the data into the old switch format. List<ParseTree> newChildren = new ArrayList<>(); newChildren.add(children.get(0)); //Initial child List<ParseTree> c = children.get(1).getChildren(); List<ParseTree> lastCodeBlock = new ArrayList<>(); CArray conditions = new CArray(t); boolean inCase = false; boolean inDefault = false; for (int i = 0; i < c.size(); i++) { //Need up to a 2 lookahead ParseTree c1 = c.get(i); ParseTree c2 = null; if (i + 1 < c.size()) { c2 = c.get(i + 1); } if (CKeyword.isKeyword(c1, "case")) { //If this is a case AND the next one is //a label, this is a case. if (c2 != null && c2.getData() instanceof CLabel) { if (inDefault) { //Default must come last throw new ConfigCompileException("Unexpected case; the default case must come last.", t); } if (lastCodeBlock.size() > 0) { //Ok, need to push some stuff on to the new children newChildren.add(new ParseTree(conditions, c2.getFileOptions())); conditions = new CArray(t); ParseTree codeBlock = new ParseTree(new CFunction(new StringHandling.sconcat().getName(), t), c2.getFileOptions()); for (ParseTree line : lastCodeBlock) { codeBlock.addChild(line); } if (codeBlock.getChildren().size() == 1) { codeBlock = codeBlock.getChildAt(0); } newChildren.add(codeBlock); lastCodeBlock = new ArrayList<>(); } //Yes, it is. Now we also have to look ahead for //other cases, because //case 1: //case 2: // code() //would be turned into array(1, 2), code() in the //old style. conditions.push(((CLabel) c2.getData()).cVal(), t); inCase = true; i++; continue; } } if (c1.getData() instanceof CLabel && CKeyword.isKeyword(((CLabel) c1.getData()).cVal(), "default")) { //Default case if (lastCodeBlock.size() > 0) { //Ok, need to push some stuff on to the new children newChildren.add(new ParseTree(conditions, c1.getFileOptions())); conditions = new CArray(t); ParseTree codeBlock = new ParseTree(new CFunction(new StringHandling.sconcat().getName(), t), c1.getFileOptions()); for (ParseTree line : lastCodeBlock) { codeBlock.addChild(line); } if (codeBlock.getChildren().size() == 1) { codeBlock = codeBlock.getChildAt(0); } newChildren.add(codeBlock); lastCodeBlock = new ArrayList<>(); } else if (conditions.size() > 0) { //Special case where they have //case 0: //default: // code(); //This causes there to be conditions, but no code, //which throws off the argument length. In actuality, //we can simply throw out the conditions, because //this block of code will run if 0 or the default is //hit, and if 0 is the condition provided, it would //work the same if it weren't specified at all. conditions = new CArray(t); } inDefault = true; continue; } //Loop forward until we get to the next case if (inCase || inDefault) { lastCodeBlock.add(c1); } } if (conditions.size() > 0) { newChildren.add(new ParseTree(conditions, children.get(0).getFileOptions())); } if (lastCodeBlock.size() > 0) { ParseTree codeBlock = new ParseTree(new CFunction(new StringHandling.sconcat().getName(), t), lastCodeBlock.get(0).getFileOptions()); for (ParseTree line : lastCodeBlock) { codeBlock.addChild(line); } if (codeBlock.getChildren().size() == 1) { codeBlock = codeBlock.getChildAt(0); } newChildren.add(codeBlock); } children.clear(); children.addAll(newChildren); } //Loop through all the conditions and make sure each is unique. Also //make sure that each value is not dynamic. String notConstant = "Cases for a switch statement must be constant, not variable"; String alreadyContains = "The switch statement already contains a case for this value, remove the duplicate value"; final equals EQUALS = new equals(); Set<Construct> values = new TreeSet<>(new Comparator<Construct>() { @Override public int compare(Construct t, Construct t1) { if (EQUALS.exec(Target.UNKNOWN, null, t, t1).getBoolean()) { return 0; } else { return t.val().compareTo(t1.val()); } } }); final boolean hasDefaultCase = (children.size() & 0b00000001) == 0; // size % 2 == 0 -> Even number means there is a default. for (int i = 1; i < children.size(); i += 2) { if (hasDefaultCase && i == children.size() - 1) { // This is the default case code. Stop checking here. break; } //To standardize the rest of the code (and to optimize), go ahead and resolve array() if (children.get(i).getData() instanceof CFunction && new DataHandling.array().getName().equals(children.get(i).getData().val())) { CArray data = new CArray(t); for (ParseTree child : children.get(i).getChildren()) { if (child.getData().isDynamic()) { throw new ConfigCompileException(notConstant, child.getTarget()); } data.push(child.getData(), t); } children.set(i, new ParseTree(data, children.get(i).getFileOptions())); } //Now we validate that the values are constant and non-repeating. if (children.get(i).getData() instanceof CArray) { List<Construct> list = ((CArray) children.get(i).getData()).asList(); for (Construct c : list) { if (c instanceof CSlice) { for (Construct cc : ((CSlice) c).asList()) { if (values.contains(cc)) { throw new ConfigCompileException(alreadyContains, cc.getTarget()); } values.add(cc); } } else { if (c.isDynamic()) { throw new ConfigCompileException(notConstant, c.getTarget()); } if (values.contains(c)) { throw new ConfigCompileException(alreadyContains, c.getTarget()); } values.add(c); } } } else { Construct c = children.get(i).getData(); if (c.isDynamic()) { throw new ConfigCompileException(notConstant, c.getTarget()); } if (values.contains(c)) { throw new ConfigCompileException(alreadyContains, c.getTarget()); } values.add(c); } } if ((children.size() > 3 || (children.size() > 1 && children.get(1).getData() instanceof CArray)) //No point in doing this optimization if there are only 3 args and the case is flat. //Also, doing this check prevents an inifinite loop during optimization. && (children.size() > 0 && !children.get(0).getData().isDynamic())) { ParseTree toReturn = null; //The item passed in is constant (or has otherwise been made constant) //so we can go ahead and condense this down to the single code path //in the switch. for (int i = 1; i < children.size(); i += 2) { Construct data = children.get(i).getData(); if (!(data instanceof CArray) || data instanceof CSlice) { //Put it in an array to make the rest of this parsing easier. data = new CArray(t); ((CArray) data).push(children.get(i).getData(), t); } for (Construct value : ((CArray) data).asList()) { if (value instanceof CSlice) { long rangeLeft = ((CSlice) value).getStart(); long rangeRight = ((CSlice) value).getFinish(); if (children.get(0).getData() instanceof CInt) { long v = Static.getInt(children.get(0).getData(), t); if ((rangeLeft < rangeRight && v >= rangeLeft && v <= rangeRight) || (rangeLeft > rangeRight && v >= rangeRight && v <= rangeLeft) || (rangeLeft == rangeRight && v == rangeLeft)) { toReturn = children.get(i + 1); break; } } } else if (EQUALS.exec(t, null, children.get(0).getData(), value).getBoolean()) { toReturn = children.get(i + 1); break; } } } //None of the values match. Return the default case, if it exists, or remove the switch entirely //if it doesn't. if (toReturn == null) { if (children.size() % 2 == 0) { toReturn = children.get(children.size() - 1); } else { return Optimizable.REMOVE_ME; } } //Unfortunately, we can't totally remove this, because otherwise break()s in the code //will go unchecked, so we need to keep switch in the code somehow. To make it easy though, //we'll make the most efficient switch we can. ParseTree ret = new ParseTree(new CFunction(new _switch().getName(), t), fileOptions); ret.addChild(new ParseTree(new CInt(1, t), fileOptions)); ret.addChild(new ParseTree(new CInt(1, t), fileOptions)); ret.addChild(toReturn); return ret; } return null; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.PRIORITY_OPTIMIZATION); } } @api @seealso({nequals.class, sequals.class, snequals.class}) public static class equals extends AbstractFunction implements Optimizable { private static final equals self = new equals(); /** * Returns the results that this function would provide, but in a java specific manner, so other code may easily * determine how this method would respond. * * @param one * @param two * @return */ public static boolean doEquals(Construct one, Construct two) { CBoolean ret = self.exec(Target.UNKNOWN, null, one, two); return ret.getBoolean(); } @Override public String getName() { return "equals"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public CBoolean exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { if (args.length <= 1) { throw new CREInsufficientArgumentsException("At least two arguments must be passed to equals", t); } boolean referenceMatch = true; for (int i = 0; i < args.length - 1; i++) { if (args[i] != args[i + 1]) { referenceMatch = false; break; } } if (referenceMatch) { return CBoolean.TRUE; } if (Static.anyNulls(args)) { boolean equals = true; for (Construct c : args) { if (!(c instanceof CNull)) { equals = false; } } return CBoolean.get(equals); } if (Static.anyBooleans(args)) { boolean equals = true; for (int i = 1; i < args.length; i++) { boolean arg1 = Static.getBoolean(args[i - 1]); boolean arg2 = Static.getBoolean(args[i]); if (arg1 != arg2) { equals = false; break; } } return CBoolean.get(equals); } { boolean equals = true; for (int i = 1; i < args.length; i++) { if (!args[i - 1].val().equals(args[i].val())) { equals = false; break; } } if (equals) { return CBoolean.TRUE; } } try { // Validate that these are numbers, so that getNumber doesn't throw an exception. if (!ArgumentValidation.isNumber(args[0])) { return CBoolean.FALSE; } boolean equals = true; for (int i = 1; i < args.length; i++) { if (!ArgumentValidation.isNumber(args[i])) { return CBoolean.FALSE; } double arg1 = Static.getNumber(args[i - 1], t); double arg2 = Static.getNumber(args[i], t); if (arg1 != arg2) { return CBoolean.FALSE; } } return CBoolean.TRUE; } catch (ConfigRuntimeException e) { return CBoolean.FALSE; } } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREInsufficientArgumentsException.class}; } @Override public String docs() { return "boolean {var1, var2[, varX...]} Returns true or false if all the arguments are equal. Operator syntax is" + " also supported: @a == @b"; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } @Override public Boolean runAsync() { return null; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "equals(1, 1.0, '1')"), new ExampleScript("Operator syntax", "1 == 1"), new ExampleScript("Not equivalent", "'one' == 'two'"),}; } } @api @seealso({equals.class, nequals.class, snequals.class}) public static class sequals extends AbstractFunction implements Optimizable { @Override public String getName() { return "sequals"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {val1, val2} Uses a strict equals check, which determines if" + " two values are not only equal, but also the same type. So, while" + " equals('1', 1) returns true, sequals('1', 1) returns false, because" + " the first one is a string, and the second one is an int. More often" + " than not, you want to use plain equals(). In addition, type juggling is" + " explicitely not performed on strings. Thus '2' !== '2.0', despite those" + " being ==. Operator syntax is also" + " supported: @a === @b"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public CBoolean exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } if (args[1].getClass().equals(args[0].getClass())) { if (args[0] instanceof CString && args[1] instanceof CString) { // Check for actual string equality, so we don't do type massaging // for numeric strings. Thus '2' !== '2.0' return CBoolean.get(args[0].val().equals(args[1].val())); } return new equals().exec(t, environment, args); } else { return CBoolean.FALSE; } } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "sequals('1', 1)"), new ExampleScript("Symbolic usage", "'1' === 1"), new ExampleScript("Symbolic usage", "'1' === '1'"),}; } } @api @seealso({sequals.class}) public static class snequals extends AbstractFunction implements Optimizable { @Override public String getName() { return "snequals"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {val1, val2} Equivalent to not(sequals(val1, val2)). Operator syntax" + " is also supported: @a !== @b"; } @Override public Class<? extends CREThrowable>[] thrown() { return null; } @Override public boolean isRestricted() { return false; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { return new sequals().exec(t, environment, args).not(); } @Override public CHVersion since() { return CHVersion.V3_3_1; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "snequals('1', 1)"), new ExampleScript("Basic usage", "snequals('1', '1')"), new ExampleScript("Operator syntax", "'1' !== '1'"), new ExampleScript("Operator syntax", "'1' !== 1"),}; } } @api @seealso({equals.class, sequals.class, snequals.class}) public static class nequals extends AbstractFunction implements Optimizable { @Override public String getName() { return "nequals"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {val1, val2} Returns true if the two values are NOT equal, or false" + " otherwise. Equivalent to not(equals(val1, val2)). Operator syntax is also" + " supported: @a != @b"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public CBoolean exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { return new equals().exec(t, env, args).not(); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "nequals('one', 'two')"), new ExampleScript("Basic usage", "nequals(1, 1)"), new ExampleScript("Operator syntax", "1 != 1"), new ExampleScript("Operator syntax", "1 != 2"),}; } } @api public static class equals_ic extends AbstractFunction implements Optimizable { @Override public String getName() { return "equals_ic"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "boolean {val1, val2[, valX...]} Returns true if all the values are equal to each other, while" + " ignoring case."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CREInsufficientArgumentsException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_2_0; } @Override public Boolean runAsync() { return null; } @Override public CBoolean exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { if (args.length <= 1) { throw new CREInsufficientArgumentsException("At least two arguments must be passed to equals_ic", t); } if (Static.anyBooleans(args)) { boolean equals = true; for (int i = 1; i < args.length; i++) { boolean arg1 = Static.getBoolean(args[i - 1]); boolean arg2 = Static.getBoolean(args[i]); if (arg1 != arg2) { equals = false; break; } } return CBoolean.get(equals); } { boolean equals = true; for (int i = 1; i < args.length; i++) { if (!args[i - 1].val().equalsIgnoreCase(args[i].val())) { equals = false; break; } } if (equals) { return CBoolean.TRUE; } } try { // Validate that these are numbers, so that getNumber doesn't throw an exception. if (!ArgumentValidation.isNumber(args[0])) { return CBoolean.FALSE; } boolean equals = true; for (int i = 1; i < args.length; i++) { if (!ArgumentValidation.isNumber(args[i])) { return CBoolean.FALSE; } double arg1 = Static.getNumber(args[i - 1], t); double arg2 = Static.getNumber(args[i], t); if (arg1 != arg2) { return CBoolean.FALSE; } } return CBoolean.TRUE; } catch (ConfigRuntimeException e) { return CBoolean.FALSE; } } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "equals_ic('test', 'TEST')"), new ExampleScript("Basic usage", "equals_ic('completely', 'DIFFERENT')"),}; } } @api public static class sequals_ic extends AbstractFunction implements Optimizable { @Override public Class<? extends CREThrowable>[] thrown() { return null; } @Override public boolean isRestricted() { return false; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { Construct v1 = args[0]; Construct v2 = args[1]; if (!v2.getClass().equals(v1.getClass())) { return CBoolean.FALSE; } return new equals_ic().exec(t, environment, v1, v2); } @Override public String getName() { return "sequals_ic"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {value1, value2} Returns true if the values are the same type, as well as equal, according to equals_ic." + " Generally, equals_ic will suffice, because usually you will be comparing two strings, however, this function" + " may be useful in various other cases, perhaps where the datatypes are unknown, but could be strings."; } @Override public Version since() { return CHVersion.V3_3_1; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "sequals_ic(1, 1)"), new ExampleScript("False result", "sequals_ic('1', 1)"), new ExampleScript("False result", "sequals_ic('false', true)") }; } } @api @seealso({equals_ic.class}) public static class nequals_ic extends AbstractFunction implements Optimizable { @Override public String getName() { return "nequals_ic"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {val1, val2} Returns true if the two values are NOT equal to each other, while" + " ignoring case."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public CBoolean exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { return new equals_ic().exec(t, environment, args).not(); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "equals_ic('test', 'TEST')"), new ExampleScript("Basic usage", "equals_ic('completely', 'DIFFERENT')"),}; } } @api public static class ref_equals extends AbstractFunction { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return false; } @Override public Boolean runAsync() { return null; } @Override public CBoolean exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args[0] instanceof CArray && args[1] instanceof CArray) { return CBoolean.get(args[0] == args[1]); } else { return new equals().exec(t, environment, args); } } @Override public String getName() { return "ref_equals"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {val1, val2} Returns true if and only if the two values are actually the same reference." + " Primitives that are equal will always be the same reference, this method is only useful for" + " object/array comparisons."; } @Override public CHVersion since() { return CHVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Usage with primitives", "msg(ref_equals(1, 1))\n" + "msg(ref_equals(1, 2))"), new ExampleScript("Usage with arrays that are the same reference", "@a = array(1, 2, 3)\n" + "@b = @a\n" + "msg(ref_equals(@a, @b)) # Note that an assignment simply sets it to reference the same underlying object, so this is true"), new ExampleScript("Usage with a cloned array", "@a = array(1, 2, 3)\n" + "@b = @a[] # Clone the array\n" + "msg(ref_equals(@a, @b)) # False, because although the arrays are == (and ===) they are different references"), new ExampleScript("Usage with a duplicated array", "@a = array(1, 2, 3)\n" + "@b = array(1, 2, 3) # New array with duplicate content\n" + "msg(ref_equals(@a, @b)) # Again, even though @a == @b and @a === @b, this is false, because they are two different references"),}; } } @api @seealso({gt.class, lte.class, gte.class}) public static class lt extends AbstractFunction implements Optimizable { @Override public String getName() { return "lt"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } double arg1 = Static.getNumber(args[0], t); double arg2 = Static.getNumber(args[1], t); return CBoolean.get(arg1 < arg2); } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public String docs() { return "boolean {var1, var2} Returns the results of a less than operation. Operator syntax" + " is also supported: @a < @b"; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } @Override public Boolean runAsync() { return null; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "lt(4, 5)"), new ExampleScript("Operator syntax, true condition", "4 < 5"), new ExampleScript("Operator syntax, false condition", "5 < 4"),}; } } @api @seealso({lt.class, lte.class, gte.class}) public static class gt extends AbstractFunction implements Optimizable { @Override public String getName() { return "gt"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } double arg1 = Static.getNumber(args[0], t); double arg2 = Static.getNumber(args[1], t); return CBoolean.get(arg1 > arg2); } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public String docs() { return "boolean {var1, var2} Returns the result of a greater than operation. Operator syntax is also supported:" + " @a > @b"; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } @Override public Boolean runAsync() { return null; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "gt(5, 4)"), new ExampleScript("Operator syntax, true condition", "5 > 4"), new ExampleScript("Operator syntax, false condition", "4 > 5"),}; } } @api @seealso({lt.class, gt.class, gte.class}) public static class lte extends AbstractFunction implements Optimizable { @Override public String getName() { return "lte"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } double arg1 = Static.getNumber(args[0], t); double arg2 = Static.getNumber(args[1], t); return CBoolean.get(arg1 <= arg2); } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public String docs() { return "boolean {var1, var2} Returns the result of a less than or equal to operation. Operator" + " syntax is also supported: @a <= @b"; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } @Override public Boolean runAsync() { return null; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "lte(4, 5)"), new ExampleScript("Operator syntax, true condition", "4 <= 5"), new ExampleScript("Operator syntax, true condition", "5 <= 5"), new ExampleScript("Operator syntax, false condition", "5 <= 4"),}; } } @api @seealso({lt.class, gt.class, lte.class}) public static class gte extends AbstractFunction implements Optimizable { @Override public String getName() { return "gte"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } double arg1 = Static.getNumber(args[0], t); double arg2 = Static.getNumber(args[1], t); return CBoolean.get(arg1 >= arg2); } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public String docs() { return "boolean {var1, var2} Returns the result of a greater than or equal to operation. Operator" + " sytnax is also supported: @a >= @b"; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } @Override public Boolean runAsync() { return null; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "gte(5, 4)"), new ExampleScript("Operator syntax, true condition", "4 >= 4"), new ExampleScript("Operator syntax, false condition", "4 >= 5"),}; } } @api(environments = {GlobalEnv.class}) @seealso({or.class}) public static class and extends AbstractFunction implements Optimizable { @Override public String getName() { return "and"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public CBoolean exec(Target t, Environment env, Construct... args) { //This will only happen if they hardcode true/false in, but we still //need to handle it appropriately. for (Construct c : args) { if (!Static.getBoolean(c)) { return CBoolean.FALSE; } } return CBoolean.TRUE; } @Override public CBoolean execs(Target t, Environment env, Script parent, ParseTree... nodes) { for (ParseTree tree : nodes) { Construct c = env.getEnv(GlobalEnv.class).GetScript().seval(tree, env); boolean b = Static.getBoolean(c); if (b == false) { return CBoolean.FALSE; } } return CBoolean.TRUE; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public String docs() { return "boolean {var1, [var2...]} Returns the boolean value of a logical AND across all arguments. Uses lazy determination, so once " + "an argument returns false, the function returns. Operator syntax is supported:" + " @a && @b"; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } @Override public Boolean runAsync() { return null; } @Override public boolean useSpecialExec() { return true; } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { OptimizationUtilities.pullUpLikeFunctions(children, getName()); Iterator<ParseTree> it = children.iterator(); boolean foundFalse = false; while (it.hasNext()) { //Remove hard coded true values, they won't affect the calculation at all //Also walk through the children, and if we find a hardcoded false, discard all the following values. //If we do find a hardcoded false, though we can know ahead of time that this statement as a whole //will be false, we can't remove everything, as the parameters beforehand may have side effects, so //we musn't remove them. ParseTree child = it.next(); if (foundFalse) { it.remove(); continue; } if (child.isConst()) { if (Static.getBoolean(child.getData()) == true) { it.remove(); } else { foundFalse = true; } } } // TODO: Can't do this yet, because children of side effect free functions may still have side effects that // we need to maintain. However, with complications introduced by code branch functions, we can't process // this yet. // if(foundFalse){ // //However, we can remove any functions that have no side effects that come before the false. // it = children.iterator(); // while(it.hasNext()){ // Construct data = it.next().getData(); // if(data instanceof CFunction && ((CFunction)data).getFunction() instanceof Optimizable){ // if(((Optimizable)((CFunction)data).getFunction()).optimizationOptions().contains(OptimizationOption.NO_SIDE_EFFECTS)){ // it.remove(); // } // } // } // } // At this point, it could be that there are some conditions with side effects, followed by a final false. However, // if false is the only remaining condition (which could be) then we can simply return false here. if (children.size() == 1 && children.get(0).isConst() && Static.getBoolean(children.get(0).getData()) == false) { return new ParseTree(CBoolean.FALSE, fileOptions); } if (children.isEmpty()) { //We've removed all the children, so return true, because they were all true. return new ParseTree(CBoolean.TRUE, fileOptions); } return null; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "and(true, true)"), new ExampleScript("Operator syntax, true condition", "true && true"), new ExampleScript("Operator syntax, false condition", "true && false"), new ExampleScript("Short circuit", "false && msg('This will not show')"),}; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); } } @api public static class dand extends AbstractFunction implements Optimizable { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return false; } @Override public Boolean runAsync() { return null; } @Override public boolean useSpecialExec() { return true; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { return CVoid.VOID; } @Override public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { for (ParseTree tree : nodes) { Construct c = env.getEnv(GlobalEnv.class).GetScript().seval(tree, env); if (!Static.getBoolean(c)) { return c; } } return CBoolean.TRUE; } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { OptimizationUtilities.pullUpLikeFunctions(children, getName()); Iterator<ParseTree> it = children.iterator(); boolean foundFalse = false; while (it.hasNext()) { //Remove hard coded true values, they won't affect the calculation at all //Also walk through the children, and if we find a hardcoded false, discard all the following values. //If we do find a hardcoded false, though we can know ahead of time that this statement as a whole //will be false, we can't remove everything, as the parameters beforehand may have side effects, so //we musn't remove them. ParseTree child = it.next(); if (foundFalse) { it.remove(); continue; } if (child.isConst()) { if (Static.getBoolean(child.getData()) == true) { it.remove(); } else { foundFalse = true; } } } // TODO: Can't do this yet, because children of side effect free functions may still have side effects that // we need to maintain. However, with complications introduced by code branch functions, we can't process // this yet. // if(foundFalse){ // //However, we can remove any functions that have no side effects that come before the false. // it = children.iterator(); // while(it.hasNext()){ // Construct data = it.next().getData(); // if(data instanceof CFunction && ((CFunction)data).getFunction() instanceof Optimizable){ // if(((Optimizable)((CFunction)data).getFunction()).optimizationOptions().contains(OptimizationOption.NO_SIDE_EFFECTS)){ // it.remove(); // } // } // } // } // At this point, it could be that there are some conditions with side effects, followed by a final false. However, // if false is the only remaining condition (which could be) then we can simply return false here. if (children.size() == 1 && children.get(0).isConst() && Static.getBoolean(children.get(0).getData()) == false) { return new ParseTree(children.get(0).getData(), fileOptions); } if (children.isEmpty()) { //We've removed all the children, so return true, because they were all true. return new ParseTree(CBoolean.TRUE, fileOptions); } return null; } @Override public String getName() { return "dand"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "mixed {...} Returns the first false value. The arguments to this function are lazily evaluated, so" + " if the first value evaluates to false, the rest of the arguments will not be evaluated." + " If none of the values are false, true is returned. Usage of" + " the operator is preferred: &&&"; } @Override public Version since() { return CHVersion.V3_3_2; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); } } @api(environments = {GlobalEnv.class}) @seealso({and.class}) public static class or extends AbstractFunction implements Optimizable { @Override public String getName() { return "or"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public CBoolean exec(Target t, Environment env, Construct... args) { //This will only happen if they hardcode true/false in, but we still //need to handle it appropriately. for (Construct c : args) { if (Static.getBoolean(c)) { return CBoolean.TRUE; } } return CBoolean.FALSE; } @Override public CBoolean execs(Target t, Environment env, Script parent, ParseTree... nodes) { for (ParseTree tree : nodes) { Construct c = env.getEnv(GlobalEnv.class).GetScript().seval(tree, env); if (Static.getBoolean(c)) { return CBoolean.TRUE; } } return CBoolean.FALSE; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public String docs() { return "boolean {var1, [var2...]} Returns the boolean value of a logical OR across all arguments. Uses lazy" + " determination, so once an argument resolves to true, the function returns. Operator syntax is also" + " supported: @a <nowiki>||</nowiki> @b"; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } @Override public Boolean runAsync() { return null; } @Override public boolean useSpecialExec() { return true; } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { OptimizationUtilities.pullUpLikeFunctions(children, getName()); Iterator<ParseTree> it = children.iterator(); boolean foundTrue = false; while (it.hasNext()) { //Remove hard coded false values, they won't affect the calculation at all //Also walk through the children, and if we find a hardcoded true, discard all the following values. //If we do find a hardcoded true, though we can know ahead of time that this statement as a whole //will be true, we can't remove everything, as the parameters beforehand may have side effects, so //we musn't remove them. ParseTree child = it.next(); if (foundTrue) { it.remove(); continue; } if (child.isConst()) { if (Static.getBoolean(child.getData()) == false) { it.remove(); } else { foundTrue = true; } } } // TODO: Can't do this yet, because children of side effect free functions may still have side effects that // we need to maintain. However, with complications introduced by code branch functions, we can't process // this yet. // if(foundTrue){ // //However, we can remove any functions that have no side effects that come before the true. // it = children.iterator(); // while(it.hasNext()){ // Construct data = it.next().getData(); // if(data instanceof CFunction && ((CFunction)data).getFunction() instanceof Optimizable){ // if(((Optimizable)((CFunction)data).getFunction()).optimizationOptions().contains(OptimizationOption.NO_SIDE_EFFECTS)){ // it.remove(); // } // } // } // } // At this point, it could be that there are some conditions with side effects, followed by a final true. However, // if true is the only remaining condition (which could be) then we can simply return true here. if (children.size() == 1 && children.get(0).isConst() && Static.getBoolean(children.get(0).getData()) == true) { return new ParseTree(CBoolean.TRUE, fileOptions); } if (children.isEmpty()) { //We've removed all the children, so return false, because they were all false. return new ParseTree(CBoolean.FALSE, fileOptions); } return null; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "or(false, true)"), new ExampleScript("Operator syntax, true condition", "true || false"), new ExampleScript("Operator syntax, false condition", "false || false"), new ExampleScript("Short circuit", "true || msg('This will not show')"),}; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); } } @api public static class dor extends AbstractFunction implements Optimizable { @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{}; } @Override public boolean isRestricted() { return false; } @Override public Boolean runAsync() { return null; } @Override public boolean useSpecialExec() { return true; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { return CVoid.VOID; } @Override public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { for (ParseTree tree : nodes) { Construct c = env.getEnv(GlobalEnv.class).GetScript().seval(tree, env); if (Static.getBoolean(c)) { return c; } } return CBoolean.FALSE; } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { OptimizationUtilities.pullUpLikeFunctions(children, getName()); Iterator<ParseTree> it = children.iterator(); boolean foundTrue = false; while (it.hasNext()) { //Remove hard coded false values, they won't affect the calculation at all //Also walk through the children, and if we find a hardcoded true, discard all the following values. //If we do find a hardcoded true, though we can know ahead of time that this statement as a whole //will be true, we can't remove everything, as the parameters beforehand may have side effects, so //we musn't remove them. ParseTree child = it.next(); if (foundTrue) { it.remove(); continue; } if (child.isConst()) { if (Static.getBoolean(child.getData()) == false) { it.remove(); } else { foundTrue = true; } } } // TODO: Can't do this yet, because children of side effect free functions may still have side effects that // we need to maintain. However, with complications introduced by code branch functions, we can't process // this yet. // if(foundTrue){ // //However, we can remove any functions that have no side effects that come before the true. // it = children.iterator(); // while(it.hasNext()){ // Construct data = it.next().getData(); // if(data instanceof CFunction && ((CFunction)data).getFunction() instanceof Optimizable){ // if(((Optimizable)((CFunction)data).getFunction()).optimizationOptions().contains(OptimizationOption.NO_SIDE_EFFECTS)){ // it.remove(); // } // } // } // } // At this point, it could be that there are some conditions with side effects, followed by a final true. However, // if true is the only remaining condition (which could be) then we can simply return true here. if (children.size() == 1 && children.get(0).isConst() && Static.getBoolean(children.get(0).getData()) == true) { return new ParseTree(children.get(0).getData(), fileOptions); } if (children.isEmpty()) { //We've removed all the children, so return false, because they were all false. return new ParseTree(CBoolean.FALSE, fileOptions); } return null; } @Override public String getName() { return "dor"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "mixed {...} Returns the first true value. The arguments to this function are lazily evaluated, so" + " if the first value evaluates to true, the rest of the arguments will not be evaluated." + " If none of the values are true, false is returned. Usage of" + " the operator is preferred: |||"; } @Override public Version since() { return CHVersion.V3_3_2; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage with default used", "@a = '';\n@b = @a ||| 'default';\nmsg(@b);"), new ExampleScript("Basic usage with first value used", "@a = 'value is set';\n@b = @a ||| 'default';\nmsg(@b);") }; } } @api public static class not extends AbstractFunction implements Optimizable { @Override public String getName() { return "not"; } @Override public Integer[] numArgs() { return new Integer[]{1}; } @Override public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { if (args.length != 1) { throw new CREFormatException(this.getName() + " expects 1 argument.", t); } return CBoolean.get(!Static.getBoolean(args[0])); } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public String docs() { return "boolean {var1} Returns the boolean value of a logical NOT for this argument. Operator syntax is also supported: !@var"; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_0_1; } @Override public Boolean runAsync() { return null; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Functional usage", "not(false)"), new ExampleScript("Operator syntax, true condition", "!false"), new ExampleScript("Operator syntax, false condition", "!true"), new ExampleScript("Operator syntax, using variable", "!@var"),}; } } @api public static class xor extends AbstractFunction implements Optimizable { @Override public String getName() { return "xor"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {val1, val2} Returns the xor of the two values."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public CBoolean exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } boolean val1 = Static.getBoolean(args[0]); boolean val2 = Static.getBoolean(args[1]); return CBoolean.get(val1 ^ val2); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "xor(true, false)"),}; } } @api @seealso({and.class}) public static class nand extends AbstractFunction { @Override public String getName() { return "nand"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "boolean {val1, [val2...]} Return the equivalent of not(and())"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) { return CNull.NULL; } @Override public CBoolean execs(Target t, Environment env, Script parent, ParseTree... nodes) { return new and().execs(t, env, parent, nodes).not(); } @Override public boolean useSpecialExec() { return true; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "nand(true, true)"),}; } } @api @seealso({or.class}) public static class nor extends AbstractFunction { @Override public String getName() { return "nor"; } @Override public Integer[] numArgs() { return new Integer[]{Integer.MAX_VALUE}; } @Override public String docs() { return "boolean {val1, [val2...]} Returns the equivalent of not(or())"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) { return CNull.NULL; } @Override public CBoolean execs(Target t, Environment environment, Script parent, ParseTree... args) throws ConfigRuntimeException { return new or().execs(t, environment, parent, args).not(); } @Override public boolean useSpecialExec() { return true; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "nor(true, false)"),}; } } @api @seealso({xor.class}) public static class xnor extends AbstractFunction implements Optimizable { @Override public String getName() { return "xnor"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "boolean {val1, val2} Returns the xnor of the two values"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public CBoolean exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } return new xor().exec(t, environment, args).not(); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "xnor(true, true)"),}; } } @api public static class bit_and extends AbstractFunction implements Optimizable { @Override public String getName() { return "bit_and"; } @Override public Integer[] numArgs() { return new Integer[]{2, Integer.MAX_VALUE}; } @Override public String docs() { return "int {int1, int2, [int3...]} Returns the bitwise AND of the values"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length < 2) { throw new CREFormatException(this.getName() + " expects at least 2 arguments.", t); } long val = Static.getInt(args[0], t); for (int i = 1; i < args.length; i++) { val = val & Static.getInt(args[i], t); } return new CInt(val, t); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN, OptimizationOption.OPTIMIZE_DYNAMIC ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "bit_and(1, 2, 4)"), new ExampleScript("Usage in masking applications. Note that 5 in binary is 101 and 4 is 100. (See bit_or for a more complete example.)", "assign(@var, 5)\nif(bit_and(@var, 4),\n\tmsg('Third bit set')\n)"),}; } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { if (children.size() < 2) { throw new ConfigCompileException("bit_and() requires at least 2 arguments.", t); } return null; } } @api public static class bit_or extends AbstractFunction implements Optimizable { @Override public String getName() { return "bit_or"; } @Override public Integer[] numArgs() { return new Integer[]{2, Integer.MAX_VALUE}; } @Override public String docs() { return "int {int1, int2, [int3...]} Returns the bitwise OR of the specified values"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length < 2) { throw new CREFormatException(this.getName() + " expects at least 2 arguments.", t); } long val = Static.getInt(args[0], t); for (int i = 1; i < args.length; i++) { val = val | Static.getInt(args[i], t); } return new CInt(val, t); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN, OptimizationOption.OPTIMIZE_DYNAMIC ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "bit_or(1, 2, 4)"), new ExampleScript("Usage in masking applications. (Used to create a mask)", "assign(@flag1, 1)\nassign(@flag2, 2)\nassign(@flag3, 4)\n" + "assign(@flags, bit_or(@flag1, @flag3))\n" + "if(bit_and(@flags, @flag1),\n\tmsg('Contains flag 1')\n)\n" + "if(!bit_and(@flags, @flag2),\n\tmsg('Does not contain flag 2')\n)"),}; } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { if (children.size() < 2) { throw new ConfigCompileException("bit_or() requires at least 2 arguments.", t); } return null; } } @api public static class bit_xor extends AbstractFunction implements Optimizable { @Override public String getName() { return "bit_xor"; } @Override public Integer[] numArgs() { return new Integer[]{2, Integer.MAX_VALUE}; } @Override public String docs() { return "int {int1, int2, [int3...]} Returns the bitwise exclusive OR of the specified values"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_1; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length < 2) { throw new CREFormatException(this.getName() + " expects at least 2 arguments.", t); } long val = Static.getInt(args[0], t); for (int i = 1; i < args.length; i++) { val = val ^ Static.getInt(args[i], t); } return new CInt(val, t); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN, OptimizationOption.OPTIMIZE_DYNAMIC ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "bit_xor(1, 2, 4)"),}; } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { if (children.size() < 2) { throw new ConfigCompileException("bit_xor() requires at least 2 arguments.", t); } return null; } } @api public static class bit_not extends AbstractFunction implements Optimizable { @Override public String getName() { return "bit_not"; } @Override public Integer[] numArgs() { return new Integer[]{1}; } @Override public String docs() { return "int {int1} Returns the bitwise NOT of the given value"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length != 1) { throw new CREFormatException(this.getName() + " expects 1 argument.", t); } return new CInt(~Static.getInt(args[0], t), t); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "bit_not(1)"),}; } } @api public static class lshift extends AbstractFunction implements Optimizable { @Override public String getName() { return "lshift"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "int {value, bitsToShift} Left shifts the value bitsToShift times"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } long value = Static.getInt(args[0], t); long toShift = Static.getInt(args[1], t); return new CInt(value << toShift, t); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "lshift(1, 1)"),}; } } @api public static class rshift extends AbstractFunction implements Optimizable { @Override public String getName() { return "rshift"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "int {value, bitsToShift} Right shifts the value bitsToShift times"; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } long value = Static.getInt(args[0], t); long toShift = Static.getInt(args[1], t); return new CInt(value >> toShift, t); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "rshift(2, 1)"), new ExampleScript("Basic usage", "rshift(-2, 1)"),}; } } @api public static class urshift extends AbstractFunction implements Optimizable { @Override public String getName() { return "urshift"; } @Override public Integer[] numArgs() { return new Integer[]{2}; } @Override public String docs() { return "int {value, bitsToShift} Right shifts value bitsToShift times, pushing a 0, making" + " this an unsigned right shift."; } @Override public Class<? extends CREThrowable>[] thrown() { return new Class[]{CRECastException.class}; } @Override public boolean isRestricted() { return false; } @Override public CHVersion since() { return CHVersion.V3_3_0; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { if (args.length != 2) { throw new CREFormatException(this.getName() + " expects 2 arguments.", t); } long value = Static.getInt(args[0], t); long toShift = Static.getInt(args[1], t); return new CInt(value >>> toShift, t); } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN ); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "urshift(2, 1)"), new ExampleScript("Basic usage", "urshift(-2, 1)"),}; } } @api public static class compile_error extends AbstractFunction implements Optimizable { @Override public Class<? extends CREThrowable>[] thrown() { return null; } @Override public boolean isRestricted() { return true; } @Override public Boolean runAsync() { return null; } @Override public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { return CVoid.VOID; } @Override public String getName() { return "compile_error"; } @Override public Integer[] numArgs() { return new Integer[]{1}; } @Override public String docs() { return "nothing {message} Throws a compile error unconditionally at link time, if the function has not been fully compiled" + " out with preprocessor directives. This is useful for causing a custom compile error if certain compilation environment" + " settings are not correct."; } @Override public Version since() { return CHVersion.V3_3_1; } @Override public Set<OptimizationOption> optimizationOptions() { return EnumSet.of(OptimizationOption.CUSTOM_LINK, OptimizationOption.OPTIMIZE_DYNAMIC); } @Override public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { if (children.isEmpty()) { throw new CREFormatException(this.getName() + " expects at least 1 argument.", t); } if (!children.get(0).isConst()) { throw new ConfigCompileException(getName() + "'s argument must be a hardcoded string.", t); } return null; } @Override public void link(Target t, List<ParseTree> children) throws ConfigCompileException { if (children.isEmpty()) { throw new CREFormatException(this.getName() + " expects at least 1 argument.", t); } throw new ConfigCompileException(children.get(0).getData().val(), t); } } }