Merge pull request #844 from gnembon/expression_optimizer

Expression optimizer
This commit is contained in:
gnembon authored and GitHub committed 2021-05-05 01:28:01 -03:00
commit 0f81be2e8e
31 files changed
+1120 -229

No files matched your search

+13 -1
View File
@@ -1,7 +1,19 @@
# Minecraft specific API and `scarpet` language add-ons and commands # 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 ## App structure
@@ -2,7 +2,7 @@
## Writing programs with more than 1 line ## 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 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 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 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. 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 Result of the evaluated expression is the same as the result of the second expression, but first expression
is also evaluated for side-effects is also evaluated for side-effects
@@ -132,15 +132,16 @@ foo(... x) -> ... # all arguments for foo are included in the list
</pre> </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 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, 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. 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 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. 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 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 except you can use it with function value, or name instead. This means you can pass functions to other user defined
+20 -6
View File
@@ -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 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: 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 Important operator is function definition `->` operator. It will be covered
in [User Defined Functions and Program Control Flow](docs/scarpet/language/FunctionsAndControlFlow.md) in [User Defined Functions and Program Control Flow](docs/scarpet/language/FunctionsAndControlFlow.md)
@@ -118,7 +124,7 @@ $);
/script run global_get_enchantment(player(), 'sharpness') /script run global_get_enchantment(player(), 'sharpness')
</pre> </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. 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 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 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. 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: Examples:
<pre> <pre>
@@ -149,7 +158,7 @@ b = [100,63,100]; b+[10,0,10] => [110,63,110]
{'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2} {'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}
</pre> </pre>
### `Just Operators % ^` ### Just Operators `%`, `^`
The modulo and exponent (power) operators work only if both operands are numbers 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 -3 ^ pi => // Error
</pre> </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 - 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` 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: function. All of these are true:
@@ -180,7 +189,12 @@ null < -1000
3 == 3.0 3 == 3.0
</pre> </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 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 second operand is not necessary, it won't be evaluated, which means one can use them as conditional statements. In
+13
View File
@@ -3,6 +3,7 @@ acos
acosh acosh
acot acot
all all
and
asin asin
asinh asinh
atan atan
@@ -21,8 +22,11 @@ cot
coth coth
csc csc
csch csch
decreasing
deg deg
delete delete
difference
equal
exit exit
fact fact
filter filter
@@ -34,6 +38,7 @@ has
hash_code hash_code
if if
import import
increasing
join join
keys keys
l l
@@ -48,14 +53,19 @@ mandelbrot
map map
max max
min min
nondecreasing
nonincreasing
not not
number number
or
outer outer
pairs pairs
perlin perlin
print print
product
profile_expr profile_expr
put put
quotient
rad rad
rand rand
range range
@@ -75,6 +85,7 @@ sleep
slice slice
sort sort
sort_key sort_key
sum
synchronize synchronize
system_variable_get system_variable_get
system_variable_set system_variable_set
@@ -90,12 +101,14 @@ task_dock
task_join task_join
task_thread task_thread
task_value task_value
then
throw throw
time time
title title
try try
type type
undef undef
unique
upper upper
values values
var var
@@ -31,6 +31,7 @@
log log10 log1p log log10 log1p
sqrt sqrt
max min abs round floor ceil max min abs round floor ceil
then sum difference product quotient equal unique increasing decreasing nonincreasing nondecreasing or and
mandelbrot relu mandelbrot relu
perlin simplex perlin simplex
+13
View File
@@ -443,6 +443,19 @@ public class CarpetSettings
) )
public static boolean scriptsAutoload = true; 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) @Rule(desc = "Enables /player command to control/spawn players", category = COMMAND)
public static String commandPlayer = "ops"; 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 // not that useful in commandline, more so in external scripts, so skipping here
if (eventPrefix != null) scarpetMatches.addAll(CarpetEventServer.Event.publicEvents(null).stream(). if (eventPrefix != null) scarpetMatches.addAll(CarpetEventServer.Event.publicEvents(null).stream().
filter(e -> e.name.startsWith(eventPrefix)).map(s -> "__on_"+s.name+"(").collect(Collectors.toList())); 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.globalFunctionNames(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.globalVariableNames(host.main, s -> s.startsWith(prefix)).collect(Collectors.toList()));
return scarpetMatches; return scarpetMatches;
} }
@@ -332,7 +332,7 @@ public class ScriptCommand
private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c) throws CommandSyntaxException private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c) throws CommandSyntaxException
{ {
CarpetScriptHost host = getHost(c); 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) private static int listEvents(ServerCommandSource source)
{ {
@@ -358,7 +358,7 @@ public class ScriptCommand
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
Messenger.m(source, "lb Stored functions"+((host == CarpetServer.scriptServer.globalHost)?":":" in "+host.getName()+":")); 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); FunctionValue fun = host.getFunction(s);
if (fun == null) if (fun == null)
{ {
@@ -379,7 +379,7 @@ public class ScriptCommand
//Messenger.m(source, "w "+code); //Messenger.m(source, "w "+code);
Messenger.m(source, "w "); Messenger.m(source, "w ");
Messenger.m(source, "lb Global variables"+((host == CarpetServer.scriptServer.globalHost)?":":" in "+host.getName()+":")); 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); LazyValue variable = host.getGlobalVariable(s);
if (variable == null) 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(); 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) if (hasTypeSupport)
{ {
@@ -1004,15 +1004,7 @@ public class CarpetScriptHost extends ScriptHost
return null; return null;
} }
String shebang = message; String shebang = message+" in "+expr.getModuleName();
if (expr.module != null)
{
shebang += " in " + expr.module.getName() + "";
}
else
{
shebang += " in system chat";
}
if (token != null) if (token != null)
{ {
String[] lines = expr.getCodeString().split("\n"); String[] lines = expr.getCodeString().split("\n");
@@ -5,6 +5,7 @@ import carpet.script.bundled.BundledModule;
import carpet.CarpetServer; import carpet.CarpetServer;
import carpet.script.bundled.FileModule; import carpet.script.bundled.FileModule;
import carpet.script.bundled.Module; import carpet.script.bundled.Module;
import carpet.script.exception.IntegrityException;
import carpet.script.exception.InvalidCallbackException; import carpet.script.exception.InvalidCallbackException;
import carpet.script.value.FunctionValue; import carpet.script.value.FunctionValue;
import carpet.script.value.Value; import carpet.script.value.Value;
@@ -403,7 +404,7 @@ public class CarpetScriptServer
{ {
host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv); host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv);
} }
catch (NullPointerException | InvalidCallbackException npe) catch (NullPointerException | InvalidCallbackException | IntegrityException npe)
{ {
if (reportFails) return -1; if (reportFails) return -1;
continue; continue;
+15 -11
View File
@@ -8,17 +8,21 @@ import java.util.Set;
public class Context public class Context
{ {
public static final int NONE = 0; public enum Type
public static final int VOID = 1; {
public static final int BOOLEAN = 2; NONE, VOID, BOOLEAN, NUMBER, STRING, LIST, ITERATOR, SIGNATURE, LOCALIZATION, LVALUE, MAPDEF
public static final int NUMBER = 3; }
public static final int STRING = 4; public static final Type NONE = Type.NONE;
public static final int LIST = 5; public static final Type VOID = Type.VOID;
public static final int ITERATOR = 6; public static final Type BOOLEAN = Type.BOOLEAN;
public static final int SIGNATURE = 7; public static final Type NUMBER = Type.NUMBER;
public static final int LOCALIZATION = 8; public static final Type STRING = Type.STRING;
public static final int LVALUE = 9; public static final Type LIST = Type.LIST;
public static final int MAPDEF = 10; 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<>(); public Map<String, LazyValue> variables = new HashMap<>();
+578 -77
View File
@@ -1,5 +1,6 @@
package carpet.script; package carpet.script;
import carpet.CarpetSettings;
import carpet.script.Fluff.AbstractFunction; import carpet.script.Fluff.AbstractFunction;
import carpet.script.Fluff.AbstractLazyFunction; import carpet.script.Fluff.AbstractLazyFunction;
import carpet.script.Fluff.AbstractLazyOperator; import carpet.script.Fluff.AbstractLazyOperator;
@@ -16,6 +17,7 @@ import carpet.script.exception.BreakStatement;
import carpet.script.exception.ContinueStatement; import carpet.script.exception.ContinueStatement;
import carpet.script.exception.ExitStatement; import carpet.script.exception.ExitStatement;
import carpet.script.exception.ExpressionException; import carpet.script.exception.ExpressionException;
import carpet.script.exception.IntegrityException;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ResolvedException; import carpet.script.exception.ResolvedException;
import carpet.script.exception.ReturnStatement; import carpet.script.exception.ReturnStatement;
@@ -31,6 +33,8 @@ import carpet.script.value.FunctionValue;
import carpet.script.value.NumericValue; import carpet.script.value.NumericValue;
import carpet.script.value.StringValue; import carpet.script.value.StringValue;
import carpet.script.value.Value; 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 it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.math.BigInteger; import java.math.BigInteger;
@@ -44,6 +48,7 @@ import java.util.Stack;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@@ -61,7 +66,7 @@ public class Expression
public String getModuleName() public String getModuleName()
{ {
return module == null?null:module.getName(); return module == null?"system chat":module.getName();
} }
public void asATextSource() public void asATextSource()
@@ -85,6 +90,27 @@ public class Expression
private final Map<String, ILazyFunction> functions = new Object2ObjectOpenHashMap<>(); private final Map<String, ILazyFunction> functions = new Object2ObjectOpenHashMap<>();
public Set<String> getFunctionNames() {return functions.keySet();} 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) public List<String> getExpressionSnippet(Tokenizer.Token token)
{ {
String code = this.getCodeString(); String code = this.getCodeString();
@@ -138,13 +164,28 @@ public class Expression
} }
public void addLazyUnaryOperator(String surface, int precedence, boolean leftAssoc, public void addLazyUnaryOperator(String surface, int precedence, boolean leftAssoc, boolean pure, Function<Context.Type,Context.Type> staticTyper,
TriFunction<Context, Integer, LazyValue, LazyValue> lazyfun) TriFunction<Context, Context.Type, LazyValue, LazyValue> lazyfun)
{ {
operators.put(surface+"u", new AbstractLazyOperator(precedence, leftAssoc) operators.put(surface+"u", new AbstractLazyOperator(precedence, leftAssoc)
{ {
@Override @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 try
{ {
@@ -159,13 +200,23 @@ public class Expression
} }
public void addLazyBinaryOperatorWithDelegation(String surface, int precedence, boolean leftAssoc, public void addLazyBinaryOperatorWithDelegation(String surface, int precedence, boolean leftAssoc, boolean pure,
SexFunction<Context, Integer, Expression, Tokenizer.Token, LazyValue, LazyValue, LazyValue> lazyfun) SexFunction<Context, Context.Type, Expression, Tokenizer.Token, LazyValue, LazyValue, LazyValue> lazyfun)
{ {
operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc)
{ {
@Override @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 try
{ {
@@ -179,13 +230,28 @@ public class Expression
}); });
} }
public void addLazyFunctionWithDelegation(String name, int numpar, public void addCustomFunction(String name, ILazyFunction fun)
QuinnFunction<Context, Integer, Expression, Tokenizer.Token, List<LazyValue>, LazyValue> lazyfun) {
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) functions.put(name, new AbstractLazyFunction(numpar, name)
{ {
@Override @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(); ILazyFunction.checkInterrupts();
try try
@@ -200,13 +266,23 @@ public class Expression
}); });
} }
public void addFunctionWithDelegation(String name, int numpar, public void addFunctionWithDelegation(String name, int numpar, boolean pure, boolean transitive,
QuinnFunction<Context, Integer, Expression, Tokenizer.Token, List<Value>, Value> fun) QuinnFunction<Context, Context.Type, Expression, Tokenizer.Token, List<Value>, Value> fun)
{ {
functions.put(name, new AbstractLazyFunction(numpar, name) functions.put(name, new AbstractLazyFunction(numpar, name)
{ {
@Override @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 try
{ {
@@ -221,13 +297,30 @@ public class Expression
}); });
} }
public void addLazyBinaryOperator(String surface, int precedence, boolean leftAssoc, public void addLazyBinaryOperator(String surface, int precedence, boolean leftAssoc, boolean pure, Function<Context.Type, Context.Type> typer,
QuadFunction<Context, Integer, LazyValue, LazyValue, LazyValue> lazyfun) QuadFunction<Context, Context.Type, LazyValue, LazyValue, LazyValue> lazyfun)
{ {
operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc)
{ {
@Override @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(); ILazyFunction.checkInterrupts();
try try
@@ -242,13 +335,23 @@ public class Expression
}); });
} }
public void addBinaryContextOperator(String surface, int precedence, boolean leftAssoc, public void addBinaryContextOperator(String surface, int precedence, boolean leftAssoc, boolean pure, boolean transitive,
QuadFunction<Context, Integer, Value, Value, Value> fun) QuadFunction<Context, Context.Type, Value, Value, Value> fun)
{ {
operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc) operators.put(surface, new AbstractLazyOperator(precedence, leftAssoc)
{ {
@Override @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 try
{ {
@@ -267,6 +370,8 @@ public class Expression
{ {
if (exc instanceof ExitStatement) if (exc instanceof ExitStatement)
return exc; return exc;
if (exc instanceof IntegrityException)
return exc;
if (exc instanceof InternalExpressionException) if (exc instanceof InternalExpressionException)
return ((InternalExpressionException) exc).promote(c, e, token); return ((InternalExpressionException) exc).promote(c, e, token);
if (exc instanceof ArithmeticException) 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) public void addBinaryFunction(String name, BiFunction<Value, Value, Value> fun)
{ {
functions.put(name, new AbstractFunction(2, name) 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) public void addMathematicalUnaryFunction(String name, Function<Double, Double> fun)
{ {
addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble()))); 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) functions.put(name, new AbstractLazyFunction(num_params, name)
{ {
@Override @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(); ILazyFunction.checkInterrupts();
try 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) functions.put(name, new AbstractLazyFunction(num_params, name)
{ {
@Override @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(); ILazyFunction.checkInterrupts();
try 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) functions.put(name, new AbstractLazyFunction(num_params, name)
{ {
@Override @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 try
{ {
@@ -448,7 +652,22 @@ public class Expression
} }
@Override @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+"(...)"); c.host.issueDeprecation(copy+"(...)");
return originalFunction.lazyEval(c, type, expr, token, lazyParams); return originalFunction.lazyEval(c, type, expr, token, lazyParams);
@@ -471,7 +690,7 @@ public class Expression
public LazyValue getOrSetAnyVariable(Context c, String name) public LazyValue getOrSetAnyVariable(Context c, String name)
{ {
LazyValue var = null; LazyValue var;
if (!name.startsWith("global_")) if (!name.startsWith("global_"))
{ {
var = c.getVariable(name); var = c.getVariable(name);
@@ -596,16 +815,16 @@ public class Expression
case OPEN_PAREN: case OPEN_PAREN:
if (previousToken != null) if (previousToken != null)
{ {
if (previousToken.type == Tokenizer.Token.TokenType.LITERAL || previousToken.type == Tokenizer.Token.TokenType.CLOSE_PAREN //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.VARIABLE
|| previousToken.type == Tokenizer.Token.TokenType.HEX_LITERAL) // || previousToken.type == Tokenizer.Token.TokenType.HEX_LITERAL)
{ //{
// Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b) // Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b)
Tokenizer.Token multiplication = new Tokenizer.Token(); // Tokenizer.Token multiplication = new Tokenizer.Token();
multiplication.append("*"); // multiplication.append("*");
multiplication.type = Tokenizer.Token.TokenType.OPERATOR; // multiplication.type = Tokenizer.Token.TokenType.OPERATOR;
stack.push(multiplication); // stack.push(multiplication);
} //}
// if the ( is preceded by a valid function, then it // if the ( is preceded by a valid function, then it
// denotes the start of a parameter list // denotes the start of a parameter list
if (previousToken.type == Tokenizer.Token.TokenType.FUNCTION) if (previousToken.type == Tokenizer.Token.TokenType.FUNCTION)
@@ -672,19 +891,15 @@ public class Expression
} }
public Value eval(Context c) public Value eval(Context c)
{
return eval(c, Context.NONE);
}
public Value eval(Context c, Integer expectedType)
{ {
if (ast == null) if (ast == null)
{ {
ast = getAST(c); 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 try
{ {
@@ -712,35 +927,67 @@ public class Expression
} }
} }
private LazyValue getAST(Context context) public static class ExpressionNode
{ {
Stack<LazyValue> stack = new Stack<>(); public LazyValue op;
List<Tokenizer.Token> rpn = shuntingYard(context); public List<ExpressionNode> args;
validate(context, rpn); public Tokenizer.Token token;
for (final Tokenizer.Token token : rpn) 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) switch (token.type)
{ {
case UNARY_OPERATOR: case UNARY_OPERATOR:
{ {
final LazyValue value = stack.pop(); final ExpressionNode node = nodeStack.pop();
LazyValue result = (c, t) -> operators.get(token.surface).lazyEval(c, t, this, token, value, null).evalValue(c, t); LazyValue result = (c, t) -> operators.get(token.surface).lazyEval(c, t, this, token, node.op, null).evalValue(c, t);
stack.push(result); nodeStack.push(new ExpressionNode(result, Collections.singletonList(node), token));
break; break;
} }
case OPERATOR: case OPERATOR:
final LazyValue v1 = stack.pop(); final ExpressionNode v1 = nodeStack.pop();
final LazyValue v2 = stack.pop(); final ExpressionNode v2 = nodeStack.pop();
LazyValue result = (c,t) -> operators.get(token.surface).lazyEval(c, t,this, token, v2, v1).evalValue(c, t); LazyValue result = (c,t) -> operators.get(token.surface).lazyEval(c, t,this, token, v2.op, v1.op).evalValue(c, t);
stack.push(result); nodeStack.push(new ExpressionNode(result, ImmutableList.of(v2, v1), token ));
break; break;
case VARIABLE: 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; break;
case FUNCTION: case FUNCTION:
String name = token.surface; String name = token.surface;
ILazyFunction f; ILazyFunction f;
ArrayList<LazyValue> p; ArrayList<ExpressionNode> p;
boolean isKnown = functions.containsKey(name); // globals will be evaluated lazily, not at compile time via . boolean isKnown = functions.containsKey(name); // globals will be evaluated lazily, not at compile time via .
if (isKnown) if (isKnown)
{ {
@@ -754,47 +1001,301 @@ public class Expression
} }
// pop parameters off the stack until we hit the start of // pop parameters off the stack until we hit the start of
// this function's parameter list // 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); 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; break;
case OPEN_PAREN: case OPEN_PAREN:
stack.push(LazyValue.PARAMS_START); nodeStack.push(ExpressionNode.PARAMS_START);
break; break;
case LITERAL: case LITERAL:
stack.push((c, t) -> Value number;
try
{ {
try number = new NumericValue(token.surface);
{ }
return new NumericValue(token.surface); catch (NumberFormatException exception)
} {
catch (NumberFormatException exception) throw new ExpressionException(context, this, token, "Not a number");
{ }
throw new ExpressionException(c, this, token, "Not a number"); token.morph(Tokenizer.Token.TokenType.CONSTANT, token.surface);
} nodeStack.push(ExpressionNode.ofConstant(number, token));
});
break; break;
case STRINGPARAM: 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; break;
case HEX_LITERAL: 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; break;
default: default:
throw new ExpressionException(context, this, token, "Unexpected token '" + token.surface + "'"); 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) private void validate(Context c, List<Tokenizer.Token> rpn)
+69 -14
View File
@@ -30,13 +30,33 @@ public abstract class Fluff
@FunctionalInterface @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 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(); int getNumParams();
boolean numParamsVaries(); 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() static void checkInterrupts()
{ {
@@ -51,13 +71,13 @@ public abstract class Fluff
Value eval(List<Value> parameters); Value eval(List<Value> parameters);
} }
public interface ILazyOperator public interface ILazyOperator extends EvalNode
{ {
int getPrecedence(); int getPrecedence();
boolean isLeftAssoc(); 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 public interface IOperator extends ILazyOperator
@@ -70,7 +90,7 @@ public abstract class Fluff
protected String name; protected String name;
int numParams; int numParams;
AbstractLazyFunction(int numParams, String name) public AbstractLazyFunction(int numParams, String name)
{ {
this.numParams = numParams; this.numParams = numParams;
this.name = name; this.name = name;
@@ -89,7 +109,7 @@ public abstract class Fluff
return numParams < 0; 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<>(); List<Value> args = new ArrayList<>();
for (LazyValue lv : lzargs) for (LazyValue lv : lzargs)
@@ -103,7 +123,7 @@ public abstract class Fluff
return args; 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<>(); List<Value> args = new ArrayList<>();
for (LazyValue lv : lzargs) for (LazyValue lv : lzargs)
@@ -118,6 +138,13 @@ public abstract class Fluff
throw new InternalExpressionException("Function " + getName() + " expected " + getNumParams() + " parameters, got " + args.size()); throw new InternalExpressionException("Function " + getName() + " expected " + getNumParams() + " parameters, got " + args.size());
return args; 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 public abstract static class AbstractFunction extends AbstractLazyFunction implements IFunction
@@ -127,7 +154,17 @@ public abstract class Fluff
} }
@Override @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 try
{ {
@@ -135,7 +172,7 @@ public abstract class Fluff
{ // eager evaluation always ignores the required type and evals params by none default { // eager evaluation always ignores the required type and evals params by none default
private List<Value> params; private List<Value> params;
public Value evalValue(Context c, Integer type) { public Value evalValue(Context c, Context.Type type) {
ILazyFunction.checkInterrupts(); ILazyFunction.checkInterrupts();
return AbstractFunction.this.eval(getParams(c)); return AbstractFunction.this.eval(getParams(c));
} }
@@ -143,7 +180,7 @@ public abstract class Fluff
private List<Value> getParams(Context c) { private List<Value> getParams(Context c) {
if (params == null) { if (params == null) {
// very likely needs to be dynamic, so not static like here, or remember if it was. // 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 else
{ {
@@ -187,13 +224,22 @@ public abstract class Fluff
AbstractOperator(int precedence, boolean leftAssoc) { AbstractOperator(int precedence, boolean leftAssoc) {
super(precedence, leftAssoc); super(precedence, leftAssoc);
} }
@Override
public boolean pure() {
return true;
}
@Override @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 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) catch (RuntimeException exc)
{ {
@@ -207,9 +253,18 @@ public abstract class Fluff
AbstractUnaryOperator(int precedence, boolean leftAssoc) { AbstractUnaryOperator(int precedence, boolean leftAssoc) {
super(precedence, leftAssoc); super(precedence, leftAssoc);
} }
@Override
public boolean pure() {
return true;
}
@Override @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 try
{ {
@@ -217,7 +272,7 @@ public abstract class Fluff
{ {
throw new ExpressionException(cc, e, t, "Did not expect a second parameter for unary operator"); 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) catch (RuntimeException exc)
{ {
+42 -7
View File
@@ -10,15 +10,50 @@ public interface LazyValue
LazyValue TRUE = (c, t) -> Value.TRUE; LazyValue TRUE = (c, t) -> Value.TRUE;
LazyValue NULL = (c, t) -> Value.NULL; LazyValue NULL = (c, t) -> Value.NULL;
LazyValue ZERO = (c, t) -> Value.ZERO; 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){ 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();
}
} }
} }
+26 -23
View File
@@ -2,6 +2,7 @@ package carpet.script;
import carpet.script.bundled.Module; import carpet.script.bundled.Module;
import carpet.script.exception.ExpressionException; import carpet.script.exception.ExpressionException;
import carpet.script.exception.IntegrityException;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.language.Arithmetic; import carpet.script.language.Arithmetic;
import carpet.script.value.FunctionValue; import carpet.script.value.FunctionValue;
@@ -121,11 +122,6 @@ public abstract class ScriptHost
void initializeModuleGlobals(ModuleData md) 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) 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"); throw new InternalExpressionException("Cannot import from module that is not imported");
} }
return Stream.concat( return Stream.concat(
globaVariableNames(source, s -> s.startsWith("global_")), globalVariableNames(source, s -> s.startsWith("global_")),
globaFunctionNames(source, s -> true) globalFunctionNames(source, s -> true)
).distinct().sorted(); ).distinct().sorted();
} }
@@ -205,7 +201,7 @@ public abstract class ScriptHost
} }
private FunctionValue getFunction(Module module, String name) 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 FunctionValue ret = local.globalFunctions.get(name); // most uses would be from local scope anyways
if (ret != null) return ret; if (ret != null) return ret;
ModuleData target = local.functionImports.get(name); 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(String name) { return getGlobalVariable(main, name); }
public LazyValue getGlobalVariable(Module module, String 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 LazyValue ret = local.globalVariables.get(name); // most uses would be from local scope anyways
if (ret != null) return ret; if (ret != null) return ret;
ModuleData target = local.globalsImports.get(name); ModuleData target = local.globalsImports.get(name);
@@ -272,55 +268,62 @@ public abstract class ScriptHost
public void delFunctionWithPrefix(Module module, String prefix) 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.globalFunctions.entrySet().removeIf(e -> e.getKey().startsWith(prefix));
data.functionImports.entrySet().removeIf(e -> e.getKey().startsWith(prefix)); data.functionImports.entrySet().removeIf(e -> e.getKey().startsWith(prefix));
} }
public void delFunction(Module module, String funName) public void delFunction(Module module, String funName)
{ {
ModuleData data = moduleData.get(module); ModuleData data = getModuleData(module);
data.globalFunctions.remove(funName); data.globalFunctions.remove(funName);
data.functionImports.remove(funName); data.functionImports.remove(funName);
} }
public void delGlobalVariableWithPrefix(Module module, String prefix) 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.globalVariables.entrySet().removeIf(e -> e.getKey().startsWith(prefix));
data.globalsImports.entrySet().removeIf(e -> e.getKey().startsWith(prefix)); data.globalsImports.entrySet().removeIf(e -> e.getKey().startsWith(prefix));
} }
public void delGlobalVariable(Module module, String varName) public void delGlobalVariable(Module module, String varName)
{ {
ModuleData data = moduleData.get(module); ModuleData data = getModuleData(module);
data.globalFunctions.remove(varName); data.globalFunctions.remove(varName);
data.functionImports.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) 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) 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( return Stream.concat(Stream.concat(
moduleData.get(module).globalVariables.keySet().stream(), getModuleData(module).globalVariables.keySet().stream(),
moduleData.get(module).globalsImports.keySet().stream() getModuleData(module).globalsImports.keySet().stream()
), moduleData.get(module).futureImports.keySet().stream().filter(s -> s.startsWith("global_"))).filter(predicate); ), 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( return Stream.concat(Stream.concat(
moduleData.get(module).globalFunctions.keySet().stream(), getModuleData(module).globalFunctions.keySet().stream(),
moduleData.get(module).functionImports.keySet().stream() getModuleData(module).functionImports.keySet().stream()
),moduleData.get(module).futureImports.keySet().stream().filter(s -> !s.startsWith("global_"))).filter(predicate); ),getModuleData(module).futureImports.keySet().stream().filter(s -> !s.startsWith("global_"))).filter(predicate);
} }
public ScriptHost retrieveForExecution(String /*Nullable*/ user) public ScriptHost retrieveForExecution(String /*Nullable*/ user)
+22 -2
View File
@@ -398,8 +398,28 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
{ {
enum TokenType enum TokenType
{ {
VARIABLE, FUNCTION, LITERAL, OPERATOR, UNARY_OPERATOR, FUNCTION(true, false), OPERATOR(true, false), UNARY_OPERATOR(true, false),
OPEN_PAREN, COMMA, CLOSE_PAREN, HEX_LITERAL, STRINGPARAM, MARKER 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 String surface = "";
public TokenType type; public TokenType type;
@@ -796,7 +796,7 @@ public class Auxiliary {
return Value.TRUE; return Value.TRUE;
}); });
expression.addFunction("logger", lv -> expression.addImpureFunction("logger", lv ->
{ {
Value res; Value res;
@@ -25,7 +25,7 @@ public class Threading
}); });
// has to be lazy due to deferred execution of the expression // 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; CarpetContext cc = (CarpetContext)c;
MinecraftServer server = cc.s.getMinecraftServer(); MinecraftServer server = cc.s.getMinecraftServer();
if (server.isOnThread()) return lv.get(0); // pass through for on thread tasks 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.Context;
import carpet.script.Expression; import carpet.script.Expression;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.value.BooleanValue;
import carpet.script.value.ListValue; import carpet.script.value.ListValue;
import carpet.script.value.NumericValue; import carpet.script.value.NumericValue;
import carpet.script.value.Value; import carpet.script.value.Value;
@@ -15,7 +16,7 @@ public class Arithmetic {
public static void apply(Expression expression) 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) -> expression.addUnaryFunction("fact", (v) ->
{ {
long number = NumericValue.asNumber(v).getLong(); 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 public static void apply(Expression expression) // public just to get the javadoc right
{ {
// needs to be lazy cause of custom contextualization // 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); lv1.evalValue(c, Context.VOID);
Value v2 = lv2.evalValue(c, t); Value v2 = lv2.evalValue(c, t);
return (cc, tt) -> v2; 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 // obvious lazy due to conditional evaluation of arguments
expression.addLazyFunction("if", -1, (c, t, lv) -> expression.addLazyFunction("if", -1, (c, t, lv) ->
{ {
@@ -50,9 +59,9 @@ public class ControlFlow {
return (cc, tt) -> Value.NULL; 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()) switch (lv.size())
{ {
@@ -189,7 +189,7 @@ public class DataStructures {
return Value.NULL; 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) if (container instanceof LContainerValue)
{ {
@@ -3,6 +3,8 @@ package carpet.script.language;
import carpet.script.Context; import carpet.script.Context;
import carpet.script.Expression; import carpet.script.Expression;
import carpet.script.Fluff; import carpet.script.Fluff;
import carpet.script.LazyValue;
import carpet.script.Tokenizer;
import carpet.script.argument.FunctionArgument; import carpet.script.argument.FunctionArgument;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ReturnStatement; 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())); 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 // needs to be lazy because of custom context of execution of arguments as a signature
expression.addLazyFunctionWithDelegation("call",-1, (c, t, expr, tok, lv) -> expression.addCustomFunction("call", new Fluff.AbstractLazyFunction(-1, "call") {
{ // adjust based on c @Override
if (lv.size() == 0) public LazyValue lazyEval(Context c, Context.Type t, Expression expr, Tokenizer.Token tok, List<LazyValue> lv)
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
{ {
List<Value> args = Fluff.AbstractFunction.unpackLazy(lv, c, Context.NONE); if (lv.size() == 0)
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, args, 0, false, true); throw new InternalExpressionException("'call' expects at least function name to call");
FunctionValue fun = functionArgument.function; //lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes
return fun.callInContext(c, t, functionArgument.args); if (t != Context.SIGNATURE) // just call the function
}
// 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())
{ {
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 else
{ {
if (varArgs != null) args.add(v.boundVariable);
throw new InternalExpressionException("Variable argument identifier is already defined as "+varArgs+", trying to overwrite with "+v.boundVariable);
varArgs = v.boundVariable;
} }
} }
else Value retval = new FunctionSignatureValue(name, args, varArgs, globals);
{ return (cc, tt) -> retval;
args.add(v.boundVariable); }
}
@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) -> expression.addContextFunction("outer", 1, (c, t, lv) ->
{ {
if (t != Context.LOCALIZATION) if (t != Context.LOCALIZATION)
@@ -91,7 +115,7 @@ public class Functions {
//assigns const procedure to the lhs, returning its previous value //assigns const procedure to the lhs, returning its previous value
// must be lazy due to RHS being an expression to save to execute // 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) if (type == Context.MAPDEF)
{ {
@@ -106,6 +130,6 @@ public class Functions {
return (cc, tt) -> result; 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' // condition and expression will get a bound '_i'
// returns last successful expression or false // returns last successful expression or false
// while(cond, limit, expr) => ?? // while(cond, limit, expr) => ??
expression.addFunction("break", lv -> expression.addImpureFunction("break", lv ->
{ {
if (lv.size()==0) throw new BreakStatement(null); if (lv.size()==0) throw new BreakStatement(null);
if (lv.size()==1) throw new BreakStatement(lv.get(0)); if (lv.size()==1) throw new BreakStatement(lv.get(0));
throw new InternalExpressionException("'break' can only be called with zero or one argument"); 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()==0) throw new ContinueStatement(null);
if (lv.size()==1) throw new ContinueStatement(lv.get(0)); 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.LazyValue;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.value.AbstractListValue; import carpet.script.value.AbstractListValue;
import carpet.script.value.BooleanValue;
import carpet.script.value.ContainerValueInterface; import carpet.script.value.ContainerValueInterface;
import carpet.script.value.FunctionAnnotationValue; import carpet.script.value.FunctionAnnotationValue;
import carpet.script.value.FunctionUnpackedArgumentsValue; import carpet.script.value.FunctionUnpackedArgumentsValue;
@@ -14,6 +15,7 @@ import carpet.script.value.MapValue;
import carpet.script.value.NumericValue; import carpet.script.value.NumericValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -38,47 +40,203 @@ public class Operators {
public static void apply(Expression expression) public static void apply(Expression expression)
{ {
expression.addBinaryOperator("+", precedence.get("addition+-"), true, Value::add); 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.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.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.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) -> expression.addBinaryOperator("%", precedence.get("multiplication*/%"), true, (v1, v2) ->
new NumericValue(NumericValue.asNumber(v1).getDouble() % NumericValue.asNumber(v2).getDouble())); new NumericValue(NumericValue.asNumber(v1).getDouble() % NumericValue.asNumber(v2).getDouble()));
expression.addBinaryOperator("^", precedence.get("exponent^"), false, (v1, v2) -> expression.addBinaryOperator("^", precedence.get("exponent^"), false, (v1, v2) ->
new NumericValue(java.lang.Math.pow(NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(v2).getDouble()))); new NumericValue(java.lang.Math.pow(NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(v2).getDouble())));
// lazy cause RHS is only conditional // 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); Value v1 = lv1.evalValue(c, Context.BOOLEAN);
if (!v1.getBoolean()) return (cc, tt) -> v1; if (!v1.getBoolean()) return (cc, tt) -> v1;
return lv2; 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 // 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); Value v1 = lv1.evalValue(c, Context.BOOLEAN);
if (v1.getBoolean()) return (cc, tt) -> v1; if (v1.getBoolean()) return (cc, tt) -> v1;
return lv2; 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("attribute~:"), true, Value::in);
expression.addBinaryOperator(">", precedence.get("compare>=><=<"), false, (v1, v2) -> 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) -> 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) -> 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) -> 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) -> expression.addBinaryOperator("==", precedence.get("equal==!="), false, (v1, v2) ->
v1.equals(v2) ? Value.TRUE : Value.FALSE); 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) -> expression.addBinaryOperator("!=", precedence.get("equal==!="), false, (v1, v2) ->
v1.equals(v2) ? Value.FALSE : Value.TRUE); 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 // 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 v1 = lv1.evalValue(c, Context.LVALUE);
Value v2 = lv2.evalValue(c); Value v2 = lv2.evalValue(c);
@@ -117,7 +275,7 @@ public class Operators {
}); });
// lazy due to assignment // 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 v1 = lv1.evalValue(c, Context.LVALUE);
Value v2 = lv2.evalValue(c); Value v2 = lv2.evalValue(c);
@@ -177,7 +335,7 @@ public class Operators {
return boundedLHS; 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) if (v1 instanceof ListValue.ListConstructorValue && v2 instanceof ListValue.ListConstructorValue)
{ {
@@ -218,12 +376,12 @@ public class Operators {
expression.addUnaryOperator("+", false, NumericValue::asNumber); expression.addUnaryOperator("+", false, NumericValue::asNumber);
// could be non-lazy, but who cares - its a small one. // 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 lv.evalValue(c, Context.BOOLEAN).getBoolean() ? (cc, tt)-> Value.FALSE : (cc, tt) -> Value.TRUE
); // might need context boolean ); // might need context boolean
// lazy because of typed evaluation of the argument // 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) if (t == Context.LOCALIZATION)
return (cc, tt) -> new FunctionAnnotationValue(lv.evalValue(c), FunctionAnnotationValue.Type.VARARG); 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("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) -> expression.addTypedContextFunction("bool", 1, Context.BOOLEAN, (c, t, lv) ->
{ {
@@ -414,7 +414,7 @@ public class Sys {
List<Value> values = new ArrayList<>(); List<Value> values = new ArrayList<>();
if (prefix.startsWith("global")) 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 else
{ {
@@ -12,7 +12,7 @@ public class Threading
{ {
public static void apply(Expression expression) 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) if (lv.size() == 0)
throw new InternalExpressionException("'task' requires at least function to call as a parameter"); throw new InternalExpressionException("'task' requires at least function to call as a parameter");
@@ -22,7 +22,7 @@ public class Threading
return thread; 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) if (lv.size() < 2)
throw new InternalExpressionException("'task' requires at least function to call as a parameter"); 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"); 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; 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 try
{ {
@@ -204,12 +204,12 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
} }
@Override @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); List<Value> resolvedParams = unpackArgs(lazyParams, c);
return execute(c, type, e, t, resolvedParams); 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) ->{ assertArgsOk(params, (fixedArgs) ->{
if (fixedArgs) // wrong number of args for fixed args 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) @Override
{ public boolean pure() {
List<LazyValue> lzargs = new ArrayList<>(args.size()); return false;
args.forEach( v -> lzargs.add( (c, t) -> v)); }
return lzargs;
@Override
public boolean transitive() {
return false;
} }
} }
@@ -192,6 +192,11 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
return copy; return copy;
} }
@Override
public Value fromConstant() {
return (Value)clone();
}
@Override @Override
public int hashCode() public int hashCode()
{ {
@@ -150,6 +150,11 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
return copy; return copy;
} }
@Override
public Value fromConstant() {
return deepcopy();
}
// stolen from HopperBlockEntity, adjusted for threaded operation // stolen from HopperBlockEntity, adjusted for threaded operation
public static Inventory getInventoryAt(ServerWorld world, BlockPos blockPos) 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; } 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; }
} }