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