package com.laytonsmith.core.functions;
import com.laytonsmith.PureUtilities.Common.StringUtils;
import com.laytonsmith.PureUtilities.LinkedComparatorSet;
import com.laytonsmith.PureUtilities.RunnableQueue;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.abstraction.StaticLayer;
import com.laytonsmith.annotations.api;
import com.laytonsmith.annotations.core;
import com.laytonsmith.annotations.seealso;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.core.Optimizable;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.Script;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.compiler.FileOptions;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CBoolean;
import com.laytonsmith.core.constructs.CClosure;
import com.laytonsmith.core.constructs.CInt;
import com.laytonsmith.core.constructs.CMutablePrimitive;
import com.laytonsmith.core.constructs.CNull;
import com.laytonsmith.core.constructs.CSlice;
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.CVoid;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.environments.GlobalEnv;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import com.laytonsmith.core.exceptions.CRE.CREFormatException;
import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException;
import com.laytonsmith.core.exceptions.CRE.CREIndexOverflowException;
import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException;
import com.laytonsmith.core.exceptions.CRE.CREPluginInternalException;
import com.laytonsmith.core.exceptions.CRE.CRERangeException;
import com.laytonsmith.core.exceptions.CRE.CREThrowable;
import com.laytonsmith.core.exceptions.CancelCommandException;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.exceptions.FunctionReturnException;
import com.laytonsmith.core.exceptions.ProgramFlowManipulationException;
import com.laytonsmith.core.functions.BasicLogic.equals;
import com.laytonsmith.core.functions.BasicLogic.equals_ic;
import com.laytonsmith.core.functions.BasicLogic.sequals;
import com.laytonsmith.core.functions.DataHandling.array;
import com.laytonsmith.core.natives.interfaces.ArrayAccess;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
*
*/
@core
public class ArrayHandling {
public static String docs() {
return "This class contains functions that provide a way to manipulate arrays. To create an array, use the <code>array</code> function."
+ " For more detailed information on array usage, see the page on [[CommandHelper/Arrays|arrays]]";
}
@api
public static class array_size extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_size";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException {
if (args[0] instanceof CArray && !(args[0] instanceof CMutablePrimitive)) {
return new CInt(((CArray) args[0]).size(), t);
}
throw new CRECastException("Argument 1 of array_size must be an array", t);
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public String docs() {
return "int {array} Returns the size of this array as an integer.";
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_0_1;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates usage", "array_size(array(1, 2, 3, 4, 5));"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api(environments={GlobalEnv.class})
@seealso({array_set.class, array.class, com.laytonsmith.tools.docgen.templates.Arrays.class})
public static class array_get extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_get";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1, 2, 3};
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException {
Construct index;
Construct defaultConstruct = null;
if (args.length >= 2) {
index = args[1];
} else {
index = new CSlice(0, -1, t);
}
if (args.length >= 3) {
defaultConstruct = args[2];
}
if (args[0] instanceof CArray) {
CArray ca = (CArray) args[0];
if (index instanceof CSlice) {
// Deep clone the array if the "index" is the initial one.
if (((CSlice) index).getStart() == 0 && ((CSlice) index).getFinish() == -1) {
return ca.deepClone(t);
} else if(ca.inAssociativeMode()) {
throw new CRECastException("Array slices are not allowed with an associative array", t);
}
//It's a range
long start = ((CSlice) index).getStart();
long finish = ((CSlice) index).getFinish();
try {
//Convert negative indexes
if (start < 0) {
start = ca.size() + start;
}
if (finish < 0) {
finish = ca.size() + finish;
}
CArray na = ca.createNew(t);
if (finish < start) {
//return an empty array in cases where the indexes don't make sense
return na;
}
for (long i = start; i <= finish; i++) {
try {
na.push(ca.get((int) i, t).clone(), t);
} catch (CloneNotSupportedException e) {
na.push(ca.get((int) i, t), t);
}
}
return na;
} catch (NumberFormatException e) {
throw new CRECastException("Ranges must be integer numbers, i.e., [0..5]", t);
}
} else {
try {
if (!ca.inAssociativeMode()) {
if(index instanceof CNull){
throw new CRECastException("Expected a number, but recieved null instead", t);
}
long iindex = Static.getInt(index, t);
if (iindex < 0) {
//negative index, convert to positive index
iindex = ca.size() + iindex;
}
return ca.get(iindex, t);
} else {
return ca.get(index, t);
}
} catch (ConfigRuntimeException e) {
if (e instanceof CREIndexOverflowException) {
if(defaultConstruct != null){
return defaultConstruct;
}
}
if(env.getEnv(GlobalEnv.class).GetFlag("array-special-get") != null){
//They are asking for an array that doesn't exist yet, so let's create it now.
CArray c;
if(ca.inAssociativeMode()){
c = CArray.GetAssociativeArray(t);
} else {
c = new CArray(t);
}
ca.set(index, c, t);
return c;
}
throw e;
}
}
} else if (args[0] instanceof ArrayAccess) {
if (index instanceof CSlice) {
ArrayAccess aa = (ArrayAccess) args[0];
//It's a range
long start = ((CSlice) index).getStart();
long finish = ((CSlice) index).getFinish();
try {
//Convert negative indexes
if (start < 0) {
start = aa.val().length() + start;
}
if (finish < 0) {
finish = aa.val().length() + finish;
}
if (finish < start) {
//return an empty array in cases where the indexes don't make sense
return new CString("", t);
}
StringBuilder b = new StringBuilder();
String val = aa.val();
for (long i = start; i <= finish; i++) {
try{
b.append(val.charAt((int) i));
} catch(StringIndexOutOfBoundsException e){
throw new CRERangeException("String bounds out of range. Tried to get character at index " + i + ", but indicies only go up to " + (val.length() - 1), t);
}
}
return new CString(b.toString(), t);
} catch (NumberFormatException e) {
throw new CRECastException("Ranges must be integer numbers, i.e., [0..5]", t);
}
} else {
try {
return new CString(args[0].val().charAt(Static.getInt32(index, t)), t);
} catch (ConfigRuntimeException e) {
if (e instanceof CRECastException) {
if(args[0] instanceof CArray){
throw new CRECastException("Expecting an integer index for the array, but found \"" + index
+ "\". (Array is not associative, and cannot accept string keys here.)", t);
} else {
throw new CRECastException("Expecting an array, but \"" + args[0] + "\" was found.", t);
}
} else {
throw e;
}
} catch (StringIndexOutOfBoundsException e) {
throw new CRERangeException("No index at " + index, t);
}
}
} else {
throw new CRECastException("Argument 1 of array_get must be an array", t);
}
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREIndexOverflowException.class};
}
@Override
public String docs() {
return "mixed {array, index, [default]} Returns the element specified at the index of the array. ---- If the element doesn't exist, an exception is thrown. "
+ "array_get(array, index). Note also that as of 3.1.2, you can use a more traditional method to access elements in an array: "
+ "array[index] is the same as array_get(array, index), where array is a variable, or function that is an array. In fact, the compiler"
+ " does some magic under the covers, and literally converts array[index] into array_get(array, index), so if there is a problem "
+ "with your code, you will get an error message about a problem with the array_get function, even though you may not be using "
+ "that function directly. If using the plain function access, then if a default is provided, the function will always return that value if the"
+ " array otherwise doesn't have a value there. This is opposed to throwing an exception or returning null.";
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_0_1;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct optimize(Target t, Construct... args) throws ConfigCompileException {
if(args.length == 0) {
throw new CRECastException("Argument 1 of array_get must be an array", t);
}
if (args[0] instanceof ArrayAccess) {
ArrayAccess aa = (ArrayAccess) args[0];
if (!aa.canBeAssociative()) {
if (!(args[1] instanceof CInt) && !(args[1] instanceof CSlice)) {
throw new ConfigCompileException("Accessing an element as an associative array, when it can only accept integers.", t);
}
}
return null;
} else {
throw new ConfigCompileException("Trying to access an element like an array, but it does not support array access.", t);
}
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates basic usage", "msg(array(0, 1, 2)[2]);"),
new ExampleScript("Demonstrates exception", "msg(array()[1]);"),
new ExampleScript("Demonstrates basic functional usage", "msg(array_get(array(1, 2, 3), 2));"),
new ExampleScript("Demonstrates default (note that you cannot use the bracket syntax with this)",
"msg(array_get(array(), 1, 'default'));"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(
OptimizationOption.OPTIMIZE_CONSTANT
);
}
}
@api
@seealso({array_get.class, array.class, array_push.class, com.laytonsmith.tools.docgen.templates.Arrays.class})
public static class array_set extends AbstractFunction {
@Override
public String getName() {
return "array_set";
}
@Override
public Integer[] numArgs() {
return new Integer[]{3};
}
@Override
public boolean useSpecialExec() {
return true;
}
@Override
public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) {
env.getEnv(GlobalEnv.class).SetFlag("array-special-get", true);
Construct array = parent.seval(nodes[0], env);
env.getEnv(GlobalEnv.class).ClearFlag("array-special-get");
Construct index = parent.seval(nodes[1], env);
Construct value = parent.seval(nodes[2], env);
if(!(array instanceof CArray)){
throw new CRECastException("Argument 1 of array_set must be an array", t);
}
try {
((CArray)array).set(index, value, t);
} catch (IndexOutOfBoundsException e) {
throw new CREIndexOverflowException("The index " + index.asString().getQuote() + " is out of bounds", t);
}
return value;
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException {
if (args[0] instanceof CArray) {
try {
((CArray) args[0]).set(args[1], args[2], t);
} catch (IndexOutOfBoundsException e) {
throw new CREIndexOverflowException("The index " + args[1].val() + " is out of bounds", t);
}
return args[2];
}
throw new CRECastException("Argument 1 of array_set must be an array", t);
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREIndexOverflowException.class};
}
@Override
public String docs() {
return "mixed {array, index, value} Sets the value of the array at the specified index. array_set(array, index, value). Returns void. If"
+ " the element at the specified index isn't already set, throws an exception. Use array_push to avoid this. The value"
+ " that was set is returned, to allow for chaining.";
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_0_1;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates using assignment",
"array @array = array(null);\n"
+ "msg(@array);\n"
+ "@array[0] = 'value0';\n"
+ "msg(@array);"),
new ExampleScript("Demonstrates functional usage",
"array @array = array(null);\n"
+ "msg(@array);\n"
+ "array_set(@array, 0, 'value0');\n"
+ "msg(@array);"),
};
}
}
@api
@seealso({array_set.class})
public static class array_push extends AbstractFunction {
@Override
public String getName() {
return "array_push";
}
@Override
public Integer[] numArgs() {
return new Integer[]{Integer.MAX_VALUE};
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException {
if(args.length < 2) {
throw new CREInsufficientArgumentsException("At least 2 arguments must be provided to array_push", t);
}
if (args[0] instanceof CArray) {
CArray array = (CArray)args[0];
int initialSize = (int)array.size();
for (int i = 1; i < args.length; i++) {
((CArray) args[0]).push(args[i], t);
for(ArrayAccess.ArrayAccessIterator iterator : env.getEnv(GlobalEnv.class).GetArrayAccessIteratorsFor(((ArrayAccess)args[0]))){
//This is always pushing after the current index.
//Given that this is the last one, we don't need to waste
//time with a call to increment the blacklist items either.
iterator.addToBlacklist(initialSize + i - 1);
}
}
return CVoid.VOID;
}
throw new CRECastException("Argument 1 of array_push must be an array", t);
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public String docs() {
return "void {array, value, [value2...]} Pushes the specified value(s) onto the end of the array. Unlike calling"
+ " array_set(@array, array_size(@array), @value) on a normal array, the size of the array is increased first."
+ " This will therefore never cause an IndexOverflowException. The special operator syntax @array[] = 'value' is"
+ " also supported, as shorthand for array_push().";
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_0_1;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Operator syntax. Note the difference between this and the array clone"
+ " operator is that this occurs on the Left Hand Side (LHS) of the assignment.",
"array @array = array();\n"
+ "@array[] = 'new value';"),
new ExampleScript("Demonstrates functional usage",
"array @array = array();\n"
+ "msg(@array);\n"
+ "array_push(@array, 0);\n"
+ "msg(@array);"),
new ExampleScript("Demonstrates pushing multiple values (note that it is not possible to use the bracket notation"
+ " and push multiple values)",
"array @array = array();\n"
+ "msg(@array);\n"
+ "array_push(@array, 0, 1, 2);\n"
+ "msg(@array);"),
};
}
}
@api
public static class array_insert extends AbstractFunction{
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREIndexOverflowException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
Construct value = args[1];
int index = Static.getInt32(args[2], t);
try{
array.push(value, index, t);
//If the push succeeded (actually an insert) we need to check to see if we are currently iterating
//and act appropriately.
for(ArrayAccess.ArrayAccessIterator iterator : environment.getEnv(GlobalEnv.class).GetArrayAccessIteratorsFor(array)){
if(index <= iterator.getCurrent()){
//The insertion happened before (or at) this index, so we need to increment the
//iterator, as well as increment all the blacklist items above this one.
iterator.incrementCurrent();
} else {
//The insertion happened after this index, so we need to increment the
//blacklist values after this one, and add this index to the blacklist
iterator.incrementBlacklistAfter(index);
iterator.addToBlacklist(index);
}
}
} catch(IllegalArgumentException e){
throw new CRECastException(e.getMessage(), t);
} catch(IndexOutOfBoundsException ex){
throw new CREIndexOverflowException(ex.getMessage(), t);
}
return CVoid.VOID;
}
@Override
public String getName() {
return "array_insert";
}
@Override
public Integer[] numArgs() {
return new Integer[]{3};
}
@Override
public String docs() {
return "void {array, item, index} Inserts an item at the specified index, and shifts all other items in the array to the right one."
+ " If index is greater than the size of the array, an IndexOverflowException is thrown, though the index may be equal"
+ " to the size, in which case this works just like array_push. The array must be normal though, associative arrays"
+ " are not supported.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "array @array = array(1, 3, 4);\n"
+ "array_insert(@array, 2, 1);\n"
+ "msg(@array);"),
new ExampleScript("Usage as if it were array_push", "@array = array(1, 2, 3);\n"
+ "array_insert(@array, 4, array_size(@array));\n"
+ "msg(@array);")
};
}
}
@api
@seealso({array_index_exists.class, array_scontains.class})
public static class array_contains extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_contains";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException {
if(!(args[0] instanceof CArray)) {
throw new CRECastException("Argument 1 of " + this.getName() + " must be an array", t);
}
CArray ca = (CArray) args[0];
for(Construct key : ca.keySet()){
if(new equals().exec(t, env, ca.get(key, t), args[1]).getBoolean()){
return CBoolean.TRUE;
}
}
return CBoolean.FALSE;
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public String docs() {
return "boolean {array, testValue} Checks to see if testValue is in array. For associative arrays, only the values are searched,"
+ " the keys are ignored. If you need to check for the existance of a particular key, use array_index_exists().";
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_0_1;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates finding a value", "array_contains(array(0, 1, 2), 2)"),
new ExampleScript("Demonstrates not finding a value", "array_contains(array(0, 1, 2), 5)"),
new ExampleScript("Demonstrates finding a value listed multiple times", "array_contains(array(1, 1, 1), 1)"),
new ExampleScript("Demonstrates finding a string", "array_contains(array('a', 'b', 'c'), 'b')"),
new ExampleScript("Demonstrates finding a value in an associative array", "array_contains(array('a': 1, 'b': 2), 2)")
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
@seealso({array_contains.class})
public static class array_contains_ic extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_contains_ic";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "boolean {array, testValue} Works like array_contains, except the comparison ignores case.";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_3_0;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
if (args[0] instanceof CArray) {
CArray ca = (CArray) args[0];
for (int i = 0; i < ca.size(); i++) {
if (new equals_ic().exec(t, environment, ca.get(i, t), args[1]).getBoolean()) {
return CBoolean.TRUE;
}
}
return CBoolean.FALSE;
} else {
throw new CRECastException("Argument 1 of " + this.getName() + " must be an array", t);
}
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates usage", "array_contains_ic(array('A', 'B', 'C'), 'A')"),
new ExampleScript("Demonstrates usage", "array_contains_ic(array('A', 'B', 'C'), 'a')"),
new ExampleScript("Demonstrates usage", "array_contains_ic(array('A', 'B', 'C'), 'd')"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
@seealso({array_index_exists.class, array_contains.class})
public static class array_scontains extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_scontains";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException {
if(!(args[0] instanceof CArray)) {
throw new CRECastException("Argument 1 of " + this.getName() + " must be an array", t);
}
CArray ca = (CArray) args[0];
for(Construct key : ca.keySet()){
if(new sequals().exec(t, env, ca.get(key, t), args[1]).getBoolean()){
return CBoolean.TRUE;
}
}
return CBoolean.FALSE;
}
@Override
public Class[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public String docs() {
return "boolean {array, testValue} Checks if the array contains a value of the same datatype and value as testValue."
+ " For associative arrays, only the values are searched, the keys are ignored."
+ " If you need to check for the existance of a particular key, use array_index_exists().";
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates finding a value", "array_scontains(array(0, 1, 2), 2)"),
new ExampleScript("Demonstrates not finding a value because of a value mismatch", "array_scontains(array(0, 1, 2), 5)"),
new ExampleScript("Demonstrates not finding a value because of a type mismatch", "array_scontains(array(0, 1, 2), '2')"),
new ExampleScript("Demonstrates finding a value listed multiple times", "array_scontains(array(1, 1, 1), 1)"),
new ExampleScript("Demonstrates finding a string", "array_scontains(array('a', 'b', 'c'), 'b')"),
new ExampleScript("Demonstrates finding a value in an associative array", "array_scontains(array('a': 1, 'b': 2), 2)")
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_index_exists extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_index_exists";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "boolean {array, index} Checks to see if the specified array has an element at index";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_1_2;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException {
if (args[0] instanceof CArray) {
if (!((CArray) args[0]).inAssociativeMode()) {
try {
int index = Static.getInt32(args[1], t);
CArray ca = (CArray) args[0];
return CBoolean.get(index <= ca.size() - 1);
} catch (ConfigRuntimeException e) {
//They sent a key that is a string. Obviously it doesn't exist.
return CBoolean.FALSE;
}
} else {
CArray ca = (CArray) args[0];
return CBoolean.get(ca.containsKey(args[1].val()));
}
} else {
throw new CRECastException("Expecting argument 1 to be an array", t);
}
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates a true condition", "array_index_exists(array(0, 1, 2), 0)"),
new ExampleScript("Demonstrates a false condition", "array_index_exists(array(0, 1, 2), 3)"),
new ExampleScript("Demonstrates an associative array", "array_index_exists(array(a: 'A', b: 'B'), 'a')"),
new ExampleScript("Demonstrates an associative array", "array_index_exists(array(a: 'A', b: 'B'), 'c')"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_resize extends AbstractFunction {
@Override
public String getName() {
return "array_resize";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2, 3};
}
@Override
public String docs() {
return "array {array, size, [fill]} Resizes the given array so that it is at least of size size, filling the blank spaces with"
+ " fill, or null by default. If the size of the array is already at least size, nothing happens; in other words this"
+ " function can only be used to increase the size of the array. A reference to the array is returned, for easy chaining.";
//+ " If the array is an associative array, the non numeric values are simply copied over.";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_2_0;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public CArray exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException {
if (args[0] instanceof CArray && args[1] instanceof CInt) {
CArray original = (CArray) args[0];
int size = (int) ((CInt) args[1]).getInt();
Construct fill = CNull.NULL;
if (args.length == 3) {
fill = args[2];
}
for (long i = original.size(); i < size; i++) {
original.push(fill, t);
}
} else {
throw new CRECastException("Argument 1 must be an array, and argument 2 must be an integer in array_resize", t);
}
return (CArray)args[0];
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates basic usage",
"array @array = array();\n"
+ "msg(@array);\n"
+ "array_resize(@array, 2);\n"
+ "msg(@array);"),
new ExampleScript("Demonstrates custom fill",
"array @array = array();\n"
+ "msg(@array);\n"
+ "array_resize(@array, 2, 'a');\n"
+ "msg(@array);"),
};
}
}
@api
public static class range extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "range";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1, 2, 3};
}
@Override
public String docs() {
return "array {start, finish, [increment] | finish} Returns an array of numbers from start to (finish - 1)"
+ " skipping increment integers per count. start defaults to 0, and increment defaults to 1. All inputs"
+ " must be integers. If the input doesn't make sense, it will reasonably degrade, and return an empty array.";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_2_0;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public CArray exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException {
long start = 0;
long finish = 0;
long increment = 1;
if (args.length == 1) {
finish = Static.getInt(args[0], t);
} else if (args.length == 2) {
start = Static.getInt(args[0], t);
finish = Static.getInt(args[1], t);
} else if (args.length == 3) {
start = Static.getInt(args[0], t);
finish = Static.getInt(args[1], t);
increment = Static.getInt(args[2], t);
}
if (start < finish && increment < 0 || start > finish && increment > 0 || increment == 0) {
return new CArray(t);
}
CArray ret = new CArray(t);
for (long i = start; (increment > 0 ? i < finish : i > finish); i = i + increment) {
ret.push(new CInt(i, t), t);
}
return ret;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "range(10)"),
new ExampleScript("Complex usage", "range(0, 10)"),
new ExampleScript("With skips", "range(0, 10, 2)"),
new ExampleScript("Invalid input", "range(0, 10, -1)"),
new ExampleScript("In reverse", "range(10, 0, -1)"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_keys extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_keys";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "array {array} Returns the keys in this array as a normal array. If the array passed in is already a normal array,"
+ " the keys will be 0 -> (array_size(array) - 1)";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_3_0;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException {
// As an exception, strings aren't supported here. There's no reason to do this for a string that isn't accidental.
if (args[0] instanceof ArrayAccess && !(args[0] instanceof CString)) {
ArrayAccess ca = (ArrayAccess) args[0];
CArray ca2 = new CArray(t);
for (Construct c : ca.keySet()) {
ca2.push(c, t);
}
return ca2;
} else {
throw new CRECastException(this.getName() + " expects arg 1 to be an array", t);
}
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "array_keys(array('a', 'b', 'c'))"),
new ExampleScript("With associative array", "array_keys(array(one: 'a', two: 'b', three: 'c'))"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_normalize extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_normalize";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "array {array} Returns a new normal array, given an associative array. (If the array passed in is not associative, a copy of the "
+ " array is returned).";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_3_0;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException {
if (args[0] instanceof ArrayAccess) {
ArrayAccess ca = (ArrayAccess) args[0];
CArray ca2 = new CArray(t);
for (Construct c : ca.keySet()) {
ca2.push(ca.get(c.val(), t), t);
}
return ca2;
} else {
throw new CRECastException(this.getName() + " expects arg 1 to be an array", t);
}
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "array_normalize(array(one: 'a', two: 'b', three: 'c'))"),
new ExampleScript("Usage with normal array", "array_normalize(array(1, 2, 3))"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_merge extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "array_merge";
}
@Override
public Integer[] numArgs() {
return new Integer[]{Integer.MAX_VALUE};
}
@Override
public String docs() {
return "array {array1, array2, [arrayN...]} Merges the specified arrays from left to right, and returns a new array. If the array"
+ " merged is associative, it will overwrite the keys from left to right, but if the arrays are normal, the keys are ignored,"
+ " and values are simply pushed.";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CREInsufficientArgumentsException.class, CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_3_0;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray newArray = new CArray(t);
if (args.length < 2) {
throw new CREInsufficientArgumentsException("array_merge must be called with at least two parameters", t);
}
for (Construct arg : args) {
if (arg instanceof ArrayAccess) {
ArrayAccess cur = (ArrayAccess) arg;
if (!cur.isAssociative()) {
for (int j = 0; j < cur.size(); j++) {
newArray.push(cur.get(j, t), t);
}
} else {
for (Construct key : cur.keySet()) {
if(key instanceof CInt){
newArray.set(key, cur.get((int)((CInt)key).getInt(), t), t);
} else {
newArray.set(key, cur.get(key.val(), t), t);
}
}
}
} else {
throw new CRECastException("All arguments to array_merge must be arrays", t);
}
}
return newArray;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "array_merge(array(1), array(2), array(3))"),
new ExampleScript("With associative arrays", "array_merge(array(one: 1), array(two: 2), array(three: 3))"),
new ExampleScript("With overwrites", "array_merge(array(one: 1), array(one: 2), array(one: 3))"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_remove extends AbstractFunction {
@Override
public String getName() {
return "array_remove";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "mixed {array, index} Removes an index from an array. If the array is a normal"
+ " array, all values' indicies are shifted left one. If the array is associative,"
+ " the index is simply removed. If the index doesn't exist, the array remains"
+ " unchanged. The value removed is returned.";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRERangeException.class, CRECastException.class, CREPluginInternalException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public CHVersion since() {
return CHVersion.V3_3_0;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
if(array.isAssociative()){
return array.remove(args[1]);
} else {
int index = Static.getInt32(args[1], t);
Construct removed = array.remove(args[1]);
//If the removed index is <= the current index, we need to decrement the counter.
for(ArrayAccess.ArrayAccessIterator iterator : environment.getEnv(GlobalEnv.class).GetArrayAccessIteratorsFor(array)){
if(index <= iterator.getCurrent()){
iterator.decrementCurrent();
}
}
return removed;
}
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "assign(@array, array(1, 2, 3))\nmsg(array_remove(@array, 2))\nmsg(@array)"),
new ExampleScript("With associative array", "assign(@array, array(one: 'a', two: 'b', three: 'c'))\nmsg(array_remove(@array, 'two'))\nmsg(@array)"),
};
}
}
@api
@seealso({StringHandling.split.class, Regex.reg_split.class})
public static class array_implode extends AbstractFunction {
@Override
public String getName() {
return "array_implode";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1, 2};
}
@Override
public String docs() {
return "string {array, [glue]} Given an array and glue, to-strings all the elements"
+ " in the array (just the values, not the keys), and joins them with the glue, defaulting to a space. For instance"
+ " array_implode(array(1, 2, 3), '-') will return \"1-2-3\".";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
if (!(args[0] instanceof CArray)) {
throw new CRECastException("Expecting argument 1 to be an array", t);
}
StringBuilder b = new StringBuilder();
CArray ca = (CArray) args[0];
String glue = " ";
if (args.length == 2) {
glue = Static.getPrimitive(args[1], t).val();
}
boolean first = true;
for (Construct key : ca.keySet()) {
Construct value = ca.get(key.val(), t);
if (!first) {
b.append(glue).append(value.val());
} else {
b.append(value.val());
first = false;
}
}
return new CString(b.toString(), t);
}
@Override
public CHVersion since() {
return CHVersion.V3_3_0;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "array_implode(array(1, 2, 3), '-')"),
new ExampleScript("With associative array", "array_implode(array(one: 'a', two: 'b', three: 'c'), '-')"),
};
}
}
@api
public static class cslice extends AbstractFunction implements Optimizable {
@Override
public String getName() {
return "cslice";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "slice {from, to} Dynamically creates an array slice, which can be used with array_get"
+ " (or the [bracket notation]) to get a range of elements. cslice(0, 5) is equivalent"
+ " to 0..5 directly in code, however with this function you can also do cslice(@var, @var),"
+ " or other more complex expressions, which are not possible in static code.";
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
return new CSlice(Static.getInt(args[0], t), Static.getInt(args[1], t), t);
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "array(1, 2, 3)[cslice(0, 1)]"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_sort extends AbstractFunction implements Optimizable {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREFormatException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
if (!(args[0] instanceof CArray)) {
throw new CRECastException("The first parameter to array_sort must be an array", t);
}
CArray ca = (CArray) args[0];
CArray.SortType sortType = CArray.SortType.REGULAR;
CClosure customSort = null;
if(ca.size() <= 1){
return ca;
}
try {
if (args.length == 2) {
if(args[1] instanceof CClosure){
sortType = null;
customSort = (CClosure) args[1];
} else {
sortType = CArray.SortType.valueOf(args[1].val());
}
}
} catch (IllegalArgumentException e) {
throw new CREFormatException("The sort type must be one of either: " + StringUtils.Join(CArray.SortType.values(), ", ", " or "), t);
}
if(sortType == null){
// It's a custom sort, which we have implemented below.
if(ca.isAssociative()){
throw new CRECastException("Associative arrays may not be sorted using a custom comparator.", t);
}
CArray sorted = customSort(ca, customSort, t);
//Clear it out and re-apply the values, so this is in place.
ca.clear();
for(Construct c : sorted.keySet()){
ca.set(c, sorted.get(c, t), t);
}
} else {
ca.sort(sortType);
}
return ca;
}
private CArray customSort(CArray ca, CClosure closure, Target t){
if(ca.size() <= 1){
return ca;
}
CArray left = new CArray(t);
CArray right = new CArray(t);
int middle = (int)(ca.size() / 2);
for(int i = 0; i < middle; i++){
left.push(ca.get(i, t), t);
}
for(int i = middle; i < ca.size(); i++){
right.push(ca.get(i, t), t);
}
left = customSort(left, closure, t);
right = customSort(right, closure, t);
return merge(left, right, closure, t);
}
private CArray merge(CArray left, CArray right, CClosure closure, Target t){
CArray result = new CArray(t);
while(left.size() > 0 || right.size() > 0){
if(left.size() > 0 && right.size() > 0){
// Compare the first two elements of each side
Construct l = left.get(0, t);
Construct r = right.get(0, t);
Construct c = null;
try {
closure.execute(l, r);
} catch(FunctionReturnException ex){
c = ex.getReturn();
}
int value;
if(c instanceof CNull){
value = 0;
} else if(c instanceof CBoolean){
if(((CBoolean)c).getBoolean()){
value = 1;
} else {
value = -1;
}
} else {
throw new CRECastException("The custom closure did not return a value. It must always return true, false, or null.", t);
}
if(value <= 0){
result.push(left.get(0, t), t);
left.remove(0);
} else {
result.push(right.get(0, t), t);
right.remove(0);
}
} else if(left.size() > 0){
result.push(left.get(0, t), t);
left.remove(0);
} else if(right.size() > 0){
result.push(right.get(0, t), t);
right.remove(0);
}
}
return result;
}
@Override
public String getName() {
return "array_sort";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1, 2};
}
@Override
public String docs() {
return "array {array, [sortType]} Sorts an array in place, and also returns a reference to the array. ---- The"
+ " complexity of this sort algorithm is guaranteed to be no worse than n log n, as it uses merge sort."
+ " The array is sorted in place, a new array is not explicitly created, so if you sort an array that"
+ " is passed in as a variable, the contents of that variable will be sorted, even if you don't re-assign"
+ " the returned array back to the variable. If you really need the old array, you should create a copy of"
+ " the array first, like so: assign(@sorted, array_sort(@array[])). The sort type may be one of the following:"
+ " " + StringUtils.Join(CArray.SortType.values(), ", ", " or ") + ", or it may be a closure, if the sort should follow"
+ " custom rules (explained below). A regular sort sorts the elements without changing types first. A"
+ " numeric sort always converts numeric values to numbers first (so 001 becomes 1). A string sort compares"
+ " values as strings, and a string_ic sort is the same as a string sort, but the comparision is case-insensitive."
+ " If the array contains array values, a CastException is thrown; inner arrays cannot be sorted against each"
+ " other. If the array is associative, a warning will be raised if the General logging channel is set to verbose,"
+ " because the array's keys will all be lost in the process. To avoid this warning, and to be more explicit,"
+ " you can use array_normalize() to normalize the array first. Note that the reason this function is an"
+ " in place sort instead of explicitely cloning the array is because in most cases, you may not need"
+ " to actually clone the array, an expensive operation. Due to this, it has slightly different behavior"
+ " than array_normalize, which could have also been implemented in place.\n\n"
+ "If the sortType is a closure, it will perform a custom sort type, and the array may contain any values, including"
+ " sub array values. The closure should accept two values, @left and @right, and should"
+ " return true if the left value is larger than the right, and false if the left value is smaller than the"
+ " right, and null if they are equal. The array will then be re-ordered using a merge sort, using your custom"
+ " comparator to determine the sort order.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(
OptimizationOption.OPTIMIZE_DYNAMIC
);
}
@Override
public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException {
if (children.size() == 2) {
if (!children.get(1).getData().isDynamic()) {
try {
CArray.SortType.valueOf(children.get(1).getData().val().toUpperCase());
} catch (IllegalArgumentException e) {
throw new ConfigCompileException("The sort type must be one of either: " + StringUtils.Join(CArray.SortType.values(), ", ", " or "), t);
}
}
}
return null;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Regular sort", "@array = array('a', 2, 4, 'string');\narray_sort(@array, 'REGULAR');\nmsg(@array);"),
new ExampleScript("Numeric sort", "@array = array('03', '02', '4', '1');\narray_sort(@array, 'NUMERIC');\nmsg(@array);"),
new ExampleScript("String sort", "@array = array('03', '02', '4', '1');\narray_sort(@array, 'STRING');\nmsg(@array);"),
new ExampleScript("String sort (with words)", "@array = array('Zeta', 'zebra', 'Minecraft', 'mojang', 'Appliance', 'apple');\narray_sort(@array, 'STRING');\nmsg(@array);"),
new ExampleScript("Ignore case sort", "@array = array('Zeta', 'zebra', 'Minecraft', 'mojang', 'Appliance', 'apple');\narray_sort(@array, 'STRING_IC');\nmsg(@array);"),
new ExampleScript("Custom sort", "@array = array(\n"
+ "\tarray(name: 'Jack', age: 20),\n"
+ "\tarray(name: 'Jill', age: 19)\n"
+ ");\n"
+ "msg(\"Before sort: @array\");\n"
+ "array_sort(@array, closure(@left, @right){\n"
+ "\t return(@left['age'] > @right['age']);\n"
+ "});\n"
+ "msg(\"After sort: @array\");")
};
}
}
@api public static class array_sort_async extends AbstractFunction{
RunnableQueue queue = new RunnableQueue("MethodScript-arraySortAsync");
boolean started = false;
private void startup(){
if(!started){
queue.invokeLater(null, new Runnable() {
@Override
public void run() {
//This warms up the queue. Apparently.
}
});
StaticLayer.GetConvertor().addShutdownHook(new Runnable() {
@Override
public void run() {
queue.shutdown();
started = false;
}
});
started = true;
}
}
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
startup();
final CArray array = Static.getArray(args[0], t);
final CString sortType = new CString(args.length > 2?args[1].val():CArray.SortType.REGULAR.name(), t);
final CClosure callback = Static.getObject((args.length==2?args[1]:args[2]), t, CClosure.class);
queue.invokeLater(environment.getEnv(GlobalEnv.class).GetDaemonManager(), new Runnable() {
@Override
public void run() {
Construct c = new array_sort().exec(Target.UNKNOWN, null, array, sortType);
callback.execute(new Construct[]{c});
}
});
return CVoid.VOID;
}
@Override
public String getName() {
return "array_sort_async";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2, 3};
}
@Override
public String docs() {
return "void {array, [sortType], closure(array)} Works like array_sort, but does the sort on another"
+ " thread, then calls the closure and sends it the sorted array. This is useful if the array"
+ " is large enough to actually \"stall\" the server when doing the sort. Sort type should be"
+ " one of " + StringUtils.Join(CArray.SortType.values(), ", ", " or ");
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
}
@api public static class array_remove_values extends AbstractFunction{
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
//This needs to be in terms of array_remove, to ensure that the iteration
//logic is followed. We will iterate backwards, however, to make the
//process more efficient, unless this is an associative array.
if(array.isAssociative()){
array.removeValues(args[1]);
} else {
for(long i = array.size() - 1; i >= 0; i--){
if(BasicLogic.equals.doEquals(array.get(i, t), args[1])){
new array_remove().exec(t, environment, array, new CInt(i, t));
}
}
}
return CVoid.VOID;
}
@Override
public String getName() {
return "array_remove_values";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "void {array, value} Removes all instances of value from the specified array."
+ " For instance, array_remove_values(array(1, 2, 2, 3), 2) would produce the"
+ " array(1, 3). Note that it returns void however, so it will simply in place"
+ " modify the array passed in, much like array_remove.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "assign(@array, array(1, 2, 2, 3))\nmsg(@array)\narray_remove_values(@array, 2)\nmsg(@array)"),
};
}
}
@api public static class array_indexes extends AbstractFunction implements Optimizable {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
if(!(args[0] instanceof CArray)){
throw new CRECastException("Expected parameter 1 to be an array, but was " + args[0].val(), t);
}
return ((CArray)args[0]).indexesOf(args[1]);
}
@Override
public String getName() {
return "array_indexes";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "array {array, value} Returns an array with all the keys of the specified array"
+ " at which the specified value is equal. That is, for the array(1, 2, 2, 3), if"
+ " value were 2, would return array(1, 2). If the value cannot be found in the"
+ " array at all, an empty array will be returned.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "assign(@array, array(1, 2, 2, 3))\nmsg(array_indexes(@array, 2))"),
new ExampleScript("Not found", "assign(@array, array(1, 2, 2, 3))\nmsg(array_indexes(@array, 5))"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api public static class array_index extends AbstractFunction{
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray ca = (CArray)new array_indexes().exec(t, environment, args);
if(ca.isEmpty()){
return CNull.NULL;
} else {
return ca.get(0, t);
}
}
@Override
public String getName() {
return "array_index";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "mixed {array, value} Works exactly like array_indexes(array, value)[0], except in the case where"
+ " the value is not found, returns null. That is to say, if the value is contained in an"
+ " array (even multiple times) the index of the first element is returned.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "assign(@array, array(1, 2, 2, 3))\nmsg(array_index(@array, 2))"),
new ExampleScript("Not found", "assign(@array, array(1, 2, 2, 3))\nmsg(array_index(@array, 5))"),
};
}
}
@api
public static class array_last_index extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray ca = (CArray)new array_indexes().exec(t, environment, args);
if(ca.isEmpty()){
return CNull.NULL;
} else {
return ca.get(ca.size() - 1, t);
}
}
@Override
public String getName() {
return "array_last_index";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "mixed {array, value} Finds the index in the array where value occurs last. If"
+ " the value is not found, returns null. That is to say, if the value is contained in an"
+ " array (even multiple times) the index of the last element is returned.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "assign(@array, array(1, 2, 2, 3))\nmsg(array_last_index(@array, 2))"),
new ExampleScript("Not found", "assign(@array, array(1, 2, 2, 3))\nmsg(array_last_index(@array, 5))"),
};
}
}
@api
public static class array_reverse extends AbstractFunction{
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
if(args[0] instanceof CArray){
((CArray)args[0]).reverse(t);
}
return CVoid.VOID;
}
@Override
public String getName() {
return "array_reverse";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "void {array} Reverses an array in place. However, if the array is associative, throws a CastException, since associative"
+ " arrays are more like a map.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "assign(@array, array(1, 2, 3))\nmsg(@array)\narray_reverse(@array)\nmsg(@array)"),
new ExampleScript("Failure", "assign(@array, array(one: 1, two: 2))\narray_reverse(@array)")
};
}
}
@api public static class array_rand extends AbstractFunction implements Optimizable {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRERangeException.class, CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
Random r = new Random(System.currentTimeMillis());
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
long number = 1;
boolean getKeys = true;
CArray array = Static.getArray(args[0], t);
CArray newArray = new CArray(t);
if(array.isEmpty()){
return newArray;
}
if(args.length > 1){
number = Static.getInt(args[1], t);
}
if(number < 1){
throw new CRERangeException("number may not be less than 1.", t);
}
if(number > Integer.MAX_VALUE){
throw new CRERangeException("Overflow detected. Number cannot be larger than " + Integer.MAX_VALUE, t);
}
if(args.length > 2){
getKeys = Static.getBoolean(args[2]);
}
LinkedHashSet<Integer> randoms = new LinkedHashSet<Integer>();
while(randoms.size() < number){
randoms.add(java.lang.Math.abs(r.nextInt() % (int)array.size()));
}
List<Construct> keySet = new ArrayList<Construct>(array.keySet());
for(Integer i : randoms){
if(getKeys){
newArray.push(keySet.get(i), t);
} else {
newArray.push(array.get(keySet.get(i), t), t);
}
}
return newArray;
}
@Override
public String getName() {
return "array_rand";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1, 2, 3};
}
@Override
public String docs() {
return "array {array, [number, [getKeys]]} Returns a random selection of keys or values from an array. The array may be"
+ " either normal or associative. Number defaults to 1, and getKey defaults to true. If number is greater than"
+ " the size of the array, a RangeException is thrown. No value will be returned twice from the array however, one it"
+ " is \"drawn\" from the array, it is not placed back in. The order of the elements in the array will also be random,"
+ " if order is important, use array_sort().";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Usage with a normal array", "assign(@array, array('a', 'b', 'c', 'd', 'e'))\nmsg(array_rand(@array))", "{1}"),
new ExampleScript("Usage with a normal array, using getKeys false, and returning 2 results",
"assign(@array, array('a', 'b', 'c', 'd', 'e'))\nmsg(array_rand(@array, 2, false))", "{b, c}"),
new ExampleScript("Usage with an associative array",
"assign(@array, array(one: 'a', two: 'b', three: 'c', four: 'd', five: 'e'))\nmsg(array_rand(@array))", "two"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_unique extends AbstractFunction implements Optimizable {
private final static equals equals = new equals();
private final static BasicLogic.sequals sequals = new BasicLogic.sequals();
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public CArray exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
boolean compareTypes = true;
if(args.length == 2){
compareTypes = Static.getBoolean(args[1]);
}
final boolean fCompareTypes = compareTypes;
if(array.inAssociativeMode()){
return array.clone();
} else {
List<Construct> asList = array.asList();
CArray newArray = new CArray(t);
Set<Construct> set = new LinkedComparatorSet<Construct>(asList, new LinkedComparatorSet.EqualsComparator<Construct>() {
@Override
public boolean checkIfEquals(Construct item1, Construct item2) {
return (fCompareTypes && Static.getBoolean(sequals.exec(t, environment, item1, item2)))
|| (!fCompareTypes && Static.getBoolean(equals.exec(t, environment, item1, item2)));
}
});
for(Construct c : set){
newArray.push(c, t);
}
return newArray;
}
}
@Override
public String getName() {
return "array_unique";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1, 2};
}
@Override
public String docs() {
return "array {array, [compareTypes]} Removes all non-unique values from an array. ---- compareTypes is true by default, which means that in the array"
+ " array(1, '1'), nothing would be removed from the array, since both values are different data types. However, if compareTypes is false,"
+ " then the first value would remain, but the second value would be removed. A new array is returned. If the array is associative, by definition,"
+ " there are no unique values, so a clone of the array is returned.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "array_unique(array(1, 2, 2, 3, 4))"),
new ExampleScript("No removal of different datatypes", "array_unique(array(1, '1'))"),
new ExampleScript("Removal of different datatypes, by setting compareTypes to false", "array_unique(array(1, '1'), false)"),
};
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS);
}
}
@api
public static class array_filter extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
ArrayAccess array;
CClosure closure;
if(!(args[0] instanceof ArrayAccess)){
throw new CRECastException("Expecting an array for argument 1", t);
}
if(!(args[1] instanceof CClosure)){
throw new CRECastException("Expecting a closure for argument 2", t);
}
array = (ArrayAccess) args[0];
closure = (CClosure) args[1];
CArray newArray;
if(array.isAssociative()){
newArray = CArray.GetAssociativeArray(t);
for(Construct key : array.keySet()){
Construct value = array.get(key, t);
Construct ret = null;
try {
closure.execute(key, value);
} catch(FunctionReturnException ex){
ret = ex.getReturn();
}
if(ret == null){
ret = CBoolean.FALSE;
}
boolean bret = Static.getBoolean(ret);
if(bret){
newArray.set(key, value, t);
}
}
} else {
newArray = new CArray(t);
for(int i = 0; i < array.size(); i++){
Construct key = new CInt(i, t);
Construct value = array.get(i, t);
Construct ret = null;
try {
closure.execute(key, value);
} catch(FunctionReturnException ex){
ret = ex.getReturn();
}
if(ret == null){
ret = CBoolean.FALSE;
}
boolean bret = Static.getBoolean(ret);
if(bret){
newArray.push(value, t);
}
}
}
return newArray;
}
@Override
public String getName() {
return "array_filter";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2, 3};
}
@Override
public String docs() {
return "array {array, boolean closure(key, value)} Filters an array by callback. The items in the array are iterated over, each"
+ " one sent to the closure one at a time, as key, value. The closure should return true if the item should be included in the array,"
+ " or false if not. The filtered array is then returned by the function. If the array is associative, the keys will continue"
+ " to map to the same values, however a normal array, the values are simply pushed onto the new array, and won't correspond"
+ " to the same values per se.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Pulls out only the odd numbers", "@array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);\n"
+ "@newArray = array_filter(@array, closure(@key, @value){\n"
+ "\treturn(@value % 2 == 1);\n"
+ "});\n"
+ "msg(@newArray);\n"),
new ExampleScript("Pulls out only the odd numbers in an associative array",
"@array = array('one': 1, 'two': 2, 'three': 3, 'four': 4);\n"
+ "@newArray = array_filter(@array, closure(@key, @value){\n"
+ "\treturn(@value % 2 == 1);\n"
+ "});\n"
+ "msg(@newArray);\n")
};
}
}
@api
public static class array_deep_clone extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
if(args.length != 1) {
throw new CREInsufficientArgumentsException("Expecting exactly one argument", t);
}
if(!(args[0] instanceof CArray)) {
throw new CRECastException("Expecting argument 1 to be an array", t);
}
return ((CArray) args[0]).deepClone(t);
}
@Override
public String getName() {
return "array_deep_clone";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "array {array} Performs a deep clone on an array (as opposed to a shallow clone). This is useful"
+ " for multidimensional arrays. See the examples for more info.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates that the array is cloned.",
"@array = array(1, 2, 3, 4)\n" +
"@deepClone = array_deep_clone(@array)\n" +
"@deepClone[1] = 'newValue'\n" +
"msg(@array)\nmsg(@deepClone)"),
new ExampleScript("Demonstrated that arrays within the array are also cloned by a deep clone.",
"@array = array(array('value'))\n" +
"@deepClone = array_deep_clone(@array)\n" +
"@deepClone[0][0] = 'newValue'\n" +
"msg(@array)\nmsg(@deepClone)")
};
}
}
@api
public static class array_shallow_clone extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
if(args.length != 1) {
throw new CREInsufficientArgumentsException("Expecting exactly one argument", t);
}
if(!(args[0] instanceof CArray)) {
throw new CRECastException("Expecting argument 1 to be an array", t);
}
CArray array = (CArray) args[0];
CArray shallowClone = (array.isAssociative() ? CArray.GetAssociativeArray(t) : new CArray(t));
for(Construct key : array.keySet()) {
shallowClone.set(key, array.get(key, t), t);
}
return shallowClone;
}
@Override
public String getName() {
return "array_shallow_clone";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "array {array} Performs a shallow clone on an array (as opposed to a deep clone). See the examples for more info.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates that the array is cloned.",
"@array = array(1, 2, 3, 4)\n" +
"@shallowClone = array_shallow_clone(@array)\n" +
"@shallowClone[1] = 'newValue'\n" +
"msg(@array)\nmsg(@shallowClone)"),
new ExampleScript("Demonstrated that arrays within the array are not cloned by a shallow clone.",
"@array = array(array('value'))\n" +
"@shallowClone = array_shallow_clone(@array)\n" +
"@shallowClone[0][0] = 'newValue'\n" +
"msg(@array)\nmsg(@shallowClone)")
};
}
}
@api
public static class array_iterate extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
CClosure closure = Static.getObject(args[1], t, CClosure.class);
for(Construct key : array.keySet()){
try {
closure.execute(key, array.get(key, t));
} catch(ProgramFlowManipulationException ex){
// Ignored
}
}
return array;
}
@Override
public String getName() {
return "array_iterate";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "array {array, closure} Iterates across an array, calling the closure for each value of the array. The closure"
+ " should accept two arguments, the key and the value."
+ " This method can be used in some code to increase readability, to increase re-usability, or keep variables"
+ " created in a loop in an isolated scope. Note that this runs at approximately the same speed as a for loop,"
+ " which is slower than a foreach loop. Any values returned from the closure are silently ignored. Returns a"
+ " reference to the original array.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic use with normal arrays", "@array = array(1, 2, 3);\n"
+ "array_iterate(@array, closure(@key, @value){\n"
+ "\tmsg(@value);\n"
+ "});"),
new ExampleScript("Use with associative arrays", "@array = array(one: 1, two: 2, three: 3);\n"
+ "array_iterate(@array, closure(@key, @value){\n"
+ "\tmsg(\"@key: @value\");\n"
+ "});")
};
}
}
@api
public static class array_reduce extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREIllegalArgumentException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
CClosure closure = Static.getObject(args[1], t, CClosure.class);
if(array.isEmpty()){
return CNull.NULL;
}
if(array.size() == 1){
// This line looks bad, but all it does is return the first (and since we know only) value in the array,
// whether or not it is associative or normal.
return array.get(array.keySet().toArray(new Construct[0])[0], t);
}
List<Construct> keys = new ArrayList<>(array.keySet());
Construct lastValue = array.get(keys.get(0), t);
for(int i = 1; i < keys.size(); ++i){
boolean hadReturn = false;
try {
closure.execute(lastValue, array.get(keys.get(i), t));
} catch(FunctionReturnException ex){
lastValue = ex.getReturn();
if(lastValue instanceof CVoid){
throw new CREIllegalArgumentException("The closure passed to " + getName() + " cannot return void.", t);
}
hadReturn = true;
}
if(!hadReturn){
throw new CREIllegalArgumentException("The closure passed to " + getName() + " must return a value, but one was not returned.", t);
}
}
return lastValue;
}
@Override
public String getName() {
return "array_reduce";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "mixed {array, closure} Reduces an array to a single value. This is useful for, for instance, summing the"
+ " values of an array. The previously calculated value, then the next value of the array are sent"
+ " to the closure, which is expected to return a value, based on the two values, which will be sent"
+ " again to the closure as the new calculated value. If the array is empty, null is returned, and if"
+ " the array has exactly one value in it, only that value is returned. Associative arrays are supported,"
+ " but the order is based on the key order, which may not be as expected. The keys of the array are ignored.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Summing the values of an array", "@array = array(1, 2, 4, 8);\n"
+ "@sum = array_reduce(@array, closure(@soFar, @next){\n"
+ "\treturn(@soFar + @next);\n"
+ "});\n"
+ "msg(@sum);"),
new ExampleScript("Combining the strings in an array", "@array = array('a', 'b', 'c');\n"
+ "@string = array_reduce(@array, closure(@soFar, @next){\n"
+ "\treturn(@soFar . @next);\n"
+ "});\n"
+ "msg(@string);")
};
}
}
@api
public static class array_reduce_right extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREIllegalArgumentException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
CClosure closure = Static.getObject(args[1], t, CClosure.class);
if(array.isEmpty()){
return CNull.NULL;
}
if(array.size() == 1){
// This line looks bad, but all it does is return the first (and since we know only) value in the array,
// whether or not it is associative or normal.
return array.get(array.keySet().toArray(new Construct[0])[0], t);
}
List<Construct> keys = new ArrayList<>(array.keySet());
Construct lastValue = array.get(keys.get(keys.size() - 1), t);
for(int i = keys.size() - 2; i >= 0; --i){
boolean hadReturn = false;
try {
closure.execute(lastValue, array.get(keys.get(i), t));
} catch(FunctionReturnException ex){
lastValue = ex.getReturn();
if(lastValue instanceof CVoid){
throw new CREIllegalArgumentException("The closure passed to " + getName() + " cannot return void.", t);
}
hadReturn = true;
}
if(!hadReturn){
throw new CREIllegalArgumentException("The closure passed to " + getName() + " must return a value, but one was not returned.", t);
}
}
return lastValue;
}
@Override
public String getName() {
return "array_reduce_right";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "mixed {array, closure} Reduces an array to a single value. This works in reverse of"
+ " array_reduce. This is useful for, for instance, summing the"
+ " values of an array. The previously calculated value, then the previous value of the array are sent"
+ " to the closure, which is expected to return a value, based on the two values, which will be sent"
+ " again to the closure as the new calculated value. If the array is empty, null is returned, and if"
+ " the array has exactly one value in it, only that value is returned. Associative arrays are supported,"
+ " but the order is based on the key order, which may not be as expected. The keys of the array are ignored.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Summing the values of an array", "@array = array(1, 2, 4, 8);\n"
+ "@sum = array_reduce_right(@array, closure(@soFar, @next){\n"
+ "\treturn(@soFar + @next);\n"
+ "});\n"
+ "msg(@sum);"),
new ExampleScript("Combining the strings in an array", "@array = array('a', 'b', 'c');\n"
+ "@string = array_reduce_right(@array, closure(@soFar, @next){\n"
+ "\treturn(@soFar . @next);\n"
+ "});\n"
+ "msg(@string);")
};
}
}
@api
public static class array_every extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
CClosure closure = Static.getObject(args[1], t, CClosure.class);
for(Construct c : array.keySet()){
boolean hasReturn = false;
try {
closure.execute(array.get(c, t));
} catch(FunctionReturnException ex){
hasReturn = true;
boolean ret = Static.getBoolean(ex.getReturn());
if(ret == false){
return CBoolean.FALSE;
}
}
if(!hasReturn){
throw new CREIllegalArgumentException("The closure passed to " + getName() + " must return a boolean.", t);
}
}
return CBoolean.TRUE;
}
@Override
public String getName() {
return "array_every";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "boolean {array, closure} Returns true if every value in the array meets some test, which the closure"
+ " should return true or false about. Not all values will necessarily be checked, once a value is"
+ " determined to fail the check, execution is stopped, and false is returned. The closure will be"
+ " passed each value in the array, one at a time, and must return a boolean.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "@array = array(1, 3, 5);\n"
+ "@arrayIsAllOdds = array_every(@array, closure(@value){\n"
+ "\treturn(@value % 2 == 1);\n"
+ "});\n"
+ "msg(@arrayIsAllOdds);"),
new ExampleScript("Basic usage, with false condition", "@array = array(1, 3, 4);\n"
+ "@arrayIsAllOdds = array_every(@array, closure(@value){\n"
+ "\treturn(@value % 2 == 1);\n"
+ "});\n"
+ "msg(@arrayIsAllOdds);")
};
}
}
@api
public static class array_some extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
CClosure closure = Static.getObject(args[1], t, CClosure.class);
for(Construct c : array.keySet()){
boolean hasReturn = false;
try {
closure.execute(array.get(c, t));
} catch(FunctionReturnException ex){
hasReturn = true;
boolean ret = Static.getBoolean(ex.getReturn());
if(ret == true){
return CBoolean.TRUE;
}
}
if(!hasReturn){
throw new CREIllegalArgumentException("The closure passed to " + getName() + " must return a boolean.", t);
}
}
return CBoolean.FALSE;
}
@Override
public String getName() {
return "array_some";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "boolean {array, closure} Returns true if any value in the array meets some test, which the closure"
+ " should return true or false about. Not all values will necessarily be checked, once a value is"
+ " determined to pass the check, execution is stopped, and true is returned. The closure will be"
+ " passed each value in the array, one at a time, and must return a boolean.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "@array = array(2, 4, 8);\n"
+ "@arrayHasOdds = array_some(@array, closure(@value){\n"
+ "\treturn(@value % 2 == 1);\n"
+ "});\n"
+ "msg(@arrayHasOdds);"),
new ExampleScript("Basic usage, with true condition", "@array = array(2, 3, 4);\n"
+ "@arrayHasOdds = array_some(@array, closure(@value){\n"
+ "\treturn(@value % 2 == 1);\n"
+ "});\n"
+ "msg(@arrayHasOdds);")
};
}
}
@api
public static class array_map extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREIllegalArgumentException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray array = Static.getArray(args[0], t);
CClosure closure = Static.getObject(args[1], t, CClosure.class);
CArray newArray = (array.isAssociative()?CArray.GetAssociativeArray(t):new CArray(t, (int)array.size()));
for(Construct c : array.keySet()){
boolean hasReturn = false;
try {
closure.execute(array.get(c, t));
} catch(FunctionReturnException ex){
hasReturn = true;
newArray.set(c, ex.getReturn(), t);
}
if(!hasReturn){
throw new CREIllegalArgumentException("The closure passed to " + getName() + " must return a value.", t);
}
}
return newArray;
}
@Override
public String getName() {
return "array_map";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "array {array, closure} Calls the closure on each element of an array, and returns an array that contains the results.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Basic usage", "@areaOfSquare = closure(@sideLength){\n"
+ "\treturn(@sideLength ** 2);\n"
+ "};\n"
+ "// A collection of square sides\n"
+ "@squares = array(1, 4, 8);\n"
+ "@areas = array_map(@squares, @areaOfSquare);\n"
+ "msg(@areas);")
};
}
}
}