From e2f997a9a18f889f7c92e16eb530e972a2d61d5c Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Wed, 8 May 2019 19:23:45 +0200 Subject: [PATCH] Begin with expression sub-language. --- eraser-base/src/main/jastadd/Expression.jrag | 10 ++ .../src/main/jastadd/Expression.relast | 25 ++++ eraser-base/src/main/jastadd/Rules.jrag | 4 + eraser-base/src/main/jastadd/Rules.relast | 2 + eraser-base/src/main/jastadd/eraser.flex | 19 ++- eraser-base/src/main/jastadd/eraser.parser | 61 ++++++++ .../st/eraser/parser/EraserParserHelper.java | 27 ++++ .../inf/st/eraser/util/ParserUtils.java | 31 +++++ .../inf/st/eraser/ExpressionParserTest.java | 131 ++++++++++++++++++ 9 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 eraser-base/src/main/jastadd/Expression.jrag create mode 100644 eraser-base/src/main/jastadd/Expression.relast create mode 100644 eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionParserTest.java diff --git a/eraser-base/src/main/jastadd/Expression.jrag b/eraser-base/src/main/jastadd/Expression.jrag new file mode 100644 index 00000000..cadc44e4 --- /dev/null +++ b/eraser-base/src/main/jastadd/Expression.jrag @@ -0,0 +1,10 @@ +aspect Expression { + syn boolean LogicalExpression.eval() = false; + eq NotExpression.eval() = !getOperand().eval(); + eq ComparingExpression.eval() { + // TODO + return false; + } + eq AndExpression.eval() = getLeftOperand().eval() && getRightOperand().eval(); + eq OrExpression.eval() = getLeftOperand().eval() || getRightOperand().eval(); +} diff --git a/eraser-base/src/main/jastadd/Expression.relast b/eraser-base/src/main/jastadd/Expression.relast new file mode 100644 index 00000000..591e112a --- /dev/null +++ b/eraser-base/src/main/jastadd/Expression.relast @@ -0,0 +1,25 @@ +abstract Expression ; + +abstract NumberExpression : Expression ; +ParenthesizedNumberExpression : NumberExpression ::= Operand:NumberExpression ; +NumberLiteralExpression : NumberExpression ::= <Value:double> ; + +// variation point of the expression language: +Designator : NumberExpression ; +rel Designator.Item -> Item ; + +abstract BinaryNumberExpression : NumberExpression ::= LeftOperand:NumberExpression RightOperand:NumberExpression ; +AddExpression : BinaryNumberExpression ; +SubExpression : BinaryNumberExpression ; +MultExpression : BinaryNumberExpression ; +DivExpression : BinaryNumberExpression ; +PowerExpression : BinaryNumberExpression ; + +abstract LogicalExpression : Expression ; +ParenthesizedLogicalExpression : LogicalExpression ::= Operand:LogicalExpression ; +NotExpression : LogicalExpression ::= Operand:LogicalExpression ; +ComparingExpression : LogicalExpression ::= LeftOperand:NumberExpression RightOperand:NumberExpression <Comparator:ComparatorType> ; + +abstract BinaryLogicalExpression : LogicalExpression ::= LeftOperand:LogicalExpression RightOperand:LogicalExpression ; +AndExpression : BinaryLogicalExpression ; +OrExpression : BinaryLogicalExpression ; diff --git a/eraser-base/src/main/jastadd/Rules.jrag b/eraser-base/src/main/jastadd/Rules.jrag index 69ae87d3..26c7e996 100644 --- a/eraser-base/src/main/jastadd/Rules.jrag +++ b/eraser-base/src/main/jastadd/Rules.jrag @@ -78,9 +78,13 @@ aspect Rules { // --- Condition.holdsFor --- syn boolean Condition.holdsFor(Item item); eq ItemStateCheckCondition.holdsFor(Item item) = getItemStateCheck().holdsFor(item); + eq ExpressionCondition.holdsFor(Item item) = getLogicalExpression().eval(); // --- Action.applyFor --- public abstract void Action.applyFor(Item item); + public void NoopAction.applyFor(Item item) { + // empty by design + } public void LambdaAction.applyFor(Item item) { getLambda().accept(item); } diff --git a/eraser-base/src/main/jastadd/Rules.relast b/eraser-base/src/main/jastadd/Rules.relast index 69854a70..88f0c3d6 100644 --- a/eraser-base/src/main/jastadd/Rules.relast +++ b/eraser-base/src/main/jastadd/Rules.relast @@ -2,7 +2,9 @@ Rule ::= Condition* Action* ; abstract Condition ; ItemStateCheckCondition : Condition ::= ItemStateCheck ; +ExpressionCondition : Condition ::= LogicalExpression ; abstract Action ; +NoopAction : Action ; LambdaAction : Action ::= <Lambda:Action2EditConsumer> ; TriggerRuleAction : Action ; diff --git a/eraser-base/src/main/jastadd/eraser.flex b/eraser-base/src/main/jastadd/eraser.flex index 58d16b9c..ffb7bdeb 100644 --- a/eraser-base/src/main/jastadd/eraser.flex +++ b/eraser-base/src/main/jastadd/eraser.flex @@ -34,7 +34,7 @@ WhiteSpace = [ ] | \t | \f | \n | \r | \r\n Text = \" ([^\"]*) \" Integer = [:digit:]+ // | "+" [:digit:]+ | "-" [:digit:]+ -//Real = [:digit:]+ "." [:digit:]* | "." [:digit:]+ +Real = [:digit:]+ "." [:digit:]* | "." [:digit:]+ Comment = "//" [^\n\r]+ @@ -56,6 +56,7 @@ Comment = "//" [^\n\r]+ "Mqtt" { return sym(Terminals.MQTT); } "Influx" { return sym(Terminals.INFLUX); } "ML" { return sym(Terminals.ML); } +"Rule" { return sym(Terminals.RULE); } // special items (group already has a token definition) "Activity" { return sym(Terminals.ACTIVITY); } "Color" { return sym(Terminals.COLOR); } @@ -101,6 +102,20 @@ Comment = "//" [^\n\r]+ // special characters "=" { return sym(Terminals.EQUALS); } //"\"" { return sym(Terminals.QUOTE); } +"<" { return sym(Terminals.LE); } +"<=" { return sym(Terminals.LT); } +"==" { return sym(Terminals.EQ); } +"!=" { return sym(Terminals.NE); } +">" { return sym(Terminals.GT); } +">=" { return sym(Terminals.GE); } +"+" { return sym(Terminals.PLUS); } +"*" { return sym(Terminals.MULT); } +"-" { return sym(Terminals.MINUS); } +"/" { return sym(Terminals.DIV); } +"^" { return sym(Terminals.POW); } +"!" { return sym(Terminals.EXCLAMATION); } +"|" { return sym(Terminals.OR); } +"&" { return sym(Terminals.AND); } ":" { return sym(Terminals.COLON); } "," { return sym(Terminals.COMMA); } ";" { return sym(Terminals.SEMICOLON); } @@ -112,8 +127,8 @@ Comment = "//" [^\n\r]+ "}" { return sym(Terminals.RB_CURLY); } //{Identifier} { return sym(Terminals.NAME); } {Text} { return symText(Terminals.TEXT); } -//{Real} { return sym(Terminals.REAL); } {Integer} { return sym(Terminals.INTEGER); } +{Real} { return sym(Terminals.REAL); } <<EOF>> { return sym(Terminals.EOF); } /* error fallback */ [^] { throw new Error("Illegal character '"+ yytext() +"' at line " + (yyline+1) + " column " + (yycolumn+1)); } diff --git a/eraser-base/src/main/jastadd/eraser.parser b/eraser-base/src/main/jastadd/eraser.parser index acd54d7c..0639a0fa 100644 --- a/eraser-base/src/main/jastadd/eraser.parser +++ b/eraser-base/src/main/jastadd/eraser.parser @@ -1,6 +1,7 @@ %header {: package de.tudresden.inf.st.eraser.jastadd.parser; import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.jastadd.model.Action; import de.tudresden.inf.st.eraser.parser.EraserParserHelper; import java.util.Map; import java.util.HashMap; @@ -23,6 +24,8 @@ import java.util.HashMap; :} ; %goal goal; +%goal number_expression; +%goal logical_expression; Root goal = thing.t goal.r {: insertZero(r.getOpenHAB2Model().getThingList(), t); return r; :} @@ -35,6 +38,7 @@ Root goal = | mqtt_root.mr goal.r {: r.setMqttRoot(mr); return r; :} | influx_root.ir goal.r {: r.setInfluxRoot(ir); return r; :} | machine_learning_root.ml goal.r {: r.setMachineLearningRoot(ml); return r; :} + | rule.rule goal.r {: r.addRule(rule); return r; :} | thing.t {: return eph.createRoot(t); :} | item.i {: return eph.createRoot(); :} | group.g {: return eph.createRoot(g); :} @@ -45,6 +49,48 @@ Root goal = | mqtt_root.mr {: return eph.createRoot(mr); :} | influx_root.ir {: return eph.createRoot(ir); :} | machine_learning_root.ml {: return eph.createRoot(ml); :} + | rule.rule {: return eph.createRoot(rule); :} + ; + +%left RB_ROUND; +%left MULT, DIV; +%left PLUS, MINUS; +%left POW; +%left LT, LE, EQ, GE, GT; +%left OR; +%left AND; + +NumberExpression number_expression = + LB_ROUND number_expression.a MULT number_expression.b RB_ROUND {: return new MultExpression(a, b); :} + | LB_ROUND number_expression.a DIV number_expression.b RB_ROUND {: return new DivExpression(a, b); :} + | LB_ROUND number_expression.a PLUS number_expression.b RB_ROUND {: return new AddExpression(a, b); :} + | LB_ROUND number_expression.a MINUS number_expression.b RB_ROUND {: return new SubExpression(a, b); :} + | LB_ROUND number_expression.a POW number_expression.b RB_ROUND {: return new PowerExpression(a, b); :} + | literal_expression.l {: return l; :} + | designator.d {: return d; :} + | LB_ROUND number_expression.e RB_ROUND {: return new ParenthesizedNumberExpression(e); :} + ; + +LogicalExpression logical_expression = + LB_ROUND logical_expression.a AND logical_expression.b RB_ROUND {: return new AndExpression(a, b); :} + | LB_ROUND logical_expression.a OR logical_expression.b RB_ROUND {: return new OrExpression(a, b); :} + | LB_ROUND EXCLAMATION logical_expression.e RB_ROUND {: return new NotExpression(e); :} + | LB_ROUND logical_expression.e RB_ROUND {: return new ParenthesizedLogicalExpression(e); :} + | LB_ROUND number_expression.a LT number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.LessThan); :} + | LB_ROUND number_expression.a LE number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.LessOrEqualThan); :} + | LB_ROUND number_expression.a EQ number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.Equals); :} + | LB_ROUND number_expression.a NE number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.NotEquals); :} + | LB_ROUND number_expression.a GE number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.GreaterOrEqualThan); :} + | LB_ROUND number_expression.a GT number_expression.b RB_ROUND {: return new ComparingExpression(a, b, ComparatorType.GreaterThan); :} + ; + +NumberLiteralExpression literal_expression = + INTEGER.n {: return new NumberLiteralExpression(Integer.parseInt(n)); :} + | REAL.n {: return new NumberLiteralExpression(Double.parseDouble(n)); :} + ; + +Designator designator = + TEXT.n {: return eph.createDesignator(n); :} ; Thing thing = @@ -208,6 +254,21 @@ MachineLearningRoot machine_learning_root_body = | {: return MachineLearningRoot.createDefault(); :} ; +// Rule: condition=condition action=a1 action=a2; +// (only allow one condition and action for now) +Rule rule = + RULE COLON CONDITION EQUALS condition.c action.a SEMICOLON {: return eph.createRule(c, a); :} + ; + +Condition condition = + logical_expression.be {: return new ExpressionCondition(be); :} + ; + +// TODO implement action cases +Action action = {: return new NoopAction(); :} + ; + +// Util: StringList, StringKeyMap, IntegerKeyMap StringList string_list = LB_SQUARE string_list_body.slb RB_SQUARE {: return slb; :} | LB_SQUARE RB_SQUARE {: return new StringList(); :} diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java index 400a09af..bc74bdd9 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/parser/EraserParserHelper.java @@ -7,6 +7,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.*; +import java.util.Comparator; import java.util.function.BiConsumer; /** @@ -29,6 +30,7 @@ public class EraserParserHelper { private Map<Channel, String> missingChannelTypeMap = new HashMap<>(); private Map<Item, String> missingTopicMap = new HashMap<>(); private Map<Item, String> missingItemCategoryMap = new HashMap<>(); + private Map<Designator, String> missingItemForDesignator = new HashMap<>(); private Map<Thing, Iterable<String>> missingChannelListMap = new HashMap<>(); private Map<Channel, Iterable<String>> missingItemLinkListMap = new HashMap<>(); @@ -64,11 +66,16 @@ public class EraserParserHelper { * @throws java.util.NoSuchElementException if a reference can not be resolved */ public void resolveReferences() { + if (this.root == null) { + // when parsing expressions + this.root = createRoot(); + } if (checkUnusedElements) { fillUnused(); } resolve(thingTypeMap, missingThingTypeMap, Thing::setType); resolve(channelTypeMap, missingChannelTypeMap, Channel::setType); + resolve(itemMap, missingItemForDesignator, Designator::setItem); missingTopicMap.forEach((topic, parts) -> ParserUtils.createMqttTopic(topic, parts, this.root)); this.root.getMqttRoot().ensureCorrectPrefixes(); @@ -448,4 +455,24 @@ public class EraserParserHelper { return result; } + public Root createRoot(Rule rule) { + Root result = createRoot(); + result.addRule(rule); + return result; + } + + //+++ newStuff (to be categorized) +++ + public Designator createDesignator(String itemName) { + Designator result = new Designator(); + missingItemForDesignator.put(result, itemName); + return result; + } + + public Rule createRule(Condition c, Action a) { + Rule result = new Rule(); + result.addCondition(c); + result.addAction(a); + return result; + } + } diff --git a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java index 5eca1e63..657a2808 100644 --- a/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java +++ b/eraser-base/src/main/java/de/tudresden/inf/st/eraser/util/ParserUtils.java @@ -192,4 +192,35 @@ public class ParserUtils { })); } + public static NumberExpression parseNumberExpression(String expression_string) throws IOException, Parser.Exception { + return (NumberExpression) parseExpression(expression_string, EraserParser.AltGoals.number_expression); + } + + public static LogicalExpression parseLogicalExpression(String expression_string) throws IOException, Parser.Exception { + return (LogicalExpression) parseExpression(expression_string, EraserParser.AltGoals.logical_expression); + } + + private static Expression parseExpression(String expression_string, short alt_goal) throws IOException, Parser.Exception { + StringReader reader = new StringReader(expression_string); + if (verboseLoading) { + EraserScanner scanner = new EraserScanner(reader); + try { + Symbol token; + while ((token = scanner.nextToken()).getId() != EraserParser.Terminals.EOF) { + logger.debug("start: {}, end: {}, id: {}, value: {}", + token.getStart(), token.getEnd(), EraserParser.Terminals.NAMES[token.getId()], token.value); + } + } catch (Scanner.Exception e) { + e.printStackTrace(); + } + } + reader = new StringReader(expression_string); + EraserScanner scanner = new EraserScanner(reader); + EraserParser parser = new EraserParser(); + Expression result = (Expression) parser.parse(scanner, alt_goal); + parser.resolveReferences(); + reader.close(); + return result; + } + } diff --git a/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionParserTest.java b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionParserTest.java new file mode 100644 index 00000000..a7f95965 --- /dev/null +++ b/eraser-base/src/test/java/de/tudresden/inf/st/eraser/ExpressionParserTest.java @@ -0,0 +1,131 @@ +package de.tudresden.inf.st.eraser; + +import beaver.Parser; +import de.tudresden.inf.st.eraser.jastadd.model.*; +import de.tudresden.inf.st.eraser.util.ParserUtils; +import org.junit.Test; + +import java.io.IOException; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertThat; + +/** + * Test correct parsing of NumberExpression and LogicalExpression. + * + * @author rschoene - Initial contribution + */ +public class ExpressionParserTest { + + @Test + public void plusExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(3 + 4)"); + assertThat(sut, instanceOf(AddExpression.class)); + AddExpression addExpression = (AddExpression) sut; + assertThat(addExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) addExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(3.0)); + assertThat(addExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) addExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(4.0)); + } + + @Test + public void minusExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(12.5 - 4.1)"); + assertThat(sut, instanceOf(SubExpression.class)); + SubExpression subExpression = (SubExpression) sut; + assertThat(subExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) subExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(12.5)); + assertThat(subExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) subExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(4.1)); + } + + @Test + public void multExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(0 * 0)"); + assertThat(sut, instanceOf(MultExpression.class)); + MultExpression multExpression = (MultExpression) sut; + assertThat(multExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) multExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(0.0)); + assertThat(multExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) multExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(0.0)); + } + + @Test + public void divExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(1.1 / 0.0)"); + assertThat(sut, instanceOf(DivExpression.class)); + DivExpression divExpression = (DivExpression) sut; + assertThat(divExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) divExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(1.1)); + assertThat(divExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) divExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(0.0)); + } + + @Test + public void powerExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(3 ^ 0.5)"); + assertThat(sut, instanceOf(PowerExpression.class)); + PowerExpression powExpression = (PowerExpression) sut; + assertThat(powExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) powExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(3.0)); + assertThat(powExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) powExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(0.5)); + } + + @Test + public void parenthesizedExpression() throws IOException, Parser.Exception { + NumberExpression sut = ParserUtils.parseNumberExpression("(3)"); + assertThat(sut, instanceOf(ParenthesizedNumberExpression.class)); + ParenthesizedNumberExpression parenExpression = (ParenthesizedNumberExpression) sut; + assertThat(parenExpression.getOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) parenExpression.getOperand(); + assertThat(left.getValue(), equalTo(3.0)); + } + + @Test + public void complexExpression() throws IOException, Parser.Exception { + ParserUtils.setVerboseLoading(true); + NumberExpression sut = ParserUtils.parseNumberExpression("((3 + 4) * (1 / (12 - 8)))"); + assertThat(sut, instanceOf(MultExpression.class)); + MultExpression multExpression = (MultExpression) sut; + + // 3+4 + assertThat(multExpression.getLeftOperand(), instanceOf(AddExpression.class)); + AddExpression addExpression = (AddExpression) multExpression.getLeftOperand(); + assertThat(addExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression left = (NumberLiteralExpression) addExpression.getLeftOperand(); + assertThat(left.getValue(), equalTo(3.0)); + assertThat(addExpression.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression right = (NumberLiteralExpression) addExpression.getRightOperand(); + assertThat(right.getValue(), equalTo(4.0)); + + // 1/(12-8) + assertThat(multExpression.getRightOperand(), instanceOf(DivExpression.class)); + DivExpression divExpression = (DivExpression) multExpression.getRightOperand(); + + assertThat(divExpression.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression leftOfDiv = (NumberLiteralExpression) divExpression.getLeftOperand(); + assertThat(leftOfDiv.getValue(), equalTo(1.0)); + + // 12-8 + assertThat(divExpression.getRightOperand(), instanceOf(SubExpression.class)); + SubExpression rightofDiv = (SubExpression) divExpression.getRightOperand(); + assertThat(rightofDiv.getLeftOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression leftOfSSub = (NumberLiteralExpression) rightofDiv.getLeftOperand(); + assertThat(leftOfSSub.getValue(), equalTo(12.0)); + assertThat(rightofDiv.getRightOperand(), instanceOf(NumberLiteralExpression.class)); + NumberLiteralExpression rightOfSub = (NumberLiteralExpression) rightofDiv.getRightOperand(); + assertThat(rightOfSub.getValue(), equalTo(8.0)); + } +} -- GitLab