From 75adf321c9e0ff7dc4136350fa163fd2893adfc1 Mon Sep 17 00:00:00 2001 From: LadyCailin Date: Thu, 4 Aug 2016 17:17:07 +0200 Subject: [PATCH] Added ||| and &&& (dor and dand) --- .../core/MethodScriptCompiler.java | 36 +- .../laytonsmith/core/constructs/CSymbol.java | 18 +- .../laytonsmith/core/constructs/Token.java | 521 +- .../core/functions/BasicLogic.java | 4896 +++++++++-------- .../laytonsmith/core/functions/Compiler.java | 30 + .../laytonsmith/core/OptimizationTest.java | 46 +- .../core/functions/BasicLogicTest.java | 17 +- 7 files changed, 2964 insertions(+), 2600 deletions(-) diff --git a/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java b/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java index a433ca721..b5938940e 100644 --- a/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java +++ b/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java @@ -403,6 +403,24 @@ public static List lex(String script, File file, boolean inPureMScript) t i++; continue; } + if (c == '&' && c2 == '&' && c3 == '&' && !state_in_quote) { + if(buf.length() > 0) { + token_list.add(new Token(TType.UNKNOWN, buf.toString(), target)); + buf = new StringBuilder(); + } + token_list.add(new Token(TType.DEFAULT_AND, "&&&", target)); + i++; i++; + continue; + } + if (c == '|' && c2 == '|' && c3 == '|' && !state_in_quote) { + if(buf.length() > 0) { + token_list.add(new Token(TType.UNKNOWN, buf.toString(), target)); + buf = new StringBuilder(); + } + token_list.add(new Token(TType.DEFAULT_OR, "|||", target)); + i++; i++; + continue; + } if (c == '&' && c2 == '&' && !state_in_quote) { if (buf.length() > 0) { token_list.add(new Token(TType.UNKNOWN, buf.toString(), target)); @@ -731,7 +749,7 @@ public static List lex(String script, File file, boolean inPureMScript) t if (t.type == TType.UNKNOWN && prev1.type.isPlusMinus() && !prev2.type.isIdentifier() && !prev2.type.equals(TType.FUNC_END) - && !IVAR_PATTERN.matcher(t.val()).matches() + && !IVAR_PATTERN.matcher(t.val()).matches() && !VAR_PATTERN.matcher(t.val()).matches()) { // Last boolean makes -@b equal to - @b, instead of a string. //It is a negative/positive number. Absorb the sign t.value = prev1.value + t.value; @@ -954,10 +972,10 @@ public static ParseTree compile(List stream) throws ConfigCompileExceptio */ Stack arrayStack = new Stack<>(); arrayStack.add(new AtomicInteger(-1)); - + Stack minusArrayStack = new Stack<>(); Stack minusFuncStack = new Stack<>(); - + int parens = 0; Token t = null; @@ -1071,7 +1089,7 @@ public static ParseTree compile(List stream) throws ConfigCompileExceptio ParseTree arrayGet = new ParseTree(new CFunction("array_get", t.target), fileOptions); arrayGet.addChild(myArray); arrayGet.addChild(myIndex); - + // Check if the @var[...] had a negating "-" in front. If so, add a neg(). if (minusArrayStack.size() != 0 && arrayStack.size() + 1 == minusArrayStack.peek().get()) { if (!next1.type.equals(TType.LSQUARE_BRACKET)) { // Wait if there are more array_get's comming. @@ -1162,7 +1180,7 @@ public static ParseTree compile(List stream) throws ConfigCompileExceptio } catch (EmptyStackException e) { throw new ConfigCompileException("Unexpected end parenthesis", t.target); } - + // Handle "-func(args)" and "-func(args)[index]". if (minusFuncStack.size() != 0 && minusFuncStack.peek().get() == parens + 1) { if(next1.type.equals(TType.LSQUARE_BRACKET)) { @@ -1177,7 +1195,7 @@ public static ParseTree compile(List stream) throws ConfigCompileExceptio } minusFuncStack.pop(); } - + } else if (t.type.equals(TType.COMMA)) { if (constructCount.peek().get() > 1) { int stacks = constructCount.peek().get(); @@ -1314,14 +1332,14 @@ public static ParseTree compile(List stream) throws ConfigCompileExceptio tree.addChild(new ParseTree(Static.resolveConstruct(t.val(), t.target), fileOptions)); constructCount.peek().incrementAndGet(); } else if (t.type.isSymbol()) { //Logic and math symbols - + // Attempt to find "-@var" and change it to "neg(@var)" if it's not @a - @b. Else just add the symbol. // Also handles "-function()" and "-@var[index]". if (t.type.equals(TType.MINUS) && !prev1.type.isAtomicLit() && !prev1.type.equals(TType.IVARIABLE) && !prev1.type.equals(TType.VARIABLE) && !prev1.type.equals(TType.RCURLY_BRACKET) && !prev1.type.equals(TType.RSQUARE_BRACKET) && !prev1.type.equals(TType.FUNC_END) && (next1.type.equals(TType.IVARIABLE) || next1.type.equals(TType.VARIABLE) || next1.type.equals(TType.FUNC_NAME))) { - + // Check if we are negating a value from an array, function or variable. if (next2.type.equals(TType.LSQUARE_BRACKET)) { minusArrayStack.push(new AtomicInteger(arrayStack.size() + 1)); // +1 because the bracket isn't counted yet. @@ -1338,7 +1356,7 @@ public static ParseTree compile(List stream) throws ConfigCompileExceptio tree.addChild(new ParseTree(new CSymbol(t.val(), t.type, t.target), fileOptions)); constructCount.peek().incrementAndGet(); } - + } else if (t.type == TType.DOT){ // Check for doubles that start with a decimal, otherwise concat Construct c = null; diff --git a/src/main/java/com/laytonsmith/core/constructs/CSymbol.java b/src/main/java/com/laytonsmith/core/constructs/CSymbol.java index 10b26c503..c90dbe292 100644 --- a/src/main/java/com/laytonsmith/core/constructs/CSymbol.java +++ b/src/main/java/com/laytonsmith/core/constructs/CSymbol.java @@ -1,8 +1,10 @@ package com.laytonsmith.core.constructs; +import com.laytonsmith.annotations.breakable; + /** * - * + * */ public class CSymbol extends Construct { @@ -58,6 +60,12 @@ public CSymbol(String symbol, Token.TType type, Target target) { case LOGICAL_OR: conversion = "or"; break; + case DEFAULT_AND: + conversion = "dand"; + break; + case DEFAULT_OR: + conversion = "dor"; + break; case LOGICAL_NOT: conversion = "not"; break; @@ -136,6 +144,14 @@ public boolean isLogicalOr() { return symbolType.isLogicalOr(); } + public boolean isDefaultAnd() { + return symbolType.isDefaultAnd(); + } + + public boolean isDefaultOr() { + return symbolType.isDefaultOr(); + } + @Override public boolean isDynamic() { //It gets turned into a function, but only after the __autoconcat__ features take over, diff --git a/src/main/java/com/laytonsmith/core/constructs/Token.java b/src/main/java/com/laytonsmith/core/constructs/Token.java index 52bf0d926..a1e0af5be 100644 --- a/src/main/java/com/laytonsmith/core/constructs/Token.java +++ b/src/main/java/com/laytonsmith/core/constructs/Token.java @@ -1,5 +1,3 @@ - - package com.laytonsmith.core.constructs; import java.io.File; @@ -20,141 +18,147 @@ public class Token { public final File file; public final Target target; - private enum TokenVariant{ - ADDITIVE, EQUALITY, EXPONENTIAL, IDENTIFIER, LOGICAL_AND, LOGICAL_OR, - MULTIPLICATIVE, PLUS_MINUS, POSTFIX, RELATIONAL, SYMBOL, UNARY, ASSIGNMENT, - SEPARATOR, ATOMIC_LIT, WHITESPACE, KEYWORD - } + private enum TokenVariant { + ADDITIVE, EQUALITY, EXPONENTIAL, IDENTIFIER, LOGICAL_AND, LOGICAL_OR, DEFAULT_AND, DEFAULT_OR, + MULTIPLICATIVE, PLUS_MINUS, POSTFIX, RELATIONAL, SYMBOL, UNARY, ASSIGNMENT, + SEPARATOR, ATOMIC_LIT, WHITESPACE, KEYWORD + } + public enum TType { - //TODO: Comment this out once the compiler is replaced, and clean up unused ones - UNKNOWN(TokenVariant.IDENTIFIER), - LSQUARE_BRACKET(TokenVariant.SEPARATOR), - RSQUARE_BRACKET(TokenVariant.SEPARATOR), - OPT_VAR_ASSIGN(TokenVariant.SEPARATOR), - ALIAS_END(TokenVariant.SEPARATOR), - COMMA(TokenVariant.SEPARATOR), - FUNC_NAME(), - FUNC_START(TokenVariant.SEPARATOR), - FUNC_END(TokenVariant.SEPARATOR), - NEWLINE(TokenVariant.WHITESPACE), - MULTILINE_START(TokenVariant.SEPARATOR), - MULTILINE_END(TokenVariant.SEPARATOR), - COMMAND(), - SEPERATOR(TokenVariant.SEPARATOR), - STRING(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), - VARIABLE(TokenVariant.IDENTIFIER), - IVARIABLE(TokenVariant.IDENTIFIER), - FINAL_VAR(TokenVariant.IDENTIFIER), - LIT(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), - DOT(TokenVariant.IDENTIFIER), - SMART_STRING(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), - BARE_STRING(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), - ROOT(), - LABEL(TokenVariant.SEPARATOR), - DEREFERENCE(), - SLICE(), - - PLUS(TokenVariant.SYMBOL, TokenVariant.UNARY, TokenVariant.ADDITIVE, TokenVariant.PLUS_MINUS), - MINUS(TokenVariant.SYMBOL, TokenVariant.UNARY, TokenVariant.ADDITIVE, TokenVariant.PLUS_MINUS), - MULTIPLICATION(TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), - DIVISION(TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), - EQUALS(TokenVariant.SYMBOL, TokenVariant.EQUALITY), - NOT_EQUALS(TokenVariant.SYMBOL, TokenVariant.EQUALITY), - STRICT_EQUALS(TokenVariant.SYMBOL, TokenVariant.EQUALITY), - STRICT_NOT_EQUALS(TokenVariant.SYMBOL, TokenVariant.EQUALITY), - - GT(TokenVariant.SYMBOL, TokenVariant.RELATIONAL), - LT(TokenVariant.SYMBOL, TokenVariant.RELATIONAL), - LTE(TokenVariant.SYMBOL, TokenVariant.RELATIONAL), - GTE(TokenVariant.SYMBOL, TokenVariant.RELATIONAL), - LOGICAL_AND(TokenVariant.SYMBOL, TokenVariant.LOGICAL_AND), - LOGICAL_OR(TokenVariant.SYMBOL, TokenVariant.LOGICAL_OR), - LOGICAL_NOT(TokenVariant.SYMBOL, TokenVariant.UNARY), - INCREMENT(TokenVariant.SYMBOL, TokenVariant.POSTFIX, TokenVariant.UNARY), - DECREMENT(TokenVariant.SYMBOL, TokenVariant.POSTFIX, TokenVariant.UNARY), - MODULO(TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), - CONCAT(TokenVariant.SYMBOL, TokenVariant.ADDITIVE), - EXPONENTIAL(TokenVariant.SYMBOL, TokenVariant.EXPONENTIAL), - - WHITESPACE(TokenVariant.WHITESPACE), - LCURLY_BRACKET(TokenVariant.SEPARATOR), - RCURLY_BRACKET(TokenVariant.SEPARATOR), - IDENTIFIER(), - - DOUBLE(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), - INTEGER(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), - CONST_START(), - ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL), - PLUS_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.ADDITIVE, TokenVariant.PLUS_MINUS), - MINUS_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.ADDITIVE, TokenVariant.PLUS_MINUS), - MULTIPLICATION_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), - DIVISION_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), - CONCAT_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.ADDITIVE), - SEMICOLON(TokenVariant.SEPARATOR), - KEYWORD(TokenVariant.KEYWORD); - - private Set variants = EnumSet.noneOf(TokenVariant.class); - private TType(TokenVariant ... variants){ - this.variants.addAll(Arrays.asList(variants)); - } - - /** - * True if the token is a symbol, i.e. + - = - * @return - */ - public boolean isSymbol() { - return this.variants.contains(TokenVariant.SYMBOL); - } - - /** - * Returns true if the token is a postfix operator. This implies it is a - * prefix operator as well. -- ++ - * @return - */ - public boolean isPostfix(){ - return this.variants.contains(TokenVariant.POSTFIX); - } - - /** - * Returns true if this symbol is a unary operator, ! - + -- ++ - * @return - */ - public boolean isUnary() { - return this.variants.contains(TokenVariant.UNARY); - } - - /** - * Returns true if this symbol is multiplicative - * @return - */ - public boolean isMultaplicative() { - return this.variants.contains(TokenVariant.MULTIPLICATIVE); - } - - /** - * Returns true if this symbol is additive + += . etc - * @return - */ - public boolean isAdditive(){ - return this.variants.contains(TokenVariant.ADDITIVE); - } - - /** - * Returns true if this symbol is relational, < > - * @return - */ - public boolean isRelational(){ - return this.variants.contains(TokenVariant.RELATIONAL); - } - - /** - * Returns true if this symbol is equalitative, === == != !== - * @return - */ - public boolean isEquality(){ - return this.variants.contains(TokenVariant.EQUALITY); - } + //TODO: Comment this out once the compiler is replaced, and clean up unused ones + UNKNOWN(TokenVariant.IDENTIFIER), + LSQUARE_BRACKET(TokenVariant.SEPARATOR), + RSQUARE_BRACKET(TokenVariant.SEPARATOR), + OPT_VAR_ASSIGN(TokenVariant.SEPARATOR), + ALIAS_END(TokenVariant.SEPARATOR), + COMMA(TokenVariant.SEPARATOR), + FUNC_NAME(), + FUNC_START(TokenVariant.SEPARATOR), + FUNC_END(TokenVariant.SEPARATOR), + NEWLINE(TokenVariant.WHITESPACE), + MULTILINE_START(TokenVariant.SEPARATOR), + MULTILINE_END(TokenVariant.SEPARATOR), + COMMAND(), + SEPERATOR(TokenVariant.SEPARATOR), + STRING(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), + VARIABLE(TokenVariant.IDENTIFIER), + IVARIABLE(TokenVariant.IDENTIFIER), + FINAL_VAR(TokenVariant.IDENTIFIER), + LIT(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), + DOT(TokenVariant.IDENTIFIER), + SMART_STRING(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), + BARE_STRING(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), + ROOT(), + LABEL(TokenVariant.SEPARATOR), + DEREFERENCE(), + SLICE(), + PLUS(TokenVariant.SYMBOL, TokenVariant.UNARY, TokenVariant.ADDITIVE, TokenVariant.PLUS_MINUS), + MINUS(TokenVariant.SYMBOL, TokenVariant.UNARY, TokenVariant.ADDITIVE, TokenVariant.PLUS_MINUS), + MULTIPLICATION(TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), + DIVISION(TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), + EQUALS(TokenVariant.SYMBOL, TokenVariant.EQUALITY), + NOT_EQUALS(TokenVariant.SYMBOL, TokenVariant.EQUALITY), + STRICT_EQUALS(TokenVariant.SYMBOL, TokenVariant.EQUALITY), + STRICT_NOT_EQUALS(TokenVariant.SYMBOL, TokenVariant.EQUALITY), + GT(TokenVariant.SYMBOL, TokenVariant.RELATIONAL), + LT(TokenVariant.SYMBOL, TokenVariant.RELATIONAL), + LTE(TokenVariant.SYMBOL, TokenVariant.RELATIONAL), + GTE(TokenVariant.SYMBOL, TokenVariant.RELATIONAL), + LOGICAL_AND(TokenVariant.SYMBOL, TokenVariant.LOGICAL_AND), + LOGICAL_OR(TokenVariant.SYMBOL, TokenVariant.LOGICAL_OR), + DEFAULT_AND(TokenVariant.SYMBOL, TokenVariant.DEFAULT_AND), + DEFAULT_OR(TokenVariant.SYMBOL, TokenVariant.DEFAULT_OR), + LOGICAL_NOT(TokenVariant.SYMBOL, TokenVariant.UNARY), + INCREMENT(TokenVariant.SYMBOL, TokenVariant.POSTFIX, TokenVariant.UNARY), + DECREMENT(TokenVariant.SYMBOL, TokenVariant.POSTFIX, TokenVariant.UNARY), + MODULO(TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), + CONCAT(TokenVariant.SYMBOL, TokenVariant.ADDITIVE), + EXPONENTIAL(TokenVariant.SYMBOL, TokenVariant.EXPONENTIAL), + WHITESPACE(TokenVariant.WHITESPACE), + LCURLY_BRACKET(TokenVariant.SEPARATOR), + RCURLY_BRACKET(TokenVariant.SEPARATOR), + IDENTIFIER(), + DOUBLE(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), + INTEGER(TokenVariant.IDENTIFIER, TokenVariant.ATOMIC_LIT), + CONST_START(), + ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL), + PLUS_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.ADDITIVE, TokenVariant.PLUS_MINUS), + MINUS_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.ADDITIVE, TokenVariant.PLUS_MINUS), + MULTIPLICATION_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), + DIVISION_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.MULTIPLICATIVE), + CONCAT_ASSIGNMENT(TokenVariant.ASSIGNMENT, TokenVariant.SYMBOL, TokenVariant.ADDITIVE), + SEMICOLON(TokenVariant.SEPARATOR), + KEYWORD(TokenVariant.KEYWORD); + + private final Set variants = EnumSet.noneOf(TokenVariant.class); + + private TType(TokenVariant... variants) { + this.variants.addAll(Arrays.asList(variants)); + } + + /** + * True if the token is a symbol, i.e. + - = + * + * @return + */ + public boolean isSymbol() { + return this.variants.contains(TokenVariant.SYMBOL); + } + + /** + * Returns true if the token is a postfix operator. This implies it is a prefix operator as well. -- ++ + * + * @return + */ + public boolean isPostfix() { + return this.variants.contains(TokenVariant.POSTFIX); + } + + /** + * Returns true if this symbol is a unary operator, ! - + -- ++ + * + * @return + */ + public boolean isUnary() { + return this.variants.contains(TokenVariant.UNARY); + } + + /** + * Returns true if this symbol is multiplicative + * + * @return + */ + public boolean isMultaplicative() { + return this.variants.contains(TokenVariant.MULTIPLICATIVE); + } + + /** + * Returns true if this symbol is additive + += . etc + * + * @return + */ + public boolean isAdditive() { + return this.variants.contains(TokenVariant.ADDITIVE); + } + + /** + * Returns true if this symbol is relational, < > + * + * @return + */ + public boolean isRelational() { + return this.variants.contains(TokenVariant.RELATIONAL); + } + + /** + * Returns true if this symbol is equalitative, === == != !== + * + * @return + */ + public boolean isEquality() { + return this.variants.contains(TokenVariant.EQUALITY); + } // public boolean isBitwiseAnd(){ // return (this == BIT_AND); @@ -167,145 +171,168 @@ public boolean isEquality(){ // public boolean isBitwiseOr(){ // return (this == BIT_OR); // } + /** + * Returns true if this is a logical and + * + * @return + */ + public boolean isLogicalAnd() { + return this.variants.contains(TokenVariant.LOGICAL_AND); + } - /** - * Returns true if this is a logical and - * @return - */ - public boolean isLogicalAnd(){ - return this.variants.contains(TokenVariant.LOGICAL_AND); - } - - /** - * Return true if this is a logical or - * @return - */ - public boolean isLogicalOr(){ - return this.variants.contains(TokenVariant.LOGICAL_OR); - } - - /** - * Returns true if this is a plus/minus operator + - += -= - * @return - */ - public boolean isPlusMinus() { - return this.variants.contains(TokenVariant.PLUS_MINUS); - } - - /** - * Returns true if this is an identifier. Variables, strings, bare strings, - * integers, doubles, etc. Unknown tokens are assumed to be identifiers. - * @return - */ - public boolean isIdentifier() { - return this.variants.contains(TokenVariant.IDENTIFIER); - } - - /** - * Returns true if this is an exponential symbol ** - * @return - */ - public boolean isExponential() { - return this.variants.contains(TokenVariant.EXPONENTIAL); - } - - /** - * Returns true if this is an assigment operator = += etc - * @return - */ - public boolean isAssignment(){ - return this.variants.contains(TokenVariant.ASSIGNMENT); - } - - /** - * Returns true if this is a separator, that is, it ends a logical - * statement. [ ] { } ( ) , ; etc - * @return - */ - public boolean isSeparator(){ - return this.variants.contains(TokenVariant.SEPARATOR); - } - - /** - * Returns true if this is an atomic literal, that is a string - * (including bare strings), integer, or double only. Variables - * are not included. - * @return - */ - public boolean isAtomicLit(){ - return this.variants.contains(TokenVariant.ATOMIC_LIT); - } - - /** - * Returns true if this is a whitespace token, i.e. space, tab, or newline. - * @return - */ - public boolean isWhitespace(){ - return this.variants.contains(TokenVariant.WHITESPACE); - } - - public boolean isKeyword(){ - return this.variants.contains(TokenVariant.KEYWORD); - } + /** + * Returns true if this is a default and + * @return + */ + public boolean isDefaultAnd() { + return this.variants.contains(TokenVariant.DEFAULT_AND); + } + + /** + * Return true if this is a logical or + * + * @return + */ + public boolean isLogicalOr() { + return this.variants.contains(TokenVariant.LOGICAL_OR); + } + + /** + * Returns true if this is a default or + * @return + */ + public boolean isDefaultOr() { + return this.variants.contains(TokenVariant.DEFAULT_OR); + } + + /** + * Returns true if this is a plus/minus operator + - += -= + * + * @return + */ + public boolean isPlusMinus() { + return this.variants.contains(TokenVariant.PLUS_MINUS); + } + + /** + * Returns true if this is an identifier. Variables, strings, bare strings, integers, doubles, etc. Unknown + * tokens are assumed to be identifiers. + * + * @return + */ + public boolean isIdentifier() { + return this.variants.contains(TokenVariant.IDENTIFIER); + } + + /** + * Returns true if this is an exponential symbol + * + ** + * @return + */ + public boolean isExponential() { + return this.variants.contains(TokenVariant.EXPONENTIAL); + } + + /** + * Returns true if this is an assigment operator = += etc + * + * @return + */ + public boolean isAssignment() { + return this.variants.contains(TokenVariant.ASSIGNMENT); + } + + /** + * Returns true if this is a separator, that is, it ends a logical statement. [ ] { } ( ) , ; etc + * + * @return + */ + public boolean isSeparator() { + return this.variants.contains(TokenVariant.SEPARATOR); + } + + /** + * Returns true if this is an atomic literal, that is a string (including bare strings), integer, or double + * only. Variables are not included. + * + * @return + */ + public boolean isAtomicLit() { + return this.variants.contains(TokenVariant.ATOMIC_LIT); + } + + /** + * Returns true if this is a whitespace token, i.e. space, tab, or newline. + * + * @return + */ + public boolean isWhitespace() { + return this.variants.contains(TokenVariant.WHITESPACE); + } + + public boolean isKeyword() { + return this.variants.contains(TokenVariant.KEYWORD); + } } public Token(TType type, String value, Target t) { - this.type = type; - this.value = value; - this.line_num = t.line(); - this.file = t.file(); - this.column = t.col(); - this.target = t; + this.type = type; + this.value = value; + this.line_num = t.line(); + this.file = t.file(); + this.column = t.col(); + this.target = t; } public String val() { - return value; + return value; } @Override public int hashCode() { - int hash = 7; - hash = 59 * hash + (this.type != null ? this.type.hashCode() : 0); - hash = 59 * hash + (this.value != null ? this.value.hashCode() : 0); - return hash; + int hash = 7; + hash = 59 * hash + (this.type != null ? this.type.hashCode() : 0); + hash = 59 * hash + (this.value != null ? this.value.hashCode() : 0); + return hash; } @Override public boolean equals(Object o) { - if (o instanceof Token) { - Token t = (Token) o; - return (this.type.equals(t.type) && this.value.equals(t.value)); - } - return false; + if (o instanceof Token) { + Token t = (Token) o; + return (this.type.equals(t.type) && this.value.equals(t.value)); + } + return false; } @Override public String toString() { - if (type.equals(TType.NEWLINE)) { - return "newline"; - } - if (type.equals(TType.STRING)) { - return "'" + value + "'"; - } - return type + ":" + value; + if (type.equals(TType.NEWLINE)) { + return "newline"; + } + if (type.equals(TType.STRING)) { + return "'" + value + "'"; + } + return type + ":" + value; } public String toSimpleString() { - if (type.equals(TType.STRING)) { - return "'" + value + "'"; - } - return value; + if (type.equals(TType.STRING)) { + return "'" + value + "'"; + } + return value; } public String toOutputString() { - if (type.equals(TType.STRING)) { - return value.replace("\\", "\\\\").replace("'", "\\'"); - } - return value; + if (type.equals(TType.STRING)) { + return value.replace("\\", "\\\\").replace("'", "\\'"); + } + return value; } - public Target getTarget(){ - return target; + public Target getTarget() { + return target; } } diff --git a/src/main/java/com/laytonsmith/core/functions/BasicLogic.java b/src/main/java/com/laytonsmith/core/functions/BasicLogic.java index 0210eeef1..180f85ee4 100644 --- a/src/main/java/com/laytonsmith/core/functions/BasicLogic.java +++ b/src/main/java/com/laytonsmith/core/functions/BasicLogic.java @@ -50,1731 +50,1972 @@ @core public class BasicLogic { - public static String docs() { - return "These functions provide basic logical operations."; + 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"; } - @api - public static class _if extends AbstractFunction implements Optimizable { + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public String getName() { - return "if"; - } + @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 Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + return CVoid.VOID; + } - @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]; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - 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 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 Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - return CVoid.VOID; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public boolean preResolveVariables() { + return false; + } - @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 CHVersion since() { + return CHVersion.V3_0_1; + } + //Doesn't matter, this function is run out of state - @Override - public boolean isRestricted() { - return false; - } + @Override + public Boolean runAsync() { + return false; + } - @Override - public boolean preResolveVariables() { - return false; - } + @Override + public boolean useSpecialExec() { + return true; + } - @Override - public CHVersion since() { - return CHVersion.V3_0_1; - } - //Doesn't matter, this function is run out of state + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.OPTIMIZE_DYNAMIC + ); + } - @Override - public Boolean runAsync() { - return false; - } + private static final String and = new and().getName(); + + @Override + public ParseTree optimizeDynamic(Target t, List args, FileOptions fileOptions) throws ConfigCompileException { + //Check for too many/too few arguments + if (args.size() == 1) { + throw new ConfigCompileException("Incorrect number of 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 boolean useSpecialExec() { - return true; - } + @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')"),}; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.OPTIMIZE_DYNAMIC - ); - } + } - private static final String and = new and().getName(); + @api(environments = {GlobalEnv.class}) + public static class ifelse extends AbstractFunction implements Optimizable { - @Override - public ParseTree optimizeDynamic(Target t, List args, FileOptions fileOptions) throws ConfigCompileException { - //Check for too many/too few arguments - if (args.size() == 1) { - throw new ConfigCompileException("Incorrect number of 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 String getName() { + return "ifelse"; + } - @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')"),}; - } + @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."; } - @api(environments = {GlobalEnv.class}) - public static class ifelse extends AbstractFunction implements Optimizable { + @Override + public Class[] thrown() { + return new Class[]{CREInsufficientArgumentsException.class}; + } - @Override - public String getName() { - return "ifelse"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public boolean preResolveVariables() { + return false; + } - @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 CHVersion since() { + return CHVersion.V3_3_0; + } - @Override - public Class[] thrown() { - return new Class[]{CREInsufficientArgumentsException.class}; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + return CNull.NULL; + } - @Override - public boolean preResolveVariables() { - return false; - } + @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 CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public boolean useSpecialExec() { + return true; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + Optimizable.OptimizationOption.OPTIMIZE_DYNAMIC + ); + } - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return CNull.NULL; - } + private static final String g = new DataHandling.g().getName(); - @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); + @Override + public ParseTree optimizeDynamic(Target t, List 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[] 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); + } } - 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); + } 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); + } } - if (Static.getBoolean(evalStatement)) { - Construct ret = env.getEnv(GlobalEnv.class).GetScript().eval(code, env); - return ret; + } 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 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 newChildren = new ArrayList<>(); + newChildren.add(children.get(0)); //Initial child + List c = children.get(1).getChildren(); + List 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 values = new TreeSet<>(new Comparator() { + + @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()); } - 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; + 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 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); } - return CVoid.VOID; - } + } + } 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 boolean useSpecialExec() { - return true; - } + @Override + public Set 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 Set optimizationOptions() { - return EnumSet.of( - Optimizable.OptimizationOption.OPTIMIZE_DYNAMIC - ); - } + @Override + public String getName() { + return "equals"; + } - private static final String g = new DataHandling.g().getName(); + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - // TODO: Redo this optimization. - return null; + @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 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}"),}; - } + @Override + public Class[] thrown() { + return new Class[]{CREInsufficientArgumentsException.class}; } - @api - @breakable - public static class _switch extends AbstractFunction implements Optimizable { + @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 String getName() { - return "switch"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public CHVersion since() { + return CHVersion.V3_0_1; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Class[] thrown() { - return new Class[]{CREInsufficientArgumentsException.class}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public boolean isRestricted() { - return false; - } + @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'"),}; + } + } - @Override - public boolean preResolveVariables() { - return false; - } + @api + @seealso({equals.class, nequals.class, snequals.class}) + public static class sequals extends AbstractFunction implements Optimizable { - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public String getName() { + return "sequals"; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return CNull.NULL; - } + @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 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 Class[] thrown() { + return new Class[]{}; + } - @Override - public boolean useSpecialExec() { - return true; - } + @Override + public boolean isRestricted() { + return false; + } - @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 CHVersion since() { + return CHVersion.V3_3_0; + } - @Override - public ParseTree optimizeDynamic(Target t, List 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 newChildren = new ArrayList<>(); - newChildren.add(children.get(0)); //Initial child - List c = children.get(1).getChildren(); - List 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); - } + @Override + public Boolean runAsync() { + return null; + } - //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 values = new TreeSet<>(new Comparator() { - - @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 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); - } - } + @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; + } + } - 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 optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.PRIORITY_OPTIMIZATION); - } + @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({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(); - } + @api + @seealso({sequals.class}) + public static class snequals extends AbstractFunction implements Optimizable { - @Override - public String getName() { - return "equals"; - } + @Override + public String getName() { + return "snequals"; + } - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @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); - } + @Override + public String docs() { + return "boolean {val1, val2} Equivalent to not(sequals(val1, val2)). Operator syntax" + + " is also supported: @a !== @b"; + } + @Override + public Class[] thrown() { + return null; + } - { - 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 boolean isRestricted() { + return false; + } - @Override - public Class[] thrown() { - return new Class[]{CREInsufficientArgumentsException.class}; - } + @Override + public Boolean runAsync() { + return null; + } - @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 Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + return new sequals().exec(t, environment, args).not(); + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public CHVersion since() { - return CHVersion.V3_0_1; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public Boolean runAsync() { - return null; - } + @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"),}; + } + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @api + @seealso({equals.class, sequals.class, snequals.class}) + public static class nequals extends AbstractFunction implements Optimizable { - @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'"),}; - } + @Override + public String getName() { + return "nequals"; } - @api - @seealso({equals.class, nequals.class, snequals.class}) - public static class sequals extends AbstractFunction implements Optimizable { + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public String getName() { - return "sequals"; - } + @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 Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public Class[] thrown() { + return new Class[]{}; + } - @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 boolean isRestricted() { + return false; + } - @Override - public Class[] thrown() { - return new Class[]{}; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public CBoolean exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + return new equals().exec(t, env, args).not(); + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @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 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"),}; + } + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @api + public static class equals_ic extends AbstractFunction implements Optimizable { - @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'"),}; - } + @Override + public String getName() { + return "equals_ic"; } - @api - @seealso({sequals.class}) - public static class snequals extends AbstractFunction implements Optimizable { + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public String getName() { - return "snequals"; - } + @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 Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public Class[] thrown() { + return new Class[]{CREInsufficientArgumentsException.class}; + } - @Override - public String docs() { - return "boolean {val1, val2} Equivalent to not(sequals(val1, val2)). Operator syntax" - + " is also supported: @a !== @b"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Class[] thrown() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_2_0; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Boolean runAsync() { + return null; + } - @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 Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return new sequals().exec(t, environment, args).not(); - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } + @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')"),}; + } + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @api + public static class sequals_ic extends AbstractFunction implements Optimizable { - @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"), - }; - } + @Override + public Class[] thrown() { + return null; } - @api - @seealso({equals.class, sequals.class, snequals.class}) - public static class nequals extends AbstractFunction implements Optimizable { + @Override + public boolean isRestricted() { + return false; + } - @Override - public String getName() { - return "nequals"; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @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 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 String getName() { + return "sequals_ic"; + } - @Override - public Class[] thrown() { - return new Class[]{}; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public CBoolean exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - return new equals().exec(t, env, args).not(); - } + @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)") + }; + } - @Override - public Set 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 + @seealso({equals_ic.class}) + public static class nequals_ic extends AbstractFunction implements Optimizable { + + @Override + public String getName() { + return "nequals_ic"; } - @api - public static class equals_ic extends AbstractFunction implements Optimizable { + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public String getName() { - return "equals_ic"; - } + @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 Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public Class[] thrown() { + return new Class[]{}; + } - @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 boolean isRestricted() { + return false; + } - @Override - public Class[] thrown() { - return new Class[]{CREInsufficientArgumentsException.class}; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public CHVersion since() { - return CHVersion.V3_2_0; - } + @Override + public CBoolean exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + return new equals_ic().exec(t, environment, args).not(); + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Set 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')"),}; + } + } - @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); - } + @api + public static class ref_equals extends AbstractFunction { - { - 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 Class[] thrown() { + return new Class[]{}; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @Override + public boolean isRestricted() { + return false; + } - @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')"),}; - } + @Override + public Boolean runAsync() { + return null; } - @api - public static class sequals_ic extends AbstractFunction implements Optimizable { + @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 Class[] thrown() { - return null; - } + @Override + public String getName() { + return "ref_equals"; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Boolean runAsync() { - return null; - } + @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 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 CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public String getName() { - return "sequals_ic"; - } + @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"),}; + } - @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."; - } + @api + @seealso({gt.class, lte.class, gte.class}) + public static class lt extends AbstractFunction implements Optimizable { - @Override - public Version since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "lt"; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @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)") - }; - } + @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[] thrown() { + return new Class[]{CRECastException.class}; } - @api - @seealso({equals_ic.class}) - public static class nequals_ic extends AbstractFunction implements Optimizable { + @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 String getName() { - return "nequals_ic"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public CHVersion since() { + return CHVersion.V3_0_1; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Class[] thrown() { - return new Class[]{}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public boolean isRestricted() { - return false; - } + @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"),}; + } + } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @api + @seealso({lt.class, lte.class, gte.class}) + public static class gt extends AbstractFunction implements Optimizable { - @Override - public Boolean runAsync() { - return null; - } + @Override + public String getName() { + return "gt"; + } - @Override - public CBoolean exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return new equals_ic().exec(t, environment, args).not(); - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @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 ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "equals_ic('test', 'TEST')"), - new ExampleScript("Basic usage", "equals_ic('completely', 'DIFFERENT')"),}; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; } - @api - public static class ref_equals extends AbstractFunction { + @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 Class[] thrown() { - return new Class[]{}; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public CHVersion since() { + return CHVersion.V3_0_1; + } - @Override - public Boolean runAsync() { - return null; - } + @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 Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public String getName() { - return "ref_equals"; - } + @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"),}; + } + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @api + @seealso({lt.class, gt.class, gte.class}) + public static class lte extends AbstractFunction implements Optimizable { - @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 String getName() { + return "lte"; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @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"),}; - } + @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[] thrown() { + return new Class[]{CRECastException.class}; } - @api - @seealso({gt.class, lte.class, gte.class}) - public static class lt extends AbstractFunction implements Optimizable { + @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 String getName() { - return "lt"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public CHVersion since() { + return CHVersion.V3_0_1; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @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 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"),}; + } + } - @Override - public boolean isRestricted() { - return false; - } + @api + @seealso({lt.class, gt.class, lte.class}) + public static class gte extends AbstractFunction implements Optimizable { - @Override - public CHVersion since() { - return CHVersion.V3_0_1; - } + @Override + public String getName() { + return "gte"; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @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 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"),}; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; } - @api - @seealso({lt.class, lte.class, gte.class}) - public static class gt extends AbstractFunction implements Optimizable { + @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 String getName() { - return "gt"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public CHVersion since() { + return CHVersion.V3_0_1; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @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 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"),}; + } + } - @Override - public boolean isRestricted() { - return false; - } + @api(environments = {GlobalEnv.class}) + @seealso({or.class}) + public static class and extends AbstractFunction implements Optimizable { - @Override - public CHVersion since() { - return CHVersion.V3_0_1; - } + @Override + public String getName() { + return "and"; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); + @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 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"),}; + @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; } - @api - @seealso({lt.class, gt.class, gte.class}) - public static class lte extends AbstractFunction implements Optimizable { + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public String getName() { - return "lte"; - } + @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 Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public boolean isRestricted() { + return false; + } - @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 CHVersion since() { + return CHVersion.V3_0_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public Boolean runAsync() { + return null; + } - @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 useSpecialExec() { + return true; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + OptimizationUtilities.pullUpLikeFunctions(children, getName()); + Iterator 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 CHVersion since() { - return CHVersion.V3_0_1; - } + @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 Boolean runAsync() { - return null; - } + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); + } + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @api + public static class dand extends AbstractFunction implements Optimizable { - @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"),}; - } + @Override + public Class[] thrown() { + return new Class[]{}; } - @api - @seealso({lt.class, gt.class, lte.class}) - public static class gte extends AbstractFunction implements Optimizable { + @Override + public boolean isRestricted() { + return false; + } - @Override - public String getName() { - return "gte"; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public boolean useSpecialExec() { + return true; + } - @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 Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + return CVoid.VOID; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; + @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 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 ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + OptimizationUtilities.pullUpLikeFunctions(children, getName()); + Iterator 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 boolean isRestricted() { - return false; - } + @Override + public String getName() { + return "dand"; + } - @Override - public CHVersion since() { - return CHVersion.V3_0_1; - } + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public Boolean runAsync() { - return null; - } + @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 Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @Override + public Version since() { + return CHVersion.V3_3_2; + } - @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"),}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); } - @api(environments = {GlobalEnv.class}) - @seealso({or.class}) - public static class and extends AbstractFunction implements Optimizable { + } - @Override - public String getName() { - return "and"; - } + @api(environments = {GlobalEnv.class}) + @seealso({and.class}) + public static class or extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public String getName() { + return "or"; + } - @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 Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @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 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 Class[] thrown() { - return new Class[]{CRECastException.class}; + @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 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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 || @b"; + } - @Override - public CHVersion since() { - return CHVersion.V3_0_1; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_0_1; + } - @Override - public boolean useSpecialExec() { - return true; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - OptimizationUtilities.pullUpLikeFunctions(children, getName()); - Iterator 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. + @Override + public boolean useSpecialExec() { + return true; + } + + @Override + public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + OptimizationUtilities.pullUpLikeFunctions(children, getName()); + Iterator 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(); @@ -1785,128 +2026,99 @@ public ParseTree optimizeDynamic(Target t, List children, FileOptions // } // } // } - // 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 optimizationOptions() { - return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); - } + // 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; } - @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 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 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 Set optimizationOptions() { + return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); + } + } - @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; - } + @api + public static class dor extends AbstractFunction implements Optimizable { - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public Class[] thrown() { + return new 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 || @b"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public CHVersion since() { - return CHVersion.V3_0_1; - } + @Override + public boolean useSpecialExec() { + 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 boolean useSpecialExec() { - return true; + @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 children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - OptimizationUtilities.pullUpLikeFunctions(children, getName()); - Iterator 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. + @Override + public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + OptimizationUtilities.pullUpLikeFunctions(children, getName()); + Iterator 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(); @@ -1919,895 +2131,917 @@ public ParseTree optimizeDynamic(Target t, List children, FileOptions // } // } // } - // 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; - } + // 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 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 String getName() { + return "dor"; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); - } + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; } - @api - public static class not extends AbstractFunction implements Optimizable { + @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 String getName() { - return "not"; - } + @Override + public Version since() { + return CHVersion.V3_3_2; + } - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC, OptimizationOption.CONSTANT_OFFLINE); + } - @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 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);") + }; + } - @Override - public Class[] 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"; - } + @api + public static class not extends AbstractFunction implements Optimizable { - @Override - public boolean isRestricted() { - return false; - } + @Override + public String getName() { + return "not"; + } - @Override - public CHVersion since() { - return CHVersion.V3_0_1; - } + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } - @Override - public Boolean runAsync() { - return null; - } + @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 Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @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"), - }; - } + @Override + public String docs() { + return "boolean {var1} Returns the boolean value of a logical NOT for this argument. Operator syntax is also supported: !@var"; } - @api - public static class xor extends AbstractFunction implements Optimizable { + @Override + public boolean isRestricted() { + return false; + } - @Override - public String getName() { - return "xor"; - } + @Override + public CHVersion since() { + return CHVersion.V3_0_1; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public String docs() { - return "boolean {val1, val2} Returns the xor of the two values."; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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"),}; + } + } - @Override - public boolean isRestricted() { - return false; - } + @api + public static class xor extends AbstractFunction implements Optimizable { - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public String getName() { + return "xor"; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @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 String docs() { + return "boolean {val1, val2} Returns the xor of the two values."; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "xor(true, false)"),}; - } + @Override + public boolean isRestricted() { + return false; } - @api - @seealso({and.class}) - public static class nand extends AbstractFunction { + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @Override - public String getName() { - return "nand"; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @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 String docs() { - return "boolean {val1, [val2...]} Return the equivalent of not(and())"; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "xor(true, false)"),}; + } + } - @Override - public boolean isRestricted() { - return false; - } + @api + @seealso({and.class}) + public static class nand extends AbstractFunction { - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public String getName() { + return "nand"; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public Construct exec(Target t, Environment environment, Construct... args) { - return CNull.NULL; - } + @Override + public String docs() { + return "boolean {val1, [val2...]} Return the equivalent of not(and())"; + } - @Override - public CBoolean execs(Target t, Environment env, Script parent, ParseTree... nodes) { - return new and().execs(t, env, parent, nodes).not(); - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public boolean useSpecialExec() { - return true; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "nand(true, true)"),}; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; } - @api - @seealso({or.class}) - public static class nor extends AbstractFunction { + @Override + public Boolean runAsync() { + return null; + } - @Override - public String getName() { - return "nor"; - } + @Override + public Construct exec(Target t, Environment environment, Construct... args) { + return CNull.NULL; + } - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public CBoolean execs(Target t, Environment env, Script parent, ParseTree... nodes) { + return new and().execs(t, env, parent, nodes).not(); + } - @Override - public String docs() { - return "boolean {val1, [val2...]} Returns the equivalent of not(or())"; - } + @Override + public boolean useSpecialExec() { + return true; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "nand(true, true)"),}; + } + } - @Override - public boolean isRestricted() { - return false; - } + @api + @seealso({or.class}) + public static class nor extends AbstractFunction { - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public String getName() { + return "nor"; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public Construct exec(Target t, Environment environment, Construct... args) { - return CNull.NULL; - } + @Override + public String docs() { + return "boolean {val1, [val2...]} Returns the equivalent of not(or())"; + } - @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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public boolean useSpecialExec() { - return true; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "nor(true, false)"),}; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; } - @api - @seealso({xor.class}) - public static class xnor extends AbstractFunction implements Optimizable { + @Override + public Boolean runAsync() { + return null; + } - @Override - public String getName() { - return "xnor"; - } + @Override + public Construct exec(Target t, Environment environment, Construct... args) { + return CNull.NULL; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @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 String docs() { - return "boolean {val1, val2} Returns the xnor of the two values"; - } + @Override + public boolean useSpecialExec() { + return true; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "nor(true, false)"),}; + } + } - @Override - public boolean isRestricted() { - return false; - } + @api + @seealso({xor.class}) + public static class xnor extends AbstractFunction implements Optimizable { - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public String getName() { + return "xnor"; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @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 String docs() { + return "boolean {val1, val2} Returns the xnor of the two values"; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "xnor(true, true)"),}; - } + @Override + public boolean isRestricted() { + return false; } - @api - public static class bit_and extends AbstractFunction implements Optimizable { + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @Override - public String getName() { - return "bit_and"; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2, Integer.MAX_VALUE}; - } + @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 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 String docs() { - return "int {int1, int2, [int3...]} Returns the bitwise AND of the values"; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2, Integer.MAX_VALUE}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; - } + @Override + public String docs() { + return "int {int1, int2, [int3...]} Returns the bitwise AND of the values"; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN, - OptimizationOption.OPTIMIZE_DYNAMIC - ); - } + @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 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 Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.OPTIMIZE_DYNAMIC + ); + } - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if (children.size() < 2){ - throw new ConfigCompileException("bit_and() requires at least 2 arguments.", t); - } - return null; - } + @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)"),}; } - @api - public static class bit_or extends AbstractFunction implements Optimizable { + @Override + public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + if (children.size() < 2) { + throw new ConfigCompileException("bit_and() requires at least 2 arguments.", t); + } + return null; + } + } - @Override - public String getName() { - return "bit_or"; - } + @api + public static class bit_or extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2, Integer.MAX_VALUE}; - } + @Override + public String getName() { + return "bit_or"; + } - @Override - public String docs() { - return "int {int1, int2, [int3...]} Returns the bitwise OR of the specified values"; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2, Integer.MAX_VALUE}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; - } + @Override + public String docs() { + return "int {int1, int2, [int3...]} Returns the bitwise OR of the specified values"; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN, - OptimizationOption.OPTIMIZE_DYNAMIC - ); - } + @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 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 Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.OPTIMIZE_DYNAMIC + ); + } - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if (children.size() < 2){ - throw new ConfigCompileException("bit_or() requires at least 2 arguments.", t); - } - return null; - } + @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)"),}; } - @api - public static class bit_xor extends AbstractFunction implements Optimizable { + @Override + public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + if (children.size() < 2) { + throw new ConfigCompileException("bit_or() requires at least 2 arguments.", t); + } + return null; + } + } - @Override - public String getName() { - return "bit_xor"; - } + @api + public static class bit_xor extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2, Integer.MAX_VALUE}; - } + @Override + public String getName() { + return "bit_xor"; + } - @Override - public String docs() { - return "int {int1, int2, [int3...]} Returns the bitwise exclusive OR of the specified values"; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2, Integer.MAX_VALUE}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; - } + @Override + public String docs() { + return "int {int1, int2, [int3...]} Returns the bitwise exclusive OR of the specified values"; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN, - OptimizationOption.OPTIMIZE_DYNAMIC - ); - } + @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 ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "bit_xor(1, 2, 4)"),}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.OPTIMIZE_DYNAMIC + ); + } - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if (children.size() < 2){ - throw new ConfigCompileException("bit_xor() requires at least 2 arguments.", t); - } - return null; - } + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "bit_xor(1, 2, 4)"),}; } - @api - public static class bit_not extends AbstractFunction implements Optimizable { + @Override + public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + if (children.size() < 2) { + throw new ConfigCompileException("bit_xor() requires at least 2 arguments.", t); + } + return null; + } + } - @Override - public String getName() { - return "bit_not"; - } + @api + public static class bit_not extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public String getName() { + return "bit_not"; + } - @Override - public String docs() { - return "int {int1} Returns the bitwise NOT of the given value"; - } + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public String docs() { + return "int {int1} Returns the bitwise NOT of the given value"; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @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 ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "bit_not(1)"),}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); } - @api - public static class lshift extends AbstractFunction implements Optimizable { + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "bit_not(1)"),}; + } + } - @Override - public String getName() { - return "lshift"; - } + @api + public static class lshift extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "lshift"; + } - @Override - public String docs() { - return "int {value, bitsToShift} Left shifts the value bitsToShift times"; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public String docs() { + return "int {value, bitsToShift} Left shifts the value bitsToShift times"; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @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 ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "lshift(1, 1)"),}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); } - @api - public static class rshift extends AbstractFunction implements Optimizable { + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "lshift(1, 1)"),}; + } + } - @Override - public String getName() { - return "rshift"; - } + @api + public static class rshift extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "rshift"; + } - @Override - public String docs() { - return "int {value, bitsToShift} Right shifts the value bitsToShift times"; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public String docs() { + return "int {value, bitsToShift} Right shifts the value bitsToShift times"; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @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 ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "rshift(2, 1)"), - new ExampleScript("Basic usage", "rshift(-2, 1)"),}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); } - @api - public static class urshift extends AbstractFunction implements Optimizable { + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "rshift(2, 1)"), + new ExampleScript("Basic usage", "rshift(-2, 1)"),}; + } + } - @Override - public String getName() { - return "urshift"; - } + @api + public static class urshift extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "urshift"; + } - @Override - public String docs() { - return "int {value, bitsToShift} Right shifts value bitsToShift times, pushing a 0, making" - + " this an unsigned right shift."; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public String docs() { + return "int {value, bitsToShift} Right shifts value bitsToShift times, pushing a 0, making" + + " this an unsigned right shift."; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN - ); - } + @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 ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "urshift(2, 1)"), - new ExampleScript("Basic usage", "urshift(-2, 1)"),}; - } + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN + ); } - @api - public static class compile_error extends AbstractFunction implements Optimizable { + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "urshift(2, 1)"), + new ExampleScript("Basic usage", "urshift(-2, 1)"),}; + } + } - @Override - public Class[] thrown() { - return null; - } + @api + public static class compile_error extends AbstractFunction implements Optimizable { - @Override - public boolean isRestricted() { - return true; - } + @Override + public Class[] thrown() { + return null; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public boolean isRestricted() { + return true; + } - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return CVoid.VOID; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public String getName() { - return "compile_error"; - } + @Override + public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + return CVoid.VOID; + } - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public String getName() { + return "compile_error"; + } - @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 Integer[] numArgs() { + return new Integer[]{1}; + } - @Override - public Version since() { - return CHVersion.V3_3_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 Set optimizationOptions() { - return EnumSet.of(OptimizationOption.CUSTOM_LINK, OptimizationOption.OPTIMIZE_DYNAMIC); - } + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public ParseTree optimizeDynamic(Target t, List 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 Set optimizationOptions() { + return EnumSet.of(OptimizationOption.CUSTOM_LINK, OptimizationOption.OPTIMIZE_DYNAMIC); + } - @Override - public void link(Target t, List 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); - } + @Override + public ParseTree optimizeDynamic(Target t, List 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 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); } + } + } diff --git a/src/main/java/com/laytonsmith/core/functions/Compiler.java b/src/main/java/com/laytonsmith/core/functions/Compiler.java index f9f5cb59a..f07c13192 100644 --- a/src/main/java/com/laytonsmith/core/functions/Compiler.java +++ b/src/main/java/com/laytonsmith/core/functions/Compiler.java @@ -392,6 +392,36 @@ public ParseTree optimizeSpecial(List list, boolean returnSConcat) th i--; } } + // default and + for (int i = 0; i < list.size() - 1; i++) { + ParseTree node = list.get(i + 1); + if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isDefaultAnd()) { + CSymbol sy = (CSymbol) node.getData(); + ParseTree conversion = new ParseTree(new CFunction(sy.convert(), node.getTarget()), node.getFileOptions()); + conversion.addChild(list.get(i)); + conversion.addChild(list.get(i + 2)); + list.set(i, conversion); + list.remove(i + 1); + list.remove(i + 1); + i--; + } + } + + // default or + for (int i = 0; i < list.size() - 1; i++) { + ParseTree node = list.get(i + 1); + if (node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isDefaultOr()) { + CSymbol sy = (CSymbol) node.getData(); + ParseTree conversion = new ParseTree(new CFunction(sy.convert(), node.getTarget()), node.getFileOptions()); + conversion.addChild(list.get(i)); + conversion.addChild(list.get(i + 2)); + list.set(i, conversion); + list.remove(i + 1); + list.remove(i + 1); + i--; + } + } + //logical and for (int i = 0; i < list.size() - 1; i++) { ParseTree node = list.get(i + 1); diff --git a/src/test/java/com/laytonsmith/core/OptimizationTest.java b/src/test/java/com/laytonsmith/core/OptimizationTest.java index 8dbc12931..68391a99d 100644 --- a/src/test/java/com/laytonsmith/core/OptimizationTest.java +++ b/src/test/java/com/laytonsmith/core/OptimizationTest.java @@ -249,39 +249,39 @@ public void testSwitch2() throws Exception { + " 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")); } @@ -309,9 +309,33 @@ public void testEmptySwitch() throws Exception { // + "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, '')")); + } + + } diff --git a/src/test/java/com/laytonsmith/core/functions/BasicLogicTest.java b/src/test/java/com/laytonsmith/core/functions/BasicLogicTest.java index 1f9413f52..f91b1fab3 100644 --- a/src/test/java/com/laytonsmith/core/functions/BasicLogicTest.java +++ b/src/test/java/com/laytonsmith/core/functions/BasicLogicTest.java @@ -387,7 +387,7 @@ public void testSwitchWithArray() throws Exception { + "array(3, 4), correct," + "5, wrong)", null)); } - + @Test(timeout = 10000) public void testSwitchWithNestedArrayAsDefaultReturn() throws Exception { assertEquals("{{correct}}", SRun("switch(5," @@ -464,18 +464,33 @@ public void testRefEquals5() throws Exception { verify(fakePlayer).sendMessage("false"); } + @Test public void testSEqualsic1() throws Exception { SRun("msg(sequals_ic(1, '1'))", fakePlayer); verify(fakePlayer).sendMessage("false"); } + @Test public void testSEqualsic2() throws Exception { SRun("msg(sequals_ic('hello', 'HELLO'))", fakePlayer); verify(fakePlayer).sendMessage("true"); } + @Test public void testSEqualsic3() throws Exception { SRun("msg(sequals_ic('false', true))", fakePlayer); verify(fakePlayer).sendMessage("false"); } + + @Test + public void testDor() throws Exception { + SRun("msg(dor('', 'b'))", fakePlayer); + verify(fakePlayer).sendMessage("b"); + } + + @Test + public void testDand() throws Exception { + SRun("msg(typeof(dand('a', 'b', false)))", fakePlayer); + verify(fakePlayer).sendMessage("boolean"); + } }