ruleby 0.8.b2 → 0.8.b4
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/engine.rb +17 -1
- data/lib/core/nodes.rb +126 -94
- metadata +3 -3
data/lib/core/engine.rb
CHANGED
|
@@ -187,6 +187,16 @@ module Ruleby
|
|
|
187
187
|
end
|
|
188
188
|
end
|
|
189
189
|
|
|
190
|
+
class Error
|
|
191
|
+
attr_reader :type, :level, :details
|
|
192
|
+
|
|
193
|
+
def initialize(type, level, details={})
|
|
194
|
+
@type = type
|
|
195
|
+
@details = details
|
|
196
|
+
@level = level
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
190
200
|
# This is the core class of the library. A new rule engine is created by
|
|
191
201
|
# instantiating it. Each rule engine has one inference engine, one rule set
|
|
192
202
|
# and one working memory.
|
|
@@ -255,7 +265,13 @@ module Ruleby
|
|
|
255
265
|
end
|
|
256
266
|
end
|
|
257
267
|
end
|
|
258
|
-
|
|
268
|
+
|
|
269
|
+
def extract_errors
|
|
270
|
+
e = @root.errors
|
|
271
|
+
@root.clear_errors
|
|
272
|
+
e
|
|
273
|
+
end
|
|
274
|
+
|
|
259
275
|
def print
|
|
260
276
|
@working_memory.print
|
|
261
277
|
@root.print
|
data/lib/core/nodes.rb
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
#
|
|
11
11
|
|
|
12
12
|
module Ruleby
|
|
13
|
-
module Core
|
|
13
|
+
module Core
|
|
14
14
|
|
|
15
15
|
# This class acts as the root-node of the network. It contains the logic
|
|
16
16
|
# for building the node-network from a set of rules, and updating it
|
|
@@ -23,13 +23,14 @@ module Ruleby
|
|
|
23
23
|
@atom_nodes = []
|
|
24
24
|
@join_nodes = []
|
|
25
25
|
@terminal_nodes = []
|
|
26
|
+
@bucket = NetworkBucket.new
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
# This method is invoked when a new rule is added to the system. The
|
|
29
30
|
# rule is processed and the appropriate nodes are added to the network.
|
|
30
31
|
def assert_rule(rule)
|
|
31
|
-
terminal_node = TerminalNode.new rule
|
|
32
|
-
build_network(rule.pattern, terminal_node)
|
|
32
|
+
terminal_node = TerminalNode.new @bucket, rule
|
|
33
|
+
build_network(rule.pattern, terminal_node)
|
|
33
34
|
@terminal_nodes.push terminal_node
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -44,15 +45,23 @@ module Ruleby
|
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
# Increments the activation counter. This is just a pass-thru to the static
|
|
47
|
-
# variable in the
|
|
48
|
+
# variable in the bucket
|
|
48
49
|
def increment_counter
|
|
49
|
-
|
|
50
|
+
@bucket.increment_counter
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
# Resets the activation counter. This is just a pass-thru to the static
|
|
53
|
-
# variable in the
|
|
54
|
+
# variable in the bucket
|
|
54
55
|
def reset_counter
|
|
55
|
-
|
|
56
|
+
@bucket.reset_counter
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def errors
|
|
60
|
+
@bucket.errors
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def clear_errors
|
|
64
|
+
@bucket.clear_errors
|
|
56
65
|
end
|
|
57
66
|
|
|
58
67
|
# When invoked, this method returns a list of all Action|MatchContext pairs
|
|
@@ -170,9 +179,9 @@ module Ruleby
|
|
|
170
179
|
# side - if the out_node is a JoinNode, this marks the side
|
|
171
180
|
def create_join_node(pattern, out_node, side)
|
|
172
181
|
if (pattern.right_pattern.kind_of?(NotPattern))
|
|
173
|
-
join_node = NotNode.new
|
|
182
|
+
join_node = NotNode.new(@bucket)
|
|
174
183
|
else
|
|
175
|
-
join_node = JoinNode.new
|
|
184
|
+
join_node = JoinNode.new(@bucket)
|
|
176
185
|
end
|
|
177
186
|
|
|
178
187
|
@join_nodes.push(join_node)
|
|
@@ -188,49 +197,49 @@ module Ruleby
|
|
|
188
197
|
|
|
189
198
|
def create_type_node(pattern)
|
|
190
199
|
if InheritsPattern === pattern
|
|
191
|
-
node = InheritsNode.new pattern.atoms[0]
|
|
200
|
+
node = InheritsNode.new(@bucket, pattern.atoms[0])
|
|
192
201
|
@inherit_nodes.each do |inode|
|
|
193
202
|
return inode if inode.shareable? node
|
|
194
203
|
end
|
|
195
204
|
@inherit_nodes << node
|
|
196
205
|
return node
|
|
197
206
|
else
|
|
198
|
-
return (@type_node ||= TypeNode.new pattern.atoms[0])
|
|
207
|
+
return (@type_node ||= TypeNode.new(@bucket, pattern.atoms[0]))
|
|
199
208
|
end
|
|
200
209
|
end
|
|
201
210
|
|
|
202
211
|
def create_bridge_node(pattern)
|
|
203
212
|
if pattern.kind_of?(CollectPattern)
|
|
204
|
-
CollectNode.new(pattern)
|
|
213
|
+
CollectNode.new(@bucket, pattern)
|
|
205
214
|
else
|
|
206
|
-
BridgeNode.new(pattern)
|
|
215
|
+
BridgeNode.new(@bucket, pattern)
|
|
207
216
|
end
|
|
208
217
|
end
|
|
209
218
|
|
|
210
219
|
def create_property_node(atom,forked)
|
|
211
|
-
node = atom.kind_of?(EqualsAtom) ? EqualsNode.new(atom) : PropertyNode.new(atom)
|
|
220
|
+
node = atom.kind_of?(EqualsAtom) ? EqualsNode.new(@bucket, atom) : PropertyNode.new(@bucket, atom)
|
|
212
221
|
@atom_nodes.each {|n| return n if n.shareable? node} unless forked
|
|
213
222
|
@atom_nodes.push node
|
|
214
223
|
return node
|
|
215
224
|
end
|
|
216
225
|
|
|
217
226
|
def create_self_reference_node(atom)
|
|
218
|
-
node = SelfReferenceNode.new atom
|
|
227
|
+
node = SelfReferenceNode.new(@bucket, atom)
|
|
219
228
|
@atom_nodes.push node
|
|
220
229
|
return node
|
|
221
230
|
end
|
|
222
231
|
|
|
223
232
|
def create_reference_node(atom)
|
|
224
|
-
node = ReferenceNode.new atom
|
|
233
|
+
node = ReferenceNode.new(@bucket, atom)
|
|
225
234
|
@atom_nodes.push node
|
|
226
235
|
return node
|
|
227
236
|
end
|
|
228
237
|
|
|
229
238
|
def create_adapter_node(side)
|
|
230
239
|
if side == :left
|
|
231
|
-
return LeftAdapterNode.new
|
|
240
|
+
return LeftAdapterNode.new(@bucket)
|
|
232
241
|
else
|
|
233
|
-
return RightAdapterNode.new
|
|
242
|
+
return RightAdapterNode.new(@bucket)
|
|
234
243
|
end
|
|
235
244
|
end
|
|
236
245
|
|
|
@@ -267,7 +276,12 @@ module Ruleby
|
|
|
267
276
|
|
|
268
277
|
# Base Node class used by all nodes in the network that do some kind
|
|
269
278
|
# of matching.
|
|
270
|
-
class Node < Printable
|
|
279
|
+
class Node < Printable
|
|
280
|
+
def initialize(bucket)
|
|
281
|
+
super()
|
|
282
|
+
@bucket = bucket
|
|
283
|
+
end
|
|
284
|
+
|
|
271
285
|
# This method determines if all common tags have equal values. If any
|
|
272
286
|
# values are not equal then the method returns false.
|
|
273
287
|
def resolve(mr1, mr2)
|
|
@@ -287,7 +301,7 @@ module Ruleby
|
|
|
287
301
|
# propagating match results.
|
|
288
302
|
class ParentNode < Node
|
|
289
303
|
attr_reader :child_nodes
|
|
290
|
-
def initialize()
|
|
304
|
+
def initialize(bucket)
|
|
291
305
|
super
|
|
292
306
|
@out_nodes = []
|
|
293
307
|
end
|
|
@@ -307,22 +321,28 @@ module Ruleby
|
|
|
307
321
|
|
|
308
322
|
def retract(fact)
|
|
309
323
|
propagate_retract(fact)
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
def
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def propagate(propagation_method, object_to_propagate, out_nodes=@out_nodes)
|
|
313
327
|
out_nodes.each do |out_node|
|
|
314
|
-
|
|
328
|
+
begin
|
|
329
|
+
out_node.send(propagation_method, object_to_propagate)
|
|
330
|
+
rescue => e
|
|
331
|
+
@bucket.add_error Error.new(:unknown, :error, {:type => e.class, :message => e.message})
|
|
332
|
+
end
|
|
315
333
|
end
|
|
316
334
|
end
|
|
317
335
|
|
|
336
|
+
def propagate_retract(fact,out_nodes=@out_nodes)
|
|
337
|
+
propagate(:retract, fact, out_nodes)
|
|
338
|
+
end
|
|
339
|
+
|
|
318
340
|
def assert(assertable)
|
|
319
341
|
propagate_assert(assertable)
|
|
320
342
|
end
|
|
321
343
|
|
|
322
344
|
def propagate_assert(assertable,out_nodes=@out_nodes)
|
|
323
|
-
|
|
324
|
-
out_node.assert(assertable)
|
|
325
|
-
end
|
|
345
|
+
propagate(:assert, assertable, out_nodes)
|
|
326
346
|
end
|
|
327
347
|
|
|
328
348
|
def modify(assertable)
|
|
@@ -330,9 +350,7 @@ module Ruleby
|
|
|
330
350
|
end
|
|
331
351
|
|
|
332
352
|
def propagate_modify(assertable,out_nodes=@out_nodes)
|
|
333
|
-
|
|
334
|
-
out_node.modify(assertable)
|
|
335
|
-
end
|
|
353
|
+
propagate(:modify, assertable, out_nodes)
|
|
336
354
|
end
|
|
337
355
|
end
|
|
338
356
|
|
|
@@ -341,8 +359,8 @@ module Ruleby
|
|
|
341
359
|
# up the Alpha network.
|
|
342
360
|
class AtomNode < ParentNode
|
|
343
361
|
attr_reader:atom
|
|
344
|
-
def initialize(atom)
|
|
345
|
-
super()
|
|
362
|
+
def initialize(bucket, atom)
|
|
363
|
+
super(bucket)
|
|
346
364
|
@atom = atom
|
|
347
365
|
end
|
|
348
366
|
|
|
@@ -363,7 +381,7 @@ module Ruleby
|
|
|
363
381
|
# that inherits this class does not evaluate each condition, instead it looks
|
|
364
382
|
# up the expected value in the hash, and gets a list of out_nodes.
|
|
365
383
|
class HashedNode < AtomNode
|
|
366
|
-
def initialize(atom)
|
|
384
|
+
def initialize(bucket, atom)
|
|
367
385
|
super
|
|
368
386
|
@values = {}
|
|
369
387
|
@values.default = []
|
|
@@ -393,10 +411,13 @@ module Ruleby
|
|
|
393
411
|
|
|
394
412
|
def assert(fact)
|
|
395
413
|
k = fact.object.send(@atom.method)
|
|
396
|
-
propagate_assert fact, @values[k]
|
|
414
|
+
propagate_assert fact, @values[k]
|
|
397
415
|
rescue NoMethodError => e
|
|
398
|
-
|
|
399
|
-
|
|
416
|
+
@bucket.add_error Error.new(:no_method, :warn, {
|
|
417
|
+
:object => fact.object.to_s,
|
|
418
|
+
:method => e.name,
|
|
419
|
+
:message => e.message
|
|
420
|
+
})
|
|
400
421
|
end
|
|
401
422
|
end
|
|
402
423
|
|
|
@@ -422,23 +443,28 @@ module Ruleby
|
|
|
422
443
|
class PropertyNode < AtomNode
|
|
423
444
|
def assert(fact)
|
|
424
445
|
begin
|
|
425
|
-
val = fact.object.send(@atom.method)
|
|
446
|
+
val = fact.object.send(@atom.method)
|
|
426
447
|
rescue NoMethodError => e
|
|
427
|
-
|
|
428
|
-
|
|
448
|
+
@bucket.add_error Error.new(:no_method, :warn, {
|
|
449
|
+
:object => fact.object.to_s,
|
|
450
|
+
:method => e.name,
|
|
451
|
+
:message => e.message
|
|
452
|
+
})
|
|
429
453
|
return
|
|
430
454
|
end
|
|
431
455
|
begin
|
|
432
456
|
super if @atom.proc.call(val)
|
|
433
457
|
rescue Exception => e
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
458
|
+
@bucket.add_error Error.new(:proc_call, :error, {
|
|
459
|
+
:object => fact.object.to_s,
|
|
460
|
+
:method => @atom.method,
|
|
461
|
+
:value => val.to_s,
|
|
462
|
+
:message => e.message
|
|
463
|
+
})
|
|
438
464
|
end
|
|
439
465
|
end
|
|
440
466
|
end
|
|
441
|
-
|
|
467
|
+
|
|
442
468
|
# This node class is used for matching properties of a fact where the
|
|
443
469
|
# condition is a simple '=='. Instead of evaluating the condition, this node
|
|
444
470
|
# will pull from a hash. This makes it significatly fast when it is shared.
|
|
@@ -469,9 +495,14 @@ module Ruleby
|
|
|
469
495
|
m.variables[@atom.tag] = val
|
|
470
496
|
return m
|
|
471
497
|
end
|
|
472
|
-
rescue NoMethodError => e
|
|
473
|
-
#
|
|
474
|
-
|
|
498
|
+
rescue NoMethodError => e
|
|
499
|
+
# I'd rather push this up to a high level, but its got to happen down low
|
|
500
|
+
@bucket.add_error Error.new(:no_method, :warn, {
|
|
501
|
+
:object => "?",
|
|
502
|
+
:method => e.name,
|
|
503
|
+
:args => e.args,
|
|
504
|
+
:message => e.message
|
|
505
|
+
})
|
|
475
506
|
end
|
|
476
507
|
return MatchResult.new
|
|
477
508
|
end
|
|
@@ -498,8 +529,8 @@ module Ruleby
|
|
|
498
529
|
# pattern and atoms above it in the network. Thus, there is one bridge node
|
|
499
530
|
# for each pattern (assuming they aren't shared).
|
|
500
531
|
class BaseBridgeNode < ParentNode
|
|
501
|
-
def initialize(pattern)
|
|
502
|
-
super()
|
|
532
|
+
def initialize(bucket, pattern)
|
|
533
|
+
super(bucket)
|
|
503
534
|
@pattern = pattern
|
|
504
535
|
end
|
|
505
536
|
end
|
|
@@ -524,7 +555,7 @@ module Ruleby
|
|
|
524
555
|
end
|
|
525
556
|
|
|
526
557
|
class CollectNode < BaseBridgeNode
|
|
527
|
-
def initialize(pattern)
|
|
558
|
+
def initialize(bucket, pattern)
|
|
528
559
|
super
|
|
529
560
|
@collection_memory = Fact.new([], :internal)
|
|
530
561
|
@should_modify = false
|
|
@@ -581,27 +612,19 @@ module Ruleby
|
|
|
581
612
|
# This class is used to plug nodes into the left input of a two-input JoinNode
|
|
582
613
|
class LeftAdapterNode < ParentNode
|
|
583
614
|
def propagate_assert(context)
|
|
584
|
-
|
|
585
|
-
out_node.assert_left(context)
|
|
586
|
-
end
|
|
615
|
+
propagate(:assert_left, context)
|
|
587
616
|
end
|
|
588
617
|
|
|
589
618
|
def propagate_retract(fact)
|
|
590
|
-
|
|
591
|
-
out_node.retract_left(fact)
|
|
592
|
-
end
|
|
619
|
+
propagate(:retract_left, fact)
|
|
593
620
|
end
|
|
594
621
|
|
|
595
622
|
def retract_resolve(match)
|
|
596
|
-
|
|
597
|
-
o.retract_resolve(match)
|
|
598
|
-
end
|
|
623
|
+
propagate(:retract_resolve, match)
|
|
599
624
|
end
|
|
600
625
|
|
|
601
626
|
def modify(context)
|
|
602
|
-
|
|
603
|
-
out_node.modify_left(context)
|
|
604
|
-
end
|
|
627
|
+
propagate(:modify_left, context)
|
|
605
628
|
end
|
|
606
629
|
end
|
|
607
630
|
|
|
@@ -609,27 +632,19 @@ module Ruleby
|
|
|
609
632
|
# JoinNode
|
|
610
633
|
class RightAdapterNode < ParentNode
|
|
611
634
|
def propagate_assert(context)
|
|
612
|
-
|
|
613
|
-
out_node.assert_right(context)
|
|
614
|
-
end
|
|
635
|
+
propagate(:assert_right, context)
|
|
615
636
|
end
|
|
616
|
-
|
|
637
|
+
|
|
617
638
|
def propagate_retract(fact)
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
end
|
|
622
|
-
|
|
639
|
+
propagate(:retract_right, fact)
|
|
640
|
+
end
|
|
641
|
+
|
|
623
642
|
def retract_resolve(match)
|
|
624
|
-
|
|
625
|
-
o.retract_resolve(match)
|
|
626
|
-
end
|
|
643
|
+
propagate(:retract_resolve, match)
|
|
627
644
|
end
|
|
628
645
|
|
|
629
646
|
def modify(context)
|
|
630
|
-
|
|
631
|
-
out_node.modify_right(context)
|
|
632
|
-
end
|
|
647
|
+
propagate(:modify_right, context)
|
|
633
648
|
end
|
|
634
649
|
end
|
|
635
650
|
|
|
@@ -641,7 +656,7 @@ module Ruleby
|
|
|
641
656
|
|
|
642
657
|
attr:ref_nodes,true
|
|
643
658
|
|
|
644
|
-
def initialize
|
|
659
|
+
def initialize(bucket)
|
|
645
660
|
super
|
|
646
661
|
@left_memory = {}
|
|
647
662
|
@right_memory = {}
|
|
@@ -743,9 +758,7 @@ module Ruleby
|
|
|
743
758
|
end
|
|
744
759
|
|
|
745
760
|
def propagate_retract_resolve(match)
|
|
746
|
-
|
|
747
|
-
o.retract_resolve(match)
|
|
748
|
-
end
|
|
761
|
+
propagate(:retract_resolve, match)
|
|
749
762
|
end
|
|
750
763
|
end
|
|
751
764
|
|
|
@@ -753,7 +766,7 @@ module Ruleby
|
|
|
753
766
|
# exist. It is a two-input node, and thus has some of the properties of the
|
|
754
767
|
# JoinNode.
|
|
755
768
|
class NotNode < JoinNode
|
|
756
|
-
def initialize
|
|
769
|
+
def initialize(bucket)
|
|
757
770
|
super
|
|
758
771
|
end
|
|
759
772
|
|
|
@@ -831,10 +844,9 @@ module Ruleby
|
|
|
831
844
|
# for a rule. The class is responsible for keeping a memory of the
|
|
832
845
|
# activations that have been generated by the network.
|
|
833
846
|
class TerminalNode < Node
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
super()
|
|
847
|
+
|
|
848
|
+
def initialize(bucket, rule)
|
|
849
|
+
super(bucket)
|
|
838
850
|
@rule = rule
|
|
839
851
|
@activations = MultiHash.new
|
|
840
852
|
end
|
|
@@ -846,7 +858,7 @@ module Ruleby
|
|
|
846
858
|
|
|
847
859
|
def assert(context)
|
|
848
860
|
match = context.match
|
|
849
|
-
a = Activation.new(@rule.action, match,
|
|
861
|
+
a = Activation.new(@rule.action, match, @bucket.counter)
|
|
850
862
|
@activations.add match.fact_ids, a
|
|
851
863
|
end
|
|
852
864
|
|
|
@@ -883,14 +895,34 @@ module Ruleby
|
|
|
883
895
|
@activations.delete_if do |activation|
|
|
884
896
|
resolve(activation.match, match)
|
|
885
897
|
end
|
|
886
|
-
end
|
|
887
|
-
|
|
888
|
-
def self.increment_counter
|
|
889
|
-
@@counter = @@counter + 1
|
|
890
898
|
end
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
private
|
|
902
|
+
|
|
903
|
+
# an instance of this class hangs on to state that is global to the network
|
|
904
|
+
class NetworkBucket
|
|
905
|
+
attr_reader :errors, :counter
|
|
906
|
+
|
|
907
|
+
def initialize
|
|
908
|
+
clear_errors
|
|
909
|
+
reset_counter
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
def increment_counter
|
|
913
|
+
@counter += 1
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
def reset_counter
|
|
917
|
+
@counter = 0
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
def add_error(error)
|
|
921
|
+
@errors << error
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
def clear_errors
|
|
925
|
+
@errors = []
|
|
894
926
|
end
|
|
895
927
|
end
|
|
896
928
|
|
metadata
CHANGED
|
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
|
5
5
|
segments:
|
|
6
6
|
- 0
|
|
7
7
|
- 8
|
|
8
|
-
-
|
|
9
|
-
version: 0.8.
|
|
8
|
+
- b4
|
|
9
|
+
version: 0.8.b4
|
|
10
10
|
platform: ruby
|
|
11
11
|
authors:
|
|
12
12
|
- Joe Kutner
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-04-
|
|
18
|
+
date: 2011-04-12 00:00:00 -05:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies: []
|
|
21
21
|
|