From 8f4017eb686796e8d0077b4169bedc6e4edcd3db Mon Sep 17 00:00:00 2001 From: EntryPoint Date: Sat, 24 Dec 2016 21:04:46 +0900 Subject: [PATCH 01/10] Add array_subset_of function --- .../core/functions/ArrayHandling.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java b/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java index 31606f94b..a743291d7 100644 --- a/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java @@ -3009,6 +3009,120 @@ public ExampleScript[] examples() throws ConfigCompileException { + "msg(@areas);") }; } + + @api + public static class array_subset_of extends AbstractFunction { + @Override + public Version since() { + return CHVersion.V3_3_2; + } + + @Override + public String getName() { + return "array_subset_of"; + } + + @Override + public Integer[] numArgs() { + return new Integer[] {2}; + } + + @Override + public Class[] thrown() { + return new Class[] {CREIllegalArgumentException.class}; + } + + @Override + public String docs() { + return "boolean {array, array} " + + "Returns true if first array is a subset of second array."; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + @Override + public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + Construct constA = args[0]; + Construct constB = args[1]; + if (!(constA instanceof CArray)) { + throw new CREIllegalArgumentException("Expecting an array, but received " + constA, t); + } + if (!(constB instanceof CArray)) { + throw new CREIllegalArgumentException("Expecting an array, but received " + constB, t); + } + return CBoolean.get(subsetOf(constA, constB, t)); + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[] { + new ExampleScript("Basic usage", + "@arrayA = array(0, 1)\n" + + "@arrayB = array(0, 1, 5, 9)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Basic usage", + "@arrayA = array(0, 1)\n" + + "@arrayB = array(0, 2, 5, 9)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Mix array", + "@arrayA = array(a: 1, b: array(one, two))\n" + + "@arrayB = array(a: 1, b: array(one, two, three), c: 3)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Mix array", + "@arrayA = array(a: 1, b: array(one, two))\n" + + "@arrayB = array(a: 1, b: array(two, one, three), c: 3)\n" + + "array_subset_of(@arrayA, @arrayB)") + }; + } + + public boolean subsetOf(Construct constA, Construct constB, Target t) { + if (constA.getCType() != constB.getCType()) { + return false; + } + if (constA instanceof CArray) { + CArray arrA = (CArray) constA; + CArray arrB = (CArray) constB; + if (arrA.isAssociative() != arrB.isAssociative()) { + return false; + } + if (arrA.isAssociative()) { + for (String key : arrA.stringKeySet()) { + if (!arrB.containsKey(key)) { + return false; + } + Construct eltA = arrA.get(key, t); + Construct eltB = arrB.get(key, t); + if (!subsetOf(eltA, eltB, t)) { + return false; + } + } + } else { + for (int i = 0; i < arrA.size(); i++) { + if (!arrB.containsKey(i)) { + return false; + } + Construct eltA = arrA.get(i, t); + Construct eltB = arrB.get(i, t); + if (!subsetOf(eltA, eltB, t)) { + return false; + } + } + } + } else { + if (!equals.doEquals(constA, constB)) { + return false; + } + } + return true; + } + } } } From 49efa63d1db4cd162dfdac8403a3a95940c2a86c Mon Sep 17 00:00:00 2001 From: EntryPoint Date: Sat, 24 Dec 2016 21:19:56 +0900 Subject: [PATCH 02/10] Fix paste mistake --- .../core/functions/ArrayHandling.java | 194 +++++++++--------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java b/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java index a743291d7..2098efd9d 100644 --- a/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java @@ -3009,120 +3009,120 @@ public ExampleScript[] examples() throws ConfigCompileException { + "msg(@areas);") }; } - - @api - public static class array_subset_of extends AbstractFunction { - @Override - public Version since() { - return CHVersion.V3_3_2; - } + } - @Override - public String getName() { - return "array_subset_of"; - } + @api + public static class array_subset_of extends AbstractFunction { + @Override + public Version since() { + return CHVersion.V3_3_2; + } - @Override - public Integer[] numArgs() { - return new Integer[] {2}; - } + @Override + public String getName() { + return "array_subset_of"; + } - @Override - public Class[] thrown() { - return new Class[] {CREIllegalArgumentException.class}; - } + @Override + public Integer[] numArgs() { + return new Integer[] {2}; + } - @Override - public String docs() { - return "boolean {array, array} " + - "Returns true if first array is a subset of second array."; - } + @Override + public Class[] thrown() { + return new Class[] {CREIllegalArgumentException.class}; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public String docs() { + return "boolean {array, array} " + + "Returns true if first array is a subset of second array."; + } - @Override - public Boolean runAsync() { - return null; + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + Construct constA = args[0]; + Construct constB = args[1]; + if (!(constA instanceof CArray)) { + throw new CREIllegalArgumentException("Expecting an array, but received " + constA, t); + } + if (!(constB instanceof CArray)) { + throw new CREIllegalArgumentException("Expecting an array, but received " + constB, t); } + return CBoolean.get(subsetOf(constA, constB, t)); + } - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - Construct constA = args[0]; - Construct constB = args[1]; - if (!(constA instanceof CArray)) { - throw new CREIllegalArgumentException("Expecting an array, but received " + constA, t); - } - if (!(constB instanceof CArray)) { - throw new CREIllegalArgumentException("Expecting an array, but received " + constB, t); - } - return CBoolean.get(subsetOf(constA, constB, t)); - } - - @Override - public ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[] { - new ExampleScript("Basic usage", - "@arrayA = array(0, 1)\n" + - "@arrayB = array(0, 1, 5, 9)\n" + - "array_subset_of(@arrayA, @arrayB)"), - new ExampleScript("Basic usage", - "@arrayA = array(0, 1)\n" + - "@arrayB = array(0, 2, 5, 9)\n" + - "array_subset_of(@arrayA, @arrayB)"), - new ExampleScript("Mix array", - "@arrayA = array(a: 1, b: array(one, two))\n" + - "@arrayB = array(a: 1, b: array(one, two, three), c: 3)\n" + - "array_subset_of(@arrayA, @arrayB)"), - new ExampleScript("Mix array", - "@arrayA = array(a: 1, b: array(one, two))\n" + - "@arrayB = array(a: 1, b: array(two, one, three), c: 3)\n" + - "array_subset_of(@arrayA, @arrayB)") - }; - } - - public boolean subsetOf(Construct constA, Construct constB, Target t) { - if (constA.getCType() != constB.getCType()) { + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[] { + new ExampleScript("Basic usage", + "@arrayA = array(0, 1)\n" + + "@arrayB = array(0, 1, 5, 9)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Basic usage", + "@arrayA = array(0, 1)\n" + + "@arrayB = array(0, 2, 5, 9)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Mix array", + "@arrayA = array(a: 1, b: array(one, two))\n" + + "@arrayB = array(a: 1, b: array(one, two, three), c: 3)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Mix array", + "@arrayA = array(a: 1, b: array(one, two))\n" + + "@arrayB = array(a: 1, b: array(two, one, three), c: 3)\n" + + "array_subset_of(@arrayA, @arrayB)") + }; + } + + public boolean subsetOf(Construct constA, Construct constB, Target t) { + if (constA.getCType() != constB.getCType()) { + return false; + } + if (constA instanceof CArray) { + CArray arrA = (CArray) constA; + CArray arrB = (CArray) constB; + if (arrA.isAssociative() != arrB.isAssociative()) { return false; } - if (constA instanceof CArray) { - CArray arrA = (CArray) constA; - CArray arrB = (CArray) constB; - if (arrA.isAssociative() != arrB.isAssociative()) { - return false; - } - if (arrA.isAssociative()) { - for (String key : arrA.stringKeySet()) { - if (!arrB.containsKey(key)) { - return false; - } - Construct eltA = arrA.get(key, t); - Construct eltB = arrB.get(key, t); - if (!subsetOf(eltA, eltB, t)) { - return false; - } + if (arrA.isAssociative()) { + for (String key : arrA.stringKeySet()) { + if (!arrB.containsKey(key)) { + return false; } - } else { - for (int i = 0; i < arrA.size(); i++) { - if (!arrB.containsKey(i)) { - return false; - } - Construct eltA = arrA.get(i, t); - Construct eltB = arrB.get(i, t); - if (!subsetOf(eltA, eltB, t)) { - return false; - } + Construct eltA = arrA.get(key, t); + Construct eltB = arrB.get(key, t); + if (!subsetOf(eltA, eltB, t)) { + return false; } } } else { - if (!equals.doEquals(constA, constB)) { - return false; + for (int i = 0; i < arrA.size(); i++) { + if (!arrB.containsKey(i)) { + return false; + } + Construct eltA = arrA.get(i, t); + Construct eltB = arrB.get(i, t); + if (!subsetOf(eltA, eltB, t)) { + return false; + } } } - return true; + } else { + if (!equals.doEquals(constA, constB)) { + return false; + } } + return true; } } } From 59e83bacdfcb3a74dd625fb50cd0bee73290cfc3 Mon Sep 17 00:00:00 2001 From: Pieter12345 Date: Wed, 16 Aug 2017 22:04:04 +0200 Subject: [PATCH 03/10] Fixed ArrayOutOfBoundsEx in the compiler. When compiling "function(['bla'])" or similar code, the compiler would attempt to convert the ['bla'] to "array_get(...)". Because the actual array to get it from does not exist, it looks for this at index "-1", causing an uncaught ArrayOutOfBoundsException. This commit adds a check for this case and gives an expected compile exception instead. --- src/main/java/com/laytonsmith/core/MethodScriptCompiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java b/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java index b0b181447..7f9e47ec8 100644 --- a/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java +++ b/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java @@ -1071,7 +1071,7 @@ public static ParseTree compile(List stream) throws ConfigCompileExceptio int array = arrayStack.pop().get(); //index is the location of the first node with the index int index = array + 1; - if (!tree.hasChildren()) { + if (!tree.hasChildren() || array == -1) { throw new ConfigCompileException("Brackets are illegal here", t.target); } ParseTree myArray = tree.getChildAt(array); From 17ef0af6035af0d398827b925fcd5737fecf9f55 Mon Sep 17 00:00:00 2001 From: LadyCailin Date: Fri, 18 Aug 2017 15:18:07 +0200 Subject: [PATCH 04/10] Copy docs from NewCompiler to master --- .../core/functions/ArrayHandling.java | 5221 ++++++++--------- src/main/resources/docs/Annotations | 94 + src/main/resources/docs/BasicBuiltinTypes | 64 + src/main/resources/docs/Cross_Casting | 162 + src/main/resources/docs/DesignFAQ | 198 + src/main/resources/docs/Developer_Guide | 177 + src/main/resources/docs/Enums_and_Masks | 156 + src/main/resources/docs/Structs | 110 + src/main/resources/docs/WebTemplating | 619 ++ 9 files changed, 4188 insertions(+), 2613 deletions(-) create mode 100644 src/main/resources/docs/Annotations create mode 100644 src/main/resources/docs/BasicBuiltinTypes create mode 100644 src/main/resources/docs/Cross_Casting create mode 100644 src/main/resources/docs/DesignFAQ create mode 100644 src/main/resources/docs/Developer_Guide create mode 100644 src/main/resources/docs/Enums_and_Masks create mode 100644 src/main/resources/docs/Structs create mode 100644 src/main/resources/docs/WebTemplating diff --git a/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java b/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java index 2098efd9d..dc00c410b 100644 --- a/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/ArrayHandling.java @@ -58,3071 +58,3066 @@ @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 array 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 { + public static String docs() { + return "This class contains functions that provide a way to manipulate arrays. To create an array, use the array function." + + " For more detailed information on array usage, see the page on [[CommandHelper/Arrays|arrays]]"; + } - @Override - public String getName() { - return "array_size"; - } + @api + public static class array_size extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public String getName() { + return "array_size"; + } - @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 Integer[] numArgs() { + return new Integer[]{1}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 String docs() { - return "int {array} Returns the size of this array as an integer."; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public String docs() { + return "int {array} Returns the size of this array as an integer."; + } - @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 ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Demonstrates usage", "array_size(array(1, 2, 3, 4, 5));"), - }; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Demonstrates usage", "array_size(array(1, 2, 3, 4, 5));"),}; + } + @Override + public Set 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"; - } + @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 Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } + @Override + public String getName() { + return "array_get"; + } - @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]; + @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 { - index = new CSlice(0, -1, t); + 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); } - if (args.length >= 3) { - defaultConstruct = args[2]; + 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); + } } - - 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); - } - } + 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 new CRECastException("Argument 1 of array_get must be an array", t); + 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[] 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 Class[] thrown() { + return new Class[]{CRECastException.class, CREIndexOverflowException.class}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 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 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 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 optimizationOptions() { - return EnumSet.of( - OptimizationOption.OPTIMIZE_CONSTANT - ); - } + @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 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"; - } + @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 Integer[] numArgs() { - return new Integer[]{3}; - } + @Override + public String getName() { + return "array_set"; + } - @Override - public boolean useSpecialExec() { - return true; - } + @Override + public Integer[] numArgs() { + return new Integer[]{3}; + } - @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 boolean useSpecialExec() { + return true; + } - @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 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 Class[] thrown() { - return new Class[]{CRECastException.class, CREIndexOverflowException.class}; - } + @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 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 Class[] thrown() { + return new Class[]{CRECastException.class, CREIndexOverflowException.class}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 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 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);"), - }; - } + @Override + public Boolean runAsync() { + return null; } - @api - @seealso({array_set.class}) - public static class array_push extends AbstractFunction { + @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);"),}; + } + } - @Override - public String getName() { - return "array_push"; - } + @api + @seealso({array_set.class}) + public static class array_push extends AbstractFunction { - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public String getName() { + return "array_push"; + } - @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 Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 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 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);"), - }; - } + @Override + public Boolean runAsync() { + return null; } - @api - public static class array_insert extends AbstractFunction{ + @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);"),}; + } + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREIndexOverflowException.class}; - } + @api + public static class array_insert extends AbstractFunction { - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREIndexOverflowException.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 { - 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 Boolean runAsync() { + return null; + } - @Override - public String getName() { - return "array_insert"; - } + @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 Integer[] numArgs() { - return new Integer[]{3}; - } + @Override + public String getName() { + return "array_insert"; + } - @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 Integer[] numArgs() { + return new Integer[]{3}; + } - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } + @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 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);") - }; - } + @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"; - } + @api + @seealso({array_index_exists.class, array_scontains.class}) + public static class array_contains extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "array_contains"; + } - @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 Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 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 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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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)") + }; } - @api - @seealso({array_contains.class}) - public static class array_contains_ic extends AbstractFunction implements Optimizable { + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } + } - @Override - public String getName() { - return "array_contains_ic"; - } + @api + @seealso({array_contains.class}) + public static class array_contains_ic extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "array_contains_ic"; + } - @Override - public String docs() { - return "boolean {array, testValue} Works like array_contains, except the comparison ignores case."; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public String docs() { + return "boolean {array, testValue} Works like array_contains, except the comparison ignores case."; + } - @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[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 Boolean runAsync() { + return null; + } - @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 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 Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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')"),}; } - @api - @seealso({array_index_exists.class, array_contains.class}) - public static class array_scontains extends AbstractFunction implements Optimizable { + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } + } - @Override - public String getName() { - return "array_scontains"; - } + @api + @seealso({array_index_exists.class, array_contains.class}) + public static class array_scontains extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "array_scontains"; + } - @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 Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 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 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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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)") + }; } - @api - public static class array_index_exists extends AbstractFunction implements Optimizable { + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } + } - @Override - public String getName() { - return "array_index_exists"; - } + @api + public static class array_index_exists extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "array_index_exists"; + } - @Override - public String docs() { - return "boolean {array, index} Checks to see if the specified array has an element at index"; - } + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public String docs() { + return "boolean {array, index...} Checks to see if the specified array has an element at index. If more than one index is specified, then it" + + " recursively checks down nested arrays."; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public CHVersion since() { - return CHVersion.V3_1_2; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public CHVersion since() { + return CHVersion.V3_1_2; + } - @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 Boolean runAsync() { + return null; + } - @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 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 Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @Override + public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + if(children.size() < 2) { + throw new ConfigCompileException(getName() + " must have 2 or more arguments", t); + } + return null; } - @api - public static class array_resize extends AbstractFunction { + @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')"), + new ExampleScript("Demonstrates nested arrays", "// Check to make sure that @array['a']['b']['c'] would work\n" + + "array_index_exists(array(a: array(b: array(c: null))), 'a', 'b', 'c');") + }; + } - @Override - public String getName() { - return "array_resize"; - } + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS, OptimizationOption.OPTIMIZE_DYNAMIC); + } + } - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } + @api + public static class array_resize extends AbstractFunction { - @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 String getName() { + return "array_resize"; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 CHVersion since() { - return CHVersion.V3_2_0; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public boolean isRestricted() { + return false; + } - @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 CHVersion since() { + return CHVersion.V3_2_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);"), - }; - } + @Override + public Boolean runAsync() { + return null; } - @api - public static class range extends AbstractFunction implements Optimizable { + @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 String getName() { - return "range"; - } + @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);"),}; + } + } - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } + @api + public static class range extends AbstractFunction implements Optimizable { - @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 String getName() { + return "range"; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2, 3}; + } - @Override - public boolean isRestricted() { - return false; - } + @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 CHVersion since() { - return CHVersion.V3_2_0; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public boolean isRestricted() { + return false; + } - @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 CHVersion since() { + return CHVersion.V3_2_0; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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 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"; - } + @api + public static class array_keys extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public String getName() { + return "array_keys"; + } - @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 Integer[] numArgs() { + return new Integer[]{1}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 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 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 Boolean runAsync() { + return null; + } - @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 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 Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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'))"),}; } - @api - public static class array_normalize extends AbstractFunction implements Optimizable { + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } + } - @Override - public String getName() { - return "array_normalize"; - } + @api + public static class array_normalize extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public String getName() { + return "array_normalize"; + } - @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 Integer[] numArgs() { + return new Integer[]{1}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 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 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 Boolean runAsync() { + return null; + } - @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 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 Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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))"),}; } - @api - public static class array_merge extends AbstractFunction implements Optimizable { + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } + } - @Override - public String getName() { - return "array_merge"; - } + @api + public static class array_merge extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } + @Override + public String getName() { + return "array_merge"; + } - @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 Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } - @Override - public Class[] thrown() { - return new Class[]{CREInsufficientArgumentsException.class, CRECastException.class}; - } + @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 boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CREInsufficientArgumentsException.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 { - 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 Boolean runAsync() { + return null; + } - @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 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 Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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))"),}; } - @api - public static class array_remove extends AbstractFunction { + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } + } - @Override - public String getName() { - return "array_remove"; - } + @api + public static class array_remove extends AbstractFunction { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "array_remove"; + } - @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 Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRERangeException.class, CRECastException.class, CREPluginInternalException.class}; - } + @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 boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRERangeException.class, CRECastException.class, CREPluginInternalException.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 { - 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 Boolean runAsync() { + return null; + } - @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)"), - }; - } + @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; + } } - @api - @seealso({StringHandling.split.class, Regex.reg_split.class}) - public static class array_implode extends AbstractFunction { + @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)"),}; + } + } - @Override - public String getName() { - return "array_implode"; - } + @api + @seealso({StringHandling.split.class, Regex.reg_split.class}) + public static class array_implode extends AbstractFunction { - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2}; - } + @Override + public String getName() { + return "array_implode"; + } - @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 Integer[] numArgs() { + return new Integer[]{1, 2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.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 { - 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 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[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 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'), '-')"), - }; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_0; } - @api - public static class cslice extends AbstractFunction implements Optimizable { + @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'), '-')"),}; + } + } - @Override - public String getName() { - return "cslice"; - } + @api + public static class cslice extends AbstractFunction implements Optimizable { - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public String getName() { + return "cslice"; + } - @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 Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.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 new CSlice(Static.getInt(args[0], t), Static.getInt(args[1], t), t); - } + @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 { + return new CSlice(Static.getInt(args[0], t), Static.getInt(args[1], t), t); + } - @Override - public ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("Basic usage", "array(1, 2, 3)[cslice(0, 1)]"), - }; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "array(1, 2, 3)[cslice(0, 1)]"),}; + } + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); } - @api - public static class array_sort extends AbstractFunction implements Optimizable { + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREFormatException.class}; - } + @api + public static class array_sort extends AbstractFunction implements Optimizable { - @Override - public boolean isRestricted() { - return false; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.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 { - 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; - } + @Override + public Boolean runAsync() { + return null; + } - private CArray customSort(CArray ca, CClosure closure, Target t){ - if(ca.size() <= 1){ - return ca; - } + @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; + } - 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); - } + 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); + } - left = customSort(left, closure, t); - right = customSort(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; + } - return merge(left, right, closure, t); - } + @Override + public String getName() { + return "array_sort"; + } - 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 Integer[] numArgs() { + return new Integer[]{1, 2}; + } - @Override - public String getName() { - return "array_sort"; - } + @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 Integer[] numArgs() { - return new Integer[]{1, 2}; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @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 Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.OPTIMIZE_DYNAMIC + ); + } - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public ParseTree optimizeDynamic(Target t, List 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 Set optimizationOptions() { - return EnumSet.of( - OptimizationOption.OPTIMIZE_DYNAMIC - ); - } + @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 ParseTree optimizeDynamic(Target t, List 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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @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\");") - }; - } + @Override + public boolean isRestricted() { + return false; } - @api public static class array_sort_async extends AbstractFunction{ + @Override + public Boolean runAsync() { + return null; + } - RunnableQueue queue = new RunnableQueue("MethodScript-arraySortAsync"); - boolean started = false; + @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() { - private void startup(){ - if(!started){ - queue.invokeLater(null, 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 void run() { - //This warms up the queue. Apparently. - } - }); - StaticLayer.GetConvertor().addShutdownHook(new Runnable() { + @Override + public String getName() { + return "array_sort_async"; + } - @Override - public void run() { - queue.shutdown(); - started = false; - } - }); - started = true; - } - } + @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 Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public boolean isRestricted() { - return false; - } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_remove_values extends AbstractFunction { - @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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public String getName() { - return "array_sort_async"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } + @Override + public Boolean runAsync() { + return null; + } - @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 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 CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_remove_values"; + } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; } - @api public static class array_remove_values extends AbstractFunction{ + @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 Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public boolean isRestricted() { - return false; - } + @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)"),}; + } - @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)); - } - } - } + @api + public static class array_indexes extends AbstractFunction implements Optimizable { - return CVoid.VOID; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public String getName() { - return "array_remove_values"; - } + @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 "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 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 CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_indexes"; + } - @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)"), - }; - } + @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."; } - @api public static class array_indexes extends AbstractFunction implements Optimizable { + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 boolean isRestricted() { - return false; - } + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } - @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]); - } + @api + public static class array_index extends AbstractFunction { - @Override - public String getName() { - return "array_indexes"; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public boolean isRestricted() { + return false; + } - @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 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 { + CArray ca = (CArray) new array_indexes().exec(t, environment, args); + if (ca.isEmpty()) { + return CNull.NULL; + } else { + return ca.get(0, t); + } + } - @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 String getName() { + return "array_index"; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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."; } - @api public static class array_index extends AbstractFunction{ + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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))"),}; + } - @Override - public boolean isRestricted() { - return false; - } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_last_index extends AbstractFunction { - @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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public String getName() { - return "array_index"; - } + @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 "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 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 CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_last_index"; + } - @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))"), - }; - } + @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."; } - @api - public static class array_last_index extends AbstractFunction { + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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))"),}; + } - @Override - public boolean isRestricted() { - return false; - } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_reverse extends AbstractFunction { - @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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public String getName() { - return "array_last_index"; - } + @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 "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 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 CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_reverse"; + } - @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))"), - }; - } + @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."; } - @api - public static class array_reverse extends AbstractFunction{ + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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)") + }; + } - @Override - public boolean isRestricted() { - return false; - } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_rand extends AbstractFunction implements Optimizable { - @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 Class[] thrown() { + return new Class[]{CRERangeException.class, CRECastException.class}; + } - @Override - public String getName() { - return "array_reverse"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public Boolean runAsync() { + return null; + } - @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."; - } + 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 randoms = new LinkedHashSet(); + while (randoms.size() < number) { + randoms.add(java.lang.Math.abs(r.nextInt() % (int) array.size())); + } + List keySet = new ArrayList(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 CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_rand"; + } - @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)") - }; - } + @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()."; } - @api public static class array_rand extends AbstractFunction implements Optimizable { + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRERangeException.class, CRECastException.class}; - } + @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 boolean isRestricted() { - return false; - } + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_unique extends AbstractFunction implements Optimizable { - 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]); - } + private final static equals equals = new equals(); + private final static BasicLogic.sequals sequals = new BasicLogic.sequals(); - LinkedHashSet randoms = new LinkedHashSet(); - while(randoms.size() < number){ - randoms.add(java.lang.Math.abs(r.nextInt() % (int)array.size())); - } - List keySet = new ArrayList(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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public String getName() { - return "array_rand"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } + @Override + public Boolean runAsync() { + return null; + } - @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 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 asList = array.asList(); + CArray newArray = new CArray(t); + Set set = new LinkedComparatorSet(asList, new LinkedComparatorSet.EqualsComparator() { + + @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 CHVersion since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_unique"; + } - @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 Integer[] numArgs() { + return new Integer[]{1, 2}; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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."; } - @api - public static class array_unique extends AbstractFunction implements Optimizable { + @Override + public CHVersion since() { + return CHVersion.V3_3_1; + } - private final static equals equals = new equals(); - private final static BasicLogic.sequals sequals = new BasicLogic.sequals(); - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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 boolean isRestricted() { - return false; - } + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); + } - @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 asList = array.asList(); - CArray newArray = new CArray(t); - Set set = new LinkedComparatorSet(asList, new LinkedComparatorSet.EqualsComparator() { - - @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; - } - } + @api + public static class array_filter extends AbstractFunction { - @Override - public String getName() { - return "array_unique"; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2}; - } + @Override + public boolean isRestricted() { + return false; + } - @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 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 { + 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 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 String getName() { + return "array_filter"; + } - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.NO_SIDE_EFFECTS); - } + @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."; } - @api - public static class array_filter extends AbstractFunction { + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @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") + }; + } - @Override - public boolean isRestricted() { - return false; - } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_deep_clone extends AbstractFunction { - @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 Class[] thrown() { + return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; + } - @Override - public String getName() { - return "array_filter"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } + @Override + public Boolean runAsync() { + return null; + } - @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 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 Version since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_deep_clone"; + } - @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") - }; - } + @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."; } - @api - public static class array_deep_clone extends AbstractFunction { + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; - } + @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)") + }; + } - @Override - public boolean isRestricted() { - return false; - } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_shallow_clone extends AbstractFunction { - @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 Class[] thrown() { + return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; + } - @Override - public String getName() { - return "array_deep_clone"; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public Boolean runAsync() { + return null; + } - @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 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 Version since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_shallow_clone"; + } - @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)") - }; - } + @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."; } - - @api - public static class array_shallow_clone extends AbstractFunction { - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class}; - } + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public boolean isRestricted() { - return false; - } + @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)") + }; + } - @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; - } + @api + public static class array_iterate extends AbstractFunction { - @Override - public String getName() { - return "array_shallow_clone"; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + @Override + public boolean isRestricted() { + return false; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Version since() { - return CHVersion.V3_3_1; - } + @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 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)") - }; - } + @Override + public String getName() { + return "array_iterate"; + } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; } - @api - public static class array_iterate extends AbstractFunction { + @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 Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public boolean isRestricted() { - return false; - } + @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" + + "});") + }; + } - @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; - } + @api + public static class array_reduce extends AbstractFunction { - @Override - public String getName() { - return "array_iterate"; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREIllegalArgumentException.class}; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public boolean isRestricted() { + return false; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Version since() { - return CHVersion.V3_3_1; - } + @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 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 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" - + "});") - }; - } + @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."; } - @api - public static class array_reduce extends AbstractFunction { + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREIllegalArgumentException.class}; - } + @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);") + }; + } - @Override - public boolean isRestricted() { - return false; - } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_reduce_right extends AbstractFunction { - @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 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 Class[] thrown() { + return new Class[]{CRECastException.class, CREIllegalArgumentException.class}; + } - @Override - public String getName() { - return "array_reduce"; - } + @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 "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 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 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 Version since() { - return CHVersion.V3_3_1; - } + @Override + public String getName() { + return "array_reduce_right"; + } - @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);") - }; - } + @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."; } - @api - public static class array_reduce_right extends AbstractFunction { + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREIllegalArgumentException.class}; - } + @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);") + }; + } - @Override - public boolean isRestricted() { - return false; - } + } - @Override - public Boolean runAsync() { - return null; - } + @api + public static class array_every extends AbstractFunction { - @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 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 Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public String getName() { - return "array_reduce_right"; - } + @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 "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 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; + } } - - @Override - public Version since() { - return CHVersion.V3_3_1; + if (!hasReturn) { + throw new CREIllegalArgumentException("The closure passed to " + getName() + " must return a boolean.", t); } + } + return CBoolean.TRUE; + } - @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);") - }; - } + @Override + public String getName() { + return "array_every"; + } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; } - @api - public static class array_every extends AbstractFunction { + @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 Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @Override - public boolean isRestricted() { - return false; - } + @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);") + }; + } - @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; - } + @api + public static class array_some extends AbstractFunction { - @Override - public String getName() { - return "array_every"; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public boolean isRestricted() { + return false; + } - @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 Boolean runAsync() { + return null; + } - @Override - public Version since() { - return CHVersion.V3_3_1; + @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; + } } - - @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);") - }; + if (!hasReturn) { + throw new CREIllegalArgumentException("The closure passed to " + getName() + " must return a boolean.", t); } - + } + return CBoolean.FALSE; } - @api - public static class array_some extends AbstractFunction { - - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class}; - } + @Override + public String getName() { + return "array_some"; + } - @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 {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 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 Version since() { + return CHVersion.V3_3_1; + } - @Override - public String getName() { - return "array_some"; - } + @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);") + }; + } - @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."; - } + @api + public static class array_map extends AbstractFunction { - @Override - public Version since() { - return CHVersion.V3_3_1; - } + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREIllegalArgumentException.class}; + } - @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);") - }; - } + @Override + public boolean isRestricted() { + return false; + } + @Override + public Boolean runAsync() { + return null; } - @api - public static class array_map extends AbstractFunction { + @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())); - @Override - public Class[] thrown() { - return new Class[]{CRECastException.class, CREIllegalArgumentException.class}; + 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); } - - @Override - public boolean isRestricted() { - return false; + if (!hasReturn) { + throw new CREIllegalArgumentException("The closure passed to " + getName() + " must return a value.", t); } + } - @Override - public Boolean runAsync() { - return null; - } + return newArray; + } - @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); - } - } + @Override + public String getName() { + return "array_map"; + } - return newArray; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public String getName() { - return "array_map"; - } + @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 Integer[] numArgs() { - return new Integer[]{2}; - } + @Override + public Version since() { + return CHVersion.V3_3_1; + } - @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 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);") + }; + } + } - @Override - public Version since() { - return CHVersion.V3_3_1; - } + @api + public static class array_subset_of extends AbstractFunction { - @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);") - }; - } + @Override + public Version since() { + return CHVersion.V3_3_2; } - @api - public static class array_subset_of extends AbstractFunction { - @Override - public Version since() { - return CHVersion.V3_3_2; - } - - @Override - public String getName() { - return "array_subset_of"; - } + @Override + public String getName() { + return "array_subset_of"; + } - @Override - public Integer[] numArgs() { - return new Integer[] {2}; - } + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } - @Override - public Class[] thrown() { - return new Class[] {CREIllegalArgumentException.class}; - } + @Override + public Class[] thrown() { + return new Class[]{CREIllegalArgumentException.class}; + } - @Override - public String docs() { - return "boolean {array, array} " + - "Returns true if first array is a subset of second array."; - } + @Override + public String docs() { + return "boolean {array, array} " + + "Returns true if first array is a subset of second array."; + } - @Override - public boolean isRestricted() { - return false; - } + @Override + public boolean isRestricted() { + return false; + } - @Override - public Boolean runAsync() { - return null; - } + @Override + public Boolean runAsync() { + return null; + } - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - Construct constA = args[0]; - Construct constB = args[1]; - if (!(constA instanceof CArray)) { - throw new CREIllegalArgumentException("Expecting an array, but received " + constA, t); - } - if (!(constB instanceof CArray)) { - throw new CREIllegalArgumentException("Expecting an array, but received " + constB, t); - } - return CBoolean.get(subsetOf(constA, constB, t)); - } + @Override + public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + Construct constA = args[0]; + Construct constB = args[1]; + if (!(constA instanceof CArray)) { + throw new CREIllegalArgumentException("Expecting an array, but received " + constA, t); + } + if (!(constB instanceof CArray)) { + throw new CREIllegalArgumentException("Expecting an array, but received " + constB, t); + } + return CBoolean.get(subsetOf(constA, constB, t)); + } - @Override - public ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[] { - new ExampleScript("Basic usage", - "@arrayA = array(0, 1)\n" + - "@arrayB = array(0, 1, 5, 9)\n" + - "array_subset_of(@arrayA, @arrayB)"), - new ExampleScript("Basic usage", - "@arrayA = array(0, 1)\n" + - "@arrayB = array(0, 2, 5, 9)\n" + - "array_subset_of(@arrayA, @arrayB)"), - new ExampleScript("Mix array", - "@arrayA = array(a: 1, b: array(one, two))\n" + - "@arrayB = array(a: 1, b: array(one, two, three), c: 3)\n" + - "array_subset_of(@arrayA, @arrayB)"), - new ExampleScript("Mix array", - "@arrayA = array(a: 1, b: array(one, two))\n" + - "@arrayB = array(a: 1, b: array(two, one, three), c: 3)\n" + - "array_subset_of(@arrayA, @arrayB)") - }; - } + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", + "@arrayA = array(0, 1)\n" + + "@arrayB = array(0, 1, 5, 9)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Basic usage", + "@arrayA = array(0, 1)\n" + + "@arrayB = array(0, 2, 5, 9)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Mix array", + "@arrayA = array(a: 1, b: array(one, two))\n" + + "@arrayB = array(a: 1, b: array(one, two, three), c: 3)\n" + + "array_subset_of(@arrayA, @arrayB)"), + new ExampleScript("Mix array", + "@arrayA = array(a: 1, b: array(one, two))\n" + + "@arrayB = array(a: 1, b: array(two, one, three), c: 3)\n" + + "array_subset_of(@arrayA, @arrayB)") + }; + } - public boolean subsetOf(Construct constA, Construct constB, Target t) { - if (constA.getCType() != constB.getCType()) { - return false; - } - if (constA instanceof CArray) { - CArray arrA = (CArray) constA; - CArray arrB = (CArray) constB; - if (arrA.isAssociative() != arrB.isAssociative()) { - return false; - } - if (arrA.isAssociative()) { - for (String key : arrA.stringKeySet()) { - if (!arrB.containsKey(key)) { - return false; - } - Construct eltA = arrA.get(key, t); - Construct eltB = arrB.get(key, t); - if (!subsetOf(eltA, eltB, t)) { - return false; - } - } - } else { - for (int i = 0; i < arrA.size(); i++) { - if (!arrB.containsKey(i)) { - return false; - } - Construct eltA = arrA.get(i, t); - Construct eltB = arrB.get(i, t); - if (!subsetOf(eltA, eltB, t)) { - return false; - } - } - } - } else { - if (!equals.doEquals(constA, constB)) { - return false; - } - } - return true; - } + public boolean subsetOf(Construct constA, Construct constB, Target t) { + if (constA.getCType() != constB.getCType()) { + return false; + } + if (constA instanceof CArray) { + CArray arrA = (CArray) constA; + CArray arrB = (CArray) constB; + if (arrA.isAssociative() != arrB.isAssociative()) { + return false; + } + if (arrA.isAssociative()) { + for (String key : arrA.stringKeySet()) { + if (!arrB.containsKey(key)) { + return false; + } + Construct eltA = arrA.get(key, t); + Construct eltB = arrB.get(key, t); + if (!subsetOf(eltA, eltB, t)) { + return false; + } + } + } else { + for (int i = 0; i < arrA.size(); i++) { + if (!arrB.containsKey(i)) { + return false; + } + Construct eltA = arrA.get(i, t); + Construct eltB = arrB.get(i, t); + if (!subsetOf(eltA, eltB, t)) { + return false; + } + } + } + } else if (!equals.doEquals(constA, constB)) { + return false; + } + return true; } + } } diff --git a/src/main/resources/docs/Annotations b/src/main/resources/docs/Annotations new file mode 100644 index 000000000..ff8170960 --- /dev/null +++ b/src/main/resources/docs/Annotations @@ -0,0 +1,94 @@ +Annotations allow for meta data to be added to elements, and are used by the system +in various places, and can also be defined and used by your code as well. + +== Usage == +The basic syntax for an annotation uses the @{} syntax. If the annotation +were named Annotation, and you were tagging a variable declaration, it might look like this: + +
+@{Annotation}
+string @s = 'The string';
+
+ +Annotations may also have annotation parameters. The parameters must be immutable, +and fully defined at compile time, which means that you can either hardcode the values +in, or use an immutable variable. If the parameter takes an array, and you only +have one value in the array, you may simply provide the single element. Additionally, +if the annotation is defined with only one parameter, and that parameter's name is +'value', you may leave off the parameter name and it will be assigned to value, +otherwise you must label the parameters the same as you would when creating an array. +Here are some valid examples: + +
+# Assuming no parameters
+@{Annotation}
+# Also valid, for no parameters
+@{Annotation()}
+
+# Assuming the one parameter is to be assigned to 'value'
+@{Annotation('the value')}
+# The same thing, only explicit
+@{Annotation(value: 'the value')}
+
+# With multiple parameters
+@{Annotation(value: 'the value', number: 3)}
+
+# Assuming 'numbers' is an array
+@{Annotation(numbers: 3)}
+# Now with an actual array
+@{Annotation(numbers: array(1, 2, 3))}
+
+# With external, but constant variables
+immutable int @a = 4;
+@{Annotation(numbers: array(1, 2, 3, @a)}
+
+ +The key principal to take away here, is that annotations require immutable data, and +are ways to add meta information to your code. They do not "execute" ever, that is, +they are to be fully resolved at compile time, because in many cases, the compiler +itself uses the annotations itself to do certain things. + +== Defining custom annotations == +To define your own annotation, define the public values in an annotations +block in a class-like structure, and +include default values if you like. Methods may also be defined in the annotation, +but constructors are not allowed, therefore preventing direct instantiations of +an annotation. + +
+annotation Annotation {
+	annotations {
+		string @value = 'default',
+		int @number = 5,
+		array @numbers = array(1, 2, 3),
+	}
+}
+
+ +To use the annotation on an element, you must use the reflection methods. Since multiple +annotations may be present on an element, you must select the annotations specifically +(or you can iterate through all of them dynamically). Annotation parameters are immutable, +though they do not work exactly the same as immutable class types, because the default value +is not used if the value is provided by the user. + +
+@{Annotation(number: 10)}
+string @var = 'the string';
+
+Annotation @a = reflect_annotation(Annotation, @var);
+msg(@a->number) # msg's 10
+
+foreach(reflect_annotations(@var), @annotation){
+	msg(typeof(@annotation)) # In this case, "Annotation"
+}
+
+ +== Meta annotations == +Annotation declarations (not the annotation usages) can be themselves annotated +with various annotations. All annotations are available at runtime, so unlike Java, +there is no Retention annotation. There is however the ability to restrict ''where'' +an annotation is placed, based on the type of code structure being annotated. The +Target annotation, which takes a ElementType enum, can be used to restrict what elements +this annotation is added to. + +%%EnumDocs|ElementType%% diff --git a/src/main/resources/docs/BasicBuiltinTypes b/src/main/resources/docs/BasicBuiltinTypes new file mode 100644 index 000000000..570fb02eb --- /dev/null +++ b/src/main/resources/docs/BasicBuiltinTypes @@ -0,0 +1,64 @@ + +MethodScript has several "built in" types. They work just like user definable types, but it is worth mentioning them specifically, +since they form the basis of all objects. + +== Inheritance Tree == + +TODO: Add inheritance tree image + +== mixed == +Mixed is the root of all possible types. It is an interface, which defines a very few methods, which are common +to all objects, built in and user defined types. + +=== primitive === + +The primitive class defines all data types that can be "typed in" directly into code, so 'string', 5, 3.1415. + +==== string ==== + +A string is created in code with quotes, either single quotes or double quotes. 'string', for example. + +==== number ==== + +The number class is the super class for all data types that support numeric operations. + +===== double ===== + +A double is a floating point number. + +===== int ===== + +An int is a integral number. + +==== boolean ==== + +A boolean is a true/false value. + +=== array === + +An array is a "collection" of other data types. + +==== map ==== + +A map is a "dictionary" of string to mixed mappings. + +==== Object ==== + +An Object is the superclass of all user defined types, and is the first +type in the hierarchy that requires instantiation with the "new" keyword. + +== byte_array == + +A byte array wraps a native byte array object, to allow for more efficient +byte array manipulations, and to allow for translation to external processes +that communicate at the lowest level. + +== auto == + +The auto type isn't a type per se, it works as a bridge between scripting language +and compiled language. If a type is marked as auto, casting is not required, and +it will attempt to cast to the correct data type at runtime, and only throw +an exception then. This is discouraged from normal use, except in the case +of receiving user input, or very small one time scripts. Additionally, string +constants and numeric constants work as if they are declared as auto. Variables +declared as auto are eligible to use the cross-casting system. diff --git a/src/main/resources/docs/Cross_Casting b/src/main/resources/docs/Cross_Casting new file mode 100644 index 000000000..bf8638007 --- /dev/null +++ b/src/main/resources/docs/Cross_Casting @@ -0,0 +1,162 @@ +Cross casting is a feature of MethodScript that allows for rapid scripting, while +maintaining strong type safety elsewhere. Cross casting is less stringent than a +fully strongly typed system though, so it should be used sparingly, and only if +there is a strong case for automatic casting. Generally it should only be used to +cross cast from primitives, instead of other objects. The cross casting system goes hand +in hand with multiple inheritance, but is not to be confused with it. Cross casting +may be taken advantage of by using the auto keyword. + +== Cross Casting == + +As a simple example, we can consider the conversion of string to enum. Assume +we have the following declared: + +
+enum Compass {
+	enums {
+		NORTH, SOUTH, EAST, WEST
+	}
+}
+
+ +If the procedure _func accepts a single Compass argument, it is acceptable to +do _func('NORTH'), but it is not acceptable to first define a string, +then attempt to cross cast: + +
+string @val = 'NORTH';
+_func(@val); # Compile error, expecting Compass type, but found string
+
+auto @val2 = 'NORTH';
+_func(@val2); # Not a compile error, because cross casting occurs
+
+Compass @val3 = 'NORTH';
+_func(@val3);   # Also not a compile error, because cross casting occurs during 
+				# the declaration of @val3
+
+ +This is because while string constants are declared as auto, variables declared +as string are not. This is because when hardcoding the string in, it is quite +obvious what the intention is; you are intending for the string to take on the +enum constant value. However, when you declare it in a variable first, it is assumed +that the input is programmer specified, and therefore is not intended to be cross +cast. If cross casting were generally allowed, then obvious bugs like this: + +
+string @val = 'NOT A REAL ENUM';
+_func(@val); # Should be a compile error
+
+ +would necessarily have to be runtime errors. In this case, since the type is programmer +specified, it is reasonable to assume that it is meant to be used as a generic string, +not the enum type. Additionally, when hard coding strings or numbers, the compiler +can check to ensure that the type is a valid cross cast type, at compile time. + +== Cross Casting User Classes == + +You can also take advantage of the cross compiling system, should it suit your classes +needs. You declare your class to be cross compilable from various types, by annotating +it with the @{CrossCast} annotation. If this annotation is declared, you must override +the cast() method, (which should be protected) and return a new instance of the +object, given the type to convert. Say you have a class, Label, which you want to +be cross castable from a string. + +
+@{CrossCast(string | number)}
+public class Label {
+	/**
+	 * The type of @value must be the type of the disjoint of the values, or a superclass of that,
+	 * and the function must return the type of the class it is defined in.
+	 */
+	protected static Label cast(primitive @value){
+		if(@value instanceof number){
+			# You might do something different here
+			return(new Label(@value->toString());
+		} else if(@value instanceof string){
+			return new Label(@value->toString());
+		} else {
+			# This is a programmer error, because it is guaranteed that if the type is
+			# determined not to be one of the types string or number, this method will
+			# not have been called, and we don't normally have to consider those cases.
+			die('Programmer error! You forgot to handle the case of '.typeof(@value))
+		}
+	}
+}
+
+
+The compiler will attempt to check the validity of the cross casting at compile
+time, if the cast method is determined to be a constant expression, so you
+should keep the logic to a minimum to allow for the compile time error checking
+to work. Once this occurs, the following code is now valid:
+
+
+# Valid
+Label @l = 'This is a label';
+
+# Valid
+auto @s = 'This is a label';
+Label @l = @s;
+
+# Not valid
+string @s = 'This is a label';
+Label @l = @s; # Compile error, expected Label, but found string
+
+ +There is a caveat to this process. An object cannot cross cast an object of the +same type, or a supertype or subtype. Otherwise, this would make reference vs copy +ambiguous. Consider the following: + +
+@{CrossCast(array(Label, string))} # Actually a compile error
+class Label {
+	protected Label cast(mixed @l){
+		if(@l instanceof Label){
+			return(new Label(@l->getLabel()))
+		} else {
+			return(new Label(@l))
+		}
+	}
+	
+	public Label(string @l){
+		# ...
+	}
+}
+
+ +If this were allowed, this would make the following code ambiguous: + +
+Label @l = new Label('The label');
+Label @l2 = @l; # Are we meaning to create a new Label, or just point to the same reference?
+				# We simply declare that this is a pointer to the same object, since that is
+				# clearer.
+
+ +Because this code is at a glance unclear on whether or not we are actually trying to allocate space +for a new Label object, or simply point to the existing object, this is not allowed. +Supertypes are also restricted, because then both of these would be allowed: + +
+# Assume AbstractLabel is the superclass of Label, and 
+# AbstractLabelFactory::instance returns a new AbstractLabel of unknown type
+AbstractLabel @al = AbstractLabelFactory::instance('Label');
+
+Label @l1 = @al; # If you meant the second one, but did this instead, you get totally
+				 # different results. Therefore, this is not allowed
+Label @l2 = (Label)@al; # This is allowed though, because we are obviously just
+						# trying to cast to the Label type, from AbstractLabel
+
+ +== Cross Casting vs. Multiple Inheritance == + +In multiple inheritance, an object actually IS multiple other objects, there is no +conversion required. For instance, assume Label extended both class One and class Two. +Then the following code is not a cast of any sort, it's just a simple assignment. + +
+Label @l = new Label('');
+One @one = @l;
+Two @two = @l;
+
+ +This further demonstrates why cross casting to a super type isn't allowed. diff --git a/src/main/resources/docs/DesignFAQ b/src/main/resources/docs/DesignFAQ new file mode 100644 index 000000000..45e19db18 --- /dev/null +++ b/src/main/resources/docs/DesignFAQ @@ -0,0 +1,198 @@ +Design and History FAQ + +===Why are bare strings autoconcatenation supported?=== +Bare strings are a holdover from the initial design of CommandHelper; a simple +way to specify aliases. This is also why variables have an identifier character, +instead of just being a-z characters. For the time being, bare strings are allowed, +and likely always will be, but a strict mode has been introduced that fixes most +of the problems that come from this. Additionally, there is no reason not to support +autoconcatenation. Some languages actually do support this general idea +anyways, for instance, C. "Hello " "World" is the equivalent of the MethodScript +'Hello' 'World'. Granted, in C, this only works with string literals, not variables, +but the general idea is there. One thing that MethodScript has a distinct lack of +is formalized "statements". Consider the code: +
+@a = 10
+divide_by_2(@a) #Assume this returns 5
+
+ +If you were to wrap all that code in an output, you would actually get "10 5", +which is a side effect of the fact that when fully compiled, the code actually +looks like this: +
+sconcat(assign(@a, 10), divide_by_2(@a))
+
+ +Generally speaking, this output will be ignored, and so it doesn't really matter +that it is concatenated, then ignored. This ''does'' cause a performance hit however, +because the sconcat function does take a non-zero amount of time to run, +so at some point, this will likely be further optimized, but that requires strict +typing first, at which point it can be determined that some of the values in the +sconcat are not intended to be sconcated because they are functions that return void. +Alternatively, this feature may just entirely be deprecated except for the ability +to auto concatenate literals and variables, but this would only happen in strict +mode. + +===Why aren't there semicolons?=== + +Quite simply, they aren't yet needed. Take javascript for instance, it does not have +them either. Javascript will however "insert" them for you wherever you have +a newline. Eventually, MethodScript will require them in a very few cases, at which +point they will be introduced. Additionally, it will be possible to detect when an ambiguous +situation is present, and trigger a compile error, instead of using javascript's approach +and inferring where the seperators are needed. They will be required once execution +of closures via () is added. Consider the following theoretical syntax: + +
+@a = 5
+@func = closure(@b){
+	msg(@b + 10)
+}
+(++@a)
+
+ +When written like this, you might assume that the following is intended: +
+assign(@a, 5)
+assign(@func, closure(@b){
+	msg(@b + 10)
+})
+preinc(@a)
+
+ +However, once we rearrange the whitespace: + +
+@a = 5
+@func = closure(@b){
+	msg(@b + 10)
+}(++@a)
+
+ +Now it becomes less obvious whether or not we meant to do this instead: + +
+assign(@a, 5)
+call_function(assign(@func, closure(@b){
+	msg(@b + 10)
+}), preinc(@a))
+
+ +As you can see, this ambiguity is only introduced when you have several "operator-like" +features in the language; the code shown with only functions is not ambiguous at all, +so until those features are added, it is not necessary to require any sort of +statement separator. However, with the addition of a semicolon after the brace, this +is no longer ambiguous, and does not require any sort of weird parenthesation anymore: + +
+@a = 5
+@func = proc(@b){
+	msg(@b + 10)
+};
+(++@a)
+
+ +If the second usage is actually desired, the following would be required: + +
+@a = 5; #Note that we require this semicolon here now, one statement above, so it doesn't
+		#think we are trying to call @a()
+(@func = proc(@b){
+	msg(@b + 10)
+})
+(++@a)
+
+ +Besides making this more readable anyways, neither of these cases are ambiguious +anymore, and an error can be raised if a loose parenthetical is used alongside +an actual function, and it will only attempt to call the procedure if two loose +parentheticals are next to each other. + +Regardless, this change is still in the future, and may be changed depending on +what features actually get added, however, semicolons will be added long before +these changes, and will be highly encouraged, then required in all cases in strict mode. +Additionally, autoconcat may be removed after this change (in strict mode), and explicit concatenation +required. It is unlikely that these shorthand features will be available outside of +strict mode, which is an additional reason that strict mode being on is highly encouraged. + +===Why is everything a function?=== +When you boil it down to making everything a function, it becomes much easier to learn +for new programmers. The only syntax they have to learn is "function name", "parenthesis", +and "arguments", instead of having to learn brace syntax, array syntax operators, etc. +This allows for a much easier learning curve, even if it makes for less readable code. +Luckily, there is no reason that the code cannot be automatically refactored into using +brackets, operators, etc. The compiler internals have to do it one way, it's in fact +even easier to go the other way. So eventually, once all the tools are written for it, +a new developer will be able to "graduate" to the advanced syntax, and the tools +can automatically convert the existing code to the new format. + +Additionally, actually implementing the functions becomes much easier, because +even things like if() with bracket notation can use a the same functional style +syntax processor, and the +bulk of the work to changing over to functional notation from bracket notation can be +left to the compiler, so new language features can easily be added, without having +to rework the entire compiler each time. + +Finally, adding infix operators is more difficult to write a parser for, so the +first versions of MethodScript did not support them, however, doing all the operations +that the operators provide is essential to even the most basic programs, so they had +to be added either way. The initial design opted to make them functions, and so that +standard framework could be reused, instead of duplicating code for both approaches, +or removing the old approach. + +===Why are trailing commas allowed?=== +Trailing commas are allowed in all functions, but their primary use is for arrays. +Consider the code: + +
+@a = array(
+	'a',
+	'b',
+	'd',
+	'c',
+)
+
+ +Now, if we want to sort the array into alphabetical order, we can just copy the 'd' +line, and paste it below the 'c' line, without having to add and remove commas. Because +arrays are created using a function, it would require a special condition in the parser +to make this an error in other cases, so it was decided to not prevent this in other +cases. So, add(1, 2,) is also valid. However, there is logic for detecting ''duplicate'' +commas, so add(1, ,2) is a compile error, since this indicates a missing parameter, not +the standardization of argument syntax. + +===Why are things that are simply warnings in other languages compile errors in MethodScript?=== +If you are not familiar with this, consider that this code causes an error at ''compile time'', +not ''runtime''. +
+reg_match('(', $userInput)
+
+As you can see, there is no way for us to completely run this function at compile time, because +the user input may vary, so we cannot predict if it will match or not. However, since the +regex itself ''is'' hardcoded, we can examine it, and see that it will ''always'' throw an +exception. + +There are no good reasons for forcing a function to throw a runtime exception, if it +can reliably be determined at compile time that it will always do so. There can be exceptions to +this rule only in a very few meta cases, for instance, testing the function's performance +in runtime vs compile time, but these reasons are all non-typical, and do not justify +the removal of such a feature. So, to this end, functions are able to do optimizations during +the compilation process, and even if some of the parameters are variable, if some of them are +constant, they are able to go ahead and do some processing on them to see if they are guaranteed +to throw an exception at run time, and if so, they go ahead and cause a compiler error. Additionally, +this same mechanism is used to fully resolve some functions, if there is no user input, and the +function needs no external inputs to do the processing. This speeds up the script at execution time, +and even if the code is only run once (say, in interpreter mode) the process will take the same +amount of time. So, functions like add() can run during compile time if the numbers to be hard +coded are constant. + +Currently, analysis is not done on variable paths, so even though @a = 5 add(@a, 2) will +always result in 7, since it uses a variable, it is assumed that it is completely dynamic, +and will not be optimized. This behavior WILL change in the future however, so it +should not be relied on. + +It is worth noting however, that in the rare cases where you absolutely must require +the function to be evaluated as if it were dynamic, but you also want to hardcode in +a value, you can use the dyn() function. This is meant to be used during testing, +to ensure that code is optimized properly, and is not documented in the API, but +is available to user code nonetheless. diff --git a/src/main/resources/docs/Developer_Guide b/src/main/resources/docs/Developer_Guide new file mode 100644 index 000000000..e67b103a9 --- /dev/null +++ b/src/main/resources/docs/Developer_Guide @@ -0,0 +1,177 @@ +This page is intended for developers of the backend MethodScript engine, not +for assistance with writing scripts in MethodScript. Before beginning development +with MethodScript, you should have a firm grasp of Java, including concepts +like inheritance, abstraction, reflection, annotations, generics, etc. Additionally, +you should have a firm grasp of how the language itself works, from the scripting +perspective. + +== Abstraction == + +In general, the compiler works under the assumption that the end runtime is unknown. When compilation +is initialized, only a few key functions are strictly required to be "core" functions. That is, +they MUST have the same implementation in all implementations. Once the Abstract Syntax +Tree (AST) is created in memory, it is then linked to a particular platform, which can then +be either used as an in-memory binary, or can be cross compiled to something else. + +The commonality between all implementations of MethodScript is limited to the +core functions including procedure related functions, exception related functions, +control flow related functions, basic logic and operator functions, object creation +related functions, and data handling functions. All implementations must accept +that the compiler will use the default implementation of all these functions during +compilation, for static code analysis. Most of these functions are deeply integrated +with the compiler anyways, and cannot be generically separated from the compilation +process regardless. + +== Compilation == +There are 3 distinct stages during compilation. Lexing, Compiling, and Optimizing/Linking. + +=== Lexing === +Lexing is a standard process that converts the source code into a token stream. This +process follows fairly standard algorithms, and uses a generic mechanism to add new +tokens easily. This mechanism could potentially be dynamically expanded per platform, +should the need arise. + +=== Compiling === +Compilation converts the token stream into a Abstract Syntax Tree (AST). This step +can emit only a few errors, (mostly mismatched parenthesis/braces) because the +lexer will have already halted if the syntax is wrong, and the optimizer is what +actually causes linking errors. The compiler is a typical recursive decent parser, +only it offloads the complexity of the infix notation parsing to the __autoconcat__ +function, which runs separately later, during optimization. + +=== Optimizing/Linking === + +Once initial code compilation has occurred, linking happens. Since +the linking process happens after control structures have been analysed, this allows +for "meta programming" using existing control structures familiar to the coder. +In addition to the ${} compiler directive statements, this allows for meta programming +to be accomplished much in the same way C++ provides #define, #ifdef, and other +preprocessor directives, but there is no way for the user to go beyond the intended +cases of simple function substitution, for instance, conditionally #ifdefing out +a closing bracket. This is because this substitution occurs AFTER the lexer runs, not +before it, as happens in C++. + +Optimization of the core functions occurs before linking, so actually this step +can be thought of as optimization/linking/optimization. By optimizing the core +functions however, we generally only ''remove'' code from the AST. Some data +transformations do occur, but most of the core functions do not "run" at that time. +Once the initial optimization happens, we link to the actual runtime requested, +by passing control to the individual functions that are linked to that particular +runtime. In combination with the environment, the platform is able to perform whatever +actions it needs to complete the process with the generic compiler, while still +custom tailoring the output to suit that particular platform. + +Individual functions have the opportunity to optimize themselves, either by running during +compile time (should they have the ability to) and therefore completely removing themselves +from the AST, or by simply emitting warnings/errors for whatever conditions they can +check for. + +== Adding a function == +Adding a function is simple. Create a new class that implements +%%GET_SIMPLE_CLASS|.*|Function%%, (or more probably extends %%GET_SIMPLE_CLASS|.*|AbstractFunction%% +and tag it with @api. It is important to read and be very familiar with the methods +in %%GET_SIMPLE_CLASS|.*|Function%%, so you know which methods that are optional should +be overridden. + +=== Using the new argument builder === +The new argument builder greatly simplifies argument parsing, by providing generic type safety, range, and other +compile time and runtime checks. This also provides the advantage of putting the documentation right +next to the elements, and creating more documentation from the elements themselves, instead of relying on the +programmer to provide the data correctly. This makes it so that if the documentation doesn't list a restriction, +for instance, but the restriction being missing is a bug, then the bug will be in both the documentation AND the +code, and will therefore be much easier to catch. Besides restrictions, which are discussed below, the biggest thing +this provides is type safety. Until strong typing is fully supported, the system will be mostly a runtime-only check, +though hard coded values that are incorrect will be able to be caught at runtime. Generics are also supported, +and are required for return types that return closures and arrays, and eventually +all parameters that are closures or arrays. These restrictions will be caught via unit test, as Java has no way +to make this a compile error. Additionally, several restrictions (used as MAnnotations) may be added, which will provide +runtime checks on the data passed in, or will check at compile time if possible. These restrictions are built in to +the typing system, and ensure that the data passed in with be within those restrictions, whether they were checked +at runtime or compile time. Using the new argument builder itself is optional. Providing the return type and arguments +with the returnType() and arguments() methods is not optional, but using it to re-parse the data is. The way it works is +that the function's arguments list is retrieved by the typing system once, to verify the arguments, using generic algorithms. +The function itself can then use the builder to cast the data into the correct types, which is guaranteed to succeed at that point +(barring programmer error, which will cause an Error to be thrown), and the code after that can safely assert that the restrictions +placed on the parameters is true. However, if the code itself duplicates these checks, or manually casts the values, that will +continue to work, since the raw arguments are passed in to the function as is. However, new code is strongly advised to use +the builder, since eventually, backwards compatibility may be broken, and the parameters may not be passed in except as a list. +Additionally, if the code simply requires the arguments to be processed manually, it is a sign that the arguments themselves +are too complicated, and should be simplified. + +=== Restrictions === +Restrictions work like parameter annotations in Java, for instance, if you had the following method signature in Java: +
+public void func(String s, @Annotation String y){
+
+}
+
+ +The parameter "y" is tagged with the @Annotation tag, which may provide meta information to some reflective library. In MethodScript, +the MAnnotations available in Java will eventually be available to scripts, but in the meantime, they are only available to built in +functions. Regardless, the parameter restrictions will all work the same, they work to reduce the effort required to checking rote +aspects of the parameter, and provide a way for the runtime to generically handle those cases. Additionally, the compiler is aware of +many of these annotations, and where possible, will provide the same functionality, but at compile time, where possible, allowing errors +to be caught more quickly. + +The annotations that are supported by either the compiler or runtime are listed below, but all of them must implement +%%GET_SIMPLE_CLASS|.*compiler|CompilerAwareAnnotation%% to be valid parameter annotations. Each annotation has it's own +documentation in the normal API. + +==== @{NonNull} ==== +Parameters tagged with this cannot be assigned null. + +==== @{Ranged} ==== +Parameters tagged with this must be between a given range. There is the option of setting the limits +to be either inclusive or exclusive. + +==== @{FormatString} ==== +Parameters tagged with this must match a regex. It is only taggable on CStrings. + +=== Documentation and argument builder === +Each function has embedded java doc and type information. This is extremely important +to keep accurate, both for the sake of the user, and for a technical need. The docs() +method should return the plain text user readable documentation, which summarizes the +behavior of the function. This information isn't used programmatically in any way by +the compiler, but it is used of course by the users, and should accurately reflect +the behavior of the function. Previously, the docs() method needed to return a string +in the format returnType {argumentList} documentation, however, this is +no longer necessary, since the return type and argument list are used programmatically +elsewhere, and therefore specified in different methods. + +The returnType() method should return an %%GET_SIMPLE_CLASS|.*arguments|Argument%%, using +the "nameless" constructor of the Argument class. This simply provides the documentation +and return type of the function, no "name" is needed. There is a pre-constructed Argument.VOID +object that should be used in the case where the function returns void. Additionally, there +are a few low level functions that cause execution to terminate in a non standard way, for instance, +the die() method, or return(). These must use the Argument.NONE member, and should also likely +provide the %%GET_SIMPLE_CLASS|.*Optimizable|OptimizationOption%%.TERMINAL optimization. + +The arguments() method returns the function's signature. For some functions, this will be quite +complex, for others, it should be straightforward. By design, no arguments are passed to this +method, because all arguments (and their defaults) should be constant, and should never vary +based on runtime parameters. (This complicates the design, but more importantly prevents +certain optimizations from occurring.) Functions may have multiple signatures, that is, +completely conflicting signature types, though this behavior is only provided for backwards +compatibility, and should not be used for new functions. Disjoint types are recommended instead, +or general simplification of the function instead. See the methods in the +%%GET_SIMPLE_CLASS|.*arguments|Argument%% class for more information on the various +options available when creating arguments, and %%GET_SIMPLE_CLASS|.*|ArgumentBuilder%% +for information about the ArgumentBuilder as a whole. + +== Adding an event == +Adding an event is only slightly more complicated than adding a function, though +the addition of platform specific abstraction layers may complicate the process +some, both for events and functions. In general, adding an event only requires +two steps, though in practice it may require additions in several places. The first +step is to provide the event object via implementing %%GET_SIMPLE_CLASS|.*|Event%% +(or extending %%GET_SIMPLE_CLASS|.*|AbstractEvent%%) and tagging it with @api. The +second step is to actually hook into whatever system there is for actually triggering +the events, and calling %%GET_SIMPLE_CLASS|.*|EventUtils%% TriggerListener method, with +the event name and event driver, and the actual event object that it will process. +This method will then find all the user bound events, and decide if they actually +need to be triggered, then call the appropriate methods in the Event object. + +The factors that can further complicate this are the fact that you will likely be using +abstract event handlers for all of the actual event objects, and that some events +are not as straightforward to modify. These pain points will hopefully be corrected +in the future. diff --git a/src/main/resources/docs/Enums_and_Masks b/src/main/resources/docs/Enums_and_Masks new file mode 100644 index 000000000..11cafbb79 --- /dev/null +++ b/src/main/resources/docs/Enums_and_Masks @@ -0,0 +1,156 @@ +Often times you may find yourself with a unique set of predefined constants, for instance, +compass directions NORTH, SOUTH, EAST, and WEST, or days of the week. Additionally, you may +have a set of these enums, which can be represented as a bit mask. For these two situations, +you should use the ''enum'' and ''mask'' types, respectively. + +== Enum Types == +An enum is a specially declared class, which follows certain extra rules, but otherwise behaves +just like a normal class. It may have data members and methods, just like any other class. The +exceptions are that the class may not extend any other classes, and the constructor, if provided, must be +private. To declare a simple enum, use the following syntax: + +
+enum Day {
+	enums {
+		SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
+		THURSDAY, FRIDAY, SATURDAY
+	}
+}
+
+ +The enum values are considered as if they are public static final members +of the class Day. Therefore, you cannot have member variables with the same name as the +enum. To use the enum value, you would use it the same as any other static member: + +
+Day::SUNDAY
+
+ +By convention, the names of enum values are all caps, with words separated by underscores. +Enums can be {{function|switch}}'d over, as such: + +
+	Day @day = Day::MONDAY; # This might be from user input
+	switch(@day){
+		case MONDAY:
+			msg('Manic Monday');
+		case WEDNESDAY:
+			msg('Hump day!');
+		case FRIDAY:
+			msg('Almost to the weekend!')
+		case SATURDAY, SUNDAY:
+			msg('Yay, weekend!')
+		default:
+			msg('What is today again?')
+	}
+
+ +Often times, you have a switch statement, which you need to be sure to update, if +a new value is added. For this, you may use the {{function|switch_all}} function. +This function requires that all enum constants have a case, and no default may be +used. + +
+	Day @day = Day::MONDAY;
+	switch_all(@day){
+		case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY:
+			msg('Weekday')
+		case SATURDAY: # Oops, forgot SUNDAY
+			msg('Weekend')
+	}
+
+ +The above code will cause a compile time error, indicating that the SUNDAY enum +was not provided for. This provides for better error checking, in the case where +each enum should have definite handling code, and is subject to new enum values +being added in the future, instead of a generic handler that throws a runtime +exception. + +You may also have more complicated enum class definitions, and you may implement +interfaces as well. If a constructor is provided, it must be private, but otherwise +is acceptable. The special constructor syntax is demonstrated in the example below +as well. As you can see, each parameter is passed in separately for each enum, so +getInfo() for each one would return different data. + +
+enum Compass {
+	enums {
+		NORTH('up'), 
+		SOUTH('down'), 
+		EAST('right'), 
+		WEST('left')
+	}
+    members {
+        private final string @info
+    }
+    private Compass(string @info){
+        this()->@info = @info;
+    }
+    
+    public string getInfo(){
+		return(@info);
+	}
+}
+
+ +Use of enum constants sometimes have special type inference. If the compiler can +definitely determine the enum to use, the class name can be left off. So, if the +procedure _func() is defined as such: proc(_func, Compass @direction, ...) +Then both _func(Compass::NORTH) and _func(NORTH) are +acceptable. This is only possible when the declared type is a concrete enum type, +and not the Enum or Object superclasses. Additionally, auto string types are cross +castable to an enum, so _func('NORTH') is also valid. + +All enums inherit a few static methods from the enum superclass as well, the +most important being valueOf() and values(). valueOf() converts a string to +the corresponding enum, and values() returns an array of all enum values in the +enum. + +== Mask Types == + +An enum mask is used when you could have a set of enums. The Mask class provides +methods for converting a set of enums to an integer, and vice versa. Enums that +have 64 or less enums may be used as a mask. The default integer mask value of each enum is +based on the ordinal value, 2 ** ordinal. However, this default may be changed by overriding +the mask() method, and returning whatever integer value you like. It is worth noting that +changing existing enum order (besides simply renaming them) causes any old integer masks +to be incompatible with the new versions. To use a mask, you must use Mask's static +method create(), and provide the enum class, if it can't be inferred. + +
+Mask @day = Mask.create(); # This can be inferred, because the return type must be Mask
+msg(Mask.create(Day)); # This must be specified, since the compiler can't infer the return type.
+
+ +Mask implements Set, so all the operations available with a Set are also available to the Mask, +but of note are two extra methods, toInt(), and the static fromInt(). + +
+enum Compass {
+	enums {
+		NORTH, # mask value: 1
+		SOUTH, # mask value: 2
+		EAST,  # mask value: 4
+		WEST,  # mask value: 8
+	}
+}
+
+Mask @c = Mask::create();
+@c->add(NORTH);
+@c->add(SOUTH);
+msg(@c->toInt()); # returns 3, which is the equivalent of bit_or(1, 2), or 0011 in binary
+
+Mask @c = Mask::fromInt(Compass, 12); # 12 in binary is 1100, which represents EAST and WEST
+
+ +The default implementation of mask() looks like this: +
+public int mask(){
+	return(2 ** ordinal());
+}
+
+ +However, you may override this to return a different value, depending on the circumstances. +Do note however, that overriding this method incurs a performance penalty, because you could +dynamically change the mask values, when fromInt is called, it will not cache the mask values +for your enum, like it will for the default. diff --git a/src/main/resources/docs/Structs b/src/main/resources/docs/Structs new file mode 100644 index 000000000..e5bb664c9 --- /dev/null +++ b/src/main/resources/docs/Structs @@ -0,0 +1,110 @@ + +A Struct is a special type of class, which allows for more precise associative array definitions, while not quite allowing for the full +power of objects. A struct may only be declared with public members, and no methods. Any associative array can be cross cast to a +struct, and vice versa. + +== Using a struct == +To create a new struct, you use it the same as if you were constructing a new object, +using new. All structs work as if they have exactly one no-arg constructor. +Assuming we have a struct named "Struct", then this code would create a new one: + +
+Struct @s = new Struct();
+
+ +This creates a new struct, with all the properties initialized to their defaults. +Additionally, since cross casting is available, the following works as well: + +
+Struct @s = array();
+
+ +Members in a struct are accessed the same as members in classes, with the -> operator. +Assuming our example struct has the int member @i, we can get and set it like this: + +
+Struct @s = new Struct();
+@s->i = 1;
+msg(@s->i); # Msgs 1
+
+# We can also set it directly via an array constructor
+Struct @s2 = array(i: 2);
+msg(@s2->i); # Msgs 2
+
+# Also, we can use the reflection/array access methods as well
+@s2['i'] = 3;
+msg(@s2['i']); # Msgs 3
+
+ +When using a struct, you gain the advantage of type safety in associative arrays, +assuming they aren't dynamic. Usually however, it may be a better idea to use full +Objects, so you can also add methods later. However, a configurable factory is a good use of structs +in combination with objects, which is demonstrated below. + +== Defining a struct == + +A struct is defined in exactly the same way as a class, except it may ONLY have a members block, and is +declared with the struct keyword. If we set the parameters with a value, that becomes their +default, which itself defaults to null. + +
+struct A {
+	members {
+		int @a = 1;
+		double @b = 2.5;
+		public string @c = 'String'; # Don't strictly need public here
+	}
+}
+
+ +Adding access modifiers is optional, though if specified, must be public. + +== Example == + +A good use of structs is when you have lots of configuration for an object. Instead +of using a constructor with lots of optional parameters, or having separate setter +methods for each field, you can use a configuration struct to simplify the configuration. + +
+class Car {
+	
+	members {
+		private string @make;
+		private string @model;
+		private int @year;
+		private CarOptions @options;
+	}
+
+	public Car(string @make, string @model, int @year, CarOptions @options = array()){
+		this->make = @make;
+		this->model = @model;
+		this->year = @year;
+		this->options = @options[]; # Clone the array, so they can't change options on us later
+	}
+
+	struct CarOptions {
+		members {
+			boolean @GPS = false;
+			boolean @leatherSeats = false;
+			boolean @XMRadio = false;
+			Color @color = 0x000000;
+			int @rentalMonths = 1;
+		}
+	}
+}
+
+# Creating a new Car object:
+
+# This constructs a new car with the default optional options, but allows
+# for us to still specify the required parameters as part of the constructor
+Car @c1 = new Car('Make', 'Model', 2014);
+
+# This constructs a new car with only some of the options selected
+Car @c2 = new Car('Make', 'Model', 2014, array(color: 0xFFFFFF));
+
+# This would cause an error at compile time, since @rentalMonths is an int
+Car @c3 = new Car('Make', 'Model', 2014, array(rentalMonths: 'string'));
+
+ +If used properly, structs can work well in cases where a "named argument list" is +desirable. diff --git a/src/main/resources/docs/WebTemplating b/src/main/resources/docs/WebTemplating new file mode 100644 index 000000000..a74ff870e --- /dev/null +++ b/src/main/resources/docs/WebTemplating @@ -0,0 +1,619 @@ +MethodScript supports templating for web (or any other template needs) through template +language tags. In many ways, the support for templates is much like JSP and PHP, but +differs in a few key ways, which allow greater flexibility when creating text via +templates. + +== Overview == +Before delving into MethodScript's templating system, it is useful to discuss the +shortcomings of existing templating systems. PHP was originally designed as a template +language. In fact, there are still relics in PHP that support inline template syntax +for mostly HTML pages: + +
+
+inline html
+
+
+ +This approach is very useful, however due to the unrestricted nature of PHP, it can cause +problems with separating concerns. [http://www.smarty.net/ Smarty] was created to assist with +the [http://en.wikipedia.org/wiki/Separation_of_concerns separation of concerns], +but requires learning an all new templating language syntax (on +top of also needing to know PHP) and so adds a layer of complication that doesn't really +solve the underlying problem (because you can still mix too much logic into the UI code +anyways). MethodScript's templating system takes these concerns into consideration, and +while it by default promotes good separation of concerns, it does not actually restrict +a user from breaking these concerns, where flexibility (or minimal code) is desired. + +The most prominent architecture of a web page (or any UI for that matter) is the +[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller Model-View-Controller (MVC)] +paradigm. This pattern allows for the best separation of concerns, by moving the +data driven portions of the application into separate code from the UI components. +A good template system would only allow access to the ''View'' portion of the paradigm, +which is what MethodScript does by default. MethodScript uses standard XHTML as its +display model, which easily translates to web applications. The UI library in MethodScript +mirrors all the XHTML tags with objects, and vice versa, and allows the creation +of more complex macro components that are made up of the native xhtml primitives, either +in pure MethodScript, or xhtml templates. +In addition, of the subset of functions that are compilable to javascript, those +scripts are compiled into javascript at compile time, using the standard xhtml +script tag, which allows for client side UI logic to be embedded or included the +same as pure javascript would preventing you from having to learn an all new language, +in addition to being able to fully optimize the logic at compile time. +It also allows for javascript code side by side, in standard script tags. All xhtml+MethodScript +template files are first class in pure MethodScript code, which allows for the business +logic to access the UI components as easily as pure MethodScript elements, while +still maintaining strong typing. In addition, most all standard CSS components have class based accessors +as well, allowing for type safety, inheritance, and variable based CSS templates +to be generated. + +== Usage == +There are two key concepts that you need to know to effectively write MethodScript +templates: variable inclusion, and dynamic scripting. All templates +that use only variable inclusion compile directly into equivalent pure +MethodScript classes, and dynamic scripting portions are compiled down to javascript +and virtual MethodScript code. + +Before we move on to the usage of these concepts, lets look at a perfectly +static page that doesn't use any MethodScript in the template itself. + +=== Barebones usage === + +A barebones example has potentially two parts, the root XHTML page, and the controller +code. In the simplest example though, it is simply a XHTML class that is accessed. We won't +consider that example yet, because there is no interaction with MethodScript in that case, +and the XHTML template would simply be passed through, virtually untouched. Instead, we +will assume that a pure MethodScript file is accessed, which uses MethodScript to display +the XHTML template instead. In the basic case, when a .ms file is accessed through the web server, it is run +once, and is expected to call the display method on a XHTML object, which will in turn output +the rendered xhtml to the client. (It can output any text in reality, which is used for instance, +in the course of ajax request responses.) HTTP headers may be buffered at any point before this, +and are sent just before the body of the HTTP response. Let's look at example code. + +
+XHTML @page = new XHTML();
+@page->setTitle('Page title');
+@page->setDescription('This escapes special symbols like & when used this way');
+@page->addChild(new XHTMLDiv('Inner text'));
+@page->display();
+
+ +This renders the following markup (assuming the output is set to be tidied and configured +to output xhtml): + +
+
+
+	
+		
+		
+		Page title
+	
+	
+		
Inner text
+ + +
+ +In fact, if you put the rendered XHTML into a template file, it would essentially compile +to equivalent MethodScript. + +Usually you won't use the XHTML object directly. If you have a standard page layout, it is +best to create a factory method that will create and set up an XHTML object for you, and +then return that, which can be further operated on for that page specifically. Now lets +consider the case where we ourselves are creating a template. Templates are defined by the +fact that they have the .mst extension. This puts the compiler into a "template compiling +mode" instead of assuming pure mscript. The component must extend an existing UIComponent +class (which all the XHTML objects extend). The class name is the name of the file it +is in, and it extends whatever type the root element is. For instance, if we had a simple +component that has static text inside a div (assuming it is saved in MyDiv.mst), we can write this: + +
+
+
+ Inner text +
+
+ +Since the root element is a div, the doctype is inherited from it, though +attributes and elements can be added as well. Because of this, all +elements have a doctype, even if it isn't directly specified. +Written in equivalent pure mscript, this class would look like this: + +
+@{XHTML}
+class MyDiv extends Div {
+	public MyDiv(){
+		this->addChild('Inner text');
+	}
+}
+
+ +and regardless of how we just defined that, we can use it via pure MethodScript like +this: + +
+XHTML @page = new XHTML();
+@page->addChild(new MyDiv());
+@page->display();
+
+ +or like this in another template: + +
+
+
+ +
+
+ +The @{XHTML} annotation is used to tell the UI compiler that this class is available +in mst templates. More advanced usage is shown later in this tutorial. + +=== Variable Inclusion === +Variable inclusion is the most straightforward way to make templates dynamic, and this +principal is core to the understanding of the templating system. When a component defines +a property, it is eligible to be used as an attribute. Attributes can be set via the component +constructor, or as individual xml attributes in a template. When creating a custom component, +attributes are defined via the constructor as a struct, or in a DTD ATTLIST in a template. +Once defined, the variable is defined in the template as a $var, and are simply expanded out. +Note that only primitive values are available in attributes, if you have a more complex +layout, you'll need to use static scripting to properly layout a page. + +==== Declaring attributes in custom components ==== + + +To declare your own element with attributes in MethodScript is a two step +process. First, you must set the attributesClass value of the @{XHTML} annotation +to your struct class, which should extend the nearest parent class's attributesClass +struct. (The top level XHTML element, which everything must +extend from ultimately defines an empty struct, called Attributes.) Secondly, +you must implement the (Attributes) or (Attributes, UIComponents...) constructor. +Upon creation (either through pure MethodScript or via mst templates) this data +will be passed in then. All attribute values must extend the primitive type, +and cannot be objects. + +For example: + +
+
+@{XHTML(attributesClass: MyDivAttributes)}
+class MyDiv extends Div {
+
+	public static struct MyDivAttributes {
+		public string @name;
+		public int @age = 21;
+	}
+
+	public MyDiv(MyDivAttributes @options){
+		this->addChild(new Div('Name: '.@options->name));
+		this->addChild(new Div('Age: '.@options->age));
+	}
+}
+
+ +The equivalent structure written in a MyDiv.mst file is slightly simpler, because +the attributes simply need to be declared with a type, and possibly a default value, +using the attr attribute in the root element. The availability of the attributes +is checked at compile time, so if you forget to declare one of them, then use it, +it will be a compile error. All attributes from the parent are also available to +be used as well inside of the template. + +
+
+
+
Name: $name
+
Age: $age
+
+
+ +If you have an attribute named $var, and you need a literal $var, you +can use the xml escape sequence &#36;var. + +=== Dynamic Scripting === +When writing UI code, especially HTML, for truly dynamic web pages, you need to +run some client side logic to do what you need. For instance, say you have a button +that when clicked makes a number count up. In this case, you need to (ultimately) +write javascript to do that. However, javascript is a cumbersome, unwieldy language +in many cases, so MethodScript offers the ability to compile to javascript in many +cases. Most of the core functionality is available in the cross compiler, and some +additional features are used specifically in these scripts. Most notably is the +ability to access the dom. Note that while this code does get compiled at the same +time as the rest of your script, it doesn't actually get run as MethodScript, it +is cross compiled to javascript, and included as part of the entire output html. +A very simple Hello World program would look like this: + +HelloWorld.mst: +
+
+
+ +
+
+ +We would deliver this to the browser using the following main code: + +
+XHTML @page = new XHTML();
+@page->addChild(new HelloWorld({output: 'Hello World!'}));
+@page->display();
+
+ +which would result in the the following html: + +
+
+
+	
+		
+		
+	
+	
+		
+	
+
+
+ +As you can see, MethodScript does most of the work for you, making it far simpler +to use than javascript. However, this does not stop you from using javascript yourself. +Any javascript you embed in the template will be included exactly as is, so existing +javascript librarys can be used. Unfortunately, you cannot interface MethodScript +with javascript directly, though MethodScript can call javascript functions indirectly. +Inside of the tag, the this keyword is available, and refers to that instance +of the html tag. + +The equivalent to doing this in pure MethodScript is equally straightforward, but +less clear. The important thing to remember is that this uses rclosure linking rules. +The XHTML->addScript method is used to add a script to an element. The same template +shown above would be written in pure MethodScript like this: + +
+@{XHTML(attributeClass: HelloWorldAttributes)}
+public class HelloWorld extends Div {
+	public static struct HelloWorldAttributes {
+		public string @output;
+	}
+	public HelloWorld(HelloWorldAttributes @options){
+		this->addScript(rclosure(){ // returns void, no args
+			alert(@options->output);
+		});
+	}
+}
+
+ +In the case of using pure MethodScript, you can even conditionally add scripts if needed. +In either case however, before the script is cross compiled to javascript, it is optimized +in the MethodScript compiler, which may significantly change the output script. This condenses +the output code so that only the elements that truly need to be client side are sent. +Any logic that can be determined at compile time will be resolved at compile time. +While it is possible to add multiple script blocks in a mst template, it doesn't +affect the output script (other than lexical correctness, if your scripts aren't +logically complete) because the script will be gathered up and placed in the appropriate +location anyways. + +External MethodScript-compiled-to-javascript can also be included, akin to how you +would include a script tag in the head of an html page. To do this, in the XHTML +object, call the useScript method, and pass it a reference to a .ms file, which will +be cross compiled and be made available in the javascript. Your server can be configured +to manage caching automatically, or if you have a volatile environment, to simply +turn off caching, and the compiled javascript will be re-rendered each time it is +requested as a part of the browser request process. + +==== DOM access ==== + +One of the most important things that javascript provides is the ability to access +the dom. MethodScript exposes this functionality in an easy to use way, so you can +take advantage of some of the type safety that the templating system uses, while still +being able to access core properties easily. The simplest and most straightforward +example would be for a div to hide itself after a few seconds. To do this, we can +use the methods in the XHTML class in a script block. + +FadeDiv.mst: +
+
+
+ +
+ Hide me! +
+
+
+ +This would compile to the following output: + +
+
+
+	
+		
+		
+	
+	
+		
+ Hide me! +
+ + + +
+ +As you can see, the dom access is generally easier, or just as easy in javascript, +but we get the advantage that in MethodScript, many invalid operations become +a compile error. For instance, the display property only recognizes some string +values, so that is an enum, so the following code would cause a compile error, +even though it would work (though it would be ignored) in javascript. + +
+#Works
+hideThis->style->display = "none";
+#Compile error!
+hideThis->style->display = "invalid";
+
+ +Additionally, when working with specific elements with ids, we don't need +to use document.getElementById("id"), we can simply use id. The compile figures +out what type that is exactly, and adds it as a valid child, if it is statically +declared. For dynamically declared elements with ids, you would have to use +getElementById, but generally that's a code smell, since you shouldn't be generating +ids programmatically anyways (use class references instead). Further, doing it this +way helps to enforce decoupling, because any javascript using an id is by definition +tightly coupled with that element, and so the code specific to it should be logically +placed in the same file anyways, to make it easier to find. Generic library code +shouldn't be using the ids anyways, and so the ids simply won't be available in the +header code, making your dom access follow proper inheritance principals. + +All CSS and DOM elements are mapped to first class MethodScript data structures, +so the type safety is applicable to all values. For experimental CSS values, raw +string based styles can be added, but the standard values all have first class mappings. + +If a first class mapping doesn't exist, or you are trying to access experimental or non-standard values, +you can use the reflection mechanism to bypass the compiler, just as you would in normal MethodScript +code. + +
+hideThis->style['display'] = "experimental value";
+
+ +The dynamic scripting can also be used to make html changes at compile time. For instance, +given the following template: + +Optimized.mst: +
+
+
+ +
+ Hide me! +
+
+
+ +This would actually result in html code that had no javascript in it, because +the dom manipulation would happen at compile time, because all the components +are known to the compiler, and they don't require any javascript to be run by the +client. The output html would be simple: + +
+
+
+	
+		
+		
+	
+	
+		
+	
+
+
+ +As you can see, the html is already pre-rendered with the correct styling. If you actually +did intend on the code to remain javascript for whatever reason (perhaps you wanted it to +flash unstyled content??) then you would have to write the javascript in directly. + +Note that the output javascript may not look anything at all like you expected, as it +is run through several optimizations to ensure the least amount of code is actually +sent to the client, however, the behavior should be consistent. You can turn debug flags +on in the compiler, and it will output comments into the html explaining in more detail +where certain javascript came from, as well as how the compilation process happened, +so if you do find a bug in your code, it is easy to trace back to the root of the problem +by reverse engineering the javascript. Additionally, obfuscation, minification, and formatting +options can be set as well. + +Barring bugs in MethodScript itself, the output javascript is guaranteed to never throw any +(unintended) exceptions, or cause undefined behavior in the DOM. + +==== DOM Events ==== +Events are handled via individualized event handlers for each event type. Each element +has its own events available to it via javascript compiled MethodScript. A simple example +is a button that pops up an alert when it is clicked. + +Alert.mst: +
+
+
+ +
+
+ +This results in the following equivalent html (the actual html rendered will be +more complicated, but for this example, it has be simplified to equivalent javascript): + +
+
+
+	
+		
+		
+	
+	
+		
+ +Each event has its own event object that is passed in, and has all the data that +would be in the equivalent javascript event, allowing full access to the event. +Check the API for each event for more information about each. + +==== Ajax wrappers ==== + +Some functions cannot be run directly on the client side, because they need data from the server. +In this case, many functions transparently will compile to applicable javascript, and +handle both the server and client side code to do manage this process for you. For instance, +let's assume we want to read information from the persistance data on the server. We +can simply use async_get_value like normal (get_value isn't available in the javascript +compiler), and the call will still work. + +persist.mst: +
+
+
+ + +
+
+ +This transforms into fairly complicated HTML, because as much code as possible is +converted to client side javascript, but since much of the script still must run +server side, this compiles into a cached script that is registered with the runtime, +and responds appropriately to the call. Since the script may expose security holes +if the data isn't properly validated, even more complication may be added to the +javascript and server side compiled script. Essentially, this turns into the following +javascript (client side): +
+//Pretend the ajax function exists.
+document.getElementById("load").onclick = function(event){
+	ajax("server_url.com/pathToAutomaticallyGeneratedFileThatWillRespondToThis?v1="
+		+ document.getElementById("input").value, function(response){
+			document.getElementById("output").innerHTML = escapeSpecialChars(response.value);
+		});
+};
+
+ +and the following equivalent pseudo MethodScript (server side): + +
+@v1 = get_request_var('v1');
+@ret = get_value('userInput.'.@v1);
+output(@ret);
+die();
+
+ +All server data methods are individually supported in this equivalent manner, that is, +whatever data is needed to run the script on the server is requested from the client, +and as little data as possible to actually run the script server side is sent, allowing for +zero data leaks to the client, while maximizing functionality. + +==== Javascript Compiler ==== +Some final notes on the MethodScript to Javascript compiler: + +Scripts may look nothing like each other. Where possible, anything that can be +done server side does not get compiled into the javascript, but the compiled javascript +is guaranteed to be equivalent to the behavior defined in the MethodScript. The +compiler does this by using a different compilation algorithm in the supported +functions, which makes them "magic" in the sense that you can't directly replicate +this behavior with custom code, unless you hook into the compiler functionality. +There are three steps when running the compiler and script: MethodScript/MST compilation, +MethodScript runtime, Javascript runtime. Each of +these steps transforms the output significantly, so tracing a problem can be tricky +if you don't understand what the system is doing at each step. + +MethodScript/MST compilation: The code is compiled into units. Templates are compiled +and validated during this stage, and all code is checked for lexical correctness, +as well as type safety. Segments of code in templates that are to be compiled into javascript +are set aside for now, though the MethodScript optimizer will have already done as +much optimization as possible. The scripts that need to be compiled to javascript +are then cross compiled into javascript and server side MethodScript (where applicable) +and cached until needed. + +MethodScript runtime: When a user accesses a web page, it starts the process of generating +the actual output html. The parameters sent by the user are used to piece together the output +from the cached sections of templates, or fully dynamically if using pure MethodScript. +Once the construction of the output is complete, the output is sent to the user. + +Javascript runtime: For dynamic pages, the javascript segments will run as needed +during the course of user interaction. If the javascript communicates back to the server, +the segments of cached async code are run, and respond appropriately to the user. + +As you can tell, the backend process is very complicated, however, most of the work +is done at compilation time, which makes actual runtime of the scripts faster, and +most of the hard work is hidden from you. + +== TypeScript support == +In addition to writing Javascript, you may also set the type="text/typescript" and the compiler +will use the builtin TypeScript compiler to cross compile from TypeScript to JavaScript. + +== Transformers == +Regardless of the transformer used, your code must always validate as well formed, +and valid xml. Once the validation has occurred though, the model is released +to the transformer to be rendered to actual text output. The default transformer +simply outputs xhtml, but other transformers can be used to change the output +based on, perhaps, the user's browser, or to create a mobile device layout, for +instance. The transformer has three stages it can choose to override individually. +The first, it is given the whole display +tree as an object, and it can choose to modify the tree as an object. Secondly, +it is given each individual element to render on it's own (with context information). +Finally, the entire output string is given to the transformer, at which point it +can do text based transformations on it. The transformer can be set statically, +or can be registered as part of the outermost UIComponent, or it can be +specified at render time when the display method is called on the outermost +UIComponent. The most specific transformer is then used. From 92698360f5ced548cb451368c24602dc89901ee4 Mon Sep 17 00:00:00 2001 From: LadyCailin Date: Fri, 18 Aug 2017 17:32:17 +0200 Subject: [PATCH 05/10] Added $$ mode to cmdline mode --- .../PureUtilities/Common/ArrayUtils.java | 116 +- .../core/events/drivers/CmdlineEvents.java | 16 +- .../laytonsmith/core/functions/Cmdline.java | 3 +- .../com/laytonsmith/tools/Interpreter.java | 1329 +++++++++-------- 4 files changed, 805 insertions(+), 659 deletions(-) diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/ArrayUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/ArrayUtils.java index 6f5d90555..38c933e11 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/ArrayUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/ArrayUtils.java @@ -5,10 +5,10 @@ /** * - * + * */ public class ArrayUtils { - + /** * Instantiating a new 0 length array is *usually* inefficient, unless you * are doing reference comparisons later. If you are generating it simply to use @@ -57,7 +57,7 @@ public class ArrayUtils { * as a "default" value for an array, consider using this instead to increase performance. */ public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; - + /** * Instantiating a new 0 length array is *usually* inefficient, unless you * are doing reference comparisons later. If you are generating it simply to use @@ -106,12 +106,12 @@ public class ArrayUtils { * as a "default" value for an array, consider using this instead to increase performance. */ public static final Boolean[] EMPTY_BOOLEAN_OBJ_ARRAY = new Boolean[0]; - - + + /*************************************************************************** * Slices ***************************************************************************/ - + /** * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also @@ -121,8 +121,9 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ + @SuppressWarnings("unchecked") public static T [] slice(T[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; Object [] newArray = new Object[size]; @@ -139,7 +140,7 @@ public class ArrayUtils { } return (T[])newArray; } - + /** * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also @@ -148,7 +149,7 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ public static char [] slice(char[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; @@ -174,7 +175,7 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ public static byte [] slice(byte[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; @@ -200,7 +201,7 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ public static short [] slice(short[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; @@ -226,7 +227,7 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ public static int [] slice(int[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; @@ -252,7 +253,7 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ public static long [] slice(long[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; @@ -278,7 +279,7 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ public static float [] slice(float[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; @@ -304,7 +305,7 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ public static double [] slice(double[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; @@ -330,7 +331,7 @@ public class ArrayUtils { * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ public static boolean [] slice(boolean[] array, int start, int finish){ int size = Math.abs(start - finish) + 1; @@ -348,17 +349,18 @@ public class ArrayUtils { } return newArray; } - + /*************************************************************************** * Unboxes ***************************************************************************/ - + /** * "Unboxes" an array, that is, unboxes all the primitives in this * array, and returns a primitive array. * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ + @SuppressWarnings("UnnecessaryUnboxing") public static char[] unbox(Character[] array){ if(array == null){ return null; @@ -371,13 +373,14 @@ public static char[] unbox(Character[] array){ } return newArray; } - + /** * "Unboxes" an array, that is, unboxes all the primitives in this * array, and returns a primitive array. * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ + @SuppressWarnings("UnnecessaryUnboxing") public static byte[] unbox(Byte[] array){ if(array == null){ return null; @@ -390,13 +393,14 @@ public static byte[] unbox(Byte[] array){ } return newArray; } - + /** * "Unboxes" an array, that is, unboxes all the primitives in this * array, and returns a primitive array. * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ + @SuppressWarnings("UnnecessaryUnboxing") public static short[] unbox(Short[] array){ if(array == null){ return null; @@ -409,13 +413,14 @@ public static short[] unbox(Short[] array){ } return newArray; } - + /** * "Unboxes" an array, that is, unboxes all the primitives in this * array, and returns a primitive array. * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ + @SuppressWarnings("UnnecessaryUnboxing") public static int[] unbox(Integer[] array){ if(array == null){ return null; @@ -428,13 +433,14 @@ public static int[] unbox(Integer[] array){ } return newArray; } - + /** * "Unboxes" an array, that is, unboxes all the primitives in this * array, and returns a primitive array. * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ + @SuppressWarnings("UnnecessaryUnboxing") public static long[] unbox(Long[] array){ if(array == null){ return null; @@ -447,13 +453,14 @@ public static long[] unbox(Long[] array){ } return newArray; } - + /** * "Unboxes" an array, that is, unboxes all the primitives in this * array, and returns a primitive array. * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ + @SuppressWarnings("UnnecessaryUnboxing") public static float[] unbox(Float[] array){ if(array == null){ return null; @@ -466,13 +473,14 @@ public static float[] unbox(Float[] array){ } return newArray; } - + /** * "Unboxes" an array, that is, unboxes all the primitives in this * array, and returns a primitive array. * @param array The "boxed" array * @return The "unboxed" array */ + @SuppressWarnings("UnnecessaryUnboxing") public static double[] unbox(Double[] array){ if(array == null){ return null; @@ -485,13 +493,14 @@ public static double[] unbox(Double[] array){ } return newArray; } - + /** * "Unboxes" an array, that is, unboxes all the primitives in this * array, and returns a primitive array. * @param array The "boxed" array * @return The "unboxed" array */ + @SuppressWarnings("UnnecessaryUnboxing") public static boolean[] unbox(Boolean[] array){ if(array == null){ return null; @@ -504,11 +513,11 @@ public static boolean[] unbox(Boolean[] array){ } return newArray; } - + /*************************************************************************** * Boxes ***************************************************************************/ - + /** * "Boxes" an array, that is, boxes all the primitives in the given array, * and returns a new "boxed" array. @@ -527,7 +536,7 @@ public static Character[] box(char[] array){ } return newArray; } - + /** * "Boxes" an array, that is, boxes all the primitives in the given array, * and returns a new "boxed" array. @@ -546,7 +555,7 @@ public static Byte[] box(byte[] array){ } return newArray; } - + /** * "Boxes" an array, that is, boxes all the primitives in the given array, * and returns a new "boxed" array. @@ -565,7 +574,7 @@ public static Short[] box(short[] array){ } return newArray; } - + /** * "Boxes" an array, that is, boxes all the primitives in the given array, * and returns a new "boxed" array. @@ -584,7 +593,7 @@ public static Integer[] box(int[] array){ } return newArray; } - + /** * "Boxes" an array, that is, boxes all the primitives in the given array, * and returns a new "boxed" array. @@ -603,7 +612,7 @@ public static Long[] box(long[] array){ } return newArray; } - + /** * "Boxes" an array, that is, boxes all the primitives in the given array, * and returns a new "boxed" array. @@ -622,7 +631,7 @@ public static Float[] box(float[] array){ } return newArray; } - + /** * "Boxes" an array, that is, boxes all the primitives in the given array, * and returns a new "boxed" array. @@ -641,7 +650,7 @@ public static Double[] box(double[] array){ } return newArray; } - + /** * "Boxes" an array, that is, boxes all the primitives in the given array, * and returns a new "boxed" array. @@ -660,25 +669,26 @@ public static Boolean[] box(boolean[] array){ } return newArray; } - + /*************************************************************************** * Misc ***************************************************************************/ - + /** * Returns a new array, based on the runtime type of the list. * @param * @param list - * @return + * @return */ - public static T[] asArray(Class clazz, List list){ + @SuppressWarnings("unchecked") + public static T[] asArray(Class clazz, List list){ T[] obj = (T[]) Array.newInstance(clazz, list.size()); for(int i = 0; i < list.size(); i++){ - obj[i] = (T)list.get(i); + obj[i] = list.get(i); } return obj; - } - + } + /** * Returns a new array, where each item has been cast to the * specified class, and the returned array is an array type @@ -687,24 +697,26 @@ public static T[] asArray(Class clazz, List list){ * @param array Despite being an Object, instead of an Object[], this will throw a ClassCastException * if it is not an array type. * @param toClass - * @return + * @return */ + @SuppressWarnings("unchecked") public static T cast(Object array, Class toArrayClass){ if(!array.getClass().isArray()){ throw new ClassCastException(); } Object obj; - Class toClass = toArrayClass.getComponentType(); + Class toClass = toArrayClass.getComponentType(); obj = toArrayClass.cast(Array.newInstance(toClass, Array.getLength(array))); - for(int i = 0; i < Array.getLength(array); i++){ + for(int i = 0; i < Array.getLength(array); i++){ doSet(obj, i, Array.get(array, i)); } return (T)obj; } - + + @SuppressWarnings("UnnecessaryUnboxing") private static void doSet(Object array, int index, Object o){ - Class componentType = array.getClass().getComponentType(); + Class componentType = array.getClass().getComponentType(); if(componentType.isPrimitive()){ if(componentType == char.class){ Array.setChar(array, index, ((Character)o).charValue()); diff --git a/src/main/java/com/laytonsmith/core/events/drivers/CmdlineEvents.java b/src/main/java/com/laytonsmith/core/events/drivers/CmdlineEvents.java index 34c3734fe..3a4f32e91 100644 --- a/src/main/java/com/laytonsmith/core/events/drivers/CmdlineEvents.java +++ b/src/main/java/com/laytonsmith/core/events/drivers/CmdlineEvents.java @@ -7,7 +7,9 @@ import com.laytonsmith.annotations.core; import com.laytonsmith.annotations.hide; import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.Construct; @@ -143,7 +145,7 @@ public String docs() { + " Fired when a command is issued from the interactive prompt. If the event is not" + " cancelled, the interpreter will handle it as normal. Otherwise, the event can" + " be cancelled, and custom handling can be triggered." - + " {command: The command that was triggered}" + + " {command: The command that was triggered, shellMode: If the shell is in shell mode (activated with $$)}" + " {}" + " {}"; } @@ -155,7 +157,8 @@ public boolean matches(Map prefilter, BindableEvent e) throws @Override public BindableEvent convert(CArray manualObject, Target t) { - CmdlinePromptInput cpi = new CmdlinePromptInput(manualObject.get("command", t).val()); + CmdlinePromptInput cpi = new CmdlinePromptInput(manualObject.get("command", t).val(), + Static.getBoolean(manualObject.get("shellMode", t))); return cpi; } @@ -164,6 +167,7 @@ public Map evaluate(BindableEvent e) throws EventException { CmdlinePromptInput cpi = (CmdlinePromptInput)e; Map map = new HashMap<>(); map.put("command", new CString(cpi.getCommand(), Target.UNKNOWN)); + map.put("shellMode", CBoolean.get(cpi.isShellMode())); return map; } @@ -191,8 +195,10 @@ public static class CmdlinePromptInput implements BindableEvent, CancellableEven private boolean isCancelled = false; private final String command; - public CmdlinePromptInput(String command){ + private final boolean shellMode; + public CmdlinePromptInput(String command, boolean shellMode){ this.command = command; + this.shellMode = shellMode; } @Override @@ -213,7 +219,9 @@ public boolean isCancelled(){ return isCancelled; } - + public boolean isShellMode() { + return this.shellMode; + } } diff --git a/src/main/java/com/laytonsmith/core/functions/Cmdline.java b/src/main/java/com/laytonsmith/core/functions/Cmdline.java index fb64c77ac..8c5c8d328 100644 --- a/src/main/java/com/laytonsmith/core/functions/Cmdline.java +++ b/src/main/java/com/laytonsmith/core/functions/Cmdline.java @@ -1736,7 +1736,8 @@ public Integer[] numArgs() { public String docs() { return "void {closure} Sets the cmdline prompt. This is only usable or useful in cmdline interpreter mode. The closure should" + " return a string, that string will be used as the prompt. The closure is called each time a prompt needs generating," - + " thereby allowing for dynamic prompts."; + + " thereby allowing for dynamic prompts. A boolean is sent to the closure, if true, the shell is in shellMode, meaning" + + " the command is interpreted as a shell command. If false, it is in normal mscript mode."; } @Override diff --git a/src/main/java/com/laytonsmith/tools/Interpreter.java b/src/main/java/com/laytonsmith/tools/Interpreter.java index 7f0bea72d..4cd350749 100644 --- a/src/main/java/com/laytonsmith/tools/Interpreter.java +++ b/src/main/java/com/laytonsmith/tools/Interpreter.java @@ -1,8 +1,10 @@ package com.laytonsmith.tools; +import com.laytonsmith.PureUtilities.Common.ArrayUtils; import com.laytonsmith.PureUtilities.Common.FileUtil; import com.laytonsmith.PureUtilities.Common.MutableObject; import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.LimitedQueue; import com.laytonsmith.PureUtilities.RunnableQueue; import com.laytonsmith.PureUtilities.SignalHandler; @@ -93,174 +95,187 @@ import static com.laytonsmith.PureUtilities.TermColors.p; import static com.laytonsmith.PureUtilities.TermColors.pl; import static com.laytonsmith.PureUtilities.TermColors.reset; +import com.laytonsmith.core.constructs.CBoolean; +import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.exceptions.CRE.CREIOException; +import com.laytonsmith.core.functions.Cmdline; +import com.laytonsmith.core.functions.Echoes; +import java.util.Locale; /** - * This is a command line implementation of the in game interpreter mode. This - * should only be run while the server is stopped, as it has full access to - * filesystem resources. Many things won't work as intended, but pure abstract + * This is a command line implementation of the in game interpreter mode. This should only be run while the server is + * stopped, as it has full access to filesystem resources. Many things won't work as intended, but pure abstract * functions should still work fine. */ public final class Interpreter { - /** - * THIS MUST NEVER EVER EVER EVER EVER EVER EVER CHANGE. EVER. - * - * BAD THINGS WILL HAPPEN TO EVERYBODY YOU LOVE IF THIS IS CHANGED! - */ - private static final String INTERPRETER_INSTALLATION_LOCATION = "/usr/local/bin/mscript"; - - private boolean inTTYMode = false; - private boolean multilineMode = false; - private String script = ""; - private Environment env; - private Thread scriptThread = null; - - private volatile boolean isExecuting = false; - - private final Queue commandHistory = new LimitedQueue<>(MAX_COMMAND_HISTORY); - - /** - * If they mash ctrlC a bunch, they probably really want to quit, so we'll - * keep track of this, and reset it only if they then run an actual command. - */ - private volatile int ctrlCcount = 0; - - /** - * After this many mashes of Ctrl+C, clearly they want to exit, so we'll - * exit the shell. - */ - private static final int MAX_CTRL_C_MASHES = 5; - - /** - * Max commands that are tracked. - */ - private static final int MAX_COMMAND_HISTORY = 100; - - public static void startWithTTY(String file, List args) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { - File fromFile = new File(file).getCanonicalFile(); - Interpreter interpreter = new Interpreter(args, fromFile.getParentFile().getPath(), true); - try { - interpreter.execute(FileUtil.read(fromFile), args, fromFile); - } catch (ConfigCompileException ex) { - ConfigRuntimeException.HandleUncaughtException(ex, null, null); - StreamUtils.GetSystemOut().println(TermColors.reset()); - System.exit(1); - } catch(ConfigCompileGroupException ex){ - ConfigRuntimeException.HandleUncaughtException(ex, null); - StreamUtils.GetSystemOut().println(TermColors.reset()); - System.exit(1); - } + /** + * THIS MUST NEVER EVER EVER EVER EVER EVER EVER CHANGE. EVER. + * + * BAD THINGS WILL HAPPEN TO EVERYBODY YOU LOVE IF THIS IS CHANGED! + */ + private static final String INTERPRETER_INSTALLATION_LOCATION = "/usr/local/bin/mscript"; + + private boolean inTTYMode = false; + private boolean multilineMode = false; + private boolean inShellMode = false; + private String script = ""; + private Environment env; + private Thread scriptThread = null; + + private volatile boolean isExecuting = false; + + private final Queue commandHistory = new LimitedQueue<>(MAX_COMMAND_HISTORY); + + /** + * If they mash ctrlC a bunch, they probably really want to quit, so we'll keep track of this, and reset it only if + * they then run an actual command. + */ + private volatile int ctrlCcount = 0; + + /** + * After this many mashes of Ctrl+C, clearly they want to exit, so we'll exit the shell. + */ + private static final int MAX_CTRL_C_MASHES = 5; + + /** + * Max commands that are tracked. + */ + private static final int MAX_COMMAND_HISTORY = 100; + + public static void startWithTTY(String file, List args) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { + File fromFile = new File(file).getCanonicalFile(); + Interpreter interpreter = new Interpreter(args, fromFile.getParentFile().getPath(), true); + try { + interpreter.execute(FileUtil.read(fromFile), args, fromFile); + } catch (ConfigCompileException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null, null); + StreamUtils.GetSystemOut().println(TermColors.reset()); + System.exit(1); + } catch (ConfigCompileGroupException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null); + StreamUtils.GetSystemOut().println(TermColors.reset()); + System.exit(1); } - - private String getHelpMsg(){ - String msg = YELLOW + "You are now in cmdline interpreter mode. Use exit() to exit, and >>> to enter" - + " multiline mode."; - try { - msg += "\nYour current working directory is: " + env.getEnv(GlobalEnv.class).GetRootFolder().getCanonicalPath(); - } catch (IOException ex) { - // + } + + private String getHelpMsg() { + String msg = YELLOW + "You are now in cmdline interpreter mode.\n" + + "- on a line by itself (outside of mulitline mode), or the exit() command exits the shell.\n" + + ">>> on a line by itself starts multiline mode, where multiple lines can be written, but not yet executed.\n" + + "<<< on a line by itself ends multiline mode, and executes the buffered script.\n" + + "- on a line by itself while in multiline mode cancels multiline mode, and clears the buffer, without executing the buffered script.\n" + + "If the line starts with $$, then the rest of the line is taken to be a shell command. The command is taken as a string, wrapped\n" + + "in shell_adv(), (where system out and system err are piped to the corresponding outputs).\n" + + "If $$ is on a line by itself, it puts the shell in shell_adv mode, and each line is taken as if it started\n" + + "with $$. Use - on a line by itself to exit this mode as well."; + try { + msg += "\nYour current working directory is: " + env.getEnv(GlobalEnv.class).GetRootFolder().getCanonicalPath(); + } catch (IOException ex) { + // + } + return msg; + } + + /** + * Creates a new Interpreter object. This object can then be manipulated via the cmdline interactively, or + * standalone, via the execute method. + * + * @param args Any arguments passed in to the script. They are set as $vars + * @param cwd The initial working directory. + * @throws IOException + * @throws DataSourceException + * @throws URISyntaxException + */ + public Interpreter(List args, String cwd) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { + this(args, cwd, false); + } + + private Interpreter(List args, String cwd, boolean inTTYMode) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { + doStartup(); + env.getEnv(GlobalEnv.class).SetRootFolder(new File(cwd)); + if (inTTYMode) { + //Ok, done. They'll have to execute from here. + return; + } + //We have two modes here, piped input, or interactive console. + if (System.console() == null) { + Scanner scanner = new Scanner(System.in); + //We need to read in everything, it's basically in multiline mode + StringBuilder script = new StringBuilder(); + String line; + try { + while ((line = scanner.nextLine()) != null) { + script.append(line).append("\n"); } - return msg; - } - - /** - * Creates a new Interpreter object. This object can then be manipulated via - * the cmdline interactively, or standalone, via the execute method. - * - * @param args Any arguments passed in to the script. They are set as $vars - * @param cwd The initial working directory. - * @throws IOException - * @throws DataSourceException - * @throws URISyntaxException - */ - public Interpreter(List args, String cwd) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { - this(args, cwd, false); - } - - private Interpreter(List args, String cwd, boolean inTTYMode) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { - doStartup(); - env.getEnv(GlobalEnv.class).SetRootFolder(new File(cwd)); - if(inTTYMode){ - //Ok, done. They'll have to execute from here. - return; + } catch (NoSuchElementException e) { + //Done + } + try { + execute(script.toString(), args); + StreamUtils.GetSystemOut().print(TermColors.reset()); + System.exit(0); + } catch (ConfigCompileException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null, null); + StreamUtils.GetSystemOut().print(TermColors.reset()); + System.exit(1); + } catch (ConfigCompileGroupException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null); + StreamUtils.GetSystemOut().println(TermColors.reset()); + System.exit(1); + } + + } else { + final ConsoleReader reader = new ConsoleReader(); + reader.setExpandEvents(false); + //Get a list of all the function names. This will be provided to the auto completer. + Set functions = FunctionList.getFunctionList(api.Platforms.INTERPRETER_JAVA); + List names = new ArrayList<>(); + for (FunctionBase f : functions) { + if (f.appearInDocumentation()) { + names.add(f.getName()); } - //We have two modes here, piped input, or interactive console. - if (System.console() == null) { - Scanner scanner = new Scanner(System.in); - //We need to read in everything, it's basically in multiline mode - StringBuilder script = new StringBuilder(); - String line; - try { - while ((line = scanner.nextLine()) != null) { - script.append(line).append("\n"); - } - } catch (NoSuchElementException e) { - //Done - } - try { - execute(script.toString(), args); - StreamUtils.GetSystemOut().print(TermColors.reset()); - System.exit(0); - } catch (ConfigCompileException ex) { - ConfigRuntimeException.HandleUncaughtException(ex, null, null); - StreamUtils.GetSystemOut().print(TermColors.reset()); - System.exit(1); - } catch(ConfigCompileGroupException ex){ - ConfigRuntimeException.HandleUncaughtException(ex, null); - StreamUtils.GetSystemOut().println(TermColors.reset()); - System.exit(1); - } + } + reader.addCompleter(new ArgumentCompleter(new ArgumentCompleter.AbstractArgumentDelimiter() { - } else { - final ConsoleReader reader = new ConsoleReader(); - reader.setExpandEvents(false); - //Get a list of all the function names. This will be provided to the auto completer. - Set functions = FunctionList.getFunctionList(api.Platforms.INTERPRETER_JAVA); - List names = new ArrayList<>(); - for(FunctionBase f : functions){ - if(f.appearInDocumentation()){ - names.add(f.getName()); - } - } - reader.addCompleter(new ArgumentCompleter(new ArgumentCompleter.AbstractArgumentDelimiter() { - - @Override - public boolean isDelimiterChar(CharSequence buffer, int pos) { - char c = buffer.charAt(pos); - return !Character.isLetter(c) && c != '_'; - } - }, new StringsCompleter(names){ + @Override + public boolean isDelimiterChar(CharSequence buffer, int pos) { + char c = buffer.charAt(pos); + return !Character.isLetter(c) && c != '_'; + } + }, new StringsCompleter(names) { - @Override - public int complete(String buffer, int cursor, List candidates) { - //The autocomplete can be improved a bit, instead of putting a space after it, - //let's put a parenthesis. - int ret = super.complete(buffer, cursor, candidates); - if(candidates.size() == 1){ - String functionName = candidates.get(0).toString().trim(); - candidates.set(0, functionName + "()"); - } - return ret; - } + @Override + public int complete(String buffer, int cursor, List candidates) { + //The autocomplete can be improved a bit, instead of putting a space after it, + //let's put a parenthesis. + int ret = super.complete(buffer, cursor, candidates); + if (candidates.size() == 1) { + String functionName = candidates.get(0).toString().trim(); + candidates.set(0, functionName + "()"); + } + return ret; + } - })); - while(true){ - String prompt; - if(multilineMode){ - prompt = TermColors.WHITE + ">" + reset(); - } else { - prompt = getPrompt(); - } - String line = reader.readLine(prompt); - if(!textLine(line)){ - break; - } - } + })); + while (true) { + String prompt; + if (multilineMode) { + prompt = TermColors.WHITE + ">" + reset(); + } else { + prompt = getPrompt(); + } + String line = reader.readLine(prompt); + if (!textLine(line)) { + break; + } + } - //Perhaps this code will be revisited in the future, so that more things - //can be done, like syntax highlighting, function keys, etc, but in order - //to do that, history, command completion, etc, will all have to be re-implemented, - //and implemented around readCharacter, which is a lot of work. + //Perhaps this code will be revisited in the future, so that more things + //can be done, like syntax highlighting, function keys, etc, but in order + //to do that, history, command completion, etc, will all have to be re-implemented, + //and implemented around readCharacter, which is a lot of work. // p(getPrompt()); // boolean exit = false; // while(true){ @@ -413,515 +428,625 @@ public int complete(String buffer, int cursor, List candidates) { // p(getPrompt()); // } // } - } } - - private String getPrompt(){ - CClosure c = (CClosure) env.getEnv(GlobalEnv.class).GetCustom("cmdline_prompt"); - if(c != null){ - try { - c.execute(); - } catch(FunctionReturnException ex){ - String val = ex.getReturn().val(); - return Static.MCToANSIColors(val) + TermColors.RESET; - } catch(ConfigRuntimeException ex){ - ConfigRuntimeException.HandleUncaughtException(ex, env); - } - } - return BLUE + ":" + TermColors.RESET; + } + + private String getPrompt() { + CClosure c = (CClosure) env.getEnv(GlobalEnv.class).GetCustom("cmdline_prompt"); + if (c != null) { + try { + c.execute(CBoolean.get(inShellMode)); + } catch (FunctionReturnException ex) { + String val = ex.getReturn().val(); + return Static.MCToANSIColors(val) + TermColors.RESET; + } catch (ConfigRuntimeException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, env); + } } + return BLUE + ":" + TermColors.RESET; + } - private void doStartup() throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { + private void doStartup() throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { - Installer.Install(MethodScriptFileLocations.getDefault().getConfigDirectory()); - Installer.InstallCmdlineInterpreter(); + Installer.Install(MethodScriptFileLocations.getDefault().getConfigDirectory()); + Installer.InstallCmdlineInterpreter(); - env = Static.GenerateStandaloneEnvironment(); - env.getEnv(GlobalEnv.class).SetCustom("cmdline", true); - if (Prefs.UseColors()) { - TermColors.EnableColors(); - } else { - TermColors.DisableColors(); - } - - String auto_include = FileUtil.read(MethodScriptFileLocations.getDefault().getCmdlineInterpreterAutoIncludeFile()); - try { - MethodScriptCompiler.execute(auto_include, MethodScriptFileLocations.getDefault().getCmdlineInterpreterAutoIncludeFile(), true, env, null, null, null); - } catch (ConfigCompileException ex) { - ConfigRuntimeException.HandleUncaughtException(ex, "Interpreter will continue to run, however.", null); - } catch (ConfigCompileGroupException ex){ - ConfigRuntimeException.HandleUncaughtException(ex, null); - } - //Install our signal handlers. - SignalHandler.SignalCallback signalHandler = new SignalHandler.SignalCallback() { - - @Override - public boolean handle(SignalType type) { - if(isExecuting){ - env.getEnv(GlobalEnv.class).SetInterrupt(true); - if(scriptThread != null){ - scriptThread.interrupt(); - } - for(Thread t : env.getEnv(GlobalEnv.class).GetDaemonManager().getActiveThreads()){ - t.interrupt(); - } - } else { - ctrlCcount++; - if(ctrlCcount > MAX_CTRL_C_MASHES){ - //Ok, ok, we get the hint. - StreamUtils.GetSystemOut().println(); - StreamUtils.GetSystemOut().flush(); - System.exit(130); //Standard Ctrl+C exit code - } - pl(YELLOW + "\nUse exit() to exit the shell." + reset()); - p(getPrompt()); - } - return true; - } - }; - try { - SignalHandler.addHandler(Signals.SIGTERM, signalHandler); - } catch(IllegalArgumentException ex){ - // Oh well. - } - try { - SignalHandler.addHandler(Signals.SIGINT, signalHandler); - } catch (IllegalArgumentException ex){ - // Oh well again. - } + env = Static.GenerateStandaloneEnvironment(); + env.getEnv(GlobalEnv.class).SetCustom("cmdline", true); + if (Prefs.UseColors()) { + TermColors.EnableColors(); + } else { + TermColors.DisableColors(); } - /** - * This evaluates each line of text - * @param line - * @return - * @throws IOException - */ - private boolean textLine(String line) throws IOException { - switch (line) { - case "-": - //Exit interpreter mode - return false; - case ">>>": - //Start multiline mode - if (multilineMode) { - pl(RED + "You are already in multiline mode!"); - } else { - multilineMode = true; - pl(YELLOW + "You are now in multiline mode. Type <<< on a line by itself to execute."); - } break; - case "<<<": - //Execute multiline - multilineMode = false; - try { - execute(script, null); - script = ""; - } catch (ConfigCompileException e) { - ConfigRuntimeException.HandleUncaughtException(e, null, null); - } catch(ConfigCompileGroupException e){ - ConfigRuntimeException.HandleUncaughtException(e, null); - } - break; - default: - if (multilineMode) { - //Queue multiline - script = script + line + "\n"; - } else { - try { - //Execute single line - execute(line, null); - } catch (ConfigCompileException ex) { - ConfigRuntimeException.HandleUncaughtException(ex, null, null); - } catch(ConfigCompileGroupException ex){ - ConfigRuntimeException.HandleUncaughtException(ex, null); - } - } break; + String auto_include = FileUtil.read(MethodScriptFileLocations.getDefault().getCmdlineInterpreterAutoIncludeFile()); + try { + MethodScriptCompiler.execute(auto_include, MethodScriptFileLocations.getDefault().getCmdlineInterpreterAutoIncludeFile(), true, env, null, null, null); + } catch (ConfigCompileException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, "Interpreter will continue to run, however.", null); + } catch (ConfigCompileGroupException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null); + } + //Install our signal handlers. + SignalHandler.SignalCallback signalHandler = new SignalHandler.SignalCallback() { + + @Override + public boolean handle(SignalType type) { + if (isExecuting) { + env.getEnv(GlobalEnv.class).SetInterrupt(true); + if (scriptThread != null) { + scriptThread.interrupt(); + } + for (Thread t : env.getEnv(GlobalEnv.class).GetDaemonManager().getActiveThreads()) { + t.interrupt(); + } + } else { + ctrlCcount++; + if (ctrlCcount > MAX_CTRL_C_MASHES) { + //Ok, ok, we get the hint. + StreamUtils.GetSystemOut().println(); + StreamUtils.GetSystemOut().flush(); + System.exit(130); //Standard Ctrl+C exit code + } + pl(YELLOW + "\nUse exit() to exit the shell." + reset()); + p(getPrompt()); } return true; + } + }; + try { + SignalHandler.addHandler(Signals.SIGTERM, signalHandler); + } catch (IllegalArgumentException ex) { + // Oh well. } - - /** - * This executes a script - * @param script - * @param args - * @throws ConfigCompileException - * @throws IOException - */ - public void execute(String script, List args) throws ConfigCompileException, IOException, ConfigCompileGroupException { - execute(script, args, null); - } - - /** - * This executes an entire script. The cmdline_prompt_event is first triggered (if used) and - * if the event is cancelled, nothing happens. - * @param script - * @param args - * @param fromFile - * @throws ConfigCompileException - * @throws IOException - */ - public void execute(String script, List args, File fromFile) throws ConfigCompileException, IOException, ConfigCompileGroupException { - CmdlineEvents.cmdline_prompt_input.CmdlinePromptInput input = new CmdlineEvents.cmdline_prompt_input.CmdlinePromptInput(script); - EventUtils.TriggerListener(Driver.CMDLINE_PROMPT_INPUT, "cmdline_prompt_input", input); - if(input.isCancelled()){ - return; + try { + SignalHandler.addHandler(Signals.SIGINT, signalHandler); + } catch (IllegalArgumentException ex) { + // Oh well again. + } + } + + /** + * This evaluates each line of text + * + * @param line + * @return + * @throws IOException + */ + private boolean textLine(String line) throws IOException { + switch (line) { + case "-": + //Exit interpreter mode + if(multilineMode) { + script = ""; + } else if(inShellMode) { + inShellMode = false; + } else { + return false; } - ctrlCcount = 0; - if("exit".equals(script)){ - pl(YELLOW + "Use exit() if you wish to exit."); - return; + break; + case ">>>": + //Start multiline mode + if (multilineMode) { + pl(RED + "You are already in multiline mode!"); + } else { + multilineMode = true; + pl(YELLOW + "You are now in multiline mode. Type <<< on a line by itself to execute."); } - if("help".equals(script)){ - pl(getHelpMsg()); - return; + break; + case "<<<": + //Execute multiline + multilineMode = false; + try { + execute(script, null); + script = ""; + } catch (ConfigCompileException e) { + ConfigRuntimeException.HandleUncaughtException(e, null, null); + } catch (ConfigCompileGroupException e) { + ConfigRuntimeException.HandleUncaughtException(e, null); } - if (fromFile == null) { - fromFile = new File("Interpreter"); + break; + case "$$": + inShellMode = true; + break; + default: + if (multilineMode) { + //Queue multiline + script = script + line + "\n"; + } else { + try { + //Execute single line + execute(line, null); + } catch (ConfigCompileException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null, null); + } catch (ConfigCompileGroupException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null); + } } - isExecuting = true; - ProfilePoint compile = env.getEnv(GlobalEnv.class).GetProfiler().start("Compilation", LogLevel.VERBOSE); - final ParseTree tree; - try { - List stream = MethodScriptCompiler.lex(script, fromFile, true); - tree = MethodScriptCompiler.compile(stream); - } finally { - compile.stop(); + break; + } + return true; + } + + /** + * This executes a script + * + * @param script + * @param args + * @throws ConfigCompileException + * @throws IOException + */ + public void execute(String script, List args) throws ConfigCompileException, IOException, ConfigCompileGroupException { + execute(script, args, null); + } + + /** + * This executes an entire script. The cmdline_prompt_event is first triggered (if used) and if the event is + * cancelled, nothing happens. + * + * @param script + * @param args + * @param fromFile + * @throws ConfigCompileException + * @throws IOException + */ + public void execute(String script, List args, File fromFile) throws ConfigCompileException, IOException, ConfigCompileGroupException { + CmdlineEvents.cmdline_prompt_input.CmdlinePromptInput input = new CmdlineEvents.cmdline_prompt_input.CmdlinePromptInput(script, inShellMode); + EventUtils.TriggerListener(Driver.CMDLINE_PROMPT_INPUT, "cmdline_prompt_input", input); + if (input.isCancelled()) { + return; + } + ctrlCcount = 0; + if ("exit".equals(script)) { + if(inShellMode) { + inShellMode = false; + return; + } + pl(YELLOW + "Use exit() if you wish to exit."); + return; + } + if ("help".equals(script)) { + pl(getHelpMsg()); + return; + } + if (fromFile == null) { + fromFile = new File("Interpreter"); + } + boolean localShellMode = false; + if(!inShellMode && script.startsWith("$$")) { + localShellMode = true; + script = script.substring(2); + } + + if(inShellMode || localShellMode) { + // Wrap this in shell_adv + if(doBuiltin(script)) { + return; + } + List shellArgs = StringUtils.ArgParser(script); + List escapedArgs = new ArrayList<>(); + for(String arg : shellArgs) { + escapedArgs.add(new CString(arg, Target.UNKNOWN).getQuote()); + } + script = "shell_adv(" + + "array(" + + StringUtils.Join(escapedArgs, ",") + + ")," + + "array(" + + "'stdout':closure(@l){sys_out(@l);}," + + "'stderr':closure(@l){sys_err(@l);})" + + ");"; + } + isExecuting = true; + ProfilePoint compile = env.getEnv(GlobalEnv.class).GetProfiler().start("Compilation", LogLevel.VERBOSE); + final ParseTree tree; + try { + List stream = MethodScriptCompiler.lex(script, fromFile, true); + tree = MethodScriptCompiler.compile(stream); + } finally { + compile.stop(); + } + //Environment env = Environment.createEnvironment(this.env.getEnv(GlobalEnv.class)); + final List vars = new ArrayList<>(); + if (args != null) { + //Build the @arguments variable, the $ vars, and $ itself. Note that + //we have special handling for $0, that is the script name, like bash. + //However, it doesn't get added to either $ or @arguments, due to the + //uncommon use of it. + StringBuilder finalArgument = new StringBuilder(); + CArray arguments = new CArray(Target.UNKNOWN); + { + //Set the $0 argument + Variable v = new Variable("$0", "", Target.UNKNOWN); + v.setVal(fromFile.toString()); + v.setDefault(fromFile.toString()); + vars.add(v); + } + for (int i = 0; i < args.size(); i++) { + String arg = args.get(i); + if (i > 0) { + finalArgument.append(" "); } - //Environment env = Environment.createEnvironment(this.env.getEnv(GlobalEnv.class)); - final List vars = new ArrayList<>(); - if (args != null) { - //Build the @arguments variable, the $ vars, and $ itself. Note that - //we have special handling for $0, that is the script name, like bash. - //However, it doesn't get added to either $ or @arguments, due to the - //uncommon use of it. - StringBuilder finalArgument = new StringBuilder(); - CArray arguments = new CArray(Target.UNKNOWN); - { - //Set the $0 argument - Variable v = new Variable("$0", "", Target.UNKNOWN); - v.setVal(fromFile.toString()); - v.setDefault(fromFile.toString()); - vars.add(v); - } - for (int i = 0; i < args.size(); i++) { - String arg = args.get(i); - if (i > 0) { - finalArgument.append(" "); + Variable v = new Variable("$" + Integer.toString(i + 1), "", Target.UNKNOWN); + v.setVal(new CString(arg, Target.UNKNOWN)); + v.setDefault(arg); + vars.add(v); + finalArgument.append(arg); + arguments.push(new CString(arg, Target.UNKNOWN), Target.UNKNOWN); + } + Variable v = new Variable("$", "", false, true, Target.UNKNOWN); + v.setVal(new CString(finalArgument.toString(), Target.UNKNOWN)); + v.setDefault(finalArgument.toString()); + vars.add(v); + env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(new CClassType("array", Target.UNKNOWN), "@arguments", arguments, Target.UNKNOWN)); + } + try { + ProfilePoint p = this.env.getEnv(GlobalEnv.class).GetProfiler().start("Interpreter Script", LogLevel.ERROR); + try { + final MutableObject wasThrown = new MutableObject<>(); + scriptThread = new Thread(new Runnable() { + + @Override + public void run() { + try { + MethodScriptCompiler.execute(tree, env, new MethodScriptComplete() { + @Override + public void done(String output) { + if (System.console() != null && !"".equals(output.trim())) { + StreamUtils.GetSystemOut().println(output); + } } - Variable v = new Variable("$" + Integer.toString(i + 1), "", Target.UNKNOWN); - v.setVal(new CString(arg, Target.UNKNOWN)); - v.setDefault(arg); - vars.add(v); - finalArgument.append(arg); - arguments.push(new CString(arg, Target.UNKNOWN), Target.UNKNOWN); + }, null, vars); + } catch (CancelCommandException e) { + //Nothing, though we could have been Ctrl+C cancelled, so we need to reset + //the interrupt flag. But we do that unconditionally below, in the finally, + //in the other thread. + } catch (ConfigRuntimeException e) { + ConfigRuntimeException.HandleUncaughtException(e, env); + //No need for the full stack trace + if (System.console() == null) { + System.exit(1); + } + } catch (NoClassDefFoundError e) { + StreamUtils.GetSystemErr().println(RED + Main.getNoClassDefFoundErrorMessage(e) + reset()); + StreamUtils.GetSystemErr().println("Since you're running from standalone interpreter mode, this is not a fatal error, but one of the functions you just used required" + + " an actual backing engine that isn't currently loaded. (It still might fail even if you load the engine though.) You simply won't be" + + " able to use that function here."); + if (System.console() == null) { + System.exit(1); + } + } catch (InvalidEnvironmentException ex) { + StreamUtils.GetSystemErr().println(RED + ex.getMessage() + " " + ex.getData() + "() cannot be used in this context."); + if (System.console() == null) { + System.exit(1); + } + } catch (RuntimeException e) { + pl(RED + e.toString()); + e.printStackTrace(StreamUtils.GetSystemErr()); + if (System.console() == null) { + System.exit(1); + } } - Variable v = new Variable("$", "", false, true, Target.UNKNOWN); - v.setVal(new CString(finalArgument.toString(), Target.UNKNOWN)); - v.setDefault(finalArgument.toString()); - vars.add(v); - env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(new CClassType("array", Target.UNKNOWN), "@arguments", arguments, Target.UNKNOWN)); + } + }, "MethodScript-Main"); + scriptThread.start(); + try { + scriptThread.join(); + } catch (InterruptedException ex) { + // } try { - ProfilePoint p = this.env.getEnv(GlobalEnv.class).GetProfiler().start("Interpreter Script", LogLevel.ERROR); - try { - final MutableObject wasThrown = new MutableObject<>(); - scriptThread = new Thread(new Runnable() { - - @Override - public void run() { - try { - MethodScriptCompiler.execute(tree, env, new MethodScriptComplete() { - @Override - public void done(String output) { - if(System.console() != null && !"".equals(output.trim())){ - StreamUtils.GetSystemOut().println(output); - } - } - }, null, vars); - } catch (CancelCommandException e) { - //Nothing, though we could have been Ctrl+C cancelled, so we need to reset - //the interrupt flag. But we do that unconditionally below, in the finally, - //in the other thread. - } catch (ConfigRuntimeException e) { - ConfigRuntimeException.HandleUncaughtException(e, env); - //No need for the full stack trace - if (System.console() == null) { - System.exit(1); - } - } catch (NoClassDefFoundError e) { - StreamUtils.GetSystemErr().println(RED + Main.getNoClassDefFoundErrorMessage(e) + reset()); - StreamUtils.GetSystemErr().println("Since you're running from standalone interpreter mode, this is not a fatal error, but one of the functions you just used required" - + " an actual backing engine that isn't currently loaded. (It still might fail even if you load the engine though.) You simply won't be" - + " able to use that function here."); - if (System.console() == null) { - System.exit(1); - } - } catch (InvalidEnvironmentException ex) { - StreamUtils.GetSystemErr().println(RED + ex.getMessage() + " " + ex.getData() + "() cannot be used in this context."); - if (System.console() == null) { - System.exit(1); - } - } catch (RuntimeException e) { - pl(RED + e.toString()); - e.printStackTrace(StreamUtils.GetSystemErr()); - if (System.console() == null) { - System.exit(1); - } - } - } - }, "MethodScript-Main"); - scriptThread.start(); - try { - scriptThread.join(); - } catch (InterruptedException ex) { - // - } - try { - env.getEnv(GlobalEnv.class).GetDaemonManager().waitForThreads(); - } catch (InterruptedException ex) { - // - } - } finally { - p.stop(); - } - } finally { - env.getEnv(GlobalEnv.class).SetInterrupt(false); - isExecuting = false; + env.getEnv(GlobalEnv.class).GetDaemonManager().waitForThreads(); + } catch (InterruptedException ex) { + // } + } finally { + p.stop(); + } + } finally { + env.getEnv(GlobalEnv.class).SetInterrupt(false); + isExecuting = false; } - - public static void install() { - if (TermColors.SYSTEM == TermColors.SYS.UNIX) { - try { - URL jar = Interpreter.class.getProtectionDomain().getCodeSource().getLocation(); - File exe = new File(INTERPRETER_INSTALLATION_LOCATION); - String bashScript = Static.GetStringResource("/interpreter-helpers/bash.sh"); - try { - bashScript = bashScript.replaceAll("%%LOCATION%%", jar.toURI().getPath()); - } catch (URISyntaxException ex) { - ex.printStackTrace(); - } - exe.createNewFile(); - if (!exe.canWrite()) { - throw new IOException(); - } - FileUtil.write(bashScript, exe); - exe.setExecutable(true, false); - File manDir = new File("/usr/local/man/man1"); - if (manDir.exists()) { - //Don't do this installation if the man pages aren't already there. - String manPage = Static.GetStringResource("/interpreter-helpers/manpage"); - manPage = DocGenTemplates.DoTemplateReplacement(manPage, DocGenTemplates.GetGenerators()); - File manPageFile = new File(manDir, "mscript.1"); - FileUtil.write(manPage, manPageFile); - } - } catch (IOException e) { - StreamUtils.GetSystemErr().println("Cannot install. You must run the command with sudo for it to succeed, however, did you do that?"); - return; - } - } else { - StreamUtils.GetSystemErr().println("Sorry, cmdline functionality is currently only supported on unix systems! Check back soon though!"); - return; + } + + public boolean doBuiltin(String script) { + List args = StringUtils.ArgParser(script); + if(args.size() > 0) { + String command = args.get(0); + args.remove(0); + command = command.toLowerCase(Locale.ENGLISH); + switch(command) { + case "help": + pl(getHelpMsg()); + pl("Shell builtins:"); + pl("cd - Runs cd() with the provided argument."); + pl("s - equivalent to cd('..')."); + pl("echo - Prints the arguments. If -e is set as the first argument, arguments are sent to colorize() first."); + pl("exit - Exits shellMode, and returns back to normal mscript mode."); + pl("logout - Exits the shell entirely with a return code of 0."); + pl("pwd - Runs pwd()"); + pl("help - Prints this message."); + return true; + case "cd": + case "s": + if("s".equals(command)) { + args.add(".."); + } + if(args.size() > 1) { + pl(RED + "Too many arguments passed to cd"); + return true; + } + Construct[] a = new Construct[0]; + if(args.size() == 1) { + a = new Construct[]{new CString(args.get(0), Target.UNKNOWN)}; + } + try { + new Cmdline.cd().exec(Target.UNKNOWN, env, a); + } catch(CREIOException ex) { + pl(RED + ex.getMessage()); + } + return true; + case "pwd": + pl(new Cmdline.pwd().exec(Target.UNKNOWN, env).val()); + return true; + case "exit": + // We need previous code to intercept, we cannot do this here. + throw new Error("I should not run"); + case "logout": + new Cmdline.exit().exec(Target.UNKNOWN, env, new CInt(0, Target.UNKNOWN)); + return true; // won't actually run + case "echo": + // TODO Probably need some variable interpolation maybe? Otherwise, I don't think this command + // is actually useful as is, because this is not supposed to be a scripting environment.. that's + // what the normal shell is for. + boolean colorize = false; + if(args.size() > 0 && "-e".equals(args.get(0))) { + colorize = true; + args.remove(0); + } + String output = StringUtils.Join(args, " "); + if(colorize) { + output = new Echoes.colorize().exec(Target.UNKNOWN, env, new CString(output, Target.UNKNOWN)).val(); + } + pl(output); + return true; + } + } + return false; + } + + public static void install() { + if (TermColors.SYSTEM == TermColors.SYS.UNIX) { + try { + URL jar = Interpreter.class.getProtectionDomain().getCodeSource().getLocation(); + File exe = new File(INTERPRETER_INSTALLATION_LOCATION); + String bashScript = Static.GetStringResource("/interpreter-helpers/bash.sh"); + try { + bashScript = bashScript.replaceAll("%%LOCATION%%", jar.toURI().getPath()); + } catch (URISyntaxException ex) { + ex.printStackTrace(); + } + exe.createNewFile(); + if (!exe.canWrite()) { + throw new IOException(); + } + FileUtil.write(bashScript, exe); + exe.setExecutable(true, false); + File manDir = new File("/usr/local/man/man1"); + if (manDir.exists()) { + //Don't do this installation if the man pages aren't already there. + String manPage = Static.GetStringResource("/interpreter-helpers/manpage"); + manPage = DocGenTemplates.DoTemplateReplacement(manPage, DocGenTemplates.GetGenerators()); + File manPageFile = new File(manDir, "mscript.1"); + FileUtil.write(manPage, manPageFile); } - StreamUtils.GetSystemOut().println("MethodScript has successfully been installed on your system. Note that you may need to rerun the install command" - + " if you change locations of the jar, or rename it. Be sure to put \"#!" + INTERPRETER_INSTALLATION_LOCATION + "\" at the top of all your scripts," - + " if you wish them to be executable on unix systems, and set the execution bit with chmod +x