omf_rete 0.5 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+