wongi-engine 0.3.9 → 0.4.0.pre.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +2 -2
- data/.gitignore +2 -0
- data/README.md +12 -12
- data/lib/wongi-engine/alpha_index.rb +58 -0
- data/lib/wongi-engine/alpha_memory.rb +2 -24
- data/lib/wongi-engine/beta/aggregate_node.rb +16 -15
- data/lib/wongi-engine/beta/assignment_node.rb +7 -2
- data/lib/wongi-engine/beta/beta_node.rb +27 -23
- data/lib/wongi-engine/beta/filter_node.rb +8 -13
- data/lib/wongi-engine/beta/join_node.rb +15 -28
- data/lib/wongi-engine/beta/ncc_node.rb +14 -26
- data/lib/wongi-engine/beta/ncc_partner.rb +18 -18
- data/lib/wongi-engine/beta/neg_node.rb +23 -55
- data/lib/wongi-engine/beta/optional_node.rb +24 -48
- data/lib/wongi-engine/beta/or_node.rb +24 -1
- data/lib/wongi-engine/beta/production_node.rb +9 -3
- data/lib/wongi-engine/beta/root_node.rb +47 -0
- data/lib/wongi-engine/beta.rb +1 -1
- data/lib/wongi-engine/compiler.rb +6 -34
- data/lib/wongi-engine/dsl/action/{base.rb → base_action.rb} +5 -1
- data/lib/wongi-engine/dsl/action/error_generator.rb +1 -1
- data/lib/wongi-engine/dsl/action/simple_action.rb +1 -1
- data/lib/wongi-engine/dsl/action/simple_collector.rb +1 -1
- data/lib/wongi-engine/dsl/action/statement_generator.rb +21 -22
- data/lib/wongi-engine/dsl/action/trace_action.rb +1 -1
- data/lib/wongi-engine/dsl/clause/fact.rb +2 -6
- data/lib/wongi-engine/dsl.rb +1 -25
- data/lib/wongi-engine/graph.rb +1 -1
- data/lib/wongi-engine/network/debug.rb +2 -10
- data/lib/wongi-engine/network.rb +44 -105
- data/lib/wongi-engine/overlay.rb +589 -0
- data/lib/wongi-engine/template.rb +22 -2
- data/lib/wongi-engine/token.rb +10 -26
- data/lib/wongi-engine/token_assignment.rb +15 -0
- data/lib/wongi-engine/version.rb +1 -1
- data/lib/wongi-engine/wme.rb +10 -39
- data/lib/wongi-engine.rb +3 -1
- data/spec/alpha_index_spec.rb +78 -0
- data/spec/bug_specs/issue_4_spec.rb +11 -11
- data/spec/high_level_spec.rb +8 -101
- data/spec/network_spec.rb +8 -6
- data/spec/overlay_spec.rb +161 -3
- data/spec/rule_specs/any_rule_spec.rb +39 -0
- data/spec/rule_specs/assign_spec.rb +1 -1
- data/spec/rule_specs/maybe_rule_spec.rb +58 -1
- data/spec/rule_specs/ncc_spec.rb +78 -19
- data/spec/rule_specs/negative_rule_spec.rb +12 -14
- data/spec/spec_helper.rb +4 -0
- data/spec/wme_spec.rb +0 -32
- metadata +11 -9
- data/lib/wongi-engine/beta/beta_memory.rb +0 -60
- data/lib/wongi-engine/data_overlay.rb +0 -149
- data/spec/rule_specs/or_rule_spec.rb +0 -40
data/lib/wongi-engine/version.rb
CHANGED
data/lib/wongi-engine/wme.rb
CHANGED
@@ -1,49 +1,20 @@
|
|
1
1
|
module Wongi::Engine
|
2
2
|
WME = Struct.new(:subject, :predicate, :object) do
|
3
|
-
|
3
|
+
def self.from_concrete_template(template)
|
4
|
+
raise "template #{template} is not concrete" unless template.concrete?
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
attr_reader :generating_tokens
|
8
|
-
attr_reader :neg_join_results, :opt_join_results
|
9
|
-
attr_accessor :overlay
|
10
|
-
|
11
|
-
attr_predicate :deleted
|
12
|
-
attr_predicate :manual
|
13
|
-
|
14
|
-
def initialize(s, p, o, r = nil)
|
15
|
-
manual!
|
16
|
-
|
17
|
-
@deleted = false
|
18
|
-
@alphas = []
|
19
|
-
@generating_tokens = []
|
20
|
-
@neg_join_results = []
|
21
|
-
@opt_join_results = []
|
22
|
-
|
23
|
-
@rete = r
|
24
|
-
|
25
|
-
# TODO: reintroduce Network#import when bringing back RDF support
|
26
|
-
super(s, p, o)
|
27
|
-
end
|
28
|
-
|
29
|
-
def import_into(r)
|
30
|
-
self.class.new(subject, predicate, object, r).tap do |wme|
|
31
|
-
wme.overlay = overlay
|
32
|
-
wme.manual = manual?
|
33
|
-
end
|
6
|
+
new(template.subject, template.predicate, template.object)
|
34
7
|
end
|
35
8
|
|
36
9
|
def dup
|
37
|
-
self.class.new(subject, predicate, object
|
38
|
-
wme.overlay = overlay
|
39
|
-
wme.manual = manual?
|
40
|
-
end
|
10
|
+
self.class.new(subject, predicate, object)
|
41
11
|
end
|
42
12
|
|
43
13
|
def ==(other)
|
44
|
-
subject == other.subject && predicate == other.predicate && object == other.object
|
14
|
+
other && subject == other.subject && predicate == other.predicate && object == other.object
|
45
15
|
end
|
46
16
|
|
17
|
+
# @param template Wongi::Engine::Template
|
47
18
|
def =~(template)
|
48
19
|
raise Wongi::Engine::Error, "Cannot match a WME against a #{template.class}" unless template.is_a?(Template)
|
49
20
|
|
@@ -51,10 +22,6 @@ module Wongi::Engine
|
|
51
22
|
result if result.match?
|
52
23
|
end
|
53
24
|
|
54
|
-
def generated?
|
55
|
-
!generating_tokens.empty?
|
56
|
-
end
|
57
|
-
|
58
25
|
def inspect
|
59
26
|
"{#{subject.inspect} #{predicate.inspect} #{object.inspect}}"
|
60
27
|
end
|
@@ -67,6 +34,10 @@ module Wongi::Engine
|
|
67
34
|
@hash ||= [subject.hash, predicate.hash, object.hash].hash
|
68
35
|
end
|
69
36
|
|
37
|
+
def eql?(other)
|
38
|
+
subject.eql?(other.subject) && predicate.eql?(other.predicate) && object.eql?(other.object)
|
39
|
+
end
|
40
|
+
|
70
41
|
protected
|
71
42
|
|
72
43
|
def match_member(mine, theirs)
|
data/lib/wongi-engine.rb
CHANGED
@@ -21,12 +21,14 @@ require 'wongi-engine/wme'
|
|
21
21
|
require 'wongi-engine/wme_match_data'
|
22
22
|
require 'wongi-engine/token'
|
23
23
|
require 'wongi-engine/filter'
|
24
|
+
require 'wongi-engine/alpha_index'
|
24
25
|
require 'wongi-engine/alpha_memory'
|
26
|
+
require 'wongi-engine/token_assignment'
|
25
27
|
require 'wongi-engine/beta'
|
26
28
|
require 'wongi-engine/dsl'
|
27
29
|
require 'wongi-engine/ruleset'
|
28
30
|
require 'wongi-engine/compiler'
|
29
|
-
require 'wongi-engine/
|
31
|
+
require 'wongi-engine/overlay'
|
30
32
|
require 'wongi-engine/enumerators'
|
31
33
|
require 'wongi-engine/network'
|
32
34
|
require 'wongi-engine/graph'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Wongi::Engine::AlphaIndex do
|
5
|
+
let(:index) { described_class.new(pattern) }
|
6
|
+
let(:wme) { Wongi::Engine::WME.new(1, 2, 3) }
|
7
|
+
|
8
|
+
# private access
|
9
|
+
let(:collection) { index.send(:index) }
|
10
|
+
|
11
|
+
before do
|
12
|
+
index.add(wme)
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'index by subject' do
|
16
|
+
let(:pattern) { %i[subject] }
|
17
|
+
let(:key) { hashed_key([1]) }
|
18
|
+
|
19
|
+
it 'indexes by pattern' do
|
20
|
+
expect(collection.keys).to eq([key])
|
21
|
+
expect(collection[key]).to eq([wme])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'index by predicate' do
|
26
|
+
let(:pattern) { %i[predicate] }
|
27
|
+
let(:key) { hashed_key([2]) }
|
28
|
+
|
29
|
+
it 'indexes by pattern' do
|
30
|
+
expect(collection.keys).to eq([key])
|
31
|
+
expect(collection[key]).to eq([wme])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'index by object' do
|
36
|
+
let(:pattern) { %i[object] }
|
37
|
+
let(:key) { hashed_key([3]) }
|
38
|
+
|
39
|
+
it 'indexes by pattern' do
|
40
|
+
expect(collection.keys).to eq([key])
|
41
|
+
expect(collection[key]).to eq([wme])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'index by subject and predicate' do
|
46
|
+
let(:pattern) { %i[subject predicate] }
|
47
|
+
let(:key) { hashed_key([1, 2]) }
|
48
|
+
|
49
|
+
it 'indexes by pattern' do
|
50
|
+
expect(collection.keys).to eq([key])
|
51
|
+
expect(collection[key]).to eq([wme])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'index by subject and object' do
|
56
|
+
let(:pattern) { %i[subject object] }
|
57
|
+
let(:key) { hashed_key([1, 3]) }
|
58
|
+
|
59
|
+
it 'indexes by pattern' do
|
60
|
+
expect(collection.keys).to eq([key])
|
61
|
+
expect(collection[key]).to eq([wme])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'index by predicate and object' do
|
66
|
+
let(:pattern) { %i[predicate object] }
|
67
|
+
let(:key) { hashed_key([2, 3]) }
|
68
|
+
|
69
|
+
it 'indexes by pattern' do
|
70
|
+
expect(collection.keys).to eq([key])
|
71
|
+
expect(collection[key]).to eq([wme])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def hashed_key(key)
|
76
|
+
key.map(&:hash)
|
77
|
+
end
|
78
|
+
end
|
@@ -22,8 +22,8 @@ describe "issue 4" do
|
|
22
22
|
numbers = engine.select :_, :is_number, true
|
23
23
|
evens = engine.select :_, :is_even, true
|
24
24
|
|
25
|
-
expect(numbers).to
|
26
|
-
expect(evens.
|
25
|
+
expect(numbers.size).to eq(0)
|
26
|
+
expect(evens.size).to eq(10)
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should correctly retract post-added items from within a rule" do
|
@@ -42,13 +42,13 @@ describe "issue 4" do
|
|
42
42
|
}
|
43
43
|
end
|
44
44
|
|
45
|
-
|
45
|
+
5.times { |i| engine << [i, :is_number, true] }
|
46
46
|
|
47
47
|
numbers = engine.select :_, :is_number, true
|
48
48
|
evens = engine.select :_, :is_even, true
|
49
49
|
|
50
|
-
expect(numbers).to
|
51
|
-
expect(evens.
|
50
|
+
expect(numbers.size).to eq(0)
|
51
|
+
expect(evens.size).to eq(5)
|
52
52
|
end
|
53
53
|
|
54
54
|
# cascaded processing affects this
|
@@ -80,9 +80,9 @@ describe "issue 4" do
|
|
80
80
|
evens = engine.select :_, :is_even, true
|
81
81
|
odds = engine.select :_, :is_odd, true
|
82
82
|
|
83
|
-
expect(numbers).to
|
84
|
-
expect(evens).to
|
85
|
-
expect(odds).to
|
83
|
+
expect(numbers.size).to eq(0)
|
84
|
+
expect(evens.size).to eq(5)
|
85
|
+
expect(odds.size).to eq(5)
|
86
86
|
end
|
87
87
|
|
88
88
|
it "should not lose track when another rule affects a set" do
|
@@ -126,8 +126,8 @@ describe "issue 4" do
|
|
126
126
|
evens = engine.select :_, :is_even, true
|
127
127
|
odds = engine.select :_, :is_odd, true
|
128
128
|
|
129
|
-
expect(numbers.
|
130
|
-
expect(evens.
|
131
|
-
expect(odds.
|
129
|
+
expect(numbers.size).to eq(5)
|
130
|
+
expect(evens.size).to eq(5)
|
131
|
+
expect(odds.size).to eq(5)
|
132
132
|
end
|
133
133
|
end
|
data/spec/high_level_spec.rb
CHANGED
@@ -30,9 +30,9 @@ describe 'the engine' do
|
|
30
30
|
engine << Wongi::Engine::WME.new("Alice", "friend", "Bob")
|
31
31
|
|
32
32
|
expect(engine.facts.to_a.length).to eq(3)
|
33
|
-
expect(engine.facts.select
|
34
|
-
generated = engine.facts.
|
35
|
-
expect(generated).to
|
33
|
+
expect(engine.facts.select { engine.current_overlay.manual?(_1) }.length).to eq(2)
|
34
|
+
generated = engine.facts.select { engine.current_overlay.generated?(_1) }
|
35
|
+
expect(generated).to eq([Wongi::Engine::WME.new("Bob", "friend", "Alice")])
|
36
36
|
end
|
37
37
|
|
38
38
|
it 'should generate wmes with an added rule' do
|
@@ -51,8 +51,8 @@ describe 'the engine' do
|
|
51
51
|
}
|
52
52
|
}
|
53
53
|
|
54
|
-
expect(engine.facts.to_a.
|
55
|
-
expect(engine.facts.select
|
54
|
+
expect(engine.facts.to_a.size).to eq(3)
|
55
|
+
expect(engine.facts.select { engine.current_overlay.manual?(_1) }.size).to eq(2)
|
56
56
|
end
|
57
57
|
|
58
58
|
it 'should not get confused by recursive activations' do
|
@@ -70,9 +70,9 @@ describe 'the engine' do
|
|
70
70
|
engine << [:p, "reflexive", true]
|
71
71
|
engine << %i[x p y]
|
72
72
|
|
73
|
-
expect(engine.wmes.
|
74
|
-
expect(engine.select(:x, :p, :x).
|
75
|
-
expect(engine.select(:y, :p, :y).
|
73
|
+
expect(engine.wmes.count).to eq(4)
|
74
|
+
expect(engine.select(:x, :p, :x).count).to eq(1)
|
75
|
+
expect(engine.select(:y, :p, :y).count).to eq(1)
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
@@ -236,97 +236,4 @@ describe 'the engine' do
|
|
236
236
|
expect(engine.results["test-query"].size).to eq(1)
|
237
237
|
end
|
238
238
|
end
|
239
|
-
|
240
|
-
context 'with timelines' do
|
241
|
-
it 'should not match with no past point' do
|
242
|
-
production = engine.rule {
|
243
|
-
forall {
|
244
|
-
has 1, 2, 3, time: -1
|
245
|
-
}
|
246
|
-
}
|
247
|
-
|
248
|
-
expect(production.size).to eq(0)
|
249
|
-
|
250
|
-
engine << [1, 2, 3]
|
251
|
-
|
252
|
-
expect(production.size).to eq(0)
|
253
|
-
end
|
254
|
-
|
255
|
-
it 'should match a simple past point' do
|
256
|
-
production = engine.rule {
|
257
|
-
forall {
|
258
|
-
has 1, 2, 3, time: -1
|
259
|
-
}
|
260
|
-
}
|
261
|
-
|
262
|
-
engine << [1, 2, 3]
|
263
|
-
engine.snapshot!
|
264
|
-
|
265
|
-
expect(production.size).to eq(1)
|
266
|
-
end
|
267
|
-
|
268
|
-
context 'using the :asserted clause' do
|
269
|
-
it 'should match asserted items' do
|
270
|
-
count = 0
|
271
|
-
production = engine.rule do
|
272
|
-
forall {
|
273
|
-
asserted 1, 2, 3
|
274
|
-
}
|
275
|
-
make { action { count += 1 } }
|
276
|
-
end
|
277
|
-
expect(production.size).to eq(0)
|
278
|
-
engine.snapshot!
|
279
|
-
engine << [1, 2, 3]
|
280
|
-
expect(production.size).to eq(1)
|
281
|
-
# puts count
|
282
|
-
end
|
283
|
-
|
284
|
-
it 'should not match kept items' do
|
285
|
-
count = 0
|
286
|
-
production = engine.rule do
|
287
|
-
forall {
|
288
|
-
asserted 1, 2, 3
|
289
|
-
}
|
290
|
-
make { action { count += 1 } }
|
291
|
-
end
|
292
|
-
engine << [1, 2, 3]
|
293
|
-
expect(production.size).to eq(1)
|
294
|
-
engine.snapshot!
|
295
|
-
expect(production.size).to eq(0)
|
296
|
-
# puts count
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
context 'using the :kept clause' do
|
301
|
-
it 'should match kept items' do
|
302
|
-
count = 0
|
303
|
-
production = engine.rule do
|
304
|
-
forall {
|
305
|
-
kept 1, 2, 3
|
306
|
-
}
|
307
|
-
make { action { count += 1 } }
|
308
|
-
end
|
309
|
-
engine << [1, 2, 3]
|
310
|
-
expect(production.size).to eq(0)
|
311
|
-
engine.snapshot!
|
312
|
-
expect(production.size).to eq(1)
|
313
|
-
# puts count
|
314
|
-
end
|
315
|
-
|
316
|
-
it 'should not match asserted wmes' do
|
317
|
-
count = 0
|
318
|
-
production = engine.rule do
|
319
|
-
forall {
|
320
|
-
kept 1, 2, 3
|
321
|
-
}
|
322
|
-
make { action { count += 1 } }
|
323
|
-
end
|
324
|
-
expect(production.size).to eq(0)
|
325
|
-
engine.snapshot!
|
326
|
-
engine << [1, 2, 3]
|
327
|
-
expect(production.size).to eq(0)
|
328
|
-
# puts count
|
329
|
-
end
|
330
|
-
end
|
331
|
-
end
|
332
239
|
end
|
data/spec/network_spec.rb
CHANGED
@@ -14,7 +14,7 @@ describe Wongi::Engine::Network do
|
|
14
14
|
it 'should retract facts' do
|
15
15
|
subject << [1, 2, 3]
|
16
16
|
subject.retract [1, 2, 3]
|
17
|
-
expect(subject.select(:_, 2, :_)).to
|
17
|
+
expect(subject.select(:_, 2, :_).count).to eq(0)
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'asserted facts end up in productions' do
|
@@ -80,7 +80,7 @@ describe Wongi::Engine::Network do
|
|
80
80
|
end
|
81
81
|
|
82
82
|
it 'retracted facts should propagate through join chains' do
|
83
|
-
|
83
|
+
assignments = nil
|
84
84
|
|
85
85
|
prod = engine << rule {
|
86
86
|
forall {
|
@@ -88,7 +88,9 @@ describe Wongi::Engine::Network do
|
|
88
88
|
has :Y, :is, :Z
|
89
89
|
}
|
90
90
|
make {
|
91
|
-
action deactivate:
|
91
|
+
action deactivate: lambda { |token|
|
92
|
+
assignments = token.assignments
|
93
|
+
}
|
92
94
|
}
|
93
95
|
}
|
94
96
|
|
@@ -99,9 +101,9 @@ describe Wongi::Engine::Network do
|
|
99
101
|
|
100
102
|
engine.retract [1, :is, 2]
|
101
103
|
expect(prod).to have(0).tokens
|
102
|
-
expect(
|
103
|
-
expect(
|
104
|
-
expect(
|
104
|
+
expect(assignments[:X].call).to be == 1
|
105
|
+
expect(assignments[:Y].call).to be == 2
|
106
|
+
expect(assignments[:Z].call).to be == 3
|
105
107
|
end
|
106
108
|
|
107
109
|
it 'retraction should reactivate neg nodes' do
|
data/spec/overlay_spec.rb
CHANGED
@@ -1,16 +1,157 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'wongi-engine/alpha_index'
|
3
|
+
require 'wongi-engine/overlay'
|
4
|
+
require 'wongi-engine/wme'
|
2
5
|
|
3
|
-
describe Wongi::Engine::
|
6
|
+
describe Wongi::Engine::Overlay do
|
4
7
|
include Wongi::Engine::DSL
|
5
8
|
|
6
9
|
let(:engine) { Wongi::Engine.create }
|
7
10
|
|
11
|
+
describe "asserting facts" do
|
12
|
+
let(:engine) { double(:engine) }
|
13
|
+
let(:overlay) { Wongi::Engine::Overlay.new(engine) }
|
14
|
+
|
15
|
+
before do
|
16
|
+
# this cannot be put in the double constructor because it causes an infinite recursion
|
17
|
+
allow(engine).to receive(:default_overlay).and_return(overlay)
|
18
|
+
allow(engine).to receive(:real_assert)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'stores WMEs and retrieves by identity' do
|
22
|
+
wmes = [
|
23
|
+
Wongi::Engine::WME.new(1, 11, 111),
|
24
|
+
Wongi::Engine::WME.new(1, 11, 112),
|
25
|
+
Wongi::Engine::WME.new(1, 11, 113),
|
26
|
+
Wongi::Engine::WME.new(1, 12, 121),
|
27
|
+
Wongi::Engine::WME.new(1, 12, 122),
|
28
|
+
Wongi::Engine::WME.new(2, 22, 222),
|
29
|
+
Wongi::Engine::WME.new(2, 22, 223),
|
30
|
+
Wongi::Engine::WME.new(3, 33, 333),
|
31
|
+
Wongi::Engine::WME.new(3, 34, 333),
|
32
|
+
]
|
33
|
+
wmes.each { overlay.assert(_1) }
|
34
|
+
|
35
|
+
wmes.each { expect(overlay.find(_1)).to equal(_1) }
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'stores WMEs and retrieves by template' do
|
39
|
+
wmes = [
|
40
|
+
Wongi::Engine::WME.new(1, 11, 111),
|
41
|
+
Wongi::Engine::WME.new(1, 11, 112),
|
42
|
+
Wongi::Engine::WME.new(1, 11, 113),
|
43
|
+
Wongi::Engine::WME.new(1, 12, 121),
|
44
|
+
Wongi::Engine::WME.new(1, 12, 122),
|
45
|
+
Wongi::Engine::WME.new(2, 11, 111),
|
46
|
+
Wongi::Engine::WME.new(2, 11, 222),
|
47
|
+
Wongi::Engine::WME.new(2, 22, 222),
|
48
|
+
Wongi::Engine::WME.new(2, 22, 223),
|
49
|
+
Wongi::Engine::WME.new(3, 33, 113),
|
50
|
+
Wongi::Engine::WME.new(3, 33, 333),
|
51
|
+
Wongi::Engine::WME.new(3, 34, 333),
|
52
|
+
]
|
53
|
+
wmes.each { overlay.assert(_1) }
|
54
|
+
|
55
|
+
expect(overlay.select(1, :_, :_)).to have(5).items
|
56
|
+
expect(overlay.select(2, :_, :_)).to have(4).items
|
57
|
+
expect(overlay.select(3, :_, :_)).to have(3).items
|
58
|
+
expect(overlay.select(1, 11, :_)).to have(3).items
|
59
|
+
expect(overlay.select(:_, 11, :_)).to have(5).items
|
60
|
+
expect(overlay.select(:_, :_, 113)).to have(2).items
|
61
|
+
expect(overlay.select(:_, 22, :_)).to have(2).items
|
62
|
+
expect(overlay.select(:_, 22, 222)).to have(1).items
|
63
|
+
expect(overlay.select(:_, :_, 222)).to have(2).items
|
64
|
+
expect(overlay.select(:_, :_, 223)).to have(1).items
|
65
|
+
|
66
|
+
expect(overlay.select(:_, :_, :_)).to have(wmes.length).items
|
67
|
+
|
68
|
+
expect(overlay.select(1, 11, 111)).to have(1).items
|
69
|
+
expect(overlay.select(1, 11, 111).first).to equal(wmes.first)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "retracting facts" do
|
74
|
+
let(:engine) { double(:engine) }
|
75
|
+
let(:overlay) { Wongi::Engine::Overlay.new(engine) }
|
76
|
+
|
77
|
+
before do
|
78
|
+
# this cannot be put in the double constructor because it causes an infinite recursion
|
79
|
+
allow(engine).to receive(:default_overlay).and_return(overlay)
|
80
|
+
allow(engine).to receive(:real_assert)
|
81
|
+
allow(engine).to receive(:real_retract)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "removes asserted facts" do
|
85
|
+
wme = Wongi::Engine::WME.new(1, 11, 111)
|
86
|
+
|
87
|
+
overlay.assert(wme)
|
88
|
+
expect(overlay.select(:_, :_, :_)).to have(1).items
|
89
|
+
|
90
|
+
overlay.retract(wme)
|
91
|
+
expect(overlay.select(:_, :_, :_)).to have(0).items
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "layered" do
|
96
|
+
let(:engine) { double(:engine) }
|
97
|
+
let(:overlay) { Wongi::Engine::Overlay.new(engine) }
|
98
|
+
|
99
|
+
before do
|
100
|
+
# this cannot be put in the double constructor because it causes an infinite recursion
|
101
|
+
allow(engine).to receive(:default_overlay).and_return(overlay)
|
102
|
+
allow(engine).to receive(:real_assert)
|
103
|
+
allow(engine).to receive(:real_retract)
|
104
|
+
end
|
105
|
+
it "maintains visibility on each layer" do
|
106
|
+
child1 = overlay.new_child
|
107
|
+
child2 = child1.new_child
|
108
|
+
|
109
|
+
wme = Wongi::Engine::WME.new(1, 2, 3)
|
110
|
+
|
111
|
+
overlay.assert(wme)
|
112
|
+
expect(overlay.find(wme)).to eq(wme)
|
113
|
+
expect(child1.find(wme)).to eq(wme)
|
114
|
+
expect(child2.find(wme)).to eq(wme)
|
115
|
+
|
116
|
+
child1.retract(wme)
|
117
|
+
expect(overlay.find(wme)).to eq(wme)
|
118
|
+
expect(child1.find(wme)).to be_nil
|
119
|
+
expect(child2.find(wme)).to be_nil
|
120
|
+
|
121
|
+
child2.assert(wme)
|
122
|
+
expect(overlay.find(wme)).to eq(wme)
|
123
|
+
expect(child1.find(wme)).to be_nil
|
124
|
+
expect(child2.find(wme)).to eq(wme)
|
125
|
+
|
126
|
+
child2.retract(wme)
|
127
|
+
expect(overlay.find(wme)).to eq(wme)
|
128
|
+
expect(child1.find(wme)).to be_nil
|
129
|
+
expect(child2.find(wme)).to be_nil
|
130
|
+
|
131
|
+
child1.assert(wme)
|
132
|
+
expect(overlay.find(wme)).to eq(wme)
|
133
|
+
expect(child1.find(wme)).to eq(wme)
|
134
|
+
expect(child2.find(wme)).to eq(wme)
|
135
|
+
|
136
|
+
overlay.retract(wme)
|
137
|
+
expect(overlay.find(wme)).to be_nil
|
138
|
+
expect(child1.find(wme)).to be_nil
|
139
|
+
expect(child2.find(wme)).to be_nil
|
140
|
+
|
141
|
+
child1.assert(wme)
|
142
|
+
expect(overlay.find(wme)).to be_nil
|
143
|
+
expect(child1.find(wme)).to eq(wme)
|
144
|
+
expect(child2.find(wme)).to eq(wme)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
8
148
|
it 'should be disposable' do
|
9
149
|
production = engine << rule {
|
10
150
|
forall {
|
11
151
|
has 1, 2, :X
|
12
152
|
}
|
13
153
|
}
|
154
|
+
|
14
155
|
engine.with_overlay { |overlay|
|
15
156
|
overlay << [1, 2, 3]
|
16
157
|
expect(production).to have(1).token
|
@@ -18,6 +159,24 @@ describe Wongi::Engine::DataOverlay do
|
|
18
159
|
expect(production).to have(0).tokens
|
19
160
|
end
|
20
161
|
|
162
|
+
it 'works with retractions' do
|
163
|
+
production = engine << rule {
|
164
|
+
forall {
|
165
|
+
has 1, 2, :X
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
engine << [1, 2, 3]
|
170
|
+
expect(production).to have(1).token
|
171
|
+
|
172
|
+
engine.with_overlay do |overlay|
|
173
|
+
overlay.retract [1, 2, 3]
|
174
|
+
expect(production).to have(0).token
|
175
|
+
end
|
176
|
+
|
177
|
+
expect(production).to have(1).token
|
178
|
+
end
|
179
|
+
|
21
180
|
it 'should generate into correct overlays' do
|
22
181
|
production = engine << rule {
|
23
182
|
forall {
|
@@ -46,12 +205,11 @@ describe Wongi::Engine::DataOverlay do
|
|
46
205
|
expect(prod).to have(1).tokens
|
47
206
|
|
48
207
|
engine.with_overlay do |overlay|
|
49
|
-
overlay << [
|
208
|
+
overlay << %i[x y z]
|
50
209
|
expect(prod).to have(0).tokens
|
51
210
|
end
|
52
211
|
|
53
212
|
expect(prod).to have(1).tokens
|
54
|
-
|
55
213
|
end
|
56
214
|
|
57
215
|
it 'works with assignments' do
|
@@ -27,6 +27,45 @@ describe "ANY rule" do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
context 'with two options' do
|
31
|
+
let :production do
|
32
|
+
engine << rule do
|
33
|
+
forall {
|
34
|
+
any {
|
35
|
+
option {
|
36
|
+
has :A, :path1, :_
|
37
|
+
}
|
38
|
+
option {
|
39
|
+
has :A, :path2, :_
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should fire on the first path' do
|
47
|
+
engine << [:x, :path1, true]
|
48
|
+
expect(production.tokens).to have(1).item
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should fire on the second path' do
|
52
|
+
engine << [:x, :path2, true]
|
53
|
+
expect(production.tokens).to have(1).item
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should fire twice on both paths at once and be retractable' do
|
57
|
+
engine << [:x, :path1, true]
|
58
|
+
engine << [:x, :path2, true]
|
59
|
+
expect(production.tokens).to have(2).items
|
60
|
+
|
61
|
+
engine.retract [:x, :path2, true]
|
62
|
+
expect(production.tokens).to have(1).items
|
63
|
+
|
64
|
+
engine.retract [:x, :path1, true]
|
65
|
+
expect(production.tokens).to have(0).items
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
30
69
|
context "with several options" do
|
31
70
|
specify "all matching branches must pass" do
|
32
71
|
engine << rule('two-options') {
|