Merge pull request #8 from razzolini/rules-dsl
Implement a domain-specific language for the definition of algebraic manipulation rules
This commit is contained in:
commit
fb43f00485
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
@ -46,8 +46,8 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -60,13 +60,15 @@ public interface Platform {
|
||||
|
||||
String[] stacktraceToString(Error e);
|
||||
|
||||
void loadPlatformRules();
|
||||
|
||||
void zip(String targetPath, String destinationFilePath, String password);
|
||||
|
||||
void unzip(String targetZipFilePath, String destinationFolderPath, String password);
|
||||
|
||||
boolean compile(String[] command, PrintWriter printWriter, PrintWriter errors);
|
||||
/**
|
||||
* Determines the list of files containing DSL rules to load.
|
||||
*
|
||||
* @return a <code>List</code> of paths of files which contain DSL rules.
|
||||
* Each <code>String</code> in the returned <code>List</code> can be passed as an argument to
|
||||
* {@link StorageUtils#getResourceStream(String)} to access the corresponding file's contents.
|
||||
* @throws IOException if an IO error occurs while getting the list of rule file paths.
|
||||
*/
|
||||
List<String> getRuleFilePaths() throws IOException;
|
||||
|
||||
public interface Gpio {
|
||||
int valueOutput();
|
||||
|
@ -1,6 +1,12 @@
|
||||
package it.cavallium.warppi.math;
|
||||
|
||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
||||
import it.cavallium.warppi.math.functions.*;
|
||||
import it.cavallium.warppi.math.functions.Number;
|
||||
import it.cavallium.warppi.math.functions.equations.Equation;
|
||||
import it.cavallium.warppi.math.functions.equations.EquationsSystem;
|
||||
import it.cavallium.warppi.math.functions.equations.EquationsSystemPart;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.*;
|
||||
import it.cavallium.warppi.math.rules.Rule;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
@ -15,6 +21,16 @@ public interface Function {
|
||||
@Override
|
||||
String toString();
|
||||
|
||||
/**
|
||||
* Indicates whether some other object is a <code>Function</code> which is <em>identical</em> to this one.
|
||||
* <p>
|
||||
* Two functions which are not identical, but only equivalent (due to, for example, the commutative property) aren't
|
||||
* considered equal.
|
||||
*
|
||||
* @param o the other object to compare.
|
||||
* @return <code>true</code> if the other object is identical to this <code>Function</code>, <code>false</code>
|
||||
* otherwise.
|
||||
*/
|
||||
@Override
|
||||
boolean equals(Object o);
|
||||
|
||||
@ -80,4 +96,49 @@ public interface Function {
|
||||
* @throws Error
|
||||
*/
|
||||
ObjectArrayList<Block> toBlock(MathContext context) throws Error;
|
||||
|
||||
/**
|
||||
* Accepts a {@code Function.Visitor<Argument, Result>} by calling the correct overload of <code>visit</code>.
|
||||
*
|
||||
* @param visitor The visitor to be accepted.
|
||||
* @param argument An additional argument to be passed to <code>visit</code>.
|
||||
* @param <Argument> The type of an additional argument to be passed to the <code>visit</code> method.
|
||||
* @param <Result> The return type of the <code>visit</code> method.
|
||||
* @return The value returned by <code>visit</code>.
|
||||
*/
|
||||
<Argument, Result> Result accept(Visitor<Argument, Result> visitor, Argument argument);
|
||||
|
||||
/**
|
||||
* Executes a different overload of a method for each <code>Function</code> implementation.
|
||||
*
|
||||
* @param <Argument> The type of an additional argument which can be passed to all <code>visit</code> method overloads.
|
||||
* If the argument is not required, this type parameter should be set to {@link Void}.
|
||||
* @param <Result> The return type of all <code>visit</code> method overloads.
|
||||
*/
|
||||
interface Visitor<Argument, Result> {
|
||||
Result visit(ArcCosine arcCosine, Argument argument);
|
||||
Result visit(ArcSine arcSine, Argument argument);
|
||||
Result visit(ArcTangent arcTangent, Argument argument);
|
||||
Result visit(Cosine cosine, Argument argument);
|
||||
Result visit(Division division, Argument argument);
|
||||
Result visit(Equation equation, Argument argument);
|
||||
Result visit(EquationsSystem equationsSystem, Argument argument);
|
||||
Result visit(EquationsSystemPart equationsSystemPart, Argument argument);
|
||||
Result visit(Expression expression, Argument argument);
|
||||
Result visit(Joke joke, Argument argument);
|
||||
Result visit(Logarithm logarithm, Argument argument);
|
||||
Result visit(Multiplication multiplication, Argument argument);
|
||||
Result visit(Negative negative, Argument argument);
|
||||
Result visit(Number number, Argument argument);
|
||||
Result visit(Power power, Argument argument);
|
||||
Result visit(Root root, Argument argument);
|
||||
Result visit(RootSquare rootSquare, Argument argument);
|
||||
Result visit(Sine sine, Argument argument);
|
||||
Result visit(Subtraction subtraction, Argument argument);
|
||||
Result visit(SumSubtraction sumSubtraction, Argument argument);
|
||||
Result visit(Sum sum, Argument argument);
|
||||
Result visit(Tangent tangent, Argument argument);
|
||||
Result visit(Undefined undefined, Argument argument);
|
||||
Result visit(Variable variable, Argument argument);
|
||||
}
|
||||
}
|
||||
|
@ -136,11 +136,9 @@ public abstract class FunctionDynamic implements Function {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return functions.hashCode() + 883 * super.hashCode();
|
||||
return Arrays.hashCode(functions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
return false;
|
||||
}
|
||||
public abstract boolean equals(Object o);
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import it.cavallium.warppi.util.Errors;
|
||||
import it.cavallium.warppi.util.Utils;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class FunctionOperator implements Function {
|
||||
|
||||
/**
|
||||
@ -175,7 +177,7 @@ public abstract class FunctionOperator implements Function {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return parameter1.hashCode() + 7 * parameter2.hashCode() + 883 * super.hashCode();
|
||||
return Objects.hash(parameter1, parameter2);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -4,6 +4,8 @@ import it.cavallium.warppi.math.rules.Rule;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class FunctionSingle implements Function {
|
||||
|
||||
private boolean simplified;
|
||||
@ -117,7 +119,7 @@ public abstract class FunctionSingle implements Function {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return parameter.hashCode() + 883 * super.hashCode();
|
||||
return Objects.hash(parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -59,4 +59,9 @@ public class Division extends FunctionOperator {
|
||||
result.add(bd);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
@ -582,6 +582,11 @@ public class Expression extends FunctionSingle {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String s = "(";
|
||||
|
@ -61,4 +61,8 @@ public class Joke implements Function {
|
||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -55,4 +55,8 @@ public class Logarithm extends FunctionOperator {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,7 @@ package it.cavallium.warppi.math.functions;
|
||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||
import it.cavallium.warppi.gui.expression.blocks.BlockParenthesis;
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.FunctionOperator;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
||||
import it.cavallium.warppi.math.*;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
@ -24,11 +21,7 @@ public class Multiplication extends FunctionOperator {
|
||||
public boolean equals(final Object o) {
|
||||
if (o instanceof Multiplication) {
|
||||
final FunctionOperator f = (FunctionOperator) o;
|
||||
if (parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2())) {
|
||||
return true;
|
||||
} else if (parameter1.equals(f.getParameter2()) && parameter2.equals(f.getParameter1())) {
|
||||
return true;
|
||||
}
|
||||
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -87,6 +80,11 @@ public class Multiplication extends FunctionOperator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
|
||||
public boolean isNegative() {
|
||||
return parameter1.equals(new Number(getMathContext(), -1)) || parameter2.equals(new Number(getMathContext(), -1));
|
||||
}
|
||||
|
@ -3,10 +3,7 @@ package it.cavallium.warppi.math.functions;
|
||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||
import it.cavallium.warppi.gui.expression.blocks.BlockParenthesis;
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.FunctionSingle;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
||||
import it.cavallium.warppi.math.*;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
@ -52,4 +49,9 @@ public class Negative extends FunctionSingle {
|
||||
return blocks;
|
||||
// throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -273,6 +273,11 @@ public class Number implements Function {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function setParameter(final int index, final Function var) throws IndexOutOfBoundsException {
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
@ -50,4 +50,9 @@ public class Power extends FunctionOperator {
|
||||
result.add(bp);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -39,4 +39,8 @@ public class Root extends FunctionOperator {
|
||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ public class RootSquare extends FunctionOperator {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o instanceof Root) {
|
||||
if (o instanceof RootSquare) {
|
||||
final FunctionOperator f = (FunctionOperator) o;
|
||||
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
|
||||
}
|
||||
@ -48,4 +48,8 @@ public class RootSquare extends FunctionOperator {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,7 @@ package it.cavallium.warppi.math.functions;
|
||||
|
||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.FunctionOperator;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
||||
import it.cavallium.warppi.math.*;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
@ -43,4 +40,8 @@ public class Subtraction extends FunctionOperator {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
@ -2,10 +2,7 @@ package it.cavallium.warppi.math.functions;
|
||||
|
||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.FunctionOperator;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
||||
import it.cavallium.warppi.math.*;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
@ -19,11 +16,7 @@ public class Sum extends FunctionOperator {
|
||||
public boolean equals(final Object o) {
|
||||
if (o instanceof Sum) {
|
||||
final FunctionOperator f = (FunctionOperator) o;
|
||||
if (parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2())) {
|
||||
return true;
|
||||
} else if (parameter1.equals(f.getParameter2()) && parameter2.equals(f.getParameter1())) {
|
||||
return true;
|
||||
}
|
||||
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -47,4 +40,8 @@ public class Sum extends FunctionOperator {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,7 @@ package it.cavallium.warppi.math.functions;
|
||||
|
||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.FunctionOperator;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
||||
import it.cavallium.warppi.math.*;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
@ -43,4 +40,8 @@ public class SumSubtraction extends FunctionOperator {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ public class Undefined implements Function {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
return false;
|
||||
return o instanceof Undefined;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -58,6 +58,11 @@ public class Undefined implements Function {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UNDEFINED";
|
||||
|
@ -8,6 +8,8 @@ import it.cavallium.warppi.math.rules.Rule;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Variable implements Function {
|
||||
|
||||
protected char var;
|
||||
@ -84,7 +86,7 @@ public class Variable implements Function {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
return Objects.hash(var, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -131,4 +133,9 @@ public class Variable implements Function {
|
||||
result.add(new BlockChar(getChar()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,10 @@ public class Equation extends FunctionOperator {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
// TODO Auto-generated method stub
|
||||
if (o instanceof Equation) {
|
||||
final FunctionOperator f = (FunctionOperator) o;
|
||||
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -68,4 +71,8 @@ public class Equation extends FunctionOperator {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
@ -22,6 +22,26 @@ public class EquationsSystem extends FunctionDynamic {
|
||||
super(root, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof EquationsSystem)) {
|
||||
return false;
|
||||
}
|
||||
final FunctionDynamic f = (FunctionDynamic) o;
|
||||
|
||||
if (functions.length != f.getParametersLength()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < functions.length; i++) {
|
||||
if (!functions[i].equals(f.getParameter(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EquationsSystem clone() {
|
||||
Function[] newFuncs = functions.clone();
|
||||
@ -46,4 +66,8 @@ public class EquationsSystem extends FunctionDynamic {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package it.cavallium.warppi.math.functions.equations;
|
||||
|
||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.FunctionSingle;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
@ -34,4 +35,8 @@ public class EquationsSystemPart extends FunctionSingle {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ public class ArcCosine extends FunctionSingle {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
// TODO Auto-generated method stub
|
||||
if (o instanceof ArcCosine) {
|
||||
final FunctionSingle f = (FunctionSingle) o;
|
||||
return parameter.equals(f.getParameter());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -38,4 +41,9 @@ public class ArcCosine extends FunctionSingle {
|
||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ public class ArcSine extends FunctionSingle {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
// TODO Auto-generated method stub
|
||||
if (o instanceof ArcSine) {
|
||||
final FunctionSingle f = (FunctionSingle) o;
|
||||
return parameter.equals(f.getParameter());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -38,4 +41,8 @@ public class ArcSine extends FunctionSingle {
|
||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ public class ArcTangent extends FunctionSingle {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
// TODO Auto-generated method stub
|
||||
if (o instanceof ArcTangent) {
|
||||
final FunctionSingle f = (FunctionSingle) o;
|
||||
return parameter.equals(f.getParameter());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -38,4 +41,8 @@ public class ArcTangent extends FunctionSingle {
|
||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ public class Cosine extends FunctionSingle {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
// TODO Auto-generated method stub
|
||||
if (o instanceof Cosine) {
|
||||
final FunctionSingle f = (FunctionSingle) o;
|
||||
return parameter.equals(f.getParameter());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -38,4 +41,8 @@ public class Cosine extends FunctionSingle {
|
||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,7 @@ public class Sine extends FunctionSingle {
|
||||
public boolean equals(final Object o) {
|
||||
if (o instanceof Sine) {
|
||||
final FunctionSingle f = (FunctionSingle) o;
|
||||
if (parameter.equals(f.getParameter())) {
|
||||
return true;
|
||||
}
|
||||
return parameter.equals(f.getParameter());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -51,4 +49,8 @@ public class Sine extends FunctionSingle {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,10 @@ public class Tangent extends FunctionSingle {
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
// TODO Auto-generated method stub
|
||||
if (o instanceof Tangent) {
|
||||
final FunctionSingle f = (FunctionSingle) o;
|
||||
return parameter.equals(f.getParameter());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -37,4 +40,8 @@ public class Tangent extends FunctionSingle {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||
return visitor.visit(this, argument);
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,25 @@
|
||||
package it.cavallium.warppi.math.rules;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import it.cavallium.warppi.Engine;
|
||||
import it.cavallium.warppi.Platform;
|
||||
import it.cavallium.warppi.Platform.ConsoleUtils;
|
||||
import it.cavallium.warppi.Platform.StorageUtils;
|
||||
import it.cavallium.warppi.Platform.URLClassLoader;
|
||||
import it.cavallium.warppi.StaticVars;
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Expression;
|
||||
import it.cavallium.warppi.math.functions.Variable;
|
||||
import it.cavallium.warppi.math.functions.Variable.V_TYPE;
|
||||
import it.cavallium.warppi.math.rules.dsl.DslAggregateException;
|
||||
import it.cavallium.warppi.math.rules.dsl.RulesDsl;
|
||||
import it.cavallium.warppi.math.rules.dsl.errorutils.DslFilesException;
|
||||
import it.cavallium.warppi.math.rules.functions.*;
|
||||
import it.cavallium.warppi.math.solver.MathSolver;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class RulesManager {
|
||||
|
||||
public static ObjectArrayList<Rule>[] rules;
|
||||
@ -38,189 +33,69 @@ public class RulesManager {
|
||||
for (final RuleType val : RuleType.values()) {
|
||||
RulesManager.rules[val.ordinal()] = new ObjectArrayList<>();
|
||||
}
|
||||
|
||||
loadBuiltinRules();
|
||||
|
||||
try {
|
||||
boolean compiledSomething = false;
|
||||
InputStream defaultRulesList;
|
||||
try {
|
||||
defaultRulesList = Engine.getPlatform().getStorageUtils().getResourceStream("/default-rules.lst");
|
||||
} catch (final IOException ex) {
|
||||
throw new FileNotFoundException("default-rules.lst not found!");
|
||||
}
|
||||
final List<String> ruleLines = new ArrayList<>();
|
||||
final File rulesPath = Engine.getPlatform().getStorageUtils().get("rules/");
|
||||
if (rulesPath.exists()) {
|
||||
for (final File f : Engine.getPlatform().getStorageUtils().walk(rulesPath)) {
|
||||
if (f.toString().endsWith(".java")) {
|
||||
String path = Engine.getPlatform().getStorageUtils().relativize(rulesPath, f).toString();
|
||||
path = path.substring(0, path.length() - ".java".length());
|
||||
ruleLines.add(path);
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", "Found external rule: " + f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
ruleLines.addAll(Engine.getPlatform().getStorageUtils().readAllLines(defaultRulesList));
|
||||
|
||||
final File tDir = Engine.getPlatform().getStorageUtils().resolve(Engine.getPlatform().getStorageUtils().get(System.getProperty("java.io.tmpdir"), "WarpPi-Calculator"), "rules-rt");
|
||||
// try {
|
||||
// final Path defaultResource = Utils.getResource("/math-rules-cache.zip");
|
||||
// }
|
||||
InputStream cacheFileStream = null;
|
||||
File cacheFilePath = null;
|
||||
cacheFilePath = new File("math-rules-cache.zip");
|
||||
boolean cacheFileExists = false;
|
||||
if (Engine.getPlatform().isJavascript()) {
|
||||
Engine.getPlatform().loadPlatformRules();
|
||||
} else {
|
||||
if (cacheFilePath.exists()) {
|
||||
cacheFileExists = true;
|
||||
cacheFileStream = new FileInputStream(cacheFilePath);
|
||||
} else {
|
||||
try {
|
||||
cacheFileStream = Engine.getPlatform().getStorageUtils().getResourceStream("/math-rules-cache.zip");//Paths.get(Utils.getJarDirectory().toString()).resolve("math-rules-cache.zip").toAbsolutePath(
|
||||
org.apache.commons.io.FileUtils.copyInputStreamToFile(cacheFileStream, cacheFilePath);
|
||||
cacheFileExists = true;
|
||||
} catch (final IOException ex) { //File does not exists.
|
||||
}
|
||||
}
|
||||
boolean useCache = false;
|
||||
if (cacheFileExists) {
|
||||
try {
|
||||
if (tDir.exists()) {
|
||||
tDir.delete();
|
||||
}
|
||||
Engine.getPlatform().unzip(cacheFilePath.toString(), tDir.getParent().toString(), "");
|
||||
useCache = !StaticVars.startupArguments.isUncached();
|
||||
} catch (final Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
for (final String rulesLine : ruleLines) {
|
||||
if (rulesLine.length() > 0) {
|
||||
final String[] ruleDetails = rulesLine.split(",", 1);
|
||||
final String ruleName = ruleDetails[0];
|
||||
final String ruleNameEscaped = ruleName.replace(".", "_");
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", "Evaluating /rules/" + ruleNameEscaped + ".java");
|
||||
final String pathWithoutExtension = "/rules/" + ruleNameEscaped;
|
||||
final String scriptFile = pathWithoutExtension + ".java";
|
||||
final InputStream resourcePath = Engine.getPlatform().getStorageUtils().getResourceStream(scriptFile);
|
||||
if (resourcePath == null) {
|
||||
System.err.println(new FileNotFoundException("/rules/" + ruleName + ".java not found!"));
|
||||
} else {
|
||||
Rule r = null;
|
||||
if (useCache) {
|
||||
try {
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", ruleName, "Trying to load cached rule");
|
||||
r = RulesManager.loadClassRuleFromSourceFile(scriptFile, tDir);
|
||||
if (r != null) {
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", ruleName, "Loaded cached rule");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", ruleName, "Can't load the rule " + ruleNameEscaped + "!");
|
||||
}
|
||||
}
|
||||
if (r == null || !useCache) {
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", ruleName, "This rule is not cached. Compiling");
|
||||
try {
|
||||
r = RulesManager.compileJavaRule(scriptFile, tDir);
|
||||
compiledSomething = true;
|
||||
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
if (r != null) {
|
||||
RulesManager.addRule(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", "Loaded all the rules successfully");
|
||||
if (!Engine.getPlatform().isJavascript() && compiledSomething) {
|
||||
if (cacheFileExists || cacheFilePath.exists()) {
|
||||
cacheFilePath.delete();
|
||||
}
|
||||
Engine.getPlatform().zip(tDir.toString(), cacheFilePath.toString(), "");
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", "Cached the compiled rules");
|
||||
}
|
||||
if (cacheFileStream != null) {
|
||||
cacheFileStream.close();
|
||||
}
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
loadDslRules();
|
||||
} catch (IOException | DslFilesException e) {
|
||||
e.printStackTrace();
|
||||
if (e instanceof DslFilesException) {
|
||||
System.err.println();
|
||||
System.err.print(((DslFilesException) e).format());
|
||||
}
|
||||
Engine.getPlatform().exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static Rule compileJavaRule(final String scriptFile, final File tDir) throws IOException, URISyntaxException,
|
||||
InstantiationException, IllegalAccessException, ClassNotFoundException {
|
||||
final InputStream resource = Engine.getPlatform().getStorageUtils().getResourceStream(scriptFile);
|
||||
final String text = Engine.getPlatform().getStorageUtils().read(resource);
|
||||
final String[] textArray = text.split("\\n", 6);
|
||||
if (textArray[3].contains("PATH=")) {
|
||||
final String javaClassDeclaration = textArray[3].substring(6);
|
||||
int extIndex = javaClassDeclaration.lastIndexOf('.');
|
||||
final String javaClassNameOnly = javaClassDeclaration.substring(extIndex + 1, javaClassDeclaration.length());
|
||||
final String javaClassNameAndPath = new StringBuilder("it.cavallium.warppi.math.rules.").append(javaClassDeclaration).toString();
|
||||
extIndex = javaClassNameAndPath.lastIndexOf('.');
|
||||
final String javaCode = new StringBuilder("package ").append(javaClassNameAndPath.substring(0, extIndex >= 0 ? extIndex : javaClassNameAndPath.length())).append(";\n").append(textArray[5]).toString();
|
||||
final File tDirPath = Engine.getPlatform().getStorageUtils().getParent(Engine.getPlatform().getStorageUtils().resolve(tDir, javaClassNameAndPath.replace('.', File.separatorChar)));
|
||||
final File tFileJava = Engine.getPlatform().getStorageUtils().resolve(tDirPath, javaClassNameOnly + ".java");
|
||||
final File tFileClass = Engine.getPlatform().getStorageUtils().resolve(tDirPath, javaClassNameOnly + ".class");
|
||||
if (!tDirPath.exists()) {
|
||||
Engine.getPlatform().getStorageUtils().createDirectories(tDirPath);
|
||||
}
|
||||
if (tFileJava.exists()) {
|
||||
tFileJava.delete();
|
||||
}
|
||||
Engine.getPlatform().getStorageUtils();
|
||||
Engine.getPlatform().getStorageUtils();
|
||||
Engine.getPlatform().getStorageUtils().write(tFileJava, javaCode.getBytes("UTF-8"), StorageUtils.OpenOptionWrite, StorageUtils.OpenOptionCreate);
|
||||
final boolean compiled = Engine.getPlatform().compile(new String[] { "-nowarn", "-1.8", "-proc:none", tFileJava.toString() }, new PrintWriter(System.out), new PrintWriter(System.err));
|
||||
if (StaticVars.startupArguments.isUncached()) {
|
||||
tFileJava.deleteOnExit();
|
||||
} else {
|
||||
tFileJava.delete();
|
||||
}
|
||||
if (compiled) {
|
||||
tFileClass.deleteOnExit();
|
||||
return RulesManager.loadClassRuleDirectly(javaClassNameAndPath, tDir);
|
||||
} else {
|
||||
throw new IOException("Can't build script file '" + scriptFile + "'");
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Can't build script file '" + scriptFile + "', the header is missing or wrong.");
|
||||
}
|
||||
private static void loadBuiltinRules() {
|
||||
Stream.of(
|
||||
new DivisionRule(),
|
||||
new EmptyNumberRule(),
|
||||
new ExpressionRule(),
|
||||
new JokeRule(),
|
||||
new MultiplicationRule(),
|
||||
new NegativeRule(),
|
||||
new NumberRule(),
|
||||
new PowerRule(),
|
||||
new RootRule(),
|
||||
new SubtractionRule(),
|
||||
new SumRule(),
|
||||
new SumSubtractionRule(),
|
||||
new VariableRule()
|
||||
).forEach(RulesManager::addRule);
|
||||
}
|
||||
|
||||
public static Rule loadClassRuleFromSourceFile(final String scriptFile, final File tDir) throws IOException,
|
||||
URISyntaxException, InstantiationException, IllegalAccessException, ClassNotFoundException {
|
||||
final InputStream resource = Engine.getPlatform().getStorageUtils().getResourceStream(scriptFile);
|
||||
final String text = Engine.getPlatform().getStorageUtils().read(resource);
|
||||
final String[] textArray = text.split("\\n", 6);
|
||||
if (textArray[3].contains("PATH=")) {
|
||||
final String javaClassName = textArray[3].substring(6);
|
||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_VERBOSE, "RulesManager", "Rule java class name: " + javaClassName);
|
||||
final String javaClassNameAndPath = new StringBuilder("it.cavallium.warppi.math.rules.").append(javaClassName).toString();
|
||||
private static void loadDslRules() throws IOException, DslFilesException {
|
||||
final Platform platform = Engine.getPlatform();
|
||||
|
||||
final DslFilesException fileErrors = new DslFilesException();
|
||||
for (final String path : platform.getRuleFilePaths()) {
|
||||
platform.getConsoleUtils().out().println(
|
||||
ConsoleUtils.OUTPUTLEVEL_NODEBUG,
|
||||
"RulesManager",
|
||||
"Found DSL rules file: " + path
|
||||
);
|
||||
|
||||
final String source;
|
||||
try (final InputStream resource = platform.getStorageUtils().getResourceStream(path)) {
|
||||
source = platform.getStorageUtils().read(resource);
|
||||
}
|
||||
|
||||
try {
|
||||
return RulesManager.loadClassRuleDirectly(javaClassNameAndPath, tDir);
|
||||
} catch (final Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
// This loop used to be written as RulesDsl.makeRules(source).forEach(RulesManager::addRule),
|
||||
// but the forEach method hangs on TeaVM.
|
||||
for (Rule rule : RulesDsl.makeRules(source)) {
|
||||
addRule(rule);
|
||||
}
|
||||
} catch (DslAggregateException e) {
|
||||
fileErrors.addFileErrors(path, source, e.getErrors());
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Can't load script file '" + scriptFile + "', the header is missing or wrong.");
|
||||
}
|
||||
}
|
||||
|
||||
public static Rule loadClassRuleDirectly(final String javaClassNameAndPath, final File tDir) throws IOException,
|
||||
URISyntaxException, InstantiationException, IllegalAccessException, ClassNotFoundException {
|
||||
final URLClassLoader cl = Engine.getPlatform().newURLClassLoader(new URL[] { tDir.toURI().toURL() });
|
||||
final Class<?> aClass = cl.loadClass(javaClassNameAndPath);
|
||||
cl.close();
|
||||
return (Rule) aClass.newInstance();
|
||||
if (fileErrors.hasErrors()) {
|
||||
throw fileErrors;
|
||||
}
|
||||
}
|
||||
|
||||
public static void warmUp() throws Error, InterruptedException {
|
||||
|
@ -0,0 +1,32 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Thrown when processing DSL code which contains one or more errors.
|
||||
*
|
||||
* Contains a non-empty list of {@link DslError}s.
|
||||
*/
|
||||
public class DslAggregateException extends Exception {
|
||||
private final List<DslError> errors;
|
||||
|
||||
/**
|
||||
* Constructs a <code>DslAggregateException</code> containing the specified list of errors.
|
||||
* @param errors The (non-empty) list of errors.
|
||||
* @throws IllegalArgumentException If the list of errors is empty.
|
||||
*/
|
||||
public DslAggregateException(final List<DslError> errors) {
|
||||
if (errors.isEmpty()) {
|
||||
throw new IllegalArgumentException("The list of errors can't be empty");
|
||||
}
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of errors detected in the DSL code.
|
||||
*/
|
||||
public List<DslError> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.IncompleteNumberLiteral;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.UnexpectedCharacters;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.UnexpectedToken;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.UnterminatedComment;
|
||||
|
||||
/**
|
||||
* Represents an error in DSL code.
|
||||
*/
|
||||
public interface DslError {
|
||||
/**
|
||||
* @return The index at which the error starts in the source string.
|
||||
*/
|
||||
int getPosition();
|
||||
|
||||
/**
|
||||
* @return The length of the error in the source string.
|
||||
*/
|
||||
int getLength();
|
||||
|
||||
/**
|
||||
* Accepts a <code>DslError.Visitor</code> by calling the correct overload of <code>visit</code>.
|
||||
*
|
||||
* @param visitor The visitor to be accepted.
|
||||
* @param <T> The return type of the <code>visit</code> method.
|
||||
* @return The value returned by <code>visit</code>.
|
||||
*/
|
||||
<T> T accept(Visitor<T> visitor);
|
||||
|
||||
/**
|
||||
* Executes a different overload of a method for each <code>DslError</code> implementation.
|
||||
*
|
||||
* @param <T> The return type of all <code>visit</code> method overloads.
|
||||
*/
|
||||
interface Visitor<T> {
|
||||
T visit(IncompleteNumberLiteral incompleteNumberLiteral);
|
||||
T visit(UndefinedSubFunction undefinedSubFunction);
|
||||
T visit(UnexpectedCharacters unexpectedCharacters);
|
||||
T visit(UnexpectedToken unexpectedToken);
|
||||
T visit(UnterminatedComment unterminatedComment);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.SubFunctionPattern;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Recognizes and generates functions of some specific shape.
|
||||
*/
|
||||
public interface Pattern {
|
||||
/**
|
||||
* Tries to match this pattern against a function and capture sub-functions.
|
||||
*
|
||||
* @param function The function to test the pattern against.
|
||||
* @return The captured sub-functions, or an empty <code>Optional</code> if
|
||||
* the pattern doesn't match.
|
||||
*/
|
||||
default Optional<Map<String, Function>> match(Function function) {
|
||||
Map<String, Function> subFunctions = new HashMap<>();
|
||||
return match(function, subFunctions) ? Optional.of(subFunctions) : Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to match this pattern against a function and capture sub-functions.
|
||||
* <p>
|
||||
* This overload is provided to allow for a more efficient implementation of matching, by mutating the given
|
||||
* <code>Map</code> instead of creating and merging multiple ones.
|
||||
* For all other purposes, use of the {@link #match(Function)} overload is recommended instead.
|
||||
* <p>
|
||||
* When the pattern matches, all captured sub-functions are added to the map (if not present already).
|
||||
* If, instead, the pattern doesn't match, the contents of the map are undefined.
|
||||
*
|
||||
* @param function The function to test the pattern against.
|
||||
* @param subFunctions The map used to capture sub-functions.
|
||||
* @return <code>true</code> if the pattern matches, or <code>false</code> otherwise.
|
||||
*/
|
||||
boolean match(Function function, Map<String, Function> subFunctions);
|
||||
|
||||
/**
|
||||
* Creates a new function by filling in sub-functions within this pattern.
|
||||
*
|
||||
* @param mathContext The <code>MathContext</code> used to construct <code>Function</code>s.
|
||||
* @param subFunctions A map of named sub-functions to be inserted into this pattern.
|
||||
* @return The resulting function.
|
||||
*/
|
||||
Function replace(MathContext mathContext, Map<String, Function> subFunctions);
|
||||
|
||||
/**
|
||||
* @return A (possibly empty) <code>Stream</code> of all sub-function patterns
|
||||
* found within this pattern and its children.
|
||||
* If there are multiple sub-function patterns with the same name, the stream still contains all of them.
|
||||
* The order of the patterns within the stream is unspecified.
|
||||
*/
|
||||
Stream<SubFunctionPattern> getSubFunctions();
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.rules.Rule;
|
||||
import it.cavallium.warppi.math.rules.RuleType;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A <code>Rule</code> which uses <code>Pattern</code>s to match and replace functions.
|
||||
*/
|
||||
public class PatternRule implements Rule {
|
||||
private final String ruleName;
|
||||
private final RuleType ruleType;
|
||||
private final Pattern target;
|
||||
private final List<Pattern> replacements;
|
||||
|
||||
/**
|
||||
* Constructs a <code>PatternRule</code> with the given name, type and <code>Pattern</code>s.
|
||||
*
|
||||
* @param ruleName the name of the rule.
|
||||
* @param ruleType the type of the rule.
|
||||
* @param target the <code>Pattern</code> used to match functions and capture sub-functions.
|
||||
* @param replacements the list of <code>Pattern</code>s used to construct replacement functions.
|
||||
* All sub-functions which are referenced within these <code>Pattern</code>s must be captured
|
||||
* by <code>target</code>, otherwise the {@link #execute} method will throw an
|
||||
* {@link UndefinedSubFunction} exception when constructing the replacement functions.
|
||||
*/
|
||||
public PatternRule(
|
||||
final String ruleName,
|
||||
final RuleType ruleType,
|
||||
final Pattern target,
|
||||
final List<Pattern> replacements
|
||||
) {
|
||||
this.ruleName = ruleName;
|
||||
this.ruleType = ruleType;
|
||||
this.target = target;
|
||||
this.replacements = replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a <code>PatternRule</code> with the given name, type and <code>Pattern</code>s.
|
||||
*
|
||||
* @param ruleName the name of the rule.
|
||||
* @param ruleType the type of the rule.
|
||||
* @param target the <code>Pattern</code> used to match functions and capture sub-functions.
|
||||
* @param replacements the <code>Pattern</code>s used to construct replacement functions.
|
||||
* All sub-functions which are referenced within these <code>Pattern</code>s must be captured
|
||||
* by <code>target</code>, otherwise the {@link #execute} method will throw an
|
||||
* {@link UndefinedSubFunction} exception when constructing the replacement functions.
|
||||
*/
|
||||
public PatternRule(
|
||||
final String ruleName,
|
||||
final RuleType ruleType,
|
||||
final Pattern target,
|
||||
final Pattern... replacements
|
||||
) {
|
||||
this(ruleName, ruleType, target, Arrays.asList(replacements));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRuleName() {
|
||||
return ruleName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuleType getRuleType() {
|
||||
return ruleType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the <code>Pattern</code> used to match functions and capture sub-functions.
|
||||
*/
|
||||
public Pattern getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of <code>Pattern</code>s used to construct replacement functions.
|
||||
*/
|
||||
public List<Pattern> getReplacements() {
|
||||
return replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UndefinedSubFunctionException if the target pattern matches, but it doesn't capture all of the
|
||||
* sub-functions required by the replacement patterns.
|
||||
* This exception will never be thrown for well-formed rules (like the ones
|
||||
* returned by {@link RulesDsl#makeRules}), in which the target pattern
|
||||
* correctly captures all sub-functions referenced by the replacement patterns.
|
||||
*/
|
||||
@Override
|
||||
public ObjectArrayList<Function> execute(final Function func) {
|
||||
return target.match(func)
|
||||
.map(subFunctions -> applyReplacements(func.getMathContext(), subFunctions))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private ObjectArrayList<Function> applyReplacements(
|
||||
final MathContext mathContext,
|
||||
final Map<String, Function> subFunctions
|
||||
) {
|
||||
return replacements.stream()
|
||||
.map(replacement -> replacement.replace(mathContext, subFunctions))
|
||||
.collect(Collectors.toCollection(ObjectArrayList::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof PatternRule)) {
|
||||
return false;
|
||||
}
|
||||
final PatternRule other = (PatternRule) o;
|
||||
return ruleName.equals(other.ruleName)
|
||||
&& ruleType == other.ruleType
|
||||
&& target.equals(other.target)
|
||||
&& replacements.equals(other.replacements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(ruleName, ruleType, target, replacements);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.rules.Rule;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.Lexer;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.Parser;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.SubFunctionPattern;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Implements a domain-specific language (DSL) for the definition of {@link Rule}s.
|
||||
*/
|
||||
public class RulesDsl {
|
||||
private RulesDsl() {}
|
||||
|
||||
/**
|
||||
* Creates rules from DSL source code.
|
||||
*
|
||||
* @param source The source code.
|
||||
* @return An unmodifiable list containing the rules defined in the DSL code.
|
||||
* @throws DslAggregateException if the code contains any errors.
|
||||
*/
|
||||
public static List<Rule> makeRules(final String source) throws DslAggregateException {
|
||||
final List<DslError> errors = new ArrayList<>();
|
||||
|
||||
final Lexer lexer = new Lexer(source, errors::add);
|
||||
final Parser parser = new Parser(lexer.lex(), errors::add);
|
||||
final List<PatternRule> rules = parser.parse();
|
||||
|
||||
for (final PatternRule rule : rules) {
|
||||
undefinedSubFunctions(rule)
|
||||
.map(subFunc -> new UndefinedSubFunction(
|
||||
parser.getSubFunctionIdentifier(subFunc)
|
||||
))
|
||||
.forEach(errors::add);
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
throw new DslAggregateException(errors);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds any sub-functions that are used in the replacement patterns of a <code>PatternRule</code>
|
||||
* without being defined (captured) in the target pattern.
|
||||
*
|
||||
* @param rule The rule to analyze.
|
||||
* @return A (possibly empty) <code>Stream</code> of undefined sub-functions.
|
||||
*/
|
||||
private static Stream<SubFunctionPattern> undefinedSubFunctions(final PatternRule rule) {
|
||||
final Set<SubFunctionPattern> defined = rule.getTarget().getSubFunctions().collect(Collectors.toSet());
|
||||
return rule.getReplacements().stream()
|
||||
.flatMap(Pattern::getSubFunctions)
|
||||
.filter(subFunc -> !defined.contains(subFunc));
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.Token;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Occurs when a sub-function is used in one of the replacement pattern of a <code>PatternRule</code>,
|
||||
* but not defined (captured) in the target pattern.
|
||||
*/
|
||||
public class UndefinedSubFunction implements DslError {
|
||||
private final Token identifier;
|
||||
|
||||
public UndefinedSubFunction(final Token identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return identifier.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return identifier.lexeme.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(final DslError.Visitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The name of the undefined sub-function.
|
||||
*/
|
||||
public String getName() {
|
||||
return identifier.lexeme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof UndefinedSubFunction)) {
|
||||
return false;
|
||||
}
|
||||
final UndefinedSubFunction other = (UndefinedSubFunction) o;
|
||||
return this.identifier.equals(other.identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(identifier);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
/**
|
||||
* Thrown when a <code>SubFunctionPattern</code> is used to generate a <code>Function</code>, but the named sub-function
|
||||
* it references is not defined.
|
||||
*/
|
||||
public class UndefinedSubFunctionException extends RuntimeException {
|
||||
private final String subFunctionName;
|
||||
|
||||
/**
|
||||
* Constructs an <code>UndefinedSubFunction</code> instance with the specified sub-function name.
|
||||
*
|
||||
* @param subFunctionName the name of the undefined sub-function.
|
||||
*/
|
||||
public UndefinedSubFunctionException(final String subFunctionName) {
|
||||
super("Sub-function '" + subFunctionName + "' is not defined");
|
||||
this.subFunctionName = subFunctionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the undefined sub-function.
|
||||
*/
|
||||
public String getSubFunctionName() {
|
||||
return subFunctionName;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.functions.*;
|
||||
import it.cavallium.warppi.math.functions.Number;
|
||||
import it.cavallium.warppi.math.functions.equations.Equation;
|
||||
import it.cavallium.warppi.math.functions.equations.EquationsSystem;
|
||||
import it.cavallium.warppi.math.functions.equations.EquationsSystemPart;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A <code>Pattern</code> which implements <code>match</code> as a visitor.
|
||||
*/
|
||||
public abstract class VisitorPattern implements Pattern, Function.Visitor<Map<String, Function>, Boolean> {
|
||||
@Override
|
||||
public boolean match(Function function, Map<String, Function> subFunctions) {
|
||||
return function.accept(this, subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final ArcCosine arcCosine, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final ArcSine arcSine, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final ArcTangent arcTangent, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Cosine cosine, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Division division, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Equation equation, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final EquationsSystem equationsSystem, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final EquationsSystemPart equationsSystemPart, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Expression expression, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Joke joke, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Logarithm logarithm, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Multiplication multiplication, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Negative negative, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Number number, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Power power, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Root root, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final RootSquare rootSquare, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Sine sine, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Subtraction subtraction, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final SumSubtraction sumSubtraction, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Sum sum, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Tangent tangent, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Undefined undefined, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Variable variable, final Map<String, Function> subFunctions) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.errorutils;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
import it.cavallium.warppi.math.rules.dsl.UndefinedSubFunction;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.*;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Creates human-readable error messages from instances of {@link DslError}.
|
||||
*/
|
||||
public class DslErrorMessageFormatter implements DslError.Visitor<String> {
|
||||
/**
|
||||
* Formats the given <code>DslError</code> as a human-readable message, according to its type.
|
||||
*
|
||||
* @param error The error to format.
|
||||
* @return One or more lines of text which describe the error (without a trailing newline).
|
||||
*/
|
||||
public String format(final DslError error) {
|
||||
return error.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visit(final IncompleteNumberLiteral incompleteNumberLiteral) {
|
||||
return String.format(
|
||||
"Number has a decimal point but no digits following it: %s",
|
||||
incompleteNumberLiteral.getLiteral()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visit(final UndefinedSubFunction undefinedSubFunction) {
|
||||
return String.format(
|
||||
"Sub-function \"%s\" is used in a replacement pattern,\nbut not defined in the target pattern",
|
||||
undefinedSubFunction.getName()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visit(final UnexpectedCharacters unexpectedCharacters) {
|
||||
final String plural = unexpectedCharacters.getLength() > 1 ? "s" : "";
|
||||
return String.format("Unexpected character%s: %s", plural, unexpectedCharacters.getUnexpectedCharacters());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visit(final UnexpectedToken unexpectedToken) {
|
||||
final String suggestions;
|
||||
if (unexpectedToken.getSuggested().isEmpty()) {
|
||||
suggestions = "";
|
||||
} else {
|
||||
suggestions = "\nSome suggestions are: " + unexpectedToken.getSuggested().stream()
|
||||
.map(DslErrorMessageFormatter::tokenTypeName)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
return String.format(
|
||||
"Unexpected %s%s",
|
||||
tokenTypeName(unexpectedToken.getUnexpected().type),
|
||||
suggestions
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visit(final UnterminatedComment unterminatedComment) {
|
||||
return "Unterminated comment";
|
||||
}
|
||||
|
||||
private static String tokenTypeName(final TokenType type) {
|
||||
switch (type) {
|
||||
case EOF:
|
||||
return "end of input";
|
||||
case COLON:
|
||||
return "\":\"";
|
||||
case ARROW:
|
||||
return "\"->\"";
|
||||
case COMMA:
|
||||
return "\",\"";
|
||||
case LEFT_PAREN:
|
||||
return "\"(\"";
|
||||
case RIGHT_PAREN:
|
||||
return "\")\"";
|
||||
case LEFT_BRACKET:
|
||||
return "\"[\"";
|
||||
case RIGHT_BRACKET:
|
||||
return "\"]\"";
|
||||
case EQUALS:
|
||||
return "\"=\"";
|
||||
case PLUS:
|
||||
return "\"+\"";
|
||||
case MINUS:
|
||||
return "\"-\"";
|
||||
case PLUS_MINUS:
|
||||
return "\"+-\"";
|
||||
case TIMES:
|
||||
return "\"*\"";
|
||||
case DIVIDE:
|
||||
return "\"/\"";
|
||||
case POWER:
|
||||
return "\"^\"";
|
||||
case REDUCTION:
|
||||
case EXPANSION:
|
||||
case CALCULATION:
|
||||
case EXISTENCE:
|
||||
case ARCCOS:
|
||||
case ARCSIN:
|
||||
case ARCTAN:
|
||||
case COS:
|
||||
case SIN:
|
||||
case TAN:
|
||||
case ROOT:
|
||||
case SQRT:
|
||||
case LOG:
|
||||
case UNDEFINED:
|
||||
case PI:
|
||||
case E:
|
||||
return '"' + type.name().toLowerCase() + '"';
|
||||
case NUMBER:
|
||||
return "number";
|
||||
case IDENTIFIER:
|
||||
return "identifier";
|
||||
}
|
||||
throw new RuntimeException("unknown token type");
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.errorutils;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Thrown when one or more DSL source files contain errors.
|
||||
*/
|
||||
public class DslFilesException extends Exception {
|
||||
private final List<FileErrors> filesErrors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Registers errors which have been found in the specified DSL source file.
|
||||
*
|
||||
* @param filePath The path of the DSL source file in which the errors occurred.
|
||||
* @param source The entire contents of the DSL source file in which the errors occurred.
|
||||
* @param errors The (non-empty) list of errors found in the DSL source file.
|
||||
* @throws IllegalArgumentException If the list of errors is empty.
|
||||
*/
|
||||
public void addFileErrors(final String filePath, final String source, final List<DslError> errors) {
|
||||
filesErrors.add(new FileErrors(filePath, source, errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any errors have been registered.
|
||||
* <p>
|
||||
* Instances of this class should only be thrown as exceptions if they actually contain errors.
|
||||
*
|
||||
* @return <code>true</code> if at least one error has been added, otherwise <code>false</code>.
|
||||
*/
|
||||
public boolean hasErrors() {
|
||||
return !filesErrors.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats all errors using a {@link FilesErrorsFormatter}.
|
||||
*
|
||||
* @return A formatted representation of all errors for display to the user.
|
||||
* @see FilesErrorsFormatter#format(List)
|
||||
*/
|
||||
public String format() {
|
||||
return new FilesErrorsFormatter().format(filesErrors);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.errorutils;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Groups one or more errors from the same DSL source file.
|
||||
* <p>
|
||||
* Also stores the file's path and contents (for error reporting).
|
||||
*/
|
||||
public class FileErrors {
|
||||
private final String filePath;
|
||||
private final String source;
|
||||
private final List<DslError> errors;
|
||||
|
||||
/**
|
||||
* Constructs a <code>FileErrors</code> instance with the given file and error data.
|
||||
*
|
||||
* @param filePath The path of the DSL source file in which the errors occurred.
|
||||
* @param source The entire contents of the DSL source file in which the errors occurred.
|
||||
* @param errors The (non-empty) list of errors found in the DSL source file.
|
||||
* @throws IllegalArgumentException If the list of errors is empty.
|
||||
*/
|
||||
public FileErrors(final String filePath, final String source, final List<DslError> errors) {
|
||||
if (errors.isEmpty()) {
|
||||
throw new IllegalArgumentException("The list of errors can't be empty");
|
||||
}
|
||||
this.filePath = filePath;
|
||||
this.source = source;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The path of the DSL source file in which the errors occurred.
|
||||
*/
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The entire contents of the DSL source file in which the errors occurred.
|
||||
*/
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of errors found in the DSL source file.
|
||||
*/
|
||||
public List<DslError> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.errorutils;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Formats DSL errors from (potentially) multiple files for display to the user.
|
||||
*/
|
||||
public class FilesErrorsFormatter {
|
||||
private static final int INDENT = 2;
|
||||
private static final int TAB_WIDTH = 4;
|
||||
|
||||
private final DslErrorMessageFormatter messageFormatter = new DslErrorMessageFormatter();
|
||||
|
||||
/**
|
||||
* Formats all errors in the given list.
|
||||
*
|
||||
* @param filesErrors The list of errors to format.
|
||||
* @return A human-readable textual representation of all errors (with a trailing newline).
|
||||
*/
|
||||
public String format(final List<FileErrors> filesErrors) {
|
||||
return filesErrors.stream()
|
||||
.sorted(Comparator.comparing(FileErrors::getFilePath))
|
||||
.flatMap(this::formatFileErrors)
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
|
||||
private Stream<String> formatFileErrors(final FileErrors fileErrors) {
|
||||
final LineMap lines = new LineMap(fileErrors.getSource());
|
||||
return fileErrors.getErrors().stream()
|
||||
.sorted(Comparator.comparing(DslError::getPosition).thenComparing(DslError::getLength))
|
||||
.map(error -> formatError(fileErrors.getFilePath(), lines, error));
|
||||
}
|
||||
|
||||
private String formatError(final String filePath, final LineMap lines, final DslError error) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
final List<LineMap.Line> spannedLines = lines.getSpannedLines(error.getPosition(), error.getLength());
|
||||
final LineMap.Line firstLine = spannedLines.get(0);
|
||||
|
||||
final int positionInFirstLine = error.getPosition() - firstLine.getStartPosition();
|
||||
final TabExpandedString expandedFirstLine = new TabExpandedString(firstLine.getText(), TAB_WIDTH);
|
||||
// When computing the column number, each tab character is counted as the number of spaces it expands to
|
||||
final int column = 1 + expandedFirstLine.substringLength(0, positionInFirstLine);
|
||||
|
||||
builder.append(filePath).append(":")
|
||||
.append(firstLine.getNumber()).append(":")
|
||||
.append(column).append(":")
|
||||
.append(System.lineSeparator());
|
||||
|
||||
final int lastLineNum = spannedLines.get(spannedLines.size() - 1).getNumber();
|
||||
final int numberWidth = String.valueOf(lastLineNum).length();
|
||||
final int padding = INDENT + numberWidth;
|
||||
|
||||
// Preceding line with just separator
|
||||
builder.append(StringUtils.repeat(' ', padding))
|
||||
.append(" |")
|
||||
.append(System.lineSeparator());
|
||||
|
||||
for (final LineMap.Line line : spannedLines) {
|
||||
// Error text line
|
||||
final TabExpandedString expanded = new TabExpandedString(line.getText(), TAB_WIDTH);
|
||||
builder.append(StringUtils.leftPad(String.valueOf(line.getNumber()), padding))
|
||||
.append(" | ")
|
||||
.append(expanded.getExpanded())
|
||||
.append(System.lineSeparator());
|
||||
|
||||
// Error underlining line
|
||||
builder.append(StringUtils.repeat(' ', padding)).append(" | ");
|
||||
underline(builder, line, expanded, error);
|
||||
builder.append(System.lineSeparator());
|
||||
}
|
||||
|
||||
builder.append(messageFormatter.format(error)).append(System.lineSeparator());
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void underline(
|
||||
final StringBuilder builder,
|
||||
final LineMap.Line line,
|
||||
final TabExpandedString expanded,
|
||||
final DslError error
|
||||
) {
|
||||
final int errorStartInLine = Math.max(line.getStartPosition(), error.getPosition());
|
||||
final int charsBeforeError = errorStartInLine - line.getStartPosition();
|
||||
final int spacesBeforeError = expanded.substringLength(0, charsBeforeError);
|
||||
builder.append(StringUtils.repeat(' ', spacesBeforeError));
|
||||
|
||||
final int underlineLength;
|
||||
if (error.getLength() == 0) {
|
||||
underlineLength = 1; // Special case for "unexpected EOF" error
|
||||
} else {
|
||||
final int errorEnd = error.getPosition() + error.getLength();
|
||||
final int errorLengthInLine = Math.min(line.getText().length() - charsBeforeError, errorEnd - errorStartInLine);
|
||||
underlineLength = expanded.substringLength(charsBeforeError, charsBeforeError + errorLengthInLine);
|
||||
}
|
||||
builder.append(StringUtils.repeat('^', underlineLength));
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.errorutils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Splits a string into lines and associates positions within the string to the lines they belong to.
|
||||
* <p>
|
||||
* For each line, the number (starting from 1), start position and content are stored.
|
||||
* <p>
|
||||
* A line can end at the end of the string, or with a line terminator ("\r", "\n" or "\r\n").
|
||||
* The terminator defines the end of a line, but not necessarily the beginning of a new one: it's considered to be part
|
||||
* of the line (however, for convenience, it's not included in the content), and a terminator at the end of the string
|
||||
* doesn't start a new empty line.
|
||||
* For example, the string <code>"abc\n\n"</code> contains two lines:
|
||||
* <ul>
|
||||
* <li> line 1 starts at position 0, and its content is <code>"abc"</code>;
|
||||
* <li> line 2 starts at position 4 (the index of the second '\n'), and its content is <code>""</code> (the empty string).
|
||||
* </ul>
|
||||
* As a consequence of these criteria, an empty string has no lines.
|
||||
*/
|
||||
public class LineMap {
|
||||
private final String text;
|
||||
private final NavigableMap<Integer, LineInfo> lines;
|
||||
|
||||
/**
|
||||
* Constructs a <code>LineMap</code> for the given string.
|
||||
*
|
||||
* @param text The string to split into lines.
|
||||
*/
|
||||
public LineMap(final String text) {
|
||||
this.text = text;
|
||||
this.lines = splitLines(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all lines which the specified substring spans.
|
||||
* <p>
|
||||
* A substring spans a line if it contains at least one of the characters which belong to the line,
|
||||
* including the terminator ("\r", "\n" or "\r\n"), within the original string.
|
||||
* However, as a special case, an empty substring (<code>length == 0</code>) still spans the line corresponding to
|
||||
* its <code>startPosition</code>, even though it doesn't contain any characters.
|
||||
* Therefore, any substring spans at least one line, unless there are no lines at all (because the original string
|
||||
* is empty).
|
||||
*
|
||||
* @param startPosition The index at which the substring starts within the original string.
|
||||
* @param length The length of the substring within the original string.
|
||||
* @return The (potentially empty) list of spanned lines (each one without the terminator characters).
|
||||
* @throws StringIndexOutOfBoundsException If the specified substring isn't valid, because:
|
||||
* <ul>
|
||||
* <li> <code>startPosition</code> is negative, or
|
||||
* <li> <code>startPosition</code> is larger than the length of the original string, or
|
||||
* <li> <code>length</code> is negative, or
|
||||
* <li> there are less than <code>length</code> characters from <code>startPosition</code>
|
||||
* to the end of the original string.
|
||||
* </ul>
|
||||
*/
|
||||
public List<Line> getSpannedLines(final int startPosition, final int length) {
|
||||
if (startPosition < 0 || startPosition > text.length()) {
|
||||
throw new StringIndexOutOfBoundsException("Substring start position out of range: " + startPosition);
|
||||
}
|
||||
int endPosition = startPosition + length;
|
||||
if (endPosition < startPosition || endPosition > text.length()) {
|
||||
throw new StringIndexOutOfBoundsException("Substring length out of range: " + length);
|
||||
}
|
||||
|
||||
if (lines.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Map.Entry<Integer, LineInfo> firstSpannedLine = lines.floorEntry(startPosition);
|
||||
if (length == 0) {
|
||||
// For empty substrings, firstSpannedLine.getKey() may be equal to endPosition.
|
||||
// In this case, the submap would be empty (because the upper bound is exclusive),
|
||||
// so the single spanned line has to be returned manually.
|
||||
return Collections.singletonList(lineFromMapEntry(firstSpannedLine));
|
||||
}
|
||||
final SortedMap<Integer, LineInfo> spannedLines = lines.subMap(firstSpannedLine.getKey(), endPosition);
|
||||
return spannedLines.entrySet().stream()
|
||||
.map(this::lineFromMapEntry)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static NavigableMap<Integer, LineInfo> splitLines(final String string) {
|
||||
final TreeMap<Integer, LineInfo> lines = new TreeMap<>();
|
||||
|
||||
int lineNum = 1;
|
||||
int lineStart = 0;
|
||||
int pos = 0;
|
||||
while (pos < string.length()) {
|
||||
final char cur = string.charAt(pos);
|
||||
int nextPos = pos + 1;
|
||||
if (nextPos < string.length() && cur == '\r' && string.charAt(nextPos) == '\n') {
|
||||
nextPos++; // Skip \n after \r because \r\n is a single line separator
|
||||
}
|
||||
if (cur == '\r' || cur == '\n') {
|
||||
lines.put(lineStart, new LineInfo(lineNum, pos));
|
||||
lineNum++;
|
||||
lineStart = nextPos;
|
||||
}
|
||||
pos = nextPos;
|
||||
}
|
||||
// If the last line has no trailing separator, the loop won't add it to the map
|
||||
if (lineStart < string.length()) {
|
||||
lines.put(lineStart, new LineInfo(lineNum, string.length()));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private Line lineFromMapEntry(final Map.Entry<Integer, LineInfo> entry) {
|
||||
final int start = entry.getKey();
|
||||
final LineInfo lineInfo = entry.getValue();
|
||||
return new Line(
|
||||
lineInfo.number,
|
||||
start,
|
||||
text.substring(start, lineInfo.end)
|
||||
);
|
||||
}
|
||||
|
||||
private static class LineInfo {
|
||||
public final int number;
|
||||
public final int end;
|
||||
|
||||
LineInfo(final int number, final int end) {
|
||||
this.number = number;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a line of text within a string.
|
||||
*/
|
||||
public static class Line {
|
||||
private final int number;
|
||||
private final int startPosition;
|
||||
private final String text;
|
||||
|
||||
Line(final int number, final int startPosition, final String text) {
|
||||
this.number = number;
|
||||
this.startPosition = startPosition;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The line number (starting from 1).
|
||||
*/
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The index at which this line starts within the original string.
|
||||
* If the line is empty, this is the index of the terminator characters ("\r", "\n" or "\r\n").
|
||||
*/
|
||||
public int getStartPosition() {
|
||||
return startPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The contents of this line, <em>without</em> the terminator characters ("\r", "\n" or "\r\n").
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Line line = (Line) o;
|
||||
return number == line.number &&
|
||||
startPosition == line.startPosition &&
|
||||
Objects.equals(text, line.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(number, startPosition, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Line{" +
|
||||
"number=" + number +
|
||||
", startPosition=" + startPosition +
|
||||
", text='" + text + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.errorutils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents a line of text in which tabs have been expanded (replaced with spaces).
|
||||
* <p>
|
||||
* Each tab character is replaced with the number of spaces required to get to the next tab stop
|
||||
* (that is, the next column which is a multiple of the tab stop width).
|
||||
*/
|
||||
public class TabExpandedString {
|
||||
private final String expanded;
|
||||
private final int[] charWidths;
|
||||
|
||||
/**
|
||||
* Constructs a tab-expanded string with the given tab stop width.
|
||||
*
|
||||
* @param string The string to expand. Must not contain any line separator characters ('\r' or '\n').
|
||||
* @param tabWidth The tab stop width.
|
||||
* @throws IllegalArgumentException If <code>string</code> contains any line separator characters.
|
||||
*/
|
||||
public TabExpandedString(final String string, final int tabWidth) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
charWidths = new int[string.length()];
|
||||
|
||||
for (int i = 0; i < string.length(); i++) {
|
||||
final char c = string.charAt(i);
|
||||
charWidths[i] = 1;
|
||||
|
||||
switch (c) {
|
||||
case '\r':
|
||||
case '\n':
|
||||
throw new IllegalArgumentException("The string to expand is not a single line: " + string);
|
||||
case '\t':
|
||||
builder.append(' ');
|
||||
while (builder.length() % tabWidth != 0) {
|
||||
builder.append(' ');
|
||||
charWidths[i]++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
builder.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expanded = builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The tab-expanded string.
|
||||
*/
|
||||
public String getExpanded() {
|
||||
return expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the length of a substring of the original string after tab expansion.
|
||||
*
|
||||
* @param beginIndex The beginning index (inclusive) within the original string.
|
||||
* @param endIndex The ending index (exclusive) within the original string.
|
||||
* @return The length of the specified substring, after tabs have been expanded.
|
||||
*/
|
||||
public int substringLength(final int beginIndex, final int endIndex) {
|
||||
return Arrays.stream(charWidths, beginIndex, endIndex).sum();
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Occurs when DSL source code contains a number literal with a decimal separator which is not followed by digits.
|
||||
* <p>
|
||||
* An example of an incomplete literal is <code>2.</code>, while <code>2</code> and <code>2.2</code> are valid.
|
||||
*/
|
||||
public class IncompleteNumberLiteral implements DslError {
|
||||
private final int position;
|
||||
private final String literal;
|
||||
|
||||
public IncompleteNumberLiteral(final int position, final String literal) {
|
||||
this.position = position;
|
||||
this.literal = literal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return literal.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(final DslError.Visitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The incomplete number literal.
|
||||
*/
|
||||
public String getLiteral() {
|
||||
return literal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof IncompleteNumberLiteral)) {
|
||||
return false;
|
||||
}
|
||||
final IncompleteNumberLiteral other = (IncompleteNumberLiteral) o;
|
||||
return this.position == other.position && this.literal.equals(other.literal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(position, literal);
|
||||
}
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static it.cavallium.warppi.math.rules.dsl.frontend.TokenType.*;
|
||||
|
||||
/**
|
||||
* Converts the source <code>String</code> to a list of {@link Token}s.
|
||||
*/
|
||||
public class Lexer {
|
||||
private static final Map<String, TokenType> KEYWORDS;
|
||||
static {
|
||||
TokenType[] keywordTokenTypes = {
|
||||
REDUCTION, EXPANSION, CALCULATION, EXISTENCE,
|
||||
ARCCOS, ARCSIN, ARCTAN, COS, SIN, TAN, ROOT, SQRT, LOG,
|
||||
UNDEFINED, PI, E
|
||||
};
|
||||
Map<String, TokenType> map = new HashMap<>();
|
||||
for (TokenType type : keywordTokenTypes) {
|
||||
map.put(type.name().toLowerCase(), type);
|
||||
}
|
||||
KEYWORDS = Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
private final String source;
|
||||
private final Consumer<? super DslError> errorReporter;
|
||||
|
||||
private boolean used = false;
|
||||
private final List<Token> tokens = new ArrayList<>();
|
||||
private int startOfLexeme = 0;
|
||||
private int curPosition = 0;
|
||||
private UnexpectedCharacters unexpectedCharacters = null;
|
||||
|
||||
/**
|
||||
* Constructs a <code>Lexer</code> that will split the given source code into {@link Token}s.
|
||||
*
|
||||
* @param source a <code>String</code> containing the DSL source code to process.
|
||||
* @param errorReporter a <code>Consumer</code> used to report each <code>DslError</code> that the
|
||||
* <code>Lexer</code> finds within the source string.
|
||||
*/
|
||||
public Lexer(final String source, final Consumer<? super DslError> errorReporter) {
|
||||
this.source = source;
|
||||
this.errorReporter = errorReporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the <code>Lexer</code>.
|
||||
* <p>
|
||||
* This method can only be called once per instance.
|
||||
*
|
||||
* @return the list of <code>Token</code>s extracted from the source string.
|
||||
* If any errors are reported, this list should not be considered to represent a valid set of DSL rules,
|
||||
* but it can still be parsed to potentially find additional errors (which may allow the user to fix more
|
||||
* errors before having to rerun the <code>Lexer</code>).
|
||||
* @throws IllegalStateException if called multiple times on the same instance.
|
||||
*/
|
||||
public List<Token> lex() {
|
||||
if (used) {
|
||||
throw new IllegalStateException("Lexer.lex can only be called once per instance");
|
||||
}
|
||||
used = true;
|
||||
|
||||
while (!atEnd()) {
|
||||
startOfLexeme = curPosition;
|
||||
lexAndHandleErrors();
|
||||
}
|
||||
// lexAndHandleErrors reports unexpected characters when they're followed by expected ones:
|
||||
// if there are unexpected characters at the end of the source, they have to be reported here
|
||||
reportAndClearUnexpectedCharacters();
|
||||
tokens.add(new Token(EOF, "", source.length()));
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private void lexAndHandleErrors() {
|
||||
try {
|
||||
lexToken();
|
||||
reportAndClearUnexpectedCharacters(); // After finding some expected characters
|
||||
} catch (final SyntaxException e) {
|
||||
final DslError error = e.getError();
|
||||
if (error instanceof UnexpectedCharacters) {
|
||||
addUnexpectedCharacters((UnexpectedCharacters) error);
|
||||
} else {
|
||||
// If there are multiple errors, report them in the order in which they occur in the source
|
||||
reportAndClearUnexpectedCharacters();
|
||||
errorReporter.accept(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addUnexpectedCharacters(final UnexpectedCharacters unexpected) {
|
||||
if (unexpectedCharacters == null) {
|
||||
unexpectedCharacters = unexpected;
|
||||
} else {
|
||||
unexpectedCharacters = unexpectedCharacters.concat(unexpected);
|
||||
}
|
||||
}
|
||||
|
||||
private void reportAndClearUnexpectedCharacters() {
|
||||
if (unexpectedCharacters == null) {
|
||||
return;
|
||||
}
|
||||
errorReporter.accept(unexpectedCharacters);
|
||||
unexpectedCharacters = null;
|
||||
}
|
||||
|
||||
private void lexToken() throws SyntaxException {
|
||||
char current = popChar();
|
||||
switch (current) {
|
||||
case ':': emitToken(COLON); break;
|
||||
case ',': emitToken(COMMA); break;
|
||||
case '(': emitToken(LEFT_PAREN); break;
|
||||
case ')': emitToken(RIGHT_PAREN); break;
|
||||
case '[': emitToken(LEFT_BRACKET); break;
|
||||
case ']': emitToken(RIGHT_BRACKET); break;
|
||||
case '=': emitToken(EQUALS); break;
|
||||
case '*': emitToken(TIMES); break;
|
||||
case '^': emitToken(POWER); break;
|
||||
|
||||
case '+':
|
||||
if (matchChar('-')) {
|
||||
emitToken(PLUS_MINUS);
|
||||
} else {
|
||||
emitToken(PLUS);
|
||||
}
|
||||
break;
|
||||
|
||||
case '-':
|
||||
if (matchChar('>')) {
|
||||
emitToken(ARROW);
|
||||
} else {
|
||||
emitToken(MINUS);
|
||||
}
|
||||
break;
|
||||
|
||||
case '/':
|
||||
if (matchChar('/')) {
|
||||
singleLineComment();
|
||||
} else if (matchChar('*')) {
|
||||
multiLineComment();
|
||||
} else {
|
||||
emitToken(DIVIDE);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (isAsciiDigit(current)) {
|
||||
number();
|
||||
} else if (Character.isJavaIdentifierStart(current)) {
|
||||
keywordOrIdentifier();
|
||||
} else if (!Character.isWhitespace(current)) {
|
||||
throw new SyntaxException(
|
||||
new UnexpectedCharacters(curPosition - 1, String.valueOf(current))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void singleLineComment() {
|
||||
matchWhile(c -> c != '\n');
|
||||
}
|
||||
|
||||
private void multiLineComment() throws SyntaxException {
|
||||
while (!(matchChar('*') && matchChar('/'))) {
|
||||
if (atEnd()) {
|
||||
throw new SyntaxException(new UnterminatedComment(startOfLexeme));
|
||||
}
|
||||
popChar();
|
||||
}
|
||||
}
|
||||
|
||||
private void number() throws SyntaxException {
|
||||
matchWhile(Lexer::isAsciiDigit);
|
||||
if (matchChar('.') && matchWhile(Lexer::isAsciiDigit) == 0) {
|
||||
throw new SyntaxException(
|
||||
new IncompleteNumberLiteral(startOfLexeme, currentLexeme())
|
||||
);
|
||||
}
|
||||
emitToken(NUMBER);
|
||||
}
|
||||
|
||||
private void keywordOrIdentifier() {
|
||||
matchWhile(Character::isJavaIdentifierPart);
|
||||
TokenType type = KEYWORDS.getOrDefault(currentLexeme(), IDENTIFIER);
|
||||
emitToken(type);
|
||||
}
|
||||
|
||||
private char popChar() {
|
||||
char current = source.charAt(curPosition);
|
||||
curPosition++;
|
||||
return current;
|
||||
}
|
||||
|
||||
private boolean matchChar(char expected) {
|
||||
if (atEnd() || source.charAt(curPosition) != expected) {
|
||||
return false;
|
||||
}
|
||||
curPosition++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private int matchWhile(Predicate<Character> predicate) {
|
||||
int matched = 0;
|
||||
while (!atEnd() && predicate.test(source.charAt(curPosition))) {
|
||||
curPosition++;
|
||||
matched++;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
private void emitToken(TokenType type) {
|
||||
tokens.add(new Token(type, currentLexeme(), startOfLexeme));
|
||||
}
|
||||
|
||||
private String currentLexeme() {
|
||||
return source.substring(startOfLexeme, curPosition);
|
||||
}
|
||||
|
||||
private boolean atEnd() {
|
||||
return curPosition >= source.length();
|
||||
}
|
||||
|
||||
// Character.isDigit also allows various Unicode digits
|
||||
private static boolean isAsciiDigit(char c) {
|
||||
return '0' <= c && c <= '9';
|
||||
}
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
||||
import it.cavallium.warppi.math.rules.RuleType;
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.PatternRule;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.*;
|
||||
import it.cavallium.warppi.util.MapFactory;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static it.cavallium.warppi.math.rules.dsl.frontend.TokenType.*;
|
||||
|
||||
/**
|
||||
* Converts a list of {@link Token}s to a list of {@link PatternRule}s.
|
||||
*/
|
||||
public class Parser {
|
||||
private static final Map<TokenType, RuleType> RULE_TYPES = MapFactory.fromEntries(
|
||||
MapFactory.entry(REDUCTION, RuleType.REDUCTION),
|
||||
MapFactory.entry(EXPANSION, RuleType.EXPANSION),
|
||||
MapFactory.entry(CALCULATION, RuleType.CALCULATION),
|
||||
MapFactory.entry(EXISTENCE, RuleType.EXISTENCE)
|
||||
);
|
||||
|
||||
private final List<Token> tokens;
|
||||
private final Consumer<? super DslError> errorReporter;
|
||||
private boolean used = false;
|
||||
private int currentIndex = 0;
|
||||
|
||||
// For error reporting
|
||||
// An IdentityHashMap is used to distinguish SubFunctionPatterns even if they're identical (equal)
|
||||
private final IdentityHashMap<SubFunctionPattern, Token> subFunctionIdentifiers = new IdentityHashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs a <code>Parser</code> that will produce a list of {@link PatternRule}s from the the given list of {@link Token}s.
|
||||
*
|
||||
* @param tokens the list of <code>Token</code>s to process.
|
||||
* @param errorReporter a <code>Consumer</code> used to report each <code>DslError</code> that the
|
||||
* <code>Parser</code> finds within the source string.
|
||||
*/
|
||||
public Parser(final List<Token> tokens, final Consumer<? super DslError> errorReporter) {
|
||||
this.tokens = tokens;
|
||||
this.errorReporter = errorReporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the <code>Parser</code>.
|
||||
* <p>
|
||||
* This method can only be called once per instance.
|
||||
*
|
||||
* @return the list of all valid <code>PatternRule</code>s constructed from the given tokens.
|
||||
* If any errors are reported, this list should not be considered to represent a valid set of DSL rules,
|
||||
* but each rule can still be analyzed to look for undefined sub-functions in replacement patterns and
|
||||
* report them (which may allow the user to fix more errors before having to rerun the <code>Lexer</code>
|
||||
* and <code>Parser</code>).
|
||||
* @throws IllegalStateException if called multiple times on the same instance.
|
||||
*/
|
||||
public List<PatternRule> parse() {
|
||||
if (used) {
|
||||
throw new IllegalStateException("Parser.parse can only be called once per instance");
|
||||
}
|
||||
used = true;
|
||||
|
||||
return rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the <code>IDENTIFIER</code> token which corresponds to the given <code>SubFunctionPattern</code>.
|
||||
* <p>
|
||||
* The information returned by this method can be used to point out the location of sub-function related errors
|
||||
* within the DSL source code.
|
||||
*
|
||||
* @param subFunction a <code>SubFunctionsPattern</code> from one of the rules returned by this <code>Parser</code>
|
||||
* instance. While <code>SubFunctionPattern</code>s with the same name are considered equal,
|
||||
* this method can distinguish between them, in order to return the exact identifier which led
|
||||
* to the creation of the specified <code>SubFunctionPattern</code> object.
|
||||
* @return the <code>Token</code> (of type <code>IDENTIFIER</code>) which corresponds to the given
|
||||
* <code>SubFunctionPattern</code>.
|
||||
*/
|
||||
public Token getSubFunctionIdentifier(final SubFunctionPattern subFunction) {
|
||||
return subFunctionIdentifiers.get(subFunction);
|
||||
}
|
||||
|
||||
// rules = { rule } , EOF ;
|
||||
private List<PatternRule> rules() {
|
||||
final List<PatternRule> rules = new ArrayList<>();
|
||||
while (!atEnd()) {
|
||||
try {
|
||||
rules.add(rule());
|
||||
} catch (final SyntaxException e) {
|
||||
errorReporter.accept(e.getError());
|
||||
synchronizeTo(RULE_TYPES.keySet()); // Skip to the next rule to minimize "false" errors
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
// rule = rule header , rule body ;
|
||||
// rule header = rule type , IDENTIFIER , COLON ;
|
||||
// rule body = pattern , ARROW , replacements ;
|
||||
private PatternRule rule() throws SyntaxException {
|
||||
final RuleType type = ruleType();
|
||||
final String name = matchOrFail(IDENTIFIER).lexeme;
|
||||
matchOrFail(COLON);
|
||||
final Pattern target = pattern();
|
||||
matchOrFail(ARROW);
|
||||
final List<Pattern> replacements = replacements();
|
||||
return new PatternRule(name, type, target, replacements);
|
||||
}
|
||||
|
||||
// rule type = REDUCTION | EXPANSION | CALCULATION | EXISTENCE ;
|
||||
private RuleType ruleType() throws SyntaxException {
|
||||
final Token curToken = pop();
|
||||
if (!RULE_TYPES.containsKey(curToken.type)) {
|
||||
throw new SyntaxException(
|
||||
new UnexpectedToken(curToken, RULE_TYPES.keySet())
|
||||
);
|
||||
}
|
||||
return RULE_TYPES.get(curToken.type);
|
||||
}
|
||||
|
||||
// pattern = equation ;
|
||||
private Pattern pattern() throws SyntaxException {
|
||||
return equation();
|
||||
}
|
||||
|
||||
// replacements = pattern
|
||||
// | LEFT_BRACKET , patterns , RIGHT_BRACKET ;
|
||||
// patterns = [ pattern , { COMMA , pattern } , [ COMMA ] ] ;
|
||||
private List<Pattern> replacements() throws SyntaxException {
|
||||
if (match(LEFT_BRACKET) == null) {
|
||||
return Collections.singletonList(pattern());
|
||||
}
|
||||
|
||||
if (match(RIGHT_BRACKET) != null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
final List<Pattern> pats = new ArrayList<>();
|
||||
do {
|
||||
pats.add(pattern());
|
||||
} while (match(COMMA) != null && peek().type != RIGHT_BRACKET);
|
||||
matchOrFail(RIGHT_BRACKET);
|
||||
return pats;
|
||||
}
|
||||
}
|
||||
|
||||
// equation = sum , [ EQUALS , sum ] ;
|
||||
private Pattern equation() throws SyntaxException {
|
||||
Pattern pat = sum();
|
||||
if (match(EQUALS) != null) {
|
||||
pat = new EquationPattern(pat, sum());
|
||||
}
|
||||
return pat;
|
||||
}
|
||||
|
||||
// sum = product , { ( PLUS | MINUS | PLUS_MINUS ) product } ;
|
||||
private Pattern sum() throws SyntaxException {
|
||||
return matchLeftAssoc(this::product, MapFactory.fromEntries(
|
||||
MapFactory.entry(PLUS, SumPattern::new),
|
||||
MapFactory.entry(MINUS, SubtractionPattern::new),
|
||||
MapFactory.entry(PLUS_MINUS, SumSubtractionPattern::new)
|
||||
));
|
||||
}
|
||||
|
||||
// product = unary , { ( TIMES | DIVIDE ) unary } ;
|
||||
private Pattern product() throws SyntaxException {
|
||||
return matchLeftAssoc(this::unary, MapFactory.fromEntries(
|
||||
MapFactory.entry(TIMES, MultiplicationPattern::new),
|
||||
MapFactory.entry(DIVIDE, DivisionPattern::new)
|
||||
));
|
||||
}
|
||||
|
||||
// unary = ( PLUS | MINUS ) unary
|
||||
// | power ;
|
||||
private Pattern unary() throws SyntaxException {
|
||||
if (match(PLUS) != null) {
|
||||
return unary();
|
||||
} else if (match(MINUS) != null) {
|
||||
return new NegativePattern(unary());
|
||||
} else {
|
||||
return power();
|
||||
}
|
||||
}
|
||||
|
||||
// power = ( function | primary ) , [ POWER , unary ] ;
|
||||
private Pattern power() throws SyntaxException {
|
||||
Pattern pat = functionOrPrimary();
|
||||
if (match(POWER) != null) {
|
||||
pat = new PowerPattern(pat, unary());
|
||||
}
|
||||
return pat;
|
||||
}
|
||||
|
||||
private Pattern functionOrPrimary() throws SyntaxException {
|
||||
final Pattern function = tryFunction();
|
||||
return function != null ? function : primary();
|
||||
}
|
||||
|
||||
// function = ( ARCCOS | ARCSIN | ARCTAN | COS | SIN | SQRT | TAN ) , LEFT_PAREN , sum , RIGHT_PAREN
|
||||
// | ( LOG | ROOT ) LEFT_PAREN , sum , COMMA , sum , RIGHT_PAREN ;
|
||||
private Pattern tryFunction() throws SyntaxException {
|
||||
final Map<TokenType, Function<Pattern, Pattern>> oneArg = MapFactory.fromEntries(
|
||||
MapFactory.entry(ARCCOS, ArcCosinePattern::new),
|
||||
MapFactory.entry(ARCSIN, ArcSinePattern::new),
|
||||
MapFactory.entry(ARCTAN, ArcTangentPattern::new),
|
||||
MapFactory.entry(COS, CosinePattern::new),
|
||||
MapFactory.entry(SIN, SinePattern::new),
|
||||
MapFactory.entry(SQRT, arg -> new RootPattern(new NumberPattern(new BigDecimal(2)), arg)),
|
||||
MapFactory.entry(TAN, TangentPattern::new)
|
||||
);
|
||||
final Map<TokenType, BiFunction<Pattern, Pattern, Pattern>> twoArg = MapFactory.fromEntries(
|
||||
MapFactory.entry(LOG, LogarithmPattern::new),
|
||||
MapFactory.entry(ROOT, RootPattern::new)
|
||||
);
|
||||
|
||||
final TokenType curType = peek().type;
|
||||
if (oneArg.containsKey(curType)) {
|
||||
pop();
|
||||
return oneArgFunction(oneArg.get(curType));
|
||||
} else if (twoArg.containsKey(curType)) {
|
||||
pop();
|
||||
return twoArgFunction(twoArg.get(curType));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Pattern oneArgFunction(final Function<Pattern, Pattern> constructor) throws SyntaxException {
|
||||
matchOrFail(LEFT_PAREN);
|
||||
final Pattern arg = pattern();
|
||||
matchOrFail(RIGHT_PAREN);
|
||||
return constructor.apply(arg);
|
||||
}
|
||||
|
||||
private Pattern twoArgFunction(final BiFunction<Pattern, Pattern, Pattern> constructor) throws SyntaxException {
|
||||
matchOrFail(LEFT_PAREN);
|
||||
final Pattern firstArg = pattern();
|
||||
matchOrFail(COMMA);
|
||||
final Pattern secondArg = pattern();
|
||||
matchOrFail(RIGHT_PAREN);
|
||||
return constructor.apply(firstArg, secondArg);
|
||||
}
|
||||
|
||||
// primary = NUMBER | constant | IDENTIFIER | UNDEFINED
|
||||
// | LEFT_PAREN sum RIGHT_PAREN ;
|
||||
// constant = PI | E ;
|
||||
private Pattern primary() throws SyntaxException {
|
||||
final Token curToken = pop();
|
||||
switch (curToken.type) {
|
||||
case PI:
|
||||
return new ConstantPattern(MathematicalSymbols.PI);
|
||||
case E:
|
||||
return new ConstantPattern(MathematicalSymbols.EULER_NUMBER);
|
||||
case UNDEFINED:
|
||||
return new UndefinedPattern();
|
||||
case NUMBER:
|
||||
return new NumberPattern(new BigDecimal(curToken.lexeme));
|
||||
case IDENTIFIER:
|
||||
final SubFunctionPattern subFunction = new SubFunctionPattern(curToken.lexeme);
|
||||
subFunctionIdentifiers.put(subFunction, curToken);
|
||||
return subFunction;
|
||||
case LEFT_PAREN:
|
||||
final Pattern grouped = sum();
|
||||
matchOrFail(RIGHT_PAREN);
|
||||
return grouped;
|
||||
}
|
||||
throw new SyntaxException(new UnexpectedToken(curToken));
|
||||
}
|
||||
|
||||
private Pattern matchLeftAssoc(
|
||||
final PatternParser operandParser,
|
||||
final Map<TokenType, BiFunction<Pattern, Pattern, Pattern>> operators
|
||||
) throws SyntaxException {
|
||||
Pattern pat = operandParser.parse();
|
||||
while (operators.containsKey(peek().type)) {
|
||||
final Token operatorToken = pop();
|
||||
final BiFunction<Pattern, Pattern, Pattern> constructor = operators.get(operatorToken.type);
|
||||
pat = constructor.apply(pat, operandParser.parse());
|
||||
}
|
||||
return pat;
|
||||
}
|
||||
|
||||
private Token matchOrFail(final TokenType expectedType) throws SyntaxException {
|
||||
final Token matched = match(expectedType);
|
||||
if (matched == null) {
|
||||
throw new SyntaxException(
|
||||
new UnexpectedToken(tokens.get(currentIndex), expectedType)
|
||||
);
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
private Token match(final TokenType expectedType) {
|
||||
final Token curToken = tokens.get(currentIndex);
|
||||
if (curToken.type != expectedType) {
|
||||
return null;
|
||||
}
|
||||
currentIndex++;
|
||||
return curToken;
|
||||
}
|
||||
|
||||
private void synchronizeTo(final Set<TokenType> types) {
|
||||
while (!atEnd() && !types.contains(tokens.get(currentIndex).type)) {
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private Token pop() throws SyntaxException {
|
||||
final Token curToken = tokens.get(currentIndex);
|
||||
if (atEnd()) {
|
||||
throw new SyntaxException(new UnexpectedToken(curToken)); // Avoid popping EOF
|
||||
}
|
||||
currentIndex++;
|
||||
return curToken;
|
||||
}
|
||||
|
||||
private Token peek() {
|
||||
return tokens.get(currentIndex);
|
||||
}
|
||||
|
||||
private boolean atEnd() {
|
||||
return tokens.get(currentIndex).type == EOF;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface PatternParser {
|
||||
Pattern parse() throws SyntaxException;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
|
||||
/**
|
||||
* Thrown when DSL source code contains a syntax error.
|
||||
* <p>
|
||||
* Used for error reporting and recovery in the implementation of {@link Lexer} and {@link Parser}.
|
||||
*/
|
||||
class SyntaxException extends Exception {
|
||||
private final DslError error;
|
||||
|
||||
SyntaxException(final DslError error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public DslError getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a single token extracted from DSL source code.
|
||||
* <p>
|
||||
* <code>Token</code>s are produced by the {@link Lexer} and consumed by the {@link Parser}.
|
||||
*/
|
||||
public class Token {
|
||||
/** The type of the token. */
|
||||
public final TokenType type;
|
||||
/**
|
||||
* The part of the source code which corresponds to the token.
|
||||
* <p>
|
||||
* Some types of token always have the same lexemes (for example, <code>PLUS</code> is always represented by
|
||||
* <code>"+"</code>), while others have variable lexemes (like <code>IDENTIFIER</code>, which may correspond to any
|
||||
* valid identifier).
|
||||
* <p>
|
||||
* As a special case, tokens of type <code>EOF</code> (which signal the end of the source code) have empty lexemes
|
||||
* (<code>""</code>). Such tokens only exist to simplify the parser code, by allowing the end of the input to be
|
||||
* treated like any other token (which is especially useful for error handling, because an unexpected end of input
|
||||
* just becomes an "unexpected token" error).
|
||||
*/
|
||||
public final String lexeme;
|
||||
/** The index at which the token starts in the source string. */
|
||||
public final int position;
|
||||
|
||||
/**
|
||||
* Constructs a <code>Token</code>.
|
||||
*
|
||||
* @param type the type of the token.
|
||||
* @param lexeme the part of the source string which corresponds to the token.
|
||||
* @param position the index at which the token starts in the source string.
|
||||
*/
|
||||
public Token(final TokenType type, final String lexeme, final int position) {
|
||||
this.type = type;
|
||||
this.lexeme = lexeme;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s(\"%s\")@%d", type, lexeme, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Token)) {
|
||||
return false;
|
||||
}
|
||||
Token other = (Token) o;
|
||||
return type == other.type && lexeme.equals(other.lexeme) && position == other.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, lexeme, position);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
/**
|
||||
* Specifies the type of a <code>Token</code>.
|
||||
*/
|
||||
public enum TokenType {
|
||||
EOF,
|
||||
// Separators and grouping
|
||||
COLON, ARROW, COMMA, LEFT_PAREN, RIGHT_PAREN, LEFT_BRACKET, RIGHT_BRACKET,
|
||||
// Operators
|
||||
EQUALS, PLUS, MINUS, PLUS_MINUS, TIMES, DIVIDE, POWER,
|
||||
// Rule types
|
||||
REDUCTION, EXPANSION, CALCULATION, EXISTENCE,
|
||||
// Functions
|
||||
ARCCOS, ARCSIN, ARCTAN, COS, SIN, TAN, ROOT, SQRT, LOG,
|
||||
// Literals
|
||||
UNDEFINED, PI, E, NUMBER, IDENTIFIER,
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Occurs when DSL source code contains one or more (consecutive) characters which are not expected by the lexer.
|
||||
*/
|
||||
public class UnexpectedCharacters implements DslError {
|
||||
private final int position;
|
||||
private final String unexpectedCharacters;
|
||||
|
||||
public UnexpectedCharacters(final int position, final String unexpectedCharacters) {
|
||||
this.position = position;
|
||||
this.unexpectedCharacters = unexpectedCharacters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return unexpectedCharacters.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(final DslError.Visitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The string of one or more consecutive unexpected characters.
|
||||
*/
|
||||
public String getUnexpectedCharacters() {
|
||||
return unexpectedCharacters;
|
||||
}
|
||||
|
||||
UnexpectedCharacters concat(UnexpectedCharacters other) {
|
||||
return new UnexpectedCharacters(this.position, this.unexpectedCharacters + other.unexpectedCharacters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof UnexpectedCharacters)) {
|
||||
return false;
|
||||
}
|
||||
final UnexpectedCharacters other = (UnexpectedCharacters) o;
|
||||
return this.position == other.position && this.unexpectedCharacters.equals(other.unexpectedCharacters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(position, unexpectedCharacters);
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Occurs when DSL source code contains a token which doesn't match the grammar.
|
||||
*/
|
||||
public class UnexpectedToken implements DslError {
|
||||
private final Token unexpected;
|
||||
private final Set<TokenType> suggested;
|
||||
|
||||
public UnexpectedToken(final Token unexpected, final Set<TokenType> suggested) {
|
||||
this.unexpected = unexpected;
|
||||
this.suggested = suggested;
|
||||
}
|
||||
|
||||
public UnexpectedToken(final Token unexpected, final TokenType... suggested) {
|
||||
this.unexpected = unexpected;
|
||||
this.suggested = new HashSet<>(Arrays.asList(suggested)); // TeaVM doesn't support Set.of
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return unexpected.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return unexpected.lexeme.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(final DslError.Visitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The unexpected token.
|
||||
*/
|
||||
public Token getUnexpected() {
|
||||
return unexpected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A (possibly empty) set of <code>TokenType</code>s which would match the grammar.
|
||||
* As the name implies, this is only a suggestion: the parser can't list all allowed token types
|
||||
* when it detects an error.
|
||||
*/
|
||||
public Set<TokenType> getSuggested() {
|
||||
return suggested;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof UnexpectedToken)) {
|
||||
return false;
|
||||
}
|
||||
final UnexpectedToken other = (UnexpectedToken) o;
|
||||
return this.unexpected.equals(other.unexpected) && this.suggested.equals(other.suggested);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(unexpected, suggested);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Occurs when DSL source code contains a multiline comment which is never terminated (closed).
|
||||
*/
|
||||
public class UnterminatedComment implements DslError {
|
||||
private final int position;
|
||||
|
||||
public UnterminatedComment(final int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return 2; // Length of comment start marker: "/*"
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(final DslError.Visitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof UnterminatedComment)) {
|
||||
return false;
|
||||
}
|
||||
final UnterminatedComment other = (UnterminatedComment) o;
|
||||
return this.position == other.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(position);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.ArcCosine;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates the arccosine of another pattern.
|
||||
*/
|
||||
public class ArcCosinePattern extends VisitorPattern {
|
||||
private final Pattern argument;
|
||||
|
||||
public ArcCosinePattern(final Pattern argument) {
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final ArcCosine arcCosine, final Map<String, Function> subFunctions) {
|
||||
return argument.match(arcCosine.getParameter(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new ArcCosine(
|
||||
mathContext,
|
||||
argument.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return argument.getSubFunctions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof ArcCosinePattern)) {
|
||||
return false;
|
||||
}
|
||||
final ArcCosinePattern other = (ArcCosinePattern) o;
|
||||
return argument.equals(other.argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(argument);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.ArcSine;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates the arcsine of another pattern.
|
||||
*/
|
||||
public class ArcSinePattern extends VisitorPattern {
|
||||
private final Pattern argument;
|
||||
|
||||
public ArcSinePattern(final Pattern argument) {
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final ArcSine arcSine, final Map<String, Function> subFunctions) {
|
||||
return argument.match(arcSine.getParameter(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new ArcSine(
|
||||
mathContext,
|
||||
argument.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return argument.getSubFunctions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof ArcSinePattern)) {
|
||||
return false;
|
||||
}
|
||||
final ArcSinePattern other = (ArcSinePattern) o;
|
||||
return argument.equals(other.argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(argument);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.ArcTangent;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates the arctangent of another pattern.
|
||||
*/
|
||||
public class ArcTangentPattern extends VisitorPattern {
|
||||
private final Pattern argument;
|
||||
|
||||
public ArcTangentPattern(final Pattern argument) {
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final ArcTangent arcTangent, final Map<String, Function> subFunctions) {
|
||||
return argument.match(arcTangent.getParameter(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new ArcTangent(
|
||||
mathContext,
|
||||
argument.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return argument.getSubFunctions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof ArcTangentPattern)) {
|
||||
return false;
|
||||
}
|
||||
final ArcTangentPattern other = (ArcTangentPattern) o;
|
||||
return argument.equals(other.argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(argument);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Variable;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a specific symbolic constant.
|
||||
*/
|
||||
public class ConstantPattern extends VisitorPattern {
|
||||
private final char symbol;
|
||||
|
||||
public ConstantPattern(final char symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Variable variable, final Map<String, Function> subFunctions) {
|
||||
return variable.getType().equals(Variable.V_TYPE.CONSTANT)
|
||||
&& variable.getChar() == symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Variable(mathContext, symbol, Variable.V_TYPE.CONSTANT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof ConstantPattern)) {
|
||||
return false;
|
||||
}
|
||||
final ConstantPattern other = (ConstantPattern) o;
|
||||
return symbol == other.symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(symbol);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.Cosine;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates the cosine of another pattern.
|
||||
*/
|
||||
public class CosinePattern extends VisitorPattern {
|
||||
private final Pattern argument;
|
||||
|
||||
public CosinePattern(final Pattern argument) {
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Cosine cosine, final Map<String, Function> subFunctions) {
|
||||
return argument.match(cosine.getParameter(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Cosine(
|
||||
mathContext,
|
||||
argument.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return argument.getSubFunctions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof CosinePattern)) {
|
||||
return false;
|
||||
}
|
||||
final CosinePattern other = (CosinePattern) o;
|
||||
return argument.equals(other.argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(argument);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Division;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a division of two other patterns.
|
||||
*/
|
||||
public class DivisionPattern extends VisitorPattern {
|
||||
private final Pattern dividend;
|
||||
private final Pattern divisor;
|
||||
|
||||
public DivisionPattern(final Pattern dividend, final Pattern divisor) {
|
||||
this.dividend = dividend;
|
||||
this.divisor = divisor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Division division, final Map<String, Function> subFunctions) {
|
||||
return dividend.match(division.getParameter1(), subFunctions)
|
||||
&& divisor.match(division.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Division(
|
||||
mathContext,
|
||||
dividend.replace(mathContext, subFunctions),
|
||||
divisor.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(dividend, divisor)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof DivisionPattern)) {
|
||||
return false;
|
||||
}
|
||||
final DivisionPattern other = (DivisionPattern) o;
|
||||
return dividend.equals(other.dividend) && divisor.equals(other.divisor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(dividend, divisor);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.equations.Equation;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates an equation of two other patterns.
|
||||
*/
|
||||
public class EquationPattern extends VisitorPattern {
|
||||
private final Pattern left;
|
||||
private final Pattern right;
|
||||
|
||||
public EquationPattern(final Pattern left, final Pattern right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Equation equation, final Map<String, Function> subFunctions) {
|
||||
return left.match(equation.getParameter1(), subFunctions)
|
||||
&& right.match(equation.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Equation(
|
||||
mathContext,
|
||||
left.replace(mathContext, subFunctions),
|
||||
right.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(left, right)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof EquationPattern)) {
|
||||
return false;
|
||||
}
|
||||
final EquationPattern other = (EquationPattern) o;
|
||||
return left.equals(other.left) && right.equals(other.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(left, right);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.equations.EquationsSystem;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a system of equations of multiple other patterns.
|
||||
*/
|
||||
public class EquationsSystemPattern extends VisitorPattern {
|
||||
private final Pattern[] patterns;
|
||||
|
||||
public EquationsSystemPattern(final Pattern[] patterns) {
|
||||
this.patterns = patterns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final EquationsSystem equationsSystem, final Map<String, Function> subFunctions) {
|
||||
if (patterns.length != equationsSystem.getParametersLength()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < patterns.length; i++) {
|
||||
final Pattern curPattern = patterns[i];
|
||||
final Function curFunction = equationsSystem.getParameter(i);
|
||||
if (!curPattern.match(curFunction, subFunctions)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
final Function[] functions = Arrays.stream(patterns)
|
||||
.map(pattern -> pattern.replace(mathContext, subFunctions))
|
||||
.toArray(Function[]::new);
|
||||
return new EquationsSystem(mathContext, functions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(patterns)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof EquationsSystemPattern)) {
|
||||
return false;
|
||||
}
|
||||
final EquationsSystemPattern other = (EquationsSystemPattern) o;
|
||||
return Arrays.equals(patterns, other.patterns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(patterns);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Logarithm;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a logarithm of base and argument patterns.
|
||||
*/
|
||||
public class LogarithmPattern extends VisitorPattern {
|
||||
private final Pattern base;
|
||||
private final Pattern argument;
|
||||
|
||||
public LogarithmPattern(final Pattern base, final Pattern argument) {
|
||||
this.base = base;
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Logarithm logarithm, final Map<String, Function> subFunctions) {
|
||||
return base.match(logarithm.getParameter1(), subFunctions)
|
||||
&& argument.match(logarithm.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Logarithm(
|
||||
mathContext,
|
||||
base.replace(mathContext, subFunctions),
|
||||
argument.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(base, argument)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof LogarithmPattern)) {
|
||||
return false;
|
||||
}
|
||||
final LogarithmPattern other = (LogarithmPattern) o;
|
||||
return base.equals(other.base) && argument.equals(other.argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(base, argument);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Multiplication;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a multiplication of two other patterns.
|
||||
*/
|
||||
public class MultiplicationPattern extends VisitorPattern {
|
||||
private final Pattern left;
|
||||
private final Pattern right;
|
||||
|
||||
public MultiplicationPattern(final Pattern left, final Pattern right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Multiplication multiplication, final Map<String, Function> subFunctions) {
|
||||
return left.match(multiplication.getParameter1(), subFunctions)
|
||||
&& right.match(multiplication.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Multiplication(
|
||||
mathContext,
|
||||
left.replace(mathContext, subFunctions),
|
||||
right.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(left, right)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof MultiplicationPattern)) {
|
||||
return false;
|
||||
}
|
||||
final MultiplicationPattern other = (MultiplicationPattern) o;
|
||||
return left.equals(other.left) && right.equals(other.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(left, right);
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Negative;
|
||||
import it.cavallium.warppi.math.functions.Number;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates the negative of another pattern.
|
||||
*/
|
||||
public class NegativePattern extends VisitorPattern {
|
||||
private final Pattern inner;
|
||||
|
||||
public NegativePattern(final Pattern inner) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Negative negative, final Map<String, Function> subFunctions) {
|
||||
return inner.match(negative.getParameter(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Number number, final Map<String, Function> subFunctions) {
|
||||
final BigDecimal value = number.getTerm();
|
||||
if (value.signum() >= 0) {
|
||||
return false;
|
||||
}
|
||||
final Number absoluteValue = new Number(number.getMathContext(), value.abs());
|
||||
return inner.match(absoluteValue, subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
final Function newInner = inner.replace(mathContext, subFunctions);
|
||||
|
||||
if (newInner instanceof Number) {
|
||||
return ((Number) newInner).multiply(new Number(mathContext, -1));
|
||||
} else {
|
||||
return new Negative(
|
||||
mathContext,
|
||||
inner.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return inner.getSubFunctions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof NegativePattern)) {
|
||||
return false;
|
||||
}
|
||||
final NegativePattern other = (NegativePattern) o;
|
||||
return inner.equals(other.inner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(inner);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Number;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a specific number.
|
||||
*/
|
||||
public class NumberPattern extends VisitorPattern {
|
||||
private final BigDecimal value;
|
||||
|
||||
public NumberPattern(final BigDecimal value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Number number, final Map<String, Function> subFunctions) {
|
||||
return number.getTerm().compareTo(value) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Number(mathContext, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof NumberPattern)) {
|
||||
return false;
|
||||
}
|
||||
final NumberPattern other = (NumberPattern) o;
|
||||
return value.compareTo(other.value) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Power;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a power (exponentiation) of base and exponent patterns.
|
||||
*/
|
||||
public class PowerPattern extends VisitorPattern {
|
||||
private final Pattern base;
|
||||
private final Pattern exponent;
|
||||
|
||||
public PowerPattern(final Pattern base, final Pattern exponent) {
|
||||
this.base = base;
|
||||
this.exponent = exponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Power power, final Map<String, Function> subFunctions) {
|
||||
return base.match(power.getParameter1(), subFunctions)
|
||||
&& exponent.match(power.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Power(
|
||||
mathContext,
|
||||
base.replace(mathContext, subFunctions),
|
||||
exponent.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(base, exponent)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof PowerPattern)) {
|
||||
return false;
|
||||
}
|
||||
final PowerPattern other = (PowerPattern) o;
|
||||
return base.equals(other.base) && exponent.equals(other.exponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(base, exponent);
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Number;
|
||||
import it.cavallium.warppi.math.functions.Root;
|
||||
import it.cavallium.warppi.math.functions.RootSquare;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a root of degree and radicand patterns.
|
||||
* <p>
|
||||
* Also matches and generates functions of type <code>RootSquare</code>.
|
||||
*/
|
||||
public class RootPattern extends VisitorPattern {
|
||||
private final Pattern degree;
|
||||
private final Pattern radicand;
|
||||
|
||||
public RootPattern(final Pattern degree, final Pattern radicand) {
|
||||
this.degree = degree;
|
||||
this.radicand = radicand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Root root, final Map<String, Function> subFunctions) {
|
||||
return degree.match(root.getParameter1(), subFunctions)
|
||||
&& radicand.match(root.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final RootSquare rootSquare, final Map<String, Function> subFunctions) {
|
||||
return degree.match(rootSquare.getParameter1(), subFunctions)
|
||||
&& radicand.match(rootSquare.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
final Function newDegree = degree.replace(mathContext, subFunctions);
|
||||
final Function newRadicand = radicand.replace(mathContext, subFunctions);
|
||||
|
||||
if (newDegree instanceof Number
|
||||
&& ((Number) newDegree).getTerm().compareTo(new BigDecimal(2)) == 0) {
|
||||
return new RootSquare(mathContext, newRadicand);
|
||||
} else {
|
||||
return new Root(mathContext, newDegree, newRadicand);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(degree, radicand)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof RootPattern)) {
|
||||
return false;
|
||||
}
|
||||
final RootPattern other = (RootPattern) o;
|
||||
return degree.equals(other.degree) && radicand.equals(other.radicand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(degree, radicand);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.Sine;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates the sine of another pattern.
|
||||
*/
|
||||
public class SinePattern extends VisitorPattern {
|
||||
private final Pattern argument;
|
||||
|
||||
public SinePattern(final Pattern argument) {
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Sine sine, final Map<String, Function> subFunctions) {
|
||||
return argument.match(sine.getParameter(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Sine(
|
||||
mathContext,
|
||||
argument.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return argument.getSubFunctions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof SinePattern)) {
|
||||
return false;
|
||||
}
|
||||
final SinePattern other = (SinePattern) o;
|
||||
return argument.equals(other.argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(argument);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.UndefinedSubFunctionException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates any function as a named sub-function.
|
||||
* <p>
|
||||
* For a <code>Function</code> to match a <code>Pattern</code>, all <code>SubFunctionPattern</code>s with the same name
|
||||
* must capture equal sub-functions (according to the <code>equals</code> method).
|
||||
* For example, the <code>x + x</code> <code>Pattern</code> matches <code>2 + 2</code> and <code>2 + 2.0</code>,
|
||||
* but not <code>2 + 3</code>, while the <code>x + y</code> <code>Pattern</code> matches all three <code>Function</code>s.
|
||||
*/
|
||||
public class SubFunctionPattern implements Pattern {
|
||||
private final String name;
|
||||
|
||||
public SubFunctionPattern(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(final Function function, final Map<String, Function> subFunctions) {
|
||||
final Function existingSubFunction = subFunctions.putIfAbsent(name, function);
|
||||
return existingSubFunction == null || existingSubFunction.equals(function);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UndefinedSubFunctionException if the <code>subFunctions</code> <code>Map</code> doesn't contain a
|
||||
* sub-function with the name specified in this
|
||||
* <code>SubFunctionPattern</code>'s constructor.
|
||||
*/
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
if (!subFunctions.containsKey(name)) {
|
||||
throw new UndefinedSubFunctionException(name);
|
||||
}
|
||||
return subFunctions.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof SubFunctionPattern)) {
|
||||
return false;
|
||||
}
|
||||
final SubFunctionPattern other = (SubFunctionPattern) o;
|
||||
return name.equals(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Subtraction;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a subtraction of two other patterns.
|
||||
*/
|
||||
public class SubtractionPattern extends VisitorPattern {
|
||||
private final Pattern left;
|
||||
private final Pattern right;
|
||||
|
||||
public SubtractionPattern(final Pattern left, final Pattern right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Subtraction subtraction, final Map<String, Function> subFunctions) {
|
||||
return left.match(subtraction.getParameter1(), subFunctions)
|
||||
&& right.match(subtraction.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Subtraction(
|
||||
mathContext,
|
||||
left.replace(mathContext, subFunctions),
|
||||
right.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(left, right)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof SubtractionPattern)) {
|
||||
return false;
|
||||
}
|
||||
final SubtractionPattern other = (SubtractionPattern) o;
|
||||
return left.equals(other.left) && right.equals(other.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(left, right);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Sum;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a sum of two other patterns.
|
||||
*/
|
||||
public class SumPattern extends VisitorPattern {
|
||||
private final Pattern left;
|
||||
private final Pattern right;
|
||||
|
||||
public SumPattern(final Pattern left, final Pattern right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Sum sum, final Map<String, Function> subFunctions) {
|
||||
return left.match(sum.getParameter1(), subFunctions)
|
||||
&& right.match(sum.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Sum(
|
||||
mathContext,
|
||||
left.replace(mathContext, subFunctions),
|
||||
right.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(left, right)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof SumPattern)) {
|
||||
return false;
|
||||
}
|
||||
final SumPattern other = (SumPattern) o;
|
||||
return left.equals(other.left) && right.equals(other.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(left, right);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.SumSubtraction;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates a sum/subtraction (±) of two other patterns.
|
||||
*/
|
||||
public class SumSubtractionPattern extends VisitorPattern {
|
||||
private final Pattern left;
|
||||
private final Pattern right;
|
||||
|
||||
public SumSubtractionPattern(final Pattern left, final Pattern right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final SumSubtraction sumSubtraction, final Map<String, Function> subFunctions) {
|
||||
return left.match(sumSubtraction.getParameter1(), subFunctions)
|
||||
&& right.match(sumSubtraction.getParameter2(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new SumSubtraction(
|
||||
mathContext,
|
||||
left.replace(mathContext, subFunctions),
|
||||
right.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.of(left, right)
|
||||
.flatMap(Pattern::getSubFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof SumSubtractionPattern)) {
|
||||
return false;
|
||||
}
|
||||
final SumSubtractionPattern other = (SumSubtractionPattern) o;
|
||||
return left.equals(other.left) && right.equals(other.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(left, right);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.Tangent;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates the tangent of another pattern.
|
||||
*/
|
||||
public class TangentPattern extends VisitorPattern {
|
||||
private final Pattern argument;
|
||||
|
||||
public TangentPattern(final Pattern argument) {
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(final Tangent tangent, final Map<String, Function> subFunctions) {
|
||||
return argument.match(tangent.getParameter(), subFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(final MathContext mathContext, final Map<String, Function> subFunctions) {
|
||||
return new Tangent(
|
||||
mathContext,
|
||||
argument.replace(mathContext, subFunctions)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return argument.getSubFunctions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof TangentPattern)) {
|
||||
return false;
|
||||
}
|
||||
final TangentPattern other = (TangentPattern) o;
|
||||
return argument.equals(other.argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(argument);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.patterns;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Undefined;
|
||||
import it.cavallium.warppi.math.rules.dsl.VisitorPattern;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Matches and generates <code>Undefined</code>.
|
||||
*/
|
||||
public class UndefinedPattern extends VisitorPattern {
|
||||
@Override
|
||||
public Boolean visit(final Undefined undefined, final Map<String, Function> subFunctions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function replace(MathContext mathContext, Map<String, Function> subFunctions) {
|
||||
return new Undefined(mathContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<SubFunctionPattern> getSubFunctions() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
return o instanceof UndefinedPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return UndefinedPattern.class.hashCode();
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.DivisionRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.EmptyNumberRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.ExpressionRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.JokeRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.MultiplicationRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.NegativeRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.NumberRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.PowerRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.RootRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.SubtractionRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.SumRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.SumSubtractionRule
|
@ -1,4 +1,4 @@
|
||||
package rules.functions;
|
||||
package it.cavallium.warppi.math.rules.functions;
|
||||
/*
|
||||
SETTINGS: (please don't move this part)
|
||||
PATH=functions.VariableRule
|
52
core/src/main/java/it/cavallium/warppi/util/MapFactory.java
Normal file
52
core/src/main/java/it/cavallium/warppi/util/MapFactory.java
Normal file
@ -0,0 +1,52 @@
|
||||
package it.cavallium.warppi.util;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Reimplements map creation methods which are not available on TeaVM.
|
||||
*/
|
||||
public class MapFactory {
|
||||
private MapFactory() {}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable map containing keys and values extracted from the given entries.
|
||||
* <p>
|
||||
* This method is equivalent to {@link Map#ofEntries(Map.Entry...)}.
|
||||
*
|
||||
* @param entries <code>Map.Entry</code>s containing the keys and values from which the map is populated
|
||||
* @param <K> the <code>Map</code>'s key type
|
||||
* @param <V> the <code>Map</code>'s value type
|
||||
* @return a Map containing the specified mappings
|
||||
* @see MapFactory#entry(K, V)
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <K, V> Map<K, V> fromEntries(Map.Entry<? extends K, ? extends V>... entries) {
|
||||
HashMap<K, V> map = new HashMap<>();
|
||||
for (Map.Entry<? extends K, ? extends V> entry : entries) {
|
||||
map.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable <code>Map.Entry</code> containing the given key and value.
|
||||
* These entries are suitable for populating Map instances using the {@link MapFactory#fromEntries(Map.Entry...)}
|
||||
* or {@link Map#ofEntries(Map.Entry...)} methods.
|
||||
* <p>
|
||||
* This method can be used as a replacement for {@link Map#entry(K, V)}, if the latter is not available (for example,
|
||||
* when compiling for TeaVM). However, unlike <code>Map.entry</code>, <code>null</code> keys and values are allowed,
|
||||
* and the returned <code>Entry</code> is serializable.
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @param <K> the key's type
|
||||
* @param <V> the value's type
|
||||
* @return an <code>Entry</code> containing the specified key and value
|
||||
*/
|
||||
public static <K, V> Map.Entry<K, V> entry(K key, V value) {
|
||||
return new AbstractMap.SimpleImmutableEntry<>(key, value);
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
functions/DivisionRule
|
||||
functions/EmptyNumberRule
|
||||
functions/ExpressionRule
|
||||
functions/JokeRule
|
||||
functions/MultiplicationRule
|
||||
functions/NegativeRule
|
||||
functions/NumberRule
|
||||
functions/PowerRule
|
||||
functions/RootRule
|
||||
functions/SubtractionRule
|
||||
functions/SumRule
|
||||
functions/SumSubtractionRule
|
||||
functions/VariableRule
|
||||
ExpandRule1
|
||||
ExpandRule2
|
||||
ExpandRule5
|
||||
ExponentRule1
|
||||
ExponentRule2
|
||||
ExponentRule3
|
||||
ExponentRule4
|
||||
ExponentRule8
|
||||
ExponentRule9
|
||||
ExponentRule15
|
||||
ExponentRule16
|
||||
ExponentRule17
|
||||
FractionsRule1
|
||||
FractionsRule2
|
||||
FractionsRule3
|
||||
FractionsRule4
|
||||
FractionsRule5
|
||||
FractionsRule6
|
||||
FractionsRule7
|
||||
FractionsRule8
|
||||
FractionsRule9
|
||||
FractionsRule10
|
||||
FractionsRule11
|
||||
FractionsRule12
|
||||
FractionsRule14
|
||||
NumberRule1
|
||||
NumberRule2
|
||||
NumberRule3
|
||||
NumberRule4
|
||||
NumberRule5
|
||||
NumberRule7
|
||||
UndefinedRule1
|
||||
UndefinedRule2
|
||||
VariableRule1
|
||||
VariableRule2
|
||||
VariableRule3
|
@ -1,35 +0,0 @@
|
||||
package it.cavallium.warppi;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
*/
|
||||
public class AppTest extends TestCase {
|
||||
/**
|
||||
* Create the test case
|
||||
*
|
||||
* @param testName
|
||||
* name of the test case
|
||||
*/
|
||||
public AppTest(final String testName) {
|
||||
super(testName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the suite of tests being tested
|
||||
*/
|
||||
public static Test suite() {
|
||||
return new TestSuite(AppTest.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rigourous Test :-)
|
||||
*/
|
||||
public void testApp() {
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.functions.Number;
|
||||
import it.cavallium.warppi.math.functions.Subtraction;
|
||||
import it.cavallium.warppi.math.functions.Sum;
|
||||
import it.cavallium.warppi.math.functions.SumSubtraction;
|
||||
import it.cavallium.warppi.math.rules.RuleType;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.*;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PatternRuleTest {
|
||||
private final MathContext mathContext = new MathContext();
|
||||
|
||||
private final Pattern x = new SubFunctionPattern("x");
|
||||
private final Pattern xPlus0 = new SumPattern(
|
||||
x,
|
||||
new NumberPattern(new BigDecimal(0))
|
||||
);
|
||||
|
||||
@Test
|
||||
void testNonMatching() throws InterruptedException, Error {
|
||||
final Function func = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 2)
|
||||
);
|
||||
|
||||
final PatternRule rule = new PatternRule("TestRule", RuleType.REDUCTION, xPlus0, xPlus0);
|
||||
assertNull(func.simplify(rule));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMatching() throws InterruptedException, Error {
|
||||
final Function func = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 0)
|
||||
);
|
||||
|
||||
final PatternRule identityRule = new PatternRule("Identity", RuleType.REDUCTION, xPlus0, xPlus0);
|
||||
final ObjectArrayList<Function> identityResult = func.simplify(identityRule);
|
||||
assertEquals(1, identityResult.size());
|
||||
assertEquals(func, identityResult.get(0));
|
||||
|
||||
final PatternRule simplifyRule = new PatternRule("Simplify", RuleType.REDUCTION, xPlus0, x);
|
||||
final ObjectArrayList<Function> simplifyResult = func.simplify(simplifyRule);
|
||||
assertEquals(1, identityResult.size());
|
||||
assertEquals(new Number(mathContext, 1), simplifyResult.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMatchingRecursive() throws InterruptedException, Error {
|
||||
final Function func = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 3),
|
||||
new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 5),
|
||||
new Number(mathContext, 0)
|
||||
)
|
||||
);
|
||||
|
||||
final PatternRule identityRule = new PatternRule("Identity", RuleType.REDUCTION, xPlus0, xPlus0);
|
||||
final ObjectArrayList<Function> identityResult = func.simplify(identityRule);
|
||||
assertEquals(1, identityResult.size());
|
||||
assertEquals(func, identityResult.get(0));
|
||||
|
||||
final PatternRule simplifyRule = new PatternRule("Simplify", RuleType.REDUCTION, xPlus0, x);
|
||||
final ObjectArrayList<Function> simplifyResult = func.simplify(simplifyRule);
|
||||
assertEquals(1, identityResult.size());
|
||||
final Function expected = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 3),
|
||||
new Number(mathContext, 5)
|
||||
);
|
||||
assertEquals(expected, simplifyResult.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleReplacements() throws InterruptedException, Error {
|
||||
final Number one = new Number(mathContext, 1);
|
||||
final Number two = new Number(mathContext, 2);
|
||||
final Function func = new SumSubtraction(mathContext, one, two);
|
||||
|
||||
final Pattern x = new SubFunctionPattern("x");
|
||||
final Pattern y = new SubFunctionPattern("y");
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
final PatternRule rule = new PatternRule(
|
||||
"TestRule",
|
||||
RuleType.EXPANSION,
|
||||
new SumSubtractionPattern(x, y),
|
||||
new SumPattern(x, y), new SubtractionPattern(x, y)
|
||||
);
|
||||
|
||||
final ObjectArrayList<Function> result = func.simplify(rule);
|
||||
final ObjectArrayList<Function> expected = ObjectArrayList.wrap(new Function[]{
|
||||
new Sum(mathContext, one, two),
|
||||
new Subtraction(mathContext, one, two)
|
||||
});
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoReplacements() throws InterruptedException, Error {
|
||||
final Function func = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 2)
|
||||
);
|
||||
|
||||
final PatternRule rule = new PatternRule(
|
||||
"TestRule",
|
||||
RuleType.REDUCTION,
|
||||
new SubFunctionPattern("x")
|
||||
);
|
||||
|
||||
assertTrue(func.simplify(rule).isEmpty());
|
||||
}
|
||||
}
|
@ -0,0 +1,336 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.Function;
|
||||
import it.cavallium.warppi.math.MathContext;
|
||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
||||
import it.cavallium.warppi.math.functions.*;
|
||||
import it.cavallium.warppi.math.functions.Number;
|
||||
import it.cavallium.warppi.math.functions.equations.Equation;
|
||||
import it.cavallium.warppi.math.functions.equations.EquationsSystem;
|
||||
import it.cavallium.warppi.math.functions.trigonometry.*;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.*;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PatternTest {
|
||||
private final MathContext mathContext = new MathContext();
|
||||
|
||||
@Test
|
||||
void subFunctionPattern() {
|
||||
final Pattern pattern = new SubFunctionPattern("x");
|
||||
|
||||
final Function func = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 2)
|
||||
);
|
||||
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(func);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
|
||||
assertEquals(func, pattern.replace(mathContext, subFunctions.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void undefinedSubFunction() {
|
||||
final Pattern pattern = new SubFunctionPattern("x");
|
||||
final Map<String, Function> subFunctions = Collections.singletonMap("y", new Number(mathContext, 1));
|
||||
|
||||
final var exception = assertThrows(UndefinedSubFunctionException.class, () ->
|
||||
pattern.replace(mathContext, subFunctions)
|
||||
);
|
||||
assertEquals("x", exception.getSubFunctionName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sumPattern() {
|
||||
final Pattern pattern = new SumPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("y")
|
||||
);
|
||||
|
||||
final Function shouldNotMatch = new Subtraction(
|
||||
mathContext,
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 2)
|
||||
);
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
|
||||
final Function shouldMatch = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 2)
|
||||
);
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertEquals(shouldMatch, pattern.replace(mathContext, subFunctions.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedSubFunction() {
|
||||
final Pattern pattern = new SumPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("x")
|
||||
);
|
||||
|
||||
final Function shouldMatch = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 1)
|
||||
);
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertEquals(shouldMatch, pattern.replace(mathContext, subFunctions.get()));
|
||||
|
||||
final Function shouldNotMatch = new Sum(
|
||||
mathContext,
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 2)
|
||||
);
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void numberPattern() {
|
||||
final Pattern pattern = new NumberPattern(BigDecimal.valueOf(Math.PI));
|
||||
|
||||
final Function shouldNotMatch = new Number(mathContext, 2);
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
|
||||
final Function shouldMatch = new Number(mathContext, Math.PI);
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertEquals(shouldMatch, pattern.replace(mathContext, subFunctions.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativePattern() {
|
||||
final Pattern pattern = new NegativePattern(
|
||||
new SubFunctionPattern("x")
|
||||
);
|
||||
|
||||
final Function shouldNotMatch = new Number(mathContext, 1);
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
|
||||
final Function shouldMatch = new Negative(
|
||||
mathContext,
|
||||
new Variable(mathContext, 'x', Variable.V_TYPE.VARIABLE)
|
||||
);
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertEquals(shouldMatch, pattern.replace(mathContext, subFunctions.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativePatternForNumber() {
|
||||
final Pattern pattern = new NegativePattern(
|
||||
new NumberPattern(new BigDecimal(1))
|
||||
);
|
||||
|
||||
final Function shouldNotMatch = new Number(mathContext, 1);
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
|
||||
final Function shouldMatch = new Number(mathContext, -1);
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertEquals(shouldMatch, pattern.replace(mathContext, subFunctions.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void undefinedPattern() {
|
||||
final Pattern pattern = new UndefinedPattern();
|
||||
|
||||
final Function shouldNotMatch = new Number(mathContext, 0);
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
|
||||
final Function shouldMatch = new Undefined(mathContext);
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertTrue(pattern.replace(mathContext, subFunctions.get()) instanceof Undefined);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equationsSystemPattern() {
|
||||
final Pattern pattern = new EquationsSystemPattern(new Pattern[]{
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("y"),
|
||||
new SubFunctionPattern("z")
|
||||
});
|
||||
|
||||
final Function shouldNotMatch = new EquationsSystem(
|
||||
mathContext,
|
||||
new Function[]{
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 2),
|
||||
}
|
||||
);
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
|
||||
final Function shouldMatch = new EquationsSystem(
|
||||
mathContext,
|
||||
new Function[]{
|
||||
new Number(mathContext, 1),
|
||||
new Number(mathContext, 2),
|
||||
new Number(mathContext, 3)
|
||||
}
|
||||
);
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertEquals(shouldMatch, pattern.replace(mathContext, subFunctions.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rootPatternForRootSquare() {
|
||||
final Pattern pattern = new RootPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("y")
|
||||
);
|
||||
|
||||
final Function root = new Root(
|
||||
mathContext,
|
||||
new Number(mathContext, 2),
|
||||
new Number(mathContext, 1)
|
||||
);
|
||||
final Optional<Map<String, Function>> rootSubFunctions = pattern.match(root);
|
||||
assertTrue(rootSubFunctions.isPresent());
|
||||
|
||||
final Function rootSquare = new RootSquare(
|
||||
mathContext,
|
||||
new Number(mathContext, 1)
|
||||
);
|
||||
final Optional<Map<String, Function>> rootSquareSubFunctions = pattern.match(rootSquare);
|
||||
assertTrue(rootSquareSubFunctions.isPresent());
|
||||
assertEquals(rootSubFunctions.get(), rootSquareSubFunctions.get());
|
||||
|
||||
final Function replacement = pattern.replace(mathContext, rootSubFunctions.get());
|
||||
assertTrue(replacement instanceof RootSquare);
|
||||
assertEquals(rootSquare, replacement);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constantPattern() {
|
||||
final Pattern pattern = new ConstantPattern(MathematicalSymbols.PI);
|
||||
|
||||
final Function shouldNotMatch = new Variable(
|
||||
mathContext,
|
||||
MathematicalSymbols.EULER_NUMBER,
|
||||
Variable.V_TYPE.CONSTANT
|
||||
);
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
|
||||
final Function shouldMatch = new Variable(
|
||||
mathContext,
|
||||
MathematicalSymbols.PI,
|
||||
Variable.V_TYPE.CONSTANT
|
||||
);
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertEquals(shouldMatch, pattern.replace(mathContext, subFunctions.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void otherBinaryPatterns() {
|
||||
final Number one = new Number(mathContext, 1);
|
||||
final Number two = new Number(mathContext, 2);
|
||||
final SubFunctionPattern x = new SubFunctionPattern("x");
|
||||
final SubFunctionPattern y = new SubFunctionPattern("y");
|
||||
|
||||
final Function shouldNotMatch = new Sum(mathContext, one, two);
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
final List<ImmutablePair<Pattern, Function>> patternsAndMatchingFunctions = Arrays.asList(
|
||||
new ImmutablePair<>(
|
||||
new DivisionPattern(x, y),
|
||||
new Division(mathContext, one, two)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new EquationPattern(x, y),
|
||||
new Equation(mathContext, one, two)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new LogarithmPattern(x, y),
|
||||
new Logarithm(mathContext, one, two)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new MultiplicationPattern(x, y),
|
||||
new Multiplication(mathContext, one, two)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new PowerPattern(x, y),
|
||||
new Power(mathContext, one, two)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new RootPattern(x, y),
|
||||
new Root(mathContext, one, two)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new SubtractionPattern(x, y),
|
||||
new Subtraction(mathContext, one, two)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new SumSubtractionPattern(x, y),
|
||||
new SumSubtraction(mathContext, one, two)
|
||||
)
|
||||
);
|
||||
|
||||
testMultiplePatterns(shouldNotMatch, patternsAndMatchingFunctions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void otherUnaryPatterns() {
|
||||
final Number one = new Number(mathContext, 1);
|
||||
final SubFunctionPattern x = new SubFunctionPattern("x");
|
||||
|
||||
final Function shouldNotMatch = new Negative(mathContext, one);
|
||||
|
||||
final List<ImmutablePair<Pattern, Function>> patternsAndMatchingFunctions = Arrays.asList(
|
||||
new ImmutablePair<>(
|
||||
new ArcCosinePattern(x),
|
||||
new ArcCosine(mathContext, one)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new ArcSinePattern(x),
|
||||
new ArcSine(mathContext, one)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new ArcTangentPattern(x),
|
||||
new ArcTangent(mathContext, one)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new CosinePattern(x),
|
||||
new Cosine(mathContext, one)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new SinePattern(x),
|
||||
new Sine(mathContext, one)
|
||||
),
|
||||
new ImmutablePair<>(
|
||||
new TangentPattern(x),
|
||||
new Tangent(mathContext, one)
|
||||
)
|
||||
);
|
||||
|
||||
testMultiplePatterns(shouldNotMatch, patternsAndMatchingFunctions);
|
||||
}
|
||||
|
||||
private void testMultiplePatterns(
|
||||
final Function shouldNotMatch,
|
||||
final List<ImmutablePair<Pattern, Function>> patternsAndMatchingFunctions
|
||||
) {
|
||||
for (final ImmutablePair<Pattern, Function> patternAndMatchingFunction : patternsAndMatchingFunctions) {
|
||||
final Pattern pattern = patternAndMatchingFunction.getLeft();
|
||||
final Function shouldMatch = patternAndMatchingFunction.getRight();
|
||||
|
||||
assertFalse(pattern.match(shouldNotMatch).isPresent());
|
||||
|
||||
final Optional<Map<String, Function>> subFunctions = pattern.match(shouldMatch);
|
||||
assertTrue(subFunctions.isPresent());
|
||||
assertEquals(shouldMatch, pattern.replace(mathContext, subFunctions.get()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package it.cavallium.warppi.math.rules.dsl;
|
||||
|
||||
import it.cavallium.warppi.math.rules.Rule;
|
||||
import it.cavallium.warppi.math.rules.RuleType;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.IncompleteNumberLiteral;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.Token;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.TokenType;
|
||||
import it.cavallium.warppi.math.rules.dsl.frontend.UnexpectedToken;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.NegativePattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.NumberPattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.SubFunctionPattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.SumPattern;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class RulesDslTest {
|
||||
@Test
|
||||
void validRules() throws DslAggregateException {
|
||||
final List<Rule> rules = RulesDsl.makeRules(
|
||||
"reduction test1: x -> x\n" +
|
||||
"expansion test2:\n" +
|
||||
" x -> --x\n" +
|
||||
"calculation test3:\n" +
|
||||
" 1 + 1 -> 2\n"
|
||||
);
|
||||
|
||||
final List<Rule> expected = Arrays.asList(
|
||||
new PatternRule(
|
||||
"test1",
|
||||
RuleType.REDUCTION,
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("x")
|
||||
),
|
||||
new PatternRule(
|
||||
"test2",
|
||||
RuleType.EXPANSION,
|
||||
new SubFunctionPattern("x"),
|
||||
new NegativePattern(new NegativePattern(new SubFunctionPattern("x")))
|
||||
),
|
||||
new PatternRule(
|
||||
"test3",
|
||||
RuleType.CALCULATION,
|
||||
new SumPattern(
|
||||
new NumberPattern(new BigDecimal(1)),
|
||||
new NumberPattern(new BigDecimal(1))
|
||||
),
|
||||
new NumberPattern(new BigDecimal(2))
|
||||
)
|
||||
);
|
||||
|
||||
assertEquals(expected, rules);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lexerError() {
|
||||
final var exception = assertThrows(DslAggregateException.class, () ->
|
||||
RulesDsl.makeRules("reduction test: 2. 5 -> 1")
|
||||
);
|
||||
final var expectedErrors = Collections.singletonList(
|
||||
new IncompleteNumberLiteral(16, "2.")
|
||||
);
|
||||
assertEquals(expectedErrors, exception.getErrors());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parserError() {
|
||||
final var exception = assertThrows(DslAggregateException.class, () ->
|
||||
RulesDsl.makeRules("existence test: x + y ->")
|
||||
);
|
||||
final var expectedErrors = Collections.singletonList(
|
||||
new UnexpectedToken(new Token(TokenType.EOF, "", 24))
|
||||
);
|
||||
assertEquals(expectedErrors, exception.getErrors());
|
||||
}
|
||||
|
||||
@Test
|
||||
void undefinedSubFunction() {
|
||||
final var exception = assertThrows(DslAggregateException.class, () ->
|
||||
RulesDsl.makeRules("expansion test: x -> x + y")
|
||||
);
|
||||
final var expectedErrors = Collections.singletonList(
|
||||
new UndefinedSubFunction(new Token(TokenType.IDENTIFIER, "y", 25))
|
||||
);
|
||||
assertEquals(expectedErrors, exception.getErrors());
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.errorutils;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class LineMapTest {
|
||||
@Test
|
||||
void emptyText() {
|
||||
String text = "";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
assertEquals(Collections.emptyList(), map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noLineSeparators() {
|
||||
String text = "single line";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Collections.singletonList(
|
||||
new LineMap.Line(1, 0, text)
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void trailingLf() {
|
||||
String text = "single line\n";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Collections.singletonList(
|
||||
new LineMap.Line(1, 0, "single line")
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void trailingCr() {
|
||||
String text = "single line\r";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Collections.singletonList(
|
||||
new LineMap.Line(1, 0, "single line")
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void trailingCrLf() {
|
||||
String text = "single line\r\n";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Collections.singletonList(
|
||||
new LineMap.Line(1, 0, "single line")
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleNonEmptyLines() {
|
||||
String text = "line 1\nline 2\rline 3\r\nline 4";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Arrays.asList(
|
||||
new LineMap.Line(1, 0, "line 1"),
|
||||
new LineMap.Line(2, 7, "line 2"),
|
||||
new LineMap.Line(3, 14, "line 3"),
|
||||
new LineMap.Line(4, 22, "line 4")
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleEmptyLine() {
|
||||
String text = "\n";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Collections.singletonList(
|
||||
new LineMap.Line(1, 0, "")
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleEmptyLines() {
|
||||
String text = "\r\n\n\r";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Arrays.asList(
|
||||
new LineMap.Line(1, 0, ""), // Terminated by \r\n
|
||||
new LineMap.Line(2, 2, ""), // Terminated by \n
|
||||
new LineMap.Line(3, 3, "") // Terminated by \r
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mixedEmptyAndNonEmptyLines() {
|
||||
String text = "line 1\nline 2\r\r\nline 4\n\n";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Arrays.asList(
|
||||
new LineMap.Line(1, 0, "line 1"),
|
||||
new LineMap.Line(2, 7, "line 2"),
|
||||
new LineMap.Line(3, 14, ""),
|
||||
new LineMap.Line(4, 16, "line 4"),
|
||||
new LineMap.Line(5, 23, "")
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(0, text.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptySubstrings() {
|
||||
String text = "single line\n";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Collections.singletonList(
|
||||
new LineMap.Line(1, 0, "single line")
|
||||
);
|
||||
for (int start = 0; start <= text.length(); start++) {
|
||||
assertEquals(expected, map.getSpannedLines(start, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void substringIsJustLineSeparator() {
|
||||
String separator = "\n";
|
||||
String text = "line 1" + separator + "line 2";
|
||||
LineMap map = new LineMap(text);
|
||||
|
||||
List<LineMap.Line> expected = Collections.singletonList(
|
||||
new LineMap.Line(1, 0, "line 1")
|
||||
);
|
||||
assertEquals(expected, map.getSpannedLines(text.indexOf(separator), separator.length()));
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static it.cavallium.warppi.math.rules.dsl.frontend.TokenType.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class LexerTest {
|
||||
private final List<DslError> errors = new ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
errors.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyInput() {
|
||||
final Lexer lexer = new Lexer("", errors::add);
|
||||
final List<Token> expected = Collections.singletonList(
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
assertEquals(expected, lexer.lex());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleLexCalls() {
|
||||
final Lexer lexer = new Lexer("", errors::add);
|
||||
lexer.lex();
|
||||
assertThrows(IllegalStateException.class, lexer::lex);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validRule() {
|
||||
final Lexer lexer = new Lexer(
|
||||
"reduction TestRule_123:\n" +
|
||||
" x + y * z = -(a_123 +- 3 / 2.2) -> [\n" +
|
||||
" x^a_123 = cos(pi) - log(e, e), // comment\n" +
|
||||
" undefined, /*\n" +
|
||||
"comment */ ]\n",
|
||||
errors::add
|
||||
);
|
||||
final List<Token> expected = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "TestRule_123", 10),
|
||||
new Token(COLON, ":", 22),
|
||||
new Token(IDENTIFIER, "x", 26),
|
||||
new Token(PLUS, "+", 28),
|
||||
new Token(IDENTIFIER, "y", 30),
|
||||
new Token(TIMES, "*", 32),
|
||||
new Token(IDENTIFIER, "z", 34),
|
||||
new Token(EQUALS, "=", 36),
|
||||
new Token(MINUS, "-", 38),
|
||||
new Token(LEFT_PAREN, "(", 39),
|
||||
new Token(IDENTIFIER, "a_123", 40),
|
||||
new Token(PLUS_MINUS, "+-", 46),
|
||||
new Token(NUMBER, "3", 49),
|
||||
new Token(DIVIDE, "/", 51),
|
||||
new Token(NUMBER, "2.2", 53),
|
||||
new Token(RIGHT_PAREN, ")", 56),
|
||||
new Token(ARROW, "->", 58),
|
||||
new Token(LEFT_BRACKET, "[", 61),
|
||||
new Token(IDENTIFIER, "x", 67),
|
||||
new Token(POWER, "^", 68),
|
||||
new Token(IDENTIFIER, "a_123", 69),
|
||||
new Token(EQUALS, "=", 75),
|
||||
new Token(COS, "cos", 77),
|
||||
new Token(LEFT_PAREN, "(", 80),
|
||||
new Token(PI, "pi", 81),
|
||||
new Token(RIGHT_PAREN, ")", 83),
|
||||
new Token(MINUS, "-", 85),
|
||||
new Token(LOG, "log", 87),
|
||||
new Token(LEFT_PAREN, "(", 90),
|
||||
new Token(E, "e", 91),
|
||||
new Token(COMMA, ",", 92),
|
||||
new Token(E, "e", 94),
|
||||
new Token(RIGHT_PAREN, ")", 95),
|
||||
new Token(COMMA, ",", 96),
|
||||
new Token(UNDEFINED, "undefined", 113),
|
||||
new Token(COMMA, ",", 122),
|
||||
new Token(RIGHT_BRACKET, "]", 138),
|
||||
new Token(EOF, "", 140)
|
||||
);
|
||||
assertEquals(expected, lexer.lex());
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void incompleteNumberOtherChar() {
|
||||
final Lexer lexer = new Lexer("2. 5 + 3", errors::add);
|
||||
|
||||
final List<Token> expectedTokens = Arrays.asList(
|
||||
new Token(NUMBER, "5", 3),
|
||||
new Token(PLUS, "+", 5),
|
||||
new Token(NUMBER, "3", 7),
|
||||
new Token(EOF, "", 8)
|
||||
);
|
||||
assertEquals(expectedTokens, lexer.lex());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(
|
||||
new IncompleteNumberLiteral(0, "2.")
|
||||
);
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void incompleteNumberEof() {
|
||||
final Lexer lexer = new Lexer("2.", errors::add);
|
||||
|
||||
final List<Token> expectedTokens = Collections.singletonList(
|
||||
new Token(EOF, "", 2)
|
||||
);
|
||||
assertEquals(expectedTokens, lexer.lex());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(
|
||||
new IncompleteNumberLiteral(0, "2.")
|
||||
);
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unexpectedCharacters() {
|
||||
final Lexer lexer = new Lexer("reduction @| .: {}", errors::add);
|
||||
|
||||
final List<Token> expectedTokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(COLON, ":", 14),
|
||||
new Token(EOF, "", 18)
|
||||
);
|
||||
assertEquals(expectedTokens, lexer.lex());
|
||||
|
||||
final List<DslError> expectedErrors = Arrays.asList(
|
||||
new UnexpectedCharacters(10, "@|"),
|
||||
new UnexpectedCharacters(13, "."),
|
||||
new UnexpectedCharacters(16, "{}")
|
||||
);
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unterminatedComment() {
|
||||
final Lexer lexer = new Lexer("reduction /* test:\n x -> x", errors::add);
|
||||
|
||||
final List<Token> expectedTokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(EOF, "", 26)
|
||||
);
|
||||
assertEquals(expectedTokens, lexer.lex());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(
|
||||
new UnterminatedComment(10)
|
||||
);
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorOrder() {
|
||||
final Lexer lexer = new Lexer(".2. @", errors::add);
|
||||
|
||||
final List<Token> expectedTokens = Collections.singletonList(
|
||||
new Token(EOF, "", 5)
|
||||
);
|
||||
assertEquals(expectedTokens, lexer.lex());
|
||||
|
||||
final List<DslError> expectedErrors = Arrays.asList(
|
||||
new UnexpectedCharacters(0, "."),
|
||||
new IncompleteNumberLiteral(1, "2."),
|
||||
new UnexpectedCharacters(4, "@")
|
||||
);
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
}
|
@ -0,0 +1,780 @@
|
||||
package it.cavallium.warppi.math.rules.dsl.frontend;
|
||||
|
||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
||||
import it.cavallium.warppi.math.rules.RuleType;
|
||||
import it.cavallium.warppi.math.rules.dsl.DslError;
|
||||
import it.cavallium.warppi.math.rules.dsl.Pattern;
|
||||
import it.cavallium.warppi.math.rules.dsl.PatternRule;
|
||||
import it.cavallium.warppi.math.rules.dsl.patterns.*;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static it.cavallium.warppi.math.rules.dsl.frontend.TokenType.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ParserTest {
|
||||
private final List<DslError> errors = new ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
errors.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noRules() {
|
||||
final List<Token> tokens = Collections.singletonList(
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
assertEquals(Collections.emptyList(), parser.parse());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleParseCalls() {
|
||||
final List<Token> tokens = Collections.singletonList(
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
parser.parse();
|
||||
assertThrows(IllegalStateException.class, parser::parse);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validRuleMultipleReplacements() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "TestRule_123", 10),
|
||||
new Token(COLON, ":", 22),
|
||||
new Token(IDENTIFIER, "x", 26),
|
||||
new Token(PLUS, "+", 28),
|
||||
new Token(IDENTIFIER, "y", 30),
|
||||
new Token(TIMES, "*", 32),
|
||||
new Token(IDENTIFIER, "z", 34),
|
||||
new Token(EQUALS, "=", 36),
|
||||
new Token(MINUS, "-", 38),
|
||||
new Token(LEFT_PAREN, "(", 39),
|
||||
new Token(IDENTIFIER, "a_123", 40),
|
||||
new Token(PLUS_MINUS, "+-", 46),
|
||||
new Token(NUMBER, "3", 49),
|
||||
new Token(DIVIDE, "/", 51),
|
||||
new Token(NUMBER, "2.2", 53),
|
||||
new Token(RIGHT_PAREN, ")", 56),
|
||||
new Token(ARROW, "->", 58),
|
||||
new Token(LEFT_BRACKET, "[", 61),
|
||||
new Token(IDENTIFIER, "x", 67),
|
||||
new Token(POWER, "^", 68),
|
||||
new Token(IDENTIFIER, "a_123", 69),
|
||||
new Token(EQUALS, "=", 75),
|
||||
new Token(COS, "cos", 77),
|
||||
new Token(LEFT_PAREN, "(", 80),
|
||||
new Token(PI, "pi", 81),
|
||||
new Token(RIGHT_PAREN, ")", 83),
|
||||
new Token(MINUS, "-", 85),
|
||||
new Token(LOG, "log", 87),
|
||||
new Token(LEFT_PAREN, "(", 90),
|
||||
new Token(E, "e", 91),
|
||||
new Token(COMMA, ",", 92),
|
||||
new Token(E, "e", 94),
|
||||
new Token(RIGHT_PAREN, ")", 95),
|
||||
new Token(COMMA, ",", 96),
|
||||
new Token(UNDEFINED, "undefined", 113),
|
||||
new Token(COMMA, ",", 122),
|
||||
new Token(RIGHT_BRACKET, "]", 138),
|
||||
new Token(EOF, "", 140)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
// x + y * z = -(a_123 +- 3 / 2.2)
|
||||
final Pattern target = new EquationPattern(
|
||||
new SumPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new MultiplicationPattern(
|
||||
new SubFunctionPattern("y"),
|
||||
new SubFunctionPattern("z")
|
||||
)
|
||||
),
|
||||
new NegativePattern(new SumSubtractionPattern(
|
||||
new SubFunctionPattern("a_123"),
|
||||
new DivisionPattern(
|
||||
new NumberPattern(new BigDecimal(3)),
|
||||
new NumberPattern(new BigDecimal("2.2"))
|
||||
)
|
||||
))
|
||||
);
|
||||
// x^a_123 = cos(pi) - log(e, e)
|
||||
final Pattern replacement1 = new EquationPattern(
|
||||
new PowerPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("a_123")
|
||||
),
|
||||
new SubtractionPattern(
|
||||
new CosinePattern(new ConstantPattern(MathematicalSymbols.PI)),
|
||||
new LogarithmPattern(
|
||||
new ConstantPattern(MathematicalSymbols.EULER_NUMBER),
|
||||
new ConstantPattern(MathematicalSymbols.EULER_NUMBER)
|
||||
)
|
||||
)
|
||||
);
|
||||
final Pattern replacement2 = new UndefinedPattern();
|
||||
final List<PatternRule> expected = Collections.singletonList(new PatternRule(
|
||||
"TestRule_123",
|
||||
RuleType.REDUCTION,
|
||||
target,
|
||||
replacement1,
|
||||
replacement2
|
||||
));
|
||||
|
||||
assertEquals(expected, parser.parse());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validRuleNoReplacements() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
final List<PatternRule> expected = Collections.singletonList(new PatternRule(
|
||||
"test",
|
||||
RuleType.EXISTENCE,
|
||||
new SumPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("y")
|
||||
)
|
||||
));
|
||||
assertEquals(expected, parser.parse());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validRuleOneReplacement() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(MINUS, "-", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(TIMES, "*", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(MINUS, "-", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(POWER, "^", 0),
|
||||
new Token(NUMBER, "2", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
final List<PatternRule> expected = Collections.singletonList(new PatternRule(
|
||||
"test",
|
||||
RuleType.REDUCTION,
|
||||
new MultiplicationPattern(
|
||||
new NegativePattern(new SubFunctionPattern("x")),
|
||||
new SubFunctionPattern("x")
|
||||
),
|
||||
new NegativePattern(new PowerPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new NumberPattern(new BigDecimal(2))
|
||||
))
|
||||
));
|
||||
assertEquals(expected, parser.parse());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validRuleOneReplacementBrackets() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(NUMBER, "1", 0),
|
||||
new Token(DIVIDE, "/", 0),
|
||||
new Token(LEFT_PAREN, "(", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(TIMES, "*", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(RIGHT_PAREN, ")", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(POWER, "^", 0),
|
||||
new Token(MINUS, "-", 0),
|
||||
new Token(NUMBER, "2", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
final List<PatternRule> expected = Collections.singletonList(new PatternRule(
|
||||
"test",
|
||||
RuleType.REDUCTION,
|
||||
new DivisionPattern(
|
||||
new NumberPattern(new BigDecimal(1)),
|
||||
new MultiplicationPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("x")
|
||||
)
|
||||
),
|
||||
new PowerPattern(
|
||||
new SubFunctionPattern("x"),
|
||||
new NegativePattern(
|
||||
new NumberPattern(new BigDecimal(2))
|
||||
)
|
||||
)
|
||||
));
|
||||
assertEquals(expected, parser.parse());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleValidRules() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "test1", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
|
||||
new Token(EXPANSION, "expansion", 0),
|
||||
new Token(IDENTIFIER, "test2", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(MINUS, "-", 0),
|
||||
new Token(MINUS, "-", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
|
||||
new Token(CALCULATION, "calculation", 0),
|
||||
new Token(IDENTIFIER, "test3", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(NUMBER, "1", 0),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(NUMBER, "1", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(NUMBER, "2", 0),
|
||||
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
final List<PatternRule> expected = Arrays.asList(
|
||||
new PatternRule(
|
||||
"test1",
|
||||
RuleType.REDUCTION,
|
||||
new SubFunctionPattern("x"),
|
||||
new SubFunctionPattern("x")
|
||||
),
|
||||
new PatternRule(
|
||||
"test2",
|
||||
RuleType.EXPANSION,
|
||||
new SubFunctionPattern("x"),
|
||||
new NegativePattern(new NegativePattern(new SubFunctionPattern("x")))
|
||||
),
|
||||
new PatternRule(
|
||||
"test3",
|
||||
RuleType.CALCULATION,
|
||||
new SumPattern(
|
||||
new NumberPattern(new BigDecimal(1)),
|
||||
new NumberPattern(new BigDecimal(1))
|
||||
),
|
||||
new NumberPattern(new BigDecimal(2))
|
||||
)
|
||||
);
|
||||
assertEquals(expected, parser.parse());
|
||||
}
|
||||
|
||||
@Test
|
||||
void subFunctionIdentifiers() {
|
||||
final List<ReferenceEqualityToken> rule0x = new ArrayList<>();
|
||||
final List<ReferenceEqualityToken> rule1x = new ArrayList<>();
|
||||
final List<ReferenceEqualityToken> rule1y = new ArrayList<>();
|
||||
final List<ReferenceEqualityToken> rule2x = new ArrayList<>();
|
||||
final List<ReferenceEqualityToken> rule3x = new ArrayList<>();
|
||||
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "test1", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(PLUS, "+", 0),
|
||||
addIdentifierToken(rule0x, "x"),
|
||||
new Token(ARROW, "->", 0),
|
||||
addIdentifierToken(rule0x, "x"),
|
||||
|
||||
new Token(EXPANSION, "expansion", 0),
|
||||
new Token(IDENTIFIER, "test2", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
addIdentifierToken(rule1x, "x"),
|
||||
new Token(POWER, "^", 0),
|
||||
new Token(MINUS, "-", 0),
|
||||
addIdentifierToken(rule1y, "y"),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(NUMBER, "1", 0),
|
||||
new Token(DIVIDE, "/", 0),
|
||||
addIdentifierToken(rule1x, "x"),
|
||||
new Token(POWER, "^", 0),
|
||||
addIdentifierToken(rule1y, "y"),
|
||||
|
||||
// Rule with the same name (and type)
|
||||
new Token(CALCULATION, "expansion", 0),
|
||||
new Token(IDENTIFIER, "test2", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
addIdentifierToken(rule2x, "x"),
|
||||
new Token(ARROW, "->", 0),
|
||||
addIdentifierToken(rule2x, "x"),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(NUMBER, "0", 0),
|
||||
|
||||
// Identical rule
|
||||
new Token(CALCULATION, "expansion", 0),
|
||||
new Token(IDENTIFIER, "test2", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
addIdentifierToken(rule3x, "x"),
|
||||
new Token(ARROW, "->", 0),
|
||||
addIdentifierToken(rule3x, "x"),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(NUMBER, "0", 0),
|
||||
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
final List<PatternRule> rules = parser.parse();
|
||||
|
||||
assertEquals(rule0x, getSubFunctionIdentifiers(parser, rules.get(0), "x"));
|
||||
assertEquals(rule1x, getSubFunctionIdentifiers(parser, rules.get(1), "x"));
|
||||
assertEquals(rule1y, getSubFunctionIdentifiers(parser, rules.get(1), "y"));
|
||||
assertEquals(rule2x, getSubFunctionIdentifiers(parser, rules.get(2), "x"));
|
||||
assertEquals(rule3x, getSubFunctionIdentifiers(parser, rules.get(3), "x"));
|
||||
}
|
||||
|
||||
private static Token addIdentifierToken(final List<ReferenceEqualityToken> list, final String identifier) {
|
||||
final Token token = new Token(IDENTIFIER, identifier, 0);
|
||||
list.add(new ReferenceEqualityToken(token));
|
||||
return token;
|
||||
}
|
||||
|
||||
private static List<ReferenceEqualityToken> getSubFunctionIdentifiers(
|
||||
final Parser parser,
|
||||
final PatternRule rule,
|
||||
final String subFunctionName
|
||||
) {
|
||||
final SubFunctionPattern exampleSubFunction = new SubFunctionPattern(subFunctionName);
|
||||
final Stream<SubFunctionPattern> allSubFunctions = Stream.concat(
|
||||
rule.getTarget().getSubFunctions(),
|
||||
rule.getReplacements().stream().flatMap(Pattern::getSubFunctions)
|
||||
);
|
||||
return allSubFunctions
|
||||
.filter(subFunc -> subFunc.equals(exampleSubFunction)) // Match the name without having access to it directly
|
||||
.map(subFunc -> new ReferenceEqualityToken(
|
||||
parser.getSubFunctionIdentifier(subFunc)
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static class ReferenceEqualityToken {
|
||||
private final Token token;
|
||||
|
||||
ReferenceEqualityToken(final Token token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (!(o instanceof ReferenceEqualityToken)) {
|
||||
return false;
|
||||
}
|
||||
return this.token == ((ReferenceEqualityToken) o).token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReferenceEqualityToken{" + ObjectUtils.identityToString(token) + '}';
|
||||
}
|
||||
}
|
||||
|
||||
// The EOF token is inserted by the lexer, therefore it can only be missing
|
||||
// in case of programming errors, and not directly because of user input.
|
||||
@Test
|
||||
void missingEof() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
assertThrows(RuntimeException.class, parser::parse);
|
||||
}
|
||||
|
||||
@Test
|
||||
void incompleteRule() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(EOF, "", 0)
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRuleType() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
REDUCTION, EXPANSION, CALCULATION, EXISTENCE
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unexpectedTokenPrimary() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(CALCULATION, "calculation", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(CALCULATION, "calculation", 0)
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRuleName() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(COLON, ":", 0),
|
||||
IDENTIFIER
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingColon() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
COLON
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingArrow() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
ARROW
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRightBracket() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(EOF, "", 0),
|
||||
RIGHT_BRACKET
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingOneArgFunctionLeftParen() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(SIN, "sin", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(RIGHT_PAREN, ")", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
LEFT_PAREN
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingOneArgFunctionRightParen() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(SIN, "sin", 0),
|
||||
new Token(LEFT_PAREN, "(", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(ARROW, "->", 0),
|
||||
RIGHT_PAREN
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingTwoArgFunctionLeftParen() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(LOG, "log", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(COMMA, ",", 0),
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
new Token(RIGHT_PAREN, ")", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
LEFT_PAREN
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingTwoArgFunctionComma() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(LOG, "log", 0),
|
||||
new Token(LEFT_PAREN, "(", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
new Token(RIGHT_PAREN, ")", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
COMMA
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingTwoArgFunctionRightParen() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(LOG, "log", 0),
|
||||
new Token(LEFT_PAREN, "(", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(COMMA, ",", 0),
|
||||
new Token(IDENTIFIER, "y", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(ARROW, "->", 0),
|
||||
RIGHT_PAREN
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingExpressionRightParen() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(EXISTENCE, "existence", 0),
|
||||
new Token(IDENTIFIER, "test", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(LEFT_PAREN, "(", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(LEFT_BRACKET, "[", 0),
|
||||
new Token(RIGHT_BRACKET, "]", 0),
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
assertTrue(parser.parse().isEmpty());
|
||||
|
||||
final List<DslError> expectedErrors = Collections.singletonList(new UnexpectedToken(
|
||||
new Token(ARROW, "->", 0),
|
||||
RIGHT_PAREN
|
||||
));
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
void recoveryToNextRule() {
|
||||
final List<Token> tokens = Arrays.asList(
|
||||
new Token(REDUCTION, "reduction", 0),
|
||||
new Token(IDENTIFIER, "test1", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(TIMES, "+", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
|
||||
new Token(EXPANSION, "expansion", 0),
|
||||
new Token(IDENTIFIER, "test2", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(MINUS, "-", 0),
|
||||
new Token(MINUS, "-", 0),
|
||||
new Token(IDENTIFIER, "x", 0),
|
||||
|
||||
new Token(CALCULATION, "calculation", 0),
|
||||
new Token(IDENTIFIER, "test3", 0),
|
||||
new Token(COLON, ":", 0),
|
||||
new Token(NUMBER, "1", 0),
|
||||
new Token(PLUS, "+", 0),
|
||||
new Token(NUMBER, "1", 0),
|
||||
new Token(ARROW, "->", 0),
|
||||
new Token(NUMBER, "2", 0),
|
||||
|
||||
new Token(EOF, "", 0)
|
||||
);
|
||||
final Parser parser = new Parser(tokens, errors::add);
|
||||
|
||||
final List<PatternRule> expectedRules = Collections.singletonList(new PatternRule(
|
||||
"test3",
|
||||
RuleType.CALCULATION,
|
||||
new SumPattern(
|
||||
new NumberPattern(new BigDecimal(1)),
|
||||
new NumberPattern(new BigDecimal(1))
|
||||
),
|
||||
new NumberPattern(new BigDecimal(2))
|
||||
));
|
||||
assertEquals(expectedRules, parser.parse());
|
||||
|
||||
final List<DslError> expectedErrors = Arrays.asList(
|
||||
new UnexpectedToken(new Token(ARROW, "->", 0)),
|
||||
new UnexpectedToken(new Token(IDENTIFIER, "x", 0), COLON)
|
||||
);
|
||||
assertEquals(expectedErrors, errors);
|
||||
}
|
||||
}
|
@ -28,16 +28,6 @@
|
||||
<artifactId>jansi</artifactId>
|
||||
<version>1.17.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.lingala.zip4j</groupId>
|
||||
<artifactId>zip4j</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.tycho</groupId>
|
||||
<artifactId>org.eclipse.jdt.core</artifactId>
|
||||
<version>3.14.0.v20171206-0802</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ar.com.hjg</groupId>
|
||||
<artifactId>pngj</artifactId>
|
||||
|
@ -5,7 +5,9 @@ import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@ -17,9 +19,6 @@ import it.cavallium.warppi.gui.graphicengine.impl.jogl.JOGLEngine;
|
||||
import it.cavallium.warppi.gui.graphicengine.impl.swing.SwingEngine;
|
||||
import it.cavallium.warppi.util.CacheUtils;
|
||||
import it.cavallium.warppi.util.Error;
|
||||
import net.lingala.zip4j.core.ZipFile;
|
||||
import net.lingala.zip4j.model.ZipParameters;
|
||||
import net.lingala.zip4j.util.Zip4jConstants;
|
||||
|
||||
public class DesktopPlatform implements Platform {
|
||||
|
||||
@ -157,53 +156,18 @@ public class DesktopPlatform implements Platform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlatformRules() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zip(final String targetPath, final String destinationFilePath, final String password) {
|
||||
try {
|
||||
final ZipParameters parameters = new ZipParameters();
|
||||
parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
|
||||
parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
|
||||
|
||||
if (password.length() > 0) {
|
||||
parameters.setEncryptFiles(true);
|
||||
parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
|
||||
parameters.setAesKeyStrength(Zip4jConstants.AES_STRENGTH_256);
|
||||
parameters.setPassword(password);
|
||||
public List<String> getRuleFilePaths() throws IOException {
|
||||
final File dslRulesPath = getStorageUtils().get("rules/");
|
||||
List<String> paths = new ArrayList<>();
|
||||
if (dslRulesPath.exists()) {
|
||||
for (final File file : getStorageUtils().walk(dslRulesPath)) {
|
||||
final String path = file.toString();
|
||||
if (path.endsWith(".rules")) {
|
||||
paths.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
final ZipFile zipFile = new ZipFile(destinationFilePath);
|
||||
|
||||
final File targetFile = new File(targetPath);
|
||||
if (targetFile.isFile())
|
||||
zipFile.addFile(targetFile, parameters);
|
||||
else if (targetFile.isDirectory())
|
||||
zipFile.addFolder(targetFile, parameters);
|
||||
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unzip(final String targetZipFilePath, final String destinationFolderPath, final String password) {
|
||||
try {
|
||||
final ZipFile zipFile = new ZipFile(targetZipFilePath);
|
||||
if (zipFile.isEncrypted())
|
||||
zipFile.setPassword(password);
|
||||
zipFile.extractAll(destinationFolderPath);
|
||||
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean compile(final String[] command, final PrintWriter printWriter, final PrintWriter errors) {
|
||||
return org.eclipse.jdt.internal.compiler.batch.Main.compile(command, printWriter, errors, null);
|
||||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user