wongi-engine 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.hgignore +6 -0
  3. data/.hgtags +2 -4
  4. data/.travis.yml +19 -6
  5. data/README.md +18 -442
  6. data/examples/ex02.rb +3 -2
  7. data/examples/graphviz.rb +1 -0
  8. data/lib/wongi-engine/alpha_memory.rb +11 -6
  9. data/lib/wongi-engine/beta/assignment_node.rb +0 -15
  10. data/lib/wongi-engine/beta/beta_memory.rb +7 -31
  11. data/lib/wongi-engine/beta/beta_node.rb +25 -95
  12. data/lib/wongi-engine/beta/join_node.rb +14 -46
  13. data/lib/wongi-engine/beta/ncc_node.rb +8 -34
  14. data/lib/wongi-engine/beta/ncc_partner.rb +3 -19
  15. data/lib/wongi-engine/beta/neg_node.rb +7 -16
  16. data/lib/wongi-engine/beta/optional_node.rb +23 -30
  17. data/lib/wongi-engine/beta/or_node.rb +0 -51
  18. data/lib/wongi-engine/beta/production_node.rb +1 -0
  19. data/lib/wongi-engine/compiler.rb +115 -0
  20. data/lib/wongi-engine/data_overlay.rb +140 -0
  21. data/lib/wongi-engine/dsl/action/base.rb +11 -0
  22. data/lib/wongi-engine/dsl/{actions → action}/error_generator.rb +3 -14
  23. data/lib/wongi-engine/dsl/action/simple_action.rb +60 -0
  24. data/lib/wongi-engine/dsl/action/simple_collector.rb +52 -0
  25. data/lib/wongi-engine/dsl/action/statement_generator.rb +45 -0
  26. data/lib/wongi-engine/dsl/action/trace_action.rb +49 -0
  27. data/lib/wongi-engine/dsl/any_rule.rb +4 -21
  28. data/lib/wongi-engine/dsl/assuming.rb +6 -12
  29. data/lib/wongi-engine/dsl/builder.rb +44 -0
  30. data/lib/wongi-engine/dsl/clause/assign.rb +15 -0
  31. data/lib/wongi-engine/dsl/clause/fact.rb +71 -0
  32. data/lib/wongi-engine/dsl/clause/gen.rb +17 -0
  33. data/lib/wongi-engine/dsl/clause/generic.rb +38 -0
  34. data/lib/wongi-engine/dsl/{dsl_extensions.rb → generated.rb} +5 -5
  35. data/lib/wongi-engine/dsl/ncc_subrule.rb +15 -0
  36. data/lib/wongi-engine/dsl/query.rb +10 -11
  37. data/lib/wongi-engine/dsl/rule.rb +84 -0
  38. data/lib/wongi-engine/dsl.rb +102 -97
  39. data/lib/wongi-engine/enumerators.rb +21 -0
  40. data/lib/wongi-engine/error.rb +13 -2
  41. data/lib/wongi-engine/filter/filter_test.rb +1 -13
  42. data/lib/wongi-engine/graph.rb +7 -7
  43. data/lib/wongi-engine/network.rb +108 -181
  44. data/lib/wongi-engine/ruleset.rb +6 -6
  45. data/lib/wongi-engine/template.rb +30 -84
  46. data/lib/wongi-engine/token.rb +3 -34
  47. data/lib/wongi-engine/version.rb +1 -1
  48. data/lib/wongi-engine/wme.rb +9 -60
  49. data/lib/wongi-engine.rb +3 -0
  50. data/spec/beta_node_spec.rb +2 -0
  51. data/spec/generation_spec.rb +1 -1
  52. data/spec/high_level_spec.rb +29 -11
  53. data/spec/overlay_spec.rb +22 -0
  54. data/spec/simple_action_spec.rb +1 -1
  55. data/spec/spec_helper.rb +1 -0
  56. data/spec/wme_spec.rb +22 -22
  57. data/wongi-engine.gemspec +1 -1
  58. metadata +37 -17
  59. data/lib/wongi-engine/dsl/action.rb +0 -12
  60. data/lib/wongi-engine/dsl/actions/simple_action.rb +0 -62
  61. data/lib/wongi-engine/dsl/actions/simple_collector.rb +0 -51
  62. data/lib/wongi-engine/dsl/actions/statement_generator.rb +0 -67
  63. data/lib/wongi-engine/dsl/actions/trace_action.rb +0 -52
  64. data/lib/wongi-engine/dsl/dsl_builder.rb +0 -44
  65. data/lib/wongi-engine/dsl/extension_clause.rb +0 -36
  66. data/lib/wongi-engine/dsl/generation_clause.rb +0 -15
  67. data/lib/wongi-engine/dsl/generic_production_rule.rb +0 -82
  68. data/lib/wongi-engine/dsl/ncc_production_rule.rb +0 -21
  69. data/lib/wongi-engine/dsl/production_rule.rb +0 -4
  70. data/lib/wongi-engine/model_context.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c142541d4a9b7743bb902181879415cd71711e28
4
- data.tar.gz: 2e3295767ec8e6d9cf0ff7b30eafe5c6b2e3c2d7
3
+ metadata.gz: 9ef395363d21e3d9fadcc8e6ce974c293b31c189
4
+ data.tar.gz: 65eb91be6334df92643463f6dee220a3467d3e8a
5
5
  SHA512:
6
- metadata.gz: c8f67c21c7182067a96b611c57bb25986c814f0cb5822b27744f8d289de2e750ad46fbc2766003912f8c3562f793c1909e5d2b2ed5fe051ac573a7b9486d88e6
7
- data.tar.gz: d615dd81a8e8f29417aae2806569278cf6d48ff58c40db352be66de6ccf7fd5de06137a489bba82a24cea64e81ac18a8a9d2d015d2b2d675dd11587b52089f6d
6
+ metadata.gz: 9b88eb68e39323b45e3b3e6c5ed291f78dc31512096a557f750c5ffa3f840025b31b844b10d4abc2de7b8667809c2f68fff995a366074032c20e7c51833256a8
7
+ data.tar.gz: eaa26f530def7d9cd1cce40f0169b5e3ecec43d9e097cc02f911ac25ec03d35c3534a49ea631ab411e8ce909917e85d90429879eeda8c0410bcb9bb58ae5594e
data/.hgignore ADDED
@@ -0,0 +1,6 @@
1
+ syntax: glob
2
+ *.DS_Store
3
+ Gemfile.lock
4
+ .rspec
5
+ .ruby-version
6
+
data/.hgtags CHANGED
@@ -1,6 +1,4 @@
1
1
  b1dfd471a2a9e4a57f0e4932ac184faa5d6fa5b5 v0.1.0.alpha1
2
+ cf7d0e690fb65541b19074e132813c79a36d12c5 v0.1.0
2
3
  accab6252d5746dafe847935ad55f8500594d0cc v0.1.1
3
- bcb2b3f279910db9fd7d831bed2eec35ea7f834c v0.1.3
4
- bcb2b3f279910db9fd7d831bed2eec35ea7f834c v0.1.3
5
- a9bbf79baec0c02897a43a41dfa0d0c427a05441 v0.1.3
6
- a1ee475ba255ea50216bb93bac9ff1a32845b8c8 v0.1.4
4
+ 0000000000000000000000000000000000000000 v0.1.1
data/.travis.yml CHANGED
@@ -1,8 +1,21 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.5
6
- - 2.2.0
7
- - rbx-2.4.1
8
- - jruby-19mode
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.7
6
+ - 2.2.3
7
+ - rbx-2.4.1
8
+ - rbx-2.5.8
9
+ - jruby-19mode
10
+ sudo: false
11
+ notifications:
12
+ hipchat:
13
+ rooms:
14
+ secure: OwpsmGlIxlSbLUWVZZI+xBErDkFc5XO2vmjs+ddQkNiL1E9N5SG35x75113S6EZ3qhqeNMPFBfZYDqem6jkhNOWsmH3EIH+QT1lWxnosrr0LgyJpvFosvjQaryvqJHVhT5tdBqzaWEYB6ObRLOUt7A5YrbtWHyTScB6ThpXCiR4=
15
+ webhooks:
16
+ urls:
17
+ - secure: Xm+R/3O6iG2sDbfrR8D5Y9AYJkB7DDjLBcKZEn+xaTVjuX/XFLF7p+v9n9v7Qs/v0VtU75pVX4Z++OmNJZJzJKB3Owgb4c1rMOGmctza2JPL+1yovmhpG0/S0hjMkO5g4CsAKKcQoaDXNrubUVHpEchLPP/aCx7/F4twCan9bfc=
18
+ on_success: always
19
+ on_failure: always
20
+ on_start: false
21
+
data/README.md CHANGED
@@ -1,467 +1,43 @@
1
1
  # Wongi::Engine
2
2
 
3
- This library contains a rule engine written in Ruby. It's based on the [Rete algorithm](http://en.wikipedia.org/wiki/Rete_algorithm) and uses a DSL to express rules in a readable way.
3
+ [![Join the chat at https://gitter.im/ulfurinn/wongi-engine](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ulfurinn/wongi-engine?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
4
 
5
5
  [![Build Status](https://travis-ci.org/ulfurinn/wongi-engine.svg?branch=master)](https://travis-ci.org/ulfurinn/wongi-engine) (MRI 1.9.3, 2.0, 2.1, 2.2, Rubinius, JRuby)
6
6
 
7
- ## Word of caution
8
-
9
- This is complex and fragile machinery, and there may be subtle bugs that are only revealed with nontrivial usage. Be conservative with upgrades, test your rules extensively, and please report any behaviour that is not consistent with your expectations.
10
-
11
- ## Tutorial
12
-
13
- To begin, say
14
-
15
- engine = Wongi::Engine.create
16
-
17
- Now let's add some facts to the system.
18
-
19
- ### Facts
20
-
21
- All knowledge in Wongi::Engine is represented by triples of { subject, predicate, object }. Predicates usually stand for subjects' properties, and objects for values of those properties. More complex types can always be decomposed into such triples.
22
-
23
- Triples can contain any Ruby object that defines the `==` comparison in a meaningful way, but some symbols have special meaning, as we will see.
24
-
25
- Try this:
26
-
27
- ```ruby
28
- engine << [ "Alice", "friend", "Bob" ]
29
- engine << [ "Alice", "age", 35 ]
30
- ```
31
-
32
- To remove facts, say:
33
-
34
- ```ruby
35
- engine.retract [ "Alice", "age", 35 ]
36
- ```
37
-
38
- What can we do with this information?
39
-
40
- ### Simple iteration
41
-
42
- Suppose we want to list all we know about Alice. You could, for instance, do:
43
-
44
- ```ruby
45
- engine.each "Alice", :_, :_ do |item|
46
- puts "Alice's #{item.predicate} is #{item.object}"
47
- end
48
- ```
49
-
50
- `each` takes three arguments for every field of a triple and tries to match the resulting template against the known facts. `:_` is the special value that matches anything. This kind of pattern matching plays a large role in Wongi::Engine; more on that later.
51
-
52
- In a similar way, you can use `select` to get an array of matching facts and `find` to get the first matching one. Both methods take three arguments.
53
-
54
- ### Simple rules
55
-
56
- It's not very interesting to use the engine like that, though. Rule engines are supposed to be declarative. Let's try this:
57
-
58
- ```ruby
59
- friends = engine.rule "friends" do
60
- forall {
61
- has :PersonA, "friend", :PersonB
62
- }
63
- end
64
- ```
65
-
66
- Here's your first taste of the engine's DSL. A rule, generally speaking, consists of a number of conditions the dataset needs to meet; those are defined in the `forall` section (also spelled `for_all`, if you prefer that). `has` (or `fact`) specifies that there needs to be a fact that matches the given pattern; in this case, one with the predicate `"friend"`.
67
-
68
- When a pattern contains a symbol that starts with an uppercase letter, it introduces a variable which will be bound to an actual triple field. Their values can be retrieved from the result set:
69
-
70
- ```ruby
71
- friends.tokens.each do |token|
72
- puts "%s and %s are friends" % [ token[ :PersonA ], token[ :PersonB ] ]
73
- end
74
- ```
75
-
76
- A **token** represents all facts that passed the rule's conditions. If you think of the dataset as of a long SQL table being joined with itself, then a token is like a row in the resulting table.
77
-
78
- If you don't care about a specific field's value, you can use the all-matcher `:_` in its place so as not to introduce unnecessary variables.
79
-
80
- Once a variable is bound, it can be used to match further facts within a rule. Let's add another friendship:
81
-
82
- ```ruby
83
- engine << [ "Bob", "friend", "Claire" ]
84
- ```
85
-
86
- and another rule:
87
-
88
- ```ruby
89
- remote = engine.rule "remote friends" do
90
- forall {
91
- has :PersonA, "friend", :PersonB
92
- has :PersonB, "friend", :PersonC
93
- }
94
- end
95
-
96
- remote.tokens.each do |token|
97
- puts "%s and %s are friends through %s" % [ token[ :PersonA ], token[ :PersonC ], token[ :PersonB ] ]
98
- end
99
- ```
100
-
101
- (`engine.rule` returns the created **production node** - an object that accumulates the rule's result set. You don't have to carry it around if you don't want to - it is always possible to retrieve it later as `engine.productions["remote friends"]`.)
102
-
103
- ### Stored queries
104
-
105
- Taking the SQL metaphor further, you can use the engine to do fancy searches:
106
-
107
- ```ruby
108
- q = engine.query "friends" do
109
- search_on :Name
110
- forall {
111
- has :Name, "friend", :Friend
112
- }
113
- end
114
-
115
- engine.execute "friends", { Name: "Alice" }
116
- q.tokens.each do |token|
117
- ... # you know the drill
118
- end
119
- ```
120
-
121
- Not that this is a particularly fancy search, but you get the idea.
122
-
123
- Queries work the same way as normal rules, but they come with some variables already bound by the time matching starts.
124
-
125
- You can also retrieve the query's production node from `engine.results["friends"]` (they are intentionally kept separate from regular productions).
126
-
127
- ### Taking an action
128
-
129
- There's more to rules than passive accumulation:
130
-
131
- ```ruby
132
- engine.rule "self-printer" do
133
- forall {
134
- has :PersonA, "friend", :PersonB
135
- }
136
- make {
137
- action { |token|
138
- puts "%s and %s are friends" % [ token[ :PersonA ], token[ :PersonB ] ]
139
- }
140
- }
141
- end
142
- ```
143
-
144
- The `make` section (also spelled `do!`, if you find it more agreeable English, because `do` is a keyword in Ruby) lists everything that happens when a rule's conditions are fully matched (we say "the production node is **activated**"). Wongi::Engine provides only a small amount of built-in actions, but you can define your own ones, and the simplest one is just `action` with a block. The block will be executed in the engine's context.
145
-
146
- ### More facts!
147
-
148
- Note how our facts define relations that always go from subject to object - they form a directed graph. In a perfect world, friendships go both ways, but to specify this in our model, we need to have two facts for each couple. Instead of duplicating everything by hand, let's automate that:
149
-
150
- ```ruby
151
- engine.rule "symmetric predicate" do
152
- forall {
153
- has :Predicate, "symmetric", true
154
- has :X, :Predicate, :Y
155
- }
156
- make {
157
- gen :Y, :Predicate, :X
158
- }
159
- end
160
-
161
- engine << ["friend", "symmetric", true]
162
- ```
163
-
164
- If you still have the "self-printer" rule installed, you will see some new friendships pop up immediately!
165
-
166
- The built-in `gen` action creates new facts, taking either fixed values or variables as arguments. (It will complain if you provide a variable that isn't bound by the time it's activated.) Here, it takes all relations we've defined to be [symmetric](http://en.wikipedia.org/wiki/Symmetric_relation), finds all couples in those sorts of relations and turns them around.
167
-
168
- ### Matchers
169
-
170
- It wouldn't be very useful if `has` were the only sort of condition that could be used. Here are some more:
171
-
172
- #### `neg subject, predicate, object`
173
-
174
- Passes if the specified template does *not* match anything in the dataset. Alias: `missing`.
175
-
176
- #### `maybe subject, predicate, object`
177
-
178
- Passes whether or not the template matches anything. It's only useful if it introduces a new variable; you can think of `LEFT JOIN`. Alias: `optional`.
179
-
180
- #### `none { ... }`
181
-
182
- The `none` block contains other matchers and passes if that *entire subchain* returns an empty set. In other words, it corresponds to an expression `not ( a and b and ... )`.
183
-
184
- #### `any { option { ... } ... }`
185
-
186
- The `any` block contains several `option` blocks, each of them containing other matchers. It passes if any of the `option` subchains matches. It's a shame that disjunction has to be so much more verbose than conjunction, but life is cruel.
187
-
188
- #### `same x, y`
189
-
190
- Passes if the arguments are equal. Alias: `eq`, `equal`.
191
-
192
- #### `diff x, y`
193
-
194
- Passes if the arguments are not equal. Alias: `ne`.
195
-
196
- #### `less x, y`, `greater x, y`
197
-
198
- Should be obvious by now.
199
-
200
- #### `assuming rule_name`
201
-
202
- This is a shortcut for extending a common base rule with additional matchers. `rule_name` must already be installed on the same engine instance. `assuming` must be the first clause in the extending rule.
203
-
204
- ```ruby
205
- engine << rule( :base ) {
206
- forall {
207
- has :x, :y, :Z
208
- }
209
- }
210
-
211
- engine << rule {
212
- forall {
213
- assuming :base
214
- has :Z, :u, :W
215
- }
216
- }
217
- ```
218
-
219
- #### `assert { |token| ... }`, `assert var1, var2, ... do |val1, val2, ... | ... end`
220
-
221
- Passes if the block evaluates to `true`. Having no arguments passes the entire token as an argument, listing some variables passes only their values.
222
-
223
- #### `assign variable do |token| ... end`
224
-
225
- Not a *matcher*, strictly speaking, because it always passes. What it does instead is introduce a new variable bound to the block's return value.
7
+ [Feature annoucements](https://github.com/ulfurinn/wongi-engine/issues?q=is%3Aopen+is%3Aissue+label%3Aannoucement)
226
8
 
227
- ### Feedback loop prevention
9
+ [Open discussions](https://github.com/ulfurinn/wongi-engine/issues?q=is%3Aopen+is%3Aissue+label%3Adiscussion)
228
10
 
229
- Consider the following rule:
230
-
231
- ```ruby
232
- engine.rule "default value" do
233
- forall {
234
- neg :car, :colour, :_
235
- }
236
- make {
237
- gen :car, :colour, "black"
238
- }
239
- end
240
- ```
241
-
242
- The intent here is to provide a default value for an attribute; however, how will it actually execute?
243
-
244
- 1. The fact is missing, activate the rule, generate the fact.
245
- 2. The fact is present, invalidate the rule, retract the generated fact.
246
- 3. The fact is missing...
247
-
248
- ...and so on until you get a stack overflow. In situations like this the engine will try to do the pragmatic thing and detect the loop, stopping after step 1. If you want to keep the "pure" behaviour, give the `unsafe: true` option to the `neg` rule and try to do the same thing with helper facts.
249
-
250
- **Note**: this is a specific use case; it is still perfectly possible to construct more elaborate infinite cascades involving several rules that will not be caught.
251
-
252
- ### Timeline
253
-
254
- Wongi::Engine has a limited concept of timed facts: time is discrete and only extends into the past. Matchers that accept a triple specification (`has`, `neg` and `maybe`) can also accept a `time` option, an integer <= 0, which will make them look at a past state of the system. "0" means the current state and is the default value, "-1" means the one just before the current, and so on.
255
-
256
- To create past states, say:
257
-
258
- ```ruby
259
- engine.snapshot!
260
- ```
261
-
262
- This will shift all facts one step into the past. The new current state will be a copy of the last one. You can only insert new facts into the current state, "retroactive" facts are not allowed.
263
-
264
- ### Time-aware matchers
265
-
266
- The following matchers are nothing but syntactic sugar for a combination of primitives.
267
-
268
- #### `asserted subject, predicate, object`
269
-
270
- Short for:
271
-
272
- ```ruby
273
- has subject, predicate, object, time: 0
274
- neg subject, predicate, object, time: -1
275
- ```
276
-
277
- That is, it passes if the fact was missing in the previous state but exists in the current one. Alias: `added`.
278
-
279
- #### `retracted subject, predicate, object`
280
-
281
- Short for:
282
-
283
- ```ruby
284
- has subject, predicate, object, time: -1
285
- neg subject, predicate, object, time: 0
286
- ```
287
-
288
- The reverse of `asserted`. Alias: `removed`.
289
-
290
- #### `kept subject, predicate, object`
291
-
292
- Short for:
293
-
294
- ```ruby
295
- has subject, predicate, object, time: -1
296
- has subject, predicate, object, time: 0
297
- ```
298
-
299
- Alias: `still_has`.
300
-
301
- #### `kept_missing subject, predicate, object`
302
-
303
- Short for:
304
-
305
- ```ruby
306
- neg subject, predicate, object, time: -1
307
- neg subject, predicate, object, time: 0
308
- ```
309
-
310
- Since neg rules cannot introduce new variables, neither can this one.
311
-
312
- Alias: `still_missing`.
313
-
314
- ### Other built-in actions
315
-
316
- #### `collect variable, collector_name`
317
-
318
- If you use this action, `engine.collection( collector_name )` will provide a `uniq`'ed array of all values `variable` has been bound to. It's a bit shorter than iterating over the tokens by hand.
319
-
320
- #### `error message`, `error { |hash_of_variable_assignments| ... }`
321
-
322
- Useful when you want to detect contradictory facts. `engine.errors` will give an array of all error messages produced when this action is activated. If you use the block form, the block needs to return a message.
323
-
324
- #### `trace options`
325
-
326
- The debugging action that will print a message every time it's activated. Possible options are:
327
-
328
- * `values` (boolean = false): whether to print variable assignments as well
329
- * `io` (IO = $stdout): which IO object to use
330
- * `generation` (boolean = false): whether this rule's `gen` action should print messages too. `trace` must come before any `gen` actions in this case.
331
- * `tracer`, `tracer_class`: a custom tracer that must respond to `trace` and accept a hash argument. Hash contents will vary depending on the action being traced.
332
-
333
- ### Custom actions
334
-
335
- We've seen one way to specify custom actions: using `action` with a block. Another way to use it is to say:
336
-
337
- ```ruby
338
- action class, ... do
339
- ...
340
- end
341
- ```
342
-
343
- Any additional arguments or blocks will be given to `initialize`, and the class must define an `execute` method taking a token. Passing any object with an `execute` method also works.
344
-
345
- If your action class inherits from `Wongi::Engine::Action`, you'll have the following (more or less useful) attributes:
346
-
347
- * `rete`: the engine instance
348
- * `rule`: the rule object that is using this action
349
- * `name`: the extension clause used to define this action (read more under [DSL extensions](#dsl-extensions))
350
- * `production`: the production node
351
-
352
- If you can't or don't want to inherit, you can define the accessors yourself. Having just the ones you need is fine.
353
-
354
- ### Organising rules
355
-
356
- Using `engine.rule` and `engine.query` is fine if you want to experiment, but to make rules and queries more manageable, you will probably want to keep them separate from the engine instance. One way to do that is to just say:
357
-
358
- ```ruby
359
- my_rule = rule "name" do
360
- ...
361
- end
362
-
363
- engine << my_rule
364
- ```
365
-
366
- For even more convenience, why not group rules together:
367
-
368
- ```ruby
369
- my_ruleset = ruleset {
370
- rule "rule 1" do
371
- ...
372
- end
373
- rule "rule 2" do
374
- ...
375
- end
376
- }
377
-
378
- engine << my_ruleset
379
- ```
380
-
381
- Again, you don't need to hold on to object references if you don't want to:
382
-
383
- ```ruby
384
- ruleset "my set" do
385
- ...
386
- end
387
-
388
- engine << Wongi::Engine::Ruleset[ "my set" ]
389
- ```
390
-
391
- ### DSL extensions
392
-
393
- This is a more advanced method of customising. In general, DSL extensions have the form:
394
-
395
- ```ruby
396
- dsl {
397
- section [ :forall | :make ]
398
- clause :my_action
399
- [ action | accept | body ] ...
400
- }
401
- ```
402
-
403
- which is then used in a rule like this:
404
-
405
- ```ruby
406
- make {
407
- my_action ...
408
- }
409
- ```
410
-
411
- DSL extensions are globally visible to all engine instances.
412
-
413
- Let's have a look at the three ways to define a clause's implementation.
414
-
415
- #### `body { |...| ... }`
416
-
417
- This simply allows you to group several other actions or matchers. It is perhaps the only way you have to extend the `forall` section, as any non-trivial matchers will require special support from the engine itself.
418
-
419
- #### `action class`, `action do |token| ... end`
420
-
421
- This works almost exactly like using the `action` action directly in a rule, but gives it a more meaningful alias. Arguments to `initialize`, however, are taken from the action's invocation in `make`, not the definition.
422
-
423
- A useful pattern is having specialised named collectors, defined like this:
11
+ This library contains a rule engine written in Ruby. It's based on the [Rete algorithm](http://en.wikipedia.org/wiki/Rete_algorithm) and uses a DSL to express rules in a readable way.
424
12
 
425
- ```ruby
426
- dsl {
427
- section :make
428
- clause :my_collection
429
- action Wongi::Engine::SimpleCollector.collector
430
- }
431
- ```
13
+ ## Word of caution
432
14
 
433
- installed like this:
15
+ This is complex and fragile machinery, and there may be subtle bugs that are only revealed with nontrivial usage. Be conservative with upgrades, test your rules extensively, and please report any behaviour that is not consistent with your expectations.
434
16
 
435
- ```ruby
436
- rule('collecting') {
437
- ...
438
- make {
439
- my_collection :X
440
- }
441
- }
442
- ```
17
+ ## How to use this thing?
443
18
 
444
- and accessed like this:
19
+ [The tutorial](http://ulfurinn.github.io/wongi-engine/) should get you started nicely.
445
20
 
446
- ```ruby
447
- collection = engine.collection :my_collection
448
- ```
21
+ ## Acknowledgements
449
22
 
450
- #### `accept class`
23
+ The Rete implementation in this library largely follows the outline presented in [\[Doorenbos, 1995\]](http://reports-archive.adm.cs.cmu.edu/anon/1995/CMU-CS-95-113.pdf).
451
24
 
452
- Most library users probably won't need this, but it's here for completion. Acceptors represent an intermediate state. They allow you to have some shared data that you customize for a given engine instance. The class needs to respond to `import_into( engine_instance )` and return something usable as an action, or to be usable as an action itself.
25
+ ## Changelog
453
26
 
454
- The class also gets arguments to `initialize` from the action's invocation.
27
+ ### 0.2.0
455
28
 
456
- ## Acknowledgements
29
+ * refactored compilation code
30
+ * [data overlays](https://github.com/ulfurinn/wongi-engine/issues/45)
31
+ * DSL methods are removed from `Object` and are available by including `Wongi::Engine::DSL` instead
457
32
 
458
- The Rete implementation in this library largely follows the outline presented in [\[Doorenbos, 1995\]](http://reports-archive.adm.cs.cmu.edu/anon/1995/CMU-CS-95-113.pdf).
33
+ ### 0.1.4
459
34
 
460
- ## Changelog
35
+ * fixed a bug in evaluation of `assign` nodes
461
36
 
462
37
  ### 0.1.0
463
38
 
464
39
  * massively rewritten rule activation; this simplifies development and debugging and opens the road for useful features such as fully reversible custom actions
40
+ * **treat this as a major upgrade and test thoroughly**
465
41
 
466
42
  ### 0.0.17
467
43
 
data/examples/ex02.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  include Wongi::Engine
2
+ include Wongi::Engine::DSL
2
3
 
3
4
  ds = Network.new
4
5
 
@@ -26,11 +27,11 @@ ds << WME.new( "Alice", "friend", "Bob" )
26
27
  puts "Asserted facts:"
27
28
 
28
29
  puts "Should print 3 facts:"
29
- puts ds.wmes
30
+ puts ds.wmes.to_a
30
31
 
31
32
 
32
33
  ds.retract WME.new( "Alice", "friend", "Bob" )
33
34
 
34
35
  puts "Should print 1 fact:"
35
- puts ds.wmes
36
+ puts ds.wmes.to_a
36
37
 
data/examples/graphviz.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'wongi-engine'
2
2
 
3
3
  include Wongi::Engine
4
+ include Wongi::Engine::DSL
4
5
 
5
6
  ds = Network.new
6
7
  ds << rule('demo') {
@@ -13,8 +13,7 @@ module Wongi::Engine
13
13
  end
14
14
 
15
15
  def activate wme
16
- @wmes << wme
17
- wme.alphas << self
16
+ wme.overlay.add_wme(wme, self)
18
17
  # TODO: it used to activate before adding to the list. mandated by the original thesis. investigate. it appears to create duplicate tokens - needs a remedy in collecting nodes
19
18
  betas.each do |beta|
20
19
  beta.alpha_activate wme
@@ -22,7 +21,7 @@ module Wongi::Engine
22
21
  end
23
22
 
24
23
  def deactivate wme
25
- @wmes.delete wme
24
+ wme.overlay.remove_wme(wme, self)
26
25
  betas.each do |beta|
27
26
  beta.alpha_deactivate wme
28
27
  end
@@ -35,17 +34,23 @@ module Wongi::Engine
35
34
  end
36
35
 
37
36
  def inspect
38
- "<Alpha #{__id__} template=#{template} wmes=#{@wmes}>"
37
+ "<Alpha #{__id__} template=#{template}>"
39
38
  end
40
39
 
41
40
  def to_s
42
41
  inspect
43
42
  end
44
43
 
44
+ def size
45
+ wmes.count
46
+ end
47
+
45
48
  def wmes
46
49
  Enumerator.new do |y|
47
- @wmes.dup.each do |wme|
48
- y << wme unless wme.deleted?
50
+ rete.overlays.each do |overlay|
51
+ overlay.raw_wmes(self).dup.each do |wme|
52
+ y << wme
53
+ end
49
54
  end
50
55
  end
51
56
  end
@@ -1,20 +1,5 @@
1
1
  module Wongi::Engine
2
2
 
3
- class Assignment
4
-
5
- def initialize variable, &body
6
- @variable, @body = variable, body
7
- end
8
-
9
- def compile context
10
- context.node = context.node.beta_memory.assignment_node( @variable, @body )
11
- context.node.context = context
12
- context.earlier << self
13
- context
14
- end
15
-
16
- end
17
-
18
3
  class AssignmentNode < BetaNode
19
4
 
20
5
  def initialize parent, variable, body