ruleby 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/core/atoms.rb CHANGED
@@ -149,6 +149,10 @@ module Ruleby
149
149
  DefTemplate === df && df.clazz == @clazz && df.mode == @mode
150
150
  end
151
151
  end
152
+
153
+ class AtomFactory
154
+ # TODO add some convenience methods for creating atoms
155
+ end
152
156
 
153
157
  end
154
158
  end
data/lib/core/engine.rb CHANGED
@@ -182,6 +182,7 @@ module Ruleby
182
182
  @working_memory = wm
183
183
  @conflict_resolver = cr
184
184
  @wm_altered = false
185
+ assert InitialFact.new
185
186
  end
186
187
 
187
188
  def facts
@@ -207,9 +208,16 @@ module Ruleby
207
208
  assert(object,&block)
208
209
  end
209
210
 
211
+ def retrieve(c)
212
+ facts.select {|f| f.kind_of?(c)}
213
+ end
214
+
210
215
  # This method adds a new rule to the system.
211
216
  def assert_rule(rule)
212
- @root = RootNode.new(@working_memory) if @root == nil
217
+ if @root == nil
218
+ @root = RootNode.new(@working_memory)
219
+ @root.reset_counter
220
+ end
213
221
  @root.assert_rule rule
214
222
  end
215
223
 
data/lib/core/nodes.rb CHANGED
@@ -28,9 +28,8 @@ module Ruleby
28
28
  # This method is invoked when a new rule is added to the system. The
29
29
  # rule is processed and the appropriate nodes are added to the network.
30
30
  def assert_rule(rule)
31
- pattern = rule.pattern
32
31
  terminal_node = TerminalNode.new rule
33
- build_network(pattern, terminal_node)
32
+ build_network(rule.pattern, terminal_node)
34
33
  @terminal_nodes.push terminal_node
35
34
  end
36
35
 
@@ -96,10 +95,16 @@ module Ruleby
96
95
  # Returns a new node in the network that wraps the given pattern and
97
96
  # is above (i.e. it outputs to) the given node.
98
97
  def build_network(pattern, out_node, side=nil)
99
- if (pattern.kind_of?(ObjectPattern))
100
- atom_node = create_atom_nodes(pattern, out_node, side)
101
- #out_node.parent_nodes.push atom_node # only used to print network
102
- return atom_node
98
+ if pattern.kind_of?(ObjectPattern)
99
+ if pattern.kind_of?(NotPattern) and (side==:left or !side)
100
+ # a NotPattern needs to be run through a NotNode, which is a beta node.
101
+ # So if the NotPattern is on the left (i.e. it is the first pattern),
102
+ # then we need to add a dummy pattern in front of it.
103
+ new_pattern = CompositePattern.new(InitialFactPattern.new, pattern)
104
+ return build_network(new_pattern, out_node, side)
105
+ else
106
+ return create_atom_nodes(pattern, out_node, side)
107
+ end
103
108
  else
104
109
  join_node = create_join_node(pattern, out_node, side)
105
110
  build_network(pattern.left_pattern, join_node, :left)
@@ -117,7 +122,7 @@ module Ruleby
117
122
  # side - if the out_node is a JoinNode, this marks the side
118
123
  def create_atom_nodes(pattern, out_node, side)
119
124
  # TODO refactor this method so it clear and concise
120
- type_node = create_type_node(pattern)
125
+ type_node = create_type_node(pattern)
121
126
  forked = false
122
127
  parent_atom = pattern.atoms[0]
123
128
  parent_node = type_node
@@ -164,10 +169,7 @@ module Ruleby
164
169
  # out_node - the Node that this pattern is directly above in thw network
165
170
  # side - if the out_node is a JoinNode, this marks the side
166
171
  def create_join_node(pattern, out_node, side)
167
- join_node = nil
168
- if (pattern.left_pattern.kind_of?(NotPattern))
169
- raise 'NotPatterns at the being of a rule are not yet supported'
170
- elsif (pattern.right_pattern.kind_of?(NotPattern))
172
+ if (pattern.right_pattern.kind_of?(NotPattern))
171
173
  join_node = NotNode.new
172
174
  else
173
175
  join_node = JoinNode.new
@@ -232,9 +234,10 @@ module Ruleby
232
234
  # working memory. It can be a costly operation because it iterates over
233
235
  # EVERY fact in working memory. It should only be used when a new rule is
234
236
  # added.
235
- def compare_to_wm(type_node)
237
+ def compare_to_wm(type_node)
236
238
  @working_memory.each_fact do |fact|
237
- type_node.assert MatchContext.new(fact)
239
+ type_node.retract fact
240
+ type_node.assert fact
238
241
  end
239
242
  end
240
243
  end
data/lib/core/patterns.rb CHANGED
@@ -99,9 +99,18 @@ module Ruleby
99
99
  end
100
100
 
101
101
  end
102
+
103
+ class InitialFactPattern < ObjectPattern
104
+ def initialize
105
+ deftemplate = DefTemplate.new InitialFact, :equals
106
+ htag = GeneratedTag.new
107
+ head = HeadAtom.new htag, deftemplate
108
+ super(head, [])
109
+ end
110
+ end
102
111
 
103
112
  class PatternFactory
104
-
113
+ # TODO add some convenience methods for creating patterns
105
114
  end
106
115
  end
107
116
  end
data/lib/core/utils.rb CHANGED
@@ -11,454 +11,461 @@
11
11
 
12
12
  module Ruleby
13
13
  module Core
14
+
15
+ # This class is used as a unique fact that is assert to an engine's working memory
16
+ # immediately after creation. This fact is used mainly when a NotPattern is put
17
+ # at the begining of a rule. This allows it to join the 'not' to something tangible.
18
+ class InitialFact
19
+
20
+ end
14
21
 
15
- # This class is a wrapper for the context under which the network executes for
16
- # for a given fact. It is essentially a wrapper for a fact and a partial
17
- # match.
18
- class MatchContext
22
+ # This class is a wrapper for the context under which the network executes for
23
+ # for a given fact. It is essentially a wrapper for a fact and a partial
24
+ # match.
25
+ class MatchContext
19
26
 
20
- attr_reader:fact
21
- attr_reader:match
27
+ attr_reader:fact
28
+ attr_reader:match
22
29
 
23
- def initialize(fact,mr)
24
- @fact = fact
25
- @match = mr
26
- end
30
+ def initialize(fact,mr)
31
+ @fact = fact
32
+ @match = mr
33
+ end
27
34
 
28
- def to_s
29
- return @match.to_s
30
- end
35
+ def to_s
36
+ return @match.to_s
37
+ end
31
38
 
32
- def ==(t)
33
- return t && @fact == t.fact && @match == t.match
39
+ def ==(t)
40
+ return t && @fact == t.fact && @match == t.match
41
+ end
34
42
  end
35
- end
36
43
 
37
- # This class represents a partial match. It contains the variables, values,
38
- # and some metadata about the match. For the most part, this metadata is used
39
- # during conflict resolution.
40
- class MatchResult
41
- # TODO this class needs to be cleaned up so that we don't have a bunch of
42
- # properties. Instead, maybe it sould have a list of facts.
44
+ # This class represents a partial match. It contains the variables, values,
45
+ # and some metadata about the match. For the most part, this metadata is used
46
+ # during conflict resolution.
47
+ class MatchResult
48
+ # TODO this class needs to be cleaned up so that we don't have a bunch of
49
+ # properties. Instead, maybe it sould have a list of facts.
43
50
 
44
- attr :variables, true
45
- attr :is_match, true
46
- attr :fact_hash, true
47
- attr :recency, true
51
+ attr :variables, true
52
+ attr :is_match, true
53
+ attr :fact_hash, true
54
+ attr :recency, true
48
55
 
49
- def initialize(variables=Hash.new,is_match=false,fact_hash={},recency=[])
50
- @variables = variables
56
+ def initialize(variables=Hash.new,is_match=false,fact_hash={},recency=[])
57
+ @variables = variables
51
58
 
52
- # a list of recencies of the facts that this matchresult depends on.
53
- @recency = recency
59
+ # a list of recencies of the facts that this matchresult depends on.
60
+ @recency = recency
54
61
 
55
- # notes where this match result is from a NotPattern or ObjectPattern
56
- # TODO this isn't really needed anymore. how can we get rid of it?
57
- @is_match = is_match
62
+ # notes where this match result is from a NotPattern or ObjectPattern
63
+ # TODO this isn't really needed anymore. how can we get rid of it?
64
+ @is_match = is_match
58
65
 
59
- # a hash of fact.ids that each tag corresponds to
60
- @fact_hash = fact_hash
61
- end
66
+ # a hash of fact.ids that each tag corresponds to
67
+ @fact_hash = fact_hash
68
+ end
62
69
 
63
- def []=(sym, object)
64
- @variables[sym] = object
65
- end
70
+ def []=(sym, object)
71
+ @variables[sym] = object
72
+ end
66
73
 
67
- def [](sym)
68
- return @variables[sym]
69
- end
74
+ def [](sym)
75
+ return @variables[sym]
76
+ end
70
77
 
71
- def fact_ids
72
- return fact_hash.values.uniq
73
- end
78
+ def fact_ids
79
+ return fact_hash.values.uniq
80
+ end
74
81
 
75
- def ==(match)
76
- return match != nil && @variables == match.variables && @is_match == match.is_match && @fact_hash == match.fact_hash
77
- end
82
+ def ==(match)
83
+ return match != nil && @variables == match.variables && @is_match == match.is_match && @fact_hash == match.fact_hash
84
+ end
78
85
 
79
- def key?(m)
80
- return @variables.key?(m)
81
- end
86
+ def key?(m)
87
+ return @variables.key?(m)
88
+ end
82
89
 
83
- def keys
84
- return @variables.keys
85
- end
90
+ def keys
91
+ return @variables.keys
92
+ end
86
93
 
87
- def update(mr)
88
- @recency = @recency | mr.recency
89
- @is_match = mr.is_match
90
- @variables = @variables.update mr.variables
91
- @fact_hash = @fact_hash.update mr.fact_hash
92
- return self
93
- end
94
+ def update(mr)
95
+ @recency = @recency | mr.recency
96
+ @is_match = mr.is_match
97
+ @variables = @variables.update mr.variables
98
+ @fact_hash = @fact_hash.update mr.fact_hash
99
+ return self
100
+ end
94
101
 
95
- def dup
96
- dup_mr = MatchResult.new
97
- dup_mr.recency = @recency.clone
98
- dup_mr.is_match = @is_match
99
- dup_mr.variables = @variables.clone
100
- dup_mr.fact_hash = @fact_hash.clone
101
- return dup_mr
102
- end
102
+ def dup
103
+ dup_mr = MatchResult.new
104
+ dup_mr.recency = @recency.clone
105
+ dup_mr.is_match = @is_match
106
+ dup_mr.variables = @variables.clone
107
+ dup_mr.fact_hash = @fact_hash.clone
108
+ return dup_mr
109
+ end
103
110
 
104
- def merge!(mr)
105
- return update(mr)
106
- end
111
+ def merge!(mr)
112
+ return update(mr)
113
+ end
107
114
 
108
- def merge(mr)
109
- new_mr = MatchResult.new
110
- new_mr.recency = @recency | mr.recency
111
- new_mr.is_match = mr.is_match
112
- new_mr.variables = @variables.merge mr.variables
113
- new_mr.fact_hash = @fact_hash.merge mr.fact_hash
114
- return new_mr
115
- end
115
+ def merge(mr)
116
+ new_mr = MatchResult.new
117
+ new_mr.recency = @recency | mr.recency
118
+ new_mr.is_match = mr.is_match
119
+ new_mr.variables = @variables.merge mr.variables
120
+ new_mr.fact_hash = @fact_hash.merge mr.fact_hash
121
+ return new_mr
122
+ end
116
123
 
117
- def clear
118
- @variables = {}
119
- @fact_hash = {}
120
- @recency = []
121
- end
124
+ def clear
125
+ @variables = {}
126
+ @fact_hash = {}
127
+ @recency = []
128
+ end
122
129
 
123
- def delete(tag)
124
- @variables.delete(tag)
125
- @fact_hash.delete(tag)
126
- end
130
+ def delete(tag)
131
+ @variables.delete(tag)
132
+ @fact_hash.delete(tag)
133
+ end
127
134
 
128
- def to_s
129
- s = '#MatchResult('
130
- s = s + 'f)(' unless @is_match
131
- s = s + object_id.to_s+')('
132
- @variables.each do |key,value|
133
- s += "#{key}=#{value}/#{@fact_hash[key]}, "
135
+ def to_s
136
+ s = '#MatchResult('
137
+ s = s + 'f)(' unless @is_match
138
+ s = s + object_id.to_s+')('
139
+ @variables.each do |key,value|
140
+ s += "#{key}=#{value}/#{@fact_hash[key]}, "
141
+ end
142
+ return s + ")"
134
143
  end
135
- return s + ")"
136
144
  end
137
- end
138
145
 
139
- # This class is used when we need to have a Hash where keys and values are
140
- # mapped many-to-many. This class allows for quick access of both key and
141
- # value. It is similar to Multimap in C++ standard lib.
142
- # This thing is a mess (and barely works). It needs to be refactored.
143
- class MultiHash
144
- def initialize(key=nil, values=[])
145
- @i = 0
146
- clear
147
- if key
148
- @keys = {key => []}
149
- values.each do |v|
150
- xref = generate_xref()
151
- xref_list = @keys[key]
152
- xref_list.push xref
153
- @keys[key] = xref_list
154
- @values = {xref => v}
155
- @backward_hash = {xref => [key]}
146
+ # This class is used when we need to have a Hash where keys and values are
147
+ # mapped many-to-many. This class allows for quick access of both key and
148
+ # value. It is similar to Multimap in C++ standard lib.
149
+ # This thing is a mess (and barely works). It needs to be refactored.
150
+ class MultiHash
151
+ def initialize(key=nil, values=[])
152
+ @i = 0
153
+ clear
154
+ if key
155
+ @keys = {key => []}
156
+ values.each do |v|
157
+ xref = generate_xref()
158
+ xref_list = @keys[key]
159
+ xref_list.push xref
160
+ @keys[key] = xref_list
161
+ @values = {xref => v}
162
+ @backward_hash = {xref => [key]}
163
+ end
156
164
  end
157
165
  end
158
- end
159
166
 
160
- def empty?
161
- return @keys.empty?
162
- end
167
+ def empty?
168
+ return @keys.empty?
169
+ end
163
170
 
164
- def rehash
165
- @keys.rehash
166
- @values.rehash
167
- @backward_hash.rehash
168
- end
171
+ def rehash
172
+ @keys.rehash
173
+ @values.rehash
174
+ @backward_hash.rehash
175
+ end
169
176
 
170
- def value?(mr)
171
- @values.value?(mr)
172
- end
177
+ def value?(mr)
178
+ @values.value?(mr)
179
+ end
173
180
 
174
- def clear
175
- @keys = {}
176
- @values = {}
177
- @backward_hash = {}
178
- end
181
+ def clear
182
+ @keys = {}
183
+ @values = {}
184
+ @backward_hash = {}
185
+ end
179
186
 
180
- def values_by_id(id)
181
- xrefs = @keys[id]
182
- values = []
183
- if xrefs
184
- xrefs.each do |k|
185
- values.push @values[k]
187
+ def values_by_id(id)
188
+ xrefs = @keys[id]
189
+ values = []
190
+ if xrefs
191
+ xrefs.each do |k|
192
+ values.push @values[k]
193
+ end
194
+ else
195
+ #???
186
196
  end
187
- else
188
- #???
197
+ return values
189
198
  end
190
- return values
191
- end
192
199
 
193
- def each_key
194
- @keys.each_key do |key|
195
- yield(key)
200
+ def each_key
201
+ @keys.each_key do |key|
202
+ yield(key)
203
+ end
196
204
  end
197
- end
198
205
 
199
- def has_key?(key)
200
- return @keys.has_key?(key)
201
- end
206
+ def has_key?(key)
207
+ return @keys.has_key?(key)
208
+ end
202
209
 
203
- def key?(key)
204
- return has_key?(key)
205
- end
210
+ def key?(key)
211
+ return has_key?(key)
212
+ end
206
213
 
207
- def +(dh)
208
- # TODO this can be faster
209
- new_dh = dh.dup
210
- dh.concat self.dup
211
- return new_dh
212
- end
214
+ def +(dh)
215
+ # TODO this can be faster
216
+ new_dh = dh.dup
217
+ dh.concat self.dup
218
+ return new_dh
219
+ end
213
220
 
214
- def add(ids,val)
215
- xref = generate_xref()
216
- ids.each do |id|
217
- xref_list = @keys[id]
218
- xref_list = [] if xref_list == @keys.default
219
- xref_list.push xref
220
- @keys[id] = xref_list
221
- end
222
- @values[xref] = val
223
- @backward_hash[xref] = ids
224
- end
221
+ def add(ids,val)
222
+ xref = generate_xref()
223
+ ids.each do |id|
224
+ xref_list = @keys[id]
225
+ xref_list = [] if xref_list == @keys.default
226
+ xref_list.push xref
227
+ @keys[id] = xref_list
228
+ end
229
+ @values[xref] = val
230
+ @backward_hash[xref] = ids
231
+ end
225
232
 
226
- # DEPRECATED
227
- # WARN this method adds a value to the MultiHash only if it is unique. It
228
- # can be a fairly costly operation, and should be avoided. We only
229
- # implemented this as part of a hack to get things working early on.
230
- def add_uniq(ids,val)
231
- xref = generate_xref()
232
- exist_list = []
233
- ids.each do |id|
234
- xref_list = @keys[id]
235
- if xref_list != @keys.default
236
- xref_list.each do |existing_xref|
237
- existing_val = @values[existing_xref]
238
- if existing_val
239
- if val == existing_val
240
- xref = existing_xref
241
- exist_list.push id
242
- break
233
+ # DEPRECATED
234
+ # WARN this method adds a value to the MultiHash only if it is unique. It
235
+ # can be a fairly costly operation, and should be avoided. We only
236
+ # implemented this as part of a hack to get things working early on.
237
+ def add_uniq(ids,val)
238
+ xref = generate_xref()
239
+ exist_list = []
240
+ ids.each do |id|
241
+ xref_list = @keys[id]
242
+ if xref_list != @keys.default
243
+ xref_list.each do |existing_xref|
244
+ existing_val = @values[existing_xref]
245
+ if existing_val
246
+ if val == existing_val
247
+ xref = existing_xref
248
+ exist_list.push id
249
+ break
250
+ end
251
+ else
252
+ # HACK there shouldn't be any xrefs like this in the
253
+ # hash to being with. Why are they there?
254
+ xref_list.delete(existing_xref)
255
+ @keys[id] = xref_list
243
256
  end
244
- else
245
- # HACK there shouldn't be any xrefs like this in the
246
- # hash to being with. Why are they there?
247
- xref_list.delete(existing_xref)
248
- @keys[id] = xref_list
249
- end
250
- end
251
- end
252
- end
253
- add_list = ids - exist_list
254
- add_list.each do |id|
255
- xref_list = @keys[id]
256
- xref_list = [] if xref_list == @keys.default
257
- xref_list.push xref
258
- @keys[id] = xref_list
259
- end
260
- @values[xref] = val if exist_list.empty?
261
- b_list = @backward_hash[xref]
262
- if b_list
263
- @backward_hash[xref] = b_list | ids
264
- else
265
- @backward_hash[xref] = ids
257
+ end
258
+ end
259
+ end
260
+ add_list = ids - exist_list
261
+ add_list.each do |id|
262
+ xref_list = @keys[id]
263
+ xref_list = [] if xref_list == @keys.default
264
+ xref_list.push xref
265
+ @keys[id] = xref_list
266
+ end
267
+ @values[xref] = val if exist_list.empty?
268
+ b_list = @backward_hash[xref]
269
+ if b_list
270
+ @backward_hash[xref] = b_list | ids
271
+ else
272
+ @backward_hash[xref] = ids
273
+ end
266
274
  end
267
- end
268
275
 
269
- def each
270
- @values.each do |xref,val|
271
- ids = @backward_hash[xref]
272
- yield(ids,val)
276
+ def each
277
+ @values.each do |xref,val|
278
+ ids = @backward_hash[xref]
279
+ yield(ids,val)
280
+ end
273
281
  end
274
- end
275
282
 
276
- def each_internal
277
- @values.each do |xref,val|
278
- ids = @backward_hash[xref]
279
- yield(ids,xref,val)
280
- end
281
- end
282
- private:each_internal
283
+ def each_internal
284
+ @values.each do |xref,val|
285
+ ids = @backward_hash[xref]
286
+ yield(ids,xref,val)
287
+ end
288
+ end
289
+ private:each_internal
283
290
 
284
- def concat(multi_hash)
285
- multi_hash.each do |ids,val|
286
- add(ids,val)
291
+ def concat(multi_hash)
292
+ multi_hash.each do |ids,val|
293
+ add(ids,val)
294
+ end
287
295
  end
288
- end
289
296
 
290
- # DEPRECATED
291
- # WARN see comments in add_uniq
292
- def concat_uniq(double_hash)
293
- double_hash.each do |ids,val|
294
- add_uniq(ids,val)
297
+ # DEPRECATED
298
+ # WARN see comments in add_uniq
299
+ def concat_uniq(double_hash)
300
+ double_hash.each do |ids,val|
301
+ add_uniq(ids,val)
302
+ end
295
303
  end
296
- end
297
304
 
298
- def default
299
- return @values.default
300
- end
305
+ def default
306
+ return @values.default
307
+ end
301
308
 
302
- def remove(id)
303
- xref_list = @keys.delete(id)
304
- if xref_list != @keys.default
305
- removed_values = []
306
- xref_list.each do |xref|
307
- value = @values.delete(xref)
308
- removed_values.push value
309
- id_list = @backward_hash.delete(xref)
310
- id_list.each do |next_id|
311
- remove_internal(next_id,xref) if next_id != id
309
+ def remove(id)
310
+ xref_list = @keys.delete(id)
311
+ if xref_list != @keys.default
312
+ removed_values = []
313
+ xref_list.each do |xref|
314
+ value = @values.delete(xref)
315
+ removed_values.push value
316
+ id_list = @backward_hash.delete(xref)
317
+ id_list.each do |next_id|
318
+ remove_internal(next_id,xref) if next_id != id
319
+ end
312
320
  end
321
+ return removed_values
322
+ else
323
+ # puts 'WARN: tried to remove from MultiHash where id does not exist'
324
+ return default
313
325
  end
314
- return removed_values
315
- else
316
- # puts 'WARN: tried to remove from MultiHash where id does not exist'
317
- return default
318
326
  end
319
- end
320
327
 
321
- def remove_internal(id,xref)
322
- xref_list = @keys[id]
323
- if xref_list # BUG this shouldn't be nil!
324
- xref_list.delete(xref)
325
- if xref_list.empty?
326
- @keys.delete(id)
327
- else
328
- @keys[id] = xref_list
328
+ def remove_internal(id,xref)
329
+ xref_list = @keys[id]
330
+ if xref_list # BUG this shouldn't be nil!
331
+ xref_list.delete(xref)
332
+ if xref_list.empty?
333
+ @keys.delete(id)
334
+ else
335
+ @keys[id] = xref_list
336
+ end
329
337
  end
330
338
  end
331
- end
332
- private:remove_internal
333
-
334
- def remove_by_xref(ids,xref)
335
- ids.each do |id|
336
- xref_list = @keys[id]
337
- xref_list.delete(xref)
338
- if xref_list.empty?
339
- @keys.delete(id)
340
- else
341
- @keys[id] = xref_list
339
+ private:remove_internal
340
+
341
+ def remove_by_xref(ids,xref)
342
+ ids.each do |id|
343
+ xref_list = @keys[id]
344
+ xref_list.delete(xref)
345
+ if xref_list.empty?
346
+ @keys.delete(id)
347
+ else
348
+ @keys[id] = xref_list
349
+ end
342
350
  end
351
+ @values.delete(xref)
352
+ @backward_hash.delete(xref)
343
353
  end
344
- @values.delete(xref)
345
- @backward_hash.delete(xref)
346
- end
347
- private:remove_by_xref
348
-
349
- def delete_if
350
- @values.delete_if do |xref,v|
351
- if yield(v)
352
- id_list = @backward_hash.delete(xref)
353
- id_list.each do |next_id|
354
- remove_internal(next_id,xref)
354
+ private:remove_by_xref
355
+
356
+ def delete_if
357
+ @values.delete_if do |xref,v|
358
+ if yield(v)
359
+ id_list = @backward_hash.delete(xref)
360
+ id_list.each do |next_id|
361
+ remove_internal(next_id,xref)
362
+ end
363
+ true
364
+ else
365
+ false
355
366
  end
356
- true
357
- else
358
- false
359
367
  end
360
368
  end
361
- end
362
369
 
363
- def values
364
- return @values.values
365
- end
370
+ def values
371
+ return @values.values
372
+ end
366
373
 
367
- def keys
368
- return @keys.keys
369
- end
374
+ def keys
375
+ return @keys.keys
376
+ end
370
377
 
371
- def dup
372
- dup_mc = MultiHash.new
373
- each do |ids,v|
374
- dup_mc.add ids, v.dup
378
+ def dup
379
+ dup_mc = MultiHash.new
380
+ each do |ids,v|
381
+ dup_mc.add ids, v.dup
382
+ end
383
+ return dup_mc
375
384
  end
376
- return dup_mc
377
- end
378
385
 
379
- def generate_xref()
380
- @i = @i + 1
381
- return @i
382
- end
383
- private:generate_xref
384
-
385
- # This method is for testing. It ensures that all the Hash's
386
- # and Array's are in order, and not corrupted (ex. some key points
387
- # to a xref that does not exist in the match_results Hash).
388
- def valid?
389
- @keys.each do |id,xrefs|
390
- # xref_list = @keys[id]
391
- # if xref_list != @keys.default
392
- # xref_list.each do |xref|
393
- # id_list = @backward_hash[xref]
394
- # unless id_list
395
- # puts 'yup'
396
- # return false
397
- # end
398
- # end
399
- # end
400
- xrefs.each do |xref|
401
- count = 0
402
- xrefs.each do |xref2|
403
- if xref == xref2
404
- count = count + 1
405
- if count > 1
406
- puts '(0) Duplicate xrefs in entry for keys'
407
- return false
386
+ def generate_xref()
387
+ @i = @i + 1
388
+ return @i
389
+ end
390
+ private:generate_xref
391
+
392
+ # This method is for testing. It ensures that all the Hash's
393
+ # and Array's are in order, and not corrupted (ex. some key points
394
+ # to a xref that does not exist in the match_results Hash).
395
+ def valid?
396
+ @keys.each do |id,xrefs|
397
+ # xref_list = @keys[id]
398
+ # if xref_list != @keys.default
399
+ # xref_list.each do |xref|
400
+ # id_list = @backward_hash[xref]
401
+ # unless id_list
402
+ # puts 'yup'
403
+ # return false
404
+ # end
405
+ # end
406
+ # end
407
+ xrefs.each do |xref|
408
+ count = 0
409
+ xrefs.each do |xref2|
410
+ if xref == xref2
411
+ count = count + 1
412
+ if count > 1
413
+ puts '(0) Duplicate xrefs in entry for keys'
414
+ return false
415
+ end
408
416
  end
409
417
  end
410
- end
411
418
 
412
- mr = @match_results[xref]
413
- if mr == @match_results.default
414
- puts '(1) Missing entry in @match_results for xref'
415
- return false
416
- end
419
+ mr = @match_results[xref]
420
+ if mr == @match_results.default
421
+ puts '(1) Missing entry in @match_results for xref'
422
+ return false
423
+ end
417
424
 
418
- # @match_results.each do |mr_xref,other_mr|
419
- # if other_mr == mr && mr_xref != xref
420
- # puts '(1a) Duplicate entry in @match_results'
421
- # return false
422
- # end
423
- # end
425
+ # @match_results.each do |mr_xref,other_mr|
426
+ # if other_mr == mr && mr_xref != xref
427
+ # puts '(1a) Duplicate entry in @match_results'
428
+ # return false
429
+ # end
430
+ # end
424
431
 
425
- id_list = @backward_hash[xref]
426
- if id_list == @backward_hash.default
427
- puts '(2) Missing entry in backward_hash for xref'
428
- return false
429
- end
432
+ id_list = @backward_hash[xref]
433
+ if id_list == @backward_hash.default
434
+ puts '(2) Missing entry in backward_hash for xref'
435
+ return false
436
+ end
430
437
 
431
- if id_list.index(id) == nil
432
- puts '(3) Entry in backward_hash is missing id'
433
- return false
434
- end
438
+ if id_list.index(id) == nil
439
+ puts '(3) Entry in backward_hash is missing id'
440
+ return false
441
+ end
435
442
 
436
- id_list.each do |ref_id|
437
- unless ref_id == id
438
- ref_xref_list = @keys[ref_id]
439
- if ref_xref_list == @keys.default
440
- puts '(4) Missing entry in keys for backward_hash id'
441
- puts "#{id},#{mr},#{xref},#{ref_id}"
442
- return false
443
- end
443
+ id_list.each do |ref_id|
444
+ unless ref_id == id
445
+ ref_xref_list = @keys[ref_id]
446
+ if ref_xref_list == @keys.default
447
+ puts '(4) Missing entry in keys for backward_hash id'
448
+ puts "#{id},#{mr},#{xref},#{ref_id}"
449
+ return false
450
+ end
444
451
 
445
- if ref_xref_list.index(xref) == nil
446
- puts '(5) Entry in keys is missing xref'
447
- puts "#{id},#{mr},#{xref},#{ref_id}"
448
- return false
452
+ if ref_xref_list.index(xref) == nil
453
+ puts '(5) Entry in keys is missing xref'
454
+ puts "#{id},#{mr},#{xref},#{ref_id}"
455
+ return false
456
+ end
449
457
  end
450
458
  end
451
459
  end
452
- end
453
- end
454
- return true
455
- end
456
- private:valid?
460
+ end
461
+ return true
462
+ end
463
+ private:valid?
457
464
 
458
- def ==(dh)
459
- # TODO need to implement this
460
- return super
465
+ def ==(dh)
466
+ # TODO need to implement this
467
+ return super
468
+ end
461
469
  end
462
470
  end
463
- end
464
471
  end
@@ -0,0 +1,23 @@
1
+
2
+ module Ruleby
3
+ class TreetopHelper
4
+
5
+ def self.rule(name, *args, &block)
6
+ options = args[0].kind_of?(Hash) ? args.shift : {}
7
+
8
+ r = Ferrari::RuleBuilder.new name
9
+ args.each do |arg|
10
+ if arg.kind_of? Array
11
+ r.when(*arg)
12
+ else
13
+ raise 'Invalid condition. All or none must be Arrays.'
14
+ end
15
+ end
16
+
17
+ r.then(&block)
18
+ r.priority = options[:priority] if options[:priority]
19
+
20
+ return r.build_rule
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,105 @@
1
+ grammar Ruleby
2
+ rule root
3
+ defrule* {
4
+ def get_rules
5
+ rs = []
6
+ elements.each do |e|
7
+ rs << e.get_rule()
8
+ end
9
+ return rs
10
+ end
11
+ }
12
+ end
13
+
14
+ rule defrule
15
+ 'rule' space name space foreach space action {
16
+ def get_rule()
17
+ action_text = action.text_value.strip
18
+ action_text.gsub!('do', '')
19
+ action_text.gsub!('end', '')
20
+ return foreach.get_rule(name.text_value.strip, action_text.strip)
21
+ end
22
+ }
23
+ end
24
+
25
+ rule name
26
+ [a-zA-Z] [a-zA-Z0-9]*
27
+ end
28
+
29
+ rule foreach
30
+ 'foreach' space head space symbol {
31
+ def get_rule(name, action_text)
32
+ class_name = head.text_value.strip
33
+ clazz = eval(class_name)
34
+ tag = symbol.text_value.strip
35
+ tag = tag[1, tag.size - 1]
36
+ return Ruleby::TreetopHelper.rule(name, [clazz, tag.to_sym], &action_text)
37
+ end
38
+ }
39
+ end
40
+
41
+
42
+ rule action
43
+ 'do' space (!'end' .)* 'end' space?
44
+ end
45
+
46
+
47
+
48
+ rule head
49
+ [A-Z] [a-zA-Z]*
50
+ end
51
+ rule symbol
52
+ ':' [a-zA-Z]+
53
+ end
54
+ rule where
55
+ 'where'
56
+ end
57
+ rule method
58
+ symbol '.' [a-z] [a-zA-Z0-9]*
59
+ end
60
+ rule string
61
+ '\'' [a-zA-Z0-9]* '\''
62
+ end
63
+ rule value
64
+ method / '-'* [0-9]+ / symbol / string
65
+ end
66
+ rule expression
67
+ value space (equal space value) / (not_equal space value) / (symbol)
68
+ end
69
+
70
+ rule clause
71
+ expression more:(and_sign clause)* {
72
+ def populate
73
+ more.elements.each do |e|
74
+ e.clause.populate
75
+ end
76
+ end
77
+ }
78
+ end
79
+ rule and_sign
80
+ 'AND'
81
+ end
82
+ rule not_equal
83
+ '!='
84
+ end
85
+ rule equal
86
+ '=='
87
+ end
88
+
89
+ rule space
90
+ white+
91
+ end
92
+
93
+
94
+ rule white
95
+ blank / eol
96
+ end
97
+
98
+ rule blank
99
+ [ \t]
100
+ end
101
+
102
+ rule eol
103
+ ("\r" "\n"?) / "\n"
104
+ end
105
+ end
data/tests/test.rb CHANGED
@@ -12,4 +12,7 @@ require 'common'
12
12
  require 'test_duck_type'
13
13
  require 'test_self_reference'
14
14
  require 'test_re'
15
+ require 'gets.rb'
16
+ require 'test_assert_facts'
17
+ require 'not_patterns'
15
18
 
metadata CHANGED
@@ -1,20 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruleby
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.4"
4
+ version: "0.5"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Kutner
8
8
  - Matt Smith
9
- autorequire: ruleby
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2008-05-18 00:00:00 -05:00
13
+ date: 2009-06-16 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
17
- description:
17
+ description: Ruleby is a rule engine written in the Ruby language. It is a system for executing a set of IF-THEN statements known as production rules. These rules are matched to objects using the forward chaining Rete algorithm. Ruleby provides an internal Domain Specific Language (DSL) for building the productions that make up a Ruleby program. Release Notes for Version 0.5 * reset the TerminalNode.counter when initializing the RootNode. Thanks to Shashank for the patch. * added InitialFact so that NotPatterns can be put at the front of a rule [#1 status:resolved] * Improved rule assertion so that rules can be added after facts have been asserted [#9 state:resolved] * Added retrieve method to engine, and a unit-test for it
18
18
  email: matt@ruleby.org
19
19
  executables: []
20
20
 
@@ -33,6 +33,9 @@ files:
33
33
  - lib/dsl/ferrari.rb
34
34
  - lib/dsl/letigre.rb
35
35
  - lib/dsl/steel.rb
36
+ - lib/dsl/treetop
37
+ - lib/dsl/treetop/treetop_helper.rb
38
+ - lib/dsl/treetop/tt_dsl.treetop
36
39
  - lib/dsl/yaml_dsl.rb
37
40
  - lib/rulebook.rb
38
41
  - lib/ruleby.rb
@@ -57,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
60
  version:
58
61
  requirements: []
59
62
 
60
- rubyforge_project:
63
+ rubyforge_project: ruleby
61
64
  rubygems_version: 1.0.1
62
65
  signing_key:
63
66
  specification_version: 2