wongi-engine 0.3.5 → 0.3.7
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/.gitignore +1 -0
- data/.rubocop.yml +80 -0
- data/.travis.yml +8 -15
- data/Gemfile +4 -0
- data/README.md +3 -3
- data/Rakefile +1 -2
- data/examples/ex01.rb +1 -1
- data/examples/ex02.rb +3 -7
- data/examples/graphviz.rb +1 -1
- data/examples/rdf.rb +1 -1
- data/examples/timeline.rb +6 -6
- data/lib/wongi-engine/alpha_memory.rb +5 -9
- data/lib/wongi-engine/beta/aggregate_node.rb +93 -0
- data/lib/wongi-engine/beta/assignment_node.rb +10 -18
- data/lib/wongi-engine/beta/beta_memory.rb +20 -9
- data/lib/wongi-engine/beta/beta_node.rb +14 -22
- data/lib/wongi-engine/beta/filter_node.rb +12 -15
- data/lib/wongi-engine/beta/join_node.rb +47 -61
- data/lib/wongi-engine/beta/ncc_node.rb +17 -20
- data/lib/wongi-engine/beta/ncc_partner.rb +17 -17
- data/lib/wongi-engine/beta/neg_node.rb +37 -37
- data/lib/wongi-engine/beta/optional_node.rb +53 -60
- data/lib/wongi-engine/beta/or_node.rb +6 -9
- data/lib/wongi-engine/beta/production_node.rb +6 -9
- data/lib/wongi-engine/beta.rb +1 -0
- data/lib/wongi-engine/compiler.rb +38 -22
- data/lib/wongi-engine/core_ext.rb +11 -20
- data/lib/wongi-engine/data_overlay.rb +23 -26
- data/lib/wongi-engine/dsl/action/assign_action.rb +1 -1
- data/lib/wongi-engine/dsl/action/base.rb +1 -4
- data/lib/wongi-engine/dsl/action/error_generator.rb +9 -9
- data/lib/wongi-engine/dsl/action/simple_action.rb +14 -10
- data/lib/wongi-engine/dsl/action/simple_collector.rb +10 -25
- data/lib/wongi-engine/dsl/action/statement_generator.rb +15 -14
- data/lib/wongi-engine/dsl/action/trace_action.rb +9 -9
- data/lib/wongi-engine/dsl/any_rule.rb +16 -20
- data/lib/wongi-engine/dsl/assuming.rb +8 -15
- data/lib/wongi-engine/dsl/builder.rb +13 -12
- data/lib/wongi-engine/dsl/clause/aggregate.rb +21 -0
- data/lib/wongi-engine/dsl/clause/assign.rb +4 -4
- data/lib/wongi-engine/dsl/clause/fact.rb +10 -6
- data/lib/wongi-engine/dsl/clause/gen.rb +3 -5
- data/lib/wongi-engine/dsl/clause/generic.rb +7 -9
- data/lib/wongi-engine/dsl/generated.rb +6 -12
- data/lib/wongi-engine/dsl/ncc_subrule.rb +6 -9
- data/lib/wongi-engine/dsl/query.rb +12 -15
- data/lib/wongi-engine/dsl/rule.rb +45 -50
- data/lib/wongi-engine/dsl.rb +30 -16
- data/lib/wongi-engine/enumerators.rb +2 -1
- data/lib/wongi-engine/error.rb +6 -7
- data/lib/wongi-engine/filter/asserting_test.rb +4 -7
- data/lib/wongi-engine/filter/equality_test.rb +15 -18
- data/lib/wongi-engine/filter/filter_test.rb +3 -6
- data/lib/wongi-engine/filter/greater_than_or_equal_test.rb +5 -2
- data/lib/wongi-engine/filter/greater_than_test.rb +15 -18
- data/lib/wongi-engine/filter/inequality_test.rb +15 -18
- data/lib/wongi-engine/filter/less_than_or_equal_test.rb +5 -2
- data/lib/wongi-engine/filter/less_than_test.rb +15 -18
- data/lib/wongi-engine/filter.rb +1 -1
- data/lib/wongi-engine/graph.rb +13 -20
- data/lib/wongi-engine/network/collectable.rb +10 -14
- data/lib/wongi-engine/network/debug.rb +16 -24
- data/lib/wongi-engine/network.rb +110 -129
- data/lib/wongi-engine/ruleset.rb +18 -20
- data/lib/wongi-engine/template.rb +31 -30
- data/lib/wongi-engine/token.rb +33 -33
- data/lib/wongi-engine/version.rb +1 -1
- data/lib/wongi-engine/wme.rb +17 -23
- data/lib/wongi-engine/wme_match_data.rb +5 -9
- data/lib/wongi-engine.rb +0 -4
- data/spec/action_class_spec.rb +43 -49
- data/spec/beta_node_spec.rb +2 -8
- data/spec/bug_specs/issue_4_spec.rb +12 -20
- data/spec/dataset_spec.rb +3 -6
- data/spec/filter_specs/assert_test_spec.rb +12 -31
- data/spec/filter_specs/greater_than_equality_test_spec.rb +2 -5
- data/spec/filter_specs/less_test_spec.rb +7 -17
- data/spec/filter_specs/less_than_equality_test_spec.rb +3 -6
- data/spec/generation_spec.rb +45 -54
- data/spec/high_level_spec.rb +95 -141
- data/spec/network_spec.rb +77 -68
- data/spec/overlay_spec.rb +4 -5
- data/spec/rule_specs/aggregate_spec.rb +197 -0
- data/spec/rule_specs/any_rule_spec.rb +73 -19
- data/spec/rule_specs/assign_spec.rb +10 -16
- data/spec/rule_specs/assuming_spec.rb +10 -17
- data/spec/rule_specs/maybe_rule_spec.rb +4 -15
- data/spec/rule_specs/ncc_spec.rb +48 -65
- data/spec/rule_specs/negative_rule_spec.rb +13 -27
- data/spec/rule_specs/or_rule_spec.rb +3 -13
- data/spec/ruleset_spec.rb +11 -17
- data/spec/simple_action_spec.rb +4 -14
- data/spec/wme_spec.rb +14 -21
- data/wongi-engine.gemspec +22 -22
- metadata +19 -41
- data/.hgignore +0 -7
- data/spec/dsl_spec.rb +0 -9
data/spec/network_spec.rb
CHANGED
@@ -1,60 +1,85 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Wongi::Engine::Network do
|
4
|
+
include Wongi::Engine::DSL
|
4
5
|
|
5
|
-
let(
|
6
|
+
let(:engine) { Wongi::Engine.create }
|
6
7
|
subject { engine }
|
7
8
|
|
8
9
|
it 'should assert facts' do
|
9
|
-
subject << [1,2,3]
|
10
|
-
expect(
|
10
|
+
subject << [1, 2, 3]
|
11
|
+
expect(subject.select(:_, 2, :_)).to have(1).item
|
11
12
|
end
|
12
13
|
|
13
14
|
it 'should retract facts' do
|
14
|
-
subject << [1,2,3]
|
15
|
-
subject.retract [1,2,3]
|
16
|
-
expect(
|
15
|
+
subject << [1, 2, 3]
|
16
|
+
subject.retract [1, 2, 3]
|
17
|
+
expect(subject.select(:_, 2, :_)).to be_empty
|
17
18
|
end
|
18
19
|
|
19
20
|
it 'asserted facts end up in productions' do
|
20
|
-
|
21
21
|
prod = subject << rule { forall { has :X, 2, :Z } }
|
22
|
-
subject << [1,2,3]
|
23
|
-
expect(
|
22
|
+
subject << [1, 2, 3]
|
23
|
+
expect(prod).to have(1).tokens
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'rules can be removed from engine' do
|
27
|
+
subject << [1, 2, 3]
|
28
|
+
subject << [4, 5, 6]
|
29
|
+
|
30
|
+
prod1 = subject << rule { forall { has :X, 2, :Z } }
|
31
|
+
prod2 = subject << rule { forall { has :X, 5, :Z } }
|
32
|
+
|
33
|
+
expect(prod1).to have(1).tokens
|
34
|
+
expect(prod2).to have(1).tokens
|
24
35
|
|
36
|
+
subject.remove_production(prod1)
|
37
|
+
|
38
|
+
expect(prod1).to have(0).tokens
|
39
|
+
expect(prod2).to have(1).tokens
|
25
40
|
end
|
26
41
|
|
27
|
-
it '
|
42
|
+
it 'new rules can be added to engine after a rule has been been removed' do
|
43
|
+
subject << [1, 2, 3]
|
44
|
+
subject << [4, 5, 6]
|
28
45
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
expect( prod ).to have(0).tokens
|
46
|
+
prod1 = subject << rule { forall { has :X, 2, :Z } }
|
47
|
+
|
48
|
+
expect(prod1).to have(1).tokens
|
33
49
|
|
50
|
+
subject.remove_production(prod1)
|
51
|
+
expect(prod1).to have(0).tokens
|
52
|
+
|
53
|
+
prod2 = subject << rule { forall { has :X, 5, :Z } }
|
54
|
+
expect(prod2).to have(1).tokens
|
34
55
|
end
|
35
56
|
|
36
|
-
it 'retracted facts
|
57
|
+
it 'retracted facts are removed from productions' do
|
58
|
+
prod = subject << rule { forall { has :X, 2, :Z } }
|
59
|
+
subject << [1, 2, 3]
|
60
|
+
subject.retract [1, 2, 3]
|
61
|
+
expect(prod).to have(0).tokens
|
62
|
+
end
|
37
63
|
|
64
|
+
it 'retracted facts should trigger deactivation' do
|
38
65
|
activated_z = nil
|
39
66
|
deactivated_z = nil
|
40
67
|
|
41
|
-
|
68
|
+
subject << rule {
|
42
69
|
forall { has :X, 2, :Z }
|
43
70
|
make {
|
44
71
|
action activate: ->(token) { activated_z = token[:Z] },
|
45
72
|
deactivate: ->(token) { deactivated_z = token[:Z] }
|
46
73
|
}
|
47
74
|
}
|
48
|
-
subject << [1,2,3]
|
49
|
-
expect(
|
50
|
-
|
51
|
-
subject.retract [1,2,3]
|
52
|
-
expect( deactivated_z ).to be == 3
|
75
|
+
subject << [1, 2, 3]
|
76
|
+
expect(activated_z).to be == 3
|
53
77
|
|
78
|
+
subject.retract [1, 2, 3]
|
79
|
+
expect(deactivated_z).to be == 3
|
54
80
|
end
|
55
81
|
|
56
82
|
it 'retracted facts should propagate through join chains' do
|
57
|
-
|
58
83
|
deactivated = nil
|
59
84
|
|
60
85
|
prod = engine << rule {
|
@@ -70,113 +95,97 @@ describe Wongi::Engine::Network do
|
|
70
95
|
engine << [1, :is, 2]
|
71
96
|
engine << [2, :is, 3]
|
72
97
|
|
73
|
-
expect(
|
98
|
+
expect(prod).to have(1).tokens
|
74
99
|
|
75
100
|
engine.retract [1, :is, 2]
|
76
|
-
expect(
|
77
|
-
expect(
|
78
|
-
expect(
|
79
|
-
expect(
|
80
|
-
|
101
|
+
expect(prod).to have(0).tokens
|
102
|
+
expect(deactivated[:X]).to be == 1
|
103
|
+
expect(deactivated[:Y]).to be == 2
|
104
|
+
expect(deactivated[:Z]).to be == 3
|
81
105
|
end
|
82
106
|
|
83
107
|
it 'retraction should reactivate neg nodes' do
|
108
|
+
prod = engine << rule { forall { neg 1, 2, 3 } }
|
84
109
|
|
85
|
-
prod
|
86
|
-
|
87
|
-
expect( prod ).to have(1).tokens
|
110
|
+
expect(prod).to have(1).tokens
|
88
111
|
|
89
|
-
engine << [1, 2
|
90
|
-
expect(
|
112
|
+
engine << [1, 2, 3]
|
113
|
+
expect(prod).to have(0).tokens
|
91
114
|
|
92
115
|
engine.retract [1, 2, 3]
|
93
|
-
expect(
|
94
|
-
|
116
|
+
expect(prod).to have(1).tokens
|
95
117
|
end
|
96
118
|
|
97
119
|
describe 'retraction with neg nodes lower in the chain' do
|
98
|
-
|
99
|
-
|
100
|
-
expect( prod ).to have(n).tokens
|
120
|
+
def expect_tokens(n)
|
121
|
+
expect(prod).to have(n).tokens
|
101
122
|
end
|
102
123
|
|
103
|
-
before
|
104
|
-
|
124
|
+
before do
|
105
125
|
engine << rule('retract') {
|
106
126
|
forall {
|
107
127
|
has :x, :u, :Y
|
108
128
|
neg :Y, :w, :_
|
109
129
|
}
|
110
130
|
}
|
111
|
-
|
112
131
|
end
|
113
132
|
|
114
|
-
let(
|
133
|
+
let(:prod) { engine.productions['retract'] }
|
115
134
|
|
116
135
|
specify 'case 1' do
|
117
|
-
|
118
|
-
engine << [:x, :u, :y]
|
136
|
+
engine << %i[x u y]
|
119
137
|
expect_tokens 1
|
120
138
|
|
121
|
-
engine << [
|
139
|
+
engine << %i[y w z]
|
122
140
|
expect_tokens 0
|
123
141
|
|
124
|
-
engine.retract [
|
142
|
+
engine.retract %i[y w z]
|
125
143
|
expect_tokens 1
|
126
144
|
|
127
|
-
engine.retract [
|
145
|
+
engine.retract %i[x u y]
|
128
146
|
expect_tokens 0
|
129
|
-
|
130
147
|
end
|
131
148
|
|
132
149
|
specify 'case 2' do
|
133
|
-
|
134
|
-
engine << [:x, :u, :y]
|
150
|
+
engine << %i[x u y]
|
135
151
|
expect_tokens 1
|
136
152
|
|
137
|
-
engine << [
|
153
|
+
engine << %i[y w z]
|
138
154
|
expect_tokens 0
|
139
155
|
|
140
|
-
engine.retract [
|
156
|
+
engine.retract %i[x u y]
|
141
157
|
expect_tokens 0
|
142
158
|
|
143
|
-
engine.retract [
|
159
|
+
engine.retract %i[y w z]
|
144
160
|
expect_tokens 0
|
145
|
-
|
146
161
|
end
|
147
162
|
|
148
163
|
specify 'case 3' do
|
149
|
-
|
150
|
-
engine << [:y, :w, :z]
|
164
|
+
engine << %i[y w z]
|
151
165
|
expect_tokens 0
|
152
166
|
|
153
|
-
engine << [
|
167
|
+
engine << %i[x u y]
|
154
168
|
expect_tokens 0
|
155
169
|
|
156
|
-
engine.retract [
|
170
|
+
engine.retract %i[x u y]
|
157
171
|
expect_tokens 0
|
158
172
|
|
159
|
-
engine.retract [
|
173
|
+
engine.retract %i[y w z]
|
160
174
|
expect_tokens 0
|
161
|
-
|
162
175
|
end
|
163
176
|
|
164
177
|
specify 'case 4' do
|
165
|
-
|
166
|
-
engine << [:y, :w, :z]
|
178
|
+
engine << %i[y w z]
|
167
179
|
expect_tokens 0
|
168
180
|
|
169
|
-
engine << [
|
181
|
+
engine << %i[x u y]
|
170
182
|
expect_tokens 0
|
171
183
|
|
172
|
-
engine.retract [
|
184
|
+
engine.retract %i[y w z]
|
173
185
|
expect_tokens 1
|
174
186
|
|
175
|
-
engine.retract [
|
187
|
+
engine.retract %i[x u y]
|
176
188
|
expect_tokens 0
|
177
|
-
|
178
189
|
end
|
179
|
-
|
180
190
|
end
|
181
|
-
|
182
191
|
end
|
data/spec/overlay_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe Wongi::Engine::DataOverlay do
|
|
12
12
|
}
|
13
13
|
}
|
14
14
|
engine.with_overlay { |overlay|
|
15
|
-
overlay << [1,2,3]
|
15
|
+
overlay << [1, 2, 3]
|
16
16
|
expect(production).to have(1).token
|
17
17
|
}
|
18
18
|
expect(production).to have(0).tokens
|
@@ -28,7 +28,7 @@ describe Wongi::Engine::DataOverlay do
|
|
28
28
|
}
|
29
29
|
}
|
30
30
|
engine.with_overlay { |overlay|
|
31
|
-
overlay << [1,2,3]
|
31
|
+
overlay << [1, 2, 3]
|
32
32
|
expect(production).to have(1).token
|
33
33
|
expect(engine.find(3, 4, 5)).not_to be_nil
|
34
34
|
}
|
@@ -49,13 +49,12 @@ describe Wongi::Engine::DataOverlay do
|
|
49
49
|
}
|
50
50
|
|
51
51
|
engine.with_overlay { |overlay|
|
52
|
-
overlay << [1,2,3]
|
52
|
+
overlay << [1, 2, 3]
|
53
53
|
expect(production).to have(1).token
|
54
|
-
expect(engine.find(:
|
54
|
+
expect(engine.find(:person, 'stuff', 6)).not_to be_nil
|
55
55
|
}
|
56
56
|
|
57
57
|
expect(production).to have(0).tokens
|
58
58
|
expect(engine.find(:_, :_, :_)).to be_nil
|
59
59
|
end
|
60
|
-
|
61
60
|
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
describe 'aggregate' do
|
5
|
+
include Wongi::Engine::DSL
|
6
|
+
|
7
|
+
let(:engine) { Wongi::Engine.create }
|
8
|
+
let(:rule_name) { SecureRandom.alphanumeric(16) }
|
9
|
+
let(:production) { engine.productions[rule_name] }
|
10
|
+
|
11
|
+
context 'generic clause' do
|
12
|
+
it 'should return a single token' do
|
13
|
+
engine << rule(rule_name) do
|
14
|
+
forall {
|
15
|
+
aggregate :_, :weight, :_, on: :object, function: :min, assign: :X
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
expect(production.size).to be == 0
|
20
|
+
|
21
|
+
engine << [:apple, :weight, 5]
|
22
|
+
expect(production.size).to be == 1
|
23
|
+
expect(production.tokens.first[:X]).to be == 5
|
24
|
+
|
25
|
+
engine << [:pea, :weight, 2]
|
26
|
+
expect(production.size).to be == 1
|
27
|
+
expect(production.tokens.first[:X]).to be == 2
|
28
|
+
|
29
|
+
engine << [:melon, :weight, 15]
|
30
|
+
expect(production.size).to be == 1
|
31
|
+
expect(production.tokens.first[:X]).to be == 2
|
32
|
+
|
33
|
+
engine.retract [:pea, :weight, 2]
|
34
|
+
expect(production.size).to be == 1
|
35
|
+
expect(production.tokens.first[:X]).to be == 5
|
36
|
+
|
37
|
+
engine.retract [:apple, :weight, 5]
|
38
|
+
expect(production.size).to be == 1
|
39
|
+
expect(production.tokens.first[:X]).to be == 15
|
40
|
+
|
41
|
+
engine.retract [:melon, :weight, 15]
|
42
|
+
expect(production.size).to be == 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'least' do
|
47
|
+
it 'works' do
|
48
|
+
engine << rule(rule_name) do
|
49
|
+
forall {
|
50
|
+
least :_, :weight, :_, on: :object, assign: :X
|
51
|
+
has :Fruit, :weight, :X
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
engine << [:apple, :weight, 5]
|
56
|
+
expect(production.size).to be == 1
|
57
|
+
expect(production.tokens.first[:X]).to be == 5
|
58
|
+
expect(production.tokens.first[:Fruit]).to be == :apple
|
59
|
+
|
60
|
+
engine << [:pea, :weight, 2]
|
61
|
+
expect(production.size).to be == 1
|
62
|
+
expect(production.tokens.first[:X]).to be == 2
|
63
|
+
expect(production.tokens.first[:Fruit]).to be == :pea
|
64
|
+
|
65
|
+
engine.retract [:pea, :weight, 2]
|
66
|
+
expect(production.size).to be == 1
|
67
|
+
expect(production.tokens.first[:X]).to be == 5
|
68
|
+
expect(production.tokens.first[:Fruit]).to be == :apple
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'min' do
|
73
|
+
it 'works' do
|
74
|
+
engine << rule(rule_name) do
|
75
|
+
forall {
|
76
|
+
min :_, :weight, :_, on: :object, assign: :X
|
77
|
+
has :Fruit, :weight, :X
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
engine << [:apple, :weight, 5]
|
82
|
+
expect(production.size).to be == 1
|
83
|
+
expect(production.tokens.first[:X]).to be == 5
|
84
|
+
expect(production.tokens.first[:Fruit]).to be == :apple
|
85
|
+
|
86
|
+
engine << [:pea, :weight, 2]
|
87
|
+
expect(production.size).to be == 1
|
88
|
+
expect(production.tokens.first[:X]).to be == 2
|
89
|
+
expect(production.tokens.first[:Fruit]).to be == :pea
|
90
|
+
|
91
|
+
engine.retract [:pea, :weight, 2]
|
92
|
+
expect(production.size).to be == 1
|
93
|
+
expect(production.tokens.first[:X]).to be == 5
|
94
|
+
expect(production.tokens.first[:Fruit]).to be == :apple
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'greatest' do
|
99
|
+
it 'works' do
|
100
|
+
engine << rule(rule_name) do
|
101
|
+
forall {
|
102
|
+
greatest :_, :weight, :_, on: :object, assign: :X
|
103
|
+
has :Fruit, :weight, :X
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
engine << [:pea, :weight, 2]
|
108
|
+
expect(production.size).to be == 1
|
109
|
+
expect(production.tokens.first[:X]).to be == 2
|
110
|
+
expect(production.tokens.first[:Fruit]).to be == :pea
|
111
|
+
|
112
|
+
engine << [:apple, :weight, 5]
|
113
|
+
expect(production.size).to be == 1
|
114
|
+
expect(production.tokens.first[:X]).to be == 5
|
115
|
+
expect(production.tokens.first[:Fruit]).to be == :apple
|
116
|
+
|
117
|
+
engine.retract [:apple, :weight, 5]
|
118
|
+
expect(production.size).to be == 1
|
119
|
+
expect(production.tokens.first[:X]).to be == 2
|
120
|
+
expect(production.tokens.first[:Fruit]).to be == :pea
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'max' do
|
125
|
+
it 'works' do
|
126
|
+
engine << rule(rule_name) do
|
127
|
+
forall {
|
128
|
+
max :_, :weight, :_, on: :object, assign: :X
|
129
|
+
has :Fruit, :weight, :X
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
engine << [:pea, :weight, 2]
|
134
|
+
expect(production.size).to be == 1
|
135
|
+
expect(production.tokens.first[:X]).to be == 2
|
136
|
+
expect(production.tokens.first[:Fruit]).to be == :pea
|
137
|
+
|
138
|
+
engine << [:apple, :weight, 5]
|
139
|
+
expect(production.size).to be == 1
|
140
|
+
expect(production.tokens.first[:X]).to be == 5
|
141
|
+
expect(production.tokens.first[:Fruit]).to be == :apple
|
142
|
+
|
143
|
+
engine.retract [:apple, :weight, 5]
|
144
|
+
expect(production.size).to be == 1
|
145
|
+
expect(production.tokens.first[:X]).to be == 2
|
146
|
+
expect(production.tokens.first[:Fruit]).to be == :pea
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'count' do
|
151
|
+
it 'works' do
|
152
|
+
engine << rule(rule_name) do
|
153
|
+
forall {
|
154
|
+
count :_, :weight, :_, assign: :Count
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
engine << [:pea, :weight, 1]
|
159
|
+
expect(production.size).to be == 1
|
160
|
+
expect(production.tokens.first[:Count]).to be == 1
|
161
|
+
|
162
|
+
engine << [:apple, :weight, 5]
|
163
|
+
expect(production.size).to be == 1
|
164
|
+
expect(production.tokens.first[:Count]).to be == 2
|
165
|
+
|
166
|
+
engine << [:watermelon, :weight, 15]
|
167
|
+
expect(production.size).to be == 1
|
168
|
+
expect(production.tokens.first[:Count]).to be == 3
|
169
|
+
|
170
|
+
engine.retract [:apple, :weight, 5]
|
171
|
+
expect(production.size).to be == 1
|
172
|
+
expect(production.tokens.first[:Count]).to be == 2
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'works with a post-filter' do
|
176
|
+
engine << rule(rule_name) do
|
177
|
+
forall {
|
178
|
+
count :_, :weight, :_, assign: :Count
|
179
|
+
gte :Count, 3 # pass if at least 3 matching facts exist
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
engine << [:pea, :weight, 1]
|
184
|
+
expect(production.size).to be == 0
|
185
|
+
|
186
|
+
engine << [:apple, :weight, 5]
|
187
|
+
expect(production.size).to be == 0
|
188
|
+
|
189
|
+
engine << [:watermelon, :weight, 15]
|
190
|
+
expect(production.size).to be == 1
|
191
|
+
expect(production.tokens.first[:Count]).to be == 3
|
192
|
+
|
193
|
+
engine.retract [:apple, :weight, 5]
|
194
|
+
expect(production.size).to be == 0
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -1,21 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "ANY rule" do
|
4
|
-
|
5
4
|
include Wongi::Engine::DSL
|
6
5
|
|
7
|
-
|
8
|
-
@engine = Wongi::Engine.create
|
9
|
-
end
|
10
|
-
|
11
|
-
def engine
|
12
|
-
@engine
|
13
|
-
end
|
6
|
+
let(:engine) { Wongi::Engine.create }
|
14
7
|
|
15
8
|
context "with just one option" do
|
16
|
-
|
17
9
|
it "should act like a positive matcher" do
|
18
|
-
|
19
10
|
engine << rule('one-option') {
|
20
11
|
forall {
|
21
12
|
any {
|
@@ -33,15 +24,11 @@ describe "ANY rule" do
|
|
33
24
|
engine << [3, 4, 5]
|
34
25
|
|
35
26
|
expect(production.size).to eq(1)
|
36
|
-
|
37
27
|
end
|
38
|
-
|
39
28
|
end
|
40
29
|
|
41
30
|
context "with several options" do
|
42
|
-
|
43
31
|
specify "all matching branches must pass" do
|
44
|
-
|
45
32
|
engine << rule('two-options') {
|
46
33
|
forall {
|
47
34
|
has 1, 2, :X
|
@@ -64,14 +51,12 @@ describe "ANY rule" do
|
|
64
51
|
engine << [1, 2, 3]
|
65
52
|
engine << [3, 4, 5]
|
66
53
|
engine << [1, 2, "three"]
|
67
|
-
engine << [
|
54
|
+
engine << %w[three four five]
|
68
55
|
|
69
56
|
expect(production.size).to eq(2)
|
70
|
-
expect(
|
71
|
-
expect(
|
72
|
-
|
57
|
+
expect(engine.collection(:threes)).to include(3)
|
58
|
+
expect(engine.collection(:threes)).to include("three")
|
73
59
|
end
|
74
|
-
|
75
60
|
end
|
76
61
|
|
77
62
|
context "with two options and same assignments" do
|
@@ -106,4 +91,73 @@ describe "ANY rule" do
|
|
106
91
|
end
|
107
92
|
end
|
108
93
|
|
94
|
+
describe "ordering rules" do
|
95
|
+
let :any_stale_pastry_rule do
|
96
|
+
rule do
|
97
|
+
forall {
|
98
|
+
# has :A, :name, :Name # uncomment this line will make test pass
|
99
|
+
any {
|
100
|
+
option {
|
101
|
+
has :A, :name, 'Donut'
|
102
|
+
}
|
103
|
+
option {
|
104
|
+
has :A, :name, 'Cookie'
|
105
|
+
}
|
106
|
+
}
|
107
|
+
has :A, :condition, 'stale' # moving this line above 'any' will make test pass
|
108
|
+
}
|
109
|
+
make {
|
110
|
+
collect :A, :stale_pastries
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
let :fresh_donut_rule do
|
116
|
+
rule do
|
117
|
+
forall {
|
118
|
+
has :A, :name, 'Donut'
|
119
|
+
has :A, :condition, 'fresh'
|
120
|
+
}
|
121
|
+
make {
|
122
|
+
collect :A, :fresh_donuts
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
let :fresh_cookie_rule do
|
128
|
+
rule do
|
129
|
+
forall {
|
130
|
+
# swapping the following lines will make test pass
|
131
|
+
has :A, :name, 'Cookie'
|
132
|
+
has :A, :condition, 'fresh'
|
133
|
+
}
|
134
|
+
make {
|
135
|
+
collect :A, :fresh_cookies
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
before do
|
141
|
+
engine << any_stale_pastry_rule # commenting this rule, or moving it to end of rules will make test pass
|
142
|
+
|
143
|
+
engine << fresh_donut_rule
|
144
|
+
engine << fresh_cookie_rule # commenting this rule will make test pass
|
145
|
+
|
146
|
+
engine << [:donut, :name, 'Donut']
|
147
|
+
engine << [:donut, :condition, 'fresh']
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'has no fresh cookies' do
|
151
|
+
# this expectation fails; the collection contains [:donut]!
|
152
|
+
expect(engine.collection(:fresh_cookies)).to match_array([])
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'has one fresh donut' do
|
156
|
+
expect(engine.collection(:fresh_donuts)).to match_array([:donut])
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'has no stale pastries' do
|
160
|
+
expect(engine.collection(:stale_pastries)).to match_array([])
|
161
|
+
end
|
162
|
+
end
|
109
163
|
end
|
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "ASSIGN rule" do
|
4
|
-
|
5
|
-
let(
|
4
|
+
include Wongi::Engine::DSL
|
5
|
+
let(:engine) { Wongi::Engine.create }
|
6
6
|
|
7
7
|
it "should assign simple expressions" do
|
8
|
-
|
9
8
|
production = engine << rule {
|
10
9
|
forall {
|
11
10
|
assign :X do
|
@@ -15,7 +14,6 @@ describe "ASSIGN rule" do
|
|
15
14
|
}
|
16
15
|
expect(production.size).to eq(1)
|
17
16
|
expect(production.tokens.first[:X]).to eq(42)
|
18
|
-
|
19
17
|
end
|
20
18
|
|
21
19
|
it 'should be available in the make section' do
|
@@ -35,7 +33,6 @@ describe "ASSIGN rule" do
|
|
35
33
|
end
|
36
34
|
|
37
35
|
it "should be able to access previous assignments" do
|
38
|
-
|
39
36
|
production = engine << rule {
|
40
37
|
forall {
|
41
38
|
has 1, 2, :X
|
@@ -47,11 +44,9 @@ describe "ASSIGN rule" do
|
|
47
44
|
|
48
45
|
engine << [1, 2, 5]
|
49
46
|
expect(production.tokens.first[:Y]).to eq(10)
|
50
|
-
|
51
47
|
end
|
52
48
|
|
53
49
|
it 'should be deactivatable' do
|
54
|
-
|
55
50
|
prod = engine << rule {
|
56
51
|
forall {
|
57
52
|
has 1, 2, :X
|
@@ -64,13 +59,12 @@ describe "ASSIGN rule" do
|
|
64
59
|
engine << [1, 2, 5]
|
65
60
|
engine.retract [1, 2, 5]
|
66
61
|
|
67
|
-
expect(
|
68
|
-
|
62
|
+
expect(prod).to have(0).tokens
|
69
63
|
end
|
70
64
|
|
71
65
|
it 'should be evaluated once' do
|
72
66
|
x = 0
|
73
|
-
|
67
|
+
engine << rule {
|
74
68
|
forall {
|
75
69
|
has :a, :b, :c
|
76
70
|
assign :T do
|
@@ -82,15 +76,15 @@ describe "ASSIGN rule" do
|
|
82
76
|
gen :f, :g, :T
|
83
77
|
}
|
84
78
|
}
|
85
|
-
engine << [
|
79
|
+
engine << %i[a b c]
|
86
80
|
expect(x).to be == 1
|
87
81
|
end
|
88
82
|
|
89
|
-
|
83
|
+
xit 'should handle booleans' do
|
90
84
|
engine << rule do
|
91
|
-
|
85
|
+
forall {
|
92
86
|
has :a, :b, :c
|
93
|
-
assign :X do |
|
87
|
+
assign :X do |_token|
|
94
88
|
false
|
95
89
|
end
|
96
90
|
}
|
@@ -98,7 +92,7 @@ describe "ASSIGN rule" do
|
|
98
92
|
gen :d, :e, :X
|
99
93
|
}
|
100
94
|
end
|
101
|
-
engine << [
|
95
|
+
engine << %i[a b c]
|
96
|
+
expect(engine.find(:d, :e, false)).not_to be_nil
|
102
97
|
end
|
103
|
-
|
104
98
|
end
|