3342 lines
152 KiB
Java
3342 lines
152 KiB
Java
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.AbstractListValue;
|
|
import carpet.script.value.ContainerValueInterface;
|
|
import carpet.script.value.FunctionSignatureValue;
|
|
import carpet.script.value.GlobalValue;
|
|
import carpet.script.value.LContainerValue;
|
|
import carpet.script.value.LazyListValue;
|
|
import carpet.script.value.ListValue;
|
|
import carpet.script.value.MapValue;
|
|
import carpet.script.value.NumericValue;
|
|
import carpet.script.value.StringValue;
|
|
import carpet.script.value.Value;
|
|
import org.apache.commons.lang3.text.WordUtils;
|
|
|
|
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;
|
|
|
|
|
|
/**
|
|
* <h1>Fundamental components of <code>scarpet</code> programming language (towards upcoming version 1.5).</h1>
|
|
*
|
|
* <p>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.</p>
|
|
*
|
|
* <p>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:
|
|
* <a href="https://github.com/uklimaschewski/EvalEx">EvalEx GitHub
|
|
* repository</a></p>
|
|
*
|
|
* <p>This specification is divided into two sections: this one is agnostic
|
|
* to any Minecraft related features and could function on its own, and CarpetExpression for
|
|
* Minecraft specific routines and world manipulation functions.</p>
|
|
*
|
|
* <h1>Synopsis</h1>
|
|
*
|
|
* <pre>
|
|
* script run print('Hello World!')
|
|
* </pre>
|
|
* <p>or an OVERLY complex example:</p>
|
|
* <pre>
|
|
* /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 in the command mode';
|
|
* 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'
|
|
*
|
|
* </pre>
|
|
* <p>or simply</p>
|
|
* <pre>
|
|
* script run print('There is'+for(rect(x,9,z,8,8,8), _ == 'diamond_ore')+' diamond ore around you')
|
|
* </pre>
|
|
* <p>It definitely pays to check what higher level <code>scarpet</code> functions have to offer.</p>
|
|
* <h1>Programs</h1>
|
|
* <p>
|
|
* You can think of an program like a mathematical expression, like
|
|
* <code>"2.4*sin(45)/(2-4)"</code> or <code>"sin(y)>0 & max(z, 3)>3"</code>
|
|
* Writing a program, is like writing a <code>2+3</code>, just a bit longer</p>
|
|
*
|
|
* <h2>Basic language components</h2>
|
|
* <p>Programs consist of constants, like <code>2</code>, <code>3.14</code>, <code>pi</code>, or <code>'foo'</code>,
|
|
* operators like <code>+</code>, <code>/</code>, <code>-></code>, variables which you can define, like <code>foo</code>
|
|
* or special ones that will be defined for you, like <code>_x</code>, or <code>_</code> , which I specific to
|
|
* a each built in function, and functions with name, and arguments in the form of <code>f(a,b,c)</code>, where
|
|
* <code>f</code> is the function name, and <code>a, b, c</code> 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.</p>
|
|
*
|
|
* <h2>Code flow</h2>
|
|
* <p>
|
|
* Like any other proper programming language, <code>scarpet</code> 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...
|
|
* </p>
|
|
* <p><code>Scarpet</code> 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 <code>if()</code> function:
|
|
* </p>
|
|
* <pre>
|
|
* 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))
|
|
* </pre>
|
|
* <p>Would you prefer to read</p>
|
|
* <pre>
|
|
* 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)
|
|
* )
|
|
* </pre>
|
|
* <p>Or rather:</p>
|
|
* <pre>
|
|
* 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)
|
|
* )
|
|
* </pre>
|
|
* <p>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</p>
|
|
*
|
|
* <h2>Functions and scoping</h2>
|
|
* <p>
|
|
* Users can define functions in the form <code>fun(args....) -> expression </code> 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'.
|
|
* </p>
|
|
*
|
|
* <h2>Outer variables</h2>
|
|
* <p>Functions can still 'borrow' variables from the outer scope,
|
|
* by adding them to the function signature wrapped around built-in function <code>outer</code>. 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, <code>nonlocal</code> variables from python. </p>
|
|
*
|
|
*
|
|
* <h2>Code delivery, line indicators</h2>
|
|
* <p>Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing your
|
|
* code in packages (files with <code>.sc</code> extension that can be placed inside "/scripts" folder in the world files
|
|
* and loaded as a module with command <code>/script load [package_name]</code>. Scarpet modules loaded from disk should only
|
|
* contain code, no need to start with "/script run" prefix</p>
|
|
* <p>The following is the code that could be provided in a <code>foo.sc</code> file located in world <code>/scripts</code> folder</p>
|
|
*
|
|
* <pre>
|
|
* run_program() -> (
|
|
* loop( 10,
|
|
* // looping 10 times
|
|
* // comments are allowed in scripts located in world files
|
|
* // since we can tell where that line ends
|
|
* foo = floor(rand(10));
|
|
* check_not_zero(foo);
|
|
* print(_+' - foo: '+foo);
|
|
* print(' reciprocal: '+ _/foo )
|
|
* )
|
|
* );
|
|
* check_not_zero(foo) -> (
|
|
* if (foo==0, foo = 1)
|
|
* )
|
|
* </pre>
|
|
* <p>Which we then call in-game with:</p>
|
|
* <pre>
|
|
* /script load foo
|
|
* /script in foo invoke run_program
|
|
* </pre>
|
|
* <p>However the following code can also be input as a command, or in a command block.</p>
|
|
*
|
|
* <p>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 <code>$</code> is the only character that is
|
|
* illegal in programs, since it will be replaced with new lines. As far as I know, <code>$</code> is not used
|
|
* anywhere inside Minecraft identifiers, so this shouldn't hinder the abilities of your programs.</p>
|
|
* <p>Consider the following program executed as command block command:</p>
|
|
* <pre>
|
|
* /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)
|
|
* )
|
|
* </pre>
|
|
* <p>Lets say that the intention was to check if the bar is zero and prevent division by zero in print,
|
|
* but because the <code>foo</code> is passed as a variable, it never changes the original foo value.
|
|
* Because of the inevitable division by zero, we get the following message:
|
|
* </p>
|
|
* <pre>
|
|
* 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))
|
|
* </pre>
|
|
*
|
|
* As we can see, we got our problem where the result of the mathematical operation was not a number (<code>Infinity</code>, 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 <code>$</code>:
|
|
* <pre>
|
|
* /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)
|
|
* $)
|
|
* </pre>
|
|
*
|
|
* <p>Then we get the following error message</p>
|
|
*
|
|
* <pre>
|
|
* Your math is wrong, Incorrect number format for Infinity at line 7, pos 5
|
|
* print(_+' - foo: '+foo);
|
|
* HERE>> print(' reciprocal: '+ _/foo )
|
|
* )
|
|
* </pre>
|
|
*
|
|
*
|
|
* <p>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</p>
|
|
*
|
|
* <p>Obviously that's not the way we intended this program to work. To get it <code>foo</code> modified via
|
|
* a function call, we would either return it as a result and assign it to the new variable:
|
|
* </p>
|
|
* <pre>
|
|
* foo = check_not_zero(foo);
|
|
* ...
|
|
* check_not_zero(foo) -> if(foo == 0, 1, foo)
|
|
* </pre>
|
|
* <p>.. or convert it to a global variable, which in this case passing as an argument is not required</p>
|
|
* <pre>
|
|
* global_foo = floor(rand(10));
|
|
* check_foo_not_zero();
|
|
* ...
|
|
* check_foo_not_zero() -> if(global_foo == 0, global_foo = 1)
|
|
* </pre>
|
|
* <p>.. or scope foo from the outer function - in this case the inner function has to determine what it is accessing</p>
|
|
* <pre>
|
|
* foo = floor(rand(10));
|
|
* check_foo_not_zero();
|
|
* ...
|
|
* check_foo_not_zero(outer(foo)) -> if(foo == 0, foo = 1)
|
|
* </pre>
|
|
*<p><code>outer</code> 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.
|
|
* </p>
|
|
* <p>For the most part - passing arguments as values, and using returned values .
|
|
* The main usecase of <code>Scarpet</code> would rather be simpler scripts, default scope for all variables is global, unless variable
|
|
* is declared with <code>local</code> scope explicitly.
|
|
* </p>
|
|
*/
|
|
|
|
public class Expression implements Cloneable
|
|
{
|
|
private static final Map<String, Integer> precedence = new HashMap<String,Integer>() {{
|
|
put("attribute~:", 80);
|
|
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<String, ILazyOperator> operators = new HashMap<>();
|
|
boolean isAnOperator(String opname) { return operators.containsKey(opname) || operators.containsKey(opname+"u");}
|
|
|
|
private Map<String, ILazyFunction> functions = new HashMap<>();
|
|
Set<String> getFunctionNames() {return functions.keySet();}
|
|
|
|
@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<String> getExpressionSnippet(Tokenizer.Token token, Expression expr)
|
|
{
|
|
String code = expr.getCodeString();
|
|
List<String> output = new ArrayList<>();
|
|
for (String line: getExpressionSnippetLeftContext(token, code, 1))
|
|
{
|
|
output.add(line);
|
|
}
|
|
List<String> 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<String> getExpressionSnippetLeftContext(Tokenizer.Token token, String expr, int contextsize)
|
|
{
|
|
List<String> 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<String> getExpressionSnippetContext(Tokenizer.Token token, String expr)
|
|
{
|
|
List<String> 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<String> getExpressionSnippetRightContext(Tokenizer.Token token, String expr, int contextsize)
|
|
{
|
|
List<String> 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<Context, Integer, LazyValue, LazyValue> 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<Context, Integer, Expression, Tokenizer.Token, LazyValue, LazyValue, LazyValue> 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<Context, Integer, LazyValue, LazyValue, LazyValue> 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);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static 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;
|
|
// unexpected really - should be caught earlier and converted to InternalExpressionException
|
|
exc.printStackTrace();
|
|
return new ExpressionException(e, token, "Error while evaluating expression: "+exc);
|
|
}
|
|
|
|
private void addUnaryOperator(String surface, boolean leftAssoc, Function<Value, Value> 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<Value, Value, Value> 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<Value, Value> fun)
|
|
{
|
|
name = name.toLowerCase(Locale.ROOT);
|
|
functions.put(name, new AbstractFunction(1)
|
|
{
|
|
@Override
|
|
public Value eval(List<Value> parameters)
|
|
{
|
|
return fun.apply(Value.assertNotNull(parameters.get(0)));
|
|
}
|
|
});
|
|
}
|
|
|
|
void addBinaryFunction(String name, BiFunction<Value, Value, Value> fun)
|
|
{
|
|
name = name.toLowerCase(Locale.ROOT);
|
|
functions.put(name, new AbstractFunction(2)
|
|
{
|
|
@Override
|
|
public Value eval(List<Value> 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<List<Value>, Value> fun)
|
|
{
|
|
name = name.toLowerCase(Locale.ROOT);
|
|
functions.put(name, new AbstractFunction(-1)
|
|
{
|
|
@Override
|
|
public Value eval(List<Value> parameters)
|
|
{
|
|
for (Value v: parameters)
|
|
Value.assertNotNull(v);
|
|
return fun.apply(parameters);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void addMathematicalUnaryFunction(String name, Function<Double, Double> fun)
|
|
{
|
|
addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble())));
|
|
}
|
|
|
|
private void addMathematicalBinaryFunction(String name, BiFunction<Double, Double, Double> 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<Context, Integer, List<LazyValue>, 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<LazyValue> 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<String> arguments, List<String> 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<LazyValue> 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<arguments.size(); i++)
|
|
{
|
|
String arg = arguments.get(i);
|
|
Value val = lazyParams.get(i).evalValue(c).reboundedTo(arg);
|
|
newFrame.setVariable(arg, (cc, tt) -> 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;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* <h1>Variables and Constants</h1>
|
|
* <div style="padding-left: 20px; border-radius: 5px 45px; border:1px solid grey;">
|
|
* <p><code>scarpet</code> provides a number of constants that can be used literally in scripts</p>
|
|
* <ul>
|
|
* <li><code>null</code>: nothing, zilch, not even false</li>
|
|
* <li><code>true</code>: pure true, or just 1 (one)</li>
|
|
* <li><code>false</code>: false truth, or true falsth, 0 (zero) actually</li>
|
|
* <li><code>pi</code>: for the fans of perimeters, its a perimeter of an apple pi of diameter 1. About 3.14</li>
|
|
* <li><code>euler</code>: clever guy. Derivative of its exponent is goto 1. About 2.72</li>
|
|
* </ul>
|
|
* <p>Apart from that, there is a bunch of system variables, that start with <code>_</code> that are set by
|
|
* <code>scarpet</code> built-ins, like <code>_</code>, typically each consecutive value in loops,
|
|
* <code>_i</code> indicating iteration, or <code>_a</code> like an accumulator for <code>reduce</code>
|
|
* function. Certain calls to Minecraft specific calls would also set <code>_x</code>,
|
|
* <code>_y</code>, <code>_z</code>, indicating block positions. All variables starting with
|
|
* <code>_</code> are read-only, and cannot be declared and modified in client code.</p>
|
|
*
|
|
* <h2>Literals</h2>
|
|
* <p><code>scarpet</code> accepts numeric and string liters constants.
|
|
* Numbers look like <code>1, 2.5, -3e-7, 0xff, </code> and are internally represented as Java's <code>double</code>
|
|
* but <code>scarpet</code> 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 <code>/script fill</code> for example,
|
|
* or when typing in jsons inside scarpet to feed back into a <code>/data merge</code> command for example. Strings also
|
|
* use backslashes <code>\</code> for quoting special characters, in both plain strings and regular expressions</p>
|
|
* <pre>
|
|
* 'foo'
|
|
* print('This doesn\'t work')
|
|
* nbt ~ '\\.foo' // matching '.' as a '.', not 'any character match'
|
|
* </pre>
|
|
* </div>
|
|
*/
|
|
|
|
public void VariablesAndConstants() // public just to get the Javadocs right
|
|
{
|
|
// all declared as global variables to save on switching scope cost
|
|
}
|
|
|
|
/**
|
|
* <h1>User-defined functions and program control flow</h1>
|
|
* <div style="padding-left: 20px; border-radius: 5px 45px; border:1px solid grey;">
|
|
* <h2>Writing programs with more than 1 line</h2>
|
|
* <h3><code>Operator ;</code></h3>
|
|
* <p>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 <code>scarpet</code> this can be achieved with <code>;</code>. Its an operator,
|
|
* and by separating statements with semicolons. And since whitespaces and <code>$</code> 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 <code>$</code> is explained in the preamble</p>
|
|
* <pre>
|
|
* expr;
|
|
* expr;
|
|
* expr;
|
|
* expr
|
|
* </pre>
|
|
* <p>Notice that the last expression is not followed by a semicolon. Since instruction separation is functional
|
|
* in <code>scarpet</code>, and not barely an instruction delimiter,
|
|
* terminating the code with a dangling operator wouldn't be valid.</p>
|
|
* <p>In general <code>expr; expr; expr; expr</code> is equivalent to
|
|
* <code>(((expr ; expr) ; expr) ; expr)</code>. In case the programmer forgets that it should be warned with a
|
|
* helpful error at compile time.</p>
|
|
* <p>Result of the evaluated expression is the same as the result of the second expression, but first expression is
|
|
* also evaluated for sideeffects</p>
|
|
* <pre>
|
|
* expr1 ; expr2 => expr2 // with expr1 as a sideeffect
|
|
* </pre>
|
|
* <p>All defined functions are compiled, stored persistently, and available globally -
|
|
* accessible to all other scripts. Functions can only be undefined via call to <code>undef('fun')</code>, which
|
|
* would erase global entry for function <code>fun</code>. Since all variables have local scope inside each function,
|
|
* one way to share large objects is via global variables
|
|
* </p>
|
|
* <h2>Global variables</h2>
|
|
* <p>Any variable that is used with a name that starts with <code>'global_'</code> 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 <code>undef</code></p>
|
|
* <pre>
|
|
* a() -> global_list+=1; global_list = l(1,2,3); a(); a(); global_list // => [1,2,3,1,1]
|
|
* </pre>
|
|
* <h3><code>Operator -></code></h3>
|
|
* <p>To organize code better than a flat sequence of operations, one can define functions. Definition is correct if
|
|
* has the following form</p>
|
|
* <pre>
|
|
* fun(args, ...) -> expr
|
|
* </pre>
|
|
* <p>Where <code>fun(args, ...)</code> 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 <code>fun</code> 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 <code>expr</code>, so all non-global variables are not
|
|
* visible from the caller scope. All parameters are passed by value to the new scope, including lists and other
|
|
* containers, however their copy will be shallow.</p>
|
|
* <pre>
|
|
* a(lst) -> lst+=1; list = l(1,2,3); a(list); a(list); list // => [1,2,3]
|
|
* </pre>
|
|
* <p>In case the inner function wants to operate and modify larger objects, lists from the outer
|
|
* scope, but not global, it needs to use <code>outer</code> function in function signature</p>
|
|
* <h3><code>outer(arg)</code></h3>
|
|
* <p><code>outer</code> 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 <code>nonlocal</code> scoping from python</p>
|
|
* <pre>
|
|
* a(outer(list)) -> list+=1; list = l(1,2,3); a(); a(); list // => [1,2,3,1,1]
|
|
* </pre>
|
|
* <p>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</p>
|
|
* <pre>
|
|
* a(lst) -> lst+=1; list = l(1,2,3); list=a(list); list=a(list); list // => [1,2,3,1,1]
|
|
* </pre>
|
|
* <p>Ability to combine more statements into one expression, with functions, passing parameters, and global and outer
|
|
* scoping allow to organize even larger scripts</p>
|
|
* <h2>Control flow</h2>
|
|
* <h3><code>return(expr?)</code></h3>
|
|
* <p>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</p>
|
|
* <p>If no argument is provided - returns null value.</p>
|
|
* <pre>
|
|
* def() -> (
|
|
* expr1;
|
|
* expr2;
|
|
* return(expr3); // function terminates returning expr3
|
|
* expr4; // skipped
|
|
* expr5 // skipped
|
|
* )
|
|
* </pre>
|
|
* <p>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.</p>
|
|
* <h3><code>exit(expr?)</code></h3>
|
|
* <p>It terminates entire program passing <code>expr</code> as the result of the program execution, or null if omitted.</p>
|
|
* <h3><code>try(expr, catch_expr(_)?) ... throw(value?)</code></h3>
|
|
* <p><code>try</code> function evaluates expression, and continues further unless <code>throw</code> function is called
|
|
* anywhere inside <code>expr</code>. In that case the <code>catch_expr</code> is evaluates with <code>_</code> set
|
|
* to the argument <code>throw</code> 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 <code>try</code> 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.</p>
|
|
* <h3><code>if(cond, expr, cond?, expr?, ..., default?)</code></h3>
|
|
* <p>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 <code>expr</code> gets returned, otherwise, if all conditions fail, the return value is
|
|
* <code>default</code> expression, or <code>null</code> if default is skipped</p>
|
|
* <p><code>if</code> function is equivalent to <code>if (cond) expr; else if (cond) expr; else default;</code>
|
|
* from Java, just in a functional form </p>
|
|
* </div>
|
|
*/
|
|
|
|
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<LazyValue> 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<String> args = new ArrayList<>();
|
|
List<String> 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<lv.size()-1; i+=2)
|
|
{
|
|
if (lv.get(i).evalValue(c, Context.BOOLEAN).getBoolean())
|
|
{
|
|
//int iFinal = i;
|
|
Value ret = lv.get(i+1).evalValue(c);
|
|
return (cc, tt) -> ret;
|
|
}
|
|
}
|
|
if (lv.size()%2 == 1)
|
|
{
|
|
Value ret = lv.get(lv.size() - 1).evalValue(c);
|
|
return (cc, tt) -> ret;
|
|
}
|
|
return (cc, tt) -> Value.NULL;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* <h1>Operators</h1>
|
|
* <div style="padding-left: 20px; border-radius: 5px 45px; border:1px solid grey;">
|
|
*
|
|
* <p>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. <code>2+2*2</code> is understood as <code>2+(2*2)</code>,
|
|
* not <code>(2+2)*2</code>, otherwise they are applied from left to right, i.e.
|
|
* <code>2+4-3</code> is interpreted as <code>(2+4)-3</code>, which in case of numbers
|
|
* doesn't matter, but since <code>scarpet</code> allows for mixing all value types
|
|
* the associativity would matter, and may lead to unintended effects: </p>
|
|
* <p>Important operator is function definition <code>-></code> operator. It will be covered
|
|
* in {@link carpet.script.Expression#UserDefinedFunctionsAndControlFlow}</p>
|
|
* <pre>
|
|
* '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)
|
|
* </pre>
|
|
* <p>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 <code>Scarpet</code> tries to make sense of whatever it
|
|
* has to deal with</p>
|
|
* <h2>Operator Precedence</h2>
|
|
* <p>
|
|
* Here is the complete list of operators in <code>scarpet</code> including control flow operators.
|
|
* Note, that commas and brackets are not technically operators, but part of the language,
|
|
* even if they look like them:
|
|
* </p>
|
|
* <ul>
|
|
* <li>Match, Get <code>~ :</code></li>
|
|
* <li>Unary <code>+ - !</code></li>
|
|
* <li>Exponent <code>^</code></li>
|
|
* <li>Multiplication <code>* / %</code></li>
|
|
* <li>Addition <code>+ -</code></li>
|
|
* <li>Comparison <code>== != > >= <= <</code></li>
|
|
* <li>Logical And<code>&&</code></li>
|
|
* <li>Logical Or <code>||</code></li>
|
|
* <li>Assignment <code>= += <></code></li>
|
|
* <li>Definition <code>-></code></li>
|
|
* <li>Next statement<code>;</code></li>
|
|
* <li>Comma <code>,</code></li>
|
|
* <li>Bracket <code>( )</code></li>
|
|
* </ul>
|
|
*
|
|
* <h3><code>Get, Accessor Operator :</code></h3>
|
|
* <p>Operator version of the <code>get(...)</code> function to access elements of lists, maps, and potentially other
|
|
* containers (i.e. NBTs). It is important to distinguish from <code>~</code> operator, which is a matching operator,
|
|
* which is expected to perform some extra computations to retrieve the result, while <code>:</code> should be
|
|
* straightforward and immediate, and the source object should behave like a container and support full container
|
|
* API, meaning <code>get(...)</code>, <code>put(...)</code>, <code>delete(...)</code>,
|
|
* and <code>has(...)</code> functions</p>
|
|
* <p>For certain operators and functions (get, put, delete, has, =, +=) objects can use <code>:</code> annotated
|
|
* fields as l-values, meaning construct like <code>foo:0 = 5</code>, would act like <code>put(foo, 0, 5)</code>,
|
|
* rather than <code>get(foo, 0) = 5</code>, which would result in an error.</p>
|
|
* <p>TODO: add more information about l-value behaviour.</p>
|
|
*
|
|
* <h3><code>Matching Operator ~</code></h3>
|
|
* <p>This operator should be understood as 'matches', 'contains', 'is_in',
|
|
* or 'find me some stuff about something else. For strings it matches the right operand as a regular
|
|
* expression to the left, returning the first match. This can be used to extract information from unparsed nbt's
|
|
* in a more convoluted way (use <code>get(...)</code> for more appropriate way of doing it).
|
|
* For lists it checks if an element is in the list, and returns the index of that element,
|
|
* or <code>null</code> if no such element was found, especially that the use of <code>first(...)</code> 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</p>
|
|
* <p>in Minecraft API portion <code>entity ~ feature</code> is a shortcode for <code>query(entity,feature)</code>
|
|
* for queries that do not take any extra arguments.</p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
* <p>Or a longer example of an ineffective way to searching for a squid</p>
|
|
* <pre>
|
|
* entities = entities_area('all',x,y,z,100,10,100);
|
|
* sid = entities ~ 'Squid';
|
|
* if(sid != null, run('execute as '+query(get(entities,sid),'id')+' run say I am here '+query(get(entities,sid),'pos') ) )
|
|
* </pre>
|
|
* <p>Or an example to find if a player has specific enchantment on a held axe (either hand) and get its level
|
|
* (not using proper NBTs query support via <code>get(...)</code>):</p>
|
|
* <pre>
|
|
* 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')
|
|
* </pre>
|
|
*
|
|
* <h3><code>Basic Arithmetic Operators + - * /</code></h3>
|
|
* <p>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 <code>str*n/n ~ str</code>
|
|
* 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</p>
|
|
* <p>Examples:</p>
|
|
* <pre>
|
|
* 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)
|
|
* </pre>
|
|
*
|
|
* <h3><code>Just Operators % ^</code></h3>
|
|
* <p>The modulo and exponent (power) operators work only if both operands are numbers</p>
|
|
* <pre>
|
|
* pi^pi%euler => 1.124....
|
|
* -9 % 4 => -1
|
|
* 9 % -4 => 0 ¯\_(ツ)_/¯ Java
|
|
* -3 ^ 2 => 9
|
|
* -3 ^ pi => // Error
|
|
* </pre>
|
|
*
|
|
* <h3><code>Comparison Operators == != < > <= >=</code></h3>
|
|
* <p>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 <code>sort</code> function. All of these are true:
|
|
* </p>
|
|
* <pre>
|
|
* null == null
|
|
* null != false
|
|
* 0 == false
|
|
* 1 == true
|
|
* null < 0
|
|
* null < -1000
|
|
* 1000 < 'a'
|
|
* 'bar' < 'foo'
|
|
* 3 == 3.0
|
|
* </pre>
|
|
*
|
|
* <h3><code>Logical Operators && ||</code></h3>
|
|
* <p>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 (<code>||</code>) or last one (<code>&&</code>).</p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
*
|
|
|
|
* <h3><code>Assignment Operators = <> +=</code></h3>
|
|
* <p>A set of assignment operators. All require bounded variable on the LHS, <code><></code> 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 <code>+=</code> is used on a list, it extends that list of that element, and returns the list (old == new).
|
|
* <code>scarpet</code> doesn't support currently removal of items. Removal of items can be obtaine via
|
|
* <code>filter</code> command, and reassigning it fo the same variable. Both operations would require rewriting of the
|
|
* array anyways.</p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
* <h3><code>Unary Operators - +</code></h3>
|
|
* <p>Require a number, flips the sign. One way to assert it's a number is by crashing the script. gg.</p>
|
|
* <pre>
|
|
* -4 => -4
|
|
* +4 => 4
|
|
* +'4' // Error message
|
|
* </pre>
|
|
*
|
|
* <h3><code>Negation Operator !</code></h3>
|
|
* <p>flips boolean condition of the expression. Equivalent of <code>bool(expr)==false</code></p>
|
|
* <pre>
|
|
* !true => 0
|
|
* !false => 1
|
|
* !null => 1
|
|
* !5 => 0
|
|
* !l() => 1
|
|
* !l(null) => 0
|
|
* </pre>
|
|
* </div>
|
|
*/
|
|
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("attribute~:"), 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, Context.LVALUE);
|
|
Value v2 = lv2.evalValue(c);
|
|
if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue)
|
|
{
|
|
List<Value> ll = ((ListValue)v1).getItems();
|
|
List<Value> 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<Value> li = ll.iterator();
|
|
Iterator<Value> 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;
|
|
}
|
|
if (v1 instanceof LContainerValue)
|
|
{
|
|
ContainerValueInterface container = ((LContainerValue) v1).getContainer();
|
|
if (container == null)
|
|
return (cc, tt) -> Value.NULL;
|
|
Value address = ((LContainerValue) v1).getAddress();
|
|
if (!(container.put(address, v2))) return (cc, tt) -> Value.NULL;
|
|
return (cc, tt) -> v2;
|
|
}
|
|
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, Context.LVALUE);
|
|
Value v2 = lv2.evalValue(c);
|
|
if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue)
|
|
{
|
|
List<Value> ll = ((ListValue)v1).getItems();
|
|
List<Value> 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<Value> li = ll.iterator();
|
|
Iterator<Value> 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;
|
|
}
|
|
if (v1 instanceof LContainerValue)
|
|
{
|
|
ContainerValueInterface cvi = ((LContainerValue) v1).getContainer();
|
|
if (cvi == null)
|
|
{
|
|
throw new InternalExpressionException("Failed to resolve left hand side of the += operation");
|
|
}
|
|
Value key = ((LContainerValue) v1).getAddress();
|
|
Value res = cvi.get(key).add(v2);
|
|
cvi.put(key, res);
|
|
return (cc, tt) -> res;
|
|
}
|
|
v1.assertAssignable();
|
|
String varname = v1.getVariable();
|
|
LazyValue boundedLHS;
|
|
if (v1 instanceof ListValue || v1 instanceof MapValue)
|
|
{
|
|
((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<Value> ll = ((ListValue)v1).getItems();
|
|
List<Value> 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<Value> li = ll.iterator();
|
|
Iterator<Value> 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
|
|
|
|
}
|
|
|
|
/**
|
|
* <h1>Arithmetic operations</h1>
|
|
* <div style="padding-left: 20px; border-radius: 5px 45px; border:1px solid grey;">
|
|
* <h2>Basic Arithmetic Functions</h2>
|
|
* <p>There is bunch of them - they require a number and spit out a number,
|
|
* doing what you would expect them to do.</p>
|
|
* <h3><code>fact(n)</code></h3>
|
|
* <p>Factorial of a number, a.k.a <code>n!</code>, just not in <code>scarpet</code>. Gets big... quick...</p>
|
|
* <h3><code>sqrt(n)</code></h3>
|
|
* <p>Square root. For other fancy roots, use <code>^</code>, math and yo noggin. Imagine square roots on a tree...</p>
|
|
* <h3><code>abs(n)</code></h3>
|
|
* <p>Absolut value.</p>
|
|
* <h3><code>round(n)</code></h3>
|
|
* <p>Closest integer value. Did you know the earth is also round?</p>
|
|
* <h3><code>floor(n)</code></h3>
|
|
* <p>Highest integer that is still no larger then <code>n</code>. Insert a floor pun here.</p>
|
|
* <h3><code>ceil(n)</code></h3>
|
|
* <p>First lucky integer that is not smalller than <code>n</code>. As you would expect, ceiling is typically
|
|
* right above the floor.</p>
|
|
* <h3><code>ln(n)</code></h3>
|
|
* <p>Natural logarithm of <code>n</code>. Naturally.</p>
|
|
* <h3><code>ln1p(n)</code></h3>
|
|
* <p>Natural logarithm of <code>n+1</code>. Very optimistic.</p>
|
|
* <h3><code>log10(n)</code></h3>
|
|
* <p>Decimal logarithm of <code>n</code>. Its ceiling is the length of its floor.</p>
|
|
* <h3><code>log(n)</code></h3>
|
|
* <p>Binary logarithm of <code>n</code>. Finally, a proper one, not like the previous 11.</p>
|
|
* <h3><code>log1p(n)</code></h3>
|
|
* <p>Binary logarithm of <code>n+1</code>. Also always positive.</p>
|
|
* <h3><code>mandelbrot(a, b, limit)</code></h3>
|
|
* <p>Computes the value of the mandelbrot set, for set <code>a</code> and <code>b</code> spot.
|
|
* Spot the beetle. Why not.</p>
|
|
* <h3><code>min(arg, ...), min(list), max(arg, ...), max(list)</code></h3>
|
|
* <p>Compute minimum or maximum of supplied arguments assuming default sorthoraphical order. In case you are
|
|
* missing <code>argmax</code>, just use <code>a ~ max(a)</code>, little less efficient, but still fun.
|
|
* </p>
|
|
* <p>
|
|
* Interesting bit - <code>min</code> and <code>max</code> 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).
|
|
* </p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
* <p>Absolutely no idea, how the latter might be useful in practice. But since it compiles, can ship it.</p>
|
|
*
|
|
* <h3><code>relu(n)</code></h3>
|
|
* <p>Linear rectifier of <code>n</code>. 0 below 0, n above. Why not. <code>max(0,n)</code>
|
|
* with less moral repercussions.</p>
|
|
* <h2>Trigonometric / Geometric Functions</h2>
|
|
* <h3><code>sin(x)</code></h3>
|
|
* <h3><code>cos(x)</code></h3>
|
|
* <h3><code>tan(x)</code></h3>
|
|
* <h3><code>asin(x)</code></h3>
|
|
* <h3><code>acos(x)</code></h3>
|
|
* <h3><code>atan(x)</code></h3>
|
|
* <h3><code>atan2(x,y)</code></h3>
|
|
* <h3><code>sinh(x)</code></h3>
|
|
* <h3><code>cosh(x)</code></h3>
|
|
* <h3><code>tanh(x)</code></h3>
|
|
* <h3><code>sec(x)</code></h3>
|
|
* <h3><code>csc(x)</code></h3>
|
|
* <h3><code>sech(x)</code></h3>
|
|
* <h3><code>csch(x)</code></h3>
|
|
* <h3><code>cot(x)</code></h3>
|
|
* <h3><code>acot(x)</code></h3>
|
|
* <h3><code>coth(x)</code></h3>
|
|
* <h3><code>asinh(x)</code></h3>
|
|
* <h3><code>acosh(x)</code></h3>
|
|
* <h3><code>atanh(x)</code></h3>
|
|
* <h3><code>rad(deg)</code></h3>
|
|
* <h3><code>deg(rad)</code></h3>
|
|
* <p>Use as you wish</p>
|
|
*
|
|
* </div>
|
|
*
|
|
*/
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* <h1>Loops, and higher order functions</h1>
|
|
* <div style="padding-left: 20px; border-radius: 5px 45px; border:1px solid grey;">
|
|
* <p>Efficient use of these functions can greatly 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. Most
|
|
* of them take a <code>list</code> argument which can be any iterable structure in scarpet, including
|
|
* generators, like <code>rect</code>, or <code>range</code>, and maps, where the iterator returns all the map keys</p>
|
|
* <h2>Loops</h2>
|
|
*
|
|
* <h3><code>for(list,expr(_,_i),exit(_,_i)?)</code></h3>
|
|
* <p>Evaluates expression over list of items from the <code>list</code>, and can optionally stop early if
|
|
* <code>exit</code> expression is specified and evaluates <code>true</code> for a given iteration. Supplies
|
|
* <code>_</code>(value) and <code>_i</code>(iteration number) to both <code>expr</code> and <code>exit</code>.
|
|
* Returns the number of times <code>expr</code> was successful</p>
|
|
* <pre>
|
|
* check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
|
|
* for(range(1000000,1100000),check_prime(_)) => 7216
|
|
* </pre>
|
|
* <p>From which we can learn that there is 7216 primes between 1M and 1.1M</p>
|
|
* <h3><code>while(cond, limit, expr)</code></h3>
|
|
* <p>Evaluates expression <code>expr</code> repeatedly until condition <code>cond</code> becomes false,
|
|
* but not more than <code>limit</code> times. Returns the result of the last <code>expr</code> evaluation,
|
|
* or <code>null</code> if nothing was successful. both <code>expr</code> and <code>cond</code> will recveived a
|
|
* bound variable <code>_</code> indicating current iteration, so its a number</p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
*
|
|
* <h3><code>loop(num,expr(_),exit(_)?)</code></h3>
|
|
* <p>Evaluates expression <code>expr</code>, <code>num</code> number of times. Optionally,
|
|
* if <code>exit</code> condition is present, stops the execution if the condition becomes true.
|
|
* Both <code>expr</code> and <code>exit</code> receive <code>_</code> system variable indicating the iteration</p>
|
|
* <pre>
|
|
* 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]]
|
|
* </pre>
|
|
* <p>In this small example we will search for first 10 primes, apparently including 0:</p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
* <h2>Higher Order Functions</h2>
|
|
* <h3><code>map(list,expr(_,_i), exit(_,_i)?)</code></h3>
|
|
* <p>Converts a <code>list</code> of values, to another list where each value is result of an expression
|
|
* <code>v = expr(_, _i)</code> where <code>_</code> is passed as each element of the list, and <code>_i</code> is
|
|
* the index of such element, optional <code>exit</code> condition is evaluated after each mapping and if it is false,
|
|
* the map is terminated and returned whatever got collected so far</p>
|
|
* <pre>
|
|
* map(range(10), _*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
|
|
* map(players('*'), _+' is stupid') [gnembon is stupid, herobrine is stupid]
|
|
* </pre>
|
|
*
|
|
* <h3><code>filter(list,expr(_,_i),exit(_,_i))</code></h3>
|
|
* <p>filters <code>list</code> elements returning only these that return positive result of the <code>expr</code>.
|
|
* Like with <code>map</code> if the optional condition <code>exit</code> gets true, the list is returned up to this point</p>
|
|
* <pre>
|
|
* 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]]
|
|
* </pre>
|
|
*
|
|
* <h3><code>first(list,expr(_,_i))</code></h3>
|
|
* <p>Finds and returns the first item in the list that satisfies <code>expr</code>. It sets <code>_</code> for current element value,
|
|
* and <code>_i</code> for index of that element</p>
|
|
* <pre>
|
|
* first(range(1000,10000), n=_; !first( range(2, sqrt(n)+1), !(n % _) ) ) => 1009 // first prime after 1000
|
|
* </pre>
|
|
* <p>Notice in the example above, that we needed to rename the outer <code>_</code> to be albe to use in in the inner
|
|
* <code>first</code> call</p>
|
|
*
|
|
*
|
|
* <h3><code>all(list,expr(_,_i))</code></h3>
|
|
* <p>Returns <code>true</code> if all elements on the list satisfy the condition. Its roughly equivalent to
|
|
* <code>all(list,expr) <=> for(list,expr)==length(list)</code>, but works with generators. <code>expr</code>
|
|
* also receives bound <code>_</code> and <code>_i</code> variables</p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
*
|
|
* <h3><code>reduce(list,expr(_a,_,_i), initial)</code></h3>
|
|
* <p>Applies <code>expr</code> for each element of the list and saves the result in <code>_a</code> accumulator.
|
|
* Consecutive calls to <code>expr</code> can access that value to apply more values. You also need to specify
|
|
* the initial value to apply for the accumulator</p>
|
|
* <pre>
|
|
* reduce([1,2,3,4],_a+_,0) => 10
|
|
* reduce([1,2,3,4],_a*_,1) => 24
|
|
* </pre>
|
|
* </div>
|
|
*/
|
|
public void LoopsAndHigherOrderFunctions()
|
|
{
|
|
// 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<limit && condition.evalValue(c, Context.BOOLEAN).getBoolean() )
|
|
{
|
|
lastOne = expr.evalValue(c);
|
|
i++;
|
|
long seriously = i;
|
|
c.setVariable("_", (cc, tt) -> 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 AbstractListValue))
|
|
throw new InternalExpressionException("First argument of map function should be a list or iterator");
|
|
Iterator<Value> iterator = ((AbstractListValue) 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<Value> 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;
|
|
}
|
|
((AbstractListValue) rval).fatality();
|
|
Value ret = ListValue.wrap(result);
|
|
//revering scope
|
|
c.setVariable("_", _val);
|
|
c.setVariable("_i", _iter);
|
|
return (cc, tt) -> 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 AbstractListValue))
|
|
throw new InternalExpressionException("First argument of filter function should be a list or iterator");
|
|
Iterator<Value> iterator = ((AbstractListValue) 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<Value> 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;
|
|
}
|
|
((AbstractListValue) rval).fatality();
|
|
Value ret = ListValue.wrap(result);
|
|
//revering scope
|
|
c.setVariable("_", _val);
|
|
c.setVariable("_i", _iter);
|
|
return (cc, tt) -> 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 AbstractListValue))
|
|
throw new InternalExpressionException("First argument of 'first' function should be a list or iterator");
|
|
Iterator<Value> iterator = ((AbstractListValue) 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
|
|
((AbstractListValue) 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 AbstractListValue))
|
|
throw new InternalExpressionException("First argument of 'all' function should be a list or iterator");
|
|
Iterator<Value> iterator = ((AbstractListValue) 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
|
|
((AbstractListValue) 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 AbstractListValue))
|
|
throw new InternalExpressionException("Second argument of 'for' function should be a list or iterator");
|
|
Iterator<Value> iterator = ((AbstractListValue) 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
|
|
((AbstractListValue) 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 AbstractListValue))
|
|
throw new InternalExpressionException("First argument of 'reduce' should be a list or iterator");
|
|
Iterator<Value> iterator = ((AbstractListValue) 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
|
|
((AbstractListValue) rval).fatality();
|
|
c.setVariable("_a", _acc);
|
|
c.setVariable("_", _val);
|
|
|
|
Value hopeItsEnoughPromise = acc;
|
|
return (cc, tt) -> hopeItsEnoughPromise;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* <h1>Lists, Maps and API support for Containers</h1>
|
|
* <div style="padding-left: 20px; border-radius: 5px 45px; border:1px solid grey;">
|
|
* <p>Scarpet supports basic container types: lists and maps (aka hashmaps, dicts etc..)</p>
|
|
* <h2>Container manipulation</h2>
|
|
* <p>Here is a list of operations that work on all types of containers: lists, maps, as well as other Minecraft
|
|
* specific modifyable containers, like NBTs</p>
|
|
*
|
|
* <h3><code>get(container, address, ...), get(lvalue), ':' operator</code></h3>
|
|
* <p>Returns the value at <code>address</code> element from the <code>value</code>.
|
|
* For lists it indicates an index, use negative numbers to reach elements from the end of the list. <code>get</code>
|
|
* call will always be able to find the index. In case there is few items, it will loop over </p>
|
|
* <p>for maps, retrieves the value under the key specified in the <code>address</code> or null otherwise</p>
|
|
* <p>[Minecraft specific usecase]: In case <code>value</code> is of <code>nbt</code> type, uses address as the
|
|
* nbt path to query,
|
|
* returning null, if path is not found, one value if there was one match, or list of values if result is a list.
|
|
* Returned elements can be of numerical type, string texts, or another compound nbt tags</p>
|
|
* <p>In case to simplify the access with nested objects, you can add chain of addresses to the arguments of
|
|
* <code>get</code> rather than calling it multiple times. <code>get(get(foo,a),b)</code> is equivalent to
|
|
* <code>get(foo, a, b)</code>, or <code>foo:a:b</code>.</p>
|
|
* <pre>
|
|
* get(l(range(10)), 5) => 5
|
|
* get(l(range(10)), -1) => 9
|
|
* get(l(range(10)), 10) => 0
|
|
* l(range(10)):93 => 3
|
|
*
|
|
* get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead
|
|
*
|
|
* get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar') => 3
|
|
* </pre>
|
|
*
|
|
* <h3><code>has(container, address, ...), has(lvalue)</code></h3>
|
|
* <p>Similar to <code>get</code>, but returns boolean value indicating if the given index / key / path is in the
|
|
* container. Can be used to determine if <code>get(...)==null</code> means the element doesn't exist, or the stored value
|
|
* for this address is <code>null</code>, and is cheaper to run than <code>get</code>.</p>
|
|
* <p>Like get, it can accept multiple addresses for chains in nested containers. In this case <code>has(foo:a:b)</code>
|
|
* is equivalent to <code>has(get(foo,a), b)</code> or <code>has(foo, a, b)</code></p>
|
|
*
|
|
* <h3><code>delete(container, address, ...), delete(lvalue)</code></h3>
|
|
* <p>Removes specific entry from the container. For the lists - removes the element and shrinks it. For maps, it
|
|
* removes the key from the map, and for nbt - removes content from a given path. For lists and maps returns previous
|
|
* entry at the address, for nbt's - number of removed objects, with 0 indicating that the original value was unaffected.</p>
|
|
* <p>Like with the <code>get</code> and <code>has</code>, <code>delete</code> can accept chained addresses, as well as
|
|
* l-value container access, removing the value
|
|
* from the leaf of the path provided, so <code>delete(foo, a, b)</code> is the same as <code>delete(get(foo,a),b)</code>
|
|
* or <code>delete(foo:a:b)</code></p>
|
|
* <p>Returns true, if container was changed, false, if it was left unchanged, and null if operation was invalid.</p>
|
|
*
|
|
* <h3><code>put(container, address, value), put(container, address, value, mode), put(lvalue, value)</code></h3>
|
|
* <p><u><b>Lists</b></u></p>
|
|
* <p>Modifies the container by replacing the value under the address with the supplied <code>value</code>.
|
|
* For lists, a valid index is required, but can be negative as well to indicate positions from the end of the list.
|
|
* If <code>null</code> is supplied as the address, it always means - add to the end of the list. </p>
|
|
* <p>There are three modes that lists can have items added to them:
|
|
* <ul>
|
|
* <li><code>replace</code>(default): Replaces item under given index(address). Doesn't change the size of the array
|
|
* unless <code>null</code> address is used, which is an exception and then it appends to the end</li>
|
|
* <li><code>insert</code>: Inserts given element at a specified index, shifting the rest of the array to make space
|
|
* for the item. Note that index of -1 points to the last element of the list, thus inserting at that position and
|
|
* moving the previous last element to the new last element position. To insert at the end, use <code>+=</code>
|
|
* operator, or <code>null</code> address in put</li>
|
|
* <li><code>extend</code>: treats the supplied value as an iterable set of values to insert at a given index,
|
|
* extending the list by this amount of items. Again use <code>null</code> address/index to point to the end of
|
|
* the list</li>
|
|
* </ul>
|
|
* <p>Due to the extra mode parameter, there is no chaining for <code>put</code>, but you can still use l-value
|
|
* container access to indicate container and address, so <code>put(foo, key, value)</code> is the same as
|
|
* <code>put(foo:key, value)</code> or <code>foo:key=value</code></p>
|
|
* <p>Returns true, if container got modified, false otherwise, and null if operation was invalid.</p>
|
|
*
|
|
* <p><u><b>Maps</b></u></p>
|
|
* <p>For maps there are no modes available (yet, seems there is no reason to). It replaces the value under the supplied
|
|
* key (address), or sets it if not currently present.</p>
|
|
* <p><u><b>NBT Tags</b></u></p>
|
|
* <p>
|
|
* The address for nbt values is a valid nbt path that you would use with <code>/data</code> command, and tag is any
|
|
* tag that would be applicable for a given insert operation. Note that to distinguish between proper types (like integer
|
|
* types, you need to use command notation, i.e. regular ints is <code>123</code>, while byte size int would be <code>123b</code>
|
|
* and an explicit string would be <code>"5"</code>, so it helps that scarpet uses single quotes in his strings. Unlike
|
|
* for lists and maps, it returns the number of affected nodes, or 0 if none were affected.
|
|
* </p>
|
|
* <p>There are three modes that NBT tags can have items added to them:
|
|
* <ul>
|
|
* <li><code>replace</code>(default): Replaces item under given path(address). Removes them first if possible, and then
|
|
* adds given element to the supplied position. The target path can indicate compound tag keys, lists, or individual
|
|
* elements of the lists.</li>
|
|
* <li><code><N></code>: Index for list insertions. Inserts given element at a specified index, inside a list
|
|
* specified with the path address. Fails if list is not specified. It behaves like <code>insert</code> mode for lists,
|
|
* i.e. it is not removing any of the existing elements. Use <code>replace</code> to remove and replace existing element.
|
|
* </li>
|
|
* <li><code>merge</code>: assumes that both path and replacement target are of compound type (dictionaries, maps,
|
|
* <code>{}</code> types), and merges keys from <code>value</code> with the compound tag under the path</li>
|
|
* </ul>
|
|
*
|
|
* <pre>
|
|
* a = l(1, 2, 3); put(a, 1, 4); a => [1, 4, 3]
|
|
* a = l(1, 2, 3); put(a, null, 4); a => [1, 2, 3, 4]
|
|
* a = l(1, 2, 3); put(a, 1, 4, 'insert'); a => [1, 4, 2, 3]
|
|
* a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a => [1, 2, 3, 4, 5, 6]
|
|
* a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a => [1, 4, 5, 6, 2, 3]
|
|
* a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
|
|
* a = m(1,2,3,4); put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null}
|
|
* tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag => {BlockData:{Properties:[1,2,3,4]}}
|
|
* tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag => {a:[{lvl:1},{lvl:1},{lvl:1}]}
|
|
* tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
|
|
* => {a:[{lvl:[1,2,1,3]},{lvl:[3,2,1,1]},{lvl:[4,5,1,6]}]}
|
|
* tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl[1]', 1); tag
|
|
* => {a:[{lvl:[1,1,3]},{lvl:[3,1,1]},{lvl:[4,1,6]}]}
|
|
* </pre>
|
|
*
|
|
* <h2>List operations</h2>
|
|
* <h3><code>l(values ...), l(iterator) </code></h3>
|
|
* <p>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 <code>l</code> call uses. In case there is only one argument and it is
|
|
* an iterator (vanilla expression specification has <code>range</code>, but Minecraft API implements
|
|
* a bunch of them, like <code>diamond</code>), 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 <code>l</code></p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
* <p>In the last example <code>l(min(foo, bar), baz)</code> creates a valid L-value, as min(foo, bar) finds the
|
|
* lower of the variables (in this case <code>foo</code>) creating a valid assignable L-list of [foo, baz], and these
|
|
* values will be assigned new values</p>
|
|
*
|
|
* <h3><code>join(delim, list), join(delim, values ...) </code></h3>
|
|
* <p>Returns a string that contains joined elements of the list, iterator, or all values, concatenated with <code>delim</code> delimiter</p>
|
|
* <pre>
|
|
* join('-',range(10)) => 0-1-2-3-4-5-6-7-8-9
|
|
* join('-','foo') => foo
|
|
* join('-', 'foo', 'bar') => foo-bar
|
|
* </pre>
|
|
*
|
|
* <h3><code>split(delim, expr)</code></h3>
|
|
* <p>Splits a string undr <code>expr</code> by <code>delim</code> which can be a regular expression</p>
|
|
* <pre>
|
|
* split('',foo) => [f, o, o]
|
|
* split('.','foo.bar') => []
|
|
* split('\\.','foo.bar') => [foo, bar]
|
|
* </pre>
|
|
*
|
|
* <h3><code>slice(expr, from, to?)</code></h3>
|
|
* <p>extracts a substring, or sublist (based on the type of the result of the expression under expr with starting index
|
|
* of <code>from</code>, and ending at <code>to</code> if provided, or the end, if omitted</p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
*
|
|
* <h3><code>sort(list), sort(values ...) </code></h3>
|
|
* <p>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</p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
*
|
|
* <h3><code>sort_key(list, key_expr)</code></h3>
|
|
* <p>Sorts a copy of the list in the order or keys as defined by the <code>key_expr</code> for each element</p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
*
|
|
* <h3><code>range(to), range(from, to), range(from, to, step)</code></h3>
|
|
* <p>Creates a range of numbers from <code>from</code>, no greater/larger than <code>to</code>.
|
|
* The <code>step</code> 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 <code>l(range(to))</code>.
|
|
* Primarily to be used in higher order functions</p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
*
|
|
* <h3><code>element(list, index)(deprecated)</code></h3>
|
|
* <p>Legacy support for older method that worked only on lists. Please use <code>get(...)</code> for equivalent
|
|
* support, or <code>.</code> operator. Also previous unique behaviours with <code>put</code> on lists has been
|
|
* removed to support all type of container types.</p>
|
|
*
|
|
* <h2>Map operations</h2>
|
|
* <p>Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with <code>null</code>
|
|
* values as sets. Apart from container access functions, (<code>. , get, put, has, delete</code>),
|
|
* the following functions:</p>
|
|
* <h3><code>m(values ...), m(iterator), m(key_value_pairs) </code></h3>
|
|
* <p>creates and initializes a map with supplied keys, and values. If the arguments contains a flat list,
|
|
* these are all treated as keys with no value, same goes with the iterator - creates a map that behaves like a set.
|
|
* If the arguments is a list of lists, they have to have two elements each, and then first is a key, and second,
|
|
* a value</p>
|
|
* <pre>
|
|
* m(1,2,'foo') => {1: null, 2: null, foo: null}
|
|
* m() => {} (empty map)
|
|
* m(range(10)) => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null}
|
|
* m(l(1, 2), l(3, 4)) => {1: 2, 3: 4}
|
|
* reduce(range(10), put(_a, _, _*_); _a, m())
|
|
* => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
|
|
* </pre>
|
|
* <h3><code>keys(map), values(map), pairs(map) </code></h3>
|
|
* <p>Returns full lists of keys, values and key-value pairs (2-element lists) for all the entries in the map</p>
|
|
*
|
|
* </div>
|
|
*/
|
|
|
|
public void BasicDataStructures()
|
|
{
|
|
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<Value> 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<Value> 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<Value> 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);
|
|
});
|
|
|
|
addFunction("m", lv ->
|
|
{
|
|
if (lv.size() == 1 && lv.get(0) instanceof LazyListValue)
|
|
return new MapValue(((LazyListValue) lv.get(0)).unroll());
|
|
return new MapValue(lv);
|
|
});
|
|
|
|
addUnaryFunction("keys", v -> {
|
|
if (v instanceof MapValue)
|
|
return new ListValue(((MapValue) v).getMap().keySet());
|
|
return Value.NULL;
|
|
});
|
|
|
|
addUnaryFunction("values", v -> {
|
|
if (v instanceof MapValue)
|
|
return new ListValue(((MapValue) v).getMap().values());
|
|
return Value.NULL;
|
|
});
|
|
|
|
addUnaryFunction("pairs", v -> {
|
|
if (v instanceof MapValue)
|
|
return ListValue.wrap(((MapValue) v).getMap().entrySet().stream().map(
|
|
(p) -> ListValue.of(p.getKey(), p.getValue())
|
|
).collect(Collectors.toList()));
|
|
return Value.NULL;
|
|
});
|
|
|
|
addLazyBinaryOperator(":", precedence.get("attribute~:"),true, (c, t, container_lv, key_lv) ->
|
|
{
|
|
Value container = container_lv.evalValue(c);
|
|
if (container instanceof LContainerValue)
|
|
{
|
|
ContainerValueInterface outerContainer = ((LContainerValue) container).getContainer();
|
|
if (outerContainer == null)
|
|
{
|
|
Value innerLValue = new LContainerValue(null, null);
|
|
return (cc, tt) -> innerLValue;
|
|
}
|
|
Value innerContainer = outerContainer.get(((LContainerValue) container).getAddress());
|
|
if (!(innerContainer instanceof ContainerValueInterface))
|
|
{
|
|
Value innerLValue = new LContainerValue(null, null);
|
|
return (cc, tt) -> innerLValue;
|
|
}
|
|
Value innerLValue = new LContainerValue((ContainerValueInterface) innerContainer, key_lv.evalValue(c));
|
|
return (cc, tt) -> innerLValue;
|
|
}
|
|
if (!(container instanceof ContainerValueInterface))
|
|
if (t == Context.LVALUE)
|
|
return (cc, tt) -> new LContainerValue(null, null);
|
|
else
|
|
return (cc, tt) -> Value.NULL;
|
|
Value address = key_lv.evalValue(c);
|
|
if (t != Context.LVALUE)
|
|
{
|
|
Value retVal = ((ContainerValueInterface) container).get(address);
|
|
return (cc, ct) -> retVal;
|
|
}
|
|
Value retVal = new LContainerValue((ContainerValueInterface) container, address);
|
|
return (cc, ct) -> retVal;
|
|
});
|
|
|
|
addLazyFunction("get", -1, (c, t, lv) ->
|
|
{
|
|
if (lv.size() == 0)
|
|
throw new InternalExpressionException("get requires parameters");
|
|
if (lv.size() == 1)
|
|
{
|
|
Value v = lv.get(0).evalValue(c, Context.LVALUE);
|
|
if (!(v instanceof LContainerValue))
|
|
return (cc, tt) -> Value.NULL;
|
|
ContainerValueInterface container = ((LContainerValue) v).getContainer();
|
|
if (container == null)
|
|
return (cc, tt) -> Value.NULL;
|
|
Value ret = container.get(((LContainerValue) v).getAddress());
|
|
return (cc, tt) -> ret;
|
|
}
|
|
Value container = lv.get(0).evalValue(c);
|
|
for (int i = 1; i < lv.size(); i++)
|
|
{
|
|
if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL;
|
|
container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c));
|
|
}
|
|
if (container == null)
|
|
return (cc, tt) -> Value.NULL;
|
|
Value finalContainer = container;
|
|
return (cc, tt) -> finalContainer;
|
|
});
|
|
|
|
addLazyFunction("has", -1, (c, t, lv) ->
|
|
{
|
|
if (lv.size() == 0)
|
|
throw new InternalExpressionException("get requires parameters");
|
|
if (lv.size() == 1)
|
|
{
|
|
Value v = lv.get(0).evalValue(c, Context.LVALUE);
|
|
if (!(v instanceof LContainerValue))
|
|
return (cc, tt) -> Value.NULL;
|
|
ContainerValueInterface container = ((LContainerValue) v).getContainer();
|
|
if (container == null)
|
|
return (cc, tt) -> Value.NULL;
|
|
Value ret = new NumericValue(container.has(((LContainerValue) v).getAddress()));
|
|
return (cc, tt) -> ret;
|
|
}
|
|
Value container = lv.get(0).evalValue(c);
|
|
for (int i = 1; i < lv.size()-1; i++)
|
|
{
|
|
if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL;
|
|
container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c));
|
|
}
|
|
if (!(container instanceof ContainerValueInterface))
|
|
return (cc, tt) -> Value.NULL;
|
|
Value ret = new NumericValue(((ContainerValueInterface) container).has(lv.get(lv.size()-1).evalValue(c)));
|
|
return (cc, tt) -> ret;
|
|
});
|
|
|
|
//Deprecated, use "get" instead, or . operator
|
|
addBinaryFunction("element", (v1, v2) ->
|
|
{
|
|
if (!(v1 instanceof ListValue))
|
|
throw new InternalExpressionException("First argument of get should be a list");
|
|
List<Value> 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);
|
|
});
|
|
|
|
addLazyFunction("put", -1, (c, t, lv) ->
|
|
{
|
|
if(lv.size()<2)
|
|
{
|
|
throw new InternalExpressionException("put takes at least three arguments, a container, address, and values to insert at that index");
|
|
}
|
|
Value container = lv.get(0).evalValue(c, Context.LVALUE);
|
|
if (container instanceof LContainerValue)
|
|
{
|
|
ContainerValueInterface internalContainer = ((LContainerValue) container).getContainer();
|
|
if (internalContainer == null)
|
|
{
|
|
return (cc, tt) -> Value.NULL;
|
|
}
|
|
Value address = ((LContainerValue) container).getAddress();
|
|
Value what = lv.get(1).evalValue(c);
|
|
Value retVal = new NumericValue( (lv.size() > 2)
|
|
? internalContainer.put(address, what, lv.get(2).evalValue(c))
|
|
: internalContainer.put(address, what));
|
|
return (cc, tt) -> retVal;
|
|
|
|
}
|
|
if(lv.size()<3)
|
|
{
|
|
throw new InternalExpressionException("put takes at least three arguments, a container, address, and values to insert at that index");
|
|
}
|
|
if (!(container instanceof ContainerValueInterface))
|
|
{
|
|
return (cc, tt) -> Value.NULL;
|
|
}
|
|
Value where = lv.get(1).evalValue(c);
|
|
Value what = lv.get(2).evalValue(c);
|
|
Value retVal = new NumericValue( (lv.size()>3)
|
|
? ((ContainerValueInterface) container).put(where, what, lv.get(3).evalValue(c))
|
|
: ((ContainerValueInterface) container).put(where, what));
|
|
return (cc, tt) -> retVal;
|
|
});
|
|
|
|
addLazyFunction("delete", -1, (c, t, lv) ->
|
|
{
|
|
if (lv.size() == 0)
|
|
throw new InternalExpressionException("delete requires parameters");
|
|
if (lv.size() == 1)
|
|
{
|
|
Value v = lv.get(0).evalValue(c, Context.LVALUE);
|
|
if (!(v instanceof LContainerValue))
|
|
return (cc, tt) -> Value.NULL;
|
|
ContainerValueInterface container = ((LContainerValue) v).getContainer();
|
|
if (container == null)
|
|
return (cc, tt) -> Value.NULL;
|
|
Value ret = new NumericValue(container.delete(((LContainerValue) v).getAddress()));
|
|
return (cc, tt) -> ret;
|
|
}
|
|
Value container = lv.get(0).evalValue(c);
|
|
for (int i = 1; i < lv.size()-1; i++)
|
|
{
|
|
if (!(container instanceof ContainerValueInterface)) return (cc, tt) -> Value.NULL;
|
|
container = ((ContainerValueInterface) container).get(lv.get(i).evalValue(c));
|
|
}
|
|
if (!(container instanceof ContainerValueInterface))
|
|
return (cc, tt) -> Value.NULL;
|
|
Value ret = new NumericValue(((ContainerValueInterface) container).delete(lv.get(lv.size()-1).evalValue(c)));
|
|
return (cc, tt) -> ret;
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
*
|
|
* <h1>System functions</h1>
|
|
* <div style="padding-left: 20px; border-radius: 5px 45px; border:1px solid grey;">
|
|
* <h2>Type conversion functions</h2>
|
|
* <h3><code>type(expr)</code></h3>
|
|
* <p>Returns the string value indicating type of the expression. Possible outcomes are
|
|
* <code>null, number, string, list, iterator</code>, as well as minecraft related concepts like
|
|
* <code>block, entity, nbt</code></p>
|
|
* <h3><code>bool(expr)</code></h3>
|
|
* <p>Returns a boolean context of the expression. Note that there are no true/false values in
|
|
* scarpet. <code>true</code> is alias of 1, and <code>false</code> 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</p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
*
|
|
* <h3><code>number(expr)</code></h3>
|
|
* <p>Returns a numeric context of the expression.
|
|
* Can be used to read numbers from strings</p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
*
|
|
* <h3><code>str(expr, params? ... ), str(expr, param_list)</code></h3>
|
|
*
|
|
* <p>Returns a formatted string representing expression.
|
|
* Accepts formatting style accepted by <code>String.format</code>.
|
|
* Supported types (with <code>"%?"</code> syntax):</p>
|
|
* <ul>
|
|
* <li>d, o, x: integers</li>
|
|
* <li>a, e, f, g: floats</li>
|
|
* <li>b: booleans</li>
|
|
* <li>s: strings</li>
|
|
* </ul>
|
|
*
|
|
* <pre>
|
|
* 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'
|
|
* </pre>
|
|
*
|
|
* <hr>
|
|
* <h2>Auxiliary functions</h2>
|
|
*
|
|
* <h3><code>lower(expr), upper(expr), title(expr)</code></h3>
|
|
* <p>Returns lowercase, uppercase or titlecase representation of a string representation of the passed expression</p>
|
|
* <pre>
|
|
* lower('aBc') => 'abc'
|
|
* upper('aBc') => 'ABC'
|
|
* title('aBc') => 'Abc'
|
|
* </pre>
|
|
*
|
|
* <h3><code>length(expr)</code></h3>
|
|
* <p>Returns length of the expression, the length of the string,
|
|
* the length of the integer part of the number, or length of the list</p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
*
|
|
* <h3><code>rand(expr)</code></h3>
|
|
* <p>returns a random number from <code>0.0</code>
|
|
* (inclusive) to <code>expr</code> (exclusive).
|
|
* In boolean context (in conditions, boolean functions, or <code>bool</code>), returns
|
|
* false if the randomly selected value is less than 1. This means that <code>rand(2)</code> returns true half
|
|
* of the time and <code>rand(5)</code> returns true for 80% (4/5) of the time</p>
|
|
* <pre>
|
|
* 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]
|
|
* </pre>
|
|
*
|
|
* <h3><code>print(expr)</code></h3>
|
|
* <p>prints the value of the expression to chat.
|
|
* Passes the result of the argument to the output unchanged, so <code>print</code>-statements can
|
|
* be weaved in code to debug programming issues</p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
*
|
|
* <h3><code>sleep(expr)</code></h3>
|
|
* <p>Halts the execution of the program (and the game itself) for <code>expr</code> milliseconds.
|
|
* All in all, its better to use <code>tick(expr)</code> to let the game do its job while the program waits</p>
|
|
* <pre>sleep(50)</pre>
|
|
* <h3><code>time()</code></h3>
|
|
* <p>Returns the number of milliseconds since 'some point',
|
|
* like Java's <code>System.nanoTime()</code>. It returns a float, which has 1 microsecond precision
|
|
* (0.001 ms)</p>
|
|
* <pre>
|
|
* start_time = time();
|
|
* flip_my_world_upside_down();
|
|
* print(str('this took %d milliseconds',time()-start_time))
|
|
* </pre>
|
|
*
|
|
* <h3><code>profile_expr(expression)</code></h3>
|
|
* <p>Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your
|
|
* code. Note that, even if its only a number, it WILL run these commands, so if they are destructive,
|
|
* you need to be careful.</p>
|
|
*
|
|
* <hr>
|
|
* <h2>Access to variables and stored functions (use with caution)</h2>
|
|
*
|
|
* <h3><code>var(expr)</code></h3>
|
|
* <p>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</p>
|
|
* <pre>
|
|
* a = 1; var('a') = 'foo'; a => a == 'foo'
|
|
* </pre>
|
|
*
|
|
* <h3><code>undef(expr)</code></h3>
|
|
* <p>Removes all bindings of a variable with a name of <code>expr</code>.
|
|
* 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 ........, </p>
|
|
* <pre>
|
|
* 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
|
|
* </pre>
|
|
* <h3><code>vars(prefix)</code></h3>
|
|
* <p>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 <code>vars</code> and <code>var</code> functions to
|
|
* be used for object counting</p>
|
|
* <pre>
|
|
* /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())
|
|
* </pre>
|
|
*
|
|
* </div>
|
|
*/
|
|
public void SystemFunctions()
|
|
{
|
|
addUnaryFunction("hash_code", v -> new NumericValue(v.hashCode()));
|
|
|
|
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<Object> 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("lower", v -> new StringValue(v.getString().toLowerCase(Locale.ROOT)));
|
|
|
|
addUnaryFunction("upper", v -> new StringValue(v.getString().toUpperCase(Locale.ROOT)));
|
|
|
|
addUnaryFunction("title", v -> new StringValue(WordUtils.capitalizeFully(v.getString())));
|
|
|
|
|
|
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<Value> 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("profile_expr", 1, (c, t, lv) ->
|
|
{
|
|
LazyValue lazy = lv.get(0);
|
|
long end = System.nanoTime()+50000000L;
|
|
long it = 0;
|
|
while (System.nanoTime()<end)
|
|
{
|
|
lazy.evalValue(c);
|
|
it++;
|
|
}
|
|
Value res = new NumericValue(it);
|
|
return (cc, tt) -> res;
|
|
});
|
|
|
|
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<Value> 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("\\t"," ").
|
|
replaceAll(";+$", "");
|
|
VariablesAndConstants();
|
|
UserDefinedFunctionsAndControlFlow();
|
|
Operators();
|
|
ArithmeticOperations();
|
|
SystemFunctions();
|
|
LoopsAndHigherOrderFunctions();
|
|
BasicDataStructures();
|
|
}
|
|
|
|
private List<Tokenizer.Token> shuntingYard()
|
|
{
|
|
List<Tokenizer.Token> outputQueue = new ArrayList<>();
|
|
Stack<Tokenizer.Token> 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<Tokenizer.Token> outputQueue, Stack<Tokenizer.Token> 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<LazyValue> 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<LazyValue> stack = new Stack<>();
|
|
List<Tokenizer.Token> 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<LazyValue> 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<Tokenizer.Token> 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<Integer> 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");
|
|
}
|
|
}
|
|
|
|
}
|