diff --git a/TLTeaInterprtationClasses.drawio b/TLTeaInterprtationClasses.drawio new file mode 100644 index 0000000..4051e3f --- /dev/null +++ b/TLTeaInterprtationClasses.drawio @@ -0,0 +1 @@ +7V1bc6M4Fv41rtp9aApxEfDYTnouW+kk08nuzsxLFzGKTTdGXsC59K9fCSMDkoxlGwyeIalKjIxl0PnOVeccJubV8u3nxF8tPuMARRNDD94m5vXEMFzPJn/pwPtmABiOtRmZJ2FQjJUDD+EPVAzqxeg6DFBaOzHDOMrCVX1whuMYzbLamJ8k+LV+2jOO6t+68udIGHiY+ZE4+t8wyBbFKIBe+cYvKJwviq92DWfzxtJnJxd3ki78AL9WhsxPE/MqwTjbvFq+XaGILh5bl83nftrx7vbCEhRnKh+48n4x7n978D8/3uLnP779mf7r38EHdhsvfrQu7vg+QUE48zNUXHb2ztYiwes4QHQ6fWJOXxdhhh5W/oy++0qoT8YW2TIiR4C8DPx0kZ9LD57DKLrCEU7IcYCe/XVELnnqR+E8JiPJZvmmLyjJyDdHH4vxDNM5xfssbp2ejt4qQ8V9/4zwEmXJOzmFvesUNChQ6HjF8WtJUqgXY4sKNU27GPQLGM23c5crTV4Ui33AwjN8Vxb+yl+n6JZwkLDw6Wu4jPwY0ZXEcfZQvAMqazgjq4OSnYs4W4RRcOO/4zW9szTzZ9/Z0XSBk/AHmdZnpCNvJ1nBigasnfFAP1kAIEEpOeeeEQZwQ5/9t9qJN36aFQMzHEX+Kg2ftrex9JN5GE9xluFlcVILdDfNOt0tU6Q7gBK6w87IbghkF6gdhTml0yzB37dCh+eiGOcnMfJH6FnGQcswCKJ8MsKnYTx/pGC4/gDKkZv8g9dmOfKlWAY6lODMz/wNmShNIv8JRfc4DbMQVzl3hcM4y9fKnk7s63wkya5wTG7CD3P6IUL+V0QhIKFsM4vsp3dBXwJWJfK6XVHXlFB3SrUYym7CNEMxStJ//FOgOLm9bEtxjsKHE33D8XX6WiJ96RAmn32Ocq20IFBBsYTmddpOyXJe6ZpNqWxckWNQHvdHeNNSIzwDSPuUt0RxThYB/W+N4tko1NsU6p5eF+q207tQh6NQV+ZtS5neQxHq9g6h7s+y8IXYyRVGH2V7+/TvX7a7AgAeCY/MUXKLs/A5JBL6ANl+mtCWIOZS5TgwdN46l1DaklDaNjqiNJPbFUrf4Dnlv9w3G1V4W6S3ORXuKqpwhpD2KS/ab6MK38clF6PCDdFAIwxDNPfCj+fofv0UheniMDE+8ncDf1ucZIe6og7vjr9FHT7y9z5uuRj+NkUT/fbukdqwhv7x9pp+ZRyQv3dfJgbMQ89PhNnhfLMo0F9SVo2fUvpvG4VNRXGw8Fcox0BGabsnAP5EOHaeh8zv1tkGW0VgPPl+R0me0dXTNd1uyy+u85wrIYotI0pXPGeKPHeFl+QewzTF8RgTaVHgGpxB5ajGRDoTuJa4vzEK3H2McjEC1xIdpf/Ql/cJfgkDakltxOxjKWVHTu/EtHIlW5ln5vTRdVLm9C3fXA6ni65TvgZ+nJXMPvJ2K7xtu3aNt4Fu987co9+kztwX5zfZopGWq3G2Vz1ydluJKJxzBvTe1TYLoo+cvZ+zt3xyOZwtGmWf3ghPB2JUgwx+pImS5OgpwpSppmSo4DoAN4c/hfT785WvhDjqCYE7+STF62SGGq6WOUCE3edIJfSMglrWpkgG2bonKPKz8AXVrk228sV09xRKFcubTyJkljibYnObxadK+okTQS5S43ATbZZBmCgHwvYeT8CGGC0bMDYYyfdig2UN9YENEwLNgOWPVUeKdSRSDNfVHLP8ARxu4HlxI7oCA8YNg8N+mQJH3HSLG+eScGOpyps+cWOZllahr1n3G61jcQO54JKQ/r4DKYRm/nvltMII2335LqdK7VrpAnmxmbFdGIqu7BaG+nYXiIUt9Q/boSKAqd+ul095aLM2PDwE26qSj0G9FwS7tmaZHvutCz7vaNPKsDQb6tvfOpr5DLGD0dy6ZBSrb/6akGShkfYgWRD8g64By6yReZuF3wVqdUtzPcsxoWvR8Bz31aVbf7hPYGtkPgOawHbINzCdyRjCVYNuOS87ET8/p6gTtQ7F4FET9ILQX+I4eFyEcQ1xoAZHw+pZqzNXtBdrkNvFhbz6VUWTDfZM1JIe5+urPN3pXo9DFn2rCM0XAoiPm3TsEMdj+LLt9AIOT0BXLaTrLHwJZaVWHLnH8CXHMRcTvoQNISolw2iKcYT8ePiWEaPNoI11LvLpgWPjEpx/6bin6qW2jRpHVC7HGDX6AUYNgUzy/js90IBjsYE/8gFdB2zg+q16/vV79egeJSG5c6q3zhVtZSGHPuAI+PDBsVY3X4cAT8bjju8BdU/U9s5gJznGbhk6PDnI8DToLSDTBFrFSXPq3p8gzZTFouVqFafSYJRjqDQ4I6rjeK0jmlYf42AsZ+7GtnY4YebairZXZ6Y169Azmtb7Testr1yMae1c1C4eW9+qWvg8f/rxDh7frxYvvzt316bzK/jpA+hPK9iQ52COM5XNEUh3dYDnmq5uQ+h4dasBGoOzlsWdvYqauEbpLAlXORuO2mKyqwtWCzqEb4mh3ueoMx0i7myMOmQfF12MDgG6yPhN2qON2H/OwmzCAhUHLvD+nBHlpBE25QCiMYAv4FNVN/w8gINL7+oFSJrk1VuvbCL9hJlp4xWxsvPgLi0V9LXUsGUrmWTVovuspJbEhWoHFVPfDcWT5IV7/qja8Sanp5qMyvjugL1qk9+q3k2YlvcSDd54VM4JsnfYFi3HyLbW7Ra0DTGyc+5zu2IcrWtddzx4mXDYny2rCt4OsOly24mC4FHFpsdjhm/x1rsKc8VYyq/LVUT764ZjDXyrgTTIocqyGvKAzhJIc2Wd40YnqJlRLsYJ8kTq5s2895uWe9l4S8mcmXb266NL768znBYRuQOs1jb4jatps4AYdJB1b+MNkfYIIuk3Qpu5jHGoc8Wh+Hwws/c0IW+MQymLYO/i2pAAXWabV7ODyHtRHiNo3N1gnTR7rEJSdiYray3r5mS3Y6JDvkbxaBOdT7FgVlnL7iOf8sqKLDtNsQC6QhbiYHxDZSx6PeZAezwd+eJY5eimu2ei3n1DoIvOodSCpHS4oZqgOzNSUUWUgOd1xPYJPMXXTKoPuZHZCrpm8pUbZisIMrhJOXnTXaAI6KJHAARi/lXdAZPnN4k7YJzTHQB6Q3XVbhndV6YDMwAGHbmzOJ/P9TzNq/xwhp5y9RM3rdBaoa0YM98jUz+HkSB5Ksxq03KYDOalK+LeVVnM0gjWWeSnaTg7GZHo4etv6dvi67c/v74AN5l+w5+WbHNiPyCPMBc6NF35ZAihvYZyOg6AUCP2K3QcD0Kgg3pNXlkEc6a8TCB5zkiC5tS9SU4ESZmDPqnlnzv2pDn9/Hh5Jyt8kGZ2eYNCF7fjcHzFJ7H+uF0QeGp58o7dFv6K68X2+85nxx3LSDGh5G8VTPU4zWRLOv91FUxtEv7VHPCtSprQuI8J6BqOWeFdPRzFMbR6Vw/blexnORJMNAVTiy/8Qh21eE5uomR7vr2VKQnuySDIa0E/IuSM/QxNqYo51XOWgtMQwCkA728Y1G3k45NiujKUtRDTlV6waCwPw2k7zUTu0WcjhivXVupog5jMpNEeK8WvyeWnm43O4DnjbVJiSVoiUufrPkH0H+HFa8KmowZrS4PZkNdgTlH2eJbtQCkExoQMZcUx5HwM6QVL6prqbUFG9m6TvU13aMytUJMwMnedVy6GucXUnrxRec7hI1+36nhyG7D98/WYwqPM10PO4JE7UGKFz6iwu1LYvD3uWX2zNlAoDBp5m2OVy2FuMU434FiOpOyrkQ6qZV+6pjvAqzEee6pOF9Ee0wCa6ZY/9U1L2+HSYZRjP6YDNZf8NWzL04EHjfod0a3SDmI/HRV6yQl7UaFHSUfd0+DaBRgtW4gBMUAeDEDLNDWLlnyZtum5XJaXA/RBxx4lW/cDxpYkFWmA2IKOZlVIXseZqx+NM9vTdN2yDMshYGVpCpeCM0mT+3qQe9y87SP0DXv3osHoRqub2hfnRxuiH81Fv0e+P4OL7TgqfO/YZ+R7Y3SxlfneaMXFlpG3M74XXewyMD6y/BnC5boSy8Ozsrwk02G4zgZQDrxMenQ2xHQ8m0vXVe/yKEKGdfwdjB9hDPUZkY1ye9AQgoapGWb5rD8uMGdD4nWWPxwg1MFlOBrQyyfDDR1nFxUXYdp20DhzXEcTwXUsnCxX02EZ76jPOzw8maI9NEuQn6H+yqiUQTOsMipgEXm1I8Xn8M0DXYgD29wVtVc8JQeGGNzvHRiqWuuIB412Cgybt1+cE4KtRF3VlZSrc1tUXSNDNHX6RoaySTyspiHESuZwAUArRg0/rxDcbw8iTc3lq42lqLdLCHv3SrvPjt71eZ6N4YCGjfO2XWspEkTzQiD23zCY1sg0Q4yhSy94qNv/Tas77MojQFwRz3BthxiT9G+9XsixjvVKTGjQpBqLTV2vb4L848/P6ZVIiaVQdkLpw/QC4b0FnuPYjz6VoxxyynNucC4VKLi+oSx7L2BIa5nr0DsebKr5AKql+sq4OomfxShokHeCfKKWHd9wI1vQWtiULCCRoeQVfmYfFdk/XfgrlAvxjBJG1tm9suxPROPOc+LdrbONcsjHAz/5fkdldpY3U9B0u7OQtOuJjcZdiWQFfN+V1mghhnnuktFq6sZq4nsJnPWJYlLqj5U5ylbTkCtzpBcsSTgartUkSZoc2tPE+AaI2x73B3fe5CZyraElqxmixT3KhaM0viWEV6XN77tJTGjq2lQh7RQt/JcQJ79Slb1KUJanJX1BKW0/fYDuP02pS2BzqXpe7CAla7cta9nCO16tUR2INjd9RFO9J3OeoHKf4JcwoI3LuIe51x/Y3vj49gtpBtXIHyfp+a46QslpK3sY124Vf7Zex03L20nvwtY0Pce+1rGaHgD++Y+KTwptLT6ukGN4cdDo8xF/fyFoiDZexz0rje56VrJ9skvrWSk0rbRdrYye2sdu3p6rgyX3NVtM72wnzF1WYe4c2cGSHOatKCunJ/5q8RkHtCH7p/8D \ No newline at end of file diff --git a/diagrams.drawio b/diagrams.drawio index 7991aea..8d4269e 100644 --- a/diagrams.drawio +++ b/diagrams.drawio @@ -1 +1 @@ -7Vpbc9o6EP41PML4Jl8ek5D0tE17mMJM2qczKlZArW1RIQfIrz8SlsGyTHBcgt2keSDe1f3bTyvt2j37Kl6/o3Ax/0RCFPUsI1z37GHPsixgu/yf0GwyjRv4mWJGcZipzL1ijB+RVBpSm+IQLZWKjJCI4YWqnJIkQVOm6CClZKVWuyeROuoCzpCmGE9hpGvvcMjmUmu6wb7gH4Rnczm0b3lZQQzzynIlyzkMyaqgsq979hUlhGVP8foKRQK8HJes3c2B0t3EKEpYnQb/ffzw6+b6B3x0vxijf610uAnjvuzlAUapXPDklss3hMZpBOXE2SZHg5I0CZHo0OjZl6s5Zmi8gFNRuuL257o5iyMumfxRn2A+GqIMrQsqOeF3iMSI0Q2vIkttid0mh13Kq4IpcnznBSu4Ugel9We7nvcA8QeJ0TPwsqrx4uSPY0i7hpdttI2XU4FXf4J0Zok1Yr7tLiI8S7iKEYEOlFKE7vlML5ccO5zMJqJs6O8Vt9viobXXfJFrM7e6OVyIYabpdyTEzMmYwiQhptxrYCIGWZJUgHR5TxI2ljNzTm4ijdNehY38ChvtDHdyIwHNSBfjSetcdrwSUHbbZHZ1MpOfKOlPKEKdg8tuHS5fg+t9whBdUMR/364DcIMSr6ucNDirAwg0S33CYxy/XRsFlmojy2/dSZv6Ve36QazQMsaMplOW0vadkGcA1Qm1fmEz9RubhhJKwgsRKgj2RXC5xFMVGLTG7Gvh+ZvAcwCkNFxLeLfCJhcSPvuvReFbUdg2csFOFM0EoaQ8QhTHWzeZtTloGr4d6BQ9tf6sHoN0htjxixoKlXBIN3TBkFU+KtdRFEGGH9Qgqsq4coQRwVsq5yxzD1yS8i6ydctWxaCn1NEujtz0qvdnBozW0ZZru2X/Bv3st00/63XQDzSln1M6RxxwXvrp8Vdj+hlF+hXJB4DGPi6UOXSAkQOgcNJQKPl73HNqcg+8Tu4Bo9RR+Yx9ae7pYeVf7h0IKTvCPad87bVL7qo296wj5/dLc08P1Tt77IIXOHZBTfr5r5N+jt8y/fTUx7Pp19z4fk3j5xFlR6zvlw4+LSasa/1ygsUC57105Sw+t/NpzhizbpQYdIsxx27XdRkTaB35A9M6M2v0/I5Mwhndy/MEQM3zdODF3AnzPH1jYJiesvM8gcZTB3/FrbP5fgxqbkerWx48cF2FFY7hNtyP5RT5mbM29ik8eH4VFGyybTXwsI5FHqdOw/yZDj7w3IHhBfs/lV5mQ3qZBlD55Rgvxq97r//xLvp1+/gBvTe/08/p4uey4quL0XjUv8OPkIYa0d7MSxd+cVLNctaXLpWGOuGh8pwURsN0ReND6CmSHg8jOuUy/sTUbSX8euZ2iPvjhfi87q+DyG1ite4hWkpydsJD1E1ydstD9MuvBJtGjc4RT9PYQXBx/1loVn3/ca19/T8= \ No newline at end of file +7Vpbc9o6EP41PMLY8v0xCUlP27SHKcykfTqjYgXU2hYVcoD8+iNhGSzLBMcl2E2aB+Jd3b/9tNKu3bOu4vU7ChfzTyREUQ8Y4bpnDXsAAMs2+D+h2WSawHczxYziMFOZe8UYPyKplO1mKQ7RUqnICIkYXqjKKUkSNGWKDlJKVmq1exKpoy7gDGmK8RRGuvYOh2wutaYb7Av+QXg2l0P7wMsKYphXlitZzmFIVgWVdd2zrighLHuK11coEuDluGTtbg6U7iZGUcLqNPjv44dfN9c/4KP7xRj9C9LhJoz7spcHGKVywZNbLt8QGqcRlBNnmxwNStIkRKJDo2ddruaYofECTkXpituf6+Ysjrhk8kd9gvloiDK0LqjkhN8hEiNGN7yKLLUkdpscdimvCqbI8Z0XrOBKHZTWn+163gPEHyRGz8ALVOPFyR/HkHYNL8toGy+7Aq/+BOnMEmvEfNtdRHiWcBUjAh0opQjd85leLjl2OJlNRNnQ3ytut8VDsNd8kWszt7o5XIhhpul3JMTMyZjCJCGm3GtgIgZZklSAdHlPEjaWM7NPbiKN016FjfwKG+0Md3IjOZqRLsaT1rlseyWgrLbJ7OpkJj9R0p9QhDoHl9U6XL4G1/uEIbqgiP++XQfgBiVeVzlp56wOINAs9QmPcfx2bRQA1UbAb91Jm/pV7fpBrBAYY0bTKUtp+07IMxzVCbV+YTP1G5uGEkrCCxEqCPZFcLnEUxUYtMbsa+H5m8Bz4EhpuJbwboVNLiR89l+LwreisG3kOjtRNBOEkvIIURxv3WTW5qBp+HagU/TU+rN6DNIZYscvaihUwiHd0AVDVvmoXEdRBBl+UIOoKuPKEUYEb6mcs8w9cEnKu8jWLVsVg55SR7s4ctOr3p8ZMFpHW67tlv0b9LPeNv3A66Cf05R+dukcsZ3z0k+PvxrTzyjSr0g+x9HYx4Uyhw4wcuAonDQUSv4e9+ya3HNeJ/cco9RR+Yx9ae7pYeVf7h0IKTvCPbt87bVK7qo298CR8/uluaeH6p09dp0XOHadmvTzXyf9bL9l+umpj2fTr7nx/ZrGzyPKjljfLx18WkxY1/rlBAtwznvpyll8bufTnDFm3Sgx6BZjjt2u6zIm0DryByY4M2v0/I5Mwhndy/MEjprn6cCLuRPmefrGwDA9Zed5Ao2nDv6KW2fz/RjU3I6gWx48cF2FFbbhNtyP5RT5mbM21ik8eH4VFGyyLDXwAMcij1OnYf5MBx947sDwgv2fSi+zIb1Mw1H5ZRsvxq97r//xLvp1+/gBvTe/08/p4uey4quL0XjUv8OPkIYa0d7MSxd+cVLNctaXLpWGOuGh8pwURsN0ReND6CmSHg8jOuUy/sTUbSX8euZ2iPvjhfi87q+DyG0CWvcQLSU5O+Eh6iY5u+Uh+uVXgk2jRvuIp2nsILi4/yw0q77/uNa6/h8= \ No newline at end of file diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpretationResult.java b/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpretationResult.java deleted file mode 100644 index 8f4358b..0000000 --- a/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpretationResult.java +++ /dev/null @@ -1,88 +0,0 @@ -package cambio.tltea.interpreter; - -import cambio.tltea.interpreter.nodes.requirements.EventActivationListener; -import cambio.tltea.interpreter.nodes.requirements.InteractionNode; -import cambio.tltea.interpreter.nodes.requirements.TriggerNotifier; -import cambio.tltea.parser.core.ASTNode; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnmodifiableView; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * @author Lion Wagner - */ -public final class BehaviorInterpretationResult { - private ASTNode modifiedAST; - private InteractionNode interpretedAST; - private List listeners; - private TriggerNotifier triggerNotifier; - - /** - */ - public BehaviorInterpretationResult(ASTNode modifiedAST, InteractionNode interpretedAST, - List listeners, - TriggerNotifier triggerNotifier) { - this.modifiedAST = modifiedAST; - this.interpretedAST = interpretedAST; - this.listeners = listeners; - this.triggerNotifier = triggerNotifier; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (BehaviorInterpretationResult) obj; - return Objects.equals(this.modifiedAST, that.modifiedAST) && - Objects.equals(this.interpretedAST, that.interpretedAST) && - Objects.equals(this.listeners, that.listeners) && - Objects.equals(this.triggerNotifier, that.triggerNotifier); - } - - @Override - public int hashCode() { - return Objects.hash(modifiedAST, interpretedAST, listeners, triggerNotifier); - } - - @Override - public String toString() { - return "BehaviorInterpretationResult[" + - "modifiedAST=" + modifiedAST + ", " + - "interpretedAST=" + interpretedAST + ", " + - "listener=" + listeners + ", " + - "triggerNotifier=" + triggerNotifier + ']'; - } - - public ASTNode getModifiedAST() { - return modifiedAST; - } - - void setModifiedAST(ASTNode modifiedAST) { - this.modifiedAST = modifiedAST; - } - - public InteractionNode getInterpretedAST() { - return interpretedAST; - } - - void setInterpretedAST(InteractionNode interpretedAST) { - this.interpretedAST = interpretedAST; - } - - @Contract(pure = true) - public @NotNull @UnmodifiableView List getListeners() { - return Collections.unmodifiableList(listeners); - } - - public TriggerNotifier getTriggerNotifier() { - return triggerNotifier; - } - - void addListener(EventActivationListener listener) { - this.listeners.add(listener); - } -} diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpretationResult.kt b/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpretationResult.kt new file mode 100644 index 0000000..16d3cc6 --- /dev/null +++ b/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpretationResult.kt @@ -0,0 +1,42 @@ +package cambio.tltea.interpreter + +import cambio.tltea.interpreter.nodes.ConsequenceDescription +import cambio.tltea.interpreter.nodes.ISubscribableTriggerNotifier +import cambio.tltea.interpreter.nodes.TriggerManager +import cambio.tltea.interpreter.nodes.consequence.ActivationData +import cambio.tltea.parser.core.ASTNode +import java.util.function.Consumer + +/** + * @author Lion Wagner + */ +class BehaviorInterpretationResult internal constructor( + val modifiedAST: ASTNode, + val consequenceDescription: ConsequenceDescription, + val triggerManager: TriggerManager = consequenceDescription.triggerManager +) : + ISubscribableTriggerNotifier { + + + fun activateProcessing() { + consequenceDescription.activateConsequence() + } + + // ----- delegating the subscriptions to the trigger manager----- + override fun subscribeEventListener(listener: Consumer>) { + consequenceDescription.triggerManager.subscribeEventListener(listener) + } + + override fun > subscribeEventListenerWithFilter( + listener: Consumer, + filter: Class + ) { + consequenceDescription.triggerManager.subscribeEventListenerWithFilter(listener, filter) + } + + override fun unsubscribe(listener: Consumer>) { + consequenceDescription.triggerManager.unsubscribe(listener) + } + //--------------------------------------------------------------- + +} \ No newline at end of file diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpreter.java b/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpreter.java deleted file mode 100644 index 7ca2935..0000000 --- a/interpreter/src/main/java/cambio/tltea/interpreter/BehaviorInterpreter.java +++ /dev/null @@ -1,126 +0,0 @@ -package cambio.tltea.interpreter; - -import cambio.tltea.interpreter.nodes.requirements.*; -import cambio.tltea.interpreter.utils.ASTManipulator; -import cambio.tltea.parser.core.*; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * @author Lion Wagner - */ -class BehaviorInterpreter { - - public final BehaviorInterpretationResult result = new BehaviorInterpretationResult(null, - null, - new LinkedList<>(), - new TriggerNotifier()); - - public BehaviorInterpretationResult interpret(ASTNode ast) { - var workCopy = ast.clone(); - result.setModifiedAST(workCopy); - var interpretedAST = interpretAsRequirement(workCopy); - result.setInterpretedAST(interpretedAST); - return result; - } - - - private InteractionNode interpretAsRequirement(ASTNode root) { - if (root instanceof ValueASTNode valueNode) { - return interpretAsRequirement(valueNode); - } else if (root instanceof UnaryOperationASTNode unNode) { - return interpretAsRequirement(unNode); - } else if (root instanceof BinaryOperationASTNode biNode) { - return interpretAsRequirement(biNode); - } - return null; - } - - private InteractionNode interpretAsRequirement(UnaryOperationASTNode unNode) { - switch (unNode.getOperator()) { - case NOT -> { - if (unNode.getChild() instanceof UnaryOperationASTNode child - && child.getOperator() == OperatorToken.NOT) { - interpretAsRequirement(ASTManipulator.removeDoubleNot(unNode)); - }//TODO: replace !true / !false with false / true - var child = interpretAsRequirement(unNode.getChild()); - return new NotInteractionNode((InteractionNode) child); - } - default -> throw new UnsupportedOperationException("Operator not yet supported: " + unNode.getOperator()); - } - } - - private InteractionNode interpretAsRequirement(BinaryOperationASTNode binaryNode) { - switch (binaryNode.getOperator()) { - case IFF: { - interpretAsRequirement(ASTManipulator.splitIFF(binaryNode)); - } - case IMPLIES: { - var left = (InteractionNode) interpretAsRequirement(binaryNode.getLeftChild()); - var right = interpretAsBehavior(binaryNode.getRightChild()); - return new ImplicationNode(left, right, result.getTriggerNotifier()); - } - case AND: { - var children = flattenRequirement(binaryNode); - return new AndInteractionNode(children.toArray(new InteractionNode[0])); - } - case OR: { - var children = flattenRequirement(binaryNode); - return new OrInteractionNode(children.toArray(new InteractionNode[0])); - } - default: { - throw new UnsupportedOperationException("Operator not yet supported: " + binaryNode.getOperator()); - } - } - } - - private InteractionNode interpretAsRequirement(ValueASTNode valueNode) { - try { - double d = Double.parseDouble(valueNode.getValue()); - return new FixedValueDescription<>(d); - } catch (NumberFormatException e) { - - } - - if (valueNode.getValue().startsWith("(") && valueNode.getValue().endsWith(")")) { - ActivatableEvent activatableEvent = new ActivatableEvent(valueNode.getValue()); - result.addListener(activatableEvent.getEventListener()); - return activatableEvent; - } else if (valueNode.getValue().startsWith("[") && valueNode.getValue().endsWith("]")) { - //TODO: return value watcher - } - return new FixedValueDescription<>(valueNode.getValue()); - - } - - - @SuppressWarnings("unchecked") //should be safe casting - private List> flattenRequirement(BinaryOperationASTNode root) { - List> children = new ArrayList<>(); - - if (root.getLeftChild() instanceof BinaryOperationASTNode leftChild && leftChild.getOperator() == root.getOperator()) { - children.addAll(flattenRequirement(leftChild)); - } else { - children.add((InteractionNode) interpretAsRequirement(root.getLeftChild())); - } - - if (root.getRightChild() instanceof BinaryOperationASTNode rightChild && rightChild.getOperator() == root.getOperator()) { - children.addAll(flattenRequirement(rightChild)); - } else { - children.add((InteractionNode) interpretAsRequirement(root.getRightChild())); - } - - return children; - } - - - private String interpretAsBehavior(ASTNode node) { - if (node instanceof ValueASTNode) { - return ((ValueASTNode) node).getValue(); - } else { - throw new IllegalArgumentException("Expected Value Node"); - } - } -} diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/Interpreter.java b/interpreter/src/main/java/cambio/tltea/interpreter/Interpreter.java deleted file mode 100644 index 7424f93..0000000 --- a/interpreter/src/main/java/cambio/tltea/interpreter/Interpreter.java +++ /dev/null @@ -1,12 +0,0 @@ -package cambio.tltea.interpreter; - -import cambio.tltea.parser.core.ASTNode; - -/** - * @author Lion Wagner - */ -public final class Interpreter { - public static BehaviorInterpretationResult interpretAsBehavior(ASTNode root) { - return new BehaviorInterpreter().interpret(root); - } -} diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/Interpreter.kt b/interpreter/src/main/java/cambio/tltea/interpreter/Interpreter.kt new file mode 100644 index 0000000..2ce2762 --- /dev/null +++ b/interpreter/src/main/java/cambio/tltea/interpreter/Interpreter.kt @@ -0,0 +1,15 @@ +package cambio.tltea.interpreter + +import cambio.tltea.interpreter.nodes.ConsequenceInterpreter +import cambio.tltea.parser.core.ASTNode + +/** + * @author Lion Wagner + */ +object Interpreter { + fun interpretAsBehavior(root: ASTNode): BehaviorInterpretationResult { + val clone = root.clone() + val consequenceDescription = ConsequenceInterpreter().interpretAsMTL(clone) + return BehaviorInterpretationResult(clone, consequenceDescription) + } +} \ No newline at end of file diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/CauseDescription.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/CauseDescription.kt new file mode 100644 index 0000000..fb0670f --- /dev/null +++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/CauseDescription.kt @@ -0,0 +1,23 @@ +@file:JvmName("CauseDescription") + +package cambio.tltea.interpreter.nodes + +import cambio.tltea.interpreter.nodes.cause.CauseNode +import cambio.tltea.interpreter.nodes.cause.EventActivationListener + +class CauseDescription( + val causeASTRoot: CauseNode, + val listeners: List +) { + + val causeChangePublisher: StateChangedPublisher = causeASTRoot + + fun activateListeners() { + listeners.forEach { it.startListening() } + } + + fun deactivateListeners() { + listeners.forEach { it.stopListening() } + } + +} \ No newline at end of file diff --git a/interpreter/src/main/java/cambio/tltea/interpreter/nodes/CauseInterpreter.kt b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/CauseInterpreter.kt new file mode 100644 index 0000000..fe1bd87 --- /dev/null +++ b/interpreter/src/main/java/cambio/tltea/interpreter/nodes/CauseInterpreter.kt @@ -0,0 +1,139 @@ +package cambio.tltea.interpreter.nodes + +import cambio.tltea.interpreter.nodes.cause.* +import cambio.tltea.interpreter.utils.ASTManipulator +import cambio.tltea.parser.core.* +import cambio.tltea.parser.core.temporal.TemporalOperatorInfo +import cambio.tltea.parser.core.temporal.TemporalUnaryOperationASTNode + +class CauseInterpreter { + + private val listeners = mutableListOf() + + fun interpretMTLCause( + root: ASTNode, + temporalContext: TemporalOperatorInfo = TemporalOperatorInfo(OperatorToken.GLOBALLY, "[0,inf]") + ): CauseDescription { + val interpretedRoot: CauseNode = interpretAsCause(root, temporalContext) + return CauseDescription(interpretedRoot, listeners) + } + + + private fun interpretAsCause(root: ASTNode, temporalContext: TemporalOperatorInfo): CauseNode { + return when (root) { + is TemporalUnaryOperationASTNode -> { + return interpretAsCause(root.child, root.toTemporalOperatorInfo()) + } + is ValueASTNode -> { + interpretAsCauseEvent(root, temporalContext) + } + is UnaryOperationASTNode -> { + interpretAsCause(root, temporalContext) + } + is BinaryOperationASTNode -> { + interpretAsCause(root, temporalContext) + } + else -> { + throw IllegalArgumentException("Unsupported ASTNode type: ${root.javaClass.name}"); + } + } + } + + private fun interpretAsCause( + unNode: UnaryOperationASTNode, temporalContext: TemporalOperatorInfo + ): CauseNode { + + when (unNode.operator) { + OperatorToken.NOT -> { + if (unNode.child is ValueASTNode) { + @Suppress("UNCHECKED_CAST") + return NotCauseNode(interpretAsCauseEvent(unNode.child as ValueASTNode, temporalContext), temporalContext) + } else return (interpretAsCause(ASTManipulator.applyNot(unNode), temporalContext)) + } + else -> { + throw UnsupportedOperationException("Operator not supported for cause description (left side of implication): " + unNode.getOperator()); + } + } + } + + private fun interpretAsCauseEvent(valueNode: ValueASTNode, temporalContext: TemporalOperatorInfo): CauseNode { + if (valueNode.containsEventName()) { + val eventActivationListener = EventActivationListener(valueNode.eventName) + listeners.add(eventActivationListener) + + //wrap event activation in a ==True comparison + return ComparisonCauseNode(OperatorToken.EQ, temporalContext, eventActivationListener, ConstantValueProvider(true)) + } else if (valueNode.value.contains("$")) { + TODO("A value watcher cannot be created yet.") + } + throw UnsupportedOperationException( + "Value %s cannot be interpreted as event Activation. Try wrapping it in parenthesis like '(%s)'.".format( + valueNode.value, + valueNode.value + ) + ) + } + + private fun interpretAsValue(valueNode: ValueASTNode): ValueProvider<*> { + try { + val d = valueNode.value.toDouble() + return ConstantValueProvider(d) + } catch (e: NumberFormatException) { + } + return ConstantValueProvider(valueNode.value) + } + + + private fun interpretAsCause(binaryNode: BinaryOperationASTNode, temporalContext: TemporalOperatorInfo): CauseNode { + return when (binaryNode.operator) { + OperatorToken.AND -> { + val children = flattenCause(binaryNode, temporalContext) + AndCauseNode(temporalContext, *children.toTypedArray()) + } + OperatorToken.OR -> { + val children = flattenCause(binaryNode, temporalContext) + OrCauseNode(temporalContext, *children.toTypedArray()) + } + else -> { + if (OperatorToken.ComparisonOperatorTokens.contains(binaryNode.operator) + && binaryNode.leftChild is ValueASTNode + && binaryNode.rightChild is ValueASTNode + ) { + return ComparisonCauseNode( + binaryNode.operator, + null, + interpretAsValue(binaryNode.leftChild as ValueASTNode), + interpretAsValue(binaryNode.rightChild as ValueASTNode) + ) + } + + throw UnsupportedOperationException( + "Operator not yet supported as cause description: " + + binaryNode.operator + + "\n(left side of implication)" + ) + } + } + } + + private fun flattenCause(root: BinaryOperationASTNode, temporalContext: TemporalOperatorInfo): List { + val children = mutableListOf() + + 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 +