omf_rete 0.5 → 0.6.1

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.
@@ -1,16 +1,17 @@
1
1
 
2
+ require 'omf_rete/tuple'
2
3
 
3
4
  module OMF::Rete
4
5
  #
5
- # This class provides functionality to process a
6
- # stream of tuples.
6
+ # This class provides functionality to process a
7
+ # stream of tuples.
7
8
  #
8
9
  class AbstractTupleStream
9
10
  attr_accessor :source
10
11
  attr_reader :description
11
-
12
+
12
13
  def initialize(description, source = nil)
13
- @description = description
14
+ @description = description
14
15
  @source = source
15
16
  end
16
17
 
@@ -19,14 +20,14 @@ module OMF::Rete
19
20
  el == bname
20
21
  end
21
22
  end
22
-
23
+
23
24
  # Return true if +tuple+ can be produced by this stream through the
24
25
  # normal (+addTuple+) channels.
25
26
  #
26
27
  def check_for_tuple(tuple)
27
28
  raise "Method 'check_for_tuple' is not implemented"
28
29
  end
29
-
30
+
30
31
  def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
31
32
  out.write(" " * offset)
32
33
  _describe(out, sep)
@@ -45,7 +46,7 @@ module OMF::Rete
45
46
  #
46
47
  class ProcessingTupleStream < AbstractTupleStream
47
48
  attr_accessor :receiver
48
-
49
+
49
50
  def initialize(project_pattern, out_description = project_pattern, in_description = nil, receiver = nil, &block)
50
51
  @project_pattern = project_pattern
51
52
  super out_description
@@ -54,33 +55,33 @@ module OMF::Rete
54
55
  self.inDescription = in_description
55
56
  end
56
57
  @receiver = receiver
57
- @block = block
58
+ @on_add_block = block
58
59
  end
59
-
60
+
60
61
  def on_add(&block)
61
- @block = block
62
+ @on_add_block = block
62
63
  end
63
-
64
+
65
+ def on_remove(&block)
66
+ @on_remove_block = block
67
+ end
68
+
64
69
  def addTuple(tuple)
65
- if @result_map
66
- rtuple = @result_map.collect do |i| tuple[i] end
67
- else
68
- rtuple = tuple
70
+ if (result = process(tuple, @on_add_block))
71
+ @receiver.addTuple(result)
69
72
  end
70
- result = @block ? @block.call(*rtuple) : rtuple
71
- # if @block
72
- # if (out = @block.call(*rtuple))
73
- # unless out.kind_of?(Array) && out.size == @result_size
74
- # raise "Expected block to return an array of size '#{@result_size}', but got '#{out.inspect}'"
75
- # end
76
- # @receiver.addTuple(out)
77
- # end
78
- # else
79
- # @receiver.addTuple(rtuple)
80
- # end
81
- process_result(result, tuple)
82
- end
83
-
73
+ end
74
+
75
+ def removeTuple(tuple)
76
+ if (result = process(tuple, @on_remove_block))
77
+ @receiver.removeTuple(result)
78
+ end
79
+ end
80
+
81
+ def clear()
82
+ @receiver.clear
83
+ end
84
+
84
85
  def source=(source)
85
86
  super
86
87
  if source
@@ -99,18 +100,30 @@ module OMF::Rete
99
100
  end
100
101
  end
101
102
  end
102
-
103
+
103
104
  private
104
-
105
- def process_result(result, original_tuple)
105
+
106
+ def process(tuple, block)
107
+ if @result_map
108
+ rtuple = @result_map.collect do |i| tuple[i] end
109
+ else
110
+ rtuple = tuple
111
+ end
112
+ result = block ? block.call(*rtuple) : rtuple
106
113
  if (result)
107
- unless result.kind_of?(Array) && result.size == @result_size
108
- raise "Expected block to return an array of size '#{@result_size}', but got '#{result.inspect}'"
109
- end
110
- @receiver.addTuple(result)
114
+ result = verify_result(result, tuple)
111
115
  end
116
+ result
112
117
  end
113
-
118
+
119
+ def verify_result(result, original_tuple)
120
+ unless result.kind_of?(Array) && result.size == @result_size
121
+ raise "Expected block to return an array of size '#{@result_size}', but got '#{result.inspect}' - #{block}"
122
+ end
123
+ result
124
+ end
125
+
126
+
114
127
  def _describe(out, sep )
115
128
  out.write("processing#{sep}")
116
129
  end
@@ -121,7 +134,7 @@ module OMF::Rete
121
134
  # for every incoming tuple.
122
135
  #
123
136
  # TODO: This should really be a subclass of +ProcessingTupleStream+, but
124
- # we have supress_duplicates in this class which may be useful for
137
+ # we have supress_duplicates in this class which may be useful for
125
138
  # +ProcessingTupleStream+ as well.
126
139
  #
127
140
  class ResultTupleStream < AbstractTupleStream
@@ -133,7 +146,7 @@ module OMF::Rete
133
146
  @results = Set.new
134
147
  end
135
148
  end
136
-
149
+
137
150
  def source=(source)
138
151
  @source = source
139
152
  if @source.description != @description
@@ -146,36 +159,55 @@ module OMF::Rete
146
159
  end
147
160
  end
148
161
  end
149
-
162
+
150
163
  def addTuple(tuple)
151
164
  if @result_map
152
- rtuple = @result_map.collect do |i| tuple[i] end
165
+ ta = @result_map.collect do |i| tuple[i] end
153
166
  else
154
- rtuple = tuple
167
+ ta = tuple
155
168
  end
169
+ rtuple = Tuple.new(ta, @description)
156
170
  if @results
157
- if @results.add?(rtuple)
158
- @block.call(rtuple)
171
+ if @results.add?(ta)
172
+ @block.arity == 1 ? @block.call(rtuple) : @block.call(rtuple, :add)
159
173
  end
160
174
  else
161
- @block.call(rtuple)
175
+ @block.arity == 1 ? @block.call(rtuple) : @block.call(rtuple, :add)
176
+ end
177
+ end
178
+
179
+ def removeTuple(tuple)
180
+ if @result_map
181
+ ta = @result_map.collect do |i| tuple[i] end
182
+ else
183
+ ta = tuple
184
+ end
185
+ rtuple = Tuple.new(ta, @description)
186
+ if @results
187
+ @results.delete(ta)
162
188
  end
189
+ @block.arity == 1 ? @block.call(rtuple) : @block.call(rtuple, :remove)
163
190
  end
164
-
191
+
192
+ def clear()
193
+ @results.clear if @results
194
+ @block.call(nil, :cleared) if @block.arity == 2
195
+ end
196
+
165
197
  # Return true if +tuple+ can be produced by this stream. A
166
198
  # +ResultStream+ only narrows a stream, so we need to
167
199
  # potentially expand it (with nil) and pass it up to the
168
200
  # +source+ of this stream.
169
201
  #
170
202
  def check_for_tuple(tuple)
171
- if @sourcce
203
+ if @sourcce
172
204
  # should check if +tuple+ has the same size as description
173
205
  if @result_map
174
206
  # need to expand
175
207
  unless @expand_map
176
208
  @expand_map = @source.description.collect do |name|
177
209
  index = @description.find_index do |n2| name == n2 end
178
- end
210
+ end
179
211
  end
180
212
  up_tuple = @expand_map.collect do |i| i nil? ? nil : tuple[i] end
181
213
  else
@@ -186,14 +218,14 @@ module OMF::Rete
186
218
  end
187
219
 
188
220
  private
189
-
221
+
190
222
  def _describe(out, sep )
191
223
  out.write("out: [#{@description.join(', ')}]#{sep}")
192
224
  end
193
225
  end # ResultTupleStream
194
226
 
195
227
  # A filtering tuple stream calls the associated processing block
196
- # for every incoming tuple and forwards the incoming tuple if the
228
+ # for every incoming tuple and forwards the incoming tuple if the
197
229
  # the block returns true, otherwise it drops the tuple.
198
230
  #
199
231
  class FilterTupleStream < ProcessingTupleStream
@@ -201,7 +233,7 @@ module OMF::Rete
201
233
  def initialize(project_pattern, description = project_pattern, receiver = nil, &block)
202
234
  super project_pattern, description, description, receiver, &block
203
235
  end
204
-
236
+
205
237
  # Return true if +tuple+ can be produced by this stream. For
206
238
  # this we need to check first if it would pass this filter
207
239
  # before we check if the source for this filter is being
@@ -210,7 +242,7 @@ module OMF::Rete
210
242
  # TODO: This currently doesn't work for tuples with wild cards.
211
243
  #
212
244
  def check_for_tuple(tuple)
213
- if @sourcce
245
+ if @sourcce
214
246
  # should check if +tuple+ has the same size as description
215
247
  if @result_map
216
248
  rtuple = @result_map.collect do |i| tuple[i] end
@@ -222,16 +254,13 @@ module OMF::Rete
222
254
  end
223
255
  end
224
256
  end
225
-
257
+
226
258
  private
227
-
228
- def process_result(result, original_tuple)
229
- if (result)
230
- @receiver.addTuple(original_tuple)
231
- end
259
+
260
+ def verify_result(decision, original_tuple)
261
+ decision ? original_tuple : nil
232
262
  end
233
-
234
-
263
+
235
264
  def _describe(out, sep )
236
265
  out.write("filtering#{sep}")
237
266
  end
@@ -1,7 +1,7 @@
1
1
 
2
2
  module OMF
3
3
  module Rete
4
- VERSION = '0.5'
4
+ VERSION = '0.6.1'
5
5
  # Used for finding the example directory
6
6
  TOP_DIR = File.dirname(File.dirname(File.dirname(__FILE__)))
7
7
  end
data/lib/omf_rete.rb CHANGED
@@ -3,7 +3,17 @@
3
3
 
4
4
  module OMF
5
5
  module Rete
6
-
6
+
7
+ # Create a Rete engine to operate on.
8
+ #
9
+ # @param opts :tuple_length Length of tuple if only one type is used
10
+ #
11
+ def self.create_engine(opts = {})
12
+ require 'omf_rete/store'
13
+ Store.create(opts.delete(:tuple_length), opts)
14
+
15
+ end
16
+
7
17
  # Defines a filter on a tuple stream. The argument is either a variable
8
18
  # number of binding variables with which the associated block is called.
9
19
  # If the argument are two arrays, the first one holds the above described
@@ -16,15 +26,15 @@ module OMF
16
26
  if projectPattern.size != 2
17
27
  raise "Wrong arguments for 'filter'. See documentation."
18
28
  end
19
- outDescription = projectPattern[1]
20
- projectPattern = projectPattern[0]
29
+ outDescription = projectPattern[1]
30
+ projectPattern = projectPattern[0]
21
31
  else
22
32
  outDescription = nil
23
33
  end
24
34
 
25
35
  FilterPlan.new(projectPattern, outDescription, &block)
26
36
  end
27
-
37
+
28
38
  def self.differ(binding1, binding2)
29
39
  filter(binding1, binding2) do |b1, b2|
30
40
  b1 != b2
data/omf_rete.gemspec CHANGED
@@ -21,4 +21,7 @@ Gem::Specification.new do |s|
21
21
 
22
22
  # specify any dependencies here; for example:
23
23
  # s.add_development_dependency "minitest", "~> 2.11.3"
24
+
25
+ s.add_runtime_dependency "omf_base"
26
+
24
27
  end
data/tests/test_filter.rb CHANGED
@@ -20,7 +20,7 @@ class TestFilter < Test::Unit::TestCase
20
20
 
21
21
  resT = []
22
22
  result = pb.materialize(outPattern) do |t|
23
- resT << t
23
+ resT << t.to_a
24
24
  end
25
25
 
26
26
  out = StringIO.new
@@ -47,4 +47,34 @@ class TestJoinOP < Test::Unit::TestCase
47
47
  r.addTuple(t2)
48
48
  assert_equal [['x', 'y', 'z']], out.to_a
49
49
  end
50
+
51
+ # [:a :x?], [?x :c]
52
+ #
53
+ def test_remove1
54
+ t1 = ['a', 'b']
55
+ t2 = ['b', 'd']
56
+ t3 = ['b', 'e']
57
+ l = OMF::Rete::IndexedTupleSet.new([:a, :x?], [:x?])
58
+ r = OMF::Rete::IndexedTupleSet.new([:x?, :b], [:x?])
59
+ out = IndexedTupleSet.new([:x?], [:x?])
60
+ JoinOP.new(l, r, out)
61
+ l.addTuple(t1)
62
+ r.addTuple(t2)
63
+ assert_equal [[t1[1]]], out.to_a
64
+
65
+ l.removeTuple(t1)
66
+ assert_equal [], out.to_a
67
+
68
+ l.addTuple(t1)
69
+ r.addTuple(t3)
70
+ #assert_equal [[t1[1]]], out.to_a
71
+ assert_equal [['b']], out.to_a
72
+
73
+ r.removeTuple(t3)
74
+ assert_equal [['b']], out.to_a
75
+ r.removeTuple(t2)
76
+ assert_equal [], out.to_a
77
+
78
+ end
79
+
50
80
  end
@@ -0,0 +1,36 @@
1
+ require 'omf_rete/store'
2
+
3
+ include OMF::Rete
4
+
5
+ class TestNamedStore < Test::Unit::TestCase
6
+
7
+ def create_store()
8
+ Store.create(3, type: :named_alpha, name: :foo)
9
+ end
10
+
11
+ def test_create_store
12
+ store = create_store()
13
+ end
14
+
15
+ def test_add_tuple
16
+ store = create_store()
17
+ store.addTuple [:foo, :b, :c]
18
+ end
19
+
20
+ def test_add_tuple2
21
+ store = create_store()
22
+ store.add :foo, :b, :c
23
+ end
24
+
25
+ def test_add_wrong_named_tuple
26
+ store = create_store()
27
+ assert_raise OMF::Rete::Store::WrongNameException do
28
+ store.add :a, :b, :c
29
+ end
30
+ end
31
+
32
+
33
+ end
34
+
35
+
36
+
@@ -0,0 +1,150 @@
1
+ require 'omf_rete/store'
2
+
3
+ include OMF::Rete
4
+
5
+ class TestObjectStore < Test::Unit::TestCase
6
+
7
+ class Obj
8
+ attr_accessor :a, :b
9
+
10
+ def initialize(a, b = nil)
11
+ self.a = a; self.b = b
12
+ end
13
+ end
14
+
15
+ class User
16
+ attr_accessor :name
17
+
18
+ def initialize(name)
19
+ self.name = name
20
+ end
21
+ end
22
+
23
+ def create_store(opts = {})
24
+ Store.create(0, opts.merge(type: :object))
25
+ end
26
+
27
+ def test_create_store
28
+ store = create_store()
29
+ end
30
+
31
+ def test_add_tuple_to_empty
32
+ store = create_store()
33
+ assert_raise OMF::Rete::Store::NotImplementedException do
34
+ store.add :a
35
+ end
36
+ end
37
+
38
+ def test_find_single
39
+ store = create_store()
40
+ store.representObject(Obj.new(1))
41
+ assert_equal Set.new([[:a, 1]]), store.find([:a, nil])
42
+ end
43
+
44
+ def test_find_single2
45
+ store = create_store(name: :p)
46
+ store.representObject(Obj.new(1))
47
+ assert_equal Set.new([[:p, :a, 1]]), store.find([:p, :a, nil])
48
+ end
49
+
50
+ def test_find_enumerable
51
+ store = create_store()
52
+ store.representObject(Obj.new([1,2]))
53
+ assert_equal Set.new([[:a, 1], [:a, 2]]), store.find([:a, nil])
54
+ end
55
+
56
+ def test_find_validate
57
+ store = create_store()
58
+ store.representObject(Obj.new(1))
59
+ assert_equal Set.new([[:a, 1]]), store.find([:a, 1])
60
+ assert_equal Set.new([]), store.find([:a, 2])
61
+ end
62
+
63
+ def test_find_validate_enumerable
64
+ store = create_store()
65
+ store.representObject(Obj.new([1,2]))
66
+ assert_equal Set.new([[:a, 1]]), store.find([:a, 1])
67
+ end
68
+
69
+ def test_tset
70
+ store = create_store()
71
+ store.representObject(Obj.new(1))
72
+ tset = store.createTSet([:a, :x?], [:x?])
73
+ assert_equal Set.new([[:a, 1]]), tset.to_set
74
+ end
75
+
76
+ def test_tset2
77
+ store = create_store(name: :p)
78
+ store.representObject(Obj.new(1))
79
+ tset = store.createTSet([:p, :a, :x?], [:x?])
80
+ assert_equal Set.new([[:p, :a, 1]]), tset.to_set
81
+ end
82
+
83
+ def test_change_object_tset
84
+ store = create_store()
85
+ store.representObject(Obj.new(1))
86
+ tset = store.createTSet([:a, :x?], [:x?])
87
+ store.representObject(Obj.new(2))
88
+ assert_equal Set.new([[:a, 2]]), tset.to_set
89
+ end
90
+
91
+ def test_change_object_subscribe
92
+
93
+ store = create_store()
94
+ store.representObject(Obj.new(1))
95
+
96
+ plan = [[:a, :x?]]
97
+
98
+ resT = Set.new
99
+ actionSet = Set.new
100
+ result = store.subscribe(:test, plan) do |t, a|
101
+ (resT << t.to_a) if a == :add
102
+ actionSet << a
103
+ end
104
+ assert_equal Set.new([[1]]), resT
105
+ assert_equal Set.new([:add]), actionSet
106
+
107
+ # Now lets change the object
108
+ resT.clear; actionSet.clear
109
+ store.representObject(Obj.new(2))
110
+ assert_equal Set.new([[2]]), resT
111
+ assert_equal Set.new([:cleared, :add]), actionSet
112
+ end
113
+
114
+ def test_change_object_subscribe_two_stores
115
+
116
+ store = Store.create(0, type: :predicate)
117
+
118
+ as_store = store.registerPredicate(:as, 3)
119
+ store.add :as, :userA, :ok
120
+
121
+ msg_store = Store.create(0, type: :object, name: :m)
122
+ store.registerPredicateStore(:m, msg_store)
123
+
124
+ plan = [[:m, :name, :n?], [:as, :n?, :y?]]
125
+
126
+ resT = Set.new
127
+ actionSet = Set.new
128
+ result = store.subscribe(:test, plan) do |t, a|
129
+ (resT << t.to_a) if a == :add
130
+ actionSet << a
131
+ end
132
+
133
+ msg_store.representObject(User.new(:userA))
134
+
135
+ assert_equal Set.new([[:m, :name, :userA]]), store.find([:m, :name, nil])
136
+ assert_equal Set.new([[:as, :userA, :ok]]), store.find([:as, nil, nil])
137
+ assert_equal Set.new([[:userA, :ok]]), resT
138
+ assert_equal Set.new([:cleared, :add]), actionSet
139
+
140
+ # Now lets change the object
141
+ resT.clear; actionSet.clear
142
+ msg_store.representObject(User.new(:userB))
143
+ assert_equal Set.new(), resT
144
+ assert_equal Set.new([:cleared]), actionSet
145
+ end
146
+
147
+ end
148
+
149
+
150
+