Compare commits

...

88 Commits

Author SHA1 Message Date
Riccardo Azzolini fb43f00485
Merge pull request #8 from razzolini/rules-dsl
Implement a domain-specific language for the definition of algebraic manipulation rules
2019-10-29 22:16:55 +01:00
Riccardo Azzolini a74f047655 Fix/improve hashCode for Functions
Some types of Function had hashCode methods which didn't match the
behavior of equals, while others could simply be rewritten in a simpler
way by using Objects.hash.
2019-10-25 22:48:49 +02:00
Riccardo Azzolini 0c316226e6 Make Function.equals test for exact equality instead of equivalence
Actually, testing for equivalence was only partially implemented (by
considering the commutative property for some FunctionOperators).

Additionally, should it be needed in the future, equivalence testing
would probably be better implemented as a separate, specialized method
anyway.
2019-10-25 22:30:10 +02:00
Riccardo Azzolini d8f401cb22 Remove zip file handling code and zip4j dependency
.zip files were only used to cache compiled Java rules, and the new rule
system has nothing to cache.
2019-10-25 20:49:31 +02:00
Riccardo Azzolini 12406c787b Ensure that lex and parse methods are only called once per instance 2019-09-09 18:34:52 +02:00
Riccardo Azzolini c37f7f52b3 Specify that TabExpandedString only works on single lines 2019-09-09 18:34:52 +02:00
Riccardo Azzolini 804fee4db0 Use tab-expanded column numbers when formatting DSL errors 2019-09-09 18:34:52 +02:00
Riccardo Azzolini b3f2ad82d0 Migrate from JUnit 4 to 5 2019-09-09 18:34:52 +02:00
Riccardo Azzolini fc119efedc Remove language level specification from warppi-core pom.xml
The language level is specified in the root pom.xml file, and there's no
need to override it here.
2019-09-09 18:34:52 +02:00
Riccardo Azzolini 082f694b1b Tweak DSL error formatting
- Add a blank line between the DslFilesException stack trace and
   formatted error messages.
 - Quote the sub-function name in the message for UndefinedSubFunction
   errors.
2019-09-09 18:34:52 +02:00
Riccardo Azzolini fbb6cf590d Throw an exception when a sub-function is undefined in SubFunctionPattern.replace 2019-09-09 18:34:52 +02:00
Riccardo Azzolini feb894bacd Improve SubFunctionPattern documentation 2019-09-09 18:34:52 +02:00
Riccardo Azzolini b4fc6dd019 Improve PatternRule documentation 2019-09-09 18:34:52 +02:00
Riccardo Azzolini eb8fcaafb9 Keep track of identifiers separately for each SubFunctionPattern object 2019-09-09 18:34:52 +02:00
Riccardo Azzolini c8656d1b30 Improve DSL front-end documentation 2019-08-12 11:28:39 +02:00
Riccardo Azzolini e297d3592f Reduce creation of Map objects in Pattern matching 2019-08-12 11:28:39 +02:00
Riccardo Azzolini a76511ea19 Remove the warppi-rules module 2019-08-12 11:28:39 +02:00
Riccardo Azzolini 2b9fb97648 Make DSL rule loading work on TeaVM 2019-08-12 11:28:39 +02:00
Riccardo Azzolini e5b64ce218 Make TeaVMStorageUtils.read work instead of hanging 2019-08-09 17:53:47 +02:00
Riccardo Azzolini fef30042ce Avoid using methods which are not available on TeaVM 2019-08-08 14:36:07 +02:00
Riccardo Azzolini d4b91c4d4f Merge branch 'master' into rules-dsl 2019-08-08 12:52:59 +02:00
Riccardo Azzolini 7819e19e6f Make Function visitor an inner interface of Function 2019-08-07 13:11:59 +02:00
Riccardo Azzolini 86c7da8c81 Test Lexer with empty input 2019-08-07 13:11:59 +02:00
Riccardo Azzolini aeb0388925 Implement DSL error pretty-printing 2019-08-07 13:11:59 +02:00
Riccardo Azzolini 788f9663e2 Use Collections.emptyMap and Collections.singletonMap in pattern matching code 2019-02-12 11:40:50 +01:00
Riccardo Azzolini 04d01565da Correctly handle and report unterminated multiline comments in Lexer 2019-02-11 16:31:03 +01:00
Riccardo Azzolini e86a7a6346 Remove Java rule compiling and loading code 2019-02-11 15:55:29 +01:00
Riccardo Azzolini fd074b16b7 Do not load DSL rules on JavaScript platforms 2019-02-11 15:11:59 +01:00
Riccardo Azzolini 2e36bc83bf Fix incorrect rules and port them to the DSL 2019-02-11 12:21:43 +01:00
Riccardo Azzolini b9c025d74c Make function rules built-in 2019-02-10 19:59:38 +01:00
Riccardo Azzolini 7d9b08affd Port most non-function rules from Java to the DSL 2019-02-10 19:05:25 +01:00
Riccardo Azzolini 6b814b84b6 Add comments to incorrect rules and partially fix ExpandRule1 2019-02-10 19:05:25 +01:00
Riccardo Azzolini df5de92931 Make NegativePattern match and produce negative numbers 2019-02-10 19:05:13 +01:00
Riccardo Azzolini d18e2d3708 Test the content of DslAggregateExceptions in RulesDslTest 2019-02-07 20:20:58 +01:00
Riccardo Azzolini e65f20382f Document the RulesDsl class and its makeRules method 2019-02-04 18:07:51 +01:00
Riccardo Azzolini 92ad2a9c1e Remove unused imports 2019-01-31 20:39:56 +01:00
Riccardo Azzolini 57011ceb86 Make EquationsSystemPattern patterns field private (was accidentally package-private) 2019-01-31 20:38:35 +01:00
Riccardo Azzolini 8eba0cd568 Correctly store sub-function identifiers for rules with the same name and identical rules 2019-01-31 19:23:37 +01:00
Riccardo Azzolini 2a347e6d19 Allow unary operators in power exponent 2019-01-31 13:10:02 +01:00
Riccardo Azzolini ebbf8b013a Replace DslException with the DslError interface and the SyntaxException wrapper 2019-01-31 12:55:15 +01:00
Riccardo Azzolini 0d89711772 Report DSL errors from RulesDsl.makeRules as a DslAggregateException 2019-01-30 21:47:00 +01:00
Riccardo Azzolini f930242ee8 Properly report undefined sub-functions in replacement patterns 2019-01-30 21:47:00 +01:00
Riccardo Azzolini 3a5ccdfc13 Keep track of sub-function identifier tokens for error reporting 2019-01-30 21:47:00 +01:00
Riccardo Azzolini bdb1fc738e Implement multiple error reporting and recovery in Parser 2019-01-30 21:47:00 +01:00
Riccardo Azzolini f0d2cdc1ab Implement multiple error reporting and recovery in Lexer 2019-01-29 12:08:35 +01:00
Riccardo Azzolini 1e0d2e5a0e Create base class for DSL exceptions 2019-01-28 21:13:24 +01:00
Riccardo Azzolini 1304755c25 Implement loading of DSL rules 2019-01-28 18:19:27 +01:00
Riccardo Azzolini c00b71e2ba Remove unneeded throws spec from PatternRule.execute 2019-01-27 21:18:53 +01:00
Riccardo Azzolini bea2eb67c8 Check that sub-functions in replacement patterns are defined 2019-01-27 21:18:53 +01:00
Riccardo Azzolini ba468d199a Add method to get sub-functions from patterns 2019-01-27 21:18:44 +01:00
Riccardo Azzolini 591813402d Prevent instantiation of PatternUtils 2019-01-27 19:56:18 +01:00
Riccardo Azzolini a92c3a3272 Implement and test RulesDsl.makeRules 2019-01-27 19:24:49 +01:00
Riccardo Azzolini 6ab69a1613 Implement and test Parser (with temporary error handling) 2019-01-27 19:24:49 +01:00
Riccardo Azzolini b959fac770 Implement equals (and hashCode) in PatternRule and patterns for testing 2019-01-27 19:24:49 +01:00
Riccardo Azzolini 6c8323daf9 Set Java language level to 9 2019-01-27 19:24:49 +01:00
Riccardo Azzolini c069e00178 Fix string indentation in LexerTest 2019-01-27 19:24:49 +01:00
Riccardo Azzolini 27b128a6ea Add single and multi-line comments 2018-11-23 18:52:36 +01:00
Riccardo Azzolini da91a5df33 Implement Lexer (with temporary error handling and basic tests) 2018-11-22 20:04:48 +01:00
Riccardo Azzolini 61d40330be Define the representation for tokens 2018-11-22 19:59:30 +01:00
Riccardo Azzolini fa2b9f20a8 Define a temporarily empty RulesDsl.makeRules method
When complete, this method will execute the DSL front-end, and return
the list of rules, if successful, otherwise report errors.
2018-11-20 19:27:04 +01:00
Riccardo Azzolini 26416dd8f8 Implement ConstantPattern 2018-11-20 19:07:34 +01:00
Riccardo Azzolini 5238c32380 Implement Pattern-based Rule 2018-11-19 13:05:28 +01:00
Riccardo Azzolini 101e90ad03 Remove EmptyNumber overload from FunctionVisitor 2018-11-19 10:49:33 +01:00
Riccardo Azzolini 2ceca91acf Merge branch 'master' into rules-dsl 2018-11-19 10:38:34 +01:00
Riccardo Azzolini 7e394a84bf Fix indentation 2018-10-16 21:55:24 +02:00
Riccardo Azzolini 4e5a77eb3e Add minimal (indentation-only) .editorconfig file 2018-10-16 21:52:06 +02:00
Riccardo Azzolini 21d8c37903 Handle RootSquare functions with RootPattern 2018-10-16 20:33:01 +02:00
Riccardo Azzolini 66a04607b3 Fix RootSquare equality with other RootSquare instances 2018-10-16 19:57:26 +02:00
Riccardo Azzolini b65723a2c6 Implement EquationsSystemPattern 2018-10-16 19:36:34 +02:00
Riccardo Azzolini c2dc02c0e1 Implement equals for EquationsSystem as ordered parameter equality 2018-10-16 19:33:44 +02:00
Riccardo Azzolini 53e2416426 Implement EquationPattern 2018-10-16 18:46:49 +02:00
Riccardo Azzolini 2aeb396b53 Implement equals for Equation 2018-10-16 18:45:37 +02:00
Riccardo Azzolini 95560e12b7 Implement UndefinedPattern 2018-10-06 19:14:24 +02:00
Riccardo Azzolini 957e85b4d0 Extract duplicated PatternTest code to testMultiplePatterns method 2018-10-06 18:45:28 +02:00
Riccardo Azzolini 0ec3974f41 Implement patterns for most FunctionSingles 2018-10-06 18:41:32 +02:00
Riccardo Azzolini 54fc13b211 Implement equals for trigonometric functions 2018-10-06 18:38:39 +02:00
Riccardo Azzolini 2ca5a8391a Implement patterns for most FunctionOperators 2018-10-06 17:41:15 +02:00
Riccardo Azzolini 0574744921 Create PatternUtils helper class 2018-10-06 16:47:03 +02:00
Riccardo Azzolini be5ae2d475 Add missing 'final' specifiers 2018-10-06 15:21:22 +02:00
Riccardo Azzolini 21de6349f4 Use actual MathContext instead of null in Pattern replace 2018-10-06 15:21:22 +02:00
Riccardo Azzolini 0afacf7ddc Implement NegativePattern 2018-10-06 15:21:22 +02:00
Riccardo Azzolini 64553c86e7 Implement NumberPattern 2018-10-06 15:21:22 +02:00
Riccardo Azzolini 5450d67497 Implement SumPattern 2018-10-06 15:21:22 +02:00
Riccardo Azzolini 277589e7a9 Create VisitorPattern abstract base class 2018-10-06 15:21:22 +02:00
Riccardo Azzolini 72de605659 Define FunctionVisitor and corresponding accept method 2018-10-06 15:21:22 +02:00
Riccardo Azzolini 75fe857139 Implement SubFunctionPattern 2018-10-06 15:21:22 +02:00
Riccardo Azzolini 0a386c13f4 Create empty PatternTest class. 2018-10-06 15:21:22 +02:00
Riccardo Azzolini d88f6a05f9 Define the Pattern interface and its core methods 2018-10-06 15:21:22 +02:00
164 changed files with 5499 additions and 20823 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
root = true
[*]
indent_style = tab

View File

@ -46,8 +46,8 @@
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -60,13 +60,15 @@ public interface Platform {
String[] stacktraceToString(Error e);
void loadPlatformRules();
void zip(String targetPath, String destinationFilePath, String password);
void unzip(String targetZipFilePath, String destinationFolderPath, String password);
boolean compile(String[] command, PrintWriter printWriter, PrintWriter errors);
/**
* Determines the list of files containing DSL rules to load.
*
* @return a <code>List</code> of paths of files which contain DSL rules.
* Each <code>String</code> in the returned <code>List</code> can be passed as an argument to
* {@link StorageUtils#getResourceStream(String)} to access the corresponding file's contents.
* @throws IOException if an IO error occurs while getting the list of rule file paths.
*/
List<String> getRuleFilePaths() throws IOException;
public interface Gpio {
int valueOutput();

View File

@ -1,6 +1,12 @@
package it.cavallium.warppi.math;
import it.cavallium.warppi.gui.expression.blocks.Block;
import it.cavallium.warppi.math.functions.*;
import it.cavallium.warppi.math.functions.Number;
import it.cavallium.warppi.math.functions.equations.Equation;
import it.cavallium.warppi.math.functions.equations.EquationsSystem;
import it.cavallium.warppi.math.functions.equations.EquationsSystemPart;
import it.cavallium.warppi.math.functions.trigonometry.*;
import it.cavallium.warppi.math.rules.Rule;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@ -15,6 +21,16 @@ public interface Function {
@Override
String toString();
/**
* Indicates whether some other object is a <code>Function</code> which is <em>identical</em> to this one.
* <p>
* Two functions which are not identical, but only equivalent (due to, for example, the commutative property) aren't
* considered equal.
*
* @param o the other object to compare.
* @return <code>true</code> if the other object is identical to this <code>Function</code>, <code>false</code>
* otherwise.
*/
@Override
boolean equals(Object o);
@ -80,4 +96,49 @@ public interface Function {
* @throws Error
*/
ObjectArrayList<Block> toBlock(MathContext context) throws Error;
/**
* Accepts a {@code Function.Visitor<Argument, Result>} by calling the correct overload of <code>visit</code>.
*
* @param visitor The visitor to be accepted.
* @param argument An additional argument to be passed to <code>visit</code>.
* @param <Argument> The type of an additional argument to be passed to the <code>visit</code> method.
* @param <Result> The return type of the <code>visit</code> method.
* @return The value returned by <code>visit</code>.
*/
<Argument, Result> Result accept(Visitor<Argument, Result> visitor, Argument argument);
/**
* Executes a different overload of a method for each <code>Function</code> implementation.
*
* @param <Argument> The type of an additional argument which can be passed to all <code>visit</code> method overloads.
* If the argument is not required, this type parameter should be set to {@link Void}.
* @param <Result> The return type of all <code>visit</code> method overloads.
*/
interface Visitor<Argument, Result> {
Result visit(ArcCosine arcCosine, Argument argument);
Result visit(ArcSine arcSine, Argument argument);
Result visit(ArcTangent arcTangent, Argument argument);
Result visit(Cosine cosine, Argument argument);
Result visit(Division division, Argument argument);
Result visit(Equation equation, Argument argument);
Result visit(EquationsSystem equationsSystem, Argument argument);
Result visit(EquationsSystemPart equationsSystemPart, Argument argument);
Result visit(Expression expression, Argument argument);
Result visit(Joke joke, Argument argument);
Result visit(Logarithm logarithm, Argument argument);
Result visit(Multiplication multiplication, Argument argument);
Result visit(Negative negative, Argument argument);
Result visit(Number number, Argument argument);
Result visit(Power power, Argument argument);
Result visit(Root root, Argument argument);
Result visit(RootSquare rootSquare, Argument argument);
Result visit(Sine sine, Argument argument);
Result visit(Subtraction subtraction, Argument argument);
Result visit(SumSubtraction sumSubtraction, Argument argument);
Result visit(Sum sum, Argument argument);
Result visit(Tangent tangent, Argument argument);
Result visit(Undefined undefined, Argument argument);
Result visit(Variable variable, Argument argument);
}
}

View File

@ -133,14 +133,12 @@ public abstract class FunctionDynamic implements Function {
@Override
public abstract FunctionDynamic clone();
@Override
public int hashCode() {
return functions.hashCode() + 883 * super.hashCode();
return Arrays.hashCode(functions);
}
@Override
public boolean equals(final Object o) {
return false;
}
public abstract boolean equals(Object o);
}

View File

@ -6,6 +6,8 @@ import it.cavallium.warppi.util.Errors;
import it.cavallium.warppi.util.Utils;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Objects;
public abstract class FunctionOperator implements Function {
/**
@ -175,7 +177,7 @@ public abstract class FunctionOperator implements Function {
@Override
public int hashCode() {
return parameter1.hashCode() + 7 * parameter2.hashCode() + 883 * super.hashCode();
return Objects.hash(parameter1, parameter2);
}
@Override

View File

@ -4,6 +4,8 @@ import it.cavallium.warppi.math.rules.Rule;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Objects;
public abstract class FunctionSingle implements Function {
private boolean simplified;
@ -117,7 +119,7 @@ public abstract class FunctionSingle implements Function {
@Override
public int hashCode() {
return parameter.hashCode() + 883 * super.hashCode();
return Objects.hash(parameter);
}
@Override

View File

@ -59,4 +59,9 @@ public class Division extends FunctionOperator {
result.add(bd);
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -582,6 +582,11 @@ public class Expression extends FunctionSingle {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
@Override
public String toString() {
String s = "(";

View File

@ -61,4 +61,8 @@ public class Joke implements Function {
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -55,4 +55,8 @@ public class Logarithm extends FunctionOperator {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -3,10 +3,7 @@ package it.cavallium.warppi.math.functions;
import it.cavallium.warppi.gui.expression.blocks.Block;
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
import it.cavallium.warppi.gui.expression.blocks.BlockParenthesis;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.FunctionOperator;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.MathematicalSymbols;
import it.cavallium.warppi.math.*;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@ -24,11 +21,7 @@ public class Multiplication extends FunctionOperator {
public boolean equals(final Object o) {
if (o instanceof Multiplication) {
final FunctionOperator f = (FunctionOperator) o;
if (parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2())) {
return true;
} else if (parameter1.equals(f.getParameter2()) && parameter2.equals(f.getParameter1())) {
return true;
}
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
}
return false;
}
@ -87,6 +80,11 @@ public class Multiplication extends FunctionOperator {
}
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
public boolean isNegative() {
return parameter1.equals(new Number(getMathContext(), -1)) || parameter2.equals(new Number(getMathContext(), -1));
}
@ -104,4 +102,4 @@ public class Multiplication extends FunctionOperator {
public static Multiplication newNegative(final MathContext context, final Function value2) {
return new Multiplication(context, new Number(context, -1), value2);
}
}
}

View File

@ -3,10 +3,7 @@ package it.cavallium.warppi.math.functions;
import it.cavallium.warppi.gui.expression.blocks.Block;
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
import it.cavallium.warppi.gui.expression.blocks.BlockParenthesis;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.FunctionSingle;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.MathematicalSymbols;
import it.cavallium.warppi.math.*;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@ -52,4 +49,9 @@ public class Negative extends FunctionSingle {
return blocks;
// throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -273,6 +273,11 @@ public class Number implements Function {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
@Override
public Function setParameter(final int index, final Function var) throws IndexOutOfBoundsException {
throw new IndexOutOfBoundsException();

View File

@ -50,4 +50,9 @@ public class Power extends FunctionOperator {
result.add(bp);
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -39,4 +39,8 @@ public class Root extends FunctionOperator {
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -17,7 +17,7 @@ public class RootSquare extends FunctionOperator {
@Override
public boolean equals(final Object o) {
if (o instanceof Root) {
if (o instanceof RootSquare) {
final FunctionOperator f = (FunctionOperator) o;
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
}
@ -48,4 +48,8 @@ public class RootSquare extends FunctionOperator {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -2,10 +2,7 @@ package it.cavallium.warppi.math.functions;
import it.cavallium.warppi.gui.expression.blocks.Block;
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.FunctionOperator;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.MathematicalSymbols;
import it.cavallium.warppi.math.*;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@ -43,4 +40,8 @@ public class Subtraction extends FunctionOperator {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -2,10 +2,7 @@ package it.cavallium.warppi.math.functions;
import it.cavallium.warppi.gui.expression.blocks.Block;
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.FunctionOperator;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.MathematicalSymbols;
import it.cavallium.warppi.math.*;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@ -19,11 +16,7 @@ public class Sum extends FunctionOperator {
public boolean equals(final Object o) {
if (o instanceof Sum) {
final FunctionOperator f = (FunctionOperator) o;
if (parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2())) {
return true;
} else if (parameter1.equals(f.getParameter2()) && parameter2.equals(f.getParameter1())) {
return true;
}
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
}
return false;
}
@ -47,4 +40,8 @@ public class Sum extends FunctionOperator {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -2,10 +2,7 @@ package it.cavallium.warppi.math.functions;
import it.cavallium.warppi.gui.expression.blocks.Block;
import it.cavallium.warppi.gui.expression.blocks.BlockChar;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.FunctionOperator;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.MathematicalSymbols;
import it.cavallium.warppi.math.*;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@ -43,4 +40,8 @@ public class SumSubtraction extends FunctionOperator {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -28,7 +28,7 @@ public class Undefined implements Function {
@Override
public boolean equals(final Object o) {
return false;
return o instanceof Undefined;
}
@Override
@ -58,6 +58,11 @@ public class Undefined implements Function {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
@Override
public String toString() {
return "UNDEFINED";

View File

@ -8,6 +8,8 @@ import it.cavallium.warppi.math.rules.Rule;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Objects;
public class Variable implements Function {
protected char var;
@ -84,7 +86,7 @@ public class Variable implements Function {
@Override
public int hashCode() {
return toString().hashCode();
return Objects.hash(var, type);
}
@Override
@ -131,4 +133,9 @@ public class Variable implements Function {
result.add(new BlockChar(getChar()));
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -58,7 +58,10 @@ public class Equation extends FunctionOperator {
@Override
public boolean equals(final Object o) {
// TODO Auto-generated method stub
if (o instanceof Equation) {
final FunctionOperator f = (FunctionOperator) o;
return parameter1.equals(f.getParameter1()) && parameter2.equals(f.getParameter2());
}
return false;
}
@ -68,4 +71,8 @@ public class Equation extends FunctionOperator {
return null;
}
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -22,6 +22,26 @@ public class EquationsSystem extends FunctionDynamic {
super(root, value);
}
@Override
public boolean equals(final Object o) {
if (!(o instanceof EquationsSystem)) {
return false;
}
final FunctionDynamic f = (FunctionDynamic) o;
if (functions.length != f.getParametersLength()) {
return false;
}
for (int i = 0; i < functions.length; i++) {
if (!functions[i].equals(f.getParameter(i))) {
return false;
}
}
return true;
}
@Override
public EquationsSystem clone() {
Function[] newFuncs = functions.clone();
@ -46,4 +66,8 @@ public class EquationsSystem extends FunctionDynamic {
return null;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -1,6 +1,7 @@
package it.cavallium.warppi.math.functions.equations;
import it.cavallium.warppi.gui.expression.blocks.Block;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.FunctionSingle;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.util.Error;
@ -34,4 +35,8 @@ public class EquationsSystemPart extends FunctionSingle {
return null;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -16,7 +16,10 @@ public class ArcCosine extends FunctionSingle {
@Override
public boolean equals(final Object o) {
// TODO Auto-generated method stub
if (o instanceof ArcCosine) {
final FunctionSingle f = (FunctionSingle) o;
return parameter.equals(f.getParameter());
}
return false;
}
@ -38,4 +41,9 @@ public class ArcCosine extends FunctionSingle {
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -16,7 +16,10 @@ public class ArcSine extends FunctionSingle {
@Override
public boolean equals(final Object o) {
// TODO Auto-generated method stub
if (o instanceof ArcSine) {
final FunctionSingle f = (FunctionSingle) o;
return parameter.equals(f.getParameter());
}
return false;
}
@ -38,4 +41,8 @@ public class ArcSine extends FunctionSingle {
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -16,7 +16,10 @@ public class ArcTangent extends FunctionSingle {
@Override
public boolean equals(final Object o) {
// TODO Auto-generated method stub
if (o instanceof ArcTangent) {
final FunctionSingle f = (FunctionSingle) o;
return parameter.equals(f.getParameter());
}
return false;
}
@ -38,4 +41,8 @@ public class ArcTangent extends FunctionSingle {
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -16,7 +16,10 @@ public class Cosine extends FunctionSingle {
@Override
public boolean equals(final Object o) {
// TODO Auto-generated method stub
if (o instanceof Cosine) {
final FunctionSingle f = (FunctionSingle) o;
return parameter.equals(f.getParameter());
}
return false;
}
@ -38,4 +41,8 @@ public class Cosine extends FunctionSingle {
throw new Error(Errors.NOT_IMPLEMENTED, "Unknown function " + getClass().getSimpleName());
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -19,9 +19,7 @@ public class Sine extends FunctionSingle {
public boolean equals(final Object o) {
if (o instanceof Sine) {
final FunctionSingle f = (FunctionSingle) o;
if (parameter.equals(f.getParameter())) {
return true;
}
return parameter.equals(f.getParameter());
}
return false;
}
@ -51,4 +49,8 @@ public class Sine extends FunctionSingle {
return result;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -15,7 +15,10 @@ public class Tangent extends FunctionSingle {
@Override
public boolean equals(final Object o) {
// TODO Auto-generated method stub
if (o instanceof Tangent) {
final FunctionSingle f = (FunctionSingle) o;
return parameter.equals(f.getParameter());
}
return false;
}
@ -37,4 +40,8 @@ public class Tangent extends FunctionSingle {
return null;
}
@Override
public <Argument, Result> Result accept(final Function.Visitor<Argument, Result> visitor, final Argument argument) {
return visitor.visit(this, argument);
}
}

View File

@ -1,30 +1,25 @@
package it.cavallium.warppi.math.rules;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import it.cavallium.warppi.Engine;
import it.cavallium.warppi.Platform;
import it.cavallium.warppi.Platform.ConsoleUtils;
import it.cavallium.warppi.Platform.StorageUtils;
import it.cavallium.warppi.Platform.URLClassLoader;
import it.cavallium.warppi.StaticVars;
import it.cavallium.warppi.math.Function;
import it.cavallium.warppi.math.MathContext;
import it.cavallium.warppi.math.functions.Expression;
import it.cavallium.warppi.math.functions.Variable;
import it.cavallium.warppi.math.functions.Variable.V_TYPE;
import it.cavallium.warppi.math.rules.dsl.DslAggregateException;
import it.cavallium.warppi.math.rules.dsl.RulesDsl;
import it.cavallium.warppi.math.rules.dsl.errorutils.DslFilesException;
import it.cavallium.warppi.math.rules.functions.*;
import it.cavallium.warppi.math.solver.MathSolver;
import it.cavallium.warppi.util.Error;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Stream;
public class RulesManager {
public static ObjectArrayList<Rule>[] rules;
@ -38,189 +33,69 @@ public class RulesManager {
for (final RuleType val : RuleType.values()) {
RulesManager.rules[val.ordinal()] = new ObjectArrayList<>();
}
loadBuiltinRules();
try {
boolean compiledSomething = false;
InputStream defaultRulesList;
try {
defaultRulesList = Engine.getPlatform().getStorageUtils().getResourceStream("/default-rules.lst");
} catch (final IOException ex) {
throw new FileNotFoundException("default-rules.lst not found!");
}
final List<String> ruleLines = new ArrayList<>();
final File rulesPath = Engine.getPlatform().getStorageUtils().get("rules/");
if (rulesPath.exists()) {
for (final File f : Engine.getPlatform().getStorageUtils().walk(rulesPath)) {
if (f.toString().endsWith(".java")) {
String path = Engine.getPlatform().getStorageUtils().relativize(rulesPath, f).toString();
path = path.substring(0, path.length() - ".java".length());
ruleLines.add(path);
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", "Found external rule: " + f.getAbsolutePath());
}
}
}
ruleLines.addAll(Engine.getPlatform().getStorageUtils().readAllLines(defaultRulesList));
final File tDir = Engine.getPlatform().getStorageUtils().resolve(Engine.getPlatform().getStorageUtils().get(System.getProperty("java.io.tmpdir"), "WarpPi-Calculator"), "rules-rt");
// try {
// final Path defaultResource = Utils.getResource("/math-rules-cache.zip");
// }
InputStream cacheFileStream = null;
File cacheFilePath = null;
cacheFilePath = new File("math-rules-cache.zip");
boolean cacheFileExists = false;
if (Engine.getPlatform().isJavascript()) {
Engine.getPlatform().loadPlatformRules();
} else {
if (cacheFilePath.exists()) {
cacheFileExists = true;
cacheFileStream = new FileInputStream(cacheFilePath);
} else {
try {
cacheFileStream = Engine.getPlatform().getStorageUtils().getResourceStream("/math-rules-cache.zip");//Paths.get(Utils.getJarDirectory().toString()).resolve("math-rules-cache.zip").toAbsolutePath(
org.apache.commons.io.FileUtils.copyInputStreamToFile(cacheFileStream, cacheFilePath);
cacheFileExists = true;
} catch (final IOException ex) { //File does not exists.
}
}
boolean useCache = false;
if (cacheFileExists) {
try {
if (tDir.exists()) {
tDir.delete();
}
Engine.getPlatform().unzip(cacheFilePath.toString(), tDir.getParent().toString(), "");
useCache = !StaticVars.startupArguments.isUncached();
} catch (final Exception ex) {
ex.printStackTrace();
}
}
for (final String rulesLine : ruleLines) {
if (rulesLine.length() > 0) {
final String[] ruleDetails = rulesLine.split(",", 1);
final String ruleName = ruleDetails[0];
final String ruleNameEscaped = ruleName.replace(".", "_");
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", "Evaluating /rules/" + ruleNameEscaped + ".java");
final String pathWithoutExtension = "/rules/" + ruleNameEscaped;
final String scriptFile = pathWithoutExtension + ".java";
final InputStream resourcePath = Engine.getPlatform().getStorageUtils().getResourceStream(scriptFile);
if (resourcePath == null) {
System.err.println(new FileNotFoundException("/rules/" + ruleName + ".java not found!"));
} else {
Rule r = null;
if (useCache) {
try {
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", ruleName, "Trying to load cached rule");
r = RulesManager.loadClassRuleFromSourceFile(scriptFile, tDir);
if (r != null) {
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", ruleName, "Loaded cached rule");
}
} catch (final Exception e) {
e.printStackTrace();
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", ruleName, "Can't load the rule " + ruleNameEscaped + "!");
}
}
if (r == null || !useCache) {
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_MIN, "RulesManager", ruleName, "This rule is not cached. Compiling");
try {
r = RulesManager.compileJavaRule(scriptFile, tDir);
compiledSomething = true;
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | IOException e) {
e.printStackTrace();
}
}
if (r != null) {
RulesManager.addRule(r);
}
}
}
}
}
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", "Loaded all the rules successfully");
if (!Engine.getPlatform().isJavascript() && compiledSomething) {
if (cacheFileExists || cacheFilePath.exists()) {
cacheFilePath.delete();
}
Engine.getPlatform().zip(tDir.toString(), cacheFilePath.toString(), "");
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_NODEBUG, "RulesManager", "Cached the compiled rules");
}
if (cacheFileStream != null) {
cacheFileStream.close();
}
} catch (URISyntaxException | IOException e) {
loadDslRules();
} catch (IOException | DslFilesException e) {
e.printStackTrace();
if (e instanceof DslFilesException) {
System.err.println();
System.err.print(((DslFilesException) e).format());
}
Engine.getPlatform().exit(1);
}
}
public static Rule compileJavaRule(final String scriptFile, final File tDir) throws IOException, URISyntaxException,
InstantiationException, IllegalAccessException, ClassNotFoundException {
final InputStream resource = Engine.getPlatform().getStorageUtils().getResourceStream(scriptFile);
final String text = Engine.getPlatform().getStorageUtils().read(resource);
final String[] textArray = text.split("\\n", 6);
if (textArray[3].contains("PATH=")) {
final String javaClassDeclaration = textArray[3].substring(6);
int extIndex = javaClassDeclaration.lastIndexOf('.');
final String javaClassNameOnly = javaClassDeclaration.substring(extIndex + 1, javaClassDeclaration.length());
final String javaClassNameAndPath = new StringBuilder("it.cavallium.warppi.math.rules.").append(javaClassDeclaration).toString();
extIndex = javaClassNameAndPath.lastIndexOf('.');
final String javaCode = new StringBuilder("package ").append(javaClassNameAndPath.substring(0, extIndex >= 0 ? extIndex : javaClassNameAndPath.length())).append(";\n").append(textArray[5]).toString();
final File tDirPath = Engine.getPlatform().getStorageUtils().getParent(Engine.getPlatform().getStorageUtils().resolve(tDir, javaClassNameAndPath.replace('.', File.separatorChar)));
final File tFileJava = Engine.getPlatform().getStorageUtils().resolve(tDirPath, javaClassNameOnly + ".java");
final File tFileClass = Engine.getPlatform().getStorageUtils().resolve(tDirPath, javaClassNameOnly + ".class");
if (!tDirPath.exists()) {
Engine.getPlatform().getStorageUtils().createDirectories(tDirPath);
}
if (tFileJava.exists()) {
tFileJava.delete();
}
Engine.getPlatform().getStorageUtils();
Engine.getPlatform().getStorageUtils();
Engine.getPlatform().getStorageUtils().write(tFileJava, javaCode.getBytes("UTF-8"), StorageUtils.OpenOptionWrite, StorageUtils.OpenOptionCreate);
final boolean compiled = Engine.getPlatform().compile(new String[] { "-nowarn", "-1.8", "-proc:none", tFileJava.toString() }, new PrintWriter(System.out), new PrintWriter(System.err));
if (StaticVars.startupArguments.isUncached()) {
tFileJava.deleteOnExit();
} else {
tFileJava.delete();
}
if (compiled) {
tFileClass.deleteOnExit();
return RulesManager.loadClassRuleDirectly(javaClassNameAndPath, tDir);
} else {
throw new IOException("Can't build script file '" + scriptFile + "'");
}
} else {
throw new IOException("Can't build script file '" + scriptFile + "', the header is missing or wrong.");
}
private static void loadBuiltinRules() {
Stream.of(
new DivisionRule(),
new EmptyNumberRule(),
new ExpressionRule(),
new JokeRule(),
new MultiplicationRule(),
new NegativeRule(),
new NumberRule(),
new PowerRule(),
new RootRule(),
new SubtractionRule(),
new SumRule(),
new SumSubtractionRule(),
new VariableRule()
).forEach(RulesManager::addRule);
}
public static Rule loadClassRuleFromSourceFile(final String scriptFile, final File tDir) throws IOException,
URISyntaxException, InstantiationException, IllegalAccessException, ClassNotFoundException {
final InputStream resource = Engine.getPlatform().getStorageUtils().getResourceStream(scriptFile);
final String text = Engine.getPlatform().getStorageUtils().read(resource);
final String[] textArray = text.split("\\n", 6);
if (textArray[3].contains("PATH=")) {
final String javaClassName = textArray[3].substring(6);
Engine.getPlatform().getConsoleUtils().out().println(ConsoleUtils.OUTPUTLEVEL_DEBUG_VERBOSE, "RulesManager", "Rule java class name: " + javaClassName);
final String javaClassNameAndPath = new StringBuilder("it.cavallium.warppi.math.rules.").append(javaClassName).toString();
private static void loadDslRules() throws IOException, DslFilesException {
final Platform platform = Engine.getPlatform();
final DslFilesException fileErrors = new DslFilesException();
for (final String path : platform.getRuleFilePaths()) {
platform.getConsoleUtils().out().println(
ConsoleUtils.OUTPUTLEVEL_NODEBUG,
"RulesManager",
"Found DSL rules file: " + path
);
final String source;
try (final InputStream resource = platform.getStorageUtils().getResourceStream(path)) {
source = platform.getStorageUtils().read(resource);
}
try {
return RulesManager.loadClassRuleDirectly(javaClassNameAndPath, tDir);
} catch (final Exception ex) {
ex.printStackTrace();
return null;
// This loop used to be written as RulesDsl.makeRules(source).forEach(RulesManager::addRule),
// but the forEach method hangs on TeaVM.
for (Rule rule : RulesDsl.makeRules(source)) {
addRule(rule);
}
} catch (DslAggregateException e) {
fileErrors.addFileErrors(path, source, e.getErrors());
}
} else {
throw new IOException("Can't load script file '" + scriptFile + "', the header is missing or wrong.");
}
}
public static Rule loadClassRuleDirectly(final String javaClassNameAndPath, final File tDir) throws IOException,
URISyntaxException, InstantiationException, IllegalAccessException, ClassNotFoundException {
final URLClassLoader cl = Engine.getPlatform().newURLClassLoader(new URL[] { tDir.toURI().toURL() });
final Class<?> aClass = cl.loadClass(javaClassNameAndPath);
cl.close();
return (Rule) aClass.newInstance();
if (fileErrors.hasErrors()) {
throw fileErrors;
}
}
public static void warmUp() throws Error, InterruptedException {

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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 + '\'' +
'}';
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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';
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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,
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.DivisionRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.EmptyNumberRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.ExpressionRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.JokeRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.MultiplicationRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.NegativeRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.NumberRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.PowerRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.RootRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.SubtractionRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.SumRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.SumSubtractionRule

View File

@ -1,4 +1,4 @@
package rules.functions;
package it.cavallium.warppi.math.rules.functions;
/*
SETTINGS: (please don't move this part)
PATH=functions.VariableRule

View 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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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()));
}
}
}

View File

@ -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());
}
}

View File

@ -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()));
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -28,16 +28,6 @@
<artifactId>jansi</artifactId>
<version>1.17.1</version>
</dependency>
<dependency>
<groupId>net.lingala.zip4j</groupId>
<artifactId>zip4j</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.14.0.v20171206-0802</version>
</dependency>
<dependency>
<groupId>ar.com.hjg</groupId>
<artifactId>pngj</artifactId>

View File

@ -5,7 +5,9 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
@ -17,9 +19,6 @@ import it.cavallium.warppi.gui.graphicengine.impl.jogl.JOGLEngine;
import it.cavallium.warppi.gui.graphicengine.impl.swing.SwingEngine;
import it.cavallium.warppi.util.CacheUtils;
import it.cavallium.warppi.util.Error;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
public class DesktopPlatform implements Platform {
@ -157,53 +156,18 @@ public class DesktopPlatform implements Platform {
}
@Override
public void loadPlatformRules() {
}
@Override
public void zip(final String targetPath, final String destinationFilePath, final String password) {
try {
final ZipParameters parameters = new ZipParameters();
parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
if (password.length() > 0) {
parameters.setEncryptFiles(true);
parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
parameters.setAesKeyStrength(Zip4jConstants.AES_STRENGTH_256);
parameters.setPassword(password);
public List<String> getRuleFilePaths() throws IOException {
final File dslRulesPath = getStorageUtils().get("rules/");
List<String> paths = new ArrayList<>();
if (dslRulesPath.exists()) {
for (final File file : getStorageUtils().walk(dslRulesPath)) {
final String path = file.toString();
if (path.endsWith(".rules")) {
paths.add(path);
}
}
final ZipFile zipFile = new ZipFile(destinationFilePath);
final File targetFile = new File(targetPath);
if (targetFile.isFile())
zipFile.addFile(targetFile, parameters);
else if (targetFile.isDirectory())
zipFile.addFolder(targetFile, parameters);
} catch (final Exception e) {
e.printStackTrace();
}
}
@Override
public void unzip(final String targetZipFilePath, final String destinationFolderPath, final String password) {
try {
final ZipFile zipFile = new ZipFile(targetZipFilePath);
if (zipFile.isEncrypted())
zipFile.setPassword(password);
zipFile.extractAll(destinationFolderPath);
} catch (final Exception e) {
e.printStackTrace();
}
}
@Override
public boolean compile(final String[] command, final PrintWriter printWriter, final PrintWriter errors) {
return org.eclipse.jdt.internal.compiler.batch.Main.compile(command, printWriter, errors, null);
return paths;
}
@Override

Some files were not shown because too many files have changed in this diff Show More