├── .gitignore ├── README.md ├── docs ├── css │ ├── default.css │ └── highlight.css ├── index.html ├── js │ ├── highlight.min.js │ ├── jquery.min.js │ └── page_effects.js ├── metamorphic.api.html ├── metamorphic.compiler.html ├── metamorphic.dewey.html ├── metamorphic.match-buffer.html ├── metamorphic.runtime.html ├── metamorphic.spec.html ├── metamorphic.trace.html ├── metamorphic.util.html ├── metamorphic.validation.html └── metamorphic.viz.html ├── project.clj ├── resources ├── match-buffer.png └── nfa.png ├── src └── metamorphic │ ├── api.cljc │ ├── compiler.cljc │ ├── dewey.cljc │ ├── match_buffer.cljc │ ├── runtime.cljc │ ├── spec.cljc │ ├── trace.cljc │ ├── util.cljc │ ├── validation.cljc │ └── viz.clj └── test └── metamorphic ├── api_test.clj └── examples.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Metamorphic 2 | 3 | Metamorphic is a complex event processing (CEP) library for Clojure and ClojureScript. Complex event processing is a technique for finding sequences of events that match a particular pattern in a stream. 4 | 5 | This library includes: 6 | 7 | - Multiple levels of event contiguity (strict, skip till next, skip till any) 8 | - Support for optional patterns 9 | - Support for kleene closure (looping) 10 | - Time-windowed constraints 11 | - Partial match recovery mid-execution 12 | - Access to timed out pattern matches 13 | - A textual tracer 14 | - A visual debugger 15 | - Data and code-level APIs 16 | 17 | ## Install 18 | 19 | ```clojure 20 | [io.pyroclast/metamorphic "0.1.0-alpha1"] 21 | ``` 22 | 23 | ## What is this? 24 | 25 | Metamorphic accepts *pattern sequences* and compiles them into a *runtime*. 26 | You feed events into the runtime one at a time, and Metamorphic will try to conform 27 | each event into the pattern sequence you defined, taking into account every other 28 | event you've ever fed into it. Pattern sequences are a linear collection of individual patterns. 29 | A single pattern is essentially a predicate - a function that denotes whether the pattern *accepts* 30 | a given event. When all of the patterns in a pattern sequence have accepted an event, in the order they are defined, 31 | a *match* is created, which is the sequence of events that conform to the pattern sequence. 32 | 33 | Metamorphic provides facilities to sort out irrelevant events, higher level constructs for looping, 34 | and the ability to look at the history of a match as it happens. It does all of this by using a memory 35 | efficient technique based on academic research (see the Prior Art section). We'll dive into all of these topics in the 36 | Usage guide. 37 | 38 | ## Quick start 39 | 40 | CEP has many concepts. We'll start with a quick example to try and familiarize you with the most important ones. 41 | Patterns can express any predicate you'd like, making them ideal for solving sophisticated, domain-specific problems. 42 | For the purposes of learning, however, we'll use the simplest example possible: matching a pattern of letters. 43 | Let's jump right into code, with comments along the way. 44 | 45 | ```clojure 46 | ;; First we bring in the Metamorphic API and it's runtime. 47 | ;; The runtime is a data structure that we'll continually pass 48 | ;; to the API functions. 49 | 50 | (ns my.metamorphic-trial 51 | (:require [metamorphic.api :as m] 52 | [metamorphic.runtime :as rt])) 53 | 54 | ;; Next, we define predicate functions that take exactly 4 arguments. 55 | ;; These predicates are obviously incredibly boring, but they help 56 | ;; save your brain power for the real concepts. 57 | 58 | ;; Each predicate will receive each event as it arrives, a history (which we'll discuss later), 59 | ;; the entire pattern sequence, and the particular pattern that this predicate 60 | ;; is being used in. This is helpful for parameterizing a predicate. 61 | 62 | (defn a? [event history pattern-sequence pattern] 63 | (= event "a")) 64 | 65 | (defn b? [event history pattern-sequence pattern] 66 | (= event "b")) 67 | 68 | (defn c? [event history pattern-sequence pattern] 69 | (= event "c")) 70 | 71 | ;; Now let's create a pattern sequence. We're looking for "a", "b", then "c". 72 | ;; This pattern says: find "a", then immediately look for "b". After you find "b", 73 | ;; look for "c", but if there's something that doesn't match in the middle, that's 74 | ;; okay. The relaxation of looking for "c" is called a contiguity constraint, denoted 75 | ;; by "followed-by" instead of "next". 76 | (let [runtime (-> (m/new-pattern-sequence "a b c") 77 | (m/begin "a" a?) 78 | (m/next "b" b?) 79 | (m/followed-by "c" c?) 80 | (rt/initialize-runtime)) 81 | events ["a" "b" "q" "c" "z"]] 82 | (:matches (reduce rt/evaluate-event runtime events))) 83 | ``` 84 | 85 | Executing this code will produce: 86 | 87 | ```clojure 88 | [("a" "b" "c")] 89 | ``` 90 | 91 | As you can see, you basically create a runtime, then run it against some events 92 | to create matches. In this case, we've reduced over a collection, but you can just 93 | as easily use a core.async channel for input as well. In the rest of this guide, 94 | we'll go through all the different kinds of patterns you can express, and the nuances of 95 | how to use Metamorphic. 96 | 97 | ## Usage 98 | 99 | ### Contiguity 100 | 101 | Contiguity, also known as an *event selection strategy*, is the policy for how to select relevant events from an input stream that has irrelevant events mixed into it. Changing the contiguity lets you filter out noise in a stream. Metamorphic provides 3 levels - strict, skip till next, and skip till any. 102 | 103 | #### strict 104 | 105 | Selected events must be contiguous in the input stream, otherwise the match is aborted. 106 | 107 | ##### Example 108 | 109 | Given the sequence `["a" "b" "a" "c" "b"]`, and the pattern: 110 | 111 | ```clojure 112 | (-> (new-pattern-sequence "test") 113 | (begin "matches-a" (fn [event & context] (= event "a"))) 114 | (next "matches-b" (fn [event & context] (= event "b")))) 115 | ``` 116 | 117 | Will result in a match on `["a" "b"]`, but will ignore the last 3 events because there's a `"c"` in the middle of `"a"` and `"b"`. 118 | 119 | #### skip till next 120 | 121 | Irrelevant events are skipped until a relevant event is encountered. 122 | 123 | ##### Example 124 | 125 | Given the sequence `["a" "c" "b" "a" "d"]`, and the pattern: 126 | 127 | ```clojure 128 | (-> (new-pattern-sequence "test") 129 | (begin "matches-a" (fn [event & context] (= event "a"))) 130 | (followed-by "matches-b" (fn [event & context] (= event "b")))) 131 | ``` 132 | 133 | Will result in a match on `["a" "b"]`. This level of contiguity skips us over `"c"` without aborting the match. 134 | 135 | #### skip till any 136 | 137 | Irrelevant events are skipped until an relevant event is encountered, but the pattern will continue to match at the same state thereafter. 138 | 139 | ##### Example 140 | 141 | Given the sequence `["a" "b" "c" "d"]`, and the pattern: 142 | 143 | ```clojure 144 | (-> (new-pattern-sequence "test") 145 | (begin "matches-a" (fn [event & context] (= event "a"))) 146 | (followed-by-any "any" (constantly true))) 147 | ``` 148 | 149 | Will result in matches: `[["a" "b"] ["a" "c"] ["a" "d"]]`. 150 | 151 | ### Optional patterns 152 | 153 | A pattern can be marked as optional, thus allowing it to match, but making the runtime tolerant to omitting it in matches. 154 | The following will match `["a" "b" "c"]`, or `["b" "c"]`: 155 | 156 | ```clojure 157 | (-> (new-pattern-sequence "test") 158 | (begin "a" (fn [event & context] (= event "a") {:optional? true})) 159 | (followed-by "b" (fn [event & context] (= event "b"))) 160 | (followed-by "c" (fn [event & context] (= event "c")))) 161 | ``` 162 | 163 | ### Looping 164 | 165 | An individual pattern may be marked as a looping pattern - meaning that it will continue to match as long as a given condition is true. 166 | Turn a *singleton* pattern into a *looping* pattern by specifying `{:repetition :one-or-more}` in its options: 167 | 168 | ```clojure 169 | (-> (new-pattern-sequence "test") 170 | (begin "a" (fn [event & context] (= event "a"))) 171 | (followed-by "b's" (fn [event & context] (= event "b")) {:repetition :one-or-more})) 172 | ``` 173 | 174 | This pattern will match an `"a"` followed by one or more `"b"`s. Adding `:optional` will make the pattern match zero or more instances. 175 | 176 | ### Inter-loop contiguity 177 | 178 | Metamorphic supports alternative contiguity modes inbetween loop iterations. Specifying `{:consecutive? true}` makes loop iterations act with `next` contiguity - all iterations must match sequentially. Specifying `{:allow-combinations? true}` makes loop iterations act with `followed-by-any` contiguity. If unspecified, iterations act with `followed-by` contiguity. 179 | 180 | ### Cleaning up matches 181 | 182 | Metamorphic aims to be as pure as possible. Each time `evaluate-event` is invoked, you pass your instance of the runtime to it, and Metamorphic returns an updated version of that runtime back to you. If there are any new matches, Metamorphic appends them to the `:matches` key in the runtime. You are responsible for removing matches from that key after each invocation of the runtime. If the matches are not removed by your program, Metamorphic will continue to append to the `:matches` sequence without removing the previous matches. 183 | 184 | Partial matches that timed out due to missing their window constraints follow the same pattern - except the key is `:timed-out-matches`. 185 | 186 | ### Time-windowed constraints 187 | 188 | Metamorphic supports the ability to force a match to occur within a given time interval. With the code level API, this is expressed with `(api/new-pattern-sequence {:within ms})`, where ms represents a duration in milliseconds of the first event to be matched. With the data level API, simply add the key `:pattern-sequence/within` to your pattern sequence data structure with the same value. 189 | 190 | For example, to express that a match must occur within a window of 15 minutes, you would create the pattern with a window of `900000` milliseconds. Metamorphic doesn't use the wallclock to assume what time it is. You supply the current timestamp as an extra parameter to `(rt/process-event)`. This timestamp is intended to be a function of the data you will be passing in. 191 | 192 | Any partial matches that time out due to exceeding the window are placed under the `:timed-out-matches` key in the runtime, which you are responsible for clearing out after each runtime iteration. 193 | 194 | ### Partial match history 195 | 196 | Sometimes, it's useful for a predicate to look at all of the events that have been matched thus far. Note that predicates have the following signature: 197 | 198 | ```clojure 199 | (defn f [event history pattern-sequence pattern]) 200 | ``` 201 | 202 | The second parameter, `history`, is a `delay`. When dereferenced, it will return a map of pattern name to collection of events. Thus, you can look behind you during a match and decide what to do given what has happened until now in the stream. Looking up the history does have a cost associated with it, which is why it's in a `delay`, so use this with care. 203 | 204 | ### Textual tracing 205 | 206 | Not sure why your events won't match your pattern? Metamorphic can show all partial matches for a data set: 207 | 208 | ```clojure 209 | (:require [metamorphic.trace :as t]) 210 | 211 | (t/trace-partial-match-history ) 212 | ``` 213 | 214 | Along similar lines, you can also get a breakdown of the patch that all successful matches took through the runtime: 215 | 216 | ```clojure 217 | (t/trace-match-history ) 218 | ``` 219 | 220 | ### Data and code level APIs 221 | 222 | While we have written this README to provide examples via the code level API, Metamorphic also supports a data level API. Supply an appropriate data structure to `initialize-runtime`. This README will not go into the specification of the data API at this time, but there are Clojure spec's provided to enforce the correct structure. The code level API's simply build up these data structures, so you can print it out and have a look at any time before calling `initialize-runtime` to get an idea of how it works. It's pretty idiomatic Clojure under the hood as far as data API's go. 223 | 224 | ### Visual debugging 225 | 226 | Metamorphic's implementation uses a static machine and a compact data structure representation to minimize memory overhead. To debug Metamorphic's compilation of your pattern sequence, ensure that you have Graphiz installed, then bring in the following: 227 | 228 | ```clojure 229 | (:require [metamorphic.validation :as v]) 230 | ``` 231 | 232 | Given the following pattern, display a visualization of the compiled state machine: 233 | 234 | ```clojure 235 | (let [runtime (-> (new-pattern-sequence "test") 236 | (begin "start" (constantly true)) 237 | (followed-by "end" (constantly true) {:repetition :one-or-more}) 238 | (rt/initialize-runtime)) 239 | events [{:x "a"} {:x "b"} {:x "c"} {:x "d"} {:x "e"}] 240 | {:keys [matches match-buffer nfa]} (reduce rt/evaluate-event runtime events)] 241 | (v/visualize-nfa nfa)) 242 | ``` 243 | 244 | 245 | 246 | For a similar effect, you can also visualize the contents of the match buffer. The following won't display much 247 | for the previous example given that we aggressively clean up the match buffer when matches are found, but this is what you'd see 248 | if the match buffer weren't purged: 249 | 250 | ```clojure 251 | (v/visualize-match-buffer match-buffer) 252 | ``` 253 | 254 | ![](resources/match-buffer.png) 255 | 256 | ## Prior Art 257 | 258 | This implementation is primarily based on the paper [Efficient Pattern Matching over Event Streams](https://people.cs.umass.edu/~yanlei/publications/sase-sigmod08.pdf). 259 | 260 | ## License 261 | 262 | Copyright © 2017 Distributed Masonry 263 | 264 | Distributed under the Eclipse Public License either version 1.0 or (at 265 | your option) any later version. 266 | -------------------------------------------------------------------------------- /docs/css/default.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica, Arial, sans-serif; 3 | font-size: 15px; 4 | } 5 | 6 | pre, code { 7 | font-family: Monaco, DejaVu Sans Mono, Consolas, monospace; 8 | font-size: 9pt; 9 | margin: 15px 0; 10 | } 11 | 12 | h1 { 13 | font-weight: normal; 14 | font-size: 29px; 15 | margin: 10px 0 2px 0; 16 | padding: 0; 17 | } 18 | 19 | h2 { 20 | font-weight: normal; 21 | font-size: 25px; 22 | } 23 | 24 | h5.license { 25 | margin: 9px 0 22px 0; 26 | color: #555; 27 | font-weight: normal; 28 | font-size: 12px; 29 | font-style: italic; 30 | } 31 | 32 | .document h1, .namespace-index h1 { 33 | font-size: 32px; 34 | margin-top: 12px; 35 | } 36 | 37 | #header, #content, .sidebar { 38 | position: fixed; 39 | } 40 | 41 | #header { 42 | top: 0; 43 | left: 0; 44 | right: 0; 45 | height: 22px; 46 | color: #f5f5f5; 47 | padding: 5px 7px; 48 | } 49 | 50 | #content { 51 | top: 32px; 52 | right: 0; 53 | bottom: 0; 54 | overflow: auto; 55 | background: #fff; 56 | color: #333; 57 | padding: 0 18px; 58 | } 59 | 60 | .sidebar { 61 | position: fixed; 62 | top: 32px; 63 | bottom: 0; 64 | overflow: auto; 65 | } 66 | 67 | .sidebar.primary { 68 | background: #e2e2e2; 69 | border-right: solid 1px #cccccc; 70 | left: 0; 71 | width: 250px; 72 | } 73 | 74 | .sidebar.secondary { 75 | background: #f2f2f2; 76 | border-right: solid 1px #d7d7d7; 77 | left: 251px; 78 | width: 200px; 79 | } 80 | 81 | #content.namespace-index, #content.document { 82 | left: 251px; 83 | } 84 | 85 | #content.namespace-docs { 86 | left: 452px; 87 | } 88 | 89 | #content.document { 90 | padding-bottom: 10%; 91 | } 92 | 93 | #header { 94 | background: #3f3f3f; 95 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); 96 | z-index: 100; 97 | } 98 | 99 | #header h1 { 100 | margin: 0; 101 | padding: 0; 102 | font-size: 18px; 103 | font-weight: lighter; 104 | text-shadow: -1px -1px 0px #333; 105 | } 106 | 107 | #header h1 .project-version { 108 | font-weight: normal; 109 | } 110 | 111 | .project-version { 112 | padding-left: 0.15em; 113 | } 114 | 115 | #header a, .sidebar a { 116 | display: block; 117 | text-decoration: none; 118 | } 119 | 120 | #header a { 121 | color: #f5f5f5; 122 | } 123 | 124 | .sidebar a { 125 | color: #333; 126 | } 127 | 128 | #header h2 { 129 | float: right; 130 | font-size: 9pt; 131 | font-weight: normal; 132 | margin: 4px 3px; 133 | padding: 0; 134 | color: #bbb; 135 | } 136 | 137 | #header h2 a { 138 | display: inline; 139 | } 140 | 141 | .sidebar h3 { 142 | margin: 0; 143 | padding: 10px 13px 0 13px; 144 | font-size: 19px; 145 | font-weight: lighter; 146 | } 147 | 148 | .sidebar h3 a { 149 | color: #444; 150 | } 151 | 152 | .sidebar h3.no-link { 153 | color: #636363; 154 | } 155 | 156 | .sidebar ul { 157 | padding: 7px 0 6px 0; 158 | margin: 0; 159 | } 160 | 161 | .sidebar ul.index-link { 162 | padding-bottom: 4px; 163 | } 164 | 165 | .sidebar li { 166 | display: block; 167 | vertical-align: middle; 168 | } 169 | 170 | .sidebar li a, .sidebar li .no-link { 171 | border-left: 3px solid transparent; 172 | padding: 0 10px; 173 | white-space: nowrap; 174 | } 175 | 176 | .sidebar li .no-link { 177 | display: block; 178 | color: #777; 179 | font-style: italic; 180 | } 181 | 182 | .sidebar li .inner { 183 | display: inline-block; 184 | padding-top: 7px; 185 | height: 24px; 186 | } 187 | 188 | .sidebar li a, .sidebar li .tree { 189 | height: 31px; 190 | } 191 | 192 | .depth-1 .inner { padding-left: 2px; } 193 | .depth-2 .inner { padding-left: 6px; } 194 | .depth-3 .inner { padding-left: 20px; } 195 | .depth-4 .inner { padding-left: 34px; } 196 | .depth-5 .inner { padding-left: 48px; } 197 | .depth-6 .inner { padding-left: 62px; } 198 | 199 | .sidebar li .tree { 200 | display: block; 201 | float: left; 202 | position: relative; 203 | top: -10px; 204 | margin: 0 4px 0 0; 205 | padding: 0; 206 | } 207 | 208 | .sidebar li.depth-1 .tree { 209 | display: none; 210 | } 211 | 212 | .sidebar li .tree .top, .sidebar li .tree .bottom { 213 | display: block; 214 | margin: 0; 215 | padding: 0; 216 | width: 7px; 217 | } 218 | 219 | .sidebar li .tree .top { 220 | border-left: 1px solid #aaa; 221 | border-bottom: 1px solid #aaa; 222 | height: 19px; 223 | } 224 | 225 | .sidebar li .tree .bottom { 226 | height: 22px; 227 | } 228 | 229 | .sidebar li.branch .tree .bottom { 230 | border-left: 1px solid #aaa; 231 | } 232 | 233 | .sidebar.primary li.current a { 234 | border-left: 3px solid #a33; 235 | color: #a33; 236 | } 237 | 238 | .sidebar.secondary li.current a { 239 | border-left: 3px solid #33a; 240 | color: #33a; 241 | } 242 | 243 | .namespace-index h2 { 244 | margin: 30px 0 0 0; 245 | } 246 | 247 | .namespace-index h3 { 248 | font-size: 16px; 249 | font-weight: bold; 250 | margin-bottom: 0; 251 | } 252 | 253 | .namespace-index .topics { 254 | padding-left: 30px; 255 | margin: 11px 0 0 0; 256 | } 257 | 258 | .namespace-index .topics li { 259 | padding: 5px 0; 260 | } 261 | 262 | .namespace-docs h3 { 263 | font-size: 18px; 264 | font-weight: bold; 265 | } 266 | 267 | .public h3 { 268 | margin: 0; 269 | float: left; 270 | } 271 | 272 | .usage { 273 | clear: both; 274 | } 275 | 276 | .public { 277 | margin: 0; 278 | border-top: 1px solid #e0e0e0; 279 | padding-top: 14px; 280 | padding-bottom: 6px; 281 | } 282 | 283 | .public:last-child { 284 | margin-bottom: 20%; 285 | } 286 | 287 | .members .public:last-child { 288 | margin-bottom: 0; 289 | } 290 | 291 | .members { 292 | margin: 15px 0; 293 | } 294 | 295 | .members h4 { 296 | color: #555; 297 | font-weight: normal; 298 | font-variant: small-caps; 299 | margin: 0 0 5px 0; 300 | } 301 | 302 | .members .inner { 303 | padding-top: 5px; 304 | padding-left: 12px; 305 | margin-top: 2px; 306 | margin-left: 7px; 307 | border-left: 1px solid #bbb; 308 | } 309 | 310 | #content .members .inner h3 { 311 | font-size: 12pt; 312 | } 313 | 314 | .members .public { 315 | border-top: none; 316 | margin-top: 0; 317 | padding-top: 6px; 318 | padding-bottom: 0; 319 | } 320 | 321 | .members .public:first-child { 322 | padding-top: 0; 323 | } 324 | 325 | h4.type, 326 | h4.dynamic, 327 | h4.added, 328 | h4.deprecated { 329 | float: left; 330 | margin: 3px 10px 15px 0; 331 | font-size: 15px; 332 | font-weight: bold; 333 | font-variant: small-caps; 334 | } 335 | 336 | .public h4.type, 337 | .public h4.dynamic, 338 | .public h4.added, 339 | .public h4.deprecated { 340 | font-size: 13px; 341 | font-weight: bold; 342 | margin: 3px 0 0 10px; 343 | } 344 | 345 | .members h4.type, 346 | .members h4.added, 347 | .members h4.deprecated { 348 | margin-top: 1px; 349 | } 350 | 351 | h4.type { 352 | color: #717171; 353 | } 354 | 355 | h4.dynamic { 356 | color: #9933aa; 357 | } 358 | 359 | h4.added { 360 | color: #508820; 361 | } 362 | 363 | h4.deprecated { 364 | color: #880000; 365 | } 366 | 367 | .namespace { 368 | margin-bottom: 30px; 369 | } 370 | 371 | .namespace:last-child { 372 | margin-bottom: 10%; 373 | } 374 | 375 | .index { 376 | padding: 0; 377 | font-size: 80%; 378 | margin: 15px 0; 379 | line-height: 16px; 380 | } 381 | 382 | .index * { 383 | display: inline; 384 | } 385 | 386 | .index p { 387 | padding-right: 3px; 388 | } 389 | 390 | .index li { 391 | padding-right: 5px; 392 | } 393 | 394 | .index ul { 395 | padding-left: 0; 396 | } 397 | 398 | .type-sig { 399 | clear: both; 400 | color: #088; 401 | } 402 | 403 | .type-sig pre { 404 | padding-top: 10px; 405 | margin: 0; 406 | } 407 | 408 | .usage code { 409 | display: block; 410 | color: #008; 411 | margin: 2px 0; 412 | } 413 | 414 | .usage code:first-child { 415 | padding-top: 10px; 416 | } 417 | 418 | p { 419 | margin: 15px 0; 420 | } 421 | 422 | .public p:first-child, .public pre.plaintext { 423 | margin-top: 12px; 424 | } 425 | 426 | .doc { 427 | margin: 0 0 26px 0; 428 | clear: both; 429 | } 430 | 431 | .public .doc { 432 | margin: 0; 433 | } 434 | 435 | .namespace-index .doc { 436 | margin-bottom: 20px; 437 | } 438 | 439 | .namespace-index .namespace .doc { 440 | margin-bottom: 10px; 441 | } 442 | 443 | .markdown p, .markdown li, .markdown dt, .markdown dd, .markdown td { 444 | line-height: 22px; 445 | } 446 | 447 | .markdown li { 448 | padding: 2px 0; 449 | } 450 | 451 | .markdown h2 { 452 | font-weight: normal; 453 | font-size: 25px; 454 | margin: 30px 0 10px 0; 455 | } 456 | 457 | .markdown h3 { 458 | font-weight: normal; 459 | font-size: 20px; 460 | margin: 30px 0 0 0; 461 | } 462 | 463 | .markdown h4 { 464 | font-size: 15px; 465 | margin: 22px 0 -4px 0; 466 | } 467 | 468 | .doc, .public, .namespace .index { 469 | max-width: 680px; 470 | overflow-x: visible; 471 | } 472 | 473 | .markdown pre > code { 474 | display: block; 475 | padding: 10px; 476 | } 477 | 478 | .markdown pre > code, .src-link a { 479 | border: 1px solid #e4e4e4; 480 | border-radius: 2px; 481 | } 482 | 483 | .markdown code:not(.hljs), .src-link a { 484 | background: #f6f6f6; 485 | } 486 | 487 | pre.deps { 488 | display: inline-block; 489 | margin: 0 10px; 490 | border: 1px solid #e4e4e4; 491 | border-radius: 2px; 492 | padding: 10px; 493 | background-color: #f6f6f6; 494 | } 495 | 496 | .markdown hr { 497 | border-style: solid; 498 | border-top: none; 499 | color: #ccc; 500 | } 501 | 502 | .doc ul, .doc ol { 503 | padding-left: 30px; 504 | } 505 | 506 | .doc table { 507 | border-collapse: collapse; 508 | margin: 0 10px; 509 | } 510 | 511 | .doc table td, .doc table th { 512 | border: 1px solid #dddddd; 513 | padding: 4px 6px; 514 | } 515 | 516 | .doc table th { 517 | background: #f2f2f2; 518 | } 519 | 520 | .doc dl { 521 | margin: 0 10px 20px 10px; 522 | } 523 | 524 | .doc dl dt { 525 | font-weight: bold; 526 | margin: 0; 527 | padding: 3px 0; 528 | border-bottom: 1px solid #ddd; 529 | } 530 | 531 | .doc dl dd { 532 | padding: 5px 0; 533 | margin: 0 0 5px 10px; 534 | } 535 | 536 | .doc abbr { 537 | border-bottom: 1px dotted #333; 538 | font-variant: none; 539 | cursor: help; 540 | } 541 | 542 | .src-link { 543 | margin-bottom: 15px; 544 | } 545 | 546 | .src-link a { 547 | font-size: 70%; 548 | padding: 1px 4px; 549 | text-decoration: none; 550 | color: #5555bb; 551 | } 552 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | github.com style (c) Vasily Polovnyov 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | color: #333; 10 | background: #f8f8f8; 11 | } 12 | 13 | .hljs-comment, 14 | .hljs-quote { 15 | color: #998; 16 | font-style: italic; 17 | } 18 | 19 | .hljs-keyword, 20 | .hljs-selector-tag, 21 | .hljs-subst { 22 | color: #333; 23 | font-weight: bold; 24 | } 25 | 26 | .hljs-number, 27 | .hljs-literal, 28 | .hljs-variable, 29 | .hljs-template-variable, 30 | .hljs-tag .hljs-attr { 31 | color: #008080; 32 | } 33 | 34 | .hljs-string, 35 | .hljs-doctag { 36 | color: #d14; 37 | } 38 | 39 | .hljs-title, 40 | .hljs-section, 41 | .hljs-selector-id { 42 | color: #900; 43 | font-weight: bold; 44 | } 45 | 46 | .hljs-subst { 47 | font-weight: normal; 48 | } 49 | 50 | .hljs-type, 51 | .hljs-class .hljs-title { 52 | color: #458; 53 | font-weight: bold; 54 | } 55 | 56 | .hljs-tag, 57 | .hljs-name, 58 | .hljs-attribute { 59 | color: #000080; 60 | font-weight: normal; 61 | } 62 | 63 | .hljs-regexp, 64 | .hljs-link { 65 | color: #009926; 66 | } 67 | 68 | .hljs-symbol, 69 | .hljs-bullet { 70 | color: #990073; 71 | } 72 | 73 | .hljs-built_in, 74 | .hljs-builtin-name { 75 | color: #0086b3; 76 | } 77 | 78 | .hljs-meta { 79 | color: #999; 80 | font-weight: bold; 81 | } 82 | 83 | .hljs-deletion { 84 | background: #fdd; 85 | } 86 | 87 | .hljs-addition { 88 | background: #dfd; 89 | } 90 | 91 | .hljs-emphasis { 92 | font-style: italic; 93 | } 94 | 95 | .hljs-strong { 96 | font-weight: bold; 97 | } 98 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 3 | Metamorphic 0.1.1-SNAPSHOT

Metamorphic 0.1.1-SNAPSHOT

Released under the Eclipse Public License

A complex event processing library for Clojure and ClojureScript.

Installation

To install, add the following dependency to your project or build file:

[io.pyroclast/metamorphic "0.1.1-SNAPSHOT"]

Namespaces

metamorphic.spec

Public variables and functions:

metamorphic.viz

Public variables and functions:

-------------------------------------------------------------------------------- /docs/js/highlight.min.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.6.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("clojure",function(e){var t={"builtin-name":"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},i=e.inherit(e.QSM,{i:null}),c=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"symbol",b:"[:]{1,2}"+n},f={b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"name",b:n,starts:h},b=[f,i,m,p,c,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,i,m,p,c,u,l,s,d]}});hljs.registerLanguage("clojure-repl",function(e){return{c:[{cN:"meta",b:/^([\w.-]+|\s*#_)=>/,starts:{e:/$/,sL:"clojure"}}]}}); -------------------------------------------------------------------------------- /docs/js/page_effects.js: -------------------------------------------------------------------------------- 1 | function visibleInParent(element) { 2 | var position = $(element).position().top 3 | return position > -50 && position < ($(element).offsetParent().height() - 50) 4 | } 5 | 6 | function hasFragment(link, fragment) { 7 | return $(link).attr("href").indexOf("#" + fragment) != -1 8 | } 9 | 10 | function findLinkByFragment(elements, fragment) { 11 | return $(elements).filter(function(i, e) { return hasFragment(e, fragment)}).first() 12 | } 13 | 14 | function scrollToCurrentVarLink(elements) { 15 | var elements = $(elements); 16 | var parent = elements.offsetParent(); 17 | 18 | if (elements.length == 0) return; 19 | 20 | var top = elements.first().position().top; 21 | var bottom = elements.last().position().top + elements.last().height(); 22 | 23 | if (top >= 0 && bottom <= parent.height()) return; 24 | 25 | if (top < 0) { 26 | parent.scrollTop(parent.scrollTop() + top); 27 | } 28 | else if (bottom > parent.height()) { 29 | parent.scrollTop(parent.scrollTop() + bottom - parent.height()); 30 | } 31 | } 32 | 33 | function setCurrentVarLink() { 34 | $('.secondary a').parent().removeClass('current') 35 | $('.anchor'). 36 | filter(function(index) { return visibleInParent(this) }). 37 | each(function(index, element) { 38 | findLinkByFragment(".secondary a", element.id). 39 | parent(). 40 | addClass('current') 41 | }); 42 | scrollToCurrentVarLink('.secondary .current'); 43 | } 44 | 45 | var hasStorage = (function() { try { return localStorage.getItem } catch(e) {} }()) 46 | 47 | function scrollPositionId(element) { 48 | var directory = window.location.href.replace(/[^\/]+\.html$/, '') 49 | return 'scroll::' + $(element).attr('id') + '::' + directory 50 | } 51 | 52 | function storeScrollPosition(element) { 53 | if (!hasStorage) return; 54 | localStorage.setItem(scrollPositionId(element) + "::x", $(element).scrollLeft()) 55 | localStorage.setItem(scrollPositionId(element) + "::y", $(element).scrollTop()) 56 | } 57 | 58 | function recallScrollPosition(element) { 59 | if (!hasStorage) return; 60 | $(element).scrollLeft(localStorage.getItem(scrollPositionId(element) + "::x")) 61 | $(element).scrollTop(localStorage.getItem(scrollPositionId(element) + "::y")) 62 | } 63 | 64 | function persistScrollPosition(element) { 65 | recallScrollPosition(element) 66 | $(element).scroll(function() { storeScrollPosition(element) }) 67 | } 68 | 69 | function sidebarContentWidth(element) { 70 | var widths = $(element).find('.inner').map(function() { return $(this).innerWidth() }) 71 | return Math.max.apply(Math, widths) 72 | } 73 | 74 | function calculateSize(width, snap, margin, minimum) { 75 | if (width == 0) { 76 | return 0 77 | } 78 | else { 79 | return Math.max(minimum, (Math.ceil(width / snap) * snap) + (margin * 2)) 80 | } 81 | } 82 | 83 | function resizeSidebars() { 84 | var primaryWidth = sidebarContentWidth('.primary') 85 | var secondaryWidth = 0 86 | 87 | if ($('.secondary').length != 0) { 88 | secondaryWidth = sidebarContentWidth('.secondary') 89 | } 90 | 91 | // snap to grid 92 | primaryWidth = calculateSize(primaryWidth, 32, 13, 160) 93 | secondaryWidth = calculateSize(secondaryWidth, 32, 13, 160) 94 | 95 | $('.primary').css('width', primaryWidth) 96 | $('.secondary').css('width', secondaryWidth).css('left', primaryWidth + 1) 97 | 98 | if (secondaryWidth > 0) { 99 | $('#content').css('left', primaryWidth + secondaryWidth + 2) 100 | } 101 | else { 102 | $('#content').css('left', primaryWidth + 1) 103 | } 104 | } 105 | 106 | $(window).ready(resizeSidebars) 107 | $(window).ready(setCurrentVarLink) 108 | $(window).ready(function() { persistScrollPosition('.primary')}) 109 | $(window).ready(function() { 110 | $('#content').scroll(setCurrentVarLink) 111 | $(window).resize(setCurrentVarLink) 112 | }) 113 | -------------------------------------------------------------------------------- /docs/metamorphic.api.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.api documentation

metamorphic.api

begin

(begin context pattern-name pred)(begin context pattern-name pred opts)
Starts a new pattern sequence. All pattern sequences must start with
 4 | a begin state. Selects the first relevent event to initiate a pattern match.

followed-by

(followed-by context pattern-name pred)(followed-by context pattern-name pred opts)
Relaxed contiguity event selection. Allows this pattern match to tolerate
 5 | irrelevant events before matching.

followed-by-any

(followed-by-any context pattern-name pred)(followed-by-any context pattern-name pred opts)
Nondeterministic relaxed contiguity event selection. Allows this pattern match to tolerate
 6 | irrelevant events before matching, and also continues to match from this point
 7 | after each match occurs.

new-pattern-sequence

(new-pattern-sequence sequence-name)(new-pattern-sequence sequence-name opts)
Creates a new pattern sequence with a name. Optionally supply:
 8 | 
 9 | - :within - The match must fully occur within N milliseconds.

next

(next context pattern-name pred)(next context pattern-name pred opts)
Strict contiguity event selection. Enforces this pattern match to occur immediately
10 | after the previous match occured.
-------------------------------------------------------------------------------- /docs/metamorphic.compiler.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.compiler documentation

metamorphic.compiler

add-begin-transitions

(add-begin-transitions context patterns)

add-final-state

(add-final-state context)

add-ignore-transitions

(add-ignore-transitions context patterns)

add-proceed-transitions

(add-proceed-transitions context patterns)

add-remain-transitions

(add-remain-transitions context patterns)

add-successor

(add-successor context patterns)

build-base-states

(build-base-states context patterns)

build-records

(build-records patterns)

build-state-contiguity

(build-state-contiguity context patterns)

compile-nfa

(compile-nfa {:keys [pattern-sequence/patterns pattern-sequence/within], :as ps})

compute-acceleration

(compute-acceleration transitions successor)

group-transitions

(group-transitions context patterns)

ignore-followed-by-any-singleton?

(ignore-followed-by-any-singleton? begin-results context)

ignore-followed-by-singleton?

(ignore-followed-by-singleton? begin-results context)

index-states

(index-states context)

INFAState

protocol

members

add-contiguity

(add-contiguity this states)

attach-begin-transitions

(attach-begin-transitions this states successor)

attach-ignore-transitions

(attach-ignore-transitions this states starting-state? successor)

attach-proceed-transitions

(attach-proceed-transitions this states successor)

attach-remain-transitions

(attach-remain-transitions this states)

states

(states this starting-state?)

maybe-proceed-to-final

(maybe-proceed-to-final successor k)
-------------------------------------------------------------------------------- /docs/metamorphic.dewey.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.dewey documentation

metamorphic.dewey

add-stage

(add-stage dn)(add-stage dn k)

compatible?

(compatible? a b)

increase

(increase dn)(increase dn k)

new-dewey-number

(new-dewey-number)(new-dewey-number start)
-------------------------------------------------------------------------------- /docs/metamorphic.match-buffer.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.match-buffer documentation

metamorphic.match-buffer

build-match-history

(build-match-history e dn)(build-match-history e dn result)
Traverses pointers to obtain a map of event history keyed by pattern name.
4 | 

event

(event payload state-name pattern-name pointers)

IPointer

protocol

members

dec-refs!

(dec-refs! this stage)

inc-refs!

(inc-refs! this stage n)

ref-count

(ref-count this stage)

referenced?

(referenced? this)

IStage

protocol

members

append

(append this event)

delete

(delete this event)

events

(events this)

ITraverse

protocol

members

text-history

(text-history this)

maybe-delete

(maybe-delete mb event children)

new-match-buffer

(new-match-buffer nfa)

prune-match

(prune-match match-buffer event pointer)

traverse-pointers

(traverse-pointers e dn)(traverse-pointers e dn result)
Traverses the pointers to obtain a linear event history.
5 | 
-------------------------------------------------------------------------------- /docs/metamorphic.runtime.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.runtime documentation

metamorphic.runtime

add-new-starting-state

(add-new-starting-state rt event-time id)

apply-patches

(apply-patches rt)

branching?

(branching? transitions)

build-manifests

(build-manifests rt state segment)

build-transition-record

(build-transition-record manifest)

collapse-pointers

(collapse-pointers rt)

edge-phase

(edge-phase state)

evaluate-event

(evaluate-event rt segment)(evaluate-event rt segment {:keys [event-time id]})

evaluate-looping-transitions

(evaluate-looping-transitions {:keys [pattern-sequence], :as rt} {:state/keys [pattern last-transition last-event version], :as state} segment)

evaluate-processing-queue

(evaluate-processing-queue {:keys [nfa processing-queue patches], :as rt} segment)

evaluate-singleton-transitions

(evaluate-singleton-transitions {:keys [pattern-sequence], :as rt} {:state/keys [pattern last-transition last-event version], :as state} segment)

evaluate-transitions

multimethod

find-accelerated-matches

(find-accelerated-matches rt)

find-final-matches

(find-final-matches rt)

get-transitions

(get-transitions rt state-name)

initialize-runtime

(initialize-runtime pattern-sequence)(initialize-runtime pattern-sequence opts)
Creates a new instance of the runtime for a pattern sequence.
4 | The runtime *is not* functionally pure and thus should not be shared
5 | across threads.

initialize-transient-values

(initialize-transient-values rt)

ITransition

protocol

members

next-manifests

(next-manifests this rt segment)

next-state

(next-state this rt)

next-version

(next-version this rt dst-state)

load-processing-queue

(load-processing-queue rt segment)

merge-matches

(merge-matches rt)

new-instance

(new-instance nfa)

prune-timed-out-runs

(prune-timed-out-runs rt event-time)

remove-transient-values

(remove-transient-values rt)

reset-transition-count

(reset-transition-count state)

trace-match

(trace-match rt state last-event version)

unwrap-pointers

(unwrap-pointers pointers)

update-last-event

(update-last-event rt)

update-match-buffer

(update-match-buffer rt segment)

update-transition-count

(update-transition-count old-state new-state transition-kind)
-------------------------------------------------------------------------------- /docs/metamorphic.spec.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.spec documentation

metamorphic.spec

pattern-kind

multimethod

-------------------------------------------------------------------------------- /docs/metamorphic.trace.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.trace documentation

metamorphic.trace

iterate-events

(iterate-events runtime events head)

trace-match-history

(trace-match-history pattern-sequence events)

trace-partial-match-history

(trace-partial-match-history pattern-sequence events)

update-history

(update-history history pending-states head)
-------------------------------------------------------------------------------- /docs/metamorphic.util.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.util documentation

metamorphic.util

conjv

(conjv xs x)

final-state-name

index-by

(index-by k xs)

invert-map-coll

(invert-map-coll m)

kw->fn

(kw->fn kw)

random-uuid

(random-uuid)

resolve-fn

(resolve-fn f)

select-keys-by

(select-keys-by m f)
-------------------------------------------------------------------------------- /docs/metamorphic.validation.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.validation documentation

metamorphic.validation

validate-final-name!

(validate-final-name! patterns)

validate-pattern-names-unique!

(validate-pattern-names-unique! patterns)

validate-pattern-sequence!

(validate-pattern-sequence! pattern-sequence)
-------------------------------------------------------------------------------- /docs/metamorphic.viz.html: -------------------------------------------------------------------------------- 1 | 3 | metamorphic.viz documentation

metamorphic.viz

visualize-match-buffer

(visualize-match-buffer match-buffer)

visualize-nfa

(visualize-nfa nfa)
-------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.pyroclast/metamorphic "0.1.1-SNAPSHOT" 2 | :description "A complex event processing library for Clojure and ClojureScript." 3 | :url "https://github.com/pyroclastio/metamorphic" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.9.0-alpha17"] 7 | [rhizome "0.2.7"]] 8 | :profiles {:dev {:plugins [[lein-codox "0.10.3"]]}} 9 | :codox {:output-path "doc"}) 10 | -------------------------------------------------------------------------------- /resources/match-buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyroclastIO/metamorphic/27dc43ad89423873ca6bcc012cd91fbe2d39271d/resources/match-buffer.png -------------------------------------------------------------------------------- /resources/nfa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyroclastIO/metamorphic/27dc43ad89423873ca6bcc012cd91fbe2d39271d/resources/nfa.png -------------------------------------------------------------------------------- /src/metamorphic/api.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.api 2 | (:refer-clojure :exclude [next]) 3 | (:require [metamorphic.util :as u])) 4 | 5 | (defn ^{:no-doc true} base-pattern [pattern-name pred contiguity] 6 | {:pattern/name pattern-name 7 | :pattern/kind :singleton 8 | :pattern/predicate pred 9 | :pattern/contiguity contiguity}) 10 | 11 | (defn ^{:no-doc true} attach-options [pattern opts] 12 | (cond-> pattern 13 | (:optional? opts) 14 | (assoc :pattern/optional? true) 15 | 16 | (:consecutive? opts) 17 | (assoc :pattern/looping-contiguity :next) 18 | 19 | (:allow-combinations? opts) 20 | (assoc :pattern/looping-contiguity :followed-by-any) 21 | 22 | (:repetition opts) 23 | ((fn [p] 24 | (-> p 25 | (assoc :pattern/kind :looping) 26 | (assoc :pattern/repetition (:repetition opts))))) 27 | 28 | (:looping-predicate opts) 29 | (assoc :pattern/looping-predicate (:looping-predicate opts)))) 30 | 31 | (defn new-pattern-sequence 32 | "Creates a new pattern sequence with a name. Optionally supply: 33 | 34 | - :within - The match must fully occur within N milliseconds." 35 | ([sequence-name] 36 | (new-pattern-sequence sequence-name nil)) 37 | ([sequence-name opts] 38 | (cond-> {:pattern-sequence/name sequence-name 39 | :pattern-sequence/patterns []} 40 | (:within opts) 41 | (assoc :pattern-sequence/within (:within opts))))) 42 | 43 | (defn begin 44 | "Starts a new pattern sequence. All pattern sequences must start with 45 | a begin state. Selects the first relevent event to initiate a pattern match." 46 | ([context pattern-name pred] 47 | (begin context pattern-name pred {})) 48 | ([context pattern-name pred opts] 49 | (update context :pattern-sequence/patterns u/conjv 50 | (-> pattern-name 51 | (base-pattern pred :next) 52 | (attach-options opts))))) 53 | 54 | (defn next 55 | "Strict contiguity event selection. Enforces this pattern match to occur immediately 56 | after the previous match occured." 57 | ([context pattern-name pred] 58 | (next context pattern-name pred {})) 59 | ([context pattern-name pred opts] 60 | (update context :pattern-sequence/patterns u/conjv 61 | (-> pattern-name 62 | (base-pattern pred :next) 63 | (attach-options opts))))) 64 | 65 | (defn followed-by 66 | "Relaxed contiguity event selection. Allows this pattern match to tolerate 67 | irrelevant events before matching." 68 | ([context pattern-name pred] 69 | (followed-by context pattern-name pred {})) 70 | ([context pattern-name pred opts] 71 | (update context :pattern-sequence/patterns u/conjv 72 | (-> pattern-name 73 | (base-pattern pred :followed-by) 74 | (attach-options opts))))) 75 | 76 | (defn followed-by-any 77 | "Nondeterministic relaxed contiguity event selection. Allows this pattern match to tolerate 78 | irrelevant events before matching, and also continues to match from this point 79 | after each match occurs." 80 | ([context pattern-name pred] 81 | (followed-by-any context pattern-name pred {})) 82 | ([context pattern-name pred opts] 83 | (update context :pattern-sequence/patterns u/conjv 84 | (-> pattern-name 85 | (base-pattern pred :followed-by-any) 86 | (attach-options opts))))) 87 | -------------------------------------------------------------------------------- /src/metamorphic/compiler.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.compiler 2 | (:require [clojure.spec.alpha :as s] 3 | [metamorphic.validation :as v] 4 | [metamorphic.util :as u] 5 | [metamorphic.spec])) 6 | 7 | (defprotocol INFAState 8 | (states [this starting-state?]) 9 | (add-contiguity [this states]) 10 | (attach-begin-transitions [this states successor]) 11 | (attach-remain-transitions [this states]) 12 | (attach-proceed-transitions [this states successor]) 13 | (attach-ignore-transitions [this states starting-state? successor])) 14 | 15 | (defn ignore-followed-by-singleton? [begin-results context] 16 | (let [{:state/keys [last-transition last-event]} context] 17 | (if (= last-transition :proceed) 18 | (and last-event (not (seq begin-results))) 19 | (not (seq begin-results))))) 20 | 21 | (defn ignore-followed-by-any-singleton? [begin-results context] 22 | (let [{:state/keys [last-transition last-event]} context] 23 | (if (= last-transition :proceed) 24 | (not (nil? last-event)) 25 | true))) 26 | 27 | (defn maybe-proceed-to-final [successor k] 28 | (if (= successor u/final-state-name) 29 | (fn [results context] 30 | (seq (get results k))) 31 | (constantly true))) 32 | 33 | (defrecord SingletonState [pattern] 34 | INFAState 35 | (states [this starting-state?] 36 | (cond-> {:base {:state/name (:pattern/name pattern) 37 | :state/kind :singleton 38 | :state/pattern pattern}} 39 | 40 | (:pattern/optional? pattern) 41 | (assoc :escape 42 | {:state/name (str (:pattern/name pattern) "-escape") 43 | :state/kind :singleton 44 | :state/pattern pattern}))) 45 | 46 | (add-contiguity [this states] 47 | (cond-> states 48 | true 49 | (update :base assoc :state/contiguity (:pattern/contiguity pattern)) 50 | 51 | (:pattern/optional? pattern) 52 | (update :escape assoc :state/contiguity (:pattern/contiguity pattern)))) 53 | 54 | (attach-begin-transitions [this states successor] 55 | (cond-> states 56 | true 57 | (update-in [:base :state/transitions] conj 58 | {:transition/to successor 59 | :transition/kind :begin 60 | :transition/condition (u/resolve-fn (:pattern/predicate pattern))}) 61 | 62 | (:pattern/optional? pattern) 63 | (update-in [:escape :state/transitions] conj 64 | {:transition/to successor 65 | :transition/kind :begin 66 | :transition/condition (u/resolve-fn (:pattern/predicate pattern))}))) 67 | 68 | (attach-remain-transitions [this states] 69 | states) 70 | 71 | (attach-proceed-transitions [this states successor] 72 | (cond-> states 73 | (:pattern/optional? pattern) 74 | (update-in [:base :state/transitions] conj 75 | {:transition/to successor 76 | :transition/kind :proceed 77 | :transition/condition (maybe-proceed-to-final successor :begin)}))) 78 | 79 | (attach-ignore-transitions [this states starting-state? successor] 80 | (let [{:keys [pattern/contiguity]} pattern] 81 | (if starting-state? 82 | states 83 | (if (not (:pattern/optional? pattern)) 84 | (cond-> states 85 | (= contiguity :followed-by) 86 | (update-in [:base :state/transitions] conj 87 | {:transition/to (get-in states [:base :state/name]) 88 | :transition/kind :ignore 89 | :transition/condition 90 | (fn [begin-results context] 91 | (ignore-followed-by-singleton? begin-results context))}) 92 | 93 | (= contiguity :followed-by-any) 94 | (update-in [:base :state/transitions] conj 95 | {:transition/to (get-in states [:base :state/name]) 96 | :transition/kind :ignore 97 | :transition/condition 98 | (fn [begin-results context] 99 | (ignore-followed-by-any-singleton? begin-results context))})) 100 | 101 | (cond-> states 102 | (= contiguity :followed-by) 103 | (update-in [:base :state/transitions] conj 104 | {:transition/to (get-in states [:escape :state/name]) 105 | :transition/kind :ignore 106 | :transition/condition 107 | (fn [begin-results context] 108 | (ignore-followed-by-singleton? begin-results context))}) 109 | 110 | (= contiguity :followed-by) 111 | (update-in [:escape :state/transitions] conj 112 | {:transition/to (get-in states [:escape :state/name]) 113 | :transition/kind :ignore 114 | :transition/condition 115 | (fn [begin-results context] 116 | (not (seq begin-results)))}) 117 | 118 | (= contiguity :followed-by-any) 119 | (update-in [:base :state/transitions] conj 120 | {:transition/to (get-in states [:escape :state/name]) 121 | :transition/kind :ignore 122 | :transition/condition 123 | (fn [begin-results context] 124 | (ignore-followed-by-any-singleton? begin-results context))}) 125 | 126 | (= contiguity :followed-by-any) 127 | (update-in [:escape :state/transitions] conj 128 | {:transition/to (get-in states [:escape :state/name]) 129 | :transition/kind :ignore 130 | :transition/condition 131 | (constantly true)}))))))) 132 | 133 | (defrecord LoopingState [pattern] 134 | INFAState 135 | (states [this starting-state?] 136 | (let [looping-contiguity (:pattern/looping-contiguity pattern :followed-by)] 137 | (cond-> {:base {:state/name (:pattern/name pattern) 138 | :state/kind :singleton 139 | :state/pattern pattern} 140 | 141 | :iterations {:state/name (str (:pattern/name pattern) "-iterations") 142 | :state/kind :looping 143 | :state/pattern pattern}} 144 | 145 | (and (not starting-state?) 146 | (:pattern/optional? pattern) 147 | (not= (:pattern/contiguity pattern) :next)) 148 | (assoc :base-escape 149 | {:state/name (str (:pattern/name pattern) "-escape") 150 | :state/kind :singleton 151 | :state/pattern pattern}) 152 | 153 | (not= looping-contiguity :next) 154 | (assoc :iterations-escape 155 | {:state/name (str (:pattern/name pattern) "-iterations-escape") 156 | :state/kind :singleton 157 | :state/pattern pattern})))) 158 | 159 | (add-contiguity [this states] 160 | (let [looping-contiguity (:pattern/looping-contiguity pattern :followed-by)] 161 | (cond-> states 162 | true 163 | (update :base assoc :state/contiguity (:pattern/contiguity pattern)) 164 | 165 | true 166 | (update :iterations assoc :state/contiguity looping-contiguity) 167 | 168 | (:base-escape states) 169 | (update :base-escape assoc :state/contiguity (:pattern/contiguity pattern)) 170 | 171 | (:iterations-escape states) 172 | (update :iterations-escape assoc :state/contiguity looping-contiguity)))) 173 | 174 | (attach-begin-transitions [this states successor] 175 | (cond-> states 176 | true 177 | (update-in [:base :state/transitions] conj 178 | {:transition/to (get-in states [:iterations :state/name]) 179 | :transition/kind :begin 180 | :transition/condition (u/resolve-fn (:pattern/predicate pattern))}) 181 | 182 | (and (:base-escape states) (not (:pattern/optional? pattern))) 183 | (update-in [:base-escape :state/transitions] conj 184 | {:transition/to (get-in states [:base :state/name]) 185 | :transition/kind :begin 186 | :transition/condition (u/resolve-fn (:pattern/predicate pattern))}) 187 | 188 | (and (:base-escape states) (:pattern/optional? pattern)) 189 | (update-in [:base-escape :state/transitions] conj 190 | {:transition/to (get-in states [:iterations :state/name]) 191 | :transition/kind :begin 192 | :transition/condition (u/resolve-fn (:pattern/predicate pattern))}) 193 | 194 | (:iterations-escape states) 195 | (update-in [:iterations-escape :state/transitions] conj 196 | {:transition/to (get-in states [:iterations :state/name]) 197 | :transition/kind :begin 198 | :transition/condition (u/resolve-fn (:pattern/predicate pattern))}))) 199 | 200 | (attach-remain-transitions [this states] 201 | (let [f (u/resolve-fn (or (:pattern/looping-predicate pattern) 202 | (:pattern/predicate pattern)))] 203 | (update-in states [:iterations :state/transitions] conj 204 | {:transition/to (get-in states [:iterations :state/name]) 205 | :transition/kind :remain 206 | :transition/condition 207 | (fn [context segment history pattern-sequence pattern] 208 | (f segment history pattern-sequence pattern))}))) 209 | 210 | (attach-proceed-transitions [this states successor] 211 | (cond-> states 212 | true 213 | (update-in [:iterations :state/transitions] conj 214 | {:transition/to successor 215 | :transition/kind :proceed 216 | :transition/condition (maybe-proceed-to-final successor :remain)}) 217 | 218 | (:pattern/optional? pattern) 219 | (update-in [:base :state/transitions] conj 220 | {:transition/to successor 221 | :transition/kind :proceed 222 | :transition/condition (maybe-proceed-to-final successor :begin)}))) 223 | 224 | (attach-ignore-transitions [this states starting-state? successor] 225 | (cond-> states 226 | (and (not (:pattern/optional? pattern)) (= (get-in states [:base :state/contiguity]) :followed-by)) 227 | (update-in [:base :state/transitions] conj 228 | {:transition/to (get-in states [:base :state/name]) 229 | :transition/kind :ignore 230 | :transition/condition 231 | (fn [begin-results context] 232 | (ignore-followed-by-singleton? begin-results context))}) 233 | 234 | (and (not (:pattern/optional? pattern)) (= (get-in states [:base :state/contiguity]) :followed-by-any)) 235 | (update-in [:base :state/transitions] conj 236 | {:transition/to (get-in states [:base :state/name]) 237 | :transition/kind :ignore 238 | :transition/condition 239 | (fn [begin-results context] 240 | (ignore-followed-by-any-singleton? begin-results context))}) 241 | 242 | (and (:base-escape states) (= (get-in states [:base :state/contiguity]) :followed-by)) 243 | (update-in [:base :state/transitions] conj 244 | {:transition/to (get-in states [:base-escape :state/name]) 245 | :transition/kind :ignore 246 | :transition/condition 247 | (fn [begin-results context] 248 | (and (not (seq begin-results)) 249 | (not (nil? (:state/last-event context)))))}) 250 | 251 | (and (:base-escape states) (= (get-in states [:base :state/contiguity]) :followed-by-any)) 252 | (update-in [:base :state/transitions] conj 253 | {:transition/to (get-in states [:base-escape :state/name]) 254 | :transition/kind :ignore 255 | :transition/condition 256 | (fn [begin-results context] 257 | (not (nil? (:state/last-event context))))}) 258 | 259 | (and (:base-escape states) (= (get-in states [:base-escape :state/contiguity]) :followed-by)) 260 | (update-in [:base-escape :state/transitions] conj 261 | {:transition/to (get-in states [:base-escape :state/name]) 262 | :transition/kind :ignore 263 | :transition/condition 264 | (fn [begin-results context] 265 | (not (seq begin-results)))}) 266 | 267 | (and (:base-escape states) (= (get-in states [:base-escape :state/contiguity]) :followed-by-any)) 268 | (update-in [:base-escape :state/transitions] conj 269 | {:transition/to (get-in states [:base-escape :state/name]) 270 | :transition/kind :ignore 271 | :transition/condition (constantly true)}) 272 | 273 | (and (:iterations-escape states) (= (get-in states [:iterations :state/contiguity]) :followed-by)) 274 | (update-in [:iterations :state/transitions] conj 275 | {:transition/to (get-in states [:iterations-escape :state/name]) 276 | :transition/kind :ignore 277 | :transition/condition 278 | (fn [remain-results context] 279 | (not (seq remain-results)))}) 280 | 281 | (and (:iterations-escape states) (= (get-in states [:iterations :state/contiguity]) :followed-by-any)) 282 | (update-in [:iterations :state/transitions] conj 283 | {:transition/to (get-in states [:iterations-escape :state/name]) 284 | :transition/kind :ignore 285 | :transition/condition (constantly true)}) 286 | 287 | (and (:iterations-escape states) (= (get-in states [:iterations-escape :state/contiguity]) :followed-by)) 288 | (update-in [:iterations-escape :state/transitions] conj 289 | {:transition/to (get-in states [:iterations-escape :state/name]) 290 | :transition/kind :ignore 291 | :transition/condition 292 | (fn [begin-results context] 293 | (not (seq begin-results)))}) 294 | 295 | (and (:iterations-escape states) (= (get-in states [:iterations-escape :state/contiguity]) :followed-by-any)) 296 | (update-in [:iterations-escape :state/transitions] conj 297 | {:transition/to (get-in states [:iterations-escape :state/name]) 298 | :transition/kind :ignore 299 | :transition/condition (constantly true)})))) 300 | 301 | (defn build-records [patterns] 302 | (reduce 303 | (fn [all pattern] 304 | (let [record (if (:pattern/repetition pattern) 305 | (->LoopingState pattern) 306 | (->SingletonState pattern))] 307 | (assoc all (:pattern/name pattern) {:record record}))) 308 | {} 309 | patterns)) 310 | 311 | (defn build-base-states [context patterns] 312 | (reduce 313 | (fn [all [state-name i]] 314 | (let [{:keys [record]} (get all state-name) 315 | starting-state? (zero? i)] 316 | (-> all 317 | (assoc-in [state-name :states] (states record starting-state?)) 318 | (assoc-in [state-name :starting-state?] starting-state?)))) 319 | context 320 | (map vector (map :pattern/name patterns) (range)))) 321 | 322 | (defn build-state-contiguity [context patterns] 323 | (reduce 324 | (fn [all state-name] 325 | (let [{:keys [record states]} (get all state-name) 326 | reified-states (add-contiguity record states)] 327 | (assoc-in all [state-name :states] reified-states))) 328 | context 329 | (map :pattern/name patterns))) 330 | 331 | (defn add-successor [context patterns] 332 | (reduce 333 | (fn [all [state successor]] 334 | (let [successor (or (:pattern/name successor) u/final-state-name)] 335 | (assoc-in all [(:pattern/name state) :successor] successor))) 336 | context 337 | (partition-all 2 1 patterns))) 338 | 339 | (defn add-begin-transitions [context patterns] 340 | (reduce 341 | (fn [all state-name] 342 | (let [{:keys [record states successor]} (get all state-name) 343 | reified-states (attach-begin-transitions record states successor)] 344 | (assoc-in all [state-name :states] reified-states))) 345 | context 346 | (map :pattern/name patterns))) 347 | 348 | (defn add-remain-transitions [context patterns] 349 | (reduce 350 | (fn [all state-name] 351 | (let [{:keys [record states]} (get all state-name) 352 | reified-states (attach-remain-transitions record states)] 353 | (assoc-in all [state-name :states] reified-states))) 354 | context 355 | (map :pattern/name patterns))) 356 | 357 | (defn add-proceed-transitions [context patterns] 358 | (reduce 359 | (fn [all state-name] 360 | (let [{:keys [record states successor]} (get all state-name) 361 | reified-states (attach-proceed-transitions record states successor)] 362 | (assoc-in all [state-name :states] reified-states))) 363 | context 364 | (map :pattern/name patterns))) 365 | 366 | (defn add-ignore-transitions [context patterns] 367 | (reduce 368 | (fn [all [state-name i]] 369 | (let [{:keys [record states starting-state? successor]} (get all state-name) 370 | reified-states (attach-ignore-transitions record states starting-state? successor)] 371 | (assoc-in all [state-name :states] reified-states))) 372 | context 373 | (map vector (map :pattern/name patterns) (range)))) 374 | 375 | (defn compute-acceleration [transitions successor] 376 | (let [targets (map (juxt :transition/to :transition/kind) transitions)] 377 | (boolean (some #{[u/final-state-name :proceed]} targets)))) 378 | 379 | (defn group-transitions [context patterns] 380 | (reduce 381 | (fn [all state-name] 382 | (let [{:keys [record states successor]} (get all state-name) 383 | reified-states 384 | (map 385 | (fn [state] 386 | (let [acceleration (compute-acceleration (:state/transitions state) successor)] 387 | (-> state 388 | (assoc :state/accelerate? acceleration) 389 | (update :state/transitions (partial group-by :transition/kind))))) 390 | (vals states))] 391 | (assoc-in all [state-name :states] reified-states))) 392 | context 393 | (map :pattern/name patterns))) 394 | 395 | (defn add-final-state [context] 396 | (let [state {:state/name u/final-state-name 397 | :state/kind :final}] 398 | (assoc-in context [(name u/final-state-name) :states] [state]))) 399 | 400 | (defn index-states [context] 401 | (reduce-kv 402 | (fn [all pattern-name {:keys [states]}] 403 | (reduce 404 | (fn [all* state] 405 | (assoc all* (:state/name state) state)) 406 | all 407 | states)) 408 | {} 409 | context)) 410 | 411 | (defn compile-nfa [{:keys [pattern-sequence/patterns pattern-sequence/within] :as ps}] 412 | (when-let [ed (s/explain-data :cep/pattern-sequence ps)] 413 | (throw (ex-info "Pattern definition failed to conform to spec." {:error-data ed}))) 414 | 415 | (v/validate-pattern-sequence! ps) 416 | 417 | (let [initial-state (:pattern/name (first patterns))] 418 | {:states (-> patterns 419 | (build-records) 420 | (build-base-states patterns) 421 | (build-state-contiguity patterns) 422 | (add-successor patterns) 423 | (add-begin-transitions patterns) 424 | (add-remain-transitions patterns) 425 | (add-proceed-transitions patterns) 426 | (add-ignore-transitions patterns) 427 | (group-transitions patterns) 428 | (add-final-state) 429 | (index-states)) 430 | :initial-state initial-state 431 | :current-state initial-state 432 | :match-period within})) 433 | -------------------------------------------------------------------------------- /src/metamorphic/dewey.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.dewey 2 | (:require [metamorphic.util :refer [conjv]])) 3 | 4 | (defn new-dewey-number 5 | ([] 6 | []) 7 | ([start] 8 | [start])) 9 | 10 | (defn increase 11 | ([dn] 12 | (increase dn 1)) 13 | ([dn k] 14 | (assert (pos? (count dn)) "Dewey number was empty.") 15 | (update dn (dec (count dn)) + k))) 16 | 17 | (defn add-stage 18 | ([dn] 19 | (conjv dn 0)) 20 | ([dn k] 21 | (conjv dn k))) 22 | 23 | (defn compatible? [a b] 24 | (cond (> (count a) (count b)) 25 | (= (take (count b) a) b) 26 | 27 | (= (count a) (count b)) 28 | (and (= (butlast a) (butlast b)) 29 | (>= (last a) (last b))) 30 | 31 | :else false)) 32 | -------------------------------------------------------------------------------- /src/metamorphic/match_buffer.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.match-buffer 2 | (:require [metamorphic.dewey :as d] 3 | [metamorphic.util :as u] 4 | [metamorphic.compiler :as c] 5 | #?(:cljs [goog.string :refer [format]]))) 6 | 7 | (defprotocol IPointer 8 | (inc-refs! [this stage n]) 9 | (dec-refs! [this stage]) 10 | (ref-count [this stage]) 11 | (referenced? [this])) 12 | 13 | (defprotocol ITraverse 14 | (text-history [this])) 15 | 16 | (defrecord Event [payload state-name pattern-name pointers refs] 17 | IPointer 18 | (inc-refs! [this stage n] 19 | (assert (pos? n)) 20 | (swap! refs update stage (fnil + 0) n)) 21 | 22 | (dec-refs! [this stage] 23 | (swap! refs update stage 24 | (fn [k] 25 | (let [n (dec (or k 0))] 26 | (if (neg? n) 0 n))))) 27 | 28 | (ref-count [this stage] 29 | (get @refs stage)) 30 | 31 | (referenced? [this] 32 | (not (zero? (reduce + 0 (vals @refs))))) 33 | 34 | ITraverse 35 | (text-history [this] 36 | (reduce-kv 37 | (fn [all pointer event] 38 | (let [line (format "%s => [%s] %s" pointer (.-pattern-name event) (:payload event))] 39 | (u/conjv all (str "\t" line)))) 40 | [(format "[%s] %s: " pattern-name payload)] 41 | pointers))) 42 | 43 | (defprotocol IStage 44 | (append [this event]) 45 | (delete [this event]) 46 | (events [this])) 47 | 48 | (defrecord MatchBufferStage [events] 49 | IStage 50 | (append [this event] 51 | (assoc this :events (conj events event))) 52 | 53 | (delete [this event] 54 | (update this :events disj event)) 55 | 56 | (events [this] 57 | events) 58 | 59 | Object 60 | (toString [this] 61 | (.toString events))) 62 | 63 | (defn event [payload state-name pattern-name pointers] 64 | (Event. payload state-name pattern-name pointers (atom {}))) 65 | 66 | (defn new-match-buffer [nfa] 67 | (let [states (disj (into #{} (keys (:states nfa))) u/final-state-name)] 68 | (reduce 69 | (fn [all state] 70 | (assoc all state (MatchBufferStage. #{}))) 71 | {} 72 | states))) 73 | 74 | (defn traverse-pointers 75 | "Traverses the pointers to obtain a linear event history." 76 | ([e dn] 77 | (traverse-pointers e dn [])) 78 | ([e dn result] 79 | (if e 80 | (let [result* (conj result (:payload e)) 81 | pointers (keys (:pointers e)) 82 | parent (first (into (vec (filter (fn [p] (= dn p)) pointers)) 83 | (vec (filter (fn [p] (d/compatible? dn p)) pointers))))] 84 | (if parent 85 | (recur (get (:pointers e) parent) parent result*) 86 | result*)) 87 | result))) 88 | 89 | (defn build-match-history 90 | "Traverses pointers to obtain a map of event history keyed by pattern name." 91 | ([e dn] 92 | (build-match-history e dn {})) 93 | ([e dn result] 94 | (if e 95 | (let [result* (update result (.-pattern-name e) u/conjv (.-payload e)) 96 | pointers (keys (:pointers e)) 97 | parent (first (filter (fn [p] (d/compatible? dn p)) pointers))] 98 | (if parent 99 | (recur (get (:pointers e) parent) parent result*) 100 | result*)) 101 | result))) 102 | 103 | (defn maybe-delete [mb event children] 104 | (if (and (not (referenced? event)) 105 | (every? zero? (map (fn [e] (ref-count e (:state-name event))) children))) 106 | (update mb (:state-name event) delete event) 107 | mb)) 108 | 109 | (defn prune-match [match-buffer event pointer] 110 | (if (nil? event) 111 | match-buffer 112 | (let [{:keys [pointers]} event] 113 | (if-let [next-pointer (first (filter (fn [p] (d/compatible? pointer p)) (keys pointers)))] 114 | (let [child (get-in event [:pointers next-pointer])] 115 | (dec-refs! child (:state-name event)) 116 | (recur (maybe-delete match-buffer event (vals pointers)) 117 | child 118 | next-pointer)) 119 | (maybe-delete match-buffer event (vals pointers)))))) 120 | -------------------------------------------------------------------------------- /src/metamorphic/runtime.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.runtime 2 | (:require [clojure.spec.alpha :as s] 3 | [metamorphic.compiler :as c] 4 | [metamorphic.util :as u] 5 | [metamorphic.dewey :as d] 6 | [metamorphic.match-buffer :as m])) 7 | 8 | (defn reset-transition-count [state] 9 | (-> state 10 | (assoc :state/n-outgoing-begins 0) 11 | (assoc :state/n-outgoing-remains 0) 12 | (assoc :state/n-outgoing-proceeds 0) 13 | (assoc :state/n-outgoing-ignores 0))) 14 | 15 | (defn update-transition-count [old-state new-state transition-kind] 16 | (let [ks [:state/n-outgoing-begins 17 | :state/n-outgoing-remains 18 | :state/n-outgoing-proceeds 19 | :state/n-outgoing-ignores] 20 | transitions (select-keys old-state ks) 21 | updated-transitions 22 | (case transition-kind 23 | :begin (update transitions :state/n-outgoing-begins inc) 24 | :remain (update transitions :state/n-outgoing-remains inc) 25 | :proceed (update transitions :state/n-outgoing-proceeds inc) 26 | :ignore (update transitions :state/n-outgoing-ignores inc))] 27 | (merge new-state updated-transitions))) 28 | 29 | (defn get-transitions [rt state-name] 30 | (get-in rt [:nfa :states state-name :state/transitions])) 31 | 32 | (defn prune-timed-out-runs [rt event-time] 33 | (let [match-period (get-in rt [:nfa :match-period])] 34 | (if (and match-period event-time) 35 | (reduce 36 | (fn [rt* pending-state] 37 | (if (> (- event-time (:state/init-time pending-state)) match-period) 38 | (if-let [tail (:state/last-event pending-state)] 39 | (let [partial-match (m/traverse-pointers tail (:state/version pending-state))] 40 | (-> rt* 41 | (update-in [:timed-out-matches] conj (reverse partial-match)) 42 | (update-in [:match-buffer] m/prune-match tail (:state/version pending-state)))) 43 | rt*) 44 | (update-in rt* [:pending-states] conj pending-state))) 45 | (assoc-in rt [:pending-states] []) 46 | (get-in rt [:pending-states])) 47 | rt))) 48 | 49 | (defmulti evaluate-transitions 50 | (fn [rt state segment] 51 | [(:state/kind state) (:state/contiguity state)])) 52 | 53 | (defn evaluate-singleton-transitions 54 | [{:keys [pattern-sequence] :as rt} {:state/keys [pattern last-transition last-event version] :as state} segment] 55 | (let [transitions (get-transitions rt (:state/name state)) 56 | {:keys [begin ignore proceed]} transitions 57 | context {:state/last-transition last-transition 58 | :state/last-event last-event} 59 | history (delay (m/build-match-history last-event version)) 60 | begin-transitions 61 | (filter 62 | (fn [t] 63 | ((:transition/condition t) segment history pattern-sequence pattern)) 64 | begin) 65 | ignore-transitions 66 | (filter 67 | (fn [t] 68 | ((:transition/condition t) begin-transitions context)) 69 | ignore) 70 | proceed-transitions 71 | (filter 72 | (fn [t] 73 | ((:transition/condition t) 74 | {:begin begin-transitions 75 | :ignore ignore-transitions} 76 | context)) 77 | proceed)] 78 | (reduce into [] [begin-transitions ignore-transitions proceed-transitions]))) 79 | 80 | (defn evaluate-looping-transitions 81 | [{:keys [pattern-sequence] :as rt} {:state/keys [pattern last-transition last-event version] :as state} segment] 82 | (let [transitions (get-transitions rt (:state/name state)) 83 | {:keys [remain ignore proceed]} transitions 84 | context {:state/last-transition last-transition 85 | :state/last-event last-event} 86 | history (delay (m/build-match-history last-event version)) 87 | remain-transitions 88 | (filter 89 | (fn [t] 90 | ((:transition/condition t) context segment history pattern-sequence pattern)) 91 | remain) 92 | ignore-transitions 93 | (filter 94 | (fn [t] 95 | ((:transition/condition t) remain-transitions context)) 96 | ignore) 97 | proceed-transitions 98 | (filter 99 | (fn [t] 100 | ((:transition/condition t) 101 | {:remain remain-transitions 102 | :ignore ignore-transitions} 103 | context)) 104 | proceed)] 105 | (reduce into [] [remain-transitions ignore-transitions proceed-transitions]))) 106 | 107 | (defmethod evaluate-transitions [:singleton :next] 108 | [rt state segment] 109 | (evaluate-singleton-transitions rt state segment)) 110 | 111 | (defmethod evaluate-transitions [:singleton :followed-by] 112 | [rt state segment] 113 | (evaluate-singleton-transitions rt state segment)) 114 | 115 | (defmethod evaluate-transitions [:singleton :followed-by-any] 116 | [rt state segment] 117 | (evaluate-singleton-transitions rt state segment)) 118 | 119 | (defmethod evaluate-transitions [:looping :next] 120 | [rt state segment] 121 | (evaluate-looping-transitions rt state segment)) 122 | 123 | (defmethod evaluate-transitions [:looping :followed-by] 124 | [rt state segment] 125 | (evaluate-looping-transitions rt state segment)) 126 | 127 | (defmethod evaluate-transitions [:looping :followed-by-any] 128 | [rt state segment] 129 | (evaluate-looping-transitions rt state segment)) 130 | 131 | (defn branching? [transitions] 132 | (let [to (map :transition/to transitions)] 133 | (apply not= to))) 134 | 135 | (defn build-manifests [rt state segment] 136 | (let [transitions (evaluate-transitions rt state segment)] 137 | (when (seq transitions) 138 | (let [split? (branching? transitions)] 139 | (map 140 | (fn [t] 141 | {:manifest/src-state state 142 | :manifest/transition t 143 | :manifest/branching? split?}) 144 | transitions))))) 145 | 146 | (defn edge-phase [state] 147 | (max 148 | 0 149 | (+ (max 0 (dec (:state/n-outgoing-begins state))) 150 | (:state/n-outgoing-remains state) 151 | (:state/n-outgoing-ignores state)))) 152 | 153 | (defprotocol ITransition 154 | (next-state [this rt]) 155 | (next-manifests [this rt segment]) 156 | (next-version [this rt dst-state])) 157 | 158 | (defrecord BeginTransition [manifest] 159 | ITransition 160 | (next-state [this rt] 161 | (let [{:manifest/keys [src-state transition]} manifest 162 | dst-state (get-in rt [:nfa :states (:transition/to transition)])] 163 | (update-transition-count src-state dst-state :begin))) 164 | 165 | (next-manifests [this rt segment] 166 | []) 167 | 168 | (next-version [this rt dst-state] 169 | (let [{:manifest/keys [src-state transition]} manifest] 170 | (if (= (:state/name src-state) (get-in rt [:nfa :initial-state])) 171 | (:state/version src-state) 172 | (let [phase (edge-phase dst-state)] 173 | (-> (:state/version src-state) 174 | (d/add-stage) 175 | (d/increase phase))))))) 176 | 177 | (defrecord RemainTransition [manifest] 178 | ITransition 179 | (next-state [this rt] 180 | (let [{:manifest/keys [src-state transition]} manifest 181 | dst-state (get-in rt [:nfa :states (:transition/to transition)])] 182 | (update-transition-count src-state dst-state :remain))) 183 | 184 | (next-manifests [this rt segment] 185 | []) 186 | 187 | (next-version [this rt dst-state] 188 | (let [{:manifest/keys [src-state branching?]} manifest] 189 | (cond (or branching? (not= (:state/last-transition src-state) :remain)) 190 | (let [phase (edge-phase dst-state)] 191 | (d/increase (d/add-stage (:state/version src-state)) phase)) 192 | 193 | branching? 194 | (let [phase (edge-phase dst-state)] 195 | (d/increase (:state/version src-state) phase)) 196 | 197 | :else 198 | (:state/version src-state))))) 199 | 200 | (defrecord IgnoreTransition [manifest] 201 | ITransition 202 | (next-state [this rt] 203 | (let [{:manifest/keys [src-state transition]} manifest 204 | dst-state (get-in rt [:nfa :states (:transition/to transition)]) 205 | chained-event (:state/last-event src-state) 206 | with-event (assoc dst-state :state/last-event chained-event)] 207 | (update-transition-count src-state with-event :ignore))) 208 | 209 | (next-manifests [this rt segment] 210 | []) 211 | 212 | (next-version [this rt dst-state] 213 | (get-in manifest [:manifest/src-state :state/version]))) 214 | 215 | (defrecord ProceedTransition [manifest] 216 | ITransition 217 | (next-state [this rt] 218 | (let [{:manifest/keys [src-state transition]} manifest 219 | dst-state (get-in rt [:nfa :states (:transition/to transition)])] 220 | (when (= (:state/name dst-state) u/final-state-name) 221 | (let [chained-event (:state/last-event src-state)] 222 | (-> src-state 223 | (update-transition-count dst-state :proceed) 224 | (assoc :state/last-event chained-event)))))) 225 | 226 | (next-manifests [this rt segment] 227 | (let [{:manifest/keys [src-state transition]} manifest 228 | next-state (get-in rt [:nfa :states (:transition/to transition)])] 229 | (when (not= (:state/name next-state) u/final-state-name) 230 | (let [chained-version (d/add-stage (:state/version src-state)) 231 | chained-event (:state/last-event src-state) 232 | chained-state (-> next-state 233 | (assoc :state/version chained-version) 234 | (assoc :state/last-event chained-event) 235 | (assoc :state/last-transition :proceed)) 236 | dst-state (update-transition-count src-state chained-state :proceed)] 237 | (build-manifests rt dst-state segment))))) 238 | 239 | (next-version [this rt dst-state] 240 | (let [{:manifest/keys [src-state]} manifest] 241 | (cond (and (= (:state/name dst-state) u/final-state-name) 242 | (= (:state/last-transition src-state) :remain)) 243 | (d/increase (:state/version src-state)) 244 | 245 | :else 246 | (d/add-stage (:state/version src-state)))))) 247 | 248 | (defn new-instance [nfa] 249 | {:match-buffer (m/new-match-buffer nfa) 250 | :next-root-version 1 251 | :pending-states []}) 252 | 253 | (defn initialize-runtime 254 | "Creates a new instance of the runtime for a pattern sequence. 255 | The runtime *is not* functionally pure and thus should not be shared 256 | across threads." 257 | ([pattern-sequence] 258 | (initialize-runtime pattern-sequence {})) 259 | ([pattern-sequence opts] 260 | (let [nfa (c/compile-nfa pattern-sequence)] 261 | (merge 262 | opts 263 | {:pattern-sequence pattern-sequence 264 | :nfa nfa 265 | :match-buffer (m/new-match-buffer nfa) 266 | :next-root-version 1 267 | :pending-states [] 268 | :matches [] 269 | :timed-out-matches []})))) 270 | 271 | (defn initialize-transient-values [rt] 272 | (-> rt 273 | (assoc :processing-queue []) 274 | (assoc :patches []) 275 | (assoc :pointers {}) 276 | (assoc :event-mapping {}) 277 | (assoc :iteration-matches []))) 278 | 279 | (defn add-new-starting-state [rt event-time id] 280 | (s/assert :cep/runtime-iteration rt) 281 | (let [initial-state (get-in rt [:nfa :initial-state]) 282 | state (get-in rt [:nfa :states initial-state]) 283 | reified-state 284 | (cond-> state 285 | true (dissoc :state/transitions) 286 | true (assoc :state/version (d/new-dewey-number (get-in rt [:next-root-version]))) 287 | true (reset-transition-count) 288 | event-time (assoc :state/init-time event-time) 289 | (:trace? rt) (assoc :state/id (u/random-uuid)) 290 | (:trace? rt) (with-meta {:state/persistent-id id}))] 291 | (-> rt 292 | (update-in [:pending-states] conj reified-state) 293 | (update-in [:next-root-version] inc)))) 294 | 295 | (defn load-processing-queue [rt segment] 296 | (s/assert :cep/runtime-iteration rt) 297 | (let [queue 298 | (reduce 299 | (fn [all state] 300 | (let [manifests (build-manifests rt state segment)] 301 | (into all manifests))) 302 | (get-in rt [:processing-queue]) 303 | (get-in rt [:pending-states]))] 304 | (assoc-in rt [:processing-queue] queue))) 305 | 306 | (defn build-transition-record [manifest] 307 | (case (get-in manifest [:manifest/transition :transition/kind]) 308 | :begin (->BeginTransition manifest) 309 | :remain (->RemainTransition manifest) 310 | :proceed (->ProceedTransition manifest) 311 | :ignore (->IgnoreTransition manifest))) 312 | 313 | (defn evaluate-processing-queue [{:keys [nfa processing-queue patches] :as rt} segment] 314 | (s/assert :cep/runtime-iteration rt) 315 | (loop [manifests processing-queue 316 | patches []] 317 | (if (not (seq manifests)) 318 | (-> rt 319 | (assoc-in [:patches] patches) 320 | (assoc-in [:processing-queue] manifests)) 321 | (let [next-manifest (first manifests) 322 | transition-record (build-transition-record next-manifest) 323 | dst-state (next-state transition-record rt) 324 | follow-on-manifests (next-manifests transition-record rt segment)] 325 | (recur (if follow-on-manifests 326 | (into (rest manifests) follow-on-manifests) 327 | (rest manifests)) 328 | (if dst-state 329 | (let [version (next-version transition-record rt dst-state)] 330 | (conj patches 331 | (-> next-manifest 332 | (assoc :manifest/dst-state dst-state) 333 | (assoc :manifest/version version)))) 334 | patches)))))) 335 | 336 | (defn collapse-pointers [rt] 337 | (s/assert :cep/runtime-iteration rt) 338 | (let [initial (get-in rt [:nfa :initial-state])] 339 | (reduce 340 | (fn [rt* {:keys [manifest/src-state manifest/transition manifest/version]}] 341 | (let [state-name (:state/name src-state)] 342 | (cond (= (:transition/kind transition) :ignore) 343 | rt* 344 | 345 | (not (:state/last-event src-state)) 346 | (update-in rt* [:pointers state-name] (fn [pointers] (or pointers {}))) 347 | 348 | :else 349 | (let [target-event (:state/last-event src-state)] 350 | (when-let [e (get-in rt* [:pointers state-name version :target-event])] 351 | (assert (= e target-event) (str "Pointers collapsed incorrectly, conflict at: " state-name " / " version "."))) 352 | 353 | (-> rt* 354 | (assoc-in [:pointers state-name version :target-event] target-event) 355 | (update-in [:pointers state-name version :n] (fn [n] ((fnil inc 0) n)))))))) 356 | rt 357 | (get-in rt [:patches])))) 358 | 359 | (defn unwrap-pointers [pointers] 360 | (reduce-kv 361 | (fn [all k {:keys [target-event]}] 362 | (assoc all k target-event)) 363 | {} 364 | pointers)) 365 | 366 | (defn update-match-buffer [rt segment] 367 | (s/assert :cep/runtime-iteration rt) 368 | (reduce-kv 369 | (fn [rt* state-name requested-pointers] 370 | (let [pattern-name (get-in rt* [:nfa :states state-name :state/pattern :pattern/name]) 371 | pointers (unwrap-pointers requested-pointers) 372 | event (m/event segment state-name pattern-name pointers)] 373 | (doseq [{:keys [target-event n]} (vals requested-pointers)] 374 | (m/inc-refs! target-event state-name n)) 375 | (-> rt* 376 | (update-in [:match-buffer state-name] m/append event) 377 | (assoc-in [:event-mapping state-name] event)))) 378 | rt 379 | (get-in rt [:pointers]))) 380 | 381 | (defn update-last-event [rt] 382 | (s/assert :cep/runtime-iteration rt) 383 | (reduce 384 | (fn [rt* [patch k]] 385 | (let [from-name (get-in patch [:manifest/src-state :state/name]) 386 | event (get (get-in rt [:event-mapping]) from-name)] 387 | (if (and event 388 | (not (some #{:ignore} #{(get-in patch [:manifest/transition :transition/kind])}))) 389 | (assoc-in rt* [:patches k :manifest/dst-state :state/last-event] event) 390 | rt*))) 391 | rt 392 | (map vector (get-in rt [:patches]) (range)))) 393 | 394 | (defn apply-patches [rt] 395 | (s/assert :cep/runtime-iteration rt) 396 | (reduce 397 | (fn [rt* patch] 398 | (let [init-time (get-in patch [:manifest/src-state :state/init-time]) 399 | kind (get-in patch [:manifest/transition :transition/kind]) 400 | state (-> (:manifest/dst-state patch) 401 | (assoc :state/version (:manifest/version patch)) 402 | (assoc :state/last-transition kind) 403 | (assoc :state/init-time init-time) 404 | (dissoc :state/transitions)) 405 | reified-state (cond-> state 406 | (:trace? rt) 407 | (assoc :state/previous-id (get-in patch [:manifest/src-state :state/id])) 408 | 409 | (:trace? rt) 410 | (assoc :state/id (u/random-uuid)) 411 | 412 | (:trace? rt) 413 | (with-meta (select-keys (meta (:manifest/src-state patch)) [:state/persistent-id])))] 414 | (update-in rt* [:pending-states] conj reified-state))) 415 | (assoc-in rt [:pending-states] []) 416 | (get-in rt [:patches]))) 417 | 418 | (defn trace-match [rt state last-event version] 419 | (cond-> (reverse (m/traverse-pointers last-event version)) 420 | (:trace? rt) 421 | (with-meta (select-keys state [:state/previous-id])))) 422 | 423 | (defn find-accelerated-matches [rt] 424 | (s/assert :cep/runtime-iteration rt) 425 | (reduce 426 | (fn [rt* state] 427 | (if (and (:state/accelerate? state) 428 | (not= (:state/name state) (get-in state [:state/last-event :state-name]))) 429 | (let [{:state/keys [last-event version]} state 430 | match (trace-match rt* state last-event version)] 431 | (-> rt* 432 | (update :iteration-matches conj match) 433 | (update :match-buffer m/prune-match last-event version) 434 | (update :pending-states conj state))) 435 | (update rt* :pending-states conj state))) 436 | (assoc rt :pending-states []) 437 | (:pending-states rt))) 438 | 439 | (defn find-final-matches [rt] 440 | (s/assert :cep/runtime-iteration rt) 441 | (reduce 442 | (fn [rt* state] 443 | (if (= (:state/name state) u/final-state-name) 444 | (let [{:state/keys [last-event version]} state 445 | match (trace-match rt* state last-event version)] 446 | (-> rt* 447 | (update :iteration-matches conj match) 448 | (update :match-buffer m/prune-match last-event version))) 449 | (update rt* :pending-states conj state))) 450 | (assoc rt :pending-states []) 451 | (:pending-states rt))) 452 | 453 | (defn merge-matches [rt] 454 | (s/assert :cep/runtime-iteration rt) 455 | (update rt :matches into (distinct (:iteration-matches rt)))) 456 | 457 | (defn remove-transient-values [rt] 458 | (s/assert :cep/runtime-iteration rt) 459 | (-> rt 460 | (dissoc :processing-queue) 461 | (dissoc :patches) 462 | (dissoc :pointers) 463 | (dissoc :event-mapping) 464 | (dissoc :iteration-matches))) 465 | 466 | (defn evaluate-event 467 | ([rt segment] 468 | (evaluate-event rt segment {})) 469 | ([rt segment {:keys [event-time id]}] 470 | (s/assert :cep/runtime rt) 471 | (let [result (-> rt 472 | (initialize-transient-values) 473 | (add-new-starting-state event-time id) 474 | (prune-timed-out-runs event-time) 475 | (load-processing-queue segment) 476 | (evaluate-processing-queue segment) 477 | (collapse-pointers) 478 | (update-match-buffer segment) 479 | (update-last-event) 480 | (apply-patches) 481 | (find-accelerated-matches) 482 | (find-final-matches) 483 | (merge-matches) 484 | (remove-transient-values))] 485 | (s/assert :cep/runtime result) 486 | result))) 487 | -------------------------------------------------------------------------------- /src/metamorphic/spec.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.spec 2 | (:require [clojure.spec.alpha :as s])) 3 | 4 | (s/def :cep/pattern-sequence 5 | (s/keys :req [:pattern-sequence/name 6 | :pattern-sequence/patterns] 7 | :opt [:pattern-sequence/within])) 8 | 9 | (s/def :pattern-sequence/name string?) 10 | 11 | (s/def :pattern-sequence/within (s/nilable nat-int?)) 12 | 13 | (s/def :pattern-sequence/patterns 14 | (s/coll-of :pattern-sequence/pattern :kind vector? :min 1)) 15 | 16 | (defmulti pattern-kind :pattern/kind) 17 | 18 | (defmethod pattern-kind :singleton 19 | [_] 20 | (s/keys :req [:pattern/name 21 | :pattern/kind 22 | :pattern/predicate 23 | :pattern/contiguity] 24 | :opt [:pattern/optional?])) 25 | 26 | (defmethod pattern-kind :looping 27 | [_] 28 | (s/keys :req [:pattern/name 29 | :pattern/kind 30 | :pattern/predicate 31 | :pattern/contiguity 32 | :pattern/repetition] 33 | :opt [:pattern/optional? 34 | :pattern/looping-predicate 35 | :pattern/looping-contiguity])) 36 | 37 | (s/def :pattern-sequence/pattern 38 | (s/multi-spec pattern-kind :pattern/kind)) 39 | 40 | (s/def :pattern/name string?) 41 | 42 | (s/def :pattern/kind #{:singleton :looping}) 43 | 44 | (s/def :pattern/optional? boolean?) 45 | 46 | (s/def :pattern/predicate (s/or :kw keyword? :f fn?)) 47 | 48 | (s/def :pattern/looping-predicate (s/or :kw keyword? :f fn?)) 49 | 50 | (s/def :pattern/contiguity #{:next :followed-by :followed-by-any}) 51 | 52 | (s/def :pattern/looping-contiguity :pattern/contiguity) 53 | 54 | (s/def :pattern/repetition #{:one-or-more}) 55 | 56 | (s/def :state/name string?) 57 | 58 | (s/def :state/version 59 | (s/coll-of nat-int? :kind vector? :min-count 1)) 60 | 61 | (s/def :state/kind #{:singleton :looping :final}) 62 | 63 | (s/def :state/pattern :pattern-sequence/pattern) 64 | 65 | (s/def :state/contiguity :pattern/contiguity) 66 | 67 | (s/def :state/accelerate? boolean?) 68 | 69 | (s/def :state/id uuid?) 70 | 71 | (s/def :state/previous-id :state/id) 72 | 73 | (s/def :transition/to :state/name) 74 | 75 | (s/def :transition/kind #{:begin :remain :proceed :ignore}) 76 | 77 | (s/def :transition/condition (s/or :fn fn? :var var?)) 78 | 79 | (s/def :state/transition 80 | (s/keys :req [:transition/to 81 | :transition/kind] 82 | :opt [:transition/condition])) 83 | 84 | (s/def :state/transitions 85 | (s/map-of :transition/kind (s/coll-of :state/transition :kind vector?))) 86 | 87 | (s/def :state/last-transition :transition/kind) 88 | 89 | (s/def :cep-compiler/state 90 | (s/keys :req [:state/name 91 | :state/kind] 92 | :opt [:state/pattern 93 | :state/accelerate? 94 | :state/contiguity 95 | :state/transitions])) 96 | 97 | (s/def :cep-compiler/initial-state :state/name) 98 | 99 | (s/def :cep-compiler/current-state :state/name) 100 | 101 | (s/def :cep-compiler/match-period (s/nilable nat-int?)) 102 | 103 | (s/def :cep-compiler/states 104 | (s/map-of :state/name :cep-compiler/state)) 105 | 106 | (s/def :cep/nfa 107 | (s/keys :req-un [:cep-compiler/states 108 | :cep-compiler/initial-state 109 | :cep-compiler/current-state 110 | :cep-compiler/match-period])) 111 | 112 | (s/def :cep/next-root-version pos-int?) 113 | 114 | (s/def :cep/match-buffer 115 | (s/map-of :state/name record?)) 116 | 117 | (s/def :state/init-time (s/nilable nat-int?)) 118 | 119 | (s/def :state/n-outgoing-begins nat-int?) 120 | 121 | (s/def :state/n-outgoing-remains nat-int?) 122 | 123 | (s/def :state/n-outgoing-proceeds nat-int?) 124 | 125 | (s/def :state/n-outgoing-ignores nat-int?) 126 | 127 | (s/def :cep/pending-state 128 | (s/keys :req [:state/name 129 | :state/kind] 130 | :opt [:state/init-time 131 | :state/version 132 | :state/last-transition 133 | :state/pattern 134 | :state/contiguity 135 | :state/accelerate? 136 | :state/id 137 | :state/previous-id 138 | :state/n-outgoing-begins 139 | :state/n-outgoing-remains 140 | :state/n-outgoing-proceeds 141 | :state/n-outgoing-ignores])) 142 | 143 | (s/def :cep/pending-states 144 | (s/coll-of :cep/pending-state :kind sequential?)) 145 | 146 | (s/def :cep/matches 147 | (s/coll-of sequential?)) 148 | 149 | (s/def :cep/timed-out-matches 150 | (s/coll-of sequential?)) 151 | 152 | (s/def :manifest/src-state :cep/pending-state) 153 | 154 | (s/def :manifest/dst-state :cep/pending-state) 155 | 156 | (s/def :manifest/transition :state/transition) 157 | 158 | (s/def :manifest/version :state/version) 159 | 160 | (s/def :manifest/branching? boolean?) 161 | 162 | (s/def :cep/transition-manifest 163 | (s/keys :req [:manifest/src-state 164 | :manifest/transition 165 | :manifest/branching?])) 166 | 167 | (s/def :cep/patch 168 | (s/keys :req [:manifest/src-state 169 | :manifest/dst-state 170 | :manifest/version 171 | :manifest/transition 172 | :manifest/branching?])) 173 | 174 | (s/def :cep/patches 175 | (s/coll-of :cep/patch :kind sequential?)) 176 | 177 | (s/def :cep/processing-queue 178 | (s/coll-of :cep/transition-manifest :kind sequential?)) 179 | 180 | (s/def :cep/runtime 181 | (s/keys :req-un [:cep/pattern-sequence 182 | :cep/nfa 183 | :cep/match-buffer 184 | :cep/next-root-version 185 | :cep/pending-states 186 | :cep/matches 187 | :cep/timed-out-matches])) 188 | 189 | (s/def :cep/runtime-iteration 190 | (s/keys :req-un [:cep/pattern-sequence 191 | :cep/nfa 192 | :cep/match-buffer 193 | :cep/next-root-version 194 | :cep/pending-states 195 | :cep/patches 196 | :cep/matches 197 | :cep/timed-out-matches 198 | :cep/processing-queue])) 199 | -------------------------------------------------------------------------------- /src/metamorphic/trace.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.trace 2 | (:require [metamorphic.runtime :as rt] 3 | [metamorphic.match-buffer :as m] 4 | [metamorphic.util :as u])) 5 | 6 | (defn update-history [history pending-states head] 7 | (reduce 8 | (fn [history* {:state/keys [previous-id id] :as pending-state}] 9 | (cond-> history* 10 | (not (get history* previous-id)) 11 | (assoc previous-id [{:action :start :name head}]) 12 | 13 | true 14 | ((fn [h] (assoc h id (get h previous-id)))) 15 | 16 | true 17 | ((fn [h] (update h id conj {:name (:pattern/name (:state/pattern pending-state)) 18 | :transition (:state/last-transition pending-state)}))))) 19 | history 20 | pending-states)) 21 | 22 | (defn iterate-events [runtime events head] 23 | (reduce 24 | (fn [all [event k]] 25 | (let [result (rt/evaluate-event (:runtime all) event {:id k})] 26 | (-> all 27 | (assoc :history (update-history (:history all) (:pending-states result) head)) 28 | (assoc :runtime result)))) 29 | {:runtime runtime 30 | :history {}} 31 | (map vector events (range)))) 32 | 33 | (defn trace-match-history [pattern-sequence events] 34 | (let [runtime (rt/initialize-runtime pattern-sequence {:trace? true}) 35 | head (get-in pattern-sequence [:pattern-sequence/patterns 0 :pattern/name]) 36 | result (iterate-events runtime events head)] 37 | (map 38 | (fn [match] 39 | (let [previous-id (:state/previous-id (meta match))] 40 | {:match match 41 | :history (conj (get (:history result) previous-id) {:action :complete})})) 42 | (get-in result [:runtime :matches])))) 43 | 44 | (defn trace-partial-match-history [pattern-sequence events] 45 | (let [runtime (rt/initialize-runtime pattern-sequence {:trace? true}) 46 | head (get-in pattern-sequence [:pattern-sequence/patterns 0 :pattern/name]) 47 | result (iterate-events runtime events head) 48 | id->event (zipmap (range (count events)) events)] 49 | (reduce 50 | (fn [all state] 51 | (let [{:state/keys [last-event version]} state 52 | {:state/keys [persistent-id]} (meta state) 53 | partial-match (reverse (m/traverse-pointers last-event version))] 54 | (update all (get id->event persistent-id) u/conjv partial-match))) 55 | {} 56 | (get-in result [:runtime :pending-states])))) 57 | -------------------------------------------------------------------------------- /src/metamorphic/util.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.util 2 | #?(:clj (:import [java.util UUID]))) 3 | 4 | (def final-state-name "__metamorphic-final__") 5 | 6 | (defn conjv [xs x] 7 | ((fnil conj []) xs x)) 8 | 9 | (defn index-by [k xs] 10 | (zipmap (map k xs) xs)) 11 | 12 | (defn kw->fn [kw] 13 | #?(:clj 14 | (let [user-ns (symbol (namespace kw)) 15 | user-fn (symbol (name kw))] 16 | (or (ns-resolve user-ns user-fn) 17 | (throw (Exception.)))) 18 | :cljs 19 | (js/eval 20 | (str (munge-str (str (namespace kw))) 21 | "." 22 | (munge-str (str (name kw))))))) 23 | 24 | (defn resolve-fn [f] 25 | (cond (keyword? f) (kw->fn f) 26 | (fn? f) f 27 | :else (throw (ex-info "Unsupported function type." {})))) 28 | 29 | (defn invert-map-coll [m] 30 | (reduce-kv 31 | (fn [all k v] 32 | (update all v conjv k)) 33 | {} 34 | m)) 35 | 36 | (defn select-keys-by [m f] 37 | (reduce-kv 38 | (fn [all k v] 39 | (if (f v) 40 | (assoc all k v) 41 | all)) 42 | {} 43 | m)) 44 | 45 | (defn random-uuid [] 46 | #?(:clj (UUID/randomUUID) 47 | :cljs (cljs.core/random-uuid))) 48 | -------------------------------------------------------------------------------- /src/metamorphic/validation.cljc: -------------------------------------------------------------------------------- 1 | (ns metamorphic.validation 2 | (:require [clojure.string :as s] 3 | [metamorphic.util :as u])) 4 | 5 | (defn validate-pattern-names-unique! [patterns] 6 | (let [pattern-names (map :pattern/name patterns) 7 | freqs (frequencies pattern-names) 8 | dupes (u/select-keys-by freqs (fn [k] (> k 1)))] 9 | (when (seq dupes) 10 | (throw (ex-info (str "Pattern names must be unique. The following were used more than once: " (s/join ", " (keys dupes)) ".") 11 | dupes))))) 12 | 13 | (defn validate-final-name! [patterns] 14 | (let [pattern-names (map :pattern/name patterns) 15 | final? (some #{u/final-state-name} pattern-names)] 16 | (when final? 17 | (throw (ex-info (str "Cannot use " u/final-state-name " as a pattern name. It is reserved.") {}))))) 18 | 19 | (defn validate-pattern-sequence! [pattern-sequence] 20 | (validate-pattern-names-unique! (:pattern-sequence/patterns pattern-sequence)) 21 | (validate-final-name! (:pattern-sequence/patterns pattern-sequence))) 22 | -------------------------------------------------------------------------------- /src/metamorphic/viz.clj: -------------------------------------------------------------------------------- 1 | (ns metamorphic.viz 2 | (:require [metamorphic.util :as u] 3 | [rhizome.viz :as viz])) 4 | 5 | (defn visualize-match-buffer [match-buffer] 6 | (viz/view-graph 7 | (mapcat (fn [stage] (:events stage)) (vals match-buffer)) 8 | (fn [event] (distinct (vals (:pointers event)))) 9 | :node->descriptor (fn [event] {:label (format "%s @%s" (:payload event) (pr-str @(:refs event)))}) 10 | :edge->descriptor 11 | (fn [src dst] 12 | (let [xs (get (u/invert-map-coll (:pointers src)) dst)] 13 | {:label (pr-str (if (<= (count xs) 1) (first xs) xs))})) 14 | :node->cluster (fn [event] (:state-name event)) 15 | :cluster->descriptor (fn [cluster] {:label cluster}) 16 | :options {:dpi 55})) 17 | 18 | (defn visualize-nfa [nfa] 19 | (viz/view-graph 20 | (keys (:states nfa)) 21 | (fn [state] 22 | (->> (vals (get-in nfa [:states state :state/transitions])) 23 | (reduce into []) 24 | (map :transition/to) 25 | (into #{}))) 26 | :node->descriptor (fn [state] {:label state}) 27 | :edge->descriptor 28 | (fn [src dst] 29 | (let [transitions (reduce into [] (vals (get-in nfa [:states src :state/transitions]))) 30 | groups (group-by :transition/to transitions)] 31 | {:label (pr-str (map :transition/kind (get groups dst)))})) 32 | :options {:dpi 60})) 33 | -------------------------------------------------------------------------------- /test/metamorphic/api_test.clj: -------------------------------------------------------------------------------- 1 | (ns metamorphic.api-test 2 | (:refer-clojure :exclude [next]) 3 | (:require [clojure.test :as t] 4 | [clojure.spec.alpha :as s] 5 | [metamorphic.api :refer :all] 6 | [metamorphic.runtime :as rt] 7 | [metamorphic.viz :as v] 8 | [metamorphic.trace :as trace])) 9 | 10 | (s/check-asserts true) 11 | 12 | (t/deftest single-pattern 13 | (let [runtime (-> (new-pattern-sequence "test") 14 | (begin "start" (fn [event & context] (> (:x event) 5))) 15 | (rt/initialize-runtime)) 16 | events [{:x 3} {:x 5} {:x 6} {:x 4} {:x 8} {:x 30} {:x 2}] 17 | {:keys [matches match-buffer nfa]} (reduce rt/evaluate-event runtime events)] 18 | (t/is (= (frequencies [[{:x 6}] 19 | [{:x 8}] 20 | [{:x 30}]]) 21 | (frequencies matches))))) 22 | 23 | (t/deftest chain-two-patterns 24 | (let [runtime (-> (new-pattern-sequence "test") 25 | (begin "start" (fn [event & context] (> (:x event) 5))) 26 | (next "bigger" (fn [event & context] (> (:x event) 9))) 27 | (rt/initialize-runtime)) 28 | events [{:x 3} {:x 6} {:x 2} {:x 7} {:x 10} {:x 11} {:x 19} {:x 5}] 29 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 30 | (t/is (= (frequencies [[{:x 7} {:x 10}] 31 | [{:x 10} {:x 11}] 32 | [{:x 11} {:x 19}]]) 33 | (frequencies matches))))) 34 | 35 | (t/deftest chain-pairs 36 | (let [runtime (-> (new-pattern-sequence "test") 37 | (begin "start" (constantly true)) 38 | (followed-by "next" (constantly true)) 39 | (rt/initialize-runtime)) 40 | events [{:x "a"} {:x "b"} {:x "c"} {:x "d"} {:x "e"}] 41 | {:keys [matches match-buffer nfa]} (reduce rt/evaluate-event runtime events)] 42 | (t/is (= (frequencies [[{:x "a"} {:x "b"}] 43 | [{:x "b"} {:x "c"}] 44 | [{:x "c"} {:x "d"}] 45 | [{:x "d"} {:x "e"}]]) 46 | (frequencies matches))))) 47 | 48 | (t/deftest followed-by-looping 49 | (let [runtime (-> (new-pattern-sequence "test") 50 | (begin "start" (constantly true)) 51 | (followed-by "end" (constantly true) {:repetition :one-or-more}) 52 | (rt/initialize-runtime)) 53 | events [{:x "a"} {:x "b"} {:x "c"} {:x "d"} {:x "e"}] 54 | {:keys [matches match-buffer nfa]} (reduce rt/evaluate-event runtime events)] 55 | (t/is (= (frequencies [[{:x "a"} {:x "b"}] 56 | [{:x "a"} {:x "b"} {:x "c"}] 57 | [{:x "b"} {:x "c"}] 58 | [{:x "b"} {:x "c"} {:x "d"}] 59 | [{:x "a"} {:x "b"} {:x "c"} {:x "d"}] 60 | [{:x "c"} {:x "d"}] 61 | [{:x "c"} {:x "d"} {:x "e"}] 62 | [{:x "a"} {:x "b"} {:x "c"} {:x "d"} {:x "e"}] 63 | [{:x "b"} {:x "c"} {:x "d"} {:x "e"}] 64 | [{:x "d"} {:x "e"}]]) 65 | (frequencies matches))))) 66 | 67 | (t/deftest followed-by-any-contiguity 68 | (let [runtime (-> (new-pattern-sequence "test") 69 | (begin "start" (constantly true)) 70 | (followed-by-any "end" (constantly true)) 71 | (rt/initialize-runtime)) 72 | events [{:x "a"} {:x "b"} {:x "c"} {:x "d"} {:x "e"}] 73 | {:keys [matches nfa match-buffer nfa]} (reduce rt/evaluate-event runtime events)] 74 | (t/is (= (frequencies [[{:x "a"} {:x "b"}] 75 | [{:x "a"} {:x "c"}] 76 | [{:x "a"} {:x "d"}] 77 | [{:x "a"} {:x "e"}] 78 | [{:x "b"} {:x "c"}] 79 | [{:x "b"} {:x "d"}] 80 | [{:x "b"} {:x "e"}] 81 | [{:x "c"} {:x "d"}] 82 | [{:x "c"} {:x "e"}] 83 | [{:x "d"} {:x "e"}]]) 84 | (frequencies matches))))) 85 | 86 | (t/deftest basic-pattern 87 | (let [runtime (-> (new-pattern-sequence "test") 88 | (begin "start" (fn [event & _] (= (:x event) "start"))) 89 | (followed-by "middle" (fn [event & _] (= (:x event) "middle"))) 90 | (followed-by "end" (fn [event & _] (= (:x event) "end"))) 91 | (rt/initialize-runtime)) 92 | events [{:x "start"} {:x "middle"} {:x "end"}] 93 | {:keys [matches match-buffer]} (reduce rt/evaluate-event runtime events)] 94 | (t/is (= (frequencies [[{:x "start"} {:x "middle"} {:x "end"}]]) 95 | (frequencies matches))))) 96 | 97 | (t/deftest strict-contiguity 98 | (let [runtime (-> (new-pattern-sequence "test") 99 | (begin "start" (fn [event & _] (= (:x event) "a"))) 100 | (next "middle" (fn [event & _] (= (:x event) "b"))) 101 | (rt/initialize-runtime)) 102 | events [{:x "a"} {:x "b"}] 103 | {:keys [matches match-buffer]} (reduce rt/evaluate-event runtime events)] 104 | (t/is (= (frequencies [[{:x "a"} {:x "b"}]]) 105 | (frequencies matches))))) 106 | 107 | (t/deftest strict-contiguity-miss 108 | (let [runtime (-> (new-pattern-sequence "test") 109 | (begin "start" (fn [event & _] (= (:x event) "a"))) 110 | (next "middle" (fn [event & _] (= (:x event) "b"))) 111 | (rt/initialize-runtime)) 112 | events [{:x "a"} {:x "c"} {:x "b"}] 113 | {:keys [matches]} (reduce rt/evaluate-event runtime events)] 114 | (t/is (= [] matches)))) 115 | 116 | (t/deftest branching-pattern 117 | (let [runtime (-> (new-pattern-sequence "test") 118 | (begin "start" (fn [event & _] (= (:x event) "a"))) 119 | (followed-by-any "middle-1" (fn [event & _] (= (:x event) "b"))) 120 | (followed-by-any "middle-2" (fn [event & _] (= (:x event) "c"))) 121 | (followed-by-any "end" (fn [event & _] (= (:x event) "d"))) 122 | (rt/initialize-runtime)) 123 | events [{:x "a"} 124 | {:x "b" :id 1} 125 | {:x "b" :id 2} 126 | {:x "b" :id 3} 127 | {:x "c" :id 1} 128 | {:x "c" :id 2} 129 | {:x "d"}] 130 | {:keys [matches nfa match-buffer pending-states]} (reduce rt/evaluate-event runtime events)] 131 | (t/is (= (frequencies 132 | [[{:x "a"} {:x "b" :id 1} {:x "c" :id 1} {:x "d"}] 133 | [{:x "a"} {:x "b" :id 2} {:x "c" :id 1} {:x "d"}] 134 | [{:x "a"} {:x "b" :id 3} {:x "c" :id 1} {:x "d"}] 135 | [{:x "a"} {:x "b" :id 1} {:x "c" :id 2} {:x "d"}] 136 | [{:x "a"} {:x "b" :id 2} {:x "c" :id 2} {:x "d"}] 137 | [{:x "a"} {:x "b" :id 3} {:x "c" :id 2} {:x "d"}]]) 138 | (frequencies matches))))) 139 | 140 | (t/deftest optional-branching-pattern 141 | (let [runtime (-> (new-pattern-sequence "test") 142 | (begin "start" (fn [event & _] (= (:x event) "c"))) 143 | (followed-by-any "middle" (fn [event & _] (= (:x event) "a")) 144 | {:repetition :one-or-more 145 | :allow-combinations? true 146 | :optional? true}) 147 | (followed-by-any "end-1" (fn [event & _] (= (:x event) "b"))) 148 | (followed-by-any "end-2" (fn [event & _] (= (:x event) "d"))) 149 | (followed-by-any "end-3" (fn [event & _] (= (:x event) "e"))) 150 | (rt/initialize-runtime)) 151 | events [{:x "c"} 152 | {:x "a" :id 1} 153 | {:x "a" :id 2} 154 | {:x "a" :id 3} 155 | {:x "b"} 156 | {:x "d" :id 1} 157 | {:x "d" :id 2} 158 | {:x "e"}] 159 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 160 | (t/is 161 | (= (frequencies 162 | [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"} {:x "d" :id 2} {:x "e"}] 163 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"} {:x "d" :id 2} {:x "e"}] 164 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 3} {:x "b"} {:x "d" :id 2} {:x "e"}] 165 | [{:x "c"} {:x "a" :id 2} {:x "a" :id 3} {:x "b"} {:x "d" :id 2} {:x "e"}] 166 | [{:x "c"} {:x "a" :id 1} {:x "b"} {:x "d" :id 2} {:x "e"}] 167 | [{:x "c"} {:x "a" :id 2} {:x "b"} {:x "d" :id 2} {:x "e"}] 168 | [{:x "c"} {:x "a" :id 3} {:x "b"} {:x "d" :id 2} {:x "e"}] 169 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"} {:x "d" :id 1} {:x "e"}] 170 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"} {:x "d" :id 1} {:x "e"}] 171 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 3} {:x "b"} {:x "d" :id 1} {:x "e"}] 172 | [{:x "c"} {:x "a" :id 2} {:x "a" :id 3} {:x "b"} {:x "d" :id 1} {:x "e"}] 173 | [{:x "c"} {:x "a" :id 1} {:x "b"} {:x "d" :id 1} {:x "e"}] 174 | [{:x "c"} {:x "a" :id 2} {:x "b"} {:x "d" :id 1} {:x "e"}] 175 | [{:x "c"} {:x "a" :id 3} {:x "b"} {:x "d" :id 1} {:x "e"}] 176 | [{:x "c"} {:x "b"} {:x "d" :id 1} {:x "e"}] 177 | [{:x "c"} {:x "b"} {:x "d" :id 2} {:x "e"}]]) 178 | (frequencies matches))))) 179 | 180 | (t/deftest zero-or-more 181 | (let [runtime (-> (new-pattern-sequence "test") 182 | (begin "start" (fn [event & _] (= (:x event) "c"))) 183 | (followed-by-any "middle" (fn [event & _] (= (:x event) "a")) 184 | {:repetition :one-or-more 185 | :allow-combinations? true 186 | :optional? true}) 187 | (followed-by-any "end" (fn [event & _] (= (:x event) "b"))) 188 | (rt/initialize-runtime)) 189 | events [{:x "c"} 190 | {:x "a" :id 1} 191 | {:x "a" :id 2} 192 | {:x "b"}] 193 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 194 | (t/is 195 | (= (frequencies [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 196 | [{:x "c"} {:x "a" :id 1} {:x "b"}] 197 | [{:x "c"} {:x "a" :id 2} {:x "b"}] 198 | [{:x "c"} {:x "b"}]]) 199 | (frequencies matches))))) 200 | 201 | (t/deftest begin-zero-or-more 202 | (let [runtime (-> (new-pattern-sequence "test") 203 | (begin "start" (fn [event & _] (= (:x event) "a")) 204 | {:repetition :one-or-more 205 | :optional? true}) 206 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 207 | (rt/initialize-runtime)) 208 | events [{:x "a" :id 1} 209 | {:x "a" :id 2} 210 | {:x "a" :id 3} 211 | {:x "b"}] 212 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 213 | (t/is 214 | (= (frequencies 215 | [[{:x "b"}] 216 | [{:x "a" :id 1} {:x "b"}] 217 | [{:x "a" :id 2} {:x "b"}] 218 | [{:x "a" :id 3} {:x "b"}] 219 | [{:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 220 | [{:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 221 | [{:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}]]) 222 | (frequencies matches))))) 223 | 224 | (t/deftest zero-or-more-repeated 225 | (let [runtime (-> (new-pattern-sequence "test") 226 | (begin "start" (fn [event & _] (= (:x event) "c"))) 227 | (followed-by-any "middle-1" (fn [event & _] (= (:x event) "a")) 228 | {:repetition :one-or-more 229 | :allow-combinations? true 230 | :optional? true}) 231 | (followed-by "middle-2" (fn [event & _] (= (:x event) "d")) 232 | {:repetition :one-or-more 233 | :allow-combinations? true 234 | :optional? true}) 235 | (followed-by "end" (fn [event & _] (= (:x event) "e"))) 236 | (rt/initialize-runtime)) 237 | events [{:x "c"} 238 | {:x "a"} 239 | {:x "d" :id 1} 240 | {:x "d" :id 2} 241 | {:x "e"}] 242 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 243 | (t/is 244 | (= (frequencies 245 | [[{:x "c"} {:x "a"} {:x "d" :id 1} {:x "d" :id 2} {:x "e"}] 246 | [{:x "c"} {:x "a"} {:x "d" :id 1} {:x "e"}] 247 | [{:x "c"} {:x "d" :id 1} {:x "d" :id 2} {:x "e"}] 248 | [{:x "c"} {:x "d" :id 1} {:x "e"}] 249 | [{:x "c"} {:x "a"} {:x "e"}] 250 | [{:x "c"} {:x "e"}]]) 251 | (frequencies matches))))) 252 | 253 | (t/deftest zero-or-more-post-branching 254 | (let [runtime (-> (new-pattern-sequence "test") 255 | (begin "start" (fn [event & _] (= (:x event) "c"))) 256 | (followed-by-any "branching" (fn [event & _] (= (:x event) "a"))) 257 | (followed-by-any "merging" (fn [event & _] (= (:x event) "f"))) 258 | (followed-by-any "looping" (fn [event & _] (= (:x event) "d")) 259 | {:repetition :one-or-more 260 | :allow-combinations? true 261 | :optional? true}) 262 | (followed-by "end" (fn [event & _] (= (:x event) "e"))) 263 | (rt/initialize-runtime)) 264 | events [{:x "c"} 265 | {:x "a" :id 1} 266 | {:x "a" :id 2} 267 | {:x "f"} 268 | {:x "d" :id 1} 269 | {:x "d" :id 2} 270 | {:x "e"}] 271 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 272 | (t/is 273 | (= 274 | (frequencies 275 | [[{:x "c"} {:x "a" :id 1} {:x "f"} {:x "e"}] 276 | [{:x "c"} {:x "a" :id 1} {:x "f"} {:x "d" :id 1} {:x "e"}] 277 | [{:x "c"} {:x "a" :id 1} {:x "f"} {:x "d" :id 2} {:x "e"}] 278 | [{:x "c"} {:x "a" :id 1} {:x "f"} {:x "d" :id 1} {:x "d" :id 2} {:x "e"}] 279 | [{:x "c"} {:x "a" :id 2} {:x "f"} {:x "e"}] 280 | [{:x "c"} {:x "a" :id 2} {:x "f"} {:x "d" :id 1} {:x "e"}] 281 | [{:x "c"} {:x "a" :id 2} {:x "f"} {:x "d" :id 2} {:x "e"}] 282 | [{:x "c"} {:x "a" :id 2} {:x "f"} {:x "d" :id 1} {:x "d" :id 2} {:x "e"}]]) 283 | (frequencies matches))))) 284 | 285 | (t/deftest next-with-optional-no-results 286 | (let [runtime (-> (new-pattern-sequence "test") 287 | (begin "start" (fn [event & _] (= (:x event) "d"))) 288 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 289 | {:repetition :one-or-more 290 | :optional? true}) 291 | (next "end" (fn [event & _] (= (:x event) "b"))) 292 | (rt/initialize-runtime)) 293 | events [{:x "d"} 294 | {:x "a" :id 1} 295 | {:x "a" :id 2} 296 | {:x "c"} 297 | {:x "b"}] 298 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 299 | (t/is (= [] matches)))) 300 | 301 | (t/deftest eager-zero-or-more 302 | (let [runtime (-> (new-pattern-sequence "test") 303 | (begin "start" (fn [event & _] (= (:x event) "c"))) 304 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 305 | {:repetition :one-or-more 306 | :optional? true}) 307 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 308 | (rt/initialize-runtime)) 309 | events [{:x "c"} 310 | {:x "a" :id 1} 311 | {:x "a" :id 2} 312 | {:x "a" :id 3} 313 | {:x "b"}] 314 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 315 | (t/is (= (frequencies 316 | [[{:x "c"} {:x "b"}] 317 | [{:x "c"} {:x "a" :id 1} {:x "b"}] 318 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 319 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}]]) 320 | (frequencies matches))))) 321 | 322 | (t/deftest begin-at-least-one 323 | (let [runtime (-> (new-pattern-sequence "test") 324 | (begin "start" (fn [event & _] (= (:x event) "a")) 325 | {:repetition :one-or-more 326 | :allow-combinations? true}) 327 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 328 | (rt/initialize-runtime)) 329 | events [{:x "a" :id 1} 330 | {:x "a" :id 2} 331 | {:x "a" :id 3} 332 | {:x "b"}] 333 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 334 | (t/is 335 | (= (frequencies 336 | [[{:x "a" :id 1} {:x "b"}] 337 | [{:x "a" :id 2} {:x "b"}] 338 | [{:x "a" :id 3} {:x "b"}] 339 | [{:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 340 | [{:x "a" :id 1} {:x "a" :id 3} {:x "b"}] 341 | [{:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 342 | [{:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}]]) 343 | (frequencies matches))))) 344 | 345 | (t/deftest next-zero-or-more 346 | (let [runtime (-> (new-pattern-sequence "test") 347 | (begin "start" (fn [event & _] (= (:x event) "a"))) 348 | (next "middle" (fn [event & _] (= (:x event) "b")) 349 | {:repetition :one-or-more 350 | :consecutive? true 351 | :optional? true}) 352 | (followed-by "end" (fn [event & _] (= (:x event) "c"))) 353 | (rt/initialize-runtime)) 354 | events [{:x "a"} 355 | {:x "d"} 356 | {:x "b" :id 1} 357 | {:x "b" :id 2} 358 | {:x "b" :id 3} 359 | {:x "c"}] 360 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 361 | (t/is (= (frequencies 362 | [[{:x "a"} {:x "c"}]]) 363 | (frequencies matches))))) 364 | 365 | (t/deftest at-least-one 366 | (let [runtime (-> (new-pattern-sequence "test") 367 | (begin "start" (fn [event & _] (= (:x event) "c"))) 368 | (followed-by-any "middle" (fn [event & _] (= (:x event) "a")) 369 | {:repetition :one-or-more 370 | :allow-combinations? true}) 371 | (followed-by-any "end" (fn [event & _] (= (:x event) "b"))) 372 | (rt/initialize-runtime)) 373 | events [{:x "c"} 374 | {:x "a" :id 1} 375 | {:x "a" :id 2} 376 | {:x "b"}] 377 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 378 | (t/is 379 | (= (frequencies 380 | [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 381 | [{:x "c"} {:x "a" :id 1} {:x "b"}] 382 | [{:x "c"} {:x "a" :id 2} {:x "b"}]]) 383 | (frequencies matches))))) 384 | 385 | (t/deftest at-least-one-eager 386 | (let [runtime (-> (new-pattern-sequence "test") 387 | (begin "start" (fn [event & _] (= (:x event) "c"))) 388 | (followed-by-any "middle" (fn [event & _] (= (:x event) "a")) 389 | {:repetition :one-or-more}) 390 | (followed-by-any "end" (fn [event & _] (= (:x event) "b"))) 391 | (rt/initialize-runtime)) 392 | events [{:x "c"} 393 | {:x "a" :id 1} 394 | {:x "a" :id 2} 395 | {:x "a" :id 3} 396 | {:x "b"}] 397 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 398 | (t/is (= (frequencies 399 | [[{:x "c"} {:x "a" :id 1} {:x "b"}] 400 | [{:x "c"} {:x "a" :id 2} {:x "b"}] 401 | [{:x "c"} {:x "a" :id 3} {:x "b"}] 402 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 403 | [{:x "c"} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 404 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}]]) 405 | (frequencies matches))))) 406 | 407 | (t/deftest test-optional 408 | (let [runtime (-> (new-pattern-sequence "test") 409 | (begin "start" (fn [event & _] (= (:x event) "c"))) 410 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 411 | {:optional? true}) 412 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 413 | (rt/initialize-runtime)) 414 | events [{:x "c"} 415 | {:x "a"} 416 | {:x "b"}] 417 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 418 | (t/is (= (frequencies 419 | [[{:x "c"} {:x "b"}] 420 | [{:x "c"} {:x "a"} {:x "b"}]]) 421 | (frequencies matches))))) 422 | 423 | (t/deftest start-and-end-with-zero-or-more 424 | (let [runtime (-> (new-pattern-sequence "test") 425 | (begin "start" (fn [event & _] (= (:x event) "a")) 426 | {:repetition :one-or-more 427 | :optional? true}) 428 | (rt/initialize-runtime)) 429 | events [{:x "c"} 430 | {:x "a" :id 1} 431 | {:x "a" :id 2} 432 | {:x "a" :id 3} 433 | {:x "d" :id 1} 434 | {:x "d" :id 2} 435 | {:x "d" :id 3}] 436 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 437 | (t/is 438 | (= (frequencies 439 | [[{:x "a" :id 1}] 440 | [{:x "a" :id 2}] 441 | [{:x "a" :id 3}] 442 | [{:x "a" :id 1} {:x "a" :id 2}] 443 | [{:x "a" :id 2} {:x "a" :id 3}] 444 | [{:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3}]]) 445 | (frequencies matches))))) 446 | 447 | (t/deftest end-with-optional 448 | (let [runtime (-> (new-pattern-sequence "test") 449 | (begin "start" (fn [event & _] (= (:x event) "c"))) 450 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 451 | {:optional? true}) 452 | (rt/initialize-runtime)) 453 | events [{:x "c"} 454 | {:x "a"}] 455 | {:keys [matches nfa]} (reduce rt/evaluate-event runtime events)] 456 | (t/is 457 | (= (frequencies 458 | [[{:x "c"}] 459 | [{:x "c"} {:x "a"}]]) 460 | (frequencies matches))))) 461 | 462 | (t/deftest end-with-one-or-more 463 | (let [runtime (-> (new-pattern-sequence "test") 464 | (begin "start" (fn [event & _] (= (:x event) "c"))) 465 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 466 | {:repetition :one-or-more}) 467 | (rt/initialize-runtime)) 468 | events [{:x "c"} 469 | {:x "a" :id 1} 470 | {:x "a" :id 2} 471 | {:x "a" :id 3}] 472 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 473 | (t/is (= (frequencies 474 | [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3}] 475 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2}] 476 | [{:x "c"} {:x "a" :id 1}]]) 477 | (frequencies matches))))) 478 | 479 | (t/deftest end-with-zero-or-more 480 | (let [runtime (-> (new-pattern-sequence "test") 481 | (begin "start" (fn [event & _] (= (:x event) "c"))) 482 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 483 | {:repetition :one-or-more 484 | :optional? true}) 485 | (rt/initialize-runtime)) 486 | events [{:x "c"} 487 | {:x "a" :id 1} 488 | {:x "a" :id 2} 489 | {:x "a" :id 3}] 490 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 491 | (t/is 492 | (= (frequencies [[{:x "c"}] 493 | [{:x "c"} {:x "a" :id 1}] 494 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2}] 495 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3}]]) 496 | (frequencies matches))))) 497 | 498 | (t/deftest mixed-branching 499 | (let [runtime (-> (new-pattern-sequence "test") 500 | (begin "start" (fn [event & _] (= (:x event) "a"))) 501 | (followed-by-any "middle-1" (fn [event & _] (= (:x event) "b"))) 502 | (followed-by "middle-2" (fn [event & _] (= (:x event) "c"))) 503 | (followed-by-any "end" (fn [event & _] (= (:x event) "d"))) 504 | (rt/initialize-runtime)) 505 | events [{:x "a"} 506 | {:x "b" :id 1} 507 | {:x "b" :id 2} 508 | {:x "b" :id 3} 509 | {:x "c" :id 1} 510 | {:x "c" :id 2} 511 | {:x "d"}] 512 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 513 | (t/is 514 | (= (frequencies [[{:x "a"} {:x "b" :id 1} {:x "c" :id 1} {:x "d"}] 515 | [{:x "a"} {:x "b" :id 2} {:x "c" :id 1} {:x "d"}] 516 | [{:x "a"} {:x "b" :id 3} {:x "c" :id 1} {:x "d"}]]) 517 | (frequencies matches))))) 518 | 519 | (t/deftest branched-skip-till-next 520 | (let [runtime (-> (new-pattern-sequence "test") 521 | (begin "start" (fn [event & _] (= (:x event) "a"))) 522 | (followed-by "middle-1" (fn [event & _] (= (:x event) "b"))) 523 | (followed-by "middle-2" (fn [event & _] (= (:x event) "c"))) 524 | (followed-by-any "end" (fn [event & _] (= (:x event) "d"))) 525 | (rt/initialize-runtime)) 526 | events [{:x "a"} 527 | {:x "b" :id 1} 528 | {:x "b" :id 2} 529 | {:x "b" :id 3} 530 | {:x "c" :id 1} 531 | {:x "c" :id 2} 532 | {:x "d"}] 533 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 534 | (t/is 535 | (= (frequencies [[{:x "a"} {:x "b" :id 1} {:x "c" :id 1} {:x "d"}]]) 536 | (frequencies matches))))) 537 | 538 | (t/deftest strict-one-or-more 539 | (let [runtime (-> (new-pattern-sequence "test") 540 | (begin "start" (fn [event & _] (= (:x event) "c"))) 541 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 542 | {:repetition :one-or-more 543 | :consecutive? true}) 544 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 545 | (rt/initialize-runtime)) 546 | events [{:x "c"} 547 | {:x "d" :id 1} 548 | {:x "a" :id 1} 549 | {:x "a" :id 2} 550 | {:x "a" :id 3} 551 | {:x "d" :id 2} 552 | {:x "a" :id 4} 553 | {:x "b"}] 554 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 555 | (t/is 556 | (= (frequencies [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 557 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 558 | [{:x "c"} {:x "a" :id 1} {:x "b"}]]) 559 | (frequencies matches))))) 560 | 561 | (t/deftest skip-till-next-one-or-more 562 | (let [runtime (-> (new-pattern-sequence "test") 563 | (begin "start" (fn [event & _] (= (:x event) "c"))) 564 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 565 | {:repetition :one-or-more}) 566 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 567 | (rt/initialize-runtime)) 568 | events [{:x "c"} 569 | {:x "d" :id 1} 570 | {:x "a" :id 1} 571 | {:x "a" :id 2} 572 | {:x "a" :id 3} 573 | {:x "d" :id 2} 574 | {:x "a" :id 4} 575 | {:x "b"}] 576 | {:keys [matches nfa match-buffer pending-states]} (reduce rt/evaluate-event runtime events)] 577 | (t/is 578 | (= (frequencies [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "a" :id 4} {:x "b"}] 579 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 580 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 581 | [{:x "c"} {:x "a" :id 1} {:x "b"}]]) 582 | (frequencies matches))))) 583 | 584 | (t/deftest skip-till-any-one-or-more 585 | (let [runtime (-> (new-pattern-sequence "test") 586 | (begin "start" (fn [event & _] (= (:x event) "c"))) 587 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 588 | {:repetition :one-or-more 589 | :allow-combinations? true}) 590 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 591 | (rt/initialize-runtime)) 592 | events [{:x "c"} 593 | {:x "d" :id 1} 594 | {:x "a" :id 1} 595 | {:x "a" :id 2} 596 | {:x "a" :id 3} 597 | {:x "d" :id 2} 598 | {:x "a" :id 4} 599 | {:x "b"}] 600 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 601 | (t/is 602 | (= (frequencies 603 | [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "a" :id 4} {:x "b"}] 604 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 3} {:x "a" :id 4} {:x "b"}] 605 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 606 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 4} {:x "b"}] 607 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 608 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 3} {:x "b"}] 609 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 4} {:x "b"}] 610 | [{:x "c"} {:x "a" :id 1} {:x "b"}]]) 611 | (frequencies matches))))) 612 | 613 | (t/deftest strict-eager-zero-or-more 614 | (let [runtime (-> (new-pattern-sequence "test") 615 | (begin "start" (fn [event & _] (= (:x event) "c"))) 616 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 617 | {:repetition :one-or-more 618 | :consecutive? true 619 | :optional? true}) 620 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 621 | (rt/initialize-runtime)) 622 | events [{:x "c"} 623 | {:x "d" :id 1} 624 | {:x "a" :id 1} 625 | {:x "a" :id 2} 626 | {:x "a" :id 3} 627 | {:x "d" :id 2} 628 | {:x "a" :id 4} 629 | {:x "b"}] 630 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 631 | (t/is 632 | (= (frequencies [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 633 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 634 | [{:x "c"} {:x "a" :id 1} {:x "b"}] 635 | [{:x "c"} {:x "b"}]]) 636 | (frequencies matches))))) 637 | 638 | (t/deftest skip-till-any-zero-or-more 639 | (let [runtime (-> (new-pattern-sequence "test") 640 | (begin "start" (fn [event & _] (= (:x event) "c"))) 641 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 642 | {:repetition :one-or-more 643 | :allow-combinations? true 644 | :optional? true}) 645 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 646 | (rt/initialize-runtime)) 647 | events [{:x "c"} 648 | {:x "d" :id 1} 649 | {:x "a" :id 1} 650 | {:x "a" :id 2} 651 | {:x "a" :id 3} 652 | {:x "d" :id 2} 653 | {:x "a" :id 4} 654 | {:x "b"}] 655 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 656 | (t/is 657 | (= (frequencies [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "a" :id 4} {:x "b"}] 658 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 4} {:x "b"}] 659 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 3} {:x "a" :id 4} {:x "b"}] 660 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 4} {:x "b"}] 661 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 662 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 663 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 3} {:x "b"}] 664 | [{:x "c"} {:x "a" :id 1} {:x "b"}] 665 | [{:x "c"} {:x "b"}]]) 666 | (frequencies matches))))) 667 | 668 | (t/deftest skip-till-next-zero-or-more 669 | (let [runtime (-> (new-pattern-sequence "test") 670 | (begin "start" (fn [event & _] (= (:x event) "c"))) 671 | (followed-by "middle" (fn [event & _] (= (:x event) "a")) 672 | {:repetition :one-or-more 673 | :optional? true}) 674 | (followed-by "end" (fn [event & _] (= (:x event) "b"))) 675 | (rt/initialize-runtime)) 676 | events [{:x "c"} 677 | {:x "d" :id 1} 678 | {:x "a" :id 1} 679 | {:x "a" :id 2} 680 | {:x "a" :id 3} 681 | {:x "d" :id 2} 682 | {:x "a" :id 4} 683 | {:x "b"}] 684 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 685 | (t/is 686 | (= (frequencies [[{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "a" :id 4} {:x "b"}] 687 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "a" :id 3} {:x "b"}] 688 | [{:x "c"} {:x "a" :id 1} {:x "a" :id 2} {:x "b"}] 689 | [{:x "c"} {:x "a" :id 1} {:x "b"}] 690 | [{:x "c"} {:x "b"}]]) 691 | (frequencies matches))))) 692 | 693 | (t/deftest start-with-one-or-more-strict 694 | (let [runtime (-> (new-pattern-sequence "test") 695 | (begin "start" (fn [event & _] (= (:x event) "a")) 696 | {:repetition :one-or-more 697 | :consecutive? true}) 698 | (rt/initialize-runtime)) 699 | events [{:x "c" :id 1} 700 | {:x "a" :id 1} 701 | {:x "c" :id 2} 702 | {:x "a" :id 2} 703 | {:x "a" :id 3}] 704 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 705 | (t/is 706 | (= (frequencies [[{:x "a" :id 1}] 707 | [{:x "a" :id 2}] 708 | [{:x "a" :id 3}] 709 | [{:x "a" :id 2} {:x "a" :id 3}]]) 710 | (frequencies matches))))) 711 | 712 | (t/deftest start-with-zero-or-more-strict 713 | (let [runtime (-> (new-pattern-sequence "test") 714 | (begin "start" (fn [event & _] (= (:x event) "a")) 715 | {:repetition :one-or-more 716 | :consecutive? true 717 | :optional? true}) 718 | (rt/initialize-runtime)) 719 | events [{:x "c" :id 1} 720 | {:x "a" :id 1} 721 | {:x "c" :id 2} 722 | {:x "a" :id 2} 723 | {:x "a" :id 3}] 724 | {:keys [matches nfa match-buffer]} (reduce rt/evaluate-event runtime events)] 725 | (t/is 726 | (= (frequencies [[{:x "a" :id 1}] 727 | [{:x "a" :id 2}] 728 | [{:x "a" :id 3}] 729 | [{:x "a" :id 2} {:x "a" :id 3}]]) 730 | (frequencies matches))))) 731 | -------------------------------------------------------------------------------- /test/metamorphic/examples.clj: -------------------------------------------------------------------------------- 1 | (ns metamorphic.examples 2 | (:refer-clojure :exclude [next]) 3 | (:require [clojure.test :as t] 4 | [clojure.spec.alpha :as s] 5 | [metamorphic.api :refer :all] 6 | [metamorphic.runtime :as rt] 7 | [metamorphic.viz :as v]) 8 | (:import [java.util.concurrent TimeUnit])) 9 | 10 | (s/check-asserts true) 11 | 12 | (defn above-average? [event history pattern-sequence pattern] 13 | (> (:temperature event) 80)) 14 | 15 | (defn warning? [event history pattern-sequence pattern] 16 | (> (:temperature event) 90)) 17 | 18 | (defn dangerous? [event history pattern-sequence pattern] 19 | (> (:temperature event) 100)) 20 | 21 | (t/deftest strictly-rising-temperatures 22 | (let [temperature-pattern 23 | {:pattern-sequence/name "rising temperatures" 24 | :pattern-sequence/patterns 25 | [{:pattern/name "above average" 26 | :pattern/predicate ::above-average? 27 | :pattern/contiguity :next 28 | :pattern/kind :singleton} 29 | 30 | {:pattern/name "warning" 31 | :pattern/predicate ::warning? 32 | :pattern/contiguity :next 33 | :pattern/kind :singleton} 34 | 35 | {:pattern/name "danger" 36 | :pattern/predicate ::dangerous? 37 | :pattern/contiguity :next 38 | :pattern/kind :singleton}]} 39 | runtime (rt/initialize-runtime temperature-pattern) 40 | events [{:temperature 60} 41 | {:temperature 82} 42 | {:temperature 78} 43 | {:temperature 85} 44 | {:temperature 93} 45 | {:temperature 102} 46 | {:temperature 101} 47 | {:temperature 84} 48 | {:temperature 68}] 49 | {:keys [matches]} (reduce rt/evaluate-event runtime events)] 50 | (t/is (= [[{:temperature 85} {:temperature 93} {:temperature 102}] 51 | [{:temperature 93} {:temperature 102} {:temperature 101}]] 52 | matches)))) 53 | 54 | (t/deftest progressively-rising-temperatures 55 | (let [temperature-pattern 56 | {:pattern-sequence/name "rising temperatures" 57 | :pattern-sequence/patterns 58 | [{:pattern/name "above average" 59 | :pattern/predicate ::above-average? 60 | :pattern/contiguity :next 61 | :pattern/kind :singleton} 62 | 63 | {:pattern/name "warning" 64 | :pattern/predicate ::warning? 65 | :pattern/contiguity :followed-by 66 | :pattern/kind :singleton} 67 | 68 | {:pattern/name "danger" 69 | :pattern/predicate ::dangerous? 70 | :pattern/contiguity :followed-by 71 | :pattern/kind :singleton}]} 72 | runtime (rt/initialize-runtime temperature-pattern) 73 | events [{:temperature 60} 74 | {:temperature 82} 75 | {:temperature 78} 76 | {:temperature 85} 77 | {:temperature 93} 78 | {:temperature 102} 79 | {:temperature 101} 80 | {:temperature 84} 81 | {:temperature 68}] 82 | {:keys [matches]} (reduce rt/evaluate-event runtime events)] 83 | (t/is (= [[{:temperature 82} {:temperature 93} {:temperature 102}] 84 | [{:temperature 85} {:temperature 93} {:temperature 102}] 85 | [{:temperature 93} {:temperature 102} {:temperature 101}]] 86 | matches)))) 87 | 88 | (t/deftest progressively-rising-temperatures-windowed 89 | (let [temperature-pattern 90 | {:pattern-sequence/name "rising temperatures" 91 | :pattern-sequence/within (.toMillis (TimeUnit/MINUTES) 1) 92 | :pattern-sequence/patterns 93 | [{:pattern/name "above average" 94 | :pattern/predicate ::above-average? 95 | :pattern/contiguity :next 96 | :pattern/kind :singleton} 97 | 98 | {:pattern/name "warning" 99 | :pattern/predicate ::warning? 100 | :pattern/contiguity :followed-by 101 | :pattern/kind :singleton} 102 | 103 | {:pattern/name "danger" 104 | :pattern/predicate ::dangerous? 105 | :pattern/contiguity :followed-by 106 | :pattern/kind :singleton}]} 107 | runtime (rt/initialize-runtime temperature-pattern) 108 | events [{:t 0 :temperature 85} 109 | {:t 50000 :temperature 95} 110 | {:t 70000 :temperature 110} 111 | 112 | {:t 100000 :temperature 83} 113 | {:t 105000 :temperature 75} 114 | {:t 130000 :temperature 92} 115 | {:t 140000 :temperature 85} 116 | {:t 150000 :temperature 101}] 117 | {:keys [matches timed-out-matches]} 118 | (reduce 119 | (fn [rt* event] 120 | (rt/evaluate-event rt* event {:event-time (:t event)})) 121 | runtime 122 | events)] 123 | (t/is (= [[{:t 100000 :temperature 83} 124 | {:t 130000 :temperature 92} 125 | {:t 150000 :temperature 101}]] 126 | matches)) 127 | (t/is (= [[{:t 0 :temperature 85} {:t 50000 :temperature 95}] 128 | [{:t 50000 :temperature 95} {:t 70000 :temperature 110}] 129 | [{:t 70000 :temperature 110} {:t 130000 :temperature 92}]] 130 | timed-out-matches)))) 131 | --------------------------------------------------------------------------------