package carpet.script; import carpet.script.Fluff.AbstractFunction; import carpet.script.Fluff.AbstractLazyFunction; import carpet.script.Fluff.AbstractLazyOperator; import carpet.script.Fluff.AbstractOperator; import carpet.script.Fluff.AbstractUnaryOperator; import carpet.script.Fluff.ILazyFunction; import carpet.script.Fluff.ILazyOperator; import carpet.script.Fluff.QuadFunction; import carpet.script.Fluff.SexFunction; import carpet.script.Fluff.TriFunction; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.value.FunctionSignatureValue; import carpet.script.value.GlobalValue; import carpet.script.value.LazyListValue; import carpet.script.value.ListValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.settings.CarpetSettings; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.IllegalFormatException; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.Stack; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.min; /** *

Fundamental components of scarpet programming language.

* *

Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language designed to provide * the ability to write custom programs to run within Minecraft and * interact with the world.

* *

The project was initially built based on the EvalEx project, * however it now diverged so far away from the original implementation, * it would be hard to tell it without this mention. * EvalEx is a handy expression evaluator for Java, that * allows to evaluate simple mathematical and boolean expressions. * For more information, see: * EvalEx GitHub * repository

* *

This specification is divided into two sections: this one is agnostic * to any Minecraft related features and could function on its own, and {@see carpet.script.CarpetExpression} for * Minecraft specific routines and world manipulation functions.

* *

Synopsis

* *
 * script run print('Hello World!')
 * 
*

or an overly complex example:

*
 * /script run
 *     block_check(x1, y1, z1, x2, y2, z2, block_to_check) ->
 *     (
 *         l(minx, maxx) = sort(l(x1, x2));
 *         l(miny, maxy) = sort(l(y1, y2));
 *         l(minz, maxz) = sort(l(z1, z2));
 *         'Need to compute the size of the area of course';
 *         'Cause this language doesn\'t support comments';
 *         xsize = maxx - minx + 1;
 *         ysize = maxy - miny + 1;
 *         zsize = maxz - minz + 1;
 *         total_count = 0;
 *         loop(xsize,
 *             xx = minx + _ ;
 *             loop(ysize,
 *                 yy = miny + _ ;
 *                 loop(zsize,
 *                     zz = minz + _ ;
 *                     if ( block(xx,yy,zz) == block_to_check,
 *                         total_count += ceil(rand(1))
 *                     )
 *                 )
 *             )
 *         );
 *         total_count
 *     );
 *     check_area_around_closest_player_for_block(block_to_check) ->
 *     (
 *         closest_player = player();
 *         l(posx, posy, posz) = query(closest_player, 'pos');
 *         total_count = block_check( posx-8,1,posz-8, posx+8,17,posz+8, block_to_check);
 *         print('There is '+total_count+' of '+block_to_check+' around you')
 *     )
 *
 * /script invoke check_area_around_closest_player_for_block 'diamond_ore'
 *
 * 
*

or simply

*
 * script run print('There is'+for(rect(x,9,z,8,8,8), _ == 'diamond_ore')+' diamond ore around you')
 * 
*

It definitely pays to check what higher level scarpet functions have to offer.

*

Programs

*

* You can think of an program like a mathematical expression, like * "2.4*sin(45)/(2-4)" or "sin(y)>0 & max(z, 3)>3" * Writing a program, is like writing a 2+3, just a bit longer

* *

Basic language components

*

Programs consist of constants, like 2, 3.14, pi, or 'foo', * operators like +, /, ->, variables which you can define, like foo * or special ones that will be defined for you, like _x, or _ , which I specific to * a each built in function, and functions with name, and arguments in the form of f(a,b,c), where * f is the function name, and a, b, c are the arguments which can be any other expression. * And that's all the parts of the language, so all in all - sounds quite simple.

* *

Code flow

*

* Like any other proper programming language, scarpet needs brackets, basically to identify * where stuff begins and where it ends. In the languages that uses much more complicated constructs, like Java, * they tend to use all sort of them, round ones to indicate function calls, curly to indicate section of code, * square to access lists, pointy for generic types etc... I mean - there is no etc, cause they have exhausted * all the bracket options... *

*

Scarpet is different, since it runs everything based on functions (although its not per se a functional language like lisp) * only needs the round brackets for everything, and it is up to the programmer to organize its code so its readable, * as adding more brackets does not have any effect on the performance of the programs as they are compiled before they are executed. * Look at the following example usage of if() function: *

*
 * if(x<y+6,set(x,8+y,z,'air');plop(x,top('surface',x,z),z,'birch'),sin(query(player(),'yaw'))>0.5,plop(0,0,0,'boulder'),particle('fire',x,y,z))
 * 
*

Would you prefer to read

*
 * if(   x<y+6,
 *            set(x,8+y,z,'air');
 *            plop(x,top('surface',x,z),z,'birch'),
 *       sin(query(player(),'yaw'))>0.5,
 *            plop(0,0,0,'boulder'),
 *       particle('fire',x,y,z)
 * )
 * 
*

Or rather:

*
 * if
 * (
 *     x<y+6,
 *     (
 *         set(x,8+y,z,'air');
 *         plop(x,top('surface',x,z),z,'birch')
 *     ),
 *
 *     sin(query(player(),'yaw'))>0.5,
 *     (
 *         plop(0,0,0,'boulder')
 *     ),
 *
 *     particle('fire',x,y,z)
 * )
 * 
*

Whichever style you prefer it doesn't matter. It typically depends on the situation and the complexity of the * subcomponents. No matter how many whitespaces and extra brackets you add - the code will evaluate to exactly the * same expression, and will run exactly the same, so make sure your programs are nice and clean so others don't * have problems with them

* *

Functions and scoping

*

* Users can define functions in the form fun(args....) -> expression and they are compiled and saved * for further execution in this but also subsequent calls of /script command. This means that once defined functions * are saved with the world for further use. There are two types of variables, * global - which are shared anywhere in the code, and those are all which name starts with 'global_', and * local variables which is everything else and those are only visible inside each function. * This also means that all the parameters in functions are passed 'by value', not 'by reference'. *

* *

Outer variables

*

Functions can still 'borrow' variables from the outer scope, * by adding them to the function signature wrapped around built-in function outer. What this does is * it borrows the reference to that variable from the outer scope to be used inside the function and any modifications to that outer * variable would result in changes of that value in the outer scope as well. Its like passing the parameters by reference, * except the calling function itself decides what variables its borrowing and what its name. This can be used to * return more than one result from a function call, although its a very ugly way of doing it - * I would still recommend returning a list of values instead. * Variables from outer scopes have a similar behaviour to, for example, nonlocal variables from python.

* * *

Line indicators

*

Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing your * code in packages (files with .sc extension that can be placed inside "/scripts" folder in the world files * and loaded as a module with command /script load [package_name]. Scarpet modules loaded from disk should only * contain code, no need to start with "/script run" prefix

*

Since the maximum command that can be input to the chat is limited in length, you will be probably inserting your * programs by pasting them to command blocks or reading from world files, however pasting to command blocks will remove some whitespaces and squish * your newlines making the code not readable. If you are pasting a program that is perfect and will never cause an error, * I salute you, but for the most part it is quite likely that your program might break, either at compile time, when * its initially analyzed, or at execute time, when you suddenly attempt to divide something by zero. In these cases * you would want to get a meaningful error message, but for that you would need to indicate for the compiler where * did you put these new lines, since command block would squish them. For that, place at the beginning * of the line to let the compiler know where are you. This makes so that $ is the only character that is * illegal in programs, since it will be replaced with new lines. As far as I know, $ is not used * anywhere inside Minecraft identifiers, so this shouldn't hinder the abilities of your programs.

*

Consider the following program executed as command block command:

*
 * /script run
 * run_program() -> (
 *   loop( 10,
 *     foo = floor(rand(10));
 *     check_not_zero(foo);
 *     print(_+' - foo: '+foo);
 *     print('  reciprocal: '+  _/foo )
 *   )
 * );
 * check_not_zero(foo) -> (
 *   if (foo==0, foo = 1)
 * )
 * 
*

Lets say that the intention was to check if the bar is zero and prevent division by zero in print, * but because the foo is passed as a variable, it never changes the original foo value. * Because of the inevitable division by zero, we get the following message: *

*
 * Your math is wrong, Incorrect number format for Infinity at pos 112
 * run_program() -> (  loop( 10,    foo = floor(rand(10));    check_not_zero(foo);
 * print(_+' - foo: '+foo);     HERE>> print('  reciprocal: '+  _/foo )  ));
 * check_not_zero(foo) -> (  if (foo==0, foo = 1))
 * 
* * As we can see, we got our problem where the result of the mathematical operation was not a number (Infinity, so not a number), * however by pasting our program * into the command made it squish the newlines so while it is clear where the error happened and we still can track the error down, * the position of the error (112) is not very helpful and wouldn't be useful if the program gets significantly longer. * To combat this issue we can precede every line of the script with dollar signs $: *
 * /script run
 * $run_program() -> (
 * $  loop( 10,
 * $    foo = floor(rand(10));
 * $    check_not_zero(foo);
 * $    print(_+' - foo: '+foo);
 * $    print('  reciprocal: '+  _/foo )
 * $  )
 * $);
 * $check_not_zero(foo) -> (
 * $  if (foo==0, foo = 1)
 * $)
 * 
* *

Then we get the following error message

* *
 * Your math is wrong, Incorrect number format for Infinity at line 7, pos 5
 *     print(_+' - foo: '+foo);
 *      HERE>> print('  reciprocal: '+  _/foo )
 *   )
 * 
* * *

As we can note not only we get much more concise snippet, but also information about the line * number and position, so means its way easier to locate the potential problems problem

* *

Obviously that's not the way we intended this program to work. To get it foo modified via * a function call, we would either return it as a result and assign it to the new variable: *

*
 *     foo = check_not_zero(foo);
 *     ...
 *     check_not_zero(foo) -> if(foo == 0, 1, foo)
 * 
*

.. or convert it to a global variable, which in this case passing as an argument is not required

*
 *     global_foo = floor(rand(10));
 *     check_foo_not_zero();
 *     ...
 *     check_foo_not_zero() -> if(global_foo == 0, global_foo = 1)
 * 
*

.. or scope foo from the outer function - in this case the inner function has to determine what it is accessing

*
 *     foo = floor(rand(10));
 *     check_foo_not_zero();
 *     ...
 *     check_foo_not_zero(outer(foo)) -> if(foo == 0, foo = 1)
 * 
*

outer scope can only be used in * function signatures to indicate outer variables. They are not arguments, but still you would want to use * locally without affecting other uses of foo in your program. *

*

For the most part - passing arguments as values, and using returned values . * The main usecase of Scarpet would rather be simpler scripts, default scope for all variables is global, unless variable * is declared with local scope explicitly. *

*/ public class Expression implements Cloneable { private static final Map precedence = new HashMap() {{ put("unary+-!", 60); put("exponent^", 40); put("multiplication*/%", 30); put("addition+-", 20); put("compare>=><=<", 10); put("equal==!=", 7); put("and&&", 5); put("or||", 4); put("assign=<>", 3); put("def->", 2); put("nextop;", 1); }}; protected static final Random randomizer = new Random(); static final Value PI = new NumericValue( "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); static final Value euler = new NumericValue( "2.71828182845904523536028747135266249775724709369995957496696762772407663"); // %[argument_index$][flags][width][.precision][t]conversion private static Pattern formatPattern = Pattern.compile("%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"); /** The current infix expression */ private String expression; String getCodeString() {return expression;} private boolean allowNewlineSubstitutions = true; private boolean allowComments = false; void asAModule() { allowNewlineSubstitutions = false; allowComments = true; } private String name; String getName() {return name;} Expression withName(String alias) { name = alias; return this; } /** Cached AST (Abstract Syntax Tree) (root) of the expression */ private LazyValue ast = null; /** script specific operatos and built-in functions */ private Map operators = new HashMap<>(); boolean isAnOperator(String opname) { return operators.containsKey(opname) || operators.containsKey(opname+"u");} private Map functions = new HashMap<>(); Set getFunctionNames() {return functions.keySet();} //static final Map globalFunctions = new HashMap<>(); //static final Map globalVariables = new HashMap<>(); /*static { setGlobals(); } static void setGlobals() { globalVariables.put("euler", (c, t) -> euler); globalVariables.put("pi", (c, t) -> PI); globalVariables.put("null", (c, t) -> Value.NULL); globalVariables.put("true", (c, t) -> Value.TRUE); globalVariables.put("false", (c, t) -> Value.FALSE); //special variables for second order functions so we don't need to check them all the time globalVariables.put("_", (c, t) -> Value.ZERO); globalVariables.put("_i", (c, t) -> Value.ZERO); globalVariables.put("_a", (c, t) -> Value.ZERO); } */ @Override protected Expression clone() throws CloneNotSupportedException { // very very shallow copy for global functions to grab the context for error msgs Expression copy = (Expression) super.clone(); copy.expression = this.expression; copy.name = this.name; return copy; } /* Exception thrown to terminate execution mid expression (aka return statement) */ static class ExitStatement extends RuntimeException { Value retval; ExitStatement(Value value) { retval = value; } } static class ReturnStatement extends ExitStatement { ReturnStatement(Value value) { super(value); } } static class ThrowStatement extends ExitStatement { ThrowStatement(Value value) { super(value); } } static List getExpressionSnippet(Tokenizer.Token token, Expression expr) { String code = expr.getCodeString(); List output = new ArrayList<>(); for (String line: getExpressionSnippetLeftContext(token, code, 1)) { output.add(line); } List context = getExpressionSnippetContext(token, code); output.add(context.get(0)+" HERE>> "+context.get(1)); for (String line: getExpressionSnippetRightContext(token, code, 1)) { output.add(line); } return output; } private static List getExpressionSnippetLeftContext(Tokenizer.Token token, String expr, int contextsize) { List output = new ArrayList<>(); String[] lines = expr.split("\n"); if (lines.length == 1) return output; for (int lno=token.lineno-1; lno >=0 && output.size() < contextsize; lno-- ) { output.add(lines[lno]); } Collections.reverse(output); return output; } private static List getExpressionSnippetContext(Tokenizer.Token token, String expr) { List output = new ArrayList<>(); String[] lines = expr.split("\n"); if (lines.length > 1) { output.add(lines[token.lineno].substring(0, token.linepos)); output.add(lines[token.lineno].substring(token.linepos)); } else { output.add( expr.substring(max(0, token.pos-40), token.pos)); output.add( expr.substring(token.pos, min(token.pos+1+40, expr.length()))); } return output; } private static List getExpressionSnippetRightContext(Tokenizer.Token token, String expr, int contextsize) { List output = new ArrayList<>(); String[] lines = expr.split("\n"); if (lines.length == 1) { return output; } for (int lno=token.lineno+1; lno < lines.length && output.size() < contextsize; lno++ ) { output.add(lines[lno]); } return output; } private void addLazyUnaryOperator(String surface, int precedence, boolean leftAssoc, TriFunction lazyfun) { operators.put(surface+"u", new AbstractLazyOperator(precedence, leftAssoc) { @Override public LazyValue lazyEval(Context c, Integer t, Expression e, Tokenizer.Token token, LazyValue v, LazyValue v2) { try { if (v2 != null) { throw new ExpressionException(e, token, "Did not expect a second parameter for unary operator"); } Value.assertNotNull(v); return lazyfun.apply(c, t, v); } catch (RuntimeException exc) { throw handleCodeException(exc, e, token); } } }); } private void addLazyBinaryOperatorWithDelegation(String surface, int precedence, boolean leftAssoc, SexFunction lazyfun) { operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) { @Override public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2) { try { Value.assertNotNull(v1, v2); return lazyfun.apply(c, type, e, t, v1, v2); } catch (RuntimeException exc) { throw handleCodeException(exc, e, t); } } }); } private void addLazyBinaryOperator(String surface, int precedence, boolean leftAssoc, QuadFunction lazyfun) { operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) { @Override public LazyValue lazyEval(Context c, Integer t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2) { try { Value.assertNotNull(v1, v2); return lazyfun.apply(c, t, v1, v2); } catch (RuntimeException exc) { throw handleCodeException(exc, e, token); } } }); } private RuntimeException handleCodeException(RuntimeException exc, Expression e, Tokenizer.Token token) { if (exc instanceof ExitStatement) return exc; if (exc instanceof InternalExpressionException) return new ExpressionException(e, token, exc.getMessage()); if (exc instanceof ArithmeticException) return new ExpressionException(e, token, "Your math is wrong, "+exc.getMessage()); if (exc instanceof ExpressionException) return exc; return new ExpressionException(e, token, "Error while evaluating expression: "+exc.getMessage()); } private void addUnaryOperator(String surface, boolean leftAssoc, Function fun) { operators.put(surface+"u", new AbstractUnaryOperator(precedence.get("unary+-!"), leftAssoc) { @Override public Value evalUnary(Value v1) { return fun.apply(Value.assertNotNull(v1)); } }); } private void addBinaryOperator(String surface, int precedence, boolean leftAssoc, BiFunction fun) { operators.put(surface, new AbstractOperator(precedence, leftAssoc) { @Override public Value eval(Value v1, Value v2) { Value.assertNotNull(v1, v2); return fun.apply(v1, v2); } }); } private void addUnaryFunction(String name, Function fun) { name = name.toLowerCase(Locale.ROOT); functions.put(name, new AbstractFunction(1) { @Override public Value eval(List parameters) { return fun.apply(Value.assertNotNull(parameters.get(0))); } }); } void addBinaryFunction(String name, BiFunction fun) { name = name.toLowerCase(Locale.ROOT); functions.put(name, new AbstractFunction(2) { @Override public Value eval(List parameters) { Value v1 = parameters.get(0); Value v2 = parameters.get(1); Value.assertNotNull(v1, v2); return fun.apply(v1, v2); } }); } private void addFunction(String name, Function, Value> fun) { name = name.toLowerCase(Locale.ROOT); functions.put(name, new AbstractFunction(-1) { @Override public Value eval(List parameters) { for (Value v: parameters) Value.assertNotNull(v); return fun.apply(parameters); } }); } private void addMathematicalUnaryFunction(String name, Function fun) { addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble()))); } private void addMathematicalBinaryFunction(String name, BiFunction fun) { addBinaryFunction(name, (w, v) -> new NumericValue(fun.apply(NumericValue.asNumber(w).getDouble(), NumericValue.asNumber(v).getDouble()))); } void addLazyFunction(String name, int num_params, TriFunction, LazyValue> fun) { name = name.toLowerCase(Locale.ROOT); functions.put(name, new AbstractLazyFunction(num_params) { @Override public LazyValue lazyEval(Context c, Integer i, Expression e, Tokenizer.Token t, List lazyParams) { try { return fun.apply(c, i, lazyParams); } catch (RuntimeException exc) { throw handleCodeException(exc, e, t); } } }); } private void addContextFunction(Context context, String name, Expression expr, Tokenizer.Token token, List arguments, List globals, LazyValue code) { name = name.toLowerCase(Locale.ROOT); if (functions.containsKey(name)) throw new ExpressionException(expr, token, "Function "+name+" would mask a built-in function"); Expression function_context; try { function_context = expr.clone(); function_context.name = name; } catch (CloneNotSupportedException e) { throw new ExpressionException(expr, token, "Problems in allocating global function "+name); } context.host.globalFunctions.put(name, new UserDefinedFunction(arguments, function_context, token) { @Override public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List lazyParams) { if (arguments.size() != lazyParams.size()) // something that might be subject to change in the future { throw new ExpressionException(e, t, "Incorrect number of arguments for function "+name+ ". Should be "+arguments.size()+", not "+lazyParams.size()+" like "+arguments ); } Context newFrame = c.recreate(); for (String global : globals) { LazyValue lv = c.getVariable(global); if (lv == null) { Value zero = Value.ZERO.reboundedTo(global); newFrame.setVariable(global, (cc, tt) -> zero); } else { newFrame.setVariable(global, lv); } } for (int i=0; i val); } Value retVal; boolean rethrow = false; try { retVal = code.evalValue(newFrame, type); // todo not sure if we need to propagete type / consider boolean context in defined functions - answer seems ye } catch (ReturnStatement returnStatement) { retVal = returnStatement.retval; } catch (ThrowStatement throwStatement) // might not be really necessary { retVal = throwStatement.retval; rethrow = true; } catch (InternalExpressionException exc) { throw new ExpressionException(function_context, t, exc.getMessage()); } catch (ArithmeticException exc) { throw new ExpressionException(function_context, t, "Your math is wrong, "+exc.getMessage()); } catch (ExitStatement | ExpressionException exit) { throw exit; } catch (Exception exc) { throw new ExpressionException(e, t, "Error while evaluating expression: "+exc.getMessage()); } for (String global: globals) { LazyValue lv = newFrame.getVariable(global); if (lv != null) { c.setVariable(global, lv); } } if (rethrow) { throw new ThrowStatement(retVal); } Value otherRetVal = retVal; return (cc, tt) -> otherRetVal; } }); } /** *

Variables and Constants

*
*

scarpet provides a number of constants that can be used literally in scripts

*
    *
  • null: nothing, zilch, not even false
  • *
  • true: pure true, or just 1 (one)
  • *
  • false: false truth, or true falsth, 0 (zero) actually
  • *
  • pi: for the fans of perimeters, its a perimeter of an apple pi of diameter 1. About 3.14
  • *
  • euler: clever guy. Derivative of its exponent is goto 1. About 2.72
  • *
*

Apart from that, there is a bunch of system variables, that start with _ that are set by * scarpet built-ins, like _, typically each consecutive value in loops, * _i indicating iteration, or _a like an accumulator for reduce * function. Certain calls to Minecraft specific calls would also set _x, * _y, _z, indicating block positions. All variables starting with * _ are read-only, and cannot be declared and modified in client code.

* *

Literals

*

scarpet accepts numeric and string liters constants. * Numbers look like 1, 2.5, -3e-7, 0xff, and are internally represented as Java's double * but scarpet will try to trim trailing zeros as much as possible so if you need to use them as intergers, * you can. Strings using single quoting, for multiple reasons, but primarily to allow for easier use of strings inside * doubly quoted command arguments (when passing a script as a parameter of /script fill for example, * or when typing in jsons inside scarpet to feed back into a /data merge command for example. Strings also * use backslashes \ for quoting special characters, in both plain strings and regular expressions

*
     * 'foo'
     * print('This doesn\'t work')
     * nbt ~ '\\.foo'   // matching '.' as a '.', not 'any character match'
     * 
*
*/ public void VariablesAndConstants() // public just to get the Javadocs right { // all declared as global variables to save on switching scope cost } /** *

User-defined functions and program control flow

*
*

Writing programs with more than 1 line

*

Operator ;

*

To effectively write programs that have more than one line, a programmer needs way to specify a sequence of * commands that execute one after another. In scarpet this can be achieved with ;. Its an operator, * and by separating statements with semicolons. And since whitespaces and $ signs are all treats as * whitespaces, how you layout your code doesn't matter, as long as it is readable to everyone involved. The * usefulness of preceding all the lines of the script with $ is explained in the preamble

*
     * expr;
     * expr;
     * expr;
     * expr
     * 
*

Notice that the last expression is not followed by a semicolon. Since instruction separation is functional * in scarpet, and not barely an instruction delimiter, * terminating the code with a dangling operator wouldn't be valid.

*

In general expr; expr; expr; expr is equivalent to * (((expr ; expr) ; expr) ; expr). In case the programmer forgets that it should be warned with a * helpful error at compile time.

*

Result of the evaluated expression is the same as the result of the second expression, but first expression is * also evaluated for sideeffects

*
     * expr1 ; expr2 => expr2  // with expr1 as a sideeffect
     * 
*

All defined functions are compiled, stored persistently, and available globally - * accessible to all other scripts. Functions can only be undefined via call to undef('fun'), which * would erase global entry for function fun. Since all variables have local scope inside each function, * one way to share large objects is via global variables *

*

Global variables

*

Any variable that is used with a name that starts with 'global_' will be stored and accessible globally, * not, inside current scope. It will also persist across scripts, so if a procedure needs to use its own construct, it needs to * define it, or initialize it explicitly, or undefine it via undef

*
     * a() -> global_list+=1; global_list = l(1,2,3); a(); a(); global_list  // => [1,2,3,1,1]
     * 
*

Operator ->

*

To organize code better than a flat sequence of operations, one can define functions. Definition is correct if * has the following form

*
     *     fun(args, ...) -> expr
     * 
*

Where fun(args, ...) is a function signature indicating function name, number of arguments, * and their names, and expr is an expression (can be complex) that is evaluated when fun is called. * Names in the signature don't need to be used anywhere else, other occurrences of these names * will be masked in this function scope. * Function call creates new scope for variables inside expr, so all non-global variables are not * visible from the caller scope. All parameters are passed by value to the new scope, including lists

*
     * a(lst) -> lst+=1; list = l(1,2,3); a(list); a(list); list  // => [1,2,3]
     * 
*

In case the inner function wants to operate and modify larger objects, lists from the outer * scope, but not global, it needs to use outer function in function signature

*

outer(arg)

*

outer function can only be used in the function signature, and it will * cause an error everywhere else. It borrows the reference to that variable from the outer scope and allows * its modification in the inner scope. Any modification of outer variable will result in change of them in * the outer function. In case the variable was not set yet in the outer scope - it will be created. This construct * is similar to nonlocal scoping from python

*
     * a(outer(list)) -> list+=1; list = l(1,2,3); a(); a(); list  // => [1,2,3,1,1]
     * 
*

The return value of a function is the value of the last expression. This as the same effect as using outer * or global lists, but is more expensive

*
     * a(lst) -> lst+=1; list = l(1,2,3); list=a(list); list=a(list); list  // => [1,2,3,1,1]
     * 
*

Ability to combine more statements into one expression, with functions, passing parameters, and global and outer * scoping allow to organize even larger scripts

*

Control flow

*

return(expr?)

*

Sometimes its convenient to break the organized control flow, or it is not practical to pass * the final result value of a function to the last statement, in this case a return statement can be used

*

If no argument is provided - returns null value.

*
     * def() -> (
     *  expr1;
     *  expr2;
     *  return(expr3); // function terminates returning expr3
     *  expr4;     // skipped
     *  expr5      // skipped
     * )
     * 
*

In general its cheaper to leave the last expression as a return value, rather than calling returns everywhere, * but it would often lead to a messy code.

*

exit(expr?)

*

It terminates entire program passing expr as the result of the program execution, or null if omitted.

*

try(expr, catch_expr(_)?) ... throw(value?)

*

try function evaluates expression, and continues further unless throw function is called * anywhere inside expr. In that case the catch_expr is evaluates with _ set * to the argument throw was called with. * This mechanic accepts skipping thrown value - it throws null instead, and catch expression - then try returns null as well * This mechanism allows to terminate large portion of a convoluted * call stack and continue program execution. There is only one level of exceptions currently in carpet, so if the inner * function also defines the try catchment area, it will received the exception first, but it can technically * rethrow the value its getting for the outer scope. Unhandled throw acts like an exit statement.

*

if(cond, expr, cond?, expr?, ..., default?)

*

If statement is a function that takes a number of conditions that are evaluated one after another and if * any of them turns out true, its expr gets returned, otherwise, if all conditions fail, the return value is * default expression, or null if default is skipped

*

if function is equivalent to if (cond) expr; else if (cond) expr; else default; * from Java, just in a functional form

*
*/ public void UserDefinedFunctionsAndControlFlow() // public just to get the javadoc right { // artificial construct to handle user defined functions and function definitions addLazyFunction(".",-1, (c, t, lv) -> { // adjust based on c String name = lv.get(lv.size()-1).evalValue(c).getString(); //lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes if (t != Context.SIGNATURE) // just call the function { if (!c.host.globalFunctions.containsKey(name)) { throw new InternalExpressionException("Function "+name+" is not defined yet"); } List lvargs = new ArrayList<>(lv.size()-1); for (int i=0; i< lv.size()-1; i++) { lvargs.add(lv.get(i)); } UserDefinedFunction acf = c.host.globalFunctions.get(name); Value retval = acf.lazyEval(c, t, acf.expression, acf.token, lvargs).evalValue(c); return (cc, tt) -> retval; ///!!!! dono might need to store expr and token in statics? (e? t?) } // gimme signature List args = new ArrayList<>(); List globals = new ArrayList<>(); for (int i = 0; i < lv.size() - 1; i++) { Value v = lv.get(i).evalValue(c, Context.LOCALIZATION); if (!v.isBound()) { throw new InternalExpressionException("Only variables can be used in function signature, not " + v.getString()); } if (v instanceof GlobalValue) { globals.add(v.boundVariable); } else { args.add(v.boundVariable); } } Value retval = new FunctionSignatureValue(name, args, globals); return (cc, tt) -> retval; }); addLazyFunction("outer", 1, (c, t, lv) -> { if (t != Context.LOCALIZATION) throw new InternalExpressionException("outer scoping of variables is only possible in function signatures"); return (cc, tt) -> new GlobalValue(lv.get(0).evalValue(c)); }); addLazyBinaryOperator(";",precedence.get("nextop;"), true, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.VOID); return lv2; }); //assigns const procedure to the lhs, returning its previous value addLazyBinaryOperatorWithDelegation("->", precedence.get("def->"), false, (c, type, e, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.SIGNATURE); if (v1 instanceof FunctionSignatureValue) { FunctionSignatureValue sign = (FunctionSignatureValue) v1; addContextFunction(c, sign.getName(), e, t, sign.getArgs(), sign.getGlobals(), lv2); } else { v1.assertAssignable(); c.setVariable(v1.getVariable(), lv2); } return (cc, tt) -> new StringValue("OK"); }); addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); }); addFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} ); addFunction("throw", (lv)-> {throw new ThrowStatement(lv.size()==0?Value.NULL:lv.get(0)); }); addLazyFunction("try", -1, (c, t, lv) -> { if (lv.size()==0) throw new InternalExpressionException("try needs at least an expression block"); try { Value retval = lv.get(0).evalValue(c, t); return (c_, t_) -> retval; } catch (ThrowStatement ret) { if (lv.size() == 1) return (c_, t_) -> Value.NULL; LazyValue __ = c.getVariable("_"); c.setVariable("_", (__c, __t) -> ret.retval.reboundedTo("_")); Value val = lv.get(1).evalValue(c, t); c.setVariable("_",__); return (c_, t_) -> val; } }); // if(cond1, expr1, cond2, expr2, ..., ?default) => value addLazyFunction("if", -1, (c, t, lv) -> { if ( lv.size() < 2 ) throw new InternalExpressionException("if statement needs to have at least one condition and one case"); for (int i=0; i ret; } } if (lv.size()%2 == 1) { Value ret = lv.get(lv.size() - 1).evalValue(c); return (cc, tt) -> ret; } return (cc, tt) -> Value.NULL; }); } /** *

Operators

*
* *

There is a number of operators you can use inside the expressions. Those could be considered * generic type operators that apply to most data types. They also follow standard operator * precedence, i.e. 2+2*2 is understood as 2+(2*2), * not (2+2)*2, otherwise they are applied from left to right, i.e. * 2+4-3 is interpreted as (2+4)-3, which in case of numbers * doesn't matter, but since scarpet allows for mixing all value types * the associativity would matter, and may lead to unintended effects:

*

Important operator is function definition -> operator. It will be covered * in {@link carpet.script.Expression#UserDefinedFunctionsAndControlFlow}

*
     * '123'+4-2 => ('123'+4)-2 => '1234'-2 => '134'
     * '123'+(4-2) => '123'+2 => '1232'
     * 3*'foo' => 'foofoofoo'
     * 1357-5 => 1352
     * 1357-'5' => 137
     * 3*'foo'-'o' => 'fff'
     * l(1,3,5)+7 => l(8,10,12)
     * 
*

As you can see, values can behave differently when mixed with other types * in the same expression. in case values are of the same types, the result * tends to be obvious, but Scarpet tries to make sense of whatever it * has to deal with

*

Operator Precedence

*

* Here is the complete list of operators in scarpet including control flow operators. * note, that commas and brackets are not operators, but behave like them: *

*
    *
  • Unary + - !
  • *
  • Exponent ^
  • *
  • Multiplication * / %
  • *
  • Addition + -
  • *
  • Comparison == != > >= <= < ~
  • *
  • Logical And&&
  • *
  • Logical Or ||
  • *
  • Assignment = += <>
  • *
  • Definition ->
  • *
  • Next statement;
  • *
  • Comma ,
  • *
  • Bracket ( )
  • *
* *

Basic Arithmetic Operators + - * /

*

Allows to add the results of two expressions. If the operands resolve to numbers, the result is * arithmetic operation. * In case of strings, adding or subtracting from a string results in string concatenation and * removal of substrings from that string. Multiplication of strings and numbers results in repeating the * string N times and division results in taking the first k'th part of the string, so that str*n/n ~ str * In case first operand is a list, either it results in a new list * with all elements modified one by one with the other operand, or if the operand is a list with the same number of * items - elementwise addition/subtraction

*

Examples:

*
     * 2+3 => 5
     * 'foo'+3+2 => 'foo32'
     * 'foo'+(3+2) => 'foo5'
     * 3+2+'bar' => '5bar'
     * 'foo'*3 => 'foofoofoo'
     * 'foofoofoo' / 3 => 'foo'
     * 'foofoofoo'-'o' => 'fff'
     * l(1,2,3)+1  => l(2,3,4)
     * b = l(100,63,100); b+l(10,0,10)  => l(110,63,110)
     * 
* *

Just Operators % ^

*

The modulo and exponent (power) operators work only if both operands are numbers

*
     * pi^pi%euler  => 1.124....
     * -9 % 4  => -1
     * 9 % -4  => 0 ¯\_(ツ)_/¯ Java
     * -3 ^ 2  => 9
     * -3 ^ pi => // Error
     * 
* *

Comparison Operators == != < > <= >=

*

Allows to compare the results of two expressions. * For numbers it is considers arithmetic order of numbers, for strings - lexicographical, * nulls are always 'less' than everything else, and lists check their elements - if the sizes * are different, the size matters, otherwise, pairwise comparisons for each elements are performed. * The same order rules than with all these operators are used with the default sortographical order as * used by sort function. All of these are true: *

*
     * null == null
     * null != false
     * 0 == false
     * 1 == true
     * null < 0
     * null < -1000
     * 1000 < 'a'
     * 'bar' < 'foo'
     * 3 == 3.0
     * 
* *

Logical Operators && ||

*

These operator compute respective boolean operation on the operands. What it important is that if calculating * of the second operand is not necessary, it won't be evaluated, which means one can use them as conditional * statements. In case of success returns first positive operand (||) or last one (&&).

*
     * true || false  => 1
     * null || false => 0
     * null != false || run('kill gnembon')  => 1 // gnembon survives
     * null != false && run('kill gnembon')  => 0 // when cheats not allowed
     * null != false && run('kill gnembon')  => 1 // gnembon dies, cheats allowed
     * 
* *

Matching Operator ~

*

This operator should be understood as 'matches', or 'in'. For strings it matches the right operand as a regular * expression to the left one, returning the first match. This can be used to extract information from unparsed nbt's * in a more efficient way. For lists it checks if an element is in the list, and returns the index of that element, * or null if no such element was found, especially that the use of first function will not * return the index. Currently it doesn't have any special behaviour for numbers - it checks for existence of characters * in string representation of the left operand with respect of the regular expression on the right hand side. * string

*

in Minecraft API portion entity ~ feature is a shortcode for query(entity,feature) * for queries that do not take any extra arguments.

*
     * l(1,2,3) ~ 2  => 1
     * l(1,2,3) ~ 4  => null
     * 'foobar' ~ '.b'  => 'ob'
     * player('*') ~ 'gnembon'  // null unless player gnembon is logged in (better to use player('gnembon') instead
     * p ~ 'sneaking' // if p is an entity returns whether p is sneaking
     * 
*

Or a longer example of an ineffective way to searching for a squid

*
     * entities = entities_area('all',x,y,z,100,10,100);
     * sid = entities ~ 'Squid';
     * if(sid != null, run('execute as '+query(element(entities,sid),'id')+' run say I am here '+query(element(entities,sid),'pos') ) )
     * 
*

Or an example to find if a player has specific enchantment on a held axe (either hand) and get its level * (despite obvious lack of support for json NBT's):

*
     * global_get_enchantment(p, ench) -> (
     * $   for(l('main','offhand'),
     * $      holds = query(p, 'holds', _);
     * $      if( holds,
     * $         l(what, count, nbt) = holds;
     * $         if( what ~ '_axe' && nbt ~ ench,
     * $            lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
     * $         )
     * $      )
     * $   );
     * $   lvl
     * $);
     * /script run global_get_enchantment(players(), 'sharpness')
     * 
*

Assignment Operators = <> +=

*

A set of assignment operators. All require bounded variable on the LHS, <> requires * bounded arguments on the right hand side as well (bounded, meaning being variables). Additionally they can also * handle list constructors with all bounded variables, and work then as list assignment operators. * When += is used on a list, it extends that list of that element, and returns the list (old == new). * scarpet doesn't support currently removal of items. Removal of items can be obtaine via * filter command, and reassigning it fo the same variable. Both operations would require rewriting of the * array anyways.

*
     * a = 5  => a == 5
     * l(a,b,c) = l(3,4,5) => a==3, b==4, c==5
     * l(minx,maxx) = sort(xi,xj);  // minx assumes min(xi, xj) and maxx, max(xi, xj)
     * l(a,b,c,d,e,f) = l(range(6)); l(a,b,c) <> l(d,e,f); l(a,b,c,d,e,f)  => [3,4,5,0,1,2]
     * a = l(1,2,3); a += 4  => [1,2,3,4]
     * a = l(1,2,3,4); a = filter(a,_!=2)  => [1,3,4]
     * 
*

Unary Operators - +

*

Require a number, flips the sign. One way to assert it's a number is by crashing the script. gg.

*
     * -4  => -4
     * +4  => 4
     * +'4'  // Error message
     * 
* *

Negation Operator !

*

flips boolean condition of the expression. Equivalent of bool(expr)==false

*
     * !true  => 0
     * !false  => 1
     * !null  => 1
     * !5  => 0
     * !l() => 1
     * !l(null) => 0
     * 
*
*/ public void Operators() { addBinaryOperator("+", precedence.get("addition+-"), true, Value::add); addBinaryOperator("-", precedence.get("addition+-"), true, Value::subtract); addBinaryOperator("*", precedence.get("multiplication*/%"), true, Value::multiply); addBinaryOperator("/", precedence.get("multiplication*/%"), true, Value::divide); addBinaryOperator("%", precedence.get("multiplication*/%"), true, (v1, v2) -> new NumericValue(NumericValue.asNumber(v1).getDouble() % NumericValue.asNumber(v2).getDouble())); addBinaryOperator("^", precedence.get("exponent^"), false, (v1, v2) -> new NumericValue(Math.pow(NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(v2).getDouble()))); addLazyBinaryOperator("&&", precedence.get("and&&"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.BOOLEAN); if (!v1.getBoolean()) return (cc, tt) -> v1; Value v2 = lv2.evalValue(c, Context.BOOLEAN); return v2.getBoolean() ? ((cc, tt) -> v2) : LazyValue.FALSE; }); addLazyBinaryOperator("||", precedence.get("or||"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.BOOLEAN); if (v1.getBoolean()) return (cc, tt) -> v1; Value v2 = lv2.evalValue(c, Context.BOOLEAN); return v2.getBoolean() ? ((cc, tt) -> v2) : LazyValue.FALSE; }); addBinaryOperator("~", precedence.get("compare>=><=<"), true, Value::in); addBinaryOperator(">", precedence.get("compare>=><=<"), false, (v1, v2) -> v1.compareTo(v2) > 0 ? Value.TRUE : Value.FALSE); addBinaryOperator(">=", precedence.get("compare>=><=<"), false, (v1, v2) -> v1.compareTo(v2) >= 0 ? Value.TRUE : Value.FALSE); addBinaryOperator("<", precedence.get("compare>=><=<"), false, (v1, v2) -> v1.compareTo(v2) < 0 ? Value.TRUE : Value.FALSE); addBinaryOperator("<=", precedence.get("compare>=><=<"), false, (v1, v2) -> v1.compareTo(v2) <= 0 ? Value.TRUE : Value.FALSE); addBinaryOperator("==", precedence.get("equal==!="), false, (v1, v2) -> v1.equals(v2) ? Value.TRUE : Value.FALSE); addBinaryOperator("!=", precedence.get("equal==!="), false, (v1, v2) -> v1.equals(v2) ? Value.FALSE : Value.TRUE); addLazyBinaryOperator("=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c); Value v2 = lv2.evalValue(c); if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue) { List ll = ((ListValue)v1).getItems(); List rl = ((ListValue)v2).getItems(); if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); for (Value v: ll) v.assertAssignable(); Iterator li = ll.iterator(); Iterator ri = rl.iterator(); while(li.hasNext()) { String lname = li.next().getVariable(); Value vval = ri.next().reboundedTo(lname); c.setVariable(lname, (cc, tt) -> vval); } return (cc, tt) -> Value.TRUE; } v1.assertAssignable(); String varname = v1.getVariable(); Value copy = v2.reboundedTo(varname); LazyValue boundedLHS = (cc, tt) -> copy; c.setVariable(varname, boundedLHS); return boundedLHS; }); addLazyBinaryOperator("+=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c); Value v2 = lv2.evalValue(c); if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue) { List ll = ((ListValue)v1).getItems(); List rl = ((ListValue)v2).getItems(); if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); for (Value v: ll) v.assertAssignable(); Iterator li = ll.iterator(); Iterator ri = rl.iterator(); while(li.hasNext()) { Value lval = li.next(); String lname = lval.getVariable(); Value result = lval.add(ri.next()).bindTo(lname); c.setVariable(lname, (cc, tt) -> result); } return (cc, tt) -> Value.TRUE; } v1.assertAssignable(); String varname = v1.getVariable(); LazyValue boundedLHS; if (v1 instanceof ListValue) { ((ListValue) v1).append(v2); boundedLHS = (cc, tt)-> v1; } else { Value result = v1.add(v2).bindTo(varname); boundedLHS = (cc, tt) -> result; } c.setVariable(varname, boundedLHS); return boundedLHS; }); addLazyBinaryOperator("<>", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c); Value v2 = lv2.evalValue(c); if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue.ListConstructorValue) { List ll = ((ListValue)v1).getItems(); List rl = ((ListValue)v2).getItems(); if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); for (Value v: ll) v.assertAssignable(); for (Value v: rl) v.assertAssignable(); Iterator li = ll.iterator(); Iterator ri = rl.iterator(); while(li.hasNext()) { Value lval = li.next(); Value rval = ri.next(); String lname = lval.getVariable(); String rname = rval.getVariable(); lval.reboundedTo(rname); rval.reboundedTo(lname); c.setVariable(lname, (cc, tt) -> rval); c.setVariable(rname, (cc, tt) -> lval); } return (cc, tt) -> Value.TRUE; } v1.assertAssignable(); v2.assertAssignable(); String lvalvar = v1.getVariable(); String rvalvar = v2.getVariable(); Value lval = v2.reboundedTo(lvalvar); Value rval = v1.reboundedTo(rvalvar); c.setVariable(lvalvar, (cc, tt) -> lval); c.setVariable(rvalvar, (cc, tt) -> rval); return (cc, tt) -> lval; }); addUnaryOperator("-", false, (v) -> new NumericValue(-NumericValue.asNumber(v).getDouble())); addUnaryOperator("+", false, (v) -> new NumericValue(NumericValue.asNumber(v).getDouble())); addLazyUnaryOperator("!", precedence.get("unary+-!"), false, (c, t, lv)-> lv.evalValue(c, Context.BOOLEAN).getBoolean() ? (cc, tt)-> Value.FALSE : (cc, tt) -> Value.TRUE); // might need context boolean } /** *

Arithmetic operations

*
*

Basic Arithmetic Functions

*

There is bunch of them - they require a number and spit out a number, * doing what you would expect them to do.

*

fact(n)

*

Factorial of a number, a.k.a n!, just not in scarpet. Gets big... quick...

*

sqrt(n)

*

Square root. For other fancy roots, use ^, math and yo noggin. Imagine square roots on a tree...

*

abs(n)

*

Absolut value.

*

round(n)

*

Closest integer value. Did you know the earth is also round?

*

floor(n)

*

Highest integer that is still no larger then n. Insert a floor pun here.

*

ceil(n)

*

First lucky integer that is not smalller than n. As you would expect, ceiling is typically * right above the floor.

*

ln(n)

*

Natural logarithm of n. Naturally.

*

ln1p(n)

*

Natural logarithm of n+1. Very optimistic.

*

log10(n)

*

Decimal logarithm of n. Its ceiling is the length of its floor.

*

log(n)

*

Binary logarithm of n. Finally, a proper one, not like the other 100.

*

log1p(n)

*

Binary logarithm of n+1. Also always positive.

*

mandelbrot(a, b, limit)

*

Computes the value of the mandelbrot set, for set a and b spot. * Spot the beetle. Why not.

*

min(arg, ...), min(list), max(arg, ...), max(list)

*

Compute minimum or maximum of supplied arguments assuming default sortraphical order. In case you are * missing argmax, just use a ~ max(a), little less efficient, but still fun. *

*

* Interesting bit - min and max don't remove variable associations from arguments, which * means can be used as LHS of assignments (obvious case), or argument spec in function definitions (far less obvious). *

*
     * a = 1; b = 2; min(a,b) = 3; l(a,b)  => [3, 2]
     * a = 1; b = 2; fun(x, min(a,b)) -> l(a,b); fun(3,5)  => [5, 0]
     * 
*

Absolutely no idea, how the latter might be useful in practice. But since it compiles, can ship it.

* *

relu(n)

*

Linear rectifier of n. 0 below 0, n above. Why not. max(0,n) * with less moral repercussions.

*

Trigonometric / Geometric Functions

*

sin(x)

*

cos(x)

*

tan(x)

*

asin(x)

*

acos(x)

*

atan(x)

*

atan2(x,y)

*

sinh(x)

*

cosh(x)

*

tanh(x)

*

sec(x)

*

csc(x)

*

sech(x)

*

csch(x)

*

cot(x)

*

acot(x)

*

coth(x)

*

asinh(x)

*

acosh(x)

*

atanh(x)

*

rad(deg)

*

deg(rad)

*

Use as you wish

* *
* */ public void ArithmeticOperations() { addLazyFunction("not", 1, (c, t, lv) -> lv.get(0).evalValue(c, Context.BOOLEAN).getBoolean() ? ((cc, tt) -> Value.FALSE) : ((cc, tt) -> Value.TRUE)); addUnaryFunction("fact", (v) -> { long number = NumericValue.asNumber(v).getLong(); long factorial = 1; for (int i = 1; i <= number; i++) { factorial = factorial * i; } return new NumericValue(factorial); }); addMathematicalUnaryFunction("sin", (d) -> Math.sin(Math.toRadians(d))); addMathematicalUnaryFunction("cos", (d) -> Math.cos(Math.toRadians(d))); addMathematicalUnaryFunction("tan", (d) -> Math.tan(Math.toRadians(d))); addMathematicalUnaryFunction("asin", (d) -> Math.toDegrees(Math.asin(d))); addMathematicalUnaryFunction("acos", (d) -> Math.toDegrees(Math.acos(d))); addMathematicalUnaryFunction("atan", (d) -> Math.toDegrees(Math.atan(d))); addMathematicalBinaryFunction("atan2", (d, d2) -> Math.toDegrees(Math.atan2(d, d2)) ); addMathematicalUnaryFunction("sinh", Math::sinh ); addMathematicalUnaryFunction("cosh", Math::cosh ); addMathematicalUnaryFunction("tanh", Math::tanh ); addMathematicalUnaryFunction("sec", (d) -> 1.0 / Math.cos(Math.toRadians(d)) ); // Formula: sec(x) = 1 / cos(x) addMathematicalUnaryFunction("csc", (d) -> 1.0 / Math.sin(Math.toRadians(d)) ); // Formula: csc(x) = 1 / sin(x) addMathematicalUnaryFunction("sech", (d) -> 1.0 / Math.cosh(d) ); // Formula: sech(x) = 1 / cosh(x) addMathematicalUnaryFunction("csch", (d) -> 1.0 / Math.sinh(d) ); // Formula: csch(x) = 1 / sinh(x) addMathematicalUnaryFunction("cot", (d) -> 1.0 / Math.tan(Math.toRadians(d)) ); // Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) addMathematicalUnaryFunction("acot", (d) -> Math.toDegrees(Math.atan(1.0 / d)) );// Formula: acot(x) = atan(1/x) addMathematicalUnaryFunction("coth", (d) -> 1.0 / Math.tanh(d) ); // Formula: coth(x) = 1 / tanh(x) addMathematicalUnaryFunction("asinh", (d) -> Math.log(d + (Math.sqrt(Math.pow(d, 2) + 1)))); // Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) addMathematicalUnaryFunction("acosh", (d) -> Math.log(d + (Math.sqrt(Math.pow(d, 2) - 1)))); // Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) addMathematicalUnaryFunction("atanh", (d) -> // Formula: atanh(x) = 0.5*ln((1 + x)/(1 - x)) { if (Math.abs(d) > 1 || Math.abs(d) == 1) throw new InternalExpressionException("Number must be |x| < 1"); return 0.5 * Math.log((1 + d) / (1 - d)); }); addMathematicalUnaryFunction("rad", Math::toRadians); addMathematicalUnaryFunction("deg", Math::toDegrees); addMathematicalUnaryFunction("ln", Math::log); addMathematicalUnaryFunction("ln1p", Math::log1p); addMathematicalUnaryFunction("log10", Math::log10); addMathematicalUnaryFunction("log", a -> Math.log(a)/Math.log(2)); addMathematicalUnaryFunction("log1p", x -> Math.log1p(x)/Math.log(2)); addMathematicalUnaryFunction("sqrt", Math::sqrt); addMathematicalUnaryFunction("abs", Math::abs); addMathematicalUnaryFunction("round", (d) -> (double)Math.round(d)); addMathematicalUnaryFunction("floor", Math::floor); addMathematicalUnaryFunction("ceil", Math::ceil); addLazyFunction("mandelbrot", 3, (c, t, lv) -> { double a0 = NumericValue.asNumber(lv.get(0).evalValue(c)).getDouble(); double b0 = NumericValue.asNumber(lv.get(1).evalValue(c)).getDouble(); long maxiter = NumericValue.asNumber(lv.get(2).evalValue(c)).getLong(); double a = 0.0D; double b = 0.0D; long iter = 0; while(a*a+b*b<4 && iter < maxiter) { double temp = a*a-b*b+a0; b = 2*a*b+b0; a = temp; iter++; } long iFinal = iter; return (cc, tt) -> new NumericValue(iFinal); }); addFunction("max", (lv) -> { if (lv.size() == 0) throw new InternalExpressionException("MAX requires at least one parameter"); Value max = null; if (lv.size()==1 && lv.get(0) instanceof ListValue) lv = ((ListValue) lv.get(0)).getItems(); for (Value parameter : lv) { if (max == null || parameter.compareTo(max) > 0) max = parameter; } return max; }); addFunction("min", (lv) -> { if (lv.size() == 0) throw new InternalExpressionException("MIN requires at least one parameter"); Value min = null; if (lv.size()==1 && lv.get(0) instanceof ListValue) lv = ((ListValue) lv.get(0)).getItems(); for (Value parameter : lv) { if (min == null || parameter.compareTo(min) < 0) min = parameter; } return min; }); addUnaryFunction("relu", (v) -> v.compareTo(Value.ZERO) < 0 ? Value.ZERO : v); } /** *

Lists, loops, and higher order functions

*
*

Efficient use of these functions cangreatly simplify your programs and speed them up, as these functions * will internalize most of the operations that need to be applied on multiple values at the same time

*

Basic list operations

*

l(values ...), l(iterator)

*

Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all * elements are variables, you coujld use it to return multiple results from one function call, if that function returns * a list of results with the same size as the l call uses. In case there is only one argument and it is * an iterator (vanilla expression specification has range, but Minecraft API implements * a bunch of them, like diamond), it will convert it to a proper list. Iterators can only be used in * high order functions, and are treated as empty lists, unless unrolled with l

*
     * l(1,2,'foo') => [1, 2, foo]
     * l() => [] (empty list)
     * l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     * l(1, 2) = l(3, 4) => Error: l is not a variable
     * l(foo, bar) = l(3,4); foo==3 && bar==4 => 1
     * l(foo, bar, baz) = l(2, 4, 6); l(min(foo, bar), baz) = l(3, 5); l(foo, bar, baz)  => [3, 4, 5]
     * 
*

In the last example l(min(foo, bar), baz) creates a valid L-value, as min(foo, bar) finds the * lower of the variables (in this case foo) creating a valid assignable L-list of [foo, baz], and these * values will be assigned new values

* *

join(delim, list), join(delim, values ...)

*

Returns a string that contains joined elements of the list, iterator, or all values, concatenated with delim delimiter

*
     *     join('-',range(10))  => 0-1-2-3-4-5-6-7-8-9
     *     join('-','foo')  => foo
     *     join('-', 'foo', 'bar')  => foo-bar
     * 
* *

split(delim, expr)

*

Splits a string undr expr by delim which can be a regular expression

*
     *     split('',foo)  => [f, o, o]
     *     split('.','foo.bar')  => []
     *     split('\\.','foo.bar')  => [foo, bar]
     * 
* *

slice(expr, from, to?)

*

extracts a substring, or sublist (based on the type of the result of the expression under expr with starting index * of from, and ending at to if provided, or the end, if omitted

*
     *     slice(l(0,1,2,3,4,5), 1, 3)  => [1, 2, 3] 
     *     slice('foobar', 0, 1)  => 'f'
     *     slice('foobar', 3)  => 'bar'
     *     slice(range(10), 3, 5)  => [3, 4, 5]
     *     slice(range(10), 5)  => [5, 6, 7, 8, 9]
     * 
* * * *

sort(list), sort(values ...)

*

Sorts in the default sortographical order either all arguments, or a list if its the only argument. It returns a new * sorted list, not affecting the list passed to the argument

*
     * sort(3,2,1)  => [1, 2, 3]
     * sort('a',3,11,1)  => [1, 3, 11, 'a']
     * list = l(4,3,2,1); sort(list)  => [1, 2, 3, 4]
     * 
* *

sort_key(list, key_expr)

*

Sorts a copy of the list in the order or keys as defined by the key_expr for each element

*
     *     sort_key([1,3,2],_)  => [1, 2, 3]
     *     sort_key([1,3,2],-_)  => [3, 2, 1]
     *     sort_key(l(range(10)),rand(1))  => [1, 0, 9, 6, 8, 2, 4, 5, 7, 3]
     *     sort_key(l(range(20)),str(_))  => [0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9]
     * 
* *

range(to), range(from, to), range(from, to, step)

*

Creates a range of numbers from from, no greater/larger than to. * The step parameter dictates not only the increment size, but also direction (can be negative). * The returned value is not a proper list, just the iterator * but if for whatever reason you need a proper list with all items evaluated, use l(range(to)). * Primarily to be used in higher order functions

*
     *     range(10)  => [...]
     *     l(range(10))  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     *     map(range(10),_*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
     *     reduce(range(10),_a+_, 0)  => 45
     *     range(5,10)  => [5, 6, 7, 8, 9]
     *     range(20, 10, -2)  => [20, 18, 16, 14, 12]
     * 
* *

get(list, index), element(list, index)(deprecated)

*

Returns the value at index element from the list. * use negative numbers to reach elements from the end of the list. get * call will always be able to find the index. In case there is few items, it will loop over

*
     *     get(l(range(10)), 5)  => 5
     *     get(l(range(10)), -1)  => 9
     *     get(l(range(10)), 10)  => 0
     *     get(l(range(10)), 93)  => 3
     * 
* *

put(list, index, values ...), put(list, null, values ...)

*

Modifies the list by replacing values starting from index with values. * use negative numbers to reach elements from the end of the list. put * call will always be able to find the index. In case there is few items, it will loop over. In case end * of the list is reached before values run out, list is extended to accomodate for more values. in case you * want to append at the end of the list, use null as index. returns number of elements inserted.

*
     *     a = l(1, 2, 3); put(a, 1, 4); a  => [1, 4, 3]
     *     a = l(1, 2, 3); put(a, null, 4, 5, 6); a  => [1, 2, 3, 4, 5, 6]
     *     a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(element(a, 1), 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
     * 
*

while(cond, limit, expr)

*

Evaluates expression expr repeatedly until condition cond becomes false, * but not more than limit times. Returns the result of the last expr evaluation, * or null if nothing was successful. both expr and cond will recveived a * bound variable _ indicating current iteration, so its a number

*
     *  while(a<100,10,a=_*_)  => 81 // loop exhausted via limit
     *  while(a<100,20,a=_*_)  => 100 // loop stopped at condition, but a has already been assigned
     *  while(_*_<100,20,a=_*_)  => 81 // loop stopped at condition, before a was assigned a value
     * 
* *

loop(num,expr(_),exit(_)?)

*

Evaluates expression expr, num number of times. Optionally, * if exit condition is present, stops the execution if the condition becomes true. * Both expr and exit receive _ system variable indicating the iteration

*
     *     loop(5, tick())  => repeat tick 5 times
     *     list = l(); loop(5, x = _; loop(5, list += l(x, _) ) ); list
     *       // double loop, produces: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [1, 0], [1, 1], ... , [4, 2], [4, 3], [4, 4]]
     * 
*

In this small example we will search for first 10 primes, apparently including 0:

*
     *     check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
     *     primes = l();
     *     loop(10000, if(check_prime(_), primes += _), length(primes) >= 10 );
     *     primes
     *
     *     // outputs: [0, 1, 2, 3, 5, 7, 11, 13, 17, 19]
     * 
*

map(list,expr(_,_i), exit(_,_i)?)

*

Converts a list of values, to another list where each value is result of an expression * v = expr(_, _i) where _ is passed as each element of the list, and _i is * the index of such element, optional exit condition is evaluated after each mapping and if it is false, * the map is terminated and returned whatever got collected so far

*
     *     map(range(10), _*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
     *     map(players('*'), _+' is stupid') [gnembon is stupid, herobrine is stupid]
     * 
* *

filter(list,expr(_,_i),exit(_,_i))

*

filters list elements returning only these that return positive result of the expr. * Like with map if the optional condition exit gets true, the list is returned up to this point

*
     *     filter(range(100), !(_%5), _*_>1000)  => [0, 5, 10, 15, 20, 25, 30]
     *     map(filter(entities_list('all'),_=='Witch'), query(_,'pos') )  => [[1082.5, 57, 1243.5]]
     * 
* *

first(list,expr(_,_i))

*

Finds and returns the first item in the list that satisfies expr. It sets _ for current element value, * and _i for index of that element

*
     *     first(range(1000,10000), n=_; !first( range(2, sqrt(n)+1), !(n % _) ) )  => 1009 // first prime after 1000
     * 
*

Notice in the example above, that we needed to rename the outer _ to be albe to use in in the inner * first call

* * *

all(list,expr(_,_i))

*

Returns true if all elements on the list satisfy the condition. Its roughly equivalent to * all(list,expr) <=> for(list,expr)==length(list), but works with generators. expr * also receives bound _ and _i variables

*
     *     all([1,2,3], check_prime(_))  => 1
     *     all(neighbours(x,y,z), _=='stone')  => 1 // if all neighbours of [x, y, z] are stone
     *     map(filter(rect(0,4,0,1000,0,1000), l(x,y,z)=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
     *       => [[-298, 4, -703], [-287, 4, -156], [-269, 4, 104], [242, 4, 250], [-159, 4, 335], [-208, 4, 416], [-510, 4, 546], [376, 4, 806]]
     *         // find all 3x3 bedrock structures in the top bedrock layer
     *     map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), l(x,y,z)=pos(_);
     *             all(rect(x,y,z,1,0,1),_=='bedrock') && for(rect(x,y-1,z,1,1,1,1,0,1),_=='bedrock')<8),
     *        pos(_) )  => [[343, 3, -642], [153, 3, -285], [674, 3, 167], [-710, 3, 398]]
     *         // ditto, but requiring at most 7 bedrock block in the 18 blocks below them
     * 
* * *

for(list,expr(_,_i),exit(_,_i)?)

*

Evaluates expression over list of items from the list, and can optionally stop early if * exit expression is specified and evaluates true for a given iteration. Supplies * _(value) and _i(iteration number) to both expr and exit. * Returns the number of times expr was successful

*
     *     check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
     *        for(range(1000000,1100000),check_prime(_))  => 7216
     * 
*

From which we can learn that there is 7216 primes between 1M and 1.1M

*

reduce(list,expr(_a,_,_i), initial)

*

Applies expr for each element of the list and saves the result in _a accumulator. * Consecutive calls to expr can access that value to apply more values. You also need to specify * the initial value to apply for the accumulator

*
     *     reduce([1,2,3,4],_a+_,0)  => 10
     *     reduce([1,2,3,4],_a*_,1)  => 24
     * 
*
*/ public void ListsLoopsAndHigherOrderFunctions() { addFunction("l", lv -> { if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) return ListValue.wrap(((LazyListValue) lv.get(0)).unroll()); return new ListValue.ListConstructorValue(lv); }); addFunction("join", (lv) -> { if (lv.size() < 2) throw new InternalExpressionException("join takes at least 2 arguments"); String delimiter = lv.get(0).getString(); List toJoin; if (lv.size()==2 && lv.get(1) instanceof LazyListValue) { toJoin = ((LazyListValue) lv.get(1)).unroll(); } else if (lv.size() == 2 && lv.get(1) instanceof ListValue) { toJoin = new ArrayList<>(((ListValue)lv.get(1)).getItems()); } else { toJoin = lv.subList(1,lv.size()); } return new StringValue(toJoin.stream().map(Value::getString).collect(Collectors.joining(delimiter))); }); addBinaryFunction("split", (d, v) -> { String delimiter = d.getString(); String hwat = v.getString(); return ListValue.wrap(Arrays.stream(hwat.split(delimiter)).map(StringValue::new).collect(Collectors.toList())); }); addFunction("slice", (lv) -> { if (lv.size() != 2 && lv.size() != 3) throw new InternalExpressionException("slice takes 2 or 3 arguments"); Value hwat = lv.get(0); long from = NumericValue.asNumber(lv.get(1)).getLong(); long to = -1; if (lv.size()== 3) to = NumericValue.asNumber(lv.get(2)).getLong(); return hwat.slice(from, to); }); addFunction("sort", (lv) -> { List toSort = lv; if (lv.size()==1 && lv.get(0) instanceof ListValue) { toSort = new ArrayList<>(((ListValue)lv.get(0)).getItems()); } Collections.sort(toSort); return ListValue.wrap(toSort); }); addLazyFunction("sort_key", 2, (c, t, lv) -> //get working with iterators { Value v = lv.get(0).evalValue(c); if (!(v instanceof ListValue)) throw new InternalExpressionException("First argument for sort_key should be a List"); LazyValue sortKey = lv.get(1); //scoping LazyValue __ = c.getVariable("_"); List toSort = new ArrayList<>(((ListValue) v).getItems()); Collections.sort(toSort,(v1, v2) -> { c.setVariable("_",(cc, tt) -> v1); Value ev1 = sortKey.evalValue(c); c.setVariable("_",(cc, tt) -> v2); Value ev2 = sortKey.evalValue(c); return ev1.compareTo(ev2); }); //revering scope c.setVariable("_", __); return (cc, tt) -> ListValue.wrap(toSort); }); addFunction("range", (lv) -> { long from = 0; long to = 0; long step = 1; int argsize = lv.size(); if (argsize == 0 || argsize > 3) throw new InternalExpressionException("range accepts from 1 to 3 arguments, not "+argsize); to = NumericValue.asNumber(lv.get(0)).getLong(); if (lv.size() > 1) { from = to; to = NumericValue.asNumber(lv.get(1)).getLong(); if (lv.size() > 2) { step = NumericValue.asNumber(lv.get(2)).getLong(); } } return LazyListValue.range(from, to, step); }); addBinaryFunction("get", (v1, v2) -> v1.getElementAt(v2)); //Deprecated, use "get" instead addBinaryFunction("element", (v1, v2) -> { if (v1 instanceof LazyListValue || !(v1 instanceof ListValue)) throw new InternalExpressionException("First argument of get should be a list"); List items = ((ListValue)v1).getItems(); long index = NumericValue.asNumber(v2).getLong(); int numitems = items.size(); long range = abs(index)/numitems; index += (range+2)*numitems; index = index % numitems; return items.get((int)index); }); addFunction("put", (lv) -> { if(lv.size()<3) { throw new InternalExpressionException("put takes at least three arguments, a list, index, and values to insert at that index"); } Value list = lv.get(0); if (list instanceof LazyListValue || !(list instanceof ListValue)) { throw new InternalExpressionException("First argument of element should be a list"); } int size = lv.size(); //List items = ((ListValue)lv.get(0)).getItems(); Value index = lv.get(1); if (index == Value.NULL) { ((ListValue) list).extend(lv.subList(2,size)); } else { ((ListValue) list).addAtIndex((int) NumericValue.asNumber(index).getLong(), lv.subList(2,size)); } return new NumericValue(size-2); }); //condition and expression will get a bound 'i' //returns last successful expression or false // while(cond, limit, expr) => ?? addLazyFunction("while", 3, (c, t, lv) -> { long limit = NumericValue.asNumber(lv.get(1).evalValue(c)).getLong(); LazyValue condition = lv.get(0); LazyValue expr = lv.get(2); long i = 0; Value lastOne = Value.NULL; //scoping LazyValue _val = c.getVariable("_"); c.setVariable("_",(cc, tt) -> new NumericValue(0).bindTo("_")); while (i new NumericValue(seriously).bindTo("_")); } //revering scope c.setVariable("_", _val); Value lastValueNoKidding = lastOne; return (cc, tt) -> lastValueNoKidding; }); // loop(Num, expr, exit_condition) => last_value // loop(list, expr) // expr receives bounded variable '_' indicating iteration addLazyFunction("loop", -1, (c, t, lv) -> { if (lv.size()<2 || lv.size()>3) { throw new InternalExpressionException("Incorrect number of attributes for loop, should be 2 or 3, not "+lv.size()); } long limit = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); Value lastOne = Value.NULL; LazyValue expr = lv.get(1); LazyValue cond = null; if(lv.size() > 2) cond = lv.get(2); //scoping LazyValue _val = c.getVariable("_"); for (long i=0; i < limit; i++) { long whyYouAsk = i; c.setVariable("_", (cc, tt) -> new NumericValue(whyYouAsk).bindTo("_")); lastOne = expr.evalValue(c); if (cond != null && cond.evalValue(c).getBoolean()) break; } //revering scope c.setVariable("_", _val); Value trulyLastOne = lastOne; return (cc, tt) -> trulyLastOne; }); // map(list or Num, expr) => list_results // receives bounded variable '_' with the expression addLazyFunction("map", -1, (c, t, lv) -> { if (lv.size()<2 || lv.size()>3) { throw new InternalExpressionException("Incorrect number of attributes for map, should be 2 or 3, not "+lv.size()); } Value rval= lv.get(0).evalValue(c); if (!(rval instanceof ListValue)) throw new InternalExpressionException("First argument of map function should be a list or iterator"); Iterator iterator = ((ListValue) rval).iterator(); LazyValue expr = lv.get(1); LazyValue cond = null; if(lv.size() > 2) cond = lv.get(2); //scoping LazyValue _val = c.getVariable("_"); LazyValue _iter = c.getVariable("_i"); List result = new ArrayList<>(); for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); int doYouReally = i; c.setVariable("_", (cc, tt) -> next); c.setVariable("_i", (cc, tt) -> new NumericValue(doYouReally).bindTo("_i")); result.add(expr.evalValue(c)); if (cond != null && cond.evalValue(c).getBoolean()) { next.boundVariable = var; break; } next.boundVariable = var; } ((ListValue) rval).fatality(); LazyValue ret = (cc, tt) -> ListValue.wrap(result); //revering scope c.setVariable("_", _val); c.setVariable("_i", _iter); return ret; }); // grep(list or num, expr, exit_expr) => list // receives bounded variable '_' with the expression, and "_i" with index // produces list of values for which the expression is true addLazyFunction("filter", -1, (c, t, lv) -> { if (lv.size()<2 || lv.size()>3) { throw new InternalExpressionException("Incorrect number of attributes for filter, should be 2 or 3, not "+lv.size()); } Value rval= lv.get(0).evalValue(c); if (!(rval instanceof ListValue)) throw new InternalExpressionException("First argument of filter function should be a list or iterator"); Iterator iterator = ((ListValue) rval).iterator(); LazyValue expr = lv.get(1); LazyValue cond = null; if(lv.size() > 2) cond = lv.get(2); //scoping LazyValue _val = c.getVariable("_"); LazyValue _iter = c.getVariable("_i"); List result = new ArrayList<>(); for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); int seriously = i; c.setVariable("_", (cc, tt) -> next); c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); if(expr.evalValue(c).getBoolean()) result.add(next); if (cond != null && cond.evalValue(c).getBoolean()) { next.boundVariable = var; break; } next.boundVariable = var; } ((ListValue) rval).fatality(); LazyValue ret = (cc, tt) -> ListValue.wrap(result); // might be a trap - lazy evaluation //revering scope c.setVariable("_", _val); c.setVariable("_i", _iter); return ret; }); // first(list, expr) => elem or null // receives bounded variable '_' with the expression, and "_i" with index // returns first element on the list for which the expr is true addLazyFunction("first", 2, (c, t, lv) -> { Value rval= lv.get(0).evalValue(c); if (!(rval instanceof ListValue)) throw new InternalExpressionException("First argument of 'first' function should be a list or iterator"); Iterator iterator = ((ListValue) rval).iterator(); LazyValue expr = lv.get(1); //scoping LazyValue _val = c.getVariable("_"); LazyValue _iter = c.getVariable("_i"); Value result = Value.NULL; for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); int seriously = i; c.setVariable("_", (cc, tt) -> next); c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); if(expr.evalValue(c).getBoolean()) { result = next; next.boundVariable = var; break; } next.boundVariable = var; } //revering scope ((ListValue) rval).fatality(); Value whyWontYouTrustMeJava = result; c.setVariable("_", _val); c.setVariable("_i", _iter); return (cc, tt) -> whyWontYouTrustMeJava; }); // all(list, expr) => boolean // receives bounded variable '_' with the expression, and "_i" with index // returns true if expr is true for all items addLazyFunction("all", 2, (c, t, lv) -> { Value rval= lv.get(0).evalValue(c); if (!(rval instanceof ListValue)) throw new InternalExpressionException("First argument of 'all' function should be a list or iterator"); Iterator iterator = ((ListValue) rval).iterator(); LazyValue expr = lv.get(1); //scoping LazyValue _val = c.getVariable("_"); LazyValue _iter = c.getVariable("_i"); LazyValue result = LazyValue.TRUE; for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); int seriously = i; c.setVariable("_", (cc, tt) -> next); c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); if(!expr.evalValue(c).getBoolean()) { result = LazyValue.FALSE; next.boundVariable = var; break; } next.boundVariable = var; } //revering scope ((ListValue) rval).fatality(); c.setVariable("_", _val); c.setVariable("_i", _iter); return result; }); // similar to map, but returns total number of successes // for(list, expr, exit_expr) => success_count // can be substituted for first and all, but first is more efficient and all doesn't require knowing list size addLazyFunction("for", -1, (c, t, lv) -> { if (lv.size()<2 || lv.size()>3) { throw new InternalExpressionException("Incorrect number of attributes for 'for', should be 2 or 3, not "+lv.size()); } Value rval= lv.get(0).evalValue(c); if (!(rval instanceof ListValue)) throw new InternalExpressionException("Second argument of 'for' function should be a list or iterator"); Iterator iterator = ((ListValue) rval).iterator(); LazyValue expr = lv.get(1); LazyValue cond = null; if(lv.size() > 2) cond = lv.get(2); //scoping LazyValue _val = c.getVariable("_"); LazyValue _iter = c.getVariable("_i"); int successCount = 0; for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); int seriously = i; c.setVariable("_", (cc, tt) -> next); c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); if(expr.evalValue(c).getBoolean()) successCount++; if (cond != null && cond.evalValue(c).getBoolean()) { next.boundVariable = var; break; } next.boundVariable = var; } //revering scope ((ListValue) rval).fatality(); c.setVariable("_", _val); c.setVariable("_i", _iter); long promiseWontChange = successCount; return (cc, tt) -> new NumericValue(promiseWontChange); }); // reduce(list, expr, ?acc) => value // reduces values in the list with expression that gets accumulator // each iteration expr receives acc - accumulator, and '_' - current list value // returned value is substituted to the accumulator addLazyFunction("reduce", 3, (c, t, lv) -> { LazyValue expr = lv.get(1); Value acc = lv.get(2).evalValue(c); Value rval= lv.get(0).evalValue(c); if (!(rval instanceof ListValue)) throw new InternalExpressionException("First argument of 'reduce' should be a list or iterator"); Iterator iterator = ((ListValue) rval).iterator(); if (!iterator.hasNext()) { Value seriouslyWontChange = acc; return (cc, tt) -> seriouslyWontChange; } //scoping LazyValue _val = c.getVariable("_"); LazyValue _acc = c.getVariable("_a"); while (iterator.hasNext()) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); Value promiseWontChangeYou = acc; c.setVariable("_a", (cc, tt) -> promiseWontChangeYou.bindTo("_a")); c.setVariable("_", (cc, tt) -> next); acc = expr.evalValue(c); next.boundVariable = var; } //reverting scope ((ListValue) rval).fatality(); c.setVariable("_a", _acc); c.setVariable("_", _val); Value hopeItsEnoughPromise = acc; return (cc, tt) -> hopeItsEnoughPromise; }); } /** * * *

System functions

*
*

Type conversion functions

*

bool(expr)

*

Returns a boolean context of the expression. Note that there are no true/false values in * scarpet. true is alias of 1, and false is 0. Bool is also interpreting * string values as boolean, which is different from other places where boolean context can be used. * This can be used in places where API functions return string values to represent binary values

*
     * bool(pi) => 1
     * bool(false) => 0
     * bool('') => 0
     * bool(l()) => 0
     * bool(l('')) => 1
     * bool('foo') => 1
     * bool('false') => 0
     * bool('nulL') => 0
     * if('false',1,0) => 1
     * 
* *

number(expr)

*

Returns a numeric context of the expression. * Can be used to read numbers from strings

*
     * number(null) => null
     * number(false) => 0
     * number('') => null
     * number('3.14') => 3.14
     * number(l()) => 0
     * number(l('')) => 1
     * number('foo') => null
     * number('3bar') => null
     * number('2')+number('2') => 4
     * 
* *

str(expr, params? ... ), str(expr, param_list)

* *

Returns a formatted string representing expression. * Accepts formatting style accepted by String.format. * Supported types (with "%?" syntax):

*
    *
  • d, o, x: integers
  • *
  • a, e, f, g: floats
  • *
  • b: booleans
  • *
  • s: strings
  • *
* *
     * str(null) => null
     * str(false) => 0
     * str('') => null
     * str('3.14') => 3.14
     * str(l()) => 0
     * str(l('')) => 1
     * str('foo') => null
     * str('3bar') => null
     * str(2)+str(2) => 22
     * str('pi: %.2f',pi) => 'pi: 3.14'
     * str('player at: %d %d %d',pos(player())) => 'player at: 567, -2423, 124'
     * 
* *
*

Auxiliary functions

* *

length(expr)

*

Returns length of the expression, the length of the string, * the length of the integer part of the number, or length of the list

*
     * length(pi) => 1
     * length(pi*pi) => 1
     * length(pi^pi) => 2
     * length(l()) => 0
     * length(l(1,2,3)) => 3
     * length('') => 0
     * length('foo') => 3
     * 
* *

rand(expr)

*

returns a random number from 0.0 * (inclusive) to expr (exclusive). * In boolean context (in conditions, boolean functions, or bool), returns * false if the randomly selected value is less than 1. This means that rand(2) returns true half * of the time and rand(5) returns true for 80% (4/5) of the time

*
     * map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
     * map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]
     * map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]
     * 
* *

print(expr)

*

prints the value of the expression to chat. * Passes the result of the argument to the output unchanged, so print-statements can * be weaved in code to debug programming issues

*
     *     print('foo') => results in foo, prints: foo
     *     a = 1; print(a = 5) => results in 5, prints: 5
     *     a = 1; print(a) = 5 => results in 5, prints: 1
     *     print('pi = '+pi) => prints: pi = 3.141592653589793
     *     print(str('pi = %.2f',pi)) => prints: pi = 3.14
     * 
* *

sleep(expr)

*

Halts the execution of the program (and the game itself) for expr milliseconds. * All in all, its better to use tick(expr) to let the game do its job while the program waits

*
sleep(50)
*

time()

*

Returns the number of milliseconds since 'some point', * like Java's System.nanoTime(). It returns a float, which has 1 microsecond precision * (0.001 ms)

*
     *     start_time = time();
     *     flip_my_world_upside_down();
     *     print(str('this took %d milliseconds',time()-start_time))
     * 
* *
*

Access to variables and stored functions

* *

var(expr)

*

Returns the variable under the name of the string value of the expression. Allows to * manipulate variables in more programmatic manner, which allows to use local variable set with a * hash map type key-value access, can also be used with global variables

*
     *     a = 1; var('a') = 'foo'; a => a == 'foo'
     * 
* *

undef(expr)

*

Removes all bindings of a variable with a name of expr. * Removes also all function definitions with that name. It can affect global variable pool, and local variable set * for a particular function which will become invalid ........,

*
     *     inc(i) -> i+1; foo = 5; inc(foo) => 6
     *     inc(i) -> i+1; foo = 5; undef('foo'); inc(foo) => 1
     *     inc(i) -> i+1; foo = 5; undef('inc'); undef('foo'); inc(foo) => Error: Function inc is not defined yet at pos 53
     *     undef('pi')  => bad idea - removes hidden variable holding the pi value
     *     undef('true')  => even worse idea, unbinds global true value, all references to true would now refer to the default 0
     * 
*

vars(prefix)

*

It returns all names of variables from local scope (if prefix does not start with 'global') * or global variables (otherwise). * Here is a larger example that uses combination of vars and var functions to * be used for object counting

*
     * /script run
     * $ count_blocks(ent) -> (
     * $   l(cx, cy, cz) = query(ent, 'pos');
     * $   scan(cx, cy, cz, 16, 16, 16, var('count_'+_) += 1);
     * $   for ( sort_key( vars('count_'), -var(_)),
     * $     print(str( '%s: %d', slice(_,6), var(_) ))
     * $   )
     * $ )
     *
     * /script run count_blocks(player())
     * 
* *
*/ public void SystemFunctions() { addLazyFunction("bool", 1, (c, t, lv) -> { Value v = lv.get(0).evalValue(c, Context.BOOLEAN); if (v instanceof StringValue) { String str = v.getString(); if ("false".equalsIgnoreCase(str) || "null".equalsIgnoreCase(str)) { return (cc, tt) -> Value.FALSE; } } Value retval = new NumericValue(v.getBoolean()); return (cc, tt) -> retval; }); addUnaryFunction("number", v -> { if (v instanceof NumericValue) return v; double res = v.readNumber(); if (Double.isNaN(res)) return Value.NULL; return new NumericValue(v.readNumber()); }); addFunction("str", lv -> { if (lv.size() == 0) throw new InternalExpressionException("str requires at least one argument"); String format = lv.get(0).getString(); if (lv.size() == 1) return new StringValue(format); int argIndex = 1; if (lv.get(1) instanceof ListValue) { lv = ((ListValue) lv.get(1)).getItems(); argIndex = 0; } List args = new ArrayList<>(); Matcher m = formatPattern.matcher(format); for (int i = 0, len = format.length(); i < len; ) { if (m.find(i)) { // Anything between the start of the string and the beginning // of the format specifier is either fixed text or contains // an invalid format string. // [[scarpet]] but we skip it and let the String.format fail char fmt = m.group(6).toLowerCase().charAt(0); if (fmt == 's') { if (argIndex >= lv.size()) throw new InternalExpressionException("Not enough arguments for "+m.group(0)); args.add(lv.get(argIndex).getString()); argIndex++; } else if (fmt == 'd' || fmt == 'o' || fmt == 'x') { if (argIndex >= lv.size()) throw new InternalExpressionException("Not enough arguments for "+m.group(0)); args.add(lv.get(argIndex).readInteger()); argIndex++; } else if (fmt == 'a' || fmt == 'e' || fmt == 'f' || fmt == 'g') { if (argIndex >= lv.size()) throw new InternalExpressionException("Not enough arguments for "+m.group(0)); args.add(lv.get(argIndex).readNumber()); argIndex++; } else if (fmt == 'b') { if (argIndex >= lv.size()) throw new InternalExpressionException("Not enough arguments for "+m.group(0)); args.add(lv.get(argIndex).getBoolean()); argIndex++; } else if (fmt == '%') { //skip /%% ; } else { throw new InternalExpressionException("format not supported: "+m.group(6)); } i = m.end(); } else { // No more valid format specifiers. Check for possible invalid // format specifiers. // [[scarpet]] but we skip it and let the String.format fail break; } } try { return new StringValue(String.format(format, args.toArray())); } catch (IllegalFormatException ife) { throw new InternalExpressionException("Illegal string format: "+ife.getMessage()); } }); addUnaryFunction("type", v -> new StringValue(v.getTypeString())); addUnaryFunction("length", v -> new NumericValue(v.length())); addLazyFunction("rand", 1, (c, t, lv) -> { Value argument = lv.get(0).evalValue(c); if (argument instanceof ListValue) { List list = ((ListValue) argument).getItems(); Value retval = list.get(randomizer.nextInt(list.size())); return (cc, tt) -> retval; } if (t == Context.BOOLEAN) { double rv = NumericValue.asNumber(argument).getDouble()*randomizer.nextFloat(); Value retval = rv<1.0D?Value.FALSE:Value.TRUE; return (cc, tt) -> retval; } Value retval = new NumericValue(NumericValue.asNumber(argument).getDouble()*randomizer.nextDouble()); return (cc, tt) -> retval; }); addUnaryFunction("print", (v) -> { System.out.println(v.getString()); return v; // pass through for variables }); addUnaryFunction("sleep", (v) -> { long time = NumericValue.asNumber(v).getLong(); try { Thread.sleep(time); Thread.yield(); } catch (InterruptedException ignored) { } return v; // pass through for variables }); addLazyFunction("time", 0, (c, t, lv) -> { Value time = new NumericValue((System.nanoTime() / 1000) / 1000.0); return (cc, tt) -> time; }); addLazyFunction("var", 1, (c, t, lv) -> { String varname = lv.get(0).evalValue(c).getString(); if (!c.isAVariable(varname)) c.setVariable(varname, (_c, _t ) -> Value.ZERO.reboundedTo(varname)); return c.getVariable(varname); }); addLazyFunction("undef", 1, (c, t, lv) -> { String varname = lv.get(0).evalValue(c).getString(); if (varname.startsWith("_")) throw new InternalExpressionException("Cannot replace local built-in variables, i.e. those that start with '_'"); if (varname.endsWith("*")) { varname = varname.replaceAll("\\*+$", ""); for (String key: c.host.globalFunctions.keySet()) { if (key.startsWith(varname)) c.host.globalFunctions.remove(key); } for (String key: c.host.globalVariables.keySet()) { if (key.startsWith(varname)) c.host.globalVariables.remove(key); } c.clearAll(varname); } else { c.host.globalFunctions.remove(varname); c.delVariable(varname); } return (cc, tt) -> Value.NULL; }); addLazyFunction("vars", 1, (c, t, lv) -> { String prefix = lv.get(0).evalValue(c).getString(); List values = new ArrayList<>(); if (prefix.startsWith("global")) { for (String k: c.host.globalVariables.keySet()) { if (k.startsWith(prefix)) values.add(new StringValue(k)); } } else { for (String k: c.getAllVariableNames()) { if (k.startsWith(prefix)) values.add(new StringValue(k)); } } Value retval = ListValue.wrap(values); return (cc, tt) -> retval; }); } static Expression none = new Expression("null"); /** * @param expression . */ public Expression(String expression) { this.expression = expression.trim(). replaceAll("\\r\\n?", "\n"). replaceAll(";+$", ""); VariablesAndConstants(); UserDefinedFunctionsAndControlFlow(); Operators(); ArithmeticOperations(); SystemFunctions(); ListsLoopsAndHigherOrderFunctions(); } private List shuntingYard() { List outputQueue = new ArrayList<>(); Stack stack = new Stack<>(); Tokenizer tokenizer = new Tokenizer(this, expression, allowComments, allowNewlineSubstitutions); Tokenizer.Token lastFunction = null; Tokenizer.Token previousToken = null; while (tokenizer.hasNext()) { Tokenizer.Token token; try { token = tokenizer.next(); } catch (StringIndexOutOfBoundsException e) { throw new ExpressionException("Script ended prematurely"); } switch (token.type) { case STRINGPARAM: //stack.push(token); // changed that so strings are treated like literals //break; case LITERAL: case HEX_LITERAL: if (previousToken != null && ( previousToken.type == Tokenizer.Token.TokenType.LITERAL || previousToken.type == Tokenizer.Token.TokenType.HEX_LITERAL || previousToken.type == Tokenizer.Token.TokenType.STRINGPARAM)) { throw new ExpressionException(this, token, "Missing operator"); } outputQueue.add(token); break; case VARIABLE: outputQueue.add(token); break; case FUNCTION: stack.push(token); lastFunction = token; break; case COMMA: if (previousToken != null && previousToken.type == Tokenizer.Token.TokenType.OPERATOR) { throw new ExpressionException(this, previousToken, "Missing parameter(s) for operator "); } while (!stack.isEmpty() && stack.peek().type != Tokenizer.Token.TokenType.OPEN_PAREN) { outputQueue.add(stack.pop()); } if (stack.isEmpty()) { if (lastFunction == null) { throw new ExpressionException(this, token, "Unexpected comma"); } else { throw new ExpressionException(this, lastFunction, "Parse error for function"); } } break; case OPERATOR: { if (previousToken != null && (previousToken.type == Tokenizer.Token.TokenType.COMMA || previousToken.type == Tokenizer.Token.TokenType.OPEN_PAREN)) { throw new ExpressionException(this, token, "Missing parameter(s) for operator '" + token+"'"); } ILazyOperator o1 = operators.get(token.surface); if (o1 == null) { throw new ExpressionException(this, token, "Unknown operator '" + token + "'"); } shuntOperators(outputQueue, stack, o1); stack.push(token); break; } case UNARY_OPERATOR: { if (previousToken != null && previousToken.type != Tokenizer.Token.TokenType.OPERATOR && previousToken.type != Tokenizer.Token.TokenType.COMMA && previousToken.type != Tokenizer.Token.TokenType.OPEN_PAREN) { throw new ExpressionException(this, token, "Invalid position for unary operator " + token ); } ILazyOperator o1 = operators.get(token.surface); if (o1 == null) { throw new ExpressionException(this, token, "Unknown unary operator '" + token.surface.substring(0, token.surface.length() - 1) + "'"); } shuntOperators(outputQueue, stack, o1); stack.push(token); break; } case OPEN_PAREN: if (previousToken != null) { if (previousToken.type == Tokenizer.Token.TokenType.LITERAL || previousToken.type == Tokenizer.Token.TokenType.CLOSE_PAREN || previousToken.type == Tokenizer.Token.TokenType.VARIABLE || previousToken.type == Tokenizer.Token.TokenType.HEX_LITERAL) { // Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b) Tokenizer.Token multiplication = new Tokenizer.Token(); multiplication.append("*"); multiplication.type = Tokenizer.Token.TokenType.OPERATOR; stack.push(multiplication); } // if the ( is preceded by a valid function, then it // denotes the start of a parameter list if (previousToken.type == Tokenizer.Token.TokenType.FUNCTION) { outputQueue.add(token); } } stack.push(token); break; case CLOSE_PAREN: if (previousToken != null && previousToken.type == Tokenizer.Token.TokenType.OPERATOR) { throw new ExpressionException(this, previousToken, "Missing parameter(s) for operator " + previousToken); } while (!stack.isEmpty() && stack.peek().type != Tokenizer.Token.TokenType.OPEN_PAREN) { outputQueue.add(stack.pop()); } if (stack.isEmpty()) { throw new ExpressionException("Mismatched parentheses"); } stack.pop(); if (!stack.isEmpty() && stack.peek().type == Tokenizer.Token.TokenType.FUNCTION) { outputQueue.add(stack.pop()); } case MARKER: if ("$".equals(token.surface)) { StringBuilder sb = new StringBuilder(expression); sb.setCharAt(token.pos, '\n'); expression = sb.toString(); } break; } if (token.type != Tokenizer.Token.TokenType.MARKER) previousToken = token; } while (!stack.isEmpty()) { Tokenizer.Token element = stack.pop(); if (element.type == Tokenizer.Token.TokenType.OPEN_PAREN || element.type == Tokenizer.Token.TokenType.CLOSE_PAREN) { throw new ExpressionException(this, element, "Mismatched parentheses"); } outputQueue.add(element); } return outputQueue; } private void shuntOperators(List outputQueue, Stack stack, ILazyOperator o1) { Tokenizer.Token nextToken = stack.isEmpty() ? null : stack.peek(); while (nextToken != null && (nextToken.type == Tokenizer.Token.TokenType.OPERATOR || nextToken.type == Tokenizer.Token.TokenType.UNARY_OPERATOR) && ((o1.isLeftAssoc() && o1.getPrecedence() <= operators.get(nextToken.surface).getPrecedence()) || (o1.getPrecedence() < operators.get(nextToken.surface).getPrecedence()))) { outputQueue.add(stack.pop()); nextToken = stack.isEmpty() ? null : stack.peek(); } } Value eval(Context c) { return eval(c, Context.NONE); } private Value eval(Context c, Integer expectedType) { if (ast == null) { ast = getAST(); } return evalValue(() -> ast, c, expectedType); } static Value evalValue(Supplier exprProvider, Context c, Integer expectedType) { try { return exprProvider.get().evalValue(c, expectedType); } catch (ExitStatement exit) { return exit.retval; } catch (StackOverflowError ignored) { throw new ExpressionException("Your thoughts are too deep"); } catch (InternalExpressionException exc) { throw new ExpressionException("Your expression result is incorrect:"+exc.getMessage()); } catch (ArithmeticException exc) { throw new ExpressionException("The final result is incorrect, "+exc.getMessage()); } } private LazyValue getAST() { Stack stack = new Stack<>(); List rpn = shuntingYard(); validate(rpn); for (final Tokenizer.Token token : rpn) { switch (token.type) { case UNARY_OPERATOR: { final LazyValue value = stack.pop(); LazyValue result = (c, t) -> operators.get(token.surface).lazyEval(c, t, this, token, value, null).evalValue(c); stack.push(result); break; } case OPERATOR: final LazyValue v1 = stack.pop(); final LazyValue v2 = stack.pop(); LazyValue result = (c,t) -> operators.get(token.surface).lazyEval(c, t,this, token, v2, v1).evalValue(c); stack.push(result); break; case VARIABLE: stack.push((c, t) -> { if (!c.isAVariable(token.surface)) // new variable { c.setVariable(token.surface, (cc, tt ) -> Value.ZERO.reboundedTo(token.surface)); } LazyValue lazyVariable = c.getVariable(token.surface); return lazyVariable.evalValue(c); }); break; case FUNCTION: String name = token.surface.toLowerCase(Locale.ROOT); ILazyFunction f; ArrayList p; boolean isKnown = functions.containsKey(name); // globals will be evaluated lazily, not at compile time via . if (isKnown) { f = functions.get(name); p = new ArrayList<>(!f.numParamsVaries() ? f.getNumParams() : 0); } else // potentially unknown function or just unknown function { f = functions.get("."); p = new ArrayList<>(); } // pop parameters off the stack until we hit the start of // this function's parameter list while (!stack.isEmpty() && stack.peek() != LazyValue.PARAMS_START) { p.add(0, stack.pop()); } if (!isKnown) p.add( (c, t) -> new StringValue(name)); if (stack.peek() == LazyValue.PARAMS_START) { stack.pop(); } stack.push((c, t) -> f.lazyEval(c, t, this, token, p).evalValue(c)); break; case OPEN_PAREN: stack.push(LazyValue.PARAMS_START); break; case LITERAL: stack.push((c, t) -> { try { return new NumericValue(token.surface); } catch (NumberFormatException exception) { throw new ExpressionException(this, token, "Not a number"); } }); break; case STRINGPARAM: stack.push((c, t) -> new StringValue(token.surface) ); // was originally null break; case HEX_LITERAL: stack.push((c, t) -> new NumericValue(new BigInteger(token.surface.substring(2), 16).doubleValue())); break; default: throw new ExpressionException(this, token, "Unexpected token '" + token.surface + "'"); } } return stack.pop(); } private void validate(List rpn) { /*- * Thanks to Norman Ramsey: * http://http://stackoverflow.com/questions/789847/postfix-notation-validation */ // each push on to this stack is a new function scope, with the value of // each // layer on the stack being the count of the number of parameters in // that scope Stack stack = new Stack<>(); // push the 'global' scope stack.push(0); for (final Tokenizer.Token token : rpn) { switch (token.type) { case UNARY_OPERATOR: if (stack.peek() < 1) { throw new ExpressionException(this, token, "Missing parameter(s) for operator " + token); } break; case OPERATOR: if (stack.peek() < 2) { if (token.surface.equalsIgnoreCase(";")) { throw new ExpressionException(this, token, "Unnecessary semicolon"); } throw new ExpressionException(this, token, "Missing parameter(s) for operator " + token); } // pop the operator's 2 parameters and add the result stack.set(stack.size() - 1, stack.peek() - 2 + 1); break; case FUNCTION: ILazyFunction f = functions.get(token.surface.toLowerCase(Locale.ROOT));// don't validate global - userdef functions int numParams = stack.pop(); if (f != null && !f.numParamsVaries() && numParams != f.getNumParams()) { throw new ExpressionException(this, token, "Function " + token + " expected " + f.getNumParams() + " parameters, got " + numParams); } if (stack.size() <= 0) { throw new ExpressionException(this, token, "Too many function calls, maximum scope exceeded"); } // push the result of the function stack.set(stack.size() - 1, stack.peek() + 1); break; case OPEN_PAREN: stack.push(0); break; default: stack.set(stack.size() - 1, stack.peek() + 1); } } if (stack.size() > 1) { throw new ExpressionException("Too many unhandled function parameter lists"); } else if (stack.peek() > 1) { throw new ExpressionException("Too many numbers or variables"); } else if (stack.peek() < 1) { throw new ExpressionException("Empty expression"); } } }