wongi-engine 0.3.9 → 0.4.0.pre.alpha2
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.
- 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 +17 -15
- data/lib/wongi-engine/beta/assignment_node.rb +7 -2
- data/lib/wongi-engine/beta/beta_node.rb +36 -23
- data/lib/wongi-engine/beta/filter_node.rb +8 -13
- data/lib/wongi-engine/beta/join_node.rb +17 -29
- 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 +25 -55
- data/lib/wongi-engine/beta/optional_node.rb +26 -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') {
|