Merge pull request #844 from gnembon/expression_optimizer
Expression optimizer
This commit is contained in:
31 files changed
+1120
-229
No files matched your search
@@ -1,7 +1,19 @@
|
||||
# Minecraft specific API and `scarpet` language add-ons and commands
|
||||
|
||||
Here is the gist of the Minecraft related functions. Otherwise the CarpetScript could live without Minecraft.
|
||||
Here is the gist of the Minecraft related functions. Otherwise the scarpet could live without Minecraft.
|
||||
|
||||
## Global scarpet options
|
||||
|
||||
These options affect directly how scarpet functions and can be triggered via `/carpet` command.
|
||||
- `commandScript`: disables `/script` command making it impossible to control apps in game. Apps will still load and run
|
||||
when loaded with the world (i.e. present in the world/scripts folder)
|
||||
- `scriptsAutoload`: when set to `false` will prevent apps loaded with the world to load automatically. You can still
|
||||
load them on demand via `/script load` command
|
||||
- `commandScriptACE`: command permission level that is used to trigger commands from scarpet scripts (regardless who triggeres
|
||||
the code that calls the command). Defaults to `ops`, could be customized to any level via a numerical value (0, 1, 2, 3 or 4)
|
||||
- `scriptsOptimization`: when disabled, disables default app compile time optimizations. If your app behaves differently with
|
||||
and without optimizations, please file a bug report on the bug tracker and disable code optimizations
|
||||
- `scriptsDebugging`: Puts detailed information about apps loading, performance and runtime in system log.
|
||||
|
||||
## App structure
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Writing programs with more than 1 line
|
||||
|
||||
### `Operator ;`
|
||||
### Operator `;`, `then(...)`
|
||||
|
||||
To effectively write programs that have more than one line, a programmer needs way to specify a sequence of commands
|
||||
that execute one after another. In `scarpet` this can be achieved with `;`. Its an operator, and by separating
|
||||
@@ -22,7 +22,7 @@ in `scarpet`, and not barely an instruction delimiter, terminating the code with
|
||||
be valid. Having said that, since many programming languages don't care about the number of op terminators
|
||||
programmers use, carpet preprocessor will remove all unnecessary semicolons from scripts when compiled.
|
||||
|
||||
In general `expr; expr; expr; expr` is equivalent to `(((expr ; expr) ; expr) ; expr)`.
|
||||
In general `expr; expr; expr; expr` is equivalent to `(((expr ; expr) ; expr) ; expr)` or `then(expr, expr, expr, expr)`.
|
||||
|
||||
Result of the evaluated expression is the same as the result of the second expression, but first expression
|
||||
is also evaluated for side-effects
|
||||
@@ -132,15 +132,16 @@ foo(... x) -> ... # all arguments for foo are included in the list
|
||||
|
||||
</pre>
|
||||
|
||||
### `import(module_name, symbols ...)`
|
||||
### `import(module_name, ? symbols ...)`
|
||||
|
||||
Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use
|
||||
them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies,
|
||||
but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them.
|
||||
|
||||
Returns full list of available symbols that could be imported from this module, which can be used to debug import
|
||||
issues, and list contents of libraries.
|
||||
|
||||
### `call(function, args.....)`
|
||||
### `call(function, ? args ...)`
|
||||
|
||||
calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly
|
||||
except you can use it with function value, or name instead. This means you can pass functions to other user defined
|
||||
|
||||
@@ -6,6 +6,12 @@ as `2+(2*2)`, not `(2+2)*2`, otherwise they are applied from left to right, i.e.
|
||||
as `(2+4)-3`, which in case of numbers doesn't matter, but since `scarpet` allows for mixing all value types
|
||||
the associativity would matter, and may lead to unintended effects:
|
||||
|
||||
Operators can be unary - with one argument prefixed by the operator (like `-`, `!`, `...`), "practically binary" (that
|
||||
clearly have left and right operands, like assignment `=` operator), and "technically binary" (all binary operators have left and
|
||||
right hand, but can be frequently chained together, like `1+2+3`). All "technically binary" operators (where chaining makes sense)
|
||||
have their functional counterparts, e.g. `1+2+3` is equivalent to `sum(1, 2, 3)`. Functional and operatoral forms are directly
|
||||
equivalent - they actually will result in the same code as scarpet will optimize long operator chains into their optimized functional forms.
|
||||
|
||||
Important operator is function definition `->` operator. It will be covered
|
||||
in [User Defined Functions and Program Control Flow](docs/scarpet/language/FunctionsAndControlFlow.md)
|
||||
|
||||
@@ -118,7 +124,7 @@ $);
|
||||
/script run global_get_enchantment(player(), 'sharpness')
|
||||
</pre>
|
||||
|
||||
### `Basic Arithmetic Operators + - * /`
|
||||
### Basic Arithmetic Operators `+`, `sum(...)`, `-`, `difference(...)`, `*`, `product(...)`, `/`, `quotient(...)`
|
||||
|
||||
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
|
||||
@@ -134,6 +140,9 @@ Addition with maps (`{}` or `m()`) results in a new map with keys from both maps
|
||||
adding elements of the right argument to the keys, of left map, or just adding the right value as a new key
|
||||
in the output map.
|
||||
|
||||
Functional forms of `-` and `/` have less intuitive multi-nary interpretation, but they might be useful in some situations.
|
||||
`x-y-z` resolves to `difference(x, y, z)`.
|
||||
|
||||
Examples:
|
||||
|
||||
<pre>
|
||||
@@ -149,7 +158,7 @@ b = [100,63,100]; b+[10,0,10] => [110,63,110]
|
||||
{'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}
|
||||
</pre>
|
||||
|
||||
### `Just Operators % ^`
|
||||
### Just Operators `%`, `^`
|
||||
|
||||
The modulo and exponent (power) operators work only if both operands are numbers
|
||||
|
||||
@@ -160,11 +169,11 @@ The modulo and exponent (power) operators work only if both operands are numbers
|
||||
-3 ^ pi => // Error
|
||||
</pre>
|
||||
|
||||
### `Comparison Operators == != < > <= >=`
|
||||
### Comparison Operators `==`, `equal()`, `!=`, `unique()`, `<`, `increasing()`, `>`, `decreasing()`, `<=`, `nondecreasing()`, `>=`, `nonincreasing()`
|
||||
|
||||
Allows to compare the results of two expressions. For numbers it is considers arithmetic order of numbers, for
|
||||
Allows to compare the results of two expressions. For numbers, it 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.
|
||||
if the sizes are different, the size matters, otherwise, pairwise comparisons for each element are performed.
|
||||
The same order rules than with all these operators are used with the default sortographical order as used by `sort`
|
||||
function. All of these are true:
|
||||
|
||||
@@ -180,7 +189,12 @@ null < -1000
|
||||
3 == 3.0
|
||||
</pre>
|
||||
|
||||
### `Logical Operators && ||`
|
||||
Functional variants of these operators allow to assert certain paradigms on multiple arguments at once. This means that
|
||||
due to formal equivalence `x < y < z` is equivalent to `x < y & y < z` because of direct mapping to `increasing(x, y, z)`. This translates through
|
||||
the parentheses, so `((x < y) < z)` is the same as `increasing(x, y, z)`. To achieve the same effect as you would see in other
|
||||
languages (not python), you would need to cast the first pair to boolean value, i.e. `bool(x < y) < z`.
|
||||
|
||||
### Logical Operators `&&`, `and(...)`, `||`, `or(...)`
|
||||
|
||||
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
|
||||
|
||||
@@ -3,6 +3,7 @@ acos
|
||||
acosh
|
||||
acot
|
||||
all
|
||||
and
|
||||
asin
|
||||
asinh
|
||||
atan
|
||||
@@ -21,8 +22,11 @@ cot
|
||||
coth
|
||||
csc
|
||||
csch
|
||||
decreasing
|
||||
deg
|
||||
delete
|
||||
difference
|
||||
equal
|
||||
exit
|
||||
fact
|
||||
filter
|
||||
@@ -34,6 +38,7 @@ has
|
||||
hash_code
|
||||
if
|
||||
import
|
||||
increasing
|
||||
join
|
||||
keys
|
||||
l
|
||||
@@ -48,14 +53,19 @@ mandelbrot
|
||||
map
|
||||
max
|
||||
min
|
||||
nondecreasing
|
||||
nonincreasing
|
||||
not
|
||||
number
|
||||
or
|
||||
outer
|
||||
pairs
|
||||
perlin
|
||||
print
|
||||
product
|
||||
profile_expr
|
||||
put
|
||||
quotient
|
||||
rad
|
||||
rand
|
||||
range
|
||||
@@ -75,6 +85,7 @@ sleep
|
||||
slice
|
||||
sort
|
||||
sort_key
|
||||
sum
|
||||
synchronize
|
||||
system_variable_get
|
||||
system_variable_set
|
||||
@@ -90,12 +101,14 @@ task_dock
|
||||
task_join
|
||||
task_thread
|
||||
task_value
|
||||
then
|
||||
throw
|
||||
time
|
||||
title
|
||||
try
|
||||
type
|
||||
undef
|
||||
unique
|
||||
upper
|
||||
values
|
||||
var
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
log log10 log1p
|
||||
sqrt
|
||||
max min abs round floor ceil
|
||||
then sum difference product quotient equal unique increasing decreasing nonincreasing nondecreasing or and
|
||||
mandelbrot relu
|
||||
perlin simplex
|
||||
|
||||
|
||||
@@ -443,6 +443,19 @@ public class CarpetSettings
|
||||
)
|
||||
public static boolean scriptsAutoload = true;
|
||||
|
||||
@Rule(
|
||||
desc = "Enables scripts debugging messages in system log",
|
||||
category = SCARPET
|
||||
)
|
||||
public static boolean scriptsDebugging = false;
|
||||
|
||||
@Rule(
|
||||
desc = "Enables scripts optimization",
|
||||
category = SCARPET
|
||||
)
|
||||
public static boolean scriptsOptimization = true;
|
||||
|
||||
|
||||
@Rule(desc = "Enables /player command to control/spawn players", category = COMMAND)
|
||||
public static String commandPlayer = "ops";
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@ public class ScriptCommand
|
||||
// not that useful in commandline, more so in external scripts, so skipping here
|
||||
if (eventPrefix != null) scarpetMatches.addAll(CarpetEventServer.Event.publicEvents(null).stream().
|
||||
filter(e -> e.name.startsWith(eventPrefix)).map(s -> "__on_"+s.name+"(").collect(Collectors.toList()));
|
||||
scarpetMatches.addAll(host.globaFunctionNames(host.main, s -> s.startsWith(prefix)).map(s -> s+"(").collect(Collectors.toList()));
|
||||
scarpetMatches.addAll(host.globaVariableNames(host.main, s -> s.startsWith(prefix)).collect(Collectors.toList()));
|
||||
scarpetMatches.addAll(host.globalFunctionNames(host.main, s -> s.startsWith(prefix)).map(s -> s+"(").collect(Collectors.toList()));
|
||||
scarpetMatches.addAll(host.globalVariableNames(host.main, s -> s.startsWith(prefix)).collect(Collectors.toList()));
|
||||
return scarpetMatches;
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ public class ScriptCommand
|
||||
private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c) throws CommandSyntaxException
|
||||
{
|
||||
CarpetScriptHost host = getHost(c);
|
||||
return host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList());
|
||||
return host.globalFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList());
|
||||
}
|
||||
private static int listEvents(ServerCommandSource source)
|
||||
{
|
||||
@@ -358,7 +358,7 @@ public class ScriptCommand
|
||||
ServerCommandSource source = context.getSource();
|
||||
|
||||
Messenger.m(source, "lb Stored functions"+((host == CarpetServer.scriptServer.globalHost)?":":" in "+host.getName()+":"));
|
||||
host.globaFunctionNames(host.main, (str) -> all || !str.startsWith("__")).sorted().forEach( (s) -> {
|
||||
host.globalFunctionNames(host.main, (str) -> all || !str.startsWith("__")).sorted().forEach( (s) -> {
|
||||
FunctionValue fun = host.getFunction(s);
|
||||
if (fun == null)
|
||||
{
|
||||
@@ -379,7 +379,7 @@ public class ScriptCommand
|
||||
//Messenger.m(source, "w "+code);
|
||||
Messenger.m(source, "w ");
|
||||
Messenger.m(source, "lb Global variables"+((host == CarpetServer.scriptServer.globalHost)?":":" in "+host.getName()+":"));
|
||||
host.globaVariableNames(host.main, (s) -> s.startsWith("global_")).sorted().forEach( (s) -> {
|
||||
host.globalVariableNames(host.main, (s) -> s.startsWith("global_")).sorted().forEach( (s) -> {
|
||||
LazyValue variable = host.getGlobalVariable(s);
|
||||
if (variable == null)
|
||||
{
|
||||
|
||||
@@ -498,7 +498,7 @@ public class CarpetScriptHost extends ScriptHost
|
||||
|
||||
boolean hasTypeSupport = appConfig.getOrDefault(StringValue.of("legacy_command_type_support"), Value.FALSE).getBoolean();
|
||||
|
||||
for (String function : globaFunctionNames(main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList()))
|
||||
for (String function : globalFunctionNames(main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList()))
|
||||
{
|
||||
if (hasTypeSupport)
|
||||
{
|
||||
@@ -1004,15 +1004,7 @@ public class CarpetScriptHost extends ScriptHost
|
||||
return null;
|
||||
}
|
||||
|
||||
String shebang = message;
|
||||
if (expr.module != null)
|
||||
{
|
||||
shebang += " in " + expr.module.getName() + "";
|
||||
}
|
||||
else
|
||||
{
|
||||
shebang += " in system chat";
|
||||
}
|
||||
String shebang = message+" in "+expr.getModuleName();
|
||||
if (token != null)
|
||||
{
|
||||
String[] lines = expr.getCodeString().split("\n");
|
||||
|
||||
@@ -5,6 +5,7 @@ import carpet.script.bundled.BundledModule;
|
||||
import carpet.CarpetServer;
|
||||
import carpet.script.bundled.FileModule;
|
||||
import carpet.script.bundled.Module;
|
||||
import carpet.script.exception.IntegrityException;
|
||||
import carpet.script.exception.InvalidCallbackException;
|
||||
import carpet.script.value.FunctionValue;
|
||||
import carpet.script.value.Value;
|
||||
@@ -403,7 +404,7 @@ public class CarpetScriptServer
|
||||
{
|
||||
host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv);
|
||||
}
|
||||
catch (NullPointerException | InvalidCallbackException npe)
|
||||
catch (NullPointerException | InvalidCallbackException | IntegrityException npe)
|
||||
{
|
||||
if (reportFails) return -1;
|
||||
continue;
|
||||
|
||||
@@ -8,17 +8,21 @@ import java.util.Set;
|
||||
|
||||
public class Context
|
||||
{
|
||||
public static final int NONE = 0;
|
||||
public static final int VOID = 1;
|
||||
public static final int BOOLEAN = 2;
|
||||
public static final int NUMBER = 3;
|
||||
public static final int STRING = 4;
|
||||
public static final int LIST = 5;
|
||||
public static final int ITERATOR = 6;
|
||||
public static final int SIGNATURE = 7;
|
||||
public static final int LOCALIZATION = 8;
|
||||
public static final int LVALUE = 9;
|
||||
public static final int MAPDEF = 10;
|
||||
public enum Type
|
||||
{
|
||||
NONE, VOID, BOOLEAN, NUMBER, STRING, LIST, ITERATOR, SIGNATURE, LOCALIZATION, LVALUE, MAPDEF
|
||||
}
|
||||
public static final Type NONE = Type.NONE;
|
||||
public static final Type VOID = Type.VOID;
|
||||
public static final Type BOOLEAN = Type.BOOLEAN;
|
||||
public static final Type NUMBER = Type.NUMBER;
|
||||
public static final Type STRING = Type.STRING;
|
||||
public static final Type LIST = Type.LIST;
|
||||
public static final Type ITERATOR = Type.ITERATOR;
|
||||
public static final Type SIGNATURE = Type.SIGNATURE;
|
||||
public static final Type LOCALIZATION = Type.LOCALIZATION;
|
||||
public static final Type LVALUE = Type.LVALUE;
|
||||
public static final Type MAPDEF = Type.MAPDEF;
|
||||
|
||||
public Map<String, LazyValue> variables = new HashMap<>();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package carpet.script;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.script.Fluff.AbstractFunction;
|
||||
import carpet.script.Fluff.AbstractLazyFunction;
|
||||
import carpet.script.Fluff.AbstractLazyOperator;
|
||||
@@ -16,6 +17,7 @@ import carpet.script.exception.BreakStatement;
|
||||
import carpet.script.exception.ContinueStatement;
|
||||
import carpet.script.exception.ExitStatement;
|
||||
import carpet.script.exception.ExpressionException;
|
||||
import carpet.script.exception.IntegrityException;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.exception.ResolvedException;
|
||||
import carpet.script.exception.ReturnStatement;
|
||||
@@ -31,6 +33,8 @@ import carpet.script.value.FunctionValue;
|
||||
import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
|
||||
import java.math.BigInteger;
|
||||
@@ -44,6 +48,7 @@ import java.util.Stack;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
@@ -61,7 +66,7 @@ public class Expression
|
||||
|
||||
public String getModuleName()
|
||||
{
|
||||
return module == null?null:module.getName();
|
||||
return module == null?"system chat":module.getName();
|
||||
}
|
||||
|
||||
public void asATextSource()
|
||||
@@ -85,6 +90,27 @@ public class Expression
|
||||
private final Map<String, ILazyFunction> functions = new Object2ObjectOpenHashMap<>();
|
||||
public Set<String> getFunctionNames() {return functions.keySet();}
|
||||
|
||||
private final Map<String, String> functionalEquivalence = new Object2ObjectOpenHashMap<>();
|
||||
public void addFunctionalEquivalence(String operator, String function)
|
||||
{
|
||||
assert operators.containsKey(operator);
|
||||
assert functions.containsKey(function);
|
||||
functionalEquivalence.put(operator, function);
|
||||
}
|
||||
|
||||
private final Map<String, Value> constants = ImmutableMap.of(
|
||||
"euler", Arithmetic.euler,
|
||||
"pi", Arithmetic.PI,
|
||||
"null", Value.NULL,
|
||||
"true", Value.TRUE,
|
||||
"false", Value.FALSE
|
||||
);
|
||||
|
||||
protected Value getConstantFor(String surface)
|
||||
{
|
||||
return constants.get(surface);
|
||||
}
|
||||
|
||||
public List<String> getExpressionSnippet(Tokenizer.Token token)
|
||||
{
|
||||
String code = this.getCodeString();
|
||||
@@ -138,13 +164,28 @@ public class Expression
|
||||
}
|
||||
|
||||
|
||||
public void addLazyUnaryOperator(String surface, int precedence, boolean leftAssoc,
|
||||
TriFunction<Context, Integer, LazyValue, LazyValue> lazyfun)
|
||||
public void addLazyUnaryOperator(String surface, int precedence, boolean leftAssoc, boolean pure, Function<Context.Type,Context.Type> staticTyper,
|
||||
TriFunction<Context, Context.Type, 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)
|
||||
public boolean pure() {
|
||||
return pure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context.Type staticType(Context.Type outerType) {
|
||||
return staticTyper.apply(outerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type t, Expression e, Tokenizer.Token token, LazyValue v, LazyValue v2)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -159,13 +200,23 @@ public class Expression
|
||||
}
|
||||
|
||||
|
||||
public void addLazyBinaryOperatorWithDelegation(String surface, int precedence, boolean leftAssoc,
|
||||
SexFunction<Context, Integer, Expression, Tokenizer.Token, LazyValue, LazyValue, LazyValue> lazyfun)
|
||||
public void addLazyBinaryOperatorWithDelegation(String surface, int precedence, boolean leftAssoc, boolean pure,
|
||||
SexFunction<Context, Context.Type, 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)
|
||||
public boolean pure() {
|
||||
return pure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -179,13 +230,28 @@ public class Expression
|
||||
});
|
||||
}
|
||||
|
||||
public void addLazyFunctionWithDelegation(String name, int numpar,
|
||||
QuinnFunction<Context, Integer, Expression, Tokenizer.Token, List<LazyValue>, LazyValue> lazyfun)
|
||||
public void addCustomFunction(String name, ILazyFunction fun)
|
||||
{
|
||||
functions.put(name, fun);
|
||||
}
|
||||
|
||||
public void addLazyFunctionWithDelegation(String name, int numpar, boolean pure, boolean transitive,
|
||||
QuinnFunction<Context, Context.Type, Expression, Tokenizer.Token, List<LazyValue>, LazyValue> lazyfun)
|
||||
{
|
||||
functions.put(name, new AbstractLazyFunction(numpar, name)
|
||||
{
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List<LazyValue> lv)
|
||||
public boolean pure() {
|
||||
return pure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return transitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, List<LazyValue> lv)
|
||||
{
|
||||
ILazyFunction.checkInterrupts();
|
||||
try
|
||||
@@ -200,13 +266,23 @@ public class Expression
|
||||
});
|
||||
}
|
||||
|
||||
public void addFunctionWithDelegation(String name, int numpar,
|
||||
QuinnFunction<Context, Integer, Expression, Tokenizer.Token, List<Value>, Value> fun)
|
||||
public void addFunctionWithDelegation(String name, int numpar, boolean pure, boolean transitive,
|
||||
QuinnFunction<Context, Context.Type, Expression, Tokenizer.Token, List<Value>, Value> fun)
|
||||
{
|
||||
functions.put(name, new AbstractLazyFunction(numpar, name)
|
||||
{
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List<LazyValue> lv)
|
||||
public boolean pure() {
|
||||
return pure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return transitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, List<LazyValue> lv)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -221,13 +297,30 @@ public class Expression
|
||||
});
|
||||
}
|
||||
|
||||
public void addLazyBinaryOperator(String surface, int precedence, boolean leftAssoc,
|
||||
QuadFunction<Context, Integer, LazyValue, LazyValue, LazyValue> lazyfun)
|
||||
public void addLazyBinaryOperator(String surface, int precedence, boolean leftAssoc, boolean pure, Function<Context.Type, Context.Type> typer,
|
||||
QuadFunction<Context, Context.Type, 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)
|
||||
public boolean pure() {
|
||||
return pure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context.Type staticType(Context.Type outerType)
|
||||
{
|
||||
return typer.apply(outerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2)
|
||||
{
|
||||
ILazyFunction.checkInterrupts();
|
||||
try
|
||||
@@ -242,13 +335,23 @@ public class Expression
|
||||
});
|
||||
}
|
||||
|
||||
public void addBinaryContextOperator(String surface, int precedence, boolean leftAssoc,
|
||||
QuadFunction<Context, Integer, Value, Value, Value> fun)
|
||||
public void addBinaryContextOperator(String surface, int precedence, boolean leftAssoc, boolean pure, boolean transitive,
|
||||
QuadFunction<Context, Context.Type, Value, Value, Value> fun)
|
||||
{
|
||||
operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc)
|
||||
{
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Integer t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2)
|
||||
public boolean pure() {
|
||||
return pure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return transitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type t, Expression e, Tokenizer.Token token, LazyValue v1, LazyValue v2)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -267,6 +370,8 @@ public class Expression
|
||||
{
|
||||
if (exc instanceof ExitStatement)
|
||||
return exc;
|
||||
if (exc instanceof IntegrityException)
|
||||
return exc;
|
||||
if (exc instanceof InternalExpressionException)
|
||||
return ((InternalExpressionException) exc).promote(c, e, token);
|
||||
if (exc instanceof ArithmeticException)
|
||||
@@ -309,6 +414,20 @@ public class Expression
|
||||
});
|
||||
}
|
||||
|
||||
public void addImpureUnaryFunction(String name, Function<Value, Value> fun)
|
||||
{
|
||||
functions.put(name, new AbstractFunction(1, name)
|
||||
{
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value eval(List<Value> parameters) { return fun.apply(parameters.get(0)); }
|
||||
});
|
||||
}
|
||||
|
||||
public void addBinaryFunction(String name, BiFunction<Value, Value, Value> fun)
|
||||
{
|
||||
functions.put(name, new AbstractFunction(2, name)
|
||||
@@ -327,6 +446,20 @@ public class Expression
|
||||
});
|
||||
}
|
||||
|
||||
public void addImpureFunction(String name, Function<List<Value>, Value> fun)
|
||||
{
|
||||
functions.put(name, new AbstractFunction(-1, name)
|
||||
{
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value eval(List<Value> parameters) { return fun.apply(parameters); }
|
||||
});
|
||||
}
|
||||
|
||||
public void addMathematicalUnaryFunction(String name, Function<Double, Double> fun)
|
||||
{
|
||||
addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble())));
|
||||
@@ -344,12 +477,22 @@ public class Expression
|
||||
}
|
||||
|
||||
|
||||
public void addLazyFunction(String name, int num_params, TriFunction<Context, Integer, List<LazyValue>, LazyValue> fun)
|
||||
public void addLazyFunction(String name, int num_params, TriFunction<Context, Context.Type, List<LazyValue>, LazyValue> fun)
|
||||
{
|
||||
functions.put(name, new AbstractLazyFunction(num_params, name)
|
||||
{
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Integer i, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
|
||||
public boolean pure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
|
||||
{
|
||||
ILazyFunction.checkInterrupts();
|
||||
try
|
||||
@@ -364,12 +507,58 @@ public class Expression
|
||||
});
|
||||
}
|
||||
|
||||
public void addContextFunction(String name, int num_params, TriFunction<Context, Integer, List<Value>, Value> fun)
|
||||
public void addPureLazyFunction(String name, int num_params, Function<Context.Type, Context.Type> typer, TriFunction<Context, Context.Type, List<LazyValue>, LazyValue> fun)
|
||||
{
|
||||
functions.put(name, new AbstractLazyFunction(num_params, name)
|
||||
{
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Integer i, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
|
||||
public boolean pure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context.Type staticType(Context.Type outerType)
|
||||
{
|
||||
return typer.apply(outerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
|
||||
{
|
||||
ILazyFunction.checkInterrupts();
|
||||
try
|
||||
{
|
||||
return fun.apply(c, i, lazyParams);
|
||||
}
|
||||
catch (RuntimeException exc)
|
||||
{
|
||||
throw handleCodeException(c, exc, e, t);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addContextFunction(String name, int num_params, TriFunction<Context, Context.Type, List<Value>, Value> fun)
|
||||
{
|
||||
functions.put(name, new AbstractLazyFunction(num_params, name)
|
||||
{
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
|
||||
{
|
||||
ILazyFunction.checkInterrupts();
|
||||
try
|
||||
@@ -385,12 +574,27 @@ public class Expression
|
||||
});
|
||||
}
|
||||
|
||||
public void addTypedContextFunction(String name, int num_params, int reqType, TriFunction<Context, Integer, List<Value>, Value> fun)
|
||||
public void addTypedContextFunction(String name, int num_params, Context.Type reqType, TriFunction<Context, Context.Type, List<Value>, Value> fun)
|
||||
{
|
||||
functions.put(name, new AbstractLazyFunction(num_params, name)
|
||||
{
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Integer i, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
|
||||
public boolean pure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context.Type staticType(Context.Type outerType) {
|
||||
return reqType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -448,7 +652,22 @@ public class Expression
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Integer type, Expression expr, Tokenizer.Token token, List<LazyValue> lazyParams)
|
||||
public boolean pure() {
|
||||
return originalFunction.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return originalFunction.transitive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context.Type staticType(Context.Type outerType) {
|
||||
return originalFunction.staticType(outerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type type, Expression expr, Tokenizer.Token token, List<LazyValue> lazyParams)
|
||||
{
|
||||
c.host.issueDeprecation(copy+"(...)");
|
||||
return originalFunction.lazyEval(c, type, expr, token, lazyParams);
|
||||
@@ -471,7 +690,7 @@ public class Expression
|
||||
|
||||
public LazyValue getOrSetAnyVariable(Context c, String name)
|
||||
{
|
||||
LazyValue var = null;
|
||||
LazyValue var;
|
||||
if (!name.startsWith("global_"))
|
||||
{
|
||||
var = c.getVariable(name);
|
||||
@@ -596,16 +815,16 @@ public class Expression
|
||||
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)
|
||||
{
|
||||
//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);
|
||||
}
|
||||
// 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)
|
||||
@@ -672,19 +891,15 @@ public class Expression
|
||||
}
|
||||
|
||||
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);
|
||||
return evalValue(() -> ast, c, Context.Type.NONE);
|
||||
}
|
||||
|
||||
public Value evalValue(Supplier<LazyValue> exprProvider, Context c, Integer expectedType)
|
||||
public Value evalValue(Supplier<LazyValue> exprProvider, Context c, Context.Type expectedType)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -712,35 +927,67 @@ public class Expression
|
||||
}
|
||||
}
|
||||
|
||||
private LazyValue getAST(Context context)
|
||||
public static class ExpressionNode
|
||||
{
|
||||
Stack<LazyValue> stack = new Stack<>();
|
||||
List<Tokenizer.Token> rpn = shuntingYard(context);
|
||||
validate(context, rpn);
|
||||
for (final Tokenizer.Token token : rpn)
|
||||
public LazyValue op;
|
||||
public List<ExpressionNode> args;
|
||||
public Tokenizer.Token token;
|
||||
public List<Tokenizer.Token> range;
|
||||
/**
|
||||
* The Value representation of the left parenthesis, used for parsing
|
||||
* varying numbers of function parameters.
|
||||
*/
|
||||
public static final ExpressionNode PARAMS_START = new ExpressionNode(null, null, Tokenizer.Token.NONE);
|
||||
public ExpressionNode(LazyValue op, List<ExpressionNode> args, Tokenizer.Token token)
|
||||
{
|
||||
this.op = op;
|
||||
this.args = args;
|
||||
this.token = token;
|
||||
range = new ArrayList<>();
|
||||
range.add(token);
|
||||
}
|
||||
public static ExpressionNode ofConstant(Value val, Tokenizer.Token token)
|
||||
{
|
||||
return new ExpressionNode(new LazyValue.Constant(val), Collections.emptyList(), token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ExpressionNode RPNToParseTree(List<Tokenizer.Token> tokens, Context context)
|
||||
{
|
||||
Stack<ExpressionNode> nodeStack = new Stack<>();
|
||||
for (final Tokenizer.Token token : tokens)
|
||||
{
|
||||
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, t);
|
||||
stack.push(result);
|
||||
final ExpressionNode node = nodeStack.pop();
|
||||
LazyValue result = (c, t) -> operators.get(token.surface).lazyEval(c, t, this, token, node.op, null).evalValue(c, t);
|
||||
nodeStack.push(new ExpressionNode(result, Collections.singletonList(node), token));
|
||||
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, t);
|
||||
stack.push(result);
|
||||
final ExpressionNode v1 = nodeStack.pop();
|
||||
final ExpressionNode v2 = nodeStack.pop();
|
||||
LazyValue result = (c,t) -> operators.get(token.surface).lazyEval(c, t,this, token, v2.op, v1.op).evalValue(c, t);
|
||||
nodeStack.push(new ExpressionNode(result, ImmutableList.of(v2, v1), token ));
|
||||
break;
|
||||
case VARIABLE:
|
||||
stack.push((c, t) -> getOrSetAnyVariable(c, token.surface).evalValue(c, t));
|
||||
Value constant = getConstantFor(token.surface);
|
||||
if (constant != null)
|
||||
{
|
||||
token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface);
|
||||
nodeStack.push(new ExpressionNode(LazyValue.ofConstant(constant), Collections.emptyList(), token));
|
||||
}
|
||||
else {
|
||||
nodeStack.push(new ExpressionNode(((c, t) -> getOrSetAnyVariable(c, token.surface).evalValue(c, t)), Collections.emptyList(), token));
|
||||
}
|
||||
break;
|
||||
case FUNCTION:
|
||||
String name = token.surface;
|
||||
ILazyFunction f;
|
||||
ArrayList<LazyValue> p;
|
||||
ArrayList<ExpressionNode> p;
|
||||
boolean isKnown = functions.containsKey(name); // globals will be evaluated lazily, not at compile time via .
|
||||
if (isKnown)
|
||||
{
|
||||
@@ -754,47 +1001,301 @@ public class Expression
|
||||
}
|
||||
// pop parameters off the stack until we hit the start of
|
||||
// this function's parameter list
|
||||
while (!stack.isEmpty() && stack.peek() != LazyValue.PARAMS_START)
|
||||
while (!nodeStack.isEmpty() && nodeStack.peek() != ExpressionNode.PARAMS_START)
|
||||
{
|
||||
p.add(stack.pop());
|
||||
p.add(nodeStack.pop());
|
||||
}
|
||||
if (!isKnown)
|
||||
{
|
||||
p.add(ExpressionNode.ofConstant(new StringValue(name), token.morphedInto(Tokenizer.Token.TokenType.STRINGPARAM, token.surface)));
|
||||
token.morph(Tokenizer.Token.TokenType.FUNCTION, "call");
|
||||
}
|
||||
if (!isKnown) p.add( (c, t) -> new StringValue(name));
|
||||
Collections.reverse(p);
|
||||
|
||||
if (stack.peek() == LazyValue.PARAMS_START)
|
||||
if (nodeStack.peek() == ExpressionNode.PARAMS_START)
|
||||
{
|
||||
stack.pop();
|
||||
}
|
||||
nodeStack.pop();
|
||||
};
|
||||
List<LazyValue> params = p.stream().map(n -> n.op).collect(Collectors.toList());
|
||||
|
||||
stack.push((c, t) -> f.lazyEval(c, t, this, token, p).evalValue(c, t));
|
||||
nodeStack.push(new ExpressionNode(
|
||||
(c, t) -> f.lazyEval(c, t, this, token, params).evalValue(c, t),
|
||||
p,token
|
||||
));
|
||||
break;
|
||||
case OPEN_PAREN:
|
||||
stack.push(LazyValue.PARAMS_START);
|
||||
nodeStack.push(ExpressionNode.PARAMS_START);
|
||||
break;
|
||||
case LITERAL:
|
||||
stack.push((c, t) ->
|
||||
Value number;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
return new NumericValue(token.surface);
|
||||
}
|
||||
catch (NumberFormatException exception)
|
||||
{
|
||||
throw new ExpressionException(c, this, token, "Not a number");
|
||||
}
|
||||
});
|
||||
number = new NumericValue(token.surface);
|
||||
}
|
||||
catch (NumberFormatException exception)
|
||||
{
|
||||
throw new ExpressionException(context, this, token, "Not a number");
|
||||
}
|
||||
token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface);
|
||||
nodeStack.push(ExpressionNode.ofConstant(number, token));
|
||||
break;
|
||||
case STRINGPARAM:
|
||||
stack.push((c, t) -> new StringValue(token.surface) ); // was originally null
|
||||
token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface);
|
||||
nodeStack.push(ExpressionNode.ofConstant(new StringValue(token.surface), token));
|
||||
break;
|
||||
case HEX_LITERAL:
|
||||
stack.push((c, t) -> new NumericValue(new BigInteger(token.surface.substring(2), 16).doubleValue()));
|
||||
Value hexNumber;
|
||||
try
|
||||
{
|
||||
hexNumber = new NumericValue(new BigInteger(token.surface.substring(2), 16).longValue());
|
||||
}
|
||||
catch (NumberFormatException exception)
|
||||
{
|
||||
throw new ExpressionException(context, this, token, "Not a number");
|
||||
}
|
||||
token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface);
|
||||
nodeStack.push(ExpressionNode.ofConstant(hexNumber, token));
|
||||
break;
|
||||
default:
|
||||
throw new ExpressionException(context, this, token, "Unexpected token '" + token.surface + "'");
|
||||
}
|
||||
}
|
||||
return stack.pop();
|
||||
return nodeStack.pop();
|
||||
}
|
||||
|
||||
private LazyValue getAST(Context context)
|
||||
{
|
||||
//Stack<LazyValue> stack = new Stack<>();
|
||||
List<Tokenizer.Token> rpn = shuntingYard(context);
|
||||
validate(context, rpn);
|
||||
ExpressionNode root = RPNToParseTree(rpn, context);
|
||||
if (!CarpetSettings.scriptsOptimization)
|
||||
return root.op;
|
||||
|
||||
CarpetScriptServer.LOG.info("Input code size for "+getModuleName()+": " + treeSize(root) + " nodes, " + treeDepth(root) + " deep");
|
||||
|
||||
boolean changed = true;
|
||||
while(changed) {
|
||||
changed = false;
|
||||
while (true) {
|
||||
int tree_size = treeSize(root);
|
||||
int tree_depth = treeDepth(root);
|
||||
boolean optimized = compactTree(root, Context.Type.NONE, 0);
|
||||
if (!optimized) break;
|
||||
changed = true;
|
||||
CarpetScriptServer.LOG.info("Compacted from " + tree_size + " nodes, " + tree_depth + " code depth to " + treeSize(root) + " nodes, " + treeDepth(root) + " code depth");
|
||||
}
|
||||
while (true) {
|
||||
int tree_size = treeSize(root);
|
||||
int tree_depth = treeDepth(root);
|
||||
boolean optimized = optimizeTree(context, root, Context.Type.NONE, 0);
|
||||
if (!optimized) break;
|
||||
changed = true;
|
||||
CarpetScriptServer.LOG.info("Optimized from " + tree_size + " nodes, " + tree_depth + " code depth to " + treeSize(root) + " nodes, " + treeDepth(root) + " code depth");
|
||||
}
|
||||
}
|
||||
return extractOp(context, root, Context.Type.NONE);
|
||||
}
|
||||
|
||||
private int treeSize(ExpressionNode node)
|
||||
{
|
||||
if (node.op instanceof LazyValue.ContextFreeLazyValue) return 1;
|
||||
return node.args.stream().mapToInt(this::treeSize).sum()+1;
|
||||
}
|
||||
private int treeDepth(ExpressionNode node)
|
||||
{
|
||||
if (node.op instanceof LazyValue.ContextFreeLazyValue) return 1;
|
||||
return node.args.stream().mapToInt(this::treeDepth).max().orElse(0)+1;
|
||||
}
|
||||
|
||||
|
||||
private boolean compactTree(ExpressionNode node, Context.Type expectedType, int indent) {
|
||||
// ctx is just to report errors, not values evaluation
|
||||
boolean optimized = false;
|
||||
Tokenizer.Token.TokenType token = node.token.type;
|
||||
if (!token.isFunctional()) return false;
|
||||
// input special cases here, like function signature
|
||||
if (node.op instanceof LazyValue.Constant) return false; // optimized already
|
||||
// function or operator
|
||||
String symbol = node.token.surface;
|
||||
Fluff.EvalNode operation = ((token == Tokenizer.Token.TokenType.FUNCTION) ? functions : operators).get(symbol);
|
||||
Context.Type requestedType = operation.staticType(expectedType);
|
||||
for (ExpressionNode arg : node.args) {
|
||||
if (compactTree(arg, requestedType, indent+1)) optimized = true;
|
||||
}
|
||||
|
||||
if (expectedType != Context.Type.MAPDEF && symbol.equals("->") && node.args.size() == 2) {
|
||||
String rop = node.args.get(1).token.surface;
|
||||
ExpressionNode returnNode = null;
|
||||
if ((rop.equals(";") || rop.equals("then"))) {
|
||||
List<ExpressionNode> thenArgs = node.args.get(1).args;
|
||||
if (thenArgs.size() > 1 && thenArgs.get(thenArgs.size() - 1).token.surface.equals("return")) {
|
||||
returnNode = thenArgs.get(thenArgs.size() - 1);
|
||||
}
|
||||
} else if (rop.equals("return")) {
|
||||
returnNode = node.args.get(1);
|
||||
}
|
||||
if (returnNode != null) // tail return
|
||||
{
|
||||
if (returnNode.args.size() > 0) {
|
||||
returnNode.op = returnNode.args.get(0).op;
|
||||
returnNode.token = returnNode.args.get(0).token;
|
||||
returnNode.range = returnNode.args.get(0).range;
|
||||
returnNode.args = returnNode.args.get(0).args;
|
||||
if (CarpetSettings.scriptsDebugging)
|
||||
CarpetScriptServer.LOG.info(" - Removed unnecessary tail return of " + returnNode.token.surface + " from function body at line " + (returnNode.token.lineno + 1) + ", node depth " + indent);
|
||||
|
||||
} else {
|
||||
returnNode.op = LazyValue.ofConstant(Value.NULL);
|
||||
returnNode.token.morph(Tokenizer.Token.TokenType.CONSTANT, "");
|
||||
returnNode.args = Collections.emptyList();
|
||||
if (CarpetSettings.scriptsDebugging)
|
||||
CarpetScriptServer.LOG.info(" - Removed unnecessary tail return from function body at line " + (returnNode.token.lineno + 1) + ", node depth " + indent);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, String> pair : functionalEquivalence.entrySet()) {
|
||||
String operator = pair.getKey();
|
||||
String function = pair.getValue();
|
||||
if (symbol.equals(operator) || symbol.equals(function)) {
|
||||
List<ExpressionNode> newargs = new ArrayList<>();
|
||||
boolean contracted = false;
|
||||
for (ExpressionNode arg : node.args) {
|
||||
String argsymbol = arg.token.surface; // child is unoptimized and also same class
|
||||
if ((argsymbol.equals(operator) || argsymbol.equals(function)) && (!(arg.op instanceof LazyValue.ContextFreeLazyValue))) {
|
||||
newargs.addAll(arg.args);
|
||||
contracted = true;
|
||||
} else {
|
||||
newargs.add(arg);
|
||||
}
|
||||
}
|
||||
if (contracted) {
|
||||
optimized = true;
|
||||
if (CarpetSettings.scriptsDebugging)
|
||||
CarpetScriptServer.LOG.info(" - " + symbol + "(" + node.args.size() + ") => " + function + "(" + newargs.size() + ") at line " + (node.token.lineno + 1) + ", node depth " + indent);
|
||||
node.token.morph(Tokenizer.Token.TokenType.FUNCTION, function);
|
||||
node.args = newargs;
|
||||
}
|
||||
}
|
||||
}
|
||||
return optimized;
|
||||
}
|
||||
|
||||
private boolean optimizeTree(Context ctx, ExpressionNode node, Context.Type expectedType, int indent) {
|
||||
// ctx is just to report errors, not values evaluation
|
||||
boolean optimized = false;
|
||||
Tokenizer.Token.TokenType token = node.token.type;
|
||||
if (!token.isFunctional()) return false;
|
||||
String symbol = node.token.surface;
|
||||
|
||||
// input special cases here, like function signature
|
||||
if (node.op instanceof LazyValue.Constant) return false; // optimized already
|
||||
// function or operator
|
||||
|
||||
Fluff.EvalNode operation = ((token == Tokenizer.Token.TokenType.FUNCTION) ? functions : operators).get(symbol);
|
||||
Context.Type requestedType = operation.staticType(expectedType);
|
||||
for (ExpressionNode arg : node.args) {
|
||||
if (optimizeTree(ctx, arg, requestedType, indent+1)) optimized = true;
|
||||
}
|
||||
|
||||
for (ExpressionNode arg : node.args) {
|
||||
if (arg.token.type.isConstant()) continue;
|
||||
if (arg.op instanceof LazyValue.ContextFreeLazyValue) continue;
|
||||
return optimized;
|
||||
}
|
||||
if (!operation.pure())
|
||||
{
|
||||
if (symbol.equals("->") && expectedType == Context.Type.MAPDEF)
|
||||
{
|
||||
|
||||
}
|
||||
else {
|
||||
return optimized;
|
||||
}
|
||||
}
|
||||
List<LazyValue> args = new ArrayList<>(node.args.size());
|
||||
for (ExpressionNode arg : node.args) {
|
||||
try {
|
||||
if (arg.op instanceof LazyValue.Constant) {
|
||||
Value val = ((LazyValue.Constant) arg.op).get();
|
||||
args.add((c, t) -> val);
|
||||
}
|
||||
else args.add((c, t) -> arg.op.evalValue(null, requestedType));
|
||||
} catch (NullPointerException npe) {
|
||||
throw new ExpressionException(ctx, this, node.token, "Attempted to evaluate context free expression");
|
||||
}
|
||||
}
|
||||
// applying argument unpacking
|
||||
args= AbstractLazyFunction.lazify(AbstractLazyFunction.unpackLazy(args, null, requestedType));
|
||||
Value result;
|
||||
if (operation instanceof ILazyFunction)
|
||||
{
|
||||
result = ((ILazyFunction) operation).lazyEval(null, expectedType, this, node.token, args).evalValue(null, expectedType);
|
||||
}
|
||||
else if (args.size() == 1)
|
||||
{
|
||||
result = ((ILazyOperator)operation).lazyEval(null, expectedType, this, node.token, args.get(0), null).evalValue(null, expectedType);
|
||||
}
|
||||
else // args == 2
|
||||
{
|
||||
result = ((ILazyOperator)operation).lazyEval(null, expectedType, this, node.token, args.get(0), args.get(1)).evalValue(null, expectedType);
|
||||
}
|
||||
node.op = LazyValue.ofConstant(result);
|
||||
//node.token.morph(Tokenizer.Token.TokenType.CONSTANT, node.token.surface);
|
||||
if (CarpetSettings.scriptsDebugging)
|
||||
CarpetScriptServer.LOG.info(" - "+symbol+"("+args.stream().map(a -> a.evalValue(null, requestedType).getString()).collect(Collectors.joining(", "))+") => "+result.getString()+" at line "+(node.token.lineno+1) +", node depth "+indent);
|
||||
return true;
|
||||
}
|
||||
|
||||
private LazyValue extractOp(Context ctx, ExpressionNode node, Context.Type expectedType)
|
||||
{
|
||||
if (node.op instanceof LazyValue.Constant)
|
||||
{
|
||||
// constants are immutable
|
||||
if (node.token.type.isConstant())
|
||||
{
|
||||
Value value = ((LazyValue.Constant) node.op).get();
|
||||
return (c, t) -> value;
|
||||
}
|
||||
return node.op;
|
||||
}
|
||||
if (node.op instanceof LazyValue.ContextFreeLazyValue)
|
||||
{
|
||||
Value ret = ((LazyValue.ContextFreeLazyValue) node.op).evalType(expectedType);
|
||||
return (c, t) -> ret;
|
||||
}
|
||||
Tokenizer.Token token = node.token;
|
||||
switch (token.type)
|
||||
{
|
||||
case UNARY_OPERATOR: {
|
||||
ILazyOperator op = operators.get(token.surface);
|
||||
Context.Type requestedType = op.staticType(expectedType);
|
||||
LazyValue arg = extractOp(ctx, node.args.get(0), requestedType);
|
||||
return (c, t) -> op.lazyEval(c, t, this, token, arg, null).evalValue(c, t);
|
||||
}
|
||||
case OPERATOR: {
|
||||
ILazyOperator op = operators.get(token.surface);
|
||||
Context.Type requestedType = op.staticType(expectedType);
|
||||
LazyValue arg = extractOp(ctx, node.args.get(0), requestedType);
|
||||
LazyValue arh = extractOp(ctx, node.args.get(1), requestedType);
|
||||
return (c, t) -> op.lazyEval(c, t, this, token, arg, arh).evalValue(c, t);
|
||||
}
|
||||
case VARIABLE:
|
||||
return (c, t) -> getOrSetAnyVariable(c, token.surface).evalValue(c, t);
|
||||
case FUNCTION: {
|
||||
ILazyFunction f = functions.get(token.surface);
|
||||
Context.Type requestedType = f.staticType(expectedType);
|
||||
List<LazyValue> params = node.args.stream().map(n -> extractOp(ctx, n, requestedType)).collect(Collectors.toList());
|
||||
return (c, t) -> f.lazyEval(c, t, this, token, params).evalValue(c, t);
|
||||
}
|
||||
case CONSTANT:
|
||||
return node.op;
|
||||
default:
|
||||
throw new ExpressionException(ctx, this, node.token, "Unexpected token '" + node.token.type +" "+node.token.surface + "'");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(Context c, List<Tokenizer.Token> rpn)
|
||||
|
||||
@@ -30,13 +30,33 @@ public abstract class Fluff
|
||||
@FunctionalInterface
|
||||
public interface SexFunction<A, B, C, D, E, F, R> { R apply(A a, B b, C c, D d, E e, F f);}
|
||||
|
||||
public interface ILazyFunction
|
||||
public interface EvalNode
|
||||
{
|
||||
/**
|
||||
* @return true if function has constant output if arguments are constant and can be evaluated
|
||||
* statically (without context)
|
||||
*/
|
||||
boolean pure();
|
||||
|
||||
/**
|
||||
* @return true if function has constant output if arguments are constant and can be evaluated
|
||||
* statically (without context)
|
||||
*/
|
||||
boolean transitive();
|
||||
|
||||
/**
|
||||
* @return required argument eval type in case its evaluated statically without context but with a given context type
|
||||
*/
|
||||
default Context.Type staticType(Context.Type outerType) {return transitive()?outerType:Context.NONE;};
|
||||
}
|
||||
|
||||
public interface ILazyFunction extends EvalNode
|
||||
{
|
||||
int getNumParams();
|
||||
|
||||
boolean numParamsVaries();
|
||||
|
||||
LazyValue lazyEval(Context c, Integer type, Expression expr, Tokenizer.Token token, List<LazyValue> lazyParams);
|
||||
LazyValue lazyEval(Context c, Context.Type type, Expression expr, Tokenizer.Token token, List<LazyValue> lazyParams);
|
||||
|
||||
static void checkInterrupts()
|
||||
{
|
||||
@@ -51,13 +71,13 @@ public abstract class Fluff
|
||||
Value eval(List<Value> parameters);
|
||||
}
|
||||
|
||||
public interface ILazyOperator
|
||||
public interface ILazyOperator extends EvalNode
|
||||
{
|
||||
int getPrecedence();
|
||||
|
||||
boolean isLeftAssoc();
|
||||
|
||||
LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2);
|
||||
LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, LazyValue v1, LazyValue v2);
|
||||
}
|
||||
|
||||
public interface IOperator extends ILazyOperator
|
||||
@@ -70,7 +90,7 @@ public abstract class Fluff
|
||||
protected String name;
|
||||
int numParams;
|
||||
|
||||
AbstractLazyFunction(int numParams, String name)
|
||||
public AbstractLazyFunction(int numParams, String name)
|
||||
{
|
||||
this.numParams = numParams;
|
||||
this.name = name;
|
||||
@@ -89,7 +109,7 @@ public abstract class Fluff
|
||||
return numParams < 0;
|
||||
}
|
||||
|
||||
public static List<Value> unpackLazy(List<LazyValue> lzargs, Context c, int contextType)
|
||||
public static List<Value> unpackLazy(List<LazyValue> lzargs, Context c, Context.Type contextType)
|
||||
{
|
||||
List<Value> args = new ArrayList<>();
|
||||
for (LazyValue lv : lzargs)
|
||||
@@ -103,7 +123,7 @@ public abstract class Fluff
|
||||
return args;
|
||||
}
|
||||
|
||||
public List<Value> unpackArgs(List<LazyValue> lzargs, Context c, int contextType)
|
||||
public List<Value> unpackArgs(List<LazyValue> lzargs, Context c, Context.Type contextType)
|
||||
{
|
||||
List<Value> args = new ArrayList<>();
|
||||
for (LazyValue lv : lzargs)
|
||||
@@ -118,6 +138,13 @@ public abstract class Fluff
|
||||
throw new InternalExpressionException("Function " + getName() + " expected " + getNumParams() + " parameters, got " + args.size());
|
||||
return args;
|
||||
}
|
||||
|
||||
public static List<LazyValue> lazify(List<Value> args)
|
||||
{
|
||||
List<LazyValue> lzargs = new ArrayList<>(args.size());
|
||||
args.forEach( v -> lzargs.add( (c, t) -> v));
|
||||
return lzargs;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class AbstractFunction extends AbstractLazyFunction implements IFunction
|
||||
@@ -127,7 +154,17 @@ public abstract class Fluff
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context cc, Integer type, Expression e, Tokenizer.Token t, final List<LazyValue> lazyParams)
|
||||
public boolean pure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context cc, Context.Type type, Expression e, Tokenizer.Token t, final List<LazyValue> lazyParams)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -135,7 +172,7 @@ public abstract class Fluff
|
||||
{ // eager evaluation always ignores the required type and evals params by none default
|
||||
private List<Value> params;
|
||||
|
||||
public Value evalValue(Context c, Integer type) {
|
||||
public Value evalValue(Context c, Context.Type type) {
|
||||
ILazyFunction.checkInterrupts();
|
||||
return AbstractFunction.this.eval(getParams(c));
|
||||
}
|
||||
@@ -143,7 +180,7 @@ public abstract class Fluff
|
||||
private List<Value> getParams(Context c) {
|
||||
if (params == null) {
|
||||
// very likely needs to be dynamic, so not static like here, or remember if it was.
|
||||
params = unpackArgs(lazyParams, c, Context.NONE);
|
||||
params = unpackArgs(lazyParams, c, Context.Type.NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,13 +224,22 @@ public abstract class Fluff
|
||||
AbstractOperator(int precedence, boolean leftAssoc) {
|
||||
super(precedence, leftAssoc);
|
||||
}
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context cc, Integer type, Expression e, Tokenizer.Token t, final LazyValue v1, final LazyValue v2)
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context cc, Context.Type type, Expression e, Tokenizer.Token t, final LazyValue v1, final LazyValue v2)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (c, type_ignored) -> AbstractOperator.this.eval(v1.evalValue(c, Context.NONE), v2.evalValue(c, Context.NONE));
|
||||
return (c, type_ignored) -> AbstractOperator.this.eval(v1.evalValue(c, Context.Type.NONE), v2.evalValue(c, Context.Type.NONE));
|
||||
}
|
||||
catch (RuntimeException exc)
|
||||
{
|
||||
@@ -207,9 +253,18 @@ public abstract class Fluff
|
||||
AbstractUnaryOperator(int precedence, boolean leftAssoc) {
|
||||
super(precedence, leftAssoc);
|
||||
}
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context cc, Integer type, Expression e, Tokenizer.Token t, final LazyValue v1, final LazyValue v2)
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context cc, Context.Type type, Expression e, Tokenizer.Token t, final LazyValue v1, final LazyValue v2)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -217,7 +272,7 @@ public abstract class Fluff
|
||||
{
|
||||
throw new ExpressionException(cc, e, t, "Did not expect a second parameter for unary operator");
|
||||
}
|
||||
return (c, ignored_type) -> AbstractUnaryOperator.this.evalUnary(v1.evalValue(c, Context.NONE));
|
||||
return (c, ignored_type) -> AbstractUnaryOperator.this.evalUnary(v1.evalValue(c, Context.Type.NONE));
|
||||
}
|
||||
catch (RuntimeException exc)
|
||||
{
|
||||
|
||||
@@ -10,15 +10,50 @@ public interface LazyValue
|
||||
LazyValue TRUE = (c, t) -> Value.TRUE;
|
||||
LazyValue NULL = (c, t) -> Value.NULL;
|
||||
LazyValue ZERO = (c, t) -> Value.ZERO;
|
||||
/**
|
||||
* The Value representation of the left parenthesis, used for parsing
|
||||
* varying numbers of function parameters.
|
||||
*/
|
||||
LazyValue PARAMS_START = (c, t) -> null;
|
||||
|
||||
Value evalValue(Context c, Integer type);
|
||||
public static LazyValue ofConstant(Value val) {
|
||||
return new Constant(val);
|
||||
}
|
||||
|
||||
Value evalValue(Context c, Context.Type type);
|
||||
|
||||
default Value evalValue(Context c){
|
||||
return evalValue(c, Context.NONE);
|
||||
return evalValue(c, Context.Type.NONE);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ContextFreeLazyValue extends LazyValue
|
||||
{
|
||||
|
||||
Value evalType(Context.Type type);
|
||||
|
||||
@Override
|
||||
default Value evalValue(Context c, Context.Type type) {
|
||||
return evalType(type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Constant implements ContextFreeLazyValue
|
||||
{
|
||||
Value result;
|
||||
|
||||
public Constant(Value value)
|
||||
{
|
||||
result = value;
|
||||
}
|
||||
|
||||
public Value get() {return result;}
|
||||
|
||||
@Override
|
||||
public Value evalType(Context.Type type) {
|
||||
|
||||
return result.fromConstant();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value evalValue(Context c, Context.Type type) {
|
||||
return result.fromConstant();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package carpet.script;
|
||||
|
||||
import carpet.script.bundled.Module;
|
||||
import carpet.script.exception.ExpressionException;
|
||||
import carpet.script.exception.IntegrityException;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.language.Arithmetic;
|
||||
import carpet.script.value.FunctionValue;
|
||||
@@ -121,11 +122,6 @@ public abstract class ScriptHost
|
||||
|
||||
void initializeModuleGlobals(ModuleData md)
|
||||
{
|
||||
md.globalVariables.put("euler", (c, t) -> Arithmetic.euler);
|
||||
md.globalVariables.put("pi", (c, t) -> Arithmetic.PI);
|
||||
md.globalVariables.put("null", (c, t) -> Value.NULL);
|
||||
md.globalVariables.put("true", (c, t) -> Value.TRUE);
|
||||
md.globalVariables.put("false", (c, t) -> Value.FALSE);
|
||||
}
|
||||
|
||||
public void importModule(Context c, String moduleName)
|
||||
@@ -181,8 +177,8 @@ public abstract class ScriptHost
|
||||
throw new InternalExpressionException("Cannot import from module that is not imported");
|
||||
}
|
||||
return Stream.concat(
|
||||
globaVariableNames(source, s -> s.startsWith("global_")),
|
||||
globaFunctionNames(source, s -> true)
|
||||
globalVariableNames(source, s -> s.startsWith("global_")),
|
||||
globalFunctionNames(source, s -> true)
|
||||
).distinct().sorted();
|
||||
}
|
||||
|
||||
@@ -205,7 +201,7 @@ public abstract class ScriptHost
|
||||
}
|
||||
private FunctionValue getFunction(Module module, String name)
|
||||
{
|
||||
ModuleData local = moduleData.get(module);
|
||||
ModuleData local = getModuleData(module);
|
||||
FunctionValue ret = local.globalFunctions.get(name); // most uses would be from local scope anyways
|
||||
if (ret != null) return ret;
|
||||
ModuleData target = local.functionImports.get(name);
|
||||
@@ -239,7 +235,7 @@ public abstract class ScriptHost
|
||||
public LazyValue getGlobalVariable(String name) { return getGlobalVariable(main, name); }
|
||||
public LazyValue getGlobalVariable(Module module, String name)
|
||||
{
|
||||
ModuleData local = moduleData.get(module);
|
||||
ModuleData local = getModuleData(module);
|
||||
LazyValue ret = local.globalVariables.get(name); // most uses would be from local scope anyways
|
||||
if (ret != null) return ret;
|
||||
ModuleData target = local.globalsImports.get(name);
|
||||
@@ -272,55 +268,62 @@ public abstract class ScriptHost
|
||||
|
||||
public void delFunctionWithPrefix(Module module, String prefix)
|
||||
{
|
||||
ModuleData data = moduleData.get(module);
|
||||
ModuleData data = getModuleData(module);
|
||||
data.globalFunctions.entrySet().removeIf(e -> e.getKey().startsWith(prefix));
|
||||
data.functionImports.entrySet().removeIf(e -> e.getKey().startsWith(prefix));
|
||||
}
|
||||
public void delFunction(Module module, String funName)
|
||||
{
|
||||
ModuleData data = moduleData.get(module);
|
||||
ModuleData data = getModuleData(module);
|
||||
data.globalFunctions.remove(funName);
|
||||
data.functionImports.remove(funName);
|
||||
}
|
||||
|
||||
public void delGlobalVariableWithPrefix(Module module, String prefix)
|
||||
{
|
||||
ModuleData data = moduleData.get(module);
|
||||
ModuleData data = getModuleData(module);
|
||||
data.globalVariables.entrySet().removeIf(e -> e.getKey().startsWith(prefix));
|
||||
data.globalsImports.entrySet().removeIf(e -> e.getKey().startsWith(prefix));
|
||||
}
|
||||
|
||||
public void delGlobalVariable(Module module, String varName)
|
||||
{
|
||||
ModuleData data = moduleData.get(module);
|
||||
ModuleData data = getModuleData(module);
|
||||
data.globalFunctions.remove(varName);
|
||||
data.functionImports.remove(varName);
|
||||
}
|
||||
|
||||
private ModuleData getModuleData(Module module)
|
||||
{
|
||||
ModuleData data = moduleData.get(module);
|
||||
if (data == null) throw new IntegrityException("Module structure changed for the app. Did you reload the app with tasks running?");
|
||||
return data;
|
||||
}
|
||||
|
||||
public void addUserDefinedFunction(Context ctx, Module module, String name, FunctionValue fun)
|
||||
{
|
||||
moduleData.get(module).globalFunctions.put(name, fun);
|
||||
getModuleData(module).globalFunctions.put(name, fun);
|
||||
}
|
||||
|
||||
public void setGlobalVariable(Module module, String name, LazyValue lv)
|
||||
{
|
||||
moduleData.get(module).globalVariables.put(name, lv);
|
||||
getModuleData(module).globalVariables.put(name, lv);
|
||||
}
|
||||
|
||||
public Stream<String> globaVariableNames(Module module, Predicate<String> predicate)
|
||||
public Stream<String> globalVariableNames(Module module, Predicate<String> predicate)
|
||||
{
|
||||
return Stream.concat(Stream.concat(
|
||||
moduleData.get(module).globalVariables.keySet().stream(),
|
||||
moduleData.get(module).globalsImports.keySet().stream()
|
||||
), moduleData.get(module).futureImports.keySet().stream().filter(s -> s.startsWith("global_"))).filter(predicate);
|
||||
getModuleData(module).globalVariables.keySet().stream(),
|
||||
getModuleData(module).globalsImports.keySet().stream()
|
||||
), getModuleData(module).futureImports.keySet().stream().filter(s -> s.startsWith("global_"))).filter(predicate);
|
||||
}
|
||||
|
||||
public Stream<String> globaFunctionNames(Module module, Predicate<String> predicate)
|
||||
public Stream<String> globalFunctionNames(Module module, Predicate<String> predicate)
|
||||
{
|
||||
return Stream.concat(Stream.concat(
|
||||
moduleData.get(module).globalFunctions.keySet().stream(),
|
||||
moduleData.get(module).functionImports.keySet().stream()
|
||||
),moduleData.get(module).futureImports.keySet().stream().filter(s -> !s.startsWith("global_"))).filter(predicate);
|
||||
getModuleData(module).globalFunctions.keySet().stream(),
|
||||
getModuleData(module).functionImports.keySet().stream()
|
||||
),getModuleData(module).futureImports.keySet().stream().filter(s -> !s.startsWith("global_"))).filter(predicate);
|
||||
}
|
||||
|
||||
public ScriptHost retrieveForExecution(String /*Nullable*/ user)
|
||||
|
||||
@@ -398,8 +398,28 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
|
||||
{
|
||||
enum TokenType
|
||||
{
|
||||
VARIABLE, FUNCTION, LITERAL, OPERATOR, UNARY_OPERATOR,
|
||||
OPEN_PAREN, COMMA, CLOSE_PAREN, HEX_LITERAL, STRINGPARAM, MARKER
|
||||
FUNCTION(true, false), OPERATOR(true, false), UNARY_OPERATOR(true, false),
|
||||
VARIABLE(false, false), CONSTANT(false, true),
|
||||
LITERAL(false, true), HEX_LITERAL(false, true), STRINGPARAM(false, true),
|
||||
OPEN_PAREN(false, true), COMMA(false, true), CLOSE_PAREN(false, true), MARKER(false, true);
|
||||
|
||||
boolean functional;
|
||||
boolean constant;
|
||||
|
||||
TokenType(boolean functional, boolean constant)
|
||||
{
|
||||
this.functional = functional;
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
public boolean isFunctional()
|
||||
{
|
||||
return functional;
|
||||
}
|
||||
|
||||
public boolean isConstant() {
|
||||
return constant;
|
||||
}
|
||||
}
|
||||
public String surface = "";
|
||||
public TokenType type;
|
||||
|
||||
@@ -796,7 +796,7 @@ public class Auxiliary {
|
||||
return Value.TRUE;
|
||||
});
|
||||
|
||||
expression.addFunction("logger", lv ->
|
||||
expression.addImpureFunction("logger", lv ->
|
||||
{
|
||||
Value res;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class Threading
|
||||
});
|
||||
|
||||
// has to be lazy due to deferred execution of the expression
|
||||
expression.addLazyFunctionWithDelegation("task_dock", 1, (c, t, expr, tok, lv) -> {
|
||||
expression.addLazyFunctionWithDelegation("task_dock", 1, false,true, (c, t, expr, tok, lv) -> {
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
MinecraftServer server = cc.s.getMinecraftServer();
|
||||
if (server.isOnThread()) return lv.get(0); // pass through for on thread tasks
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package carpet.script.exception;
|
||||
|
||||
public class IntegrityException extends RuntimeException
|
||||
{
|
||||
public IntegrityException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package carpet.script.language;
|
||||
import carpet.script.Context;
|
||||
import carpet.script.Expression;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.value.BooleanValue;
|
||||
import carpet.script.value.ListValue;
|
||||
import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.Value;
|
||||
@@ -15,7 +16,7 @@ public class Arithmetic {
|
||||
|
||||
public static void apply(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.addTypedContextFunction("not", 1, Context.Type.BOOLEAN, (c, t, lv) -> BooleanValue.of(lv.get(0).getBoolean()) );
|
||||
expression.addUnaryFunction("fact", (v) ->
|
||||
{
|
||||
long number = NumericValue.asNumber(v).getLong();
|
||||
|
||||
@@ -21,13 +21,22 @@ public class ControlFlow {
|
||||
public static void apply(Expression expression) // public just to get the javadoc right
|
||||
{
|
||||
// needs to be lazy cause of custom contextualization
|
||||
expression.addLazyBinaryOperator(";", Operators.precedence.get("nextop;"), true, (c, t, lv1, lv2) ->
|
||||
expression.addLazyBinaryOperator(";", Operators.precedence.get("nextop;"), true, true, t -> Context.Type.VOID, (c, t, lv1, lv2) ->
|
||||
{
|
||||
lv1.evalValue(c, Context.VOID);
|
||||
Value v2 = lv2.evalValue(c, t);
|
||||
return (cc, tt) -> v2;
|
||||
});
|
||||
|
||||
expression.addPureLazyFunction("then", -1, t -> Context.Type.VOID, (c, t, lv) -> {
|
||||
int imax = lv.size()-1;
|
||||
for (int i = 0; i < imax; i++) lv.get(i).evalValue(c, Context.VOID);
|
||||
Value v = lv.get(imax).evalValue(c, t);
|
||||
return (cc, tt) -> v;
|
||||
});
|
||||
expression.addFunctionalEquivalence(";", "then");
|
||||
|
||||
|
||||
// obvious lazy due to conditional evaluation of arguments
|
||||
expression.addLazyFunction("if", -1, (c, t, lv) ->
|
||||
{
|
||||
@@ -50,9 +59,9 @@ public class ControlFlow {
|
||||
return (cc, tt) -> Value.NULL;
|
||||
});
|
||||
|
||||
expression.addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); });
|
||||
expression.addImpureFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); });
|
||||
|
||||
expression.addFunction("throw", lv->
|
||||
expression.addImpureFunction("throw", lv->
|
||||
{
|
||||
switch (lv.size())
|
||||
{
|
||||
|
||||
@@ -189,7 +189,7 @@ public class DataStructures {
|
||||
return Value.NULL;
|
||||
});
|
||||
|
||||
expression.addBinaryContextOperator(":", Operators.precedence.get("attribute~:"),true, (c, t, container, address) ->
|
||||
expression.addBinaryContextOperator(":", Operators.precedence.get("attribute~:"),true, true, false, (ctx, t, container, address) ->
|
||||
{
|
||||
if (container instanceof LContainerValue)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,8 @@ package carpet.script.language;
|
||||
import carpet.script.Context;
|
||||
import carpet.script.Expression;
|
||||
import carpet.script.Fluff;
|
||||
import carpet.script.LazyValue;
|
||||
import carpet.script.Tokenizer;
|
||||
import carpet.script.argument.FunctionArgument;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.exception.ReturnStatement;
|
||||
@@ -35,53 +37,75 @@ public class Functions {
|
||||
return ListValue.wrap(c.host.availableImports(moduleName).map(StringValue::new).collect(Collectors.toList()));
|
||||
});
|
||||
|
||||
|
||||
// needs to be lazy because of custom context of execution of arguments as a signature
|
||||
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
|
||||
expression.addCustomFunction("call", new Fluff.AbstractLazyFunction(-1, "call") {
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Context.Type t, Expression expr, Tokenizer.Token tok, List<LazyValue> lv)
|
||||
{
|
||||
List<Value> args = Fluff.AbstractFunction.unpackLazy(lv, c, Context.NONE);
|
||||
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, args, 0, false, true);
|
||||
FunctionValue fun = functionArgument.function;
|
||||
return fun.callInContext(c, t, functionArgument.args);
|
||||
}
|
||||
// gimme signature
|
||||
String name = lv.get(0).evalValue(c, Context.NONE).getString();
|
||||
List<String> args = new ArrayList<>();
|
||||
List<String> globals = new ArrayList<>();
|
||||
String varArgs = null;
|
||||
for (int i = 1; i < lv.size(); i++)
|
||||
{
|
||||
Value v = lv.get(i).evalValue(c, Context.LOCALIZATION);
|
||||
if (!v.isBound())
|
||||
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
|
||||
{
|
||||
throw new InternalExpressionException("Only variables can be used in function signature, not " + v.getString());
|
||||
List<Value> args = Fluff.AbstractFunction.unpackLazy(lv, c, Context.NONE);
|
||||
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, args, 0, false, true);
|
||||
FunctionValue fun = functionArgument.function;
|
||||
return fun.callInContext(c, t, functionArgument.args);
|
||||
}
|
||||
if (v instanceof FunctionAnnotationValue)
|
||||
// gimme signature
|
||||
String name = lv.get(0).evalValue(c, Context.NONE).getString();
|
||||
List<String> args = new ArrayList<>();
|
||||
List<String> globals = new ArrayList<>();
|
||||
String varArgs = null;
|
||||
for (int i = 1; i < lv.size(); i++)
|
||||
{
|
||||
if (((FunctionAnnotationValue) v).type == FunctionAnnotationValue.Type.GLOBAL)
|
||||
Value v = lv.get(i).evalValue(c, Context.LOCALIZATION);
|
||||
if (!v.isBound())
|
||||
{
|
||||
globals.add(v.boundVariable);
|
||||
throw new InternalExpressionException("Only variables can be used in function signature, not " + v.getString());
|
||||
}
|
||||
if (v instanceof FunctionAnnotationValue)
|
||||
{
|
||||
if (((FunctionAnnotationValue) v).type == FunctionAnnotationValue.Type.GLOBAL)
|
||||
{
|
||||
globals.add(v.boundVariable);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (varArgs != null)
|
||||
throw new InternalExpressionException("Variable argument identifier is already defined as "+varArgs+", trying to overwrite with "+v.boundVariable);
|
||||
varArgs = v.boundVariable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (varArgs != null)
|
||||
throw new InternalExpressionException("Variable argument identifier is already defined as "+varArgs+", trying to overwrite with "+v.boundVariable);
|
||||
varArgs = v.boundVariable;
|
||||
args.add(v.boundVariable);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
args.add(v.boundVariable);
|
||||
}
|
||||
Value retval = new FunctionSignatureValue(name, args, varArgs, globals);
|
||||
return (cc, tt) -> retval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pure()
|
||||
{
|
||||
return false; //true for sinature, but lets leave it for later
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context.Type staticType(Context.Type outerType)
|
||||
{
|
||||
return outerType==Context.SIGNATURE?Context.LOCALIZATION:Context.NONE;
|
||||
}
|
||||
Value retval = new FunctionSignatureValue(name, args, varArgs, globals);
|
||||
return (cc, tt) -> retval;
|
||||
});
|
||||
|
||||
|
||||
expression.addContextFunction("outer", 1, (c, t, lv) ->
|
||||
{
|
||||
if (t != Context.LOCALIZATION)
|
||||
@@ -91,7 +115,7 @@ public class Functions {
|
||||
|
||||
//assigns const procedure to the lhs, returning its previous value
|
||||
// must be lazy due to RHS being an expression to save to execute
|
||||
expression.addLazyBinaryOperatorWithDelegation("->", Operators.precedence.get("def->"), false, (c, type, e, t, lv1, lv2) ->
|
||||
expression.addLazyBinaryOperatorWithDelegation("->", Operators.precedence.get("def->"), false, false, (c, type, e, t, lv1, lv2) ->
|
||||
{
|
||||
if (type == Context.MAPDEF)
|
||||
{
|
||||
@@ -106,6 +130,6 @@ public class Functions {
|
||||
return (cc, tt) -> result;
|
||||
});
|
||||
|
||||
expression.addFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} );
|
||||
expression.addImpureFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} );
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,14 @@ public class Loops {
|
||||
// condition and expression will get a bound '_i'
|
||||
// returns last successful expression or false
|
||||
// while(cond, limit, expr) => ??
|
||||
expression.addFunction("break", lv ->
|
||||
expression.addImpureFunction("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 ->
|
||||
expression.addImpureFunction("continue", lv ->
|
||||
{
|
||||
if (lv.size()==0) throw new ContinueStatement(null);
|
||||
if (lv.size()==1) throw new ContinueStatement(lv.get(0));
|
||||
|
||||
@@ -5,6 +5,7 @@ import carpet.script.Expression;
|
||||
import carpet.script.LazyValue;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.value.AbstractListValue;
|
||||
import carpet.script.value.BooleanValue;
|
||||
import carpet.script.value.ContainerValueInterface;
|
||||
import carpet.script.value.FunctionAnnotationValue;
|
||||
import carpet.script.value.FunctionUnpackedArgumentsValue;
|
||||
@@ -14,6 +15,7 @@ import carpet.script.value.MapValue;
|
||||
import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.Value;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -38,47 +40,203 @@ public class Operators {
|
||||
public static void apply(Expression expression)
|
||||
{
|
||||
expression.addBinaryOperator("+", precedence.get("addition+-"), true, Value::add);
|
||||
expression.addFunction("sum", lv -> {
|
||||
int size = lv.size();
|
||||
if (size == 0) return Value.NULL;
|
||||
Value accumulator = lv.get(0);
|
||||
for (Value v: lv.subList(1, size)) accumulator = accumulator.add(v);
|
||||
return accumulator;
|
||||
});
|
||||
expression.addFunctionalEquivalence("+", "sum");
|
||||
|
||||
expression.addBinaryOperator("-", precedence.get("addition+-"), true, Value::subtract);
|
||||
expression.addFunction("difference", lv -> {
|
||||
int size = lv.size();
|
||||
if (size == 0) return Value.NULL;
|
||||
Value accumulator = lv.get(0);
|
||||
for (Value v: lv.subList(1, size)) accumulator = accumulator.subtract(v);
|
||||
return accumulator;
|
||||
});
|
||||
expression.addFunctionalEquivalence("-", "difference");
|
||||
|
||||
expression.addBinaryOperator("*", precedence.get("multiplication*/%"), true, Value::multiply);
|
||||
expression.addFunction("product", lv -> {
|
||||
int size = lv.size();
|
||||
if (size == 0) return Value.NULL;
|
||||
Value accumulator = lv.get(0);
|
||||
for (Value v: lv.subList(1, size)) accumulator = accumulator.multiply(v);
|
||||
return accumulator;
|
||||
});
|
||||
expression.addFunctionalEquivalence("*", "product");
|
||||
|
||||
expression.addBinaryOperator("/", precedence.get("multiplication*/%"), true, Value::divide);
|
||||
expression.addFunction("quotient", lv -> {
|
||||
int size = lv.size();
|
||||
if (size == 0) return Value.NULL;
|
||||
Value accumulator = lv.get(0);
|
||||
for (Value v: lv.subList(1, size)) accumulator = accumulator.multiply(v);
|
||||
return accumulator;
|
||||
});
|
||||
expression.addFunctionalEquivalence("/", "quotient");
|
||||
|
||||
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(java.lang.Math.pow(NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(v2).getDouble())));
|
||||
|
||||
// lazy cause RHS is only conditional
|
||||
expression.addLazyBinaryOperator("&&", precedence.get("and&&"), false, (c, t, lv1, lv2) ->
|
||||
{
|
||||
expression.addLazyBinaryOperator("&&", precedence.get("and&&"), false, true, t -> Context.Type.BOOLEAN, (c, t, lv1, lv2) ->
|
||||
{ // todo check how is optimizations going
|
||||
Value v1 = lv1.evalValue(c, Context.BOOLEAN);
|
||||
if (!v1.getBoolean()) return (cc, tt) -> v1;
|
||||
return lv2;
|
||||
});
|
||||
|
||||
expression.addPureLazyFunction("and", -1, t -> Context.Type.BOOLEAN, (c, t, lv) -> {
|
||||
int last = lv.size()-1;
|
||||
if (last == -1) return LazyValue.TRUE;
|
||||
for (LazyValue l: lv.subList(0, last))
|
||||
{
|
||||
Value val = l.evalValue(c, Context.Type.BOOLEAN);
|
||||
if (val instanceof FunctionUnpackedArgumentsValue)
|
||||
{
|
||||
for (Value it : (FunctionUnpackedArgumentsValue) val)
|
||||
if (!it.getBoolean()) return (cc, tt) -> it;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!val.getBoolean()) return (cc, tt) -> val;
|
||||
}
|
||||
}
|
||||
return lv.get(last);
|
||||
});
|
||||
expression.addFunctionalEquivalence("&&", "and");
|
||||
|
||||
// lazy cause RHS is only conditional
|
||||
expression.addLazyBinaryOperator("||", precedence.get("or||"), false, (c, t, lv1, lv2) ->
|
||||
expression.addLazyBinaryOperator("||", precedence.get("or||"), false, true, t -> Context.Type.BOOLEAN, (c, t, lv1, lv2) ->
|
||||
{
|
||||
Value v1 = lv1.evalValue(c, Context.BOOLEAN);
|
||||
if (v1.getBoolean()) return (cc, tt) -> v1;
|
||||
return lv2;
|
||||
});
|
||||
|
||||
expression.addPureLazyFunction("or", -1, t -> Context.Type.BOOLEAN, (c, t, lv) -> {
|
||||
int last = lv.size()-1;
|
||||
if (last == -1) return LazyValue.FALSE;
|
||||
for (LazyValue l: lv.subList(0, last))
|
||||
{
|
||||
Value val = l.evalValue(c, Context.Type.BOOLEAN);
|
||||
if (val instanceof FunctionUnpackedArgumentsValue)
|
||||
{
|
||||
for (Value it : (FunctionUnpackedArgumentsValue) val)
|
||||
if (it.getBoolean()) return (cc, tt) -> it;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (val.getBoolean()) return (cc, tt) -> val;
|
||||
}
|
||||
}
|
||||
return lv.get(last);
|
||||
});
|
||||
expression.addFunctionalEquivalence("||", "or");
|
||||
|
||||
expression.addBinaryOperator("~", precedence.get("attribute~:"), true, Value::in);
|
||||
|
||||
expression.addBinaryOperator(">", precedence.get("compare>=><=<"), false, (v1, v2) ->
|
||||
v1.compareTo(v2) > 0 ? Value.TRUE : Value.FALSE);
|
||||
BooleanValue.of(v1.compareTo(v2) > 0));
|
||||
expression.addFunction("decreasing", lv -> {
|
||||
int size = lv.size();
|
||||
if (size < 2) return Value.TRUE;
|
||||
Value prev = lv.get(0);
|
||||
for (Value next: lv.subList(1, size))
|
||||
{
|
||||
if (prev.compareTo(next) <= 0) return Value.FALSE;
|
||||
prev = next;
|
||||
}
|
||||
return Value.TRUE;
|
||||
});
|
||||
expression.addFunctionalEquivalence(">", "decreasing");
|
||||
|
||||
expression.addBinaryOperator(">=", precedence.get("compare>=><=<"), false, (v1, v2) ->
|
||||
v1.compareTo(v2) >= 0 ? Value.TRUE : Value.FALSE);
|
||||
BooleanValue.of(v1.compareTo(v2) >= 0));
|
||||
expression.addFunction("nonincreasing", lv -> {
|
||||
int size = lv.size();
|
||||
if (size < 2) return Value.TRUE;
|
||||
Value prev = lv.get(0);
|
||||
for (Value next: lv.subList(1, size))
|
||||
{
|
||||
if (prev.compareTo(next) < 0) return Value.FALSE;
|
||||
prev = next;
|
||||
}
|
||||
return Value.TRUE;
|
||||
});
|
||||
expression.addFunctionalEquivalence(">=", "nonincreasing");
|
||||
|
||||
expression.addBinaryOperator("<", precedence.get("compare>=><=<"), false, (v1, v2) ->
|
||||
v1.compareTo(v2) < 0 ? Value.TRUE : Value.FALSE);
|
||||
BooleanValue.of(v1.compareTo(v2) < 0));
|
||||
expression.addFunction("increasing", lv -> {
|
||||
int size = lv.size();
|
||||
if (size < 2) return Value.TRUE;
|
||||
Value prev = lv.get(0);
|
||||
for (Value next: lv.subList(1, size))
|
||||
{
|
||||
if (prev.compareTo(next) >= 0) return Value.FALSE;
|
||||
prev = next;
|
||||
}
|
||||
return Value.TRUE;
|
||||
});
|
||||
expression.addFunctionalEquivalence("<", "increasing");
|
||||
|
||||
expression.addBinaryOperator("<=", precedence.get("compare>=><=<"), false, (v1, v2) ->
|
||||
v1.compareTo(v2) <= 0 ? Value.TRUE : Value.FALSE);
|
||||
BooleanValue.of(v1.compareTo(v2) <= 0));
|
||||
expression.addFunction("nondecreasing", lv -> {
|
||||
int size = lv.size();
|
||||
if (size < 2) return Value.TRUE;
|
||||
Value prev = lv.get(0);
|
||||
for (Value next: lv.subList(1, size))
|
||||
{
|
||||
if (prev.compareTo(next) > 0) return Value.FALSE;
|
||||
prev = next;
|
||||
}
|
||||
return Value.TRUE;
|
||||
});
|
||||
expression.addFunctionalEquivalence("<=", "nondecreasing");
|
||||
|
||||
expression.addBinaryOperator("==", precedence.get("equal==!="), false, (v1, v2) ->
|
||||
v1.equals(v2) ? Value.TRUE : Value.FALSE);
|
||||
expression.addFunction("equal", lv -> {
|
||||
int size = lv.size();
|
||||
if (size < 2) return Value.TRUE;
|
||||
Value prev = lv.get(0);
|
||||
for (Value next: lv.subList(1, size))
|
||||
{
|
||||
if (!prev.equals(next)) return Value.FALSE;
|
||||
prev = next;
|
||||
}
|
||||
return Value.TRUE;
|
||||
});
|
||||
expression.addFunctionalEquivalence("==", "equal");
|
||||
|
||||
|
||||
expression.addBinaryOperator("!=", precedence.get("equal==!="), false, (v1, v2) ->
|
||||
v1.equals(v2) ? Value.FALSE : Value.TRUE);
|
||||
expression.addFunction("unique", lv -> {
|
||||
int size = lv.size();
|
||||
if (size < 2) return Value.TRUE;
|
||||
// need to order them so same obejects will be next to each other.
|
||||
lv.sort(Comparator.comparingInt(Value::hashCode));
|
||||
Value prev = lv.get(0);
|
||||
for (Value next: lv.subList(1, size))
|
||||
{
|
||||
if (prev.equals(next)) return Value.FALSE;
|
||||
prev = next;
|
||||
}
|
||||
return Value.TRUE;
|
||||
});
|
||||
expression.addFunctionalEquivalence("!=", "unique");
|
||||
|
||||
// lazy cause of assignment which is non-trivial
|
||||
expression.addLazyBinaryOperator("=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) ->
|
||||
expression.addLazyBinaryOperator("=", precedence.get("assign=<>"), false, false, t -> Context.Type.LVALUE, (c, t, lv1, lv2) ->
|
||||
{
|
||||
Value v1 = lv1.evalValue(c, Context.LVALUE);
|
||||
Value v2 = lv2.evalValue(c);
|
||||
@@ -117,7 +275,7 @@ public class Operators {
|
||||
});
|
||||
|
||||
// lazy due to assignment
|
||||
expression.addLazyBinaryOperator("+=", precedence.get("assign=<>"), false, (c, t, lv1, lv2) ->
|
||||
expression.addLazyBinaryOperator("+=", precedence.get("assign=<>"), false, false, t -> Context.Type.LVALUE, (c, t, lv1, lv2) ->
|
||||
{
|
||||
Value v1 = lv1.evalValue(c, Context.LVALUE);
|
||||
Value v2 = lv2.evalValue(c);
|
||||
@@ -177,7 +335,7 @@ public class Operators {
|
||||
return boundedLHS;
|
||||
});
|
||||
|
||||
expression.addBinaryContextOperator("<>", precedence.get("assign=<>"), false, (c, t, v1, v2) ->
|
||||
expression.addBinaryContextOperator("<>", precedence.get("assign=<>"), false, false, false, (c, t, v1, v2) ->
|
||||
{
|
||||
if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue.ListConstructorValue)
|
||||
{
|
||||
@@ -218,12 +376,12 @@ public class Operators {
|
||||
expression.addUnaryOperator("+", false, NumericValue::asNumber);
|
||||
|
||||
// could be non-lazy, but who cares - its a small one.
|
||||
expression.addLazyUnaryOperator("!", precedence.get("unary+-!..."), false, (c, t, lv) ->
|
||||
expression.addLazyUnaryOperator("!", precedence.get("unary+-!..."), false, true, x -> Context.Type.BOOLEAN, (c, t, lv) ->
|
||||
lv.evalValue(c, Context.BOOLEAN).getBoolean() ? (cc, tt)-> Value.FALSE : (cc, tt) -> Value.TRUE
|
||||
); // might need context boolean
|
||||
|
||||
// lazy because of typed evaluation of the argument
|
||||
expression.addLazyUnaryOperator("...", Operators.precedence.get("unary+-!..."), false, (c, t, lv) ->
|
||||
expression.addLazyUnaryOperator("...", Operators.precedence.get("unary+-!..."), false, true, t -> t== Context.Type.LOCALIZATION?Context.NONE:t, (c, t, lv) ->
|
||||
{
|
||||
if (t == Context.LOCALIZATION)
|
||||
return (cc, tt) -> new FunctionAnnotationValue(lv.evalValue(c), FunctionAnnotationValue.Type.VARARG);
|
||||
|
||||
@@ -36,7 +36,7 @@ public class Sys {
|
||||
{
|
||||
expression.addUnaryFunction("hash_code", v -> new NumericValue(v.hashCode()));
|
||||
|
||||
expression.addUnaryFunction("copy", Value::deepcopy);
|
||||
expression.addImpureUnaryFunction("copy", Value::deepcopy);
|
||||
|
||||
expression.addTypedContextFunction("bool", 1, Context.BOOLEAN, (c, t, lv) ->
|
||||
{
|
||||
@@ -414,7 +414,7 @@ public class Sys {
|
||||
List<Value> values = new ArrayList<>();
|
||||
if (prefix.startsWith("global"))
|
||||
{
|
||||
c.host.globaVariableNames(expression.module, (s) -> s.startsWith(prefix)).forEach(s -> values.add(new StringValue(s)));
|
||||
c.host.globalVariableNames(expression.module, (s) -> s.startsWith(prefix)).forEach(s -> values.add(new StringValue(s)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ public class Threading
|
||||
{
|
||||
public static void apply(Expression expression)
|
||||
{
|
||||
expression.addFunctionWithDelegation("task", -1, (c, t, expr, tok, lv) ->
|
||||
expression.addFunctionWithDelegation("task", -1, false, false, (c, t, expr, tok, lv) ->
|
||||
{
|
||||
if (lv.size() == 0)
|
||||
throw new InternalExpressionException("'task' requires at least function to call as a parameter");
|
||||
@@ -22,7 +22,7 @@ public class Threading
|
||||
return thread;
|
||||
});
|
||||
|
||||
expression.addFunctionWithDelegation("task_thread", -1, (c, t, expr, tok, lv) ->
|
||||
expression.addFunctionWithDelegation("task_thread", -1, false, false, (c, t, expr, tok, lv) ->
|
||||
{
|
||||
if (lv.size() < 2)
|
||||
throw new InternalExpressionException("'task' requires at least function to call as a parameter");
|
||||
|
||||
@@ -15,4 +15,9 @@ public abstract class AbstractListValue extends Value implements Iterable<Value>
|
||||
{
|
||||
throw new InternalExpressionException("Cannot append a value to an abstract list");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value fromConstant() {
|
||||
return this.deepcopy();
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
|
||||
return varArgs != null;
|
||||
}
|
||||
|
||||
public LazyValue callInContext(Context c, Integer type, List<Value> params)
|
||||
public LazyValue callInContext(Context c, Context.Type type, List<Value> params)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -204,12 +204,12 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyValue lazyEval(Context c, Integer type, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams) {
|
||||
public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, List<LazyValue> lazyParams) {
|
||||
List<Value> resolvedParams = unpackArgs(lazyParams, c);
|
||||
return execute(c, type, e, t, resolvedParams);
|
||||
}
|
||||
|
||||
public LazyValue execute(Context c, Integer type, Expression e, Tokenizer.Token t, List<Value> params)
|
||||
public LazyValue execute(Context c, Context.Type type, Expression e, Tokenizer.Token t, List<Value> params)
|
||||
{
|
||||
assertArgsOk(params, (fixedArgs) ->{
|
||||
if (fixedArgs) // wrong number of args for fixed args
|
||||
@@ -301,10 +301,13 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
|
||||
}
|
||||
}
|
||||
|
||||
public static List<LazyValue> lazify(List<Value> args)
|
||||
{
|
||||
List<LazyValue> lzargs = new ArrayList<>(args.size());
|
||||
args.forEach( v -> lzargs.add( (c, t) -> v));
|
||||
return lzargs;
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transitive() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -192,6 +192,11 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value fromConstant() {
|
||||
return (Value)clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
|
||||
@@ -150,6 +150,11 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value fromConstant() {
|
||||
return deepcopy();
|
||||
}
|
||||
|
||||
// stolen from HopperBlockEntity, adjusted for threaded operation
|
||||
public static Inventory getInventoryAt(ServerWorld world, BlockPos blockPos)
|
||||
{
|
||||
|
||||
@@ -244,4 +244,11 @@ public abstract class Value implements Comparable<Value>, Cloneable
|
||||
}
|
||||
|
||||
public boolean isNull() { return false; }
|
||||
|
||||
/**
|
||||
* @return retrieves useful in-run value of an optimized code-base value.
|
||||
* For immutable values (most of them) it can return itself,
|
||||
* but for mutables, it needs to be its copy or deep copy.
|
||||
*/
|
||||
public Value fromConstant() { return this; }
|
||||
}
|
||||
Reference in new issue
Block a user