()
+
+ val leftChild = root.leftChild
+ if (leftChild is BinaryOperationASTNode && leftChild.operator == root.operator) {
+ children.addAll(flattenCause(leftChild, temporalContext))
+ } else {
+ children.add(interpretAsCause(leftChild, temporalContext))
+ }
+
+ val rightChild = root.rightChild
+ if (rightChild is BinaryOperationASTNode && rightChild.operator == root.operator) {
+ children.addAll(flattenCause(rightChild, temporalContext))
+ } else {
+ children.add(interpretAsCause(rightChild, temporalContext))
+ }
+
+ return children
+ }
+
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ConsequenceDescription.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ConsequenceDescription.kt
new file mode 100644
index 0000000..535bea6
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ConsequenceDescription.kt
@@ -0,0 +1,17 @@
+package cambio.tltea.interpreter.nodes
+
+import cambio.tltea.interpreter.nodes.consequence.ConsequenceNode
+
+class ConsequenceDescription(val triggerManager: TriggerManager) {
+
+ internal fun activateConsequence() {
+ consequenceAST?.activateConsequence()
+ }
+
+ internal fun deactivateConsequence() {
+ consequenceAST?.deactivateConsequence()
+ }
+
+ var consequenceAST: ConsequenceNode? = null
+
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ConsequenceInterpreter.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ConsequenceInterpreter.kt
new file mode 100644
index 0000000..2bf63ce
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ConsequenceInterpreter.kt
@@ -0,0 +1,273 @@
+package cambio.tltea.interpreter.nodes
+
+import cambio.tltea.interpreter.nodes.consequence.*
+import cambio.tltea.interpreter.utils.ASTManipulator
+import cambio.tltea.parser.core.*
+import cambio.tltea.parser.core.temporal.*
+
+
+/**
+ * Expects a future MTL formula.
+ *
+ * Operators G[...] - Globally/Always F[...] - Finally/Eventually N[...] - Next
+ *
+ * Binary R[...] - Release U[...] - Until
+ *
+ * Not Allowed:
+ *
+ * S[...] - Since H[...] - History (always in the past) P[...] - Past (Once in the Past) Back to
+ *
+ *
+ *
+ * Simplifications:
+ *
+ * F[...] α = TRUE U[...] β R[...] α = NOT β U[...] NOT α N[TimeInstance] α = FALSE U[...] α
+ *
+ *
+ * NOT G NOT α = F α NOT F NOT α = G α
+ *
+ * @author Lion Wagner
+ */
+
+//until[x,y] => left is true till right is true
+//Roughly, but not exactly G[x,y](!right -> left)
+class ConsequenceInterpreter {
+
+ /**
+ * @param mtlRoot the root of the parsed MTL formula
+ * @param triggerManager this parameter can be used to combine multiple {@link TriggerNotifier}s from previous interpretations
+ */
+ @JvmOverloads
+ fun interpretAsMTL(mtlRoot: ASTNode, triggerManager: TriggerManager = TriggerManager()): ConsequenceDescription {
+
+ val consequenceDescription = ConsequenceDescription(triggerManager)
+ //gather initial temporal info
+ val temporalContext = if (mtlRoot is ITemporalOperationInfoHolder) mtlRoot.toTemporalOperatorInfo()
+ else TemporalOperatorInfo(OperatorToken.GLOBALLY, TemporalInterval(0.0, Double.POSITIVE_INFINITY))
+
+ val consequenceAST = interpret(mtlRoot, temporalContext, consequenceDescription)
+ consequenceDescription.consequenceAST = consequenceAST
+ return consequenceDescription
+ }
+
+
+ /**
+ * General matcher function
+ */
+ private fun interpret(
+ node: ASTNode,
+ temporalContext: TemporalOperatorInfo,
+ consequenceDescription: ConsequenceDescription
+ ): ConsequenceNode {
+ return when (node) {
+ is TemporalBinaryOperationASTNode -> interpretTemporalBinaryOperation(
+ node,
+ temporalContext,
+ consequenceDescription
+ )
+ is TemporalUnaryOperationASTNode -> interpretTemporalUnaryOperation(
+ node,
+ temporalContext,
+ consequenceDescription
+ )
+ is BinaryOperationASTNode -> interpretBinaryOperation(
+ node,
+ temporalContext,
+ consequenceDescription
+ )
+ is UnaryOperationASTNode -> interpretUnaryOperation(
+ node,
+ temporalContext,
+ consequenceDescription
+ )
+ is ValueASTNode -> interpretValueNode(node, temporalContext, consequenceDescription)
+ else -> {
+ throw IllegalArgumentException("Cannot interpret node of type ${node.javaClass.simpleName}")
+ }
+ }
+
+ }
+
+
+ private fun interpretTemporalBinaryOperation(
+ node: TemporalBinaryOperationASTNode,
+ temporalContext: TemporalOperatorInfo,
+ consequenceDescription: ConsequenceDescription
+ ): ConsequenceNode {
+ val temporalContext = node.toTemporalOperatorInfo()
+ if (node.leftChild is ITemporalOperationInfoHolder || node.rightChild is ITemporalOperationInfoHolder) {
+ throw IllegalArgumentException("Cannot interpret two consecutive temporal operators")
+ }
+ if (node.operator != OperatorToken.UNTIL) {
+ throw IllegalArgumentException("Cannot interpret temporal operator ${node.operator}")
+ }
+ // UNTIL
+
+ TODO("Implement until node interpretation")
+
+ }
+
+ private fun interpretTemporalUnaryOperation(
+ node: TemporalUnaryOperationASTNode,
+ temporalContext: TemporalOperatorInfo,
+ consequenceDescription: ConsequenceDescription
+ ): ConsequenceNode {
+ if (node.child is ITemporalOperationInfoHolder) {
+ throw IllegalArgumentException("Cannot interpret two consecutive temporal operators")
+ }
+ //TODO: for now the temporal context is simply passed down. In the future multiple temporal contexts may need to interact.
+ //Prophecy, Globally, Finally, Next
+ val temporalContext = node.toTemporalOperatorInfo()
+ return interpret(node.child, temporalContext, consequenceDescription)
+ }
+
+
+ private fun interpretBinaryOperation(
+ node: BinaryOperationASTNode,
+ temporalContext: TemporalOperatorInfo,
+ consequenceDescription: ConsequenceDescription
+ ): ConsequenceNode {
+
+ if (node is TemporalBinaryOperationASTNode) {
+ return interpretTemporalBinaryOperation(node, temporalContext, consequenceDescription)
+ } else if (OperatorToken.ComparisonOperatorTokens.contains(node.operator)) {
+ //create consequence comparison node
+ val leftChild = node.leftChild as ValueASTNode
+ val rightChild = node.rightChild as ValueASTNode
+
+ var targetEventHolder: ValueASTNode = leftChild
+ var targetValueHolder: ValueASTNode = rightChild
+
+ if (rightChild.containsEventName()) {
+ targetEventHolder = rightChild
+ targetValueHolder = leftChild
+ } else if (!leftChild.containsEventName()) {
+ throw IllegalArgumentException("Cannot interpret value event. Neither left nor right child is a target property or event.")
+ }
+
+ val targetValue = try {
+ targetValueHolder.value.toDouble()
+ } catch (e: NumberFormatException) {
+ targetValueHolder.value
+ }
+
+ return ValueEventConsequenceNode(
+ targetEventHolder.eventName,
+ targetValue,
+ node.operator,
+ consequenceDescription.triggerManager,
+ temporalContext
+ )
+
+ } else {
+ val interpretShorthand =
+ { child: ASTNode -> interpret(child, temporalContext, consequenceDescription) }
+ when (node.operator) {
+ OperatorToken.IMPLIES -> {
+ //create implication node
+ return interpretImplication(node, temporalContext, consequenceDescription)
+ }
+ OperatorToken.AND -> {
+ //create consequence "and" node
+ val children = ASTManipulator.flatten(node)
+ return AndConsequenceNode(
+ consequenceDescription.triggerManager,
+ temporalContext,
+ children.map { interpretShorthand(it) })
+ }
+ OperatorToken.OR -> {
+ //create consequence "or" node
+ val children = ASTManipulator.flatten(node)
+ return OrConsequenceNode(
+ consequenceDescription.triggerManager,
+ temporalContext,
+ children.map { interpretShorthand(it) })
+ }
+ OperatorToken.IFF -> {
+ //split <-> into <- and ->
+ //this only works in rare cases
+ return interpretBinaryOperation(
+ ASTManipulator.splitIFF(node),
+ temporalContext,
+ consequenceDescription
+ )
+ }
+ else -> {
+ throw IllegalArgumentException("Cannot interpret node of type ${node.javaClass.simpleName}")
+ }
+ }
+ }
+
+ }
+
+ private fun interpretUnaryOperation(
+ node: UnaryOperationASTNode,
+ temporalContext: TemporalOperatorInfo,
+ consequenceDescription: ConsequenceDescription
+ ): ConsequenceNode {
+ if (node.operator == OperatorToken.NOT) {
+ val child = node.child
+ if (child is ValueASTNode) {
+ val value = if (child.containsEventName()) child.eventName else child.value
+ return EventPreventionConsequenceNode(consequenceDescription.triggerManager, temporalContext, value)
+ } else if (child is UnaryOperationASTNode && child.operator == OperatorToken.NOT) {
+ return interpret(
+ ASTManipulator.removeDoubleNot(node),
+ temporalContext,
+ consequenceDescription
+ )
+ } else {
+ return interpret(
+ ASTManipulator.applyNot(node),
+ temporalContext,
+ consequenceDescription
+ )
+ }
+
+ } else {
+ throw IllegalArgumentException("Cannot interpret node of type ${node.javaClass.simpleName}")
+ }
+ }
+
+ private fun interpretValueNode(
+ node: ValueASTNode,
+ temporalContext: TemporalOperatorInfo,
+ consequenceDescription: ConsequenceDescription
+ ): ActivationConsequenceNode {
+ if (node.containsEventName()) {
+ return EventActivationConsequenceNode(
+ consequenceDescription.triggerManager,
+ temporalContext,
+ node.eventName
+ )
+ } else {
+ throw IllegalArgumentException("Cannot interpret node of type ${node.javaClass.simpleName}")
+ }
+ }
+
+ private fun interpretImplication(
+ root: BinaryOperationASTNode,
+ temporalContext: TemporalOperatorInfo,
+ consequenceDescription: ConsequenceDescription
+ ): ConsequenceNode {
+ if (root.operator != OperatorToken.IMPLIES) {
+ throw IllegalArgumentException("Expected operator ${OperatorToken.IMPLIES}, but found ${root.operator}")
+ }
+
+ val left = root.leftChild
+ val right = root.rightChild
+
+ val cause = CauseInterpreter().interpretMTLCause(left,temporalContext)
+ val consequence = ConsequenceInterpreter().interpretAsMTL(right, consequenceDescription.triggerManager)
+
+ consequenceDescription.triggerManager.eventActivationListeners.addAll(cause.listeners)
+
+
+ return ImplicationNode(
+ cause,
+ consequence,
+ temporalContext,
+ consequenceDescription.triggerManager,
+ )
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ISubscribableTriggerNotifier.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ISubscribableTriggerNotifier.kt
new file mode 100644
index 0000000..4aa9991
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ISubscribableTriggerNotifier.kt
@@ -0,0 +1,14 @@
+package cambio.tltea.interpreter.nodes
+
+import cambio.tltea.interpreter.nodes.consequence.ActivationData
+import java.util.function.Consumer
+
+interface ISubscribableTriggerNotifier {
+ fun subscribeEventListener(listener: Consumer>)
+ fun > subscribeEventListenerWithFilter(
+ listener: Consumer,
+ filter: Class
+ )
+
+ fun unsubscribe(listener: Consumer>)
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ITriggerListener.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ITriggerListener.java
similarity index 68%
rename from interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ITriggerListener.java
rename to interpreter/src/main/java/cambio/tltea/interpreter/nodes/ITriggerListener.java
index 3c0ad57..be42659 100644
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ITriggerListener.java
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ITriggerListener.java
@@ -1,4 +1,4 @@
-package cambio.tltea.interpreter.nodes.requirements;
+package cambio.tltea.interpreter.nodes;
@FunctionalInterface
public interface ITriggerListener {
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ImplicationNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ImplicationNode.kt
new file mode 100644
index 0000000..c08729b
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/ImplicationNode.kt
@@ -0,0 +1,45 @@
+package cambio.tltea.interpreter.nodes
+
+import cambio.tltea.interpreter.nodes.consequence.ConsequenceNode
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+/**
+ * @author Lion Wagner
+ */
+class ImplicationNode(
+ private val causeDescription: CauseDescription,
+ private val consequence: ConsequenceDescription,
+ temporalContext: TemporalOperatorInfo,
+ triggerManager: TriggerManager
+) : ConsequenceNode(triggerManager, temporalContext), StateChangeListener {
+
+ override fun activateConsequence() {
+ //activate listeners to input of cause
+ causeDescription.activateListeners()
+
+ //activate self listening to changes
+ causeDescription.causeChangePublisher.subscribe(this)
+ }
+
+ override fun deactivateConsequence() {
+ causeDescription.deactivateListeners()
+ causeDescription.causeChangePublisher.unsubscribe(this)
+ }
+
+ override fun onEvent(event: StateChangeEvent) {
+ //activate consequence if the new value carried by the event is "true"
+ if (event.newValue) {
+ consequence.activateConsequence()
+ }
+ else {
+ consequence.deactivateConsequence()
+ }
+ }
+
+ /**
+ * @return whether the cause expression is satisfied
+ */
+ val currentValue: Boolean
+ get() = causeDescription.causeASTRoot.currentValue
+
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangeEvent.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangeEvent.java
index afeb45d..a3b4620 100644
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangeEvent.java
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangeEvent.java
@@ -1,10 +1,13 @@
package cambio.tltea.interpreter.nodes;
+import cambio.tltea.parser.core.temporal.ITemporalValue;
+
/**
* @author Lion Wagner
*/
public record StateChangeEvent(StateChangedPublisher publisher,
- T newValue, T oldValue) {
+ T newValue, T oldValue, ITemporalValue when) {
+
public T getNewValue() {
return newValue;
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangeListener.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangeListener.java
new file mode 100644
index 0000000..1b86521
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangeListener.java
@@ -0,0 +1,8 @@
+package cambio.tltea.interpreter.nodes;
+
+import cambio.tltea.parser.core.temporal.ITemporalValue;
+
+@FunctionalInterface
+public interface StateChangeListener{
+ void onEvent(StateChangeEvent event);
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangedPublisher.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangedPublisher.java
index 83d4b28..7282229 100644
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangedPublisher.java
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/StateChangedPublisher.java
@@ -1,28 +1,24 @@
package cambio.tltea.interpreter.nodes;
-import cambio.tltea.interpreter.nodes.requirements.InteractionListener;
-
import java.util.Collection;
import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
/**
* @author Lion Wagner
*/
public abstract class StateChangedPublisher {
- protected final Collection> subscribers = new HashSet<>();
+ protected final Collection> subscribers = new HashSet<>();
- public final void subscribe(InteractionListener listener) {
+ public final void subscribe(StateChangeListener listener) {
subscribers.add(listener);
}
- public final void unsubscribe(InteractionListener listener) {
+ public final void unsubscribe(StateChangeListener listener) {
subscribers.remove(listener);
}
- protected final void notifySubscribers(StateChangeEvent event) {
- for (InteractionListener listener : subscribers) {
+ protected void notifySubscribers(StateChangeEvent event) {
+ for (StateChangeListener listener : subscribers) {
listener.onEvent(event);
}
}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/TriggerManager.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/TriggerManager.kt
new file mode 100644
index 0000000..6f5bddb
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/TriggerManager.kt
@@ -0,0 +1,43 @@
+package cambio.tltea.interpreter.nodes
+
+import cambio.tltea.interpreter.nodes.cause.EventActivationListener
+import cambio.tltea.interpreter.nodes.consequence.ActivationData
+import java.util.function.Consumer
+
+/**
+ * @author Lion Wagner
+ */
+class TriggerManager : ISubscribableTriggerNotifier {
+
+ internal val eventActivationListeners: MutableCollection = HashSet()
+
+ private val anySubscribers: MutableCollection>> = HashSet()
+
+ private val filteredSubscribers: HashMap, MutableSet>>> =
+ HashMap()
+
+ override fun subscribeEventListener(listener: Consumer>) {
+ anySubscribers.add(listener)
+ }
+
+ override fun > subscribeEventListenerWithFilter(
+ listener: Consumer,
+ filter: Class
+ ) {
+ if (!filteredSubscribers.containsKey(filter)) {
+ filteredSubscribers[filter] = HashSet()
+ }
+ filteredSubscribers[filter]!!.add(listener as Consumer>)
+ }
+
+ override fun unsubscribe(listener: Consumer>) {
+ anySubscribers.remove(listener)
+ filteredSubscribers.values.forEach { it.remove(listener) }
+ }
+
+ internal fun trigger(activationData: ActivationData<*>) {
+ anySubscribers.forEach { it.accept(activationData) }
+ filteredSubscribers[activationData.javaClass]?.forEach { it.accept(activationData) }
+ }
+
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/AndInteractionNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/AndCauseNode.java
similarity index 64%
rename from interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/AndInteractionNode.java
rename to interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/AndCauseNode.java
index b2225f6..fa0696e 100644
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/AndInteractionNode.java
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/AndCauseNode.java
@@ -1,7 +1,8 @@
-package cambio.tltea.interpreter.nodes.requirements;
+package cambio.tltea.interpreter.nodes.cause;
import cambio.tltea.interpreter.nodes.StateChangeEvent;
-import cambio.tltea.interpreter.nodes.StateChangedPublisher;
+import cambio.tltea.interpreter.nodes.StateChangeListener;
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -9,19 +10,20 @@
/**
* @author Lion Wagner
*/
-public class AndInteractionNode extends InteractionNode implements InteractionListener {
+public class AndCauseNode extends CauseNode implements StateChangeListener {
private boolean state = false;
- private final LinkedList> children = new LinkedList<>();
+ private final LinkedList children = new LinkedList<>();
private final AtomicInteger trueCount = new AtomicInteger(0);
- public AndInteractionNode(InteractionNode... children) {
- for (InteractionNode child : children) {
+ public AndCauseNode(TemporalOperatorInfo temporalContext, CauseNode... children) {
+ super(temporalContext);
+ for (CauseNode child : children) {
this.children.add(child);
child.subscribe(this);
- if (child.getValue()) {
+ if (child.getCurrentValue()) {
trueCount.incrementAndGet();
}
}
@@ -40,13 +42,13 @@ public void onEvent(StateChangeEvent event) {
state = trueCount.get() == this.children.size();
if (oldSate != state) {
- notifySubscribers(new StateChangeEvent<>(this, true, false));
+ notifySubscribers(new StateChangeEvent<>(this, true, false, event.when()));
}
}
}
@Override
- public Boolean getValue() {
+ public Boolean getCurrentValue() {
return state;
}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/CauseNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/CauseNode.java
new file mode 100644
index 0000000..f11d9cc
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/CauseNode.java
@@ -0,0 +1,45 @@
+package cambio.tltea.interpreter.nodes.cause;
+
+import cambio.tltea.interpreter.nodes.StateChangeEvent;
+import cambio.tltea.interpreter.nodes.StateChangedPublisher;
+import cambio.tltea.parser.core.temporal.*;
+
+/**
+ * @author Lion Wagner
+ */
+public abstract class CauseNode extends StateChangedPublisher {
+
+ private final TemporalOperatorInfo temporalContext;
+
+ public CauseNode(TemporalOperatorInfo temporalContext) {
+ this.temporalContext = temporalContext;
+ }
+
+ public abstract Boolean getCurrentValue();
+
+ protected final boolean satisfiesTemporalContext(ITemporalValue activationTime) {
+ if (temporalContext.temporalValueExpression() instanceof TemporalInterval interval) {
+ if (activationTime instanceof TemporalInterval activationInterval) {
+ return interval.contains(activationInterval);
+ } else if (activationTime instanceof TimeInstance timeInstance) {
+ return interval.contains(timeInstance.getTime());
+ }
+ } else if (temporalContext.temporalValueExpression() instanceof TimeInstance timeInstance) {
+ if (activationTime instanceof TimeInstance activationTimeInstance) {
+ return timeInstance.equals(activationTimeInstance);
+ }
+ } else if (temporalContext.temporalValueExpression() instanceof TemporalEventDescription temporalEventDescription) {
+ if (activationTime instanceof TemporalEventDescription activationTemporalEventDescription) {
+ return temporalEventDescription.equals(activationTemporalEventDescription);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void notifySubscribers(StateChangeEvent event) {
+ if (satisfiesTemporalContext(event.when())) {
+ super.notifySubscribers(event);
+ }
+ }
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ComparisonCauseNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ComparisonCauseNode.java
new file mode 100644
index 0000000..cc9e418
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ComparisonCauseNode.java
@@ -0,0 +1,108 @@
+package cambio.tltea.interpreter.nodes.cause;
+
+import cambio.tltea.interpreter.nodes.StateChangeEvent;
+import cambio.tltea.parser.core.OperatorToken;
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo;
+
+/**
+ * @author Lion Wagner
+ */
+public class ComparisonCauseNode extends CauseNode {
+ private final OperatorToken operator;
+ private final ValueProvider> left;
+ private final ValueProvider> right;
+
+ private boolean lastValue = false;
+
+ public ComparisonCauseNode(OperatorToken operator,
+ TemporalOperatorInfo temporalContext,
+ ValueProvider> left,
+ ValueProvider> right) {
+ super(temporalContext);
+ if (!OperatorToken.ComparisonOperatorTokens.contains(operator)) {
+ throw new IllegalArgumentException("Operator not supported as comparison: " + operator);
+ }
+ this.operator = operator;
+ this.left = left;
+ this.right = right;
+
+ this.left.subscribe(event -> {
+ var lastValue = this.lastValue;
+ super.notifySubscribers(new StateChangeEvent<>(this, getCurrentValue(), lastValue, event.when()));
+ });
+ this.right.subscribe(event -> {
+ var lastValue = this.lastValue;
+ super.notifySubscribers(new StateChangeEvent<>(this, getCurrentValue(), lastValue, event.when()));
+ });
+ }
+
+
+ //TODO: optimization: we always take the same path through this method therefore we may be able to optimize
+ @Override
+ public Boolean getCurrentValue() {
+ return lastValue = compareSides();
+ }
+
+ private boolean compareSides() {
+ var val1 = left.currentValue;
+ var val2 = right.currentValue;
+
+ if (val1 == null || val2 == null) {
+ return false;
+ }
+ if (val1 == val2) {
+ return operator == OperatorToken.EQ;
+ }
+ if (val1 instanceof String && val2 instanceof String) {
+ return switch (operator) {
+ case EQ -> val1.equals(val2);
+ case NEQ -> !val1.equals(val2);
+ default -> throw new IllegalStateException("Operator not supported for string comparison:" + operator);
+ };
+ } else if (val1 instanceof Number n1 && val2 instanceof Number n2) {
+ return switch (operator) {
+ case EQ -> val1.equals(val2);
+ case NEQ -> !val1.equals(val2);
+ case GT -> n1.doubleValue() > n2.doubleValue();
+ case GEQ -> n1.doubleValue() >= n2.doubleValue();
+ case LT -> n1.doubleValue() < n2.doubleValue();
+ case LEQ -> n1.doubleValue() <= n2.doubleValue();
+ default -> throw new IllegalStateException("Operator not supported as comparison: " + operator);
+ };
+ } else if (val1 instanceof Comparable || val2 instanceof Comparable) {
+ try {
+ int compareValue = 0;
+ if (val1 instanceof Comparable c1) {
+ try {
+ compareValue = c1.compareTo(val2);
+ } catch (Exception e) {
+ if (val2 instanceof Comparable c2) {
+ compareValue = -c2.compareTo(val1);//need to be reversed because we are comparing the other way around
+ }
+ }
+ } else {
+ compareValue = -((Comparable) val2).compareTo(val1);//need to be reversed because we are comparing the other way around
+ }
+
+ return switch (operator) {
+ case EQ -> compareValue == 0;
+ case NEQ -> compareValue != 0;
+ case GT -> compareValue > 0;
+ case GEQ -> compareValue >= 0;
+ case LT -> compareValue < 0;
+ case LEQ -> compareValue <= 0;
+ default -> throw new IllegalStateException("Operator not supported as comparison: " + operator);
+ };
+ } catch (ClassCastException ignored) {
+ }
+ }
+
+ throw new IllegalStateException("Value type could not be compared: " + val1.getClass() + " and " + val2.getClass());
+ }
+
+
+ public OperatorToken getOperator() {
+ return operator;
+ }
+
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ConstantValueProvider.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ConstantValueProvider.java
new file mode 100644
index 0000000..bdf2150
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ConstantValueProvider.java
@@ -0,0 +1,16 @@
+package cambio.tltea.interpreter.nodes.cause;
+
+/**
+ * @author Lion Wagner
+ */
+public class ConstantValueProvider extends ValueProvider {
+
+ public ConstantValueProvider(T value) {
+ super();
+ this.currentValue = value;
+ }
+
+ public ConstantValueProvider clone() {
+ return new ConstantValueProvider<>(currentValue);
+ }
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/EventActivationListener.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/EventActivationListener.java
new file mode 100644
index 0000000..31e4935
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/EventActivationListener.java
@@ -0,0 +1,46 @@
+package cambio.tltea.interpreter.nodes.cause;
+
+import cambio.tltea.parser.core.temporal.ITemporalValue;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Lion Wagner
+ */
+public final class EventActivationListener extends ValueProvider {
+
+ private final String eventName;
+
+ public EventActivationListener(String eventName) {
+ this.eventName = eventName;
+ }
+
+ public String getEventName() {
+ return eventName;
+ }
+
+ public boolean isActivated() {
+ return currentValue;
+ }
+
+
+ public void setActivated(ITemporalValue time) {
+ activate(time);
+ }
+
+ public void activate(ITemporalValue time) {
+ changeStateAndNotify(true, time);
+ }
+
+ public void reset(ITemporalValue time) {
+ deactivate(time);
+ }
+
+ public void deactivate(ITemporalValue time) {
+ changeStateAndNotify(false, time);
+ }
+
+ public @NotNull EventActivationListener clone() {
+ return new EventActivationListener(eventName);
+ }
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/NotCauseNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/NotCauseNode.java
new file mode 100644
index 0000000..838667e
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/NotCauseNode.java
@@ -0,0 +1,29 @@
+package cambio.tltea.interpreter.nodes.cause;
+
+import cambio.tltea.interpreter.nodes.StateChangeEvent;
+import cambio.tltea.interpreter.nodes.StateChangeListener;
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo;
+
+/**
+ * @author Lion Wagner
+ */
+public final class NotCauseNode extends CauseNode implements StateChangeListener {
+
+ private final CauseNode child;
+
+ public NotCauseNode(CauseNode child, TemporalOperatorInfo temporalContext) {
+ super(temporalContext);
+ this.child = child;
+ child.subscribe(this);
+ }
+
+ @Override
+ public Boolean getCurrentValue() {
+ return !child.getCurrentValue();
+ }
+
+ @Override
+ public void onEvent(StateChangeEvent event) {
+ this.notifySubscribers(new StateChangeEvent<>(this, !event.getNewValue(), !event.getOldValue(), event.when()));
+ }
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/OrInteractionNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/OrCauseNode.java
similarity index 65%
rename from interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/OrInteractionNode.java
rename to interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/OrCauseNode.java
index 9f3c5c5..e05c5ae 100644
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/OrInteractionNode.java
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/OrCauseNode.java
@@ -1,7 +1,8 @@
-package cambio.tltea.interpreter.nodes.requirements;
+package cambio.tltea.interpreter.nodes.cause;
import cambio.tltea.interpreter.nodes.StateChangeEvent;
-import cambio.tltea.interpreter.nodes.StateChangedPublisher;
+import cambio.tltea.interpreter.nodes.StateChangeListener;
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -9,20 +10,21 @@
/**
* @author Lion Wagner
*/
-public class OrInteractionNode extends InteractionNode implements InteractionListener {
+public class OrCauseNode extends CauseNode implements StateChangeListener {
private boolean state = false;
- private final LinkedList> children = new LinkedList<>();
+ private final LinkedList children = new LinkedList<>();
private final AtomicInteger trueCount = new AtomicInteger(0);
- public OrInteractionNode(InteractionNode... children) {
+ public OrCauseNode(TemporalOperatorInfo temporalContext, CauseNode... children) {
+ super(temporalContext);
for (var child : children) {
this.children.add(child);
child.subscribe(this);
- if (child.getValue()) {
+ if (child.getCurrentValue()) {
trueCount.incrementAndGet();
}
}
@@ -31,6 +33,7 @@ public OrInteractionNode(InteractionNode... children) {
@Override
public void onEvent(StateChangeEvent event) {
+
if (event.getNewValue() != event.getOldValue()) {
if (event.getNewValue()) {
trueCount.incrementAndGet();
@@ -41,13 +44,13 @@ public void onEvent(StateChangeEvent event) {
state = trueCount.get() > 0;
if (oldSate != state) {
- notifySubscribers(new StateChangeEvent<>(this, true, false));
+ notifySubscribers(new StateChangeEvent<>(this, true, false, event.when()));
}
}
}
@Override
- public Boolean getValue() {
+ public Boolean getCurrentValue() {
return state;
}
}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ValueListener.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ValueListener.java
new file mode 100644
index 0000000..8d46bd1
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ValueListener.java
@@ -0,0 +1,17 @@
+package cambio.tltea.interpreter.nodes.cause;
+
+import cambio.tltea.parser.core.temporal.ITemporalValue;
+
+/**
+ * @author Lion Wagner
+ */
+public class ValueListener extends ValueProvider {
+
+ public void updateValue(T value, ITemporalValue time) {
+ super.changeStateAndNotify(value, time);
+ }
+
+ public ValueListener clone() {
+ return new ValueListener<>();
+ }
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ValueProvider.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ValueProvider.java
new file mode 100644
index 0000000..dafbb80
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/cause/ValueProvider.java
@@ -0,0 +1,32 @@
+package cambio.tltea.interpreter.nodes.cause;
+
+import cambio.tltea.interpreter.nodes.StateChangeEvent;
+import cambio.tltea.interpreter.nodes.StateChangedPublisher;
+import cambio.tltea.parser.core.temporal.ITemporalValue;
+
+public abstract class ValueProvider extends StateChangedPublisher{
+ private boolean isListening;
+ protected T currentValue;
+
+ protected void changeStateAndNotify(T newValue, ITemporalValue time) {
+ if (!isListening) {
+ return;
+ }
+ this.currentValue = newValue;
+ subscribers.forEach(listener -> listener.onEvent(new StateChangeEvent<>(this,
+ newValue,
+ currentValue,
+ time)));
+ }
+
+
+ public void startListening() {
+ isListening = true;
+ }
+
+ public void stopListening() {
+ isListening = false;
+ }
+
+ public abstract ValueProvider clone();
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ActivationConsequenceNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ActivationConsequenceNode.kt
new file mode 100644
index 0000000..a2a72ba
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ActivationConsequenceNode.kt
@@ -0,0 +1,9 @@
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+abstract class ActivationConsequenceNode(
+ triggerManager: TriggerManager,
+ temporalContext: TemporalOperatorInfo
+) : ConsequenceNode(triggerManager, temporalContext)
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/AndConsequenceNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/AndConsequenceNode.kt
new file mode 100644
index 0000000..220047c
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/AndConsequenceNode.kt
@@ -0,0 +1,21 @@
+@file:JvmName("AndConsequence")
+
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+class AndConsequenceNode(
+ triggerManager: TriggerManager,
+ temporalContext: TemporalOperatorInfo,
+ children: Collection
+) :
+ ChildrenOwningConsequenceNode(triggerManager, temporalContext, children) {
+ override fun activateConsequence() {
+ children.forEach { it.activateConsequence() }
+ }
+
+ override fun deactivateConsequence() {
+ children.forEach { it.deactivateConsequence() }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ChildrenOwningConsequenceNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ChildrenOwningConsequenceNode.kt
new file mode 100644
index 0000000..0d5f6b2
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ChildrenOwningConsequenceNode.kt
@@ -0,0 +1,10 @@
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+abstract class ChildrenOwningConsequenceNode(
+ triggerManager: TriggerManager,
+ temporalContext: TemporalOperatorInfo,
+ protected val children: Collection
+) : ConsequenceNode(triggerManager, temporalContext)
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ConsequenceNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ConsequenceNode.kt
new file mode 100644
index 0000000..6c5be70
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ConsequenceNode.kt
@@ -0,0 +1,17 @@
+@file:JvmName("ConsequenceNode")
+
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+/**
+ * @author Lion Wagner
+ */
+abstract class ConsequenceNode(
+ protected val triggerManager: TriggerManager,
+ protected val temporalContext: TemporalOperatorInfo
+) {
+ abstract fun activateConsequence()
+ abstract fun deactivateConsequence()
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventActivationConsequenceNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventActivationConsequenceNode.kt
new file mode 100644
index 0000000..79aaacc
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventActivationConsequenceNode.kt
@@ -0,0 +1,18 @@
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+class EventActivationConsequenceNode(
+ triggerManager: TriggerManager,
+ temporalContext: TemporalOperatorInfo,
+ private val eventName: String,
+) : ActivationConsequenceNode(triggerManager, temporalContext) {
+ override fun activateConsequence() {
+ triggerManager.trigger(EventActivationData(eventName, temporalContext))
+ }
+
+ override fun deactivateConsequence() {
+ triggerManager.trigger(EventPreventionData(eventName, temporalContext))
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventActivationData.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventActivationData.kt
new file mode 100644
index 0000000..220e11b
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventActivationData.kt
@@ -0,0 +1,30 @@
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.parser.core.OperatorToken
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+abstract class ActivationData(val data: T, val temporalContext: TemporalOperatorInfo)
+
+class EventPreventionData(eventName: String, temporalContext: TemporalOperatorInfo) :
+ ActivationData(eventName, temporalContext)
+
+class EventActivationData(eventName: String, temporalContext: TemporalOperatorInfo) :
+ ActivationData(eventName, temporalContext)
+
+class ValueEventActivationData(
+ val targetProperty: String,
+ data: T,
+ val operator: OperatorToken,
+ val active: Boolean,
+ temporalContext: TemporalOperatorInfo
+) : ActivationData(
+ data,
+ temporalContext
+) {
+
+ init {
+ if (!OperatorToken.ComparisonOperatorTokens.contains(operator)) {
+ throw IllegalArgumentException("Operator must be a comparison operator")
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventPreventionConsequenceNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventPreventionConsequenceNode.kt
new file mode 100644
index 0000000..f2863a1
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/EventPreventionConsequenceNode.kt
@@ -0,0 +1,18 @@
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+class EventPreventionConsequenceNode(
+ triggerManager: TriggerManager,
+ temporalContext: TemporalOperatorInfo,
+ private val eventName: String,
+) : ActivationConsequenceNode(triggerManager, temporalContext) {
+ override fun activateConsequence() {
+ TODO("Not yet implemented")
+ }
+
+ override fun deactivateConsequence() {
+ TODO("Not yet implemented")
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/OrConsequenceNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/OrConsequenceNode.kt
new file mode 100644
index 0000000..0194865
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/OrConsequenceNode.kt
@@ -0,0 +1,18 @@
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+class OrConsequenceNode(
+ triggerManager: TriggerManager,
+ temporalContext: TemporalOperatorInfo,
+ children: List
+) : ChildrenOwningConsequenceNode(triggerManager, temporalContext, children) {
+ override fun activateConsequence() {
+ TODO("Not yet implemented")
+ }
+
+ override fun deactivateConsequence() {
+ TODO("Not yet implemented")
+ }
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ValueEventConsequenceNode.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ValueEventConsequenceNode.kt
new file mode 100644
index 0000000..8473c74
--- /dev/null
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/consequence/ValueEventConsequenceNode.kt
@@ -0,0 +1,26 @@
+package cambio.tltea.interpreter.nodes.consequence
+
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.parser.core.OperatorToken
+import cambio.tltea.parser.core.temporal.TemporalOperatorInfo
+
+class ValueEventConsequenceNode(
+ val targetProperty: String,
+ val targetValue: T,
+ val operator: OperatorToken,
+ triggerManager: TriggerManager,
+ temporalContext: TemporalOperatorInfo
+) : ActivationConsequenceNode(triggerManager, temporalContext) {
+
+ override fun activateConsequence() {
+ triggerManager.trigger(ValueEventActivationData(targetProperty, targetValue, operator, true, temporalContext))
+ }
+
+ override fun deactivateConsequence() {
+ triggerManager.trigger(ValueEventActivationData(targetProperty, targetValue, operator, false, temporalContext))
+ }
+
+ fun getType(): Class {
+ return targetValue::class.javaObjectType
+ }
+}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ActivatableEvent.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ActivatableEvent.java
deleted file mode 100644
index 89b9340..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ActivatableEvent.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-
-import cambio.tltea.interpreter.nodes.StateChangeEvent;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * @author Lion Wagner
- */
-public final class ActivatableEvent extends IValueDescription implements InteractionListener {
-
- private final EventActivationListener listener;
-
- public ActivatableEvent(String eventName) {
- listener = new EventActivationListener(eventName);
- listener.subscribe(this);
- }
-
- public EventActivationListener getEventListener() {
- return listener;
- }
-
- @Override
- public @NotNull Boolean getValue() {
- return listener.isActivated();
- }
-
- @Override
- public void onEvent(StateChangeEvent event) {
- notifySubscribers(new StateChangeEvent<>(this, event.getNewValue(), event.getOldValue()));
- }
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ComparisonInteractionNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ComparisonInteractionNode.java
deleted file mode 100644
index 2e40e28..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ComparisonInteractionNode.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-import cambio.tltea.parser.core.OperatorToken;
-
-/**
- * @author Lion Wagner
- */
-public class ComparisonInteractionNode extends InteractionNode {
- private final OperatorToken operator;
- private final IValueDescription> left;
- private final IValueDescription> right;
-
- public ComparisonInteractionNode(OperatorToken operator,
- IValueDescription> left,
- IValueDescription> right) {
- this.operator = operator;
- this.left = left;
- this.right = right;
- }
-
-
- @Override
- public Boolean getValue() {
- var val1 = left.getValue();
- var val2 = right.getValue();
-
- if (val1 == null || val2 == null) {
- return false;
- }
- if (val1 instanceof String && val2 instanceof String) {
- return switch (operator) {
- case EQ -> val1.equals(val2);
- case NEQ -> !val1.equals(val2);
- default -> throw new IllegalArgumentException("Operator not supported for string comparison:" + operator);
- };
- } else if (val1 instanceof Number n1 && val2 instanceof Number n2) {
- return switch (operator) {
- case EQ -> val1.equals(val2);
- case NEQ -> !val1.equals(val2);
- case GT -> n1.doubleValue() > n2.doubleValue();
- case GEQ -> n1.doubleValue() >= n2.doubleValue();
- case LT -> n1.doubleValue() < n2.doubleValue();
- case LEQ -> n1.doubleValue() <= n2.doubleValue();
- default -> throw new IllegalArgumentException("Operator not supported as comparison: " + operator);
- };
- }
- else throw new IllegalArgumentException("Value type could not be compared: " + val1.getClass() + " and " + val2.getClass());
- }
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/EventActivationListener.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/EventActivationListener.java
deleted file mode 100644
index dd2361d..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/EventActivationListener.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-import cambio.tltea.interpreter.nodes.StateChangeEvent;
-import cambio.tltea.interpreter.nodes.StateChangedPublisher;
-
-/**
- * @author Lion Wagner
- */
-public final class EventActivationListener extends StateChangedPublisher {
-
- private final String eventName;
-
- private boolean activated = false;
-
- public EventActivationListener(String eventName) {
- this.eventName = eventName;
- }
-
- public String getEventName() {
- return eventName;
- }
-
- public boolean isActivated() {
- return activated;
- }
-
-
- public void setActivated() {
- activate();
- }
-
- public void activate() {
- notifyAndChangeState(true);
- }
-
- public void reset() {
- deactivate();
- }
-
- public void deactivate() {
- notifyAndChangeState(false);
- }
-
- private void notifyAndChangeState(boolean activated) {
- subscribers.forEach(listener -> listener.onEvent(new StateChangeEvent<>(this, activated, this.activated)));
- this.activated = activated;
- }
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/FixedValueDescription.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/FixedValueDescription.java
deleted file mode 100644
index 5022f1a..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/FixedValueDescription.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-/**
- * @author Lion Wagner
- */
-public class FixedValueDescription extends IValueDescription {
-
- protected final T value;
-
- public FixedValueDescription(T value) {
- this.value = value;
- }
-
- @Override
- public T getValue() {
- return value;
- }
-
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/IValueDescription.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/IValueDescription.java
deleted file mode 100644
index d194969..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/IValueDescription.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-public abstract class IValueDescription extends InteractionNode {
- public abstract T getValue();
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ImplicationNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ImplicationNode.java
deleted file mode 100644
index 07d0428..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/ImplicationNode.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-import cambio.tltea.interpreter.nodes.StateChangeEvent;
-
-/**
- * @author Lion Wagner
- */
-public class ImplicationNode extends InteractionNode implements InteractionListener {
-
- private final InteractionNode requirement;
- private final String consequence;
- private final TriggerNotifier notifier;
-
- public ImplicationNode(InteractionNode requirement, String consequence, TriggerNotifier notifier) {
- this.requirement = requirement;
- this.consequence = consequence;
- this.notifier = notifier;
-
- requirement.subscribe(this);
- }
-
- @Override
- public void onEvent(StateChangeEvent event) {
- if (event.getNewValue()) {
- notifier.trigger(consequence);
- }
- }
-
- /**
- * @return whether the requirement is satisfied
- */
- @Override
- public Boolean getValue() {
- return requirement.getValue();
- }
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/InteractionListener.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/InteractionListener.java
deleted file mode 100644
index f097fc0..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/InteractionListener.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-import cambio.tltea.interpreter.nodes.StateChangeEvent;
-
-@FunctionalInterface
-public interface InteractionListener{
- void onEvent(StateChangeEvent event);
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/InteractionNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/InteractionNode.java
deleted file mode 100644
index 7c07bd4..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/InteractionNode.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-import cambio.tltea.interpreter.nodes.StateChangedPublisher;
-
-/**
- * @author Lion Wagner
- */
-public abstract class InteractionNode extends StateChangedPublisher {
- public abstract T getValue();
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/NotInteractionNode.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/NotInteractionNode.java
deleted file mode 100644
index 545c8d2..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/NotInteractionNode.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-import cambio.tltea.interpreter.nodes.StateChangeEvent;
-
-/**
- * @author Lion Wagner
- */
-public final class NotInteractionNode extends InteractionNode implements InteractionListener {
-
- private final InteractionNode child;
-
- public NotInteractionNode(InteractionNode child) {
- this.child = child;
- child.subscribe(this);
- }
-
- @Override
- public Boolean getValue() {
- return !child.getValue();
- }
-
- @Override
- public void onEvent(StateChangeEvent event) {
- this.notifySubscribers(new StateChangeEvent<>(this, !event.getNewValue(), !event.getOldValue()));
- }
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/TriggerNotifier.java b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/TriggerNotifier.java
deleted file mode 100644
index 8366f83..0000000
--- a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/requirements/TriggerNotifier.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package cambio.tltea.interpreter.nodes.requirements;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-/**
- * @author Lion Wagner
- */
-public class TriggerNotifier {
-
- protected final Collection subscribers = new HashSet<>();
-
- public final void subscribe(ITriggerListener listener) {
- subscribers.add(listener);
- }
-
- public final void unsubscribe(ITriggerListener listener) {
- subscribers.remove(listener);
- }
-
- void trigger(String eventName, Object... args) {
- for (ITriggerListener listener : subscribers) {
- listener.onTrigger(eventName, args);
- }
- }
-}
diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/utils/ASTManipulator.java b/interpreter/src/main/java/cambio/tltea/interpreter/utils/ASTManipulator.java
index 6a56190..56cef81 100644
--- a/interpreter/src/main/java/cambio/tltea/interpreter/utils/ASTManipulator.java
+++ b/interpreter/src/main/java/cambio/tltea/interpreter/utils/ASTManipulator.java
@@ -1,8 +1,14 @@
package cambio.tltea.interpreter.utils;
import cambio.tltea.parser.core.*;
+import cambio.tltea.parser.core.temporal.TemporalUnaryOperationASTNode;
+import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
/**
* @author Lion Wagner
*/
@@ -49,45 +55,159 @@ public final class ASTManipulator {
/**
- * Removes double negation from an AST.
- * Accepts both parent and child negation nodes as input.
- * E.g. morphs (a) && !!(b) into (a) && (b), if given the first or second NOT node as input.
- *
+ * Removes double negation from an AST. Accepts both parent and child negation nodes as input. E.g. morphs (a) &&
+ * !!(b) into (a) && (b), if given the first or second NOT node as input.
+ *
* It is recommended to always use the parent as input to ensure a correct interpretation.
*
- * @param unNode Negation node that should be removed.
+ * @param doubleNotParent Negation node that should be removed.
* @return The node that was double negated.
*/
- public static @NotNull ASTNode removeDoubleNot(@NotNull UnaryOperationASTNode unNode) {
- if (unNode.getOperator() != OperatorToken.NOT) {
+ public static @NotNull OperatorASTNode removeDoubleNot(@NotNull UnaryOperationASTNode doubleNotParent) {
+ if (doubleNotParent.getOperator() != OperatorToken.NOT) {
throw new IllegalArgumentException("Expected a NOT node.");
}
- ASTNode targetChild = null;
- ASTNode targetParent = null;
- if (unNode.getParent() instanceof UnaryOperationASTNode unParent &&
- unParent.getOperator() == OperatorToken.NOT) {
- targetChild = unNode.getChild();
- targetParent = unParent.getParent();
- } else if (unNode.getChild() instanceof UnaryOperationASTNode unChild &&
- unChild.getOperator() == OperatorToken.NOT) {
- targetChild = unChild.getChild();
- targetParent = unNode.getParent();
+ if (doubleNotParent.getChild() instanceof UnaryOperationASTNode doubleNotChild && doubleNotChild.getOperator() == OperatorToken.NOT) {
+ redirectParent(doubleNotParent.getParent(),
+ doubleNotParent,
+ ((UnaryOperationASTNode) doubleNotParent.getChild()).getChild());
+ return doubleNotParent.getParent();
+ }
+ return doubleNotParent;
+ }
+
+ public static TemporalUnaryOperationASTNode insertImplicitGloballyRoot(ASTNode root) {
+ if (root instanceof TemporalUnaryOperationASTNode temporalNode) {
+ return temporalNode;
+ }
+
+ var newNode = new TemporalUnaryOperationASTNode(OperatorToken.GLOBALLY, root);
+ insertBefore(root, newNode);
+ return newNode;
+ }
+
+ public static TemporalUnaryOperationASTNode insertImplicitFinallyRoot(ASTNode root) {
+ if (root instanceof TemporalUnaryOperationASTNode temporalNode) {
+ return temporalNode;
+ }
+
+ var newNode = new TemporalUnaryOperationASTNode(OperatorToken.FINALLY, root);
+ insertBefore(root, newNode);
+ return newNode;
+ }
+
+
+ public static ASTNode applyNot(@NotNull UnaryOperationASTNode notNode) {
+ if (notNode.getOperator() != OperatorToken.NOT) {
+ throw new IllegalArgumentException("Expected a NOT node.");
}
- if (targetChild == null) {
- throw new IllegalStateException("Expected a double NOT node. Or remaining child was empty.");
+ if (notNode.getChild() instanceof BinaryOperationASTNode binaryNode) {
+ if (OperatorToken.ComparisonOperatorTokens.contains(binaryNode.getOperator())) {
+ return applyNotToComparison(binaryNode);
+ }
+
+ return switch (binaryNode.getOperator()) {
+ case AND -> applyNotToAnd(binaryNode);
+ case OR -> applyNotToOr(binaryNode);
+ default -> throw new IllegalArgumentException("Operator not supported.");
+ };
+ } else if (notNode.getChild() instanceof UnaryOperationASTNode unaryChild &&
+ unaryChild.getOperator() == OperatorToken.NOT) {
+ return removeDoubleNot(notNode);
}
+ throw new IllegalArgumentException("Cannot apply NOT (yet?) to " + notNode.getChild()
+ .getClass()
+ .getSimpleName() + " node.");
+ }
+
+ @Contract("_ -> new")
+ private static @NotNull OperatorASTNode applyNotToOr(@NotNull BinaryOperationASTNode binaryNode) {
+ return new BinaryOperationASTNode(OperatorToken.AND,
+ new UnaryOperationASTNode(OperatorToken.NOT, binaryNode.getLeftChild()),
+ new UnaryOperationASTNode(OperatorToken.NOT, binaryNode.getRightChild()));
+ }
+
+ @Contract("_ -> new")
+ private static @NotNull OperatorASTNode applyNotToAnd(@NotNull BinaryOperationASTNode binaryNode) {
+ return new BinaryOperationASTNode(OperatorToken.OR,
+ new UnaryOperationASTNode(OperatorToken.NOT, binaryNode.getLeftChild()),
+ new UnaryOperationASTNode(OperatorToken.NOT, binaryNode.getRightChild()));
+ }
- if (targetParent instanceof BinaryOperationASTNode binParent) {
- if (binParent.getLeftChild() == unNode) {
- binParent.setLeftChild(targetChild);
+
+ /**
+ * Inserts the insertedNode before the currentNode.
+ *
+ * @param currentNode
+ * @param insertedNode
+ */
+ private static void insertBefore(ASTNode currentNode, UnaryOperationASTNode insertedNode) {
+ redirectParent(currentNode.getParent(), currentNode, insertedNode);
+ }
+
+ /**
+ * Replaces the given oldChild with the newChild. Does handle {@link UnaryOperationASTNode}s and {@link
+ * BinaryOperationASTNode}s.
+ *
+ * This does not replace the relation that the oldChild potentially has to its children!
+ *
+ * @param parent The parent of the oldChild.
+ * @param oldChild The child that should be replaced.
+ * @param newChild The new child that should replace the oldChild.
+ */
+ public static void redirectParent(@NotNull OperatorASTNode parent,
+ @NotNull ASTNode oldChild,
+ @NotNull ASTNode newChild) {
+ if (parent instanceof UnaryOperationASTNode unParent) {
+ if (unParent.getChild() == oldChild) {
+ unParent.setChild(newChild);
+ } else {
+ throw new IllegalStateException("Expected oldChild to be a child of parent.");
+ }
+ } else if (parent instanceof BinaryOperationASTNode binParent) {
+ if (binParent.getLeftChild() == oldChild) {
+ binParent.setLeftChild(newChild);
+ } else if (binParent.getRightChild() == oldChild) {
+ binParent.setRightChild(newChild);
} else {
- binParent.setRightChild(targetChild);
+ throw new IllegalStateException("Expected oldChild to be a child of parent.");
}
- } else if (targetParent instanceof UnaryOperationASTNode unParent) {
- unParent.setChild(targetChild);
+ } else {
+ throw new IllegalStateException("Unexpected parent type: " + parent.getClass().getSimpleName());
+ }
+ }
+
+ private static BinaryOperationASTNode applyNotToComparison(@NotNull BinaryOperationASTNode comparisonNode) {
+ OperatorToken reversedOperator = comparisonNode.getOperator().invert();
+ UnaryOperationASTNode notParent = (UnaryOperationASTNode) comparisonNode.getParent();
+ BinaryOperationASTNode replacer = new BinaryOperationASTNode(reversedOperator,
+ comparisonNode.getLeftChild(),
+ comparisonNode.getRightChild());
+ redirectParent(notParent.getParent(), notParent, replacer);
+ return replacer;
+ }
+
+ public static @NotNull Collection flatten(@NotNull BinaryOperationASTNode node) {
+ OperatorToken op = node.getOperator();
+ if (op != OperatorToken.AND && op != OperatorToken.OR) {
+ throw new IllegalArgumentException("Expected AND or OR operator, but got " + op);
+ }
+
+ Collection result = new HashSet<>();
+
+ ASTNode leftChild = node.getLeftChild();
+ ASTNode rightChild = node.getRightChild();
+
+ result.add(leftChild);
+ result.add(rightChild);
+ if (leftChild instanceof BinaryOperationASTNode binLeft && binLeft.getOperator() == op) {
+ result.addAll(flatten(binLeft));
+ }
+ if (rightChild instanceof BinaryOperationASTNode binRight && binRight.getOperator() == op) {
+ result.addAll(flatten(binRight));
}
- return targetChild;
+ return result;
}
}
diff --git a/interpreter/src/test/java/cambio/tltea/interpreter/BehaviorInterpreterTest.kt b/interpreter/src/test/java/cambio/tltea/interpreter/BehaviorInterpreterTest.kt
new file mode 100644
index 0000000..a400dd4
--- /dev/null
+++ b/interpreter/src/test/java/cambio/tltea/interpreter/BehaviorInterpreterTest.kt
@@ -0,0 +1,32 @@
+package cambio.tltea.interpreter
+
+import cambio.tltea.interpreter.Interpreter.interpretAsBehavior
+import cambio.tltea.parser.core.temporal.TimeInstance
+import cambio.tltea.parser.mtl.generated.MTLParser
+import cambio.tltea.parser.mtl.generated.ParseException
+import org.junit.jupiter.api.Test
+
+internal class BehaviorInterpreterTest {
+ @Test
+ @Throws(ParseException::class)
+ fun debugTest() {
+ val input = "((P)&(!!(C))|!(D))->((Q)==1234)"
+ val ast = MTLParser(input).parse()
+ val result = interpretAsBehavior(ast)
+ result.activateProcessing();
+
+
+ result.triggerManager.subscribeEventListener {
+ println("hi $it")
+ }
+
+ result.triggerManager.eventActivationListeners
+ .filter { !it.eventName.contains("F") }
+ .forEach { it.activate(TimeInstance(0.0)) }
+ result.triggerManager.eventActivationListeners
+ .filter { it.eventName.contains("F") }
+ .forEach { it.activate(TimeInstance(0.0)) }
+
+ println(result)
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/test/java/cambio/tltea/interpreter/InterpreterTest.kt b/interpreter/src/test/java/cambio/tltea/interpreter/InterpreterTest.kt
new file mode 100644
index 0000000..98405c8
--- /dev/null
+++ b/interpreter/src/test/java/cambio/tltea/interpreter/InterpreterTest.kt
@@ -0,0 +1,68 @@
+package cambio.tltea.interpreter
+
+import cambio.tltea.interpreter.testutils.TestBase
+import cambio.tltea.parser.core.temporal.TemporalEventDescription
+import cambio.tltea.parser.core.temporal.TimeInstance
+import cambio.tltea.parser.mtl.generated.ParseException
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Test
+
+internal class InterpreterTest : TestBase() {
+
+ @Test
+ fun testFailOnEmptyString() {
+ assertThrows(ParseException::class.java) {
+ val res = interpretFormula("")
+ }
+ assertThrows(ParseException::class.java) {
+ interpretFormula(" ")
+ }
+ }
+
+
+ @Test
+ fun timeInstanceInIntervalTest() {
+ interpretFormula("G[5,10]((A)->(B))")
+
+ activateEvent("A", TimeInstance(4.0))
+ activateEvent("A", TimeInstance(5.0))
+ activateEvent("A", TimeInstance(7.5))
+ activateEvent("A", TimeInstance(10.0))
+ activateEvent("A", TimeInstance(11.0))
+
+ assertLogSizes(3,0,0)
+ }
+
+
+ @Test
+ fun eventIntervalTest() {
+ interpretFormula("G[A]((A)->(B))")
+ getEventListeners("A")?.activate(TemporalEventDescription("A"))
+ getEventListeners("A")?.activate(TemporalEventDescription("B"))
+
+ assertLogSizes(1,0,0)
+ }
+
+ @Test
+ fun exactTimeInstanceTest() {
+ interpretFormula("G[1]((A)->(B))")
+ activateEvent("A", TimeInstance(0))
+ activateEvent("A", TimeInstance(1))
+ activateEvent("A", TimeInstance(2))
+
+ assertLogSizes(1,0,0)
+ }
+
+ @Test
+ fun complexScenario1() {
+ interpretFormula("(A)->((B)->(C))")
+ activateEvent("A", TimeInstance(0))
+ assertLogSizes(0,0,0)
+ activateEvent("B", TimeInstance(0))
+ assertLogSizes(1,0,0)
+ deactivateEvent("A", TimeInstance(0))
+ activateEvent("B", TimeInstance(0))
+ assertLogSizes(1,0,0)
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/test/java/cambio/tltea/interpreter/nodes/ConsequenceInterpreterTest.kt b/interpreter/src/test/java/cambio/tltea/interpreter/nodes/ConsequenceInterpreterTest.kt
new file mode 100644
index 0000000..e736ed8
--- /dev/null
+++ b/interpreter/src/test/java/cambio/tltea/interpreter/nodes/ConsequenceInterpreterTest.kt
@@ -0,0 +1,19 @@
+package cambio.tltea.interpreter.nodes
+
+import cambio.tltea.interpreter.nodes.consequence.ValueEventActivationData
+import cambio.tltea.parser.mtl.generated.MTLParser
+import org.junit.jupiter.api.Test
+
+import org.junit.jupiter.api.Assertions.*
+
+internal class ConsequenceInterpreterTest {
+
+ @Test
+ fun interpretSimpleImplication() {
+ val formula = "(a) -> (b)"
+ val ast = MTLParser.parse(formula)
+ val interpretationResult = ConsequenceInterpreter().interpretAsMTL(ast)
+
+ println(interpretationResult.consequenceAST)
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/test/java/cambio/tltea/interpreter/nodes/cause/ComparisonCauseNodeTest.java b/interpreter/src/test/java/cambio/tltea/interpreter/nodes/cause/ComparisonCauseNodeTest.java
new file mode 100644
index 0000000..e19c86c
--- /dev/null
+++ b/interpreter/src/test/java/cambio/tltea/interpreter/nodes/cause/ComparisonCauseNodeTest.java
@@ -0,0 +1,233 @@
+package cambio.tltea.interpreter.nodes.cause;
+
+import cambio.tltea.parser.core.OperatorToken;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Random;
+import java.util.function.BiConsumer;
+
+class ComparisonCauseNodeTest {
+
+
+ private static void testComparison(boolean expected,
+ ValueProvider> value1,
+ ValueProvider> value2,
+ OperatorToken op) {
+ ComparisonCauseNode node = new ComparisonCauseNode(op, null, value1, value2);
+ Assertions.assertEquals(expected, node.getCurrentValue());
+ }
+
+ private static void testThrowsException(ValueProvider> value1, ValueProvider> value2, OperatorToken op) {
+ try {
+ new ComparisonCauseNode(op, null, value1, value2).getCurrentValue();
+ } catch (IllegalStateException | IllegalArgumentException e) {
+ return;
+ }
+ Assertions.fail("Expected IllegalStateException or IllegalArgumentException");
+ }
+
+ private static void testEQNEQ(ConstantValueProvider> value,
+ ConstantValueProvider> differentValue) {
+ testComparison(true, value, value, OperatorToken.EQ); //identity
+ testComparison(false, value, value, OperatorToken.NEQ); //inverted identity
+ testComparison(true, value, value.clone(), OperatorToken.EQ); //equality
+ testComparison(false, value, value.clone(), OperatorToken.NEQ); //inverted equality
+ testComparison(false, value, differentValue, OperatorToken.EQ); //difference
+ testComparison(true, value, differentValue, OperatorToken.NEQ); //inverted difference
+ }
+
+
+ @Test
+ void stringComparisonTest() {
+ ConstantValueProvider value = new ConstantValueProvider<>("a");
+ ConstantValueProvider other = new ConstantValueProvider<>("B");
+ testEQNEQ(value, other);
+ testThrowsException(value, other, OperatorToken.GT);
+ testThrowsException(value, other, OperatorToken.GEQ);
+ testThrowsException(value, other, OperatorToken.LT);
+ testThrowsException(value, other, OperatorToken.LEQ);
+
+ }
+
+
+ private static void testComparisonOperators(ValueProvider smallProvider,
+ ValueProvider largeProvider) {
+ testComparison(true, smallProvider, smallProvider, OperatorToken.EQ);
+ testComparison(true, smallProvider, smallProvider.clone(), OperatorToken.EQ);
+ testComparison(false, smallProvider, smallProvider, OperatorToken.NEQ);
+ testComparison(false, smallProvider, smallProvider.clone(), OperatorToken.NEQ);
+ testComparison(true, smallProvider, largeProvider, OperatorToken.LT);
+ testComparison(false, smallProvider, largeProvider, OperatorToken.LT.invert());
+ testComparison(true, smallProvider, largeProvider, OperatorToken.LEQ);
+ testComparison(false, smallProvider, largeProvider, OperatorToken.LEQ.invert());
+ testComparison(false, smallProvider, largeProvider, OperatorToken.GT);
+ testComparison(true, smallProvider, largeProvider, OperatorToken.GT.invert());
+ testComparison(false, smallProvider, largeProvider, OperatorToken.GEQ);
+ testComparison(true, smallProvider, largeProvider, OperatorToken.GEQ.invert());
+ }
+
+ @Test
+ void numberComparisonTest() {
+
+ Random rng = new Random();
+
+ BiConsumer constantNumberProviderTest = (smaller, larger) -> {
+ ConstantValueProvider smallProvider = new ConstantValueProvider<>(smaller);
+ ConstantValueProvider largeProvider = new ConstantValueProvider<>(larger);
+
+ testEQNEQ(smallProvider, largeProvider);
+
+ testComparisonOperators(smallProvider, largeProvider);
+
+ Arrays.stream(OperatorToken.values())
+ .filter(op -> !OperatorToken.ComparisonOperatorTokens.contains(op))
+ .forEach(op -> testThrowsException(smallProvider, largeProvider, op));
+ };
+
+
+ for (int i = 0; i < 1000; i++) {
+ int other, value = rng.nextInt();
+
+ do {
+ other = rng.nextInt();
+ } while (other >= value);
+
+ constantNumberProviderTest.accept(other, value);
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ double other, value = Integer.MAX_VALUE * rng.nextDouble();
+
+ do {
+ other = Integer.MAX_VALUE * rng.nextDouble();
+ } while (other >= value);
+
+ constantNumberProviderTest.accept(other, value);
+ }
+ }
+
+ abstract static class Parent implements Comparable> {
+ protected final T value;
+
+ @Override
+ public abstract int compareTo(@NotNull ComparisonCauseNodeTest.Parent o);
+
+ public Parent(T value) {
+ this.value = value;
+ }
+
+ }
+
+ static class Child1 extends Parent {
+
+ public Child1(T value) {
+ super(value);
+ }
+
+ @Override
+ public int compareTo(@NotNull ComparisonCauseNodeTest.Parent o) {
+ return value == o.value ? 0 : value.hashCode() - o.value.hashCode();
+ }
+ }
+
+ static class Child2 extends Parent {
+
+ public Child2(T value) {
+ super(value);
+ }
+
+ @Override
+ public int compareTo(@NotNull ComparisonCauseNodeTest.Parent o) {
+ return value == o.value ? 0 : value.hashCode() - o.value.hashCode();
+ }
+ }
+
+
+ static class IntComparable implements Comparable{
+
+ private final int value;
+
+ public IntComparable(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public int compareTo(@NotNull Integer o) {
+ return value == o ? 0 : value - o;
+ }
+ }
+
+ static class NotComparable {
+ public final int value;
+ NotComparable(int value) {
+ this.value = value;
+ }
+ }
+ static class NotComparableComparable implements Comparable{
+ public final int value;
+ NotComparableComparable(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public int compareTo(@NotNull ComparisonCauseNodeTest.NotComparable o) {
+ return value == o.value ? 0 : value - o.value;
+ }
+ }
+
+ @Test
+ void objectComparisonTest() {
+ Parent parent1 = new Child1<>(1);
+ Parent parent2 = new Child2<>(2);
+
+ var smallProvider = new ConstantValueProvider<>(parent1);
+ var largeProvider = new ConstantValueProvider<>(parent2);
+
+ testComparison(true, smallProvider, smallProvider, OperatorToken.EQ);
+ testComparison(true, smallProvider, smallProvider.clone(), OperatorToken.EQ);
+ testComparison(false, smallProvider, smallProvider, OperatorToken.NEQ);
+ testComparison(false, smallProvider, smallProvider.clone(), OperatorToken.NEQ);
+ testComparison(true, smallProvider, largeProvider, OperatorToken.LT);
+ testComparison(false, smallProvider, largeProvider, OperatorToken.LT.invert());
+ testComparison(true, smallProvider, largeProvider, OperatorToken.LEQ);
+ testComparison(false, smallProvider, largeProvider, OperatorToken.LEQ.invert());
+ testComparison(false, smallProvider, largeProvider, OperatorToken.GT);
+ testComparison(true, smallProvider, largeProvider, OperatorToken.GT.invert());
+ testComparison(false, smallProvider, largeProvider, OperatorToken.GEQ);
+ testComparison(true, smallProvider, largeProvider, OperatorToken.GEQ.invert());
+
+ }
+
+ @Test
+ void oneWayObjectComparisonTest(){
+
+ var smallProvider2 = new ConstantValueProvider<>(1);
+ var largeProvider2 = new ConstantValueProvider<>(new IntComparable(2));
+
+ testComparison(true, smallProvider2, smallProvider2, OperatorToken.EQ);
+ testComparison(true, smallProvider2, smallProvider2.clone(), OperatorToken.EQ);
+ testComparison(false, smallProvider2, smallProvider2, OperatorToken.NEQ);
+ testComparison(false, smallProvider2, smallProvider2.clone(), OperatorToken.NEQ);
+ testComparison(true, smallProvider2, largeProvider2, OperatorToken.LT);
+ testComparison(false, smallProvider2, largeProvider2, OperatorToken.LT.invert());
+ testComparison(true, smallProvider2, largeProvider2, OperatorToken.LEQ);
+ testComparison(false, smallProvider2, largeProvider2, OperatorToken.LEQ.invert());
+ testComparison(false, smallProvider2, largeProvider2, OperatorToken.GT);
+ testComparison(true, smallProvider2, largeProvider2, OperatorToken.GT.invert());
+ testComparison(false, smallProvider2, largeProvider2, OperatorToken.GEQ);
+ testComparison(true, smallProvider2, largeProvider2, OperatorToken.GEQ.invert());
+
+
+
+ var notComparable = new ConstantValueProvider<>(new NotComparable(1));
+ var notComparableComparable = new ConstantValueProvider<>(new NotComparableComparable(1));
+
+ testComparison(true, notComparable, notComparableComparable, OperatorToken.EQ);
+ testComparison(true, notComparable, notComparableComparable.clone(), OperatorToken.EQ);
+ testComparison(false, notComparable, notComparableComparable, OperatorToken.NEQ);
+ testComparison(false, notComparable, notComparableComparable.clone(), OperatorToken.NEQ);
+ }
+}
\ No newline at end of file
diff --git a/interpreter/src/test/java/cambio/tltea/interpreter/testutils/TestBase.kt b/interpreter/src/test/java/cambio/tltea/interpreter/testutils/TestBase.kt
new file mode 100644
index 0000000..4f26277
--- /dev/null
+++ b/interpreter/src/test/java/cambio/tltea/interpreter/testutils/TestBase.kt
@@ -0,0 +1,70 @@
+package cambio.tltea.interpreter.testutils
+
+import cambio.tltea.interpreter.BehaviorInterpretationResult
+import cambio.tltea.interpreter.Interpreter
+import cambio.tltea.interpreter.nodes.TriggerManager
+import cambio.tltea.interpreter.nodes.cause.EventActivationListener
+import cambio.tltea.interpreter.nodes.consequence.EventActivationData
+import cambio.tltea.interpreter.nodes.consequence.EventPreventionData
+import cambio.tltea.interpreter.nodes.consequence.ValueEventActivationData
+import cambio.tltea.parser.core.temporal.ITemporalValue
+import cambio.tltea.parser.mtl.generated.MTLParser
+import org.junit.jupiter.api.Assertions.assertEquals
+
+open class TestBase {
+
+ protected lateinit var currentInterpretationResult: BehaviorInterpretationResult
+ protected lateinit var triggerManager: TriggerManager
+ protected lateinit var generalActivationLog: MutableList
+ protected lateinit var eventActivationLog: MutableList
+ protected lateinit var eventPreventionLog: MutableList
+ protected lateinit var valueEventActivationLog: MutableList
+
+
+ protected fun interpretFormula(formula: String): BehaviorInterpretationResult {
+ val interpretAsBehavior = Interpreter.interpretAsBehavior(MTLParser.parse(formula))
+
+ currentInterpretationResult = interpretAsBehavior
+ triggerManager = interpretAsBehavior.triggerManager
+ generalActivationLog = mutableListOf()
+ eventActivationLog = mutableListOf()
+ eventPreventionLog = mutableListOf()
+ valueEventActivationLog = mutableListOf()
+
+ triggerManager.subscribeEventListener { t -> generalActivationLog.add(t.toString()) }
+ triggerManager.subscribeEventListenerWithFilter(
+ { t -> eventActivationLog.add(t.toString()) },
+ EventActivationData::class.java
+ )
+ triggerManager.subscribeEventListenerWithFilter(
+ { t -> eventPreventionLog.add(t.toString()) },
+ EventPreventionData::class.java
+ )
+ triggerManager.subscribeEventListenerWithFilter(
+ { t -> valueEventActivationLog.add(t.toString()) },
+ ValueEventActivationData::class.java
+ )
+
+ interpretAsBehavior.activateProcessing()
+ return interpretAsBehavior
+ }
+
+ protected fun getEventListeners(eventName: String): EventActivationListener? {
+ return triggerManager.eventActivationListeners.find { eventActivationListener -> eventActivationListener.eventName == eventName }
+ }
+
+ protected fun assertLogSizes(eventActivation: Int, eventPrevention: Int, valueEvents: Int) {
+ assertEquals(eventActivation, eventActivationLog.size)
+ assertEquals(eventPrevention, eventPreventionLog.size)
+ assertEquals(valueEvents, valueEventActivationLog.size)
+ assertEquals(eventActivation + eventPrevention + valueEvents, generalActivationLog.size)
+ }
+
+ protected fun activateEvent(eventName: String, `when`: ITemporalValue) {
+ getEventListeners(eventName)?.activate(`when`)
+ }
+
+ protected fun deactivateEvent(eventName: String, `when`: ITemporalValue) {
+ getEventListeners(eventName)?.deactivate(`when`)
+ }
+}
\ No newline at end of file
diff --git a/parser/src/main/java/cambio/tltea/parser/core/ASTNode.java b/parser/src/main/java/cambio/tltea/parser/core/ASTNode.java
index 7c854f1..51a84a1 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/ASTNode.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/ASTNode.java
@@ -1,5 +1,7 @@
package cambio.tltea.parser.core;
+import java.io.PrintStream;
+
/**
* @author Lion Wagner
*/
@@ -20,8 +22,26 @@ protected final void setParent(OperatorASTNode parent) {
this.parent = parent;
}
+ public final int depth() {
+ if (parent == null) {
+ return 0;
+ }
+ return 1 + parent.depth();
+ }
+
public abstract int getSize();
+ public abstract int getTreeWidth();
+
+ public final void printTree() {
+ printTree(System.out);
+ }
+
+ public final void printTree(PrintStream out) {
+ ASTNodePrinter.INSTANCE.print(this, out);
+ }
+
+
/**
* Get children count.
*/
@@ -46,6 +66,8 @@ public String toString() {
'}';
}
+ public abstract String toFormulaString();
+
public boolean isBracketed() {
return isBracketed;
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/ASTNodePrinter.kt b/parser/src/main/java/cambio/tltea/parser/core/ASTNodePrinter.kt
new file mode 100644
index 0000000..d8d8cda
--- /dev/null
+++ b/parser/src/main/java/cambio/tltea/parser/core/ASTNodePrinter.kt
@@ -0,0 +1,121 @@
+package cambio.tltea.parser.core
+
+import java.io.PrintStream
+import kotlin.math.min
+
+object ASTNodePrinter {
+
+ private const val DEFAULT_CONSOLE_COLUMN_COUNT = 80
+
+ fun print(root: ASTNode) {
+ print(root, System.out)
+ }
+
+ fun print(root: ASTNode, out: PrintStream) {
+ val treeWidth = root.treeWidth * 3 // *3 to reserve width of <->
+ if (treeWidth > DEFAULT_CONSOLE_COLUMN_COUNT) {
+ out.printf(
+ "[TL-Tea] Warning: Tree might be to large (~%d columns) to be printed properly on a default console (80 columns)%n",
+ treeWidth
+ )
+ }
+ val dataMap1 = mutableMapOf>()
+ dataMap1[root] = Pair(treeWidth, true)
+
+ val dataMap = createNodeInfo(root, treeWidth, dataMap1)
+ val sb = StringBuilder()
+ print(sb, root, dataMap)
+ out.println(sb.toString())
+ }
+
+ private fun createNodeInfo(
+ root: ASTNode,
+ totalWidth: Int,
+ dataMap: MutableMap>
+ ): Map> {
+ when (root) {
+ is UnaryOperationASTNode -> {
+ dataMap[root.child] = Pair(totalWidth, true)
+ createNodeInfo(root.child, totalWidth, dataMap)
+ }
+ is BinaryOperationASTNode -> {
+ dataMap[root.leftChild] = Pair(totalWidth/2, false)
+ dataMap[root.rightChild] = Pair(totalWidth/2, true)
+ createNodeInfo(root.leftChild, totalWidth / 2, dataMap)
+ createNodeInfo(root.rightChild, totalWidth / 2, dataMap)
+ }
+ }
+ return dataMap
+ }
+
+ private fun print(sb: StringBuilder, node: ASTNode, widthData: Map>) {
+ var currentDepth = 0
+ val stack = ArrayDeque()
+ val lastDepthNodes = mutableListOf()
+ stack.addFirst(node)
+
+ while (stack.isNotEmpty()) {
+ val current = stack.removeFirst()
+ if (current.depth() > currentDepth) {
+ sb.append("\n")
+ currentDepth = current.depth()
+
+ for (node in lastDepthNodes) {
+ when (node) {
+ is UnaryOperationASTNode -> {
+ printWithPadding(sb, widthData[node]!!.first, "|")
+ }
+ is BinaryOperationASTNode -> {
+ val quaterLength = widthData[node]!!.first / 4
+ printWithPadding(sb, quaterLength + 1, "")
+ printWithPadding(sb, quaterLength, "/")
+ sb.append(" ")
+ printWithPadding(sb, quaterLength, "\\", false)
+ printWithPadding(sb, quaterLength + 1, "")//
+ }
+ }
+ }
+ sb.append("\n")
+ lastDepthNodes.clear()
+ }
+ lastDepthNodes.add(current)
+ printWithPadding(sb, widthData[current]!!.first, current.toFormulaString(), widthData[current]!!.second)
+
+ when (current) {
+ is UnaryOperationASTNode -> {
+ stack.addLast(current.child)
+ }
+ is BinaryOperationASTNode -> {
+ stack.addLast(current.leftChild)
+ stack.addLast(current.rightChild)
+ }
+ }
+ }
+ }
+
+
+ private fun printWithPadding(sb: StringBuilder, totalLength: Int, s: String, preferRightPadding: Boolean = true) {
+ var leftPadding = (totalLength - s.length) / 2
+ var rightPadding = leftPadding
+ if ((totalLength % 2 == 0) xor (s.length % 2 == 0)) {
+ if (preferRightPadding) {
+ rightPadding++
+ } else {
+ leftPadding++
+ }
+ }
+ if (rightPadding > 0 || leftPadding > 0) {
+ for (i in 0 until rightPadding) {
+ sb.append(" ")
+ }
+ sb.append(s)
+ for (i in 0 until leftPadding) {
+ sb.append(" ")
+ }
+ } else {
+ sb.append(s.substring(0, min(totalLength, s.length)))
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/parser/src/main/java/cambio/tltea/parser/core/BinaryOperationASTNode.java b/parser/src/main/java/cambio/tltea/parser/core/BinaryOperationASTNode.java
index deeadfb..8e3a7a2 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/BinaryOperationASTNode.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/BinaryOperationASTNode.java
@@ -32,7 +32,7 @@ public BinaryOperationASTNode(String operatorImage, ASTNode left, ASTNode right)
}
public BinaryOperationASTNode(LiteralOperatorInfo operator, ASTNode left, ASTNode right) {
- this(operator.operatorImage(), left, right);
+ this(operator.operator(), left, right);
}
@@ -45,12 +45,11 @@ public BinaryOperationASTNode(LiteralOperatorInfo operator, ASTNode left, ASTNod
*/
public final BinaryOperationASTNode seepIn(IOperatorInfo operatorInfo, ASTNode leftNode) {
- if (operatorPriority > BinaryOperatorPrecedenceMap.INSTANCE.getPrecedence(
- OperatorTokenImageMap.INSTANCE.getToken(operatorInfo.operatorImage()))
+ if (operatorPriority > BinaryOperatorPrecedenceMap.INSTANCE.getPrecedence(operatorInfo.operator())
|| this.isBracketed()) {
BinaryOperationASTNode newParent = null;
if (operatorInfo instanceof LiteralOperatorInfo info) {
- newParent = new BinaryOperationASTNode(info.operatorImage(), leftNode, this);
+ newParent = new BinaryOperationASTNode(info.operator(), leftNode, this);
} else if (operatorInfo instanceof TemporalOperatorInfo info) {
newParent = new TemporalBinaryOperationASTNode(info, leftNode, this);
}
@@ -65,7 +64,7 @@ public final BinaryOperationASTNode seepIn(IOperatorInfo operatorInfo, ASTNode l
} else {
BinaryOperationASTNode newChild = null;
if (operatorInfo instanceof LiteralOperatorInfo info) {
- newChild = new BinaryOperationASTNode(info.operatorImage(), leftNode, left);
+ newChild = new BinaryOperationASTNode(info.operator(), leftNode, left);
}
if (operatorInfo instanceof TemporalOperatorInfo info) {
newChild = new TemporalBinaryOperationASTNode(info, leftNode, left);
@@ -85,6 +84,11 @@ public String toString() {
'}';
}
+ @Override
+ public String toFormulaString() {
+ return operator.getShorthandImage();
+ }
+
@Override
public ASTNode clone() {
return new BinaryOperationASTNode(operator, left.clone(), right.clone());
@@ -95,6 +99,11 @@ public int getSize() {
return 1 + left.getSize() + right.getSize();
}
+ @Override
+ public int getTreeWidth() {
+ return left.getTreeWidth() + right.getTreeWidth();
+ }
+
@Override
public int getChildrenCount() {
return 2;
@@ -115,10 +124,12 @@ public ASTNode getRightChild() {
public void setLeftChild(ASTNode left) {
this.left = left;
+ left.setParent(this);
}
public void setRightChild(ASTNode right) {
this.right = right;
+ right.setParent(this);
}
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/IOperatorInfo.java b/parser/src/main/java/cambio/tltea/parser/core/IOperatorInfo.java
index 5c8d65d..71c4d3b 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/IOperatorInfo.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/IOperatorInfo.java
@@ -2,5 +2,5 @@
public interface IOperatorInfo {
- String operatorImage();
+ OperatorToken operator();
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/LiteralOperatorInfo.java b/parser/src/main/java/cambio/tltea/parser/core/LiteralOperatorInfo.java
index 6f18069..f0a6e1d 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/LiteralOperatorInfo.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/LiteralOperatorInfo.java
@@ -1,4 +1,8 @@
package cambio.tltea.parser.core;
-public record LiteralOperatorInfo(String operatorImage) implements IOperatorInfo {
+public record LiteralOperatorInfo(OperatorToken operator) implements IOperatorInfo {
+
+ public LiteralOperatorInfo(String operatorImage) {
+ this(OperatorTokenImageMap.INSTANCE.getToken(operatorImage));
+ }
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/OperatorASTNode.java b/parser/src/main/java/cambio/tltea/parser/core/OperatorASTNode.java
index 14e52cf..6a266a8 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/OperatorASTNode.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/OperatorASTNode.java
@@ -7,11 +7,20 @@ public abstract class OperatorASTNode extends ASTNode {
protected final OperatorToken operator;
public OperatorASTNode(String operatorImage) {
+ this(OperatorTokenImageMap.INSTANCE.getToken(operatorImage));
+ }
+
+ public OperatorASTNode(OperatorToken operator) {
super();
- this.operator = OperatorTokenImageMap.INSTANCE.getToken(operatorImage);
+ this.operator = operator;
}
public OperatorToken getOperator() {
return operator;
}
+
+ @Override
+ public String toString() {
+ return operator.toString();
+ }
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/OperatorToken.java b/parser/src/main/java/cambio/tltea/parser/core/OperatorToken.java
index 5aa2883..d86ab26 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/OperatorToken.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/OperatorToken.java
@@ -1,31 +1,59 @@
package cambio.tltea.parser.core;
+import java.util.EnumSet;
+import java.util.Set;
+
public enum OperatorToken {
- TRUE(""),
- FALSE(""),
- NOT(""),
- AND(""),
- OR(""),
- IMPLIES(""),
- IFF(""),
- EQ(""),
- NEQ(""),
- LT(""),
- LEQ(""),
- GT(""),
- GEQ(""),
- NEXT(""),
- GLOBALLY(""),
- FINALLY(""),
- UNTIL(""),
- RELEASE(""),
- WEAKUNTIL(""),
- UNKNOWN("");
+ TRUE("", "T"),
+ FALSE("", "F"),
+ NOT("", "¬"),
+ AND("", "∧"),
+ OR("", "∨"),
+ IMPLIES("", "->"),
+ IFF("", "<->"),
+ EQ("", "=="),
+ NEQ("", "!="),
+ LT("", "<"),
+ LEQ("", "<="),
+ GT("", ">"),
+ GEQ("", ">="),
+ NEXT("", "X"),
+ BEFORE("", "B"),
+ GLOBALLY("", "G"),
+ FINALLY("", "F"),
+ UNTIL("", "U"),
+ RELEASE("", "R"),
+ WEAKUNTIL("", "W"),
+ SINCE("", "S"),
+ PAST("", "P"),
+ PROPHECY("", "▷"),
+ HISTORY("", "H"),
+ UNKNOWN("", "");
+
+ public static final Set TemporalOperatorTokens = EnumSet.of(OperatorToken.GLOBALLY,
+ OperatorToken.FINALLY,
+ OperatorToken.UNTIL,
+ OperatorToken.SINCE,
+ OperatorToken.RELEASE,
+ OperatorToken.WEAKUNTIL,
+ OperatorToken.BEFORE,
+ OperatorToken.NEXT,
+ OperatorToken.PAST,
+ OperatorToken.HISTORY,
+ OperatorToken.PROPHECY);
+ public static final Set ComparisonOperatorTokens = EnumSet.of(OperatorToken.EQ,
+ OperatorToken.NEQ,
+ OperatorToken.LT,
+ OperatorToken.LEQ,
+ OperatorToken.GT,
+ OperatorToken.GEQ);
private String image;
+ private String shorthandImage;
- OperatorToken(String image) {
+ OperatorToken(String image, String shorthandString) {
this.image = image;
+ this.shorthandImage = shorthandString;
}
public static OperatorToken fromString(String text) {
@@ -42,6 +70,7 @@ public static OperatorToken fromString(String text) {
private OperatorToken setImage(String image) {
this.image = image;
+ this.shorthandImage = image;
return this;
}
@@ -53,6 +82,14 @@ public String image() {
return image;
}
+ public String getShorthandImage() {
+ return shorthandImage;
+ }
+
+ public String shorthandImage() {
+ return shorthandImage;
+ }
+
public static OperatorToken UNKNOWN(String image) {
return OperatorToken.UNKNOWN.setImage(image);
}
@@ -61,4 +98,23 @@ public static OperatorToken UNKNOWN(String image) {
public String toString() {
return String.format("%s[%s]", this.name(), this.image);
}
+
+ /**
+ * If the operator is a comparison operator, returns the corresponding inverted comparison operator token. E.g.
+ * {@link OperatorToken#EQ} -> {@link OperatorToken#NEQ} and {@link OperatorToken#LT} -> {@link OperatorToken#GEQ}.
+ *
+ * @return the inverted comparison operator token
+ * @throws IllegalArgumentException if the operator is not invertible operator
+ */
+ public OperatorToken invert() {
+ return switch (this) {
+ case EQ -> OperatorToken.NEQ;
+ case NEQ -> OperatorToken.EQ;
+ case LT -> OperatorToken.GEQ;
+ case LEQ -> OperatorToken.GT;
+ case GT -> OperatorToken.LEQ;
+ case GEQ -> OperatorToken.LT;
+ default -> throw new IllegalArgumentException("Cannot invert operator " + this);
+ };
+ }
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/OperatorTokenImageMap.java b/parser/src/main/java/cambio/tltea/parser/core/OperatorTokenImageMap.java
index e68c417..0948c0a 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/OperatorTokenImageMap.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/OperatorTokenImageMap.java
@@ -30,6 +30,7 @@ public enum OperatorTokenImageMap {
INSTANCE.put(OperatorToken.GLOBALLY, "G", "g", "☐");
INSTANCE.put(OperatorToken.FINALLY, "F", "f", "◇");
INSTANCE.put(OperatorToken.UNTIL, "U", "u");
+ INSTANCE.put(OperatorToken.SINCE, "S", "s");
INSTANCE.put(OperatorToken.RELEASE, "R", "r");
INSTANCE.put(OperatorToken.WEAKUNTIL, "W", "w");
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/UnaryOperationASTNode.java b/parser/src/main/java/cambio/tltea/parser/core/UnaryOperationASTNode.java
index 3be5f7e..9d9039c 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/UnaryOperationASTNode.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/UnaryOperationASTNode.java
@@ -10,7 +10,9 @@ public class UnaryOperationASTNode extends OperatorASTNode {
private ASTNode child;
public UnaryOperationASTNode(@NotNull OperatorToken operator, ASTNode child) {
- this(operator.image(), child);
+ super(operator);
+ this.child = child;
+ child.setParent(this);
}
public UnaryOperationASTNode(String operator, ASTNode child) {
@@ -24,12 +26,18 @@ public int getSize() {
return getChildrenCount() + child.getSize();
}
+ @Override
+ public int getTreeWidth() {
+ return child.getTreeWidth();
+ }
+
public ASTNode getChild() {
return child;
}
public void setChild(ASTNode child) {
this.child = child;
+ child.setParent(this);
}
@Override
@@ -42,6 +50,11 @@ public boolean isLeaf() {
return false;
}
+ @Override
+ public String toFormulaString() {
+ return operator.getShorthandImage();
+ }
+
@Override
public ASTNode clone() {
return new UnaryOperationASTNode(this.operator, child.clone());
diff --git a/parser/src/main/java/cambio/tltea/parser/core/ValueASTNode.java b/parser/src/main/java/cambio/tltea/parser/core/ValueASTNode.java
index 96f0e2d..7acc990 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/ValueASTNode.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/ValueASTNode.java
@@ -1,5 +1,7 @@
package cambio.tltea.parser.core;
+import org.jetbrains.annotations.NotNull;
+
/**
* @author Lion Wagner
*/
@@ -20,6 +22,11 @@ public int getSize() {
return 1;
}
+ @Override
+ public int getTreeWidth() {
+ return 1;
+ }
+
@Override
public int getChildrenCount() {
return 0;
@@ -37,8 +44,26 @@ public String toString() {
'}';
}
+ @Override
+ public String toFormulaString() {
+ return value;
+ }
+
@Override
public ASTNode clone() {
return new ValueASTNode(value);
}
+
+
+ public boolean containsEventName() {
+ return value.startsWith("(") && value.endsWith(")");
+ }
+
+ public @NotNull String getEventName() {
+ if (containsEventName()) {
+ return value.substring(1, value.length() - 1);
+ } else {
+ throw new IllegalStateException("Value does not contain event name");
+ }
+ }
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/DoubleInterval.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/DoubleInterval.java
deleted file mode 100644
index 2ab12dd..0000000
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/DoubleInterval.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package cambio.tltea.parser.core.temporal;
-
-public final class DoubleInterval extends Interval {
-
- public DoubleInterval(double start, double end) {
- super(start, end, true, !Double.isInfinite(end));
- }
-
- public DoubleInterval(double start, double end, boolean startInclusive) {
- super(start, end, startInclusive, !Double.isInfinite(end));
- }
-
- public DoubleInterval(double start, double end, boolean startInclusive, boolean endInclusive) {
- super(start, end, startInclusive, endInclusive && !Double.isInfinite(end));
- }
-
- @Override
- public Double getDuration() {
- return end - start;
- }
-
- @Override
- public String toString() {
- return (startInclusive ? "[" : "(") + start + "," + end + (endInclusive ? "]" : ")");
- }
-}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalExpressionValueHolder.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalExpressionValueHolder.java
index 3cacafd..db63e6e 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalExpressionValueHolder.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalExpressionValueHolder.java
@@ -2,7 +2,11 @@
public interface ITemporalExpressionValueHolder {
- public void setTemporalExpressionValue(String temporalValueExpression);
+ default void setTemporalExpressionValue(String temporalValueExpression){
+ setTemporalExpressionValue(TemporalPropositionParser.parse(temporalValueExpression));
+ }
+
+ void setTemporalExpressionValue(ITemporalValue temporalValueExpression);
ITemporalValue getTemporalValue();
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalOperationInfoHolder.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalOperationInfoHolder.java
new file mode 100644
index 0000000..f9a2e19
--- /dev/null
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalOperationInfoHolder.java
@@ -0,0 +1,5 @@
+package cambio.tltea.parser.core.temporal;
+
+public interface ITemporalOperationInfoHolder {
+ TemporalOperatorInfo toTemporalOperatorInfo();
+}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalValue.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalValue.java
index ea3fcb7..b0031bb 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalValue.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/ITemporalValue.java
@@ -2,7 +2,13 @@
/**
* Interface to mark temporal value classes.
+ *
+ * For available implementations see
+ *
+ * @see TemporalInterval
+ * @see TemporalEventDescription
+ * @see TimeInstance
*/
-public interface ITemporalValue {
+public sealed interface ITemporalValue permits TemporalInterval, TemporalEventDescription, TimeInstance {
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/IntInterval.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/IntInterval.java
deleted file mode 100644
index 819e3b3..0000000
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/IntInterval.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package cambio.tltea.parser.core.temporal;
-
-public class IntInterval extends Interval {
- public IntInterval(int start, int end) {
- super(start, end);
- }
-
- @Override
- public Integer getDuration() {
- return end - start;
- }
-}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/Interval.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/Interval.java
deleted file mode 100644
index 21b6d2a..0000000
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/Interval.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package cambio.tltea.parser.core.temporal;
-
-import java.util.Objects;
-
-public abstract class Interval implements ITemporalValue {
-
- protected final T start;
- protected final T end;
- protected final boolean startInclusive;
- protected final boolean endInclusive;
-
- public Interval(T start, T end) {
- this(start, end, true);
- }
-
- public Interval(T start, T end, boolean startInclusive) {
- this(start, end, startInclusive, true);
- }
-
- public Interval(T start, T end, boolean startInclusive, boolean endInclusive) {
- this.start = start;
- this.end = end;
- this.startInclusive = startInclusive;
- this.endInclusive = endInclusive;
- }
-
- public T getStart() {
- return start;
- }
-
- public T getEnd() {
- return end;
- }
-
- public boolean isStartInclusive() {
- return startInclusive;
- }
-
- public boolean isEndInclusive() {
- return endInclusive;
- }
-
-
- public boolean contains(T value) {
- return (startInclusive && value.doubleValue() >= start.doubleValue()) || (!startInclusive && value.doubleValue() > start.doubleValue()) &&
- (endInclusive && value.doubleValue() <= end.doubleValue()) || (!endInclusive && value.doubleValue() < end.doubleValue());
-
- }
-
-
- public boolean contains(Interval interval) {
- return (startInclusive && interval.getStart().doubleValue() >= start.doubleValue()) || (!startInclusive && interval.getStart().doubleValue() > start.doubleValue()) &&
- (endInclusive && interval.getEnd().doubleValue() <= end.doubleValue()) || (!endInclusive && interval.getEnd().doubleValue() < end.doubleValue());
- }
-
-
- public boolean overlaps(Interval interval) {
- return !(isBefore(interval) || isAfter(interval));
- }
-
-
- public boolean isBefore(Interval interval) {
- //TODO: inclusive intervals behavior
- return end.doubleValue() < interval.getStart().doubleValue();
- }
-
- public boolean isAfter(Interval interval) {
- //TODO: inclusive intervals behavior
- return start.doubleValue() > interval.getEnd().doubleValue();
- }
-
- public abstract T getDuration();
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Interval> interval = (Interval>) o;
- return startInclusive == interval.startInclusive && endInclusive == interval.endInclusive && start.equals(interval.start) && end.equals(interval.end);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(start, end, startInclusive, endInclusive);
- }
-}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalBinaryOperationASTNode.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalBinaryOperationASTNode.java
index f9b2d4d..86b883b 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalBinaryOperationASTNode.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalBinaryOperationASTNode.java
@@ -6,7 +6,7 @@
/**
* @author Lion Wagner
*/
-public final class TemporalBinaryOperationASTNode extends BinaryOperationASTNode implements ITemporalExpressionValueHolder {
+public final class TemporalBinaryOperationASTNode extends BinaryOperationASTNode implements ITemporalExpressionValueHolder, ITemporalOperationInfoHolder {
private ITemporalValue temporalValueExpression;
@@ -16,14 +16,18 @@ public TemporalBinaryOperationASTNode(String operator, ASTNode left, ASTNode rig
}
public TemporalBinaryOperationASTNode(TemporalOperatorInfo operatorInfo, ASTNode left, ASTNode right) {
- super(operatorInfo.operatorImage(), left, right);
+ super(operatorInfo.operator(), left, right);
this.setTemporalExpressionValue(operatorInfo.temporalValueExpression());
}
+ @Override
+ public TemporalOperatorInfo toTemporalOperatorInfo() {
+ return new TemporalOperatorInfo(this.getOperator(), this.getTemporalValue());
+ }
@Override
- public void setTemporalExpressionValue(String temporalValueExpression) {
- this.temporalValueExpression = TemporalPropositionParser.parse(temporalValueExpression);
+ public void setTemporalExpressionValue(ITemporalValue temporalValueExpression) {
+ this.temporalValueExpression = temporalValueExpression;
}
@Override
@@ -31,4 +35,16 @@ public ITemporalValue getTemporalValue() {
return temporalValueExpression;
}
+ @Override
+ public String toFormulaString() {
+ return super.toFormulaString() + this.getTemporalValue().toString();
+ }
+
+ @Override
+ public ASTNode clone() {
+ return new TemporalBinaryOperationASTNode(this.toTemporalOperatorInfo(),
+ this.getLeftChild().clone(),
+ this.getRightChild().clone());
+ }
+
}
\ No newline at end of file
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalEventDescription.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalEventDescription.java
index c63e985..acb138f 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalEventDescription.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalEventDescription.java
@@ -2,34 +2,27 @@
import java.util.Objects;
-public class TemporalEventDescription implements ITemporalValue {
-
- private final String value;
-
- public TemporalEventDescription(String value) {
- this.value = value;
- }
-
+public record TemporalEventDescription(String value) implements ITemporalValue {
public String getValue() {
return value;
}
- @Override
- public String toString() {
- return "TemporalEvent: "+ value;
- }
-
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TemporalEventDescription)) {
+ return false;
+ }
+
TemporalEventDescription that = (TemporalEventDescription) o;
return Objects.equals(value, that.value);
}
@Override
public int hashCode() {
- return Objects.hash(value);
+ return value != null ? value.hashCode() : 0;
}
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalInterval.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalInterval.java
new file mode 100644
index 0000000..2b00c74
--- /dev/null
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalInterval.java
@@ -0,0 +1,118 @@
+package cambio.tltea.parser.core.temporal;
+
+import java.util.Objects;
+
+public final class TemporalInterval implements ITemporalValue {
+
+ private final double start;
+ private final double end;
+ private final boolean startInclusive;
+ private final boolean endInclusive;
+
+ public TemporalInterval(double start, double end) {
+ this(start, end, true);
+ }
+
+ public TemporalInterval(double start, double end, boolean startInclusive) {
+ this(start, end, startInclusive, true);
+ }
+
+ public TemporalInterval(double start, double end, boolean startInclusive, boolean endInclusive) {
+ this.start = start;
+ this.end = end;
+ this.startInclusive = startInclusive && start != Double.NEGATIVE_INFINITY;
+ this.endInclusive = endInclusive && end != Double.POSITIVE_INFINITY;
+
+ if (start > end) {
+ throw new IllegalArgumentException("Start value must be less than end value");
+ }
+ else if (start == end) {
+ if (!startInclusive || !endInclusive) {
+ throw new IllegalArgumentException("Start and end values must be equal if they are not inclusive");
+ }
+ }
+ }
+
+ public Double getStart() {
+ return start;
+ }
+
+ public Double getEnd() {
+ return end;
+ }
+
+ public boolean isStartInclusive() {
+ return startInclusive;
+ }
+
+ public boolean isEndInclusive() {
+ return endInclusive;
+ }
+
+
+ public boolean contains(double value) {
+ return ((startInclusive && value >= start) ||
+ (!startInclusive && value > start))
+ &&
+ ((endInclusive && value <= end) ||
+ (!endInclusive && value < end));
+
+ }
+
+
+ public boolean contains(TemporalInterval interval) {
+ return contains(interval.getStart()) && contains(interval.getEnd());
+ }
+
+
+ public boolean overlaps(TemporalInterval interval) {
+ return !(isBefore(interval) || isAfter(interval));
+ }
+
+
+ public boolean isBefore(TemporalInterval interval) {
+ if (end <= interval.getStart()) {
+ if (end == interval.getStart()) {
+ return !(endInclusive && interval.isStartInclusive());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isAfter(TemporalInterval interval) {
+ if (start >= interval.getEnd()) {
+ if (start == interval.getEnd()) {
+ return !(startInclusive && interval.isEndInclusive());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public Double getDuration() {
+ return end - start;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TemporalInterval interval = (TemporalInterval) o;
+ return startInclusive == interval.startInclusive && endInclusive == interval.endInclusive && (start == interval.start) && (end == interval.end);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(start, end, startInclusive, endInclusive);
+ }
+
+ @Override
+ public String toString() {
+ return (startInclusive ? "[" : "(") + start + "," + end + (endInclusive ? "]" : ")");
+ }
+}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalOperatorInfo.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalOperatorInfo.java
index 3b4fa20..a4064d8 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalOperatorInfo.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalOperatorInfo.java
@@ -1,6 +1,68 @@
package cambio.tltea.parser.core.temporal;
import cambio.tltea.parser.core.IOperatorInfo;
+import cambio.tltea.parser.core.OperatorToken;
+import cambio.tltea.parser.core.OperatorTokenImageMap;
+import org.jetbrains.annotations.Contract;
+
+import java.util.*;
+
+public final class TemporalOperatorInfo implements IOperatorInfo {
+ private final OperatorToken operator;
+ private final ITemporalValue temporalValueExpression;
+
+
+ public TemporalOperatorInfo(OperatorToken operator, ITemporalValue temporalValueExpression) {
+ if (!OperatorToken.TemporalOperatorTokens.contains(operator)) {
+ throw new IllegalArgumentException("Operator " + operator + " is not allowed");
+ }
+ this.operator = operator;
+ this.temporalValueExpression = temporalValueExpression;
+ }
+
+ public TemporalOperatorInfo(String operatorImage, String temporalValueExpression) {
+ this(OperatorTokenImageMap.INSTANCE.getToken(operatorImage), temporalValueExpression);
+ }
+
+ public TemporalOperatorInfo(String operator, ITemporalValue temporalValueExpression) {
+ this(OperatorTokenImageMap.INSTANCE.getToken(operator), temporalValueExpression);
+ }
+
+ public TemporalOperatorInfo(OperatorToken operator, String temporalValueExpression) {
+ this(operator, TemporalPropositionParser.parse(temporalValueExpression));
+ }
+@Contract
+ public OperatorToken operator() {
+ return operator;
+ }
+
+ public ITemporalValue temporalValueExpression() {
+ return temporalValueExpression;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || obj.getClass() != this.getClass()) {
+ return false;
+ }
+ var that = (TemporalOperatorInfo) obj;
+ return Objects.equals(this.operator, that.operator) &&
+ Objects.equals(this.temporalValueExpression, that.temporalValueExpression);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(operator, temporalValueExpression);
+ }
+
+ @Override
+ public String toString() {
+ return "TemporalOperatorInfo[" +
+ "operator=" + operator + ", " +
+ "temporalValueExpression=" + temporalValueExpression + ']';
+ }
-public record TemporalOperatorInfo(String operatorImage, String temporalValueExpression) implements IOperatorInfo {
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalPropositionParser.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalPropositionParser.java
index 6f3133f..2000ee5 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalPropositionParser.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalPropositionParser.java
@@ -1,9 +1,15 @@
package cambio.tltea.parser.core.temporal;
import java.util.List;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public final class TemporalPropositionParser {
+
+ private static final List infList = List.of("inf", "infinity", "∞");
+
public static ITemporalValue parse(String expression) {
expression = stripBrackets(expression.trim()); // trim and remove brackets
@@ -13,36 +19,41 @@ public static ITemporalValue parse(String expression) {
} catch (NumberFormatException e) {
}
- // check if it is a directional time value
+ // check if it is a relative time value and parse it to an instance or interval
try {
if (expression.startsWith("=")) {
return new TimeInstance(Double.parseDouble(expression.substring(1)));
} else if (expression.startsWith(">=")) {
- return new DoubleInterval(Double.parseDouble(expression.substring(2)), Double.POSITIVE_INFINITY);
+ return new TemporalInterval(Double.parseDouble(expression.substring(2)), Double.POSITIVE_INFINITY);
} else if (expression.startsWith(">")) {
- return new DoubleInterval(Double.parseDouble(expression.substring(1)), Double.POSITIVE_INFINITY, false);
+ return new TemporalInterval(Double.parseDouble(expression.substring(1)), Double.POSITIVE_INFINITY, false);
} else if (expression.startsWith("<=")) {
- return new DoubleInterval(0, Double.parseDouble(expression.substring(2)));
+ return new TemporalInterval(0, Double.parseDouble(expression.substring(2)));
} else if (expression.startsWith("<")) {
- return new DoubleInterval(0, Double.parseDouble(expression.substring(1)), true, false);
+ return new TemporalInterval(0, Double.parseDouble(expression.substring(1)), true, false);
}
- } catch (NumberFormatException e) {
+ } catch (NumberFormatException ignored) {
}
+
+ //create pattern for [,;:]
+ Pattern pattern = Pattern.compile("(\\d+\\.?\\d*)([,;:])(\\+?((\\d+\\.?\\d*)|(inf|infinity|∞)))");
+ Matcher matcher = pattern.matcher(expression.toLowerCase().replaceAll("\s", ""));
+
+
// check if it is an interval description
- if (expression.contains(",")) {
- String[] parts = expression.toLowerCase().replaceAll("\s", "").split(",");
- try {
- //remove + from the start of parts[1]
- if (parts[1].startsWith("+")) {
- parts[1] = parts[1].substring(1);
- }
- List infList = List.of("inf", "infinity", "∞");
- if (infList.contains(parts[1])) {
- return new DoubleInterval(Double.parseDouble(parts[0]), Double.POSITIVE_INFINITY);
- } else return new DoubleInterval(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]));
- } catch (NumberFormatException e) {
+
+ if (matcher.matches()) {
+ MatchResult result = matcher.toMatchResult();
+ //remove starting + from result.group(3) if it exists
+ String secondNumber = result.group(3).startsWith("+") ? result.group(3).substring(1) : result.group(3);
+
+ if (infList.contains(secondNumber)) {
+ return new TemporalInterval(Double.parseDouble(result.group(1)), Double.POSITIVE_INFINITY);
+ } else {
+ return new TemporalInterval(Double.parseDouble(result.group(1)), Double.parseDouble(result.group(3)));
}
+
}
// otherwise, return a TemporalEventDescription wrapper
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalUnaryOperationASTNode.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalUnaryOperationASTNode.java
index 7705fe6..c9ff6ad 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalUnaryOperationASTNode.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/TemporalUnaryOperationASTNode.java
@@ -1,12 +1,13 @@
package cambio.tltea.parser.core.temporal;
import cambio.tltea.parser.core.ASTNode;
+import cambio.tltea.parser.core.OperatorToken;
import cambio.tltea.parser.core.UnaryOperationASTNode;
/**
* @author Lion Wagner
*/
-public final class TemporalUnaryOperationASTNode extends UnaryOperationASTNode implements ITemporalExpressionValueHolder {
+public final class TemporalUnaryOperationASTNode extends UnaryOperationASTNode implements ITemporalExpressionValueHolder, ITemporalOperationInfoHolder {
ITemporalValue temporalValueExpression;
@@ -15,19 +16,37 @@ public TemporalUnaryOperationASTNode(String operator, ASTNode child) {
this.setTemporalExpressionValue("[0, ∞]");
}
+ public TemporalUnaryOperationASTNode(OperatorToken operator, ASTNode child) {
+ this(operator.image(), child);
+ }
+
public TemporalUnaryOperationASTNode(TemporalOperatorInfo operatorInfo, ASTNode child) {
- this(operatorInfo.operatorImage(), child);
+ this(operatorInfo.operator(), child);
this.setTemporalExpressionValue(operatorInfo.temporalValueExpression());
}
+ @Override
+ public TemporalOperatorInfo toTemporalOperatorInfo() {
+ return new TemporalOperatorInfo(this.getOperator(), this.getTemporalValue());
+ }
@Override
- public void setTemporalExpressionValue(String temporalValueExpression) {
- this.temporalValueExpression = TemporalPropositionParser.parse(temporalValueExpression);
+ public void setTemporalExpressionValue(ITemporalValue temporalValueExpression) {
+ this.temporalValueExpression = temporalValueExpression;
}
@Override
public ITemporalValue getTemporalValue() {
return temporalValueExpression;
}
+
+ @Override
+ public String toFormulaString() {
+ return super.toFormulaString() + this.getTemporalValue().toString();
+ }
+
+ @Override
+ public ASTNode clone() {
+ return new TemporalUnaryOperationASTNode(this.toTemporalOperatorInfo(), this.getChild().clone());
+ }
}
diff --git a/parser/src/main/java/cambio/tltea/parser/core/temporal/TimeInstance.java b/parser/src/main/java/cambio/tltea/parser/core/temporal/TimeInstance.java
index 76178c2..68244d8 100644
--- a/parser/src/main/java/cambio/tltea/parser/core/temporal/TimeInstance.java
+++ b/parser/src/main/java/cambio/tltea/parser/core/temporal/TimeInstance.java
@@ -1,5 +1,7 @@
package cambio.tltea.parser.core.temporal;
+import org.jetbrains.annotations.NotNull;
+
import java.util.Objects;
public final class TimeInstance implements ITemporalValue {
@@ -9,6 +11,18 @@ public TimeInstance(double time) {
this.time = time;
}
+ public TimeInstance(int time) {
+ this.time = time;
+ }
+
+ public TimeInstance(@NotNull TimeInstance other) {
+ this.time = other.time;
+ }
+
+ public TimeInstance(@NotNull Number time) {
+ this.time = time.doubleValue();
+ }
+
public double getTime() {
return time;
}
diff --git a/parser/src/test/java/cambio/tltea/parser/core/ASTNodePrinterTest.kt b/parser/src/test/java/cambio/tltea/parser/core/ASTNodePrinterTest.kt
new file mode 100644
index 0000000..5ec58ed
--- /dev/null
+++ b/parser/src/test/java/cambio/tltea/parser/core/ASTNodePrinterTest.kt
@@ -0,0 +1,21 @@
+package cambio.tltea.parser.core
+
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+
+internal class ASTNodePrinterTest
+{
+ @Test
+ fun testPrint()
+ {
+ //create ASTTree for "(!B) & (A | C)"
+ val leaf1 = ValueASTNode("B")
+ val leaf2 = ValueASTNode("A")
+ val leaf3 = ValueASTNode("C")
+ val node1 = UnaryOperationASTNode(OperatorToken.NOT, leaf1)
+ val node2 = BinaryOperationASTNode(OperatorToken.OR, leaf2, leaf3)
+ val root = BinaryOperationASTNode(OperatorToken.AND,node1, node2)
+ ASTNodePrinter.print(root)
+ }
+}
+
diff --git a/parser/src/test/java/cambio/tltea/parser/core/ASTNodeTest.java b/parser/src/test/java/cambio/tltea/parser/core/ASTNodeTest.java
index 0924f35..06d81bd 100644
--- a/parser/src/test/java/cambio/tltea/parser/core/ASTNodeTest.java
+++ b/parser/src/test/java/cambio/tltea/parser/core/ASTNodeTest.java
@@ -4,6 +4,8 @@
import cambio.tltea.parser.core.BinaryOperationASTNode;
import cambio.tltea.parser.core.UnaryOperationASTNode;
import cambio.tltea.parser.core.ValueASTNode;
+import cambio.tltea.parser.mtl.generated.MTLParser;
+import cambio.tltea.parser.mtl.generated.ParseException;
import cambio.tltea.parser.testhelper.ASTTreeComparison;
import org.junit.jupiter.api.Test;
@@ -43,4 +45,13 @@ void cloneTest() {
assertNotSame(root, clone);
ASTTreeComparison.compareAST(root, clone);
}
+
+
+ @Test
+ void cloneTest2() throws ParseException {
+ MTLParser parser = new MTLParser("G[0,1]((A)&(B)|F[A](C)->!(F))");
+ ASTNode result = parser.parse();
+ ASTNode clone = result.clone();
+ ASTTreeComparison.compareAST(result, clone);
+ }
}
\ No newline at end of file
diff --git a/parser/src/test/java/cambio/tltea/parser/core/temporal/TemporalIntervalTest.java b/parser/src/test/java/cambio/tltea/parser/core/temporal/TemporalIntervalTest.java
new file mode 100644
index 0000000..6dd7fb7
--- /dev/null
+++ b/parser/src/test/java/cambio/tltea/parser/core/temporal/TemporalIntervalTest.java
@@ -0,0 +1,192 @@
+package cambio.tltea.parser.core.temporal;
+
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Test;
+
+import java.util.Random;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TemporalIntervalTest {
+
+ private final static Random RANDOM = new Random();
+
+ private final static int repetitions = 100;
+
+ @RepeatedTest(repetitions)
+ void getStart_getEnd() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var interval = new TemporalInterval(start, end);
+ assertEquals(start, interval.getStart());
+ assertEquals(end, interval.getEnd());
+ }
+
+ @RepeatedTest(repetitions)
+ void isStartInclusive() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var interval = new TemporalInterval(start, end);
+ assertTrue(interval.isStartInclusive());
+
+ var interval2 = new TemporalInterval(start, end, false);
+ assertFalse(interval2.isStartInclusive());
+ assertFalse(interval2.contains(start));
+
+ var interval3 = new TemporalInterval(start, end, false, false);
+ assertFalse(interval3.isStartInclusive());
+ assertFalse(interval3.contains(start));
+ }
+
+ @RepeatedTest(repetitions)
+ void isEndInclusive() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var interval = new TemporalInterval(start, end);
+ assertTrue(interval.isEndInclusive());
+
+ var interval2 = new TemporalInterval(start, end, false, false);
+ assertFalse(interval2.isEndInclusive());
+ assertFalse(interval2.contains(end));
+
+ var interval3 = new TemporalInterval(start, end, false, false);
+ assertFalse(interval3.isEndInclusive());
+ assertFalse(interval3.contains(end));
+ }
+
+ @RepeatedTest(repetitions)
+ void contains() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var interval = new TemporalInterval(start, end);
+ assertTrue(interval.contains(start));
+ assertTrue(interval.contains(end));
+
+ for (int i = 0; i < 100; i++) {
+ var inInterval = start + RANDOM.nextDouble() * (end - start);
+ assertTrue(interval.contains(inInterval));
+ }
+ }
+
+
+ @RepeatedTest(repetitions)
+ void containsInterval() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var interval = new TemporalInterval(start, end);
+ assertTrue(interval.contains(start));
+ assertTrue(interval.contains(end));
+
+ for (int i = 0; i < 100; i++) {
+ var inIntervalStart = start + RANDOM.nextDouble() * (end - start);
+ var inIntervalEnd = inIntervalStart + RANDOM.nextDouble() * (end - inIntervalStart);
+ var inInterval = new TemporalInterval(inIntervalStart, inIntervalEnd);
+ assertTrue(interval.contains(inInterval));
+ }
+
+ var exclusiveInterval = new TemporalInterval(start, end, false, false);
+ assertTrue(interval.contains(exclusiveInterval));
+ var inclusiveInterval = new TemporalInterval(start, end, true, true);
+ assertTrue(interval.contains(inclusiveInterval));
+
+
+ var exclusiveInterval2 = new TemporalInterval(start - (RANDOM.nextDouble() + 0.000000001), end, false, false);
+ assertFalse(interval.contains(exclusiveInterval2));
+ var inclusiveInterval2 = new TemporalInterval(start, end + (RANDOM.nextDouble() + 0.000000001), true, true);
+ assertFalse(interval.contains(inclusiveInterval2));
+
+ }
+
+ @RepeatedTest(repetitions)
+ void overlaps() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var interval = new TemporalInterval(start, end);
+
+ for (int i = 0; i < 50; i++) {
+ var inIntervalStart = start + RANDOM.nextDouble() * (end - start);
+ var inIntervalEnd = inIntervalStart + RANDOM.nextDouble() * (Double.MAX_VALUE - inIntervalStart);
+ var inInterval = new TemporalInterval(inIntervalStart, inIntervalEnd);
+ assertTrue(interval.overlaps(inInterval));
+ }
+ for (int i = 0; i < 50; i++) {
+ var inIntervalEnd = start + RANDOM.nextDouble() * (end - start);
+ var inIntervalStart = RANDOM.nextDouble() * inIntervalEnd;
+ var inInterval = new TemporalInterval(inIntervalStart, inIntervalEnd);
+ assertTrue(interval.overlaps(inInterval));
+ }
+
+ var exclusiveInterval = new TemporalInterval(-1, start, false, false);
+ assertFalse(interval.overlaps(exclusiveInterval));
+ var exclusiveInterval2 = new TemporalInterval(end, end + RANDOM.nextDouble() * end, false, false);
+ assertFalse(interval.overlaps(exclusiveInterval2));
+
+ }
+
+ @RepeatedTest(repetitions)
+ void isBefore() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var inclusiveInterval = new TemporalInterval(start, end, true, true);
+ var exclusiveInterval = new TemporalInterval(start, end, false, false);
+
+ var beforeInterval = new TemporalInterval(-1, start - (RANDOM.nextDouble() + 0.000000001));
+ var beforeInterval2 = new TemporalInterval(-1, start, true, true);
+
+ var overlappingStart = end * RANDOM.nextDouble();
+ var overlappingEnd = exclusiveInterval.contains(overlappingStart)
+ ? overlappingStart + (Double.MAX_VALUE - overlappingStart) * RANDOM.nextDouble()
+ : start + (end - start) * RANDOM.nextDouble();
+ var overlappingInterval = new TemporalInterval(overlappingStart, overlappingEnd);
+
+ assertTrue(beforeInterval.isBefore(inclusiveInterval));
+ assertTrue(inclusiveInterval.isAfter(beforeInterval));
+
+ assertTrue(beforeInterval2.isBefore(exclusiveInterval));
+ assertTrue(exclusiveInterval.isAfter(beforeInterval2));
+
+ assertFalse(overlappingInterval.isBefore(inclusiveInterval));
+ assertFalse(inclusiveInterval.isAfter(overlappingInterval));
+ assertFalse(overlappingInterval.isBefore(exclusiveInterval));
+ assertFalse(exclusiveInterval.isAfter(overlappingInterval));
+ }
+
+
+ @RepeatedTest(repetitions)
+ void getDuration() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var interval = new TemporalInterval(start, end, RANDOM.nextBoolean(), RANDOM.nextBoolean());
+ assertEquals(end - start, interval.getDuration());
+ }
+
+ @RepeatedTest(repetitions)
+ void testEquals() {
+ double start = (Integer.MAX_VALUE / 2.0) * RANDOM.nextDouble();
+ double end = start + start * RANDOM.nextDouble();
+ var interval = new TemporalInterval(start, end);
+ var interval2 = new TemporalInterval(start, end);
+ var interval3 = new TemporalInterval(start, end, true, false);
+ var interval4 = new TemporalInterval(start, end, false, true);
+ var interval5 = new TemporalInterval(start, end, false, false);
+
+ //noinspection SimplifiableAssertion,ConstantConditions
+ assertFalse(interval.equals(null));
+ assertEquals(interval, interval);
+ assertEquals(interval, interval2);
+ assertNotEquals(interval, interval3);
+ assertNotEquals(interval, interval4);
+ assertNotEquals(interval, interval5);
+
+ var interval6 = new TemporalInterval(start + RANDOM.nextDouble(), end);
+ var interval7 = new TemporalInterval(start, end + RANDOM.nextDouble());
+ var interval8 = new TemporalInterval(start + RANDOM.nextDouble(), end + RANDOM.nextDouble());
+
+ assertNotEquals(interval, interval6);
+ assertNotEquals(interval, interval7);
+ assertNotEquals(interval, interval8);
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/parser/src/test/java/cambio/tltea/parser/core/temporal/TemporalPropositionParserTest.java b/parser/src/test/java/cambio/tltea/parser/core/temporal/TemporalPropositionParserTest.java
index 48441ec..b8159e2 100644
--- a/parser/src/test/java/cambio/tltea/parser/core/temporal/TemporalPropositionParserTest.java
+++ b/parser/src/test/java/cambio/tltea/parser/core/temporal/TemporalPropositionParserTest.java
@@ -1,20 +1,42 @@
package cambio.tltea.parser.core.temporal;
+import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Test;
-import java.lang.reflect.Array;
-import java.util.ArrayList;
import java.util.List;
+import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
class TemporalPropositionParserTest {
+ @Test
+ void fuzzer() {
+ Random gen = new Random();
+ for (int i = 0; i < 100; i++) {
+ String input = RandomStringUtils.random(gen.nextInt(10000));
+ ITemporalValue result = TemporalPropositionParser.parse(input);
+ assertTrue(result instanceof TemporalEventDescription);
+ }
+ }
+
+
+ @Test
+ void empty_String() {
+ String input = "";
+ ITemporalValue result = TemporalPropositionParser.parse(input);
+ assertTrue(result instanceof TemporalEventDescription);
+ TemporalEventDescription temporalEventDescription = (TemporalEventDescription) result;
+ assertEquals("", temporalEventDescription.getValue());
+ }
+
private static String stripBrackets(String s) {
if (s.startsWith("[") && s.endsWith("]")) {
return s.substring(1, s.length() - 1);
- } else return s;
+ } else {
+ return s;
+ }
}
private void TemporalEventTest(String input, String expectedOut) {
@@ -34,8 +56,8 @@ private void TimeInstantTest(String input, double i) {
private void IntervalTest(String input, double expectedStart, double expectedEnd, boolean expectedStartInclusive, boolean expectedEndInclusive) {
ITemporalValue result = TemporalPropositionParser.parse(input);
- assertTrue(result instanceof DoubleInterval);
- DoubleInterval interval = (DoubleInterval) result;
+ assertTrue(result instanceof TemporalInterval);
+ TemporalInterval interval = (TemporalInterval) result;
assertEquals(expectedStart, interval.getStart());
assertEquals(expectedEnd, interval.getEnd());
assertEquals(expectedStartInclusive, interval.isStartInclusive());
@@ -47,8 +69,9 @@ void TemporalEventDescriptionTest() {
List inputs = List.of(" [42a]","[[42a]]","[>42a]","[1337,42a]","[endOf Time]");
for (String input : inputs) {
String expected = stripBrackets(input.trim());
- System.out.println("Testing: " + input + " -> " + expected);
+ System.out.print("Testing: \"" + input + "\" -> \"" + expected+"\"...");
TemporalEventTest(input, expected);
+ System.out.println("success");
}
}
@@ -136,9 +159,10 @@ void IntervalInfTest() {
List l = List.of("inf", "+inf", "INF", "Inf", "infinity", "+infinity", "∞", "+∞");
for (String end : l) {
- System.out.printf("Testing parsing of %s to infinity%n", end);
+ System.out.printf("Testing parsing of %s to infinity...", end);
String input = "[42," + end + "]";
IntervalTest(input, 42, Double.POSITIVE_INFINITY, true, false);
+ System.out.println("success");
}
}
diff --git a/pom.xml b/pom.xml
index 2078b23..b04d664 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,8 @@
17
17
17
+ 1.6.20-M1
+ true
@@ -32,6 +34,32 @@
org.apache.maven.plugins
maven-compiler-plugin
3.2
+
+
+
+ default-compile
+ none
+
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+ java-test-compile
+ test-compile
+
+ testCompile
+
+
+
org.apache.maven.plugins
@@ -40,6 +68,44 @@
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/main/java
+
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/main/java
+
+
+
+
+
+ 1.8
+
+
+
@@ -62,6 +128,17 @@
3.12.0
test
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test
+ ${kotlin.version}
+ test
+