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>
|
<artifactId>commons-lang3</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit-jupiter</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -60,13 +60,15 @@ public interface Platform {
|
|||||||
|
|
||||||
String[] stacktraceToString(Error e);
|
String[] stacktraceToString(Error e);
|
||||||
|
|
||||||
void loadPlatformRules();
|
/**
|
||||||
|
* Determines the list of files containing DSL rules to load.
|
||||||
void zip(String targetPath, String destinationFilePath, String password);
|
*
|
||||||
|
* @return a <code>List</code> of paths of files which contain DSL rules.
|
||||||
void unzip(String targetZipFilePath, String destinationFolderPath, String password);
|
* 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.
|
||||||
boolean compile(String[] command, PrintWriter printWriter, PrintWriter errors);
|
* @throws IOException if an IO error occurs while getting the list of rule file paths.
|
||||||
|
*/
|
||||||
|
List<String> getRuleFilePaths() throws IOException;
|
||||||
|
|
||||||
public interface Gpio {
|
public interface Gpio {
|
||||||
int valueOutput();
|
int valueOutput();
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
package it.cavallium.warppi.math;
|
package it.cavallium.warppi.math;
|
||||||
|
|
||||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
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.math.rules.Rule;
|
||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
@ -15,6 +21,16 @@ public interface Function {
|
|||||||
@Override
|
@Override
|
||||||
String toString();
|
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
|
@Override
|
||||||
boolean equals(Object o);
|
boolean equals(Object o);
|
||||||
|
|
||||||
@ -80,4 +96,49 @@ public interface Function {
|
|||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
ObjectArrayList<Block> toBlock(MathContext context) 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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return functions.hashCode() + 883 * super.hashCode();
|
return Arrays.hashCode(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object o) {
|
public abstract boolean equals(Object o);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import it.cavallium.warppi.util.Errors;
|
|||||||
import it.cavallium.warppi.util.Utils;
|
import it.cavallium.warppi.util.Utils;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public abstract class FunctionOperator implements Function {
|
public abstract class FunctionOperator implements Function {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,7 +177,7 @@ public abstract class FunctionOperator implements Function {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return parameter1.hashCode() + 7 * parameter2.hashCode() + 883 * super.hashCode();
|
return Objects.hash(parameter1, parameter2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4,6 +4,8 @@ import it.cavallium.warppi.math.rules.Rule;
|
|||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public abstract class FunctionSingle implements Function {
|
public abstract class FunctionSingle implements Function {
|
||||||
|
|
||||||
private boolean simplified;
|
private boolean simplified;
|
||||||
@ -117,7 +119,7 @@ public abstract class FunctionSingle implements Function {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return parameter.hashCode() + 883 * super.hashCode();
|
return Objects.hash(parameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -59,4 +59,9 @@ public class Division extends FunctionOperator {
|
|||||||
result.add(bd);
|
result.add(bd);
|
||||||
return result;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||||
|
return visitor.visit(this, argument);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String s = "(";
|
String s = "(";
|
||||||
|
@ -61,4 +61,8 @@ public class Joke implements Function {
|
|||||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
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;
|
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.Block;
|
||||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||||
import it.cavallium.warppi.gui.expression.blocks.BlockParenthesis;
|
import it.cavallium.warppi.gui.expression.blocks.BlockParenthesis;
|
||||||
import it.cavallium.warppi.math.Function;
|
import it.cavallium.warppi.math.*;
|
||||||
import it.cavallium.warppi.math.FunctionOperator;
|
|
||||||
import it.cavallium.warppi.math.MathContext;
|
|
||||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
|
||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
@ -24,11 +21,7 @@ public class Multiplication extends FunctionOperator {
|
|||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
if (o instanceof Multiplication) {
|
if (o instanceof Multiplication) {
|
||||||
final FunctionOperator f = (FunctionOperator) o;
|
final FunctionOperator f = (FunctionOperator) o;
|
||||||
if (parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2())) {
|
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
|
||||||
return true;
|
|
||||||
} else if (parameter1.equals(f.getParameter2()) && parameter2.equals(f.getParameter1())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
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() {
|
public boolean isNegative() {
|
||||||
return parameter1.equals(new Number(getMathContext(), -1)) || parameter2.equals(new Number(getMathContext(), -1));
|
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.Block;
|
||||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||||
import it.cavallium.warppi.gui.expression.blocks.BlockParenthesis;
|
import it.cavallium.warppi.gui.expression.blocks.BlockParenthesis;
|
||||||
import it.cavallium.warppi.math.Function;
|
import it.cavallium.warppi.math.*;
|
||||||
import it.cavallium.warppi.math.FunctionSingle;
|
|
||||||
import it.cavallium.warppi.math.MathContext;
|
|
||||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
|
||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
@ -52,4 +49,9 @@ public class Negative extends FunctionSingle {
|
|||||||
return blocks;
|
return blocks;
|
||||||
// throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
// 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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||||
|
return visitor.visit(this, argument);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Function setParameter(final int index, final Function var) throws IndexOutOfBoundsException {
|
public Function setParameter(final int index, final Function var) throws IndexOutOfBoundsException {
|
||||||
throw new IndexOutOfBoundsException();
|
throw new IndexOutOfBoundsException();
|
||||||
|
@ -50,4 +50,9 @@ public class Power extends FunctionOperator {
|
|||||||
result.add(bp);
|
result.add(bp);
|
||||||
return result;
|
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());
|
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
|
@Override
|
||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
if (o instanceof Root) {
|
if (o instanceof RootSquare) {
|
||||||
final FunctionOperator f = (FunctionOperator) o;
|
final FunctionOperator f = (FunctionOperator) o;
|
||||||
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
|
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
|
||||||
}
|
}
|
||||||
@ -48,4 +48,8 @@ public class RootSquare extends FunctionOperator {
|
|||||||
return result;
|
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.Block;
|
||||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||||
import it.cavallium.warppi.math.Function;
|
import it.cavallium.warppi.math.*;
|
||||||
import it.cavallium.warppi.math.FunctionOperator;
|
|
||||||
import it.cavallium.warppi.math.MathContext;
|
|
||||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
|
||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
@ -43,4 +40,8 @@ public class Subtraction extends FunctionOperator {
|
|||||||
return result;
|
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.Block;
|
||||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||||
import it.cavallium.warppi.math.Function;
|
import it.cavallium.warppi.math.*;
|
||||||
import it.cavallium.warppi.math.FunctionOperator;
|
|
||||||
import it.cavallium.warppi.math.MathContext;
|
|
||||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
|
||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
@ -19,11 +16,7 @@ public class Sum extends FunctionOperator {
|
|||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
if (o instanceof Sum) {
|
if (o instanceof Sum) {
|
||||||
final FunctionOperator f = (FunctionOperator) o;
|
final FunctionOperator f = (FunctionOperator) o;
|
||||||
if (parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2())) {
|
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
|
||||||
return true;
|
|
||||||
} else if (parameter1.equals(f.getParameter2()) && parameter2.equals(f.getParameter1())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -47,4 +40,8 @@ public class Sum extends FunctionOperator {
|
|||||||
return result;
|
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.Block;
|
||||||
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
|
||||||
import it.cavallium.warppi.math.Function;
|
import it.cavallium.warppi.math.*;
|
||||||
import it.cavallium.warppi.math.FunctionOperator;
|
|
||||||
import it.cavallium.warppi.math.MathContext;
|
|
||||||
import it.cavallium.warppi.math.MathematicalSymbols;
|
|
||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
@ -43,4 +40,8 @@ public class SumSubtraction extends FunctionOperator {
|
|||||||
return result;
|
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
|
@Override
|
||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
return false;
|
return o instanceof Undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -58,6 +58,11 @@ public class Undefined implements Function {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
|
||||||
|
return visitor.visit(this, argument);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "UNDEFINED";
|
return "UNDEFINED";
|
||||||
|
@ -8,6 +8,8 @@ import it.cavallium.warppi.math.rules.Rule;
|
|||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class Variable implements Function {
|
public class Variable implements Function {
|
||||||
|
|
||||||
protected char var;
|
protected char var;
|
||||||
@ -84,7 +86,7 @@ public class Variable implements Function {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return toString().hashCode();
|
return Objects.hash(var, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -131,4 +133,9 @@ public class Variable implements Function {
|
|||||||
result.add(new BlockChar(getChar()));
|
result.add(new BlockChar(getChar()));
|
||||||
return result;
|
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
|
@Override
|
||||||
public boolean equals(final Object o) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,4 +71,8 @@ public class Equation extends FunctionOperator {
|
|||||||
return null;
|
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);
|
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
|
@Override
|
||||||
public EquationsSystem clone() {
|
public EquationsSystem clone() {
|
||||||
Function[] newFuncs = functions.clone();
|
Function[] newFuncs = functions.clone();
|
||||||
@ -46,4 +66,8 @@ public class EquationsSystem extends FunctionDynamic {
|
|||||||
return null;
|
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;
|
package it.cavallium.warppi.math.functions.equations;
|
||||||
|
|
||||||
import it.cavallium.warppi.gui.expression.blocks.Block;
|
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.FunctionSingle;
|
||||||
import it.cavallium.warppi.math.MathContext;
|
import it.cavallium.warppi.math.MathContext;
|
||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
@ -34,4 +35,8 @@ public class EquationsSystemPart extends FunctionSingle {
|
|||||||
return null;
|
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
|
@Override
|
||||||
public boolean equals(final Object o) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,4 +41,9 @@ public class ArcCosine extends FunctionSingle {
|
|||||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
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
|
@Override
|
||||||
public boolean equals(final Object o) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,4 +41,8 @@ public class ArcSine extends FunctionSingle {
|
|||||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
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
|
@Override
|
||||||
public boolean equals(final Object o) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,4 +41,8 @@ public class ArcTangent extends FunctionSingle {
|
|||||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
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
|
@Override
|
||||||
public boolean equals(final Object o) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,4 +41,8 @@ public class Cosine extends FunctionSingle {
|
|||||||
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
|
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) {
|
public boolean equals(final Object o) {
|
||||||
if (o instanceof Sine) {
|
if (o instanceof Sine) {
|
||||||
final FunctionSingle f = (FunctionSingle) o;
|
final FunctionSingle f = (FunctionSingle) o;
|
||||||
if (parameter.equals(f.getParameter())) {
|
return parameter.equals(f.getParameter());
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -51,4 +49,8 @@ public class Sine extends FunctionSingle {
|
|||||||
return result;
|
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
|
@Override
|
||||||
public boolean equals(final Object o) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,4 +40,8 @@ public class Tangent extends FunctionSingle {
|
|||||||
return null;
|
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;
|
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.Engine;
|
||||||
|
import it.cavallium.warppi.Platform;
|
||||||
import it.cavallium.warppi.Platform.ConsoleUtils;
|
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.Function;
|
||||||
import it.cavallium.warppi.math.MathContext;
|
import it.cavallium.warppi.math.MathContext;
|
||||||
import it.cavallium.warppi.math.functions.Expression;
|
import it.cavallium.warppi.math.functions.Expression;
|
||||||
import it.cavallium.warppi.math.functions.Variable;
|
import it.cavallium.warppi.math.functions.Variable;
|
||||||
import it.cavallium.warppi.math.functions.Variable.V_TYPE;
|
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.math.solver.MathSolver;
|
||||||
import it.cavallium.warppi.util.Error;
|
import it.cavallium.warppi.util.Error;
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class RulesManager {
|
public class RulesManager {
|
||||||
|
|
||||||
public static ObjectArrayList<Rule>[] rules;
|
public static ObjectArrayList<Rule>[] rules;
|
||||||
@ -38,189 +33,69 @@ public class RulesManager {
|
|||||||
for (final RuleType val : RuleType.values()) {
|
for (final RuleType val : RuleType.values()) {
|
||||||
RulesManager.rules[val.ordinal()] = new ObjectArrayList<>();
|
RulesManager.rules[val.ordinal()] = new ObjectArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadBuiltinRules();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boolean compiledSomething = false;
|
loadDslRules();
|
||||||
InputStream defaultRulesList;
|
} catch (IOException | DslFilesException e) {
|
||||||
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) {
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
if (e instanceof DslFilesException) {
|
||||||
|
System.err.println();
|
||||||
|
System.err.print(((DslFilesException) e).format());
|
||||||
|
}
|
||||||
Engine.getPlatform().exit(1);
|
Engine.getPlatform().exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Rule compileJavaRule(final String scriptFile, final File tDir) throws IOException, URISyntaxException,
|
private static void loadBuiltinRules() {
|
||||||
InstantiationException, IllegalAccessException, ClassNotFoundException {
|
Stream.of(
|
||||||
final InputStream resource = Engine.getPlatform().getStorageUtils().getResourceStream(scriptFile);
|
new DivisionRule(),
|
||||||
final String text = Engine.getPlatform().getStorageUtils().read(resource);
|
new EmptyNumberRule(),
|
||||||
final String[] textArray = text.split("\\n", 6);
|
new ExpressionRule(),
|
||||||
if (textArray[3].contains("PATH=")) {
|
new JokeRule(),
|
||||||
final String javaClassDeclaration = textArray[3].substring(6);
|
new MultiplicationRule(),
|
||||||
int extIndex = javaClassDeclaration.lastIndexOf('.');
|
new NegativeRule(),
|
||||||
final String javaClassNameOnly = javaClassDeclaration.substring(extIndex + 1, javaClassDeclaration.length());
|
new NumberRule(),
|
||||||
final String javaClassNameAndPath = new StringBuilder("it.cavallium.warppi.math.rules.").append(javaClassDeclaration).toString();
|
new PowerRule(),
|
||||||
extIndex = javaClassNameAndPath.lastIndexOf('.');
|
new RootRule(),
|
||||||
final String javaCode = new StringBuilder("package ").append(javaClassNameAndPath.substring(0, extIndex >= 0 ? extIndex : javaClassNameAndPath.length())).append(";\n").append(textArray[5]).toString();
|
new SubtractionRule(),
|
||||||
final File tDirPath = Engine.getPlatform().getStorageUtils().getParent(Engine.getPlatform().getStorageUtils().resolve(tDir, javaClassNameAndPath.replace('.', File.separatorChar)));
|
new SumRule(),
|
||||||
final File tFileJava = Engine.getPlatform().getStorageUtils().resolve(tDirPath, javaClassNameOnly + ".java");
|
new SumSubtractionRule(),
|
||||||
final File tFileClass = Engine.getPlatform().getStorageUtils().resolve(tDirPath, javaClassNameOnly + ".class");
|
new VariableRule()
|
||||||
if (!tDirPath.exists()) {
|
).forEach(RulesManager::addRule);
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Rule loadClassRuleFromSourceFile(final String scriptFile, final File tDir) throws IOException,
|
private static void loadDslRules() throws IOException, DslFilesException {
|
||||||
URISyntaxException, InstantiationException, IllegalAccessException, ClassNotFoundException {
|
final Platform platform = Engine.getPlatform();
|
||||||
final InputStream resource = Engine.getPlatform().getStorageUtils().getResourceStream(scriptFile);
|
|
||||||
final String text = Engine.getPlatform().getStorageUtils().read(resource);
|
final DslFilesException fileErrors = new DslFilesException();
|
||||||
final String[] textArray = text.split("\\n", 6);
|
for (final String path : platform.getRuleFilePaths()) {
|
||||||
if (textArray[3].contains("PATH=")) {
|
platform.getConsoleUtils().out().println(
|
||||||
final String javaClassName = textArray[3].substring(6);
|
ConsoleUtils.OUTPUTLEVEL_NODEBUG,
|
||||||
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_VERBOSE, "RulesManager", "Rule java class name: " + javaClassName);
|
"RulesManager",
|
||||||
final String javaClassNameAndPath = new StringBuilder("it.cavallium.warppi.math.rules.").append(javaClassName).toString();
|
"Found DSL rules file: " + path
|
||||||
|
);
|
||||||
|
|
||||||
|
final String source;
|
||||||
|
try (final InputStream resource = platform.getStorageUtils().getResourceStream(path)) {
|
||||||
|
source = platform.getStorageUtils().read(resource);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return RulesManager.loadClassRuleDirectly(javaClassNameAndPath, tDir);
|
// This loop used to be written as RulesDsl.makeRules(source).forEach(RulesManager::addRule),
|
||||||
} catch (final Exception ex) {
|
// but the forEach method hangs on TeaVM.
|
||||||
ex.printStackTrace();
|
for (Rule rule : RulesDsl.makeRules(source)) {
|
||||||
return null;
|
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,
|
if (fileErrors.hasErrors()) {
|
||||||
URISyntaxException, InstantiationException, IllegalAccessException, ClassNotFoundException {
|
throw fileErrors;
|
||||||
final URLClassLoader cl = Engine.getPlatform().newURLClassLoader(new URL[] { tDir.toURI().toURL() });
|
}
|
||||||
final Class<?> aClass = cl.loadClass(javaClassNameAndPath);
|
|
||||||
cl.close();
|
|
||||||
return (Rule) aClass.newInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void warmUp() throws Error, InterruptedException {
|
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)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.DivisionRule
|
PATH=functions.DivisionRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.EmptyNumberRule
|
PATH=functions.EmptyNumberRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.ExpressionRule
|
PATH=functions.ExpressionRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.JokeRule
|
PATH=functions.JokeRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.MultiplicationRule
|
PATH=functions.MultiplicationRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.NegativeRule
|
PATH=functions.NegativeRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.NumberRule
|
PATH=functions.NumberRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.PowerRule
|
PATH=functions.PowerRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.RootRule
|
PATH=functions.RootRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.SubtractionRule
|
PATH=functions.SubtractionRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.SumRule
|
PATH=functions.SumRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.SumSubtractionRule
|
PATH=functions.SumSubtractionRule
|
@ -1,4 +1,4 @@
|
|||||||
package rules.functions;
|
package it.cavallium.warppi.math.rules.functions;
|
||||||
/*
|
/*
|
||||||
SETTINGS: (please don't move this part)
|
SETTINGS: (please don't move this part)
|
||||||
PATH=functions.VariableRule
|
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>
|
<artifactId>jansi</artifactId>
|
||||||
<version>1.17.1</version>
|
<version>1.17.1</version>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>ar.com.hjg</groupId>
|
<groupId>ar.com.hjg</groupId>
|
||||||
<artifactId>pngj</artifactId>
|
<artifactId>pngj</artifactId>
|
||||||
|
@ -5,7 +5,9 @@ import java.io.IOException;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
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.gui.graphicengine.impl.swing.SwingEngine;
|
||||||
import it.cavallium.warppi.util.CacheUtils;
|
import it.cavallium.warppi.util.CacheUtils;
|
||||||
import it.cavallium.warppi.util.Error;
|
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 {
|
public class DesktopPlatform implements Platform {
|
||||||
|
|
||||||
@ -157,53 +156,18 @@ public class DesktopPlatform implements Platform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadPlatformRules() {
|
public List<String> getRuleFilePaths() throws IOException {
|
||||||
|
final File dslRulesPath = getStorageUtils().get("rules/");
|
||||||
}
|
List<String> paths = new ArrayList<>();
|
||||||
|
if (dslRulesPath.exists()) {
|
||||||
@Override
|
for (final File file : getStorageUtils().walk(dslRulesPath)) {
|
||||||
public void zip(final String targetPath, final String destinationFilePath, final String password) {
|
final String path = file.toString();
|
||||||
try {
|
if (path.endsWith(".rules")) {
|
||||||
final ZipParameters parameters = new ZipParameters();
|
paths.add(path);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
return paths;
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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