wongi-engine 0.4.0.pre.alpha2 → 0.4.0.pre.alpha4
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/.rubocop.yml +0 -3
- data/lib/wongi-engine/beta/aggregate_node.rb +44 -53
- data/lib/wongi-engine/beta/ncc_partner.rb +1 -1
- data/lib/wongi-engine/beta/neg_node.rb +2 -5
- data/lib/wongi-engine/compiler.rb +5 -8
- data/lib/wongi-engine/dsl/action/statement_generator.rb +34 -10
- data/lib/wongi-engine/dsl/clause/aggregate.rb +8 -10
- data/lib/wongi-engine/dsl/clause/fact.rb +1 -4
- data/lib/wongi-engine/dsl.rb +18 -8
- data/lib/wongi-engine/token.rb +4 -0
- data/lib/wongi-engine/version.rb +1 -1
- data/spec/action_class_spec.rb +1 -1
- data/spec/aggregate_spec.rb +180 -0
- data/spec/{rule_specs/any_rule_spec.rb → any_rule_spec.rb} +4 -4
- data/spec/{filter_specs/assert_test_spec.rb → assert_test_spec.rb} +5 -5
- data/spec/{rule_specs/assign_spec.rb → assign_spec.rb} +6 -6
- data/spec/{rule_specs/assuming_spec.rb → assuming_spec.rb} +3 -3
- data/spec/beta_node_spec.rb +1 -1
- data/spec/bug_specs/issue_4_spec.rb +4 -4
- data/spec/dataset_spec.rb +1 -1
- data/spec/generation_spec.rb +7 -7
- data/spec/{filter_specs/greater_than_equality_test_spec.rb → greater_than_equality_test_spec.rb} +1 -1
- data/spec/high_level_spec.rb +12 -12
- data/spec/{filter_specs/less_test_spec.rb → less_test_spec.rb} +1 -1
- data/spec/{filter_specs/less_than_equality_test_spec.rb → less_than_equality_test_spec.rb} +1 -1
- data/spec/{rule_specs/maybe_rule_spec.rb → maybe_rule_spec.rb} +7 -7
- data/spec/{rule_specs/ncc_spec.rb → ncc_spec.rb} +28 -5
- data/spec/{rule_specs/negative_rule_spec.rb → negative_rule_spec.rb} +3 -25
- data/spec/network_spec.rb +4 -3
- data/spec/overlay_spec.rb +3 -2
- data/spec/ruleset_spec.rb +6 -6
- data/spec/simple_action_spec.rb +1 -1
- data/spec/wme_spec.rb +4 -4
- metadata +14 -14
- data/spec/rule_specs/aggregate_spec.rb +0 -197
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 566bc82ac763603c9e6a5556677364af9e57449bcb839c00865c3dd8cefbc1dc
|
4
|
+
data.tar.gz: d8285247fbe76bcf603740f7e6a6dd603226ea5c1e47564223b851c087a148e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a88bbad2800cf1701d66277cbffc646fe92e0a283bb6da8f3bc828fbfdea001d3f4040be35a039372d7db4680c7acb763ccfe91e94f233be90b5182697f24602
|
7
|
+
data.tar.gz: b00126ee326301079e7c257548b6ce650682f0d8637056445760a3f0cc25da96411d4be8fc50edea8f343cdf1523f9ab82ec8d719a20a232128e7622f99ae331
|
data/.rubocop.yml
CHANGED
@@ -1,41 +1,37 @@
|
|
1
1
|
module Wongi::Engine
|
2
2
|
class AggregateNode < BetaNode
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :var, :over, :partition, :aggregate, :map
|
4
4
|
|
5
|
-
def initialize(parent,
|
5
|
+
def initialize(parent, var, over, partition, aggregate, map)
|
6
6
|
super(parent)
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@assign = assign
|
7
|
+
@var = var
|
8
|
+
@over = over
|
9
|
+
@partition = make_partition_fn(partition)
|
10
|
+
@aggregate = make_aggregate_fn(aggregate)
|
11
|
+
@map = make_map_fn(map)
|
13
12
|
end
|
14
13
|
|
15
|
-
def
|
16
|
-
return
|
17
|
-
return false if self.assignment_pattern != assignment_pattern
|
18
|
-
return true if self.tests.empty? && tests.empty?
|
19
|
-
return false if self.tests.length != tests.length
|
14
|
+
def make_partition_fn(partition)
|
15
|
+
return nil if partition.nil?
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
}
|
25
|
-
|
17
|
+
if Template.variable?(partition)
|
18
|
+
->(token) { token[partition] }
|
19
|
+
elsif partition.is_a?(Array) && partition.all? { Template.variable?(_1) }
|
20
|
+
->(token) { token.values_at(*partition) }
|
21
|
+
else
|
22
|
+
partition
|
23
|
+
end
|
26
24
|
end
|
27
25
|
|
28
|
-
def
|
29
|
-
|
30
|
-
tokens.each do |token|
|
31
|
-
evaluate(wme: wme, token: token)
|
32
|
-
end
|
26
|
+
def make_aggregate_fn(agg)
|
27
|
+
agg
|
33
28
|
end
|
34
29
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
30
|
+
def make_map_fn(map)
|
31
|
+
if map.nil?
|
32
|
+
->(token) { token[over] }
|
33
|
+
else
|
34
|
+
map
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
@@ -43,42 +39,37 @@ module Wongi::Engine
|
|
43
39
|
return if tokens.find { |t| t.duplicate? token }
|
44
40
|
|
45
41
|
overlay.add_token(token)
|
46
|
-
evaluate
|
42
|
+
evaluate
|
47
43
|
end
|
48
44
|
|
49
45
|
def beta_deactivate(token)
|
50
46
|
overlay.remove_token(token)
|
51
|
-
beta_deactivate_children(token:
|
47
|
+
beta_deactivate_children(token:)
|
48
|
+
evaluate
|
52
49
|
end
|
53
50
|
|
54
51
|
def refresh_child(child)
|
55
|
-
|
56
|
-
evaluate(wme: nil, token: token, child: child)
|
57
|
-
end
|
52
|
+
evaluate(child: child)
|
58
53
|
end
|
59
54
|
|
60
|
-
def evaluate(
|
61
|
-
|
62
|
-
|
63
|
-
beta_deactivate_children(token: token)
|
64
|
-
|
65
|
-
template = specialize(alpha.template, tests, token)
|
66
|
-
candidates = select_wmes(template) { |asserted_wme| matches?(token, asserted_wme) }
|
67
|
-
|
68
|
-
return if candidates.empty?
|
69
|
-
|
70
|
-
mapped = candidates.map(&map)
|
71
|
-
value = if function.is_a?(Symbol) && mapped.respond_to?(function)
|
72
|
-
mapped.send(function)
|
73
|
-
else
|
74
|
-
function.call(mapped)
|
75
|
-
end
|
76
|
-
assignments = { assign => value }
|
77
|
-
if child
|
78
|
-
child.beta_activate(Token.new(child, token, wme, assignments))
|
55
|
+
def evaluate(child: nil)
|
56
|
+
groups = if partition
|
57
|
+
tokens.group_by(&partition).values
|
79
58
|
else
|
80
|
-
|
81
|
-
|
59
|
+
# just a single group of everything
|
60
|
+
[tokens]
|
61
|
+
end
|
62
|
+
|
63
|
+
groups.each do |tokens|
|
64
|
+
aggregated = self.aggregate.call(tokens.map(&self.map))
|
65
|
+
assignment = { var => aggregated }
|
66
|
+
children = child ? [child] : self.children
|
67
|
+
tokens.each do |token|
|
68
|
+
# TODO: optimize this to work with a diff of actual changes
|
69
|
+
beta_deactivate_children(token:, children:)
|
70
|
+
children.each do |beta|
|
71
|
+
beta.beta_activate(Token.new(beta, token, nil, assignment))
|
72
|
+
end
|
82
73
|
end
|
83
74
|
end
|
84
75
|
end
|
@@ -5,17 +5,16 @@ module Wongi
|
|
5
5
|
class NegNode < BetaNode
|
6
6
|
attr_reader :alpha, :tests
|
7
7
|
|
8
|
-
def initialize(parent, tests, alpha
|
8
|
+
def initialize(parent, tests, alpha)
|
9
9
|
super(parent)
|
10
10
|
@tests = tests
|
11
11
|
@alpha = alpha
|
12
|
-
@unsafe = unsafe
|
13
12
|
end
|
14
13
|
|
15
14
|
def alpha_activate(wme, children: self.children)
|
16
15
|
# p alpha_activate: {class: self.class, object_id:, wme:}
|
17
16
|
tokens.each do |token|
|
18
|
-
next unless matches?(token, wme)
|
17
|
+
next unless matches?(token, wme)
|
19
18
|
|
20
19
|
# order matters for proper invalidation
|
21
20
|
overlay.add_neg_join_result(NegJoinResult.new(token, wme))
|
@@ -71,8 +70,6 @@ module Wongi
|
|
71
70
|
end
|
72
71
|
end
|
73
72
|
|
74
|
-
protected
|
75
|
-
|
76
73
|
def matches?(token, wme)
|
77
74
|
puts "matching #{wme} against #{token}" if debug?
|
78
75
|
@tests.each do |test|
|
@@ -42,9 +42,9 @@ module Wongi::Engine
|
|
42
42
|
node.tap(&:refresh)
|
43
43
|
end
|
44
44
|
|
45
|
-
def neg_node(condition, tests
|
45
|
+
def neg_node(condition, tests)
|
46
46
|
alpha = rete.compile_alpha(condition)
|
47
|
-
self.node = NegNode.new(node, tests, alpha
|
47
|
+
self.node = NegNode.new(node, tests, alpha).tap do |node|
|
48
48
|
alpha.betas << node unless alpha_deaf
|
49
49
|
node.refresh
|
50
50
|
end
|
@@ -58,12 +58,9 @@ module Wongi::Engine
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def aggregate_node(
|
62
|
-
declare(
|
63
|
-
|
64
|
-
self.node = AggregateNode.new(node, alpha, tests, assignment, map, function, assign).tap do |node|
|
65
|
-
alpha.betas << node unless alpha_deaf
|
66
|
-
end.tap(&:refresh)
|
61
|
+
def aggregate_node(var, over, partition, aggregate, map)
|
62
|
+
declare(var)
|
63
|
+
self.node = AggregateNode.new(node, var, over, partition, aggregate, map).tap(&:refresh)
|
67
64
|
end
|
68
65
|
|
69
66
|
def or_node(variants)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "set"
|
2
|
+
|
1
3
|
module Wongi::Engine
|
2
4
|
module DSL::Action
|
3
5
|
class StatementGenerator < BaseAction
|
@@ -16,23 +18,45 @@ module Wongi::Engine
|
|
16
18
|
subject, predicate, object = template.resolve!(token)
|
17
19
|
|
18
20
|
wme = WME.new(subject, predicate, object)
|
19
|
-
|
20
|
-
origin = GeneratorOrigin.new(token, self)
|
21
|
+
wme = overlay.find(wme) || wme
|
21
22
|
|
22
23
|
production.tracer.trace(action: self, wme: wme) if production.tracer
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
if overlay.generated?(wme)
|
27
|
-
token.generated_wmes << existing
|
28
|
-
overlay.assert(existing, generator: origin)
|
29
|
-
end
|
30
|
-
else
|
24
|
+
|
25
|
+
if should_assert?(wme, token)
|
26
|
+
origin = GeneratorOrigin.new(token, self)
|
31
27
|
token.generated_wmes << wme
|
32
28
|
overlay.assert(wme, generator: origin)
|
33
29
|
end
|
34
30
|
end
|
35
31
|
|
32
|
+
private def should_assert?(wme, token)
|
33
|
+
considered_tokens = Set.new
|
34
|
+
tokens_to_consider = [token]
|
35
|
+
until tokens_to_consider.empty?
|
36
|
+
token = tokens_to_consider.shift
|
37
|
+
considered_tokens.add(token)
|
38
|
+
|
39
|
+
# self-affirming reasoning
|
40
|
+
return false if token.wme == wme
|
41
|
+
|
42
|
+
# asserting this WME would invalidate the match
|
43
|
+
return false if token.node.is_a?(NegNode) && token.node.matches?(token, wme)
|
44
|
+
|
45
|
+
if token.parent && !considered_tokens.include?(token.parent)
|
46
|
+
tokens_to_consider.push(token.parent)
|
47
|
+
end
|
48
|
+
|
49
|
+
if token.wme
|
50
|
+
overlay.generators(token.wme).each do |generator|
|
51
|
+
tokens_to_consider.push(generator.token) unless considered_tokens.include?(generator.token)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# we could not prove that the new WME should not be asserted
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
36
60
|
def deexecute(token)
|
37
61
|
origin = GeneratorOrigin.new(token, self)
|
38
62
|
|
@@ -1,20 +1,18 @@
|
|
1
1
|
module Wongi::Engine
|
2
2
|
module DSL::Clause
|
3
|
-
class Aggregate
|
4
|
-
attr_reader :
|
3
|
+
class Aggregate
|
4
|
+
attr_reader :var, :over, :partition, :aggregate, :map
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
|
6
|
+
def initialize(var, options = {})
|
7
|
+
@var = var
|
8
|
+
@over = options[:over]
|
9
|
+
@partition = options[:partition]
|
10
|
+
@aggregate = options[:using]
|
8
11
|
@map = options[:map]
|
9
|
-
@function = options[:function]
|
10
|
-
@assign = options[:assign]
|
11
|
-
@map ||= ->(wme) { wme.send(member) }
|
12
|
-
super
|
13
12
|
end
|
14
13
|
|
15
14
|
def compile(context)
|
16
|
-
|
17
|
-
context.tap { |c| c.aggregate_node(self, tests, assignment, map, function, assign) }
|
15
|
+
context.tap { |c| c.aggregate_node(var, over, partition, aggregate, map) }
|
18
16
|
end
|
19
17
|
end
|
20
18
|
end
|
@@ -5,7 +5,6 @@ module Wongi::Engine
|
|
5
5
|
attr_predicate :debug
|
6
6
|
|
7
7
|
def initialize(s, p, o, options = {})
|
8
|
-
@unsafe = options[:unsafe] || false
|
9
8
|
debug! if options[:debug]
|
10
9
|
super(s, p, o)
|
11
10
|
end
|
@@ -43,13 +42,11 @@ module Wongi::Engine
|
|
43
42
|
end
|
44
43
|
|
45
44
|
class Neg < Has
|
46
|
-
attr_reader :unsafe
|
47
|
-
|
48
45
|
def compile(context)
|
49
46
|
tests, assignment = parse_variables(context)
|
50
47
|
raise DefinitionError, "Negative matches may not introduce new variables: #{assignment.variables}" unless assignment.root?
|
51
48
|
|
52
|
-
context.tap { |c| c.neg_node(self, tests
|
49
|
+
context.tap { |c| c.neg_node(self, tests) }
|
53
50
|
end
|
54
51
|
|
55
52
|
def inspect
|
data/lib/wongi-engine/dsl.rb
CHANGED
@@ -89,19 +89,29 @@ module Wongi::Engine::DSL
|
|
89
89
|
clause :aggregate
|
90
90
|
accept Clause::Aggregate
|
91
91
|
|
92
|
-
clause :
|
93
|
-
body { |
|
94
|
-
aggregate
|
92
|
+
clause :min
|
93
|
+
body { |var, opts|
|
94
|
+
aggregate var, opts.merge(using: ->(collection) { collection.min })
|
95
95
|
}
|
96
96
|
|
97
|
-
clause :
|
98
|
-
body { |
|
99
|
-
aggregate
|
97
|
+
clause :max
|
98
|
+
body { |var, opts|
|
99
|
+
aggregate var, opts.merge(using: ->(collection) { collection.max })
|
100
100
|
}
|
101
101
|
|
102
102
|
clause :count
|
103
|
-
body { |
|
104
|
-
aggregate
|
103
|
+
body { |var, opts = {}|
|
104
|
+
aggregate var, opts.merge(using: ->(collection) { collection.count })
|
105
|
+
}
|
106
|
+
|
107
|
+
clause :sum
|
108
|
+
body { |var, opts|
|
109
|
+
aggregate var, opts.merge(using: ->(collection) { collection.inject(:+) })
|
110
|
+
}
|
111
|
+
|
112
|
+
clause :product
|
113
|
+
body { |var, opts|
|
114
|
+
aggregate var, opts.merge(using: ->(collection) { collection.inject(:*) })
|
105
115
|
}
|
106
116
|
|
107
117
|
clause :assert, :dynamic
|
data/lib/wongi-engine/token.rb
CHANGED
data/lib/wongi-engine/version.rb
CHANGED
data/spec/action_class_spec.rb
CHANGED
@@ -0,0 +1,180 @@
|
|
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 'min' do
|
12
|
+
it 'works' do
|
13
|
+
engine << rule(rule_name) do
|
14
|
+
forall {
|
15
|
+
has :_, :weight, :Weight
|
16
|
+
min :X, over: :Weight
|
17
|
+
has :Fruit, :weight, :X
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
engine << [:apple, :weight, 5]
|
22
|
+
expect(production.size).to be == 1
|
23
|
+
production.tokens.each do |token|
|
24
|
+
expect(token[:X]).to be == 5
|
25
|
+
expect(token[:Fruit]).to be == :apple
|
26
|
+
end
|
27
|
+
|
28
|
+
engine << [:pea, :weight, 2]
|
29
|
+
expect(production.size).to be == 2
|
30
|
+
production.tokens.each do |token|
|
31
|
+
expect(token[:X]).to be == 2
|
32
|
+
expect(token[:Fruit]).to be == :pea
|
33
|
+
end
|
34
|
+
|
35
|
+
engine.retract [:pea, :weight, 2]
|
36
|
+
expect(production.size).to be == 1
|
37
|
+
production.tokens.each do |token|
|
38
|
+
expect(token[:X]).to be == 5
|
39
|
+
expect(token[:Fruit]).to be == :apple
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'max' do
|
45
|
+
it 'works' do
|
46
|
+
engine << rule(rule_name) do
|
47
|
+
forall {
|
48
|
+
has :_, :weight, :Weight
|
49
|
+
max :X, over: :Weight
|
50
|
+
has :Fruit, :weight, :X
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
engine << [:pea, :weight, 2]
|
55
|
+
expect(production.size).to be == 1
|
56
|
+
production.tokens.each do |token|
|
57
|
+
expect(token[:X]).to be == 2
|
58
|
+
expect(token[:Fruit]).to be == :pea
|
59
|
+
end
|
60
|
+
|
61
|
+
engine << [:apple, :weight, 5]
|
62
|
+
expect(production.size).to be == 2
|
63
|
+
production.tokens.each do |token|
|
64
|
+
expect(token[:X]).to be == 5
|
65
|
+
expect(token[:Fruit]).to be == :apple
|
66
|
+
end
|
67
|
+
|
68
|
+
engine.retract [:apple, :weight, 5]
|
69
|
+
expect(production.size).to be == 1
|
70
|
+
production.tokens.each do |token|
|
71
|
+
expect(token[:X]).to be == 2
|
72
|
+
expect(token[:Fruit]).to be == :pea
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'count' do
|
78
|
+
it 'works' do
|
79
|
+
engine << rule(rule_name) do
|
80
|
+
forall {
|
81
|
+
has :_, :weight, :Weight
|
82
|
+
count :Count
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
engine << [:pea, :weight, 1]
|
87
|
+
expect(production.size).to be == 1
|
88
|
+
production.tokens.each do |token|
|
89
|
+
expect(token[:Count]).to be == 1
|
90
|
+
end
|
91
|
+
|
92
|
+
engine << [:apple, :weight, 5]
|
93
|
+
expect(production.size).to be == 2
|
94
|
+
production.tokens.each do |token|
|
95
|
+
expect(token[:Count]).to be == 2
|
96
|
+
end
|
97
|
+
|
98
|
+
engine << [:watermelon, :weight, 15]
|
99
|
+
expect(production.size).to be == 3
|
100
|
+
production.tokens.each do |token|
|
101
|
+
expect(token[:Count]).to be == 3
|
102
|
+
end
|
103
|
+
|
104
|
+
engine.retract [:apple, :weight, 5]
|
105
|
+
expect(production.size).to be == 2
|
106
|
+
production.tokens.each do |token|
|
107
|
+
expect(token[:Count]).to be == 2
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'works with a post-filter' do
|
112
|
+
engine << rule(rule_name) do
|
113
|
+
forall {
|
114
|
+
has :_, :weight, :Weight
|
115
|
+
count :Count
|
116
|
+
gte :Count, 3 # pass if at least 3 matching facts exist
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
engine << [:pea, :weight, 1]
|
121
|
+
expect(production.size).to be == 0
|
122
|
+
|
123
|
+
engine << [:apple, :weight, 5]
|
124
|
+
expect(production.size).to be == 0
|
125
|
+
|
126
|
+
engine << [:watermelon, :weight, 15]
|
127
|
+
expect(production.size).to be == 3
|
128
|
+
production.tokens.each do |token|
|
129
|
+
expect(token[:Count]).to be == 3
|
130
|
+
end
|
131
|
+
|
132
|
+
engine.retract [:apple, :weight, 5]
|
133
|
+
expect(production.size).to be == 0
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'partitioning by a single var' do
|
138
|
+
it 'works' do
|
139
|
+
engine << rule(rule_name) do
|
140
|
+
forall {
|
141
|
+
has :factor, :Number, :Factor
|
142
|
+
product :Product, over: :Factor, partition: :Number
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
engine << [:factor, 10, 2]
|
147
|
+
engine << [:factor, 10, 5]
|
148
|
+
engine << [:factor, 12, 3]
|
149
|
+
engine << [:factor, 12, 4]
|
150
|
+
|
151
|
+
expect(production).to have(4).tokens
|
152
|
+
production.tokens.each do |token|
|
153
|
+
expect(token[:Product]).to be_a(Integer)
|
154
|
+
expect(token[:Product]).to eq(token[:Number])
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'partitioning by a list' do
|
160
|
+
it 'works' do
|
161
|
+
engine << rule(rule_name) do
|
162
|
+
forall {
|
163
|
+
has :factor, :Number, :Factor
|
164
|
+
product :Product, over: :Factor, partition: [:Number]
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
engine << [:factor, 10, 2]
|
169
|
+
engine << [:factor, 10, 5]
|
170
|
+
engine << [:factor, 12, 3]
|
171
|
+
engine << [:factor, 12, 4]
|
172
|
+
|
173
|
+
expect(production).to have(4).tokens
|
174
|
+
production.tokens.each do |token|
|
175
|
+
expect(token[:Product]).to be_a(Integer)
|
176
|
+
expect(token[:Product]).to eq(token[:Number])
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -6,7 +6,7 @@ describe "ANY rule" do
|
|
6
6
|
let(:engine) { Wongi::Engine.create }
|
7
7
|
|
8
8
|
context "with just one option" do
|
9
|
-
it "
|
9
|
+
it "acts like a positive matcher" do
|
10
10
|
engine << rule('one-option') {
|
11
11
|
forall {
|
12
12
|
any {
|
@@ -43,17 +43,17 @@ describe "ANY rule" do
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
it '
|
46
|
+
it 'fires on the first path' do
|
47
47
|
engine << [:x, :path1, true]
|
48
48
|
expect(production.tokens).to have(1).item
|
49
49
|
end
|
50
50
|
|
51
|
-
it '
|
51
|
+
it 'fires on the second path' do
|
52
52
|
engine << [:x, :path2, true]
|
53
53
|
expect(production.tokens).to have(1).item
|
54
54
|
end
|
55
55
|
|
56
|
-
it '
|
56
|
+
it 'fires twice on both paths at once and be retractable' do
|
57
57
|
engine << [:x, :path1, true]
|
58
58
|
engine << [:x, :path2, true]
|
59
59
|
expect(production.tokens).to have(2).items
|
@@ -10,7 +10,7 @@ describe "ASSERT test" do
|
|
10
10
|
@production = (engine << rule('test-rule', &block))
|
11
11
|
end
|
12
12
|
|
13
|
-
it "
|
13
|
+
it "passes with a constant 'true'" do
|
14
14
|
test_rule {
|
15
15
|
forall {
|
16
16
|
assert { |_token|
|
@@ -22,7 +22,7 @@ describe "ASSERT test" do
|
|
22
22
|
expect(production).to have(1).token
|
23
23
|
end
|
24
24
|
|
25
|
-
it "
|
25
|
+
it "fails with a constant 'false'" do
|
26
26
|
test_rule {
|
27
27
|
forall {
|
28
28
|
assert { |_token|
|
@@ -34,7 +34,7 @@ describe "ASSERT test" do
|
|
34
34
|
expect(production).to have(0).tokens
|
35
35
|
end
|
36
36
|
|
37
|
-
it "
|
37
|
+
it "uses the token with no arguments" do
|
38
38
|
test_rule {
|
39
39
|
forall {
|
40
40
|
has :X, "is", :Y
|
@@ -50,7 +50,7 @@ describe "ASSERT test" do
|
|
50
50
|
expect(production.tokens.first[:X]).to eq("resistance")
|
51
51
|
end
|
52
52
|
|
53
|
-
it "
|
53
|
+
it "is retractable" do
|
54
54
|
test_rule {
|
55
55
|
forall {
|
56
56
|
has :X, "is", :Y
|
@@ -65,7 +65,7 @@ describe "ASSERT test" do
|
|
65
65
|
expect(production).to have(0).tokens
|
66
66
|
end
|
67
67
|
|
68
|
-
it "
|
68
|
+
it "uses individual variables with arguments" do
|
69
69
|
test_rule {
|
70
70
|
forall {
|
71
71
|
has :X, "is", :Y
|