ruleby 0.3 → 0.4
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.
- data/lib/core/atoms.rb +76 -27
- data/lib/core/engine.rb +11 -5
- data/lib/core/nodes.rb +175 -71
- data/lib/core/patterns.rb +21 -18
- data/lib/dsl/ferrari.rb +50 -33
- data/lib/dsl/letigre.rb +43 -17
- data/lib/dsl/steel.rb +1 -0
- data/lib/dsl/yaml_dsl.rb +23 -0
- data/lib/rulebook.rb +18 -3
- data/lib/ruleby.rb +37 -0
- data/tests/test.rb +15 -0
- metadata +53 -57
- data/benchmarks/basic_rules.rb +0 -66
- data/benchmarks/joined_rules.rb +0 -73
- data/benchmarks/miss_manners/data.rb +0 -146
- data/benchmarks/miss_manners/miss_manners.rb +0 -33
- data/benchmarks/miss_manners/model.rb +0 -193
- data/benchmarks/miss_manners/rules.rb +0 -104
- data/benchmarks/model.rb +0 -36
- data/examples/example_diagnosis.rb +0 -117
- data/examples/example_hello.rb +0 -46
- data/examples/example_politician.rb +0 -97
- data/examples/example_ticket.rb +0 -113
- data/examples/fibonacci_example1.rb +0 -44
- data/examples/fibonacci_example2.rb +0 -40
- data/examples/fibonacci_rulebook.rb +0 -84
- data/examples/test_self_reference.rb +0 -77
data/lib/core/atoms.rb
CHANGED
@@ -14,33 +14,24 @@ module Ruleby
|
|
14
14
|
module Core
|
15
15
|
|
16
16
|
class Atom
|
17
|
-
attr_reader:
|
18
|
-
attr_reader:tag
|
19
|
-
attr_reader:proc
|
20
|
-
attr_reader:clazz
|
17
|
+
attr_reader :tag, :proc, :method, :deftemplate
|
21
18
|
|
22
|
-
def initialize(tag,
|
19
|
+
def initialize(tag, method, deftemplate, &block)
|
23
20
|
@tag = tag
|
24
|
-
@
|
25
|
-
@
|
21
|
+
@method = method
|
22
|
+
@deftemplate = deftemplate
|
26
23
|
@proc = Proc.new(&block) if block_given?
|
27
|
-
|
28
|
-
# QUESTION we should probably change the '@clazz' variable to be called
|
29
|
-
# something else. Like '@deftemplate' maybe?
|
30
|
-
|
31
|
-
# QUESTION we should probably change the '@name' variable to be called
|
32
|
-
# something else. Like '@method' maybe?
|
33
24
|
end
|
34
25
|
|
35
26
|
def to_s
|
36
|
-
return "#{self.class}
|
27
|
+
return "#{self.class},#{@tag},#{@method},#{@deftemplate}"
|
37
28
|
end
|
38
29
|
end
|
39
30
|
|
40
|
-
# This kind of atom is used to match
|
31
|
+
# This kind of atom is used to match a simple condition.
|
41
32
|
# For example:
|
42
33
|
#
|
43
|
-
# a.
|
34
|
+
# a.person{ |p| p.is_a? Person }
|
44
35
|
#
|
45
36
|
# So there are no references to other atoms.
|
46
37
|
class PropertyAtom < Atom
|
@@ -49,36 +40,72 @@ module Ruleby
|
|
49
40
|
end
|
50
41
|
|
51
42
|
def shareable?(atom)
|
52
|
-
return
|
43
|
+
return PropertyAtom === atom &&
|
44
|
+
@method == atom.method &&
|
45
|
+
@deftemplate == atom.deftemplate &&
|
46
|
+
@proc == atom.proc
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# TODO use this
|
51
|
+
class BlockAtom < PropertyAtom
|
52
|
+
def shareable?(atom)
|
53
|
+
return super &&
|
54
|
+
BlockAtom === atom &&
|
55
|
+
@proc == atom.proc
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# This kind of atom is used to match just a single, hard coded value.
|
60
|
+
# For example:
|
61
|
+
#
|
62
|
+
# a.name == 'John'
|
63
|
+
#
|
64
|
+
# So there are no references to other atoms.
|
65
|
+
class EqualsAtom < PropertyAtom
|
66
|
+
attr_reader :value
|
67
|
+
def initialize(tag, method, deftemplate, value)
|
68
|
+
super(tag,method,deftemplate)
|
69
|
+
@value = value
|
70
|
+
end
|
71
|
+
|
72
|
+
def shareable?(atom)
|
73
|
+
return EqualsAtom === atom &&
|
74
|
+
@method == atom.method &&
|
75
|
+
@deftemplate == atom.deftemplate
|
53
76
|
end
|
54
77
|
end
|
55
78
|
|
56
79
|
# This kind of atom is used to match a class type. For example:
|
57
80
|
#
|
58
|
-
#
|
81
|
+
# 'For each Person as :p'
|
59
82
|
#
|
60
83
|
# It is only used at the start of a pattern.
|
61
|
-
class
|
62
|
-
def initialize(tag,
|
63
|
-
|
84
|
+
class HeadAtom < Atom
|
85
|
+
def initialize(tag, deftemplate)
|
86
|
+
if deftemplate.mode == :equals
|
87
|
+
super tag, :class, deftemplate do |t| t == deftemplate.clazz end
|
88
|
+
elsif deftemplate.mode == :inherits
|
89
|
+
super tag, :class, deftemplate do |t| t === deftemplate.clazz end
|
90
|
+
end
|
64
91
|
end
|
65
92
|
|
66
93
|
def shareable?(atom)
|
67
|
-
return atom && @
|
94
|
+
return HeadAtom === atom && @deftemplate == atom.deftemplate
|
68
95
|
end
|
69
96
|
end
|
70
97
|
|
71
98
|
# This kind of atom is used for matching a value that is a variable.
|
72
99
|
# For example:
|
73
|
-
#
|
74
|
-
#
|
100
|
+
#
|
101
|
+
# #name == #:your_name
|
75
102
|
#
|
76
103
|
# The expression for this atom depends on some other atom.
|
77
104
|
class ReferenceAtom < Atom
|
78
105
|
attr_reader :vars
|
79
106
|
|
80
|
-
def initialize(tag,
|
81
|
-
super(tag,
|
107
|
+
def initialize(tag, method, vars, deftemplate, &block)
|
108
|
+
super(tag, method, deftemplate, &block)
|
82
109
|
@vars = vars # list of referenced variable names
|
83
110
|
end
|
84
111
|
|
@@ -87,7 +114,11 @@ module Ruleby
|
|
87
114
|
end
|
88
115
|
|
89
116
|
def ==(atom)
|
90
|
-
return
|
117
|
+
return ReferenceAtom === atom &&
|
118
|
+
@proc == atom.proc &&
|
119
|
+
@tag == atom.tag &&
|
120
|
+
@vars == atom.vars &&
|
121
|
+
@deftemplate == atom.deftemplate
|
91
122
|
end
|
92
123
|
|
93
124
|
def to_s
|
@@ -100,6 +131,24 @@ module Ruleby
|
|
100
131
|
# *methods* that this atom references (not the variable names)!
|
101
132
|
class SelfReferenceAtom < ReferenceAtom
|
102
133
|
end
|
134
|
+
|
135
|
+
# This class encapsulates the criteria the HeadAtom uses to match. The clazz
|
136
|
+
# attribute represents a Class type, and the mode defines whether the head
|
137
|
+
# will match only class that are exactly a particular type, or if it will
|
138
|
+
# match classes that inherit that type also.
|
139
|
+
class DefTemplate
|
140
|
+
attr_reader :clazz
|
141
|
+
attr_reader :mode
|
142
|
+
|
143
|
+
def initialize(clazz,mode=:equals)
|
144
|
+
@clazz = clazz
|
145
|
+
@mode = mode
|
146
|
+
end
|
147
|
+
|
148
|
+
def ==(df)
|
149
|
+
DefTemplate === df && df.clazz == @clazz && df.mode == @mode
|
150
|
+
end
|
151
|
+
end
|
103
152
|
|
104
153
|
end
|
105
154
|
end
|
data/lib/core/engine.rb
CHANGED
@@ -30,8 +30,8 @@ module Ruleby
|
|
30
30
|
@priority = 0
|
31
31
|
end
|
32
32
|
|
33
|
-
def fire(
|
34
|
-
@proc.call(
|
33
|
+
def fire(match)
|
34
|
+
@proc.call(match)
|
35
35
|
end
|
36
36
|
|
37
37
|
def ==(a2)
|
@@ -55,9 +55,9 @@ module Ruleby
|
|
55
55
|
@used = false
|
56
56
|
end
|
57
57
|
|
58
|
-
def fire(
|
58
|
+
def fire()
|
59
59
|
@used = true
|
60
|
-
@action.fire
|
60
|
+
@action.fire @match
|
61
61
|
end
|
62
62
|
|
63
63
|
def <=>(a2)
|
@@ -135,6 +135,8 @@ module Ruleby
|
|
135
135
|
# inference engine will compare these facts with the rules to produce some
|
136
136
|
# outcomes.
|
137
137
|
class WorkingMemory
|
138
|
+
attr_reader :facts
|
139
|
+
|
138
140
|
def initialize
|
139
141
|
@recency = 0
|
140
142
|
@facts = Array.new
|
@@ -181,6 +183,10 @@ module Ruleby
|
|
181
183
|
@conflict_resolver = cr
|
182
184
|
@wm_altered = false
|
183
185
|
end
|
186
|
+
|
187
|
+
def facts
|
188
|
+
@working_memory.facts.collect{|f| f.object}
|
189
|
+
end
|
184
190
|
|
185
191
|
# This method id called to add a new fact to working memory
|
186
192
|
def assert(object,&block)
|
@@ -217,7 +223,7 @@ module Ruleby
|
|
217
223
|
agenda = @conflict_resolver.resolve agenda
|
218
224
|
activation = agenda.pop
|
219
225
|
used_agenda.push activation
|
220
|
-
activation.fire
|
226
|
+
activation.fire
|
221
227
|
if @wm_altered
|
222
228
|
agenda = @root.matches(false)
|
223
229
|
@root.increment_counter
|
data/lib/core/nodes.rb
CHANGED
@@ -18,7 +18,8 @@ module Ruleby
|
|
18
18
|
class RootNode
|
19
19
|
def initialize(working_memory)
|
20
20
|
@working_memory = working_memory
|
21
|
-
@
|
21
|
+
@type_node = nil
|
22
|
+
@inherit_nodes = []
|
22
23
|
@atom_nodes = []
|
23
24
|
@join_nodes = []
|
24
25
|
@terminal_nodes = []
|
@@ -37,14 +38,10 @@ module Ruleby
|
|
37
38
|
# this method is called. It finds any nodes that depend on it, and updates
|
38
39
|
# them accordingly.
|
39
40
|
def assert_fact(fact)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
else
|
45
|
-
node.retract fact
|
46
|
-
end
|
47
|
-
end
|
41
|
+
@type_node and fact.token == :plus ? @type_node.assert(fact) : @type_node.retract(fact)
|
42
|
+
@inherit_nodes.each do |node|
|
43
|
+
fact.token == :plus ? node.assert(fact) : node.retract(fact)
|
44
|
+
end
|
48
45
|
end
|
49
46
|
|
50
47
|
# Increments the activation counter. This is just a pass-thru to the static
|
@@ -78,11 +75,15 @@ module Ruleby
|
|
78
75
|
end
|
79
76
|
|
80
77
|
def print
|
81
|
-
puts 'NETWORK:'
|
82
|
-
@terminal_nodes.each do |
|
83
|
-
|
78
|
+
puts 'NETWORK:'
|
79
|
+
@terminal_nodes.each do |n|
|
80
|
+
n.print(' ')
|
84
81
|
end
|
85
82
|
end
|
83
|
+
|
84
|
+
def child_nodes
|
85
|
+
return @inherit_nodes + [@type_node]
|
86
|
+
end
|
86
87
|
|
87
88
|
private
|
88
89
|
|
@@ -116,27 +117,31 @@ module Ruleby
|
|
116
117
|
# side - if the out_node is a JoinNode, this marks the side
|
117
118
|
def create_atom_nodes(pattern, out_node, side)
|
118
119
|
# TODO refactor this method so it clear and concise
|
119
|
-
type_node = create_type_node(pattern)
|
120
|
-
|
120
|
+
type_node = create_type_node(pattern)
|
121
|
+
forked = false
|
122
|
+
parent_atom = pattern.atoms[0]
|
121
123
|
parent_node = type_node
|
122
|
-
|
123
|
-
|
124
|
-
|
124
|
+
|
125
|
+
pattern.atoms[1..-1].each do |atom|
|
126
|
+
# If the network has been forked, we don't want to share nodes anymore
|
127
|
+
forked = true if parent_node.forks?(parent_atom)
|
128
|
+
|
125
129
|
if atom.kind_of?(SelfReferenceAtom)
|
126
130
|
node = create_self_reference_node(atom)
|
127
131
|
elsif atom.kind_of?(ReferenceAtom)
|
128
132
|
node = create_reference_node(atom)
|
129
133
|
out_node.ref_nodes.push node
|
130
134
|
else
|
131
|
-
node = create_property_node(atom)
|
135
|
+
node = create_property_node(atom,forked)
|
132
136
|
end
|
133
|
-
parent_node.
|
137
|
+
parent_node.add_out_node node, parent_atom
|
134
138
|
node.parent_nodes.push parent_node
|
135
139
|
parent_node = node
|
140
|
+
parent_atom = atom
|
136
141
|
end
|
137
142
|
|
138
143
|
bridge_node = create_bridge_node(pattern)
|
139
|
-
parent_node.
|
144
|
+
parent_node.add_out_node bridge_node, parent_atom
|
140
145
|
bridge_node.parent_nodes.push parent_node
|
141
146
|
parent_node = bridge_node
|
142
147
|
|
@@ -144,11 +149,11 @@ module Ruleby
|
|
144
149
|
|
145
150
|
if out_node.kind_of?(JoinNode)
|
146
151
|
adapter_node = create_adapter_node(side)
|
147
|
-
parent_node.
|
152
|
+
parent_node.add_out_node adapter_node
|
148
153
|
parent_node = adapter_node
|
149
154
|
end
|
150
155
|
|
151
|
-
parent_node.
|
156
|
+
parent_node.add_out_node out_node
|
152
157
|
compare_to_wm(type_node)
|
153
158
|
return type_node
|
154
159
|
end
|
@@ -172,29 +177,33 @@ module Ruleby
|
|
172
177
|
parent_node = join_node
|
173
178
|
if out_node.kind_of?(JoinNode)
|
174
179
|
adapter_node = create_adapter_node(side)
|
175
|
-
parent_node.
|
180
|
+
parent_node.add_out_node adapter_node
|
176
181
|
parent_node = adapter_node
|
177
182
|
end
|
178
|
-
parent_node.
|
183
|
+
parent_node.add_out_node out_node
|
179
184
|
return join_node
|
180
185
|
end
|
181
186
|
|
182
187
|
def create_type_node(pattern)
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
+
if InheritsPattern === pattern
|
189
|
+
node = InheritsNode.new pattern.atoms[0]
|
190
|
+
@inherit_nodes.each do |inode|
|
191
|
+
return inode if inode.shareable? node
|
192
|
+
end
|
193
|
+
@inherit_nodes << node
|
194
|
+
return node
|
195
|
+
else
|
196
|
+
return (@type_node ||= TypeNode.new pattern.atoms[0])
|
188
197
|
end
|
189
|
-
return node
|
190
198
|
end
|
191
199
|
|
192
200
|
def create_bridge_node(pattern)
|
193
201
|
return BridgeNode.new(pattern)
|
194
202
|
end
|
195
203
|
|
196
|
-
def create_property_node(atom)
|
197
|
-
node = PropertyNode.new
|
204
|
+
def create_property_node(atom,forked)
|
205
|
+
node = atom.kind_of?(EqualsAtom) ? EqualsNode.new(atom) : PropertyNode.new(atom)
|
206
|
+
@atom_nodes.each {|n| return n if n.shareable? node} unless forked
|
198
207
|
@atom_nodes.push node
|
199
208
|
return node
|
200
209
|
end
|
@@ -233,8 +242,7 @@ module Ruleby
|
|
233
242
|
# Any node in the network that needs to be printed extends this class. It
|
234
243
|
# provides handles to the nodes above it in the network. These are not used
|
235
244
|
# for matching (i.e. no backward-chaining).
|
236
|
-
class Printable
|
237
|
-
|
245
|
+
class Printable
|
238
246
|
attr_reader:parent_nodes
|
239
247
|
|
240
248
|
def initialize
|
@@ -252,8 +260,7 @@ module Ruleby
|
|
252
260
|
|
253
261
|
# Base Node class used by all nodes in the network that do some kind
|
254
262
|
# of matching.
|
255
|
-
class Node < Printable
|
256
|
-
|
263
|
+
class Node < Printable
|
257
264
|
# This method determines if all common tags have equal values. If any
|
258
265
|
# values are not equal then the method returns false.
|
259
266
|
def resolve(mr1, mr2)
|
@@ -271,21 +278,32 @@ module Ruleby
|
|
271
278
|
# This is the base class for all nodes in the network that output to some
|
272
279
|
# other node (i.e. they are not at the bottom). It contains methods for
|
273
280
|
# propagating match results.
|
274
|
-
class ParentNode < Node
|
275
|
-
|
276
|
-
attr_reader:out_nodes
|
277
|
-
|
281
|
+
class ParentNode < Node
|
282
|
+
attr_reader :child_nodes
|
278
283
|
def initialize()
|
279
284
|
super
|
280
285
|
@out_nodes = []
|
281
286
|
end
|
282
287
|
|
288
|
+
def add_out_node(node,atom=nil)
|
289
|
+
unless @out_nodes.index node
|
290
|
+
@out_nodes.push node
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# returns true if this node is already being used for the same atom. That
|
295
|
+
# is, if it is used again it will fork the network (or the network may
|
296
|
+
# already be forked).
|
297
|
+
def forks?(atom)
|
298
|
+
return !@out_nodes.empty?
|
299
|
+
end
|
300
|
+
|
283
301
|
def retract(fact)
|
284
302
|
propagate_retract(fact)
|
285
303
|
end
|
286
304
|
|
287
|
-
def propagate_retract(fact)
|
288
|
-
|
305
|
+
def propagate_retract(fact,out_nodes=@out_nodes)
|
306
|
+
out_nodes.each do |out_node|
|
289
307
|
out_node.retract(fact)
|
290
308
|
end
|
291
309
|
end
|
@@ -294,8 +312,8 @@ module Ruleby
|
|
294
312
|
propagate_assert(assertable)
|
295
313
|
end
|
296
314
|
|
297
|
-
def propagate_assert(assertable)
|
298
|
-
|
315
|
+
def propagate_assert(assertable,out_nodes=@out_nodes)
|
316
|
+
out_nodes.each do |out_node|
|
299
317
|
out_node.assert(assertable)
|
300
318
|
end
|
301
319
|
end
|
@@ -304,49 +322,132 @@ module Ruleby
|
|
304
322
|
# This is a base class for all single input nodes that match facts based on
|
305
323
|
# some properties. It is essentially a wrapper for an Atom. These nodes make
|
306
324
|
# up the Alpha network.
|
307
|
-
class AtomNode < ParentNode
|
308
|
-
|
309
|
-
attr_reader:atom
|
310
|
-
|
325
|
+
class AtomNode < ParentNode
|
326
|
+
attr_reader:atom
|
311
327
|
def initialize(atom)
|
312
328
|
super()
|
313
329
|
@atom = atom
|
314
330
|
end
|
331
|
+
|
332
|
+
def ==(node)
|
333
|
+
return AtomNode === node && @atom == node.atom
|
334
|
+
end
|
335
|
+
|
336
|
+
def shareable?(node)
|
337
|
+
return @atom.shareable?(node.atom)
|
338
|
+
end
|
339
|
+
|
340
|
+
def to_s
|
341
|
+
super + " - #{@atom.method}"
|
342
|
+
end
|
315
343
|
end
|
316
344
|
|
317
|
-
# This
|
318
|
-
class
|
319
|
-
|
320
|
-
|
345
|
+
# This is a base class for any node that hashes out_nodes by value. A node
|
346
|
+
# that inherits this class does not evaluate each condition, instead it looks
|
347
|
+
# up the expected value in the hash, and gets a list of out_nodes.
|
348
|
+
class HashedNode < AtomNode
|
349
|
+
def initialize(atom)
|
350
|
+
super
|
351
|
+
@values = {}
|
352
|
+
@values.default = []
|
353
|
+
end
|
354
|
+
|
355
|
+
# returns true if this node is already being used for the same atom. That
|
356
|
+
# is, if it is used again it will fork the network (or the network may
|
357
|
+
# already be forked).
|
358
|
+
def forks?(atom)
|
359
|
+
k = hash_by(atom)
|
360
|
+
return !@values[k].empty?
|
361
|
+
end
|
362
|
+
|
363
|
+
def add_out_node(node,atom)
|
364
|
+
k = hash_by(atom)
|
365
|
+
v = @values[k]
|
366
|
+
if v.empty?
|
367
|
+
@values[k] = [node]
|
368
|
+
elsif !v.index node
|
369
|
+
@values[k] = v << node
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def retract(fact)
|
374
|
+
propagate_retract fact, @values.values.flatten
|
375
|
+
end
|
376
|
+
|
377
|
+
def assert(fact)
|
378
|
+
k = fact.object.send(@atom.method)
|
379
|
+
propagate_assert fact, @values[k]
|
380
|
+
rescue NoMethodError => e
|
381
|
+
# If the method does not exist, it is the same as if it evaluted to
|
382
|
+
# false, and the network traverse stops
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# This node class is used to match the type of a fact. In this case the type
|
387
|
+
# is matched exactly (ignoring inheritance).
|
388
|
+
class TypeNode < HashedNode
|
389
|
+
def hash_by(atom)
|
390
|
+
atom.deftemplate.clazz
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# This class is used for the same purpose as the TypeNode, but it matches
|
395
|
+
# if the fact's inheritance chain includes the specified class.
|
396
|
+
class InheritsNode < TypeNode
|
397
|
+
def assert(fact)
|
398
|
+
@values.each do |clazz,nodes|
|
399
|
+
propagate_assert fact, nodes if clazz === fact.object
|
400
|
+
end
|
321
401
|
end
|
322
402
|
end
|
323
403
|
|
324
404
|
# This node class is used for matching properties of a fact.
|
325
405
|
class PropertyNode < AtomNode
|
326
406
|
def assert(fact)
|
327
|
-
|
328
|
-
|
407
|
+
begin
|
408
|
+
val = fact.object.send(@atom.method)
|
409
|
+
rescue NoMethodError => e
|
410
|
+
# If the method does not exist, it is the same as if it evaluted to
|
411
|
+
# false, and the network traverse stops
|
412
|
+
return
|
413
|
+
end
|
414
|
+
super if @atom.proc.call(val)
|
329
415
|
end
|
330
416
|
end
|
331
417
|
|
418
|
+
# This node class is used for matching properties of a fact where the
|
419
|
+
# condition is a simple '=='. Instead of evaluating the condition, this node
|
420
|
+
# will pull from a hash. This makes it significatly fast when it is shared.
|
421
|
+
class EqualsNode < HashedNode
|
422
|
+
def hash_by(atom)
|
423
|
+
atom.value
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
332
427
|
# This node class is used to match properties of one with the properties
|
333
428
|
# of any other already matched fact. It differs from the other AtomNodes
|
334
429
|
# because it does not perform any inline matching. The match method is only
|
335
430
|
# invoked by the two input node.
|
336
431
|
class ReferenceNode < AtomNode
|
337
432
|
def match(left_context,right_fact)
|
338
|
-
val = right_fact.object.send(
|
433
|
+
val = right_fact.object.send(@atom.method)
|
339
434
|
args = [val]
|
340
435
|
match = left_context.match
|
341
436
|
@atom.vars.each do |var|
|
342
437
|
args.push match.variables[var]
|
343
|
-
end
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
438
|
+
end
|
439
|
+
begin
|
440
|
+
if @atom.proc.call(*args)
|
441
|
+
m = MatchResult.new(match.variables.clone, true,
|
442
|
+
match.fact_hash.clone, match.recency)
|
443
|
+
m.recency.push right_fact.recency
|
444
|
+
m.fact_hash[@atom.tag] = right_fact.id
|
445
|
+
m.variables[@atom.tag] = val
|
446
|
+
return m
|
447
|
+
end
|
448
|
+
rescue NoMethodError => e
|
449
|
+
# If the method does not exist, it is the same as if it evaluted to
|
450
|
+
# false, and the network traverse stops
|
350
451
|
end
|
351
452
|
return MatchResult.new
|
352
453
|
end
|
@@ -360,7 +461,7 @@ module Ruleby
|
|
360
461
|
end
|
361
462
|
|
362
463
|
def match(fact)
|
363
|
-
args = [fact.object.send(
|
464
|
+
args = [fact.object.send(@atom.method)]
|
364
465
|
@atom.vars.each do |var|
|
365
466
|
args.push fact.object.send(var)
|
366
467
|
end
|
@@ -389,13 +490,10 @@ module Ruleby
|
|
389
490
|
# HACK its a pain to have to check for this, can we make it special
|
390
491
|
mr[atom.tag] = fact.object
|
391
492
|
else
|
392
|
-
mr[atom.tag] = fact.object.send(
|
493
|
+
mr[atom.tag] = fact.object.send(atom.method)
|
393
494
|
end
|
394
495
|
end
|
395
|
-
|
396
|
-
context = MatchContext.new(fact,mr)
|
397
|
-
|
398
|
-
super(context)
|
496
|
+
super(MatchContext.new(fact,mr))
|
399
497
|
end
|
400
498
|
end
|
401
499
|
|
@@ -561,9 +659,11 @@ module Ruleby
|
|
561
659
|
end
|
562
660
|
end
|
563
661
|
|
564
|
-
|
662
|
+
def assert_left(context)
|
565
663
|
add_to_left_memory(context)
|
566
|
-
|
664
|
+
if @ref_nodes.empty? && @right_memory.empty?
|
665
|
+
propagate_assert(context)
|
666
|
+
else
|
567
667
|
propagate = true
|
568
668
|
@right_memory.values.each do |right_context|
|
569
669
|
if match_ref_nodes(context,right_context)
|
@@ -577,7 +677,11 @@ module Ruleby
|
|
577
677
|
|
578
678
|
def assert_right(context)
|
579
679
|
@right_memory[context.fact.id] = context
|
580
|
-
|
680
|
+
if @ref_nodes.empty?
|
681
|
+
@left_memory.values.flatten.each do |left_context|
|
682
|
+
propagate_retract_resolve(left_context.match)
|
683
|
+
end
|
684
|
+
else
|
581
685
|
@left_memory.values.flatten.each do |left_context|
|
582
686
|
if match_ref_nodes(left_context,context)
|
583
687
|
# QUESTION is there a more efficient way to retract here?
|