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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -3
  3. data/lib/wongi-engine/beta/aggregate_node.rb +44 -53
  4. data/lib/wongi-engine/beta/ncc_partner.rb +1 -1
  5. data/lib/wongi-engine/beta/neg_node.rb +2 -5
  6. data/lib/wongi-engine/compiler.rb +5 -8
  7. data/lib/wongi-engine/dsl/action/statement_generator.rb +34 -10
  8. data/lib/wongi-engine/dsl/clause/aggregate.rb +8 -10
  9. data/lib/wongi-engine/dsl/clause/fact.rb +1 -4
  10. data/lib/wongi-engine/dsl.rb +18 -8
  11. data/lib/wongi-engine/token.rb +4 -0
  12. data/lib/wongi-engine/version.rb +1 -1
  13. data/spec/action_class_spec.rb +1 -1
  14. data/spec/aggregate_spec.rb +180 -0
  15. data/spec/{rule_specs/any_rule_spec.rb → any_rule_spec.rb} +4 -4
  16. data/spec/{filter_specs/assert_test_spec.rb → assert_test_spec.rb} +5 -5
  17. data/spec/{rule_specs/assign_spec.rb → assign_spec.rb} +6 -6
  18. data/spec/{rule_specs/assuming_spec.rb → assuming_spec.rb} +3 -3
  19. data/spec/beta_node_spec.rb +1 -1
  20. data/spec/bug_specs/issue_4_spec.rb +4 -4
  21. data/spec/dataset_spec.rb +1 -1
  22. data/spec/generation_spec.rb +7 -7
  23. data/spec/{filter_specs/greater_than_equality_test_spec.rb → greater_than_equality_test_spec.rb} +1 -1
  24. data/spec/high_level_spec.rb +12 -12
  25. data/spec/{filter_specs/less_test_spec.rb → less_test_spec.rb} +1 -1
  26. data/spec/{filter_specs/less_than_equality_test_spec.rb → less_than_equality_test_spec.rb} +1 -1
  27. data/spec/{rule_specs/maybe_rule_spec.rb → maybe_rule_spec.rb} +7 -7
  28. data/spec/{rule_specs/ncc_spec.rb → ncc_spec.rb} +28 -5
  29. data/spec/{rule_specs/negative_rule_spec.rb → negative_rule_spec.rb} +3 -25
  30. data/spec/network_spec.rb +4 -3
  31. data/spec/overlay_spec.rb +3 -2
  32. data/spec/ruleset_spec.rb +6 -6
  33. data/spec/simple_action_spec.rb +1 -1
  34. data/spec/wme_spec.rb +4 -4
  35. metadata +14 -14
  36. data/spec/rule_specs/aggregate_spec.rb +0 -197
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49eb7efc3f4049b38a7035208884d2078e0e31e68d396fb647deb81add7a030c
4
- data.tar.gz: 6916568d628dbfce536001472c224f0fcee9ab580d4d9f2df30f04edb77bb45d
3
+ metadata.gz: 566bc82ac763603c9e6a5556677364af9e57449bcb839c00865c3dd8cefbc1dc
4
+ data.tar.gz: d8285247fbe76bcf603740f7e6a6dd603226ea5c1e47564223b851c087a148e6
5
5
  SHA512:
6
- metadata.gz: 437466d4256f5ff96bf741e879054f2e5a7499f3876ed73cbd00b155caf6b5f06cfb60378941e36cba6dd912b8d61cbe8b4cdfec58a0b60f121841cdd63c3bd9
7
- data.tar.gz: 5e8ca5469c6b20cb63928a0c4c128f3391ae74c29e09608b0a7045d6d979bc03d9d8b95f49f4282898531ee8f69e7bd9abcd8ebb8164aa4d612292e01b509be3
6
+ metadata.gz: a88bbad2800cf1701d66277cbffc646fe92e0a283bb6da8f3bc828fbfdea001d3f4040be35a039372d7db4680c7acb763ccfe91e94f233be90b5182697f24602
7
+ data.tar.gz: b00126ee326301079e7c257548b6ce650682f0d8637056445760a3f0cc25da96411d4be8fc50edea8f343cdf1523f9ab82ec8d719a20a232128e7622f99ae331
data/.rubocop.yml CHANGED
@@ -42,9 +42,6 @@ Naming/MethodParameterName:
42
42
  Layout/LineLength:
43
43
  Enabled: false
44
44
 
45
- RSpec:
46
- Enabled: false
47
-
48
45
  Style/SafeNavigation:
49
46
  Enabled: false
50
47
 
@@ -1,41 +1,37 @@
1
1
  module Wongi::Engine
2
2
  class AggregateNode < BetaNode
3
- attr_reader :alpha, :tests, :assignment_pattern, :map, :function, :assign
3
+ attr_reader :var, :over, :partition, :aggregate, :map
4
4
 
5
- def initialize(parent, alpha, tests, assignment, map, function, assign)
5
+ def initialize(parent, var, over, partition, aggregate, map)
6
6
  super(parent)
7
- @alpha = alpha
8
- @tests = tests
9
- @assignment_pattern = assignment
10
- @map = map
11
- @function = function
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 equivalent?(alpha, tests, assignment_pattern)
16
- return false if self.alpha != alpha
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
- self.tests.all? { |my_test|
22
- tests.any? { |new_test|
23
- my_test.equivalent? new_test
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 alpha_activate(wme)
29
- # we need to re-run all WMEs through the aggregator, so the new incoming one doesn't matter
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 alpha_deactivate(wme)
36
- # we need to re-run all WMEs through the aggregator, so the new incoming one doesn't matter
37
- tokens.each do |token|
38
- evaluate(wme: wme, token: token)
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(wme: nil, token: token)
42
+ evaluate
47
43
  end
48
44
 
49
45
  def beta_deactivate(token)
50
46
  overlay.remove_token(token)
51
- beta_deactivate_children(token: token)
47
+ beta_deactivate_children(token:)
48
+ evaluate
52
49
  end
53
50
 
54
51
  def refresh_child(child)
55
- tokens.each do |token|
56
- evaluate(wme: nil, token: token, child: child)
57
- end
52
+ evaluate(child: child)
58
53
  end
59
54
 
60
- def evaluate(wme:, token:, child: nil)
61
- # clean up previous decisions
62
- # # TODO: optimise: only clean up if the value changed
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
- children.each do |beta|
81
- beta.beta_activate(Token.new(beta, token, wme, assignments))
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
@@ -25,7 +25,7 @@ module Wongi
25
25
  overlay.remove_token(token)
26
26
  return unless owner
27
27
 
28
- ncc.ncc_activate owner if overlay.ncc_tokens_for(owner).empty?
28
+ ncc.ncc_activate(owner) if overlay.ncc_tokens_for(owner).empty?
29
29
  end
30
30
 
31
31
  def owner_for(token)
@@ -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, unsafe)
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) && (@unsafe || !token.generated?(wme)) # feedback loop protection
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, unsafe)
45
+ def neg_node(condition, tests)
46
46
  alpha = rete.compile_alpha(condition)
47
- self.node = NegNode.new(node, tests, alpha, unsafe).tap do |node|
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(condition, tests, assignment, map, function, assign)
62
- declare(assign)
63
- alpha = rete.compile_alpha(condition)
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
- if (existing = overlay.find(wme))
24
- # do not mark purely manual tokens as generated, because a circular rule such as the symmetric friend generator this would cause both sides to become self-sustaining
25
- # TODO: but this may have to be smarter, because there may be more indirect ways of creating such a situation
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 < Has
4
- attr_reader :map, :function, :assign
3
+ class Aggregate
4
+ attr_reader :var, :over, :partition, :aggregate, :map
5
5
 
6
- def initialize(s, p, o, options = {})
7
- member = options[:on]
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
- tests, assignment = parse_variables(context)
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, unsafe) }
49
+ context.tap { |c| c.neg_node(self, tests) }
53
50
  end
54
51
 
55
52
  def inspect
@@ -89,19 +89,29 @@ module Wongi::Engine::DSL
89
89
  clause :aggregate
90
90
  accept Clause::Aggregate
91
91
 
92
- clause :least, :min
93
- body { |s, p, o, opts|
94
- aggregate s, p, o, on: opts[:on], function: :min, assign: opts[:assign]
92
+ clause :min
93
+ body { |var, opts|
94
+ aggregate var, opts.merge(using: ->(collection) { collection.min })
95
95
  }
96
96
 
97
- clause :greatest, :max
98
- body { |s, p, o, opts|
99
- aggregate s, p, o, on: opts[:on], function: :max, assign: opts[:assign]
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 { |s, p, o, opts|
104
- aggregate s, p, o, map: ->(_) { 1 }, function: ->(collection) { collection.inject(&:+) }, assign: opts[:assign]
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
@@ -40,6 +40,10 @@ module Wongi::Engine
40
40
  a.respond_to?(:call) ? a.call(self) : a
41
41
  end
42
42
 
43
+ def values_at(*vars)
44
+ vars.map { self[_1] }
45
+ end
46
+
43
47
  def has_var?(x)
44
48
  assignments.key? x
45
49
  end
@@ -1,5 +1,5 @@
1
1
  module Wongi
2
2
  module Engine
3
- VERSION = "0.4.0-alpha2".freeze
3
+ VERSION = "0.4.0-alpha4".freeze
4
4
  end
5
5
  end
@@ -23,7 +23,7 @@ describe 'action classes' do
23
23
  end
24
24
  end
25
25
 
26
- it 'should have appropriate callbacks executed' do
26
+ it 'has appropriate callbacks executed' do
27
27
  executed = 0
28
28
  deexecuted = 0
29
29
 
@@ -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 "should act like a positive matcher" do
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 'should fire on the first path' do
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 'should fire on the second path' do
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 'should fire twice on both paths at once and be retractable' do
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 "should pass with a constant 'true'" do
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 "should fail with a constant 'false'" do
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 "should use the token with no arguments" do
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 "should be retractable" do
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 "should use individual variables with arguments" do
68
+ it "uses individual variables with arguments" do
69
69
  test_rule {
70
70
  forall {
71
71
  has :X, "is", :Y