├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
├── antlr4
│ └── me
│ │ └── ivanyu
│ │ └── RuleSetGrammar.g4
└── java
│ └── me
│ └── ivanyu
│ ├── CompilerApplication.java
│ ├── compiler
│ ├── Compiler.java
│ ├── ExceptionThrowingErrorHandler.java
│ └── TreeBuilder.java
│ └── pojos
│ ├── AndExpression.java
│ ├── ArithmeticExpression.java
│ ├── ComparisonExpression.java
│ ├── ComparisonOperand.java
│ ├── Conclusion.java
│ ├── LogicalConstant.java
│ ├── LogicalExpression.java
│ ├── LogicalVariable.java
│ ├── Negation.java
│ ├── NumericConstant.java
│ ├── NumericEntity.java
│ ├── NumericVariable.java
│ ├── OrExpression.java
│ ├── RealArithmeticExpression.java
│ ├── Rule.java
│ ├── RuleSet.java
│ ├── RuleSetPojo.java
│ └── serializers
│ └── ConclusionSerializer.java
└── test
└── java
└── me
└── ivanyu
├── CompilerTest.java
└── GrammarTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.jar
8 | *.war
9 | *.ear
10 |
11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
12 | hs_err_pid*
13 | /target
14 | /rule-compiler.iml
15 | /.idea
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Ivan Yurchenko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | logical-rules-parser-antlr
2 | ==========================
3 |
4 | A simple example of a parser built with ANTLR.
5 |
6 | There is [the blog post](http://ivanyu.me/blog/2014/09/13/creating-a-simple-parser-with-antlr/) describes the parser.
7 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | me.ivanyu
6 | rule-compiler
7 | 1.0-SNAPSHOT
8 | jar
9 |
10 | rule-compiler
11 | http://maven.apache.org
12 |
13 |
14 | UTF-8
15 | UTF-8
16 |
17 |
18 |
19 |
20 | junit
21 | junit
22 | 4.11
23 | test
24 |
25 |
26 |
27 | com.fasterxml.jackson.core
28 | jackson-annotations
29 | 2.3.0
30 |
31 |
32 | com.fasterxml.jackson.core
33 | jackson-databind
34 | 2.3.3
35 |
36 |
37 | com.fasterxml.jackson.core
38 | jackson-core
39 | 2.3.1
40 |
41 |
42 |
43 | org.antlr
44 | antlr4-runtime
45 | 4.3
46 |
47 |
48 |
49 |
50 |
51 |
52 | org.antlr
53 | antlr4-maven-plugin
54 | 4.3
55 |
56 |
57 |
58 | antlr4
59 |
60 |
61 |
62 |
63 |
64 |
65 | org.apache.maven.plugins
66 | maven-compiler-plugin
67 | 3.1
68 |
69 | 1.7
70 | 1.7
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/main/antlr4/me/ivanyu/RuleSetGrammar.g4:
--------------------------------------------------------------------------------
1 | grammar RuleSetGrammar;
2 |
3 | /* Lexical rules */
4 |
5 | IF : 'if' ;
6 | THEN : 'then';
7 |
8 | AND : 'and' ;
9 | OR : 'or' ;
10 |
11 | TRUE : 'true' ;
12 | FALSE : 'false' ;
13 |
14 | MULT : '*' ;
15 | DIV : '/' ;
16 | PLUS : '+' ;
17 | MINUS : '-' ;
18 |
19 | GT : '>' ;
20 | GE : '>=' ;
21 | LT : '<' ;
22 | LE : '<=' ;
23 | EQ : '=' ;
24 |
25 | LPAREN : '(' ;
26 | RPAREN : ')' ;
27 |
28 | // DECIMAL, IDENTIFIER, COMMENTS, WS are set using regular expressions
29 |
30 | DECIMAL : '-'?[0-9]+('.'[0-9]+)? ;
31 |
32 | IDENTIFIER : [a-zA-Z_][a-zA-Z_0-9]* ;
33 |
34 | SEMI : ';' ;
35 |
36 | // COMMENT and WS are stripped from the output token stream by sending
37 | // to a different channel 'skip'
38 |
39 | COMMENT : '//' .+? ('\n'|EOF) -> skip ;
40 |
41 | WS : [ \r\t\u000C\n]+ -> skip ;
42 |
43 |
44 | /* Parser rules */
45 |
46 | rule_set : single_rule* EOF ;
47 |
48 | single_rule : IF condition THEN conclusion SEMI ;
49 |
50 | condition : logical_expr ;
51 | conclusion : IDENTIFIER ;
52 |
53 | logical_expr
54 | : logical_expr AND logical_expr # LogicalExpressionAnd
55 | | logical_expr OR logical_expr # LogicalExpressionOr
56 | | comparison_expr # ComparisonExpression
57 | | LPAREN logical_expr RPAREN # LogicalExpressionInParen
58 | | logical_entity # LogicalEntity
59 | ;
60 |
61 | comparison_expr : comparison_operand comp_operator comparison_operand
62 | # ComparisonExpressionWithOperator
63 | | LPAREN comparison_expr RPAREN # ComparisonExpressionParens
64 | ;
65 |
66 | comparison_operand : arithmetic_expr
67 | ;
68 |
69 | comp_operator : GT
70 | | GE
71 | | LT
72 | | LE
73 | | EQ
74 | ;
75 |
76 | arithmetic_expr
77 | : arithmetic_expr MULT arithmetic_expr # ArithmeticExpressionMult
78 | | arithmetic_expr DIV arithmetic_expr # ArithmeticExpressionDiv
79 | | arithmetic_expr PLUS arithmetic_expr # ArithmeticExpressionPlus
80 | | arithmetic_expr MINUS arithmetic_expr # ArithmeticExpressionMinus
81 | | MINUS arithmetic_expr # ArithmeticExpressionNegation
82 | | LPAREN arithmetic_expr RPAREN # ArithmeticExpressionParens
83 | | numeric_entity # ArithmeticExpressionNumericEntity
84 | ;
85 |
86 | logical_entity : (TRUE | FALSE) # LogicalConst
87 | | IDENTIFIER # LogicalVariable
88 | ;
89 |
90 | numeric_entity : DECIMAL # NumericConst
91 | | IDENTIFIER # NumericVariable
92 | ;
93 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/CompilerApplication.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fasterxml.jackson.databind.SerializationFeature;
6 | import me.ivanyu.compiler.Compiler;
7 | import me.ivanyu.pojos.RuleSet;
8 |
9 | public class CompilerApplication {
10 | public static void main(String[] args) {
11 | Compiler compiler = new Compiler();
12 | RuleSet ruleSet = compiler.compile("if -(A + 2) > 0.5 then be_careful;");
13 |
14 | // JSON serialization
15 | ObjectMapper mapper = new ObjectMapper();
16 | mapper.enable(SerializationFeature.INDENT_OUTPUT);
17 |
18 | String jsonString = null;
19 | try {
20 | jsonString = mapper.writeValueAsString(ruleSet);
21 | } catch (JsonProcessingException e) {
22 | e.printStackTrace();
23 | }
24 | System.out.println(jsonString);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/compiler/Compiler.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.compiler;
2 |
3 | import me.ivanyu.RuleSetGrammarLexer;
4 | import me.ivanyu.RuleSetGrammarParser;
5 | import me.ivanyu.pojos.RuleSet;
6 | import org.antlr.v4.runtime.ANTLRInputStream;
7 | import org.antlr.v4.runtime.CommonTokenStream;
8 | import org.antlr.v4.runtime.TokenStream;
9 |
10 | public class Compiler {
11 | public RuleSet compile(String inputString) {
12 | ANTLRInputStream input = new ANTLRInputStream(inputString);
13 | RuleSetGrammarLexer lexer = new RuleSetGrammarLexer(input);
14 | TokenStream tokens = new CommonTokenStream(lexer);
15 | RuleSetGrammarParser parser = new RuleSetGrammarParser(tokens);
16 |
17 | TreeBuilder treeBuilder = new TreeBuilder();
18 | parser.addParseListener(treeBuilder);
19 | parser.setErrorHandler(new ExceptionThrowingErrorHandler());
20 |
21 | parser.rule_set();
22 |
23 | return treeBuilder.getRuleSet();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/compiler/ExceptionThrowingErrorHandler.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.compiler;
2 |
3 | import org.antlr.v4.runtime.*;
4 |
5 | /**
6 | * Error handler which throws exception on any parsing error.
7 | */
8 | public class ExceptionThrowingErrorHandler extends DefaultErrorStrategy {
9 | @Override
10 | public void recover(Parser recognizer, RecognitionException e) {
11 | throw new RuntimeException(e);
12 | }
13 |
14 | @Override
15 | public Token recoverInline(Parser recognizer) throws RecognitionException {
16 | throw new RuntimeException(new InputMismatchException(recognizer));
17 | }
18 |
19 | @Override
20 | public void sync(Parser recognizer) throws RecognitionException {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/compiler/TreeBuilder.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.compiler;
2 |
3 | import me.ivanyu.RuleSetGrammarBaseListener;
4 | import me.ivanyu.RuleSetGrammarParser;
5 | import me.ivanyu.pojos.*;
6 | import org.antlr.v4.runtime.misc.NotNull;
7 |
8 | import java.math.BigDecimal;
9 | import java.text.DecimalFormat;
10 | import java.text.DecimalFormatSymbols;
11 | import java.text.ParseException;
12 | import java.util.Stack;
13 |
14 | public class TreeBuilder extends RuleSetGrammarBaseListener {
15 | private RuleSet ruleSet = null;
16 | private Rule rule = null;
17 | private LogicalExpression condition = null;
18 |
19 | private Stack logicalExpressions = new Stack<>();
20 | private Stack comparisonOperands = new Stack<>();
21 | private Stack arithmeticExpressions = new Stack<>();
22 |
23 | public RuleSet getRuleSet() {
24 | return ruleSet;
25 | }
26 |
27 | @Override
28 | public void enterRule_set(@NotNull RuleSetGrammarParser.Rule_setContext ctx) {
29 | assert ruleSet == null;
30 | assert rule == null;
31 | assert condition == null;
32 |
33 | assert logicalExpressions.empty();
34 | assert comparisonOperands.empty();
35 | assert arithmeticExpressions.empty();
36 |
37 | this.ruleSet = new RuleSet();
38 | }
39 |
40 | @Override
41 | public void enterSingle_rule(@NotNull RuleSetGrammarParser.Single_ruleContext ctx) {
42 | this.rule = new Rule();
43 | }
44 |
45 | @Override
46 | public void exitConclusion(@NotNull RuleSetGrammarParser.ConclusionContext ctx) {
47 | this.rule.setConclusion(ctx.getText());
48 | }
49 |
50 | @Override
51 | public void exitSingle_rule(@NotNull RuleSetGrammarParser.Single_ruleContext ctx) {
52 | this.rule.setCondition(this.logicalExpressions.pop());
53 | this.ruleSet.addRule(this.rule);
54 | this.rule = null;
55 | }
56 |
57 | @Override
58 | public void exitNumericVariable(@NotNull RuleSetGrammarParser.NumericVariableContext ctx) {
59 | this.arithmeticExpressions.add(new NumericVariable(ctx.getText()));
60 | }
61 |
62 | @Override
63 | public void exitNumericConst(@NotNull RuleSetGrammarParser.NumericConstContext ctx) {
64 | DecimalFormatSymbols symbols = new DecimalFormatSymbols();
65 | symbols.setDecimalSeparator('.');
66 | String pattern = "#0.0#";
67 |
68 | DecimalFormat decimalFormat = new DecimalFormat(pattern, symbols);
69 | decimalFormat.setParseBigDecimal(true);
70 | BigDecimal value;
71 | try {
72 | value = (BigDecimal)decimalFormat.parse(ctx.getText());
73 | } catch (ParseException e) {
74 | throw new RuntimeException(e);
75 | }
76 | this.arithmeticExpressions.push(new NumericConstant(value));
77 | }
78 |
79 | @Override
80 | public void enterNumericConst(@NotNull RuleSetGrammarParser.NumericConstContext ctx) {
81 | super.enterNumericConst(ctx);
82 | }
83 |
84 | @Override
85 | public void exitArithmeticExpressionMult(@NotNull RuleSetGrammarParser.ArithmeticExpressionMultContext ctx) {
86 | exitRealArithmeticExpression("*");
87 | }
88 |
89 | @Override
90 | public void exitArithmeticExpressionDiv(@NotNull RuleSetGrammarParser.ArithmeticExpressionDivContext ctx) {
91 | exitRealArithmeticExpression("/");
92 | }
93 |
94 | @Override
95 | public void exitArithmeticExpressionPlus(@NotNull RuleSetGrammarParser.ArithmeticExpressionPlusContext ctx) {
96 | exitRealArithmeticExpression("+");
97 | }
98 |
99 | @Override
100 | public void exitArithmeticExpressionMinus(@NotNull RuleSetGrammarParser.ArithmeticExpressionMinusContext ctx) {
101 | exitRealArithmeticExpression("-");
102 | }
103 |
104 | protected void exitRealArithmeticExpression(String op) {
105 | // popping order matters
106 | ArithmeticExpression right = this.arithmeticExpressions.pop();
107 | ArithmeticExpression left = this.arithmeticExpressions.pop();
108 | RealArithmeticExpression expr = new RealArithmeticExpression(op, left, right);
109 | this.arithmeticExpressions.push(expr);
110 | }
111 |
112 | @Override
113 | public void exitArithmeticExpressionNegation(
114 | @NotNull RuleSetGrammarParser.ArithmeticExpressionNegationContext ctx) {
115 | Negation negation = new Negation(this.arithmeticExpressions.pop());
116 | this.arithmeticExpressions.push(negation);
117 | }
118 |
119 | @Override
120 | public void exitComparison_operand(@NotNull RuleSetGrammarParser.Comparison_operandContext ctx) {
121 | ArithmeticExpression expr = this.arithmeticExpressions.pop();
122 | this.comparisonOperands.push(expr);
123 | }
124 |
125 | @Override
126 | public void exitComparisonExpressionWithOperator(
127 | @NotNull RuleSetGrammarParser.ComparisonExpressionWithOperatorContext ctx) {
128 | // popping order matters
129 | ComparisonOperand right = this.comparisonOperands.pop();
130 | ComparisonOperand left = this.comparisonOperands.pop();
131 | String op = ctx.getChild(1).getText();
132 | ComparisonExpression expr = new ComparisonExpression(op, left, right);
133 | this.logicalExpressions.push(expr);
134 | }
135 |
136 | @Override
137 | public void exitLogicalConst(@NotNull RuleSetGrammarParser.LogicalConstContext ctx) {
138 | switch (ctx.getText().toUpperCase()) {
139 | case "TRUE":
140 | this.logicalExpressions.push(LogicalConstant.getTrue());
141 | break;
142 | case "FALSE":
143 | this.logicalExpressions.push(LogicalConstant.getFalse());
144 | break;
145 | default:
146 | throw new RuntimeException("Unknown logical constant: " + ctx.getText());
147 | }
148 | }
149 |
150 | @Override
151 | public void exitLogicalVariable(@NotNull RuleSetGrammarParser.LogicalVariableContext ctx) {
152 | LogicalVariable variable = new LogicalVariable(ctx.getText());
153 | this.logicalExpressions.push(variable);
154 | }
155 |
156 | @Override
157 | public void exitLogicalExpressionOr(@NotNull RuleSetGrammarParser.LogicalExpressionOrContext ctx) {
158 | // popping order matters
159 | LogicalExpression right = logicalExpressions.pop();
160 | LogicalExpression left = logicalExpressions.pop();
161 | OrExpression expr = new OrExpression(left, right);
162 | this.logicalExpressions.push(expr);
163 | }
164 |
165 | @Override
166 | public void exitLogicalExpressionAnd(@NotNull RuleSetGrammarParser.LogicalExpressionAndContext ctx) {
167 | // popping order matters
168 | LogicalExpression right = logicalExpressions.pop();
169 | LogicalExpression left = logicalExpressions.pop();
170 | AndExpression expr = new AndExpression(left, right);
171 | this.logicalExpressions.push(expr);
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/AndExpression.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class AndExpression extends LogicalExpression {
6 | private final LogicalExpression left;
7 | private final LogicalExpression right;
8 |
9 | public AndExpression(LogicalExpression left, LogicalExpression right) {
10 | super("and");
11 |
12 | this.left = left;
13 | this.right = right;
14 | }
15 |
16 | @JsonProperty("left")
17 | public LogicalExpression getLeft() {
18 | return left;
19 | }
20 |
21 | @JsonProperty("right")
22 | public LogicalExpression getRight() {
23 | return right;
24 | }
25 |
26 | @Override
27 | public boolean equals(Object o) {
28 | if (this == o) return true;
29 | if (o == null || getClass() != o.getClass()) return false;
30 |
31 | AndExpression other = (AndExpression) o;
32 |
33 | if (left != null ? !left.equals(other.left) : other.left != null) return false;
34 | if (right != null ? !right.equals(other.right) : other.right != null) return false;
35 |
36 | return true;
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | int result = left != null ? left.hashCode() : 0;
42 | result = 31 * result + (right != null ? right.hashCode() : 0);
43 | return result;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/ArithmeticExpression.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | public interface ArithmeticExpression extends ComparisonOperand {
4 | }
5 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/ComparisonExpression.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class ComparisonExpression extends LogicalExpression {
6 | private final String operator;
7 | private final ComparisonOperand left;
8 | private final ComparisonOperand right;
9 |
10 | public ComparisonExpression(String operator, ComparisonOperand left, ComparisonOperand right) {
11 | super("comp");
12 | this.operator = operator;
13 | this.left = left;
14 | this.right = right;
15 | }
16 |
17 | @JsonProperty("op")
18 | public String getOperator() {
19 | return operator;
20 | }
21 |
22 | @JsonProperty("left")
23 | public ComparisonOperand getLeft() {
24 | return left;
25 | }
26 |
27 | @JsonProperty("right")
28 | public ComparisonOperand getRight() {
29 | return right;
30 | }
31 |
32 | @Override
33 | public boolean equals(Object o) {
34 | if (this == o) return true;
35 | if (o == null || getClass() != o.getClass()) return false;
36 |
37 | ComparisonExpression other = (ComparisonExpression) o;
38 |
39 | if (left != null ? !left.equals(other.left) : other.left != null) return false;
40 | if (operator != null ? !operator.equals(other.operator) : other.operator != null) return false;
41 | if (right != null ? !right.equals(other.right) : other.right != null) return false;
42 |
43 | return true;
44 | }
45 |
46 | @Override
47 | public int hashCode() {
48 | int result = operator != null ? operator.hashCode() : 0;
49 | result = 31 * result + (left != null ? left.hashCode() : 0);
50 | result = 31 * result + (right != null ? right.hashCode() : 0);
51 | return result;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/ComparisonOperand.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | public interface ComparisonOperand {
4 | }
5 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/Conclusion.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize;
5 | import me.ivanyu.pojos.serializers.ConclusionSerializer;
6 |
7 | @JsonSerialize(using= ConclusionSerializer.class)
8 | public class Conclusion implements RuleSetPojo {
9 | private final String name;
10 |
11 | public Conclusion(String name) {
12 | this.name = name;
13 | }
14 |
15 | @JsonProperty
16 | public String getName() {
17 | return name;
18 | }
19 |
20 | @Override
21 | public boolean equals(Object o) {
22 | if (this == o) return true;
23 | if (o == null || getClass() != o.getClass()) return false;
24 |
25 | Conclusion other = (Conclusion) o;
26 |
27 | if (name != null ? !name.equals(other.name) : other.name != null) return false;
28 |
29 | return true;
30 | }
31 |
32 | @Override
33 | public int hashCode() {
34 | return name != null ? name.hashCode() : 0;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/LogicalConstant.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class LogicalConstant extends LogicalExpression {
6 | private final boolean value;
7 |
8 | private LogicalConstant(boolean value) {
9 | super("const");
10 |
11 | this.value = value;
12 | }
13 |
14 | @JsonProperty("v")
15 | public boolean getValue() {
16 | return value;
17 | }
18 |
19 | public static LogicalConstant getTrue(){
20 | return new LogicalConstant(true);
21 | }
22 |
23 | public static LogicalConstant getFalse(){
24 | return new LogicalConstant(false);
25 | }
26 |
27 | @Override
28 | public boolean equals(Object o) {
29 | if (this == o) return true;
30 | if (o == null || getClass() != o.getClass()) return false;
31 |
32 | LogicalConstant other = (LogicalConstant) o;
33 | return value == other.value;
34 | }
35 |
36 | @Override
37 | public int hashCode() {
38 | return (value ? 1 : 0);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/LogicalExpression.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public abstract class LogicalExpression implements RuleSetPojo {
6 | private final String type;
7 |
8 | protected LogicalExpression(String type) {
9 | this.type = type;
10 | }
11 |
12 | @JsonProperty("t")
13 | public String getType() {
14 | return type;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/LogicalVariable.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class LogicalVariable extends LogicalExpression {
6 | private final String variableName;
7 |
8 | public LogicalVariable(String variableName) {
9 | super("var");
10 |
11 | this.variableName = variableName;
12 | }
13 |
14 | @JsonProperty("n")
15 | public String getVariableName() {
16 | return variableName;
17 | }
18 |
19 | @Override
20 | public boolean equals(Object o) {
21 | if (this == o) return true;
22 | if (o == null || getClass() != o.getClass()) return false;
23 |
24 | LogicalVariable other = (LogicalVariable) o;
25 | if (variableName != null
26 | ? !variableName.equals(other.variableName)
27 | : other.variableName != null) return false;
28 |
29 | return true;
30 | }
31 |
32 | @Override
33 | public int hashCode() {
34 | return variableName != null ? variableName.hashCode() : 0;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/Negation.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class Negation implements ArithmeticExpression {
6 | private final ArithmeticExpression expression;
7 |
8 | public Negation(ArithmeticExpression expression) {
9 | this.expression = expression;
10 | }
11 |
12 | @JsonProperty("t")
13 | public String getType() { return "neg"; }
14 |
15 | @JsonProperty("expr")
16 | public ArithmeticExpression getExpression() {
17 | return expression;
18 | }
19 |
20 | @Override
21 | public boolean equals(Object o) {
22 | if (this == o) return true;
23 | if (o == null || getClass() != o.getClass()) return false;
24 |
25 | Negation other = (Negation) o;
26 | if (expression != null
27 | ? !expression.equals(other.expression)
28 | : other.expression != null) return false;
29 |
30 | return true;
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return expression != null ? expression.hashCode() : 0;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/NumericConstant.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | import java.math.BigDecimal;
6 |
7 | public class NumericConstant extends NumericEntity {
8 | private final BigDecimal value;
9 |
10 | public NumericConstant(BigDecimal value) {
11 | super("const");
12 | this.value = value;
13 | }
14 |
15 | @JsonProperty("v")
16 | public BigDecimal getValue() {
17 | return value;
18 | }
19 |
20 | @Override
21 | public boolean equals(Object o) {
22 | if (this == o) return true;
23 | if (o == null || getClass() != o.getClass()) return false;
24 |
25 | NumericConstant other = (NumericConstant) o;
26 | if (value != null ? !value.equals(other.value) : other.value != null) return false;
27 | return true;
28 | }
29 |
30 | @Override
31 | public int hashCode() {
32 | return value != null ? value.hashCode() : 0;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/NumericEntity.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public abstract class NumericEntity implements ArithmeticExpression, ComparisonOperand {
6 | private final String type;
7 |
8 | public NumericEntity(String type) {
9 | this.type = type;
10 | }
11 |
12 | @JsonProperty("t")
13 | public String getType() {
14 | return type;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/NumericVariable.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class NumericVariable extends NumericEntity {
6 | private final String variableName;
7 |
8 | public NumericVariable(String variableName) {
9 | super("var");
10 |
11 | this.variableName = variableName;
12 | }
13 |
14 | @JsonProperty("n")
15 | public String getVariableName() {
16 | return variableName;
17 | }
18 |
19 | @Override
20 | public boolean equals(Object o) {
21 | if (this == o) return true;
22 | if (o == null || getClass() != o.getClass()) return false;
23 |
24 | NumericVariable other = (NumericVariable) o;
25 | if (variableName != null ? !variableName.equals(other.variableName) : other.variableName != null)
26 | return false;
27 |
28 | return true;
29 | }
30 |
31 | @Override
32 | public int hashCode() {
33 | return variableName != null ? variableName.hashCode() : 0;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/OrExpression.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class OrExpression extends LogicalExpression {
6 | private final LogicalExpression left;
7 | private final LogicalExpression right;
8 |
9 | public OrExpression(LogicalExpression left, LogicalExpression right) {
10 | super("or");
11 |
12 | this.left = left;
13 | this.right = right;
14 | }
15 |
16 | @JsonProperty("left")
17 | public LogicalExpression getLeft() {
18 | return left;
19 | }
20 |
21 | @JsonProperty("right")
22 | public LogicalExpression getRight() {
23 | return right;
24 | }
25 |
26 | @Override
27 | public boolean equals(Object o) {
28 | if (this == o) return true;
29 | if (o == null || getClass() != o.getClass()) return false;
30 |
31 | OrExpression other = (OrExpression) o;
32 |
33 | if (left != null ? !left.equals(other.left) : other.left != null) return false;
34 | if (right != null ? !right.equals(other.right) : other.right != null) return false;
35 |
36 | return true;
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | int result = left != null ? left.hashCode() : 0;
42 | result = 31 * result + (right != null ? right.hashCode() : 0);
43 | return result;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/RealArithmeticExpression.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class RealArithmeticExpression implements ArithmeticExpression {
6 | private final String operator;
7 | private final ArithmeticExpression left;
8 | private final ArithmeticExpression right;
9 |
10 | public RealArithmeticExpression(String operator, ArithmeticExpression left, ArithmeticExpression right) {
11 | this.operator = operator;
12 | this.left = left;
13 | this.right = right;
14 | }
15 |
16 | @JsonProperty("t")
17 | public String getType() { return "arith"; }
18 |
19 | @JsonProperty("op")
20 | public String getOperator() {
21 | return operator;
22 | }
23 |
24 | @JsonProperty("left")
25 | public ArithmeticExpression getLeft() {
26 | return left;
27 | }
28 |
29 | @JsonProperty("right")
30 | public ArithmeticExpression getRight() {
31 | return right;
32 | }
33 |
34 | @Override
35 | public boolean equals(Object o) {
36 | if (this == o) return true;
37 | if (o == null || getClass() != o.getClass()) return false;
38 |
39 | RealArithmeticExpression that = (RealArithmeticExpression) o;
40 |
41 | if (left != null ? !left.equals(that.left) : that.left != null) return false;
42 | if (operator != null ? !operator.equals(that.operator) : that.operator != null) return false;
43 | if (right != null ? !right.equals(that.right) : that.right != null) return false;
44 |
45 | return true;
46 | }
47 |
48 | @Override
49 | public int hashCode() {
50 | int result = operator != null ? operator.hashCode() : 0;
51 | result = 31 * result + (left != null ? left.hashCode() : 0);
52 | result = 31 * result + (right != null ? right.hashCode() : 0);
53 | return result;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/Rule.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class Rule implements RuleSetPojo {
6 | private LogicalExpression condition;
7 |
8 | private Conclusion conclusion;
9 |
10 | public Rule() {
11 | }
12 |
13 | public Rule(LogicalExpression condition, Conclusion conclusion) {
14 | this.condition = condition;
15 | this.conclusion = conclusion;
16 | }
17 |
18 | @JsonProperty("cond")
19 | public LogicalExpression getCondition() {
20 | return condition;
21 | }
22 |
23 | public void setCondition(LogicalExpression condition) {
24 | this.condition = condition;
25 | }
26 |
27 | @JsonProperty("concl")
28 | public Conclusion getConclusion() {
29 | return conclusion;
30 | }
31 |
32 | public void setConclusion(String conclusion) {
33 | this.conclusion = new Conclusion(conclusion);
34 | }
35 |
36 | @Override
37 | public boolean equals(Object o) {
38 | if (this == o) return true;
39 | if (o == null || getClass() != o.getClass()) return false;
40 |
41 | Rule rule = (Rule) o;
42 |
43 | if (conclusion != null ? !conclusion.equals(rule.conclusion) : rule.conclusion != null) return false;
44 | if (condition != null ? !condition.equals(rule.condition) : rule.condition != null) return false;
45 |
46 | return true;
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | int result = condition != null ? condition.hashCode() : 0;
52 | result = 31 * result + (conclusion != null ? conclusion.hashCode() : 0);
53 | return result;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/RuleSet.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.Collections;
6 | import java.util.List;
7 |
8 | public class RuleSet implements RuleSetPojo {
9 | public final List rules;
10 |
11 | public RuleSet() {
12 | this.rules = new ArrayList<>();
13 | }
14 |
15 | public RuleSet(Collection rules) {
16 | this.rules = new ArrayList<>(rules);
17 | }
18 |
19 | public List getRules() {
20 | return Collections.unmodifiableList(rules);
21 | }
22 |
23 | public void addRule(Rule rule) {
24 | this.rules.add(rule);
25 | }
26 |
27 | @Override
28 | public boolean equals(Object o) {
29 | if (this == o) return true;
30 | if (o == null || getClass() != o.getClass()) return false;
31 |
32 | RuleSet ruleSet = (RuleSet) o;
33 |
34 | return this.rules.equals(ruleSet.rules);
35 | }
36 |
37 | @Override
38 | public int hashCode() {
39 | return rules != null ? rules.hashCode() : 0;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/RuleSetPojo.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos;
2 |
3 | public interface RuleSetPojo {
4 | }
5 |
--------------------------------------------------------------------------------
/src/main/java/me/ivanyu/pojos/serializers/ConclusionSerializer.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu.pojos.serializers;
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator;
4 | import com.fasterxml.jackson.core.JsonProcessingException;
5 | import com.fasterxml.jackson.databind.JsonSerializer;
6 | import com.fasterxml.jackson.databind.SerializerProvider;
7 | import me.ivanyu.pojos.Conclusion;
8 |
9 | import java.io.IOException;
10 |
11 | public class ConclusionSerializer extends JsonSerializer {
12 | @Override
13 | public void serialize(Conclusion conclusion,
14 | JsonGenerator jsonGenerator,
15 | SerializerProvider serializerProvider)
16 | throws IOException, JsonProcessingException {
17 | jsonGenerator.writeString(conclusion.getName());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/me/ivanyu/CompilerTest.java:
--------------------------------------------------------------------------------
1 | package me.ivanyu;
2 |
3 | import me.ivanyu.compiler.Compiler;
4 | import me.ivanyu.pojos.*;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.junit.runners.Parameterized;
8 |
9 | import java.math.BigDecimal;
10 | import java.util.Arrays;
11 | import java.util.Collection;
12 |
13 | import static org.junit.Assert.assertEquals;
14 |
15 | @RunWith(Parameterized.class)
16 | public class CompilerTest {
17 | private static Conclusion standardConclusion = new Conclusion("TheConclusion");
18 |
19 | private static RuleSet createRuleSet(LogicalExpression... conditions) {
20 | RuleSet ruleSet = new RuleSet();
21 | for (LogicalExpression condition : conditions) {
22 | Rule rule = new Rule(condition, standardConclusion);
23 | ruleSet.addRule(rule);
24 | }
25 | return ruleSet;
26 | }
27 |
28 | @Parameterized.Parameters
29 | public static Collection