wongi-engine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +349 -0
- data/Rakefile +2 -0
- data/examples/ex01.rb +23 -0
- data/examples/ex02.rb +36 -0
- data/examples/graphviz.rb +15 -0
- data/examples/timeline.rb +48 -0
- data/lib/wongi-engine.rb +22 -0
- data/lib/wongi-engine/alpha_memory.rb +46 -0
- data/lib/wongi-engine/beta.rb +10 -0
- data/lib/wongi-engine/beta/beta_memory.rb +48 -0
- data/lib/wongi-engine/beta/beta_node.rb +164 -0
- data/lib/wongi-engine/beta/filter_node.rb +109 -0
- data/lib/wongi-engine/beta/join_node.rb +127 -0
- data/lib/wongi-engine/beta/ncc_node.rb +46 -0
- data/lib/wongi-engine/beta/ncc_partner.rb +43 -0
- data/lib/wongi-engine/beta/neg_node.rb +58 -0
- data/lib/wongi-engine/beta/optional_node.rb +43 -0
- data/lib/wongi-engine/beta/or_node.rb +76 -0
- data/lib/wongi-engine/beta/production_node.rb +31 -0
- data/lib/wongi-engine/core_ext.rb +57 -0
- data/lib/wongi-engine/dsl.rb +112 -0
- data/lib/wongi-engine/dsl/action.rb +12 -0
- data/lib/wongi-engine/dsl/actions/error_generator.rb +42 -0
- data/lib/wongi-engine/dsl/actions/simple_action.rb +23 -0
- data/lib/wongi-engine/dsl/actions/simple_collector.rb +51 -0
- data/lib/wongi-engine/dsl/actions/statement_generator.rb +52 -0
- data/lib/wongi-engine/dsl/actions/trace_action.rb +52 -0
- data/lib/wongi-engine/dsl/any_rule.rb +48 -0
- data/lib/wongi-engine/dsl/dsl_builder.rb +44 -0
- data/lib/wongi-engine/dsl/dsl_extensions.rb +43 -0
- data/lib/wongi-engine/dsl/extension_clause.rb +36 -0
- data/lib/wongi-engine/dsl/generation_clause.rb +15 -0
- data/lib/wongi-engine/dsl/generic_production_rule.rb +78 -0
- data/lib/wongi-engine/dsl/ncc_production_rule.rb +21 -0
- data/lib/wongi-engine/dsl/production_rule.rb +4 -0
- data/lib/wongi-engine/dsl/query.rb +24 -0
- data/lib/wongi-engine/graph.rb +71 -0
- data/lib/wongi-engine/model_context.rb +13 -0
- data/lib/wongi-engine/network.rb +416 -0
- data/lib/wongi-engine/network/collectable.rb +42 -0
- data/lib/wongi-engine/network/debug.rb +25 -0
- data/lib/wongi-engine/ruleset.rb +74 -0
- data/lib/wongi-engine/template.rb +111 -0
- data/lib/wongi-engine/token.rb +137 -0
- data/lib/wongi-engine/version.rb +5 -0
- data/lib/wongi-engine/wme.rb +134 -0
- data/lib/wongi-engine/wme_match_data.rb +34 -0
- data/spec/dataset_spec.rb +26 -0
- data/spec/dsl_spec.rb +9 -0
- data/spec/high_level_spec.rb +341 -0
- data/spec/ruleset_spec.rb +54 -0
- data/spec/simple_action_spec.rb +40 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/wme_spec.rb +83 -0
- data/wongi-engine.gemspec +19 -0
- metadata +110 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Wongi::Engine
|
2
|
+
|
3
|
+
class WMEMatchData
|
4
|
+
|
5
|
+
attr_reader :assignments
|
6
|
+
|
7
|
+
def initialize assignments = { }, match = false
|
8
|
+
@assignments = assignments
|
9
|
+
@match = match
|
10
|
+
end
|
11
|
+
|
12
|
+
def [] key
|
13
|
+
assignments[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def []= key, value
|
17
|
+
assignments[key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def match?
|
21
|
+
@match
|
22
|
+
end
|
23
|
+
|
24
|
+
def match!
|
25
|
+
@match = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def & other
|
29
|
+
WMEMatchData.new( assignments.merge( other.assignments ), match? && other.match? )
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wongi::Engine::Network do
|
4
|
+
|
5
|
+
it 'should expose compiled productions' do
|
6
|
+
|
7
|
+
ds = Wongi::Engine::Network.new
|
8
|
+
|
9
|
+
ds << rule('test-rule') {
|
10
|
+
forall {
|
11
|
+
has 1, 2, 3
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
production = ds.productions['test-rule']
|
16
|
+
production.should_not be_nil
|
17
|
+
|
18
|
+
production.tokens.should be_empty
|
19
|
+
|
20
|
+
ds << [1, 2, 3]
|
21
|
+
|
22
|
+
production.should have(1).tokens
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/spec/dsl_spec.rb
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
dsl {
|
4
|
+
|
5
|
+
section :make
|
6
|
+
clause :test_collector
|
7
|
+
action Wongi::Engine::SimpleCollector.collector
|
8
|
+
|
9
|
+
}
|
10
|
+
|
11
|
+
describe 'the engine' do
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
@rete = Wongi::Engine::Network.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def rete
|
18
|
+
@rete
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with a simple generative positive rule' do
|
22
|
+
|
23
|
+
it 'should generate wmes with an existing rule' do
|
24
|
+
|
25
|
+
rete << rule('symmetric') {
|
26
|
+
forall {
|
27
|
+
has :P, "symmetric", true
|
28
|
+
has :A, :P, :B
|
29
|
+
}
|
30
|
+
make {
|
31
|
+
gen :B, :P, :A
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
rete << Wongi::Engine::WME.new( "friend", "symmetric", true )
|
36
|
+
rete << Wongi::Engine::WME.new( "Alice", "friend", "Bob" )
|
37
|
+
|
38
|
+
rete.should have(3).facts
|
39
|
+
rete.facts.select( &:manual? ).should have(2).items
|
40
|
+
generated = rete.facts.find( &:generated? )
|
41
|
+
generated.should == Wongi::Engine::WME.new( "Bob", "friend", "Alice" )
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should generate wmes with an added rule' do
|
45
|
+
|
46
|
+
rete << Wongi::Engine::WME.new( "friend", "symmetric", true )
|
47
|
+
rete << Wongi::Engine::WME.new( "Alice", "friend", "Bob" )
|
48
|
+
|
49
|
+
rete.should have(2).facts
|
50
|
+
|
51
|
+
rete << rule('symmetric') {
|
52
|
+
forall {
|
53
|
+
has :P, "symmetric", true
|
54
|
+
has :A, :P, :B
|
55
|
+
}
|
56
|
+
make {
|
57
|
+
gen :B, :P, :A
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
rete.should have(3).facts
|
62
|
+
rete.facts.select( &:manual? ).should have(2).items
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should check equality' do
|
68
|
+
|
69
|
+
rete << rule('equality') {
|
70
|
+
forall {
|
71
|
+
fact :A, "same", :B
|
72
|
+
same :A, :B
|
73
|
+
}
|
74
|
+
make {
|
75
|
+
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
rete << [ 42, "same", 42 ]
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should use collectors' do
|
84
|
+
|
85
|
+
rete << rule('collector') {
|
86
|
+
forall {
|
87
|
+
has :X, :_, 42
|
88
|
+
}
|
89
|
+
make {
|
90
|
+
test_collector :X
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
rete << [ "answer", "is", 42 ]
|
95
|
+
rete << [ "question", "is", -1 ]
|
96
|
+
|
97
|
+
collection = rete.collection(:test_collector)
|
98
|
+
collection.should have(1).item
|
99
|
+
collection.first.should == "answer"
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should use generic collectors' do
|
104
|
+
|
105
|
+
rete << rule('generic-collector') {
|
106
|
+
forall {
|
107
|
+
has :X, :_, 42
|
108
|
+
}
|
109
|
+
make {
|
110
|
+
collect :X, :things_that_are_42
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
rete << [ "answer", "is", 42 ]
|
115
|
+
rete << [ "question", "is", -1 ]
|
116
|
+
|
117
|
+
collection = rete.collection(:things_that_are_42)
|
118
|
+
collection.should have(1).item
|
119
|
+
collection.first.should == "answer"
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should accept several rules' do
|
124
|
+
|
125
|
+
lambda do
|
126
|
+
|
127
|
+
rete << rule('generic-collector') {
|
128
|
+
forall {
|
129
|
+
has :X, :_, 42
|
130
|
+
}
|
131
|
+
make {
|
132
|
+
collect :X, :things_that_are_42
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
rete << rule('collector') {
|
137
|
+
forall {
|
138
|
+
has :X, :_, 42
|
139
|
+
}
|
140
|
+
make {
|
141
|
+
test_collector :X
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
end.should_not raise_error
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should process negative nodes' do
|
150
|
+
|
151
|
+
production = (rete << rule('negative') {
|
152
|
+
forall {
|
153
|
+
neg :_, :_, 42
|
154
|
+
}
|
155
|
+
})
|
156
|
+
|
157
|
+
production.should have(1).tokens
|
158
|
+
|
159
|
+
rete << [ "answer", "is", 42 ]
|
160
|
+
|
161
|
+
production.should have(0).tokens
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should support prepared queries' do
|
166
|
+
|
167
|
+
rete << query("test-query") {
|
168
|
+
search_on :X
|
169
|
+
forall {
|
170
|
+
has :X, "is", :Y
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
rete << ["answer", "is", 42]
|
175
|
+
|
176
|
+
rete.execute "test-query", {X: "answer"}
|
177
|
+
rete.results["test-query"].should have(1).tokens
|
178
|
+
rete.results["test-query"].tokens.first[:Y].should == 42
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should support negative subnets' do
|
183
|
+
|
184
|
+
production = (rete << rule('ncc') {
|
185
|
+
forall {
|
186
|
+
has "base", "is", :Base
|
187
|
+
none {
|
188
|
+
has :Base, 2, :X
|
189
|
+
has :X, 4, 5
|
190
|
+
}
|
191
|
+
}
|
192
|
+
})
|
193
|
+
|
194
|
+
rete << ["base", "is", 1]
|
195
|
+
|
196
|
+
production.should have(1).tokens
|
197
|
+
|
198
|
+
rete << [1, 2, 3]
|
199
|
+
|
200
|
+
production.should have(1).tokens
|
201
|
+
|
202
|
+
rete << [3, 4, 5]
|
203
|
+
|
204
|
+
production.should have(0).tokens
|
205
|
+
|
206
|
+
rete << ["base", "is", 2]
|
207
|
+
|
208
|
+
production.should have(1).tokens
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should support optional matches' do
|
213
|
+
|
214
|
+
production = (rete << rule('optional') {
|
215
|
+
forall {
|
216
|
+
has "answer", "is", :Answer
|
217
|
+
maybe :Answer, "is", :Kind
|
218
|
+
}
|
219
|
+
})
|
220
|
+
|
221
|
+
rete << ["answer", "is", 42]
|
222
|
+
rete << ["answer", "is", 43]
|
223
|
+
rete << [42, "is", "canonical"]
|
224
|
+
|
225
|
+
production.should have(2).tokens
|
226
|
+
|
227
|
+
canon = production.tokens.select { |token| not token[:Kind].nil? }
|
228
|
+
canon.should have(1).items
|
229
|
+
canon.first[:Answer].should == 42
|
230
|
+
canon.first[:Kind].should == "canonical"
|
231
|
+
|
232
|
+
non_canon = production.tokens.select { |token| token[:Kind].nil? }
|
233
|
+
non_canon.should have(1).items
|
234
|
+
non_canon.first[:Answer].should == 43
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
context 'with timelines' do
|
239
|
+
|
240
|
+
it 'should not match with no past point' do
|
241
|
+
|
242
|
+
production = rete.rule {
|
243
|
+
forall {
|
244
|
+
has 1, 2, 3, -1
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
248
|
+
production.should have(0).tokens
|
249
|
+
|
250
|
+
rete << [1, 2, 3]
|
251
|
+
|
252
|
+
production.should have(0).tokens
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'should match a simple past point' do
|
257
|
+
|
258
|
+
production = rete.rule {
|
259
|
+
forall {
|
260
|
+
has 1, 2, 3, -1
|
261
|
+
}
|
262
|
+
}
|
263
|
+
|
264
|
+
rete << [1, 2, 3]
|
265
|
+
rete.snapshot!
|
266
|
+
|
267
|
+
production.should have(1).tokens
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
context 'using the :asserted clause' do
|
272
|
+
|
273
|
+
it 'should match asserted items' do
|
274
|
+
count = 0
|
275
|
+
production = rete.rule do
|
276
|
+
forall {
|
277
|
+
asserted 1, 2, 3
|
278
|
+
}
|
279
|
+
make { action { count += 1} }
|
280
|
+
end
|
281
|
+
production.should have(0).tokens
|
282
|
+
rete.snapshot!
|
283
|
+
rete << [1, 2, 3]
|
284
|
+
production.should have(1).tokens
|
285
|
+
puts count
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'should not match kept items' do
|
289
|
+
count = 0
|
290
|
+
production = rete.rule do
|
291
|
+
forall {
|
292
|
+
asserted 1, 2, 3
|
293
|
+
}
|
294
|
+
make { action { count += 1} }
|
295
|
+
end
|
296
|
+
rete << [1, 2, 3]
|
297
|
+
production.should have(1).tokens
|
298
|
+
rete.snapshot!
|
299
|
+
production.should have(0).tokens
|
300
|
+
puts count
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
|
305
|
+
context 'using the :kept clause' do
|
306
|
+
|
307
|
+
it 'should match kept items' do
|
308
|
+
count = 0
|
309
|
+
production = rete.rule do
|
310
|
+
forall {
|
311
|
+
kept 1, 2, 3
|
312
|
+
}
|
313
|
+
make { action { count += 1} }
|
314
|
+
end
|
315
|
+
rete << [1, 2, 3]
|
316
|
+
production.should have(0).tokens
|
317
|
+
rete.snapshot!
|
318
|
+
production.should have(1).tokens
|
319
|
+
puts count
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'should not match asserted wmes' do
|
323
|
+
count = 0
|
324
|
+
production = rete.rule do
|
325
|
+
forall {
|
326
|
+
kept 1, 2, 3
|
327
|
+
}
|
328
|
+
make { action { count += 1} }
|
329
|
+
end
|
330
|
+
production.should have(0).tokens
|
331
|
+
rete.snapshot!
|
332
|
+
rete << [1, 2, 3]
|
333
|
+
production.should have(0).tokens
|
334
|
+
puts count
|
335
|
+
end
|
336
|
+
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wongi::Engine::Ruleset do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
Wongi::Engine::Ruleset.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'initially' do
|
10
|
+
|
11
|
+
it 'should have no rules' do
|
12
|
+
Wongi::Engine::Ruleset.rulesets.should be_empty
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when creating' do
|
18
|
+
|
19
|
+
it 'should not register itself when not given a name' do
|
20
|
+
ruleset = Wongi::Engine::Ruleset.new
|
21
|
+
ruleset.name.should be_nil
|
22
|
+
Wongi::Engine::Ruleset.rulesets.should be_empty
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should have a name' do
|
26
|
+
ruleset = Wongi::Engine::Ruleset.new 'testing-ruleset'
|
27
|
+
ruleset.name.should == 'testing-ruleset'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should register itself when given a name' do
|
31
|
+
ruleset = Wongi::Engine::Ruleset.new 'testing-ruleset'
|
32
|
+
Wongi::Engine::Ruleset.rulesets.should_not be_empty
|
33
|
+
Wongi::Engine::Ruleset[ruleset.name].should == ruleset
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should be able to clear registered rulesets' do
|
39
|
+
ruleset = Wongi::Engine::Ruleset.new 'testing-ruleset'
|
40
|
+
Wongi::Engine::Ruleset.reset
|
41
|
+
Wongi::Engine::Ruleset.rulesets.should be_empty
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should install creating rules into a rete' do
|
45
|
+
rete = mock 'rete'
|
46
|
+
|
47
|
+
ruleset = Wongi::Engine::Ruleset.new
|
48
|
+
rule = ruleset.rule( 'test-rule' ) { }
|
49
|
+
|
50
|
+
rete.should_receive(:<<).with(rule).once
|
51
|
+
ruleset.install rete
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|