diff --git a/src/main/java/com/eliotlash/molang/ConstantFunctions.java b/src/main/java/com/eliotlash/molang/ConstantFunctions.java
new file mode 100644
index 0000000..27c2c3c
--- /dev/null
+++ b/src/main/java/com/eliotlash/molang/ConstantFunctions.java
@@ -0,0 +1,44 @@
+package com.eliotlash.molang;
+
+import com.eliotlash.molang.ast.Expr;
+import com.eliotlash.molang.functions.FunctionDefinition;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This holds a list of function that can be compiled without needing a context.
+ * These functions, if only passed constants, can be compiled ahead of time into a constant.
+ *
+ * You are able to add your own functions into this list.
+ *
+ * @author FX
+ */
+public class ConstantFunctions {
+
+ private static final List CONSTANT_FUNCTIONS = new ArrayList<>();
+
+ public static boolean isConstant(Expr.Call expr) {
+ FunctionDefinition functionDefinition = new FunctionDefinition(expr.target(), expr.member());
+ return isFunctionDefinitionConstant(functionDefinition) && areArgumentsConstant(expr.arguments());
+ }
+
+ public static boolean isFunctionDefinitionConstant(FunctionDefinition functionDefinition) {
+ return CONSTANT_FUNCTIONS.contains(functionDefinition);
+ }
+
+ public static boolean areArgumentsConstant(List arguments) {
+ for (Expr expr : arguments) {
+ if (!(expr instanceof Expr.Constant)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static void addConstantFunction(FunctionDefinition functionDefinition) {
+ if (!CONSTANT_FUNCTIONS.contains(functionDefinition)) {
+ CONSTANT_FUNCTIONS.add(functionDefinition);
+ }
+ }
+}
diff --git a/src/main/java/com/eliotlash/molang/Parser.java b/src/main/java/com/eliotlash/molang/Parser.java
index b7eab99..05d2892 100644
--- a/src/main/java/com/eliotlash/molang/Parser.java
+++ b/src/main/java/com/eliotlash/molang/Parser.java
@@ -17,7 +17,7 @@ public class Parser {
private final List input;
private int current = 0;
- private CompileConstants constants = new CompileConstants();
+ private final CompileConstants constants = new CompileConstants();
public Parser(List input) {
this.input = input;
@@ -97,8 +97,6 @@ private Stmt continueStatement() {
}
private Stmt loopStatement() {
- var loop = previous();
-
consume(OPEN_PAREN, "Expect '(' for loop args.");
List arguments = arguments();
@@ -172,8 +170,6 @@ private Expr.Block elseStatement() {
}
private Stmt returnStatement() {
- var returnTok = previous();
-
Expr value = expression();
consume(SEMICOLON, "Expect ';' after return statement.");
@@ -205,7 +201,6 @@ private Expr disjunction() {
var expr = conjunction();
while (match(OR)) {
- Token operator = previous();
Expr right = conjunction();
expr = new Expr.BinOp(Operator.OR, expr, right);
}
@@ -216,7 +211,6 @@ private Expr conjunction() {
var expr = equality();
while (match(AND)) {
- Token operator = previous();
Expr right = equality();
expr = new Expr.BinOp(Operator.AND, expr, right);
}
@@ -251,7 +245,6 @@ private Expr coalesce() {
var expr = term();
while (match(COALESCE)) {
- Token coalesce = previous();
Expr right = term();
expr = new Expr.Coalesce(expr, right);
}
diff --git a/src/main/java/com/eliotlash/molang/ast/ASTTransformation.java b/src/main/java/com/eliotlash/molang/ast/ASTTransformation.java
index 61f7440..48f9c18 100644
--- a/src/main/java/com/eliotlash/molang/ast/ASTTransformation.java
+++ b/src/main/java/com/eliotlash/molang/ast/ASTTransformation.java
@@ -1,5 +1,7 @@
package com.eliotlash.molang.ast;
+import com.eliotlash.molang.ConstantFunctions;
+
public class ASTTransformation implements Expr.Visitor, Stmt.Visitor {
@Override
@@ -55,7 +57,11 @@ public Expr visitBlock(Expr.Block expr) {
@Override
public Expr visitCall(Expr.Call expr) {
- return new Expr.Call((Expr.Variable) visit(expr.target()), expr.member(), expr.arguments().stream().map(this::visit).toList());
+ Expr.Call call = new Expr.Call((Expr.Variable) visit(expr.target()), expr.member(), expr.arguments().stream().map(this::visit).toList());
+ if (ConstantFunctions.isConstant(call)) {
+ return new Expr.Constant(Evaluator.getGlobalEvaluator().visitCall(call));
+ }
+ return call;
}
@Override
diff --git a/src/main/java/com/eliotlash/molang/ast/Evaluatable.java b/src/main/java/com/eliotlash/molang/ast/Evaluatable.java
index 56585c9..1e9a865 100644
--- a/src/main/java/com/eliotlash/molang/ast/Evaluatable.java
+++ b/src/main/java/com/eliotlash/molang/ast/Evaluatable.java
@@ -1,43 +1,22 @@
package com.eliotlash.molang.ast;
import java.util.List;
-import java.util.Objects;
-public class Evaluatable {
- private Expr expr = null;
- private List stmts = null;
- private boolean constant = false;
+public interface Evaluatable {
- public Evaluatable(Expr expr) {
- this.expr = Objects.requireNonNull(expr);
- constant = expr instanceof Expr.Constant;
+ default boolean isConstant() {
+ return false;
}
- public Evaluatable(List stmts) {
- this.stmts = Objects.requireNonNull(stmts);
- }
-
- public double evaluate(Evaluator evaluator) {
- if (stmts != null) {
- return evaluator.evaluate(stmts);
- }
- if (expr != null) {
- Double result = evaluator.evaluate(expr);
- return result == null ? 0 : result;
- }
-
- //should never get here
+ default double getConstant() {
return 0.0;
}
- public boolean isConstant() {
- return constant;
+ static Evaluatable of(Expr expression) {
+ return new EvaluatableExpr(expression);
}
- public double getConstant() {
- if(!isConstant()) {
- return 0.0;
- }
- return Evaluator.getGlobalEvaluator().evaluate(expr);
+ static Evaluatable of(List statements) {
+ return new EvaluatableStmt(statements);
}
}
diff --git a/src/main/java/com/eliotlash/molang/ast/EvaluatableExpr.java b/src/main/java/com/eliotlash/molang/ast/EvaluatableExpr.java
new file mode 100644
index 0000000..6094337
--- /dev/null
+++ b/src/main/java/com/eliotlash/molang/ast/EvaluatableExpr.java
@@ -0,0 +1,30 @@
+package com.eliotlash.molang.ast;
+
+import java.util.Objects;
+
+public class EvaluatableExpr implements Evaluatable {
+
+ private final Expr expr;
+ private final boolean constant;
+
+ public EvaluatableExpr(Expr expr) {
+ this.expr = Objects.requireNonNull(expr);
+ this.constant = expr instanceof Expr.Constant;
+ }
+
+ public double evaluate(Evaluator evaluator) {
+ Double result = evaluator.evaluate(this.expr);
+ return result == null ? 0 : result;
+ }
+
+ public boolean isConstant() {
+ return this.constant;
+ }
+
+ public double getConstant() {
+ if(!isConstant()) {
+ return 0.0;
+ }
+ return Evaluator.getGlobalEvaluator().evaluate(this.expr);
+ }
+}
diff --git a/src/main/java/com/eliotlash/molang/ast/EvaluatableStmt.java b/src/main/java/com/eliotlash/molang/ast/EvaluatableStmt.java
new file mode 100644
index 0000000..dea235b
--- /dev/null
+++ b/src/main/java/com/eliotlash/molang/ast/EvaluatableStmt.java
@@ -0,0 +1,17 @@
+package com.eliotlash.molang.ast;
+
+import java.util.List;
+import java.util.Objects;
+
+public class EvaluatableStmt implements Evaluatable {
+
+ private final List stmts;
+
+ public EvaluatableStmt(List stmts) {
+ this.stmts = Objects.requireNonNull(stmts);
+ }
+
+ public double evaluate(Evaluator evaluator) {
+ return evaluator.evaluate(this.stmts);
+ }
+}
diff --git a/src/main/java/com/eliotlash/molang/ast/Evaluator.java b/src/main/java/com/eliotlash/molang/ast/Evaluator.java
index 2aa7195..229adc7 100644
--- a/src/main/java/com/eliotlash/molang/ast/Evaluator.java
+++ b/src/main/java/com/eliotlash/molang/ast/Evaluator.java
@@ -1,6 +1,5 @@
package com.eliotlash.molang.ast;
-import com.eliotlash.molang.ParseException;
import com.eliotlash.molang.functions.Function;
import com.eliotlash.molang.functions.FunctionDefinition;
import com.eliotlash.molang.utils.MolangUtils;
@@ -22,7 +21,7 @@ public class Evaluator implements Expr.Visitor, Stmt.Visitor {
globalEvaluator.setExecutionContext(globalContext);
}
- private ExecutionContext context = new ExecutionContext(this);
+ private ExecutionContext context;
public static Evaluator getGlobalEvaluator() {
return globalEvaluator;
diff --git a/src/main/java/com/eliotlash/molang/functions/Function.java b/src/main/java/com/eliotlash/molang/functions/Function.java
index fa5804d..beccd53 100644
--- a/src/main/java/com/eliotlash/molang/functions/Function.java
+++ b/src/main/java/com/eliotlash/molang/functions/Function.java
@@ -4,7 +4,7 @@
import com.eliotlash.molang.variables.ExecutionContext;
public abstract class Function {
- protected String name;
+ private final String name;
public Function(String name) {
this.name = name;
@@ -17,6 +17,14 @@ public String getName() {
return this.name;
}
+ /**
+ * If all arguments passed are constant,
+ * will this function always give the same result without the need of a context.
+ */
+ public boolean isConstant() {
+ return true;
+ }
+
/**
* Get minimum count of arguments this function needs
*/
diff --git a/src/main/java/com/eliotlash/molang/functions/utility/DiceRoll.java b/src/main/java/com/eliotlash/molang/functions/utility/DiceRoll.java
index 8339a7a..aaf679b 100644
--- a/src/main/java/com/eliotlash/molang/functions/utility/DiceRoll.java
+++ b/src/main/java/com/eliotlash/molang/functions/utility/DiceRoll.java
@@ -4,13 +4,16 @@
import com.eliotlash.molang.functions.Function;
import com.eliotlash.molang.variables.ExecutionContext;
-public class DiceRoll extends Function {
- public java.util.Random random;
+import java.util.concurrent.ThreadLocalRandom;
+public class DiceRoll extends Function {
- public DiceRoll(String name){
+ public DiceRoll(String name) {
super(name);
- this.random = new java.util.Random();
+ }
+
+ public boolean isConstant() {
+ return false;
}
@Override
@@ -21,11 +24,13 @@ public int getRequiredArguments() {
public double _evaluate(Expr[] arguments, ExecutionContext ctx) {
double returnValue = 0;
double rollCount = this.evaluateArgument(arguments, ctx, 0);
- double min = this.evaluateArgument(arguments, ctx, 1);
- double max = this.evaluateArgument(arguments, ctx, 2);
+ if (rollCount > 0) {
+ double min = this.evaluateArgument(arguments, ctx, 1);
+ double max = this.evaluateArgument(arguments, ctx, 2);
- for (int i = 0; i < rollCount; i++) {
- returnValue += this.random.nextDouble() * (max - min) + min;
+ for (int i = 0; i < rollCount; i++) {
+ returnValue += ThreadLocalRandom.current().nextDouble() * (max - min) + min;
+ }
}
return returnValue;
diff --git a/src/main/java/com/eliotlash/molang/functions/utility/DiceRollInteger.java b/src/main/java/com/eliotlash/molang/functions/utility/DiceRollInteger.java
index 475e2c6..e851e4a 100644
--- a/src/main/java/com/eliotlash/molang/functions/utility/DiceRollInteger.java
+++ b/src/main/java/com/eliotlash/molang/functions/utility/DiceRollInteger.java
@@ -4,13 +4,16 @@
import com.eliotlash.molang.functions.Function;
import com.eliotlash.molang.variables.ExecutionContext;
-public class DiceRollInteger extends Function {
- public java.util.Random random;
+import java.util.concurrent.ThreadLocalRandom;
+public class DiceRollInteger extends Function {
- public DiceRollInteger(String name){
+ public DiceRollInteger(String name) {
super(name);
- this.random = new java.util.Random();
+ }
+
+ public boolean isConstant() {
+ return false;
}
@Override
@@ -21,11 +24,13 @@ public int getRequiredArguments() {
public double _evaluate(Expr[] arguments, ExecutionContext ctx) {
double returnValue = 0;
double rollCount = this.evaluateArgument(arguments, ctx, 0);
- double min = this.evaluateArgument(arguments, ctx, 1);
- double max = this.evaluateArgument(arguments, ctx, 2);
+ if (rollCount > 0) {
+ double min = this.evaluateArgument(arguments, ctx, 1);
+ double max = this.evaluateArgument(arguments, ctx, 2);
- for (int i = 0; i < rollCount; i++) {
- returnValue += Math.round(this.random.nextDouble() * (max - min) + min);
+ for (int i = 0; i < rollCount; i++) {
+ returnValue += Math.round(ThreadLocalRandom.current().nextDouble() * (max - min) + min);
+ }
}
return returnValue;
diff --git a/src/main/java/com/eliotlash/molang/functions/utility/Random.java b/src/main/java/com/eliotlash/molang/functions/utility/Random.java
index 2e8b84d..87209a3 100644
--- a/src/main/java/com/eliotlash/molang/functions/utility/Random.java
+++ b/src/main/java/com/eliotlash/molang/functions/utility/Random.java
@@ -13,6 +13,10 @@ public Random(String name){
this.random = new java.util.Random();
}
+ public boolean isConstant() {
+ return false;
+ }
+
public double _evaluate(Expr[] arguments, ExecutionContext ctx) {
double random = 0;
diff --git a/src/main/java/com/eliotlash/molang/functions/utility/RandomInteger.java b/src/main/java/com/eliotlash/molang/functions/utility/RandomInteger.java
index 5cba090..f1ef337 100644
--- a/src/main/java/com/eliotlash/molang/functions/utility/RandomInteger.java
+++ b/src/main/java/com/eliotlash/molang/functions/utility/RandomInteger.java
@@ -13,6 +13,10 @@ public RandomInteger(String name){
this.random = new java.util.Random();
}
+ public boolean isConstant() {
+ return false;
+ }
+
public double _evaluate(Expr[] arguments, ExecutionContext ctx) {
double random = 0;
diff --git a/src/main/java/com/eliotlash/molang/variables/ExecutionContext.java b/src/main/java/com/eliotlash/molang/variables/ExecutionContext.java
index 4f4148d..d166e6b 100644
--- a/src/main/java/com/eliotlash/molang/variables/ExecutionContext.java
+++ b/src/main/java/com/eliotlash/molang/variables/ExecutionContext.java
@@ -1,5 +1,6 @@
package com.eliotlash.molang.variables;
+import com.eliotlash.molang.ConstantFunctions;
import com.eliotlash.molang.ast.Assignable;
import com.eliotlash.molang.ast.Evaluator;
import com.eliotlash.molang.ast.Expr;
@@ -23,14 +24,17 @@
import java.util.*;
public class ExecutionContext {
- private Evaluator evaluator;
+
+ private static final Map MATH_FUNCTIONS;
+
+ private final Evaluator evaluator;
public final Stack contextStack = new Stack<>();
public final Map>> flavorCache = new HashMap<>();
public final Object2DoubleMap assignableMap = new Object2DoubleOpenHashMap<>();
public Object2DoubleMap functionScopedArguments = new Object2DoubleOpenHashMap<>();
- private Map functionMap = new HashMap<>();
+ private final Map functionMap = new HashMap<>();
private final Object2DoubleMap variableMap = new Object2DoubleOpenHashMap<>();
private final Map variableCache = new HashMap<>();
private final Object2DoubleMap structMap = new Object2DoubleOpenHashMap<>();
@@ -38,34 +42,7 @@ public class ExecutionContext {
public ExecutionContext(Evaluator evaluator) {
this.evaluator = evaluator;
- registerFunction("math", new Abs("abs"));
- registerFunction("math", new CosDegrees("cos"));
- registerFunction("math", new Cos("cosradians"));
- registerFunction("math", new SinDegrees("sin"));
- registerFunction("math", new Sin("sinradians"));
- registerFunction("math", new Asin("asin"));
- registerFunction("math", new Acos("acos"));
- registerFunction("math", new Atan("atan"));
- registerFunction("math", new Atan2("atan2"));
- registerFunction("math", new Exp("exp"));
- registerFunction("math", new Ln("ln"));
- registerFunction("math", new Mod("mod"));
- registerFunction("math", new Pow("pow"));
- registerFunction("math", new Sqrt("sqrt"));
- registerFunction("math", new Clamp("clamp"));
- registerFunction("math", new Max("max"));
- registerFunction("math", new Min("min"));
- registerFunction("math", new Ceil("ceil"));
- registerFunction("math", new Floor("floor"));
- registerFunction("math", new Round("round"));
- registerFunction("math", new Trunc("trunc"));
- registerFunction("math", new Lerp("lerp"));
- registerFunction("math", new LerpRotate("lerprotate"));
- registerFunction("math", new MinAngle("min_angle"));
- registerFunction("math", new Random("random"));
- registerFunction("math", new RandomInteger("random_integer"));
- registerFunction("math", new DiceRoll("dice_roll"));
- registerFunction("math", new DiceRollInteger("dice_roll_integer"));
+ registerFunctions(MATH_FUNCTIONS);
}
public Evaluator getEvaluator() {
@@ -124,14 +101,62 @@ public void setVariable(String var, boolean val) {
}
public void registerFunction(String target, Function function) {
- functionMap.putIfAbsent(new FunctionDefinition(new Expr.Variable(target), function.getName()), function);
+ registerFunction(asFunctionDefinition(target, function), function);
}
public void registerFunction(FunctionDefinition definition, Function function) {
functionMap.putIfAbsent(definition, function);
+ if (function.isConstant()) {
+ ConstantFunctions.addConstantFunction(definition);
+ }
+ }
+
+ public void registerFunctions(Map map) {
+ map.forEach(this::registerFunction);
}
public Function getFunction(FunctionDefinition definition) {
return this.functionMap.get(definition);
}
+
+ private static FunctionDefinition asFunctionDefinition(String target, Function function) {
+ return new FunctionDefinition(new Expr.Variable(target), function.getName());
+ }
+
+ private static void addFunction(Map map, String target, Function func) {
+ map.put(asFunctionDefinition(target, func), func);
+ }
+
+ static {
+ Map map = new HashMap<>();
+ addFunction(map, "math", new Abs("abs"));
+ addFunction(map, "math", new CosDegrees("cos"));
+ addFunction(map, "math", new Cos("cosradians"));
+ addFunction(map, "math", new SinDegrees("sin"));
+ addFunction(map, "math", new Sin("sinradians"));
+ addFunction(map, "math", new Asin("asin"));
+ addFunction(map, "math", new Acos("acos"));
+ addFunction(map, "math", new Atan("atan"));
+ addFunction(map, "math", new Atan2("atan2"));
+ addFunction(map, "math", new Exp("exp"));
+ addFunction(map, "math", new Ln("ln"));
+ addFunction(map, "math", new Mod("mod"));
+ addFunction(map, "math", new Pow("pow"));
+ addFunction(map, "math", new Sqrt("sqrt"));
+ addFunction(map, "math", new Clamp("clamp"));
+ addFunction(map, "math", new Max("max"));
+ addFunction(map, "math", new Min("min"));
+ addFunction(map, "math", new Ceil("ceil"));
+ addFunction(map, "math", new Floor("floor"));
+ addFunction(map, "math", new Round("round"));
+ addFunction(map, "math", new Trunc("trunc"));
+ addFunction(map, "math", new Lerp("lerp"));
+ addFunction(map, "math", new LerpRotate("lerprotate"));
+ addFunction(map, "math", new MinAngle("min_angle"));
+ addFunction(map, "math", new Random("random"));
+ addFunction(map, "math", new RandomInteger("random_integer"));
+ addFunction(map, "math", new DiceRoll("dice_roll"));
+ addFunction(map, "math", new DiceRollInteger("dice_roll_integer"));
+ MATH_FUNCTIONS = Map.copyOf(map);
+ }
}
diff --git a/src/test/java/com/eliotlash/molang/FunctionsTest.java b/src/test/java/com/eliotlash/molang/FunctionsTest.java
index adb98a6..f5652e2 100644
--- a/src/test/java/com/eliotlash/molang/FunctionsTest.java
+++ b/src/test/java/com/eliotlash/molang/FunctionsTest.java
@@ -3,6 +3,7 @@
import com.eliotlash.molang.ast.Evaluator;
import com.eliotlash.molang.ast.Expr;
import com.eliotlash.molang.ast.Transformations;
+import com.eliotlash.molang.variables.ExecutionContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -15,6 +16,7 @@ class FunctionsTest {
@BeforeEach
private void setupEval() {
evaluator = new Evaluator();
+ evaluator.setExecutionContext(new ExecutionContext(evaluator));
}
@Test
@@ -22,7 +24,7 @@ void testConstant() throws Exception {
assertConstant("PI");
assertConstant("E");
assertConstant("500");
-/* assertConstant("math.sqrt(2)");
+ assertConstant("math.sqrt(2)");
assertConstant("math.floor(2.5)");
assertConstant("math.round(2.5)");
assertConstant("math.ceil(2.5)");
@@ -36,7 +38,22 @@ void testConstant() throws Exception {
assertConstant("math.exp(5)");
assertConstant("math.ln(E)");
assertConstant("math.mod(10, 3)");
- assertConstant("math.pow(20, 2)");*/
+ assertConstant("math.pow(20, 2)");
+ assertNotConstant("math.sqrt(v.val)");
+ assertNotConstant("math.floor(v.val)");
+ assertNotConstant("math.round(v.val)");
+ assertNotConstant("math.ceil(v.val)");
+ assertNotConstant("math.trunc(v.val)");
+ assertNotConstant("math.clamp(10, v.val, 1)");
+ assertNotConstant("math.max(1, v.val)");
+ assertNotConstant("math.min(v.val, 2)");
+ assertNotConstant("math.abs(-v.val)");
+ assertNotConstant("math.cos(v.val)");
+ assertNotConstant("math.sin(v.val)");
+ assertNotConstant("math.exp(v.val)");
+ assertNotConstant("math.ln(v.val)");
+ assertNotConstant("math.mod(10, v.val)");
+ assertNotConstant("math.pow(v.val, 2)");
}
@Test
diff --git a/src/test/java/com/eliotlash/molang/ast/EvaluatorTest.java b/src/test/java/com/eliotlash/molang/ast/EvaluatorTest.java
index 817d208..5b11607 100644
--- a/src/test/java/com/eliotlash/molang/ast/EvaluatorTest.java
+++ b/src/test/java/com/eliotlash/molang/ast/EvaluatorTest.java
@@ -21,6 +21,7 @@ class EvaluatorTest extends TestBase {
@BeforeEach
void setUp() throws Exception {
eval = new Evaluator();
+ eval.setExecutionContext(new ExecutionContext(eval));
}
@Test