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.QuinnFunction; import carpet.script.Fluff.SexFunction; import carpet.script.Fluff.TriFunction; import carpet.script.argument.FunctionArgument; import carpet.script.bundled.Module; import carpet.script.exception.BreakStatement; import carpet.script.exception.ContinueStatement; import carpet.script.exception.ExitStatement; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ResolvedException; import carpet.script.exception.ReturnStatement; import carpet.script.exception.ThrowStatement; import carpet.script.utils.PerlinNoiseSampler; import carpet.script.utils.SimplexNoiseSampler; import carpet.script.value.AbstractListValue; import carpet.script.value.ContainerValueInterface; import carpet.script.value.FunctionSignatureValue; import carpet.script.value.FunctionValue; 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.NullValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.ThreadValue; import carpet.script.value.Value; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.apache.commons.lang3.text.WordUtils; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; 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; public class Expression { private static final Map precedence = new HashMap() {{ 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); }}; public static final Random randomizer = new Random(); public static final Value PI = new NumericValue( "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); public static final Value euler = new NumericValue( "2.71828182845904523536028747135266249775724709369995957496696762772407663"); // %[argument_index$][flags][width][.precision][t]conversion private static final 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; public Module module = null; public String getModuleName() { return module == null?null:module.getName(); } public void asATextSource() { allowNewlineSubstitutions = false; allowComments = true; } public void asAModule(Module mi) { module = mi; } /** Cached AST (Abstract Syntax Tree) (root) of the expression */ private LazyValue ast = null; /** script specific operatos and built-in functions */ private final Map operators = new Object2ObjectOpenHashMap<>(); public boolean isAnOperator(String opname) { return operators.containsKey(opname) || operators.containsKey(opname+"u");} private final Map functions = new Object2ObjectOpenHashMap<>(); public Set getFunctionNames() {return functions.keySet();} public List getExpressionSnippet(Tokenizer.Token token) { String code = this.getCodeString(); List output = new ArrayList<>(getExpressionSnippetLeftContext(token, code, 1)); List context = getExpressionSnippetContext(token, code); output.add(context.get(0)+" HERE>> "+context.get(1)); output.addAll(getExpressionSnippetRightContext(token, code, 1)); return output; } private static List getExpressionSnippetLeftContext(Tokenizer.Token token, String expr, int contextsize) { List output = new ArrayList<>(); String[] lines = expr.split("\n"); if (lines.length == 1) return output; for (int lno=token.lineno-1; lno >=0 && output.size() < contextsize; lno-- ) { output.add(lines[lno]); } Collections.reverse(output); return output; } private static List getExpressionSnippetContext(Tokenizer.Token token, String expr) { List output = new ArrayList<>(); String[] lines = expr.split("\n"); if (lines.length > 1) { output.add(lines[token.lineno].substring(0, token.linepos)); output.add(lines[token.lineno].substring(token.linepos)); } else { output.add( expr.substring(max(0, token.pos-40), token.pos)); output.add( expr.substring(token.pos, min(token.pos+1+40, expr.length()))); } return output; } private static List getExpressionSnippetRightContext(Tokenizer.Token token, String expr, int contextsize) { List output = new ArrayList<>(); String[] lines = expr.split("\n"); if (lines.length == 1) { return output; } for (int lno=token.lineno+1; lno < lines.length && output.size() < contextsize; lno++ ) { output.add(lines[lno]); } return output; } public void addLazyUnaryOperator(String surface, int precedence, boolean leftAssoc, TriFunction lazyfun) { operators.put(surface+"u", new AbstractLazyOperator(precedence, leftAssoc) { @Override public LazyValue lazyEval(Context c, Integer t, Expression e, Tokenizer.Token token, LazyValue v, LazyValue v2) { try { if (v2 != null) { throw new ExpressionException(c, 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(c, exc, e, token); } } }); } public void addLazyBinaryOperatorWithDelegation(String surface, int precedence, boolean leftAssoc, SexFunction lazyfun) { operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) { @Override public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2) { try { Value.assertNotNull(v1, v2); return lazyfun.apply(c, type, e, t, v1, v2); } catch (RuntimeException exc) { throw handleCodeException(c, exc, e, t); } } }); } public void addLazyFunctionWithDelegation(String name, int numpar, QuinnFunction, LazyValue> lazyfun) { functions.put(name, new AbstractLazyFunction(numpar) { @Override public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List lv) { try { return lazyfun.apply(c, type, e, t, lv); } catch (RuntimeException exc) { throw handleCodeException(c, exc, e, t); } } }); } public void addLazyBinaryOperator(String surface, int precedence, boolean leftAssoc, QuadFunction lazyfun) { operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) { @Override public LazyValue lazyEval(Context c, Integer t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2) { try { Value.assertNotNull(v1, v2); return lazyfun.apply(c, t, v1, v2); } catch (RuntimeException exc) { throw handleCodeException(c, exc, e, token); } } }); } public static RuntimeException handleCodeException(Context c, RuntimeException exc, Expression e, Tokenizer.Token token) { if (exc instanceof ExitStatement) return exc; if (exc instanceof InternalExpressionException) return new ExpressionException(c, e, token, exc.getMessage(), ((InternalExpressionException) exc).stack); if (exc instanceof ArithmeticException) return new ExpressionException(c, e, token, "Your math is wrong, "+exc.getMessage()); if (exc instanceof ResolvedException) return exc; // unexpected really - should be caught earlier and converted to InternalExpressionException exc.printStackTrace(); return new ExpressionException(c, e, token, "Error while evaluating expression: "+exc); } public void addUnaryOperator(String surface, boolean leftAssoc, Function fun) { operators.put(surface+"u", new AbstractUnaryOperator(precedence.get("unary+-!"), leftAssoc) { @Override public Value evalUnary(Value v1) { return fun.apply(Value.assertNotNull(v1)); } }); } public void addBinaryOperator(String surface, int precedence, boolean leftAssoc, BiFunction fun) { operators.put(surface, new AbstractOperator(precedence, leftAssoc) { @Override public Value eval(Value v1, Value v2) { Value.assertNotNull(v1, v2); return fun.apply(v1, v2); } }); } public void addUnaryFunction(String name, Function fun) { functions.put(name, new AbstractFunction(1) { @Override public Value eval(List parameters) { return fun.apply(Value.assertNotNull(parameters.get(0))); } }); } public void addBinaryFunction(String name, BiFunction fun) { functions.put(name, new AbstractFunction(2) { @Override public Value eval(List parameters) { Value v1 = parameters.get(0); Value v2 = parameters.get(1); Value.assertNotNull(v1, v2); return fun.apply(v1, v2); } }); } public void addFunction(String name, Function, Value> fun) { functions.put(name, new AbstractFunction(-1) { @Override public Value eval(List parameters) { for (Value v: parameters) Value.assertNotNull(v); return fun.apply(parameters); } }); } public void addMathematicalUnaryFunction(String name, Function fun) { addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble()))); } public void addMathematicalBinaryFunction(String name, BiFunction fun) { addBinaryFunction(name, (w, v) -> new NumericValue(fun.apply(NumericValue.asNumber(w).getDouble(), NumericValue.asNumber(v).getDouble()))); } public void addLazyFunction(String name, int num_params, TriFunction, LazyValue> fun) { functions.put(name, new AbstractLazyFunction(num_params) { @Override public LazyValue lazyEval(Context c, Integer i, Expression e, Tokenizer.Token t, List lazyParams) { try { return fun.apply(c, i, lazyParams); } catch (RuntimeException exc) { throw handleCodeException(c, exc, e, t); } } }); } public FunctionValue addContextFunction(Context context, String name, Expression expr, Tokenizer.Token token, List arguments, List outers, LazyValue code) { if (functions.containsKey(name)) throw new ExpressionException(context, expr, token, "Function "+name+" would mask a built-in function"); Map contextValues = new HashMap<>(); for (String outer : outers) { LazyValue lv = context.getVariable(outer); if (lv == null) { throw new InternalExpressionException("Variable "+outer+" needs to be defined in outer scope to be used as outer parameter, and cannot be global"); } else { contextValues.put(outer, lv); } } if (contextValues.isEmpty()) contextValues = null; FunctionValue result = new FunctionValue(expr, token, name, code, arguments, contextValues); // do not store lambda definitions if (!name.equals("_")) context.host.addUserDefinedFunction(context, module, name, result); return result; } public void alias(String copy, String original) { functions.put(copy, functions.get(original)); } private static void UserDefinedFunctionsAndControlFlow(Expression expression) // public just to get the javadoc right { // artificial construct to handle user defined functions and function definitions expression.addLazyFunction("import", -1, (c, t, lv) -> { if (lv.size() < 1) throw new InternalExpressionException("'import' needs at least a module name to import, and list of values to import"); String moduleName = lv.get(0).evalValue(c).getString(); c.host.importModule(c, moduleName); moduleName = moduleName.toLowerCase(Locale.ROOT); if (lv.size() > 1) c.host.importNames(c, expression.module, moduleName, lv.subList(1, lv.size()).stream().map((l) -> l.evalValue(c).getString()).collect(Collectors.toList())); if (t == Context.VOID) return LazyValue.NULL; ListValue list = ListValue.wrap(c.host.availableImports(moduleName).map(StringValue::new).collect(Collectors.toList())); return (cc, tt) -> list; }); expression.addLazyFunctionWithDelegation("call",-1, (c, t, expr, tok, lv) -> { // adjust based on c if (lv.size() == 0) throw new InternalExpressionException("'call' expects at least function name to call"); //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 { FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, false, true); FunctionValue fun = functionArgument.function; Value retval = fun.callInContext(expr, c, t, fun.getExpression(), fun.getToken(), functionArgument.args).evalValue(c); return (cc, tt) -> retval; ///!!!! dono might need to store expr and token in statics? (e? t?) } // gimme signature String name = lv.get(0).evalValue(c).getString(); List args = new ArrayList<>(); List globals = new ArrayList<>(); for (int i = 1; i < lv.size(); 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; }); expression.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)); }); expression.addLazyBinaryOperator(";",precedence.get("nextop;"), true, (c, t, lv1, lv2) -> { lv1.evalValue(c, Context.VOID); Value v2 = lv2.evalValue(c, t); return (cc, tt) -> v2; }); //assigns const procedure to the lhs, returning its previous value expression.addLazyBinaryOperatorWithDelegation("->", precedence.get("def->"), false, (c, type, e, t, lv1, lv2) -> { if (type == Context.MAPDEF) { Value result = ListValue.of(lv1.evalValue(c), lv2.evalValue(c)); return (cc, tt) -> result; } Value v1 = lv1.evalValue(c, Context.SIGNATURE); if (!(v1 instanceof FunctionSignatureValue)) throw new InternalExpressionException("'->' operator requires a function signature on the LHS"); FunctionSignatureValue sign = (FunctionSignatureValue) v1; Value result = expression.addContextFunction(c, sign.getName(), e, t, sign.getArgs(), sign.getGlobals(), lv2); return (cc, tt) -> result; }); expression.addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); }); expression.addFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} ); expression.addFunction("throw", (lv)-> {throw new ThrowStatement(lv.size()==0?Value.NULL:lv.get(0)); }); expression.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 expression.addLazyFunction("if", -1, (c, t, lv) -> { if ( lv.size() < 2 ) throw new InternalExpressionException("'if' statement needs to have at least one condition and one case"); for (int i=0; i ret; } } if (lv.size()%2 == 1) { Value ret = lv.get(lv.size() - 1).evalValue(c, t); return (cc, tt) -> ret; } return (cc, tt) -> Value.NULL; }); } private static void Operators(Expression expression) { expression.addBinaryOperator("+", precedence.get("addition+-"), true, Value::add); expression.addBinaryOperator("-", precedence.get("addition+-"), true, Value::subtract); expression.addBinaryOperator("*", precedence.get("multiplication*/%"), true, Value::multiply); expression.addBinaryOperator("/", precedence.get("multiplication*/%"), true, Value::divide); expression.addBinaryOperator("%", precedence.get("multiplication*/%"), true, (v1, v2) -> new NumericValue(NumericValue.asNumber(v1).getDouble() % NumericValue.asNumber(v2).getDouble())); expression.addBinaryOperator("^", precedence.get("exponent^"), false, (v1, v2) -> new NumericValue(Math.pow(NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(v2).getDouble()))); expression.addLazyBinaryOperator("&&", precedence.get("and&&"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.BOOLEAN); if (!v1.getBoolean()) return (cc, tt) -> v1; return lv2; }); expression.addLazyBinaryOperator("||", precedence.get("or||"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.BOOLEAN); if (v1.getBoolean()) return (cc, tt) -> v1; return lv2; }); expression.addBinaryOperator("~", precedence.get("attribute~:"), true, Value::in); expression.addBinaryOperator(">", precedence.get("compare>=><=<"), false, (v1, v2) -> v1.compareTo(v2) > 0 ? Value.TRUE : Value.FALSE); expression.addBinaryOperator(">=", precedence.get("compare>=><=<"), false, (v1, v2) -> v1.compareTo(v2) >= 0 ? Value.TRUE : Value.FALSE); expression.addBinaryOperator("<", precedence.get("compare>=><=<"), false, (v1, v2) -> v1.compareTo(v2) < 0 ? Value.TRUE : Value.FALSE); expression.addBinaryOperator("<=", precedence.get("compare>=><=<"), false, (v1, v2) -> v1.compareTo(v2) <= 0 ? Value.TRUE : Value.FALSE); expression.addBinaryOperator("==", precedence.get("equal==!="), false, (v1, v2) -> v1.equals(v2) ? Value.TRUE : Value.FALSE); expression.addBinaryOperator("!=", precedence.get("equal==!="), false, (v1, v2) -> v1.equals(v2) ? Value.FALSE : Value.TRUE); expression.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 ll = ((ListValue)v1).getItems(); List rl = ((ListValue)v2).getItems(); if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); for (Value v: ll) v.assertAssignable(); Iterator li = ll.iterator(); Iterator ri = rl.iterator(); while(li.hasNext()) { String lname = li.next().getVariable(); Value vval = ri.next().reboundedTo(lname); expression.setAnyVariable(c, 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; expression.setAnyVariable(c, varname, boundedLHS); return boundedLHS; }); expression.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 ll = ((ListValue)v1).getItems(); List rl = ((ListValue)v2).getItems(); if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); for (Value v: ll) v.assertAssignable(); Iterator li = ll.iterator(); Iterator ri = rl.iterator(); while(li.hasNext()) { Value lval = li.next(); String lname = lval.getVariable(); Value result = lval.add(ri.next()).bindTo(lname); expression.setAnyVariable(c, 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 value = cvi.get(key); if (value instanceof ListValue || value instanceof MapValue) { ((AbstractListValue) value).append(v2); return (cc, tt) -> value; } else { Value res = value.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) { ((AbstractListValue) v1).append(v2); boundedLHS = (cc, tt)-> v1; } else { Value result = v1.add(v2).bindTo(varname); boundedLHS = (cc, tt) -> result; } expression.setAnyVariable(c, varname, boundedLHS); return boundedLHS; }); expression.addLazyBinaryOperator("<>", precedence.get("assign=<>"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c); Value v2 = lv2.evalValue(c); if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue.ListConstructorValue) { List ll = ((ListValue)v1).getItems(); List rl = ((ListValue)v2).getItems(); if (ll.size() < rl.size()) throw new InternalExpressionException("Too many values to unpack"); if (ll.size() > rl.size()) throw new InternalExpressionException("Too few values to unpack"); for (Value v: ll) v.assertAssignable(); for (Value v: rl) v.assertAssignable(); Iterator li = ll.iterator(); Iterator ri = rl.iterator(); while(li.hasNext()) { Value lval = li.next(); Value rval = ri.next(); String lname = lval.getVariable(); String rname = rval.getVariable(); lval.reboundedTo(rname); rval.reboundedTo(lname); expression.setAnyVariable(c, lname, (cc, tt) -> rval); expression.setAnyVariable(c, 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); expression.setAnyVariable(c, lvalvar, (cc, tt) -> lval); expression.setAnyVariable(c, rvalvar, (cc, tt) -> rval); return (cc, tt) -> lval; }); expression.addUnaryOperator("-", false, (v) -> new NumericValue(-NumericValue.asNumber(v).getDouble())); expression.addUnaryOperator("+", false, (v) -> new NumericValue(NumericValue.asNumber(v).getDouble())); expression.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 } private static void ArithmeticOperations(Expression expression) { expression.addLazyFunction("not", 1, (c, t, lv) -> lv.get(0).evalValue(c, Context.BOOLEAN).getBoolean() ? ((cc, tt) -> Value.FALSE) : ((cc, tt) -> Value.TRUE)); expression.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); }); expression.addMathematicalUnaryFunction("sin", (d) -> Math.sin(Math.toRadians(d))); expression.addMathematicalUnaryFunction("cos", (d) -> Math.cos(Math.toRadians(d))); expression.addMathematicalUnaryFunction("tan", (d) -> Math.tan(Math.toRadians(d))); expression.addMathematicalUnaryFunction("asin", (d) -> Math.toDegrees(Math.asin(d))); expression.addMathematicalUnaryFunction("acos", (d) -> Math.toDegrees(Math.acos(d))); expression.addMathematicalUnaryFunction("atan", (d) -> Math.toDegrees(Math.atan(d))); expression.addMathematicalBinaryFunction("atan2", (d, d2) -> Math.toDegrees(Math.atan2(d, d2)) ); expression.addMathematicalUnaryFunction("sinh", Math::sinh ); expression.addMathematicalUnaryFunction("cosh", Math::cosh ); expression.addMathematicalUnaryFunction("tanh", Math::tanh ); expression.addMathematicalUnaryFunction("sec", (d) -> 1.0 / Math.cos(Math.toRadians(d)) ); // Formula: sec(x) = 1 / cos(x) expression.addMathematicalUnaryFunction("csc", (d) -> 1.0 / Math.sin(Math.toRadians(d)) ); // Formula: csc(x) = 1 / sin(x) expression.addMathematicalUnaryFunction("sech", (d) -> 1.0 / Math.cosh(d) ); // Formula: sech(x) = 1 / cosh(x) expression.addMathematicalUnaryFunction("csch", (d) -> 1.0 / Math.sinh(d) ); // Formula: csch(x) = 1 / sinh(x) expression.addMathematicalUnaryFunction("cot", (d) -> 1.0 / Math.tan(Math.toRadians(d)) ); // Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x) expression.addMathematicalUnaryFunction("acot", (d) -> Math.toDegrees(Math.atan(1.0 / d)) );// Formula: acot(x) = atan(1/x) expression.addMathematicalUnaryFunction("coth", (d) -> 1.0 / Math.tanh(d) ); // Formula: coth(x) = 1 / tanh(x) expression.addMathematicalUnaryFunction("asinh", (d) -> Math.log(d + (Math.sqrt(Math.pow(d, 2) + 1)))); // Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) expression.addMathematicalUnaryFunction("acosh", (d) -> Math.log(d + (Math.sqrt(Math.pow(d, 2) - 1)))); // Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) expression.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)); }); expression.addMathematicalUnaryFunction("rad", Math::toRadians); expression.addMathematicalUnaryFunction("deg", Math::toDegrees); expression.addMathematicalUnaryFunction("ln", Math::log); expression.addMathematicalUnaryFunction("ln1p", Math::log1p); expression.addMathematicalUnaryFunction("log10", Math::log10); expression.addMathematicalUnaryFunction("log", a -> Math.log(a)/Math.log(2)); expression.addMathematicalUnaryFunction("log1p", x -> Math.log1p(x)/Math.log(2)); expression.addMathematicalUnaryFunction("sqrt", Math::sqrt); expression.addMathematicalUnaryFunction("abs", Math::abs); expression.addMathematicalUnaryFunction("round", (d) -> (double)Math.round(d)); expression.addMathematicalUnaryFunction("floor", Math::floor); expression.addMathematicalUnaryFunction("ceil", Math::ceil); expression.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); }); expression.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; }); expression.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; }); expression.addUnaryFunction("relu", (v) -> v.compareTo(Value.ZERO) < 0 ? Value.ZERO : v); } private static void LoopsAndHigherOrderFunctions(Expression expression) { // condition and expression will get a bound '_i' // returns last successful expression or false // while(cond, limit, expr) => ?? expression.addFunction("break", lv -> { if (lv.size()==0) throw new BreakStatement(null); if (lv.size()==1) throw new BreakStatement(lv.get(0)); throw new InternalExpressionException("'break' can only be called with zero or one argument"); }); expression.addFunction("continue", lv -> { if (lv.size()==0) throw new ContinueStatement(null); if (lv.size()==1) throw new ContinueStatement(lv.get(0)); throw new InternalExpressionException("'continue' can only be called with zero or one argument"); }); expression.addLazyFunction("while", 3, (c, t, lv) -> { long limit = NumericValue.asNumber(lv.get(1).evalValue(c)).getLong(); LazyValue condition = lv.get(0); LazyValue expr = lv.get(2); long i = 0; Value lastOne = Value.NULL; //scoping LazyValue _val = c.getVariable("_"); c.setVariable("_",(cc, tt) -> new NumericValue(0).bindTo("_")); while (i new NumericValue(seriously).bindTo("_")); } //revering scope c.setVariable("_", _val); Value lastValueNoKidding = lastOne; return (cc, tt) -> lastValueNoKidding; }); // loop(Num, expr) => last_value // expr receives bounded variable '_' indicating iteration expression.addLazyFunction("loop", 2, (c, t, lv) -> { long limit = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong(); Value lastOne = Value.NULL; LazyValue expr = lv.get(1); //scoping LazyValue _val = c.getVariable("_"); for (long i=0; i < limit; i++) { long whyYouAsk = i; c.setVariable("_", (cc, tt) -> new NumericValue(whyYouAsk).bindTo("_")); try { lastOne = expr.evalValue(c, t); } catch (BreakStatement | ContinueStatement stmt) { if (stmt.retval != null) lastOne = stmt.retval; if (stmt instanceof BreakStatement) 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 expression.addLazyFunction("map", 2, (c, t, lv) -> { 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 iterator = ((AbstractListValue) rval).iterator(); LazyValue expr = lv.get(1); //scoping LazyValue _val = c.getVariable("_"); LazyValue _iter = c.getVariable("_i"); List result = new ArrayList<>(); for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); int doYouReally = i; c.setVariable("_", (cc, tt) -> next); c.setVariable("_i", (cc, tt) -> new NumericValue(doYouReally).bindTo("_i")); try { result.add(expr.evalValue(c)); } catch (BreakStatement | ContinueStatement stmt) { if (stmt.retval != null) result.add(stmt.retval); if (stmt instanceof BreakStatement) { 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) => list // receives bounded variable '_' with the expression, and "_i" with index // produces list of values for which the expression is true expression.addLazyFunction("filter", 2, (c, t, lv) -> { 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 iterator = ((AbstractListValue) rval).iterator(); LazyValue expr = lv.get(1); //scoping LazyValue _val = c.getVariable("_"); LazyValue _iter = c.getVariable("_i"); List result = new ArrayList<>(); for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); int seriously = i; c.setVariable("_", (cc, tt) -> next); c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); try { if(expr.evalValue(c, Context.BOOLEAN).getBoolean()) result.add(next); } catch (BreakStatement | ContinueStatement stmt) { if (stmt.retval != null && stmt.retval.getBoolean()) result.add(next); if (stmt instanceof BreakStatement) { 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 expression.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 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")); try { if(expr.evalValue(c, Context.BOOLEAN).getBoolean()) { result = next; next.boundVariable = var; break; } } catch (BreakStatement stmt) { result = stmt.retval == null? next : stmt.retval; next.boundVariable = var; break; } catch (ContinueStatement ignored) { throw new InternalExpressionException("'continue' inside 'first' function has no sense"); } 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 expression.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 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, Context.BOOLEAN).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; }); // runs traditional for(init, condition, increment, body) tri-argument for loop with body in between expression.addLazyFunction("c_for", 4, (c, t, lv) -> { LazyValue initial = lv.get(0); LazyValue condition = lv.get(1); LazyValue increment = lv.get(2); LazyValue body = lv.get(3); int iterations = 0; for (initial.evalValue(c, Context.VOID); condition.evalValue(c, Context.BOOLEAN).getBoolean(); increment.evalValue(c, Context.VOID)) { try { body.evalValue(c, Context.VOID); } catch (BreakStatement stmt) { break; } catch (ContinueStatement ignored) { } iterations++; } int finalIterations = iterations; return (cc, tt) -> new NumericValue(finalIterations); }); // similar to map, but returns total number of successes // for(list, expr) => success_count // can be substituted for first and all, but first is more efficient and all doesn't require knowing list size expression.addLazyFunction("for", 2, (c, t, lv) -> { Value rval= lv.get(0).evalValue(c); if (!(rval instanceof AbstractListValue)) throw new InternalExpressionException("First argument of 'for' function should be a list or iterator"); Iterator iterator = ((AbstractListValue) rval).iterator(); LazyValue expr = lv.get(1); //scoping LazyValue _val = c.getVariable("_"); LazyValue _ite = 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")); Value result = Value.FALSE; try { result = expr.evalValue(c, t); } catch (BreakStatement | ContinueStatement stmt) { if (stmt.retval != null) result = stmt.retval; if (stmt instanceof BreakStatement) { next.boundVariable = var; break; } } if(t != Context.VOID && result.getBoolean()) successCount++; next.boundVariable = var; } //revering scope ((AbstractListValue) rval).fatality(); c.setVariable("_", _val); c.setVariable("_i", _ite); 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 expression.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 iterator = ((AbstractListValue) rval).iterator(); if (!iterator.hasNext()) { Value seriouslyWontChange = acc; return (cc, tt) -> seriouslyWontChange; } //scoping LazyValue _val = c.getVariable("_"); LazyValue _acc = c.getVariable("_a"); LazyValue _ite = c.getVariable("_i"); for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); Value promiseWontChangeYou = acc; int seriously = i; c.setVariable("_a", (cc, tt) -> promiseWontChangeYou.bindTo("_a")); c.setVariable("_", (cc, tt) -> next); c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); try { acc = expr.evalValue(c, t); } catch (BreakStatement | ContinueStatement stmt) { if (stmt.retval != null) acc = stmt.retval; if (stmt instanceof BreakStatement) { next.boundVariable = var; break; } } next.boundVariable = var; } //reverting scope ((AbstractListValue) rval).fatality(); c.setVariable("_a", _acc); c.setVariable("_", _val); c.setVariable("_i", _ite); Value hopeItsEnoughPromise = acc; return (cc, tt) -> hopeItsEnoughPromise; }); } private static void BasicDataStructures(Expression expression) { expression.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); }); expression.addFunction("join", (lv) -> { if (lv.size() < 2) throw new InternalExpressionException("'join' takes at least 2 arguments"); String delimiter = lv.get(0).getString(); List toJoin; if (lv.size()==2 && lv.get(1) instanceof LazyListValue) { toJoin = ((LazyListValue) lv.get(1)).unroll(); } else if (lv.size() == 2 && lv.get(1) instanceof ListValue) { toJoin = new ArrayList<>(((ListValue)lv.get(1)).getItems()); } else { toJoin = lv.subList(1,lv.size()); } return new StringValue(toJoin.stream().map(Value::getString).collect(Collectors.joining(delimiter))); }); expression.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())); }); expression.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); }); expression.addFunction("sort", (lv) -> { List toSort = lv; if (lv.size()==1 && lv.get(0) instanceof ListValue) { toSort = new ArrayList<>(((ListValue)lv.get(0)).getItems()); } Collections.sort(toSort); return ListValue.wrap(toSort); }); expression.addLazyFunction("sort_key", -1, (c, t, lv) -> //get working with iterators { if (lv.size() == 0) throw new InternalExpressionException("First argument for 'sort_key' should be a List"); Value v = lv.get(0).evalValue(c); if (!(v instanceof ListValue)) throw new InternalExpressionException("First argument for 'sort_key' should be a List"); List toSort = new ArrayList<>(((ListValue) v).getItems()); if (lv.size()==1) { Collections.shuffle(toSort); Value ret = ListValue.wrap(toSort); return (_c, _t) -> ret; } LazyValue sortKey = lv.get(1); //scoping LazyValue __ = c.getVariable("_"); 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); }); expression.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); }); expression.addLazyFunction("m", -1, (c, t, llv) -> { List lv = new ArrayList<>(); for (LazyValue lazyParam : llv) { lv.add(lazyParam.evalValue(c, Context.MAPDEF)); // none type default by design } Value ret; if (lv.size() == 1 && lv.get(0) instanceof LazyListValue) ret = new MapValue(((LazyListValue) lv.get(0)).unroll()); else ret = new MapValue(lv); return (cc, tt) -> ret; }); expression.addUnaryFunction("keys", v -> { if (v instanceof MapValue) return new ListValue(((MapValue) v).getMap().keySet()); return Value.NULL; }); expression.addUnaryFunction("values", v -> { if (v instanceof MapValue) return new ListValue(((MapValue) v).getMap().values()); return Value.NULL; }); expression.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; }); expression.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; }); expression.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; }); expression.addLazyFunction("has", -1, (c, t, lv) -> { if (lv.size() == 0) throw new InternalExpressionException("'has' 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; }); expression.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; }); expression.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; }); } private static void SystemFunctions(Expression expression) { expression.addUnaryFunction("hash_code", v -> new NumericValue(v.hashCode())); expression.addUnaryFunction("copy", Value::deepcopy); expression.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; }); expression.addUnaryFunction("number", v -> { if (v instanceof NumericValue) { return v; } try { return new NumericValue(v.getString()); } catch (NumberFormatException format) { return Value.NULL; } }); expression.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.size() == 2) { lv = ((ListValue) lv.get(1)).getItems(); argIndex = 0; } List args = new ArrayList<>(); Matcher m = formatPattern.matcher(format); for (int i = 0, len = format.length(); i < len; ) { if (m.find(i)) { // Anything between the start of the string and the beginning // of the format specifier is either fixed text or contains // an invalid format string. // [[scarpet]] but we skip it and let the String.format fail char fmt = m.group(6).toLowerCase().charAt(0); if (fmt == 's') { if (argIndex >= lv.size()) throw new InternalExpressionException("Not enough arguments for "+m.group(0)); args.add(lv.get(argIndex).getString()); argIndex++; } else if (fmt == 'd' || fmt == 'o' || fmt == 'x') { if (argIndex >= lv.size()) throw new InternalExpressionException("Not enough arguments for "+m.group(0)); args.add(lv.get(argIndex).readInteger()); argIndex++; } else if (fmt == 'a' || fmt == 'e' || fmt == 'f' || fmt == 'g') { if (argIndex >= lv.size()) throw new InternalExpressionException("Not enough arguments for "+m.group(0)); args.add(lv.get(argIndex).readDoubleNumber()); 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(Locale.ROOT, format, args.toArray())); } catch (IllegalFormatException ife) { throw new InternalExpressionException("Illegal string format: "+ife.getMessage()); } }); expression.addUnaryFunction("lower", v -> new StringValue(v.getString().toLowerCase(Locale.ROOT))); expression.addUnaryFunction("upper", v -> new StringValue(v.getString().toUpperCase(Locale.ROOT))); expression.addUnaryFunction("title", v -> new StringValue(WordUtils.capitalizeFully(v.getString()))); expression.addFunction("replace", (lv) -> { if (lv.size() != 3 && lv.size() !=2) throw new InternalExpressionException("'replace' expects string to read, pattern regex, and optional replacement string"); String data = lv.get(0).getString(); String regex = lv.get(1).getString(); String replacement = ""; if (lv.size() == 3) replacement = lv.get(2).getString(); return new StringValue(data.replaceAll(regex, replacement)); }); expression.addFunction("replace_first", (lv) -> { if (lv.size() != 3 && lv.size() !=2) throw new InternalExpressionException("'replace_first' expects string to read, pattern regex, and optional replacement string"); String data = lv.get(0).getString(); String regex = lv.get(1).getString(); String replacement = ""; if (lv.size() == 3) replacement = lv.get(2).getString(); return new StringValue(data.replaceFirst(regex, replacement)); }); expression.addUnaryFunction("type", v -> new StringValue(v.getTypeString())); expression.addUnaryFunction("length", v -> new NumericValue(v.length())); expression.addLazyFunction("rand", -1, (c, t, lv) -> { int argsize = lv.size(); Random randomizer = Expression.randomizer; if (argsize != 1 && argsize != 2) throw new InternalExpressionException("'rand' takes one (range) or two arguments (range and seed)"); if (argsize == 2) randomizer = c.host.getRandom(NumericValue.asNumber(lv.get(1).evalValue(c)).getLong()); Value argument = lv.get(0).evalValue(c); if (argument instanceof ListValue) { List list = ((ListValue) argument).getItems(); Value retval = list.get(randomizer.nextInt(list.size())); return (cc, tt) -> retval; } if (t == Context.BOOLEAN) { double rv = NumericValue.asNumber(argument).getDouble()*randomizer.nextFloat(); Value retval = rv<1.0D?Value.FALSE:Value.TRUE; return (cc, tt) -> retval; } Value retval = new NumericValue(NumericValue.asNumber(argument).getDouble()*randomizer.nextDouble()); return (cc, tt) -> retval; }); expression.addLazyFunction("perlin", -1, (c, t, lv) -> { PerlinNoiseSampler sampler; Value x, y, z; if (lv.size() >= 4) { x = lv.get(0).evalValue(c); y = lv.get(1).evalValue(c); z = lv.get(2).evalValue(c); sampler = PerlinNoiseSampler.getPerlin(NumericValue.asNumber(lv.get(3).evalValue(c)).getLong()); } else { sampler = PerlinNoiseSampler.instance; y = Value.NULL; z = Value.NULL; if (lv.size() == 0 ) throw new InternalExpressionException("'perlin' requires at least one dimension to sample from"); x = NumericValue.asNumber(lv.get(0).evalValue(c)); if (lv.size() > 1) { y = NumericValue.asNumber(lv.get(1).evalValue(c)); if (lv.size() > 2) z = NumericValue.asNumber(lv.get(2).evalValue(c)); } } double result; if (z instanceof NullValue) if (y instanceof NullValue) result = sampler.sample1d(NumericValue.asNumber(x).getDouble()); else result = sampler.sample2d(NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble()); else result = sampler.sample3d( NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble(), NumericValue.asNumber(z).getDouble()); Value ret = new NumericValue(result); return (cc, tt) -> ret; }); expression.addLazyFunction("simplex", -1, (c, t, lv) -> { SimplexNoiseSampler sampler; Value x, y, z; if (lv.size() >= 4) { x = lv.get(0).evalValue(c); y = lv.get(1).evalValue(c); z = lv.get(2).evalValue(c); sampler = SimplexNoiseSampler.getSimplex(NumericValue.asNumber(lv.get(3).evalValue(c)).getLong()); } else { sampler = SimplexNoiseSampler.instance; z = Value.NULL; if (lv.size() < 2 ) throw new InternalExpressionException("'simplex' requires at least two dimensions to sample from"); x = NumericValue.asNumber(lv.get(0).evalValue(c)); y = NumericValue.asNumber(lv.get(1).evalValue(c)); if (lv.size() > 2) z = NumericValue.asNumber(lv.get(2).evalValue(c)); } double result; if (z instanceof NullValue) result = sampler.sample2d(NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble()); else result = sampler.sample3d( NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble(), NumericValue.asNumber(z).getDouble()); Value ret = new NumericValue(result); return (cc, tt) -> ret; }); expression.addUnaryFunction("print", (v) -> { System.out.println(v.getString()); return v; // pass through for variables }); expression.addUnaryFunction("sleep", (v) -> { long time = NumericValue.asNumber(v).getLong(); try { Thread.sleep(time); Thread.yield(); } catch (InterruptedException ignored) { } return v; // pass through for variables }); expression.addLazyFunction("time", 0, (c, t, lv) -> { Value time = new NumericValue((System.nanoTime() / 1000) / 1000.0); return (cc, tt) -> time; }); expression.addLazyFunction("unix_time", 0, (c, t, lv) -> { Value time = new NumericValue(System.currentTimeMillis()); return (cc, tt) -> time; }); expression.addFunction("convert_date", lv -> { int argsize = lv.size(); if (lv.size() == 0) throw new InternalExpressionException("'convert_date' requires at least one parameter"); Value value = lv.get(0); if (argsize == 1 && !(value instanceof ListValue)) { Calendar cal = new GregorianCalendar(Locale.ROOT); cal.setTimeInMillis(NumericValue.asNumber(value, "timestamp").getLong()); int weekday = cal.get(Calendar.DAY_OF_WEEK)-1; if (weekday == 0) weekday = 7; Value retVal = ListValue.ofNums( cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), weekday, cal.get(Calendar.DAY_OF_YEAR), cal.get(Calendar.WEEK_OF_YEAR) ); return retVal; } else if(value instanceof ListValue) { lv = ((ListValue) value).getItems(); argsize = lv.size(); } Calendar cal = new GregorianCalendar(0, 0, 0, 0, 0, 0); if (argsize == 3) { cal.set( NumericValue.asNumber(lv.get(0)).getInt(), NumericValue.asNumber(lv.get(1)).getInt()-1, NumericValue.asNumber(lv.get(2)).getInt() ); } else if (argsize == 6) { cal.set( NumericValue.asNumber(lv.get(0)).getInt(), NumericValue.asNumber(lv.get(1)).getInt()-1, NumericValue.asNumber(lv.get(2)).getInt(), NumericValue.asNumber(lv.get(3)).getInt(), NumericValue.asNumber(lv.get(4)).getInt(), NumericValue.asNumber(lv.get(5)).getInt() ); } else throw new InternalExpressionException("Date conversion requires 3 arguments for Dates or 6 arguments, for time"); return new NumericValue(cal.getTimeInMillis()); }); expression.addLazyFunction("profile_expr", 1, (c, t, lv) -> { LazyValue lazy = lv.get(0); long end = System.nanoTime()+50000000L; long it = 0; while (System.nanoTime() res; }); expression.addLazyFunction("var", 1, (c, t, lv) -> { String varname = lv.get(0).evalValue(c).getString(); return expression.getOrSetAnyVariable(c, varname); }); expression.addLazyFunction("undef", 1, (c, t, lv) -> { Value remove = lv.get(0).evalValue(c); if (remove instanceof FunctionValue) { c.host.delFunction(expression.module, remove.getString()); return (cc, tt) -> Value.NULL; } String varname = remove.getString(); boolean isPrefix = varname.endsWith("*"); if (isPrefix) varname = varname.replaceAll("\\*+$", ""); if (isPrefix) { c.host.delFunctionWithPrefix(expression.module, varname); if (varname.startsWith("global_")) { c.host.delGlobalVariableWithPrefix(expression.module, varname); } else if (!varname.startsWith("_")) { c.removeVariablesMatching(varname); } } else { c.host.delFunction(expression.module, varname); if (varname.startsWith("global_")) { c.host.delGlobalVariable(expression.module, varname); } else if (!varname.startsWith("_")) { c.delVariable(varname); } } return (cc, tt) -> Value.NULL; }); //deprecate expression.addLazyFunction("vars", 1, (c, t, lv) -> { String prefix = lv.get(0).evalValue(c).getString(); List values = new ArrayList<>(); if (prefix.startsWith("global")) { c.host.globaVariableNames(expression.module, (s) -> s.startsWith(prefix)).forEach(s -> values.add(new StringValue(s))); } else { c.getAllVariableNames().stream().filter(s -> s.startsWith(prefix)).forEach(s -> values.add(new StringValue(s))); } Value retval = ListValue.wrap(values); return (cc, tt) -> retval; }); expression.addLazyFunctionWithDelegation("task", -1, (c, t, expr, tok, lv) -> { if (lv.size() == 0) throw new InternalExpressionException("'task' requires at least function to call as a parameter"); FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 0, true, false); Value queue = Value.NULL; if (lv.size() > functionArgument.offset) queue = lv.get(functionArgument.offset).evalValue(c); ThreadValue thread = new ThreadValue(queue, functionArgument.function, expr, tok, c, functionArgument.args); Thread.yield(); return (cc, tt) -> thread; }); expression.addFunction("task_count", (lv) -> { if (lv.size() > 0) { return new NumericValue(ThreadValue.taskCount(lv.get(0))); } return new NumericValue(ThreadValue.taskCount()); }); expression.addUnaryFunction("task_value", (v) -> { if (!(v instanceof ThreadValue)) throw new InternalExpressionException("'task_value' could only be used with a task value"); return ((ThreadValue) v).getValue(); }); expression.addUnaryFunction("task_join", (v) -> { if (!(v instanceof ThreadValue)) throw new InternalExpressionException("'task_join' could only be used with a task value"); return ((ThreadValue) v).join(); }); expression.addLazyFunction("task_dock", 1, (c, t, lv) -> { // pass through placeholder // implmenetation should dock the task on the main thread. return lv.get(0); }); expression.addUnaryFunction("task_completed", (v) -> { if (!(v instanceof ThreadValue)) throw new InternalExpressionException("'task_completed' could only be used with a task value"); return new NumericValue(((ThreadValue) v).isFinished()); }); expression.addLazyFunction("synchronize", -1, (c, t, lv) -> { if (lv.size() == 0) throw new InternalExpressionException("'synchronize' require at least an expression to synchronize"); Value lockValue = Value.NULL; int ind = 0; if (lv.size() == 2) { lockValue = lv.get(0).evalValue(c); ind = 1; } synchronized (ThreadValue.getLock(lockValue)) { Value ret = lv.get(ind).evalValue(c, t); return (_c, _t) -> ret; } }); expression.addLazyFunction("system_variable_get", -1, (c, t, lv) -> { if (lv.size() == 0) throw new InternalExpressionException("'system_variable_get' expects at least a key to be fetched"); Value key = lv.get(0).evalValue(c); if (lv.size() > 1) { ScriptHost.systemGlobals.computeIfAbsent(key, k -> lv.get(1).evalValue(c)); } Value res = ScriptHost.systemGlobals.get(key); if (res!=null) return (cc, tt) -> res; return (cc, tt) -> Value.NULL; }); expression.addLazyFunction("system_variable_set", 2, (c, t, lv) -> { Value key = lv.get(0).evalValue(c); Value value = lv.get(1).evalValue(c); Value res = ScriptHost.systemGlobals.put(key, value); if (res!=null) return (cc, tt) -> res; return (cc, tt) -> Value.NULL; }); } public void setAnyVariable(Context c, String name, LazyValue lv) { if (name.startsWith("global_")) { c.host.setGlobalVariable(module, name, lv); } else { c.setVariable(name, lv); } } public LazyValue getOrSetAnyVariable(Context c, String name) { LazyValue var = null; if (!name.startsWith("global_")) { var = c.getVariable(name); if (var != null) return var; } var = c.host.getGlobalVariable(module, name); if (var != null) return var; var = (_c, _t ) -> Value.ZERO.reboundedTo(name); setAnyVariable(c, name, var); return var; } public static final Expression none = new Expression("null"); /** * @param expression . */ public Expression(String expression) { this.expression = expression.trim().replaceAll("\\r\\n?", "\n").replaceAll("\\t"," "); UserDefinedFunctionsAndControlFlow(this); Operators(this); ArithmeticOperations(this); SystemFunctions(this); LoopsAndHigherOrderFunctions(this); BasicDataStructures(this); } private List shuntingYard(Context c) { List outputQueue = new ArrayList<>(); Stack stack = new Stack<>(); Tokenizer tokenizer = new Tokenizer(c, this, expression, allowComments, allowNewlineSubstitutions); // stripping lousy but acceptable semicolons List cleanedTokens = tokenizer.postProcess(); Tokenizer.Token lastFunction = null; Tokenizer.Token previousToken = null; for (Tokenizer.Token token : cleanedTokens) { 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(c, 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(c, 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(c, this, token, "Unexpected comma"); } else { throw new ExpressionException(c, 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(c, this, token, "Missing parameter(s) for operator '" + token+"'"); } ILazyOperator o1 = operators.get(token.surface); if (o1 == null) { throw new ExpressionException(c, 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(c, this, token, "Invalid position for unary operator " + token ); } ILazyOperator o1 = operators.get(token.surface); if (o1 == null) { throw new ExpressionException(c, 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(c, 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(c, this, "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(c, this, element, "Mismatched parentheses"); } outputQueue.add(element); } return outputQueue; } private void shuntOperators(List outputQueue, Stack stack, ILazyOperator o1) { Tokenizer.Token nextToken = stack.isEmpty() ? null : stack.peek(); while (nextToken != null && (nextToken.type == Tokenizer.Token.TokenType.OPERATOR || nextToken.type == Tokenizer.Token.TokenType.UNARY_OPERATOR) && ((o1.isLeftAssoc() && o1.getPrecedence() <= operators.get(nextToken.surface).getPrecedence()) || (o1.getPrecedence() < operators.get(nextToken.surface).getPrecedence()))) { outputQueue.add(stack.pop()); nextToken = stack.isEmpty() ? null : stack.peek(); } } public Value eval(Context c) { return eval(c, Context.NONE); } public Value eval(Context c, Integer expectedType) { if (ast == null) { ast = getAST(c); } return evalValue(() -> ast, c, expectedType); } public Value evalValue(Supplier exprProvider, Context c, Integer expectedType) { try { return exprProvider.get().evalValue(c, expectedType); } catch (ContinueStatement | BreakStatement | ReturnStatement exc) { throw new ExpressionException(c, this, "Control flow functions, like continue, break, or return, should only be used in loops and functions respectively."); } catch (ExitStatement exit) { return exit.retval==null?Value.NULL:exit.retval; } catch (StackOverflowError ignored) { throw new ExpressionException(c, this, "Your thoughts are too deep"); } catch (InternalExpressionException exc) { throw new ExpressionException(c, this, "Your expression result is incorrect: "+exc.getMessage()); } catch (ArithmeticException exc) { throw new ExpressionException(c, this, "The final result is incorrect: "+exc.getMessage()); } } private LazyValue getAST(Context context) { Stack stack = new Stack<>(); List rpn = shuntingYard(context); validate(context, 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) -> getOrSetAnyVariable(c, token.surface).evalValue(c)); break; case FUNCTION: String name = token.surface; ILazyFunction f; ArrayList p; boolean isKnown = functions.containsKey(name); // globals will be evaluated lazily, not at compile time via . if (isKnown) { f = functions.get(name); p = new ArrayList<>(!f.numParamsVaries() ? f.getNumParams() : 0); } else // potentially unknown function or just unknown function { f = functions.get("call"); 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(stack.pop()); } if (!isKnown) p.add( (c, t) -> new StringValue(name)); Collections.reverse(p); 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(c, 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(context, this, token, "Unexpected token '" + token.surface + "'"); } } return stack.pop(); } private void validate(Context c, List rpn) { /*- * Thanks to Norman Ramsey: * http://http://stackoverflow.com/questions/789847/postfix-notation-validation */ // each push on to this stack is a new function scope, with the value of // each // layer on the stack being the count of the number of parameters in // that scope Stack stack = new Stack<>(); // push the 'global' scope stack.push(0); for (final Tokenizer.Token token : rpn) { switch (token.type) { case UNARY_OPERATOR: if (stack.peek() < 1) { throw new ExpressionException(c, this, token, "Missing parameter(s) for operator " + token); } break; case OPERATOR: if (stack.peek() < 2) { if (token.surface.equals(";")) { throw new ExpressionException(c, this, token, "Unnecessary semicolon"); } throw new ExpressionException(c, 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);// don't validate global - userdef functions int numParams = stack.pop(); if (f != null && !f.numParamsVaries() && numParams != f.getNumParams()) { throw new ExpressionException(c, this, token, "Function " + token + " expected " + f.getNumParams() + " parameters, got " + numParams); } if (stack.size() <= 0) { throw new ExpressionException(c, 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(c, this, "Too many unhandled function parameter lists"); } else if (stack.peek() > 1) { throw new ExpressionException(c, this, "Too many numbers or variables"); } else if (stack.peek() < 1) { throw new ExpressionException(c, this, "Empty expression"); } } }