ruleby 0.8.b2 → 0.8.b4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|